Delicious.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618
  1. <?php
  2. /**
  3. * Zend Framework
  4. *
  5. * LICENSE
  6. *
  7. * This source file is subject to the new BSD license that is bundled
  8. * with this package in the file LICENSE.txt.
  9. * It is also available through the world-wide-web at this URL:
  10. * http://framework.zend.com/license/new-bsd
  11. * If you did not receive a copy of the license and are unable to
  12. * obtain it through the world-wide-web, please send an email
  13. * to license@zend.com so we can send you a copy immediately.
  14. *
  15. * @category Zend
  16. * @package Zend_Service
  17. * @subpackage Delicious
  18. * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
  19. * @license http://framework.zend.com/license/new-bsd New BSD License
  20. * @version $Id$
  21. */
  22. /**
  23. * @see Zend_Rest_Client
  24. */
  25. require_once 'Zend/Rest/Client.php';
  26. /**
  27. * @see Zend_Json_Decoder
  28. */
  29. require_once 'Zend/Json/Decoder.php';
  30. /**
  31. * @see Zend_Service_Delicious_SimplePost
  32. */
  33. require_once 'Zend/Service/Delicious/SimplePost.php';
  34. /**
  35. * @see Zend_Service_Delicious_Post
  36. */
  37. require_once 'Zend/Service/Delicious/Post.php';
  38. /**
  39. * @see Zend_Service_Delicious_PostList
  40. */
  41. require_once 'Zend/Service/Delicious/PostList.php';
  42. /** @see Zend_Xml_Security */
  43. require_once 'Zend/Xml/Security.php';
  44. /**
  45. * Zend_Service_Delicious is a concrete implementation of the del.icio.us web service
  46. *
  47. * @category Zend
  48. * @package Zend_Service
  49. * @subpackage Delicious
  50. * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
  51. * @license http://framework.zend.com/license/new-bsd New BSD License
  52. */
  53. class Zend_Service_Delicious
  54. {
  55. const API_URI = 'https://api.del.icio.us';
  56. const PATH_UPDATE = '/v1/posts/update';
  57. const PATH_TAGS = '/v1/tags/get';
  58. const PATH_TAG_RENAME = '/v1/tags/rename';
  59. const PATH_BUNDLES = '/v1/tags/bundles/all';
  60. const PATH_BUNDLE_DELETE = '/v1/tags/bundles/delete';
  61. const PATH_BUNDLE_ADD = '/v1/tags/bundles/set';
  62. const PATH_DATES = '/v1/posts/dates';
  63. const PATH_POST_DELETE = '/v1/posts/delete';
  64. const PATH_POSTS_GET = '/v1/posts/get';
  65. const PATH_POSTS_ALL = '/v1/posts/all';
  66. const PATH_POSTS_ADD = '/v1/posts/add';
  67. const PATH_POSTS_RECENT = '/v1/posts/recent';
  68. const JSON_URI = 'http://del.icio.us';
  69. const JSON_POSTS = '/feeds/json/%s/%s';
  70. const JSON_TAGS = '/feeds/json/tags/%s';
  71. const JSON_NETWORK = '/feeds/json/network/%s';
  72. const JSON_FANS = '/feeds/json/fans/%s';
  73. const JSON_URL = '/feeds/json/url/data';
  74. /**
  75. * Zend_Service_Rest instance
  76. *
  77. * @var Zend_Service_Rest
  78. */
  79. protected $_rest;
  80. /**
  81. * Username
  82. *
  83. * @var string
  84. */
  85. protected $_authUname;
  86. /**
  87. * Password
  88. *
  89. * @var string
  90. */
  91. protected $_authPass;
  92. /**
  93. * Microtime of last request
  94. *
  95. * @var float
  96. */
  97. protected static $_lastRequestTime = 0;
  98. /**
  99. * Constructs a new del.icio.us Web Services Client
  100. *
  101. * @param string $uname Client username
  102. * @param string $pass Client password
  103. * @return void
  104. */
  105. public function __construct($uname = null, $pass = null)
  106. {
  107. $this->_rest = new Zend_Rest_Client();
  108. $this->_rest->getHttpClient()->setConfig(array('ssltransport' => 'ssl'));
  109. $this->setAuth($uname, $pass);
  110. }
  111. /**
  112. * Set client username and password
  113. *
  114. * @param string $uname Client user name
  115. * @param string $pass Client password
  116. * @return Zend_Service_Delicious Provides a fluent interface
  117. */
  118. public function setAuth($uname, $pass)
  119. {
  120. $this->_authUname = $uname;
  121. $this->_authPass = $pass;
  122. return $this;
  123. }
  124. /**
  125. * Get time of the last update
  126. *
  127. * @throws Zend_Service_Delicious_Exception
  128. * @return Zend_Date
  129. */
  130. public function getLastUpdate()
  131. {
  132. $response = $this->makeRequest(self::PATH_UPDATE);
  133. $rootNode = $response->documentElement;
  134. if ($rootNode && $rootNode->nodeName == 'update') {
  135. /**
  136. * @todo replace strtotime() with Zend_Date equivalent
  137. */
  138. return new Zend_Date(strtotime($rootNode->getAttribute('time')));
  139. } else {
  140. /**
  141. * @see Zend_Service_Delicious_Exception
  142. */
  143. require_once 'Zend/Service/Delicious/Exception.php';
  144. throw new Zend_Service_Delicious_Exception('del.icio.us web service has returned something odd!');
  145. }
  146. }
  147. /**
  148. * Get all tags, returning an array with tags as keys and number of corresponding posts as values
  149. *
  150. * @return array list of tags
  151. */
  152. public function getTags()
  153. {
  154. $response = $this->makeRequest(self::PATH_TAGS);
  155. return self::_xmlResponseToArray($response, 'tags', 'tag', 'tag', 'count');
  156. }
  157. /**
  158. * Rename a tag
  159. *
  160. * @param string $old Old tag name
  161. * @param string $new New tag name
  162. * @return Zend_Service_Delicious Provides a fluent interface
  163. */
  164. public function renameTag($old, $new)
  165. {
  166. $response = $this->makeRequest(self::PATH_TAG_RENAME, array('old' => $old, 'new' => $new));
  167. self::_evalXmlResult($response);
  168. return $this;
  169. }
  170. /**
  171. * Get all bundles, returning an array with bundles as keys and array of tags as values
  172. *
  173. * @return array list of bundles
  174. */
  175. public function getBundles()
  176. {
  177. $response = $this->makeRequest(self::PATH_BUNDLES);
  178. $bundles = self::_xmlResponseToArray($response, 'bundles', 'bundle', 'name', 'tags');
  179. foreach ($bundles as &$tags) {
  180. $tags = explode(' ', $tags);
  181. }
  182. return $bundles;
  183. }
  184. /**
  185. * Adds a new bundle
  186. *
  187. * @param string $bundle Name of new bundle
  188. * @param array $tags Array of tags
  189. * @return Zend_Service_Delicious Provides a fluent interface
  190. */
  191. public function addBundle($bundle, array $tags)
  192. {
  193. $tags = implode(' ', (array) $tags);
  194. $response = $this->makeRequest(self::PATH_BUNDLE_ADD, array('bundle' => $bundle, 'tags' => $tags));
  195. self::_evalXmlResult($response);
  196. return $this;
  197. }
  198. /**
  199. * Delete a bundle
  200. *
  201. * @param string $bundle Name of bundle to be deleted
  202. * @return Zend_Service_Delicious Provides a fluent interface
  203. */
  204. public function deleteBundle($bundle)
  205. {
  206. $response = $this->makeRequest(self::PATH_BUNDLE_DELETE, array('bundle' => $bundle));
  207. self::_evalXmlResult($response);
  208. return $this;
  209. }
  210. /**
  211. * Delete a post
  212. *
  213. * @param string $url URL of post to be deleted
  214. * @return Zend_Service_Delicious Provides a fluent interface
  215. */
  216. public function deletePost($url)
  217. {
  218. $response = $this->makeRequest(self::PATH_POST_DELETE, array('url' => $url));
  219. self::_evalXmlResult($response);
  220. return $this;
  221. }
  222. /**
  223. * Get number of posts by date
  224. *
  225. * Returns array where keys are dates and values are numbers of posts
  226. *
  227. * @param string $tag Optional filtering by tag
  228. * @return array list of dates
  229. */
  230. public function getDates($tag = null)
  231. {
  232. $parms = array();
  233. if ($tag) {
  234. $parms['tag'] = $tag;
  235. }
  236. $response = $this->makeRequest(self::PATH_DATES, $parms);
  237. return self::_xmlResponseToArray($response, 'dates', 'date', 'date', 'count');
  238. }
  239. /**
  240. * Get posts matching the arguments
  241. *
  242. * If no date or url is given, most recent date will be used
  243. *
  244. * @param string $tag Optional filtering by tag
  245. * @param Zend_Date $dt Optional filtering by date
  246. * @param string $url Optional filtering by url
  247. * @throws Zend_Service_Delicious_Exception
  248. * @return Zend_Service_Delicious_PostList
  249. */
  250. public function getPosts($tag = null, Zend_Date $dt = null, $url = null)
  251. {
  252. $parms = array();
  253. if ($tag) {
  254. $parms['tag'] = $tag;
  255. }
  256. if ($url) {
  257. $parms['url'] = $url;
  258. }
  259. if ($dt) {
  260. $parms['dt'] = $dt->get('Y-m-d\TH:i:s\Z');
  261. }
  262. $response = $this->makeRequest(self::PATH_POSTS_GET, $parms);
  263. return $this->_parseXmlPostList($response);
  264. }
  265. /**
  266. * Get all posts
  267. *
  268. * @param string $tag Optional filtering by tag
  269. * @return Zend_Service_Delicious_PostList
  270. */
  271. public function getAllPosts($tag = null)
  272. {
  273. $parms = array();
  274. if ($tag) {
  275. $parms['tag'] = $tag;
  276. }
  277. $response = $this->makeRequest(self::PATH_POSTS_ALL, $parms);
  278. return $this->_parseXmlPostList($response);
  279. }
  280. /**
  281. * Get recent posts
  282. *
  283. * @param string $tag Optional filtering by tag
  284. * @param string $count Maximum number of posts to be returned (default 15)
  285. * @return Zend_Service_Delicious_PostList
  286. */
  287. public function getRecentPosts($tag = null, $count = 15)
  288. {
  289. $parms = array();
  290. if ($tag) {
  291. $parms['tag'] = $tag;
  292. }
  293. if ($count) {
  294. $parms['count'] = $count;
  295. }
  296. $response = $this->makeRequest(self::PATH_POSTS_RECENT, $parms);
  297. return $this->_parseXmlPostList($response);
  298. }
  299. /**
  300. * Create new post
  301. *
  302. * @return Zend_Service_Delicious_Post
  303. */
  304. public function createNewPost($title, $url)
  305. {
  306. return new Zend_Service_Delicious_Post($this, array('title' => $title, 'url' => $url));
  307. }
  308. /**
  309. * Get posts of a user
  310. *
  311. * @param string $user Owner of the posts
  312. * @param int $count Number of posts (default 15, max. 100)
  313. * @param string $tag Optional filtering by tag
  314. * @return Zend_Service_Delicious_PostList
  315. */
  316. public function getUserPosts($user, $count = null, $tag = null)
  317. {
  318. $parms = array();
  319. if ($count) {
  320. $parms['count'] = $count;
  321. }
  322. $path = sprintf(self::JSON_POSTS, $user, $tag);
  323. $res = $this->makeRequest($path, $parms, 'json');
  324. return new Zend_Service_Delicious_PostList($this, $res);
  325. }
  326. /**
  327. * Get tags of a user
  328. *
  329. * Returned array has tags as keys and number of posts as values
  330. *
  331. * @param string $user Owner of the posts
  332. * @param int $atleast Include only tags for which there are at least ### number of posts
  333. * @param int $count Number of tags to get (default all)
  334. * @param string $sort Order of returned tags ('alpha' || 'count')
  335. * @return array
  336. */
  337. public function getUserTags($user, $atleast = null, $count = null, $sort = 'alpha')
  338. {
  339. $parms = array();
  340. if ($atleast) {
  341. $parms['atleast'] = $atleast;
  342. }
  343. if ($count) {
  344. $parms['count'] = $count;
  345. }
  346. if ($sort) {
  347. $parms['sort'] = $sort;
  348. }
  349. $path = sprintf(self::JSON_TAGS, $user);
  350. return $this->makeRequest($path, $parms, 'json');
  351. }
  352. /**
  353. * Get network of a user
  354. *
  355. * @param string $user Owner of the network
  356. * @return array
  357. */
  358. public function getUserNetwork($user)
  359. {
  360. $path = sprintf(self::JSON_NETWORK, $user);
  361. return $this->makeRequest($path, array(), 'json');
  362. }
  363. /**
  364. * Get fans of a user
  365. *
  366. * @param string $user Owner of the fans
  367. * @return array
  368. */
  369. public function getUserFans($user)
  370. {
  371. $path = sprintf(self::JSON_FANS, $user);
  372. return $this->makeRequest($path, array(), 'json');
  373. }
  374. /**
  375. * Get details on a particular bookmarked URL
  376. *
  377. * Returned array contains four elements:
  378. * - hash - md5 hash of URL
  379. * - top_tags - array of tags and their respective usage counts
  380. * - url - URL for which details were returned
  381. * - total_posts - number of users that have bookmarked URL
  382. *
  383. * If URL hasen't been bookmarked null is returned.
  384. *
  385. * @param string $url URL for which to get details
  386. * @return array
  387. */
  388. public function getUrlDetails($url)
  389. {
  390. $parms = array('hash' => md5($url));
  391. $res = $this->makeRequest(self::JSON_URL, $parms, 'json');
  392. if(isset($res[0])) {
  393. return $res[0];
  394. } else {
  395. return null;
  396. }
  397. }
  398. /**
  399. * Handles all GET requests to a web service
  400. *
  401. * @param string $path Path
  402. * @param array $parms Array of GET parameters
  403. * @param string $type Type of a request ("xml"|"json")
  404. * @return mixed decoded response from web service
  405. * @throws Zend_Service_Delicious_Exception
  406. */
  407. public function makeRequest($path, array $parms = array(), $type = 'xml')
  408. {
  409. // if previous request was made less then 1 sec ago
  410. // wait until we can make a new request
  411. $timeDiff = microtime(true) - self::$_lastRequestTime;
  412. if ($timeDiff < 1) {
  413. usleep((1 - $timeDiff) * 1000000);
  414. }
  415. $this->_rest->getHttpClient()->setAuth($this->_authUname, $this->_authPass);
  416. switch ($type) {
  417. case 'xml':
  418. $this->_rest->setUri(self::API_URI);
  419. break;
  420. case 'json':
  421. $parms['raw'] = true;
  422. $this->_rest->setUri(self::JSON_URI);
  423. break;
  424. default:
  425. /**
  426. * @see Zend_Service_Delicious_Exception
  427. */
  428. require_once 'Zend/Service/Delicious/Exception.php';
  429. throw new Zend_Service_Delicious_Exception('Unknown request type');
  430. }
  431. self::$_lastRequestTime = microtime(true);
  432. $response = $this->_rest->restGet($path, $parms);
  433. if (!$response->isSuccessful()) {
  434. /**
  435. * @see Zend_Service_Delicious_Exception
  436. */
  437. require_once 'Zend/Service/Delicious/Exception.php';
  438. throw new Zend_Service_Delicious_Exception("Http client reported an error: '{$response->getMessage()}'");
  439. }
  440. $responseBody = $response->getBody();
  441. switch ($type) {
  442. case 'xml':
  443. $dom = new DOMDocument() ;
  444. if (!$dom = @Zend_Xml_Security::scan($responseBody, $dom)) {
  445. /**
  446. * @see Zend_Service_Delicious_Exception
  447. */
  448. require_once 'Zend/Service/Delicious/Exception.php';
  449. throw new Zend_Service_Delicious_Exception('XML Error');
  450. }
  451. return $dom;
  452. case 'json':
  453. return Zend_Json_Decoder::decode($responseBody);
  454. }
  455. }
  456. /**
  457. * Transform XML string to array
  458. *
  459. * @param DOMDocument $response
  460. * @param string $root Name of root tag
  461. * @param string $child Name of children tags
  462. * @param string $attKey Attribute of child tag to be used as a key
  463. * @param string $attValue Attribute of child tag to be used as a value
  464. * @return array
  465. * @throws Zend_Service_Delicious_Exception
  466. */
  467. private static function _xmlResponseToArray(DOMDocument $response, $root, $child, $attKey, $attValue)
  468. {
  469. $rootNode = $response->documentElement;
  470. $arrOut = array();
  471. if ($rootNode->nodeName == $root) {
  472. $childNodes = $rootNode->childNodes;
  473. for ($i = 0; $i < $childNodes->length; $i++) {
  474. $currentNode = $childNodes->item($i);
  475. if ($currentNode->nodeName == $child) {
  476. $arrOut[$currentNode->getAttribute($attKey)] = $currentNode->getAttribute($attValue);
  477. }
  478. }
  479. } else {
  480. /**
  481. * @see Zend_Service_Delicious_Exception
  482. */
  483. require_once 'Zend/Service/Delicious/Exception.php';
  484. throw new Zend_Service_Delicious_Exception('del.icio.us web service has returned something odd!');
  485. }
  486. return $arrOut;
  487. }
  488. /**
  489. * Constructs Zend_Service_Delicious_PostList from XML response
  490. *
  491. * @param DOMDocument $response
  492. * @return Zend_Service_Delicious_PostList
  493. * @throws Zend_Service_Delicious_Exception
  494. */
  495. private function _parseXmlPostList(DOMDocument $response)
  496. {
  497. $rootNode = $response->documentElement;
  498. if ($rootNode->nodeName == 'posts') {
  499. return new Zend_Service_Delicious_PostList($this, $rootNode->childNodes);
  500. } else {
  501. /**
  502. * @see Zend_Service_Delicious_Exception
  503. */
  504. require_once 'Zend/Service/Delicious/Exception.php';
  505. throw new Zend_Service_Delicious_Exception('del.icio.us web service has returned something odd!');
  506. }
  507. }
  508. /**
  509. * Evaluates XML response
  510. *
  511. * @param DOMDocument $response
  512. * @return void
  513. * @throws Zend_Service_Delicious_Exception
  514. */
  515. private static function _evalXmlResult(DOMDocument $response)
  516. {
  517. $rootNode = $response->documentElement;
  518. if ($rootNode && $rootNode->nodeName == 'result') {
  519. if ($rootNode->hasAttribute('code')) {
  520. $strResponse = $rootNode->getAttribute('code');
  521. } else {
  522. $strResponse = $rootNode->nodeValue;
  523. }
  524. if ($strResponse != 'done' && $strResponse != 'ok') {
  525. /**
  526. * @see Zend_Service_Delicious_Exception
  527. */
  528. require_once 'Zend/Service/Delicious/Exception.php';
  529. throw new Zend_Service_Delicious_Exception("del.icio.us web service: '{$strResponse}'");
  530. }
  531. } else {
  532. /**
  533. * @see Zend_Service_Delicious_Exception
  534. */
  535. require_once 'Zend/Service/Delicious/Exception.php';
  536. throw new Zend_Service_Delicious_Exception('del.icio.us web service has returned something odd!');
  537. }
  538. }
  539. }