Beispiel #1
0
 public function save($userID = false)
 {
     if (!$this->libraryID) {
         trigger_error("Library ID must be set before saving", E_USER_ERROR);
     }
     Zotero_Items::editCheck($this);
     if (!$this->hasChanged()) {
         Z_Core::debug("Item {$this->id} has not changed");
         return false;
     }
     // Make sure there are no gaps in the creator indexes
     $creators = $this->getCreators();
     $lastPos = -1;
     foreach ($creators as $pos => $creator) {
         if ($pos != $lastPos + 1) {
             trigger_error("Creator index {$pos} out of sequence for item {$this->id}", E_USER_ERROR);
         }
         $lastPos++;
     }
     $shardID = Zotero_Shards::getByLibraryID($this->libraryID);
     Zotero_DB::beginTransaction();
     try {
         //
         // New item, insert and return id
         //
         if (!$this->id || !$this->exists()) {
             Z_Core::debug('Saving data for new item to database');
             $isNew = true;
             $sqlColumns = array();
             $sqlValues = array();
             //
             // Primary fields
             //
             $itemID = $this->id ? $this->id : Zotero_ID::get('items');
             $key = $this->key ? $this->key : $this->generateKey();
             $sqlColumns = array('itemID', 'itemTypeID', 'libraryID', 'key', 'dateAdded', 'dateModified', 'serverDateModified');
             $timestamp = Zotero_DB::getTransactionTimestamp();
             $sqlValues = array($itemID, $this->itemTypeID, $this->libraryID, $key, $this->dateAdded ? $this->dateAdded : $timestamp, $this->dateModified ? $this->dateModified : $timestamp, $timestamp);
             //
             // Primary fields
             //
             $sql = 'INSERT INTO items (`' . implode('`, `', $sqlColumns) . '`) VALUES (';
             // Insert placeholders for bind parameters
             for ($i = 0; $i < sizeOf($sqlValues); $i++) {
                 $sql .= '?, ';
             }
             $sql = substr($sql, 0, -2) . ')';
             // Save basic data to items table
             $insertID = Zotero_DB::query($sql, $sqlValues, $shardID);
             if (!$this->id) {
                 if (!$insertID) {
                     throw new Exception("Item id not available after INSERT");
                 }
                 $itemID = $insertID;
                 $this->serverDateModified = $timestamp;
             }
             // Group item data
             if (Zotero_Libraries::getType($this->libraryID) == 'group' && $userID) {
                 $sql = "INSERT INTO groupItems VALUES (?, ?, ?)";
                 Zotero_DB::query($sql, array($itemID, $userID, null), $shardID);
             }
             //
             // ItemData
             //
             if ($this->changed['itemData']) {
                 // Use manual bound parameters to speed things up
                 $origInsertSQL = "INSERT INTO itemData (itemID, fieldID, value) VALUES ";
                 $insertSQL = $origInsertSQL;
                 $insertParams = array();
                 $insertCounter = 0;
                 $maxInsertGroups = 40;
                 $max = Zotero_Items::$maxDataValueLength;
                 $fieldIDs = array_keys($this->changed['itemData']);
                 foreach ($fieldIDs as $fieldID) {
                     $value = $this->getField($fieldID, true, false, true);
                     if ($value == 'CURRENT_TIMESTAMP' && Zotero_ItemFields::getID('accessDate') == $fieldID) {
                         $value = Zotero_DB::getTransactionTimestamp();
                     }
                     // Check length
                     if (strlen($value) > $max) {
                         $fieldName = Zotero_ItemFields::getLocalizedString($this->itemTypeID, $fieldID);
                         throw new Exception("={$fieldName} field " . "'" . substr($value, 0, 50) . "...' too long");
                     }
                     if ($insertCounter < $maxInsertGroups) {
                         $insertSQL .= "(?,?,?),";
                         $insertParams = array_merge($insertParams, array($itemID, $fieldID, $value));
                     }
                     if ($insertCounter == $maxInsertGroups - 1) {
                         $insertSQL = substr($insertSQL, 0, -1);
                         $stmt = Zotero_DB::getStatement($insertSQL, true, $shardID);
                         Zotero_DB::queryFromStatement($stmt, $insertParams);
                         $insertSQL = $origInsertSQL;
                         $insertParams = array();
                         $insertCounter = -1;
                     }
                     $insertCounter++;
                 }
                 if ($insertCounter > 0 && $insertCounter < $maxInsertGroups) {
                     $insertSQL = substr($insertSQL, 0, -1);
                     $stmt = Zotero_DB::getStatement($insertSQL, true, $shardID);
                     Zotero_DB::queryFromStatement($stmt, $insertParams);
                 }
             }
             //
             // Creators
             //
             if ($this->changed['creators']) {
                 $indexes = array_keys($this->changed['creators']);
                 // TODO: group queries
                 $sql = "INSERT INTO itemCreators\n\t\t\t\t\t\t\t\t(itemID, creatorID, creatorTypeID, orderIndex) VALUES ";
                 $placeholders = array();
                 $sqlValues = array();
                 $cacheRows = array();
                 foreach ($indexes as $orderIndex) {
                     Z_Core::debug('Adding creator in position ' . $orderIndex, 4);
                     $creator = $this->getCreator($orderIndex);
                     if (!$creator) {
                         continue;
                     }
                     if ($creator['ref']->hasChanged()) {
                         Z_Core::debug("Auto-saving changed creator {$creator['ref']->id}");
                         $creator['ref']->save();
                     }
                     $placeholders[] = "(?, ?, ?, ?)";
                     array_push($sqlValues, $itemID, $creator['ref']->id, $creator['creatorTypeID'], $orderIndex);
                     $cacheRows[] = array('creatorID' => $creator['ref']->id, 'creatorTypeID' => $creator['creatorTypeID'], 'orderIndex' => $orderIndex);
                 }
                 if ($sqlValues) {
                     $sql = $sql . implode(',', $placeholders);
                     Zotero_DB::query($sql, $sqlValues, $shardID);
                 }
             }
             // Deleted item
             if ($this->changed['deleted']) {
                 $deleted = $this->getDeleted();
                 if ($deleted) {
                     $sql = "REPLACE INTO deletedItems (itemID) VALUES (?)";
                 } else {
                     $sql = "DELETE FROM deletedItems WHERE itemID=?";
                 }
                 Zotero_DB::query($sql, $itemID, $shardID);
             }
             // Note
             if ($this->isNote() || $this->changed['note']) {
                 $noteIsSanitized = false;
                 // If we don't have a sanitized note, generate one
                 if (is_null($this->noteTextSanitized)) {
                     $noteTextSanitized = Zotero_Notes::sanitize($this->noteText);
                     // But if the same as original, just use reference
                     if ($this->noteText == $noteTextSanitized) {
                         $this->noteTextSanitized =& $this->noteText;
                         $noteIsSanitized = true;
                     } else {
                         $this->noteTextSanitized = $noteTextSanitized;
                     }
                 }
                 // If note is sanitized already, store empty string
                 // If not, store sanitized version
                 $noteTextSanitized = $noteIsSanitized ? '' : $this->noteTextSanitized;
                 $title = Zotero_Notes::noteToTitle($this->noteTextSanitized);
                 $sql = "INSERT INTO itemNotes\n\t\t\t\t\t\t\t(itemID, sourceItemID, note, noteSanitized, title, hash)\n\t\t\t\t\t\t\tVALUES (?,?,?,?,?,?)";
                 $parent = $this->isNote() ? $this->getSource() : null;
                 $hash = $this->noteText ? md5($this->noteText) : '';
                 $bindParams = array($itemID, $parent ? $parent : null, $this->noteText ? $this->noteText : '', $noteTextSanitized, $title, $hash);
                 try {
                     Zotero_DB::query($sql, $bindParams, $shardID);
                 } catch (Exception $e) {
                     if (strpos($e->getMessage(), "Incorrect string value") !== false) {
                         throw new Exception("=Invalid character in note '" . Zotero_Utilities::ellipsize($title, 70) . "'", Z_ERROR_INVALID_INPUT);
                     }
                     throw $e;
                 }
                 Zotero_Notes::updateNoteCache($this->libraryID, $itemID, $this->noteText);
                 Zotero_Notes::updateHash($this->libraryID, $itemID, $hash);
             }
             // Attachment
             if ($this->isAttachment()) {
                 $sql = "INSERT INTO itemAttachments\n\t\t\t\t\t\t\t(itemID, sourceItemID, linkMode, mimeType, charsetID, path, storageModTime, storageHash)\n\t\t\t\t\t\t\tVALUES (?,?,?,?,?,?,?,?)";
                 $parent = $this->getSource();
                 if ($parent) {
                     $parentItem = Zotero_Items::get($this->libraryID, $parent);
                     if (!$parentItem) {
                         throw new Exception("Parent item {$parent} not found");
                     }
                     if ($parentItem->getSource()) {
                         trigger_error("Parent item cannot be a child attachment", E_USER_ERROR);
                     }
                 }
                 $linkMode = $this->attachmentLinkMode;
                 $charsetID = Zotero_CharacterSets::getID($this->attachmentCharset);
                 $path = $this->attachmentPath;
                 $storageModTime = $this->attachmentStorageModTime;
                 $storageHash = $this->attachmentStorageHash;
                 $bindParams = array($itemID, $parent ? $parent : null, $linkMode + 1, $this->attachmentMIMEType, $charsetID ? $charsetID : null, $path ? $path : '', $storageModTime ? $storageModTime : null, $storageHash ? $storageHash : null);
                 Zotero_DB::query($sql, $bindParams, $shardID);
             }
             // Sort fields
             $sortTitle = Zotero_Items::getSortTitle($this->getDisplayTitle(true));
             if (mb_substr($sortTitle, 0, 5) == mb_substr($this->getField('title', false, true), 0, 5)) {
                 $sortTitle = null;
             }
             $creatorSummary = $this->isRegularItem() ? mb_strcut($this->getCreatorSummary(), 0, Zotero_Creators::$creatorSummarySortLength) : '';
             $sql = "INSERT INTO itemSortFields (itemID, sortTitle, creatorSummary) VALUES (?, ?, ?)";
             Zotero_DB::query($sql, array($itemID, $sortTitle, $creatorSummary), $shardID);
             //
             // Source item id
             //
             if ($sourceItemID = $this->getSource()) {
                 $newSourceItem = Zotero_Items::get($this->libraryID, $sourceItemID);
                 if (!$newSourceItem) {
                     throw new Exception("Cannot set source to invalid item");
                 }
                 switch (Zotero_ItemTypes::getName($this->itemTypeID)) {
                     case 'note':
                         $newSourceItem->incrementNoteCount();
                         break;
                     case 'attachment':
                         $newSourceItem->incrementAttachmentCount();
                         break;
                 }
             }
             // Related items
             if (!empty($this->changed['relatedItems'])) {
                 $removed = array();
                 $newids = array();
                 $currentIDs = $this->relatedItems;
                 if (!$currentIDs) {
                     $currentIDs = array();
                 }
                 foreach ($this->previousData['relatedItems'] as $id) {
                     if (!in_array($id, $currentIDs)) {
                         $removed[] = $id;
                     }
                 }
                 foreach ($currentIDs as $id) {
                     if (in_array($id, $this->previousData['relatedItems'])) {
                         continue;
                     }
                     $newids[] = $id;
                 }
                 if ($removed) {
                     $sql = "DELETE FROM itemRelated WHERE itemID=?\n\t\t\t\t\t\t\t\tAND linkedItemID IN (";
                     $sql .= implode(', ', array_fill(0, sizeOf($removed), '?')) . ")";
                     Zotero_DB::query($sql, array_merge(array($this->id), $removed), $shardID);
                 }
                 if ($newids) {
                     $sql = "INSERT INTO itemRelated (itemID, linkedItemID)\n\t\t\t\t\t\t\t\tVALUES (?,?)";
                     $insertStatement = Zotero_DB::getStatement($sql, false, $shardID);
                     foreach ($newids as $linkedItemID) {
                         $insertStatement->execute(array($itemID, $linkedItemID));
                     }
                 }
             }
             // Remove from delete log if it's there
             $sql = "DELETE FROM syncDeleteLogKeys WHERE libraryID=? AND objectType='item' AND `key`=?";
             Zotero_DB::query($sql, array($this->libraryID, $key), $shardID);
         } else {
             Z_Core::debug('Updating database with new item data', 4);
             $isNew = false;
             //
             // Primary fields
             //
             $sql = "UPDATE items SET ";
             $sqlValues = array();
             $timestamp = Zotero_DB::getTransactionTimestamp();
             $updateFields = array('itemTypeID', 'libraryID', 'key', 'dateAdded', 'dateModified');
             foreach ($updateFields as $updateField) {
                 if (in_array($updateField, $this->changed['primaryData'])) {
                     $sql .= "`{$updateField}`=?, ";
                     $sqlValues[] = $this->{$updateField};
                 } else {
                     if ($updateField == 'dateModified') {
                         $sql .= "`{$updateField}`=?, ";
                         $sqlValues[] = $timestamp;
                     }
                 }
             }
             $sql .= "serverDateModified=?, version=IF(version = 65535, 0, version + 1) WHERE itemID=?";
             array_push($sqlValues, $timestamp, $this->id);
             Zotero_DB::query($sql, $sqlValues, $shardID);
             $this->serverDateModified = $timestamp;
             // Group item data
             if (Zotero_Libraries::getType($this->libraryID) == 'group' && $userID) {
                 $sql = "INSERT INTO groupItems VALUES (?, ?, ?)\n\t\t\t\t\t\t\t\tON DUPLICATE KEY UPDATE lastModifiedByUserID=?";
                 Zotero_DB::query($sql, array($this->id, null, $userID, $userID), $shardID);
             }
             //
             // ItemData
             //
             if ($this->changed['itemData']) {
                 $del = array();
                 $origReplaceSQL = "REPLACE INTO itemData (itemID, fieldID, value) VALUES ";
                 $replaceSQL = $origReplaceSQL;
                 $replaceParams = array();
                 $replaceCounter = 0;
                 $maxReplaceGroups = 40;
                 $max = Zotero_Items::$maxDataValueLength;
                 $fieldIDs = array_keys($this->changed['itemData']);
                 foreach ($fieldIDs as $fieldID) {
                     $value = $this->getField($fieldID, true, false, true);
                     // If field changed and is empty, mark row for deletion
                     if ($value === "") {
                         $del[] = $fieldID;
                         continue;
                     }
                     if ($value == 'CURRENT_TIMESTAMP' && Zotero_ItemFields::getID('accessDate') == $fieldID) {
                         $value = Zotero_DB::getTransactionTimestamp();
                     }
                     // Check length
                     if (strlen($value) > $max) {
                         $fieldName = Zotero_ItemFields::getLocalizedString($this->itemTypeID, $fieldID);
                         throw new Exception("={$fieldName} field " . "'" . substr($value, 0, 50) . "...' too long");
                     }
                     if ($replaceCounter < $maxReplaceGroups) {
                         $replaceSQL .= "(?,?,?),";
                         $replaceParams = array_merge($replaceParams, array($this->id, $fieldID, $value));
                     }
                     if ($replaceCounter == $maxReplaceGroups - 1) {
                         $replaceSQL = substr($replaceSQL, 0, -1);
                         $stmt = Zotero_DB::getStatement($replaceSQL, true, $shardID);
                         Zotero_DB::queryFromStatement($stmt, $replaceParams);
                         $replaceSQL = $origReplaceSQL;
                         $replaceParams = array();
                         $replaceCounter = -1;
                     }
                     $replaceCounter++;
                 }
                 if ($replaceCounter > 0 && $replaceCounter < $maxReplaceGroups) {
                     $replaceSQL = substr($replaceSQL, 0, -1);
                     $stmt = Zotero_DB::getStatement($replaceSQL, true, $shardID);
                     Zotero_DB::queryFromStatement($stmt, $replaceParams);
                 }
                 // Update memcached with used fields
                 $fids = array();
                 foreach ($this->itemData as $fieldID => $value) {
                     if ($value !== false && $value !== null) {
                         $fids[] = $fieldID;
                     }
                 }
                 // Delete blank fields
                 if ($del) {
                     $sql = 'DELETE from itemData WHERE itemID=? AND fieldID IN (';
                     $sqlParams = array($this->id);
                     foreach ($del as $d) {
                         $sql .= '?, ';
                         $sqlParams[] = $d;
                     }
                     $sql = substr($sql, 0, -2) . ')';
                     Zotero_DB::query($sql, $sqlParams, $shardID);
                 }
             }
             //
             // Creators
             //
             if ($this->changed['creators']) {
                 $indexes = array_keys($this->changed['creators']);
                 $sql = "INSERT INTO itemCreators\n\t\t\t\t\t\t\t\t(itemID, creatorID, creatorTypeID, orderIndex) VALUES ";
                 $placeholders = array();
                 $sqlValues = array();
                 $cacheRows = array();
                 foreach ($indexes as $orderIndex) {
                     Z_Core::debug('Creator in position ' . $orderIndex . ' has changed', 4);
                     $creator = $this->getCreator($orderIndex);
                     $sql2 = 'DELETE FROM itemCreators WHERE itemID=? AND orderIndex=?';
                     Zotero_DB::query($sql2, array($this->id, $orderIndex), $shardID);
                     if (!$creator) {
                         continue;
                     }
                     if ($creator['ref']->hasChanged()) {
                         Z_Core::debug("Auto-saving changed creator {$creator['ref']->id}");
                         $creator['ref']->save();
                     }
                     $placeholders[] = "(?, ?, ?, ?)";
                     array_push($sqlValues, $this->id, $creator['ref']->id, $creator['creatorTypeID'], $orderIndex);
                 }
                 if ($sqlValues) {
                     $sql = $sql . implode(',', $placeholders);
                     Zotero_DB::query($sql, $sqlValues, $shardID);
                 }
             }
             // Deleted item
             if ($this->changed['deleted']) {
                 $deleted = $this->getDeleted();
                 if ($deleted) {
                     $sql = "REPLACE INTO deletedItems (itemID) VALUES (?)";
                 } else {
                     $sql = "DELETE FROM deletedItems WHERE itemID=?";
                 }
                 Zotero_DB::query($sql, $this->id, $shardID);
             }
             // In case this was previously a standalone item,
             // delete from any collections it may have been in
             if ($this->changed['source'] && $this->getSource()) {
                 $sql = "DELETE FROM collectionItems WHERE itemID=?";
                 Zotero_DB::query($sql, $this->id, $shardID);
             }
             //
             // Note or attachment note
             //
             if ($this->changed['note']) {
                 $noteIsSanitized = false;
                 // If we don't have a sanitized note, generate one
                 if (is_null($this->noteTextSanitized)) {
                     $noteTextSanitized = Zotero_Notes::sanitize($this->noteText);
                     // But if the same as original, just use reference
                     if ($this->noteText == $noteTextSanitized) {
                         $this->noteTextSanitized =& $this->noteText;
                         $noteIsSanitized = true;
                     } else {
                         $this->noteTextSanitized = $noteTextSanitized;
                     }
                 }
                 // If note is sanitized already, store empty string
                 // If not, store sanitized version
                 $noteTextSanitized = $noteIsSanitized ? '' : $this->noteTextSanitized;
                 $title = Zotero_Notes::noteToTitle($this->noteTextSanitized);
                 // Only record sourceItemID in itemNotes for notes
                 if ($this->isNote()) {
                     $sourceItemID = $this->getSource();
                 }
                 $sourceItemID = !empty($sourceItemID) ? $sourceItemID : null;
                 $hash = $this->noteText ? md5($this->noteText) : '';
                 $sql = "INSERT INTO itemNotes\n\t\t\t\t\t\t\t(itemID, sourceItemID, note, noteSanitized, title, hash)\n\t\t\t\t\t\t\tVALUES (?,?,?,?,?,?)\n\t\t\t\t\t\t\tON DUPLICATE KEY UPDATE sourceItemID=?, note=?, noteSanitized=?, title=?, hash=?";
                 $bindParams = array($this->id, $sourceItemID, $this->noteText ? $this->noteText : '', $noteTextSanitized, $title, $hash, $sourceItemID, $this->noteText ? $this->noteText : '', $noteTextSanitized, $title, $hash);
                 Zotero_DB::query($sql, $bindParams, $shardID);
                 Zotero_Notes::updateNoteCache($this->libraryID, $this->id, $this->noteText);
                 Zotero_Notes::updateHash($this->libraryID, $this->id, $hash);
                 // TODO: handle changed source?
             }
             // Attachment
             if ($this->changed['attachmentData']) {
                 $sql = "REPLACE INTO itemAttachments\n\t\t\t\t\t\t(itemID, sourceItemID, linkMode, mimeType, charsetID, path, storageModTime, storageHash)\n\t\t\t\t\t\tVALUES (?,?,?,?,?,?,?,?)";
                 $parent = $this->getSource();
                 if ($parent) {
                     $parentItem = Zotero_Items::get($this->libraryID, $parent);
                     if (!$parentItem) {
                         throw new Exception("Parent item {$parent} not found");
                     }
                     if ($parentItem->getSource()) {
                         trigger_error("Parent item cannot be a child attachment", E_USER_ERROR);
                     }
                 }
                 $linkMode = $this->attachmentLinkMode;
                 $charsetID = Zotero_CharacterSets::getID($this->attachmentCharset);
                 $path = $this->attachmentPath;
                 $storageModTime = $this->attachmentStorageModTime;
                 $storageHash = $this->attachmentStorageHash;
                 $bindParams = array($this->id, $parent ? $parent : null, $linkMode + 1, $this->attachmentMIMEType, $charsetID ? $charsetID : null, $path ? $path : '', $storageModTime ? $storageModTime : null, $storageHash ? $storageHash : null);
                 Zotero_DB::query($sql, $bindParams, $shardID);
             }
             // Sort fields
             if (!empty($this->changed['primaryData']['itemTypeID']) || $this->changed['itemData'] || $this->changed['creators']) {
                 $sql = "UPDATE itemSortFields SET sortTitle=?";
                 $params = array();
                 $sortTitle = Zotero_Items::getSortTitle($this->getDisplayTitle(true));
                 if (mb_substr($sortTitle, 0, 5) == mb_substr($this->getField('title', false, true), 0, 5)) {
                     $sortTitle = null;
                 }
                 $params[] = $sortTitle;
                 if ($this->changed['creators']) {
                     $creatorSummary = mb_strcut($this->getCreatorSummary(), 0, Zotero_Creators::$creatorSummarySortLength);
                     $sql .= ", creatorSummary=?";
                     $params[] = $creatorSummary;
                 }
                 $sql .= " WHERE itemID=?";
                 $params[] = $this->id;
                 Zotero_DB::query($sql, $params, $shardID);
             }
             //
             // Source item id
             //
             if ($this->changed['source']) {
                 $type = Zotero_ItemTypes::getName($this->itemTypeID);
                 $Type = ucwords($type);
                 // Update DB, if not a note or attachment we already changed above
                 if (!$this->changed['attachmentData'] && (!$this->changed['note'] || !$this->isNote())) {
                     $sql = "UPDATE item" . $Type . "s SET sourceItemID=? WHERE itemID=?";
                     $parent = $this->getSource();
                     $bindParams = array($parent ? $parent : null, $this->id);
                     Zotero_DB::query($sql, $bindParams, $shardID);
                 }
             }
             if (false && $this->changed['source']) {
                 trigger_error("Unimplemented", E_USER_ERROR);
                 $newItem = Zotero_Items::get($this->libraryID, $sourceItemID);
                 // FK check
                 if ($newItem) {
                     if ($sourceItemID) {
                     } else {
                         trigger_error("Cannot set {$type} source to invalid item {$sourceItemID}", E_USER_ERROR);
                     }
                 }
                 $oldSourceItemID = $this->getSource();
                 if ($oldSourceItemID == $sourceItemID) {
                     Z_Core::debug("{$Type} source hasn't changed", 4);
                 } else {
                     $oldItem = Zotero_Items::get($this->libraryID, $oldSourceItemID);
                     if ($oldSourceItemID && $oldItem) {
                     } else {
                         //$oldItemNotifierData = null;
                         Z_Core::debug("Old source item {$oldSourceItemID} didn't exist in setSource()", 2);
                     }
                     // If this was an independent item, remove from any collections where it
                     // existed previously and add source instead if there is one
                     if (!$oldSourceItemID) {
                         $sql = "SELECT collectionID FROM collectionItems WHERE itemID=?";
                         $changedCollections = Zotero_DB::query($sql, $itemID, $shardID);
                         if ($changedCollections) {
                             trigger_error("Unimplemented", E_USER_ERROR);
                             if ($sourceItemID) {
                                 $sql = "UPDATE OR REPLACE collectionItems " . "SET itemID=? WHERE itemID=?";
                                 Zotero_DB::query($sql, array($sourceItemID, $this->id), $shardID);
                             } else {
                                 $sql = "DELETE FROM collectionItems WHERE itemID=?";
                                 Zotero_DB::query($sql, $this->id, $shardID);
                             }
                         }
                     }
                     $sql = "UPDATE item{$Type}s SET sourceItemID=?\n\t\t\t\t\t\t\t\tWHERE itemID=?";
                     $bindParams = array($sourceItemID ? $sourceItemID : null, $itemID);
                     Zotero_DB::query($sql, $bindParams, $shardID);
                     //Zotero.Notifier.trigger('modify', 'item', $this->id, notifierData);
                     // Update the counts of the previous and new sources
                     if ($oldItem) {
                         /*
                         switch ($type) {
                         	case 'note':
                         		$oldItem->decrementNoteCount();
                         		break;
                         	case 'attachment':
                         		$oldItem->decrementAttachmentCount();
                         		break;
                         }
                         */
                         //Zotero.Notifier.trigger('modify', 'item', oldSourceItemID, oldItemNotifierData);
                     }
                     if ($newItem) {
                         /*
                         switch ($type) {
                         	case 'note':
                         		$newItem->incrementNoteCount();
                         		break;
                         	case 'attachment':
                         		$newItem->incrementAttachmentCount();
                         		break;
                         }
                         */
                         //Zotero.Notifier.trigger('modify', 'item', sourceItemID, newItemNotifierData);
                     }
                 }
             }
             // Related items
             if (!empty($this->changed['relatedItems'])) {
                 $removed = array();
                 $newids = array();
                 $currentIDs = $this->relatedItems;
                 if (!$currentIDs) {
                     $currentIDs = array();
                 }
                 foreach ($this->previousData['relatedItems'] as $id) {
                     if (!in_array($id, $currentIDs)) {
                         $removed[] = $id;
                     }
                 }
                 foreach ($currentIDs as $id) {
                     if (in_array($id, $this->previousData['relatedItems'])) {
                         continue;
                     }
                     $newids[] = $id;
                 }
                 if ($removed) {
                     $sql = "DELETE FROM itemRelated WHERE itemID=?\n\t\t\t\t\t\t\t\tAND linkedItemID IN (";
                     $q = array_fill(0, sizeOf($removed), '?');
                     $sql .= implode(', ', $q) . ")";
                     Zotero_DB::query($sql, array_merge(array($this->id), $removed), $shardID);
                 }
                 if ($newids) {
                     $sql = "INSERT INTO itemRelated (itemID, linkedItemID)\n\t\t\t\t\t\t\t\tVALUES (?,?)";
                     $insertStatement = Zotero_DB::getStatement($sql, false, $shardID);
                     foreach ($newids as $linkedItemID) {
                         $insertStatement->execute(array($this->id, $linkedItemID));
                     }
                 }
             }
         }
         Zotero_DB::commit();
     } catch (Exception $e) {
         Zotero_DB::rollback();
         throw $e;
     }
     if (!$this->id) {
         $this->id = $itemID;
     }
     if (!$this->key) {
         $this->key = $key;
     }
     if ($isNew) {
         Zotero_Items::cache($this);
         Zotero_Items::cacheLibraryKeyID($this->libraryID, $key, $itemID);
     }
     // TODO: invalidate memcache
     Zotero_Items::reload($this->libraryID, $this->id);
     if ($isNew) {
         //Zotero.Notifier.trigger('add', 'item', $this->getID());
         return $this->id;
     }
     //Zotero.Notifier.trigger('modify', 'item', $this->getID(), { old: $this->_preChangeArray });
     return true;
 }
