fb_canvas.module in Drupal for Facebook 5.2
Same filename and directory in other branches
This module provides some app-specific navigation to facebook apps.
File
fb_canvas.moduleView source
<?php
/**
* @file
*
* This module provides some app-specific navigation to facebook apps.
*
*/
/**
* Implementation of hook_fb.
*/
function fb_canvas_fb($op, $data, &$return) {
$fb = $data['fb'];
$fb_app = $data['fb_app'];
//watchdog('XXX', "fb_canvas_fb($op) data is " . dprint_r($data, 1));
if ($op == FB_OP_CURRENT_APP) {
if ($apikey = $_REQUEST[FB_APP_REQ_API_KEY]) {
// If facebook has passed the app key, let's use that.
$fb_app = fb_get_app(array(
'apikey' => $apikey,
));
}
else {
if (function_exists('fb_settings')) {
// See fb_settings.inc
if ($nid = fb_settings(FB_SETTINGS_APP_NID)) {
// Here if we're in iframe, using our /fb_canvas/nid/ path convention.
$fb_app = fb_get_app(array(
'nid' => $nid,
));
}
}
}
if ($fb_app) {
$return = $fb_app;
}
}
else {
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'];
$is_canvas = FALSE;
// Set an app-specific theme.
global $custom_theme;
// Set by this function.
if (fb_canvas_is_fbml()) {
$custom_theme = $fb_canvas_data['theme_fbml'];
$is_canvas = TRUE;
}
else {
if (fb_canvas_is_iframe()) {
$custom_theme = $fb_canvas_data['theme_iframe'];
$is_canvas = TRUE;
}
}
// 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();
}
if ($is_canvas && $_GET['q'] == drupal_get_normal_path(variable_get('site_frontpage', 'node'))) {
if ($fb
->get_loggedin_user()) {
if ($fb->api_client
->users_isAppUser()) {
$front = $fb_canvas_data['front_added'];
}
else {
$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
}
}
}
else {
if ($op == FB_OP_SET_PROPERTIES) {
// Compute properties which we can set automatically.
$callback_url = url('', NULL, NULL, TRUE) . FB_SETTINGS_APP_NID . '/' . $fb_app->nid . '/';
$return['callback_url'] = $callback_url;
}
else {
if ($op == FB_OP_LIST_PROPERTIES) {
$return[t('Callback URL')] = 'callback_url';
$return[t('Canvas Page Suffix')] = 'canvas_name';
}
}
}
}
}
}
/**
* 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);
$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 to facebook'),
'#description' => t('Leave blank to use the site-wide front page. See <a href="!link" target=_blank>Public Canvas Pages</a>.', array(
'!link' => 'http://wiki.developers.facebook.com/index.php/Public_Canvas_Pages',
)),
'#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 to facebook, but is not a user of 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 for users of 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()) {
//dpm($form, "fb_canvas_form_alter($form_id)"); // debug
// We're in a facebook callback
if (!isset($form['fb_canvas_form_handler'])) {
$form['fb_canvas_form_handler'] = array();
// This variable tells us to handle the form on submit.
// Can't use 'fb_handling_form' because facebook strips it.
$form['fb_canvas_form_handler']['_fb_handling_form'] = array(
'#value' => TRUE,
'#type' => 'hidden',
);
// We need to make sure the action goes to our domain and not apps.facebook.com, so here we tweak the form action.
$form['fb_canvas_form_handler']['#action_old'] = $form['action'];
if ($form['#action'] == '') {
$form['#action'] = $_GET['q'];
}
$form['#action'] = _fb_canvas_make_form_action_local($form['#action']);
$form['fb_canvas_form_handler']['#action_new'] = $form['#action'];
// We've stored #action_old and #action_new so custom modules have the option to change it back.
}
}
else {
if (fb_canvas_is_iframe()) {
//dpm($form, 'fb_canvas_form_alter');
}
}
}
/**
* Call this from your form_alter hook to prevent changes to the
* form's default action.
*/
function fb_canvas_form_action_via_facebook(&$form) {
if ($form['fb_canvas_form_handler']) {
$form['#action'] = $form['fb_canvas_form_handler']['#action_old'];
}
$form['fb_canvas_form_handler'] = array();
}
function fb_canvas_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
if ($op == 'view' && $node->type == 'fb_app') {
if (user_access('administer fb apps')) {
$fb_app = $node->fb_app;
$output = theme('dl', array(
t('Canvas URL') => "http://apps.facebook.com/{$fb_app->canvas}",
));
$node->content['fb_canvas'] = array(
'#value' => $output,
'#weight' => 2,
);
}
}
}
function fb_canvas_footer($is_front) {
if (fb_canvas_is_fbml()) {
// Add FBJS only to FBML pages.
global $fb, $fb_app;
$data = array(
'fb' => $fb,
'fb_app' => $fb_app,
);
$extra = fb_invoke(FB_OP_CANVAS_FBJS_INIT, $data, array());
//dpm($extra, "FB_OP_CANVAS_FBJS_INIT returning"); // XXX
if (count($extra)) {
$extra_js = implode("\n", $extra);
fb_add_js('', '');
// prime javascript
drupal_add_js($extra_js, 'inline', 'fbml');
}
}
}
function fb_canvas_is_fbml() {
global $fb, $fb_app;
if ($fb && $fb_app) {
// Facebook events are not canvas pages
if (arg(0) == 'fb_app' && arg(1) == 'event') {
return FALSE;
}
else {
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;
}
/**
* Uses $fb->redirect on canvas pages, otherwise drupal_goto.
*/
function fb_canvas_goto($path) {
global $fb, $fb_app;
if ($fb && (fb_canvas_is_fbml() || fb_canvas_is_iframe())) {
$url = fb_canvas_fix_url(url($path, NULL, NULL, TRUE), $fb_app);
$fb
->redirect($url);
}
else {
drupal_goto($path);
}
exit;
}
/**
* Convert a local fully qualified path to a facebook app path. This needs to
* be used internally, to fix drupal_gotos upon form submission. Third party
* modules should not need to call this, 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;
}
/**
* 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();
}
}
/**
* This function uses regular expressions to convert links on canvas pages
* to URLs that begin http://apps.facebook.com/...
*
* Call this method from themes when producing either FBML or iframe canvas
* pages. This is a relatively expensive operation. Its unfortunate that we
* must do it on every page request. However to the best of my knowledge,
* Drupal provides no better way.
*
* @param $output is the page (or iframe block) about to be returned.
*
* @param $add_target will cause target=_top to be added when producing an
* iframe.
*
*/
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
// Links ending in #something:
$patterns[] = "|=\"{$base}([^\"]*#)|";
$replacements[] = "=\"/{$fb_app->canvas}/\$1app{$fb_app->id}_";
// Other links
$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).
if ($base_path != "/{$fb_app->canvas}/") {
$patterns[] = '|="' . $base_path . "(?!{$fb_app->canvas})|";
$replacements[] = '="' . $base_url . '/';
}
// Experimental! Change 1234@facebook to an <fb:name> tag. This is our
// default user name convention when creating new users. Ideally, this
// would be accomplished with something like:
// http://drupal.org/node/102679. In the meantime, this may help for
// canvas pages only.
// Regexp avoids "1234@facebook" (surrounded by quotes) because that can
// appear in some forms. Also avoids 1234@facebook.com, which can also
// appear in forms because it is used in authmaps. TODO: investigate
// the efficiency of this regexp (and/or make it optional)
$patterns[] = '|(?<!["\\d])([\\d]*)@facebook(?!\\.com)|';
$replacements[] = '<fb:name uid=$1 linked=false ifcantsee="$1@facebook" useyou=false />';
}
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
}
}
if (count($patterns)) {
$return = preg_replace($patterns, $replacements, $output);
return $return;
}
else {
return $output;
}
}
//This API needs testing and may need to be improved...
/**
* Similar to fb_canvas_process, this also uses regular expressions to alter
* link destinations. Use this function when producing FBML for a profile box
* or news feed, and the pages need to link to canvas pages rather than the
* default URL.
*
*/
function fb_canvas_process_fbml($output, $fb_app) {
$patterns = array();
$replacements = array();
$base = url();
// short URL with rewrite applied.
if ($fb_app->canvas) {
// Change links to use canvas on Facebook
$patterns[] = "|href=\"{$base}|";
$replacements[] = "href=\"http://apps.facebook.com/{$fb_app->canvas}/";
}
if (count($patterns)) {
$return = preg_replace($patterns, $replacements, $output);
return $return;
}
else {
return $output;
}
}
Functions
Name | Description |
---|---|
fb_canvas_fb | Implementation of hook_fb. |
fb_canvas_fix_url | Convert a local fully qualified path to a facebook app path. This needs to be used internally, to fix drupal_gotos upon form submission. Third party modules should not need to call this, I believe. |
fb_canvas_footer | |
fb_canvas_form_action_via_facebook | Call this from your form_alter hook to prevent changes to the form's default action. |
fb_canvas_form_alter | Implementation of hook_form_alter. |
fb_canvas_goto | Uses $fb->redirect on canvas pages, otherwise drupal_goto. |
fb_canvas_handling_form | |
fb_canvas_is_fbml | |
fb_canvas_is_iframe | |
fb_canvas_nodeapi | |
fb_canvas_page_type | Returns the 'type' of the page. This helps themes determine whether they are to provide an iframe or an iframe within FBML. |
fb_canvas_primary_links | |
fb_canvas_process | This function uses regular expressions to convert links on canvas pages to URLs that begin http://apps.facebook.com/... |
fb_canvas_process_fbml | Similar to fb_canvas_process, this also uses regular expressions to alter link destinations. Use this function when producing FBML for a profile box or news feed, and the pages need to link to canvas pages rather than the default URL. |
fb_canvas_secondary_links | |
_fb_canvas_make_form_action_local |