fixes: #1166272 by Dave Cohen, John Klehm: support for facebooks oauth 2. Mostly...
[project/fb.git] / fb_connect.module
CommitLineData
c93a3625 1<?php
ff03b416
DC
2/**
3 * @file
4 * Support for Facebook Connect features
19092d2f 5 *
ff03b416
DC
6 * Note that Facebook connect will work properly only with themes that are
7 * Facebook Connect aware.
8 */
236aad22 9
b29d7387 10// Drupal variables
67523320 11define('FB_CONNECT_VAR_PRIMARY', 'fb_connect_primary_label');
8dd84187 12
a4d0f936
DC
13define('FB_CONNECT_VAR_THEME_USERNAME_1', 'fb_connect_theme_username_1');
14define('FB_CONNECT_VAR_THEME_USERNAME_2', 'fb_connect_theme_username_2');
2544ecdc 15define('FB_CONNECT_VAR_THEME_USERPIC_1', 'fb_connect_theme_userpic_1');
a4d0f936 16define('FB_CONNECT_VAR_THEME_USERPIC_2', 'fb_connect_theme_userpic_2');
b29d7387 17
c93a3625 18/**
3cf45120 19 * Implements hook_menu().
c93a3625 20 */
a9167ce4 21function fb_connect_menu() {
236aad22 22 $items = array();
ac0b023a 23
b29d7387 24 // Admin pages
67523320 25 $items[FB_PATH_ADMIN . '/fb_connect'] = array(
b29d7387 26 'title' => 'Facebook Connect',
456be90c 27 'description' => 'Configure Facebook Connect',
b29d7387
DC
28 'page callback' => 'drupal_get_form',
29 'page arguments' => array('fb_connect_admin_settings'),
cda8c9fd 30 'access arguments' => array(FB_PERM_ADMINISTER),
b29d7387 31 'file' => 'fb_connect.admin.inc',
cda8c9fd 32 'type' => MENU_LOCAL_TASK,
b29d7387 33 );
19092d2f 34
b29d7387 35
236aad22
DC
36 return $items;
37}
38
f54b3a2b 39
236aad22 40/**
236aad22
DC
41 * Prepare for fbConnect use. Because a single Drupal might support
42 * multiple apps, we don't know in advance which is the fbConnect app.
236aad22
DC
43 */
44function fb_connect_app_init($fb_app) {
3946309e 45 if (isset($GLOBALS['_fb_app']) &&
f54b3a2b 46 $GLOBALS['_fb_app']->apikey != $fb_app->apikey) {
cda8c9fd
DC
47 // If we're in an iframe, only support connect for the iframe app.
48 return;
49 }
19092d2f 50
3946309e
DC
51 if ($fb = fb_api_init($fb_app)) {
52 $fbu = $fb->getUser();
1c301495 53 if ($fbu &&
f54b3a2b 54 (!isset($GLOBALS['_fb_app']) || $GLOBALS['_fb_app']->apikey != $fb_app->apikey)) {
7920cd6c 55 // The user has authorized the app and we now know something about them. Use a hook to trigger the actions of other modules.
a46642fe 56 fb_invoke(FB_OP_APP_IS_AUTHORIZED, array(
f54b3a2b
DC
57 'fbu' => $fbu,
58 'fb_app' => $fb_app,
59 'fb' => $fb));
7920cd6c 60 }
19092d2f 61
123ed3d0
DC
62 // Remember which app we've initialized.
63 _fb_connect_set_app($fb_app);
43256e0a 64 _fb_connect_add_js($fb_app, $fb);
236aad22 65 }
1c301495 66 return $fb;
236aad22
DC
67}
68
69/**
123ed3d0 70 * Helper function for other modules to know page is connected.
19092d2f 71 *
123ed3d0
DC
72 * Note that this may return data on connect pages and in iframe apps
73 * (depending on how iframe is configured).
74 */
75function fb_connect_get_app() {
76 return _fb_connect_set_app();
77}
78function _fb_connect_set_app($fb_app = NULL) {
3cf45120 79 $cache = &drupal_static(__FUNCTION__);
123ed3d0
DC
80 if (isset($fb_app)) {
81 $cache = $fb_app;
82 }
83 return $cache;
84}
85
86/**
3cf45120 87 * Implements hook_fb().
19a42c4b 88 */
236aad22 89function fb_connect_fb($op, $data, &$return) {
83985921 90 if ($op == FB_OP_CURRENT_APP && !$return && !fb_is_canvas()) {
a46642fe 91 // This will cause fb.module to set the global $_fb when user is logged in via fbConnect.
b3adf089
DC
92 if ($id = variable_get(FB_VAR_ID, NULL)) {
93 // Use $conf['fb_id'] if set in settings.php.
94 $return = fb_get_app(array('id' => $id));
95 }
96 elseif ($apikey = variable_get(FB_VAR_APIKEY, NULL)) { // Deprecated. Use fb_id instead.
b8212b1b 97 // Use $conf['fb_apikey'] if set in settings.php.
b52013a0
DC
98 $return = fb_get_app(array('apikey' => $apikey));
99 }
100 elseif ($label = variable_get(FB_CONNECT_VAR_PRIMARY, NULL)) {
67523320 101 $return = fb_get_app(array('label' => $label));
19a42c4b
DC
102 }
103 }
456be90c 104 elseif ($op == FB_OP_POST_INIT) {
9a81ce2e 105 if (!fb_is_tab()) {
c950d831 106 // Init Facebook javascript for primary app
43256e0a 107 _fb_connect_add_js($data['fb_app'], $data['fb']);
d467256c 108 }
19092d2f 109
67523320
DC
110 // Include our admin hooks.
111 if (fb_is_fb_admin_page()) {
f54b3a2b 112 module_load_include('inc', 'fb_connect', 'fb_connect.admin');
67523320 113 }
d467256c 114 }
236aad22
DC
115}
116
8af7dfe4
DC
117/**
118 * This wrapper function around drupal_add_js() ensures that our
119 * settings are added once and only once when needed.
6f3bdff9 120 *
8af7dfe4 121 */
43256e0a 122function _fb_connect_add_js($fb_app, $fb) {
3cf45120 123 $just_once = &drupal_static(__FUNCTION__);
43256e0a
DC
124
125 if (fb_is_tab())
126 // Tabs are FBML.
127 return;
128
bb5f3a82 129 if (!isset($just_once)) {
f54b3a2b
DC
130 drupal_add_js(array(
131 'fb_connect' => array(
132 'front_url' => url('<front>'),
133 'fbu' => fb_facebook_user(),
d8f85b06 134 'uid' => $GLOBALS['user']->uid,
f54b3a2b
DC
135 ),
136 ), 'setting');
137 drupal_add_js(drupal_get_path('module', 'fb_connect') . '/fb_connect.js');
bb5f3a82
DC
138 $just_once = TRUE;
139 }
43256e0a
DC
140
141 // If we are not the global $_fb_app
2b5faa67
DC
142 if ($fb_app) {
143 $settings = fb_js_settings();
cb150b03
DC
144 if (!isset($settings['fb_init_settings']['appId']) ||
145 $settings['fb_init_settings']['appId'] != $fb_app->id) {
2b5faa67
DC
146 // Ensure JS initializes with the proper apikey. We may reach this if
147 // there is no "primary" app.
148 // @TODO fb.module should have a helper to make this cleaner.
43256e0a 149
cb150b03 150 $settings['fb_init_settings']['appId'] = $fb_app->id;
2b5faa67 151 fb_js_settings('apikey', $fb_app->apikey);
43256e0a 152 fb_js_settings('fbu', fb_facebook_user($fb));
2b5faa67 153 fb_js_settings('fb_init_settings', $settings['fb_init_settings']);
43256e0a 154 // fb.module will add settings to footer.
2b5faa67
DC
155 }
156 }
bb5f3a82
DC
157}
158
389fa665 159
6f3bdff9
DC
160/**
161 * Default markup for our login block.
162 */
d467256c
DC
163function _fb_connect_block_login_defaults() {
164 return array('anon_not_connected' => array(
165 'title' => t('Facebook Connect'),
79bfec16 166 'body' => array('value' => '<fb:login-button scope="!perms" v="2">Connect</fb:login-button>'),
d467256c
DC
167 ),
168 'user_not_connected' => array(
169 'title' => t('Facebook Connect'),
79bfec16 170 'body' => array('value' => '<fb:login-button scope="!perms" v="2">Connect</fb:login-button>'),
d467256c
DC
171 ),
172 'connected' => array(
173 'title' => t('Facebook Connect'),
261663f9 174 // Logout commented out, because drupal has logout link.
3cf45120 175 'body' => array('value' => '<fb:profile-pic uid=!fbu></fb:profile-pic><!--<fb:login-button autologoutlink=true></fb:login-button>-->'),
d467256c
DC
176 ),
177 );
71f79c78
DC
178}
179
caef280f 180/**
3cf45120 181 * Implements hook_block_info().
caef280f 182 */
3cf45120
DC
183function fb_connect_block_info() {
184 $items = array();
185 foreach (fb_get_all_apps() as $fb_app) {
186 $d = 'login_' . $fb_app->label;
187 $items[$d] = array(
188 'info' => t('Facebook Connect Login to !app',
189 array('!app' => $fb_app->title)),
190 );
71f79c78 191 }
3cf45120
DC
192 return $items;
193}
194
195/**
196 * Implements hook_block_configure().
197 */
198function fb_connect_block_configure($delta = '') {
199 $orig_defaults = _fb_connect_block_login_defaults();
200 $defaults = variable_get('fb_connect_block_' . $delta, $orig_defaults);
201 $form['config'] = array('#tree' => TRUE);
202
203 // Settings for each user status that we can detect.
204 foreach (array('anon_not_connected', 'user_not_connected', 'connected') as $key) {
205 $form['config'][$key] = array(
206 '#type' => 'fieldset',
207 // title and description below
208 '#collapsible' => TRUE,
209 '#collapsed' => FALSE,
210 );
211 $form['config'][$key]['title'] = array(
212 '#type' => 'textfield',
213 '#title' => t('Default title'),
214 '#default_value' => $defaults[$key]['title'],
215 );
216 $textformat = isset($defaults[$key]['body']['format']) ? $defaults[$key]['body']['format'] : 'full_html';
217 $form['config'][$key]['body'] = array(
218 '#type' => 'text_format',
219 '#title' => t('Body'),
220 '#base_type' => 'textarea',
221 '#format' => $textformat,
222 '#default_value' => $defaults[$key]['body']['value'],
223 );
71f79c78 224 }
3cf45120
DC
225
226 $form['config'][] = array(
227 '#markup' => "<p><strong>Be sure to select a format that allows XFBML tags!</strong> (That is, use <em>Full HTML</em> or <em>PHP code</em> (PHP Filter module must be enabled), rather than <em>Filtered HTML</em>.)</p>",
228 '#weight' => -10,
229 );
230 $form['config']['anon_not_connected']['#title'] = t('Anonymous user, not connected');
231 $form['config']['anon_not_connected']['#description'] = t('Settings when local user is Anonymous, and not connected to Facebook. Typically a new account will be created when the user clicks the connect button.');
232 $form['config']['anon_not_connected']['body']['#description'] = t('Suggestion: %default .', array('%default' => $orig_defaults['anon_not_connected']['body']['value']));
233
234 $form['config']['user_not_connected']['#title'] = t('Registered user, not connected');
235 $form['config']['user_not_connected']['#description'] = t('Settings when local user is registered, and not connected to Facebook. Typically the facebook id will be linked to the local id after the user clicks the connect button.');
236 $form['config']['user_not_connected']['body']['#description'] = t('Suggestion: %default .', array('%default' => $orig_defaults['user_not_connected']['body']['value']));
237 $form['config']['connected']['#title'] = t('Connected user');
e5a511fc
DC
238 $form['config']['connected']['#description'] = t('Settings when local user is connected to Facebook. You may render facebook\'s logout button, and/or information about the user. Consider using <a target="_blank" href="!xfbml_url">XFBML</a> such as &lt;fb:name uid=!fbu&gt;&lt;/fb:name&gt; or &lt;fb:profile-pic uid=!fbu&gt;&lt;/fb:profile-pic&gt;', array('!xfbml_url' => 'http://wiki.developers.facebook.com/index.php/XFBML'));
239 $form['config']['connected']['body']['#description'] = t('Note that <strong>!fbu</strong> will be replaced with the user\'s facebook id.<br/>Suggestion: %default .', array('%default' => $orig_defaults['connected']['body']['value']));
3cf45120
DC
240
241 return $form;
242}
243
244/**
245 * Implements hook_block_save().
246 */
247function fb_connect_block_save($delta = '', $edit = array()) {
248 variable_set('fb_connect_block_' . $delta, $edit['config']);
249}
250
251function fb_connect_block_view($delta = '') {
252 if (!fb_is_tab()) {
8982c677 253 // Hide block on tabs, where the $fbu is actually the page, not the user.
71f79c78
DC
254 if (strpos($delta, 'login_') === 0) {
255 // Login block
256 $label = substr($delta, 6); // length of 'login_'
257 $fb_app = fb_get_app(array('label' => $label));
79f665b2
DC
258 if ($fb_app &&
259 ($fb = fb_connect_app_init($fb_app))) {
3946309e
DC
260 $fbu = $fb->getUser();
261
43256e0a 262 //_fb_connect_add_js($fb_app); moved to fb_connect_app_init()
19092d2f 263
cda8c9fd 264 $base = drupal_get_path('module', 'fb_connect');
19092d2f 265
3cf45120 266 $config = variable_get('fb_connect_block_' . $delta, _fb_connect_block_login_defaults());
cda8c9fd 267 if ($fbu) {
3cf45120 268 $key = 'connected';
19092d2f 269 }
f2ee8348 270 elseif ($GLOBALS['user']->uid >= 1) {
3cf45120 271 $key = 'user_not_connected';
19092d2f 272 }
456be90c 273 else {
3cf45120 274 $key = 'anon_not_connected';
cda8c9fd 275 }
ed9d8a6d 276
3cf45120
DC
277 $subject = $config[$key]['title'];
278 $content = $config[$key]['body']['value'];
279
280 // substitute fbu
281 $content = str_replace('!fbu', $fbu, $content);
282
ed9d8a6d
DC
283 // substitute perms
284 $perms = array();
285 drupal_alter('fb_required_perms', $perms);
286 $content = str_replace('!perms', implode(',', $perms), $content);
19092d2f 287
3cf45120
DC
288 // Filter output according to settings in block configuration
289 $subject = check_plain($subject);
290 if (isset($config[$key]['body']['format'])) {
291 $content = check_markup($content, $config[$key]['body']['format'], '', FALSE);
cda8c9fd 292 }
19092d2f 293
cda8c9fd
DC
294 $block = array(
295 'subject' => $subject,
296 'content' => $content,
297 );
298 return $block;
71f79c78 299 }
236aad22 300 }
c93a3625
DC
301 }
302}
303
d8f85b06
DC
304/**
305 * Helper returns configuration for this module, on a per-app basis.
306 */
307function _fb_connect_get_config($fb_app) {
308 $fb_app_data = fb_get_app_data($fb_app);
309 $config = isset($fb_app_data['fb_connect']) ? $fb_app_data['fb_connect'] : array();
19092d2f 310
d8f85b06
DC
311 // Merge in defaults
312 $config += array(
313
314 );
315 return $config;
316}
d467256c 317
6f3bdff9
DC
318/**
319 * Implements hook_form_alter().
320 */
bd4e4ff6 321function fb_connect_form_alter(&$form, &$form_state, $form_id) {
d467256c 322 // Add our settings to the fb_app edit form.
81e5e23a 323 if (isset($form['fb_app_data'])) {
67523320 324 $fb_app = $form['#fb_app'];
19092d2f 325
d467256c
DC
326 $form['fb_app_data']['fb_connect'] = array(
327 '#type' => 'fieldset',
038f3ecf 328 '#title' => 'Facebook connect',
d467256c
DC
329 '#tree' => TRUE,
330 '#collapsible' => TRUE,
d8f85b06 331 '#collapsed' => $fb_app->label ? TRUE : FALSE,
d467256c 332 );
19092d2f 333
a4d0f936 334 // "Primary" will be initialized on every non-canvas page.
67523320 335 $primary_label = variable_get(FB_CONNECT_VAR_PRIMARY, NULL);
d467256c
DC
336 $form['fb_app_data']['fb_connect']['primary'] = array(
337 '#type' => 'checkbox',
338 '#title' => t('Primary'),
339 '#description' => t('Initialize fbConnect javascript on all (non-canvas) pages. If this site supports multiple Facebook Apps, this may be checked for at most one.'),
67523320 340 '#default_value' => isset($fb_app->label) && ($primary_label == $fb_app->label),
d467256c 341 );
67523320 342 if (($primary_label) && ($primary_label != $fb_app->label)) {
19092d2f 343 $form['fb_app_data']['fb_connect']['primary']['#description'] .= '<br/>' .
67523320 344 t('Note that checking this will replace %app as the primary Facebook Connect app.', array('%app' => $primary_label));
d467256c 345 }
3cf45120 346 $form['buttons']['submit']['#submit'][] = 'fb_connect_app_submit';
19092d2f 347 }
d467256c
DC
348}
349
6f3bdff9
DC
350/**
351 * Submit callback. Sets or unsets "primary" app.
352 */
67523320
DC
353function fb_connect_app_submit($form, &$form_state) {
354 $values = $form_state['values'];
355 $label = $values['label'];
356 $data = $values['fb_app_data']['fb_connect'];
357 if ($data['primary']) {
358 variable_set(FB_CONNECT_VAR_PRIMARY, $label);
359 drupal_set_message(t('%label is the primary Facebook Connect application.', array('%label' => $label)));
360 }
456be90c 361 elseif ($label == variable_get(FB_CONNECT_VAR_PRIMARY, NULL)) {
67523320 362 // This app was the primary one, but the user has unchecked it.
19092d2f 363 variable_set(FB_CONNECT_VAR_PRIMARY, NULL);
d467256c
DC
364 }
365}
366
2bc64ac0
DC
367
368/**
3cf45120 369 * Implements hook_theme_registry_alter().
2bc64ac0
DC
370 *
371 * Override theme functions for things that can be displayed using
372 * XFBML. Currently overriding username and user_picture. We rename
373 * the original entries, as we will use them for users without
374 * javascript enabled.
375 *
376 * This hook is not well documented. Who knows what its supposed to
377 * return? No doubt this will need updating with each new version of
378 * Drupal.
379 */
380function fb_connect_theme_registry_alter(&$theme_registry) {
55a49b28 381 // Ideally, we'd do this only on themes which will certainly be used for facebook connect pages.
a4d0f936
DC
382 if (variable_get(FB_CONNECT_VAR_THEME_USERNAME_2, TRUE) ||
383 (variable_get(FB_CONNECT_VAR_THEME_USERNAME_1, TRUE) &&
384 $theme_registry['username']['type'] == 'module')) {
55a49b28
DC
385 // Re-register the original theme function under a new name.
386 $theme_registry['fb_connect_username_orig'] = $theme_registry['username'];
387 // Override theme username
388 $theme_registry['username'] = array(
3cf45120 389 'variables' => array('object' => NULL),
55a49b28
DC
390 'function' => 'fb_connect_theme_username_override',
391 'type' => 'module',
3cf45120 392 'theme path' => drupal_get_path('module', 'fb_connect'), // something is needed here but it isn't used
55a49b28
DC
393 );
394 }
395
a4d0f936
DC
396 if (variable_get(FB_CONNECT_VAR_THEME_USERPIC_2, TRUE) ||
397 (variable_get(FB_CONNECT_VAR_THEME_USERPIC_1, TRUE) &&
398 $theme_registry['user_picture']['type'] == 'module')) {
55a49b28
DC
399 // Re-register the original theme function under a new name.
400 $theme_registry['fb_connect_user_picture_orig'] = $theme_registry['user_picture'];
401 // Override theme username
402 $theme_registry['user_picture'] = array(
3cf45120 403 'variables' => array('account' => NULL),
55a49b28
DC
404 'function' => 'fb_connect_theme_user_picture_override',
405 'type' => 'module',
3cf45120 406 'theme path' => drupal_get_path('module', 'fb_connect'), // something is needed here but it isn't used
55a49b28
DC
407 );
408 }
2bc64ac0
DC
409}
410
411/**
412 * Our replacement for theme('user_picture', ...)
413 */
3cf45120
DC
414function fb_connect_theme_user_picture_override($variables) {
415 $account = $variables['account'];
d9e0705c 416 // Markup without fb_connect.
3cf45120 417 $orig = theme('fb_connect_user_picture_orig', array('account' => $account));
55a49b28 418
d9e0705c
DC
419 // Respect Drupal's profile pic, if uploaded.
420 if (isset($account->picture) && $account->picture) {
cda8c9fd
DC
421 return $orig;
422 }
423
424 if ($fbu = fb_get_object_fbu($account)) {
3cf45120
DC
425 $output = theme('fb_user_picture', array(
426 'fbu' => $fbu,
427 'account' => $account,
428 'orig' => $orig,
429 ));
d9e0705c 430 }
2bc64ac0 431 else {
cda8c9fd 432 $output = $orig;
2bc64ac0 433 }
2bc64ac0 434 return $output;
2bc64ac0
DC
435}
436
437/**
438 * Our replacement for theme('username', ...)
439 */
3cf45120
DC
440function fb_connect_theme_username_override($variables) {
441 $account = $variables['account'];
442 $orig = theme('fb_connect_username_orig', $variables);
6886ef93 443
3cf45120 444 if ($fbu = fb_get_object_fbu($account)) {
55a49b28 445 // Theme the username with XFBML, using original username as backup.
3cf45120
DC
446 return theme('fb_username', array(
447 'fbu' => $fbu,
448 'account' => $account,
449 'orig' => $orig,
450 ));
bb5f3a82
DC
451 }
452 else {
cda8c9fd 453 return $orig;
bb5f3a82
DC
454 }
455}
d467256c 456
12abde23 457
cda8c9fd 458