Beispiel #2
0
	/**
	* Set an item note
	*
	* Note: This can only be called on notes and attachments
	**/
	public function setNote($text) {
		if (!$this->isNote() && !$this->isAttachment()) {
			trigger_error("setNote() can only be called on notes and attachments", E_USER_ERROR);
		}
		
		if (!is_string($text)) {
			$text = '';
		}
		
		if (mb_strlen($text) > Zotero_Notes::$MAX_NOTE_LENGTH) {
			// UTF-8 &nbsp; (0xC2 0xA0) isn't trimmed by default
			$whitespace = chr(0x20) . chr(0x09) . chr(0x0A) . chr(0x0D)
							. chr(0x00) . chr(0x0B) . chr(0xC2) . chr(0xA0);
			$excerpt = iconv(
				"UTF-8",
				"UTF-8//IGNORE",
				Zotero_Notes::noteToTitle(trim($text), true)
			);
			$excerpt = trim($excerpt, $whitespace);
			// If tag-stripped version is empty, just return raw HTML
			if ($excerpt == '') {
				$excerpt = iconv(
					"UTF-8",
					"UTF-8//IGNORE",
					preg_replace(
						'/\s+/',
						' ',
						mb_substr(trim($text), 0, Zotero_Notes::$MAX_TITLE_LENGTH)
					)
				);
				$excerpt = html_entity_decode($excerpt);
				$excerpt = trim($excerpt, $whitespace);
			}
			
			$msg = "=Note '" . $excerpt . "...' too long";
			if ($this->key) {
				$msg .= " for item '" . $this->libraryID . "/" . $this->key . "'";
			}
			throw new Exception($msg, Z_ERROR_NOTE_TOO_LONG);
		}
		
		$sanitizedText = Zotero_Notes::sanitize($text);
		
		if ($sanitizedText === $this->getNote(true)) {
			Z_Core::debug("Note text hasn't changed in setNote()");
			return;
		}
		
		$this->noteText = $text;
		// If sanitized version is the same as original, store empty string
		if ($text === $sanitizedText) {
			$this->noteTextSanitized = '';
		}
		else {
			$this->noteTextSanitized = $sanitizedText;
		}
		$this->changed['note'] = true;
	}
