]> Tony Duckles's Git Repositories (git.nynim.org) - delicious-utils.git/blob - delicious-dump/php-delicious/php-delicious.inc.php
delicious-dump: Handle changed XML output format
[delicious-utils.git] / delicious-dump / php-delicious / php-delicious.inc.php
1 <?php
2 /***************************************************************/
3 /* PhpDelicious - a library for accessing the del.ico.us API
4
5 Software License Agreement (BSD License)
6
7 Copyright (C) 2005-2008, Edward Eliot.
8 All rights reserved.
9
10 Redistribution and use in source and binary forms, with or without
11 modification, are permitted provided that the following conditions are met:
12
13 * Redistributions of source code must retain the above copyright
14 notice, this list of conditions and the following disclaimer.
15 * Redistributions in binary form must reproduce the above copyright
16 notice, this list of conditions and the following disclaimer in the
17 documentation and/or other materials provided with the distribution.
18 * Neither the name of Edward Eliot nor the names of its contributors
19 may be used to endorse or promote products derived from this software
20 without specific prior written permission of Edward Eliot.
21
22 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS IS" AND ANY
23 EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
24 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
25 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY
26 DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
27 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
29 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
31 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32
33 Last Updated: 20th January 2008 (see readme.txt)
34 */
35 /***************************************************************/
36
37 // include required files
38 require('xmlparser.inc.php');
39 require('cache.inc.php');
40
41 // Project Homepage
42 define('PHP_DELICIOUS_PROJECT_HOMEPAGE', 'http://www.ejeliot.com/projects/php-delicious/');
43
44 // del.icio.us API base URL
45 define('PHP_DELICIOUS_BASE_URL', 'https://api.del.icio.us/v1/');
46 define('PHP_DELICIOUS_JSON_URL', 'http://badges.del.icio.us/feeds/json/');
47
48 // del.icio.us requires a custom user agent string - standard ones are likely to result in you being blocked
49 define('PHP_DELICIOUS_USER_AGENT', 'PhpDelicious v2.0 ('.PHP_DELICIOUS_PROJECT_HOMEPAGE.')');
50
51 define('PHP_DELICIOUS_CONNECT_TIMEOUT', 5); // specified in seconds
52 define('PHP_DELICIOUS_TRANSFER_TIMEOUT', 10); // specified in seconds, maximum time allowed for full transfer
53 define('PHP_DELICIOUS_DNS_TIMEOUT', 86400); // specified in seconds (1 day)
54
55 // error codes
56 define('PHP_DELICIOUS_ERR_CONNECTION_FAILED', 1);
57 define('PHP_DELICIOUS_ERR_INCORRECT_LOGIN', 2);
58 define('PHP_DELICIOUS_ERR_THROTTLED', 3);
59 define('PHP_DELICIOUS_ERR_XML_PARSE', 4);
60 define('PHP_DELICIOUS_ERR_UNKNOWN', 5);
61
62 // folder to store cache files
63 define('PHP_DELICIOUS_CACHE_PATH', '/tmp/');
64
65 class PhpDelicious {
66 protected $sUsername; // your del.icio.us username
67 protected $sPassword; // your del.icio.us password
68 protected $iCacheTime; // the length of time in seconds to cache retrieved data
69 protected $oXmlParser; // the XML parser object used to process del.icio.us returned data
70 protected $iLastRequest = null;
71 protected $iLastError = 0;
72
73 /************************ constructor ************************/
74
75 public function __construct($sUsername, $sPassword, $iCacheTime = 10) {
76 // assign parameters
77 $this->sUsername = urlencode($sUsername);
78 $this->sPassword = urlencode($sPassword);
79 $this->iCacheTime = $iCacheTime;
80
81 // create instance of XML parser class
82 $this->oXmlParser = new XmlParser();
83 }
84
85 /************************ private methods ************************/
86
87 protected function FromDeliciousDate($sDate) {
88 return trim(str_replace(array('T', 'Z'), ' ', $sDate));
89 }
90
91 protected function ToDeliciousDate($sDate) {
92 return date('Y-m-d\TH:i:s\Z', strtotime($sDate));
93 }
94
95 protected function GetBoolReturn($sInput) {
96 return ($sInput == 'done' || $sInput == 'ok');
97 }
98
99 protected function Delay() {
100 // could use microtime but not supported on all systems
101 if (!is_null($this->iLastRequest) && time() - $this->iLastRequest < 1) {
102 sleep(1);
103 } else {
104 $this->iLastRequest = time();
105 }
106 }
107
108 public function HttpRequest($sCmd) {
109 // check for curl lib, use in preference to file_get_contents if available
110 if (function_exists('curl_init')) {
111 // initiate session
112 $oCurl = curl_init($sCmd);
113 // set options
114 curl_setopt_array($oCurl, array(
115 CURLOPT_RETURNTRANSFER => true,
116 CURLOPT_USERAGENT => PHP_DELICIOUS_USER_AGENT,
117 CURLOPT_CONNECTTIMEOUT => PHP_DELICIOUS_CONNECT_TIMEOUT,
118 CURLOPT_TIMEOUT => PHP_DELICIOUS_TRANSFER_TIMEOUT,
119 CURLOPT_DNS_CACHE_TIMEOUT => PHP_DELICIOUS_DNS_TIMEOUT,
120 CURLOPT_USERPWD => "$this->sUsername:$this->sPassword"
121 ));
122 // request URL
123 if ($sResult = curl_exec($oCurl)) {
124 switch (curl_getinfo($oCurl, CURLINFO_HTTP_CODE)) {
125 case 200:
126 return $sResult;
127 break;
128 case 503:
129 $this->iLastError = PHP_DELICIOUS_ERR_THROTTLED;
130 break;
131 case 401:
132 $this->iLastError = PHP_DELICIOUS_ERR_INCORRECT_LOGIN;
133 break;
134 default:
135 $this->iLastError = PHP_DELICIOUS_ERR_CONNECTION_FAILED;
136 }
137 }
138 // close session
139 curl_close($oCurl);
140
141 return false;
142 } else {
143 // set user agent
144 ini_set('user_agent', PHP_DELICIOUS_USER_AGENT);
145
146 // add basic auth details
147 $sCmd = str_replace('https://', "https://$this->sUsername:$this->sPassword@", $sCmd);
148
149 // fopen_wrappers need to be enabled for this to work - see http://www.php.net/manual/en/function.file-get-contents.php
150 if ($sResult = @file_get_contents($sCmd)) {
151 if (strstr($http_response_header[0], '503')) {
152 $this->iLastError = PHP_DELICIOUS_ERR_THROTTLED;
153 } else {
154 if (strstr($http_response_header[0], '401')) {
155 $this->iLastError = PHP_DELICIOUS_ERR_INCORRECT_LOGIN;
156 } else {
157 return $sResult;
158 }
159 }
160 } else {
161 $this->iLastError = PHP_DELICIOUS_ERR_CONNECTION_FAILED;
162 }
163 }
164 return false;
165 }
166
167 protected function DeliciousRequest($sCmd, $aParameters = array()) {
168 if ($this->LastError() >= 1 && $this->LastError() <= 3) {
169 return false;
170 }
171
172 // reset the last error
173 $this->iLastError = 0;
174
175 // construct URL - add username, password and command to run
176 $sCmd = PHP_DELICIOUS_BASE_URL.$sCmd;
177
178 // check for parameters
179 if (count($aParameters) > 0) {
180 $sCmd .= '?';
181 }
182
183 // add parameters to command
184 $iCount = 0;
185
186 foreach ($aParameters as $sKey => $sValue) {
187 if ($sValue != '') {
188 if ($iCount > 0) {
189 $sCmd .= '&';
190 }
191 $sCmd .= "$sKey=".urlencode($sValue);
192 $iCount++;
193 }
194 }
195
196 if ($sXml = $this->HttpRequest($sCmd)) {
197 // return result passed as array
198 if ($aXml = $this->oXmlParser->Parse($sXml)) {
199 return $aXml;
200 } else {
201 $this->iLastError = PHP_DELICIOUS_ERR_XML_PARSE;
202 }
203 }
204 return false;
205 }
206
207 // generic function to get post listings
208 protected function GetList($sCmd, $sTag = '', $sDate = '', $sUrl = '', $iCount = -1) {
209 $oCache = new Cache($this->sUsername.$sCmd.$sTag.$sDate.$sUrl.$iCount, $this->iCacheTime);
210
211 if (!$oCache->Check()) {
212 if ($sCmd == 'posts/all' && $oCache->Exists()) {
213 $sLastUpdate = $this->GetLastUpdate();
214
215 $aData = $oCache->Get();
216
217 if ($aData['last-update'] == $sLastUpdate) {
218 $oCache->Set($aData);
219 return $aData['items'];
220 }
221 }
222
223 // initialise parameters array
224 $aParameters = array();
225
226 // check for optional parameters
227 if ($sTag != '') {
228 $aParameters['tag'] = $sTag;
229 }
230 if ($sDate != '') {
231 $aParameters['dt'] = $this->ToDeliciousDate($sDate);
232 }
233 if ($sUrl != '') {
234 $aParameters['url'] = $sUrl;
235 }
236 if ($iCount != -1) {
237 $aParameters['count'] = $iCount;
238 }
239
240 // make request
241 if ($aResult = $this->DeliciousRequest($sCmd, $aParameters)) {
242 $aPosts = array();
243 $aPosts['last-update'] = $this->FromDeliciousDate($aResult['attributes']['UPDATE']);
244 $aPosts['items'] = array();
245 foreach ($aResult['items'] as $aCurPost) {
246 // check absence of tags for current URL
247 $aCurPost['attributes']['TAG'] != 'system:unfiled' ? $aTags = explode(' ', $aCurPost['attributes']['TAG']) : $aTags = array();
248
249 $aNewPost = array(
250 'url' => $aCurPost['attributes']['HREF'],
251 'desc' => $aCurPost['attributes']['DESCRIPTION'],
252 'notes' => $aCurPost['attributes']['EXTENDED'],
253 'hash' => $aCurPost['attributes']['HASH'],
254 'tags' => $aTags,
255 'updated' => $this->FromDeliciousDate($aCurPost['attributes']['TIME'])
256 );
257
258 if ($sCmd == 'posts/get') {
259 $aNewPost['count'] = $aCurPost['attributes']['OTHERS'];
260 }
261
262 $aPosts['items'][] = $aNewPost;
263 }
264 $oCache->Set($aPosts);
265 } else {
266 $oCache->Set(false);
267 }
268 }
269 $aData = $oCache->Get();
270 return $aData['items'];
271 }
272
273 /************************ public methods ************************/
274
275 public function LastError() { // alias to LastErrorNo for backwards compatibility
276 return $this->LastErrorNo();
277 }
278
279 public function LastErrorNo() {
280 return $this->iLastError;
281 }
282
283 public function LastErrorString() {
284 switch ($this->iLastError) {
285 case 1:
286 return 'Connection to del.icio.us failed.';
287 case 2:
288 return 'Incorrect del.icio.us username or password.';
289 case 3:
290 return 'Del.icio.us API access throttled.';
291 case 4:
292 return 'XML parse error has occurred.';
293 case 5:
294 return 'An unknown error has occurred.';
295 default:
296 return '';
297 }
298 }
299
300 public function GetLastUpdate() {
301 // get last time the user updated their del.icio.us account
302 if ($aResult = $this->DeliciousRequest('posts/update')) {
303 return $this->FromDeliciousDate($aResult['attributes']['TIME']);
304 }
305 return false;
306 }
307
308 public function GetAllTags() {
309 $oCache = new Cache($this->sUsername.'tags/get', $this->iCacheTime);
310
311 if (!$oCache->Check()) {
312 if ($aResult = $this->DeliciousRequest('tags/get')) {
313 $aTags = array();
314 foreach ($aResult['items'] as $aTag) {
315 $aTags[] = array(
316 'tag' => $aTag['attributes']['TAG'],
317 'count' => $aTag['attributes']['COUNT']
318 );
319 }
320 $oCache->Set($aTags);
321 } else {
322 $oCache->Set(false);
323 }
324 }
325 return $oCache->Get();
326 }
327
328 public function RenameTag($sOld, $sNew) {
329 $this->Delay();
330
331 if ($aResult = $this->DeliciousRequest('tags/rename', array('old' => $sOld, 'new' => $sNew))) {
332 if ($aResult['content'] == 'done') {
333 return true;
334 }
335 }
336 return false;
337 }
338
339 public function GetPosts(
340 $sTag = '', // filter by tag
341 $sDate = '', // filter by date - format YYYY-MM-DD HH:MM:SS
342 $sUrl = '' // filter by URL
343 ) {
344 return $this->GetList('posts/get', $sTag, $sDate, $sUrl);
345 }
346
347 public function GetRecentPosts(
348 $sTag = '', // filter by tag
349 $iCount = 15 // number of posts to retrieve, min 15, max 100
350 ) {
351 return $this->GetList('posts/recent', $sTag, '', '', $iCount);
352 }
353
354 public function GetAllPosts(
355 $sTag = '' // filter by tag
356 ) {
357 return $this->GetList('posts/all', $sTag, '', '', -1);
358 }
359
360 public function GetDates(
361 $sTag = '' // filter by tag
362 ) {
363 // set up cache object
364 $oCache = new Cache($this->sUsername."posts/dates$sTag", $this->iCacheTime);
365
366 // check for cached data
367 if (!$oCache->Check()) {
368 // return number of posts for each date
369 if ($aResult = $this->DeliciousRequest('posts/dates', array('tag' => $sTag))) {
370 $aDates = array();
371
372 foreach ($aResult['items'] as $aCurDate) {
373 $aDates[] = array(
374 'date' => $this->FromDeliciousDate($aCurDate['attributes']['DATE']),
375 'count' => $aCurDate['attributes']['COUNT']
376 );
377 }
378 $oCache->Set($aDates);
379 } else {
380 $oCache->Set(false);
381 }
382 }
383 // return data from cache
384 return $oCache->Get();
385 }
386
387 public function AddPost(
388 $sUrl, // URL of post
389 $sDescription, // description of post
390 $sNotes = '', // additional notes relating to post
391 $aTags = array(), // tags to assign to the post
392 $sDate = '', // date of the post, format YYYY-MM-DD HH:MM:SS - default is current date and time
393 $bReplace = true // if set, any existing post with the same URL will be replaced
394 ) {
395 $this->Delay();
396
397 $aParameters = array(
398 'url' => $sUrl,
399 'description' => $sDescription,
400 'extended' => $sNotes,
401 'tags' => implode(' ', $aTags)
402 );
403
404 if ($sDate != '') {
405 $aParameters['dt'] = $this->ToDeliciousDate($sDate);
406 }
407 if (!$bReplace) {
408 $aParameters['replace'] = 'no';
409 }
410
411 if ($aResult = $this->DeliciousRequest('posts/add', $aParameters)) {
412 return $this->GetBoolReturn($aResult['attributes']['CODE']);
413 }
414
415 return false;
416 }
417
418 public function DeletePost($sUrl) {
419 $this->Delay();
420
421 if ($aResult = $this->DeliciousRequest('posts/delete', array('url' => $sUrl))) {
422 return $this->GetBoolReturn($aResult['attributes']['CODE']);
423 }
424 return false;
425 }
426
427 public function GetAllBundles() {
428 $oCache = new Cache($this->sUsername.'tags/bundles/all', $this->iCacheTime);
429
430 if (!$oCache->Check()) {
431 if ($aResult = $this->DeliciousRequest('tags/bundles/all')) {
432 $aBundles = array();
433 foreach ($aResult['items'] as $aCurBundle) {
434 $aBundles[] = array(
435 'name' => $aCurBundle['attributes']['NAME'],
436 'tags' => $aCurBundle['attributes']['TAGS']
437 );
438 }
439 $oCache->Set($aBundles);
440 } else {
441 $oCache->Set(false);
442 }
443 }
444 return $oCache->Get();
445 }
446
447 public function AddBundle($sName, $aTags) {
448 $this->Delay();
449
450 if ($aResult = $this->DeliciousRequest('tags/bundles/set', array('bundle' => $sName, 'tags' => implode(' ', $aTags)))) {
451 return $this->GetBoolReturn($aResult['content']);
452 }
453 return false;
454 }
455
456 public function DeleteBundle($sName) {
457 $this->Delay();
458
459 if ($aResult = $this->DeliciousRequest('tags/bundles/delete', array('bundle' => $sName))) {
460 return $this->GetBoolReturn($aResult['content']);
461 }
462 return false;
463 }
464
465 // the remaining methods call the JSON API
466
467 public function GetUrlDetails(
468 $vUrls // this can take a single URL or an array of URLs (up to 15)
469 ) {
470 if (function_exists('json_decode')) {
471 $oCache = new Cache('url/data'.implode($vUrls), $this->iCacheTime);
472
473 if (!$oCache->Check()) {
474 $sUrl = PHP_DELICIOUS_JSON_URL.'url/data?';
475
476 if (is_array($vUrls)) {
477 foreach ($vUrls as $sCurrentUrl) {
478 $sUrl .= 'hash='.md5($sCurrentUrl).'&';
479 }
480 $sUrl .= rtrim($sUrl, '&');
481 } else {
482 $sUrl .= 'hash='.md5($vUrls);
483 }
484
485 if ($sJson = $this->HttpRequest($sUrl)) {
486 $oCache->Set(json_decode($sJson));
487 } else {
488 $oCache->Set(false);
489 }
490 }
491
492 return $oCache->Get();
493 }
494 return false;
495 }
496
497 public function GetNetwork($sUsername) {
498 if (function_exists('json_decode')) {
499 $oCache = new Cache("network/$sUsername", $this->iCacheTime);
500
501 if (!$oCache->Check()) {
502 if ($sJson = $this->HttpRequest(PHP_DELICIOUS_JSON_URL."network/$sUsername")) {
503 $oCache->Set(json_decode($sJson));
504 } else {
505 $oCache->Set(false);
506 }
507 }
508
509 return $oCache->Get();
510 }
511 return false;
512 }
513
514 public function GetMyNetwork() {
515 return $this->GetNetwork($this->sUsername);
516 }
517
518 public function GetFans($sUsername) {
519 if (function_exists('json_decode')) {
520 $oCache = new Cache("fans/$sUsername", $this->iCacheTime);
521
522 if (!$oCache->Check()) {
523 if ($sJson = $this->HttpRequest(PHP_DELICIOUS_JSON_URL."fans/$sUsername")) {
524 $oCache->Set(json_decode($sJson));
525 } else {
526 $oCache->Set(false);
527 }
528 }
529
530 return $oCache->Get();
531 }
532 return false;
533 }
534
535 public function GetMyFans() {
536 return $this->GetFans($this->sUsername);
537 }
538 }
539 ?>