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

/modules/facebook/classes/facebook.php

https://bitbucket.org/alvinpd/monsterninja
PHP | 778 lines | 432 code | 64 blank | 282 comment | 56 complexity | 86bb53cd29f98e7f3040b16e22651c68 MD5 | raw file
  1. <?php defined('SYSPATH') or die('No direct script access.');
  2. if (!function_exists('curl_init')) {
  3. throw new Exception('Facebook needs the CURL PHP extension.');
  4. }
  5. if (!function_exists('json_decode')) {
  6. throw new Exception('Facebook needs the JSON PHP extension.');
  7. }
  8. /**
  9. * Thrown when an API call returns an exception.
  10. *
  11. * @package Facebook
  12. * @author Naitik Shah <naitik@facebook.com>
  13. */
  14. class FacebookApiException extends Exception
  15. {
  16. /**
  17. * The result from the API server that represents the exception information.
  18. */
  19. protected $result;
  20. /**
  21. * Make a new API Exception with the given result.
  22. *
  23. * @param Array $result the result from the API server
  24. */
  25. public function __construct($result) {
  26. $this->result = $result;
  27. $code = isset($result['error_code']) ? $result['error_code'] : 0;
  28. $msg = isset($result['error'])
  29. ? $result['error']['message'] : $result['error_msg'];
  30. parent::__construct($msg, $code);
  31. }
  32. /**
  33. * Return the associated result object returned by the API server.
  34. *
  35. * @returns Array the result from the API server
  36. */
  37. public function getResult() {
  38. return $this->result;
  39. }
  40. /**
  41. * Returns the associated type for the error. This will default to
  42. * 'Exception' when a type is not available.
  43. *
  44. * @return String
  45. */
  46. public function getType() {
  47. return
  48. isset($this->result['error']) && isset($this->result['error']['type'])
  49. ? $this->result['error']['type']
  50. : 'Exception';
  51. }
  52. /**
  53. * To make debugging easier.
  54. *
  55. * @returns String the string representation of the error
  56. */
  57. public function __toString() {
  58. $str = $this->getType() . ': ';
  59. if ($this->code != 0) {
  60. $str .= $this->code . ': ';
  61. }
  62. return $str . $this->message;
  63. }
  64. }
  65. /**
  66. * Provides access to the Facebook Platform.
  67. *
  68. * @package Facebook
  69. * @author Naitik Shah <naitik@facebook.com>
  70. */
  71. class Facebook
  72. {
  73. /**
  74. * Version.
  75. */
  76. const VERSION = '2.0.4';
  77. /**
  78. * Default options for curl.
  79. */
  80. public static $CURL_OPTS = array(
  81. CURLOPT_CONNECTTIMEOUT => 10,
  82. CURLOPT_RETURNTRANSFER => true,
  83. CURLOPT_TIMEOUT => 60,
  84. CURLOPT_USERAGENT => 'facebook-php-2.0',
  85. CURLOPT_SSL_VERIFYPEER => FALSE,
  86. );
  87. /**
  88. * List of query parameters that get automatically dropped when rebuilding
  89. * the current URL.
  90. */
  91. protected static $DROP_QUERY_PARAMS = array(
  92. 'session',
  93. );
  94. /**
  95. * Maps aliases to Facebook domains.
  96. */
  97. public static $DOMAIN_MAP = array(
  98. 'api' => 'https://api.facebook.com/',
  99. 'api_read' => 'https://api-read.facebook.com/',
  100. 'graph' => 'https://graph.facebook.com/',
  101. 'www' => 'https://www.facebook.com/',
  102. );
  103. /**
  104. * The Application ID.
  105. */
  106. protected $appId;
  107. /**
  108. * The Application API Secret.
  109. */
  110. protected $apiSecret;
  111. /**
  112. * The active user session, if one is available.
  113. */
  114. protected $session;
  115. /**
  116. * Indicates that we already loaded the session as best as we could.
  117. */
  118. protected $sessionLoaded = false;
  119. /**
  120. * Indicates if Cookie support should be enabled.
  121. */
  122. protected $cookieSupport = false;
  123. /**
  124. * Base domain for the Cookie.
  125. */
  126. protected $baseDomain = '';
  127. /**
  128. * Initialize a Facebook Application.
  129. *
  130. * The configuration:
  131. * - appId: the application ID
  132. * - secret: the application secret
  133. * - cookie: (optional) boolean true to enable cookie support
  134. * - domain: (optional) domain for the cookie
  135. *
  136. * @param Array $config the application configuration
  137. */
  138. public function __construct($config) {
  139. $this->setAppId($config['appId']);
  140. $this->setApiSecret($config['secret']);
  141. if (isset($config['cookie'])) {
  142. $this->setCookieSupport($config['cookie']);
  143. }
  144. if (isset($config['domain'])) {
  145. $this->setBaseDomain($config['domain']);
  146. }
  147. }
  148. /**
  149. * Set the Application ID.
  150. *
  151. * @param String $appId the Application ID
  152. */
  153. public function setAppId($appId) {
  154. $this->appId = $appId;
  155. return $this;
  156. }
  157. /**
  158. * Get the Application ID.
  159. *
  160. * @return String the Application ID
  161. */
  162. public function getAppId() {
  163. return $this->appId;
  164. }
  165. /**
  166. * Set the API Secret.
  167. *
  168. * @param String $appId the API Secret
  169. */
  170. public function setApiSecret($apiSecret) {
  171. $this->apiSecret = $apiSecret;
  172. return $this;
  173. }
  174. /**
  175. * Get the API Secret.
  176. *
  177. * @return String the API Secret
  178. */
  179. public function getApiSecret() {
  180. return $this->apiSecret;
  181. }
  182. /**
  183. * Set the Cookie Support status.
  184. *
  185. * @param Boolean $cookieSupport the Cookie Support status
  186. */
  187. public function setCookieSupport($cookieSupport) {
  188. $this->cookieSupport = $cookieSupport;
  189. return $this;
  190. }
  191. /**
  192. * Get the Cookie Support status.
  193. *
  194. * @return Boolean the Cookie Support status
  195. */
  196. public function useCookieSupport() {
  197. return $this->cookieSupport;
  198. }
  199. /**
  200. * Set the base domain for the Cookie.
  201. *
  202. * @param String $domain the base domain
  203. */
  204. public function setBaseDomain($domain) {
  205. $this->baseDomain = $domain;
  206. return $this;
  207. }
  208. /**
  209. * Get the base domain for the Cookie.
  210. *
  211. * @return String the base domain
  212. */
  213. public function getBaseDomain() {
  214. return $this->baseDomain;
  215. }
  216. /**
  217. * Set the Session.
  218. *
  219. * @param Array $session the session
  220. * @param Boolean $write_cookie indicate if a cookie should be written. this
  221. * value is ignored if cookie support has been disabled.
  222. */
  223. public function setSession($session=null, $write_cookie=true) {
  224. $session = $this->validateSessionObject($session);
  225. $this->sessionLoaded = $session == null ? false : true;
  226. $this->session = $session;
  227. if ($write_cookie) {
  228. $this->setCookieFromSession($session);
  229. }
  230. return $this;
  231. }
  232. /**
  233. * Get the session object. This will automatically look for a signed session
  234. * sent via the Cookie or Query Parameters if needed.
  235. *
  236. * @return Array the session
  237. */
  238. public function getSession() {
  239. if (!$this->sessionLoaded) {
  240. $session = null;
  241. $write_cookie = true;
  242. // try loading session from $_REQUEST
  243. if (isset($_GET['session'])) {
  244. $session = json_decode(
  245. get_magic_quotes_gpc()
  246. ? stripslashes($_GET['session'])
  247. : $_GET['session'],
  248. true
  249. );
  250. $session = $this->validateSessionObject($session);
  251. }
  252. // try loading session from cookie if necessary
  253. if (!$session && $this->useCookieSupport()) {
  254. $cookieName = $this->getSessionCookieName();
  255. if (isset($_COOKIE[$cookieName])) {
  256. $session = array();
  257. parse_str(trim(
  258. get_magic_quotes_gpc()
  259. ? stripslashes($_COOKIE[$cookieName])
  260. : $_COOKIE[$cookieName],
  261. '"'
  262. ), $session);
  263. $session = $this->validateSessionObject($session);
  264. // write only if we need to delete a invalid session cookie
  265. $write_cookie = empty($session);
  266. }
  267. }
  268. $this->setSession($session, $write_cookie);
  269. }
  270. return $this->session;
  271. }
  272. /**
  273. * Get the UID from the session.
  274. *
  275. * @return String the UID if available
  276. */
  277. public function getUser() {
  278. $session = $this->getSession();
  279. return $session ? $session['uid'] : null;
  280. }
  281. /**
  282. * Get a Login URL for use with redirects. By default, full page redirect is
  283. * assumed. If you are using the generated URL with a window.open() call in
  284. * JavaScript, you can pass in display=popup as part of the $params.
  285. *
  286. * The parameters:
  287. * - next: the url to go to after a successful login
  288. * - cancel_url: the url to go to after the user cancels
  289. * - req_perms: comma separated list of requested extended perms
  290. * - display: can be "page" (default, full page) or "popup"
  291. *
  292. * @param Array $params provide custom parameters
  293. * @return String the URL for the login flow
  294. */
  295. public function getLoginUrl($params=array()) {
  296. $currentUrl = $this->getCurrentUrl();
  297. return $this->getUrl(
  298. 'www',
  299. 'login.php',
  300. array_merge(array(
  301. 'api_key' => $this->getAppId(),
  302. 'cancel_url' => $currentUrl,
  303. 'display' => 'page',
  304. 'fbconnect' => 1,
  305. 'next' => $currentUrl,
  306. 'return_session' => 1,
  307. 'session_version' => 3,
  308. 'v' => '1.0',
  309. ), $params)
  310. );
  311. }
  312. /**
  313. * Get a Logout URL suitable for use with redirects.
  314. *
  315. * The parameters:
  316. * - next: the url to go to after a successful logout
  317. *
  318. * @param Array $params provide custom parameters
  319. * @return String the URL for the logout flow
  320. */
  321. public function getLogoutUrl($params=array()) {
  322. $session = $this->getSession();
  323. return $this->getUrl(
  324. 'www',
  325. 'logout.php',
  326. array_merge(array(
  327. 'api_key' => $this->getAppId(),
  328. 'next' => $this->getCurrentUrl(),
  329. 'session_key' => $session['session_key'],
  330. ), $params)
  331. );
  332. }
  333. /**
  334. * Get a login status URL to fetch the status from facebook.
  335. *
  336. * The parameters:
  337. * - ok_session: the URL to go to if a session is found
  338. * - no_session: the URL to go to if the user is not connected
  339. * - no_user: the URL to go to if the user is not signed into facebook
  340. *
  341. * @param Array $params provide custom parameters
  342. * @return String the URL for the logout flow
  343. */
  344. public function getLoginStatusUrl($params=array()) {
  345. return $this->getUrl(
  346. 'www',
  347. 'extern/login_status.php',
  348. array_merge(array(
  349. 'api_key' => $this->getAppId(),
  350. 'no_session' => $this->getCurrentUrl(),
  351. 'no_user' => $this->getCurrentUrl(),
  352. 'ok_session' => $this->getCurrentUrl(),
  353. 'session_version' => 3,
  354. ), $params)
  355. );
  356. }
  357. /**
  358. * Make an API call.
  359. *
  360. * @param Array $params the API call parameters
  361. * @return the decoded response
  362. */
  363. public function api(/* polymorphic */) {
  364. $args = func_get_args();
  365. if (is_array($args[0])) {
  366. return $this->_restserver($args[0]);
  367. } else {
  368. return call_user_func_array(array($this, '_graph'), $args);
  369. }
  370. }
  371. /**
  372. * Invoke the old restserver.php endpoint.
  373. *
  374. * @param Array $params method call object
  375. * @return the decoded response object
  376. * @throws FacebookApiException
  377. */
  378. protected function _restserver($params) {
  379. // generic application level parameters
  380. $params['api_key'] = $this->getAppId();
  381. $params['format'] = 'json-strings';
  382. $result = json_decode($this->_oauthRequest(
  383. $this->getApiUrl($params['method']),
  384. $params
  385. ), true);
  386. // results are returned, errors are thrown
  387. if (is_array($result) && isset($result['error_code'])) {
  388. throw new FacebookApiException($result);
  389. }
  390. return $result;
  391. }
  392. /**
  393. * Invoke the Graph API.
  394. *
  395. * @param String $path the path (required)
  396. * @param String $method the http method (default 'GET')
  397. * @param Array $params the query/post data
  398. * @return the decoded response object
  399. * @throws FacebookApiException
  400. */
  401. protected function _graph($path, $method='GET', $params=array()) {
  402. if (is_array($method) && empty($params)) {
  403. $params = $method;
  404. $method = 'GET';
  405. }
  406. $params['method'] = $method; // method override as we always do a POST
  407. $result = json_decode($this->_oauthRequest(
  408. $this->getUrl('graph', $path),
  409. $params
  410. ), true);
  411. // results are returned, errors are thrown
  412. if (is_array($result) && isset($result['error'])) {
  413. $e = new FacebookApiException($result);
  414. if ($e->getType() === 'OAuthException') {
  415. $this->setSession(null);
  416. }
  417. throw $e;
  418. }
  419. return $result;
  420. }
  421. /**
  422. * Make a OAuth Request
  423. *
  424. * @param String $path the path (required)
  425. * @param Array $params the query/post data
  426. * @return the decoded response object
  427. * @throws FacebookApiException
  428. */
  429. protected function _oauthRequest($url, $params) {
  430. if (!isset($params['access_token'])) {
  431. $session = $this->getSession();
  432. // either user session signed, or app signed
  433. if ($session) {
  434. $params['access_token'] = $session['access_token'];
  435. } else {
  436. $params['access_token'] = $this->getAppId() .'|'. $this->getApiSecret();
  437. }
  438. }
  439. // json_encode all params values that are not strings
  440. foreach ($params as $key => $value) {
  441. if (!is_string($value)) {
  442. $params[$key] = json_encode($value);
  443. }
  444. }
  445. return $this->makeRequest($url, $params);
  446. }
  447. /**
  448. * Makes an HTTP request. This method can be overriden by subclasses if
  449. * developers want to do fancier things or use something other than curl to
  450. * make the request.
  451. *
  452. * @param String $url the URL to make the request to
  453. * @param Array $params the parameters to use for the POST body
  454. * @param CurlHandler $ch optional initialized curl handle
  455. * @return String the response text
  456. */
  457. protected function makeRequest($url, $params, $ch=null) {
  458. if (!$ch) {
  459. $ch = curl_init();
  460. }
  461. $opts = self::$CURL_OPTS;
  462. $opts[CURLOPT_POSTFIELDS] = $params;
  463. $opts[CURLOPT_URL] = $url;
  464. curl_setopt_array($ch, $opts);
  465. $result = curl_exec($ch);
  466. if ($result === false) {
  467. $e = new FacebookApiException(array(
  468. 'error_code' => curl_errno($ch),
  469. 'error' => array(
  470. 'message' => curl_error($ch),
  471. 'type' => 'CurlException',
  472. ),
  473. ));
  474. curl_close($ch);
  475. throw $e;
  476. }
  477. curl_close($ch);
  478. return $result;
  479. }
  480. /**
  481. * The name of the Cookie that contains the session.
  482. *
  483. * @return String the cookie name
  484. */
  485. protected function getSessionCookieName() {
  486. return 'fbs_' . $this->getAppId();
  487. }
  488. /**
  489. * Set a JS Cookie based on the _passed in_ session. It does not use the
  490. * currently stored session -- you need to explicitly pass it in.
  491. *
  492. * @param Array $session the session to use for setting the cookie
  493. */
  494. protected function setCookieFromSession($session=null) {
  495. if (!$this->useCookieSupport()) {
  496. return;
  497. }
  498. $cookieName = $this->getSessionCookieName();
  499. $value = 'deleted';
  500. $expires = time() - 3600;
  501. $domain = $this->getBaseDomain();
  502. if ($session) {
  503. $value = '"' . http_build_query($session, null, '&') . '"';
  504. if (isset($session['base_domain'])) {
  505. $domain = $session['base_domain'];
  506. }
  507. $expires = $session['expires'];
  508. }
  509. // prepend dot if a domain is found
  510. if ($domain) {
  511. $domain = '.' . $domain;
  512. }
  513. // if an existing cookie is not set, we dont need to delete it
  514. if ($value == 'deleted' && empty($_COOKIE[$cookieName])) {
  515. return;
  516. }
  517. if (headers_sent()) {
  518. // disable error log if we are running in a CLI environment
  519. // @codeCoverageIgnoreStart
  520. if (php_sapi_name() != 'cli') {
  521. error_log('Could not set cookie. Headers already sent.');
  522. }
  523. // @codeCoverageIgnoreEnd
  524. // ignore for code coverage as we will never be able to setcookie in a CLI
  525. // environment
  526. // @codeCoverageIgnoreStart
  527. } else {
  528. setcookie($cookieName, $value, $expires, '/', $domain);
  529. }
  530. // @codeCoverageIgnoreEnd
  531. }
  532. /**
  533. * Validates a session_version=3 style session object.
  534. *
  535. * @param Array $session the session object
  536. * @return Array the session object if it validates, null otherwise
  537. */
  538. protected function validateSessionObject($session) {
  539. // make sure some essential fields exist
  540. if (is_array($session) &&
  541. isset($session['uid']) &&
  542. isset($session['session_key']) &&
  543. isset($session['secret']) &&
  544. isset($session['access_token']) &&
  545. isset($session['sig'])) {
  546. // validate the signature
  547. $session_without_sig = $session;
  548. unset($session_without_sig['sig']);
  549. $expected_sig = self::generateSignature(
  550. $session_without_sig,
  551. $this->getApiSecret()
  552. );
  553. if ($session['sig'] != $expected_sig) {
  554. // disable error log if we are running in a CLI environment
  555. // @codeCoverageIgnoreStart
  556. if (php_sapi_name() != 'cli') {
  557. error_log('Got invalid session signature in cookie.');
  558. }
  559. // @codeCoverageIgnoreEnd
  560. $session = null;
  561. }
  562. // check expiry time
  563. } else {
  564. $session = null;
  565. }
  566. return $session;
  567. }
  568. /**
  569. * Build the URL for api given parameters.
  570. *
  571. * @param $method String the method name.
  572. * @return String the URL for the given parameters
  573. */
  574. protected function getApiUrl($method) {
  575. static $READ_ONLY_CALLS =
  576. array('admin.getallocation' => 1,
  577. 'admin.getappproperties' => 1,
  578. 'admin.getbannedusers' => 1,
  579. 'admin.getlivestreamvialink' => 1,
  580. 'admin.getmetrics' => 1,
  581. 'admin.getrestrictioninfo' => 1,
  582. 'application.getpublicinfo' => 1,
  583. 'auth.getapppublickey' => 1,
  584. 'auth.getsession' => 1,
  585. 'auth.getsignedpublicsessiondata' => 1,
  586. 'comments.get' => 1,
  587. 'connect.getunconnectedfriendscount' => 1,
  588. 'dashboard.getactivity' => 1,
  589. 'dashboard.getcount' => 1,
  590. 'dashboard.getglobalnews' => 1,
  591. 'dashboard.getnews' => 1,
  592. 'dashboard.multigetcount' => 1,
  593. 'dashboard.multigetnews' => 1,
  594. 'data.getcookies' => 1,
  595. 'events.get' => 1,
  596. 'events.getmembers' => 1,
  597. 'fbml.getcustomtags' => 1,
  598. 'feed.getappfriendstories' => 1,
  599. 'feed.getregisteredtemplatebundlebyid' => 1,
  600. 'feed.getregisteredtemplatebundles' => 1,
  601. 'fql.multiquery' => 1,
  602. 'fql.query' => 1,
  603. 'friends.arefriends' => 1,
  604. 'friends.get' => 1,
  605. 'friends.getappusers' => 1,
  606. 'friends.getlists' => 1,
  607. 'friends.getmutualfriends' => 1,
  608. 'gifts.get' => 1,
  609. 'groups.get' => 1,
  610. 'groups.getmembers' => 1,
  611. 'intl.gettranslations' => 1,
  612. 'links.get' => 1,
  613. 'notes.get' => 1,
  614. 'notifications.get' => 1,
  615. 'pages.getinfo' => 1,
  616. 'pages.isadmin' => 1,
  617. 'pages.isappadded' => 1,
  618. 'pages.isfan' => 1,
  619. 'permissions.checkavailableapiaccess' => 1,
  620. 'permissions.checkgrantedapiaccess' => 1,
  621. 'photos.get' => 1,
  622. 'photos.getalbums' => 1,
  623. 'photos.gettags' => 1,
  624. 'profile.getinfo' => 1,
  625. 'profile.getinfooptions' => 1,
  626. 'stream.get' => 1,
  627. 'stream.getcomments' => 1,
  628. 'stream.getfilters' => 1,
  629. 'users.getinfo' => 1,
  630. 'users.getloggedinuser' => 1,
  631. 'users.getstandardinfo' => 1,
  632. 'users.hasapppermission' => 1,
  633. 'users.isappuser' => 1,
  634. 'users.isverified' => 1,
  635. 'video.getuploadlimits' => 1);
  636. $name = 'api';
  637. if (isset($READ_ONLY_CALLS[strtolower($method)])) {
  638. $name = 'api_read';
  639. }
  640. return self::getUrl($name, 'restserver.php');
  641. }
  642. /**
  643. * Build the URL for given domain alias, path and parameters.
  644. *
  645. * @param $name String the name of the domain
  646. * @param $path String optional path (without a leading slash)
  647. * @param $params Array optional query parameters
  648. * @return String the URL for the given parameters
  649. */
  650. protected function getUrl($name, $path='', $params=array()) {
  651. $url = self::$DOMAIN_MAP[$name];
  652. if ($path) {
  653. if ($path[0] === '/') {
  654. $path = substr($path, 1);
  655. }
  656. $url .= $path;
  657. }
  658. if ($params) {
  659. $url .= '?' . http_build_query($params);
  660. }
  661. return $url;
  662. }
  663. /**
  664. * Returns the Current URL, stripping it of known FB parameters that should
  665. * not persist.
  666. *
  667. * @return String the current URL
  668. */
  669. protected function getCurrentUrl() {
  670. $protocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on'
  671. ? 'https://'
  672. : 'http://';
  673. $currentUrl = $protocol . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
  674. $parts = parse_url($currentUrl);
  675. // drop known fb params
  676. $query = '';
  677. if (!empty($parts['query'])) {
  678. $params = array();
  679. parse_str($parts['query'], $params);
  680. foreach(self::$DROP_QUERY_PARAMS as $key) {
  681. unset($params[$key]);
  682. }
  683. if (!empty($params)) {
  684. $query = '?' . http_build_query($params);
  685. }
  686. }
  687. // use port if non default
  688. $port =
  689. isset($parts['port']) &&
  690. (($protocol === 'http://' && $parts['port'] !== 80) ||
  691. ($protocol === 'https://' && $parts['port'] !== 443))
  692. ? ':' . $parts['port'] : '';
  693. // rebuild
  694. return $protocol . $parts['host'] . $port . $parts['path'] . $query;
  695. }
  696. /**
  697. * Generate a signature for the given params and secret.
  698. *
  699. * @param Array $params the parameters to sign
  700. * @param String $secret the secret to sign with
  701. * @return String the generated signature
  702. */
  703. protected static function generateSignature($params, $secret) {
  704. // work with sorted data
  705. ksort($params);
  706. // generate the base string
  707. $base_string = '';
  708. foreach($params as $key => $value) {
  709. $base_string .= $key . '=' . $value;
  710. }
  711. $base_string .= $secret;
  712. return md5($base_string);
  713. }
  714. }