We are no longer offering accounts on this server. Consider https://gitlab.freedesktop.org/ as a place to host projects.

Commit 118e84be authored by Zach Copley's avatar Zach Copley

Update to latest Facebook PHP sdk

parent 8e501584
<?php
/**
* Copyright 2011 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
if (!function_exists('curl_init')) {
throw new Exception('Facebook needs the CURL PHP extension.');
}
if (!function_exists('json_decode')) {
throw new Exception('Facebook needs the JSON PHP extension.');
}
/**
* Thrown when an API call returns an exception.
*
* @author Naitik Shah <naitik@facebook.com>
*/
class FacebookApiException extends Exception
{
/**
* The result from the API server that represents the exception information.
*/
protected $result;
/**
* Make a new API Exception with the given result.
*
* @param array $result The result from the API server
*/
public function __construct($result) {
$this->result = $result;
$code = isset($result['error_code']) ? $result['error_code'] : 0;
if (isset($result['error_description'])) {
// OAuth 2.0 Draft 10 style
$msg = $result['error_description'];
} else if (isset($result['error']) && is_array($result['error'])) {
// OAuth 2.0 Draft 00 style
$msg = $result['error']['message'];
} else if (isset($result['error_msg'])) {
// Rest server style
$msg = $result['error_msg'];
} else {
$msg = 'Unknown Error. Check getResult()';
}
parent::__construct($msg, $code);
}
/**
* Return the associated result object returned by the API server.
*
* @return array The result from the API server
*/
public function getResult() {
return $this->result;
}
/**
* Returns the associated type for the error. This will default to
* 'Exception' when a type is not available.
*
* @return string
*/
public function getType() {
if (isset($this->result['error'])) {
$error = $this->result['error'];
if (is_string($error)) {
// OAuth 2.0 Draft 10 style
return $error;
} else if (is_array($error)) {
// OAuth 2.0 Draft 00 style
if (isset($error['type'])) {
return $error['type'];
}
}
}
return 'Exception';
}
/**
* To make debugging easier.
*
* @return string The string representation of the error
*/
public function __toString() {
$str = $this->getType() . ': ';
if ($this->code != 0) {
$str .= $this->code . ': ';
}
return $str . $this->message;
}
}
/**
* Provides access to the Facebook Platform. This class provides
* a majority of the functionality needed, but the class is abstract
* because it is designed to be sub-classed. The subclass must
* implement the four abstract methods listed at the bottom of
* the file.
*
* @author Naitik Shah <naitik@facebook.com>
*/
abstract class BaseFacebook
{
/**
* Version.
*/
const VERSION = '3.1.1';
/**
* Default options for curl.
*/
public static $CURL_OPTS = array(
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 60,
CURLOPT_USERAGENT => 'facebook-php-3.1',
);
/**
* List of query parameters that get automatically dropped when rebuilding
* the current URL.
*/
protected static $DROP_QUERY_PARAMS = array(
'code',
'state',
'signed_request',
);
/**
* Maps aliases to Facebook domains.
*/
public static $DOMAIN_MAP = array(
'api' => 'https://api.facebook.com/',
'api_video' => 'https://api-video.facebook.com/',
'api_read' => 'https://api-read.facebook.com/',
'graph' => 'https://graph.facebook.com/',
'www' => 'https://www.facebook.com/',
);
/**
* The Application ID.
*
* @var string
*/
protected $appId;
/**
* The Application API Secret.
*
* @var string
*/
protected $apiSecret;
/**
* The ID of the Facebook user, or 0 if the user is logged out.
*
* @var integer
*/
protected $user;
/**
* The data from the signed_request token.
*/
protected $signedRequest;
/**
* A CSRF state variable to assist in the defense against CSRF attacks.
*/
protected $state;
/**
* The OAuth access token received in exchange for a valid authorization
* code. null means the access token has yet to be determined.
*
* @var string
*/
protected $accessToken = null;
/**
* Indicates if the CURL based @ syntax for file uploads is enabled.
*
* @var boolean
*/
protected $fileUploadSupport = false;
/**
* Initialize a Facebook Application.
*
* The configuration:
* - appId: the application ID
* - secret: the application secret
* - fileUpload: (optional) boolean indicating if file uploads are enabled
*
* @param array $config The application configuration
*/
public function __construct($config) {
$this->setAppId($config['appId']);
$this->setApiSecret($config['secret']);
if (isset($config['fileUpload'])) {
$this->setFileUploadSupport($config['fileUpload']);
}
$state = $this->getPersistentData('state');
if (!empty($state)) {
$this->state = $this->getPersistentData('state');
}
}
/**
* Set the Application ID.
*
* @param string $appId The Application ID
* @return BaseFacebook
*/
public function setAppId($appId) {
$this->appId = $appId;
return $this;
}
/**
* Get the Application ID.
*
* @return string the Application ID
*/
public function getAppId() {
return $this->appId;
}
/**
* Set the API Secret.
*
* @param string $apiSecret The API Secret
* @return BaseFacebook
*/
public function setApiSecret($apiSecret) {
$this->apiSecret = $apiSecret;
return $this;
}
/**
* Get the API Secret.
*
* @return string the API Secret
*/
public function getApiSecret() {
return $this->apiSecret;
}
/**
* Set the file upload support status.
*
* @param boolean $fileUploadSupport The file upload support status.
* @return BaseFacebook
*/
public function setFileUploadSupport($fileUploadSupport) {
$this->fileUploadSupport = $fileUploadSupport;
return $this;
}
/**
* Get the file upload support status.
*
* @return boolean true if and only if the server supports file upload.
*/
public function useFileUploadSupport() {
return $this->fileUploadSupport;
}
/**
* Sets the access token for api calls. Use this if you get
* your access token by other means and just want the SDK
* to use it.
*
* @param string $access_token an access token.
* @return BaseFacebook
*/
public function setAccessToken($access_token) {
$this->accessToken = $access_token;
return $this;
}
/**
* Determines the access token that should be used for API calls.
* The first time this is called, $this->accessToken is set equal
* to either a valid user access token, or it's set to the application
* access token if a valid user access token wasn't available. Subsequent
* calls return whatever the first call returned.
*
* @return string The access token
*/
public function getAccessToken() {
if ($this->accessToken !== null) {
// we've done this already and cached it. Just return.
return $this->accessToken;
}
// first establish access token to be the application
// access token, in case we navigate to the /oauth/access_token
// endpoint, where SOME access token is required.
$this->setAccessToken($this->getApplicationAccessToken());
if ($user_access_token = $this->getUserAccessToken()) {
$this->setAccessToken($user_access_token);
}
return $this->accessToken;
}
/**
* Determines and returns the user access token, first using
* the signed request if present, and then falling back on
* the authorization code if present. The intent is to
* return a valid user access token, or false if one is determined
* to not be available.
*
* @return string A valid user access token, or false if one
* could not be determined.
*/
protected function getUserAccessToken() {
// first, consider a signed request if it's supplied.
// if there is a signed request, then it alone determines
// the access token.
$signed_request = $this->getSignedRequest();
if ($signed_request) {
// apps.facebook.com hands the access_token in the signed_request
if (array_key_exists('oauth_token', $signed_request)) {
$access_token = $signed_request['oauth_token'];
$this->setPersistentData('access_token', $access_token);
return $access_token;
}
// the JS SDK puts a code in with the redirect_uri of ''
if (array_key_exists('code', $signed_request)) {
$code = $signed_request['code'];
$access_token = $this->getAccessTokenFromCode($code, '');
if ($access_token) {
$this->setPersistentData('code', $code);
$this->setPersistentData('access_token', $access_token);
return $access_token;
}
}
// signed request states there's no access token, so anything
// stored should be cleared.
$this->clearAllPersistentData();
return false; // respect the signed request's data, even
// if there's an authorization code or something else
}
$code = $this->getCode();
if ($code && $code != $this->getPersistentData('code')) {
$access_token = $this->getAccessTokenFromCode($code);
if ($access_token) {
$this->setPersistentData('code', $code);
$this->setPersistentData('access_token', $access_token);
return $access_token;
}
// code was bogus, so everything based on it should be invalidated.
$this->clearAllPersistentData();
return false;
}
// as a fallback, just return whatever is in the persistent
// store, knowing nothing explicit (signed request, authorization
// code, etc.) was present to shadow it (or we saw a code in $_REQUEST,
// but it's the same as what's in the persistent store)
return $this->getPersistentData('access_token');
}
/**
* Retrieve the signed request, either from a request parameter or,
* if not present, from a cookie.
*
* @return string the signed request, if available, or null otherwise.
*/
public function getSignedRequest() {
if (!$this->signedRequest) {
if (isset($_REQUEST['signed_request'])) {
$this->signedRequest = $this->parseSignedRequest(
$_REQUEST['signed_request']);
} else if (isset($_COOKIE[$this->getSignedRequestCookieName()])) {
$this->signedRequest = $this->parseSignedRequest(
$_COOKIE[$this->getSignedRequestCookieName()]);
}
}
return $this->signedRequest;
}
/**
* Get the UID of the connected user, or 0
* if the Facebook user is not connected.
*
* @return string the UID if available.
*/
public function getUser() {
if ($this->user !== null) {
// we've already determined this and cached the value.
return $this->user;
}
return $this->user = $this->getUserFromAvailableData();
}
/**
* Determines the connected user by first examining any signed
* requests, then considering an authorization code, and then
* falling back to any persistent store storing the user.
*
* @return integer The id of the connected Facebook user,
* or 0 if no such user exists.
*/
protected function getUserFromAvailableData() {
// if a signed request is supplied, then it solely determines
// who the user is.
$signed_request = $this->getSignedRequest();
if ($signed_request) {
if (array_key_exists('user_id', $signed_request)) {
$user = $signed_request['user_id'];
$this->setPersistentData('user_id', $signed_request['user_id']);
return $user;
}
// if the signed request didn't present a user id, then invalidate
// all entries in any persistent store.
$this->clearAllPersistentData();
return 0;
}
$user = $this->getPersistentData('user_id', $default = 0);
$persisted_access_token = $this->getPersistentData('access_token');
// use access_token to fetch user id if we have a user access_token, or if
// the cached access token has changed.
$access_token = $this->getAccessToken();
if ($access_token &&
$access_token != $this->getApplicationAccessToken() &&
!($user && $persisted_access_token == $access_token)) {
$user = $this->getUserFromAccessToken();
if ($user) {
$this->setPersistentData('user_id', $user);
} else {
$this->clearAllPersistentData();
}
}
return $user;
}
/**
* Get a Login URL for use with redirects. By default, full page redirect is
* assumed. If you are using the generated URL with a window.open() call in
* JavaScript, you can pass in display=popup as part of the $params.
*
* The parameters:
* - redirect_uri: the url to go to after a successful login
* - scope: comma separated list of requested extended perms
*
* @param array $params Provide custom parameters
* @return string The URL for the login flow
*/
public function getLoginUrl($params=array()) {
$this->establishCSRFTokenState();
$currentUrl = $this->getCurrentUrl();
// if 'scope' is passed as an array, convert to comma separated list
$scopeParams = isset($params['scope']) ? $params['scope'] : null;
if ($scopeParams && is_array($scopeParams)) {
$params['scope'] = implode(',', $scopeParams);
}
return $this->getUrl(
'www',
'dialog/oauth',
array_merge(array(
'client_id' => $this->getAppId(),
'redirect_uri' => $currentUrl, // possibly overwritten
'state' => $this->state),
$params));
}
/**
* Get a Logout URL suitable for use with redirects.
*
* The parameters:
* - next: the url to go to after a successful logout
*
* @param array $params Provide custom parameters
* @return string The URL for the logout flow
*/
public function getLogoutUrl($params=array()) {
return $this->getUrl(
'www',
'logout.php',
array_merge(array(
'next' => $this->getCurrentUrl(),
'access_token' => $this->getAccessToken(),
), $params)
);
}
/**
* Get a login status URL to fetch the status from Facebook.
*
* The parameters:
* - ok_session: the URL to go to if a session is found
* - no_session: the URL to go to if the user is not connected
* - no_user: the URL to go to if the user is not signed into facebook
*
* @param array $params Provide custom parameters
* @return string The URL for the logout flow
*/
public function getLoginStatusUrl($params=array()) {
return $this->getUrl(
'www',
'extern/login_status.php',
array_merge(array(
'api_key' => $this->getAppId(),
'no_session' => $this->getCurrentUrl(),
'no_user' => $this->getCurrentUrl(),
'ok_session' => $this->getCurrentUrl(),
'session_version' => 3,
), $params)
);
}
/**
* Make an API call.
*
* @return mixed The decoded response
*/
public function api(/* polymorphic */) {
$args = func_get_args();
if (is_array($args[0])) {
return $this->_restserver($args[0]);
} else {
return call_user_func_array(array($this, '_graph'), $args);
}
}
/**
* Constructs and returns the name of the cookie that
* potentially houses the signed request for the app user.
* The cookie is not set by the BaseFacebook class, but
* it may be set by the JavaScript SDK.
*
* @return string the name of the cookie that would house
* the signed request value.
*/
protected function getSignedRequestCookieName() {
return 'fbsr_'.$this->getAppId();
}
/**
* Get the authorization code from the query parameters, if it exists,
* and otherwise return false to signal no authorization code was
* discoverable.
*
* @return mixed The authorization code, or false if the authorization
* code could not be determined.
*/
protected function getCode() {
if (isset($_REQUEST['code'])) {
if ($this->state !== null &&
isset($_REQUEST['state']) &&
$this->state === $_REQUEST['state']) {
// CSRF state has done its job, so clear it
$this->state = null;
$this->clearPersistentData('state');
return $_REQUEST['code'];
} else {
self::errorLog('CSRF state token does not match one provided.');
return false;
}
}
return false;
}
/**
* Retrieves the UID with the understanding that
* $this->accessToken has already been set and is
* seemingly legitimate. It relies on Facebook's Graph API
* to retrieve user information and then extract
* the user ID.
*
* @return integer Returns the UID of the Facebook user, or 0
* if the Facebook user could not be determined.
*/
protected function getUserFromAccessToken() {
try {
$user_info = $this->api('/me');
return $user_info['id'];
} catch (FacebookApiException $e) {
return 0;
}
}
/**
* Returns the access token that should be used for logged out
* users when no authorization code is available.
*
* @return string The application access token, useful for gathering
* public information about users and applications.
*/
protected function getApplicationAccessToken() {
return $this->appId.'|'.$this->apiSecret;
}
/**
* Lays down a CSRF state token for this process.
*
* @return void
*/
protected function establishCSRFTokenState() {
if ($this->state === null) {
$this->state = md5(uniqid(mt_rand(), true));
$this->setPersistentData('state', $this->state);
}
}
/**
* Retrieves an access token for the given authorization code
* (previously generated from www.facebook.com on behalf of
* a specific user). The authorization code is sent to graph.facebook.com
* and a legitimate access token is generated provided the access token