public static function updateFromJSON(Zotero_Item $item, $json, Zotero_Item $parentItem = null, $requestParams, $userID, $requireVersion = 0, $partialUpdate = false) { $json = Zotero_API::extractEditableJSON($json); $exists = Zotero_API::processJSONObjectKey($item, $json, $requestParams); // computerProgram used 'version' instead of 'versionNumber' before v3 if ($requestParams['v'] < 3 && isset($json->version)) { $json->versionNumber = $json->version; unset($json->version); } Zotero_API::checkJSONObjectVersion($item, $json, $requestParams, $requireVersion); self::validateJSONItem($json, $item->libraryID, $exists ? $item : null, $parentItem || ($exists ? !!$item->getSourceKey() : false), $requestParams, $partialUpdate && $exists); $changed = false; $twoStage = false; if (!Zotero_DB::transactionInProgress()) { Zotero_DB::beginTransaction(); $transactionStarted = true; } else { $transactionStarted = false; } // Set itemType first if (isset($json->itemType)) { $item->setField("itemTypeID", Zotero_ItemTypes::getID($json->itemType)); } $changedDateModified = false; foreach ($json as $key => $val) { switch ($key) { case 'key': case 'version': case 'itemKey': case 'itemVersion': case 'itemType': case 'deleted': continue; case 'parentItem': $item->setSourceKey($val); break; case 'creators': if (!$val && !$item->numCreators()) { continue 2; } $orderIndex = -1; foreach ($val as $newCreatorData) { // 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; } // Skip empty creators if (Zotero_Utilities::unicodeTrim($newCreatorData->firstName) === "" && Zotero_Utilities::unicodeTrim($newCreatorData->lastName) === "") { break; } $orderIndex++; $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 ($exists && ($indexes = array_keys($item->getCreators()))) { $i = max($indexes); while ($i > $orderIndex) { $item->removeCreator($i); $i--; } } break; case 'tags': $item->setTags($val); break; case 'collections': $item->setCollections($val); break; case 'relations': $item->setRelations($val); 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; case 'dateModified': $changedDateModified = $item->setField($key, $val); break; default: $item->setField($key, $val); break; } } if ($parentItem) { $item->setSource($parentItem->id); } else { if ($requestParams['v'] >= 2 && !$partialUpdate && $item->getSourceKey() && !isset($json->parentItem)) { $item->setSourceKey(false); } } $item->deleted = !empty($json->deleted); // If item has changed, update it with the current timestamp if ($item->hasChanged() && !$changedDateModified) { $item->dateModified = Zotero_DB::getTransactionTimestamp(); } $changed = $item->save($userID) || $changed; // 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 $attachmentJSON) { $childItem = new Zotero_Item(); $childItem->libraryID = $item->libraryID; self::updateFromJSON($childItem, $attachmentJSON, $item, $requestParams, $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; } } } if ($transactionStarted) { Zotero_DB::commit(); } return $changed; }
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(); }