PageRenderTime 59ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/phpexamples/zreports/libZoteroSingle.php

https://github.com/kmlawson/libZotero
PHP | 3185 lines | 2073 code | 314 blank | 798 comment | 207 complexity | 80719ac1c1c73947b69292ba0761b398 MD5 | raw file
  1. <?php
  2. class Zotero_Exception extends Exception
  3. {
  4. }
  5. class Zotero_Mappings
  6. {
  7. public $itemTypes = array();
  8. public $itemFields = array();
  9. public $itemTypeCreatorTypes = array();
  10. public $creatorFields = array();
  11. }
  12. /**
  13. * Representation of a Zotero Feed
  14. *
  15. * @copyright Copyright (c) 2008 Center for History and New Media (http://chnm.gmu.edu)
  16. * @license http://www.opensource.org/licenses/ecl1.php ECL License
  17. * @since Class available since Release 0.0
  18. */
  19. class Zotero_Feed
  20. {
  21. /**
  22. * @var string
  23. */
  24. public $lastModified;
  25. /**
  26. * @var string
  27. */
  28. public $title;
  29. /**
  30. * @var string
  31. */
  32. public $dateUpdated;
  33. /**
  34. * @var int
  35. */
  36. public $totalResults;
  37. /**
  38. * @var int
  39. */
  40. public $apiVersion;
  41. /**
  42. * @var string
  43. */
  44. public $id;
  45. /**
  46. * @var array
  47. */
  48. public $links = array();
  49. /**
  50. * @var array
  51. */
  52. public $entries = array();
  53. public $entryNodes;
  54. public function __construct($doc)
  55. {
  56. if(!($doc instanceof DOMDocument)){
  57. $domdoc = new DOMDocument();
  58. $domdoc->loadXml($doc);
  59. $doc = $domdoc;
  60. }
  61. foreach($doc->getElementsByTagName("feed") as $feed){
  62. $this->title = $feed->getElementsByTagName("title")->item(0)->nodeValue;
  63. $this->id = $feed->getElementsByTagName("id")->item(0)->nodeValue;
  64. $this->dateUpdated = $feed->getElementsByTagName("updated")->item(0)->nodeValue;
  65. $this->apiVersion = $feed->getElementsByTagName("apiVersion")->item(0)->nodeValue;
  66. $this->totalResults = $feed->getElementsByTagName("totalResults")->item(0)->nodeValue;
  67. // Get all of the link elements
  68. foreach($feed->childNodes as $childNode){
  69. if($childNode->nodeName == "link"){
  70. $linkNode = $childNode;
  71. $this->links[$linkNode->getAttribute('rel')] = array('type'=>$linkNode->getAttribute('type'), 'href'=>$linkNode->getAttribute('href'));
  72. }
  73. }
  74. $entryNodes = $doc->getElementsByTagName("entry");
  75. $this->entryNodes = $entryNodes;
  76. /*
  77. //detect zotero entry type with sample entry node and parse entries appropriately
  78. $firstEntry = $entryNodes->item(0);
  79. $this->entryType = $this->detectZoteroEntryType($firstEntry);
  80. foreach($entryNodes as $entryNode){
  81. switch($this->entryType) {
  82. case 'item': $entry = new Zotero_Item($entryNode); break;
  83. case 'collection': $entry = new Zotero_Collection($entryNode); break;
  84. case 'group': $entry = new Zotero_Group($entryNode); break;
  85. case 'user': $entry = new Zotero_User($entryNode); break;
  86. case 'tag': $entry = new Zotero_Tag($entryNode); break;
  87. default: throw new Zend_Exception("Unknown entry type");
  88. }
  89. $this->entries[] = $entry;
  90. }
  91. */
  92. }
  93. }
  94. public function detectZoteroEntryType($entryNode){
  95. $itemTypeNodes = $entryNode->getElementsByTagName("itemType");
  96. $numCollectionsNodes = $entryNode->getElementsByTagName("numCollections");
  97. $numItemsNodes = $entryNode->getElementsByTagName("numItems");
  98. /*
  99. $itemType = $xpath->evaluate("//zapi:itemType")->item(0)->nodeValue;
  100. $collectionKey = $xpath->evaluate("//zapi:collectionKey")->item(0)->nodeValue;
  101. $numItems = $xpath->evaluate("//zapi:numItems")->item(0)->nodeValue;
  102. */
  103. if($itemTypeNodes->length) return 'item';
  104. if($numCollectionsNodes->length) return 'collection';
  105. if($numItemsNodes->length && !($collectionKeyNodes->length)) return 'tag';
  106. //if($userID) return 'user';
  107. //if($groupID) return 'group';
  108. }
  109. public function nestEntries(){
  110. // Look for item and collection entries with rel="up" links and move them under their parent entry
  111. if($nest && ($entryType == "collections" || $entryType == "items")){
  112. foreach($this->feed->entries as $key => $entry){
  113. if(isset($entry->links['up']['application/atom+xml'])){
  114. // This flag will be set to true if a parent is found
  115. $this->foundParent = false;
  116. // Search for a parent
  117. $this->nestEntry($entry, $this->feed->entries);
  118. // If we found a parent to nest under, remove the entry from the top level
  119. if($this->foundParent == true){
  120. unset($this->feed->entries[$key]);
  121. }
  122. }
  123. }
  124. }
  125. }
  126. public function dataObject()
  127. {
  128. $jsonItem = new stdClass;
  129. $jsonItem->lastModified = $this->lastModified;
  130. $jsonItem->title = $this->title;
  131. $jsonItem->dateUpdated = $this->dateUpdated;
  132. $jsonItem->totalResults = $this->totalResults;
  133. $jsonItem->id = $this->id;
  134. // foreach($this->links as $link){
  135. // $jsonItem->links[] = $link;
  136. // }
  137. $jsonItem->links = $this->links;
  138. $jsonItem->entries = array();
  139. foreach($this->entries as $entry){
  140. $jsonItem->entries[] = $entry->dataObject();
  141. }
  142. return $jsonItem;
  143. }
  144. }
  145. /**
  146. * Representation of a Zotero Item
  147. *
  148. * @copyright Copyright (c) 2008 Center for History and New Media (http://chnm.gmu.edu)
  149. * @license http://www.opensource.org/licenses/ecl1.php ECL License
  150. * @since Class available since Release 0.0
  151. * @see Zend_Service_Abstract
  152. */
  153. class Zotero_Entry
  154. {
  155. /**
  156. * @var string
  157. */
  158. public $title;
  159. /**
  160. * @var string
  161. */
  162. public $dateAdded;
  163. /**
  164. * @var string
  165. */
  166. public $dateUpdated;
  167. /**
  168. * @var string
  169. */
  170. public $id;
  171. /**
  172. * @var array
  173. */
  174. public $links = array();
  175. public $contentArray = array();
  176. /**
  177. * @var array
  178. */
  179. public $entries = array();
  180. public function __construct($entryNode)
  181. {
  182. $parseFields = array('title', 'id', 'dateAdded', 'dateUpdated', 'author');
  183. $this->title = $entryNode->getElementsByTagName("title")->item(0)->nodeValue;
  184. $this->id = $entryNode->getElementsByTagName("id")->item(0)->nodeValue;
  185. $this->dateAdded = $entryNode->getElementsByTagName("published")->item(0)->nodeValue;
  186. $this->dateUpdated = $entryNode->getElementsByTagName("updated")->item(0)->nodeValue;
  187. // Get all of the link elements
  188. foreach($entryNode->getElementsByTagName("link") as $linkNode){
  189. if($linkNode->getAttribute('rel') == "enclosure"){
  190. $this->links['enclosure'][$linkNode->getAttribute('type')] = array(
  191. 'href'=>$linkNode->getAttribute('href'),
  192. 'title'=>$linkNode->getAttribute('title'),
  193. 'length'=>$linkNode->getAttribute('length'));
  194. }
  195. else{
  196. $this->links[$linkNode->getAttribute('rel')][$linkNode->getAttribute('type')] = array(
  197. 'href'=>$linkNode->getAttribute('href')
  198. );
  199. }
  200. }
  201. }
  202. public function getContentType($entryNode){
  203. $contentNode = $entryNode->getElementsByTagName('content')->item(0);
  204. if($contentNode) return $contentNode->getAttribute('type') || $contentNode->getAttribute('zapi:type');
  205. else return false;
  206. }
  207. }
  208. /**
  209. * Representation of a Zotero Collection
  210. *
  211. * @copyright Copyright (c) 2008 Center for History and New Media (http://chnm.gmu.edu)
  212. * @license http://www.opensource.org/licenses/ecl1.php ECL License
  213. * @since Class available since Release 0.0
  214. * @see Zotero_Entry
  215. */
  216. class Zotero_Collection extends Zotero_Entry
  217. {
  218. /**
  219. * @var int
  220. */
  221. public $collectionKey = null;
  222. public $name = '';
  223. /**
  224. * @var int
  225. */
  226. public $numCollections = 0;
  227. /**
  228. * @var int
  229. */
  230. public $numItems = 0;
  231. public $topLevel;
  232. /**
  233. * @var string
  234. */
  235. public $parentCollectionKey = false;
  236. public $childKeys = array();
  237. public function __construct($entryNode)
  238. {
  239. if(!$entryNode){
  240. return;
  241. }
  242. parent::__construct($entryNode);
  243. // Extract the collectionKey
  244. $this->collectionKey = $entryNode->getElementsByTagNameNS('*', 'key')->item(0)->nodeValue;
  245. $this->numCollections = $entryNode->getElementsByTagName('numCollections')->item(0)->nodeValue;
  246. $this->numItems = $entryNode->getElementsByTagName('numItems')->item(0)->nodeValue;
  247. $contentNode = $entryNode->getElementsByTagName('content')->item(0);
  248. $contentType = parent::getContentType($entryNode);
  249. if($contentType == 'application/json'){
  250. $this->contentArray = json_decode($contentNode->nodeValue, true);
  251. $this->etag = $contentNode->getAttribute('etag');
  252. $this->parentCollectionKey = $this->contentArray['parent'];
  253. $this->name = $this->contentArray['name'];
  254. }
  255. elseif($contentType == 'xhtml'){
  256. //$this->parseXhtmlContent($contentNode);
  257. }
  258. }
  259. public function collectionJson(){
  260. return json_encode(array('name'=>$collection->name, 'parent'=>$collection->parentCollectionKey));
  261. }
  262. public function dataObject() {
  263. $jsonItem = new stdClass;
  264. //inherited from Entry
  265. $jsonItem->title = $this->title;
  266. $jsonItem->dateAdded = $this->dateAdded;
  267. $jsonItem->dateUpdated = $this->dateUpdated;
  268. $jsonItem->id = $this->id;
  269. $jsonItem->links = $this->links;
  270. $jsonItem->collectionKey = $this->collectionKey;
  271. $jsonItem->childKeys = $this->childKeys;
  272. $jsonItem->parentCollectionKey = $this->parentCollectionKey;
  273. return $jsonItem;
  274. }
  275. }
  276. class Zotero_Collections
  277. {
  278. public $orderedArray;
  279. public $collectionObjects;
  280. public $dirty;
  281. public $loaded;
  282. public function __construct(){
  283. $this->orderedArray = array();
  284. $this->collectionObjects = array();
  285. }
  286. public static function sortByTitleCompare($a, $b){
  287. if(strtolower($a->title) == strtolower($b->title)){
  288. return 0;
  289. }
  290. if(strtolower($a->title) < strtolower($b->title)){
  291. return -1;
  292. }
  293. return 1;
  294. }
  295. public function addCollection($collection) {
  296. $this->collectionObjects[$collection->collectionKey] = $collection;
  297. $this->orderedArray[] = $collection;
  298. }
  299. public function getCollection($collectionKey) {
  300. if(isset($this->collectionObjects[$collectionKey])){
  301. return $this->collectionObjects[$collectionKey];
  302. }
  303. return false;
  304. }
  305. public function addCollectionsFromFeed($feed) {
  306. $entries = $feed->entryNodes;
  307. $addedCollections = array();
  308. foreach($entries as $entry){
  309. $collection = new Zotero_Collection($entry);
  310. $this->addCollection($collection);
  311. $addedCollections[] = $collection;
  312. }
  313. return $addedCollections;
  314. }
  315. //add keys of child collections to array
  316. public function nestCollections(){
  317. foreach($this->collectionObjects as $key=>$collection){
  318. if($collection->parentCollectionKey){
  319. $parentCollection = $this->getCollection($collection->parentCollectionKey);
  320. $parentCollection->childKeys[] = $collection->collectionKey;
  321. }
  322. }
  323. }
  324. public function orderCollections(){
  325. $orderedArray = array();
  326. foreach($this->collectionObjects as $key=>$collection){
  327. $orderedArray[] = $collection;
  328. }
  329. usort($orderedArray, array('Zotero_Collections', 'sortByTitleCompare'));
  330. $this->orderedArray = $orderedArray;
  331. return $this->orderedArray;
  332. }
  333. public function topCollectionKeys($collections){
  334. $topCollections = array();
  335. foreach($collections as $collection){
  336. if($collection->parentCollectionKey == false){
  337. $topCollections[] = $collection->collectionKey;
  338. }
  339. }
  340. return $topCollections;
  341. }
  342. public function collectionsJson(){
  343. $collections = array();
  344. foreach($this->collectionObjects as $collection){
  345. $collections[] = $collection->dataObject();
  346. }
  347. return json_encode($collections);
  348. }
  349. }
  350. class Zotero_Items
  351. {
  352. public $itemObjects = array();
  353. //get an item from this container of items by itemKey
  354. public function getItem($itemKey) {
  355. if(isset($this->itemObjects[$itemKey])){
  356. return $this->itemObjects[$itemKey];
  357. }
  358. return false;
  359. }
  360. //add a Zotero_Item to this container of items
  361. public function addItem($item) {
  362. $itemKey = $item->itemKey;
  363. $this->itemObjects[$itemKey] = $item;
  364. }
  365. //add items to this container from a Zotero_Feed object
  366. public function addItemsFromFeed($feed) {
  367. $entries = $feed->entryNodes;
  368. $addedItems = array();
  369. foreach($entries as $entry){
  370. $item = new Zotero_Item($entry);
  371. $this->addItem($item);
  372. $addedItems[] = $item;
  373. }
  374. return $addedItems;
  375. }
  376. //replace an item in this container with a new Zotero_Item object with the same itemKey
  377. //useful for example after updating an item when the etag is out of date and to make sure
  378. //the current item we have reflects the best knowledge of the api
  379. public function replaceItem($item) {
  380. $this->addItem($item);
  381. }
  382. public function addChildKeys() {
  383. //empty existing childkeys first
  384. foreach($this->itemObjects as $key=>$item){
  385. $item->childKeys = array();
  386. }
  387. //run through and add item keys to their parent's item if we have the parent
  388. foreach($this->itemObjects as $key=>$item){
  389. if($item->parentKey){
  390. $pitem = $this->getItem($item->parentKey);
  391. if($pitem){
  392. $pitem->childKeys[] = $item->itemKey;
  393. }
  394. }
  395. }
  396. }
  397. public function getPreloadedChildren($item){
  398. $children = array();
  399. foreach($item->childKeys as $childKey){
  400. $childItem = $this->getItem($childKey);
  401. if($childItem){
  402. $children[] = $childItem;
  403. }
  404. }
  405. return $children;
  406. }
  407. }
  408. /**
  409. * Zend Framework
  410. *
  411. * LICENSE
  412. *
  413. * This source file is subject to the new BSD license that is bundled
  414. * with this package in the file LICENSE.txt.
  415. * It is also available through the world-wide-web at this URL:
  416. * http://framework.zend.com/license/new-bsd
  417. * If you did not receive a copy of the license and are unable to
  418. * obtain it through the world-wide-web, please send an email
  419. * to license@zend.com so we can send you a copy immediately.
  420. *
  421. * @category Zend
  422. * @package Zend_Http
  423. * @subpackage Response
  424. * @version $Id: Response.php 23484 2010-12-10 03:57:59Z mjh_ca $
  425. * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
  426. * @license http://framework.zend.com/license/new-bsd New BSD License
  427. */
  428. /**
  429. * Zend_Http_Response represents an HTTP 1.0 / 1.1 response message. It
  430. * includes easy access to all the response's different elemts, as well as some
  431. * convenience methods for parsing and validating HTTP responses.
  432. *
  433. * @package Zend_Http
  434. * @subpackage Response
  435. * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
  436. * @license http://framework.zend.com/license/new-bsd New BSD License
  437. */
  438. //class Zend_Http_Response
  439. class libZotero_Http_Response
  440. {
  441. /**
  442. * List of all known HTTP response codes - used by responseCodeAsText() to
  443. * translate numeric codes to messages.
  444. *
  445. * @var array
  446. */
  447. protected static $messages = array(
  448. // Informational 1xx
  449. 100 => 'Continue',
  450. 101 => 'Switching Protocols',
  451. // Success 2xx
  452. 200 => 'OK',
  453. 201 => 'Created',
  454. 202 => 'Accepted',
  455. 203 => 'Non-Authoritative Information',
  456. 204 => 'No Content',
  457. 205 => 'Reset Content',
  458. 206 => 'Partial Content',
  459. // Redirection 3xx
  460. 300 => 'Multiple Choices',
  461. 301 => 'Moved Permanently',
  462. 302 => 'Found', // 1.1
  463. 303 => 'See Other',
  464. 304 => 'Not Modified',
  465. 305 => 'Use Proxy',
  466. // 306 is deprecated but reserved
  467. 307 => 'Temporary Redirect',
  468. // Client Error 4xx
  469. 400 => 'Bad Request',
  470. 401 => 'Unauthorized',
  471. 402 => 'Payment Required',
  472. 403 => 'Forbidden',
  473. 404 => 'Not Found',
  474. 405 => 'Method Not Allowed',
  475. 406 => 'Not Acceptable',
  476. 407 => 'Proxy Authentication Required',
  477. 408 => 'Request Timeout',
  478. 409 => 'Conflict',
  479. 410 => 'Gone',
  480. 411 => 'Length Required',
  481. 412 => 'Precondition Failed',
  482. 413 => 'Request Entity Too Large',
  483. 414 => 'Request-URI Too Long',
  484. 415 => 'Unsupported Media Type',
  485. 416 => 'Requested Range Not Satisfiable',
  486. 417 => 'Expectation Failed',
  487. // Server Error 5xx
  488. 500 => 'Internal Server Error',
  489. 501 => 'Not Implemented',
  490. 502 => 'Bad Gateway',
  491. 503 => 'Service Unavailable',
  492. 504 => 'Gateway Timeout',
  493. 505 => 'HTTP Version Not Supported',
  494. 509 => 'Bandwidth Limit Exceeded'
  495. );
  496. /**
  497. * The HTTP version (1.0, 1.1)
  498. *
  499. * @var string
  500. */
  501. protected $version;
  502. /**
  503. * The HTTP response code
  504. *
  505. * @var int
  506. */
  507. protected $code;
  508. /**
  509. * The HTTP response code as string
  510. * (e.g. 'Not Found' for 404 or 'Internal Server Error' for 500)
  511. *
  512. * @var string
  513. */
  514. protected $message;
  515. /**
  516. * The HTTP response headers array
  517. *
  518. * @var array
  519. */
  520. protected $headers = array();
  521. /**
  522. * The HTTP response body
  523. *
  524. * @var string
  525. */
  526. protected $body;
  527. /**
  528. * HTTP response constructor
  529. *
  530. * In most cases, you would use Zend_Http_Response::fromString to parse an HTTP
  531. * response string and create a new Zend_Http_Response object.
  532. *
  533. * NOTE: The constructor no longer accepts nulls or empty values for the code and
  534. * headers and will throw an exception if the passed values do not form a valid HTTP
  535. * responses.
  536. *
  537. * If no message is passed, the message will be guessed according to the response code.
  538. *
  539. * @param int $code Response code (200, 404, ...)
  540. * @param array $headers Headers array
  541. * @param string $body Response body
  542. * @param string $version HTTP version
  543. * @param string $message Response code as text
  544. * @throws Exception
  545. */
  546. public function __construct($code, array $headers, $body = null, $version = '1.1', $message = null)
  547. {
  548. // Make sure the response code is valid and set it
  549. if (self::responseCodeAsText($code) === null) {
  550. throw new Exception("{$code} is not a valid HTTP response code");
  551. }
  552. $this->code = $code;
  553. foreach ($headers as $name => $value) {
  554. if (is_int($name)) {
  555. $header = explode(":", $value, 2);
  556. if (count($header) != 2) {
  557. throw new Exception("'{$value}' is not a valid HTTP header");
  558. }
  559. $name = trim($header[0]);
  560. $value = trim($header[1]);
  561. }
  562. $this->headers[ucwords(strtolower($name))] = $value;
  563. }
  564. // Set the body
  565. $this->body = $body;
  566. // Set the HTTP version
  567. if (! preg_match('|^\d\.\d$|', $version)) {
  568. throw new Exception("Invalid HTTP response version: $version");
  569. }
  570. $this->version = $version;
  571. // If we got the response message, set it. Else, set it according to
  572. // the response code
  573. if (is_string($message)) {
  574. $this->message = $message;
  575. } else {
  576. $this->message = self::responseCodeAsText($code);
  577. }
  578. }
  579. /**
  580. * Check whether the response is an error
  581. *
  582. * @return boolean
  583. */
  584. public function isError()
  585. {
  586. $restype = floor($this->code / 100);
  587. if ($restype == 4 || $restype == 5) {
  588. return true;
  589. }
  590. return false;
  591. }
  592. /**
  593. * Check whether the response in successful
  594. *
  595. * @return boolean
  596. */
  597. public function isSuccessful()
  598. {
  599. $restype = floor($this->code / 100);
  600. if ($restype == 2 || $restype == 1) { // Shouldn't 3xx count as success as well ???
  601. return true;
  602. }
  603. return false;
  604. }
  605. /**
  606. * Check whether the response is a redirection
  607. *
  608. * @return boolean
  609. */
  610. public function isRedirect()
  611. {
  612. $restype = floor($this->code / 100);
  613. if ($restype == 3) {
  614. return true;
  615. }
  616. return false;
  617. }
  618. /**
  619. * Get the response body as string
  620. *
  621. * This method returns the body of the HTTP response (the content), as it
  622. * should be in it's readable version - that is, after decoding it (if it
  623. * was decoded), deflating it (if it was gzip compressed), etc.
  624. *
  625. * If you want to get the raw body (as transfered on wire) use
  626. * $this->getRawBody() instead.
  627. *
  628. * @return string
  629. */
  630. public function getBody()
  631. {
  632. //added by fcheslack - curl adapter handles these things already so they are transparent to Zend_Response
  633. return $this->getRawBody();
  634. $body = '';
  635. // Decode the body if it was transfer-encoded
  636. switch (strtolower($this->getHeader('transfer-encoding'))) {
  637. // Handle chunked body
  638. case 'chunked':
  639. $body = self::decodeChunkedBody($this->body);
  640. break;
  641. // No transfer encoding, or unknown encoding extension:
  642. // return body as is
  643. default:
  644. $body = $this->body;
  645. break;
  646. }
  647. // Decode any content-encoding (gzip or deflate) if needed
  648. switch (strtolower($this->getHeader('content-encoding'))) {
  649. // Handle gzip encoding
  650. case 'gzip':
  651. $body = self::decodeGzip($body);
  652. break;
  653. // Handle deflate encoding
  654. case 'deflate':
  655. $body = self::decodeDeflate($body);
  656. break;
  657. default:
  658. break;
  659. }
  660. return $body;
  661. }
  662. /**
  663. * Get the raw response body (as transfered "on wire") as string
  664. *
  665. * If the body is encoded (with Transfer-Encoding, not content-encoding -
  666. * IE "chunked" body), gzip compressed, etc. it will not be decoded.
  667. *
  668. * @return string
  669. */
  670. public function getRawBody()
  671. {
  672. return $this->body;
  673. }
  674. /**
  675. * Get the HTTP version of the response
  676. *
  677. * @return string
  678. */
  679. public function getVersion()
  680. {
  681. return $this->version;
  682. }
  683. /**
  684. * Get the HTTP response status code
  685. *
  686. * @return int
  687. */
  688. public function getStatus()
  689. {
  690. return $this->code;
  691. }
  692. /**
  693. * Return a message describing the HTTP response code
  694. * (Eg. "OK", "Not Found", "Moved Permanently")
  695. *
  696. * @return string
  697. */
  698. public function getMessage()
  699. {
  700. return $this->message;
  701. }
  702. /**
  703. * Get the response headers
  704. *
  705. * @return array
  706. */
  707. public function getHeaders()
  708. {
  709. return $this->headers;
  710. }
  711. /**
  712. * Get a specific header as string, or null if it is not set
  713. *
  714. * @param string$header
  715. * @return string|array|null
  716. */
  717. public function getHeader($header)
  718. {
  719. $header = ucwords(strtolower($header));
  720. if (! is_string($header) || ! isset($this->headers[$header])) return null;
  721. return $this->headers[$header];
  722. }
  723. /**
  724. * Get all headers as string
  725. *
  726. * @param boolean $status_line Whether to return the first status line (IE "HTTP 200 OK")
  727. * @param string $br Line breaks (eg. "\n", "\r\n", "<br />")
  728. * @return string
  729. */
  730. public function getHeadersAsString($status_line = true, $br = "\n")
  731. {
  732. $str = '';
  733. if ($status_line) {
  734. $str = "HTTP/{$this->version} {$this->code} {$this->message}{$br}";
  735. }
  736. // Iterate over the headers and stringify them
  737. foreach ($this->headers as $name => $value)
  738. {
  739. if (is_string($value))
  740. $str .= "{$name}: {$value}{$br}";
  741. elseif (is_array($value)) {
  742. foreach ($value as $subval) {
  743. $str .= "{$name}: {$subval}{$br}";
  744. }
  745. }
  746. }
  747. return $str;
  748. }
  749. /**
  750. * Get the entire response as string
  751. *
  752. * @param string $br Line breaks (eg. "\n", "\r\n", "<br />")
  753. * @return string
  754. */
  755. public function asString($br = "\n")
  756. {
  757. return $this->getHeadersAsString(true, $br) . $br . $this->getRawBody();
  758. }
  759. /**
  760. * Implements magic __toString()
  761. *
  762. * @return string
  763. */
  764. public function __toString()
  765. {
  766. return $this->asString();
  767. }
  768. /**
  769. * A convenience function that returns a text representation of
  770. * HTTP response codes. Returns 'Unknown' for unknown codes.
  771. * Returns array of all codes, if $code is not specified.
  772. *
  773. * Conforms to HTTP/1.1 as defined in RFC 2616 (except for 'Unknown')
  774. * See http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10 for reference
  775. *
  776. * @param int $code HTTP response code
  777. * @param boolean $http11 Use HTTP version 1.1
  778. * @return string
  779. */
  780. public static function responseCodeAsText($code = null, $http11 = true)
  781. {
  782. $messages = self::$messages;
  783. if (! $http11) $messages[302] = 'Moved Temporarily';
  784. if ($code === null) {
  785. return $messages;
  786. } elseif (isset($messages[$code])) {
  787. return $messages[$code];
  788. } else {
  789. return 'Unknown';
  790. }
  791. }
  792. /**
  793. * Extract the response code from a response string
  794. *
  795. * @param string $response_str
  796. * @return int
  797. */
  798. public static function extractCode($response_str)
  799. {
  800. preg_match("|^HTTP/[\d\.x]+ (\d+)|", $response_str, $m);
  801. if (isset($m[1])) {
  802. return (int) $m[1];
  803. } else {
  804. return false;
  805. }
  806. }
  807. /**
  808. * Extract the HTTP message from a response
  809. *
  810. * @param string $response_str
  811. * @return string
  812. */
  813. public static function extractMessage($response_str)
  814. {
  815. preg_match("|^HTTP/[\d\.x]+ \d+ ([^\r\n]+)|", $response_str, $m);
  816. if (isset($m[1])) {
  817. return $m[1];
  818. } else {
  819. return false;
  820. }
  821. }
  822. /**
  823. * Extract the HTTP version from a response
  824. *
  825. * @param string $response_str
  826. * @return string
  827. */
  828. public static function extractVersion($response_str)
  829. {
  830. preg_match("|^HTTP/([\d\.x]+) \d+|", $response_str, $m);
  831. if (isset($m[1])) {
  832. return $m[1];
  833. } else {
  834. return false;
  835. }
  836. }
  837. /**
  838. * Extract the headers from a response string
  839. *
  840. * @param string $response_str
  841. * @return array
  842. */
  843. public static function extractHeaders($response_str)
  844. {
  845. $headers = array();
  846. // First, split body and headers
  847. $parts = preg_split('|(?:\r?\n){2}|m', $response_str, 2);
  848. if (! $parts[0]) return $headers;
  849. // Split headers part to lines
  850. $lines = explode("\n", $parts[0]);
  851. unset($parts);
  852. $last_header = null;
  853. foreach($lines as $line) {
  854. $line = trim($line, "\r\n");
  855. if ($line == "") break;
  856. // Locate headers like 'Location: ...' and 'Location:...' (note the missing space)
  857. if (preg_match("|^([\w-]+):\s*(.+)|", $line, $m)) {
  858. unset($last_header);
  859. $h_name = strtolower($m[1]);
  860. $h_value = $m[2];
  861. if (isset($headers[$h_name])) {
  862. if (! is_array($headers[$h_name])) {
  863. $headers[$h_name] = array($headers[$h_name]);
  864. }
  865. $headers[$h_name][] = $h_value;
  866. } else {
  867. $headers[$h_name] = $h_value;
  868. }
  869. $last_header = $h_name;
  870. } elseif (preg_match("|^\s+(.+)$|", $line, $m) && $last_header !== null) {
  871. if (is_array($headers[$last_header])) {
  872. end($headers[$last_header]);
  873. $last_header_key = key($headers[$last_header]);
  874. $headers[$last_header][$last_header_key] .= $m[1];
  875. } else {
  876. $headers[$last_header] .= $m[1];
  877. }
  878. }
  879. }
  880. return $headers;
  881. }
  882. /**
  883. * Extract the body from a response string
  884. *
  885. * @param string $response_str
  886. * @return string
  887. */
  888. public static function extractBody($response_str)
  889. {
  890. $parts = preg_split('|(?:\r?\n){2}|m', $response_str, 2);
  891. if (isset($parts[1])) {
  892. return $parts[1];
  893. }
  894. return '';
  895. }
  896. /**
  897. * Decode a "chunked" transfer-encoded body and return the decoded text
  898. *
  899. * @param string $body
  900. * @return string
  901. */
  902. public static function decodeChunkedBody($body)
  903. {
  904. // Added by Dan S. -- don't fail on Transfer-encoding:chunked response
  905. //that isn't really chunked
  906. if (! preg_match("/^([\da-fA-F]+)[^\r\n]*\r\n/sm", trim($body), $m)) {
  907. return $body;
  908. }
  909. $decBody = '';
  910. // If mbstring overloads substr and strlen functions, we have to
  911. // override it's internal encoding
  912. if (function_exists('mb_internal_encoding') &&
  913. ((int) ini_get('mbstring.func_overload')) & 2) {
  914. $mbIntEnc = mb_internal_encoding();
  915. mb_internal_encoding('ASCII');
  916. }
  917. while (trim($body)) {
  918. if (! preg_match("/^([\da-fA-F]+)[^\r\n]*\r\n/sm", $body, $m)) {
  919. throw new Exception("Error parsing body - doesn't seem to be a chunked message");
  920. }
  921. $length = hexdec(trim($m[1]));
  922. $cut = strlen($m[0]);
  923. $decBody .= substr($body, $cut, $length);
  924. $body = substr($body, $cut + $length + 2);
  925. }
  926. if (isset($mbIntEnc)) {
  927. mb_internal_encoding($mbIntEnc);
  928. }
  929. return $decBody;
  930. }
  931. /**
  932. * Decode a gzip encoded message (when Content-encoding = gzip)
  933. *
  934. * Currently requires PHP with zlib support
  935. *
  936. * @param string $body
  937. * @return string
  938. */
  939. public static function decodeGzip($body)
  940. {
  941. if (! function_exists('gzinflate')) {
  942. throw new Exception(
  943. 'zlib extension is required in order to decode "gzip" encoding'
  944. );
  945. }
  946. return gzinflate(substr($body, 10));
  947. }
  948. /**
  949. * Decode a zlib deflated message (when Content-encoding = deflate)
  950. *
  951. * Currently requires PHP with zlib support
  952. *
  953. * @param string $body
  954. * @return string
  955. */
  956. public static function decodeDeflate($body)
  957. {
  958. if (! function_exists('gzuncompress')) {
  959. throw new Exception(
  960. 'zlib extension is required in order to decode "deflate" encoding'
  961. );
  962. }
  963. /**
  964. * Some servers (IIS ?) send a broken deflate response, without the
  965. * RFC-required zlib header.
  966. *
  967. * We try to detect the zlib header, and if it does not exsit we
  968. * teat the body is plain DEFLATE content.
  969. *
  970. * This method was adapted from PEAR HTTP_Request2 by (c) Alexey Borzov
  971. *
  972. * @link http://framework.zend.com/issues/browse/ZF-6040
  973. */
  974. $zlibHeader = unpack('n', substr($body, 0, 2));
  975. if ($zlibHeader[1] % 31 == 0) {
  976. return gzuncompress($body);
  977. } else {
  978. return gzinflate($body);
  979. }
  980. }
  981. /**
  982. * Create a new Zend_Http_Response object from a string
  983. *
  984. * @param string $response_str
  985. * @return Zend_Http_Response
  986. */
  987. public static function fromString($response_str)
  988. {
  989. $code = self::extractCode($response_str);
  990. $headers = self::extractHeaders($response_str);
  991. $body = self::extractBody($response_str);
  992. $version = self::extractVersion($response_str);
  993. $message = self::extractMessage($response_str);
  994. return new libZotero_Http_Response($code, $headers, $body, $version, $message);
  995. }
  996. }
  997. /**
  998. * Representation of a Zotero Item
  999. *
  1000. * @copyright Copyright (c) 2008 Center for History and New Media (http://chnm.gmu.edu)
  1001. * @license http://www.opensource.org/licenses/ecl1.php ECL License
  1002. * @since Class available since Release 0.0
  1003. * @see Zotero_Entry
  1004. */
  1005. class Zotero_Item extends Zotero_Entry
  1006. {
  1007. /**
  1008. * @var int
  1009. */
  1010. public $itemKey = '';
  1011. /**
  1012. * @var string
  1013. */
  1014. public $itemType = null;
  1015. /**
  1016. * @var string
  1017. */
  1018. public $creatorSummary = '';
  1019. /**
  1020. * @var string
  1021. */
  1022. public $numChildren = 0;
  1023. /**
  1024. * @var string
  1025. */
  1026. public $numTags = 0;
  1027. /**
  1028. * @var array
  1029. */
  1030. public $childKeys = array();
  1031. /**
  1032. * @var string
  1033. */
  1034. public $parentKey = '';
  1035. /**
  1036. * @var array
  1037. */
  1038. public $creators = array();
  1039. /**
  1040. * @var string
  1041. */
  1042. public $createdByUserID = null;
  1043. /**
  1044. * @var string
  1045. */
  1046. public $lastModifiedByUserID = null;
  1047. /**
  1048. * @var string
  1049. */
  1050. public $note = null;
  1051. /**
  1052. * @var int Represents the relationship of the child to the parent. 0:file, 1:file, 2:snapshot, 3:web-link
  1053. */
  1054. public $linkMode = null;
  1055. /**
  1056. * @var string
  1057. */
  1058. public $mimeType = null;
  1059. public $parsedJson = null;
  1060. public $etag = '';
  1061. /**
  1062. * @var string content node of response useful if formatted bib request and we need to use the raw content
  1063. */
  1064. public $content;
  1065. public $apiObject = array();
  1066. /**
  1067. * @var array
  1068. */
  1069. public static $fieldMap = array(
  1070. "creator" => "Creator",
  1071. "itemType" => "Type",
  1072. "title" => "Title",
  1073. "dateAdded" => "Date Added",
  1074. "dateModified" => "Modified",
  1075. "source" => "Source",
  1076. "notes" => "Notes",
  1077. "tags" => "Tags",
  1078. "attachments" => "Attachments",
  1079. "related" => "Related",
  1080. "url" => "URL",
  1081. "rights" => "Rights",
  1082. "series" => "Series",
  1083. "volume" => "Volume",
  1084. "issue" => "Issue",
  1085. "edition" => "Edition",
  1086. "place" => "Place",
  1087. "publisher" => "Publisher",
  1088. "pages" => "Pages",
  1089. "ISBN" => "ISBN",
  1090. "publicationTitle" => "Publication",
  1091. "ISSN" => "ISSN",
  1092. "date" => "Date",
  1093. "section" => "Section",
  1094. "callNumber" => "Call Number",
  1095. "archiveLocation" => "Loc. in Archive",
  1096. "distributor" => "Distributor",
  1097. "extra" => "Extra",
  1098. "journalAbbreviation" => "Journal Abbr",
  1099. "DOI" => "DOI",
  1100. "accessDate" => "Accessed",
  1101. "seriesTitle" => "Series Title",
  1102. "seriesText" => "Series Text",
  1103. "seriesNumber" => "Series Number",
  1104. "institution" => "Institution",
  1105. "reportType" => "Report Type",
  1106. "code" => "Code",
  1107. "session" => "Session",
  1108. "legislativeBody" => "Legislative Body",
  1109. "history" => "History",
  1110. "reporter" => "Reporter",
  1111. "court" => "Court",
  1112. "numberOfVolumes" => "# of Volumes",
  1113. "committee" => "Committee",
  1114. "assignee" => "Assignee",
  1115. "patentNumber" => "Patent Number",
  1116. "priorityNumbers" => "Priority Numbers",
  1117. "issueDate" => "Issue Date",
  1118. "references" => "References",
  1119. "legalStatus" => "Legal Status",
  1120. "codeNumber" => "Code Number",
  1121. "artworkMedium" => "Medium",
  1122. "number" => "Number",
  1123. "artworkSize" => "Artwork Size",
  1124. "libraryCatalog" => "Library Catalog",
  1125. "videoRecordingType" => "Recording Type",
  1126. "interviewMedium" => "Medium",
  1127. "letterType" => "Type",
  1128. "manuscriptType" => "Type",
  1129. "mapType" => "Type",
  1130. "scale" => "Scale",
  1131. "thesisType" => "Type",
  1132. "websiteType" => "Website Type",
  1133. "audioRecordingType" => "Recording Type",
  1134. "label" => "Label",
  1135. "presentationType" => "Type",
  1136. "meetingName" => "Meeting Name",
  1137. "studio" => "Studio",
  1138. "runningTime" => "Running Time",
  1139. "network" => "Network",
  1140. "postType" => "Post Type",
  1141. "audioFileType" => "File Type",
  1142. "version" => "Version",
  1143. "system" => "System",
  1144. "company" => "Company",
  1145. "conferenceName" => "Conference Name",
  1146. "encyclopediaTitle" => "Encyclopedia Title",
  1147. "dictionaryTitle" => "Dictionary Title",
  1148. "language" => "Language",
  1149. "programmingLanguage" => "Language",
  1150. "university" => "University",
  1151. "abstractNote" => "Abstract",
  1152. "websiteTitle" => "Website Title",
  1153. "reportNumber" => "Report Number",
  1154. "billNumber" => "Bill Number",
  1155. "codeVolume" => "Code Volume",
  1156. "codePages" => "Code Pages",
  1157. "dateDecided" => "Date Decided",
  1158. "reporterVolume" => "Reporter Volume",
  1159. "firstPage" => "First Page",
  1160. "documentNumber" => "Document Number",
  1161. "dateEnacted" => "Date Enacted",
  1162. "publicLawNumber" => "Public Law Number",
  1163. "country" => "Country",
  1164. "applicationNumber" => "Application Number",
  1165. "forumTitle" => "Forum/Listserv Title",
  1166. "episodeNumber" => "Episode Number",
  1167. "blogTitle" => "Blog Title",
  1168. "caseName" => "Case Name",
  1169. "nameOfAct" => "Name of Act",
  1170. "subject" => "Subject",
  1171. "proceedingsTitle" => "Proceedings Title",
  1172. "bookTitle" => "Book Title",
  1173. "shortTitle" => "Short Title",
  1174. "docketNumber" => "Docket Number",
  1175. "numPages" => "# of Pages"
  1176. );
  1177. /**
  1178. * @var array
  1179. */
  1180. public static $typeMap = array(
  1181. "note" => "Note",
  1182. "attachment" => "Attachment",
  1183. "book" => "Book",
  1184. "bookSection" => "Book Section",
  1185. "journalArticle" => "Journal Article",
  1186. "magazineArticle" => "Magazine Article",
  1187. "newspaperArticle" => "Newspaper Article",
  1188. "thesis" => "Thesis",
  1189. "letter" => "Letter",
  1190. "manuscript" => "Manuscript",
  1191. "interview" => "Interview",
  1192. "film" => "Film",
  1193. "artwork" => "Artwork",
  1194. "webpage" => "Web Page",
  1195. "report" => "Report",
  1196. "bill" => "Bill",
  1197. "case" => "Case",
  1198. "hearing" => "Hearing",
  1199. "patent" => "Patent",
  1200. "statute" => "Statute",
  1201. "email" => "E-mail",
  1202. "map" => "Map",
  1203. "blogPost" => "Blog Post",
  1204. "instantMessage" => "Instant Message",
  1205. "forumPost" => "Forum Post",
  1206. "audioRecording" => "Audio Recording",
  1207. "presentation" => "Presentation",
  1208. "videoRecording" => "Video Recording",
  1209. "tvBroadcast" => "TV Broadcast",
  1210. "radioBroadcast" => "Radio Broadcast",
  1211. "podcast" => "Podcast",
  1212. "computerProgram" => "Computer Program",
  1213. "conferencePaper" => "Conference Paper",
  1214. "document" => "Document",
  1215. "encyclopediaArticle" => "Encyclopedia Article",
  1216. "dictionaryEntry" => "Dictionary Entry",
  1217. );
  1218. /**
  1219. * @var array
  1220. */
  1221. public static $creatorMap = array(
  1222. "author" => "Author",
  1223. "contributor" => "Contributor",
  1224. "editor" => "Editor",
  1225. "translator" => "Translator",
  1226. "seriesEditor" => "Series Editor",
  1227. "interviewee" => "Interview With",
  1228. "interviewer" => "Interviewer",
  1229. "director" => "Director",
  1230. "scriptwriter" => "Scriptwriter",
  1231. "producer" => "Producer",
  1232. "castMember" => "Cast Member",
  1233. "sponsor" => "Sponsor",
  1234. "counsel" => "Counsel",
  1235. "inventor" => "Inventor",
  1236. "attorneyAgent" => "Attorney/Agent",
  1237. "recipient" => "Recipient",
  1238. "performer" => "Performer",
  1239. "composer" => "Composer",
  1240. "wordsBy" => "Words By",
  1241. "cartographer" => "Cartographer",
  1242. "programmer" => "Programmer",
  1243. "reviewedAuthor" => "Reviewed Author",
  1244. "artist" => "Artist",
  1245. "commenter" => "Commenter",
  1246. "presenter" => "Presenter",
  1247. "guest" => "Guest",
  1248. "podcaster" => "Podcaster"
  1249. );
  1250. public function __construct($entryNode=null)
  1251. {
  1252. if(!$entryNode){
  1253. return;
  1254. }
  1255. elseif(is_string($entryNode)){
  1256. $xml = $entryNode;
  1257. $doc = new DOMDocument();
  1258. $doc->loadXml($xml);
  1259. $entryNode = $doc->getElementsByTagName('entry')->item(0);
  1260. }
  1261. parent::__construct($entryNode);
  1262. //check if we have multiple subcontent nodes
  1263. $subcontentNodes = $entryNode->getElementsByTagNameNS("*", "subcontent");
  1264. //save raw Content node in case we need it
  1265. if($entryNode->getElementsByTagName("content")->length > 0){
  1266. $d = $entryNode->ownerDocument;
  1267. $this->contentNode = $entryNode->getElementsByTagName("content")->item(0);
  1268. $this->content = $d->saveXml($this->contentNode);
  1269. }
  1270. // Extract the itemId and itemType
  1271. $this->itemKey = $entryNode->getElementsByTagNameNS('*', 'key')->item(0)->nodeValue;
  1272. $this->itemType = $entryNode->getElementsByTagNameNS('*', 'itemType')->item(0)->nodeValue;
  1273. // Look for numChildren node
  1274. $numChildrenNode = $entryNode->getElementsByTagNameNS('*', "numChildren")->item(0);
  1275. if($numChildrenNode){
  1276. $this->numChildren = $numChildrenNode->nodeValue;
  1277. }
  1278. // Look for numTags node
  1279. $numTagsNode = $entryNode->getElementsByTagNameNS('*', "numTags")->item(0);
  1280. if($numTagsNode){
  1281. $this->numTags = $numTagsNode->nodeValue;
  1282. }
  1283. $creatorSummaryNode = $entryNode->getElementsByTagNameNS('*', "creatorSummary")->item(0);
  1284. if($creatorSummaryNode){
  1285. $this->creatorSummary = $creatorSummaryNode->nodeValue;
  1286. }
  1287. if($subcontentNodes->length > 0){
  1288. for($i = 0; $i < $subcontentNodes->length; $i++){
  1289. $scnode = $subcontentNodes->item($i);
  1290. $type = $scnode->getAttribute('zapi:type');
  1291. if($type == 'application/json' || $type == 'json'){
  1292. $this->apiObject = json_decode($scnode->nodeValue, true);
  1293. $this->etag = $scnode->getAttribute('zapi:etag');
  1294. if(isset($this->apiObject['creators'])){
  1295. $this->creators = $this->apiObject['creators'];
  1296. }
  1297. else{
  1298. $this->creators = array();
  1299. }
  1300. }
  1301. elseif($type == 'bib'){
  1302. $bibNode = $scnode->getElementsByTagName('div')->item(0);
  1303. $this->bibContent = $bibNode->ownerDocument->saveXML($bibNode);
  1304. }
  1305. else{
  1306. throw new Exception("Unknown zapi:subcontent type " . $type);
  1307. }
  1308. }
  1309. }
  1310. else{
  1311. $contentNode = $entryNode->getElementsByTagName('content')->item(0);
  1312. $contentType = parent::getContentType($entryNode);
  1313. if($contentType == 'application/json' || $contentType == 'json'){
  1314. $this->apiObject = json_decode($contentNode->nodeValue, true);
  1315. $this->etag = $contentNode->getAttribute('zapi:etag');
  1316. if(isset($this->apiObject['creators'])){
  1317. $this->creators = $this->apiObject['creators'];
  1318. }
  1319. else{
  1320. $this->creators = array();
  1321. }
  1322. }
  1323. }
  1324. if(isset($this->links['up'])){
  1325. $parentLink = $this->links['up']['application/atom+xml']['href'];
  1326. $matches = array();
  1327. preg_match("/items\/([A-Z0-9]{8})/", $parentLink, $matches);
  1328. if(count($matches) == 2){
  1329. $this->parentKey = $matches[1];
  1330. }
  1331. }
  1332. else{
  1333. $this->parentKey = false;
  1334. }
  1335. }
  1336. public function get($key){
  1337. if($key == 'tags'){
  1338. if(isset($this->apiObject['tags'])){
  1339. return $this->apiObject['tags'];
  1340. }
  1341. }
  1342. elseif($key == 'creators'){
  1343. //special case
  1344. if(isset($this->apiObject['creators'])){
  1345. return $this->apiObject['creators'];
  1346. }
  1347. }
  1348. else{
  1349. if(isset($this->apiObject[$key])){
  1350. return $this->apiObject[$key];
  1351. }
  1352. else{
  1353. return null;
  1354. }
  1355. }
  1356. }
  1357. public function set($key, $val){
  1358. if($key == 'creators' || $key == 'tags'){
  1359. //TODO: special case empty value and correctly in arrays
  1360. $this->apiObject[$key] = $val;
  1361. }
  1362. else{
  1363. //if(in_array($key, array_keys($this->fieldMap))) {
  1364. $this->apiObject[$key] = $val;
  1365. //}
  1366. }
  1367. }
  1368. public function addCreator($creatorArray){
  1369. $this->creators[] = $creatorArray;
  1370. $this->apiObject['creators'][] = $creatorArray;
  1371. }
  1372. public function updateItemObject(){
  1373. $updateItem = $this->apiObject;
  1374. //remove notes as they can't be in update json
  1375. unset($updateItem['notes']);
  1376. $newCreatorsArray = array();
  1377. foreach($updateItem['creators'] as $creator){
  1378. if($creator['creatorType']){
  1379. if(empty($creator['name']) && empty($creator['firstName']) && empty($creator['lastName'])){
  1380. continue;
  1381. }
  1382. else{
  1383. $newCreatorsArray[] = $creator;
  1384. }
  1385. }
  1386. }
  1387. $updateItem['creators'] = $newCreatorsArray;
  1388. return $updateItem;
  1389. }
  1390. public function newItemObject(){
  1391. $newItem = $this->apiObject;
  1392. $newCreatorsArray = array();
  1393. if(!isset($newItem['creators'])) {
  1394. return $newItem;
  1395. }
  1396. foreach($newItem['creators'] as $creator){
  1397. if($creator['creatorType']){
  1398. if(empty($creator['name']) && empty($creator['firstName']) && empty($creator['lastName'])){
  1399. continue;
  1400. }
  1401. else{
  1402. $newCreatorsArray[] = $creator;
  1403. }
  1404. }
  1405. }
  1406. $newItem['creators'] = $newCreatorsArray;
  1407. return $newItem;
  1408. }
  1409. public function isAttachment(){
  1410. if($this->itemType == 'attachment'){
  1411. return true;
  1412. }
  1413. }
  1414. public function hasFile(){
  1415. if(!$this->isAttachment()){
  1416. return false;
  1417. }
  1418. $hasEnclosure = isset($this->links['enclosure']);
  1419. $linkMode = $this->apiObject['linkMode'];
  1420. if($hasEnclosure && ($linkMode == 0 || $linkMode == 1)){
  1421. return true;
  1422. }
  1423. }
  1424. public function json(){
  1425. return json_encode($this->apiObject());
  1426. }
  1427. public function fullItemJSON(){
  1428. return json_encode($this->fullItemArray());
  1429. }
  1430. public function fullItemArray(){
  1431. $jsonItem = array();
  1432. //inherited from Entry
  1433. $jsonItem['title'] = $this->title;
  1434. $jsonItem['dateAdded'] = $this->dateAdded;
  1435. $jsonItem['dateUpdated'] = $this->dateUpdated;
  1436. $jsonItem['id'] = $this->id;
  1437. $jsonItem['links'] = $this->links;
  1438. //Item specific vars
  1439. $jsonItem['itemKey'] = $this->itemKey;
  1440. $jsonItem['itemType'] = $this->itemType;
  1441. $jsonItem['creatorSummary'] = $this->creatorSummary;
  1442. $jsonItem['numChildren'] = $this->numChildren;
  1443. $jsonItem['numTags'] = $this->numTags;
  1444. $jsonItem['creators'] = $this->creators;
  1445. $jsonItem['createdByUserID'] = $this->createdByUserID;
  1446. $jsonItem['lastModifiedByUserID'] = $this->lastModifiedByUserID;
  1447. $jsonItem['note'] = $this->note;
  1448. $jsonItem['linkMode'] = $this->linkMode;
  1449. $jsonItem['mimeType'] = $this->mimeType;
  1450. $jsonItem['apiObject'] = $this->apiObject;
  1451. return $jsonItem;
  1452. }
  1453. public function formatItemField($field){
  1454. switch($field){
  1455. case "title":
  1456. return $this->title;
  1457. break;
  1458. case "creator":
  1459. if(isset($this->creatorSummary)){
  1460. return $this->creatorSummary;
  1461. }
  1462. else{
  1463. return '';
  1464. }
  1465. break;
  1466. case "dateModified":
  1467. return $this->dateModified;
  1468. break;
  1469. case "dateAdded":
  1470. return $this->dateAdded;
  1471. break;
  1472. default:
  1473. if(isset($this->apiObject[$field])){
  1474. return $this->apiObject[$field];
  1475. }
  1476. else{
  1477. return '';
  1478. }
  1479. }
  1480. }
  1481. public function compareItem($otherItem){
  1482. $diff = array_diff_assoc($this->apiObject, $otherItem->apiObject);
  1483. return $diff;
  1484. }
  1485. }
  1486. /**
  1487. * Representation of a Zotero Group
  1488. *
  1489. * @copyright Copyright (c) 2008 Center for History and New Media (http://chnm.gmu.edu)
  1490. * @license http://www.opensource.org/licenses/ecl1.php ECL License
  1491. * @since Class available since Release 0.0
  1492. * @see Zotero_Entry
  1493. */
  1494. class Zotero_Group extends Zotero_Entry
  1495. {
  1496. /**
  1497. * @var array
  1498. */
  1499. public $properties;
  1500. /**
  1501. * @var int
  1502. */
  1503. public $id;
  1504. /**
  1505. * @var int
  1506. */
  1507. public $groupID;
  1508. /**
  1509. * @var int
  1510. */
  1511. public $owner;
  1512. /**
  1513. * @var string
  1514. */
  1515. public $type;
  1516. /**
  1517. * @var string
  1518. */
  1519. public $name;
  1520. /**
  1521. * @var bool
  1522. */
  1523. public $libraryEnabled;
  1524. /**
  1525. * @var string
  1526. */
  1527. public $libraryEditing;
  1528. /**
  1529. * @var string
  1530. */
  1531. public $libraryReading;
  1532. /**
  1533. * @var string
  1534. */
  1535. public $fileEditing;
  1536. /**
  1537. * @var bool
  1538. */
  1539. public $hasImage;
  1540. /**
  1541. * @var string
  1542. */
  1543. public $description;
  1544. /**
  1545. * @var array
  1546. */
  1547. public $disciplines;
  1548. /**
  1549. * @var bool
  1550. */
  1551. public $enableComments;
  1552. /**
  1553. * @var string
  1554. */
  1555. public $url = '';
  1556. /**
  1557. * @var array
  1558. */
  1559. public $adminIDs;
  1560. /**
  1561. * @var array
  1562. */
  1563. public $memberIDs;
  1564. public function __construct($entryNode = null)
  1565. {
  1566. if(!$entryNode){
  1567. return;
  1568. }
  1569. elseif(is_string($entryNode)){
  1570. $xml = $entryNode;
  1571. $doc = new DOMDocument();
  1572. $doc->loadXml($xml);
  1573. $entryNode = $doc->getElementsByTagName('entry')->item(0);
  1574. }
  1575. parent::__construct($entryNode);
  1576. if(!$entryNode){
  1577. return;
  1578. }
  1579. $contentNode = $entryNode->getElementsByTagName('content')->item(0);
  1580. $contentType = parent::getContentType($entryNode);
  1581. if($contentType == 'application/json'){
  1582. $this->apiObject = json_decode($contentNode->nodeValue, true);
  1583. //$this->etag = $contentNode->getAttribute('etag');
  1584. }
  1585. $this->name = $this->apiObject['name'];
  1586. $this->ownerID = $this->apiObject['owner'];
  1587. $this->groupType = $this->apiObject['type'];
  1588. $this->description = $this->apiObject['description'];
  1589. $this->url = $this->apiObject['url'];
  1590. $this->libraryEnabled = $this->apiObject['libraryEnabled'];
  1591. $this->libraryEditing = $this->apiObject['libraryEditing'];
  1592. $this->libraryReading = $this->apiObject['libraryReading'];
  1593. $this->fileEditing = $this->apiObject['fileEditing'];
  1594. if(!empty($this->apiObject['admins'])){
  1595. $this->adminIDs = $this->apiObject['admins'];
  1596. }
  1597. else {
  1598. $this->adminIDs = array();
  1599. }
  1600. if(!empty($this->apiObject['members'])){
  1601. $this->memberIDs = $this->apiObject['members'];
  1602. }
  1603. else{
  1604. $this->memberIDs = array();
  1605. }
  1606. $this->numItems = $entryNode->getElementsByTagNameNS('*', 'numItems')->item(0)->nodeValue;
  1607. /*
  1608. // Extract the groupID and groupType
  1609. $groupElements = $entryNode->getElementsByTagName("group");
  1610. $groupElement = $groupElements->item(0);
  1611. if(!$groupElement) return;
  1612. $groupAttributes = $groupElement->attributes;
  1613. foreach($groupAttributes as $attrName => $attrNode){
  1614. $this->properties[$attrName] = urldecode($attrNode->value);
  1615. if($attrName == 'name'){
  1616. $this->$attrName = $attrNode->value;
  1617. }
  1618. else{
  1619. $this->$attrName = urldecode($attrNode->value);
  1620. }
  1621. }
  1622. $this->groupID = $this->properties['id'];
  1623. $description = $entryNode->getElementsByTagName("description")->item(0);
  1624. if($description) {
  1625. $this->properties['description'] = urldecode($description->nodeValue);
  1626. $this->description = urldecode($description->nodeValue);
  1627. }
  1628. $url = $entryNode->getElementsByTagName("url")->item(0);
  1629. if($url) {
  1630. $this->properties['url'] = $url->nodeValue;
  1631. $this->url = $url->nodeValue;
  1632. }
  1633. $this->adminIDs = array();
  1634. $admins = $entryNode->getElementsByTagName("admins")->item(0);
  1635. if($admins){
  1636. $this->adminIDs = $admins === null ? array() : explode(" ", $admins->nodeValue);
  1637. }
  1638. $this->adminIDs[] = $this->owner;
  1639. $this->memberIDs = array();
  1640. $members = $entryNode->getElementsByTagName("members")->item(0);
  1641. if($members){
  1642. $this->memberIDs = $members === null ? array() : explode(" ", $members->nodeValue);
  1643. }
  1644. */
  1645. //initially disallow library access
  1646. $this->userReadable = false;
  1647. $this->userEditable = false;
  1648. }
  1649. public function setProperty($key, $val)
  1650. {
  1651. $this->properties[$key] = $val;
  1652. return $this;
  1653. }
  1654. public function updateString()
  1655. {
  1656. $view = new Zend_View();
  1657. $view->setScriptPath('../library/Zotero/Service/Zotero/views');
  1658. $view->group = $this;
  1659. return $view->render("group.phtml");
  1660. }
  1661. public function dataObject() {
  1662. $jsonItem = new stdClass;
  1663. //inherited from Entry
  1664. $jsonItem->title = $this->title;
  1665. $jsonItem->dateAdded = $this->dateAdded;
  1666. $jsonItem->dateUpdated = $this->dateUpdated;
  1667. $jsonItem->id = $this->id;
  1668. //Group vars
  1669. $jsonItem->groupID = $this->groupID;
  1670. $jsonItem->owner = $this->owner;
  1671. $jsonItem->memberIDs = $this->memberIDs;
  1672. $jsonItem->adminIDs = $this->adminIDs;
  1673. $jsonItem->type = $this->type;
  1674. $jsonItem->name = $this->name;
  1675. $jsonItem->libraryEnabled = $this->libraryEnabled;
  1676. $jsonItem->libraryEditing = $this->libraryEditing;
  1677. $jsonItem->libraryReading = $this->libraryReading;
  1678. $jsonItem->hasImage = $this->hadImage;
  1679. $jsonItem->description = $this->description;
  1680. $jsonItem->url = $this->url;
  1681. return $jsonItem;
  1682. }
  1683. }
  1684. /**
  1685. * Representation of a Zotero Tag
  1686. *
  1687. * @copyright Copyright (c) 2008 Center for History and New Media (http://chnm.gmu.edu)
  1688. * @license http://www.opensource.org/licenses/ecl1.php ECL License
  1689. * @since Class available since Release 0.0
  1690. * @see Zotero_Entry
  1691. */
  1692. class Zotero_Tag extends Zotero_Entry
  1693. {
  1694. /**
  1695. * @var int
  1696. */
  1697. /* public $tagID;
  1698. public $libraryID;
  1699. public $key;
  1700. public $name;
  1701. public $dateAdded;
  1702. public $dateModified;
  1703. public $type;
  1704. */
  1705. public $numItems = 0;
  1706. public function __construct($entryNode)
  1707. {
  1708. if(!$entryNode){
  1709. libZoteroDebug( "no entryNode in tag constructor\n" );
  1710. return;
  1711. }
  1712. elseif(is_string($entryNode)){
  1713. libZoteroDebug( "entryNode is string in tag constructor\n" );
  1714. $xml = $entryNode;
  1715. $doc = new DOMDocument();
  1716. libZoteroDebug( $xml );
  1717. $doc->loadXml($xml);
  1718. $entryNode = $doc->getElementsByTagName('entry')->item(0);
  1719. }
  1720. parent::__construct($entryNode);
  1721. $this->name = $this->title;
  1722. if(!$entryNode){
  1723. libZoteroDebug( "second no entryNode in tag constructor\n" );
  1724. return;
  1725. }
  1726. $numItems = $entryNode->getElementsByTagNameNS('*', "numItems")->item(0);
  1727. if($numItems) {
  1728. $this->numItems = (int)$numItems->nodeValue;
  1729. }
  1730. $tagElements = $entryNode->getElementsByTagName("tag");
  1731. $tagElement = $tagElements->item(0);
  1732. }
  1733. public function dataObject() {
  1734. $jsonItem = new stdClass;
  1735. //inherited from Entry
  1736. $jsonItem->title = $this->title;
  1737. $jsonItem->dateAdded = $this->dateAdded;
  1738. $jsonItem->dateUpdated = $this->dateUpdated;
  1739. $jsonItem->id = $this->id;
  1740. $jsonItem->properties = $this->properties;
  1741. return $jsonItem;
  1742. }
  1743. }
  1744. /**
  1745. * Representation of a Zotero User
  1746. *
  1747. * @copyright Copyright (c) 2008 Center for History and New Media (http://chnm.gmu.edu)
  1748. * @license http://www.opensource.org/licenses/ecl1.php ECL License
  1749. * @since Class available since Release 0.0
  1750. * @see Zotero_Entry
  1751. */
  1752. class Zotero_User extends Zotero_Entry
  1753. {
  1754. /**
  1755. * @var int
  1756. */
  1757. public $userID;
  1758. public function __construct($entryNode)
  1759. {
  1760. parent::__construct($entryNode);
  1761. }
  1762. }
  1763. class Zotero_Creator
  1764. {
  1765. public $creatorType = null;
  1766. public $localized = null;
  1767. public $firstName = null;
  1768. public $lastName = null;
  1769. public $name = null;
  1770. public function getWriteObject(){
  1771. if(empty($this->creatorType) || (empty($this->name) && empty($this->firstName) && empty($this->lastName) ) ){
  1772. return false;
  1773. }
  1774. $a = array('creatorType'=>$this->creatorType);
  1775. if(!empty($this->name)){
  1776. $a['name'] = $this->name;
  1777. }
  1778. else{
  1779. $a['firstName'] = $this->firstName;
  1780. $a['lastName'] = $this->lastName;
  1781. }
  1782. return $a;
  1783. }
  1784. }
  1785. define('LIBZOTERO_DEBUG', 0);
  1786. function libZoteroDebug($m){
  1787. if(LIBZOTERO_DEBUG){
  1788. echo $m;
  1789. }
  1790. return;
  1791. }
  1792. class Zotero_Library
  1793. {
  1794. const ZOTERO_URI = 'https://api.zotero.org';
  1795. protected $_apiKey = '';
  1796. protected $_ch = null;
  1797. public $libraryType = null;
  1798. public $libraryID = null;
  1799. public $libraryString = null;
  1800. public $libraryUrlIdentifier = null;
  1801. public $libraryBaseWebsiteUrl = null;
  1802. public $items = null;
  1803. public $collections = null;
  1804. public $dirty = null;
  1805. public $useLibraryAsContainer = true;
  1806. protected $_lastResponse = null;
  1807. protected $_lastFeed = null;
  1808. protected $_cacheResponses = false;
  1809. protected $_cachettl = 0;
  1810. /**
  1811. * Constructor for Zotero_Library
  1812. *
  1813. * @param string $libraryType user|group
  1814. * @param string $libraryID id for zotero library, unique when combined with libraryType
  1815. * @param string $libraryUrlIdentifier library identifier used in urls, either ID or slug
  1816. * @param string $apiKey zotero api key
  1817. * @param string $baseWebsiteUrl base url to use when generating links to the website version of items
  1818. * @param string $cachettl cache time to live in seconds, cache disabled if 0
  1819. * @return Zotero_Library
  1820. */
  1821. public function __construct($libraryType = null, $libraryID = 'me', $libraryUrlIdentifier = null, $apiKey = null, $baseWebsiteUrl="http://www.zotero.org", $cachettl=0)
  1822. {
  1823. $this->_apiKey = $apiKey;
  1824. if (extension_loaded('curl')) {
  1825. //$this->_ch = curl_init();
  1826. } else {
  1827. throw new Exception("You need cURL");
  1828. }
  1829. $this->libraryType = $libraryType;
  1830. $this->libraryID = $libraryID;
  1831. $this->libraryString = $this->libraryString($this->libraryType, $this->libraryID);
  1832. $this->libraryUrlIdentifier = $libraryUrlIdentifier;
  1833. $this->libraryBaseWebsiteUrl = $baseWebsiteUrl . '/';
  1834. if($this->libraryType == 'group'){
  1835. $this->libraryBaseWebsiteUrl .= 'groups/';
  1836. }
  1837. $this->libraryBaseWebsiteUrl .= $this->libraryUrlIdentifier . '/items';
  1838. $this->items = new Zotero_Items();
  1839. $this->collections = new Zotero_Collections();
  1840. $this->collections->libraryUrlIdentifier = $this->libraryUrlIdentifier;
  1841. $this->dirty = false;
  1842. if($cachettl > 0){
  1843. $this->_cachettl = $cachettl;
  1844. $this->_cacheResponses = true;
  1845. }
  1846. }
  1847. /**
  1848. * Destructor, closes cURL.
  1849. */
  1850. public function __destruct() {
  1851. //curl_close($this->_ch);
  1852. }
  1853. /**
  1854. * set the cache time to live after initialization
  1855. *
  1856. * @param int $cachettl cache time to live in seconds, 0 disables
  1857. * @return null
  1858. */
  1859. public function setCacheTtl($cachettl){
  1860. if($cachettl == 0){
  1861. $this->_cacheResponses = false;
  1862. $this->_cachettl = 0;
  1863. }
  1864. else{
  1865. $this->_cacheResponses = true;
  1866. $this->_cachettl = $cachettl;
  1867. }
  1868. }
  1869. /**
  1870. * Make http request to zotero api
  1871. *
  1872. * @param string $url target api url
  1873. * @param string $method http method GET|POST|PUT|DELETE
  1874. * @param string $body request body if write
  1875. * @param array $headers headers to set on request
  1876. * @return HTTP_Response
  1877. */
  1878. public function _request($url, $method="GET", $body=NULL, $headers=array()) {
  1879. libZoteroDebug( "url being requested: " . $url . "\n\n");
  1880. $ch = curl_init();
  1881. $httpHeaders = array();
  1882. foreach($headers as $key=>$val){
  1883. $httpHeaders[] = "$key: $val";
  1884. }
  1885. curl_setopt($ch, CURLOPT_URL, $url);
  1886. curl_setopt($ch, CURLOPT_HEADER, true);
  1887. curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  1888. curl_setopt($ch, CURLINFO_HEADER_OUT, true);
  1889. curl_setopt($ch, CURLOPT_HTTPHEADER, $httpHeaders);
  1890. curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
  1891. curl_setopt($ch, CURLOPT_MAXREDIRS, 3);
  1892. $umethod = strtoupper($method);
  1893. switch($umethod){
  1894. case "GET":
  1895. curl_setopt($ch, CURLOPT_HTTPGET, true);
  1896. break;
  1897. case "POST":
  1898. curl_setopt($ch, CURLOPT_POST, true);
  1899. curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
  1900. break;
  1901. case "PUT":
  1902. curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PUT");
  1903. curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
  1904. break;
  1905. case "DELETE":
  1906. curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "DELETE");
  1907. break;
  1908. }
  1909. $gotCached = false;
  1910. if($this->_cacheResponses && $umethod == 'GET'){
  1911. $cachedResponse = apc_fetch($url, $success);
  1912. if($success){
  1913. $responseBody = $cachedResponse['responseBody'];
  1914. $responseInfo = $cachedResponse['responseInfo'];
  1915. $zresponse = libZotero_Http_Response::fromString($responseBody);
  1916. $gotCached = true;
  1917. }
  1918. }
  1919. if(!$gotCached){
  1920. $responseBody = curl_exec($ch);
  1921. $responseInfo = curl_getinfo($ch);
  1922. //libZoteroDebug( "{$method} url:" . $url . "\n");
  1923. //libZoteroDebug( "%%%%%" . $responseBody . "%%%%%\n\n");
  1924. $zresponse = libZotero_Http_Response::fromString($responseBody);
  1925. //Zend Response does not parse out the multiple sets of headers returned when curl automatically follows
  1926. //a redirect and the new headers are left in the body. Zend_Http_Client gets around this by manually
  1927. //handling redirects. That may end up being a better solution, but for now we'll just re-read responses
  1928. //until a non-redirect is read
  1929. while($zresponse->isRedirect()){
  1930. $redirectedBody = $zresponse->getBody();
  1931. $zresponse = libZotero_Http_Response::fromString($redirectedBody);
  1932. }
  1933. $saveCached = array(
  1934. 'responseBody'=>$responseBody,
  1935. 'responseInfo'=>$responseInfo,
  1936. );
  1937. if($this->_cacheResponses){
  1938. apc_store($url, $saveCached, $this->_cachettl);
  1939. }
  1940. }
  1941. $this->_lastResponse = $zresponse;
  1942. return $zresponse;
  1943. }
  1944. public function proxyHttpRequest($url, $method='GET', $body=null, $headers=array()) {
  1945. $endPoint = $url;
  1946. try{
  1947. $response = $this->_request($url, $method, $body, $headers);
  1948. if($response->getStatus() == 303){
  1949. //this might not account for GET parameters in the first url depending on the server
  1950. $newLocation = $response->getHeader("Location");
  1951. $reresponse = $this->_request($newLocation, $method, $body, $headers);
  1952. return $reresponse;
  1953. }
  1954. }
  1955. catch(Exception $e){
  1956. $r = new libZotero_Http_Response(500, array(), $e->getMessage());
  1957. return $r;
  1958. }
  1959. return $response;
  1960. }
  1961. public function _cacheSave(){
  1962. }
  1963. public function _cacheLoad(){
  1964. }
  1965. /**
  1966. * get the last HTTP_Response returned
  1967. *
  1968. * @return HTTP_Response
  1969. */
  1970. public function getLastResponse(){
  1971. return $this->_lastResponse;
  1972. }
  1973. /**
  1974. * Get the last Zotero_Feed parsed
  1975. *
  1976. * @return Zotero_Feed
  1977. */
  1978. public function getLastFeed(){
  1979. return $this->_lastFeed;
  1980. }
  1981. /**
  1982. * Construct a string that uniquely identifies a library
  1983. * This is not related to the server GUIDs
  1984. *
  1985. * @return string
  1986. */
  1987. public static function libraryString($type, $libraryID){
  1988. $lstring = '';
  1989. if($type == 'user') $lstring = 'u';
  1990. elseif($type == 'group') $lstring = 'g';
  1991. $lstring .= $libraryID;
  1992. return $lstring;
  1993. }
  1994. /**
  1995. * generate an api url for a request based on array of parameters
  1996. *
  1997. * @param array $params list of parameters that define the request
  1998. * @param string $base the base api url
  1999. * @return string
  2000. */
  2001. public function apiRequestUrl($params = array(), $base = Zotero_Library::ZOTERO_URI) {
  2002. //var_dump($params);
  2003. if(!isset($params['target'])){
  2004. throw new Exception("No target defined for api request");
  2005. }
  2006. //special case for www based api requests until those methods are mapped for api.zotero
  2007. if($params['target'] == 'user' || $params['target'] == 'cv'){
  2008. $base = 'https://www.zotero.org/api';
  2009. }
  2010. //allow overriding of libraryType and ID in params if they are passed
  2011. //otherwise use the settings for this instance of library
  2012. if(!empty($params['libraryType']) && !empty($params['libraryID'])){
  2013. $url = $base . '/' . $params['libraryType'] . 's/' . $params['libraryID'];
  2014. }
  2015. else{
  2016. $url = $base . '/' . $this->libraryType . 's/' . $this->libraryID;
  2017. }
  2018. if(!empty($params['collectionKey'])){
  2019. $url .= '/collections/' . $params['collectionKey'];
  2020. }
  2021. switch($params['target']){
  2022. case 'items':
  2023. $url .= '/items';
  2024. break;
  2025. case 'item':
  2026. if($params['itemKey']){
  2027. $url .= '/items/' . $params['itemKey'];
  2028. }
  2029. else{
  2030. $url .= '/items';
  2031. }
  2032. break;
  2033. case 'collections':
  2034. $url .= '/collections';
  2035. break;
  2036. case 'collection':
  2037. break;
  2038. case 'tags':
  2039. $url .= '/tags';
  2040. break;
  2041. case 'children':
  2042. $url .= '/items/' . $params['itemKey'] . '/children';
  2043. break;
  2044. case 'itemTemplate':
  2045. $url = $base . '/items/new';
  2046. break;
  2047. case 'key':
  2048. $url = $base . '/users/' . $params['userID'] . '/keys/' . $params['apiKey'];
  2049. break;
  2050. case 'userGroups':
  2051. $url = $base . '/users/' . $params['userID'] . '/groups';
  2052. break;
  2053. case 'trash':
  2054. $url .= '/items/trash';
  2055. break;
  2056. case 'cv':
  2057. $url .= '/cv';
  2058. break;
  2059. default:
  2060. return false;
  2061. }
  2062. if(isset($params['targetModifier'])){
  2063. switch($params['targetModifier']){
  2064. case 'top':
  2065. $url .= '/top';
  2066. break;
  2067. case 'children':
  2068. $url .= '/children';
  2069. break;
  2070. case 'file':
  2071. if($params['target'] != 'item'){
  2072. throw new Exception('Trying to get file on non-item target');
  2073. }
  2074. $url .= '/file';
  2075. break;
  2076. }
  2077. }
  2078. //print "apiRequestUrl: " . $url . "\n";
  2079. return $url;
  2080. }
  2081. /**
  2082. * generate an api query string for a request based on array of parameters
  2083. *
  2084. * @param array $passedParams list of parameters that define the request
  2085. * @return string
  2086. */
  2087. public function apiQueryString($passedParams=array()){
  2088. // Tags query formats
  2089. //
  2090. // ?tag=foo
  2091. // ?tag=foo bar // phrase
  2092. // ?tag=-foo // negation
  2093. // ?tag=\-foo // literal hyphen (only for first character)
  2094. // ?tag=foo&tag=bar // AND
  2095. // ?tag=foo&tagType=0
  2096. // ?tag=foo bar || bar&tagType=0
  2097. $queryParamOptions = array('start',
  2098. 'limit',
  2099. 'order',
  2100. 'sort',
  2101. 'content',
  2102. 'q',
  2103. 'itemType',
  2104. 'locale',
  2105. 'key',
  2106. 'itemKey',
  2107. 'tag',
  2108. 'tagType',
  2109. 'style',
  2110. 'format'
  2111. );
  2112. //build simple api query parameters object
  2113. if((!isset($passedParams['key'])) && $this->_apiKey){
  2114. $passedParams['key'] = $this->_apiKey;
  2115. }
  2116. $queryParams = array();
  2117. foreach($queryParamOptions as $i=>$val){
  2118. if(isset($passedParams[$val]) && ($passedParams[$val] != '')) {
  2119. if($val == 'itemKey' && isset($passedParams['target']) && ($passedParams['target'] != 'items') ) continue;
  2120. $queryParams[$val] = $passedParams[$val];
  2121. }
  2122. }
  2123. $queryString = '?';
  2124. $queryParamsArray = array();
  2125. foreach($queryParams as $index=>$value){
  2126. if(is_array($value)){
  2127. foreach($value as $key=>$val){
  2128. if(is_string($val) || is_int($val)){
  2129. $queryParamsArray[] = urlEncode($index) . '=' . urlencode($val);
  2130. }
  2131. }
  2132. }
  2133. elseif(is_string($value) || is_int($value)){
  2134. $queryParamsArray[] = urlencode($index) . '=' . urlencode($value);
  2135. }
  2136. }
  2137. $queryString .= implode('&', $queryParamsArray);
  2138. //print "apiQueryString: " . $queryString . "\n";
  2139. return $queryString;
  2140. }
  2141. /**
  2142. * parse a query string and separate into parameters
  2143. * without using the php way of representing query strings
  2144. *
  2145. * @param string $query
  2146. * @return array
  2147. */
  2148. public function parseQueryString($query){
  2149. $params = explode('&', $query);
  2150. $aparams = array();
  2151. foreach($params as $val){
  2152. $t = explode('=', $val);
  2153. $aparams[urldecode($t[0])] = urldecode($t[1]);
  2154. }
  2155. return $aparams;
  2156. }
  2157. /**
  2158. * Load all collections in the library into the collections container
  2159. *
  2160. * @param array $params list of parameters limiting the request
  2161. * @return null
  2162. */
  2163. public function fetchAllCollection($params = array()){
  2164. $aparams = array_merge(array('target'=>'collections', 'content'=>'json', 'limit'=>100), array('key'=>$this->_apiKey), $params);
  2165. $reqUrl = $this->apiRequestUrl($aparams) . $this->apiQueryString($aparams);
  2166. do{
  2167. $response = $this->_request($reqUrl);
  2168. if($response->isError()){
  2169. throw new Exception("Error fetching collections");
  2170. }
  2171. $body = $response->getRawBody();
  2172. $doc = new DOMDocument();
  2173. $doc->loadXml($body);
  2174. $feed = new Zotero_Feed($doc);
  2175. $this->collections->addCollectionsFromFeed($feed);
  2176. if(isset($feed->links['next'])){
  2177. $nextUrl = $feed->links['next']['href'];
  2178. $parsedNextUrl = parse_url($nextUrl);
  2179. $parsedNextUrl['query'] = $this->apiQueryString(array_merge(array('key'=>$this->_apiKey), $this->parseQueryString($parsedNextUrl['query']) ) );
  2180. $reqUrl = $parsedNextUrl['scheme'] . '://' . $parsedNextUrl['host'] . $parsedNextUrl['path'] . $parsedNextUrl['query'];
  2181. }
  2182. else{
  2183. $reqUrl = false;
  2184. }
  2185. } while($reqUrl);
  2186. $this->collections->loaded = true;
  2187. }
  2188. /**
  2189. * Load 1 request worth of collections in the library into the collections container
  2190. *
  2191. * @param array $params list of parameters limiting the request
  2192. * @return null
  2193. */
  2194. public function fetchCollections($params = array()){
  2195. $aparams = array_merge(array('target'=>'collections', 'content'=>'json', 'limit'=>100), array('key'=>$this->_apiKey), $params);
  2196. $reqUrl = $this->apiRequestUrl($aparams) . $this->apiQueryString($aparams);
  2197. $response = $this->_request($reqUrl);
  2198. if($response->isError()){
  2199. throw new Exception("Error fetching collections");
  2200. }
  2201. $body = $response->getRawBody();
  2202. $doc = new DOMDocument();
  2203. $doc->loadXml($body);
  2204. $feed = new Zotero_Feed($doc);
  2205. $addedCollections = $this->collections->addCollectionsFromFeed($feed);
  2206. if(isset($feed->links['next'])){
  2207. $nextUrl = $feed->links['next']['href'];
  2208. $parsedNextUrl = parse_url($nextUrl);
  2209. $parsedNextUrl['query'] = $this->apiQueryString(array_merge(array('key'=>$this->_apiKey), $this->parseQueryString($parsedNextUrl['query']) ) );
  2210. $reqUrl = $parsedNextUrl['scheme'] . '://' . $parsedNextUrl['host'] . $parsedNextUrl['path'] . $parsedNextUrl['query'];
  2211. }
  2212. else{
  2213. $reqUrl = false;
  2214. }
  2215. return $addedCollections;
  2216. }
  2217. /**
  2218. * Make a single request loading top level items
  2219. *
  2220. * @param array $params list of parameters that define the request
  2221. * @return array of fetched items
  2222. */
  2223. public function fetchItemsTop($params=array()){
  2224. $params['targetModifier'] = 'top';
  2225. return $this->fetchItems($params);
  2226. }
  2227. /**
  2228. * Make a single request loading item keys
  2229. *
  2230. * @param array $params list of parameters that define the request
  2231. * @return array of fetched items
  2232. */
  2233. public function fetchItemKeys($params=array()){
  2234. $fetchedKeys = array();
  2235. $aparams = array_merge(array('target'=>'items', 'format'=>'keys'), array('key'=>$this->_apiKey), $params);
  2236. $reqUrl = $this->apiRequestUrl($aparams) . $this->apiQueryString($aparams);
  2237. $response = $this->_request($reqUrl);
  2238. if($response->isError()){
  2239. throw new Exception("Error fetching item keys");
  2240. }
  2241. $body = $response->getRawBody();
  2242. $fetchedKeys = explode("\n", trim($body) );
  2243. return $fetchedKeys;
  2244. }
  2245. /**
  2246. * Make a single request loading items in the trash
  2247. *
  2248. * @param array $params list of parameters additionally filtering the request
  2249. * @return array of fetched items
  2250. */
  2251. public function fetchTrashedItems($params=array()){
  2252. $fetchedItems = array();
  2253. $aparams = array_merge(array('target'=>'trash', 'content'=>'json'), array('key'=>$this->_apiKey), $params);
  2254. $reqUrl = $this->apiRequestUrl($aparams) . $this->apiQueryString($aparams);
  2255. libZoteroDebug( "\n");
  2256. libZoteroDebug( $reqUrl . "\n" );
  2257. //die;
  2258. $response = $this->_request($reqUrl);
  2259. if($response->isError()){
  2260. throw new Exception("Error fetching items");
  2261. }
  2262. $body = $response->getRawBody();
  2263. $doc = new DOMDocument();
  2264. $doc->loadXml($body);
  2265. $feed = new Zotero_Feed($doc);
  2266. $this->_lastFeed = $feed;
  2267. $fetchedItems = $this->items->addItemsFromFeed($feed);
  2268. return $fetchedItems;
  2269. }
  2270. /**
  2271. * Make a single request loading a list of items
  2272. *
  2273. * @param array $params list of parameters that define the request
  2274. * @return array of fetched items
  2275. */
  2276. public function fetchItems($params = array()){
  2277. $fetchedItems = array();
  2278. $aparams = array_merge(array('target'=>'items', 'content'=>'json'), array('key'=>$this->_apiKey), $params);
  2279. $reqUrl = $this->apiRequestUrl($aparams) . $this->apiQueryString($aparams);
  2280. libZoteroDebug( "\n" );
  2281. libZoteroDebug( $reqUrl . "\n" );
  2282. //die;
  2283. $response = $this->_request($reqUrl);
  2284. if($response->isError()){
  2285. throw new Exception("Error fetching items");
  2286. }
  2287. $body = $response->getRawBody();
  2288. $doc = new DOMDocument();
  2289. $doc->loadXml($body);
  2290. $feed = new Zotero_Feed($doc);
  2291. $this->_lastFeed = $feed;
  2292. $fetchedItems = $this->items->addItemsFromFeed($feed);
  2293. return $fetchedItems;
  2294. }
  2295. /**
  2296. * Make a single request loading a list of items
  2297. *
  2298. * @param string $itemKey key of item to stop retrieval at
  2299. * @param array $params list of parameters that define the request
  2300. * @return array of fetched items
  2301. */
  2302. public function fetchItemsAfter($itemKey, $params = array()){
  2303. $fetchedItems = array();
  2304. $itemKeys = $this->fetchItemKeys($params);
  2305. if($itemKey != ''){
  2306. $index = array_search($itemKey, $itemKeys);
  2307. if($index == false){
  2308. return array();
  2309. }
  2310. }
  2311. $offset = 0;
  2312. while($offset < $index){
  2313. if($index - $offset > 50){
  2314. $uindex = $offset + 50;
  2315. }
  2316. else{
  2317. $uindex = $index;
  2318. }
  2319. $itemKeysToFetch = array_slice($itemKeys, 0, $uindex);
  2320. $offset == $uindex;
  2321. $params['itemKey'] = implode(',', $itemKeysToFetch);
  2322. $fetchedSet = $this->fetchItems($params);
  2323. $fetchedItems = array_merge($fetchedItems, $fetchedSet);
  2324. }
  2325. return $fetchedItems;
  2326. }
  2327. /**
  2328. * Load a single item by itemKey
  2329. *
  2330. * @param string $itemKey
  2331. * @return Zotero_Item
  2332. */
  2333. public function fetchItem($itemKey){
  2334. $aparams = array('target'=>'item', 'content'=>'json', 'itemKey'=>$itemKey);
  2335. $reqUrl = $this->apiRequestUrl($aparams) . $this->apiQueryString($aparams);
  2336. $response = $this->_request($reqUrl, 'GET');
  2337. if($response->isError()){
  2338. var_dump($response);
  2339. throw new Exception("Error fetching items");
  2340. }
  2341. $body = $response->getRawBody();
  2342. $doc = new DOMDocument();
  2343. $doc->loadXml($body);
  2344. $entries = $doc->getElementsByTagName("entry");
  2345. if(!$entries->length){
  2346. throw new Exception("no item with specified key found");
  2347. }
  2348. else{
  2349. $entry = $entries->item(0);
  2350. $item = new Zotero_Item($entry);
  2351. $this->items->addItem($item);
  2352. return $item;
  2353. }
  2354. }
  2355. /**
  2356. * construct the url for file download of the item if it exists
  2357. *
  2358. * @param string $itemKey
  2359. * @return string
  2360. */
  2361. public function itemDownloadLink($itemKey){
  2362. $aparams = array('target'=>'item', 'itemKey'=>$itemKey, 'targetModifier'=>'file');
  2363. return $this->apiRequestUrl($aparams) . $this->apiQueryString($aparams);
  2364. }
  2365. /**
  2366. * Write a modified item back to the api
  2367. *
  2368. * @param Zotero_Item $item the modified item to be written back
  2369. * @return Zotero_Response
  2370. */
  2371. public function writeUpdatedItem($item){
  2372. if(is_string($item)){
  2373. $itemKey = $item;
  2374. $item = $this->items->getItem($itemKey);
  2375. }
  2376. $updateItemJson = json_encode($item->updateItemObject());
  2377. $etag = $item->etag;
  2378. $aparams = array('target'=>'item', 'itemKey'=>$item->itemKey);
  2379. $reqUrl = $this->apiRequestUrl($aparams) . $this->apiQueryString($aparams);
  2380. $response = $this->_request($reqUrl, 'PUT', $updateItemJson, array('If-Match'=>$etag));
  2381. return $response;
  2382. }
  2383. /**
  2384. * Make API request to create a new item
  2385. *
  2386. * @param Zotero_Item $item the newly created Zotero_Item to be added to the server
  2387. * @return Zotero_Response
  2388. */
  2389. public function createItem($item){
  2390. $createItemJson = json_encode(array('items'=>array($item->newItemObject())));;
  2391. //libZoteroDebug( $createItemJson );die;
  2392. $aparams = array('target'=>'items');
  2393. $reqUrl = $this->apiRequestUrl($aparams) . $this->apiQueryString($aparams);
  2394. $response = $this->_request($reqUrl, 'POST', $createItemJson);
  2395. return $response;
  2396. }
  2397. /**
  2398. * Get a template for a new item of a certain type
  2399. *
  2400. * @param string $itemType type of item the template is for
  2401. * @return Zotero_Item
  2402. */
  2403. public function getTemplateItem($itemType){
  2404. $newItem = new Zotero_Item();
  2405. $aparams = array('target'=>'itemTemplate', 'itemType'=>$itemType);
  2406. $reqUrl = $this->apiRequestUrl($aparams) . $this->apiQueryString($aparams);
  2407. $response = $this->_request($reqUrl);
  2408. if($response->isError()){
  2409. throw new Exception("Error with api");
  2410. }
  2411. $itemTemplate = json_decode($response->getRawBody(), true);
  2412. $newItem->apiObject = $itemTemplate;
  2413. return $newItem;
  2414. }
  2415. /**
  2416. * Add child notes to a parent item
  2417. *
  2418. * @param Zotero_Item $parentItem the item the notes are to be children of
  2419. * @param Zotero_Item|array $noteItem the note item or items
  2420. * @return Zotero_Response
  2421. */
  2422. public function addNotes($parentItem, $noteItem){
  2423. $aparams = array('target'=>'children', 'itemKey'=>$parentItem->itemKey);
  2424. $reqUrl = $this->apiRequestUrl($aparams) . $this->apiQueryString($aparams);
  2425. if(!is_array($noteItem)){
  2426. $noteJson = json_encode(array('items'=>array($noteItem->newItemObject())));
  2427. }
  2428. else{
  2429. $notesArray = array();
  2430. foreach($noteItem as $nitem){
  2431. $notesArray[] = $nitem->newItemObject();
  2432. }
  2433. $noteJson = json_encode(array('items'=>$notesArray));
  2434. }
  2435. $response = $this->_request($reqUrl, 'POST', $noteJson);
  2436. return $response;
  2437. }
  2438. /**
  2439. * Create a new collection in this library
  2440. *
  2441. * @param string $name the name of the new item
  2442. * @param Zotero_Item $parent the optional parent collection for the new collection
  2443. * @return Zotero_Response
  2444. */
  2445. public function createCollection($name, $parent = false){
  2446. $collection = new Zotero_Collection();
  2447. $collection->name = $name;
  2448. $collection->parentCollectionKey = $parent;
  2449. $json = $collection->collectionJson();
  2450. $aparams = array('target'=>'collections');
  2451. $reqUrl = $this->apiRequestUrl($aparams) . $this->apiQueryString($aparams);
  2452. $response = $this->_request($reqUrl, 'POST', $json);
  2453. return $response;
  2454. }
  2455. /**
  2456. * Delete a collection from the library
  2457. *
  2458. * @param Zotero_Collection $collection collection object to be deleted
  2459. * @return Zotero_Response
  2460. */
  2461. public function removeCollection($collection){
  2462. $aparams = array('target'=>'collection', 'collectionKey'=>$collection->collectionKey);
  2463. $reqUrl = $this->apiRequestUrl($aparams) . $this->apiQueryString($aparams);
  2464. $response = $this->_request($reqUrl, 'DELETE', null, array('If-Match'=>$collection->etag));
  2465. return $response;
  2466. }
  2467. /**
  2468. * Add Items to a collection
  2469. *
  2470. * @param Zotero_Collection $collection to add items to
  2471. * @param array $items
  2472. * @return Zotero_Response
  2473. */
  2474. public function addItemsToCollection($collection, $items){
  2475. $aparams = array('target'=>'items', 'collectionKey'=>$collection->collectionKey);
  2476. $itemKeysString = '';
  2477. foreach($items as $item){
  2478. $itemKeysString .= $item->itemKey;
  2479. }
  2480. $itemKeysString = trim($itemKeysString);
  2481. $reqUrl = $this->apiRequestUrl($aparams) . $this->apiQueryString($aparams);
  2482. $response = $this->_request($reqUrl, 'POST', $itemKeysString);
  2483. return $response;
  2484. }
  2485. /**
  2486. * Remove items from a collection
  2487. *
  2488. * @param Zotero_Collection $collection to add items to
  2489. * @param array $items
  2490. * @return array $removedItemKeys list of itemKeys successfully removed
  2491. */
  2492. public function removeItemsFromCollection($collection, $items){
  2493. $removedItemKeys = array();
  2494. foreach($items as $item){
  2495. $response = $this->removeItemFromCollection($collection, $item);
  2496. if(!$response->isError()){
  2497. $removedItemKeys[] = $item->itemKey;
  2498. }
  2499. }
  2500. return $removedItemKeys;
  2501. }
  2502. /**
  2503. * Remove a single item from a collection
  2504. *
  2505. * @param Zotero_Collection $collection to add items to
  2506. * @param Zotero_Item $item
  2507. * @return Zotero_Response
  2508. */
  2509. public function removeItemFromCollection($collection, $item){
  2510. $aparams = array('target'=>'items', 'collectionKey'=>$collection->collectionKey);
  2511. $reqUrl = $this->apiRequestUrl($aparams) . $this->apiQueryString($aparams);
  2512. $response = $this->_request($reqUrl, 'DELETE', null, array('If-Match'=>$collection->etag));
  2513. return $response;
  2514. }
  2515. /**
  2516. * Write a modified collection object back to the api
  2517. *
  2518. * @param Zotero_Collection $collection to modify
  2519. * @return Zotero_Response
  2520. */
  2521. public function writeUpdatedCollection($collection){
  2522. $json = $collection->collectionJson();
  2523. $aparams = array('target'=>'collection', 'collectionKey'=>$collection->collectionKey);
  2524. $reqUrl = $this->apiRequestUrl($aparams) . $this->apiQueryString($aparams);
  2525. $response = $this->_request($reqUrl, 'PUT', $json, array('If-Match'=>$collection->etag));
  2526. return $response;
  2527. }
  2528. /**
  2529. * Permanently delete an item from the API
  2530. *
  2531. * @param Zotero_Item $item
  2532. * @return Zotero_Response
  2533. */
  2534. public function deleteItem($item){
  2535. $aparams = array('target'=>'item', 'itemKey'=>$item->itemKey);
  2536. $reqUrl = $this->apiRequestUrl($aparams) . $this->apiQueryString($aparams);
  2537. $response = $this->_request($reqUrl, 'DELETE', null, array('If-Match'=>$item->etag));
  2538. return $response;
  2539. }
  2540. /**
  2541. * Put an item in the trash
  2542. *
  2543. * @param Zotero_Item $item
  2544. * @return Zotero_Response
  2545. */
  2546. public function trashItem($item){
  2547. $item->set('deleted', 1);
  2548. $this->writeUpdatedItem($item);
  2549. }
  2550. /**
  2551. * Fetch any child items of a particular item
  2552. *
  2553. * @param Zotero_Item $item
  2554. * @return array $fetchedItems
  2555. */
  2556. public function fetchItemChildren($item){
  2557. $aparams = array('target'=>'children', 'itemKey'=>$item->itemKey, 'content'=>'json');
  2558. $reqUrl = $this->apiRequestUrl($aparams) . $this->apiQueryString($aparams);
  2559. $response = $this->_request($reqUrl, 'GET');
  2560. //load response into item objects
  2561. $fetchedItems = array();
  2562. if($response->isError()){
  2563. throw new Exception("Error fetching items");
  2564. }
  2565. $body = $response->getRawBody();
  2566. $doc = new DOMDocument();
  2567. $doc->loadXml($body);
  2568. $feed = new Zotero_Feed($doc);
  2569. $this->_lastFeed = $feed;
  2570. $fetchedItems = $this->items->addItemsFromFeed($feed);
  2571. return $fetchedItems;
  2572. }
  2573. /**
  2574. * Get the list of itemTypes the API knows about
  2575. *
  2576. * @return array $itemTypes
  2577. */
  2578. public function getItemTypes(){
  2579. $reqUrl = Zotero_Library::ZOTERO_URI . 'itemTypes';
  2580. $response = $this->_request($reqUrl, 'GET');
  2581. if($response->isError()){
  2582. throw new Zotero_Exception("failed to fetch itemTypes");
  2583. }
  2584. $itemTypes = json_decode($response->getBody(), true);
  2585. return $itemTypes;
  2586. }
  2587. /**
  2588. * Get the list of item Fields the API knows about
  2589. *
  2590. * @return array $itemFields
  2591. */
  2592. public function getItemFields(){
  2593. $reqUrl = Zotero_Library::ZOTERO_URI . 'itemFields';
  2594. $response = $this->_request($reqUrl, 'GET');
  2595. if($response->isError()){
  2596. throw new Zotero_Exception("failed to fetch itemFields");
  2597. }
  2598. $itemFields = json_decode($response->getBody(), true);
  2599. return $itemFields;
  2600. }
  2601. /**
  2602. * Get the creatorTypes associated with an itemType
  2603. *
  2604. * @param string $itemType
  2605. * @return array $creatorTypes
  2606. */
  2607. public function getCreatorTypes($itemType){
  2608. $reqUrl = Zotero_Library::ZOTERO_URI . 'itemTypeCreatorTypes?itemType=' . $itemType;
  2609. $response = $this->_request($reqUrl, 'GET');
  2610. if($response->isError()){
  2611. throw new Zotero_Exception("failed to fetch creatorTypes");
  2612. }
  2613. $creatorTypes = json_decode($response->getBody(), true);
  2614. return $creatorTypes;
  2615. }
  2616. /**
  2617. * Get the creator Fields the API knows about
  2618. *
  2619. * @return array $creatorFields
  2620. */
  2621. public function getCreatorFields(){
  2622. $reqUrl = Zotero_Library::ZOTERO_URI . 'creatorFields';
  2623. $response = $this->_request($reqUrl, 'GET');
  2624. if($response->isError()){
  2625. throw new Zotero_Exception("failed to fetch creatorFields");
  2626. }
  2627. $creatorFields = json_decode($response->getBody(), true);
  2628. return $creatorFields;
  2629. }
  2630. /**
  2631. * Fetch all the tags defined by the passed parameters
  2632. *
  2633. * @param array $params list of parameters defining the request
  2634. * @return array $tags
  2635. */
  2636. public function fetchAllTags($params){
  2637. $aparams = array_merge(array('target'=>'tags', 'content'=>'json', 'limit'=>50), $params);
  2638. $reqUrl = $this->apiRequestUrl($aparams) . $this->apiQueryString($aparams);
  2639. do{
  2640. $response = $this->_request($reqUrl, 'GET');
  2641. if($response->isError()){
  2642. return false;
  2643. }
  2644. $doc = new DOMDocument();
  2645. $doc->loadXml($response->getBody());
  2646. $feed = new Zotero_Feed($doc);
  2647. $entries = $doc->getElementsByTagName('entry');
  2648. $tags = array();
  2649. foreach($entries as $entry){
  2650. $tag = new Zotero_Tag($entry);
  2651. $tags[] = $tag;
  2652. }
  2653. if(isset($feed->links['next'])){
  2654. $nextUrl = $feed->links['next']['href'];
  2655. $parsedNextUrl = parse_url($nextUrl);
  2656. $parsedNextUrl['query'] = $this->apiQueryString(array_merge(array('key'=>$this->_apiKey), $this->parseQueryString($parsedNextUrl['query']) ) );
  2657. $reqUrl = $parsedNextUrl['scheme'] . '://' . $parsedNextUrl['host'] . $parsedNextUrl['path'] . $parsedNextUrl['query'];
  2658. }
  2659. else{
  2660. $reqUrl = false;
  2661. }
  2662. } while($reqUrl);
  2663. return $tags;
  2664. }
  2665. /**
  2666. * Make a single request for Zotero tags in this library defined by the passed parameters
  2667. *
  2668. * @param array $params list of parameters defining the request
  2669. * @return array $tags
  2670. */
  2671. public function fetchTags($params){
  2672. $aparams = array_merge(array('target'=>'tags', 'content'=>'json', 'limit'=>50), $params);
  2673. $reqUrl = $this->apiRequestUrl($aparams) . $this->apiQueryString($aparams);
  2674. $response = $this->_request($reqUrl, 'GET');
  2675. if($response->isError()){
  2676. libZoteroDebug( $response->getMessage() . "\n" );
  2677. libZoteroDebug( $response->getBody() );
  2678. return false;
  2679. }
  2680. $doc = new DOMDocument();
  2681. $doc->loadXml($response->getBody());
  2682. $feed = new Zotero_Feed($doc);
  2683. $entries = $doc->getElementsByTagName('entry');
  2684. $tags = array();
  2685. foreach($entries as $entry){
  2686. $tag = new Zotero_Tag($entry);
  2687. $tags[] = $tag;
  2688. }
  2689. return $tags;
  2690. }
  2691. /**
  2692. * Get the permissions a key has for a library
  2693. * if no key is passed use the currently set key for the library
  2694. *
  2695. * @param int|string $userID
  2696. * @param string $key
  2697. * @return array $keyPermissions
  2698. */
  2699. public function getKeyPermissions($userID=null, $key=false) {
  2700. if($userID === null){
  2701. $userID = $this->libraryID;
  2702. }
  2703. if($key == false){
  2704. if($this->_apiKey == '') {
  2705. false;
  2706. }
  2707. $key = $this->_apiKey;
  2708. }
  2709. $reqUrl = $this->apiRequestUrl(array('target'=>'key', 'apiKey'=>$key, 'userID'=>$userID));
  2710. $response = $this->_request($reqUrl, 'GET');
  2711. if($response->isError()){
  2712. return false;
  2713. }
  2714. $body = $response->getBody();
  2715. $doc = new DOMDocument();
  2716. $doc->loadXml($body);
  2717. $keyNode = $doc->getElementsByTagName('key')->item(0);
  2718. $keyPerms = $this->parseKey($keyNode);
  2719. return $keyPerms;
  2720. }
  2721. /**
  2722. * Parse a key response into an array
  2723. *
  2724. * @param $keyNode DOMNode from key response
  2725. * @return array $keyPermissions
  2726. */
  2727. public function parseKey($keyNode){
  2728. $key = array();
  2729. $keyPerms = array("library"=>"0", "notes"=>"0", "write"=>"0", 'groups'=>array());
  2730. $accessEls = $keyNode->getElementsByTagName('access');
  2731. foreach($accessEls as $access){
  2732. if($libraryAccess = $access->getAttribute("library")){
  2733. $keyPerms['library'] = $libraryAccess;
  2734. }
  2735. if($notesAccess = $access->getAttribute("notes")){
  2736. $keyPerms['notes'] = $notesAccess;
  2737. }
  2738. if($groupAccess = $access->getAttribute("group")){
  2739. $groupPermission = $access->getAttribute("write") == '1' ? 'write' : 'read';
  2740. $keyPerms['groups'][$groupAccess] = $groupPermission;
  2741. }
  2742. elseif($writeAccess = $access->getAttribute("write")) {
  2743. $keyPerms['write'] = $writeAccess;
  2744. }
  2745. }
  2746. return $keyPerms;
  2747. }
  2748. /**
  2749. * Get groups a user belongs to
  2750. *
  2751. * @param string $userID
  2752. * @return array $groups
  2753. */
  2754. public function fetchGroups($userID=''){
  2755. if($userID == ''){
  2756. $userID = $this->libraryID;
  2757. }
  2758. $aparams = array('target'=>'userGroups', 'userID'=>$userID, 'content'=>'json');
  2759. $reqUrl = $this->apiRequestUrl($aparams) . $this->apiQueryString($aparams);
  2760. $response = $this->_request($reqUrl, 'GET');
  2761. if($response->isError()){
  2762. return false;
  2763. }
  2764. $doc = new DOMDocument();
  2765. $doc->loadXml($response->getBody());
  2766. $entries = $doc->getElementsByTagName('entry');
  2767. $groups = array();
  2768. foreach($entries as $entry){
  2769. $group = new Zotero_Group($entry);
  2770. $groups[] = $group;
  2771. }
  2772. return $groups;
  2773. }
  2774. /**
  2775. * Get CV for a user
  2776. *
  2777. * @param string $userID
  2778. * @return array $groups
  2779. */
  2780. public function getCV($userID=''){
  2781. if($userID == '' && $this->libraryType == 'user'){
  2782. $userID = $this->libraryID;
  2783. }
  2784. $aparams = array('target'=>'cv', 'libraryType'=>'user', 'libraryID'=>$userID);
  2785. $reqUrl = $this->apiRequestUrl($aparams);// . $this->apiQueryString($aparams);
  2786. $response = $this->_request($reqUrl, 'GET');
  2787. if($response->isError()){
  2788. var_dump($response);
  2789. return false;
  2790. }
  2791. $doc = new DOMDocument();
  2792. $doc->loadXml($response->getBody());
  2793. $sectionNodes = $doc->getElementsByTagNameNS('*', 'cvsection');
  2794. $sections = array();
  2795. foreach($sectionNodes as $sectionNode){
  2796. $c = $sectionNode->nodeValue;
  2797. $sections[] = $c;
  2798. }
  2799. return $sections;
  2800. }
  2801. //these functions aren't really necessary for php since serializing
  2802. //or apc caching works fine, with only the possible loss of a curl
  2803. //handle that will be re-initialized
  2804. public function saveLibrary(){
  2805. $serialized = serialize($this);
  2806. return $serialized;
  2807. }
  2808. public static function loadLibrary($dump){
  2809. return unserialize($dump);
  2810. }
  2811. }
  2812. ?>