2 /***************************************************************/
3 /* PhpDelicious - a library for accessing the del.ico.us API
5 Software License Agreement (BSD License)
7 Copyright (C) 2005-2008, Edward Eliot.
10 Redistribution and use in source and binary forms, with or without
11 modification, are permitted provided that the following conditions are met:
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.
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.
33 Last Updated: 20th January 2008 (see readme.txt)
35 /***************************************************************/
37 // include required files
38 require('xmlparser.inc.php');
39 require('cache.inc.php');
42 define('PHP_DELICIOUS_PROJECT_HOMEPAGE', 'http://www.ejeliot.com/projects/php-delicious/');
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/');
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
.')');
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)
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);
62 // folder to store cache files
63 define('PHP_DELICIOUS_CACHE_PATH', '/tmp/');
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;
73 /************************ constructor ************************/
75 public function __construct($sUsername, $sPassword, $iCacheTime = 10) {
77 $this->sUsername
= urlencode($sUsername);
78 $this->sPassword
= urlencode($sPassword);
79 $this->iCacheTime
= $iCacheTime;
81 // create instance of XML parser class
82 $this->oXmlParser
= new XmlParser();
85 /************************ private methods ************************/
87 protected function FromDeliciousDate($sDate) {
88 return trim(str_replace(array('T', 'Z'), ' ', $sDate));
91 protected function ToDeliciousDate($sDate) {
92 return date('Y-m-d\TH:i:s\Z', strtotime($sDate));
95 protected function GetBoolReturn($sInput) {
96 return ($sInput == 'done' || $sInput == 'ok');
99 protected function Delay() {
100 // could use microtime but not supported on all systems
101 if (!is_null($this->iLastRequest
) && time() - $this->iLastRequest
< 1) {
104 $this->iLastRequest
= time();
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')) {
112 $oCurl = curl_init($sCmd);
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"
123 if ($sResult = curl_exec($oCurl)) {
124 switch (curl_getinfo($oCurl, CURLINFO_HTTP_CODE
)) {
129 $this->iLastError
= PHP_DELICIOUS_ERR_THROTTLED
;
132 $this->iLastError
= PHP_DELICIOUS_ERR_INCORRECT_LOGIN
;
135 $this->iLastError
= PHP_DELICIOUS_ERR_CONNECTION_FAILED
;
144 ini_set('user_agent', PHP_DELICIOUS_USER_AGENT
);
146 // add basic auth details
147 $sCmd = str_replace('https://', "https://$this->sUsername:$this->sPassword@", $sCmd);
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
;
154 if (strstr($http_response_header[0], '401')) {
155 $this->iLastError
= PHP_DELICIOUS_ERR_INCORRECT_LOGIN
;
161 $this->iLastError
= PHP_DELICIOUS_ERR_CONNECTION_FAILED
;
167 protected function DeliciousRequest($sCmd, $aParameters = array()) {
168 if ($this->LastError() >= 1 && $this->LastError() <= 3) {
172 // reset the last error
173 $this->iLastError
= 0;
175 // construct URL - add username, password and command to run
176 $sCmd = PHP_DELICIOUS_BASE_URL
.$sCmd;
178 // check for parameters
179 if (count($aParameters) > 0) {
183 // add parameters to command
186 foreach ($aParameters as $sKey => $sValue) {
191 $sCmd .= "$sKey=".urlencode($sValue);
196 if ($sXml = $this->HttpRequest($sCmd)) {
197 // return result passed as array
198 if ($aXml = $this->oXmlParser
->Parse($sXml)) {
201 $this->iLastError
= PHP_DELICIOUS_ERR_XML_PARSE
;
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
);
211 if (!$oCache->Check()) {
212 if ($sCmd == 'posts/all' && $oCache->Exists()) {
213 $sLastUpdate = $this->GetLastUpdate();
215 $aData = $oCache->Get();
217 if ($aData['last-update'] == $sLastUpdate) {
218 $oCache->Set($aData);
219 return $aData['items'];
223 // initialise parameters array
224 $aParameters = array();
226 // check for optional parameters
228 $aParameters['tag'] = $sTag;
231 $aParameters['dt'] = $this->ToDeliciousDate($sDate);
234 $aParameters['url'] = $sUrl;
237 $aParameters['count'] = $iCount;
241 if ($aResult = $this->DeliciousRequest($sCmd, $aParameters)) {
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();
250 'url' => $aCurPost['attributes']['HREF'],
251 'desc' => $aCurPost['attributes']['DESCRIPTION'],
252 'notes' => $aCurPost['attributes']['EXTENDED'],
253 'hash' => $aCurPost['attributes']['HASH'],
255 'updated' => $this->FromDeliciousDate($aCurPost['attributes']['TIME'])
258 if ($sCmd == 'posts/get') {
259 $aNewPost['count'] = $aCurPost['attributes']['OTHERS'];
262 $aPosts['items'][] = $aNewPost;
264 $oCache->Set($aPosts);
269 $aData = $oCache->Get();
270 return $aData['items'];
273 /************************ public methods ************************/
275 public function LastError() { // alias to LastErrorNo for backwards compatibility
276 return $this->LastErrorNo();
279 public function LastErrorNo() {
280 return $this->iLastError
;
283 public function LastErrorString() {
284 switch ($this->iLastError
) {
286 return 'Connection to del.icio.us failed.';
288 return 'Incorrect del.icio.us username or password.';
290 return 'Del.icio.us API access throttled.';
292 return 'XML parse error has occurred.';
294 return 'An unknown error has occurred.';
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']);
308 public function GetAllTags() {
309 $oCache = new Cache($this->sUsername
.'tags/get', $this->iCacheTime
);
311 if (!$oCache->Check()) {
312 if ($aResult = $this->DeliciousRequest('tags/get')) {
314 foreach ($aResult['items'] as $aTag) {
316 'tag' => $aTag['attributes']['TAG'],
317 'count' => $aTag['attributes']['COUNT']
320 $oCache->Set($aTags);
325 return $oCache->Get();
328 public function RenameTag($sOld, $sNew) {
331 if ($aResult = $this->DeliciousRequest('tags/rename', array('old' => $sOld, 'new' => $sNew))) {
332 if ($aResult['content'] == 'done') {
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
344 return $this->GetList('posts/get', $sTag, $sDate, $sUrl);
347 public function GetRecentPosts(
348 $sTag = '', // filter by tag
349 $iCount = 15 // number of posts to retrieve, min 15, max 100
351 return $this->GetList('posts/recent', $sTag, '', '', $iCount);
354 public function GetAllPosts(
355 $sTag = '' // filter by tag
357 return $this->GetList('posts/all', $sTag, '', '', -1);
360 public function GetDates(
361 $sTag = '' // filter by tag
363 // set up cache object
364 $oCache = new Cache($this->sUsername
."posts/dates$sTag", $this->iCacheTime);
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))) {
372 foreach ($aResult['items'] as $aCurDate) {
374 'date' => $this->FromDeliciousDate($aCurDate['attributes']['DATE']),
375 'count' => $aCurDate['attributes']['COUNT']
378 $oCache->Set($aDates);
383 // return data from cache
384 return $oCache->Get();
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
397 $aParameters = array(
399 'description' => $sDescription,
400 'extended' => $sNotes,
401 'tags' => implode(' ', $aTags)
405 $aParameters['dt'] = $this->ToDeliciousDate($sDate);
408 $aParameters['replace'] = 'no';
411 if ($aResult = $this->DeliciousRequest('posts/add', $aParameters)) {
412 return $this->GetBoolReturn($aResult['attributes']['CODE']);
418 public function DeletePost($sUrl) {
421 if ($aResult = $this->DeliciousRequest('posts/delete', array('url' => $sUrl))) {
422 return $this->GetBoolReturn($aResult['attributes']['CODE']);
427 public function GetAllBundles() {
428 $oCache = new Cache($this->sUsername.'tags/bundles/all', $this->iCacheTime);
430 if (!$oCache->Check()) {
431 if ($aResult = $this->DeliciousRequest('tags/bundles/all')) {
433 foreach ($aResult['items'] as $aCurBundle) {
435 'name' => $aCurBundle['attributes']['NAME'],
436 'tags' => $aCurBundle['attributes']['TAGS']
439 $oCache->Set($aBundles);
444 return $oCache->Get();
447 public function AddBundle($sName, $aTags) {
450 if ($aResult = $this->DeliciousRequest('tags/bundles/set', array('bundle' => $sName, 'tags' => implode(' ', $aTags)))) {
451 return $this->GetBoolReturn($aResult['content']);
456 public function DeleteBundle($sName) {
459 if ($aResult = $this->DeliciousRequest('tags/bundles/delete', array('bundle' => $sName))) {
460 return $this->GetBoolReturn($aResult['content']);
465 // the remaining methods call the JSON API
467 public function GetUrlDetails(
468 $vUrls // this can take a single URL or an array of URLs (up to 15)
470 if (function_exists('json_decode')) {
471 $oCache = new Cache('url/data'.implode($vUrls), $this->iCacheTime);
473 if (!$oCache->Check()) {
474 $sUrl = PHP_DELICIOUS_JSON_URL.'url/data?';
476 if (is_array($vUrls)) {
477 foreach ($vUrls as $sCurrentUrl) {
478 $sUrl .= 'hash='.md5($sCurrentUrl).'&';
480 $sUrl .= rtrim($sUrl, '&');
482 $sUrl .= 'hash='.md5($vUrls);
485 if ($sJson = $this->HttpRequest($sUrl)) {
486 $oCache->Set(json_decode($sJson));
492 return $oCache->Get();
497 public function GetNetwork($sUsername) {
498 if (function_exists('json_decode')) {
499 $oCache = new Cache("network
/$sUsername", $this->iCacheTime
);
501 if (!$oCache->Check()) {
502 if ($sJson = $this->HttpRequest(PHP_DELICIOUS_JSON_URL
."network/$sUsername")) {
503 $oCache->Set(json_decode($sJson));
509 return $oCache->Get();
514 public function GetMyNetwork() {
515 return $this->GetNetwork($this->sUsername);
518 public function GetFans($sUsername) {
519 if (function_exists('json_decode')) {
520 $oCache = new Cache("fans
/$sUsername", $this->iCacheTime
);
522 if (!$oCache->Check()) {
523 if ($sJson = $this->HttpRequest(PHP_DELICIOUS_JSON_URL
."fans/$sUsername")) {
524 $oCache->Set(json_decode($sJson));
530 return $oCache->Get();
535 public function GetMyFans() {
536 return $this->GetFans($this->sUsername);