PageRenderTime 76ms CodeModel.GetById 26ms RepoModel.GetById 1ms app.codeStats 0ms

/build/libZoteroSingle.php

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