'Canvas Pages',
'description' => 'Configure Canvas Pages',
'page callback' => 'drupal_get_form',
'page arguments' => array('fb_canvas_admin_settings'),
'access arguments' => array(FB_PERM_ADMINISTER),
'file' => 'fb_canvas.admin.inc',
'type' => MENU_LOCAL_TASK,
);
return $items;
}
/**
* Implementation of hook_fb().
*/
function fb_canvas_fb($op, $data, &$return) {
static $original_uid;
global $user;
$fb = isset($data['fb']) ? $data['fb'] : NULL;
$fb_app = isset($data['fb_app']) ? $data['fb_app'] : NULL;
if ($op == FB_OP_CURRENT_APP) {
if (function_exists('fb_settings')) {
if ((fb_settings(FB_SETTINGS_TYPE) == FB_SETTINGS_TYPE_CANVAS)) {
// fb_settings.inc has determined this is a canvas page.
if ($app_key = fb_settings(FB_SETTINGS_CB)) {
// Using fb_url_rewrite.
$fb_app = fb_get_app(array('apikey' => $app_key));
if (!$fb_app) {
// DEPRECATED. For backward compatibility, accept label in FB_SETTINGS_CB
$fb_app = fb_get_app(array('label' => $app_key));
}
}
elseif ($id = fb_settings(FB_SETTINGS_ID)) {
// New SDK includes ID when session is present.
$fb_app = fb_get_app(array('id' => $id));
}
elseif ($apikey = fb_settings(FB_SETTINGS_APIKEY)) {
// Old SDK tells us APIKEY. Deprecated.
$fb_app = fb_get_app(array('apikey' => $apikey));
}
}
}
if ($fb_app) {
$return = $fb_app;
}
}
elseif ($op == FB_OP_INITIALIZE) {
// Get our configuration settings.
$fb_canvas_data = _fb_canvas_get_config($fb_app);
$is_canvas = FALSE;
$use_ob = FALSE;
// Set an app-specific theme.
global $custom_theme; // Set by this function.
if (fb_canvas_is_iframe()) {
$custom_theme = $fb_canvas_data['theme_iframe'];
$is_canvas = TRUE;
$use_ob = variable_get(FB_CANVAS_VAR_PROCESS_IFRAME, TRUE);
}
if ($is_canvas) {
// We are serving a canvas page.
global $conf;
$conf['admin_theme'] = $custom_theme;
// Hack to init the theme before _drupal_maintenance_theme initializes the wrong one.
if (variable_get('site_offline', FALSE)) {
$dummy = theme('dummy');
}
}
// Store entire page in output buffer. Will post-process on exit.
if ($use_ob) {
ob_start();
$GLOBALS['fb_canvas_post_process'] = TRUE;
}
if ($is_canvas &&
$_GET['q'] == drupal_get_normal_path(
variable_get('site_frontpage', 'node'))) {
if ($fb->getUser()) {
$front = $fb_canvas_data['front_added'];
}
else {
$front = $fb_canvas_data['front_anonymous'];
}
if ($front)
menu_set_active_item(drupal_get_normal_path($front));
}
}
elseif ($op == FB_OP_POST_INIT) {
if (fb_canvas_is_iframe()) {
// The ?destination=... url param means something to drupal but something
// else to facebook. If ?fb_canvas_destination=... is set, we honor that.
if (isset($_REQUEST['fb_canvas_destination'])) {
$_REQUEST['destination'] = $_REQUEST['fb_canvas_destination'];
}
// Include our javascript.
drupal_add_js(array(
'fb_canvas' => array(
'fbu' => fb_facebook_user(),
'uid' => $GLOBALS['user']->uid,
'canvas' => $fb_app->canvas,
),
), 'setting');
drupal_add_js(drupal_get_path('module', 'fb_canvas') . '/fb_canvas.js');
}
// Include our admin hooks.
if (fb_is_fb_admin_page()) {
require drupal_get_path('module', 'fb_canvas') . '/fb_canvas.admin.inc';
}
}
elseif ($op == FB_OP_EXIT) {
/* We do some unpleasant stuff in this hook... on FBML canvas
pages we might use $fb->redirect(), in which case other
modules' hook_exit() might not be called.
In other cases we call drupal_goto(), in which case other
modules' hook_exit() might be called twice. I hate to do this
but so far have not figured another way. And so far no
problems... if problems arise, please post to issue queue.
*/
$destination = $return;
if (isset($GLOBALS['fb_canvas_post_process']) &&
$GLOBALS['fb_canvas_post_process']) {
$output = ob_get_contents();
ob_end_clean();
if (fb_canvas_is_iframe()) {
$output = fb_canvas_process($output, array(
'add_target' => TRUE,
'absolute_links' => variable_get(FB_CANVAS_VAR_PROCESS_ABSOLUTE, TRUE),
));
}
}
if (fb_canvas_is_iframe() &&
(!isset($GLOBALS['_fb_canvas_goto']))) {
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.
$app_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.
// Unfortunately, other modules' hook_exit() may not be called.
if (fb_verbose()) {
watchdog('fb_debug', "FB_OP_EXIT on canvas page redirecting to $app_destination (original destination was $destination).");
}
fb_canvas_redirect($app_destination);
}
}
if (isset($output)) {
print($output);
}
}
}
function fb_canvas_redirect($url) {
echo "";
exit;
}
/**
* Is the current request being displayed in an iframe canvas page?
*/
function fb_canvas_is_iframe() {
// Use either parameters passed from facebook, or url rewriting.
return (fb_settings(FB_SETTINGS_TYPE) == FB_SETTINGS_TYPE_CANVAS);
}
/**
* Helper returns configuration for this module, on a per-app basis.
*/
function _fb_canvas_get_config($fb_app) {
$fb_app_data = fb_get_app_data($fb_app);
$fb_canvas_data = isset($fb_app_data['fb_canvas']) ? $fb_app_data['fb_canvas'] : array();
// Merge in defaults
$fb_canvas_data += array(
'require_login' => FB_CANVAS_OPTION_ALLOW_ANON, // @TODO - can this still be supported?
'theme_fbml' => 'fb_fbml',
'theme_iframe' => 'fb_fbml',
'front_anonymous' => NULL,
'front_loggedin' => NULL, // Facebook API no longer supports this.
'front_added' => NULL,
);
return $fb_canvas_data;
}
/**
* Implementation of hook_form_alter.
*/
function fb_canvas_form_alter(&$form, &$form_state, $form_id) {
if (isset($form['fb_app_data']) && is_array($form['fb_app_data'])) {
// Add our settings to the fb_app edit form.
//require 'fb_canvas.admin.inc';
fb_canvas_admin_form_alter($form, $form_state, $form_id);
}
}
/**
* Uses javascript on iframe canvas pages change top frame, otherwise drupal_goto().
*
* @see drupal_goto()
*/
function fb_canvas_goto($path) {
global $_fb, $_fb_app;
if ($_fb && fb_canvas_is_iframe()) {
$url = fb_canvas_fix_url(url($path, array('absolute' => TRUE)), $_fb_app);
// Allow modules to react to the end of the page request before redirecting.
// We do not want this while running update.php.
if (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update') {
$GLOBALS['_fb_canvas_goto'] = TRUE; // prevents fb_canvas_exit from calling redirect.
module_invoke_all('exit', $url);
}
fb_canvas_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.
*/
function fb_canvas_fix_url($url, $fb_app) {
//dpm(debug_backtrace(), "fb_canvas_fix_url($url)");
global $base_url;
// Url rewrites still used for iframe canvas pages.
$patterns[] = "|{$base_url}/" . FB_SETTINGS_CB . "/{$fb_app->apikey}/|";
// Here we hard-wire apps.facebook.com. Is there an API to get that?
$replacements[] = "http://apps.facebook.com/{$fb_app->canvas}/";
// Fully qualified paths.
$patterns[] = "|" . url('', array('absolute' => TRUE)) . "|";
$replacements[] = "http://apps.facebook.com/{$fb_app->canvas}/";
// Facebook will prepend "appNNN_" all our ids
$patterns[] = "|#([^\?]*)|";
$replacements[] = "#app{$fb_app->id}_$1";
$url = preg_replace($patterns, $replacements, $url);
return $url;
}
/**
* Define custom_url_rewrite_outbound() if not defined already.
*
* The bulk of URL rewriting is performed in fb_url_rewrite.inc. That file
* should be included in settings.php. The url rewriting below was originally
* an attempt to define those function here, only for canvas pages. That
* turned out to not be possible, so now the function just changes the
* destination parameter to not confict with facebook's parameter of the same
* name.
*
* For best results, admins should include fb_url_rewrite in their settings.php.
*
* @see fb_url_rewrite.inc
*/
if (!function_exists('custom_url_rewrite_outbound')) {
function custom_url_rewrite_outbound(&$path, &$options, $original_path) {
fb_canvas_url_outbound_alter($path, $options, $original_path);
}
}
/**
* Implements hook_url_outbound_alter().
*
* @param $options
* If $options['fb_canvas'] == TRUE, create an absolute URL to a canvas
* page. The URL will begin http://apps.facebook.com/... Also if
* $options['fb_canvas'] is an application label the url will link to that
* particular application.
*/
function fb_canvas_url_outbound_alter(&$path, &$options, $original_path) {
if (isset($options['fb_canvas']) && is_string($options['fb_canvas'])) {
$fb_app = fb_get_app(array('label' => $options['fb_canvas']));
}
else {
$fb_app = isset($GLOBALS['_fb_app']) ? $GLOBALS['_fb_app'] : NULL;
}
if ($fb_app && isset($fb_app->canvas)) {
if ((!isset($options['fb_url_alter']) || $options['fb_url_alter']) &&
(isset($options['fb_canvas']) && $options['fb_canvas'])) {
// Make a url starting with apps.facebook.com/...
$options['external'] = TRUE;
$options[FB_SETTINGS_CB] = FALSE; // prevent fb_url_rewrite.inc from altering.
$options['absolute'] = TRUE;
$options['base_url'] = 'http://apps.facebook.com/' . $fb_app->canvas;
}
if (fb_canvas_is_iframe()) {
if (!$options['absolute']) {
// Could append session param to internal links. But for now we rely on fb_canvas_process.
}
else {
//dpm($options, "fb_canvas_url_outbound_alter($path)");
}
// Drupal has a habit of adding ?destination=... to some URLs.
// And Facebook for no good reason screws up when you do that.
if ($options['query']) {
$options['query'] = str_replace('destination=', 'fb_canvas_destination=', $options['query']);
}
}
}
}
/**
* 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 iframe canvas
* pages, or from a page_preprocess hook.
*
* This is a relatively expensive operation. In the past, this had to be run
* on all FBML canvas pages. Now with iframe canvas pages, this is not
* strictly needed, but remains useful. If processed, links on the page will
* change the parent frame (the one with the URL shown in the browser). If
* not processed, the links will change only the iframe.
*
* In Drupal 7.x, there may be a way to alter URLs before they are
* rendered. That could provide a more efficient solution. Until
* then we are stuck with this.
*
* @param $output is the page (or iframe block) about to be returned.
*
* @param $options - 'add_target' will cause target=_top to be added
* when producing an iframe. 'absolute_links' will change hrefs with absolute
* URLs to refer to canvas pages.
*
*/
function fb_canvas_process($output, $options = array()) {
global $base_url;
global $_fb, $_fb_app;
$patterns = array();
$replacements = array();
$base_path = base_path();
if ($_fb) {
if (function_exists('fb_url_outbound_alter')) {
$base_before_rewrite = '';
$rewrite_options = array(FB_SETTINGS_CB_SESSION => FALSE, FB_SETTINGS_CB_TYPE => FALSE);
$base = $base_path . fb_url_outbound_alter($base_before_rewrite, $rewrite_options, ''); // short URL with rewrite applied.
}
else {
// If no url_alter, use normal base_path.
$base = $base_path;
}
// Make relative links point to canvas pages.
$patterns[] = "|]*)href=\"{$base}|";
$replacements[] = "canvas}/";
// 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 (isset($options['add_target']) && $options['add_target']) {
// Add target=_top to all links
$patterns[] = "|]*)href=\"|";
$replacements[] = "]*)action=\"([^:\"]*):|";
$replacements[] = "