PageRenderTime 63ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/Lampcms/Twitter.php

https://github.com/snytkine/LampCMS
PHP | 557 lines | 202 code | 84 blank | 271 comment | 33 complexity | fb4f71713a41383bd0b5566ecb65ea4c MD5 | raw file
Possible License(s): LGPL-3.0
  1. <?php
  2. /**
  3. *
  4. * License, TERMS and CONDITIONS
  5. *
  6. * This software is licensed under the GNU LESSER GENERAL PUBLIC LICENSE (LGPL) version 3
  7. * Please read the license here : http://www.gnu.org/licenses/lgpl-3.0.txt
  8. *
  9. * Redistribution and use in source and binary forms, with or without
  10. * modification, are permitted provided that the following conditions are met:
  11. * 1. Redistributions of source code must retain the above copyright
  12. * notice, this list of conditions and the following disclaimer.
  13. * 2. Redistributions in binary form must reproduce the above copyright
  14. * notice, this list of conditions and the following disclaimer in the
  15. * documentation and/or other materials provided with the distribution.
  16. * 3. The name of the author may not be used to endorse or promote products
  17. * derived from this software without specific prior written permission.
  18. *
  19. * ATTRIBUTION REQUIRED
  20. * 4. All web pages generated by the use of this software, or at least
  21. * the page that lists the recent questions (usually home page) must include
  22. * a link to the http://www.lampcms.com and text of the link must indicate that
  23. * the website's Questions/Answers functionality is powered by lampcms.com
  24. * An example of acceptable link would be "Powered by <a href="http://www.lampcms.com">LampCMS</a>"
  25. * The location of the link is not important, it can be in the footer of the page
  26. * but it must not be hidden by style attributes
  27. *
  28. * THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED
  29. * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
  30. * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
  31. * IN NO EVENT SHALL THE FREEBSD PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY
  32. * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  33. * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  34. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  35. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  36. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
  37. * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  38. *
  39. * This product includes GeoLite data created by MaxMind,
  40. * available from http://www.maxmind.com/
  41. *
  42. *
  43. * @author Dmitri Snytkine <cms@lampcms.com>
  44. * @copyright 2005-2012 (or current year) Dmitri Snytkine
  45. * @license http://www.gnu.org/licenses/lgpl-3.0.txt GNU LESSER GENERAL PUBLIC LICENSE (LGPL) version 3
  46. * @link http://www.lampcms.com Lampcms.com project
  47. * @version Release: @package_version@
  48. *
  49. *
  50. */
  51. namespace Lampcms;
  52. use Lampcms\Interfaces\TwitterUser;
  53. /**
  54. * Base class for interacting with Twitter API
  55. *
  56. * @author Dmitri Snytkine
  57. *
  58. */
  59. class Twitter extends LampcmsObject
  60. {
  61. const URL_VERIFY_CREDENTIALS = 'https://api.twitter.com/1.1/account/verify_credentials.json';
  62. const URL_STATUS = 'https://api.twitter.com/1.1/statuses/update.json';
  63. const URL_FAVORITE = 'https://api.twitter.com/1.1/favorites/create.json';
  64. const URL_FAVORITE_DESTROY = 'https://api.twitter.com/1.1/favorites/destroy.json';
  65. const URL_FOLLOW = 'https://api.twitter.com/1.1/friendships/create.json';
  66. /**
  67. * TWITTER section of !config.inc
  68. *
  69. * @var array
  70. */
  71. protected $aTwitterConfig = array();
  72. /**
  73. * Object of php oAuth class
  74. *
  75. * @var object
  76. */
  77. protected $oAuth;
  78. /**
  79. * Instance of TwitterUserInterface which is also User object
  80. *
  81. * @var object User
  82. */
  83. protected $User;
  84. /**
  85. * URL of Twitter API where to post stuff
  86. *
  87. * @var string
  88. */
  89. protected $url = null;
  90. /**
  91. * Constructor
  92. *
  93. * @param Registry $Registry
  94. *
  95. * @throws DevException
  96. * @throws Exception
  97. * @return \Lampcms\Twitter
  98. */
  99. public function __construct(Registry $Registry)
  100. {
  101. if (!extension_loaded('oauth')) {
  102. throw new \Lampcms\Exception('Cannot use this class because php extension "oauth" is not loaded');
  103. }
  104. $this->Registry = $Registry;
  105. $oViewer = $Registry->Viewer;
  106. if ($oViewer instanceof TwitterUser) {
  107. $this->User = $oViewer;
  108. //d(' $this->User: '.print_r($this->User, 1));
  109. }
  110. d('cp');
  111. $this->aTwitterConfig = $Registry->Ini->offsetGet('TWITTER');
  112. d('$this->aTwitterConfig: ' . print_r($this->aTwitterConfig, 1));
  113. if (empty($this->aTwitterConfig) || empty($this->aTwitterConfig['TWITTER_OAUTH_KEY']) || empty($this->aTwitterConfig['TWITTER_OAUTH_SECRET'])) {
  114. throw new DevException('Missing configuration parameters for TWITTER API');
  115. }
  116. try {
  117. d('cp');
  118. $this->oAuth = new \OAuth($this->aTwitterConfig['TWITTER_OAUTH_KEY'], $this->aTwitterConfig['TWITTER_OAUTH_SECRET']); // , OAUTH_SIG_METHOD_HMACSHA1, OAUTH_AUTH_TYPE_URI
  119. $this->oAuth->disableSSLChecks(); // this is required when running on localhost or with older curl version
  120. $this->oAuth->enableDebug(); // This will generate debug output in your error_log
  121. d('cp');
  122. } catch ( \OAuthException $e ) {
  123. e('OAuthException: ' . $e->getMessage() . ' ' . print_r($e, 1));
  124. throw new Exception('Something went wrong during Twitter authorization. This has nothing to do with your account. Please try again later' . $e->getMessage());
  125. }
  126. }
  127. /**
  128. * Setter for $this->User
  129. *
  130. * @param \Lampcms\TwitterUser $User
  131. *
  132. * @return object $this
  133. */
  134. public function setUser(\Lampcms\TwitterUser $User)
  135. {
  136. $this->User = $User;
  137. return $this;
  138. }
  139. /**
  140. * Getter for this->oAuth object
  141. *
  142. * @return object of type php oAuth
  143. */
  144. public function getOAuth()
  145. {
  146. return $this->oAuth;
  147. }
  148. /**
  149. * Factory method
  150. *
  151. * @param Registry $Registry
  152. * @param User $User
  153. *
  154. * @return object of this class
  155. */
  156. /*public static function factory(Registry $Registry, TwitterUser $User = null)
  157. {
  158. $o = new self($Registry);
  159. if(null !== $User){
  160. $o->setUser($User);
  161. }
  162. return $o;
  163. }*/
  164. /**
  165. * Verify user oAuth credentials
  166. * token/secret
  167. * This will confirm or deny that user is
  168. * authorizing us to use Twitter account
  169. *
  170. * @param mixed $token string oauth token or object of type User
  171. * @param string $secret
  172. *
  173. * @throws TwitterAuthException
  174. * @throws DevException
  175. * @throws Exception
  176. * @return array of user profile on success
  177. */
  178. public function verifyCredentials($token = null, $secret = null)
  179. {
  180. if (null !== $token && !is_string($token) && !is_object($token)) {
  181. throw new DevException('$token is not a string and not an object');
  182. }
  183. $User = null;
  184. if (is_string($token) && is_string($secret)) {
  185. $sToken = $token;
  186. $sSecret = $secret;
  187. }
  188. if (null === $token) {
  189. if (!isset($this->User)) {
  190. throw new DevException('$token is null and $this->User is not set');
  191. }
  192. $token = $this->User;
  193. }
  194. if (is_object($token)) {
  195. if (!($token instanceof TwitterUser)) {
  196. throw new DevException('param $token is not a string and not instance of User ' . get_class($token));
  197. }
  198. $User = $token;
  199. $sToken = $User->getTwitterToken();
  200. $sSecret = $User->getTwitterSecret();
  201. }
  202. if (empty($sToken) || empty($sSecret)) {
  203. throw new TwitterAuthException('empty_token');
  204. }
  205. try {
  206. $this->oAuth->setToken($sToken, $sSecret);
  207. $this->oAuth->fetch(self::URL_VERIFY_CREDENTIALS, null, OAUTH_HTTP_METHOD_GET, array('Connection'=> 'close'));
  208. return $this->getResponse($User);
  209. } catch ( \OAuthException $e ) {
  210. e('LampcmsError OAuthException: ' . $e->getMessage());
  211. $aDebug = $this->oAuth->getLastResponseInfo();
  212. d('debug: ' . print_r($aDebug, 1));
  213. $lastResponseHeaders = $this->oAuth->getLastResponseHeaders();
  214. d('$lastResponseHeaders: ' . $lastResponseHeaders);
  215. /**
  216. * Should NOT throw LampcmsTwitterAuthException because
  217. * we are not sure it was actually due to authorization
  218. * or many Twitter was bugged down or something else
  219. */
  220. throw new Exception('Something went wrong during authorization. Please try again later' . $e->getMessage());
  221. }
  222. }
  223. /**
  224. * Post message (update) to Twitter
  225. * using oAuth credentials from User
  226. *
  227. * If post is successful then we update the User
  228. * with the latest profile data we get from Twitter
  229. * and call save(), so that our record will be updated
  230. * if profile data has changed
  231. *
  232. * @param string $sMessage message to post
  233. *
  234. * @param int $inReplyToId twitter status id
  235. * Optional. The ID of an existing status that the update is in reply to.
  236. * Note: This parameter will be ignored
  237. * unless the author of the tweet this parameter references is mentioned
  238. * within the status text.
  239. * Therefore, you must include
  240. *
  241. * @internal param object $User
  242. *
  243. * @username
  244. * where username is the author of the referenced tweet, within the update.
  245. *
  246. * @return array of data returned by Twitter
  247. */
  248. public function postMessage($sMessage, $inReplyToId = null)
  249. {
  250. d('cp');
  251. $this->url = self::URL_STATUS;
  252. $args = array('status' => $sMessage);
  253. if (null !== $inReplyToId) {
  254. $args['in_reply_to_status_id'] = $inReplyToId;
  255. }
  256. return $this->apiPost($args);
  257. }
  258. /**
  259. * Save Twitter status ID, userID and timestamp
  260. * into TWEETS collection
  261. * This is just for basic statistics to know
  262. * which user twitted how many times and when
  263. * and then can possibly get the full tweets
  264. * from Twitter by _id if necessary
  265. *
  266. * Currently this method is not used anywhere
  267. *
  268. *
  269. * @param mixed $tweet array in case of success
  270. * or any possible format returned by Twitter API otherwise
  271. *
  272. * @return object $this
  273. */
  274. protected function saveTweet($tweet)
  275. {
  276. if (empty($tweet)
  277. || !is_array($tweet)
  278. || empty($tweet['http_code'])
  279. || ('200' != $tweet['http_code'])
  280. || empty($tweet['id_str'])
  281. ) {
  282. d('Not successful tweet. Nothing to save');
  283. return $this;
  284. }
  285. try {
  286. $coll = $this->Registry->Mongo->TWEETS;
  287. $coll->ensureIndex(array('i_uid' => 1));
  288. $aData = array('_id' => $tweet['id_str'], 'i_uid' => $this->User->getUid(), 'i_ts' => time());
  289. $coll->save($aData);
  290. } catch ( \Exception $e ) {
  291. e('Unable to save data to TWEETS collection because of ' . $e->getMessage() . ' in file: ' . $e->getFile() . ' on line: ' . $e->getLine());
  292. }
  293. return $this;
  294. }
  295. /**
  296. * Prepare the raw string before posting to Twitter
  297. * It will convert string to guaranteed utf8,
  298. * then strip html tags then truncate to 140 chars
  299. *
  300. * @param Utf8String $Message
  301. * @param string $inReplyToId
  302. *
  303. * @internal param string $sMessage
  304. * @return array of response data from Twitter API
  305. */
  306. public function prepareAndPost(Utf8String $Message, $inReplyToId = null)
  307. {
  308. $body = $Message->htmlspecialchars()->truncate(140)->valueOf();
  309. return $this->postMessage($body, $inReplyToId);
  310. }
  311. /**
  312. * Add or delete status from user's favorites
  313. *
  314. * @param $statusId
  315. * @param bool $isDelete
  316. *
  317. * @internal param \Lampcms\TwitterUserInterface $intStatusId
  318. * @internal param $User
  319. * @return
  320. */
  321. public function updateFavorites($statusId, $isDelete = false)
  322. {
  323. d('$statusId: ' . $statusId);
  324. $action = ($isDelete) ? self::URL_FAVORITE : self::URL_FAVORITE_DESTROY;
  325. $args = array('id' => $statusId);
  326. $this->url = $action;
  327. return $this->postApi($args);
  328. }
  329. /**
  330. * Set the authenticated user to follow
  331. * another user (usually to follow our own account)
  332. *
  333. * @param string $twitterUserId username or twitter user ID (WITHOUT THE @ sign in front or username!)
  334. * if not set then we will use the one from !config.inc
  335. * TWITTER -> TWITTER_USERNAME
  336. *
  337. * @param bool $isUserID if true indicates that $twitterUserId is actually the id otherwise it is a twitter screen name
  338. *
  339. * @return array
  340. */
  341. public function followUser($twitterUserId = null, $isUserID = false)
  342. {
  343. if (empty($twitterUserId)) {
  344. $twitterUserId = $this->aTwitterConfig['TWITTER_USERNAME'];
  345. }
  346. if (empty($twitterUserId)) {
  347. /**
  348. * If no user to follow then nothing to do
  349. */
  350. return;
  351. }
  352. $this->url = self::URL_FOLLOW;
  353. $postData = array('follow' => true);
  354. if ($isUserID) {
  355. $postData['user_id'] = $twitterUserId;
  356. } else {
  357. $postData['screen_name'] = $twitterUserId;
  358. }
  359. return $this->apiPost($postData);
  360. }
  361. /**
  362. * Get oAuth token, secret from User and set
  363. * the values in this oAuth object
  364. *
  365. * @param null $token
  366. * @param null $secret
  367. *
  368. * @internal param \Lampcms\TwitterUserInterface $User
  369. * @return object $this
  370. */
  371. public function setOAuthTokens($token = null, $secret = null)
  372. {
  373. d('this->User: ' . print_r($this->User->getArrayCopy(), 1));
  374. $token = (!empty($token)) ? $token : $this->User->getTwitterToken();
  375. $secret = (!empty($secret)) ? $secret : $this->User->getTwitterSecret();
  376. d('setting $token: ' . $token . ' secret: ' . $secret);
  377. $this->oAuth->setToken($token, $secret);
  378. return $this;
  379. }
  380. /**
  381. * Get the array of data that was
  382. * sent in response to Twitter API request
  383. * This will examine the value of http header
  384. * and will throw exception if header is 401 meaning
  385. * authorization failed
  386. *
  387. * @throws TwitterException
  388. * @throws TwitterAuthException
  389. * @return mixed array of data returned by Twitter on success
  390. * nothing is returned on failure because an exception is thrown instead
  391. */
  392. protected function getResponse()
  393. {
  394. $aData = \json_decode($this->oAuth->getLastResponse(), 1);
  395. $aDebug = $this->oAuth->getLastResponseInfo();
  396. d('debug: ' . print_r($aDebug, 1));
  397. if ('200' == $aDebug['http_code']) {
  398. $aData['http_code'] = $aDebug['http_code'];
  399. return $aData;
  400. } elseif ('401' == $aDebug['http_code']) {
  401. d('Twitter oauth failed with 401 http code. Data: ' . print_r($aData, 1));
  402. /**
  403. * If this method was passed User
  404. * then null the tokens
  405. * and save the data to table
  406. * so that next time we will know
  407. * that this User does not have tokens
  408. */
  409. if (is_object($this->User)) {
  410. d('Going to revoke access tokens for user object');
  411. $this->User->revokeOauthToken();
  412. /**
  413. * Important to post this update
  414. * so that user object will be removed from cache
  415. */
  416. $this->Registry->Dispatcher->post($this->User, 'onTwitterUserUpdate');
  417. }
  418. /**
  419. * This exception should be caught all the way in WebPage and it will
  420. * cause the ajax message with special key=>value which will
  421. * trigger the popup to be shown to user with link
  422. * to signing with Twitter
  423. */
  424. throw new TwitterAuthException('twitter_credentials_failed');
  425. } else {
  426. e('verifyCredentials failed http code was: ' . $aDebug['http_code'] . ' full debug: ' . print_r($aDebug, 1) . ' response: ' . print_r($aData, 1));
  427. throw new TwitterException('twitter_auth_failed', array(), $aDebug['http_code']);
  428. }
  429. }
  430. /**
  431. * Makes a POST request to Twitter API
  432. *
  433. * @param mixed $aData
  434. *
  435. * @throws TwitterException
  436. * @return array with response data from Twitter API
  437. */
  438. protected function apiPost($aData = null)
  439. {
  440. if (!is_object($this->User)) {
  441. throw new TwitterException('Object of type UserTwitter was not set');
  442. }
  443. d('this->url: ' . $this->url);
  444. try {
  445. /**
  446. * For posting a Twitter update we must set
  447. * the authType to OAUTH_AUTH_TYPE_FORM
  448. * so that we will be using the POST method
  449. * when sending data to Twitter API
  450. */
  451. //$authType = constant('OAUTH_AUTH_TYPE_FORM');
  452. //d('$authType: ' . $authType);
  453. //$this->oAuth->setAuthType($authType);
  454. $this->setOAuthTokens();
  455. d('fetching: ' . $this->url . ' data: ' . print_r($aData, 1));
  456. $this->oAuth->fetch($this->url, $aData, OAUTH_HTTP_METHOD_POST);
  457. } catch ( \OAuthException $e ) {
  458. $aDebug = $this->oAuth->getLastResponseInfo();
  459. d('debug: ' . print_r($aDebug, 1));
  460. e('OAuthException: ' . $e->getMessage());
  461. /**
  462. * Should NOT throw TwitterException because
  463. * we are not sure it was actually due to authorization
  464. * or maybe Twitter was bugged down or something else
  465. */
  466. throw new TwitterException('Something went wrong during connection with Twitter. Please try again later' . $e->getMessage());
  467. }
  468. $aResponse = $this->getResponse();
  469. d('Twitter returned data: ' . print_r($aResponse, 1));
  470. return $aResponse;
  471. }
  472. }