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, 'oauth' => TRUE, ); if (variable_get(FB_VAR_USE_COOKIE, TRUE)) { $fb_init_settings['cookie'] = TRUE; } // Figure out which app the current request is for. $_fb_app = fb_invoke(FB_OP_CURRENT_APP); 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; // Add perms to settings, for calling FB.login(). $perms = array(); drupal_alter('fb_required_perms', $perms); fb_js_settings('perms', implode(',', $perms)); // Initialize the PHP API. $_fb = fb_api_init($_fb_app); if ($_fb) { // This helps when third party cookies disabled. if (isset($_SESSION['_fb_' . $_fb_app->id])) { // Facebook and their damned undocumented data structures. $php_sr = $_fb->getSignedRequest(); $js_sr = array( 'accessToken' => $_fb->getAccessToken(), 'userID' => $php_sr['user_id'], 'signedRequest' => $_SESSION['_fb_' . $_fb_app->id], 'expiresIn' => 6988, // ??? ); $fb_init_settings['authResponse'] = (object) $js_sr; // Try setting cookie explicitly. /* This does not appear to work, disabling... if (variable_get(FB_VAR_TRY_TO_SET_COOKIE, TRUE) && variable_get(FB_VAR_USE_COOKIE, TRUE)) { dpm('setting cookie.', __FUNCTION__); setcookie('fbsr_' . $_fb_app->id, $_SESSION['_fb_' . $_fb_app->id], time() + 6988, '/'); } */ } // @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); } } else watchdog('fb', "URL indicates a facebook app, but could not initialize Facebook", array(), WATCHDOG_ERROR); if (isset($_REQUEST['destination'])) { $destination = $_REQUEST['destination']; } elseif (isset($_REQUEST['q'])) { $destination = $_REQUEST['q']; } else { $destination = ''; } 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), // http://drupal.org/node/1000452 ))); fb_js_settings('reload_url_append_hash', variable_get(FB_VAR_RELOAD_APPEND_HASH, FALSE)); } 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('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 { $user_language = user_preferred_language($GLOBALS['user']); $fb_lang = variable_get('fb_language_' . $user_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'); // See also fb_footer(), where we initialize facebook's SDK. } /** * * 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) { static $fb_js_settings = array(); 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) { static $cache = array(); // This helps with uncaught exceptions. However, it should be configurable // or at least not overwrite previously declared handler. set_exception_handler('fb_handle_exception'); 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' => 'README.txt', '%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($_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), )); // 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)) { $GLOBALS['conf']['cache'] = 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() { 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, $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('Content-Type' => 'application/x-www-form-urlencoded'); // Needed?? $data = http_build_query($params, '', '&'); $http = drupal_http_request($url, $headers, $method, $data); } 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_graph failed querying !path. !detail', array( '!path' => $path, '!detail' => $http->error, )), 'error_code' => $http->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) { static $cache; if (!isset($cache)) $cache = array(); if (!$fb) $fb = $GLOBALS['_fb']; if (!$fb) return; $app_id = $fb->getAppId(); $cache_key = $app_id; if (!$fbu) { // Get the application token. if (!isset($cache[$cache_key])) { $path = "https://graph.facebook.com/oauth/access_token?client_id=" . $app_id . "&client_secret=" . $fb->getApiSecret() . "&type=client_cred"; $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; } } } 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()) { $params['query'] = $query; //$result = fb_call_method($fb, 'fql.query', $params); $params['method'] = 'fql.query'; $result = $fb->api($params); return $result; } /** * 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; } /** * Implements hook_footer(). */ function fb_footer($is_front) { global $_fb, $_fb_app; // This element recommended by facebook. http://developers.facebook.com/docs/reference/javascript/ $output = "
\n"; $settings = fb_js_settings(); $output .= "\n"; return $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() { global $_fb; 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; $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; $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.")); } unset($_SESSION['fb'][$fb->getAppId()]); // 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() { session_destroy(); // Fix for http://bugs.php.net/bug.php?id=32330 session_set_save_handler('sess_open', 'sess_close', 'sess_read', 'sess_write', 'sess_destroy_sid', 'sess_gc'); $GLOBALS['user'] = drupal_anonymous_user(); // Unsetting the javasript fbu can be helpful when third-party cookies disabled. //fb_js_settings('fbu', 0); @TODO still needed?? helpful??? } /** * 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(fb_scrub_urls($_REQUEST['q']), array('absolute' => TRUE, 'fb_canvas' => fb_is_canvas())); // Which permissions to prompt for? $perms = array(); drupal_alter('fb_required_perms', $perms); $scope = implode(',', $perms); $url = "https://graph.facebook.com/oauth/authorize?client_id=$client_id&scope=$scope&redirect_uri=$redirect_uri"; drupal_goto($url); } 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) && ($uid->uid) && isset($uid->fbu) && $uid->fbu) return $uid->fbu; elseif (is_object($uid)) $uid = $uid->uid; 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) { static $cache; 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 ($pos = strpos($object->name, '@facebook')) { $fbu = substr($object->name, 0, $pos); } elseif ($object->uid > 0) { // 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 (!isset($fb_app->fb_app_data)) { $fb_app->fb_app_data = isset($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_properties($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->application_name)) { return $fb_app->application_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) { static $cache = array(); 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) { static $cache = array(); $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 Applications', '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 * * Seems to get called a lot(!) so we cache. */ function fb_load($id) { static $cache; 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 hook_perm(). */ function fb_perm() { return array(FB_PERM_ADMINISTER); } /** * 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 b_canvas.module. */ function fb_exit($destination = NULL) { global $_fb_app, $_fb; if ($_fb_app && $_fb) { // Invoke other modules. fb_invoke(FB_OP_EXIT, array('fb_app' => $_fb_app, 'fb' => $GLOBALS['_fb']), $destination); } } /** * Invoke hook_fb. */ 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(); $message .= ' ' . $e->getCode(); if ($fb) { $message .= '. (' . t('logged into facebook as %fbu', array('%fbu' => $fb->getUser())) . ')'; } if (fb_verbose()) { $message .= '
' . $e . '
'; } watchdog('fb', $message, array(), WATCHDOG_ERROR); if (user_access(FB_PERM_ADMINISTER)) { drupal_set_message($message, 'error'); } } /** * Exception handler for PHP5 exceptions. */ function fb_handle_exception($exception) { $message = t('Facebook API exception %message. !trace', array( '%message' => $exception->getMessage(), '!trace' => '
'. $exception->getTraceAsString() .'
', )); watchdog('fb', $message, array(), WATCHDOG_ERROR); //drupal_set_message($message, 'error'); print $message; print "
\$_REQUEST:\n";
  print_r($_REQUEST);
  print "\n\nREQUEST_URI:\n" . request_uri();
  print "
