public function testItemVersionAfterSave() { $item = new Zotero_Item("book"); $item->libraryID = self::$config['userLibraryID']; $item->save(); $this->assertEquals(0, $item->itemVersion); $item->itemTypeID = 3; $item->save(); $this->assertEquals(1, $item->itemVersion); $item->setField("title", "Foo"); $item->save(); $this->assertEquals(2, $item->itemVersion); }
/** * Converts a DOMElement item to a Zotero_Item object * * @param DOMElement $xml Item data as DOMElement * @return Zotero_Item Zotero item object */ public static function convertXMLToItem(DOMElement $xml) { // Get item type id, adding custom type if necessary $itemTypeName = $xml->getAttribute('itemType'); $itemTypeID = Zotero_ItemTypes::getID($itemTypeName); if (!$itemTypeID) { $itemTypeID = Zotero_ItemTypes::addCustomType($itemTypeName); } // Primary fields $libraryID = (int) $xml->getAttribute('libraryID'); $itemObj = self::getByLibraryAndKey($libraryID, $xml->getAttribute('key')); if (!$itemObj) { $itemObj = new Zotero_Item(); $itemObj->libraryID = $libraryID; $itemObj->key = $xml->getAttribute('key'); } $itemObj->setField('itemTypeID', $itemTypeID, false, true); $itemObj->setField('dateAdded', $xml->getAttribute('dateAdded'), false, true); $itemObj->setField('dateModified', $xml->getAttribute('dateModified'), false, true); $xmlFields = array(); $xmlCreators = array(); $xmlNote = null; $xmlPath = null; $xmlRelated = null; $childNodes = $xml->childNodes; foreach ($childNodes as $child) { switch ($child->nodeName) { case 'field': $xmlFields[] = $child; break; case 'creator': $xmlCreators[] = $child; break; case 'note': $xmlNote = $child; break; case 'path': $xmlPath = $child; break; case 'related': $xmlRelated = $child; break; } } // Item data $setFields = array(); foreach ($xmlFields as $field) { // TODO: add custom fields $fieldName = $field->getAttribute('name'); // Special handling for renamed computerProgram 'version' field if ($itemTypeID == 32 && $fieldName == 'version') { $fieldName = 'versionNumber'; } $itemObj->setField($fieldName, $field->nodeValue, false, true); $setFields[$fieldName] = true; } $previousFields = $itemObj->getUsedFields(true); foreach ($previousFields as $field) { if (!isset($setFields[$field])) { $itemObj->setField($field, false, false, true); } } $deleted = $xml->getAttribute('deleted'); $itemObj->deleted = $deleted == 'true' || $deleted == '1'; // Creators $i = 0; foreach ($xmlCreators as $creator) { // TODO: add custom creator types $pos = (int) $creator->getAttribute('index'); if ($pos != $i) { throw new Exception("No creator in position {$i} for item " . $itemObj->key); } $key = $creator->getAttribute('key'); $creatorObj = Zotero_Creators::getByLibraryAndKey($libraryID, $key); // If creator doesn't exist locally (e.g., if it was deleted locally // and appears in a new/modified item remotely), get it from within // the item's creator block, where a copy should be provided if (!$creatorObj) { $subcreator = $creator->getElementsByTagName('creator')->item(0); if (!$subcreator) { throw new Exception("Data for missing local creator {$key} not provided", Z_ERROR_CREATOR_NOT_FOUND); } $creatorObj = Zotero_Creators::convertXMLToCreator($subcreator, $libraryID); if ($creatorObj->key != $key) { throw new Exception("Creator key " . $creatorObj->key . " does not match item creator key {$key}"); } } if (Zotero_Utilities::unicodeTrim($creatorObj->firstName) === '' && Zotero_Utilities::unicodeTrim($creatorObj->lastName) === '') { continue; } $creatorTypeID = Zotero_CreatorTypes::getID($creator->getAttribute('creatorType')); $itemObj->setCreator($pos, $creatorObj, $creatorTypeID); $i++; } // Remove item's remaining creators not in XML $numCreators = $itemObj->numCreators(); $rem = $numCreators - $i; for ($j = 0; $j < $rem; $j++) { // Keep removing last creator $itemObj->removeCreator($i); } // Both notes and attachments might have parents and notes if ($itemTypeName == 'note' || $itemTypeName == 'attachment') { $sourceItemKey = $xml->getAttribute('sourceItem'); $itemObj->setSource($sourceItemKey ? $sourceItemKey : false); $itemObj->setNote($xmlNote ? $xmlNote->nodeValue : ""); } // Attachment metadata if ($itemTypeName == 'attachment') { $itemObj->attachmentLinkMode = (int) $xml->getAttribute('linkMode'); $itemObj->attachmentMIMEType = $xml->getAttribute('mimeType'); $itemObj->attachmentCharset = $xml->getAttribute('charset'); // Cast to string to be 32-bit safe $storageModTime = (string) $xml->getAttribute('storageModTime'); $itemObj->attachmentStorageModTime = $storageModTime ? $storageModTime : null; $storageHash = $xml->getAttribute('storageHash'); $itemObj->attachmentStorageHash = $storageHash ? $storageHash : null; $itemObj->attachmentPath = $xmlPath ? $xmlPath->nodeValue : ""; } // Related items if ($xmlRelated && $xmlRelated->nodeValue) { $relatedKeys = explode(' ', $xmlRelated->nodeValue); } else { $relatedKeys = array(); } $itemObj->relatedItems = $relatedKeys; return $itemObj; }
public static function updateFromJSON(Zotero_Item $item, $json, $isNew = false, Zotero_Item $parentItem = null, $userID = null) { self::validateJSONItem($json, $item->libraryID, $isNew ? null : $item, !is_null($parentItem)); Zotero_DB::beginTransaction(); // Mark library as updated if (!$isNew) { $timestamp = Zotero_Libraries::updateTimestamps($item->libraryID); Zotero_DB::registerTransactionTimestamp($timestamp); } $forceChange = false; $twoStage = false; // Set itemType first $item->setField("itemTypeID", Zotero_ItemTypes::getID($json->itemType)); foreach ($json as $key => $val) { switch ($key) { case 'itemType': continue; case 'deleted': continue; case 'creators': if (!$val && !$item->numCreators()) { continue 2; } $orderIndex = -1; foreach ($val as $orderIndex => $newCreatorData) { if ((!isset($newCreatorData->name) || trim($newCreatorData->name) == "") && (!isset($newCreatorData->firstName) || trim($newCreatorData->firstName) == "") && (!isset($newCreatorData->lastName) || trim($newCreatorData->lastName) == "")) { // This should never happen, because of check in validateJSONItem() if (!$isNew) { throw new Exception("Nameless creator in update request"); } // On item creation, ignore creators with empty names, // because that's in the item template that the API returns break; } // JSON uses 'name' and 'firstName'/'lastName', // so switch to just 'firstName'/'lastName' if (isset($newCreatorData->name)) { $newCreatorData->firstName = ''; $newCreatorData->lastName = $newCreatorData->name; unset($newCreatorData->name); $newCreatorData->fieldMode = 1; } else { $newCreatorData->fieldMode = 0; } $newCreatorTypeID = Zotero_CreatorTypes::getID($newCreatorData->creatorType); // Same creator in this position $existingCreator = $item->getCreator($orderIndex); if ($existingCreator && $existingCreator['ref']->equals($newCreatorData)) { // Just change the creatorTypeID if ($existingCreator['creatorTypeID'] != $newCreatorTypeID) { $item->setCreator($orderIndex, $existingCreator['ref'], $newCreatorTypeID); } continue; } // Same creator in a different position, so use that $existingCreators = $item->getCreators(); for ($i = 0, $len = sizeOf($existingCreators); $i < $len; $i++) { if ($existingCreators[$i]['ref']->equals($newCreatorData)) { $item->setCreator($orderIndex, $existingCreators[$i]['ref'], $newCreatorTypeID); continue; } } // Make a fake creator to use for the data lookup $newCreator = new Zotero_Creator(); $newCreator->libraryID = $item->libraryID; foreach ($newCreatorData as $key => $val) { if ($key == 'creatorType') { continue; } $newCreator->{$key} = $val; } // Look for an equivalent creator in this library $candidates = Zotero_Creators::getCreatorsWithData($item->libraryID, $newCreator, true); if ($candidates) { $c = Zotero_Creators::get($item->libraryID, $candidates[0]); $item->setCreator($orderIndex, $c, $newCreatorTypeID); continue; } // None found, so make a new one $creatorID = $newCreator->save(); $newCreator = Zotero_Creators::get($item->libraryID, $creatorID); $item->setCreator($orderIndex, $newCreator, $newCreatorTypeID); } // Remove all existing creators above the current index if (!$isNew && ($indexes = array_keys($item->getCreators()))) { $i = max($indexes); while ($i > $orderIndex) { $item->removeCreator($i); $i--; } } break; case 'tags': // If item isn't yet saved, add tags below if (!$item->id) { $twoStage = true; break; } if ($item->setTags($val)) { $forceChange = true; } break; case 'attachments': case 'notes': if (!$val) { continue; } $twoStage = true; break; case 'note': $item->setNote($val); break; // Attachment properties // Attachment properties case 'linkMode': $item->attachmentLinkMode = Zotero_Attachments::linkModeNameToNumber($val, true); break; case 'contentType': case 'charset': case 'filename': $k = "attachment" . ucwords($key); $item->{$k} = $val; break; case 'md5': $item->attachmentStorageHash = $val; break; case 'mtime': $item->attachmentStorageModTime = $val; break; default: $item->setField($key, $val); break; } } if ($parentItem) { $item->setSource($parentItem->id); } $item->deleted = !empty($json->deleted); // For changes that don't register as changes internally, force a dateModified update if ($forceChange) { $item->setField('dateModified', Zotero_DB::getTransactionTimestamp()); } $item->save($userID); // Additional steps that have to be performed on a saved object if ($twoStage) { foreach ($json as $key => $val) { switch ($key) { case 'attachments': if (!$val) { continue; } foreach ($val as $attachment) { $childItem = new Zotero_Item(); $childItem->libraryID = $item->libraryID; self::updateFromJSON($childItem, $attachment, true, $item, $userID); } break; case 'notes': if (!$val) { continue; } $noteItemTypeID = Zotero_ItemTypes::getID("note"); foreach ($val as $note) { $childItem = new Zotero_Item(); $childItem->libraryID = $item->libraryID; $childItem->itemTypeID = $noteItemTypeID; $childItem->setSource($item->id); $childItem->setNote($note->note); $childItem->save(); } break; case 'tags': if ($item->setTags($val)) { $forceChange = true; } break; } } // For changes that don't register as changes internally, force a dateModified update if ($forceChange) { $item->setField('dateModified', Zotero_DB::getTransactionTimestamp()); } $item->save($userID); } Zotero_DB::commit(); }