PageRenderTime 49ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 1ms

/wp-content/plugins/seo-facebook-comments/facebook/base_facebook.php

https://bitbucket.org/MaheshDhaduk/androidmobiles
PHP | 1147 lines | 570 code | 110 blank | 467 comment | 100 complexity | 2ad01c68653a739875f7ae4d8e4afc86 MD5 | raw file
Possible License(s): GPL-3.0, LGPL-2.1, AGPL-1.0
  1. <?php
  2. /**
  3. * Copyright 2011 Facebook, Inc.
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License"); you may
  6. * not use this file except in compliance with the License. You may obtain
  7. * a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  13. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  14. * License for the specific language governing permissions and limitations
  15. * under the License.
  16. */
  17. if (!function_exists('curl_init')) {
  18. throw new Exception('Facebook needs the CURL PHP extension.');
  19. }
  20. if (!function_exists('json_decode')) {
  21. throw new Exception('Facebook needs the JSON PHP extension.');
  22. }
  23. if (!class_exists('FacebookApiException'))
  24. {
  25. /**
  26. * Thrown when an API call returns an exception.
  27. *
  28. * @author Naitik Shah <naitik@facebook.com>
  29. */
  30. class FacebookApiException extends Exception
  31. {
  32. /**
  33. * The result from the API server that represents the exception information.
  34. */
  35. protected $result;
  36. /**
  37. * Make a new API Exception with the given result.
  38. *
  39. * @param array $result The result from the API server
  40. */
  41. public function __construct($result) {
  42. $this->result = $result;
  43. $code = isset($result['error_code']) ? $result['error_code'] : 0;
  44. if (isset($result['error_description'])) {
  45. // OAuth 2.0 Draft 10 style
  46. $msg = $result['error_description'];
  47. } else if (isset($result['error']) && is_array($result['error'])) {
  48. // OAuth 2.0 Draft 00 style
  49. $msg = $result['error']['message'];
  50. } else if (isset($result['error_msg'])) {
  51. // Rest server style
  52. $msg = $result['error_msg'];
  53. } else {
  54. $msg = 'Unknown Error. Check getResult()';
  55. }
  56. parent::__construct($msg, $code);
  57. }
  58. /**
  59. * Return the associated result object returned by the API server.
  60. *
  61. * @return array The result from the API server
  62. */
  63. public function getResult() {
  64. return $this->result;
  65. }
  66. /**
  67. * Returns the associated type for the error. This will default to
  68. * 'Exception' when a type is not available.
  69. *
  70. * @return string
  71. */
  72. public function getType() {
  73. if (isset($this->result['error'])) {
  74. $error = $this->result['error'];
  75. if (is_string($error)) {
  76. // OAuth 2.0 Draft 10 style
  77. return $error;
  78. } else if (is_array($error)) {
  79. // OAuth 2.0 Draft 00 style
  80. if (isset($error['type'])) {
  81. return $error['type'];
  82. }
  83. }
  84. }
  85. return 'Exception';
  86. }
  87. /**
  88. * To make debugging easier.
  89. *
  90. * @return string The string representation of the error
  91. */
  92. public function __toString() {
  93. $str = $this->getType() . ': ';
  94. if ($this->code != 0) {
  95. $str .= $this->code . ': ';
  96. }
  97. return $str . $this->message;
  98. }
  99. }
  100. }
  101. if (!class_exists('BaseFacebook'))
  102. {
  103. /**
  104. * Provides access to the Facebook Platform. This class provides
  105. * a majority of the functionality needed, but the class is abstract
  106. * because it is designed to be sub-classed. The subclass must
  107. * implement the four abstract methods listed at the bottom of
  108. * the file.
  109. *
  110. * @author Naitik Shah <naitik@facebook.com>
  111. */
  112. abstract class BaseFacebook
  113. {
  114. /**
  115. * Version.
  116. */
  117. const VERSION = '3.1.1';
  118. /**
  119. * Default options for curl.
  120. */
  121. public static $CURL_OPTS = array(
  122. CURLOPT_CONNECTTIMEOUT => 10,
  123. CURLOPT_RETURNTRANSFER => true,
  124. CURLOPT_TIMEOUT => 60,
  125. CURLOPT_USERAGENT => 'facebook-php-3.1',
  126. );
  127. /**
  128. * List of query parameters that get automatically dropped when rebuilding
  129. * the current URL.
  130. */
  131. protected static $DROP_QUERY_PARAMS = array(
  132. 'code',
  133. 'state',
  134. 'signed_request',
  135. );
  136. /**
  137. * Maps aliases to Facebook domains.
  138. */
  139. public static $DOMAIN_MAP = array(
  140. 'api' => 'https://api.facebook.com/',
  141. 'api_video' => 'https://api-video.facebook.com/',
  142. 'api_read' => 'https://api-read.facebook.com/',
  143. 'graph' => 'https://graph.facebook.com/',
  144. 'www' => 'https://www.facebook.com/',
  145. );
  146. /**
  147. * The Application ID.
  148. *
  149. * @var string
  150. */
  151. protected $appId;
  152. /**
  153. * The Application API Secret.
  154. *
  155. * @var string
  156. */
  157. protected $apiSecret;
  158. /**
  159. * The ID of the Facebook user, or 0 if the user is logged out.
  160. *
  161. * @var integer
  162. */
  163. protected $user;
  164. /**
  165. * The data from the signed_request token.
  166. */
  167. protected $signedRequest;
  168. /**
  169. * A CSRF state variable to assist in the defense against CSRF attacks.
  170. */
  171. protected $state;
  172. /**
  173. * The OAuth access token received in exchange for a valid authorization
  174. * code. null means the access token has yet to be determined.
  175. *
  176. * @var string
  177. */
  178. protected $accessToken = null;
  179. /**
  180. * Indicates if the CURL based @ syntax for file uploads is enabled.
  181. *
  182. * @var boolean
  183. */
  184. protected $fileUploadSupport = false;
  185. /**
  186. * Initialize a Facebook Application.
  187. *
  188. * The configuration:
  189. * - appId: the application ID
  190. * - secret: the application secret
  191. * - fileUpload: (optional) boolean indicating if file uploads are enabled
  192. *
  193. * @param array $config The application configuration
  194. */
  195. public function __construct($config) {
  196. $this->setAppId($config['appId']);
  197. $this->setApiSecret($config['secret']);
  198. if (isset($config['fileUpload'])) {
  199. $this->setFileUploadSupport($config['fileUpload']);
  200. }
  201. $state = $this->getPersistentData('state');
  202. if (!empty($state)) {
  203. $this->state = $this->getPersistentData('state');
  204. }
  205. }
  206. /**
  207. * Set the Application ID.
  208. *
  209. * @param string $appId The Application ID
  210. * @return BaseFacebook
  211. */
  212. public function setAppId($appId) {
  213. $this->appId = $appId;
  214. return $this;
  215. }
  216. /**
  217. * Get the Application ID.
  218. *
  219. * @return string the Application ID
  220. */
  221. public function getAppId() {
  222. return $this->appId;
  223. }
  224. /**
  225. * Set the API Secret.
  226. *
  227. * @param string $apiSecret The API Secret
  228. * @return BaseFacebook
  229. */
  230. public function setApiSecret($apiSecret) {
  231. $this->apiSecret = $apiSecret;
  232. return $this;
  233. }
  234. /**
  235. * Get the API Secret.
  236. *
  237. * @return string the API Secret
  238. */
  239. public function getApiSecret() {
  240. return $this->apiSecret;
  241. }
  242. /**
  243. * Set the file upload support status.
  244. *
  245. * @param boolean $fileUploadSupport The file upload support status.
  246. * @return BaseFacebook
  247. */
  248. public function setFileUploadSupport($fileUploadSupport) {
  249. $this->fileUploadSupport = $fileUploadSupport;
  250. return $this;
  251. }
  252. /**
  253. * Get the file upload support status.
  254. *
  255. * @return boolean true if and only if the server supports file upload.
  256. */
  257. public function useFileUploadSupport() {
  258. return $this->fileUploadSupport;
  259. }
  260. /**
  261. * Sets the access token for api calls. Use this if you get
  262. * your access token by other means and just want the SDK
  263. * to use it.
  264. *
  265. * @param string $access_token an access token.
  266. * @return BaseFacebook
  267. */
  268. public function setAccessToken($access_token) {
  269. $this->accessToken = $access_token;
  270. return $this;
  271. }
  272. /**
  273. * Determines the access token that should be used for API calls.
  274. * The first time this is called, $this->accessToken is set equal
  275. * to either a valid user access token, or it's set to the application
  276. * access token if a valid user access token wasn't available. Subsequent
  277. * calls return whatever the first call returned.
  278. *
  279. * @return string The access token
  280. */
  281. public function getAccessToken() {
  282. if ($this->accessToken !== null) {
  283. // we've done this already and cached it. Just return.
  284. return $this->accessToken;
  285. }
  286. // first establish access token to be the application
  287. // access token, in case we navigate to the /oauth/access_token
  288. // endpoint, where SOME access token is required.
  289. $this->setAccessToken($this->getApplicationAccessToken());
  290. $user_access_token = $this->getUserAccessToken();
  291. if ($user_access_token) {
  292. $this->setAccessToken($user_access_token);
  293. }
  294. return $this->accessToken;
  295. }
  296. /**
  297. * Determines and returns the user access token, first using
  298. * the signed request if present, and then falling back on
  299. * the authorization code if present. The intent is to
  300. * return a valid user access token, or false if one is determined
  301. * to not be available.
  302. *
  303. * @return string A valid user access token, or false if one
  304. * could not be determined.
  305. */
  306. protected function getUserAccessToken() {
  307. // first, consider a signed request if it's supplied.
  308. // if there is a signed request, then it alone determines
  309. // the access token.
  310. $signed_request = $this->getSignedRequest();
  311. if ($signed_request) {
  312. // apps.facebook.com hands the access_token in the signed_request
  313. if (array_key_exists('oauth_token', $signed_request)) {
  314. $access_token = $signed_request['oauth_token'];
  315. $this->setPersistentData('access_token', $access_token);
  316. return $access_token;
  317. }
  318. // the JS SDK puts a code in with the redirect_uri of ''
  319. if (array_key_exists('code', $signed_request)) {
  320. $code = $signed_request['code'];
  321. $access_token = $this->getAccessTokenFromCode($code, '');
  322. if ($access_token) {
  323. $this->setPersistentData('code', $code);
  324. $this->setPersistentData('access_token', $access_token);
  325. return $access_token;
  326. }
  327. }
  328. // signed request states there's no access token, so anything
  329. // stored should be cleared.
  330. $this->clearAllPersistentData();
  331. return false; // respect the signed request's data, even
  332. // if there's an authorization code or something else
  333. }
  334. $code = $this->getCode();
  335. if ($code && $code != $this->getPersistentData('code')) {
  336. $access_token = $this->getAccessTokenFromCode($code);
  337. if ($access_token) {
  338. $this->setPersistentData('code', $code);
  339. $this->setPersistentData('access_token', $access_token);
  340. return $access_token;
  341. }
  342. // code was bogus, so everything based on it should be invalidated.
  343. $this->clearAllPersistentData();
  344. return false;
  345. }
  346. // as a fallback, just return whatever is in the persistent
  347. // store, knowing nothing explicit (signed request, authorization
  348. // code, etc.) was present to shadow it (or we saw a code in $_REQUEST,
  349. // but it's the same as what's in the persistent store)
  350. return $this->getPersistentData('access_token');
  351. }
  352. /**
  353. * Retrieve the signed request, either from a request parameter or,
  354. * if not present, from a cookie.
  355. *
  356. * @return string the signed request, if available, or null otherwise.
  357. */
  358. public function getSignedRequest() {
  359. if (!$this->signedRequest) {
  360. if (isset($_REQUEST['signed_request'])) {
  361. $this->signedRequest = $this->parseSignedRequest(
  362. $_REQUEST['signed_request']);
  363. } else if (isset($_COOKIE[$this->getSignedRequestCookieName()])) {
  364. $this->signedRequest = $this->parseSignedRequest(
  365. $_COOKIE[$this->getSignedRequestCookieName()]);
  366. }
  367. }
  368. return $this->signedRequest;
  369. }
  370. /**
  371. * Get the UID of the connected user, or 0
  372. * if the Facebook user is not connected.
  373. *
  374. * @return string the UID if available.
  375. */
  376. public function getUser() {
  377. if ($this->user !== null) {
  378. // we've already determined this and cached the value.
  379. return $this->user;
  380. }
  381. return $this->user = $this->getUserFromAvailableData();
  382. }
  383. /**
  384. * Determines the connected user by first examining any signed
  385. * requests, then considering an authorization code, and then
  386. * falling back to any persistent store storing the user.
  387. *
  388. * @return integer The id of the connected Facebook user,
  389. * or 0 if no such user exists.
  390. */
  391. protected function getUserFromAvailableData() {
  392. // if a signed request is supplied, then it solely determines
  393. // who the user is.
  394. $signed_request = $this->getSignedRequest();
  395. if ($signed_request) {
  396. if (array_key_exists('user_id', $signed_request)) {
  397. $user = $signed_request['user_id'];
  398. $this->setPersistentData('user_id', $signed_request['user_id']);
  399. return $user;
  400. }
  401. // if the signed request didn't present a user id, then invalidate
  402. // all entries in any persistent store.
  403. $this->clearAllPersistentData();
  404. return 0;
  405. }
  406. $user = $this->getPersistentData('user_id', $default = 0);
  407. $persisted_access_token = $this->getPersistentData('access_token');
  408. // use access_token to fetch user id if we have a user access_token, or if
  409. // the cached access token has changed.
  410. $access_token = $this->getAccessToken();
  411. if ($access_token &&
  412. $access_token != $this->getApplicationAccessToken() &&
  413. !($user && $persisted_access_token == $access_token)) {
  414. $user = $this->getUserFromAccessToken();
  415. if ($user) {
  416. $this->setPersistentData('user_id', $user);
  417. } else {
  418. $this->clearAllPersistentData();
  419. }
  420. }
  421. return $user;
  422. }
  423. /**
  424. * Get a Login URL for use with redirects. By default, full page redirect is
  425. * assumed. If you are using the generated URL with a window.open() call in
  426. * JavaScript, you can pass in display=popup as part of the $params.
  427. *
  428. * The parameters:
  429. * - redirect_uri: the url to go to after a successful login
  430. * - scope: comma separated list of requested extended perms
  431. *
  432. * @param array $params Provide custom parameters
  433. * @return string The URL for the login flow
  434. */
  435. public function getLoginUrl($params=array()) {
  436. $this->establishCSRFTokenState();
  437. $currentUrl = $this->getCurrentUrl();
  438. // if 'scope' is passed as an array, convert to comma separated list
  439. $scopeParams = isset($params['scope']) ? $params['scope'] : null;
  440. if ($scopeParams && is_array($scopeParams)) {
  441. $params['scope'] = implode(',', $scopeParams);
  442. }
  443. return $this->getUrl(
  444. 'www',
  445. 'dialog/oauth',
  446. array_merge(array(
  447. 'client_id' => $this->getAppId(),
  448. 'redirect_uri' => $currentUrl, // possibly overwritten
  449. 'state' => $this->state),
  450. $params));
  451. }
  452. /**
  453. * Get a Logout URL suitable for use with redirects.
  454. *
  455. * The parameters:
  456. * - next: the url to go to after a successful logout
  457. *
  458. * @param array $params Provide custom parameters
  459. * @return string The URL for the logout flow
  460. */
  461. public function getLogoutUrl($params=array()) {
  462. return $this->getUrl(
  463. 'www',
  464. 'logout.php',
  465. array_merge(array(
  466. 'next' => $this->getCurrentUrl(),
  467. 'access_token' => $this->getAccessToken(),
  468. ), $params)
  469. );
  470. }
  471. /**
  472. * Get a login status URL to fetch the status from Facebook.
  473. *
  474. * The parameters:
  475. * - ok_session: the URL to go to if a session is found
  476. * - no_session: the URL to go to if the user is not connected
  477. * - no_user: the URL to go to if the user is not signed into facebook
  478. *
  479. * @param array $params Provide custom parameters
  480. * @return string The URL for the logout flow
  481. */
  482. public function getLoginStatusUrl($params=array()) {
  483. return $this->getUrl(
  484. 'www',
  485. 'extern/login_status.php',
  486. array_merge(array(
  487. 'api_key' => $this->getAppId(),
  488. 'no_session' => $this->getCurrentUrl(),
  489. 'no_user' => $this->getCurrentUrl(),
  490. 'ok_session' => $this->getCurrentUrl(),
  491. 'session_version' => 3,
  492. ), $params)
  493. );
  494. }
  495. /**
  496. * Make an API call.
  497. *
  498. * @return mixed The decoded response
  499. */
  500. public function api(/* polymorphic */) {
  501. $args = func_get_args();
  502. if (is_array($args[0])) {
  503. return $this->_restserver($args[0]);
  504. } else {
  505. return call_user_func_array(array($this, '_graph'), $args);
  506. }
  507. }
  508. /**
  509. * Constructs and returns the name of the cookie that
  510. * potentially houses the signed request for the app user.
  511. * The cookie is not set by the BaseFacebook class, but
  512. * it may be set by the JavaScript SDK.
  513. *
  514. * @return string the name of the cookie that would house
  515. * the signed request value.
  516. */
  517. protected function getSignedRequestCookieName() {
  518. return 'fbsr_'.$this->getAppId();
  519. }
  520. /**
  521. * Get the authorization code from the query parameters, if it exists,
  522. * and otherwise return false to signal no authorization code was
  523. * discoverable.
  524. *
  525. * @return mixed The authorization code, or false if the authorization
  526. * code could not be determined.
  527. */
  528. protected function getCode() {
  529. if (isset($_REQUEST['code'])) {
  530. if ($this->state !== null &&
  531. isset($_REQUEST['state']) &&
  532. $this->state === $_REQUEST['state']) {
  533. // CSRF state has done its job, so clear it
  534. $this->state = null;
  535. $this->clearPersistentData('state');
  536. return $_REQUEST['code'];
  537. } else {
  538. self::errorLog('CSRF state token does not match one provided.');
  539. return false;
  540. }
  541. }
  542. return false;
  543. }
  544. /**
  545. * Retrieves the UID with the understanding that
  546. * $this->accessToken has already been set and is
  547. * seemingly legitimate. It relies on Facebook's Graph API
  548. * to retrieve user information and then extract
  549. * the user ID.
  550. *
  551. * @return integer Returns the UID of the Facebook user, or 0
  552. * if the Facebook user could not be determined.
  553. */
  554. protected function getUserFromAccessToken() {
  555. try {
  556. $user_info = $this->api('/me');
  557. return $user_info['id'];
  558. } catch (FacebookApiException $e) {
  559. return 0;
  560. }
  561. }
  562. /**
  563. * Returns the access token that should be used for logged out
  564. * users when no authorization code is available.
  565. *
  566. * @return string The application access token, useful for gathering
  567. * public information about users and applications.
  568. */
  569. protected function getApplicationAccessToken() {
  570. return $this->appId.'|'.$this->apiSecret;
  571. }
  572. /**
  573. * Lays down a CSRF state token for this process.
  574. *
  575. * @return void
  576. */
  577. protected function establishCSRFTokenState() {
  578. if ($this->state === null) {
  579. $this->state = md5(uniqid(mt_rand(), true));
  580. $this->setPersistentData('state', $this->state);
  581. }
  582. }
  583. /**
  584. * Retrieves an access token for the given authorization code
  585. * (previously generated from www.facebook.com on behalf of
  586. * a specific user). The authorization code is sent to graph.facebook.com
  587. * and a legitimate access token is generated provided the access token
  588. * and the user for which it was generated all match, and the user is
  589. * either logged in to Facebook or has granted an offline access permission.
  590. *
  591. * @param string $code An authorization code.
  592. * @return mixed An access token exchanged for the authorization code, or
  593. * false if an access token could not be generated.
  594. */
  595. protected function getAccessTokenFromCode($code, $redirect_uri = null) {
  596. if (empty($code)) {
  597. return false;
  598. }
  599. if ($redirect_uri === null) {
  600. $redirect_uri = $this->getCurrentUrl();
  601. }
  602. try {
  603. // need to circumvent json_decode by calling _oauthRequest
  604. // directly, since response isn't JSON format.
  605. $access_token_response =
  606. $this->_oauthRequest(
  607. $this->getUrl('graph', '/oauth/access_token'),
  608. $params = array('client_id' => $this->getAppId(),
  609. 'client_secret' => $this->getApiSecret(),
  610. 'redirect_uri' => $redirect_uri,
  611. 'code' => $code));
  612. } catch (FacebookApiException $e) {
  613. // most likely that user very recently revoked authorization.
  614. // In any event, we don't have an access token, so say so.
  615. return false;
  616. }
  617. if (empty($access_token_response)) {
  618. return false;
  619. }
  620. $response_params = array();
  621. parse_str($access_token_response, $response_params);
  622. if (!isset($response_params['access_token'])) {
  623. return false;
  624. }
  625. return $response_params['access_token'];
  626. }
  627. /**
  628. * Invoke the old restserver.php endpoint.
  629. *
  630. * @param array $params Method call object
  631. *
  632. * @return mixed The decoded response object
  633. * @throws FacebookApiException
  634. */
  635. protected function _restserver($params) {
  636. // generic application level parameters
  637. $params['api_key'] = $this->getAppId();
  638. $params['format'] = 'json-strings';
  639. $result = json_decode($this->_oauthRequest(
  640. $this->getApiUrl($params['method']),
  641. $params
  642. ), true);
  643. // results are returned, errors are thrown
  644. if (is_array($result) && isset($result['error_code'])) {
  645. $this->throwAPIException($result);
  646. }
  647. if ($params['method'] === 'auth.expireSession' ||
  648. $params['method'] === 'auth.revokeAuthorization') {
  649. $this->destroySession();
  650. }
  651. return $result;
  652. }
  653. /**
  654. * Invoke the Graph API.
  655. *
  656. * @param string $path The path (required)
  657. * @param string $method The http method (default 'GET')
  658. * @param array $params The query/post data
  659. *
  660. * @return mixed The decoded response object
  661. * @throws FacebookApiException
  662. */
  663. protected function _graph($path, $method = 'GET', $params = array()) {
  664. if (is_array($method) && empty($params)) {
  665. $params = $method;
  666. $method = 'GET';
  667. }
  668. $params['method'] = $method; // method override as we always do a POST
  669. $result = json_decode($this->_oauthRequest(
  670. $this->getUrl('graph', $path),
  671. $params
  672. ), true);
  673. // results are returned, errors are thrown
  674. if (is_array($result) && isset($result['error'])) {
  675. $this->throwAPIException($result);
  676. }
  677. return $result;
  678. }
  679. /**
  680. * Make a OAuth Request.
  681. *
  682. * @param string $url The path (required)
  683. * @param array $params The query/post data
  684. *
  685. * @return string The decoded response object
  686. * @throws FacebookApiException
  687. */
  688. protected function _oauthRequest($url, $params) {
  689. if (!isset($params['access_token'])) {
  690. $params['access_token'] = $this->getAccessToken();
  691. }
  692. // json_encode all params values that are not strings
  693. foreach ($params as $key => $value) {
  694. if (!is_string($value)) {
  695. $params[$key] = json_encode($value);
  696. }
  697. }
  698. return $this->makeRequest($url, $params);
  699. }
  700. /**
  701. * Makes an HTTP request. This method can be overridden by subclasses if
  702. * developers want to do fancier things or use something other than curl to
  703. * make the request.
  704. *
  705. * @param string $url The URL to make the request to
  706. * @param array $params The parameters to use for the POST body
  707. * @param CurlHandler $ch Initialized curl handle
  708. *
  709. * @return string The response text
  710. */
  711. protected function makeRequest($url, $params, $ch=null) {
  712. if (!$ch) {
  713. $ch = curl_init();
  714. }
  715. $opts = self::$CURL_OPTS;
  716. if ($this->useFileUploadSupport()) {
  717. $opts[CURLOPT_POSTFIELDS] = $params;
  718. } else {
  719. $opts[CURLOPT_POSTFIELDS] = http_build_query($params, null, '&');
  720. }
  721. $opts[CURLOPT_URL] = $url;
  722. // disable the 'Expect: 100-continue' behaviour. This causes CURL to wait
  723. // for 2 seconds if the server does not support this header.
  724. if (isset($opts[CURLOPT_HTTPHEADER])) {
  725. $existing_headers = $opts[CURLOPT_HTTPHEADER];
  726. $existing_headers[] = 'Expect:';
  727. $opts[CURLOPT_HTTPHEADER] = $existing_headers;
  728. } else {
  729. $opts[CURLOPT_HTTPHEADER] = array('Expect:');
  730. }
  731. curl_setopt_array($ch, $opts);
  732. $result = curl_exec($ch);
  733. if (curl_errno($ch) == 60) { // CURLE_SSL_CACERT
  734. self::errorLog('Invalid or no certificate authority found, '.
  735. 'using bundled information');
  736. curl_setopt($ch, CURLOPT_CAINFO,
  737. dirname(__FILE__) . '/fb_ca_chain_bundle.crt');
  738. $result = curl_exec($ch);
  739. }
  740. if ($result === false) {
  741. $e = new FacebookApiException(array(
  742. 'error_code' => curl_errno($ch),
  743. 'error' => array(
  744. 'message' => curl_error($ch),
  745. 'type' => 'CurlException',
  746. ),
  747. ));
  748. curl_close($ch);
  749. throw $e;
  750. }
  751. curl_close($ch);
  752. return $result;
  753. }
  754. /**
  755. * Parses a signed_request and validates the signature.
  756. *
  757. * @param string $signed_request A signed token
  758. * @return array The payload inside it or null if the sig is wrong
  759. */
  760. protected function parseSignedRequest($signed_request) {
  761. list($encoded_sig, $payload) = explode('.', $signed_request, 2);
  762. // decode the data
  763. $sig = self::base64UrlDecode($encoded_sig);
  764. $data = json_decode(self::base64UrlDecode($payload), true);
  765. if (strtoupper($data['algorithm']) !== 'HMAC-SHA256') {
  766. self::errorLog('Unknown algorithm. Expected HMAC-SHA256');
  767. return null;
  768. }
  769. // check sig
  770. $expected_sig = hash_hmac('sha256', $payload,
  771. $this->getApiSecret(), $raw = true);
  772. if ($sig !== $expected_sig) {
  773. self::errorLog('Bad Signed JSON signature!');
  774. return null;
  775. }
  776. return $data;
  777. }
  778. /**
  779. * Build the URL for api given parameters.
  780. *
  781. * @param $method String the method name.
  782. * @return string The URL for the given parameters
  783. */
  784. protected function getApiUrl($method) {
  785. static $READ_ONLY_CALLS =
  786. array('admin.getallocation' => 1,
  787. 'admin.getappproperties' => 1,
  788. 'admin.getbannedusers' => 1,
  789. 'admin.getlivestreamvialink' => 1,
  790. 'admin.getmetrics' => 1,
  791. 'admin.getrestrictioninfo' => 1,
  792. 'application.getpublicinfo' => 1,
  793. 'auth.getapppublickey' => 1,
  794. 'auth.getsession' => 1,
  795. 'auth.getsignedpublicsessiondata' => 1,
  796. 'comments.get' => 1,
  797. 'connect.getunconnectedfriendscount' => 1,
  798. 'dashboard.getactivity' => 1,
  799. 'dashboard.getcount' => 1,
  800. 'dashboard.getglobalnews' => 1,
  801. 'dashboard.getnews' => 1,
  802. 'dashboard.multigetcount' => 1,
  803. 'dashboard.multigetnews' => 1,
  804. 'data.getcookies' => 1,
  805. 'events.get' => 1,
  806. 'events.getmembers' => 1,
  807. 'fbml.getcustomtags' => 1,
  808. 'feed.getappfriendstories' => 1,
  809. 'feed.getregisteredtemplatebundlebyid' => 1,
  810. 'feed.getregisteredtemplatebundles' => 1,
  811. 'fql.multiquery' => 1,
  812. 'fql.query' => 1,
  813. 'friends.arefriends' => 1,
  814. 'friends.get' => 1,
  815. 'friends.getappusers' => 1,
  816. 'friends.getlists' => 1,
  817. 'friends.getmutualfriends' => 1,
  818. 'gifts.get' => 1,
  819. 'groups.get' => 1,
  820. 'groups.getmembers' => 1,
  821. 'intl.gettranslations' => 1,
  822. 'links.get' => 1,
  823. 'notes.get' => 1,
  824. 'notifications.get' => 1,
  825. 'pages.getinfo' => 1,
  826. 'pages.isadmin' => 1,
  827. 'pages.isappadded' => 1,
  828. 'pages.isfan' => 1,
  829. 'permissions.checkavailableapiaccess' => 1,
  830. 'permissions.checkgrantedapiaccess' => 1,
  831. 'photos.get' => 1,
  832. 'photos.getalbums' => 1,
  833. 'photos.gettags' => 1,
  834. 'profile.getinfo' => 1,
  835. 'profile.getinfooptions' => 1,
  836. 'stream.get' => 1,
  837. 'stream.getcomments' => 1,
  838. 'stream.getfilters' => 1,
  839. 'users.getinfo' => 1,
  840. 'users.getloggedinuser' => 1,
  841. 'users.getstandardinfo' => 1,
  842. 'users.hasapppermission' => 1,
  843. 'users.isappuser' => 1,
  844. 'users.isverified' => 1,
  845. 'video.getuploadlimits' => 1);
  846. $name = 'api';
  847. if (isset($READ_ONLY_CALLS[strtolower($method)])) {
  848. $name = 'api_read';
  849. } else if (strtolower($method) == 'video.upload') {
  850. $name = 'api_video';
  851. }
  852. return self::getUrl($name, 'restserver.php');
  853. }
  854. /**
  855. * Build the URL for given domain alias, path and parameters.
  856. *
  857. * @param $name string The name of the domain
  858. * @param $path string Optional path (without a leading slash)
  859. * @param $params array Optional query parameters
  860. *
  861. * @return string The URL for the given parameters
  862. */
  863. protected function getUrl($name, $path='', $params=array()) {
  864. $url = self::$DOMAIN_MAP[$name];
  865. if ($path) {
  866. if ($path[0] === '/') {
  867. $path = substr($path, 1);
  868. }
  869. $url .= $path;
  870. }
  871. if ($params) {
  872. $url .= '?' . http_build_query($params, null, '&');
  873. }
  874. return $url;
  875. }
  876. /**
  877. * Returns the Current URL, stripping it of known FB parameters that should
  878. * not persist.
  879. *
  880. * @return string The current URL
  881. */
  882. protected function getCurrentUrl() {
  883. if (isset($_SERVER['HTTPS']) && ($_SERVER['HTTPS'] == 'on' || $_SERVER['HTTPS'] == 1)
  884. || isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https'
  885. ) {
  886. $protocol = 'https://';
  887. }
  888. else {
  889. $protocol = 'http://';
  890. }
  891. $currentUrl = $protocol . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
  892. $parts = parse_url($currentUrl);
  893. $query = '';
  894. if (!empty($parts['query'])) {
  895. // drop known fb params
  896. $params = explode('&', $parts['query']);
  897. $retained_params = array();
  898. foreach ($params as $param) {
  899. if ($this->shouldRetainParam($param)) {
  900. $retained_params[] = $param;
  901. }
  902. }
  903. if (!empty($retained_params)) {
  904. $query = '?'.implode($retained_params, '&');
  905. }
  906. }
  907. // use port if non default
  908. $port =
  909. isset($parts['port']) &&
  910. (($protocol === 'http://' && $parts['port'] !== 80) ||
  911. ($protocol === 'https://' && $parts['port'] !== 443))
  912. ? ':' . $parts['port'] : '';
  913. // rebuild
  914. return $protocol . $parts['host'] . $port . $parts['path'] . $query;
  915. }
  916. /**
  917. * Returns true if and only if the key or key/value pair should
  918. * be retained as part of the query string. This amounts to
  919. * a brute-force search of the very small list of Facebook-specific
  920. * params that should be stripped out.
  921. *
  922. * @param string $param A key or key/value pair within a URL's query (e.g.
  923. * 'foo=a', 'foo=', or 'foo'.
  924. *
  925. * @return boolean
  926. */
  927. protected function shouldRetainParam($param) {
  928. foreach (self::$DROP_QUERY_PARAMS as $drop_query_param) {
  929. if (strpos($param, $drop_query_param.'=') === 0) {
  930. return false;
  931. }
  932. }
  933. return true;
  934. }
  935. /**
  936. * Analyzes the supplied result to see if it was thrown
  937. * because the access token is no longer valid. If that is
  938. * the case, then the persistent store is cleared.
  939. *
  940. * @param $result array A record storing the error message returned
  941. * by a failed API call.
  942. */
  943. protected function throwAPIException($result) {
  944. $e = new FacebookApiException($result);
  945. switch ($e->getType()) {
  946. // OAuth 2.0 Draft 00 style
  947. case 'OAuthException':
  948. // OAuth 2.0 Draft 10 style
  949. case 'invalid_token':
  950. $message = $e->getMessage();
  951. if ((strpos($message, 'Error validating access token') !== false) ||
  952. (strpos($message, 'Invalid OAuth access token') !== false)) {
  953. $this->setAccessToken(null);
  954. $this->user = 0;
  955. $this->clearAllPersistentData();
  956. }
  957. }
  958. throw $e;
  959. }
  960. /**
  961. * Prints to the error log if you aren't in command line mode.
  962. *
  963. * @param string $msg Log message
  964. */
  965. protected static function errorLog($msg) {
  966. // disable error log if we are running in a CLI environment
  967. // @codeCoverageIgnoreStart
  968. if (php_sapi_name() != 'cli') {
  969. error_log($msg);
  970. }
  971. // uncomment this if you want to see the errors on the page
  972. // print 'error_log: '.$msg."\n";
  973. // @codeCoverageIgnoreEnd
  974. }
  975. /**
  976. * Base64 encoding that doesn't need to be urlencode()ed.
  977. * Exactly the same as base64_encode except it uses
  978. * - instead of +
  979. * _ instead of /
  980. *
  981. * @param string $input base64UrlEncoded string
  982. * @return string
  983. */
  984. protected static function base64UrlDecode($input) {
  985. return base64_decode(strtr($input, '-_', '+/'));
  986. }
  987. /**
  988. * Destroy the current session
  989. */
  990. public function destroySession() {
  991. $this->setAccessToken(null);
  992. $this->user = 0;
  993. $this->clearAllPersistentData();
  994. }
  995. /**
  996. * Each of the following four methods should be overridden in
  997. * a concrete subclass, as they are in the provided Facebook class.
  998. * The Facebook class uses PHP sessions to provide a primitive
  999. * persistent store, but another subclass--one that you implement--
  1000. * might use a database, memcache, or an in-memory cache.
  1001. *
  1002. * @see Facebook
  1003. */
  1004. /**
  1005. * Stores the given ($key, $value) pair, so that future calls to
  1006. * getPersistentData($key) return $value. This call may be in another request.
  1007. *
  1008. * @param string $key
  1009. * @param array $value
  1010. *
  1011. * @return void
  1012. */
  1013. abstract protected function setPersistentData($key, $value);
  1014. /**
  1015. * Get the data for $key, persisted by BaseFacebook::setPersistentData()
  1016. *
  1017. * @param string $key The key of the data to retrieve
  1018. * @param boolean $default The default value to return if $key is not found
  1019. *
  1020. * @return mixed
  1021. */
  1022. abstract protected function getPersistentData($key, $default = false);
  1023. /**
  1024. * Clear the data with $key from the persistent storage
  1025. *
  1026. * @param string $key
  1027. * @return void
  1028. */
  1029. abstract protected function clearPersistentData($key);
  1030. /**
  1031. * Clear all data from the persistent storage
  1032. *
  1033. * @return void
  1034. */
  1035. abstract protected function clearAllPersistentData();
  1036. }
  1037. }