$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 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 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(''); $secondary_options = $root_menus; $secondary_options[0] = t(''); $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_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 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[] = '|(?'; } 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[] = "|canvas}/"; } else { // Add target=_top to only external links $patterns[] = "|]*)href=\"([^:\"]*):|"; $replacements[] = "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; } ?>