"; } /** * 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', 'name', 'is_app_user', 'pic', 'pic_big', 'pic_square', 'profile_update_time', 'proxied_email', 'status', '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); } /** * This function will be replaced, hopefully, by format_username in D7. * * @see http://drupal.org/node/192056 */ function fb_format_username($account) { $name = !empty($account->name) ? $account->name : variable_get('anonymous', t('Anonymous')); drupal_alter('username', $name, $account); return $name; } /** * hook_username_alter(). * * Return a user's facebook name, instead of local username. */ function fb_username_alter(&$name, $account) { if ($fbu = fb_get_fbu($account)) { // @TODO - is fb_get_fbu() a performance hit here? $info = fb_users_getInfo(array($fbu)); if (is_array($info) && isset($info[0])) { if ($info[0]['name']) { $name = $info[0]['name']; } } } } /** * 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' => '', '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']; } else { unset($_SESSION['_fb_' . $_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, // POSTed via ajax. ); $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' => '
' . print_r(debug_backtrace(), 1) . '
', ), WATCHDOG_ERROR); } } drupal_json(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 instructing browser to cache this page. // Do these work? drupal_set_header("Cache-Control: public"); drupal_set_header("Expires: Sun, 17-Jan-2038 19:14:07 GMT"); $date = format_date(time()); $output = "\n"; $url = fb_js_settings('js_sdk_url'); $output .= "\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 log into facebook to view this page.')); } drupal_access_denied(); exit(); }