You are here

fb_user_app.module in Drupal for Facebook 7.3

Same filename and directory in other branches
  1. 6.3 contrib/fb_user_app.module

This module manages relations between local Drupal user accounts and their accounts on facebook.com by application.

Drupal refers to a local user id as 'uid'. Facebook's documentation and code also uses 'uid'. In these modules we use 'fbu' for facebook's id and 'uid' for Drupal's id.

File

contrib/fb_user_app.module
View source
<?php

/**
 * @file
 * This module manages relations between local Drupal user accounts
 * and their accounts on facebook.com by application.
 *
 * Drupal refers to a local user id as 'uid'.  Facebook's documentation
 * and code also uses 'uid'.  In these modules we use 'fbu' for facebook's
 * id and 'uid' for Drupal's id.
 */
define('FB_USER_APP_VAR_TRACK_EVERY_PAGE', 'fb_user_app_track_every_page');
define('FB_USER_APP_VAR_USERS_THAT_GRANT_OFFLINE', 'fb_user_app_users_that_grant_offline');
define('FB_USER_APP_VAR_TRACK_USERS', 'fb_user_app_track_users');
define('FB_USER_APP_VAR_TRACK_PAGES', 'fb_user_app_track_pages');

//// Menu structure.

/**
 * Implementation of hook_menu().
 */
function fb_user_app_menu() {
  $items = array();

  // Admin pages overview.
  $items[FB_PATH_ADMIN . "/fb_user_app"] = array(
    'title' => 'Tracking',
    'description' => 'Settings that track statistics in Drupal for Facebook',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'fb_user_app_admin_settings',
    ),
    'access arguments' => array(
      FB_PERM_ADMINISTER,
    ),
    'file' => 'fb_user_app.admin.inc',
    'type' => MENU_LOCAL_TASK,
  );
  return $items;
}

/**
 * Implementation of hook_fb()
 */
function fb_user_app_fb($op, $data, &$return) {
  $fb_app = isset($data['fb_app']) ? $data['fb_app'] : NULL;
  $fb = isset($data['fb']) ? $data['fb'] : NULL;
  global $user;
  if ($op == FB_OP_APP_IS_AUTHORIZED && variable_get(FB_USER_APP_VAR_TRACK_EVERY_PAGE, FALSE)) {

    // This hook is called on every page request, if the user has authorized
    // the app/page and permission has been granted in the settings. We used
    // to create accounts and maps here.  That code is now
    // in FB_OP_AJAX_EVENT, because it turns out this hook is invoked even on
    // page not found and access denied pages.
    fb_user_app_track($fb, $fb_app, $user);
  }
  elseif ($op == FB_APP_OP_EVENT) {

    // Facebook has notified us of some event.
    // We handle some of the events here.
    $event_type = $data['event_type'];

    // Ensure fb_user_app table accurately reflects whether user has authorized.
    if ($event_type == FB_APP_EVENT_POST_AUTHORIZE) {

      // Track new facebook user, $GLOBAL['user'] not valid during post-authorize.
      fb_user_app_track($fb, $fb_app);
    }
    elseif ($event_type == FB_APP_EVENT_POST_REMOVE) {
      $fbu = fb_settings(FB_SETTINGS_FBU);

      // User has removed the app from their account.
      db_query("DELETE FROM {fb_user_app} WHERE apikey=:apikey AND fbu=:fbu", array(
        ':apikey' => $fb_app->apikey,
        ':fbu' => $fbu,
      ));
    }
  }
  elseif ($op == FB_OP_GET_USER_SESSION) {

    // The fb module is asking for session login information.  For example, to
    // log in as the user when not on a canvas page.  This module may be able
    // to provide it, depending on whether the user has logged in, and whether
    // the session has expired.
    $fbu = $data['fbu'];
    $result = db_query("SELECT * FROM {fb_user_app} WHERE apikey = :apikey and fbu = :fbu", array(
      ':apikey' => $fb_app->apikey,
      ':fbu' => $fbu,
    ));
    $data = $result
      ->fetchObject();
    if ($data && $data->session_key) {

      // Return array with FB id and apikey.
      $return = array(
        'fbu' => $data->fbu,
        'access_token' => $data->session_key,
        'expires' => $data->session_key_expires,
      );
    }
  }
  elseif ($op == FB_OP_AJAX_EVENT) {

    // handle internal login
    // @TODO - global user is not correct here.
    // fb.js has notified us of an event via AJAX.  Not the same as facebook event callback above.
    if ($data['event_type'] == 'session_change' && isset($data['event_data']['fbu'])) {

      // A user has logged in.
      fb_user_app_track($fb, $fb_app, $user);
    }
  }
}

/**
 * Implements hook_user_delete().
 *
 * @TODO confirm there is no race condition between this module and fb_user.
 * That is, during delete, does fb_get_fbu() still work?
 */
function fb_user_app_user_delete($account) {

  // Given the uid, fetch the fbu so that we can delete
  $fbu = fb_get_fbu($account->uid);
  db_query('DELETE FROM {fb_user_app} WHERE fbu=:fbu', array(
    ':fbu' => $fbu,
  ));
}

/**
 * Keep track of when the user has visited the app.
 *
 * Historically we could learn a user's ID even if they hadn't authorized
 * ("added") the app.  No longer the case, so all entries in fb_user_app
 * should be for authorized users.
 *
 * A "signed request" should be fully-formed (have an oauth_token) on canvas
 * pages, and on post authorize events (for as long as facebook continues to
 * support them).  So this tracking will work best for canvas page apps and
 * less reliably for connect.
 */
