public function testExistsByLibraryAndKey() { $this->assertFalse(Zotero_Items::existsByLibraryAndKey(self::$config['userLibraryID'], "AAAAAAAA")); $item = new Zotero_Item(); $item->libraryID = self::$config['userLibraryID']; $item->itemTypeID = Zotero_ItemTypes::getID("book"); $item->save(); $key = $item->key; $this->assertTrue(Zotero_Items::existsByLibraryAndKey(self::$config['userLibraryID'], $key)); Zotero_Items::delete(self::$config['userLibraryID'], $key); $this->assertFalse(Zotero_Items::existsByLibraryAndKey(self::$config['userLibraryID'], $key)); }
private static function validateJSONItem($json, $libraryID, Zotero_Item $item = null, $isChild, $requestParams, $partialUpdate = false) { $isNew = !$item || !$item->version; if (!is_object($json)) { throw new Exception("Invalid item object (found " . gettype($json) . " '" . $json . "')", Z_ERROR_INVALID_INPUT); } if (isset($json->items) && is_array($json->items)) { throw new Exception("An 'items' array is not valid for single-item updates", Z_ERROR_INVALID_INPUT); } $apiVersion = $requestParams['v']; if ($partialUpdate) { $requiredProps = []; } else { if (isset($json->itemType) && $json->itemType == "attachment") { $requiredProps = array('linkMode', 'tags'); } else { if (isset($json->itemType) && $json->itemType == "attachment") { $requiredProps = array('tags'); } else { if ($isNew) { $requiredProps = array('itemType'); } else { if ($apiVersion < 2) { $requiredProps = array('itemType', 'tags'); } else { $requiredProps = array('itemType', 'tags', 'relations'); if (!$isChild) { $requiredProps[] = 'collections'; } } } } } } foreach ($requiredProps as $prop) { if (!isset($json->{$prop})) { throw new Exception("'{$prop}' property not provided", Z_ERROR_INVALID_INPUT); } } // For partial updates where item type isn't provided, use the existing item type if (!isset($json->itemType) && $partialUpdate) { $itemType = Zotero_ItemTypes::getName($item->itemTypeID); } else { $itemType = $json->itemType; } foreach ($json as $key => $val) { switch ($key) { // Handled by Zotero_API::checkJSONObjectVersion() case 'key': case 'version': if ($apiVersion < 3) { throw new Exception("Invalid property '{$key}'", Z_ERROR_INVALID_INPUT); } break; case 'itemKey': case 'itemVersion': if ($apiVersion != 2) { throw new Exception("Invalid property '{$key}'", Z_ERROR_INVALID_INPUT); } break; case 'parentItem': if ($apiVersion < 2) { throw new Exception("Invalid property '{$key}'", Z_ERROR_INVALID_INPUT); } if (!Zotero_ID::isValidKey($val) && $val !== false) { throw new Exception("'{$key}' must be a valid item key", Z_ERROR_INVALID_INPUT); } break; case 'itemType': if (!is_string($val)) { throw new Exception("'itemType' must be a string", Z_ERROR_INVALID_INPUT); } if ($isChild || !empty($json->parentItem)) { switch ($val) { case 'note': case 'attachment': break; default: throw new Exception("Child item must be note or attachment", Z_ERROR_INVALID_INPUT); } } else { if ($val == 'attachment' && (!$item || !$item->getSource())) { if ($json->linkMode == 'linked_url' || $json->linkMode == 'imported_url' && (empty($json->contentType) || $json->contentType != 'application/pdf')) { throw new Exception("Only file attachments and PDFs can be top-level items", Z_ERROR_INVALID_INPUT); } } } if (!Zotero_ItemTypes::getID($val)) { throw new Exception("'{$val}' is not a valid itemType", Z_ERROR_INVALID_INPUT); } break; case 'tags': if (!is_array($val)) { throw new Exception("'{$key}' property must be an array", Z_ERROR_INVALID_INPUT); } foreach ($val as $tag) { $empty = true; if (is_string($tag)) { if ($tag === "") { throw new Exception("Tag cannot be empty", Z_ERROR_INVALID_INPUT); } continue; } if (!is_object($tag)) { throw new Exception("Tag must be an object", Z_ERROR_INVALID_INPUT); } foreach ($tag as $k => $v) { switch ($k) { case 'tag': if (!is_scalar($v)) { throw new Exception("Invalid tag name", Z_ERROR_INVALID_INPUT); } if ($v === "") { throw new Exception("Tag cannot be empty", Z_ERROR_INVALID_INPUT); } break; case 'type': if (!is_numeric($v)) { throw new Exception("Invalid tag type '{$v}'", Z_ERROR_INVALID_INPUT); } break; default: throw new Exception("Invalid tag property '{$k}'", Z_ERROR_INVALID_INPUT); } $empty = false; } if ($empty) { throw new Exception("Tag object is empty", Z_ERROR_INVALID_INPUT); } } break; case 'collections': if (!is_array($val)) { throw new Exception("'{$key}' property must be an array", Z_ERROR_INVALID_INPUT); } if ($isChild && $val) { throw new Exception("Child items cannot be assigned to collections", Z_ERROR_INVALID_INPUT); } foreach ($val as $k) { if (!Zotero_ID::isValidKey($k)) { throw new Exception("'{$k}' is not a valid collection key", Z_ERROR_INVALID_INPUT); } } break; case 'relations': if ($apiVersion < 2) { throw new Exception("Invalid property '{$key}'", Z_ERROR_INVALID_INPUT); } if (!is_object($val) && !(is_array($val) && empty($val))) { throw new Exception("'{$key}' property must be an object", Z_ERROR_INVALID_INPUT); } foreach ($val as $predicate => $object) { switch ($predicate) { case 'owl:sameAs': case 'dc:replaces': case 'dc:relation': break; default: throw new Exception("Unsupported predicate '{$predicate}'", Z_ERROR_INVALID_INPUT); } $arr = is_string($object) ? [$object] : $object; foreach ($arr as $uri) { if (!preg_match('/^http:\\/\\/zotero.org\\/(users|groups)\\/[0-9]+\\/(publications\\/)?items\\/[A-Z0-9]{8}$/', $uri)) { throw new Exception("'{$key}' values currently must be Zotero item URIs", Z_ERROR_INVALID_INPUT); } } } break; case 'creators': if (!is_array($val)) { throw new Exception("'{$key}' property must be an array", Z_ERROR_INVALID_INPUT); } foreach ($val as $creator) { $empty = true; if (!isset($creator->creatorType)) { throw new Exception("creator object must contain 'creatorType'", Z_ERROR_INVALID_INPUT); } if ((!isset($creator->name) || trim($creator->name) == "") && (!isset($creator->firstName) || trim($creator->firstName) == "") && (!isset($creator->lastName) || trim($creator->lastName) == "")) { // On item creation, ignore single nameless creator, // because that's in the item template that the API returns if (sizeOf($val) == 1 && $isNew) { continue; } else { throw new Exception("creator object must contain 'firstName'/'lastName' or 'name'", Z_ERROR_INVALID_INPUT); } } foreach ($creator as $k => $v) { switch ($k) { case 'creatorType': $creatorTypeID = Zotero_CreatorTypes::getID($v); if (!$creatorTypeID) { throw new Exception("'{$v}' is not a valid creator type", Z_ERROR_INVALID_INPUT); } $itemTypeID = Zotero_ItemTypes::getID($itemType); if (!Zotero_CreatorTypes::isValidForItemType($creatorTypeID, $itemTypeID)) { // Allow 'author' in all item types, but reject other invalid creator types if ($creatorTypeID != Zotero_CreatorTypes::getID('author')) { throw new Exception("'{$v}' is not a valid creator type for item type '{$itemType}'", Z_ERROR_INVALID_INPUT); } } break; case 'firstName': if (!isset($creator->lastName)) { throw new Exception("'lastName' creator field must be set if 'firstName' is set", Z_ERROR_INVALID_INPUT); } if (isset($creator->name)) { throw new Exception("'firstName' and 'name' creator fields are mutually exclusive", Z_ERROR_INVALID_INPUT); } break; case 'lastName': if (!isset($creator->firstName)) { throw new Exception("'firstName' creator field must be set if 'lastName' is set", Z_ERROR_INVALID_INPUT); } if (isset($creator->name)) { throw new Exception("'lastName' and 'name' creator fields are mutually exclusive", Z_ERROR_INVALID_INPUT); } break; case 'name': if (isset($creator->firstName)) { throw new Exception("'firstName' and 'name' creator fields are mutually exclusive", Z_ERROR_INVALID_INPUT); } if (isset($creator->lastName)) { throw new Exception("'lastName' and 'name' creator fields are mutually exclusive", Z_ERROR_INVALID_INPUT); } break; default: throw new Exception("Invalid creator property '{$k}'", Z_ERROR_INVALID_INPUT); } $empty = false; } if ($empty) { throw new Exception("Creator object is empty", Z_ERROR_INVALID_INPUT); } } break; case 'note': switch ($itemType) { case 'note': case 'attachment': break; default: throw new Exception("'note' property is valid only for note and attachment items", Z_ERROR_INVALID_INPUT); } break; case 'attachments': case 'notes': if ($apiVersion > 1) { throw new Exception("'{$key}' property is no longer supported", Z_ERROR_INVALID_INPUT); } if (!$isNew) { throw new Exception("'{$key}' property is valid only for new items", Z_ERROR_INVALID_INPUT); } if (!is_array($val)) { throw new Exception("'{$key}' property must be an array", Z_ERROR_INVALID_INPUT); } foreach ($val as $child) { // Check child item type ('attachment' or 'note') $t = substr($key, 0, -1); if (isset($child->itemType) && $child->itemType != $t) { throw new Exception("Child {$t} must be of itemType '{$t}'", Z_ERROR_INVALID_INPUT); } if ($key == 'note') { if (!isset($child->note)) { throw new Exception("'note' property not provided for child note", Z_ERROR_INVALID_INPUT); } } } break; case 'deleted': break; // Attachment properties // Attachment properties case 'linkMode': try { $linkMode = Zotero_Attachments::linkModeNameToNumber($val, true); } catch (Exception $e) { throw new Exception("'{$val}' is not a valid linkMode", Z_ERROR_INVALID_INPUT); } // Don't allow changing of linkMode if (!$isNew && $linkMode != $item->attachmentLinkMode) { throw new Exception("Cannot change attachment linkMode", Z_ERROR_INVALID_INPUT); } break; case 'contentType': case 'charset': case 'filename': case 'md5': case 'mtime': if ($itemType != 'attachment') { throw new Exception("'{$key}' is valid only for attachment items", Z_ERROR_INVALID_INPUT); } switch ($key) { case 'filename': case 'md5': case 'mtime': $lm = isset($json->linkMode) ? $json->linkMode : Zotero_Attachments::linkModeNumberToName($item->attachmentLinkMode); if (strpos(strtolower($lm), 'imported_') !== 0) { throw new Exception("'{$key}' is valid only for imported attachment items", Z_ERROR_INVALID_INPUT); } break; } switch ($key) { case 'contentType': case 'charset': case 'filename': $propName = 'attachment' . ucwords($key); break; case 'md5': $propName = 'attachmentStorageHash'; break; case 'mtime': $propName = 'attachmentStorageModTime'; break; } if (Zotero_Libraries::getType($libraryID) == 'group') { if ($item && $item->{$propName} !== $val || !$item && $val !== null && $val !== "") { throw new Exception("Cannot change '{$key}' directly in group library", Z_ERROR_INVALID_INPUT); } } else { if ($key == 'md5') { if ($val && !preg_match("/^[a-f0-9]{32}\$/", $val)) { throw new Exception("'{$val}' is not a valid MD5 hash", Z_ERROR_INVALID_INPUT); } } } break; case 'accessDate': if ($apiVersion >= 3 && $val !== '' && $val != 'CURRENT_TIMESTAMP' && !Zotero_Date::isSQLDate($val) && !Zotero_Date::isSQLDateTime($val) && !Zotero_Date::isISO8601($val)) { throw new Exception("'{$key}' must be in ISO 8601 or UTC 'YYYY-MM-DD[ hh-mm-dd]' format or 'CURRENT_TIMESTAMP' ({$val})", Z_ERROR_INVALID_INPUT); } break; case 'dateAdded': if (!Zotero_Date::isSQLDateTime($val) && !Zotero_Date::isISO8601($val)) { throw new Exception("'{$key}' must be in ISO 8601 or UTC 'YYYY-MM-DD hh-mm-dd' format", Z_ERROR_INVALID_INPUT); } if (!$isNew) { // Convert ISO date to SQL date for equality comparison if (Zotero_Date::isISO8601($val)) { $val = Zotero_Date::iso8601ToSQL($val); } if ($val != $item->{$key}) { throw new Exception("'{$key}' cannot be modified for existing items", Z_ERROR_INVALID_INPUT); } } break; case 'dateModified': if (!Zotero_Date::isSQLDateTime($val) && !Zotero_Date::isISO8601($val)) { throw new Exception("'{$key}' must be in ISO 8601 or UTC 'YYYY-MM-DD hh-mm-dd' format ({$val})", Z_ERROR_INVALID_INPUT); } break; default: if (!Zotero_ItemFields::getID($key)) { throw new Exception("Invalid property '{$key}'", Z_ERROR_INVALID_INPUT); } if (is_array($val)) { throw new Exception("Unexpected array for property '{$key}'", Z_ERROR_INVALID_INPUT); } break; } } // Publications libraries have additional restrictions if (Zotero_Libraries::getType($libraryID) == 'publications') { Zotero_Publications::validateJSONItem($json); } }
public function testNumAttachments() { $item = new Zotero_Item(); $item->libraryID = self::$config['userLibraryID']; $item->itemTypeID = Zotero_ItemTypes::getID("book"); $item->save(); $this->assertEquals(0, $item->numAttachments()); $attachmentItem = new Zotero_Item(); $attachmentItem->libraryID = self::$config['userLibraryID']; $attachmentItem->itemTypeID = Zotero_ItemTypes::getID("attachment"); $attachmentItem->setSource($item->id); $attachmentItem->save(); $this->assertEquals(1, $item->numAttachments()); }
public function toJSON($asArray=false, $requestParams=array(), $includeEmpty=false, $unformattedFields=false) { if ($this->id || $this->key) { if (!$this->loaded['primaryData']) { $this->loadPrimaryData(); } if (!$this->loaded['itemData']) { $this->loadItemData(); } } if (!isset($requestParams['v'])) { $requestParams['v'] = 3; } $regularItem = $this->isRegularItem(); $arr = array(); if ($requestParams['v'] >= 2) { if ($requestParams['v'] >= 3) { $arr['key'] = $this->key; $arr['version'] = $this->version; } else { $arr['itemKey'] = $this->key; $arr['itemVersion'] = $this->version; } $key = $this->getSourceKey(); if ($key) { $arr['parentItem'] = $key; } } $arr['itemType'] = Zotero_ItemTypes::getName($this->itemTypeID); if ($this->isAttachment()) { $val = $this->attachmentLinkMode; $arr['linkMode'] = strtolower(Zotero_Attachments::linkModeNumberToName($val)); } // For regular items, show title and creators first if ($regularItem) { // Get 'title' or the equivalent base-mapped field $titleFieldID = Zotero_ItemFields::getFieldIDFromTypeAndBase($this->itemTypeID, 'title'); $titleFieldName = Zotero_ItemFields::getName($titleFieldID); if ($includeEmpty || $this->itemData[$titleFieldID] !== false) { $arr[$titleFieldName] = $this->itemData[$titleFieldID] !== false ? $this->itemData[$titleFieldID] : ""; } // Creators $arr['creators'] = array(); $creators = $this->getCreators(); foreach ($creators as $creator) { $c = array(); $c['creatorType'] = Zotero_CreatorTypes::getName($creator['creatorTypeID']); // Single-field mode if ($creator['ref']->fieldMode == 1) { $c['name'] = $creator['ref']->lastName; } // Two-field mode else { $c['firstName'] = $creator['ref']->firstName; $c['lastName'] = $creator['ref']->lastName; } $arr['creators'][] = $c; } if (!$arr['creators'] && !$includeEmpty) { unset($arr['creators']); } } else { $titleFieldID = false; } // Item metadata $fields = array_keys($this->itemData); foreach ($fields as $field) { if ($field == $titleFieldID) { continue; } if ($unformattedFields) { $value = $this->itemData[$field]; } else { $value = $this->getField($field); } if (!$includeEmpty && ($value === false || $value === "")) { continue; } $fieldName = Zotero_ItemFields::getName($field); // TEMP if ($fieldName == 'versionNumber') { if ($requestParams['v'] < 3) { $fieldName = 'version'; } } else if ($fieldName == 'accessDate') { if ($requestParams['v'] >= 3 && $value !== false && $value !== "") { $value = Zotero_Date::sqlToISO8601($value); } } $arr[$fieldName] = ($value !== false && $value !== "") ? $value : ""; } if ($requestParams['v'] >= 3) { $arr['dateAdded'] = Zotero_Date::sqlToISO8601($this->dateAdded); $arr['dateModified'] = Zotero_Date::sqlToISO8601($this->dateModified); } // Embedded note for notes and attachments if (!$regularItem) { // Use sanitized version $arr['note'] = $this->getNote(true); } if ($this->isAttachment()) { $val = $this->attachmentLinkMode; $arr['linkMode'] = strtolower(Zotero_Attachments::linkModeNumberToName($val)); $val = $this->attachmentMIMEType; if ($includeEmpty || ($val !== false && $val !== "")) { $arr['contentType'] = $val; } $val = $this->attachmentCharset; if ($includeEmpty || ($val !== false && $val !== "")) { $arr['charset'] = $val; } if ($this->isImportedAttachment()) { $arr['filename'] = $this->attachmentFilename; $val = $this->attachmentStorageHash; if ($includeEmpty || $val) { $arr['md5'] = $val; } $val = $this->attachmentStorageModTime; if ($includeEmpty || $val) { $arr['mtime'] = $val; } } } if ($this->getDeleted()) { $arr['deleted'] = 1; } // Tags $arr['tags'] = array(); $tags = $this->getTags(); if ($tags) { foreach ($tags as $tag) { $t = array( 'tag' => $tag->name ); if ($tag->type != 0) { $t['type'] = $tag->type; } $arr['tags'][] = $t; } } if ($requestParams['v'] >= 2) { if ($this->isTopLevelItem()) { $collections = $this->getCollections(true); $arr['collections'] = $collections; } $arr['relations'] = $this->getRelations(); } if ($asArray) { return $arr; } // Before v3, additional characters were escaped in the JSON, for unclear reasons $escapeAll = $requestParams['v'] <= 2; return Zotero_Utilities::formatJSON($arr, $escapeAll); }
public function toSolrDocument() { $doc = new SolrInputDocument(); $uri = Zotero_Solr::getItemURI($this->libraryID, $this->key); $doc->addField("uri", $uri); // Primary fields foreach (Zotero_Items::$primaryFields as $field) { switch ($field) { case 'itemID': case 'numAttachments': case 'numNotes': continue 2; case 'itemTypeID': $xmlField = 'itemType'; $xmlValue = Zotero_ItemTypes::getName($this->{$field}); break; case 'dateAdded': case 'dateModified': case 'serverDateModified': $xmlField = $field; $xmlValue = Zotero_Date::sqlToISO8601($this->{$field}); break; default: $xmlField = $field; $xmlValue = $this->{$field}; } $doc->addField($xmlField, $xmlValue); } // Title for sorting $title = $this->getDisplayTitle(true); $title = $title ? $title : ''; // Strip HTML from note titles if ($this->isNote()) { // Clean and strip HTML, giving us an HTML-encoded plaintext string $title = strip_tags($GLOBALS['HTMLPurifier']->purify($title)); // Unencode plaintext string $title = html_entity_decode($title); } // Strip some characters $sortTitle = preg_replace("/^[\\[\\'\"]*(.*)[\\]\\'\"]*\$/", "\$1", $title); if ($sortTitle) { $doc->addField('titleSort', $sortTitle); } // Item data $fieldIDs = $this->getUsedFields(); foreach ($fieldIDs as $fieldID) { $val = $this->getField($fieldID); if ($val == '') { continue; } $fieldName = Zotero_ItemFields::getName($fieldID); switch ($fieldName) { // As is case 'title': $val = $title; break; // Date fields // Date fields case 'date': // Add user part as text $doc->addField($fieldName . "_t", Zotero_Date::multipartToStr($val)); // Add as proper date, if there is one $sqlDate = Zotero_Date::multipartToSQL($val); if (!$sqlDate || $sqlDate == '0000-00-00') { continue 2; } $fieldName .= "_tdt"; $val = Zotero_Date::sqlToISO8601($sqlDate); break; case 'accessDate': if (!Zotero_Date::isSQLDateTime($val)) { continue 2; } $fieldName .= "_tdt"; $val = Zotero_Date::sqlToISO8601($val); break; default: $fieldName .= "_t"; } $doc->addField($fieldName, $val); } // Deleted item flag if ($this->getDeleted()) { $doc->addField('deleted', true); } if ($this->isNote() || $this->isAttachment()) { $sourceItemID = $this->getSource(); if ($sourceItemID) { $sourceItem = Zotero_Items::get($this->libraryID, $sourceItemID); if (!$sourceItem) { throw new Exception("Source item {$sourceItemID} not found"); } $doc->addField('sourceItem', $sourceItem->key); } } // Group modification info $createdByUserID = null; $lastModifiedByUserID = null; switch (Zotero_Libraries::getType($this->libraryID)) { case 'group': $createdByUserID = $this->createdByUserID; $lastModifiedByUserID = $this->lastModifiedByUserID; break; } if ($createdByUserID) { $doc->addField('createdByUserID', $createdByUserID); } if ($lastModifiedByUserID) { $doc->addField('lastModifiedByUserID', $lastModifiedByUserID); } // Note if ($this->isNote()) { $doc->addField('note', $this->getNote()); } if ($this->isAttachment()) { $doc->addField('linkMode', $this->attachmentLinkMode); $doc->addField('mimeType', $this->attachmentMIMEType); if ($this->attachmentCharset) { $doc->addField('charset', $this->attachmentCharset); } // TODO: get from a constant if ($this->attachmentLinkMode != 3) { $doc->addField('path', $this->attachmentPath); } $note = $this->getNote(); if ($note) { $doc->addField('note', $note); } } // Creators $creators = $this->getCreators(); if ($creators) { foreach ($creators as $index => $creator) { $c = $creator['ref']; $doc->addField('creatorKey', $c->key); if ($c->fieldMode == 0) { $doc->addField('creatorFirstName', $c->firstName); } $doc->addField('creatorLastName', $c->lastName); $doc->addField('creatorType', Zotero_CreatorTypes::getName($creator['creatorTypeID'])); $doc->addField('creatorIndex', $index); } } // Tags $tags = $this->getTags(); if ($tags) { foreach ($tags as $tag) { $doc->addField('tagKey', $tag->key); $doc->addField('tag', $tag->name); $doc->addField('tagType', $tag->type); } } // Related items /*$related = $this->relatedItems; if ($related) { $related = Zotero_Items::get($this->libraryID, $related); $keys = array(); foreach ($related as $item) { $doc->addField('relatedItem', $item->key); } }*/ return $doc; }
public function newItem() { if (empty($_GET['itemType'])) { $this->e400("'itemType' not provided"); } $itemType = $_GET['itemType']; if ($itemType == 'attachment') { if (empty($_GET['linkMode'])) { $this->e400("linkMode required for itemType=attachment"); } $linkModeName = $_GET['linkMode']; try { $linkMode = Zotero_Attachments::linkModeNameToNumber(strtoupper($linkModeName)); } catch (Exception $e) { $this->e400("Invalid linkMode '{$linkModeName}'"); } } $itemTypeID = Zotero_ItemTypes::getID($itemType); if (!$itemTypeID) { $this->e400("Invalid item type '{$itemType}'"); } // TODO: check If-Modified-Since and return 304 if not changed $cacheKey = "newItemJSON_" . $itemTypeID; if ($itemType == 'attachment') { $cacheKey .= "_" . $linkMode; } $ttl = 60; if ($this->queryParams['pprint']) { $cacheKey .= "_pprint"; } $json = Z_Core::$MC->get($cacheKey); if ($json) { header("Content-Type: application/json"); echo $json; exit; } // Generate template $json = array('itemType' => $itemType); if ($itemType == 'attachment') { $json['linkMode'] = $linkModeName; } $fieldIDs = Zotero_ItemFields::getItemTypeFields($itemTypeID); $first = true; foreach ($fieldIDs as $fieldID) { $fieldName = Zotero_ItemFields::getName($fieldID); if ($itemType == 'attachment' && $fieldName == 'url' && !preg_match('/_url$/', $linkModeName)) { continue; } $json[$fieldName] = ""; if ($first && $itemType != 'note' && $itemType != 'attachment') { $creatorTypeID = Zotero_CreatorTypes::getPrimaryIDForType($itemTypeID); $creatorTypeName = Zotero_CreatorTypes::getName($creatorTypeID); $json['creators'] = array(array('creatorType' => $creatorTypeName, 'firstName' => '', 'lastName' => '')); $first = false; } } if ($itemType == 'note' || $itemType == 'attachment') { $json['note'] = ''; } $json['tags'] = array(); if ($itemType != 'note' && $itemType != 'attachment') { $json['attachments'] = array(); $json['notes'] = array(); } if ($itemType == 'attachment') { $json['contentType'] = ''; $json['charset'] = ''; if (preg_match('/^imported_/', $linkModeName)) { $json['filename'] = ''; $json['md5'] = null; $json['mtime'] = null; //$json['zip'] = false; } } header("Content-Type: application/json"); if ($this->queryParams['pprint']) { $json = Zotero_Utilities::json_encode_pretty($json); Z_Core::$MC->set($cacheKey, $json, $ttl); } else { $json = json_encode($json); Z_Core::$MC->set($cacheKey, $json, $ttl); } echo $json; exit; }
private static function validateJSONItem($json, $libraryID, $item = null, $isChild = false) { $isNew = !$item; if (!is_object($json)) { throw new Exception("Invalid item object (found " . gettype($json) . " '" . $json . "')", Z_ERROR_INVALID_INPUT); } if (isset($json->items) && is_array($json->items)) { throw new Exception("An 'items' array is not valid for item updates", Z_ERROR_INVALID_INPUT); } if (isset($json->itemType) && $json->itemType == "attachment") { $requiredProps = array('linkMode', 'tags'); } else { if (isset($json->itemType) && $json->itemType == "attachment") { $requiredProps = array('tags'); } else { if ($isNew) { $requiredProps = array('itemType'); } else { $requiredProps = array('itemType', 'creators', 'tags'); } } } foreach ($requiredProps as $prop) { if (!isset($json->{$prop})) { throw new Exception("'{$prop}' property not provided", Z_ERROR_INVALID_INPUT); } } foreach ($json as $key => $val) { switch ($key) { case 'itemType': if (!is_string($val)) { throw new Exception("'itemType' must be a string", Z_ERROR_INVALID_INPUT); } if ($isChild) { switch ($val) { case 'note': case 'attachment': break; default: throw new Exception("Child item must be note or attachment", Z_ERROR_INVALID_INPUT); } } else { if ($val == 'attachment' && (!$item || !$item->getSource())) { if ($json->linkMode == 'linked_url' || $json->linkMode == 'imported_url' && (empty($json->contentType) || $json->contentType != 'application/pdf')) { throw new Exception("Only file attachments and PDFs can be top-level items", Z_ERROR_INVALID_INPUT); } } } if (!Zotero_ItemTypes::getID($val)) { throw new Exception("'{$val}' is not a valid itemType", Z_ERROR_INVALID_INPUT); } break; case 'tags': if (!is_array($val)) { throw new Exception("'tags' property must be an array", Z_ERROR_INVALID_INPUT); } foreach ($val as $tag) { $empty = true; foreach ($tag as $k => $v) { switch ($k) { case 'tag': if (!is_scalar($v)) { throw new Exception("Invalid tag name", Z_ERROR_INVALID_INPUT); } if ($v === "") { throw new Exception("Tag cannot be empty", Z_ERROR_INVALID_INPUT); } break; case 'type': if (!is_numeric($v)) { throw new Exception("Invalid tag type '{$v}'", Z_ERROR_INVALID_INPUT); } break; default: throw new Exception("Invalid tag property '{$k}'", Z_ERROR_INVALID_INPUT); } $empty = false; } if ($empty) { throw new Exception("Tag object is empty", Z_ERROR_INVALID_INPUT); } } break; case 'creators': if (!is_array($val)) { throw new Exception("'creators' property must be an array", Z_ERROR_INVALID_INPUT); } foreach ($val as $creator) { $empty = true; if (!isset($creator->creatorType)) { throw new Exception("creator object must contain 'creatorType'", Z_ERROR_INVALID_INPUT); } if ((!isset($creator->name) || trim($creator->name) == "") && (!isset($creator->firstName) || trim($creator->firstName) == "") && (!isset($creator->lastName) || trim($creator->lastName) == "")) { // On item creation, ignore single nameless creator, // because that's in the item template that the API returns if (sizeOf($val) == 1 && $isNew) { continue; } else { throw new Exception("creator object must contain 'firstName'/'lastName' or 'name'", Z_ERROR_INVALID_INPUT); } } foreach ($creator as $k => $v) { switch ($k) { case 'creatorType': $creatorTypeID = Zotero_CreatorTypes::getID($v); if (!$creatorTypeID) { throw new Exception("'{$v}' is not a valid creator type", Z_ERROR_INVALID_INPUT); } $itemTypeID = Zotero_ItemTypes::getID($json->itemType); if (!Zotero_CreatorTypes::isValidForItemType($creatorTypeID, $itemTypeID)) { throw new Exception("'{$v}' is not a valid creator type for item type '" . $json->itemType . "'", Z_ERROR_INVALID_INPUT); } break; case 'firstName': if (!isset($creator->lastName)) { throw new Exception("'lastName' creator field must be set if 'firstName' is set", Z_ERROR_INVALID_INPUT); } if (isset($creator->name)) { throw new Exception("'firstName' and 'name' creator fields are mutually exclusive", Z_ERROR_INVALID_INPUT); } break; case 'lastName': if (!isset($creator->firstName)) { throw new Exception("'firstName' creator field must be set if 'lastName' is set", Z_ERROR_INVALID_INPUT); } if (isset($creator->name)) { throw new Exception("'lastName' and 'name' creator fields are mutually exclusive", Z_ERROR_INVALID_INPUT); } break; case 'name': if (isset($creator->firstName)) { throw new Exception("'firstName' and 'name' creator fields are mutually exclusive", Z_ERROR_INVALID_INPUT); } if (isset($creator->lastName)) { throw new Exception("'lastName' and 'name' creator fields are mutually exclusive", Z_ERROR_INVALID_INPUT); } break; default: throw new Exception("Invalid creator property '{$k}'", Z_ERROR_INVALID_INPUT); } $empty = false; } if ($empty) { throw new Exception("Creator object is empty", Z_ERROR_INVALID_INPUT); } } break; case 'note': switch ($json->itemType) { case 'note': case 'attachment': break; default: throw new Exception("'note' property is valid only for note and attachment items", Z_ERROR_INVALID_INPUT); } break; case 'attachments': case 'notes': if (!$isNew) { throw new Exception("'{$key}' property is valid only for new items", Z_ERROR_INVALID_INPUT); } if (!is_array($val)) { throw new Exception("'{$key}' property must be an array", Z_ERROR_INVALID_INPUT); } foreach ($val as $child) { // Check child item type ('attachment' or 'note') $t = substr($key, 0, -1); if (isset($child->itemType) && $child->itemType != $t) { throw new Exception("Child {$t} must be of itemType '{$t}'", Z_ERROR_INVALID_INPUT); } if ($key == 'note') { if (!isset($child->note)) { throw new Exception("'note' property not provided for child note", Z_ERROR_INVALID_INPUT); } } } break; case 'deleted': break; // Attachment properties // Attachment properties case 'linkMode': try { $linkMode = Zotero_Attachments::linkModeNameToNumber($val, true); } catch (Exception $e) { throw new Exception("'{$val}' is not a valid linkMode", Z_ERROR_INVALID_INPUT); } // Don't allow changing of linkMode if ($item && $linkMode != $item->attachmentLinkMode) { throw new Exception("Cannot change attachment linkMode", Z_ERROR_INVALID_INPUT); } break; case 'contentType': case 'charset': case 'filename': case 'md5': case 'mtime': if ($json->itemType != 'attachment') { throw new Exception("'{$key}' is valid only for attachment items", Z_ERROR_INVALID_INPUT); } switch ($key) { case 'filename': case 'md5': case 'mtime': if (strpos($json->linkMode, 'imported_') !== 0) { throw new Exception("'{$key}' is valid only for imported attachment items", Z_ERROR_INVALID_INPUT); } break; } switch ($key) { case 'contentType': case 'charset': case 'filename': $propName = 'attachment' . ucwords($key); break; case 'md5': $propName = 'attachmentStorageHash'; break; case 'mtime': $propName = 'attachmentStorageModTime'; break; } if (Zotero_Libraries::getType($libraryID) == 'group') { if ($item && $item->{$propName} !== $val || !$item && $val !== null && $val !== "") { throw new Exception("Cannot change '{$key}' directly in group library", Z_ERROR_INVALID_INPUT); } } else { if ($key == 'md5') { if ($val && !preg_match("/^[a-f0-9]{32}\$/", $val)) { throw new Exception("'{$val}' is not a valid MD5 hash", Z_ERROR_INVALID_INPUT); } } } break; default: if (!Zotero_ItemFields::getID($key)) { throw new Exception("Invalid property '{$key}'", Z_ERROR_INVALID_INPUT); } if (is_array($val)) { throw new Exception("Unexpected array for property '{$key}'", Z_ERROR_INVALID_INPUT); } break; } } }
public static function getBaseIDFromTypeAndField($itemType, $typeField) { $itemTypeID = Zotero_ItemTypes::getID($itemType); if (!$itemTypeID) { throw new Exception("Invalid item type '{$itemType}'"); } $typeFieldID = self::getID($typeField); if (!$typeFieldID) { throw new Exception("Invalid field '{$typeField}'"); } if (!self::isValidForType($typeFieldID, $itemTypeID)) { throw new Exception("'{$typeField}' is not a valid field for item type '{$itemType}'"); } // If typeField is already a base field, just return that if (self::isBaseField($typeFieldID)) { return $typeFieldID; } return Zotero_DB::valueQuery("SELECT baseFieldID FROM baseFieldMappings\n\t\t\tWHERE itemTypeID=? AND fieldID=?", array($itemTypeID, $typeFieldID)); }
public static function retrieveItem($zoteroItem) { if (!$zoteroItem) { throw new Exception("Zotero item not provided"); } // don't return URL or accessed information for journal articles if a // pages field exists $itemType = Zotero_ItemTypes::getName($zoteroItem->itemTypeID); $cslType = isset(self::$zoteroTypeMap[$itemType]) ? self::$zoteroTypeMap[$itemType] : false; if (!$cslType) { $cslType = "article"; } $ignoreURL = ($zoteroItem->getField("accessDate", true, true, true) || $zoteroItem->getField("url", true, true, true)) && in_array($itemType, array("journalArticle", "newspaperArticle", "magazineArticle")) && $zoteroItem->getField("pages", false, false, true) && self::$citePaperJournalArticleURL; $cslItem = array('id' => $zoteroItem->libraryID . "/" . $zoteroItem->key, 'type' => $cslType); // get all text variables (there must be a better way) // TODO: does citeproc-js permit short forms? foreach (self::$zoteroFieldMap as $variable => $fields) { if ($variable == "URL" && $ignoreURL) { continue; } foreach ($fields as $field) { $value = $zoteroItem->getField($field, false, true, true); if ($value !== "") { // Strip enclosing quotes if (preg_match(self::$quotedRegexp, $value)) { $value = substr($value, 1, strlen($value) - 2); } $cslItem[$variable] = $value; break; } } } // separate name variables $authorID = Zotero_CreatorTypes::getPrimaryIDForType($zoteroItem->itemTypeID); $creators = $zoteroItem->getCreators(); foreach ($creators as $creator) { if ($creator['creatorTypeID'] == $authorID) { $creatorType = "author"; } else { $creatorType = Zotero_CreatorTypes::getName($creator['creatorTypeID']); } $creatorType = isset(self::$zoteroNameMap[$creatorType]) ? self::$zoteroNameMap[$creatorType] : false; if (!$creatorType) { continue; } $nameObj = array('family' => $creator['ref']->lastName, 'given' => $creator['ref']->firstName); if (isset($cslItem[$creatorType])) { $cslItem[$creatorType][] = $nameObj; } else { $cslItem[$creatorType] = array($nameObj); } } // get date variables foreach (self::$zoteroDateMap as $key => $val) { $date = $zoteroItem->getField($val, false, true, true); if ($date) { $cslItem[$key] = array("raw" => $date); continue; $date = Zotero_Date::strToDate($date); if (!empty($date['part']) && !$date['month']) { // if there's a part but no month, interpret literally $cslItem[$variable] = array("literal" => $date['part']); } else { // otherwise, use date-parts $dateParts = array(); if ($date['year']) { $dateParts[] = $date['year']; if ($date['month']) { $dateParts[] = $date['month'] + 1; // Mimics JS if ($date['day']) { $dateParts[] = $date['day']; } } } $cslItem[$key] = array("date-parts" => array($dateParts)); } } } return $cslItem; }
public function newItem() { if (empty($_GET['itemType'])) { $this->e400("'itemType' not provided"); } $itemType = $_GET['itemType']; if ($itemType == 'attachment') { if (empty($_GET['linkMode'])) { $this->e400("linkMode required for itemType=attachment"); } $linkModeName = $_GET['linkMode']; try { $linkMode = Zotero_Attachments::linkModeNameToNumber(strtoupper($linkModeName)); } catch (Exception $e) { $this->e400("Invalid linkMode '{$linkModeName}'"); } } $itemTypeID = Zotero_ItemTypes::getID($itemType); if (!$itemTypeID) { $this->e400("Invalid item type '{$itemType}'"); } // TODO: check If-Modified-Since and return 304 if not changed $cacheVersion = 1; $cacheKey = "newItemJSON" . "_" . $this->apiVersion . "_" . $itemTypeID . "_" . $cacheVersion; if ($itemType == 'attachment') { $cacheKey .= "_" . $linkMode; } $cacheKey .= '_' . $this->apiVersion; $ttl = 60; $json = Z_Core::$MC->get($cacheKey); if ($json) { header("Content-Type: application/json"); echo $json; exit; } // Generate template $json = array('itemType' => $itemType); if ($itemType == 'attachment') { $json['linkMode'] = $linkModeName; } $fieldIDs = Zotero_ItemFields::getItemTypeFields($itemTypeID); $first = true; foreach ($fieldIDs as $fieldID) { $fieldName = Zotero_ItemFields::getName($fieldID); // Before v3, computerProgram's 'versionNumber' was just 'version' if ($this->apiVersion < 3 && $fieldID == 81) { $fieldName = 'version'; } if ($itemType == 'attachment' && $fieldName == 'url' && !preg_match('/_url$/', $linkModeName)) { continue; } $json[$fieldName] = ""; if ($first && $itemType != 'note' && $itemType != 'attachment') { $creatorTypeID = Zotero_CreatorTypes::getPrimaryIDForType($itemTypeID); $creatorTypeName = Zotero_CreatorTypes::getName($creatorTypeID); $json['creators'] = array(array('creatorType' => $creatorTypeName, 'firstName' => '', 'lastName' => '')); $first = false; } } if ($itemType == 'note' || $itemType == 'attachment') { $json['note'] = ''; } $json['tags'] = array(); if ($this->apiVersion >= 2) { $json['collections'] = array(); $json['relations'] = new stdClass(); } if ($this->apiVersion == 1) { if ($itemType != 'note' && $itemType != 'attachment') { $json['attachments'] = array(); $json['notes'] = array(); } } if ($itemType == 'attachment') { $json['contentType'] = ''; $json['charset'] = ''; if (preg_match('/^imported_/', $linkModeName)) { $json['filename'] = ''; $json['md5'] = null; $json['mtime'] = null; //$json['zip'] = false; } } header("Content-Type: application/json"); $json = Zotero_Utilities::formatJSON($json); Z_Core::$MC->set($cacheKey, $json, $ttl); echo $json; exit; }