3 * Copyright 2011 Facebook, Inc.
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
9 * http://www.apache.org/licenses/LICENSE-2.0
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
18 if (!function_exists('curl_init')) {
19 throw new Exception('Facebook needs the CURL PHP extension.');
21 if (!function_exists('json_decode')) {
22 throw new Exception('Facebook needs the JSON PHP extension.');
26 * Thrown when an API call returns an exception.
28 * @author Naitik Shah <naitik@facebook.com>
30 class FacebookApiException extends Exception
33 * The result from the API server that represents the exception information.
38 * Make a new API Exception with the given result.
40 * @param array $result The result from the API server
42 public function __construct($result) {
43 $this->result = $result;
45 $code = isset($result['error_code']) ? $result['error_code'] : 0;
47 if (isset($result['error_description'])) {
48 // OAuth 2.0 Draft 10 style
49 $msg = $result['error_description'];
50 } else if (isset($result['error']) && is_array($result['error'])) {
51 // OAuth 2.0 Draft 00 style
52 $msg = $result['error']['message'];
53 } else if (isset($result['error_msg'])) {
55 $msg = $result['error_msg'];
57 $msg = 'Unknown Error. Check getResult()';
60 parent::__construct($msg, $code);
64 * Return the associated result object returned by the API server.
66 * @return array The result from the API server
68 public function getResult() {
73 * Returns the associated type for the error. This will default to
74 * 'Exception' when a type is not available.
78 public function getType() {
79 if (isset($this->result['error'])) {
80 $error = $this->result['error'];
81 if (is_string($error)) {
82 // OAuth 2.0 Draft 10 style
84 } else if (is_array($error)) {
85 // OAuth 2.0 Draft 00 style
86 if (isset($error['type'])) {
87 return $error['type'];
96 * To make debugging easier.
98 * @return string The string representation of the error
100 public function __toString() {
101 $str = $this->getType() . ': ';
102 if ($this->code != 0) {
103 $str .= $this->code . ': ';
105 return $str . $this->message;
110 * Provides access to the Facebook Platform. This class provides
111 * a majority of the functionality needed, but the class is abstract
112 * because it is designed to be sub-classed. The subclass must
113 * implement the four abstract methods listed at the bottom of
116 * @author Naitik Shah <naitik@facebook.com>
118 abstract class BaseFacebook
123 const VERSION = '3.1.1';
126 * Default options for curl.
128 public static $CURL_OPTS = array(
129 CURLOPT_CONNECTTIMEOUT => 10,
130 CURLOPT_RETURNTRANSFER => true,
131 CURLOPT_TIMEOUT => 60,
132 CURLOPT_USERAGENT => 'facebook-php-3.1',
136 * List of query parameters that get automatically dropped when rebuilding
139 protected static $DROP_QUERY_PARAMS = array(
146 * Maps aliases to Facebook domains.
148 public static $DOMAIN_MAP = array(
149 'api' => 'https://api.facebook.com/',
150 'api_video' => 'https://api-video.facebook.com/',
151 'api_read' => 'https://api-read.facebook.com/',
152 'graph' => 'https://graph.facebook.com/',
153 'www' => 'https://www.facebook.com/',
157 * The Application ID.
164 * The Application API Secret.
168 protected $apiSecret;
171 * The ID of the Facebook user, or 0 if the user is logged out.
178 * The data from the signed_request token.
180 protected $signedRequest;
183 * A CSRF state variable to assist in the defense against CSRF attacks.
188 * The OAuth access token received in exchange for a valid authorization
189 * code. null means the access token has yet to be determined.
193 protected $accessToken = null;
196 * Indicates if the CURL based @ syntax for file uploads is enabled.
200 protected $fileUploadSupport = false;
203 * Initialize a Facebook Application.
206 * - appId: the application ID
207 * - secret: the application secret
208 * - fileUpload: (optional) boolean indicating if file uploads are enabled
210 * @param array $config The application configuration
212 public function __construct($config) {
213 $this->setAppId($config['appId']);
214 $this->setApiSecret($config['secret']);
215 if (isset($config['fileUpload'])) {
216 $this->setFileUploadSupport($config['fileUpload']);
219 $state = $this->getPersistentData('state');
220 if (!empty($state)) {
221 $this->state = $this->getPersistentData('state');
226 * Set the Application ID.
228 * @param string $appId The Application ID
229 * @return BaseFacebook
231 public function setAppId($appId) {
232 $this->appId = $appId;
237 * Get the Application ID.
239 * @return string the Application ID
241 public function getAppId() {
246 * Set the API Secret.
248 * @param string $apiSecret The API Secret
249 * @return BaseFacebook
251 public function setApiSecret($apiSecret) {
252 $this->apiSecret = $apiSecret;
257 * Get the API Secret.
259 * @return string the API Secret
261 public function getApiSecret() {
262 return $this->apiSecret;
266 * Set the file upload support status.
268 * @param boolean $fileUploadSupport The file upload support status.
269 * @return BaseFacebook
271 public function setFileUploadSupport($fileUploadSupport) {
272 $this->fileUploadSupport = $fileUploadSupport;
277 * Get the file upload support status.
279 * @return boolean true if and only if the server supports file upload.
281 public function useFileUploadSupport() {
282 return $this->fileUploadSupport;
286 * Sets the access token for api calls. Use this if you get
287 * your access token by other means and just want the SDK
290 * @param string $access_token an access token.
291 * @return BaseFacebook
293 public function setAccessToken($access_token) {
294 $this->accessToken = $access_token;
299 * Determines the access token that should be used for API calls.
300 * The first time this is called, $this->accessToken is set equal
301 * to either a valid user access token, or it's set to the application
302 * access token if a valid user access token wasn't available. Subsequent
303 * calls return whatever the first call returned.
305 * @return string The access token
307 public function getAccessToken() {
308 if ($this->accessToken !== null) {
309 // we've done this already and cached it. Just return.
310 return $this->accessToken;
313 // first establish access token to be the application
314 // access token, in case we navigate to the /oauth/access_token
315 // endpoint, where SOME access token is required.
316 $this->setAccessToken($this->getApplicationAccessToken());
317 if ($user_access_token = $this->getUserAccessToken()) {
318 $this->setAccessToken($user_access_token);
321 return $this->accessToken;
325 * Determines and returns the user access token, first using
326 * the signed request if present, and then falling back on
327 * the authorization code if present. The intent is to
328 * return a valid user access token, or false if one is determined
329 * to not be available.
331 * @return string A valid user access token, or false if one
332 * could not be determined.
334 protected function getUserAccessToken() {
335 // first, consider a signed request if it's supplied.
336 // if there is a signed request, then it alone determines
338 $signed_request = $this->getSignedRequest();
339 if ($signed_request) {
340 // apps.facebook.com hands the access_token in the signed_request
341 if (array_key_exists('oauth_token', $signed_request)) {
342 $access_token = $signed_request['oauth_token'];
343 $this->setPersistentData('access_token', $access_token);
344 return $access_token;
347 // the JS SDK puts a code in with the redirect_uri of ''
348 if (array_key_exists('code', $signed_request)) {
349 $code = $signed_request['code'];
350 $access_token = $this->getAccessTokenFromCode($code, '');
352 $this->setPersistentData('code', $code);
353 $this->setPersistentData('access_token', $access_token);
354 return $access_token;
358 // signed request states there's no access token, so anything
359 // stored should be cleared.
360 $this->clearAllPersistentData();
361 return false; // respect the signed request's data, even
362 // if there's an authorization code or something else
365 $code = $this->getCode();
366 if ($code && $code != $this->getPersistentData('code')) {
367 $access_token = $this->getAccessTokenFromCode($code);
369 $this->setPersistentData('code', $code);
370 $this->setPersistentData('access_token', $access_token);
371 return $access_token;
374 // code was bogus, so everything based on it should be invalidated.
375 $this->clearAllPersistentData();
379 // as a fallback, just return whatever is in the persistent
380 // store, knowing nothing explicit (signed request, authorization
381 // code, etc.) was present to shadow it (or we saw a code in $_REQUEST,
382 // but it's the same as what's in the persistent store)
383 return $this->getPersistentData('access_token');
387 * Retrieve the signed request, either from a request parameter or,
388 * if not present, from a cookie.
390 * @return string the signed request, if available, or null otherwise.
392 public function getSignedRequest() {
393 if (!$this->signedRequest) {
394 if (isset($_REQUEST['signed_request'])) {
395 $this->signedRequest = $this->parseSignedRequest(
396 $_REQUEST['signed_request']);
397 } else if (isset($_COOKIE[$this->getSignedRequestCookieName()])) {
398 $this->signedRequest = $this->parseSignedRequest(
399 $_COOKIE[$this->getSignedRequestCookieName()]);
402 return $this->signedRequest;
406 * Get the UID of the connected user, or 0
407 * if the Facebook user is not connected.
409 * @return string the UID if available.
411 public function getUser() {
412 if ($this->user !== null) {
413 // we've already determined this and cached the value.
417 return $this->user = $this->getUserFromAvailableData();
421 * Determines the connected user by first examining any signed
422 * requests, then considering an authorization code, and then
423 * falling back to any persistent store storing the user.
425 * @return integer The id of the connected Facebook user,
426 * or 0 if no such user exists.
428 protected function getUserFromAvailableData() {
429 // if a signed request is supplied, then it solely determines
431 $signed_request = $this->getSignedRequest();
432 if ($signed_request) {
433 if (array_key_exists('user_id', $signed_request)) {
434 $user = $signed_request['user_id'];
435 $this->setPersistentData('user_id', $signed_request['user_id']);
439 // if the signed request didn't present a user id, then invalidate
440 // all entries in any persistent store.
441 $this->clearAllPersistentData();
445 $user = $this->getPersistentData('user_id', $default = 0);
446 $persisted_access_token = $this->getPersistentData('access_token');
448 // use access_token to fetch user id if we have a user access_token, or if
449 // the cached access token has changed.
450 $access_token = $this->getAccessToken();
452 $access_token != $this->getApplicationAccessToken() &&
453 !($user && $persisted_access_token == $access_token)) {
454 $user = $this->getUserFromAccessToken();
456 $this->setPersistentData('user_id', $user);
458 $this->clearAllPersistentData();
466 * Get a Login URL for use with redirects. By default, full page redirect is
467 * assumed. If you are using the generated URL with a window.open() call in
468 * JavaScript, you can pass in display=popup as part of the $params.
471 * - redirect_uri: the url to go to after a successful login
472 * - scope: comma separated list of requested extended perms
474 * @param array $params Provide custom parameters
475 * @return string The URL for the login flow
477 public function getLoginUrl($params=array()) {
478 $this->establishCSRFTokenState();
479 $currentUrl = $this->getCurrentUrl();
481 // if 'scope' is passed as an array, convert to comma separated list
482 $scopeParams = isset($params['scope']) ? $params['scope'] : null;
483 if ($scopeParams && is_array($scopeParams)) {
484 $params['scope'] = implode(',', $scopeParams);
487 return $this->getUrl(
491 'client_id' => $this->getAppId(),
492 'redirect_uri' => $currentUrl, // possibly overwritten
493 'state' => $this->state),
498 * Get a Logout URL suitable for use with redirects.
501 * - next: the url to go to after a successful logout
503 * @param array $params Provide custom parameters
504 * @return string The URL for the logout flow
506 public function getLogoutUrl($params=array()) {
507 return $this->getUrl(
511 'next' => $this->getCurrentUrl(),
512 'access_token' => $this->getAccessToken(),
518 * Get a login status URL to fetch the status from Facebook.
521 * - ok_session: the URL to go to if a session is found
522 * - no_session: the URL to go to if the user is not connected
523 * - no_user: the URL to go to if the user is not signed into facebook
525 * @param array $params Provide custom parameters
526 * @return string The URL for the logout flow
528 public function getLoginStatusUrl($params=array()) {
529 return $this->getUrl(
531 'extern/login_status.php',
533 'api_key' => $this->getAppId(),
534 'no_session' => $this->getCurrentUrl(),
535 'no_user' => $this->getCurrentUrl(),
536 'ok_session' => $this->getCurrentUrl(),
537 'session_version' => 3,
545 * @return mixed The decoded response
547 public function api(/* polymorphic */) {
548 $args = func_get_args();
549 if (is_array($args[0])) {
550 return $this->_restserver($args[0]);
552 return call_user_func_array(array($this, '_graph'), $args);
557 * Constructs and returns the name of the cookie that
558 * potentially houses the signed request for the app user.
559 * The cookie is not set by the BaseFacebook class, but
560 * it may be set by the JavaScript SDK.
562 * @return string the name of the cookie that would house
563 * the signed request value.
565 protected function getSignedRequestCookieName() {
566 return 'fbsr_'.$this->getAppId();
570 * Get the authorization code from the query parameters, if it exists,
571 * and otherwise return false to signal no authorization code was
574 * @return mixed The authorization code, or false if the authorization
575 * code could not be determined.
577 protected function getCode() {
578 if (isset($_REQUEST['code'])) {
579 if ($this->state !== null &&
580 isset($_REQUEST['state']) &&
581 $this->state === $_REQUEST['state']) {
583 // CSRF state has done its job, so clear it
585 $this->clearPersistentData('state');
586 return $_REQUEST['code'];
588 self::errorLog('CSRF state token does not match one provided.');
597 * Retrieves the UID with the understanding that
598 * $this->accessToken has already been set and is
599 * seemingly legitimate. It relies on Facebook's Graph API
600 * to retrieve user information and then extract
603 * @return integer Returns the UID of the Facebook user, or 0
604 * if the Facebook user could not be determined.
606 protected function getUserFromAccessToken() {
608 $user_info = $this->api('/me');
609 return $user_info['id'];
610 } catch (FacebookApiException $e) {
616 * Returns the access token that should be used for logged out
617 * users when no authorization code is available.
619 * @return string The application access token, useful for gathering
620 * public information about users and applications.
622 protected function getApplicationAccessToken() {
623 return $this->appId.'|'.$this->apiSecret;
627 * Lays down a CSRF state token for this process.
631 protected function establishCSRFTokenState() {
632 if ($this->state === null) {
633 $this->state = md5(uniqid(mt_rand(), true));
634 $this->setPersistentData('state', $this->state);
639 * Retrieves an access token for the given authorization code
640 * (previously generated from www.facebook.com on behalf of
641 * a specific user). The authorization code is sent to graph.facebook.com
642 * and a legitimate access token is generated provided the access token
643 * and the user for which it was generated all match, and the user is
644 * either logged in to Facebook or has granted an offline access permission.
646 * @param string $code An authorization code.
647 * @return mixed An access token exchanged for the authorization code, or
648 * false if an access token could not be generated.
650 protected function getAccessTokenFromCode($code, $redirect_uri = null) {
655 if ($redirect_uri === null) {
656 $redirect_uri = $this->getCurrentUrl();
660 // need to circumvent json_decode by calling _oauthRequest
661 // directly, since response isn't JSON format.
662 $access_token_response =
663 $this->_oauthRequest(
664 $this->getUrl('graph', '/oauth/access_token'),
665 $params = array('client_id' => $this->getAppId(),
666 'client_secret' => $this->getApiSecret(),
667 'redirect_uri' => $redirect_uri,
669 } catch (FacebookApiException $e) {
670 // most likely that user very recently revoked authorization.
671 // In any event, we don't have an access token, so say so.
675 if (empty($access_token_response)) {
679 $response_params = array();
680 parse_str($access_token_response, $response_params);
681 if (!isset($response_params['access_token'])) {
685 return $response_params['access_token'];
689 * Invoke the old restserver.php endpoint.
691 * @param array $params Method call object
693 * @return mixed The decoded response object
694 * @throws FacebookApiException
696 protected function _restserver($params) {
697 // generic application level parameters
698 $params['api_key'] = $this->getAppId();
699 $params['format'] = 'json-strings';
701 $result = json_decode($this->_oauthRequest(
702 $this->getApiUrl($params['method']),
706 // results are returned, errors are thrown
707 if (is_array($result) && isset($result['error_code'])) {
708 throw new FacebookApiException($result);
715 * Invoke the Graph API.
717 * @param string $path The path (required)
718 * @param string $method The http method (default 'GET')
719 * @param array $params The query/post data
721 * @return mixed The decoded response object
722 * @throws FacebookApiException
724 protected function _graph($path, $method = 'GET', $params = array()) {
725 if (is_array($method) && empty($params)) {
729 $params['method'] = $method; // method override as we always do a POST
731 $result = json_decode($this->_oauthRequest(
732 $this->getUrl('graph', $path),
736 // results are returned, errors are thrown
737 if (is_array($result) && isset($result['error'])) {
738 $this->throwAPIException($result);
745 * Make a OAuth Request.
747 * @param string $url The path (required)
748 * @param array $params The query/post data
750 * @return string The decoded response object
751 * @throws FacebookApiException
753 protected function _oauthRequest($url, $params) {
754 if (!isset($params['access_token'])) {
755 $params['access_token'] = $this->getAccessToken();
758 // json_encode all params values that are not strings
759 foreach ($params as $key => $value) {
760 if (!is_string($value)) {
761 $params[$key] = json_encode($value);
765 return $this->makeRequest($url, $params);
769 * Makes an HTTP request. This method can be overridden by subclasses if
770 * developers want to do fancier things or use something other than curl to
773 * @param string $url The URL to make the request to
774 * @param array $params The parameters to use for the POST body
775 * @param CurlHandler $ch Initialized curl handle
777 * @return string The response text
779 protected function makeRequest($url, $params, $ch=null) {
784 $opts = self::$CURL_OPTS;
785 if ($this->useFileUploadSupport()) {
786 $opts[CURLOPT_POSTFIELDS] = $params;
788 $opts[CURLOPT_POSTFIELDS] = http_build_query($params, null, '&');
790 $opts[CURLOPT_URL] = $url;
792 // disable the 'Expect: 100-continue' behaviour. This causes CURL to wait
793 // for 2 seconds if the server does not support this header.
794 if (isset($opts[CURLOPT_HTTPHEADER])) {
795 $existing_headers = $opts[CURLOPT_HTTPHEADER];
796 $existing_headers[] = 'Expect:';
797 $opts[CURLOPT_HTTPHEADER] = $existing_headers;
799 $opts[CURLOPT_HTTPHEADER] = array('Expect:');
802 curl_setopt_array($ch, $opts);
803 $result = curl_exec($ch);
805 if (curl_errno($ch) == 60) { // CURLE_SSL_CACERT
806 self::errorLog('Invalid or no certificate authority found, '.
807 'using bundled information');
808 curl_setopt($ch, CURLOPT_CAINFO,
809 dirname(__FILE__) . '/fb_ca_chain_bundle.crt');
810 $result = curl_exec($ch);
813 if ($result === false) {
814 $e = new FacebookApiException(array(
815 'error_code' => curl_errno($ch),
817 'message' => curl_error($ch),
818 'type' => 'CurlException',
829 * Parses a signed_request and validates the signature.
831 * @param string $signed_request A signed token
832 * @return array The payload inside it or null if the sig is wrong
834 protected function parseSignedRequest($signed_request) {
835 list($encoded_sig, $payload) = explode('.', $signed_request, 2);
838 $sig = self::base64UrlDecode($encoded_sig);
839 $data = json_decode(self::base64UrlDecode($payload), true);
841 if (strtoupper($data['algorithm']) !== 'HMAC-SHA256') {
842 self::errorLog('Unknown algorithm. Expected HMAC-SHA256');
847 $expected_sig = hash_hmac('sha256', $payload,
848 $this->getApiSecret(), $raw = true);
849 if ($sig !== $expected_sig) {
850 self::errorLog('Bad Signed JSON signature!');
858 * Build the URL for api given parameters.
860 * @param $method String the method name.
861 * @return string The URL for the given parameters
863 protected function getApiUrl($method) {
864 static $READ_ONLY_CALLS =
865 array('admin.getallocation' => 1,
866 'admin.getappproperties' => 1,
867 'admin.getbannedusers' => 1,
868 'admin.getlivestreamvialink' => 1,
869 'admin.getmetrics' => 1,
870 'admin.getrestrictioninfo' => 1,
871 'application.getpublicinfo' => 1,
872 'auth.getapppublickey' => 1,
873 'auth.getsession' => 1,
874 'auth.getsignedpublicsessiondata' => 1,
876 'connect.getunconnectedfriendscount' => 1,
877 'dashboard.getactivity' => 1,
878 'dashboard.getcount' => 1,
879 'dashboard.getglobalnews' => 1,
880 'dashboard.getnews' => 1,
881 'dashboard.multigetcount' => 1,
882 'dashboard.multigetnews' => 1,
883 'data.getcookies' => 1,
885 'events.getmembers' => 1,
886 'fbml.getcustomtags' => 1,
887 'feed.getappfriendstories' => 1,
888 'feed.getregisteredtemplatebundlebyid' => 1,
889 'feed.getregisteredtemplatebundles' => 1,
890 'fql.multiquery' => 1,
892 'friends.arefriends' => 1,
894 'friends.getappusers' => 1,
895 'friends.getlists' => 1,
896 'friends.getmutualfriends' => 1,
899 'groups.getmembers' => 1,
900 'intl.gettranslations' => 1,
903 'notifications.get' => 1,
904 'pages.getinfo' => 1,
905 'pages.isadmin' => 1,
906 'pages.isappadded' => 1,
908 'permissions.checkavailableapiaccess' => 1,
909 'permissions.checkgrantedapiaccess' => 1,
911 'photos.getalbums' => 1,
912 'photos.gettags' => 1,
913 'profile.getinfo' => 1,
914 'profile.getinfooptions' => 1,
916 'stream.getcomments' => 1,
917 'stream.getfilters' => 1,
918 'users.getinfo' => 1,
919 'users.getloggedinuser' => 1,
920 'users.getstandardinfo' => 1,
921 'users.hasapppermission' => 1,
922 'users.isappuser' => 1,
923 'users.isverified' => 1,
924 'video.getuploadlimits' => 1);
926 if (isset($READ_ONLY_CALLS[strtolower($method)])) {
928 } else if (strtolower($method) == 'video.upload') {
931 return self::getUrl($name, 'restserver.php');
935 * Build the URL for given domain alias, path and parameters.
937 * @param $name string The name of the domain
938 * @param $path string Optional path (without a leading slash)
939 * @param $params array Optional query parameters
941 * @return string The URL for the given parameters
943 protected function getUrl($name, $path='', $params=array()) {
944 $url = self::$DOMAIN_MAP[$name];
946 if ($path[0] === '/') {
947 $path = substr($path, 1);
952 $url .= '?' . http_build_query($params, null, '&');
959 * Returns the Current URL, stripping it of known FB parameters that should
962 * @return string The current URL
964 protected function getCurrentUrl() {
965 if (isset($_SERVER['HTTPS']) && ($_SERVER['HTTPS'] == 'on' || $_SERVER['HTTPS'] == 1)
966 || isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https'
968 $protocol = 'https://';
971 $protocol = 'http://';
973 $currentUrl = $protocol . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
974 $parts = parse_url($currentUrl);
977 if (!empty($parts['query'])) {
978 // drop known fb params
979 $params = explode('&', $parts['query']);
980 $retained_params = array();
981 foreach ($params as $param) {
982 if ($this->shouldRetainParam($param)) {
983 $retained_params[] = $param;
987 if (!empty($retained_params)) {
988 $query = '?'.implode($retained_params, '&');
992 // use port if non default
994 isset($parts['port']) &&
995 (($protocol === 'http://' && $parts['port'] !== 80) ||
996 ($protocol === 'https://' && $parts['port'] !== 443))
997 ? ':' . $parts['port'] : '';
1000 return $protocol . $parts['host'] . $port . $parts['path'] . $query;
1004 * Returns true if and only if the key or key/value pair should
1005 * be retained as part of the query string. This amounts to
1006 * a brute-force search of the very small list of Facebook-specific
1007 * params that should be stripped out.
1009 * @param string $param A key or key/value pair within a URL's query (e.g.
1010 * 'foo=a', 'foo=', or 'foo'.
1014 protected function shouldRetainParam($param) {
1015 foreach (self::$DROP_QUERY_PARAMS as $drop_query_param) {
1016 if (strpos($param, $drop_query_param.'=') === 0) {
1025 * Analyzes the supplied result to see if it was thrown
1026 * because the access token is no longer valid. If that is
1027 * the case, then the persistent store is cleared.
1029 * @param $result array A record storing the error message returned
1030 * by a failed API call.
1032 protected function throwAPIException($result) {
1033 $e = new FacebookApiException($result);
1034 switch ($e->getType()) {
1035 // OAuth 2.0 Draft 00 style
1036 case 'OAuthException':
1037 // OAuth 2.0 Draft 10 style
1038 case 'invalid_token':
1039 $message = $e->getMessage();
1040 if ((strpos($message, 'Error validating access token') !== false) ||
1041 (strpos($message, 'Invalid OAuth access token') !== false)) {
1042 $this->setAccessToken(null);
1044 $this->clearAllPersistentData();
1053 * Prints to the error log if you aren't in command line mode.
1055 * @param string $msg Log message
1057 protected static function errorLog($msg) {
1058 // disable error log if we are running in a CLI environment
1059 // @codeCoverageIgnoreStart
1060 if (php_sapi_name() != 'cli') {
1063 // uncomment this if you want to see the errors on the page
1064 // print 'error_log: '.$msg."\n";
1065 // @codeCoverageIgnoreEnd
1069 * Base64 encoding that doesn't need to be urlencode()ed.
1070 * Exactly the same as base64_encode except it uses
1074 * @param string $input base64UrlEncoded string
1077 protected static function base64UrlDecode($input) {
1078 return base64_decode(strtr($input, '-_', '+/'));
1082 * Each of the following four methods should be overridden in
1083 * a concrete subclass, as they are in the provided Facebook class.
1084 * The Facebook class uses PHP sessions to provide a primitive
1085 * persistent store, but another subclass--one that you implement--
1086 * might use a database, memcache, or an in-memory cache.
1092 * Stores the given ($key, $value) pair, so that future calls to
1093 * getPersistentData($key) return $value. This call may be in another request.
1095 * @param string $key
1096 * @param array $value
1100 abstract protected function setPersistentData($key, $value);
1103 * Get the data for $key, persisted by BaseFacebook::setPersistentData()
1105 * @param string $key The key of the data to retrieve
1106 * @param boolean $default The default value to return if $key is not found
1110 abstract protected function getPersistentData($key, $default = false);
1113 * Clear the data with $key from the persistent storage
1115 * @param string $key
1118 abstract protected function clearPersistentData($key);
1121 * Clear all data from the persistent storage
1125 abstract protected function clearAllPersistentData();