'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) && ($apikey = fb_settings(FB_SETTINGS_APIKEY))) { // This is the proper way to discover the app on canvas pages. $fb_app = fb_get_app(array('apikey' => $apikey)); // If were on an iframe excursion, the fb_sig will be in session. if (!isset($_REQUEST['fb_sig']) && isset($_SESSION['fb_canvas_fb_sig'])) { foreach ($_SESSION['fb_canvas_fb_params'] as $k => $value) { $key = 'fb_sig_' . $k; if (!isset($_REQUEST[$key])) { // We use $_REQUEST, facebook uses $_GET. $_REQUEST[$key] = $value; $_GET[$key] = $value; } } $_REQUEST['fb_sig'] = $_SESSION['fb_canvas_fb_sig']; $_GET['fb_sig'] = $_SESSION['fb_canvas_fb_sig']; } } elseif ($id = fb_settings(FB_SETTINGS_CB)) { // This is the old way dependent on url rewriting. $fb_app = fb_get_app(array('id' => $id)); } } else { // Fallback in case fb_settings not used. if (isset($_REQUEST[FB_APP_REQ_API_KEY]) && ($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)); } } if ($fb_app) $return = $fb_app; } elseif ($op == FB_OP_INITIALIZE) { // Get our configuration settings. $fb_app_data = fb_get_app_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; $use_ob = variable_get(FB_CANVAS_VAR_PROCESS_FBML, TRUE); } elseif (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. $conf['admin_theme'] = $custom_theme; if ($fb_canvas_data['require_login'] == FB_CANVAS_OPTION_REQUIRE_LOGIN) { // The application is configured to require login on all canvas pages. // However, there are exceptions. if (fb_is_profile_tab()) { // Redirects are not allowed for the profile tab. } else { // There may be other exceptions, for example some ajax callbacks. Potential todo item. $fb->require_login(); } } // Remember the user id. If fb_user.module changes it, we'll need to refresh the page. See FB_OP_POST_INIT. $original_uid = $user->uid; // Hack to init the theme before _drupal_maintenance_theme initializes the wrong one. if (variable_get('site_offline', FALSE)) { $dummy = theme('dummy'); } } // Use buffer for form posts. if (!$use_ob && fb_canvas_handling_form()) { $use_ob = variable_get(FB_CANVAS_VAR_PROCESS_FBML_FORM, TRUE); } // 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->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)); } } elseif ($op == FB_OP_POST_INIT) { //dpm($_REQUEST, "request post init"); if (isset($original_uid) && $original_uid != $user->uid) { // The user has changed, presumably fb_user.module recognized the facebookuser. We need to refresh canvas pages. if (!(arg(0) == 'fb_app' && arg(1) == 'event')) { // In order to ensure that drupal handles // permissions properly, the user must make the request all over // again. Skip this for the profile tab, as facebook does not allow // redirects (or persistent session) there. if ((fb_canvas_is_fbml() || fb_canvas_is_iframe()) && !fb_is_profile_tab()) { fb_canvas_goto(fb_scrub_urls($_REQUEST['q'])); } } } // 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']; } // Give FBML themes a chance to render tabs. if (fb_canvas_is_fbml()) { _fb_canvas_menu_hack(); } // 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 ($GLOBALS['fb_canvas_post_process']) { $output = ob_get_contents(); ob_end_clean(); if (fb_canvas_is_fbml()) { $output = fb_canvas_process($output, array('force_absolute_canvas' => TRUE)); } elseif (fb_canvas_is_iframe()) { $output = fb_canvas_process($output, array('add_target' => TRUE)); } } if (fb_canvas_handling_form() && fb_canvas_handling_form() !== 'avoid_loop' && $output) { // Avoid infinite loop if we get called from drupal_goto. fb_canvas_handling_form('avoid_loop'); // Special handling for forms submitted to Drupal from FBML canvas pages. // Cache the results to show the user later. $token = uniqid('fb_'); $cid = session_id() . "_$token"; cache_set($cid, $output, 'cache_page', time() + (60 * 5), drupal_get_headers()); // (60 * 5) == 5 minutes $dest = 'http://apps.facebook.com/' . $fb_app->canvas . "/fb/form_cache/$cid"; // $fb->redirect($dest); // 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']); } if (fb_verbose() == 'extreme') { watchdog('fb', "Storing cached form page $cid, then redirecting to $dest, query is " . implode('&', $query)); } drupal_goto($dest, implode('&', $query), NULL, 303); // appears to work } if ((fb_canvas_is_fbml() || 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->redirect($app_destination); } } } if (isset($output)) { print($output); } } } /** * 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 = $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, 'theme_fbml' => 'fb_fbml', 'theme_iframe' => 'fb_fbml', ); return $fb_canvas_data; } /** * Implementation of hook_form_alter. */ function fb_canvas_form_alter(&$form, &$form_state, $form_id) { // Add our settings to the fb_app edit form. if (isset($form['fb_app_data']) && is_array($form['fb_app_data'])) { $fb_app = $form['#fb_app']; $fb_canvas_data = _fb_canvas_get_config($fb_app); $form['fb_app_data']['fb_canvas'] = array( '#type' => 'fieldset', '#collapsible' => TRUE, '#collapsed' => TRUE, '#title' => t('Facebook canvas pages'), '#description' => t('Settings which apply to canvas pages.', array('!url' => 'http://developers.facebook.com/get_started.php?tab=anatomy#canvas')), ); $form['fb_app_data']['fb_canvas']['require_login'] = array( '#type' => 'radios', '#title' => t('Require authorization'), '#description' => t('Require authorization if you want Drupal for Facebook to call require_login() on every canvas page.'), '#options' => array( FB_CANVAS_OPTION_ALLOW_ANON => t('Allow anonymous visitors'), FB_CANVAS_OPTION_REQUIRE_LOGIN => t('Require all users to authorize the application'), ), '#default_value' => $fb_canvas_data['require_login'], '#required' => TRUE, ); $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 Public Canvas Pages.', 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 authorized users of this application'), '#description' => t('Leave blank to use the site-wide front page.'), '#default_value' => $fb_canvas_data['front_added'], ); // 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'], ); } // 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'])) { // Use the form data structure to keep track of whether data is // posted directly to us, via absolute URL, or instead via // relative URL on apps.facebook.com/... $form['fb_canvas_form_handler'] = array( '#action_fb' => $form['#action'], '#action_local' => _fb_canvas_make_form_action_local($form['#action']), ); // This helper function allows third-party modules to control // where the post goes. By default use local server. fb_canvas_form_action_via_facebook($form, FALSE); } // Drupal includes wacky markup for javascript junk in node forms. It // makes things look terrible in FBML. It only works when javascript is // enabled and it should have been implemented to degrade gracefully, but // it wasn't. if (isset($form['body_field'])) { unset($form['body_field']['teaser_js']); unset($form['body_field']['teaser_include']); } } elseif (fb_canvas_is_iframe()) { // Include the fb_sig so that when post is received, we know we are still in a canvas. foreach ($_REQUEST as $key => $value) { if (strpos($key, 'fb_sig') === 0) { $form['fb_canvas_iframe'][$key] = array( '#type' => 'hidden', '#value' => $value, ); } } } } /** * Call this from your form_alter hook to prevent changes to the * form's default action. */ function fb_canvas_form_action_via_facebook(&$form, $use_facebook = TRUE) { if (isset($form['fb_canvas_form_handler'])) { if ($use_facebook) { $form['#action'] = $form['fb_canvas_form_handler']['#action_fb']; $form['fb_canvas_form_handler']['_fb_handling_form'] = array( '#value' => FALSE, '#type' => 'hidden'); } else { $form['#action'] = $form['fb_canvas_form_handler']['#action_local']; $form['fb_canvas_form_handler']['_fb_handling_form'] = array( '#value' => TRUE, '#type' => 'hidden'); } } } /** * Implementation of hook_footer(). * * Invoke FB_OP_CANVAS_FBJS_INIT, allowing other modules to insert * FBJS to the current page. */ function fb_canvas_footer($main = 0) { 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()); 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($value = NULL) { global $_fb; // Test whether a form has been submitted via facebook canvas page. if ($_fb && isset($_REQUEST['_fb_handling_form'])) { $result = $_REQUEST['_fb_handling_form']; } if (isset($value)) { $_REQUEST['_fb_handling_form'] = $value; } return $result; } /** * This is intended to help sites which have some FBML and some iframe pages. * Makes the current FBML canvas page an iframe. And attempts to preserve * session while visiting multiple iframe pages. * * Set $conf['fb_session_cookieless_iframe'] = TRUE for best results (but * perhaps less session security). */ function fb_canvas_iframe_excursion() { if (fb_canvas_is_fbml()) { $query = $_GET; unset($query['q']); $path = $_GET['q']; $query['XDEBUG_SESSION_START']=1; if (function_exists('fb_settings')) { fb_settings(FB_SETTINGS_CB, $GLOBALS['_fb_app']->label); fb_settings(FB_SETTINGS_CB_SESSION, fb_settings(FB_SETTINGS_SESSION_KEY)); } // Make the FBML page a canvas page. print (''); unset($_SESSION['fb_canvas_fb_sig']); unset($_SESSION['fb_canvas_fb_params']); exit(); } else if (fb_canvas_is_iframe()) { // Add to session paramaters that iframes will need. if (!isset($_SESSION['fb_canvas_fb_sig'])) { $_SESSION['fb_canvas_fb_params'] = $GLOBALS['_fb']->get_valid_fb_params($_REQUEST); $_SESSION['fb_canvas_fb_sig'] = $_REQUEST['fb_sig']; } if (module_exists('fb_connect')) { // Resize the iframe. fb_connect_require_feature('CanvasUtil'); fb_connect_init_js("FB.CanvasClient.startTimerToSizeToContent();"); } } } function fb_canvas_iframe_excursion_end() { //fb_settings(FB_SETTINGS_CB, NULL); //fb_settings(FB_SETTINGS_CB_SESSION, NULL); } // Recent changes may have broken the "old way". function _fb_canvas_make_form_action_local($action = NULL) { global $base_path; if (!isset($action)) { // Is this ever reached? if (function_exists('dpm')) dpm("_fb_canvas_make_form_action_local($action)"); // XXX $action = $_GET['q']; } // If action is fully qualified, do not change it if (strpos($action, ':')) { if (function_exists('dpm')) dpm($action, "leaving form action untouched"); // XXX 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) { if (fb_verbose()) { watchdog('fb_canvas', "fixing badly formed action: " . $action); } $action = array_shift($parts) . '?' . array_shift($parts); $action .= '&' . implode('&', $parts); } $relative = url(''); $absolute = url('', array('absolute' => TRUE)); $base_path = base_path(); // By default, '#action' will be request_uri(), which starts with base_path(); global $_fb_app; if (strpos($action, FB_SETTINGS_CB)) { // XXX old way. Will go away. $action = $absolute . substr($action, strlen($relative)); } elseif (strpos($action, $relative) === 0 && function_exists('fb_url_inbound_alter')) { // XXX another old way. // Replace relative action with absolute. // Include fb settings // TODO: FB_SETTINGS_CB_TYPE $action = $absolute . FB_SETTINGS_CB . '/' . $_fb_app->label . '/' . substr($action, strlen($relative)); } else { // New way... if (strpos($action, $relative) === 0) { // If here, the action was made by call to url() and we prepended // canvas_name. Now remove the canvas name for a proper absolute url. // (Comment forms reach this clause but node forms do not.) $action = substr($action, strlen($relative)); } elseif ($base_path && strpos($action, $base_path) === 0) { $action = substr($action, strlen($base_path)); } $action = url(ltrim($action, '/'), array('absolute' => TRUE)); } // Changed code to work when $base_path set. Need to test on server where base_path is ''. //dpm($action, "_fb_canvas_make_form_action_local returning
"); return $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, 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->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 excursions. $patterns[] = "|{$base_url}/" . FB_SETTINGS_CB . "/{$fb_app->label}/|"; // Here we hard-wire apps.facebook.com. Is there an API to get that? $replacements[] = "http://apps.facebook.com/{$fb_app->canvas}/"; // Page type helps themes support multiple iframes. $patterns[] = "|" . FB_SETTINGS_CB_TYPE . "/[^/]*/|"; $replacements[] = ""; $patterns[] = "|" . FB_SETTINGS_CB_SESSION . "/[^/]*/|"; $replacements[] = ""; // 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. */ 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); } } function fb_canvas_url_outbound_alter(&$path, &$options, $original_path) { global $_fb, $_fb_app; if ($_fb_app && $_fb_app->canvas) { if (fb_canvas_is_fbml()) { if (!$options['absolute']) { if (FALSE && file_exists($path)) { // Disabled on purpose // This is disabled because Drupal is unlikely to honor the changes // we make here. Sadly, routines like theme_image() do not honor url // alters. // Make absolute link to local file. $options['absolute'] = TRUE; } else { // Make a relative link to the application. // base_path is not in options, so we kludge a faux absolute url. $options['base_url'] = '/' . $_fb_app->canvas; $options['absolute'] = TRUE; // Note that facebook will prepend canvas_name to our relative url. } } } else if (fb_canvas_is_iframe()) { if (!$options['absolute']) { // Could append all 'fb_sig' params to internal links. But for now we rely on fb_canvas_process. } } // 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 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. * * In Drupal 7.x, there should 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. 'force_absolute_canvas' will turn * relative urls for canvas pages into absolute, for use on profile * tabs. * */ 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(); $base = $base_path . fb_url_outbound_alter($base_before_rewrite, $rewrite_options, ''); // short URL with rewrite applied. } else { // Paving the way to make url alters optional. (may not work properly) $base = $base_path; //print("in fb_canvas_process, base is '$base'."); } if (fb_canvas_is_fbml()) { //dpm($output, "before fb_canvas_process"); // We're producing FBML for a canvas page // Fix for relative images $absolute_base = url('', array('absolute' => TRUE)); $patterns[] = "|src=\"{$base}|"; $replacements[] = "src=\"$absolute_base"; // This fixes places in drupal that use base_url() . something, instead of calling url(). // including links to stylesheets and such. $pattern = "|href=\"{$base}(?!" . preg_quote($_fb_app->canvas) . ")|"; //print ("pattern is $pattern \n\n"); // debug $patterns[] = $pattern; $replacements[] = "href=\"$absolute_base"; // This is needed under the old url rewriting scheme. // Hope to do away with this for good eventually! XXX if (isset($options['force_absolute_canvas']) && $options['force_absolute_canvas'] && $base != '/') { // Change links to use canvas on Facebook // Links ending in #something: $patterns[] = "|=\"{$base}([^\"]*#)|"; $replacements[] = "=\"http://apps.facebook.com/{$_fb_app->canvas}/$1app{$_fb_app->id}_"; // Other links $patterns[] = "|=\"{$base}|"; $replacements[] = "=\"http://apps.facebook.com/{$_fb_app->canvas}/"; } /* else { // 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}/"; } // 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 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 forms and FBML tags. 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[] = '|(?'; } elseif (fb_canvas_is_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 (isset($options['add_target']) && $options['add_target']) { // Add target=_top to all links $patterns[] = "|]*)href=\"|"; $replacements[] = "canvas}/"; } else { // Add target=_top to only external links $patterns[] = "|]*)href=\"([^:\"]*):|"; $replacements[] = " array( 'arguments' => array( 'fbu' => NULL, 'object' => NULL, 'orig_username' => NULL, ), ), 'fb_canvas_fbml_user_picture' => array( 'arguments' => array( 'fbu' => NULL, 'account' => NULL, 'orig' => NULL, ), ), ); } /** * Implementation of hook_theme_registry_alter(). * * Wrap original theme functions in our overrides. */ function fb_canvas_theme_registry_alter(&$theme_registry) { // Ideally, we'd do this only on themes which will certainly be used for FBML canvas pages. if ($theme_registry['username']['type'] == 'module') { // Override theme_username $theme_registry['fb_canvas_username_orig'] = $theme_registry['username']; $theme_registry['username'] = array( 'arguments' => array('object' => NULL), 'function' => 'fb_canvas_theme_username_override', 'type' => 'module', ); } if ($theme_registry['user_picture']['type'] == 'module') { // Override theme_user_picture $theme_registry['fb_canvas_user_picture_orig'] = $theme_registry['user_picture']; $theme_registry['user_picture'] = array( 'arguments' => array('account' => NULL), 'function' => 'fb_canvas_theme_user_picture_override', 'type' => 'module', ); } } /** * Build tabs in facebook-aware theme. */ function _fb_canvas_menu_hack() { if (fb_canvas_is_fbml()) { // We have to go out of our way here to theme the tabs. // The code in menu.inc that themes them is complex, // incomprehensible, and tangles the theme layer with the logic // layer. It doesn't help that the same theme functions are called // for tabs as are called for all other menus. So we use a global // to keep track of what we're doing. global $_fb_canvas_state; $_fb_canvas_state = 'tabs'; // Why does a call to menu_tab_root_path theme the tabs? I have no // idea, but it does and caches the result. menu_tab_root_path(); $_fb_canvas_state = NULL; } } /** * Our replacement for theme('username', ...) */ function fb_canvas_theme_username_override($object) { $orig = theme('fb_canvas_username_orig', $object); // Make no changes to non-FBML pages. if (fb_canvas_is_fbml() && ($fbu = fb_get_object_fbu($object))) { // Theme the username with FBML, using original username as backup. return theme('fb_canvas_fbml_username', $fbu, $object, $orig); } else { return $orig; } } /** * Theme FBML markup for username. */ function theme_fb_canvas_fbml_username($fbu, $object, $orig) { if (!$fbu) return $orig; $wrap_pre = ''; $wrap_post = ''; if ($object->uid && user_access('access user profiles')) { // Provide link if local account. $wrap_pre = $wrap_pre . ''; $wrap_post = '' . $wrap_post; $ifcantsee = 'ifcantsee="' . addslashes(check_plain($object->name)) . '"'; } $fbml = ""; return $wrap_pre . $fbml . $wrap_post; } /** * Our replacement for theme('user_picture', ...) */ function fb_canvas_theme_user_picture_override($object) { $orig = theme('fb_canvas_user_picture_orig', $object); // Respect Drupal's profile pic, if uploaded. if (isset($object->picture) && $object->picture) { return $orig; } // Make no changes to non-FBML pages. if (fb_canvas_is_fbml() && ($fbu = fb_get_object_fbu($object))) { // Theme the username with FBML, using original username as backup. return theme('fb_canvas_fbml_user_picture', $fbu, $object, $orig); } else { return $orig; } } /** * Theme FBML markup for user_picture. */ function theme_fb_canvas_fbml_user_picture($fbu, $object, $orig) { if (!$fbu) return $orig; $fbml = ""; $wrap_pre = ''; $wrap_post = ''; if ($object->uid && user_access('access user profiles')) { // Provide link if local account. $wrap_pre = $wrap_pre . ''; $wrap_post = '' . $wrap_post; } return $wrap_pre . $fbml . $wrap_post; }