function fb_user_app_track($fb, $fb_app) {

  // Coming from a user adding the app or a page adding the app?
  $fb_user_type = "user";
  $fbu = fb_facebook_user($fb);
  if (array_key_exists('fb_sig_page_added', $_REQUEST)) {

    // It's a post-authorize event for app added to page.
    $fb_user_type = "page";
    $fbu = $_REQUEST['fb_sig_page_id'];
  }
  $sr = $fb
    ->getSignedRequest();

  //watchdog('fb_user_app', __FUNCTION__ . " signed request is <pre>" . print_r($sr,1) . "</pre>"); // debug
  if (isset($sr['oauth_token'])) {
    $access_token = $sr['oauth_token'];
    $expires = $sr['expires'];
    $fbu = $sr['user_id'];
  }
  else {

    // @TODO: with new SDK, is there any useful tracking info?
    return;
  }

  // when 'expires' == 0 app has been granted offline access
  if ($fb_user_type == 'user' && $expires != 0 && variable_get(FB_USER_APP_VAR_USERS_THAT_GRANT_OFFLINE, FALSE)) {

    // Note, with new SDK, facebook provides 'expires' date even when user HAS GRANTED offline_access!
    // @TODO: find some way to tell whether an access token will actually expire!
    return;
  }

  // Track this event only if allowed to and only for users, not pages
  if (variable_get(FB_USER_APP_VAR_TRACK_USERS, TRUE) && ($fb_user_type = "user") || variable_get(FB_USER_APP_VAR_TRACK_PAGES, TRUE) && ($fb_user_type = "page")) {
    $result1 = db_query("UPDATE {fb_user_app} SET time_access=:time, session_key=:token, session_key_expires=:expires, user_type=:type WHERE apikey=:apikey AND fbu=:fbu", array(
      ':time' => REQUEST_TIME,
      ':token' => $access_token,
      ':expires' => $expires,
      ':type' => $fb_user_type,
      ':apikey' => $fb_app->id,
      ':fbu' => fb_facebook_user($fb),
    ));
    if ($result1 && $result1
      ->rowCount() == 0) {

      // The row for this user was never inserted, or it was deleted, or the times were the same.
      $fbu = fb_facebook_user($fb);
      if ($fbu) {

        // First make sure it was not just the same time
        $result = db_query("SELECT * FROM {fb_user_app} WHERE apikey=:apikey AND fbu=:fbu", array(
          ':apikey' => $fb_app->apikey,
          ':fbu' => $fbu,
        ));
        if (!$result
          ->fetchObject()) {

          //This row does not exist, even with the same time.  Insert now
          list($data) = fb_fql_query($fb, "SELECT name, is_app_user, email, proxied_email FROM user WHERE uid={$fbu}", array(
            'access_Token' => $fb_session_key,
          ));

          //watchdog('fb_user_app', "fb user data <pre>" . print_r($data, 1) . '</pre>');
          $fb_user_type = "user";
          $result = db_query("INSERT INTO {fb_user_app} (apikey, fbu, added, user_type, session_key, session_key_expires, time_access, proxied_email, time_cron) VALUES (:apikey, :fbu, :added, :user_type, :session_key, :session_key_expires, :time_access, :proxied_email, :time_cron)", array(
            ':apikey' => $fb_app->apikey,
            ':fbu' => $fbu,
            ':added' => $data['is_app_user'],
            ':user_type' => $fb_user_type,
            ':session_key' => $access_token,
            ':session_key_expires' => $expires,
            ':time_access' => REQUEST_TIME,
            ':proxied_email' => $data['email'] ? $data['email'] : ($data['proxied_email'] ? $data['proxied_email'] : ''),
            // test accounts will not have
            ':time_cron' => 0,
          ));
        }
      }
    }
    if (FALSE && $result === FALSE) {

      // XXX upgrade to D7???
      watchdog('fb_user_app', "Failed to update fb_user_app table.", array(), WATCHDOG_ERROR);
    }
  }
}

/**
 * Learn the user's proxied email address.
 *
 */
function fb_user_app_get_proxied_email($fbu, $fb_app) {

  // Try to learn from local database
  $result = db_query("SELECT * FROM {fb_user_app} WHERE apikey=:apikey AND fbu=:fbu", array(
    ':apikey' => $fb_app->apikey,
    ':fbu' => $fbu,
  ));
  if ($data = $result
    ->fetchObject()) {
    $mail = $data->proxied_email;
  }
  if (!isset($mail) || !$mail) {

    // Ask facebook for info.
    $fb = fb_api_init($fb_app);
    $info = fb_users_getInfo(array(
      $fbu,
    ), $fb);
    $data = $info[0];
    $mail = $data['proxied_email'];
    if ($mail && variable_get(FB_USER_APP_VAR_TRACK_USERS, TRUE)) {

      // Store locally.
      $result = db_query("UPDATE {fb_user_app} SET proxied_email=:mail WHERE apikey=:apikey AND fbu=:fbu", array(
        ':mail' => $mail,
        ':apikey' => $fb_app->apikey,
        ':fbu' => $fbu,
      ));
    }
  }
  return $mail;
}

Functions

Namesort descending Description
fb_user_app_fb Implementation of hook_fb()
fb_user_app_get_proxied_email Learn the user's proxied email address.
fb_user_app_menu Implementation of hook_menu().
fb_user_app_track Keep track of when the user has visited the app.
fb_user_app_user_delete Implements hook_user_delete().

Constants

Namesort descending Description
FB_USER_APP_VAR_TRACK_EVERY_PAGE @file This module manages relations between local Drupal user accounts and their accounts on facebook.com by application.
FB_USER_APP_VAR_TRACK_PAGES
FB_USER_APP_VAR_TRACK_USERS
FB_USER_APP_VAR_USERS_THAT_GRANT_OFFLINE