/** * Converts a Zotero_Item object to a SimpleXMLElement item * * @param object $item Zotero_Item object * @param array $data * @return SimpleXMLElement Item data as SimpleXML element */ public static function convertItemToXML(Zotero_Item $item, $data = array()) { $t = microtime(true); // Check cache for all items except imported attachments, // which don't have their versions updated when the client // updates their file metadata if (!$item->isImportedAttachment()) { $cacheVersion = 1; $cacheKey = "syncXMLItem_" . $item->libraryID . "/" . $item->id . "_" . $item->version . "_" . md5(json_encode($data)) . "_" . $cacheVersion . (isset(Z_CONFIG::$CACHE_VERSION_SYNC_XML_ITEM) ? "_" . Z_CONFIG::$CACHE_VERSION_SYNC_XML_ITEM : ""); $xmlstr = Z_Core::$MC->get($cacheKey); } else { $cacheKey = false; $xmlstr = false; } if ($xmlstr) { $xml = new SimpleXMLElement($xmlstr); StatsD::timing("api.items.itemToSyncXML.cached", (microtime(true) - $t) * 1000); StatsD::increment("memcached.items.itemToSyncXML.hit"); // Skip the cache every 10 times for now, to ensure cache sanity if (Z_Core::probability(10)) { //$xmlstr = $xml->saveXML(); } else { Z_Core::debug("Using cached sync XML item"); return $xml; } } $xml = new SimpleXMLElement('<item/>'); // Primary fields foreach (self::$primaryFields as $field) { switch ($field) { case 'id': case 'serverDateModified': case 'version': continue 2; case 'itemTypeID': $xmlField = 'itemType'; $xmlValue = Zotero_ItemTypes::getName($item->{$field}); break; default: $xmlField = $field; $xmlValue = $item->{$field}; } $xml[$xmlField] = $xmlValue; } // Item data $itemTypeID = $item->itemTypeID; $fieldIDs = $item->getUsedFields(); foreach ($fieldIDs as $fieldID) { $val = $item->getField($fieldID); if ($val == '') { continue; } $f = $xml->addChild('field', htmlspecialchars($val)); $fieldName = Zotero_ItemFields::getName($fieldID); // Special handling for renamed computerProgram 'version' field if ($itemTypeID == 32 && $fieldName == 'versionNumber') { $fieldName = 'version'; } $f['name'] = htmlspecialchars($fieldName); } // Deleted item flag if ($item->deleted) { $xml['deleted'] = '1'; } if ($item->isNote() || $item->isAttachment()) { $sourceItemID = $item->getSource(); if ($sourceItemID) { $sourceItem = Zotero_Items::get($item->libraryID, $sourceItemID); if (!$sourceItem) { throw new Exception("Source item {$sourceItemID} not found"); } $xml['sourceItem'] = $sourceItem->key; } } // Group modification info $createdByUserID = null; $lastModifiedByUserID = null; switch (Zotero_Libraries::getType($item->libraryID)) { case 'group': $createdByUserID = $item->createdByUserID; $lastModifiedByUserID = $item->lastModifiedByUserID; break; } if ($createdByUserID) { $xml['createdByUserID'] = $createdByUserID; } if ($lastModifiedByUserID) { $xml['lastModifiedByUserID'] = $lastModifiedByUserID; } if ($item->isAttachment()) { $xml['linkMode'] = $item->attachmentLinkMode; $xml['mimeType'] = $item->attachmentMIMEType; if ($item->attachmentCharset) { $xml['charset'] = $item->attachmentCharset; } $storageModTime = $item->attachmentStorageModTime; if ($storageModTime) { $xml['storageModTime'] = $storageModTime; } $storageHash = $item->attachmentStorageHash; if ($storageHash) { $xml['storageHash'] = $storageHash; } // TODO: get from a constant if ($item->attachmentLinkMode != 3) { $xml->addChild('path', htmlspecialchars($item->attachmentPath)); } } // Note if ($item->isNote() || $item->isAttachment()) { // Get htmlspecialchars'ed note $note = $item->getNote(false, true); if ($note !== '') { $xml->addChild('note', $note); } else { if ($item->isNote()) { $xml->addChild('note', ''); } } } // Creators $creators = $item->getCreators(); if ($creators) { foreach ($creators as $index => $creator) { $c = $xml->addChild('creator'); $c['key'] = $creator['ref']->key; $c['creatorType'] = htmlspecialchars(Zotero_CreatorTypes::getName($creator['creatorTypeID'])); $c['index'] = $index; if (empty($data['updatedCreators']) || !in_array($creator['ref']->id, $data['updatedCreators'])) { $cNode = dom_import_simplexml($c); $creatorXML = Zotero_Creators::convertCreatorToXML($creator['ref'], $cNode->ownerDocument); $cNode->appendChild($creatorXML); } } } // Related items $relatedKeys = $item->relatedItems; $keys = array(); foreach ($relatedKeys as $relatedKey) { if (Zotero_Items::getByLibraryAndKey($item->libraryID, $relatedKey)) { $keys[] = $relatedKey; } } if ($keys) { $xml->related = implode(' ', $keys); } if ($xmlstr) { $uncached = $xml->saveXML(); if ($xmlstr != $uncached) { error_log("Cached sync XML item does not match"); error_log(" Cached: " . $xmlstr); error_log("Uncached: " . $uncached); } } else { $xmlstr = $xml->saveXML(); if ($cacheKey) { Z_Core::$MC->set($cacheKey, $xmlstr, 3600); // 1 hour for now } StatsD::timing("api.items.itemToSyncXML.uncached", (microtime(true) - $t) * 1000); StatsD::increment("memcached.items.itemToSyncXML.miss"); } return $xml; }
/** * Converts a Zotero_Item object to a SimpleXMLElement item * * @param object $item Zotero_Item object * @param array $data * @return SimpleXMLElement Item data as SimpleXML element */ public static function convertItemToXML(Zotero_Item $item, $data = array(), $apiVersion = null) { $xml = new SimpleXMLElement('<item/>'); // Primary fields foreach (Zotero_Items::$primaryFields as $field) { switch ($field) { case 'itemID': case 'serverDateModified': case 'itemVersion': case 'numAttachments': case 'numNotes': continue 2; case 'itemTypeID': $xmlField = 'itemType'; $xmlValue = Zotero_ItemTypes::getName($item->{$field}); break; default: $xmlField = $field; $xmlValue = $item->{$field}; } $xml[$xmlField] = $xmlValue; } // Item data $fieldIDs = $item->getUsedFields(); foreach ($fieldIDs as $fieldID) { $val = $item->getField($fieldID); if ($val == '') { continue; } $f = $xml->addChild('field', htmlspecialchars($val)); $f['name'] = htmlspecialchars(Zotero_ItemFields::getName($fieldID)); } // Deleted item flag if ($item->deleted) { $xml['deleted'] = '1'; } if ($item->isNote() || $item->isAttachment()) { $sourceItemID = $item->getSource(); if ($sourceItemID) { $sourceItem = Zotero_Items::get($item->libraryID, $sourceItemID); if (!$sourceItem) { throw new Exception("Source item {$sourceItemID} not found"); } $xml['sourceItem'] = $sourceItem->key; } } // Group modification info $createdByUserID = null; $lastModifiedByUserID = null; switch (Zotero_Libraries::getType($item->libraryID)) { case 'group': $createdByUserID = $item->createdByUserID; $lastModifiedByUserID = $item->lastModifiedByUserID; break; } if ($createdByUserID) { $xml['createdByUserID'] = $createdByUserID; } if ($lastModifiedByUserID) { $xml['lastModifiedByUserID'] = $lastModifiedByUserID; } if ($item->isAttachment()) { $xml['linkMode'] = $item->attachmentLinkMode; $xml['mimeType'] = $item->attachmentMIMEType; if ($apiVersion == 1 || $item->attachmentCharset) { $xml['charset'] = $item->attachmentCharset; } $storageModTime = $item->attachmentStorageModTime; if ($apiVersion > 1 && $storageModTime) { $xml['storageModTime'] = $storageModTime; } $storageHash = $item->attachmentStorageHash; if ($apiVersion > 1 && $storageHash) { $xml['storageHash'] = $storageHash; } // TODO: get from a constant if ($item->attachmentLinkMode != 3) { $xml->addChild('path', htmlspecialchars($item->attachmentPath)); } } // Note if ($item->isNote() || $item->isAttachment()) { // Get htmlspecialchars'ed note $note = $item->getNote(false, true); if ($note !== '') { $xml->addChild('note', $note); } else { if ($item->isNote()) { $xml->addChild('note', ''); } } } // Creators $creators = $item->getCreators(); if ($creators) { foreach ($creators as $index => $creator) { $c = $xml->addChild('creator'); $c['key'] = $creator['ref']->key; $c['creatorType'] = htmlspecialchars(Zotero_CreatorTypes::getName($creator['creatorTypeID'])); $c['index'] = $index; if (empty($data['updatedCreators']) || !in_array($creator['ref']->id, $data['updatedCreators'])) { $cNode = dom_import_simplexml($c); $creatorXML = Zotero_Creators::convertCreatorToXML($creator['ref'], $cNode->ownerDocument); $cNode->appendChild($creatorXML); } } } // Related items $related = $item->relatedItems; if ($related) { $related = Zotero_Items::get($item->libraryID, $related); $keys = array(); foreach ($related as $item) { $keys[] = $item->key; } if ($keys) { $xml->related = implode(' ', $keys); } } return $xml; }