Beispiel #3
0
 /**
  * Handle uploaded data, overwriting existing data
  */
 public function upload()
 {
     $this->sessionCheck();
     // Another session is either queued or writing — upload data won't be valid,
     // so client should wait and return to /updated with 'upload' flag
     Zotero_DB::beginTransaction();
     if (Zotero_Sync::userIsReadLocked($this->userID) || Zotero_Sync::userIsWriteLocked($this->userID)) {
         Zotero_DB::commit();
         $locked = $this->responseXML->addChild('locked');
         $locked['wait'] = $this->getWaitTime($this->sessionID);
         $this->end();
     }
     Zotero_DB::commit();
     $this->clearWaitTime($this->sessionID);
     if (empty($_REQUEST['updateKey'])) {
         $this->error(400, 'INVALID_UPLOAD_DATA', 'Update key not provided');
     }
     if ($_REQUEST['updateKey'] != Zotero_Users::getUpdateKey($this->userID)) {
         $this->e409("Server data has changed since last retrieval");
     }
     // TODO: change to POST
     if (empty($_REQUEST['data'])) {
         $this->error(400, 'MISSING_UPLOAD_DATA', 'Uploaded data not provided');
     }
     $xmldata =& $_REQUEST['data'];
     try {
         $doc = new DOMDocument();
         $doc->loadXML($xmldata, LIBXML_PARSEHUGE);
         // For huge uploads, make sure notes aren't bigger than SimpleXML can parse
         if (strlen($xmldata) > 7000000) {
             $xpath = new DOMXPath($doc);
             $results = $xpath->query('/data/items/item/note[string-length(text()) > ' . Zotero_Notes::$MAX_NOTE_LENGTH . ']');
             if ($results->length) {
                 $noteElem = $results->item(0);
                 $text = $noteElem->textContent;
                 $libraryID = $noteElem->parentNode->getAttribute('libraryID');
                 $key = $noteElem->parentNode->getAttribute('key');
                 // UTF-8 &nbsp; (0xC2 0xA0) isn't trimmed by default
                 $whitespace = chr(0x20) . chr(0x9) . chr(0xa) . chr(0xd) . chr(0x0) . chr(0xb) . chr(0xc2) . chr(0xa0);
                 $excerpt = iconv("UTF-8", "UTF-8//IGNORE", Zotero_Notes::noteToTitle(trim($text), true));
                 $excerpt = trim($excerpt, $whitespace);
                 // If tag-stripped version is empty, just return raw HTML
                 if ($excerpt == '') {
                     $excerpt = iconv("UTF-8", "UTF-8//IGNORE", preg_replace('/\\s+/', ' ', mb_substr(trim($text), 0, Zotero_Notes::$MAX_TITLE_LENGTH)));
                     $excerpt = html_entity_decode($excerpt);
                     $excerpt = trim($excerpt, $whitespace);
                 }
                 $msg = "=Note '" . $excerpt . "...' too long";
                 if ($key) {
                     $msg .= " for item '" . $libraryID . "/" . $key . "'";
                 }
                 throw new Exception($msg, Z_ERROR_NOTE_TOO_LONG);
             }
         }
     } catch (Exception $e) {
         $this->handleUploadError($e, $xmldata);
     }
     function relaxNGErrorHandler($errno, $errstr)
     {
         //Z_Core::logError($errstr);
     }
     set_error_handler('relaxNGErrorHandler');
     set_time_limit(60);
     if (!$doc->relaxNGValidate(Z_ENV_MODEL_PATH . 'relax-ng/upload.rng')) {
         $id = substr(md5(uniqid(rand(), true)), 0, 10);
         $str = date("D M j G:i:s T Y") . "\n";
         $str .= "IP address: " . $_SERVER['REMOTE_ADDR'] . "\n";
         if (isset($_SERVER['HTTP_X_ZOTERO_VERSION'])) {
             $str .= "Version: " . $_SERVER['HTTP_X_ZOTERO_VERSION'] . "\n";
         }
         $str .= "Error: RELAX NG validation failed\n\n";
         $str .= $xmldata;
         if (!file_put_contents(Z_CONFIG::$SYNC_ERROR_PATH . $id, $str)) {
             error_log("Unable to save error report to " . Z_CONFIG::$SYNC_ERROR_PATH . $id);
         }
         $this->error(500, 'INVALID_UPLOAD_DATA', "Uploaded data not well-formed (Report ID: {$id})");
     }
     restore_error_handler();
     try {
         $xml = simplexml_import_dom($doc);
         $queue = true;
         if (Z_ENV_TESTING_SITE && !empty($_GET['noqueue'])) {
             $queue = false;
         }
         if ($queue) {
             $affectedLibraries = Zotero_Sync::parseAffectedLibraries($xmldata);
             // Relations-only uploads don't have affected libraries
             if (!$affectedLibraries) {
                 $affectedLibraries = array(Zotero_Users::getLibraryIDFromUserID($this->userID));
             }
             Zotero_Sync::queueUpload($this->userID, $this->sessionID, $xmldata, $affectedLibraries);
             try {
                 Zotero_Processors::notifyProcessors('upload');
                 Zotero_Processors::notifyProcessors('error');
                 usleep(750000);
             } catch (Exception $e) {
                 Z_Core::logError($e);
             }
             // Give processor a chance to finish while we're still here
             $this->uploadstatus();
         } else {
             set_time_limit(210);
             $timestamp = Zotero_Sync::processUpload($this->userID, $xml);
             $this->responseXML['timestamp'] = $timestamp;
             $this->responseXML->addChild('uploaded');
             $this->end();
         }
     } catch (Exception $e) {
         $this->handleUploadError($e, $xmldata);
     }
 }