PageRenderTime 55ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/libraries/twitter/twitter.php

https://github.com/MilkZoft/zan
PHP | 930 lines | 773 code | 125 blank | 32 comment | 83 complexity | 85d3e6467db0e7cb40dd58177f8fb000 MD5 | raw file
Possible License(s): LGPL-2.1
  1. <?php
  2. class EpiOAuth
  3. {
  4. public $version = '1.0';
  5. protected $requestTokenUrl;
  6. protected $accessTokenUrl;
  7. protected $authenticateUrl;
  8. protected $authorizeUrl;
  9. protected $consumerKey;
  10. protected $consumerSecret;
  11. protected $token;
  12. protected $tokenSecret;
  13. protected $callback;
  14. protected $signatureMethod;
  15. protected $debug = false;
  16. protected $useSSL = false;
  17. protected $followLocation = false;
  18. protected $headers = array();
  19. protected $userAgent = 'EpiOAuth (http://github.com/jmathai/twitter-async/tree/)';
  20. protected $connectionTimeout = 5;
  21. protected $requestTimeout = 30;
  22. public function addHeader($header)
  23. {
  24. if(is_array($header) && !empty($header))
  25. $this->headers = array_merge($this->headers, $header);
  26. elseif(!empty($header))
  27. $this->headers[] = $header;
  28. }
  29. public function getAccessToken($params = null)
  30. {
  31. if (isset($_GET['oauth_verifier']) && !isset($params['oauth_verifier']))
  32. {
  33. $params['oauth_verifier'] = $_GET['oauth_verifier'];
  34. }
  35. $resp = $this->httpRequest('POST', $this->getUrl($this->accessTokenUrl), $params);
  36. return new EpiOAuthResponse($resp);
  37. }
  38. public function getAuthenticateUrl($token = null, $params = null)
  39. {
  40. $token = $token ? $token : $this->getRequestToken($params);
  41. if (is_object($token)) $token = $token->oauth_token;
  42. $addlParams = empty($params) ? '' : '&'.http_build_query($params, '', '&');
  43. return $this->getUrl($this->authenticateUrl) . '?oauth_token=' . $token . $addlParams;
  44. }
  45. public function getAuthorizeUrl($token = null, $params = null)
  46. {
  47. $token = $token ? $token : $this->getRequestToken($params);
  48. if (is_object($token)) $token = $token->oauth_token;
  49. return $this->getUrl($this->authorizeUrl) . '?oauth_token=' . $token;
  50. }
  51. // DEPRECATED in favor of getAuthorizeUrl()
  52. public function getAuthorizationUrl($token = null)
  53. {
  54. return $this->getAuthorizeUrl($token);
  55. }
  56. public function getRequestToken($params = null)
  57. {
  58. if (isset($this->callback) && !isset($params['oauth_callback']))
  59. {
  60. $params['oauth_callback'] = $this->callback;
  61. }
  62. $resp = $this->httpRequest('POST', $this->getUrl($this->requestTokenUrl), $params);
  63. return new EpiOAuthResponse($resp);
  64. }
  65. public function getUrl($url)
  66. {
  67. if($this->useSSL === true)
  68. return preg_replace('/^http:/', 'https:', $url);
  69. return $url;
  70. }
  71. public function httpRequest($method = null, $url = null, $params = null, $isMultipart = false)
  72. {
  73. if(empty($method) || empty($url))
  74. return false;
  75. if(empty($params['oauth_signature']))
  76. $params = $this->prepareParameters($method, $url, $params);
  77. switch($method)
  78. {
  79. case 'GET':
  80. return $this->httpGet($url, $params);
  81. break;
  82. case 'POST':
  83. return $this->httpPost($url, $params, $isMultipart);
  84. break;
  85. case 'DELETE':
  86. return $this->httpDelete($url, $params);
  87. break;
  88. }
  89. }
  90. public function setDebug($bool=false)
  91. {
  92. $this->debug = (bool)$bool;
  93. }
  94. public function setFollowLocation($bool)
  95. {
  96. $this->followLocation = (bool)$bool;
  97. }
  98. public function setTimeout($requestTimeout = null, $connectionTimeout = null)
  99. {
  100. if($requestTimeout !== null)
  101. $this->requestTimeout = floatval($requestTimeout);
  102. if($connectionTimeout !== null)
  103. $this->connectionTimeout = floatval($connectionTimeout);
  104. }
  105. public function setToken($token = null, $secret = null)
  106. {
  107. $this->token = $token;
  108. $this->tokenSecret = $secret;
  109. }
  110. public function setCallback($callback = null)
  111. {
  112. $this->callback = $callback;
  113. }
  114. public function useSSL($use = false)
  115. {
  116. $this->useSSL = (bool)$use;
  117. }
  118. protected function addDefaultHeaders($url, $oauthHeaders)
  119. {
  120. $_h = array('Expect:');
  121. $urlParts = parse_url($url);
  122. $oauth = 'Authorization: OAuth realm="' . $urlParts['scheme'] . '://' . $urlParts['host'] . $urlParts['path'] . '",';
  123. foreach($oauthHeaders as $name => $value)
  124. {
  125. $oauth .= "{$name}=\"{$value}\",";
  126. }
  127. $_h[] = substr($oauth, 0, -1);
  128. $_h[] = "User-Agent: {$this->userAgent}";
  129. $this->addHeader($_h);
  130. }
  131. protected function buildHttpQueryRaw($params)
  132. {
  133. $retval = '';
  134. foreach((array)$params as $key => $value)
  135. $retval .= "{$key}={$value}&";
  136. $retval = substr($retval, 0, -1);
  137. return $retval;
  138. }
  139. protected function curlInit($url)
  140. {
  141. $ch = curl_init($url);
  142. curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  143. curl_setopt($ch, CURLOPT_HTTPHEADER, $this->headers);
  144. curl_setopt($ch, CURLOPT_TIMEOUT, $this->requestTimeout);
  145. curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $this->connectionTimeout);
  146. curl_setopt($ch, CURLOPT_ENCODING, '');
  147. if($this->followLocation)
  148. curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
  149. if(isset($_SERVER ['SERVER_ADDR']) && !empty($_SERVER['SERVER_ADDR']) && $_SERVER['SERVER_ADDR'] != '127.0.0.1')
  150. curl_setopt($ch, CURLOPT_INTERFACE, $_SERVER ['SERVER_ADDR']);
  151. // if the certificate exists then use it, else bypass ssl checks
  152. if(file_exists($cert = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'ca-bundle.crt'))
  153. {
  154. curl_setopt($ch, CURLOPT_CAINFO, $cert);
  155. curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
  156. curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
  157. }
  158. else
  159. {
  160. curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
  161. curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
  162. }
  163. return $ch;
  164. }
  165. protected function emptyHeaders()
  166. {
  167. $this->headers = array();
  168. }
  169. protected function encode_rfc3986($string)
  170. {
  171. return str_replace('+', ' ', str_replace('%7E', '~', rawurlencode(($string))));
  172. }
  173. protected function generateNonce()
  174. {
  175. if(isset($this->nonce)) // for unit testing
  176. return $this->nonce;
  177. return md5(uniqid(rand(), true));
  178. }
  179. // parameters should already have been passed through prepareParameters
  180. // no need to double encode
  181. protected function generateSignature($method = null, $url = null, $params = null)
  182. {
  183. if(empty($method) || empty($url))
  184. return false;
  185. // concatenating and encode
  186. $concatenatedParams = $this->encode_rfc3986($this->buildHttpQueryRaw($params));
  187. // normalize url
  188. $normalizedUrl = $this->encode_rfc3986($this->normalizeUrl($url));
  189. $method = $this->encode_rfc3986($method); // don't need this but why not?
  190. $signatureBaseString = "{$method}&{$normalizedUrl}&{$concatenatedParams}";
  191. return $this->signString($signatureBaseString);
  192. }
  193. protected function executeCurl($ch)
  194. {
  195. if($this->isAsynchronous)
  196. return $this->curl->addCurl($ch);
  197. else
  198. return $this->curl->addEasyCurl($ch);
  199. }
  200. protected function httpDelete($url, $params) {
  201. $this->addDefaultHeaders($url, $params['oauth']);
  202. $ch = $this->curlInit($url);
  203. curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
  204. curl_setopt($ch, CURLOPT_POSTFIELDS, $this->buildHttpQueryRaw($params['request']));
  205. $resp = $this->executeCurl($ch);
  206. $this->emptyHeaders();
  207. return $resp;
  208. }
  209. protected function httpGet($url, $params = null)
  210. {
  211. if(count($params['request']) > 0)
  212. {
  213. $url .= '?';
  214. foreach($params['request'] as $k => $v)
  215. {
  216. $url .= "{$k}={$v}&";
  217. }
  218. $url = substr($url, 0, -1);
  219. }
  220. $this->addDefaultHeaders($url, $params['oauth']);
  221. $ch = $this->curlInit($url);
  222. $resp = $this->executeCurl($ch);
  223. $this->emptyHeaders();
  224. return $resp;
  225. }
  226. protected function httpPost($url, $params = null, $isMultipart)
  227. {
  228. $this->addDefaultHeaders($url, $params['oauth']);
  229. $ch = $this->curlInit($url);
  230. curl_setopt($ch, CURLOPT_POST, 1);
  231. // php's curl extension automatically sets the content type
  232. // based on whether the params are in string or array form
  233. if($isMultipart)
  234. curl_setopt($ch, CURLOPT_POSTFIELDS, $params['request']);
  235. else
  236. curl_setopt($ch, CURLOPT_POSTFIELDS, $this->buildHttpQueryRaw($params['request']));
  237. $resp = $this->executeCurl($ch);
  238. $this->emptyHeaders();
  239. return $resp;
  240. }
  241. protected function normalizeUrl($url = null)
  242. {
  243. $urlParts = parse_url($url);
  244. $scheme = strtolower($urlParts['scheme']);
  245. $host = strtolower($urlParts['host']);
  246. $port = isset($urlParts['port']) ? intval($urlParts['port']) : 0;
  247. $retval = strtolower($scheme) . '://' . strtolower($host);
  248. if(!empty($port) && (($scheme === 'http' && $port != 80) || ($scheme === 'https' && $port != 443)))
  249. $retval .= ":{$port}";
  250. $retval .= $urlParts['path'];
  251. if(!empty($urlParts['query']))
  252. {
  253. $retval .= "?{$urlParts['query']}";
  254. }
  255. return $retval;
  256. }
  257. protected function isMultipart($params = null)
  258. {
  259. if($params)
  260. {
  261. foreach($params as $k => $v)
  262. {
  263. if(strncmp('@',$k,1) === 0)
  264. return true;
  265. }
  266. }
  267. return false;
  268. }
  269. protected function prepareParameters($method = null, $url = null, $params = null)
  270. {
  271. if(empty($method) || empty($url))
  272. return false;
  273. $oauth['oauth_consumer_key'] = $this->consumerKey;
  274. $oauth['oauth_token'] = $this->token;
  275. $oauth['oauth_nonce'] = $this->generateNonce();
  276. $oauth['oauth_timestamp'] = !isset($this->timestamp) ? time() : $this->timestamp; // for unit test
  277. $oauth['oauth_signature_method'] = $this->signatureMethod;
  278. if(isset($params['oauth_verifier']))
  279. {
  280. $oauth['oauth_verifier'] = $params['oauth_verifier'];
  281. unset($params['oauth_verifier']);
  282. }
  283. $oauth['oauth_version'] = $this->version;
  284. // encode all oauth values
  285. foreach($oauth as $k => $v)
  286. $oauth[$k] = $this->encode_rfc3986($v);
  287. // encode all non '@' params
  288. // keep sigParams for signature generation (exclude '@' params)
  289. // rename '@key' to 'key'
  290. $sigParams = array();
  291. $hasFile = false;
  292. if(is_array($params))
  293. {
  294. foreach($params as $k => $v)
  295. {
  296. if(strncmp('@',$k,1) !== 0)
  297. {
  298. $sigParams[$k] = $this->encode_rfc3986($v);
  299. $params[$k] = $this->encode_rfc3986($v);
  300. }
  301. else
  302. {
  303. $params[substr($k, 1)] = $v;
  304. unset($params[$k]);
  305. $hasFile = true;
  306. }
  307. }
  308. if($hasFile === true)
  309. $sigParams = array();
  310. }
  311. $sigParams = array_merge($oauth, (array)$sigParams);
  312. // sorting
  313. ksort($sigParams);
  314. // signing
  315. $oauth['oauth_signature'] = $this->encode_rfc3986($this->generateSignature($method, $url, $sigParams));
  316. return array('request' => $params, 'oauth' => $oauth);
  317. }
  318. protected function signString($string = null)
  319. {
  320. $retval = false;
  321. switch($this->signatureMethod)
  322. {
  323. case 'HMAC-SHA1':
  324. $key = $this->encode_rfc3986($this->consumerSecret) . '&' . $this->encode_rfc3986($this->tokenSecret);
  325. $retval = base64_encode(hash_hmac('sha1', $string, $key, true));
  326. break;
  327. }
  328. return $retval;
  329. }
  330. public function __construct($consumerKey, $consumerSecret, $signatureMethod='HMAC-SHA1')
  331. {
  332. $this->consumerKey = $consumerKey;
  333. $this->consumerSecret = $consumerSecret;
  334. $this->signatureMethod = $signatureMethod;
  335. $this->curl = EpiCurl::getInstance();
  336. }
  337. }
  338. class EpiOAuthResponse
  339. {
  340. private $__resp;
  341. protected $debug = false;
  342. public function __construct($resp)
  343. {
  344. $this->__resp = $resp;
  345. }
  346. public function __get($name)
  347. {
  348. if($this->__resp->code != 200)
  349. EpiOAuthException::raise($this->__resp, $this->debug);
  350. parse_str($this->__resp->data, $result);
  351. foreach($result as $k => $v)
  352. {
  353. $this->$k = $v;
  354. }
  355. return isset($result[$name]) ? $result[$name] : null;
  356. }
  357. public function __toString()
  358. {
  359. return $this->__resp->data;
  360. }
  361. }
  362. class EpiOAuthException extends Exception
  363. {
  364. public static function raise($response, $debug)
  365. {
  366. $message = $response->responseText;
  367. switch($response->code)
  368. {
  369. case 400:
  370. throw new EpiOAuthBadRequestException($message, $response->code);
  371. case 401:
  372. throw new EpiOAuthUnauthorizedException($message, $response->code);
  373. default:
  374. throw new EpiOAuthException($message, $response->code);
  375. }
  376. }
  377. }
  378. class EpiOAuthBadRequestException extends EpiOAuthException{}
  379. class EpiOAuthUnauthorizedException extends EpiOAuthException{}
  380. class EpiTwitter extends EpiOAuth
  381. {
  382. const EPITWITTER_SIGNATURE_METHOD = 'HMAC-SHA1';
  383. const EPITWITTER_AUTH_OAUTH = 'oauth';
  384. const EPITWITTER_AUTH_BASIC = 'basic';
  385. protected $requestTokenUrl= 'https://api.twitter.com/oauth/request_token';
  386. protected $accessTokenUrl = 'https://api.twitter.com/oauth/access_token';
  387. protected $authorizeUrl = 'https://api.twitter.com/oauth/authorize';
  388. protected $authenticateUrl= 'https://api.twitter.com/oauth/authenticate';
  389. protected $apiUrl = 'https://api.twitter.com';
  390. protected $userAgent = 'EpiTwitter (http://github.com/jmathai/twitter-async/tree/)';
  391. protected $apiVersion = '1.1';
  392. protected $isAsynchronous = false;
  393. /**
  394. * The Twitter API version 1.0 search URL.
  395. * @var string
  396. */
  397. protected $searchUrl = 'https://search.twitter.com';
  398. /* OAuth methods */
  399. public function delete($endpoint, $params = null)
  400. {
  401. return $this->request('DELETE', $endpoint, $params);
  402. }
  403. public function get($endpoint, $params = null)
  404. {
  405. return $this->request('GET', $endpoint, $params);
  406. }
  407. public function post($endpoint, $params = null)
  408. {
  409. return $this->request('POST', $endpoint, $params);
  410. }
  411. /* Basic auth methods */
  412. public function delete_basic($endpoint, $params = null, $username = null, $password = null)
  413. {
  414. return $this->request_basic('DELETE', $endpoint, $params, $username, $password);
  415. }
  416. public function get_basic($endpoint, $params = null, $username = null, $password = null)
  417. {
  418. return $this->request_basic('GET', $endpoint, $params, $username, $password);
  419. }
  420. public function post_basic($endpoint, $params = null, $username = null, $password = null)
  421. {
  422. return $this->request_basic('POST', $endpoint, $params, $username, $password);
  423. }
  424. public function useApiUrl($url = '')
  425. {
  426. $this->apiUrl = rtrim( $url, '/' );
  427. }
  428. public function useApiVersion($version = null)
  429. {
  430. $this->apiVersion = $version;
  431. }
  432. public function useAsynchronous($async = true)
  433. {
  434. $this->isAsynchronous = (bool)$async;
  435. }
  436. public function __construct($consumerKey = null, $consumerSecret = null, $oauthToken = null, $oauthTokenSecret = null)
  437. {
  438. parent::__construct($consumerKey, $consumerSecret, self::EPITWITTER_SIGNATURE_METHOD);
  439. $this->setToken($oauthToken, $oauthTokenSecret);
  440. }
  441. public function __call($name, $params = null/*, $username, $password*/)
  442. {
  443. $parts = explode('_', $name);
  444. $method = strtoupper(array_shift($parts));
  445. $parts = implode('_', $parts);
  446. $endpoint = '/' . preg_replace('/[A-Z]|[0-9]+/e', "'/'.strtolower('\\0')", $parts) . '.json';
  447. /* HACK: this is required for list support that starts with a user id */
  448. $endpoint = str_replace('//','/',$endpoint);
  449. $args = !empty($params) ? array_shift($params) : null;
  450. // calls which do not have a consumerKey are assumed to not require authentication
  451. if($this->consumerKey === null)
  452. {
  453. $username = null;
  454. $password = null;
  455. if(!empty($params))
  456. {
  457. $username = array_shift($params);
  458. $password = !empty($params) ? array_shift($params) : null;
  459. }
  460. return $this->request_basic($method, $endpoint, $args, $username, $password);
  461. }
  462. return $this->request($method, $endpoint, $args);
  463. }
  464. private function getApiUrl($endpoint)
  465. {
  466. if ($this->apiVersion === '1' && preg_match('@^/search[./]?(?=(json|daily|current|weekly))@', $endpoint))
  467. {
  468. return $this->searchUrl.$endpoint;
  469. }
  470. return $this->apiUrl.'/'.$this->apiVersion.$endpoint;
  471. }
  472. private function request($method, $endpoint, $params = null)
  473. {
  474. $url = $this->getUrl($this->getApiUrl($endpoint));
  475. $resp= new EpiTwitterJson(call_user_func(array($this, 'httpRequest'), $method, $url, $params, $this->isMultipart($params)), $this->debug);
  476. if(!$this->isAsynchronous)
  477. $resp->response;
  478. return $resp;
  479. }
  480. private function request_basic($method, $endpoint, $params = null, $username = null, $password = null)
  481. {
  482. $url = $this->getApiUrl($endpoint);
  483. if($method === 'GET')
  484. $url .= is_null($params) ? '' : '?'.http_build_query($params, '', '&');
  485. $ch = curl_init($url);
  486. curl_setopt($ch, CURLOPT_HTTPHEADER, array('Expect:'));
  487. curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  488. curl_setopt($ch, CURLOPT_TIMEOUT, $this->requestTimeout);
  489. curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
  490. if($method === 'POST' && $params !== null)
  491. {
  492. if($this->isMultipart($params))
  493. curl_setopt($ch, CURLOPT_POSTFIELDS, $params);
  494. else
  495. curl_setopt($ch, CURLOPT_POSTFIELDS, $this->buildHttpQueryRaw($params));
  496. }
  497. if(!empty($username) && !empty($password))
  498. curl_setopt($ch, CURLOPT_USERPWD, "{$username}:{$password}");
  499. $resp = new EpiTwitterJson(EpiCurl::getInstance()->addCurl($ch), $this->debug);
  500. if(!$this->isAsynchronous)
  501. $resp->response;
  502. return $resp;
  503. }
  504. }
  505. class EpiTwitterJson implements ArrayAccess, Countable, IteratorAggregate
  506. {
  507. private $debug;
  508. private $__resp;
  509. public function __construct($response, $debug = false)
  510. {
  511. $this->__resp = $response;
  512. $this->debug = $debug;
  513. }
  514. // ensure that calls complete by blocking for results, NOOP if already returned
  515. public function __destruct()
  516. {
  517. $this->responseText;
  518. }
  519. // Implementation of the IteratorAggregate::getIterator() to support foreach ($this as $...)
  520. public function getIterator ()
  521. {
  522. if ($this->__obj) {
  523. return new ArrayIterator($this->__obj);
  524. } else {
  525. return new ArrayIterator($this->response);
  526. }
  527. }
  528. // Implementation of Countable::count() to support count($this)
  529. public function count ()
  530. {
  531. return count($this->response);
  532. }
  533. // Next four functions are to support ArrayAccess interface
  534. // 1
  535. public function offsetSet($offset, $value)
  536. {
  537. $this->response[$offset] = $value;
  538. }
  539. // 2
  540. public function offsetExists($offset)
  541. {
  542. return isset($this->response[$offset]);
  543. }
  544. // 3
  545. public function offsetUnset($offset)
  546. {
  547. unset($this->response[$offset]);
  548. }
  549. // 4
  550. public function offsetGet($offset)
  551. {
  552. return isset($this->response[$offset]) ? $this->response[$offset] : null;
  553. }
  554. public function __get($name)
  555. {
  556. $accessible = array('responseText'=>1,'headers'=>1,'code'=>1);
  557. $this->responseText = $this->__resp->data;
  558. $this->headers = $this->__resp->headers;
  559. $this->code = $this->__resp->code;
  560. if(isset($accessible[$name]) && $accessible[$name])
  561. return $this->$name;
  562. elseif(($this->code < 200 || $this->code >= 400) && !isset($accessible[$name]))
  563. EpiTwitterException::raise($this->__resp, $this->debug);
  564. // Call appears ok so we can fill in the response
  565. $this->response = json_decode($this->responseText, 1);
  566. $this->__obj = json_decode($this->responseText);
  567. if(gettype($this->__obj) === 'object')
  568. {
  569. foreach($this->__obj as $k => $v)
  570. {
  571. $this->$k = $v;
  572. }
  573. }
  574. if (property_exists($this, $name)) {
  575. return $this->$name;
  576. }
  577. return null;
  578. }
  579. public function __isset($name)
  580. {
  581. $value = self::__get($name);
  582. return !empty($name);
  583. }
  584. }
  585. class EpiTwitterException extends Exception
  586. {
  587. public static function raise($response, $debug)
  588. {
  589. $message = $response->data;
  590. switch($response->code)
  591. {
  592. case 400:
  593. throw new EpiTwitterBadRequestException($message, $response->code);
  594. case 401:
  595. throw new EpiTwitterNotAuthorizedException($message, $response->code);
  596. case 403:
  597. throw new EpiTwitterForbiddenException($message, $response->code);
  598. case 404:
  599. throw new EpiTwitterNotFoundException($message, $response->code);
  600. case 406:
  601. throw new EpiTwitterNotAcceptableException($message, $response->code);
  602. case 420:
  603. throw new EpiTwitterEnhanceYourCalmException($message, $response->code);
  604. case 500:
  605. throw new EpiTwitterInternalServerException($message, $response->code);
  606. case 502:
  607. throw new EpiTwitterBadGatewayException($message, $response->code);
  608. case 503:
  609. throw new EpiTwitterServiceUnavailableException($message, $response->code);
  610. default:
  611. throw new EpiTwitterException($message, $response->code);
  612. }
  613. }
  614. }
  615. class EpiTwitterBadRequestException extends EpiTwitterException{}
  616. class EpiTwitterNotAuthorizedException extends EpiTwitterException{}
  617. class EpiTwitterForbiddenException extends EpiTwitterException{}
  618. class EpiTwitterNotFoundException extends EpiTwitterException{}
  619. class EpiTwitterNotAcceptableException extends EpiTwitterException{}
  620. class EpiTwitterEnhanceYourCalmException extends EpiTwitterException{}
  621. class EpiTwitterInternalServerException extends EpiTwitterException{}
  622. class EpiTwitterBadGatewayException extends EpiTwitterException{}
  623. class EpiTwitterServiceUnavailableException extends EpiTwitterException{}
  624. class EpiCurl
  625. {
  626. const timeout = 3;
  627. static $inst = null;
  628. static $singleton = 0;
  629. private $mc;
  630. private $msgs;
  631. private $running;
  632. private $execStatus;
  633. private $selectStatus;
  634. private $sleepIncrement = 1.1;
  635. private $requests = array();
  636. private $responses = array();
  637. private $properties = array();
  638. private static $timers = array();
  639. function __construct()
  640. {
  641. if(self::$singleton == 0)
  642. {
  643. throw new Exception('This class cannot be instantiated by the new keyword. You must instantiate it using: $obj = EpiCurl::getInstance();');
  644. }
  645. $this->mc = curl_multi_init();
  646. $this->properties = array(
  647. 'code' => CURLINFO_HTTP_CODE,
  648. 'time' => CURLINFO_TOTAL_TIME,
  649. 'length'=> CURLINFO_CONTENT_LENGTH_DOWNLOAD,
  650. 'type' => CURLINFO_CONTENT_TYPE,
  651. 'url' => CURLINFO_EFFECTIVE_URL
  652. );
  653. }
  654. public function addEasyCurl($ch)
  655. {
  656. $key = $this->getKey($ch);
  657. $this->requests[$key] = $ch;
  658. curl_setopt($ch, CURLOPT_HEADERFUNCTION, array($this, 'headerCallback'));
  659. $done = array('handle' => $ch);
  660. $this->storeResponse($done, false);
  661. $this->startTimer($key);
  662. return new EpiCurlManager($key);
  663. }
  664. public function addCurl($ch)
  665. {
  666. $key = $this->getKey($ch);
  667. $this->requests[$key] = $ch;
  668. curl_setopt($ch, CURLOPT_HEADERFUNCTION, array($this, 'headerCallback'));
  669. $code = curl_multi_add_handle($this->mc, $ch);
  670. $this->startTimer($key);
  671. // (1)
  672. if($code === CURLM_OK || $code === CURLM_CALL_MULTI_PERFORM)
  673. {
  674. do {
  675. $code = $this->execStatus = curl_multi_exec($this->mc, $this->running);
  676. } while ($this->execStatus === CURLM_CALL_MULTI_PERFORM);
  677. return new EpiCurlManager($key);
  678. }
  679. else
  680. {
  681. return $code;
  682. }
  683. }
  684. public function getResult($key = null)
  685. {
  686. if($key != null)
  687. {
  688. if(isset($this->responses[$key]))
  689. {
  690. return $this->responses[$key];
  691. }
  692. $innerSleepInt = $outerSleepInt = 1;
  693. while($this->running && ($this->execStatus == CURLM_OK || $this->execStatus == CURLM_CALL_MULTI_PERFORM))
  694. {
  695. usleep(intval($outerSleepInt));
  696. $outerSleepInt = intval(max(1, ($outerSleepInt*$this->sleepIncrement)));
  697. $ms=curl_multi_select($this->mc, 0);
  698. if($ms > 0)
  699. {
  700. do{
  701. $this->execStatus = curl_multi_exec($this->mc, $this->running);
  702. usleep(intval($innerSleepInt));
  703. $innerSleepInt = intval(max(1, ($innerSleepInt*$this->sleepIncrement)));
  704. }while($this->execStatus==CURLM_CALL_MULTI_PERFORM);
  705. $innerSleepInt = 1;
  706. }
  707. $this->storeResponses();
  708. if(isset($this->responses[$key]['data']))
  709. {
  710. return $this->responses[$key];
  711. }
  712. $runningCurrent = $this->running;
  713. }
  714. return null;
  715. }
  716. return false;
  717. }
  718. public static function getSequence()
  719. {
  720. return new EpiSequence(self::$timers);
  721. }
  722. public static function getTimers()
  723. {
  724. return self::$timers;
  725. }
  726. private function getKey($ch)
  727. {
  728. return (string)$ch;
  729. }
  730. private function headerCallback($ch, $header)
  731. {
  732. $_header = trim($header);
  733. $colonPos= strpos($_header, ':');
  734. if($colonPos > 0)
  735. {
  736. $key = substr($_header, 0, $colonPos);
  737. $val = preg_replace('/^\W+/','',substr($_header, $colonPos));
  738. $this->responses[$this->getKey($ch)]['headers'][$key] = $val;
  739. }
  740. return strlen($header);
  741. }
  742. private function storeResponses()
  743. {
  744. while($done = curl_multi_info_read($this->mc))
  745. {
  746. $this->storeResponse($done);
  747. }
  748. }
  749. private function storeResponse($done, $isAsynchronous = true)
  750. {
  751. $key = $this->getKey($done['handle']);
  752. $this->stopTimer($key, $done);
  753. if($isAsynchronous)
  754. $this->responses[$key]['data'] = curl_multi_getcontent($done['handle']);
  755. else
  756. $this->responses[$key]['data'] = curl_exec($done['handle']);
  757. foreach($this->properties as $name => $const)
  758. {
  759. $this->responses[$key][$name] = curl_getinfo($done['handle'], $const);
  760. }
  761. if($isAsynchronous)
  762. curl_multi_remove_handle($this->mc, $done['handle']);
  763. curl_close($done['handle']);
  764. }
  765. private function startTimer($key)
  766. {
  767. self::$timers[$key]['start'] = microtime(true);
  768. }
  769. private function stopTimer($key, $done)
  770. {
  771. self::$timers[$key]['end'] = microtime(true);
  772. self::$timers[$key]['api'] = curl_getinfo($done['handle'], CURLINFO_EFFECTIVE_URL);
  773. self::$timers[$key]['time'] = curl_getinfo($done['handle'], CURLINFO_TOTAL_TIME);
  774. self::$timers[$key]['code'] = curl_getinfo($done['handle'], CURLINFO_HTTP_CODE);
  775. }
  776. static function getInstance()
  777. {
  778. if(self::$inst == null)
  779. {
  780. self::$singleton = 1;
  781. self::$inst = new EpiCurl();
  782. }
  783. return self::$inst;
  784. }
  785. }
  786. class EpiCurlManager
  787. {
  788. private $key;
  789. private $epiCurl;
  790. public function __construct($key)
  791. {
  792. $this->key = $key;
  793. $this->epiCurl = EpiCurl::getInstance();
  794. }
  795. public function __get($name)
  796. {
  797. $responses = $this->epiCurl->getResult($this->key);
  798. return isset($responses[$name]) ? $responses[$name] : null;
  799. }
  800. public function __isset($name)
  801. {
  802. $val = self::__get($name);
  803. return empty($val);
  804. }
  805. }