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