Advertisement
Guest User

Untitled

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