fb.module in Drupal for Facebook 7.3
Same filename and directory in other branches
This is the core required module of Drupal for Facebook.
See also
File
fb.moduleView source
<?php
/**
* @file
* This is the core required module of Drupal for Facebook.
*
* @see http://drupal.org/project/fb
*
*/
// hook_fb
define('FB_HOOK', 'fb');
// Paths.
define('FB_PATH_ADMIN', 'admin/structure/fb');
define('FB_PATH_ADMIN_ARGS', 3);
// how many args in path.
define('FB_PATH_ADMIN_APPS', 'admin/structure/fb/app');
define('FB_PATH_ADMIN_APPS_ARGS', 4);
define('FB_PATH_AJAX_EVENT', 'fb/ajax');
define('FB_PATH_AJAX_EVENT_ARGS', 2);
define('FB_FACEBOOK_BASE_URL', '//www.facebook.com');
// permissions
define('FB_PERM_ADMINISTER', 'administer fb apps');
// Ops for hook_fb.
define('FB_OP_GET_APP', 'get_app');
// Load data from a known app
define('FB_OP_GET_ALL_APPS', 'get_all_apps');
// Load data about all apps
define('FB_OP_CURRENT_APP', 'current_app');
// determine active app in canvas page or facebook connect
define('FB_OP_INITIALIZE', 'init');
//
define('FB_OP_POST_INIT', 'post init');
//
define('FB_OP_EXIT', 'exit');
// End an FB callback. DEPRECATED
define('FB_OP_GET_FBU', 'get_fbu');
// Query the local user's FB account
define('FB_OP_GET_UID', 'get_uid');
// Query the facebook user's local account
define('FB_OP_GET_USER_SESSION', 'get_user_sess');
define('FB_OP_APP_IS_AUTHORIZED', 'app_authorized');
// Invoked if user has authorized an app. Triggers creation of user accounts or fb_user entries
define('FB_OP_JS', 'fb_op_js');
// A chance to inject javascript onto the page.
define('FB_OP_AJAX_EVENT', 'fb_op_ajax');
// Notification of an event via ajax.
// Variables and $conf[] keys.
define('FB_VAR_LANGUAGE_OVERRIDE', 'fb_language_override');
define('FB_VAR_JS_SDK', 'fb_js_sdk');
define('FB_VAR_API_FILE', 'fb_api_file');
define('FB_VAR_JS_CHANNEL', 'fb_js_channel');
define('FB_VAR_VERBOSE', 'fb_verbose');
define('FB_VAR_APIKEY', 'fb_apikey');
// Deprecated. Use FB_VAR_ID
define('FB_VAR_ID', 'fb_id');
define('FB_VAR_USE_COOKIE', 'fb_use_cookie');
define('FB_VAR_USE_SESSION', 'fb_use_session');
define('FB_VAR_JS_USE_SESSION', 'fb_js_session_token');
// Initialize JS with token from session.
define('FB_VAR_JS_GET_LOGIN_STATUS', 'fb_js_get_login_status');
// call FB.getLoginStatus during js init?
define('FB_VAR_JS_TEST_LOGIN_STATUS', 'fb_js_test_login_status');
define('FB_VAR_JS_OAUTH', 'fb_js_oauth');
// Whether to pass oauth: true to FB.init().
define('FB_VAR_CURL_NOVERIFY', 'fb_curl_noverify');
define('FB_VAR_SECURE_URLS', 'fb_secure_urls');
define('FB_VAR_ALTER_USERNAME', 'fb_format_username');
define('FB_VAR_ALTER_USERNAME_AND_CACHE', 'fb_cache_username');
// Possible choices for secure urls.
define('FB_SECURE_URLS_ALWAYS', 1);
define('FB_SECURE_URLS_SOMETIMES', 0);
define('FB_SECURE_URLS_NEVER', -1);
// Possible choices for formatting username.
define('FB_ALTER_USERNAME_ALWAYS', 'always');
define('FB_ALTER_USERNAME_NEVER', 'never');
define('FB_ALTER_USERNAME_NOT_THEMING', 'when_not_theming');
// Possible choices for caching the formated username
define('FB_ALTER_USERNAME_AND_CACHE', 'on');
define('FB_ALTER_USERNAME_DONT_CACHE', 'off');
// node_access realms (belongs here?)
define('FB_GRANT_REALM_FRIEND', 'fb_friend');
define('FB_GRANT_REALM_GROUP', 'fb_group');
// NOTE: on Connect Pages, using anything other than FB_FBU_CURRENT will cause cookies to be set which cause problems on subsequent pages. So only use something other than FB_FBU_CURRENT if you absolutely must!
// @TODO - new libs, are these FBU values still needed???
define('FB_FBU_CURRENT', 'fbu_current');
// Canvas pages and Connect pages
define('FB_FBU_ANY', 'fbu_any');
// Use current user on canvas page, fall back to infinite session otherwise.
//// Constants for internal use
define('FB_APP_CURRENT', '000_app_current');
// Canvas pages only. 000 makes it appear first in options list
/**
* Controls are one way to customize the behavior of Drupal for Facebook modules.
*
* Controls are stored as an array of flags. Each flag overrides a
* configurable or built-in behavior. Third-party modules can use this to
* provide exceptions to otherwise useful behavior. For example see
* fb_user.module, where this is used to suppress some behavior in off-line
* mode.
*
* Controls take effect not just for the current page request, but also for
* ajax callbacks generated by the subsequent page.
*
* Because ajax controls could be spoofed by a malicious client, flags should
* not enable any "risky" features. For example, fb_user.module provides a
* control to suppress the creation of account, but not a control to enable
* new accounts, as that would be a security risk.
*
*/
function fb_controls($control = NULL, $value = NULL) {
$controls =& drupal_static(__FUNCTION__);
if (!isset($controls)) {
// Initialize.
if (isset($_REQUEST['fb_controls'])) {
// Comma separated list passed to ajax calls.
foreach (explode(',', $_REQUEST['fb_controls']) as $key) {
$controls[$key] = TRUE;
}
}
else {
$controls = array();
}
// @TODO - would a drupal_alter() be useful here?
}
if (isset($control)) {
if ($value === FALSE) {
unset($controls[$control]);
return;
}
elseif ($value === TRUE) {
$controls[$control] = TRUE;
}
return isset($controls[$control]) ? $controls[$control] : FALSE;
// Return requested control.
}
return array_keys($controls);
// Return all controls.
}
/**
* Implements hook_custom_theme().
*
* hook_custom_theme() is the new hook_init(). It is called before
* hook_init(), and therefore the first opportunity that a module has to
* act. Although this module is not interested in setting a custom theme,
* fb_canvas and fb_tab might, so we must initialize some data here.
*
* This function is also called before node_access hooks. So the more we can
* initialize here, the better. That's why we initialize both $_fb_app and $_fb.
*
* @see fb_canvas_custom_theme().
* @see fb_init().
*/
function fb_custom_theme() {
// The code here is conceptually part of fb_init().
global $_fb_app;
global $_fb;
// fb_settings.inc may have been included in settings.php. If not, include it now.
if (!function_exists('fb_settings')) {
module_load_include('inc', 'fb', 'fb_settings');
// trigger test in fb_devel.module
$GLOBALS['fb_init_no_settings'] = TRUE;
}
// Figure out which app the current request is for.
$_fb_app = fb_invoke(FB_OP_CURRENT_APP);
if ($_fb_app) {
// Initialize the PHP API.
$_fb = fb_api_init($_fb_app);
}
}
/**
* Implements hook_init().
*
* Initializes facebook's javascript.
* Determines whether we are servicing a Facebook App request.
*
* We invoke our hook, first to determine which application is being invoked
* (because we support more than one in the same Drupal instance). We invoke
* the hook again to let interested modules know the sdk is initialized.
*
*/
function fb_init() {
// Globals provided for internal use and convenience to third-parties.
global $_fb;
global $_fb_app;
// When the site is in maintenance mode, hook_custom_theme won't be invoked so
// we call our implementation manually.
if (!function_exists('fb_settings')) {
fb_custom_theme();
}
// Javascript settings needed by fb.js.
fb_js_settings('base_url', trim(url('', array(
'absolute' => TRUE,
)), '/'));
fb_js_settings('ajax_event_url', url(FB_PATH_AJAX_EVENT, array(
'absolute' => TRUE,
)));
fb_js_settings('is_anonymous', !user_is_logged_in());
// Data structure to pass to FB.init();
$fb_init_settings = array(
'xfbml' => FALSE,
'status' => FALSE,
// We will call getLoginStatus() instead.
'oauth' => variable_get(FB_VAR_JS_OAUTH, TRUE),
);
if (variable_get(FB_VAR_USE_COOKIE, TRUE)) {
$fb_init_settings['cookie'] = TRUE;
}
if ($_fb_app) {
// An App is configured.
// Javascript settings needed by fb.js and/or other modules.
fb_js_settings('label', $_fb_app->label);
fb_js_settings('namespace', $_fb_app->canvas);
fb_js_settings('page_type', fb_settings(FB_SETTINGS_TYPE));
// canvas or connect.
// Add perms to settings, for calling FB.login().
$perms = array();
drupal_alter('fb_required_perms', $perms);
fb_js_settings('perms', implode(',', $perms));
//$fb_init_settings['apiKey'] = $_fb_app->apikey;
$fb_init_settings['appId'] = $_fb_app->id;
if ($_fb) {
// @TODO: test if this is still true: Sometimes when canvas page is open in one tab, and user logs out of
// facebook in another, the canvas page has bogus session info when
// refreshed. Here we attempt to detect and cleanup.
// Give other modules a chance to initialize.
fb_invoke(FB_OP_INITIALIZE, array(
'fb_app' => $_fb_app,
'fb' => $_fb,
));
// See if the facebook user id is known
if ($fbu = $_fb
->getUser()) {
fb_invoke(FB_OP_APP_IS_AUTHORIZED, array(
'fb_app' => $_fb_app,
'fb' => $_fb,
'fbu' => $fbu,
));
fb_js_settings('fbu', $fbu);
}
if (!empty($_REQUEST['fb_reload'])) {
// Tell javascript not to reload indefinately.
fb_js_settings('fb_reloading', $_REQUEST['fb_reload']);
}
// When not using cookies, or third-party cookies disabled, we can pass all the auth details in javascript settings.
if (variable_get(FB_VAR_JS_USE_SESSION, TRUE)) {
if (!empty($_REQUEST['fb_login_status'])) {
// Remember for duration of session whether test failed.
$_SESSION['fb_get_login_status_test'] = $_REQUEST['fb_login_status'];
}
if ($fb_uid = $_fb
->getUser()) {
if (!empty($_SESSION['fb_get_login_status_test']) && $_SESSION['fb_get_login_status_test'] == 'false') {
$fb_token = $_fb
->getAccessToken();
// This uses a data structure not documented by facebook. May not continue to work.
$js_sr = array(
'accessToken' => $fb_token,
'userID' => $fb_uid,
);
$fb_init_settings['authResponse'] = (object) $js_sr;
}
}
else {
fb_js_settings('get_login_status_test', TRUE);
}
}
}
else {
unset($_fb_app);
watchdog('fb', "Could not initialize Facebook API.", array(), WATCHDOG_ERROR);
}
if (isset($_REQUEST['destination'])) {
$destination = $_REQUEST['destination'];
}
elseif (current_path()) {
$destination = current_path();
}
else {
$destination = '<front>';
}
if (fb_is_canvas()) {
$destination = fb_scrub_urls($destination);
// Needed?
}
//Stripping the fragment out to be tacked on during the javascript redirect
if (strpos($destination, '#') !== FALSE) {
list($destination, $fragment) = explode('#', $destination, 2);
fb_js_settings('reload_url_fragment', $fragment);
}
fb_js_settings('reload_url', url($destination, array(
'absolute' => TRUE,
'fb_canvas' => fb_is_canvas(),
'language' => (object) array(
'prefix' => NULL,
'language' => NULL,
),
)));
}
if ($channel = variable_get(FB_VAR_JS_CHANNEL, TRUE)) {
if (!is_string($channel)) {
$channel = url('fb/channel', array(
'absolute' => TRUE,
'fb_url_alter' => FALSE,
));
}
$fb_init_settings['channelUrl'] = $channel;
}
fb_js_settings('fb_init_settings', $fb_init_settings);
// Allow third-parties to act, even if we did not initialize $_fb.
fb_invoke(FB_OP_POST_INIT, array(
'fb_app' => $_fb_app,
'fb' => $_fb,
));
fb_js_settings('test_login_status', variable_get(FB_VAR_JS_TEST_LOGIN_STATUS, TRUE));
fb_js_settings('get_login_status', variable_get(FB_VAR_JS_GET_LOGIN_STATUS, TRUE));
fb_js_settings('controls', implode(',', fb_controls()));
if (!fb_js_settings('js_sdk_url')) {
if (isset($_SESSION['fb_locale']) && variable_get(FB_VAR_LANGUAGE_OVERRIDE, 'override')) {
// @TODO - get locale from signed request. It appears to contain it now.
$fb_lang = $_SESSION['fb_locale'];
}
else {
global $language;
$fb_lang = variable_get('fb_language_' . $language->language, 'en_US');
}
$js_sdk = fb_protocol() . "://connect.facebook.net/{$fb_lang}/all.js";
fb_js_settings('js_sdk_url', variable_get(FB_VAR_JS_SDK, $js_sdk));
}
// Add our module's javascript.
drupal_add_js(drupal_get_path('module', 'fb') . '/fb.js', array(
'type' => 'file',
'scope' => 'header',
'group' => JS_LIBRARY,
));
// See also fb_page_alter(), where we initialize facebook's SDK.
}
/**
* Implements hook_tokens().
*
* Nothing to do with facebook access tokens. This drupal hook supports
* token replacement via token_replace().
*/
function fb_tokens($type, $tokens, array $data = array(), array $options = array()) {
if (strpos($type, 'fb') === 0) {
$items = array();
// Add defaults to $data array.
$data = $data + fb_vars();
if (!isset($data['fb_signed_request']) && !empty($data['fb'])) {
$data['fb_signed_request'] = $data['fb']
->getSignedRequest();
}
if (!isset($data['fb_settings']) && function_exists('fb_settings')) {
$data['fb_settings'] = fb_settings();
}
if (is_array($data[$type])) {
foreach ($data[$type] as $key => $value) {
if (!is_array($value)) {
$items["[{$type}:{$key}]"] = $value;
}
// TODO: support nested values (i.e. recurse into arrays)
}
}
// TODO support additional fb token types. I.e. fb_app, fb_settings.
return $items;
}
}
/**
* Implements hook_rdf_namespaces().
* Adds the xmlns:fb attribute to html tag.
*/
function fb_rdf_namespaces() {
return array(
// It's unclear from facebook doc which of the URLs below is correct.
'fb' => 'http://www.facebook.com/2008/fbml',
);
}
/**
* Helper to get the configured variables.
*
* Adds the javascript setting with the supplied key/value. This function
* merely keeps track of the settings and writes them as late as possible.
* Currently, in the fb_footer() function. There has been a lot of
* experimentation as to the best place to initialize the facebook javascript
* SDK. The footer appears to be the best place because we may not know all
* settings until well after hook_init().
*
* @param $key
* The javascript setting name. If the key is null then nothing is modified and the settings are returned.
* @param $value
* The value of the javascript setting. If the key is not NULL by the value is the setting is removed
* @return
* The associative array containing the current fb javascript settings
*/
function fb_js_settings($key = NULL, $value = NULL) {
$fb_js_settings =& drupal_static(__FUNCTION__);
if (isset($key) && isset($value)) {
$fb_js_settings[$key] = $value;
return $value;
}
elseif (isset($key)) {
return isset($fb_js_settings[$key]) ? $fb_js_settings[$key] : NULL;
}
else {
return $fb_js_settings;
}
}
/**
* Include and initialize Facebook's PHP SDK.
*/
function fb_api_init($fb_app) {
$cache =& drupal_static(__FUNCTION__);
if (isset($cache[$fb_app->id])) {
return $cache[$fb_app->id];
}
// Find Facebook's PHP SDK. Use libraries API if enabled.
$fb_lib_path = function_exists('libraries_get_path') ? libraries_get_path('facebook-php-sdk') : 'sites/all/libraries/facebook-php-sdk';
$fb_platform = variable_get(FB_VAR_API_FILE, $fb_lib_path . '/src/facebook.php');
try {
if (!class_exists('Facebook') && !@(include $fb_platform)) {
$message = t('Failed to find the Facebook client libraries at %filename. Read the !readme and follow the instructions carefully.', array(
'!drupal_for_facebook' => l(t('Drupal for Facebook'), 'http://drupal.org/project/fb'),
// This link should work with clean URLs disabled.
'!readme' => '<a href=' . base_path() . '/' . drupal_get_path('module', 'fb') . '/README.txt>README.txt</a>',
'%filename' => $fb_platform,
));
drupal_set_message($message, 'error');
watchdog('fb', $message);
return NULL;
}
if (Facebook::VERSION < "3") {
$message = 'This version of modules/fb is compatible with Facebook PHP SDK version 3.x.y, but %version was found (%fb_platform).';
$args = array(
'%fb_platform' => $fb_platform,
'%version' => Facebook::VERSION,
);
if (user_access('access administration pages')) {
drupal_set_message(t($message, $args));
}
watchdog('fb', $message, $args, WATCHDOG_ERROR);
return NULL;
}
// Hack. In case third-party cookies disabled, put signed request where facebook's SDK will find it.
if (variable_get(FB_VAR_USE_SESSION, TRUE) && !isset($_REQUEST['signed_request']) && !isset($_COOKIE['fbsr_' . $fb_app->id]) && isset($_SESSION['_fb_' . $fb_app->id])) {
$_REQUEST['signed_request'] = $_SESSION['_fb_' . $fb_app->id];
}
// We don't have a cached resource for this app, so we're going to create one.
$fb = new Facebook(array(
'appId' => $fb_app->id,
'secret' => isset($fb_app->secret) ? $fb_app->secret : NULL,
'cookie' => variable_get(FB_VAR_USE_COOKIE, TRUE),
));
// Hack in case third-party cookies disabled, find access token in session.
// This comes into play when oauth is not used in JS.
if (variable_get(FB_VAR_USE_SESSION, TRUE) && !isset($_REQUEST['signed_request']) && isset($_SESSION['_fb_token_' . $fb_app->id])) {
$fb
->setAccessToken($_SESSION['_fb_token_' . $fb_app->id]);
}
// Some servers need these settings.
if (variable_get(FB_VAR_CURL_NOVERIFY, TRUE)) {
Facebook::$CURL_OPTS[CURLOPT_SSL_VERIFYPEER] = FALSE;
Facebook::$CURL_OPTS[CURLOPT_SSL_VERIFYHOST] = FALSE;
//Facebook::$CURL_OPTS[CURLOPT_VERBOSE] = 1; // debug
}
// Cache the result, in case we're called again.
$cache[$fb_app->id] = $fb;
// Tell Drupal not to store the current page in the cache when user is logged into facebook.
if (!user_is_logged_in() && fb_facebook_user($fb)) {
//dpm("Disabling cache for connected user.", __FUNCTION__);
$GLOBALS['conf']['cache'] = 0;
// CACHE_DISABLED == 0
}
return $fb;
} catch (Exception $e) {
fb_log_exception($e, t('Failed to construct Facebook client API.'));
}
}
/**
* Wrapper function for fb_api_init. This helps for functions that should
* work whether or not we are on a canvas page. For canvas pages, the active
* fb object is used. For non-canvas pages, it will initialize the API using
* an infinite session, if configured.
*
* @param $fb_app Note this is ignored on canvas pages.
*
* This is for internal use. Third party modules use fb_api_init().
*/
function _fb_api_init($fb_app = NULL) {
$fb = $GLOBALS['_fb'];
// Default to active app on canvas pages
if (!$fb && $fb_app) {
// Otherwise, log into facebook api.
$fb = fb_api_init($fb_app, FB_FBU_ANY);
}
if (!$fb) {
watchdog('fb', '%function unable to initialize Facebook API.', array(
'%function' => '_fb_api_init()',
), WATCHDOG_ERROR);
return;
}
else {
return $fb;
}
}
/**
* Helper function to get the most commonly used values. In your custom
* module, call extract(fb_vars()); to set $fb_app, $fb, and $fbu.
*/
function fb_vars() {
// Access callback are called before hook_init, so make sure FB api initialized.
if ($GLOBALS['_fb_app'] && !$GLOBALS['_fb']) {
$GLOBALS['_fb'] = fb_api_init($GLOBALS['_fb_app']);
}
return array(
'fb' => $GLOBALS['_fb'],
'fb_app' => $GLOBALS['_fb_app'],
'fbu' => fb_facebook_user(),
);
}
/**
* Helper function to work with facebook "open" graph.
*/
function fb_graph($path, $params = array(), $method = 'GET', $fb = NULL) {
if (!$fb) {
$fb = $GLOBALS['_fb'];
}
if ($method == 'GET') {
$url = url("https://graph.facebook.com/{$path}", array(
'query' => $params,
));
$http = drupal_http_request($url);
}
else {
$url = "https://graph.facebook.com/{$path}";
$headers = array();
//$headers = array('Content-Type' => 'application/x-www-form-urlencoded'); // Needed??
$query = http_build_query($params, '', '&');
$http = drupal_http_request($url, array(
'headers' => $headers,
'method' => $method,
'data' => $query,
));
}
if (isset($http->data)) {
$data = json_decode($http->data, TRUE);
// Most times graph returns JSON, but other times query string. Thanks Facebook!
if (!$data) {
parse_str($http->data, $data);
}
}
else {
$data = array();
// avoid php warnings.
}
if (!isset($http->error) && !empty($data)) {
if (is_array($data)) {
if (isset($data['error_code'])) {
throw new FacebookApiException($data);
}
}
elseif ($http->data == 'true' || $http->code == 200) {
// No problems.
}
else {
// Never reach this???
if (function_exists('dpm')) {
dpm($http, __FUNCTION__ . " unexpected result from {$url}");
}
// XXX
}
return $data;
}
elseif (!empty($data)) {
// Error has a message.
// TODO: parse error code from message.
$message = t('fb_graph failed querying !path. !type: !detail', array(
'!path' => $path,
'!type' => $data['error']['type'],
'!detail' => $data['error']['message'],
));
throw new Exception($message);
// Do we need our own code???
}
else {
$data = json_decode($http->data, TRUE);
$message = t('fb_graph failed querying !path. !detail', array(
'!path' => $path,
'!detail' => $http->error,
));
throw new Exception($message);
// Do we need our own code???
}
}
/**
* Helper to get the tokens needed to accss facebook's API.
*
* You would think that facebook's SDK would provide basic functions like this.
*
* @param $fb
* Get the token for this API instance. If NULL, use the global $_fb.
*
* @param $fbu
* Get the user-specific token. If NULL, get the application token.
*/
function fb_get_token($fb = NULL, $fbu = NULL) {
$cache =& drupal_static(__FUNCTION__);
if (!isset($cache)) {
$cache = array();
}
if (!$fb) {
$fb = $GLOBALS['_fb'];
}
if (!$fb) {
return;
}
$app_id = $fb
->getAppId();
$cache_key = "fb_token_{$app_id}";
// @TODO if both fb and fbu are NULL, it might be better performance to use the current user's token, if available.
if (!$fbu) {
// Get the application token.
if (empty($cache[$cache_key])) {
// try Drupal cache
$cache_obj = cache_get($cache_key);
if ($cache_obj && $cache_obj->data) {
$cache[$cache_key] = $cache_obj->data;
}
}
if (empty($cache[$cache_key])) {
// Query facebook for token.
// http://developers.facebook.com/docs/authentication/applications/
$path = "https://graph.facebook.com/oauth/access_token?client_id=" . $app_id . "&client_secret=" . $fb
->getApiSecret() . "&grant_type=client_credentials";
$http = drupal_http_request($path);
if ($http->code == 200 && isset($http->data)) {
$data = explode('=', $http->data);
$token = $data[1];
if ($token) {
$cache[$cache_key] = $token;
$result = cache_set($cache_key, $token, 'cache', CACHE_TEMPORARY);
}
}
if (empty($token)) {
watchdog('fb', 'Failed to get application (%app_id) access token.', array(
'%app_id' => $app_id,
), WATCHDOG_ERROR);
}
}
}
else {
$cache_key .= '_' . $fbu;
// Get the user access token.
if ($fbu == 'me' || $fbu == fb_facebook_user($fb)) {
$cache[$cache_key] = $fb
->getAccessToken();
}
else {
$session_data = fb_invoke(FB_OP_GET_USER_SESSION, array(
'fb' => $fb,
'fb_app' => fb_get_app(array(
'id' => $app_id,
)),
'fbu' => $fbu,
), array());
if (count($session_data)) {
$cache[$cache_key] = $session_data['access_token'];
}
}
}
return isset($cache[$cache_key]) ? $cache[$cache_key] : NULL;
}
/**
* This helper original written because facebook's $fb->api() function was
* very buggy. I'm not sure this is still needed. On the other hand, a
* future version of modules/fb might use this instead of faceobok's PHP SDK,
* eliminating the need for it entirely.
*/
function fb_call_method($fb, $method, $params = array()) {
if (!isset($params['access_token'])) {
$params['access_token'] = fb_get_token($fb);
}
$params['format'] = 'json-strings';
// Here's how to create a url that conforms to standards:
$url = url("https://api.facebook.com/method/{$method}", array(
'query' => $params,
));
// If facebook gives errors like "Invalid OAuth 2.0 Access Token 190/Unable to get application prop" it might be necessary to uncomment the urldecode below.
// http://forum.developers.facebook.net/viewtopic.php?id=76228
// $url = rawurldecode($url);
$http = drupal_http_request($url);
if (!isset($http->error) && isset($http->data)) {
$data = json_decode($http->data, TRUE);
// Yes, it's double encoded. At least sometimes.
if (is_string($data)) {
$data = json_decode($data, TRUE);
}
if (is_array($data)) {
if (isset($data['error_code'])) {
throw new FacebookApiException($data);
}
}
elseif ($http->data == 'true' || $http->code == 200) {
// No problems.
}
else {
// Never reach this???
if (function_exists('dpm')) {
dpm($http, __FUNCTION__ . " unexpected result from {$url}");
}
// XXX
}
return $data;
}
else {
// Should we throw FacebookApiException, or plain old exception?
throw new FacebookApiException(array(
'error_msg' => t('fb_call_method failed calling !method. !detail', array(
'!method' => $method,
'!detail' => $http->error,
)),
'error_code' => $http->code,
));
}
}
/**
* Helper function for fql queries.
*
* Use $params to pass a session_key, when needed.
*/
function fb_fql_query($fb, $query, $params = array()) {
try {
$params['query'] = $query;
//$result = fb_call_method($fb, 'fql.query', $params);
$params['method'] = 'fql.query';
$result = $fb
->api($params);
return $result;
} catch (Exception $e) {
fb_log_exception($e, t("FQL query failed. The query was \"%query\".", array(
'%query' => $query,
)));
}
}
/**
* Helper function for facebook's batch graph api.
*
* This function accepts a simpler interface than facebook's. The queries are
* passed in as a simple array, and the data is parsed into a PHP data
* structure.
*
* @TODO: when $method=='GET', share caching with fb_api().
*/
function fb_api_batch($fb, $queries, $params, $method = 'GET') {
$data = array();
// Build facebook's data structure. Our's supports only GET or POST at a time.
$fb_queries = array();
foreach ($queries as $query) {
$fb_queries[] = array(
'method' => $method,
'relative_url' => $query,
);
}
$wrapped_data = $fb
->api('/?batch=' . json_encode($fb_queries), 'POST', $params);
// Use POST, not $method.
foreach ($wrapped_data as $w_d) {
if ($w_d['code'] == 200 && isset($w_d['body'])) {
$data[] = json_decode($w_d['body'], TRUE);
}
else {
// Unexpected code
$data[] = $w_d;
}
}
return $data;
}
/**
* Helper function determines a name from data returned from fb_graph().
* Almost always, graph data includes a 'name'. But in rare cases that is
* not present and we use an id instead.
*/
function _fb_get_name($graph_data) {
if (!empty($graph_data['name'])) {
return $graph_data['name'];
}
elseif (!empty($graph_data['id'])) {
return $graph_data['id'];
}
else {
return t('Unknown');
}
}
/**
* Implements hook_page_alter().
* Can alter the $page['page_bottom'] hidden region here.
*/
function fb_page_alter(&$page) {
global $_fb, $_fb_app;
// This element recommended by facebook. http://developers.facebook.com/docs/reference/javascript/
$output = "<div id=\"fb-root\" class=\"fb_module\"></div>\n";
$settings = fb_js_settings();
$output .= "<script type=\"text/javascript\">\n";
$output .= "<!--//--><![CDATA[//><!--\n";
// Pending javascript that needs to execute after FB is initialized.
$js_array = fb_invoke(FB_OP_JS, array(
'fb' => $GLOBALS['_fb'],
'fb_app' => $GLOBALS['_fb_app'],
), array());
if (count($js_array)) {
// The function we define in the footer will be called after FB is initialized.
$output .= "FB_JS.initHandler = function() {\n";
//$output .= "debugger;\n";
$output .= implode("\n ", $js_array);
$output .= "};\n";
$output .= "jQuery(document).bind('fb_init', FB_JS.initHandler);\n\n";
}
// Our settings. We add them here, as late during request as possible.
$output .= " jQuery.extend(Drupal.settings, " . json_encode(array(
'fb' => fb_js_settings(),
)) . ");\n\n";
// Initialize FB.
$output .= "if (typeof(FB) == 'undefined') {\n";
// Load the JS SDK asynchronously.
// http://developers.facebook.com/docs/reference/javascript/
$output .= " var e = document.createElement('script');\n";
$output .= " e.async = true;\n";
$output .= " e.src = Drupal.settings.fb.js_sdk_url;\n";
$output .= " document.getElementById('fb-root').appendChild(e);\n";
$output .= "}\n\n";
$output .= "\n//--><!]]>\n";
$output .= "\n</script>\n";
$page['page_bottom']['fb'] = array(
'#type' => 'markup',
'#markup' => $output,
);
}
/**
* Is the current request a canvas page?
*/
function fb_is_canvas() {
if (fb_is_tab()) {
return FALSE;
}
elseif (fb_settings(FB_SETTINGS_CB)) {
// Using fb_url_rewrite.
return TRUE;
}
elseif (fb_settings(FB_SETTINGS_TYPE) == FB_SETTINGS_TYPE_CANVAS) {
// No rewrite, but fb_settings.inc has detected type.
return TRUE;
}
return FALSE;
}
/**
* Is the current page a profile tab?
*/
function fb_is_tab() {
if (fb_settings(FB_SETTINGS_TYPE) == FB_SETTINGS_TYPE_PAGE_TAB) {
return TRUE;
}
elseif (fb_settings(FB_SETTINGS_TYPE) == FB_SETTINGS_TYPE_PROFILE) {
// deprecated FBML tab
return TRUE;
}
elseif (isset($_REQUEST['fb_sig_in_profile_tab']) && $_REQUEST['fb_sig_in_profile_tab']) {
// deprecated ancient history
// Old way, no migrations enabled.
return TRUE;
}
return FALSE;
}
/**
* Does the current user like the current page?
*
* Expect this to work only when fb_is_tab() returns TRUE.
*/
function fb_is_page_liked() {
global $_fb;
if (!empty($_fb)) {
$sr = $_fb
->getSignedRequest();
return isset($sr['page']) && $sr['page']['liked'];
}
}
/**
* Does the current user administer the current page?
*
* Expect this to work only when fb_is_tab() returns TRUE.
*/
function fb_is_page_admin() {
global $_fb;
if (!empty($_fb)) {
$sr = $_fb
->getSignedRequest();
return isset($sr['page']) && $sr['page']['admin'];
}
}
/**
* Sometimes calls to fb_api_init succeed, but calls to the client api
* will fail because cookies are obsolete or what have you. This
* function makes a call to facebook to test the session. Expensive,
* so use only when necessary.
*
*/
function fb_api_check_session($fb) {
$success = FALSE;
try {
$me = $fb
->api('me');
// Store the locale if set.
if (isset($me['locale'])) {
$_SESSION['fb_locale'] = $me['locale'];
}
// Does not matter what is returned, as long as exception is not thrown.
$success = TRUE;
} catch (Exception $e) {
if (fb_verbose()) {
watchdog('fb', 'fb_api_check_session failed. Possible attempt to spoof a facebook session!');
//watchdog('fb', print_r($fb->getSession(), 1));
}
$success = FALSE;
if (fb_verbose()) {
fb_log_exception($e, t("fb_api_check_session failed."));
}
$app_id = $fb
->getAppId();
unset($_SESSION['fb'][$app_id]);
unset($_SESSION['_fb_' . $app_id]);
unset($_SESSION['_fb_token_' . $app_id]);
// Unsetting the javasript fbu can be helpful when third-party cookies disabled.
//fb_js_settings('fbu', 0); @TODO still needed? helpful?
}
return $success;
}
/**
* Helper to ensure local user is logged out, or an anonymous session is refreshed.
*/
function _fb_logout() {
if (session_name()) {
// Avoid PHP warning.
session_start();
// Make sure there is a session before destroy.
session_destroy();
$GLOBALS['user'] = drupal_anonymous_user();
drupal_session_initialize();
}
}
/**
* Returns the facebook user id currently visiting a canvas page, or if
* set_user has been called. Unlike fb_get_fbu(), works only on canvas and
* connect pages, or when infinite session has been initialized.
*/
function fb_facebook_user($fb = NULL) {
if (!isset($fb)) {
$fb = $GLOBALS['_fb'];
}
if (!$fb) {
return;
}
try {
$fbu = $fb
->getUser();
return $fbu;
} catch (FacebookApiException $e) {
fb_log_exception($e, t('Failed to get Facebook user id. detail: !detail', array(
'!detail' => print_r($e, 1),
)));
}
}
/**
* Helper function to ensure user has authorized an application.
*
* Similar to the old require_login() provided by the old facebook API.
* Works by redirecting the user as described in http://developers.facebook.com/docs/authentication/.
*
* @TODO handle users who skip.
*/
function fb_require_authorization($fb = NULL, $destination = NULL) {
if (!$fb) {
$fb = $GLOBALS['_fb'];
}
if (!$fb) {
throw new Exception(t('Failed to authorize facebook application. Could not determine application id.'));
}
$fbu = fb_facebook_user($fb);
if (!$fbu) {
$client_id = $fb
->getAppId();
$redirect_uri = $destination ? $destination : url(current_path(), array(
'absolute' => TRUE,
'fb_canvas' => fb_is_canvas(),
));
$url = "https://graph.facebook.com/oauth/authorize?client_id={$client_id}&redirect_uri={$redirect_uri}";
// Which permissions to prompt for?
$perms = array();
drupal_alter('fb_required_perms', $perms);
if (count($perms)) {
$url .= '&scope=' . implode(',', $perms);
}
if (fb_is_canvas() || fb_is_tab()) {
fb_iframe_redirect($url);
}
else {
header('Location: ' . $url);
// drupal_goto is for internal redirects only.
}
}
else {
return $fbu;
}
}
/**
* Helper tells other modules when to load admin hooks.
*/
function fb_is_fb_admin_page() {
if (arg(0) == 'admin' && (arg(1) == 'fb' || arg(2) == 'fb')) {
// Keep consistant titles across tabs served by multiple modules.
if ($label = arg(FB_PATH_ADMIN_APPS_ARGS)) {
drupal_set_title($label);
}
else {
drupal_set_title(t('Drupal for Facebook'));
}
return TRUE;
}
}
/**
* Given a facebook user id, learn the local uid, if any.
*
*/
function fb_get_uid($fbu, $fb_app = NULL) {
$uid = NULL;
if ($fbu) {
$uid = fb_invoke(FB_OP_GET_UID, array(
'fbu' => $fbu,
'fb_app' => $fb_app,
));
}
return $uid;
}
/**
* Given a local user id, find the facebook id.
*
* Invokes hook_fb(FB_OP_GET_FBU) in order to ask other modules what the fbu
* is. Typically, fb_user.module will answer the question.
*/
function fb_get_fbu($uid, $fb_app = NULL) {
// Accept either a user object or uid passed in.
if (is_object($uid) && isset($uid->uid) && !empty($uid->fbu)) {
return $uid->fbu;
}
elseif (is_object($uid)) {
$uid = isset($uid->uid) ? $uid->uid : 0;
}
if ($uid) {
// User management is handled by another module. Use our hook to ask for mapping.
$fbu = fb_invoke(FB_OP_GET_FBU, array(
'uid' => $uid,
'fb' => $GLOBALS['_fb'],
));
}
else {
$fbu = NULL;
}
return $fbu;
}
/**
* Convenience function to learn the fbu associated with a user, node or comment.
* Used in theming (X)FBML tags.
*/
function fb_get_object_fbu($object) {
$cache =& drupal_static(__FUNCTION__);
if (!isset($cache)) {
$cache = array();
}
if (isset($object->uid) && isset($cache[$object->uid])) {
$fbu = $cache[$object->uid];
return $fbu;
}
elseif (isset($object->fbu)) {
// Explicitly set.
$fbu = $object->fbu;
}
elseif (isset($object->init) && ($pos = strpos($object->init, '@facebook'))) {
// Naming convention used by fb_user when creating accounts.
// $object->init may be present when object is a user.
$fbu = substr($object->init, 0, $pos);
}
elseif (!empty($object->name) && ($pos = strpos($object->name, '@facebook'))) {
$fbu = substr($object->name, 0, $pos);
}
elseif (!empty($object->uid)) {
// This can be expensive on pages with many comments or nodes!
$fbu = fb_get_fbu($object->uid);
}
if (isset($fbu) && is_numeric($fbu)) {
if (isset($object->uid) && $object->uid > 0) {
$cache[$object->uid] = $fbu;
}
return $fbu;
}
}
/**
* Convenience method to get app info based on id or nid.
*/
function fb_get_app($search_data) {
// $search_data can be an apikey, or an array of other search params.
if (!is_array($search_data)) {
$search_data = array(
'id' => $search_data,
);
}
$fb_app = fb_invoke(FB_OP_GET_APP, $search_data);
return $fb_app;
}
/**
* Convenience method for other modules to attach data to the fb_app
* object.
*
* It is assumed the fb_app implementation will fill in the data
* field. We really should clean up the separation between modules,
* or merge fb_app.module into this one.
*/
function fb_get_app_data(&$fb_app) {
if (!$fb_app) {
// Avoid PHP strict error.
return array();
}
if (!isset($fb_app->fb_app_data)) {
$fb_app->fb_app_data = !empty($fb_app->data) ? unserialize($fb_app->data) : array();
}
return $fb_app->fb_app_data;
}
/**
* Will return a human-readable name if the fb_app module supports it, or
* fb_admin_get_app_info($fb_app) has been called. However we don't
* take the relatively expensive step of calling that ourselves.
*/
function fb_get_app_title($fb_app) {
if (isset($fb_app->title)) {
return $fb_app->title;
}
elseif (isset($fb_app->name)) {
return $fb_app->name;
}
else {
return $fb_app->label;
}
}
/**
* Convenience method to return array of all know fb_apps.
*/
function fb_get_all_apps() {
$apps = fb_invoke(FB_OP_GET_ALL_APPS, NULL, array());
return $apps;
}
/**
* A convenience method for returning a list of facebook friends.
*
* This should work efficiently in canvas pages for finding friends of
* the current user.
*
* @TODO - also support users who have permitted offline access.
*
* @return: an array of facebook ids
*/
function fb_get_friends($fbu, $fb_app = NULL) {
$cache =& drupal_static(__FUNCTION__);
if (!$fb_app) {
$fb_app = $GLOBALS['_fb_app'];
}
// Facebook only allows us to query the current user's friends, so let's try
// to log in as that user. It will only actually work if they are the
// current user of a canvas page, or they've signed up for an infinite
// session.
$fb = fb_api_init($fb_app, $fbu);
if (!$fb || !$fbu) {
return;
}
$items = array();
if (!isset($cache[$fbu])) {
if ($fb === $GLOBALS['_fb'] && $fbu == fb_facebook_user($fb)) {
try {
$items = fb_call_method($fb, 'friends.get', array(
'uid' => $fbu,
));
$cache[$fbu] = $items;
} catch (Exception $e) {
fb_log_exception($e, t('Failed call to friends.get'), $fb);
}
}
// friends_get does not work in cron call, so we double check. @TODO - still needed?
if (!$items || !count($items)) {
$logged_in = fb_facebook_user($fb);
$query = "SELECT uid2 FROM friend WHERE uid1={$fbu}";
// FQL, no {curly_brackets}!
try {
$result = fb_call_method($fb, 'fql.query', array(
'query' => $query,
));
//dpm($result, "FQL " . $query); // debug
if (is_array($result)) {
foreach ($result as $data) {
$items[] = $data['uid2'];
}
}
// Facebook's API has the annoying habit of returning an item even if user
// has no friends. We need to clean that up.
if (!$items[0]) {
unset($items[0]);
}
$cache[$fbu] = $items;
} catch (Exception $e) {
fb_log_exception($e, t('Failed call to fql.query: !query', array(
'!query' => $query,
)), $fb);
}
}
}
if (isset($cache[$fbu])) {
return $cache[$fbu];
}
}
// Return array of facebook gids
function fb_get_groups($fbu, $fb_app = NULL) {
$items = array();
$groups = fb_get_groups_data($fbu);
if ($groups && count($groups)) {
foreach ($groups as $data) {
$items[] = $data['gid'];
}
}
return $items;
}
function fb_get_groups_data($fbu, $fb_app = NULL) {
$cache =& drupal_static(__FUNCTION__);
$fb = _fb_api_init($fb_app);
if (!$fb || !$fbu) {
return;
}
if (!isset($cache[$fbu])) {
$cache[$fbu] = fb_call_method($fb, 'groups.get', array(
'uid' => $fbu,
));
}
return $cache[$fbu];
}
//// Menu structure.
/**
* Implements hook_menu().
*/
function fb_menu() {
$items = array();
// Admin pages overview.
$items[FB_PATH_ADMIN] = array(
'title' => 'Facebook Apps',
'description' => 'Facebook Applications',
'page callback' => 'fb_admin_page',
'access arguments' => array(
FB_PERM_ADMINISTER,
),
'file' => 'fb.admin.inc',
'type' => MENU_NORMAL_ITEM,
);
$items[FB_PATH_ADMIN . '/list'] = array(
'title' => 'List Apps',
'weight' => -2,
'type' => MENU_DEFAULT_LOCAL_TASK,
);
$items[FB_PATH_ADMIN . '/settings'] = array(
'title' => 'Settings',
'access arguments' => array(
FB_PERM_ADMINISTER,
),
'weight' => -1,
'type' => MENU_LOCAL_TASK,
'page callback' => 'drupal_get_form',
'page arguments' => array(
'fb_admin_settings',
),
'file' => 'fb.admin.inc',
);
// Admin pages for each app.
$items[FB_PATH_ADMIN_APPS . '/%fb'] = array(
'title' => 'Application Detail',
'description' => 'Facebook Applications',
'page callback' => 'fb_admin_app_page',
'page arguments' => array(
FB_PATH_ADMIN_APPS_ARGS,
),
'access arguments' => array(
FB_PERM_ADMINISTER,
),
'file' => 'fb.admin.inc',
'type' => MENU_CALLBACK,
);
$items[FB_PATH_ADMIN_APPS . '/%fb/fb'] = array(
'title' => 'View',
'weight' => -2,
'type' => MENU_DEFAULT_LOCAL_TASK,
);
$items[FB_PATH_ADMIN_APPS . '/%fb/fb/set_props'] = array(
'title' => 'Set Properties',
'description' => 'Set Facebook Application Properties',
'page callback' => 'drupal_get_form',
'page arguments' => array(
'fb_admin_set_properties_form',
FB_PATH_ADMIN_APPS_ARGS,
),
'access arguments' => array(
FB_PERM_ADMINISTER,
),
'file' => 'fb.admin.inc',
'type' => MENU_CALLBACK,
);
// Javascript helper
$items['fb/js'] = array(
'page callback' => 'fb_js_cb',
'type' => MENU_CALLBACK,
'access callback' => TRUE,
);
// Ajax event handler.
$items[FB_PATH_AJAX_EVENT . '/%'] = array(
'page callback' => 'fb_ajax_event',
'type' => MENU_CALLBACK,
'access callback' => TRUE,
'page arguments' => array(
FB_PATH_AJAX_EVENT_ARGS,
),
);
// "Channel" http://developers.facebook.com/docs/reference/javascript/FB.init
$items['fb/channel'] = array(
'page callback' => 'fb_channel_page',
'type' => MENU_CALLBACK,
'access callback' => TRUE,
);
return $items;
}
/**
* Implementation of a %wildcard_load(). http://drupal.org/node/224170
*
* Handles menu items with %fb in the path. Seems to get called a lot(!) so we cache.
*/
function fb_load($id) {
$cache =& drupal_static(__FUNCTION__);
if (!isset($cache)) {
$cache = array();
}
if (!isset($cache[$id])) {
$query = array(
'label' => $id,
);
if (fb_is_fb_admin_page()) {
// Show disabled apps to admins.
$query['status'] = 0;
// status >= 0
}
$cache[$id] = fb_get_app($query);
}
return $cache[$id];
}
/**
* Implementation of a %wildcard_load(). http://drupal.org/node/224170
*
* Handles menu items with %fbu in the path. Simply returns the numerical id.
*/
function fbu_load($id) {
if (is_numeric($id)) {
return $id;
}
elseif ($id == 'me') {
// Drupal stupidly calls this before fb_init()! So _fb may not be initialized.
if ($GLOBALS['_fb_app'] && !$GLOBALS['_fb']) {
$fb = fb_api_init($GLOBALS['_fb_app']);
$fbu = fb_facebook_user($fb);
}
else {
$fbu = fb_facebook_user();
}
return $fbu;
}
return NULL;
}
/**
* Implementation of a %wildcard_load(). http://drupal.org/node/224170
*
* Handles menu items with %fb_graph in the path. Seems to get called a lot(!) so we cache.
*/
function fb_graph_load($id) {
extract(fb_vars());
// Drupal stupidly calls this before fb_init()! So _fb may not be initialized.
if ($GLOBALS['_fb_app'] && !$GLOBALS['_fb']) {
$fb = fb_api_init($GLOBALS['_fb_app']);
$fbu = fb_facebook_user($fb);
}
$cache =& drupal_static(__FUNCTION__);
if (!isset($cache)) {
$cache = array();
}
if (!isset($cache[$id])) {
$params = array(
'access_token' => fb_get_token($fb, $fbu),
'metadata' => 1,
);
try {
$cache[$id] = fb_graph($id, $params, 'GET', $fb);
} catch (Exception $e) {
fb_log_exception($e, t('Failed to load facebook graph object %id.', array(
'%id' => $id,
)));
}
}
return $cache[$id];
}
/**
* Implements hook_permission().
*/
function fb_permission() {
return array(
FB_PERM_ADMINISTER => array(
'title' => t('Administer Facebook settings in fb.module'),
),
);
}
/**
* Implements hook_exit().
*
* When completing a canvas page we need special processing for the session. See fb_session.inc.
*
* Also invoke hook_fb(FB_OP_EXIT), so that other modules can handle special
* cases (in particular form support in fb_canvas.module.
*
* TODO: remove this hook, for compatibility with Drupal's aggressive caching. Modules that currently look for FB_OP_EXIT will need to implement hook_exit on their own.
*/
function fb_exit($destination = NULL) {
global $_fb_app, $_fb;
$on_exit =& drupal_static('fb_invoke_async');
if (!empty($on_exit)) {
while ($args = array_shift($on_exit)) {
$func = array_shift($args);
if (fb_verbose()) {
watchdog('fb', t("Processing delayed call to %function."), array(
'%function' => $func,
));
}
try {
$result = call_user_func_array($func, $args);
} catch (Exception $e) {
fb_log_exception($e, t('Failed calling %function.', array(
'%function' => $func,
)));
}
}
}
if ($_fb_app && $_fb) {
// Invoke other modules.
fb_invoke(FB_OP_EXIT, array(
'fb_app' => $_fb_app,
'fb' => $GLOBALS['_fb'],
), $destination);
}
}
/**
* Implements hook_module_implements_alter().
*
* Unfortunately fb_canvas has to process hook_exit last, so we tweak the order of hooks to enforce that order.
*/
function fb_module_implements_alter(&$implementations, $hook) {
if ($hook == 'exit') {
// Move our implementation to end of list.
$group = $implementations['fb'];
unset($implementations['fb']);
$implementations['fb'] = $group;
}
if ($hook == 'fb' && isset($implementations['fb_canvas'])) {
$group = $implementations['fb_canvas'];
unset($implementations['fb_canvas']);
$implementations['fb_canvas'] = $group;
}
}
function fb_iframe_redirect($url) {
// Unset drupal's header
if (function_exists('header_remove')) {
// php 5.3
header_remove('Location');
}
echo "<script type=\"text/javascript\">\ntop.location.href = \"{$url}\";\n</script>";
exit;
}
/**
* Convert a local fully qualified path to a facebook app path. This needs to
* be used internally, to fix drupal_gotos upon form submission. Third party
* modules should not need to call this.
*/
function fb_iframe_fix_url($url, $iframe_base) {
global $base_url;
if ($app_id = fb_settings(FB_SETTINGS_ID)) {
// Fully qualified paths.
$patterns[] = "|" . url('', array(
'absolute' => TRUE,
)) . "|";
$replacements[] = $iframe_base;
// Url rewrites still used for iframe pages. Still needed ???
$patterns[] = "|{$base_url}/" . FB_SETTINGS_CB . '/' . $app_id . '/|';
$replacements[] = $iframe_base;
$url = preg_replace($patterns, $replacements, $url);
if (strpos($url, $iframe_base) !== FALSE) {
// Facebook expects "appNNN_" prepended to hash
$patterns = "|#([^\\?]*)|";
$replacements = "#app{$app_id}_\$1";
$url = preg_replace($patterns, $replacements, $url);
}
}
return $url;
}
/**
* Call a function not now but later. As late as possible during the current request.
*
* In practice the function will be invoked during hook_exit(). In an
* ideal world it might be nice to be invoked even later, similar to the
* way drupal processes cron in drupal_page_footer() without delaying the
* page returned to the user. However I know of no drupal hook called
* later than hook_exit.
*
* Provided as a convenience for third-party modules that want to invoke
* some facebook API, but cannot do so right away. For example say your
* module implements hook_node_insert and wants to publish a message about
* the new node to facebook. You could try invoking
* fb_graph_publish_action() from your hook_node_insert(). Unfortunately
* facebook would immediately look up the new node on your server (to
* populate graph details), but drupal would return page not found instead
* of the inspected node because the insert has not yet finished! To work
* around this call fb_invoke_async('fb_graph_publish_action', ...)
* instead.
*
* @see fb_exit().
*/
function fb_invoke_async() {
$cache =& drupal_static(__FUNCTION__);
$cache[] = func_get_args();
}
/**
* Invoke hook_fb().
* Only modules/fb modules should invoke this helper function which calls third-party hooks.
*/
function fb_invoke($op, $data = NULL, $return = NULL, $hook = FB_HOOK) {
foreach (module_implements($hook) as $name) {
$function = $name . '_' . $hook;
try {
$function($op, $data, $return);
} catch (Exception $e) {
if (isset($data['fb_app'])) {
fb_log_exception($e, t('Exception calling %function(%op) (!app)', array(
'%function' => $function,
'%op' => $op,
'%label' => $data['fb_app']->label,
'%id' => $data['fb_app']->id,
'!app' => l($data['fb_app']->label, FB_PATH_ADMIN_APPS . '/' . $data['fb_app']->label),
)));
}
else {
fb_log_exception($e, t('Exception calling %function(%op)', array(
'%function' => $function,
'%op' => $op,
)));
}
}
}
return $return;
}
/**
* This method will clean up URLs. When serving canvas pages, extra
* information is included in URLs. This will remove the extra
* information. Useful when linking back to the website from a canvas page or
* wall post.
*
* For example in the following code, $url2 will link out to the server's domain:
*
* $url = url('node/42', array('absolute' => TRUE)); // i.e. http://apps.facebook.com/example/node/42
* $url2 = fb_scrub_urls($url); // i.e. http://example.com/node/42
*
*
* @see fb_url_rewrite.inc
*/
function fb_scrub_urls($content) {
if (function_exists('_fb_settings_url_rewrite_prefixes')) {
foreach (_fb_settings_url_rewrite_prefixes() as $key) {
$patterns[] = "|{$key}/[^/]*/|";
$replacements[] = "";
}
$content = preg_replace($patterns, $replacements, $content);
}
return $content;
}
/**
* Convenience function to log and report exceptions.
*/
function fb_log_exception($e, $text = '', $fb = NULL) {
if ($text) {
$message = $text . ': ' . $e
->getMessage();
}
else {
$message = $e
->getMessage();
}
if ($code = $e
->getCode()) {
$message = "(#{$code}) {$message}";
}
if ($fb) {
$message .= '. (' . t('logged into facebook as %fbu', array(
'%fbu' => $fb
->getUser(),
)) . ')';
}
if (fb_verbose()) {
$message .= '<pre>' . $e . '</pre>';
}
watchdog('fb', $message, array(), WATCHDOG_ERROR);
if (user_access(FB_PERM_ADMINISTER)) {
drupal_set_message($message, 'error');
}
}
/**
* Simple wrapper around $fb->api() which caches data. Does not support the
* polymorphic arguments of $fb->api(). This is not a replacement for that
* function.
*
* This is intended to avoid performace problems when, for example,
* $fb->api('me') is called several times in a single request.
*
* @param $graph_path
* Something facebook's graph API will understand. Could be an ID or a path like 'me/accounts', for example.
*
* @param $params
* Extras to pass to the graph API. When making a request that requires a
* token, try array('access_token' => fb_get_token()).
*/
function fb_api($graph_path, $params = array()) {
static $cache;
$fb = $GLOBALS['_fb'];
if (!$fb) {
return;
}
if (!isset($cache)) {
$cache = array();
}
if (!isset($cache[$graph_path])) {
$cache[$graph_path] = $fb
->api($graph_path, $params);
}
return $cache[$graph_path];
}
/**
* DEPRECATED. Use fb_api() instead.
* Returns information about one or more facebook users.
*
* Historically, this helper function used facebook's users_getInfo API, hence
* the name. Now it uses fql.query, but accomplishes the same thing.
*
* @param $oids
* Array of facebook object IDs. In this case they should each be a user id.
*
* @param $fb
* Rarely needed. For cases when global $_fb is not set, or more than one
* facebook api has been initialized.
*
* @param $refresh_cache
* If true, force a call to facebook instead of relying on temporarily stored
* data.
*/
function fb_users_getInfo($oids, $fb = NULL, $refresh_cache = FALSE) {
if (!$fb) {
$fb = $GLOBALS['_fb'];
}
$infos = array();
if (!is_array($oids)) {
$oids = array();
}
if ($fb) {
$app_id = $fb
->getAppId();
// First try cache
if (!$refresh_cache && isset($_SESSION['fb'])) {
foreach ($oids as $oid) {
if (isset($_SESSION['fb'][$app_id]['userinfo'][$oid])) {
$info = $_SESSION['fb'][$app_id]['userinfo'][$oid];
$infos[] = $info;
}
}
}
if (count($infos) != count($oids)) {
// Session cache did not include all users, update the cache.
$fields = array(
'about_me',
'affiliations',
'birthday',
'name',
'first_name',
'last_name',
'is_app_user',
'pic',
'pic_big',
'pic_square',
'profile_update_time',
'proxied_email',
'email_hashes',
'email',
'uid',
);
try {
$infos = fb_fql_query($fb, 'SELECT ' . implode(', ', $fields) . ' FROM user WHERE uid in(' . implode(', ', $oids) . ')', array(
fb_get_token($fb),
));
// Update cache with recent results.
if (is_array($infos)) {
foreach ($infos as $info) {
$_SESSION['fb'][$app_id]['userinfo'][$info['uid']] = $info;
}
}
} catch (FacebookApiException $e) {
fb_log_exception($e, t('Failed to query facebook user info'), $fb);
}
}
return $infos;
}
}
/**
* For debugging, add $conf['fb_verbose'] = TRUE; to settings.php.
*/
function fb_verbose() {
return variable_get(FB_VAR_VERBOSE, NULL);
}
/**
* hook_username_alter().
*
* Return a user's facebook name, instead of local username.
*/
function fb_username_alter(&$name, $account) {
// This function can be called very early in the bootstrap process, before
// the modules are initialized, in which case we will fail to alter the
// name.
$is_theming_username =& drupal_static('fb_theming_username');
$enabled = variable_get(FB_VAR_ALTER_USERNAME, FB_ALTER_USERNAME_NOT_THEMING);
if ($enabled == FB_ALTER_USERNAME_NEVER || $enabled == FB_ALTER_USERNAME_NOT_THEMING && $is_theming_username) {
// Altering disabled.
return;
}
// Skip on admin pages.
if (arg(0) == 'admin') {
return;
}
if (!strpos($name, '@facebook')) {
// Only alter unique names created by fb_user.module.
return;
}
if ($fbu = fb_get_fbu($account)) {
// Querying names from facebook is expensive, so some trickery here to optimize things.
// First we try the static cache.
$names =& drupal_static(__FUNCTION__);
if (!isset($names[$fbu])) {
// Next try database cache.
$use_cache = variable_get(FB_VAR_ALTER_USERNAME_AND_CACHE, FB_ALTER_USERNAME_DONT_CACHE);
if ($use_cache == FB_ALTER_USERNAME_AND_CACHE) {
if ($cache = cache_get('fb_username_' . $fbu)) {
$names[$fbu] = $cache->data;
}
}
}
if (!isset($names[$fbu]) && !empty($GLOBALS['_fb'])) {
// Nothing from the previous attempts worked so we have to query facebook.com.
try {
// Use fql query instead of graph api, because it will succeed more often.
$data = fb_fql_query($GLOBALS['_fb'], "SELECT name FROM user WHERE uid={$fbu}", array(
'access_token' => fb_get_token($GLOBALS['_fb']),
));
if (count($data) && isset($data[0]['name'])) {
$names[$fbu] = $data[0]['name'];
if ($use_cache == FB_ALTER_USERNAME_AND_CACHE) {
cache_set('fb_username_' . $fbu, $names[$fbu]);
}
}
} catch (Exception $e) {
fb_log_exception($e, t('Failed to alter username for facebook user %fbu', array(
'%fbu' => $fbu,
)));
}
}
if (!empty($names[$fbu])) {
$name = $names[$fbu];
}
}
}
/**
* Implements hook_theme().
*
* Returns description of theme functions.
*
* @see fb.theme.inc
*/
function fb_theme() {
return array(
'fb_username' => array(
'arguments' => array(
'fbu' => NULL,
'object' => NULL,
'orig' => NULL,
),
'file' => 'fb.theme.inc',
),
'fb_user_picture' => array(
'arguments' => array(
'fbu' => NULL,
'account' => NULL,
'orig' => NULL,
),
'file' => 'fb.theme.inc',
),
'fb_fbml_popup' => array(
'arguments' => array(
'elements' => NULL,
),
'file' => 'fb.theme.inc',
),
'fb_login_button' => array(
'arguments' => array(
'text' => 'Connect with Facebook',
'options' => NULL,
),
'file' => 'fb.theme.inc',
),
'fb_markup' => array(
'arguments' => array(
'not_connected_markup' => NULL,
'connected_markup' => '<fb:profile-pic linked=false uid=loggedinuser></fb:profile-pic>',
'options' => NULL,
),
'file' => 'fb.theme.inc',
),
);
}
//// Javascript and Ajax helpers
/**
* Ajax javascript callback.
*
* For sites which use ajax, various events may create javascript which is
* normally embedded in a page. For example, posting to a user's wall. When
* ajax is used instead of a page reload, this callback will provide any
* javascript which should be run.
*/
function fb_js_cb() {
$js_array = fb_invoke(FB_OP_JS, array(
'fb' => $GLOBALS['_fb'],
'fb_app' => $GLOBALS['_fb_app'],
), array());
$extra_js = implode("\n", $extra);
print $extra_js;
exit;
}
/**
* Ajax callback handles an event from facebook's javascript sdk.
*
* @see
* fb.js and
* http://developers.facebook.com/docs/reference/javascript/FB.Event.subscribe
*
* @return
* Array of javascript to be evaluated by the page which called this
* callback.
*/
function fb_ajax_event($event_type) {
global $_fb, $_fb_app;
$js_array = array();
if (isset($_REQUEST['appId'])) {
$_fb_app = fb_get_app(array(
'id' => $_REQUEST['appId'],
));
// Remember signed_request in session, in case third party cookies are disabled.
if (isset($_REQUEST['signed_request']) && $_REQUEST['signed_request'] && variable_get(FB_VAR_USE_SESSION, TRUE)) {
$_SESSION['_fb_' . $_fb_app->id] = $_REQUEST['signed_request'];
}
elseif (isset($_REQUEST['access_token']) && $_REQUEST['access_token'] && variable_get(FB_VAR_USE_SESSION, TRUE)) {
$_SESSION['_fb_token_' . $_fb_app->id] = $_REQUEST['access_token'];
}
else {
unset($_SESSION['_fb_' . $_fb_app->id]);
unset($_SESSION['_fb_token_' . $_fb_app->id]);
}
if ($_fb_app) {
$_fb = fb_api_init($_fb_app);
// Data to pass to hook_fb.
$data = array(
'fb_app' => $_fb_app,
'fb' => $_fb,
'event_type' => $event_type,
'event_data' => $_POST,
);
$js_array = fb_invoke(FB_OP_AJAX_EVENT, $data, $js_array);
}
else {
watchdog('fb', 'fb_ajax_event did not find application %id', array(
'%id' => $_REQUEST['appId'],
), WATCHDOG_ERROR);
}
if ($event_type == 'session_change') {
// Session change is a special case. If user has logged out of
// facebook, we want a new drupal session. We do this here, even if
// fb_user.module is not enabled.
if (!isset($_POST['fbu']) || !$_POST['fbu']) {
// Logout, not login.
_fb_logout();
}
}
}
else {
watchdog('fb', 'fb_ajax_event called badly. Not passed appId.', array(), WATCHDOG_ERROR);
// Trying to track down what makes this happen.
if (fb_verbose() == 'extreme') {
watchdog('fb', 'fb_ajax_event called badly. Not passed appId. trace: !trace', array(
'!trace' => '<pre>' . print_r(debug_backtrace(), 1) . '</pre>',
), WATCHDOG_ERROR);
}
}
drupal_json_output(array_values($js_array));
//exit();
}
/**
* Menu callback for custom channel.
*
* @see http://developers.facebook.com/docs/reference/javascript/FB.init
*/
function fb_channel_page() {
// Headers instruct browser to cache this page.
drupal_add_http_header("Cache-Control", "public");
drupal_add_http_header("Expires", "Sun, 17-Jan-2038 19:14:07 GMT");
$date = format_date(time());
$output = "<!-- modules/fb fb_channel_page() {$date} -->\n";
$url = fb_js_settings('js_sdk_url');
$output .= "<script src=\"{$url}\"></script>\n";
print $output;
exit;
}
//// Miscellaneous helpers and convenience functions.
/**
* Protocol (http or https) of the current request.
*/
function fb_protocol() {
return isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on' ? 'https' : 'http';
}
/**
* Convenience wrapper around drupal_access_denied(). Call on pages where the
* access is denied because the user is not logged into facebook.
*/
function fb_access_denied() {
if (!fb_facebook_user()) {
drupal_set_message(t('You must <a href="#" onclick="FB.login(function(response) {}, {perms:Drupal.settings.fb.perms}); return false;">log into facebook to view this page</a>.'));
}
drupal_access_denied();
exit;
}
Functions
Name | Description |
---|---|
fbu_load | Implementation of a %wildcard_load(). http://drupal.org/node/224170 |
fb_access_denied | Convenience wrapper around drupal_access_denied(). Call on pages where the access is denied because the user is not logged into facebook. |
fb_ajax_event | Ajax callback handles an event from facebook's javascript sdk. |
fb_api | Simple wrapper around $fb->api() which caches data. Does not support the polymorphic arguments of $fb->api(). This is not a replacement for that function. |
fb_api_batch | Helper function for facebook's batch graph api. |
fb_api_check_session | Sometimes calls to fb_api_init succeed, but calls to the client api will fail because cookies are obsolete or what have you. This function makes a call to facebook to test the session. Expensive, so use only when necessary. |
fb_api_init | Include and initialize Facebook's PHP SDK. |
fb_call_method | This helper original written because facebook's $fb->api() function was very buggy. I'm not sure this is still needed. On the other hand, a future version of modules/fb might use this instead of faceobok's PHP SDK, eliminating the… |
fb_channel_page | Menu callback for custom channel. |
fb_controls | Controls are one way to customize the behavior of Drupal for Facebook modules. |
fb_custom_theme | Implements hook_custom_theme(). |
fb_exit | Implements hook_exit(). |
fb_facebook_user | Returns the facebook user id currently visiting a canvas page, or if set_user has been called. Unlike fb_get_fbu(), works only on canvas and connect pages, or when infinite session has been initialized. |
fb_fql_query | Helper function for fql queries. |
fb_get_all_apps | Convenience method to return array of all know fb_apps. |
fb_get_app | Convenience method to get app info based on id or nid. |
fb_get_app_data | Convenience method for other modules to attach data to the fb_app object. |
fb_get_app_title | Will return a human-readable name if the fb_app module supports it, or fb_admin_get_app_info($fb_app) has been called. However we don't take the relatively expensive step of calling that ourselves. |
fb_get_fbu | Given a local user id, find the facebook id. |
fb_get_friends | A convenience method for returning a list of facebook friends. |
fb_get_groups | |
fb_get_groups_data | |
fb_get_object_fbu | Convenience function to learn the fbu associated with a user, node or comment. Used in theming (X)FBML tags. |
fb_get_token | Helper to get the tokens needed to accss facebook's API. |
fb_get_uid | Given a facebook user id, learn the local uid, if any. |
fb_graph | Helper function to work with facebook "open" graph. |
fb_graph_load | Implementation of a %wildcard_load(). http://drupal.org/node/224170 |
fb_iframe_fix_url | Convert a local fully qualified path to a facebook app path. This needs to be used internally, to fix drupal_gotos upon form submission. Third party modules should not need to call this. |
fb_iframe_redirect | |
fb_init | Implements hook_init(). |
fb_invoke | Invoke hook_fb(). Only modules/fb modules should invoke this helper function which calls third-party hooks. |
fb_invoke_async | Call a function not now but later. As late as possible during the current request. |
fb_is_canvas | Is the current request a canvas page? |
fb_is_fb_admin_page | Helper tells other modules when to load admin hooks. |
fb_is_page_admin | Does the current user administer the current page? |
fb_is_page_liked | Does the current user like the current page? |
fb_is_tab | Is the current page a profile tab? |
fb_js_cb | Ajax javascript callback. |
fb_js_settings | Helper to get the configured variables. |
fb_load | Implementation of a %wildcard_load(). http://drupal.org/node/224170 |
fb_log_exception | Convenience function to log and report exceptions. |
fb_menu | Implements hook_menu(). |
fb_module_implements_alter | Implements hook_module_implements_alter(). |
fb_page_alter | Implements hook_page_alter(). Can alter the $page['page_bottom'] hidden region here. |
fb_permission | Implements hook_permission(). |
fb_protocol | Protocol (http or https) of the current request. |
fb_rdf_namespaces | Implements hook_rdf_namespaces(). Adds the xmlns:fb attribute to html tag. |
fb_require_authorization | Helper function to ensure user has authorized an application. |
fb_scrub_urls | This method will clean up URLs. When serving canvas pages, extra information is included in URLs. This will remove the extra information. Useful when linking back to the website from a canvas page or wall post. |
fb_theme | Implements hook_theme(). |
fb_tokens | Implements hook_tokens(). |
fb_username_alter | hook_username_alter(). |
fb_users_getInfo | DEPRECATED. Use fb_api() instead. Returns information about one or more facebook users. |
fb_vars | Helper function to get the most commonly used values. In your custom module, call extract(fb_vars()); to set $fb_app, $fb, and $fbu. |
fb_verbose | For debugging, add $conf['fb_verbose'] = TRUE; to settings.php. |
_fb_api_init | Wrapper function for fb_api_init. This helps for functions that should work whether or not we are on a canvas page. For canvas pages, the active fb object is used. For non-canvas pages, it will initialize the API using an infinite session, if configured. |
_fb_get_name | Helper function determines a name from data returned from fb_graph(). Almost always, graph data includes a 'name'. But in rare cases that is not present and we use an id instead. |
_fb_logout | Helper to ensure local user is logged out, or an anonymous session is refreshed. |