menu item needs fb.admin.inc.
[project/fb.git] / fb.module
CommitLineData
186f033e
DC
1<?php
2
225abd5d
DC
3/**
4 * @file
5 * This is the core required module of Drupal for Facebook.
6 *
7 * @see http://drupal.org/project/fb
19092d2f 8 *
225abd5d
DC
9 */
10
8fbcf653 11// hook_fb
549aa675
DC
12define('FB_HOOK', 'fb');
13
14// Ops for hook_fb.
19a42c4b 15define('FB_OP_GET_APP', 'get_app'); // Load data from a known app
b1c7ed93 16define('FB_OP_GET_ALL_APPS', 'get_all_apps'); // Load data about all apps
19a42c4b
DC
17
18define('FB_OP_CURRENT_APP', 'current_app'); // determine active app in canvas page or facebook connect
19092d2f
DC
19define('FB_OP_INITIALIZE', 'init'); //
20define('FB_OP_POST_INIT', 'post init'); //
19a42c4b
DC
21
22define('FB_OP_EXIT', 'exit'); // End an FB callback
3a512626 23define('FB_OP_GET_FBU', 'get_fbu'); // Query the local user's FB account
346e5a9b 24define('FB_OP_GET_UID', 'get_uid'); // Query the facebook user's local account
2b08ca9d 25define('FB_OP_GET_USER_SESSION', 'get_user_sess');
8fbcf653 26
cd11871d 27define('FB_OP_APP_IS_AUTHORIZED', 'app_authorized'); // Invoked if user has authorized an app. Triggers creation of user accounts or fb_user entries
186f033e 28
ebb5649d 29define('FB_OP_JS', 'fb_op_js'); // A chance to inject javascript onto the page.
a24a1868 30define('FB_OP_AJAX_EVENT', 'fb_op_ajax'); // Notification of an event via ajax.
ebb5649d 31
b24ac6ad
DC
32
33// Paths.
34define('FB_PATH_ADMIN', 'admin/build/fb');
35define('FB_PATH_ADMIN_ARGS', 3); // how many args in path.
36define('FB_PATH_ADMIN_APPS', 'admin/build/fb/app');
37define('FB_PATH_ADMIN_APPS_ARGS', 4);
38define('FB_PATH_AJAX_EVENT', 'fb/ajax');
39define('FB_PATH_AJAX_EVENT_ARGS', 2);
40
41// permissions
42define('FB_PERM_ADMINISTER', 'administer fb apps');
43
44// Variables and $conf[] keys.
45define('FB_VAR_LANGUAGE_OVERRIDE', 'fb_language_override');
46define('FB_VAR_JS_SDK', 'fb_js_sdk');
47define('FB_VAR_API_FILE', 'fb_api_file');
48define('FB_VAR_JS_CHANNEL', 'fb_js_channel');
49define('FB_VAR_VERBOSE', 'fb_verbose');
995936db
DC
50define('FB_VAR_APIKEY', 'fb_apikey'); // Deprecated. Use FB_VAR_ID
51define('FB_VAR_ID', 'fb_id');
875843a4
DC
52define('FB_VAR_USE_COOKIE', 'fb_use_cookie');
53define('FB_VAR_USE_SESSION', 'fb_use_session');
733004f5 54define('FB_VAR_RELOAD_APPEND_HASH', 'fb_reload_append_hash');
49c878eb 55define('FB_VAR_CURL_NOVERIFY', 'fb_curl_noverify');
b24ac6ad 56
549aa675 57// node_access realms (belongs here?)
186f033e
DC
58define('FB_GRANT_REALM_FRIEND', 'fb_friend');
59define('FB_GRANT_REALM_GROUP', 'fb_group');
60
245f3cf6
DC
61// 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!
62
92a6e858 63// @TODO - new libs, are these FBU values still needed???
245f3cf6 64define('FB_FBU_CURRENT', 'fbu_current'); // Canvas pages and Connect pages
0f4300e5 65define('FB_FBU_ANY', 'fbu_any'); // Use current user on canvas page, fall back to infinite session otherwise.
4c0bff53 66
549aa675 67//// Constants for internal use
4c0bff53
DC
68define('FB_APP_CURRENT', '000_app_current'); // Canvas pages only. 000 makes it appear first in options list
69
92a6e858 70
549aa675 71/**
92a6e858
DC
72 * Controls are one way to customize the behavior of Drupal for Facebook modules.
73 *
74 * Controls are stored as an array of flags. Each flag overrides a
75 * configurable or built-in behavior. Third-party modules can use this to
76 * provide exceptions to otherwise useful behavior. For example see
77 * fb_user.module, where this is used to suppress some behavior in off-line
78 * mode.
79 *
80 * Controls take effect not just for the current page request, but also for
81 * ajax callbacks generated by the subsequent page.
82 *
83 * Because ajax controls could be spoofed by a malicious client, flags should
84 * not enable any "risky" features. For example, fb_user.module provides a
85 * control to suppress the creation of account, but not a control to enable
86 * new accounts, as that would be a security risk.
87 *
88 */
89function fb_controls($control = NULL, $value = NULL) {
90 static $controls;
91 if (!isset($controls)) {
92 // Initialize.
93 if (isset($_REQUEST['fb_controls'])) {
94 // Comma separated list passed to ajax calls.
cd64a628 95 foreach (explode(',', $_REQUEST['fb_controls']) as $key) {
92a6e858
DC
96 $controls[$key] = TRUE;
97 }
98 }
99 else {
100 $controls = array();
101 }
102 // @TODO - would a drupal_alter() be useful here?
103 }
104 if (isset($control)) {
105 if ($value === FALSE) {
106 unset($controls[$control]);
107 return;
108 }
109 elseif ($value === TRUE)
110 $controls[$control] = TRUE;
19092d2f 111
e44a9ee0 112 return isset($controls[$control]) ? $controls[$control] : FALSE; // Return requested control.
92a6e858
DC
113 }
114 return array_keys($controls); // Return all controls.
115}
116
117/**
118 * Implements hook_init().
225abd5d
DC
119 *
120 * Initializes facebook's javascript.
549aa675 121 * Determines whether we are servicing a Facebook App request.
19092d2f 122 *
225abd5d
DC
123 * We invoke our hook, first to determine which application is being invoked
124 * (because we support more than one in the same Drupal instance). We invoke
125 * the hook again to let interested modules know the sdk is initialized.
19092d2f 126 *
549aa675
DC
127 */
128function fb_init() {
a46642fe
DC
129 // Globals provided for internal use and convenience to third-parties.
130 global $_fb;
131 global $_fb_app;
19092d2f 132
875843a4 133 // fb_settings.inc may have been included in settings.php. If not, include it now.
f54b3a2b 134 if (!function_exists('fb_settings')) {
af716bdd 135 module_load_include('inc', 'fb', 'fb_settings');
225abd5d
DC
136 // trigger test in fb_devel.module
137 $GLOBALS['fb_init_no_settings'] = TRUE;
549aa675 138 }
5d492838 139
f671097b 140 // Javascript settings needed by fb.js.
8c514893
DC
141 fb_js_settings('ajax_event_url', url(FB_PATH_AJAX_EVENT, array(
142 'absolute' => TRUE,
143 )));
97ed5deb 144
875843a4
DC
145 // Data structure to pass to FB.init();
146 $fb_init_settings = array(
147 'xfbml' => FALSE,
148 'status' => FALSE,
149 'cookie' => variable_get(FB_VAR_USE_COOKIE, TRUE),
150 );
19092d2f 151
214f9ccf 152 // Figure out which app the current request is for.
a46642fe 153 $_fb_app = fb_invoke(FB_OP_CURRENT_APP);
67523320 154
a46642fe 155 if ($_fb_app) {
5d492838 156 // An App is configured.
214f9ccf 157
f671097b 158 // Javascript settings needed by fb.js.
75be9c97 159 //fb_js_settings('apikey', $_fb_app->apikey); // deprecated XXX
f671097b
DC
160 fb_js_settings('label', $_fb_app->label);
161 fb_js_settings('page_type', fb_settings(FB_SETTINGS_TYPE)); // canvas or connect.
75be9c97 162 //$fb_init_settings['apiKey'] = $_fb_app->apikey;
ef4e7efd 163 $fb_init_settings['appId'] = $_fb_app->id;
19092d2f 164
5d492838
DC
165 // Initialize the PHP API.
166 $_fb = fb_api_init($_fb_app);
19092d2f 167
a46642fe 168 if ($_fb) {
875843a4 169
e357a4a5 170 // Look for session info from several sources.
875843a4 171 if ($session = $_fb->getSession()) {
e357a4a5 172 // Learned session from cookie or signed request.
875843a4
DC
173 // Below, we store in our $_SESSION, just in case third-party cookies are not enabled.
174 }
175 elseif (isset($_REQUEST['fb_js_session'])) {
176 // Ajax callback via fb.js.
177 $_fb->setSession(json_decode($_REQUEST['fb_js_session'], TRUE));
178 $session = $_fb->getSession();
179 }
75be9c97 180 elseif (isset($_SESSION['fb'][$_fb_app->id]['session']) && arg(0) != 'logout') {
875843a4 181 // Use the session previously stored.
75be9c97 182 $_fb->setSession($_SESSION['fb'][$_fb_app->id]['session']);
875843a4
DC
183 }
184
185 // Store session for future use. We'll need it if third-party cookies
186 // disabled, or we are not using facebook's cookie.
187 if (isset($session) && variable_get(FB_VAR_USE_SESSION, TRUE)) {
75be9c97 188 $_SESSION['fb'][$_fb_app->id]['session'] = $session;
875843a4
DC
189 }
190
e357a4a5 191 // Make javascript work even when third-party cookies disabled.
875843a4
DC
192 $fb_init_settings['session'] = $_fb->getSession();
193
e357a4a5
DC
194 // Sometimes when canvas page is open in one tab, and user logs out of
195 // facebook in another, the canvas page has bogus session info when
196 // refreshed. Here we attempt to detect and cleanup.
197 if (isset($session) && ($request = $_fb->getSignedRequest())) {
198 if (!isset($request['user_id']) ||
199 $request['user_id'] != $session['uid']) {
200 _fb_logout();
201 $_fb->setSession(NULL);
202 unset($session);
75be9c97 203 unset($_SESSION['fb'][$_fb_app->id]);
e357a4a5
DC
204 unset($fb_init_settings['session']);
205 }
206 }
207
225abd5d 208 // Give other modules a chance to initialize.
123ed3d0 209 fb_invoke(FB_OP_INITIALIZE, array(
5d492838 210 'fb_app' => $_fb_app,
024cd4af
DC
211 'fb' => $_fb,
212 ));
19092d2f 213
8fbcf653 214 // See if the facebook user id is known
3946309e 215 if ($fbs = $_fb->getSession()) {
123ed3d0 216 fb_invoke(FB_OP_APP_IS_AUTHORIZED, array(
3946309e
DC
217 'fb_app' => $_fb_app,
218 'fb' => $_fb,
219 'fbu' => $_fb->getUser(),
220 ));
f671097b 221 fb_js_settings('fbu', $_fb->getUser());
8fbcf653 222 }
853c3f1b
DC
223 else {
224 // Add perms to settings, for calling FB.login().
225 $perms = array();
226 drupal_alter('fb_required_perms', $perms);
f671097b 227 fb_js_settings('perms', implode(',', $perms));
853c3f1b 228 }
549aa675
DC
229 }
230 else
a9167ce4 231 watchdog('fb', "URL indicates a facebook app, but could not initialize Facebook", array(), WATCHDOG_ERROR);
a24a1868 232
6927b5ad
DC
233 if (isset($_REQUEST['destination'])) {
234 $destination = $_REQUEST['destination'];
235 }
236 elseif (isset($_REQUEST['q'])) {
237 $destination = $_REQUEST['q'];
8f0e1ce9
DC
238 }
239 else {
6927b5ad 240 $destination = '<front>';
8f0e1ce9 241 }
6927b5ad
DC
242 if (fb_is_canvas()) {
243 $destination = fb_scrub_urls($destination); // Needed?
244 }
245
5500234f
DC
246 //Stripping the fragment out to be tacked on during the javascript redirect
247 if (strpos($destination, '#') !== FALSE) {
248 list($destination, $fragment) = explode('#', $destination, 2);
249 fb_js_settings('reload_url_fragment', $fragment);
250 }
251
ef4e7efd
DC
252 fb_js_settings('reload_url', url($destination, array(
253 'absolute' => TRUE,
254 'fb_canvas' => fb_is_canvas(),
255 'language' => (object) array('prefix' => NULL, 'language' => NULL), // http://drupal.org/node/1000452
256 )));
733004f5 257 fb_js_settings('reload_url_append_hash', variable_get(FB_VAR_RELOAD_APPEND_HASH, FALSE));
024cd4af 258 }
af9b07be 259
b24ac6ad 260 if ($channel = variable_get(FB_VAR_JS_CHANNEL, TRUE)) {
0de240eb
DC
261 if (!is_string($channel)) {
262 $channel = url('fb/channel', array('absolute' => TRUE, 'fb_url_alter' => FALSE));
263 }
875843a4 264 $fb_init_settings['channelUrl'] = $channel;
0de240eb
DC
265 }
266
267 fb_js_settings('fb_init_settings', $fb_init_settings);
268
269
0c0eb937
DC
270 // Allow third-parties to act, even if we did not initialize $_fb.
271 fb_invoke(FB_OP_POST_INIT, array('fb_app' => $_fb_app,
272 'fb' => $_fb));
19092d2f 273
f671097b
DC
274 fb_js_settings('controls', implode(',', fb_controls()));
275
276 if (!fb_js_settings('js_sdk_url')) {
af9b07be 277 if (isset($_SESSION['fb_locale']) &&
b24ac6ad 278 variable_get(FB_VAR_LANGUAGE_OVERRIDE, 'override')) {
875843a4 279 // @TODO - get locale from signed request. It appears to contain it now.
f671097b
DC
280 $fb_lang = $_SESSION['fb_locale'];
281 }
282 else {
283 $user_language = user_preferred_language($GLOBALS['user']);
284 $fb_lang = variable_get('fb_language_' . $user_language->language, 'en_US');
285 }
af9b07be 286
562c4b37
DC
287 $js_sdk = fb_protocol() . "://connect.facebook.net/$fb_lang/all.js";
288 fb_js_settings('js_sdk_url', variable_get(FB_VAR_JS_SDK, $js_sdk));
f671097b 289 }
19092d2f 290
97ed5deb 291 // Add our module's javascript.
a24a1868 292 drupal_add_js(drupal_get_path('module', 'fb') . '/fb.js');
97ed5deb
DC
293
294 // See also fb_footer(), where we initialize facebook's SDK.
549aa675
DC
295}
296
f671097b
DC
297/**
298 *
97ed5deb
DC
299 * Adds the javascript setting with the supplied key/value. This function
300 * merely keeps track of the settings and writes them as late as possible.
301 * Currently, in the fb_footer() function. There has been a lot of
302 * experimentation as to the best place to initialize the facebook javascript
303 * SDK. The footer appears to be the best place because we may not know all
304 * settings until well after hook_init().
f671097b
DC
305 *
306 * @param $key
307 * The javascript setting name. If the key is null then nothing is modified and the settings are returned.
308 * @param $value
309 * The value of the javascript setting. If the key is not null by the value is the setting is removed
310 * @return
311 * The associative array containing the current fb javascript settings
312 */
f404514e 313function fb_js_settings($key = NULL, $value = NULL) {
f671097b
DC
314 static $fb_js_settings = array();
315
316 if (isset($key) && isset($value)) {
317 $fb_js_settings[$key] = $value;
af9b07be 318 return $value;
f671097b
DC
319 }
320 elseif (isset($key)) {
321 return isset($fb_js_settings[$key]) ? $fb_js_settings[$key] : NULL;
322 }
323 else {
324 return $fb_js_settings;
325 }
326}
549aa675
DC
327
328
186f033e 329/**
ebb5649d 330 * Include and initialize Facebook's PHP SDK.
186f033e 331 */
3946309e 332function fb_api_init($fb_app) {
beed4c38 333 static $cache = array();
b1b8915b
DC
334 // This helps with uncaught exceptions. However, it should be configurable
335 // or at least not overwrite previously declared handler.
28922d16 336 set_exception_handler('fb_handle_exception');
19092d2f 337
75be9c97
DC
338 if (isset($cache[$fb_app->id])) {
339 return $cache[$fb_app->id];
3946309e 340 }
19092d2f 341
c93f57b9
DC
342 // Find Facebook's PHP SDK. Use libraries API if enabled.
343 $fb_lib_path = function_exists('libraries_get_path') ? libraries_get_path('facebook-php-sdk') : 'sites/all/libraries/facebook-php-sdk';
344 $fb_platform = variable_get(FB_VAR_API_FILE, $fb_lib_path . '/src/facebook.php');
345
3946309e 346 try {
c93f57b9 347 if (!class_exists('Facebook') && !include($fb_platform)) {
d3c72c12
DC
348 $message = t('Failed to find the Facebook client libraries at %filename. Read the !readme and follow the instructions carefully.', array(
349 '!drupal_for_facebook' => l(t('Drupal for Facebook'), 'http://drupal.org/project/fb'),
350 // This link should work with clean URLs disabled.
351 '!readme' => '<a href='. base_path() . '/' . drupal_get_path('module', 'fb') . '/README.txt>README.txt</a>',
352 '%filename' => $filename,
353 ));
354 drupal_set_message($message, 'error');
355 watchdog('fb', $message);
356 return NULL;
357 }
358
3946309e
DC
359 // We don't have a cached resource for this app, so we're going to create one.
360 $fb = new Facebook(array(
91e9397d 361 'appId' => $fb_app->id,
ff2501ec 362 'secret' => isset($fb_app->secret) ? $fb_app->secret : NULL,
f404514e 363 'cookie' => variable_get(FB_VAR_USE_COOKIE, TRUE),
3946309e 364 ));
dca2f231 365
49c878eb
DC
366 // Some servers need these settings.
367 if (variable_get(FB_VAR_CURL_NOVERIFY, TRUE)) {
368 Facebook::$CURL_OPTS[CURLOPT_SSL_VERIFYPEER] = FALSE;
369 Facebook::$CURL_OPTS[CURLOPT_SSL_VERIFYHOST] = FALSE;
370 //Facebook::$CURL_OPTS[CURLOPT_VERBOSE] = 1; // debug
371 }
19092d2f 372
3946309e 373 // Cache the result, in case we're called again.
75be9c97 374 $cache[$fb_app->id] = $fb;
19092d2f 375
3946309e 376 return $fb;
beed4c38 377 }
3946309e
DC
378 catch (Exception $e) {
379 fb_log_exception($e, t('Failed to construct Facebook client API.'));
186f033e 380 }
186f033e
DC
381}
382
beed4c38
DC
383/**
384 * Wrapper function for fb_api_init. This helps for functions that should
385 * work whether or not we are on a canvas page. For canvas pages, the active
386 * fb object is used. For non-canvas pages, it will initialize the API using
387 * an infinite session, if configured.
19092d2f 388 *
beed4c38 389 * @param $fb_app Note this is ignored on canvas pages.
19092d2f 390 *
beed4c38
DC
391 * This is for internal use. Third party modules use fb_api_init().
392 */
393function _fb_api_init($fb_app = NULL) {
a46642fe 394 $fb = $GLOBALS['_fb']; // Default to active app on canvas pages
beed4c38
DC
395 if (!$fb && $fb_app)
396 // Otherwise, log into facebook api.
397 $fb = fb_api_init($fb_app, FB_FBU_ANY);
19092d2f 398
beed4c38 399 if (!$fb) {
a9167ce4
DC
400 watchdog('fb', '%function unable to initialize Facebook API.',
401 array('%function' => '_fb_api_init()'), WATCHDOG_ERROR);
beed4c38
DC
402 return;
403 }
404 else
405 return $fb;
406}
407
3946309e 408
d3ef187d 409/**
dca2f231
DC
410 * Helper function to get the most commonly used values. In your custom
411 * module, call extract(fb_vars()); to set $fb_app, $fb, and $fbu.
412 */
413function fb_vars() {
414 return array(
415 'fb' => $GLOBALS['_fb'],
416 'fb_app' => $GLOBALS['_fb_app'],
417 'fbu' => fb_facebook_user(),
418 );
419}
420
421
422/**
449e690e
DC
423 * Helper to get the tokens needed to accss facebook's API.
424 *
81618599 425 * You would think that facebook's SDK would provide basic functions like this.
19092d2f 426 *
449e690e
DC
427 * @param $fb
428 * Get the token for this API instance. If NULL, use the global $_fb.
429 *
430 * @param $fbu
431 * Get the user-specific token. If NULL, get the application token.
d3ef187d 432 */
449e690e
DC
433function fb_get_token($fb = NULL, $fbu = NULL) {
434 static $cache;
435 if (!isset($cache))
436 $cache = array();
437
438 if (!$fb)
439 $fb = $GLOBALS['_fb'];
81618599
DC
440 if (!$fb)
441 return;
449e690e 442
920d803c
DC
443 $app_id = $fb->getAppId();
444 $cache_key = $app_id;
449e690e
DC
445
446 if (!$fbu) {
447 // Get the application token.
448 if (!isset($cache[$cache_key])) {
920d803c 449 $path = "https://graph.facebook.com/oauth/access_token?client_id=" . $app_id . "&client_secret=" . $fb->getApiSecret() . "&type=client_cred";
449e690e 450 $http = drupal_http_request($path);
dca2f231 451 if ($http->code == 200 && isset($http->data)) {
449e690e
DC
452 $data = explode('=', $http->data);
453 $token = $data[1];
454 if ($token)
455 $cache[$cache_key] = $token;
456 }
457 }
458 }
459 else {
460 $cache_key .= '_' . $fbu;
461 // Get the user access token.
462 if ($fbu == 'me' || $fbu == fb_facebook_user($fb)) {
463 $session = $fb->getSession();
464 $cache[$cache_key] = $session['access_token'];
465 }
466 else {
024ebf40
DC
467 $session_data = fb_invoke(FB_OP_GET_USER_SESSION, array(
468 'fb' => $fb,
920d803c 469 'fb_app' => fb_get_app(array('id' => $app_id)),
024ebf40
DC
470 'fbu' => $fbu,
471 ), array());
472 if (count($session_data)) {
473 $cache[$cache_key] = $session_data['access_token'];
474 }
449e690e
DC
475 }
476 }
dca2f231 477 return isset($cache[$cache_key]) ? $cache[$cache_key] : NULL;
449e690e
DC
478}
479
0ab1fd2c 480/**
8533770d
DC
481 * This helper original written because facebook's $fb->api() function was
482 * very buggy. I'm not sure this is still needed. On the other hand, a
483 * future version of modules/fb might use this instead of faceobok's PHP SDK,
484 * eliminating the need for it entirely.
0ab1fd2c 485 */
d3ef187d 486function fb_call_method($fb, $method, $params = array()) {
e9df1cf1
DC
487 if (!isset($params['access_token'])) {
488 $params['access_token'] = fb_get_token($fb);
489 }
d3ef187d 490 $params['format'] = 'json-strings';
11665fdd
DC
491
492 // Here's how to create a url that conforms to standards:
3946309e
DC
493 $url = url("https://api.facebook.com/method/{$method}", array(
494 'query' => $params,
495 ));
11665fdd
DC
496 // 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.
497 // http://forum.developers.facebook.net/viewtopic.php?id=76228
498 // $url = rawurldecode($url);
19092d2f 499
3946309e 500 $http = drupal_http_request($url);
d3ef187d 501
dca2f231 502 if (!isset($http->error) && isset($http->data)) {
d3ef187d
DC
503 $data = json_decode($http->data, TRUE);
504 // Yes, it's double encoded. At least sometimes.
505 if (is_string($data)) {
506 $data = json_decode($data, TRUE);
507 }
508 if (is_array($data)) {
509 if (isset($data['error_code'])) {
dcb2712f 510 throw new FacebookApiException($data);
d3ef187d
DC
511 }
512 }
02376a09
DC
513 elseif ($http->data == 'true' || $http->code == 200) {
514 // No problems.
515 }
d3ef187d
DC
516 else {
517 // Never reach this???
5d87b01a 518 if (function_exists('dpm')) dpm($http, __FUNCTION__ . " unexpected result from $url"); // XXX
d3ef187d
DC
519 }
520 return $data;
3946309e 521 }
51ecb79f
DC
522 else {
523 // Should we throw FacebookApiException, or plain old exception?
524 throw new FacebookApiException(
525 array(
526 'error_msg' => t('fb_call_method failed calling !method. !detail', array(
527 '!method' => $method,
528 '!detail' => $http->error,
529 )),
530 'error_code' => $http->code,
531 ));
532 }
3946309e
DC
533}
534
ebb5649d
DC
535/**
536 * Helper function for fql queries.
537 *
538 * Use $params to pass a session_key, when needed.
539 */
d3ef187d 540function fb_fql_query($fb, $query, $params = array()) {
ebb5649d 541 $params['query'] = $query;
d3ef187d
DC
542 //$result = fb_call_method($fb, 'fql.query', $params);
543 $params['method'] = 'fql.query';
544 $result = $fb->api($params);
dcb2712f 545
ebb5649d
DC
546 return $result;
547}
3946309e 548
de5d0e5a
DC
549/**
550 * Helper function for facebook's batch graph api.
551 *
6b8f8dcc
DC
552 * This function accepts a simpler interface than facebook's. The queries are
553 * passed in as a simple array, and the data is parsed into a PHP data
554 * structure.
de5d0e5a 555 *
6b8f8dcc 556 * @TODO: when $method=='GET', share caching with fb_api().
de5d0e5a 557 */
6b8f8dcc 558function fb_api_batch($fb, $queries, $params, $method = 'GET') {
de5d0e5a
DC
559 $data = array();
560
6b8f8dcc
DC
561 // Build facebook's data structure. Our's supports only GET or POST at a time.
562 $fb_queries = array();
563 foreach ($queries as $query) {
564 $fb_queries[] = array('method' => $method, 'relative_url' => $query);
565 }
566
567 $wrapped_data = $fb->api('/?batch=' . json_encode($fb_queries), 'POST', $params); // Use POST, not $method.
de5d0e5a
DC
568
569 foreach ($wrapped_data as $w_d) {
570 if ($w_d['code'] == 200 && isset($w_d['body'])) {
571 $data[] = json_decode($w_d['body'], TRUE);
572 }
573 else {
574 // Unexpected code
575 $data[] = $w_d;
576 }
577 }
578
579 return $data;
580}
581
3946309e 582
5d492838
DC
583/**
584 * Implements hook_footer().
585 */
3946309e
DC
586function fb_footer($is_front) {
587 global $_fb, $_fb_app;
dca2f231
DC
588
589 // This element recommended by facebook. http://developers.facebook.com/docs/reference/javascript/
5d492838 590 $output = "<div id=\"fb-root\"></div>\n";
ebb5649d 591
97ed5deb
DC
592 $settings = fb_js_settings();
593
594 $output .= "<script type=\"text/javascript\">\n";
595 $output .= "<!--//--><![CDATA[//><!--\n";
596 $output .= "jQuery.extend(Drupal.settings, " . json_encode(array('fb' => fb_js_settings())) . ");\n";
ebb5649d
DC
597 $js_array = fb_invoke(FB_OP_JS, array('fb' => $GLOBALS['_fb'], 'fb_app' => $GLOBALS['_fb_app']), array());
598 if (count($js_array)) {
ebb5649d
DC
599 // The function we define in the footer will be called after FB is initialized.
600 $output .= "FB_JS.initHandler = function() {\n";
ff2501ec 601 //$output .= "debugger;\n";
ebb5649d
DC
602 $output .= implode("\n", $js_array);
603 $output .= "};\n";
604 $output .= "jQuery(document).bind('fb_init', FB_JS.initHandler);\n";
ebb5649d 605 }
dca2f231
DC
606
607 // Load the JS SDK asynchronously.
608 // http://developers.facebook.com/docs/reference/javascript/
609 $output .= "var e = document.createElement('script');\n";
610 $output .= "e.async = true;\n";
611 $output .= "e.src = Drupal.settings.fb.js_sdk_url;\n";
612 $output .= "document.getElementById('fb-root').appendChild(e);\n";
613
97ed5deb
DC
614 $output .= "\n//--><!]]>\n";
615 $output .= "\n</script>\n";
3946309e
DC
616 return $output;
617}
618
5d492838
DC
619/**
620 * Is the current request a canvas page?
621 */
3946309e 622function fb_is_canvas() {
5441e7bd
DC
623 if (fb_is_tab()) {
624 return FALSE;
625 }
626 elseif (fb_settings(FB_SETTINGS_CB)) {
c43fb19a
DC
627 // Using fb_url_rewrite.
628 return TRUE;
629 }
630 elseif (fb_settings(FB_SETTINGS_TYPE) == FB_SETTINGS_TYPE_CANVAS) {
631 // No rewrite, but fb_settings.inc has detected type.
632 return TRUE;
633 }
634 return FALSE;
3946309e
DC
635}
636
360cf127 637/**
5441e7bd
DC
638 * Is the current page a profile tab.
639 *
640 * Only works when "Canvas Session Parameter" is disabled.
641 */
642function fb_is_tab() {
643 global $_fb;
727e0e00
DC
644
645 if (fb_settings(FB_SETTINGS_TYPE) == FB_SETTINGS_TYPE_PAGE_TAB) {
646 return TRUE;
647 }
648 elseif (fb_settings(FB_SETTINGS_TYPE) == FB_SETTINGS_TYPE_PROFILE) { // deprecated FBML tab
5441e7bd
DC
649 return TRUE;
650 }
c12b0663 651 elseif (isset($_REQUEST['fb_sig_in_profile_tab']) &&
727e0e00 652 $_REQUEST['fb_sig_in_profile_tab']) { // deprecated ancient history
c12b0663 653 // Old way, no migrations enabled.
5441e7bd
DC
654 return TRUE;
655 }
656 return FALSE;
657}
658
659
660/**
360cf127
DC
661 * Sometimes calls to fb_api_init succeed, but calls to the client api
662 * will fail because cookies are obsolete or what have you. This
663 * function makes a call to facebook to test the session. Expensive,
664 * so use only when necessary.
3946309e 665 *
360cf127
DC
666 */
667function fb_api_check_session($fb) {
360cf127
DC
668 $success = FALSE;
669 try {
f671097b
DC
670 $me = $fb->api('me');
671
672 // Store the locale if set.
673 if (isset($me['locale'])) {
674 $_SESSION['fb_locale'] = $me['locale'];
853c3f1b 675 }
f671097b
DC
676
677 // Does not matter what is returned, as long as exception is not thrown.
678 $success = TRUE;
360cf127
DC
679 }
680 catch (Exception $e) {
7308ca43
DC
681 if (fb_verbose()) {
682 watchdog('fb', 'fb_api_check_session failed. Possible attempt to spoof a facebook session!');
91e9397d 683 //watchdog('fb', print_r($fb->getSession(), 1));
7308ca43 684 }
360cf127 685 $success = FALSE;
f5ac8b5b
DC
686 if (fb_verbose()) {
687 fb_log_exception($e, t("fb_api_check_session failed."));
688 }
af9b07be 689
875843a4 690 unset($_SESSION['fb'][$fb->getAppId()]);
af9b07be
DC
691 // Unsetting the javasript fbu can be helpful when third-party cookies disabled.
692 fb_js_settings('fbu', 0);
693
694 // Might as well try to clean up the mess.
695 if (isset($_COOKIE['fbs_' . $fb->getAppId()])) {
696 setcookie('fbs_' . $fb->getAppId(), '', time() - 42000, '/');
697 }
698
360cf127
DC
699 }
700 return $success;
701}
beed4c38 702
f671097b 703
186f033e 704/**
875843a4
DC
705 * Helper to ensure local user is logged out, or an anonymous session is refreshed.
706 */
707function _fb_logout() {
708 session_destroy();
709 // Fix for http://bugs.php.net/bug.php?id=32330
710 session_set_save_handler('sess_open', 'sess_close', 'sess_read', 'sess_write', 'sess_destroy_sid', 'sess_gc');
711 $GLOBALS['user'] = drupal_anonymous_user();
712
713 // Unsetting the javasript fbu can be helpful when third-party cookies disabled.
714 fb_js_settings('fbu', 0);
715
91e9397d
DC
716 // Clean up facebook cookies.
717 if (isset($GLOBALS['_fb_app'])) {
75be9c97 718 if (isset($_COOKIE['fbs_' . $GLOBALS['_fb_app']->apikey])) { // still needed?
91e9397d
DC
719 setcookie('fbs_' . $GLOBALS['_fb_app']->apikey, '', time() - 42000, '/');
720 }
721 if (isset($_COOKIE['fbs_' . $GLOBALS['_fb_app']->id])) {
722 setcookie('fbs_' . $GLOBALS['_fb_app']->id, '', time() - 42000, '/');
723 }
875843a4
DC
724 }
725}
726
727
728/**
225abd5d
DC
729 * Returns the facebook user id currently visiting a canvas page, or if
730 * set_user has been called. Unlike fb_get_fbu(), works only on canvas and
731 * connect pages, or when infinite session has been initialized.
186f033e 732 */
549aa675
DC
733function fb_facebook_user($fb = NULL) {
734 if (!isset($fb))
a46642fe 735 $fb = $GLOBALS['_fb'];
19092d2f 736
186f033e
DC
737 if (!$fb)
738 return;
19092d2f 739
3946309e
DC
740 try {
741 $fbu = $fb->getUser();
742 return $fbu;
5e212726 743 }
3946309e
DC
744 catch (FacebookApiException $e) {
745 fb_log_exception($e,
35163e17
DC
746 t('Failed to get Facebook user id. detail: !detail',
747 array('!detail' => print_r($e, 1))));
5e212726
DC
748 }
749}
67523320
DC
750
751/**
449e690e
DC
752 * Helper function to ensure user has authorized an application.
753 *
754 * Similar to the old require_login() provided by the old facebook API.
755 * Works by redirecting the user as described in http://developers.facebook.com/docs/authentication/.
756 *
cf551949 757 * @TODO handle users who skip.
449e690e
DC
758 */
759function fb_require_authorization($fb = NULL, $destination = NULL) {
760 if (!$fb)
761 $fb = $GLOBALS['_fb'];
762
727e0e00
DC
763 if (!$fb) {
764 throw new Exception(t('Failed to authorize facebook application. Could not determine application id.'));
765 }
766
449e690e
DC
767 $fbu = fb_facebook_user($fb);
768 if (!$fbu) {
769 $client_id = $fb->getAppId();
770 $redirect_uri = $destination ? $destination : url(fb_scrub_urls($_REQUEST['q']), array('absolute' => TRUE, 'fb_canvas' => fb_is_canvas()));
771
cf551949
DC
772 // Which permissions to prompt for?
773 $perms = array();
774 drupal_alter('fb_required_perms', $perms);
775 $scope = implode(',', $perms);
776
777 $url = "https://graph.facebook.com/oauth/authorize?client_id=$client_id&scope=$scope&redirect_uri=$redirect_uri";
449e690e
DC
778 drupal_goto($url);
779 }
780 else {
781 return $fbu;
782 }
783}
784
785/**
67523320
DC
786 * Helper tells other modules when to load admin hooks.
787 */
788function fb_is_fb_admin_page() {
7217ba45
DC
789 if (arg(0) == 'admin' && (arg(1) == 'fb' || arg(2) == 'fb')) {
790 // Keep consistant titles across tabs served by multiple modules.
791 if ($label = arg(FB_PATH_ADMIN_APPS_ARGS))
792 drupal_set_title($label);
793 else
794 drupal_set_title(t('Drupal for Facebook'));
795
796 return TRUE;
797 }
67523320
DC
798}
799
8e801805 800/**
346e5a9b
DC
801 * Given a facebook user id, learn the local uid, if any.
802 *
19092d2f 803 */
346e5a9b
DC
804function fb_get_uid($fbu, $fb_app = NULL) {
805 $uid = NULL;
806 if ($fbu) {
807 $uid = fb_invoke(FB_OP_GET_UID, array('fbu' => $fbu, 'fb_app' => $fb_app));
808 }
809 return $uid;
810}
811
812
813/**
186f033e 814 * Given a local user id, find the facebook id.
225abd5d
DC
815 *
816 * Invokes hook_fb(FB_OP_GET_FBU) in order to ask other modules what the fbu
817 * is. Typically, fb_user.module will answer the question.
186f033e 818 */
3a512626 819function fb_get_fbu($uid, $fb_app = NULL) {
186f033e 820 // Accept either a user object or uid passed in.
19092d2f 821 if (is_object($uid) && ($uid->uid) &&
2cdd1dcf 822 isset($uid->fbu) && $uid->fbu)
186f033e 823 return $uid->fbu;
a46642fe 824 elseif (is_object($uid))
186f033e 825 $uid = $uid->uid;
19092d2f 826
2cdd1dcf
DC
827 if ($uid) {
828 // User management is handled by another module. Use our hook to ask for mapping.
cd11871d 829 $fbu = fb_invoke(FB_OP_GET_FBU, array('uid' => $uid,
a46642fe 830 'fb' => $GLOBALS['_fb']));
2cdd1dcf 831 }
e44a9ee0
DC
832 else {
833 $fbu = NULL;
834 }
186f033e
DC
835 return $fbu;
836}
837
4c0bff53 838/**
9559f7ff
DC
839 * Convenience function to learn the fbu associated with a user, node or comment.
840 * Used in theming (X)FBML tags.
841 */
842function fb_get_object_fbu($object) {
843 static $cache;
844 if (!isset($cache))
845 $cache = array();
846
847 if (isset($object->uid) && isset($cache[$object->uid])) {
848 $fbu = $cache[$object->uid];
849 return $fbu;
850 }
a46642fe 851 elseif (isset($object->fbu)) {
9559f7ff
DC
852 // Explicitly set.
853 $fbu = $object->fbu;
854 }
cd11871d
DC
855 elseif (isset($object->init) &&
856 ($pos = strpos($object->init, '@facebook'))) {
857 // Naming convention used by fb_user when creating accounts.
858 // $object->init may be present when object is a user.
859 $fbu = substr($object->init, 0, $pos);
860 }
a46642fe 861 elseif ($pos = strpos($object->name, '@facebook')) {
9559f7ff
DC
862 $fbu = substr($object->name, 0, $pos);
863 }
a46642fe 864 elseif ($object->uid > 0) {
cd11871d
DC
865 // This can be expensive on pages with many comments or nodes!
866 $fbu = fb_get_fbu($object->uid);
9559f7ff 867 }
19092d2f 868
9559f7ff
DC
869 if (isset($fbu) && is_numeric($fbu)) {
870 if (isset($object->uid) && ($object->uid > 0)) {
871 $cache[$object->uid] = $fbu;
872 }
873 return $fbu;
874 }
875}
876
877
878/**
75be9c97 879 * Convenience method to get app info based on id or nid.
4c0bff53 880 */
2c870dd0
DC
881function fb_get_app($search_data) {
882 // $search_data can be an apikey, or an array of other search params.
883 if (!is_array($search_data))
75be9c97 884 $search_data = array('id' => $search_data);
19092d2f 885
19a42c4b 886 $fb_app = fb_invoke(FB_OP_GET_APP, $search_data);
4c0bff53
DC
887 return $fb_app;
888}
889
890/**
67523320 891 * Convenience method for other modules to attach data to the fb_app
19092d2f
DC
892 * object.
893 *
67523320
DC
894 * It is assumed the fb_app implementation will fill in the data
895 * field. We really should clean up the separation between modules,
896 * or merge fb_app.module into this one.
4c0bff53 897 */
67523320
DC
898function fb_get_app_data(&$fb_app) {
899 if (!isset($fb_app->fb_app_data)) {
900 $fb_app->fb_app_data = isset($fb_app->data) ? unserialize($fb_app->data) : array();
4c0bff53 901 }
67523320 902 return $fb_app->fb_app_data;
4c0bff53
DC
903}
904
29be740a
DC
905/**
906 * Will return a human-readable name if the fb_app module supports it, or
907 * fb_admin_get_app_properties($fb_app) has been called. However we don't
908 * take the relatively expensive step of calling that ourselves.
909 */
910function fb_get_app_title($fb_app) {
911 if (isset($fb_app->title))
912 return $fb_app->title;
913 elseif (isset($fb_app->application_name)) {
914 return $fb_app->application_name;
915 }
916 else {
917 return $fb_app->label;
918 }
919}
67523320 920
2c870dd0
DC
921/**
922 * Convenience method to return array of all know fb_apps.
923 */
924function fb_get_all_apps() {
8fbcf653 925 $apps = fb_invoke(FB_OP_GET_ALL_APPS, NULL, array());
2c870dd0
DC
926 return $apps;
927}
928
beed4c38 929/**
19092d2f 930 * A convenience method for returning a list of facebook friends.
c0af5627
DC
931 *
932 * This should work efficiently in canvas pages for finding friends of
3946309e
DC
933 * the current user.
934 *
19092d2f
DC
935 * @TODO - also support users who have permitted offline access.
936 *
beed4c38
DC
937 * @return: an array of facebook ids
938 */
939function fb_get_friends($fbu, $fb_app = NULL) {
186f033e 940 static $cache = array();
c0af5627 941 if (!$fb_app)
a46642fe 942 $fb_app = $GLOBALS['_fb_app'];
19092d2f 943
beed4c38
DC
944 // Facebook only allows us to query the current user's friends, so let's try
945 // to log in as that user. It will only actually work if they are the
946 // current user of a canvas page, or they've signed up for an infinite
947 // session.
948 $fb = fb_api_init($fb_app, $fbu);
186f033e
DC
949 if (!$fb || !$fbu)
950 return;
e8503dbe
DC
951
952 $items = array();
186f033e 953 if (!isset($cache[$fbu])) {
19092d2f 954 if ($fb === $GLOBALS['_fb'] &&
3946309e 955 $fbu == fb_facebook_user($fb)) {
e8503dbe
DC
956 try {
957 $items = fb_call_method($fb, 'friends.get', array(
958 'uid' => $fbu,
959 ));
960 }
961 catch (Exception $e) {
962 fb_log_exception($e, t('Failed call to friends.get'), $fb);
963 }
964
3946309e 965 }
853c3f1b 966 // friends_get does not work in cron call, so we double check. @TODO - still needed?
186f033e 967 if (!$items || !count($items)) {
beed4c38 968 $logged_in = fb_facebook_user($fb);
7a2b37de 969 $query = "SELECT uid2 FROM friend WHERE uid1=$fbu"; // FQL, no {curly_brackets}!
e8503dbe
DC
970 try {
971 $result = fb_call_method($fb, 'fql.query', array(
972 'query' => $query,
973 ));
974 //dpm($result, "FQL " . $query); // debug
975 }
976 catch (Exception $e) {
977 fb_log_exception($e, t('Failed call to fql.query: !query', array('!query' => $query)), $fb);
978 }
19092d2f
DC
979
980 if (is_array($result))
186f033e
DC
981 foreach ($result as $data) {
982 $items[] = $data['uid2'];
983 }
984 }
4566b785
DC
985 // Facebook's API has the annoying habit of returning an item even if user
986 // has no friends. We need to clean that up.
987 if (!$items[0])
988 unset($items[0]);
19092d2f 989
186f033e
DC
990 $cache[$fbu] = $items;
991 }
19092d2f 992
186f033e
DC
993 return $cache[$fbu];
994}
995
996// Return array of facebook gids
beed4c38 997function fb_get_groups($fbu, $fb_app = NULL) {
186f033e
DC
998 $items = array();
999 $groups = fb_get_groups_data($fbu);
1000
1001 if ($groups && count($groups))
1002 foreach ($groups as $data) {
1003 $items[] = $data['gid'];
1004 }
1005 return $items;
1006}
1007
beed4c38 1008function fb_get_groups_data($fbu, $fb_app = NULL) {
186f033e 1009 static $cache = array();
beed4c38
DC
1010
1011 $fb = _fb_api_init($fb_app);
186f033e
DC
1012 if (!$fb || !$fbu)
1013 return;
19092d2f 1014
186f033e 1015 if (!isset($cache[$fbu])) {
853c3f1b 1016 $cache[$fbu] = fb_call_method($fb, 'groups.get', array(
3946309e 1017 'uid' => $fbu,
3946309e 1018 ));
186f033e 1019 }
19092d2f 1020
186f033e
DC
1021 return $cache[$fbu];
1022}
1023
1024
f77127b5 1025//// Menu structure.
67523320 1026/**
0de240eb 1027 * Implements hook_menu().
67523320 1028 */
a9167ce4 1029function fb_menu() {
186f033e 1030 $items = array();
19092d2f 1031
f77127b5 1032 // Admin pages overview.
67523320
DC
1033 $items[FB_PATH_ADMIN] = array(
1034 'title' => 'Facebook Applications',
7a2b37de 1035 'description' => 'Facebook Applications',
9559f7ff
DC
1036 'page callback' => 'fb_admin_page',
1037 'access arguments' => array(FB_PERM_ADMINISTER),
1038 'file' => 'fb.admin.inc',
0c4a9f92 1039 'type' => MENU_NORMAL_ITEM,
9559f7ff 1040 );
f77127b5 1041 $items[FB_PATH_ADMIN . '/list'] = array(
875843a4 1042 'title' => 'List Apps',
67523320 1043 'weight' => -2,
9559f7ff
DC
1044 'type' => MENU_DEFAULT_LOCAL_TASK,
1045 );
f77127b5 1046
875843a4
DC
1047 $items[FB_PATH_ADMIN . '/settings'] = array(
1048 'title' => 'Settings',
f671097b 1049 'access arguments' => array(FB_PERM_ADMINISTER),
875843a4 1050 'weight' => -1,
f671097b
DC
1051 'type' => MENU_LOCAL_TASK,
1052 'page callback' => 'drupal_get_form',
875843a4 1053 'page arguments' => array('fb_admin_settings'),
f671097b
DC
1054 'file' => 'fb.admin.inc',
1055 );
d3c72c12 1056
875843a4 1057
7217ba45 1058 // Admin pages for each app.
f77127b5 1059 $items[FB_PATH_ADMIN_APPS . '/%fb'] = array(
7a2b37de
DC
1060 'title' => 'Application Detail',
1061 'description' => 'Facebook Applications',
f77127b5
DC
1062 'page callback' => 'fb_admin_app_page',
1063 'page arguments' => array(FB_PATH_ADMIN_APPS_ARGS),
1064 'access arguments' => array(FB_PERM_ADMINISTER),
1065 'file' => 'fb.admin.inc',
1066 'type' => MENU_CALLBACK,
1067 );
1068
1069 $items[FB_PATH_ADMIN_APPS .'/%fb/fb'] = array(
1070 'title' => 'View',
1071 'weight' => -2,
1072 'type' => MENU_DEFAULT_LOCAL_TASK,
1073 );
853c3f1b
DC
1074 $items[FB_PATH_ADMIN_APPS . '/%fb/fb/set_props'] = array(
1075 'title' => 'Set Properties',
1076 'description' => 'Set Facebook Application Properties',
1077 'page callback' => 'drupal_get_form',
1078 'page arguments' => array('fb_admin_set_properties_form', FB_PATH_ADMIN_APPS_ARGS),
1079 'access arguments' => array(FB_PERM_ADMINISTER),
cf551949 1080 'file' => 'fb.admin.inc',
853c3f1b
DC
1081 'type' => MENU_CALLBACK,
1082 );
1083
ebb5649d
DC
1084 // Javascript helper
1085 $items['fb/js'] = array(
1086 'page callback' => 'fb_js_cb',
1087 'type' => MENU_CALLBACK,
1088 'access callback' => TRUE,
1089 );
a24a1868
DC
1090
1091 // Ajax event handler.
1092 $items[FB_PATH_AJAX_EVENT . '/%'] = array(
1093 'page callback' => 'fb_ajax_event',
1094 'type' => MENU_CALLBACK,
1095 'access callback' => TRUE,
1096 'page arguments' => array(FB_PATH_AJAX_EVENT_ARGS),
1097 );
19092d2f 1098
0de240eb
DC
1099 // "Channel" http://developers.facebook.com/docs/reference/javascript/FB.init
1100 $items['fb/channel'] = array(
1101 'page callback' => 'fb_channel_page',
1102 'type' => MENU_CALLBACK,
1103 'access callback' => TRUE,
1104 );
1105
186f033e
DC
1106 return $items;
1107}
1108
1109/**
f77127b5 1110 * Implementation of a %wildcard_load(). http://drupal.org/node/224170
853c3f1b
DC
1111 *
1112 * Seems to get called a lot(!) so we cache.
f77127b5
DC
1113 */
1114function fb_load($id) {
853c3f1b
DC
1115 static $cache;
1116 if (!isset($cache))
1117 $cache = array();
1118 if (!isset($cache[$id])) {
1119 $query = array('label' => $id);
1120 if (fb_is_fb_admin_page()) {
1121 // Show disabled apps to admins.
1122 $query['status'] = 0; // status >= 0
1123 }
1124 $cache[$id] = fb_get_app($query);
f77127b5 1125 }
853c3f1b 1126 return $cache[$id];
f77127b5
DC
1127}
1128
1129/**
9559f7ff
DC
1130 * Implementation of hook_perm().
1131 */
1132function fb_perm() {
7217ba45 1133 return array(FB_PERM_ADMINISTER);
9559f7ff
DC
1134}
1135
1136
1137/**
f54b3a2b
DC
1138 * Implements hook_exit().
1139 *
1140 * When completing a canvas page we need special processing for the session. See fb_session.inc.
1141 *
1142 * Also invoke hook_fb(FB_OP_EXIT), so that other modules can handle special
1143 * cases (in particular form support in b_canvas.module.
186f033e
DC
1144 */
1145function fb_exit($destination = NULL) {
a46642fe 1146 global $_fb_app, $_fb;
19092d2f 1147
a46642fe 1148 if ($_fb_app && $_fb) {
19092d2f 1149
f54b3a2b 1150 // Invoke other modules.
a46642fe
DC
1151 fb_invoke(FB_OP_EXIT, array('fb_app' => $_fb_app,
1152 'fb' => $GLOBALS['_fb']),
19a42c4b
DC
1153 $destination);
1154 }
186f033e
DC
1155}
1156
186f033e 1157
b1b8915b
DC
1158/**
1159 * Invoke hook_fb.
1160 */
67523320
DC
1161function fb_invoke($op, $data = NULL, $return = NULL, $hook = FB_HOOK) {
1162 foreach (module_implements($hook) as $name) {
1163 $function = $name . '_' . $hook;
8fbcf653
DC
1164 try {
1165 $function($op, $data, $return);
1166 }
1167 catch (Exception $e) {
ab102d9a
DC
1168 if (isset($data['fb_app'])) {
1169 fb_log_exception($e, t('Exception calling %function(%op) (!app)', array(
1170 '%function' => $function,
1171 '%op' => $op,
1172 '%label' => $data['fb_app']->label,
75be9c97 1173 '%id' => $data['fb_app']->id,
ab102d9a
DC
1174 '!app' => l($data['fb_app']->label, FB_PATH_ADMIN_APPS . '/' . $data['fb_app']->label),
1175 )));
1176 }
1177 else {
1178 fb_log_exception($e, t('Exception calling %function(%op)', array(
1179 '%function' => $function,
1180 '%op' => $op)));
1181 }
8fbcf653 1182 }
186f033e 1183 }
3a512626 1184 return $return;
186f033e
DC
1185}
1186
1187/**
bed8b838 1188 * This method will clean up URLs. When serving canvas pages, extra
38bb647b
DC
1189 * information is included in URLs. This will remove the extra
1190 * information. Useful when linking back to the website from a canvas page or
1191 * wall post.
1192 *
1193 * For example in the following code, $url2 will link out to the server's domain:
1194 *
1195 * $url = url('node/42', array('absolute' => TRUE)); // i.e. http://apps.facebook.com/example/node/42
1196 * $url2 = fb_scrub_urls($url); // i.e. http://example.com/node/42
1197 *
19092d2f 1198 *
38bb647b 1199 * @see fb_url_rewrite.inc
bed8b838
DC
1200 */
1201function fb_scrub_urls($content) {
f54b3a2b
DC
1202 if (function_exists('_fb_settings_url_rewrite_prefixes')) {
1203 foreach (_fb_settings_url_rewrite_prefixes() as $key) {
1204 $patterns[] = "|$key/[^/]*/|";
1205 $replacements[] = "";
1206 }
1207 $content = preg_replace($patterns, $replacements, $content);
bed8b838 1208 }
bed8b838
DC
1209 return $content;
1210}
1211
225abd5d
DC
1212/**
1213 * Convenience function to log and report exceptions.
1214 */
48389ede 1215function fb_log_exception($e, $text = '', $fb = NULL) {
28922d16
DC
1216 if ($text)
1217 $message = $text .': '. $e->getMessage();
1218 else
1219 $message = $e->getMessage();
1220 $message .= ' ' . $e->getCode();
19092d2f 1221
48389ede 1222 if ($fb) {
5d492838 1223 $message .= '. (' . t('logged into facebook as %fbu', array('%fbu' => $fb->getUser())) . ')';
48389ede 1224 }
360cf127
DC
1225 if (fb_verbose()) {
1226 $message .= '<pre>' . $e . '</pre>';
1227 }
a9167ce4 1228 watchdog('fb', $message, array(), WATCHDOG_ERROR);
9559f7ff 1229 if (user_access(FB_PERM_ADMINISTER)) {
19092d2f 1230 drupal_set_message($message, 'error');
28922d16
DC
1231 }
1232}
1233
0f4300e5
DC
1234/**
1235 * Exception handler for PHP5 exceptions.
1236 */
28922d16 1237 function fb_handle_exception($exception) {
7a2b37de
DC
1238 $message = t('Facebook API exception %message. !trace', array(
1239 '%message' => $exception->getMessage(),
1240 '!trace' => '<pre>'. $exception->getTraceAsString() .'</pre>',
1241 ));
a9167ce4 1242 watchdog('fb', $message, array(), WATCHDOG_ERROR);
bed8b838 1243 //drupal_set_message($message, 'error');
0f4300e5 1244 print $message;
bed8b838
DC
1245
1246 print "<pre>\$_REQUEST:\n";
1247 print_r($_REQUEST);
7a2b37de 1248 print "\n\nREQUEST_URI:\n" . request_uri();
bed8b838
DC
1249 print "</pre>";
1250
0f4300e5
DC
1251}
1252
5d2a0a39 1253/**
49c878eb
DC
1254 * Simple wrapper around $fb->api() which caches data. Does not support the
1255 * polymorphic arguments of $fb->api(). This is not a replacement for that
1256 * function.
02376a09
DC
1257 *
1258 * This is intended to avoid performace problems when, for example,
1259 * $fb->api('me') is called several times in a single request.
49c878eb
DC
1260 *
1261 * @param $graph_path
1262 * Something facebook's graph API will understand. Could be an ID or a path like 'me/accounts', for example.
1263 *
1264 * @param $params
1265 * Extras to pass to the graph API. When making a request that requires a
1266 * token, try array('access_token' => fb_get_token()).
02376a09 1267 */
49c878eb 1268function fb_api($graph_path, $params = array()) {
02376a09
DC
1269 static $cache;
1270 $fb = $GLOBALS['_fb'];
1271 if (!$fb) {
1272 return;
1273 }
1274 if (!isset($cache)) {
1275 $cache = array();
1276 }
1277 if (!isset($cache[$graph_path])) {
49c878eb 1278 $cache[$graph_path] = $fb->api($graph_path, $params);
02376a09
DC
1279 }
1280 return $cache[$graph_path];
1281}
1282
1283/**
1284 * DEPRECATED. Use fb_api() instead.
dcb2712f 1285 * Returns information about one or more facebook users.
5d2a0a39 1286 *
dcb2712f
DC
1287 * Historically, this helper function used facebook's users_getInfo API, hence
1288 * the name. Now it uses fql.query, but accomplishes the same thing.
5d2a0a39 1289 *
5d2a0a39
DC
1290 * @param $oids
1291 * Array of facebook object IDs. In this case they should each be a user id.
dcb2712f
DC
1292 *
1293 * @param $fb
1294 * Rarely needed. For cases when global $_fb is not set, or more than one
1295 * facebook api has been initialized.
1296 *
1297 * @param $refresh_cache
1298 * If true, force a call to facebook instead of relying on temporarily stored
1299 * data.
5d2a0a39 1300 */
7e665271 1301function fb_users_getInfo($oids, $fb = NULL, $refresh_cache = FALSE) {
5d2a0a39 1302 if (!$fb) {
a46642fe 1303 $fb = $GLOBALS['_fb'];
5d2a0a39
DC
1304 }
1305 $infos = array();
dcb2712f 1306
74c6f7b6
DC
1307 if (!is_array($oids))
1308 $oids = array();
dcb2712f 1309
5d2a0a39 1310 if ($fb) {
920d803c 1311 $app_id = $fb->getAppId();
5d2a0a39 1312 // First try cache
8f0e1ce9 1313 if (!$refresh_cache && isset($_SESSION['fb'])) {
7e665271 1314 foreach ($oids as $oid) {
920d803c
DC
1315 if (isset($_SESSION['fb'][$app_id]['userinfo'][$oid])) {
1316 $info = $_SESSION['fb'][$app_id]['userinfo'][$oid];
7e665271 1317 $infos[] = $info;
cd11871d 1318 }
7e665271 1319 }
8f0e1ce9 1320 }
5d2a0a39
DC
1321 if (count($infos) != count($oids)) {
1322 // Session cache did not include all users, update the cache.
dcb2712f
DC
1323 $fields = array(
1324 'about_me',
1325 'affiliations',
1326 'name',
1327 'is_app_user',
1328 'pic',
1329 'pic_big',
1330 'pic_square',
1331 'profile_update_time',
1332 'proxied_email',
1333 'status',
1334 'email_hashes',
1335 'email',
b4f75c02 1336 'uid',
dcb2712f 1337 );
5d492838 1338 try {
81618599 1339 $infos = fb_fql_query($fb, 'SELECT ' . implode(', ', $fields) . ' FROM user WHERE uid in(' . implode(', ', $oids) . ')', array(fb_get_token($fb)));
5d492838
DC
1340 // Update cache with recent results.
1341 if (is_array($infos)) {
1342 foreach ($infos as $info) {
920d803c 1343 $_SESSION['fb'][$app_id]['userinfo'][$info['uid']] = $info;
5d492838 1344 }
a0534e56 1345 }
cd64a628 1346 } catch (FacebookApiException $e) {
dcb2712f 1347 fb_log_exception($e, t('Failed to query facebook user info'), $fb);
5d2a0a39
DC
1348 }
1349 }
dcb2712f 1350
3946309e 1351 return $infos;
01c37060 1352 }
01c37060
DC
1353}
1354
1355/**
1356 * For debugging, add $conf['fb_verbose'] = TRUE; to settings.php.
1357 */
1d3d574b 1358function fb_verbose() {
b24ac6ad 1359 return variable_get(FB_VAR_VERBOSE, NULL);
1d3d574b 1360}
ebb5649d 1361
56c9f8cc 1362/**
613fc5c5
DC
1363 * This function will be replaced, hopefully, by format_username in D7.
1364 *
225abd5d 1365 * @see http://drupal.org/node/192056
613fc5c5
DC
1366 */
1367function fb_format_username($account) {
1368 $name = !empty($account->name) ? $account->name : variable_get('anonymous', t('Anonymous'));
1369 drupal_alter('username', $name, $account);
1370 return $name;
1371}
1372
1373/**
1374 * hook_username_alter().
1375 *
1376 * Return a user's facebook name, instead of local username.
1377 */
1378function fb_username_alter(&$name, $account) {
374c5a75
DC
1379 if ($fbu = fb_get_fbu($account)) { // @TODO - is fb_get_fbu() a performance hit here?
1380 $info = fb_users_getInfo(array($fbu));
11665fdd 1381 if (is_array($info) && isset($info[0])) {
7217ba45
DC
1382 if ($info[0]['name']) {
1383 $name = $info[0]['name'];
1384 }
613fc5c5
DC
1385 }
1386 }
1387}
3946309e 1388
8dd84187
DC
1389/**
1390 * Implements hook_theme().
225abd5d
DC
1391 *
1392 * Returns description of theme functions.
1393 *
1394 * @see fb.theme.inc
8dd84187
DC
1395 */
1396function fb_theme() {
1397 return array(
1398 'fb_username' => array(
19092d2f 1399 'arguments' => array(
8dd84187
DC
1400 'fbu' => NULL,
1401 'object' => NULL,
1402 'orig' => NULL,
1403 ),
1404 'file' => 'fb.theme.inc',
1405 ),
1406 'fb_user_picture' => array(
19092d2f 1407 'arguments' => array(
8dd84187
DC
1408 'fbu' => NULL,
1409 'account' => NULL,
1410 'orig' => NULL,
1411 ),
1412 'file' => 'fb.theme.inc',
1413 ),
7d7e57b4 1414 'fb_fbml_popup' => array(
8dd84187
DC
1415 'arguments' => array('elements' => NULL),
1416 'file' => 'fb.theme.inc',
1417 ),
1418 'fb_login_button' => array(
1419 'arguments' => array(
1420 'text' => 'Connect with Facebook',
1421 'options' => NULL),
1422 'file' => 'fb.theme.inc',
1423 ),
1424 );
1425}
1426
ebb5649d
DC
1427//// Javascript and Ajax helpers
1428
1429/**
1430 * Ajax javascript callback.
1431 *
1432 * For sites which use ajax, various events may create javascript which is
1433 * normally embedded in a page. For example, posting to a user's wall. When
1434 * ajax is used instead of a page reload, this callback will provide any
1435 * javascript which should be run.
1436 */
1437function fb_js_cb() {
1438 $js_array = fb_invoke(FB_OP_JS, array('fb' => $GLOBALS['_fb'], 'fb_app' => $GLOBALS['_fb_app']), array());
1439 $extra_js = implode("\n", $extra);
1440 print $extra_js;
1441 exit();
1442}
3946309e 1443
a24a1868
DC
1444/**
1445 * Ajax callback handles an event from facebook's javascript sdk.
1446 *
d32d32f8
DC
1447 * @see
1448 * fb.js and
1449 * http://developers.facebook.com/docs/reference/javascript/FB.Event.subscribe
a24a1868 1450 *
d32d32f8
DC
1451 * @return
1452 * Array of javascript to be evaluated by the page which called this
1453 * callback.
a24a1868
DC
1454 */
1455function fb_ajax_event($event_type) {
92a6e858 1456 global $_fb, $_fb_app;
ab102d9a 1457 $js_array = array();
19092d2f 1458
75be9c97
DC
1459 if (isset($_REQUEST['appId'])) {
1460 $_fb_app = fb_get_app(array('id' => $_REQUEST['appId']));
ab102d9a
DC
1461 if ($_fb_app) {
1462 $_fb = fb_api_init($_fb_app);
1463 // Data to pass to hook_fb.
1464 $data = array(
1465 'fb_app' => $_fb_app,
1466 'fb' => $_fb,
1467 'event_type' => $event_type,
1468 'event_data' => $_POST, // POSTed via ajax.
1469 );
19092d2f 1470
ab102d9a 1471 $js_array = fb_invoke(FB_OP_AJAX_EVENT, $data, array());
19092d2f 1472
91e9397d
DC
1473 }
1474 else {
75be9c97 1475 watchdog('fb', 'fb_ajax_event did not find application %id', array('%id' => $_REQUEST['appId']), WATCHDOG_ERROR);
91e9397d 1476 }
875843a4 1477
91e9397d
DC
1478 if ($event_type == 'session_change') {
1479 // Session change is a special case. If user has logged out of
1480 // facebook, we want a new drupal session. We do this here, even if
1481 // fb_user.module is not enabled.
1482 if (!isset($_POST['fbu']) || !$_POST['fbu']) { // Logout, not login.
1483 _fb_logout();
1484 }
ab102d9a 1485 }
91e9397d 1486
ab102d9a
DC
1487 }
1488 else {
6b8f8dcc 1489 watchdog('fb', 'fb_ajax_event called badly. Not passed appId.', array(), WATCHDOG_ERROR);
875843a4 1490 // Trying to track down what makes this happen.
8c514893 1491 if (fb_verbose() == 'extreme') {
6b8f8dcc 1492 watchdog('fb', 'fb_ajax_event called badly. Not passed appId. trace: !trace', array(
91e9397d
DC
1493 '!trace' => '<pre>' . print_r(debug_backtrace(), 1) . '</pre>',
1494 ), WATCHDOG_ERROR);
ab102d9a 1495 }
7308ca43 1496 }
7308ca43 1497 drupal_json($js_array);
a24a1868 1498 exit();
7308ca43 1499}
1461dd7b
DC
1500
1501/**
0de240eb
DC
1502 * Menu callback for custom channel.
1503 *
1504 * @see http://developers.facebook.com/docs/reference/javascript/FB.init
1505 */
1506function fb_channel_page() {
1507 //headers instructing browser to cache this page.
1508 // Do these work?
1509 drupal_set_header("Cache-Control: public");
1510 drupal_set_header("Expires: Sun, 17-Jan-2038 19:14:07 GMT");
1511
1512 $date = format_date(time());
1513 $output = "<!-- modules/fb fb_channel_page() $date -->\n";
1514 $url = fb_js_settings('js_sdk_url');
1515 $output .= "<script src=\"$url\"></script>\n";
1516 print $output;
1517 exit();
562c4b37
DC
1518}
1519
1520//// Miscellaneous helpers and convenience functions.
1521
1522/**
1523 * Protocol (http or https) of the current request.
1524 */
1525function fb_protocol() {
1526 return (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') ? 'https' : 'http';
1527}
1528
1529
1530/**
1531 * Convenience wrapper around drupal_access_denied(). Call on pages where the
1532 * access is denied because the user is not logged into facebook.
1533 */
1534function fb_access_denied() {
1535 if (!fb_facebook_user()) {
1536 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>.'));
1537 }
1538 drupal_access_denied();
1539 exit();
1540}
1541