Newer
Older
<?php
/**
* @file
*
* This module provides some app-specific navigation to facebook apps. This
* is fairly experimental material. May change a lot in near future.
*
*/
/**
* Implementation of hook_fb.
*/
function fb_canvas_fb($fb, $fb_app, $op, &$return) {
if ($op == FB_OP_INITIALIZE) {
// Get our configuration settings.
$fb_app_data = fb_app_get_data($fb_app);
$fb_canvas_data = $fb_app_data['fb_canvas'];
// Set an app-specific theme.
global $custom_theme; // Set by this function.
if (fb_canvas_is_fbml())
$custom_theme = $fb_canvas_data['theme_fbml'];
else if (fb_canvas_is_iframe())
$custom_theme = $fb_canvas_data['theme_iframe'];
// This is for backward compatability (delete eventually)
if (!$custom_theme)
$custom_theme = variable_get('fb_theme', 'fb_fbml');
// Special handling for forms, as they are submitted directly to us, not
// to apps.facebook.com/canvas
// we will buffer, and later cache, the results.
if (fb_canvas_handling_form())
ob_start();
//drupal_set_message("fb_canvas_fb" . dpr($_GET, 1) . 'arg(0) = ' . dpr(arg(0), 1) . 'arg(1) = ' . dpr(arg(1), 1) . 'frontpage ' . dpr(variable_get('site_frontpage', 'node'), 1));
if ($_GET['q'] == drupal_get_normal_path(variable_get('site_frontpage', 'node'))) {
if ($fb->api_client->users_isAppAdded())
$front = $fb_canvas_data['front_added'];
else if ($fb->get_loggedin_user())
$front = $fb_canvas_data['front_loggedin'];
else
$front = $fb_canvas_data['front_anonymous'];
if ($front)
menu_set_active_item(drupal_get_normal_path($front));
}
}
else if ($op == FB_OP_EXIT) {
$destination = $return;
if (fb_canvas_handling_form() && $fb_app) {
$output = ob_get_contents();
ob_end_clean();
if ($destination) {
// Fully qualified URLs need to be modified to point to facebook app.
// URLs are fully qualified when a form submit handler returns a path,
// or any call to drupal_goto.
$destination = fb_canvas_fix_url($destination, $fb_app);
// If here, drupal_goto has been called, but it may not work within a
// canvas page, so we'll use Facebook's method.
// Will this preempt other hook_exits?
if ($fb) {
$fb->redirect($destination);
}
}
else {
// Save the results to show the user later
$token = uniqid('fb_');
$cid = session_id() . "_$token";
watchdog('fb', "Storing cached form page $cid, then redirecting");
cache_set($cid, 'cache_page', $output, time() + (60 * 5), drupal_get_headers()); // (60 * 5) == 5 minutes
$dest = 'http://apps.facebook.com/' . $fb_app->canvas . "/fb/form_cache/$cid";
// $fb->redirect($url); // Does not work!
// Preserve some URL parameters
$query = array();
foreach (array('fb_force_mode') as $key) {
if ($_REQUEST[$key])
$query[] = $key . '=' . $_REQUEST[$key];
}
//drupal_goto honors $_REQUEST['destination'], but we only want that when no errors occurred
if (form_get_errors()) {
unset($_REQUEST['destination']);
if ($_REQUEST['edit'])
unset($_REQUEST['edit']['destination']);
}
drupal_goto($dest, implode('&', $query), NULL, 303); // appears to work
}
}
}
}
/**
* Implementation of hook_form_alter.
*/
function fb_canvas_form_alter($form_id, &$form) {
// Add our settings to the fb_app edit form.
if (is_array($form['fb_app_data'])) {
$node = $form['#node'];
$fb_app_data = fb_app_get_data($node->fb_app);
$fb_canvas_data = $fb_app_data['fb_canvas'];
// defaults
if (!$fb_canvas_data)
$fb_canvas_data = array();
$fb_canvas_data = array_merge(array('theme_fbml' => 'fb_fbml',
'theme_iframe' => 0),
$fb_canvas_data);
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
$form['fb_app_data']['fb_canvas'] = array('#type' => 'fieldset',
'#collapsible' => TRUE,
'#collapsed' => TRUE,
'#title' => t('Facebook canvas page settings'),
'#description' => t('Allows application-specific front page and navigation links.')
);
$form['fb_app_data']['fb_canvas']['front_anonymous'] =
array('#type' => 'textfield',
'#title' => t('Front page when user is not logged in'),
'#description' => t('Leave blank to use the site-wide front page.'),
'#default_value' => $fb_canvas_data['front_anonymous'],
);
$form['fb_app_data']['fb_canvas']['front_loggedin'] =
array('#type' => 'textfield',
'#title' => t('Front page when user is logged in, but has not added the app.'),
'#description' => t('Leave blank to use the site-wide front page.'),
'#default_value' => $fb_canvas_data['front_loggedin'],
);
$form['fb_app_data']['fb_canvas']['front_added'] =
array('#type' => 'textfield',
'#title' => t('Front page when user has added this application.'),
'#description' => t('Leave blank to use the site-wide front page.'),
'#default_value' => $fb_canvas_data['front_added'],
);
// Allow primary links to be different on facebook versus the rest of the
// site. Code from menu_configure() in menu.module.
$root_menus = menu_get_root_menus();
$primary_options = $root_menus;
$primary_options[0] = t('<use sitewide setting>');
$secondary_options = $root_menus;
$secondary_options[0] = t('<use sitewide setting>');
$form['fb_app_data']['fb_canvas']['primary_links'] =
array('#type' => 'select',
'#title' => t('Menu containing primary links'),
'#description' => t('Your application can have primary links different from those used elsewhere on your site.'),
'#default_value' => $fb_canvas_data['primary_links'],
'#options' => $primary_options,
);
$form['fb_app_data']['fb_canvas']['secondary_links'] =
array('#type' => 'select',
'#title' => t('Menu containing secondary links'),
'#default_value' => $fb_canvas_data['secondary_links'],
'#options' => $secondary_options,
'#description' => t('If you select the same menu as primary links then secondary links will display the appropriate second level of your navigation hierarchy.'),
);
// Override themes
$themes = system_theme_data();
ksort($themes);
$theme_options[0] = t('System default');
foreach ($themes as $theme) {
$theme_options[$theme->name] = $theme->name;
}
$form['fb_app_data']['fb_canvas']['theme_fbml'] =
array('#type' => 'select',
'#title' => t('Theme for FBML pages'),
'#description' => t('Choose only a theme that is FBML-aware.'),
'#options' => $theme_options,
'#required' => TRUE,
'#default_value' => $fb_canvas_data['theme_fbml'],
);
$form['fb_app_data']['fb_canvas']['theme_iframe'] =
array('#type' => 'select',
'#title' => t('Theme for iframe pages'),
'#description' => t('Choose only a facebook-aware theme'),
'#options' => $theme_options,
'#required' => TRUE,
'#default_value' => $fb_canvas_data['theme_iframe'],
);
}
global $fb, $fb_app;
// We will send all form submission directly to us, not via
// apps.facebook.com/whatever.
if (fb_canvas_is_fbml()) {
// We're in a facebook callback
// Can't use 'fb_handling_form' because facebook strips it.
$form['_fb_handling_form'] = array('#value' => TRUE,
'#type' => 'hidden');
// We need to make sure the action goes to our domain and not apps.facebook.com
if ($form['#action'] == '') {
$form['#action'] = $_GET['q'];
}
$form['#action'] = _fb_canvas_make_form_action_local($form['#action']);
// Let's hope no subsequent hook_form_alters mess with #action.
}
else if (fb_canvas_is_iframe()) {
//dpm($form, 'fb_canvas_form_alter');
}
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
}
function fb_canvas_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
if ($op == 'view' && $node->type == 'fb_app') {
//dpm(func_get_args(), 'fb_canvas_nodeapi');
if (user_access('administer fb apps')) {
$fb_app = $node->fb_app;
$output = theme('dl',
array(t('Canvas page') => "http://apps.facebook.com/$fb_app->canvas",
t('Callback URL') => t("%clean_url<br /> (or %advanced_url only if your theme is designed to support PAGE_TYPE.)<br/>Make sure you have enabled clean URLs, and include the trailing '/'.",
array("%clean_url" => url('', NULL, NULL, TRUE) . FB_SETTINGS_APP_NID . '/' . $node->nid .'/',
"%advanced_url" => url('', NULL, NULL, TRUE) . FB_SETTINGS_APP_NID . '/' . $node->nid . '/' . FB_SETTINGS_PAGE_TYPE . '/PAGE_TYPE/',
)
),
));
$node->content['fb_canvas'] =
array('#value' => $output,
'#weight' => 2);
}
}
}
function fb_canvas_is_fbml() {
global $fb, $fb_app;
if ($fb && $fb_app)
return ($fb->in_fb_canvas() || fb_canvas_handling_form());
}
function fb_canvas_is_iframe() {
global $fb, $fb_app;
if ($fb && $fb_app) {
return ($fb->in_frame() || !fb_canvas_is_fbml());
}
}
function fb_canvas_handling_form() {
global $fb;
// Test whether a form has been submitted via facebook canvas page.
if ($fb && $_REQUEST['form_id'] && $_REQUEST['_fb_handling_form'])
return TRUE;
}
// This may need work
function _fb_canvas_make_form_action_local($action) {
// If action is fully qualified, do not change it
if (strpos($action, ':'))
return $action;
// I'm not sure where the problem is, but sometimes actions have two question marks. I.e.
// /htdocs/?app=foo&q=user/login?destination=comment/reply/1%2523comment-form
// Here we replace 3rd (or more) '?' with '&'.
$parts = explode('?', $action);
if (count($parts) > 2) {
$action = array_shift($parts) . '?' . array_shift($parts);
$action .= '&' . implode('&', $parts);
}
//drupal_set_message("form action now " . "http://".$_SERVER['HTTP_HOST']. $action); // debug
return "http://".$_SERVER['HTTP_HOST']. $action;
}
/**
* Convert a local fully qualified path to a facebook app path. This needs to
* be used internally, to fix drupal_gotos upon form submission. Third party
* module should not need to call this, I believe.
*/
function fb_canvas_fix_url($url, $fb_app) {
global $base_url;
$patterns[] = "|{$base_url}/" . FB_SETTINGS_APP_NID . "/{$fb_app->nid}/|";
// Here we assume apps.facebook.com. Is this safe?
$replacements[] = "http://apps.facebook.com/{$fb_app->canvas}/";
$patterns[] = "|fb_cb_type/[^/]*/|";
$replacements[] = "";
// Facebook will prepend "appNNN_" all our ids
$patterns[] = "|#([^\?]*)|";
$replacements[] = "#app{$fb_app->id}_$1";
$url = preg_replace($patterns, $replacements, $url);
return $url;
}
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
/**
* Returns the 'type' of the page. This helps themes determine whether they
* are to provide an iframe or an iframe within FBML.
*/
function fb_canvas_page_type() {
return fb_settings(FB_SETTINGS_PAGE_TYPE);
}
function fb_canvas_primary_links() {
global $fb_app;
$mid = 0;
if ($fb_app) {
$fb_app_data = fb_app_get_data($fb_app);
$fb_canvas_data = $fb_app_data['fb_canvas'];
$mid = $fb_canvas_data['primary_links'];
}
if ($mid)
return menu_primary_links(1, $mid);
else
return menu_primary_links();
}
function fb_canvas_secondary_links() {
global $fb_app;
if ($fb_app) {
$fb_app_data = fb_app_get_data($fb_app);
$fb_canvas_data = $fb_app_data['fb_canvas'];
$mid1 = $fb_canvas_data['primary_links'];
$mid2 = $fb_canvas_data['secondary_links'];
}
if ($mid2) {
if ($mid2 == $mid1)
return menu_primary_links(2, $mid2);
else
return menu_primary_links(1, $mid2);
}
else
return menu_secondary_links();
}
function fb_canvas_process($output, $add_target = TRUE) {
global $base_path, $base_url;
global $fb, $fb_app;
$patterns = array();
$replacements = array();
if ($fb) {
$page_type = fb_settings(FB_SETTINGS_PAGE_TYPE);
$nid = $fb_app->nid;
$base = url(); // short URL with rewrite applied.
if (fb_canvas_is_fbml()) {
//dpm($output, "before fb_canvas_process");
// We're producing FBML for a canvas page
// Change links to use canvas on Facebook
$patterns[] = "|=\"{$base}|";
$replacements[] = "=\"/{$fb_app->canvas}/";
// Workaround Drupal does not let us rewrite the frontpage url
/* Not needed thanks to: http://drupal.org/node/241878
$patterns[] = "|=\"{$base_path}\"|";
$replacements[] = "=\"/{$fb_app->canvas}/\"";
*/
// Change paths to files to fully qualified URLs. This matches relative
// URLs that do not include the canvas (that is, not matched by previous
// patterns).
$patterns[] = '|="'.$base_path."(?!{$fb_app->canvas})|";
$replacements[] = '="'.$base_url.'/';
}
else {
// In iframe
// Add target=_top so that entire pages do not appear within an iframe.
// TODO: make these pattern replacements more sophisticated, detect whether target is already set.
if ($add_target) {
// Add target=_top to all links
$patterns[] = "|<a |";
$replacements[] = "<a target=\"_top\" ";
// Do not change local forms, but do change external ones
$patterns[] = "|<form([^>]*)action=\"([^:\"]*):|";
$replacements[] = "<form target=\"_top\" $1 action=\"$2:";
// Make internal links point to canvas pages
$patterns[] = "|<a([^>]*)href=\"{$base}|";
$replacements[] = "<a $1 href=\"http://apps.facebook.com/{$fb_app->canvas}/";
}
else {
// Add target=_top to only external links
$patterns[] = "|<a([^>]*)href=\"([^:\"]*):|";
$replacements[] = "<a target=\"_top\" $1 href=\"$2:";
$patterns[] = "|<form([^>]*)action=\"([^:\"]*):|";
$replacements[] = "<form target=\"_top\" $1 action=\"$2:";
}
// Workaround Drupal does not let us rewrite the frontpage url
//$patterns[] = "|=\"{$base_path}\"|";
//$replacements[] = "=\"{$base_path}fb_canvas/{$page_type}/{$nid}/\""; // XXX