Skip to content
fb_app.module 16.9 KiB
Newer Older
/**
 * @file
 *   Defines a custom node type that stores a facebook application configuration.
 */

define('FB_APP_REQ_API_KEY', 'fb_sig_api_key');

// Events that a facebook app might need to know about:
define('FB_APP_EVENT_POST_AUTHORIZE', 'post_authorize');
define('FB_APP_EVENT_POST_REMOVE', 'post_remove');
define('FB_APP_PATH_EVENT', 'fb_app/event');
define('FB_APP_OP_EVENT', 'fb_app_event');

function fb_app_fb($op, $data, &$return) {
  $fb = $data['fb'];
  $fb_app = $data['fb_app'];
  if ($op == FB_OP_GET_APP) {
    // Load app data, using the criteria passed in
    if ($data['nid'])
      $fb_app = db_fetch_object(db_query("SELECT * FROM {fb_app} fb INNER JOIN {node} n ON n.nid=fb.nid WHERE fb.nid=%d and status=1",
                                         $data['nid']));
    else if ($data['apikey']) {
      $fb_app = db_fetch_object(db_query("SELECT * FROM {fb_app} fb INNER JOIN {node} n ON n.nid=fb.nid WHERE apikey='%s' and status=1",
                                         $data['apikey']));
    }
    else if ($data['label']) {
      $fb_app = db_fetch_object(db_query("SELECT * FROM {fb_app} fb INNER JOIN {node} n ON n.nid=fb.nid WHERE label='%s' and status=1",
                                         $data['label']));
    }
      $return = $fb_app;
    // If we didn't find the app, maybe someone else has written a module that
    // stores app info in a different way.
  else if ($op == FB_OP_GET_ALL_APPS) {
    // Return all known applications
    $result = _fb_app_query_all();
    while ($app = db_fetch_object($result)) {
      $return[] = $app;
    }
  }
  else if ($op == FB_OP_SET_PROPERTIES) {
    $url = url(FB_SETTINGS_APP_NID . '/' . $fb_app->nid . '/' . FB_APP_PATH_EVENT, NULL, NULL, TRUE) . '/';
    $return['uninstall_url'] = $url . FB_APP_EVENT_POST_REMOVE;
    $return['authorize_url'] = $url . FB_APP_EVENT_POST_AUTHORIZE;
  }
  else if ($op == FB_OP_LIST_PROPERTIES) {
    $return[t('Application Name')] = 'application_name';
    $return[t('About URL')] = 'about_url';
    $return[t('Post-Authorize Callback URL')] = 'authorize_url';
    $return[t('Post-Remove Callback URL')] = 'uninstall_url';
  }
/**
 * hook_menu
 */
function fb_app_menu($may_cache) {
  $items = array();
  if ($may_cache) {
    // Allow facebook to notify on various events, like adding or removing an app.
    $items[] = array('path' => FB_APP_PATH_EVENT,
                     'access' => TRUE,
                     'callback' => 'fb_app_event_cb',
                     'type' => MENU_CALLBACK,                     
    );
  }
  return $items;
}

/**
 * Callback for FB_APP_PATH_EVENT.
 * 
 * We don't act on the events directly.  We pass the information along via
 * hook_fb.  Other modules are thus notified of the event and can take action.
 */
function fb_app_event_cb($event_type) {
  fb_invoke(FB_APP_OP_EVENT, array('event_type' => $event_type,
				   'fb_app' => $GLOBALS['fb_app'],
				   'fb' => $GLOBALS['fb']));
  // This page is called by facebook, not a user's browser.
  print('Thanks Facebook, for your fancy API!');
  exit();
}

function fb_app_node_info() {
  return array('fb_app' =>
               array('name' => t('Facebook Application'),
                     'module' => 'fb_app',
                     'description' => t('Information such as apikey and secret for a facebook.com application.'),
                     'help' => t('Configure the behavior of your Facebook Application.'),
               ),
  );
}

function fb_app_access($op, $node) {
  global $user;
  if (user_access('administer fb apps'))
    return TRUE;
  if ($op == 'create' && user_access('create fb apps'))
    return TRUE;
  else if ($op == 'update' || $op == 'delete') {
    if ($node->uid == $user->uid && 
        user_access('edit own fb apps'))
      return TRUE;
  }
}

function fb_app_perm() {
  return array('administer fb apps', 'create fb apps', 'edit own fb apps');
}

function fb_app_form(&$node, &$param) {
  // Helpful link
  if (!$node->nid) {
    drupal_set_message(t("Before completing this form, <a href=!url>create your application</a> on Facebook.",
                         array('!url' => 'http://www.facebook.com/developers/editapp.php?new')));
  $form = array();
  $type = node_get_types('type', $node);
  // We need to define form elements for the node's title and body. 
  $form['title'] = array('#type' => 'textfield', 
                         '#title' => check_plain($type->title_label), 
                         '#required' => TRUE, 
                         '#default_value' => $node->title, 
                         '#weight' => -5,
                         '#description' => t('Identifies the application to site administrators.'),
  // We want the body and filter elements to be adjacent. We could try doing 
  // this by setting their weights, but another module might add elements to the 
  // form with the same weights and end up between ours. By putting them into a 
  // sub-array together, we're able force them to be rendered together. 
  $form['body_filter']['body'] = 
    array('#type' => 'textarea', 
          '#title' => check_plain($type->body_label), 
          '#default_value' => $node->body, 
          '#required' => FALSE,
          '#description' => 'A brief description of the application.',
    ); 
  $form['body_filter']['filter'] = filter_form($node->format); 
  
  // Now we define the form elements specific to our node type. 
  
  $form['fb_app'] = array('#tree' => TRUE,
                          '#weight' => -4,
                          '#validate' => array('fb_app_validate_fb_app' => array()),
  );
  $form['fb_app']['label'] = array('#type' => 'textfield',
                                   '#title' => t('Label'),
                                   '#required' => TRUE,
                                   '#default_value' => $node->fb_app->label,
                                   '#description' => t('Used behind the scenes, for naming roles, styles, etc.  Use no spaces or weird characters.'),
  );
  $form['fb_app']['apikey'] = array('#type' => 'textfield',
                                    '#title' => t('API Key'),
                                    '#required' => TRUE,
                                    '#default_value' => $node->fb_app->apikey,
                                    '#description' => t('Facebook will generate this value when you create the application.'),
  );
  $form['fb_app']['secret'] = array('#type' => 'textfield',
                                    '#title' => t('Secret'),
                                    '#required' => TRUE,
                                    '#default_value' => $node->fb_app->secret,
                                    '#description' => t('Facebook will generate this value when you create the application.'),
  $form['fb_app']['id'] = array('#type' => 'textfield',
                                '#title' => t('Facebook App ID'),
                                '#required' => FALSE,
                                '#default_value' => $node->fb_app->id,
                                '#description' => t('To learn this number, visit your app\'s About Page (or other links from Facebook\'s Developer App).  The URL will end in ?id=1234...  Enter the number that follows "?id=" here.'),
  );
  // fb_app_data is a placeholder to make it easier for other module to attach
  // various settings to the node.
  $form['fb_app_data'] = array('#tree' => TRUE);  
  // Add our own fields to fb_app_data.  Other modules use hook_form_alter to do this.
  $fb_app_data = fb_app_get_data($node->fb_app);
  $data = $fb_app_data['fb_app'];
  if (!$data)
    $data = array('set_app_props' => TRUE); // defaults

  $form['fb_app_data']['fb_app']['set_app_props'] = array(
    '#type' => 'checkbox',
    '#title' => t('Set Application Properties Automatically'),
    '#description' => t('Automatically update Facebook settings for this application.  Disable to prevent customized settings from being overwritten.'),
    '#default_value' => $data['set_app_props'],
  );

  return $form;
}

function fb_app_validate($node) {
  // TODO: check label is unique, and role name will be unique.
  // check apikey is unique, canvas page is unique
  
  $fb_app = (object) $node->fb_app;
  fb_app_get_app_properties($fb_app);
  if (!$fb_app->application_name) {
    form_set_error('fb_app][apikey', t('Unable to get application properties.  Confirm both apikey and secret.'));
  }
}

function fb_app_validate_fb_app() {
  //dpm(func_get_args(), 'fb_app_validate_fb_app');
}

function fb_app_load($node) {
  $fb_app = db_fetch_object(db_query('SELECT * FROM {fb_app} WHERE nid=%d',
                                   $node->nid));
  $fb_app_data = fb_app_get_data($fb_app);
  return array('fb_app' => $fb_app,
               'fb_app_data' => $fb_app_data,
  );
}

function fb_app_view($node, $teaser=FALSE, $page=FALSE) {
  $node = node_prepare($node, $teaser);
  $fb_app = (object) $node->fb_app; // cast in case of preview
  fb_app_get_app_properties($fb_app);
  // Perhaps this info should be hidden, unless user can edit node.
  if (user_access('administer fb apps')) {
    if($fb_app->edit_url) {
      $node->content['edit_link'] = array(
        '#value' => '<p>' . t('<a href="!url">Edit this application</a> on Facebook.', array('!url' => $fb_app->edit_url)) . '</p>',
        '#type' => 'markup',
      );
    }

    $node->content['fb_app'] = 
      array('#value' => theme('fb_app', $fb_app),
            '#weight' => 1);
function fb_app_get_about_url($fb_app) {
  if ($fb_app->id)
    return url("http://www.facebook.com/apps/application.php", "id=$fb_app->id");
}

function theme_fb_app($fb_app) {
  // Get known properties
  $props_map = fb_invoke(FB_OP_LIST_PROPERTIES, array('fb_app' => $fb_app), array());
  $data = array(
    t('Label') => $fb_app->label,
    t('API Key') => $fb_app->apikey,
    t('Secret') => $fb_app->secret,
  );
  foreach ($props_map as $name => $key) {
    if ($fb_app->$key)
      $data[$name] = $fb_app->$key;
  }
  $output .= theme('dl', $data);
}

// this belongs elsewhere
function theme_dl($items) {
  if (count($items)) {
    $output = "<dl>\n";
    foreach ($items as $term => $data) {
      $output .= "  <dt>$term</dt><dd>$data</dd>\n";
    }
    $output .= "</dl>\n";
    return $output;
  }
}

/**
 * Get properties from Facebook.  Fills in the data that we need to
 * know by querying facebook.
 */
function fb_app_get_app_properties(&$fb_app) {
  static $cache;
  static $props_map;
  if (!isset($cache)) {
    $cache = array();
    // http://wiki.developers.facebook.com/index.php/ApplicationProperties
      t('About URL') => 'about_url',
      t('Application Name') => 'application_name',
      t('Edit URL') => 'edit_url',
    $props_map = fb_invoke(FB_OP_LIST_PROPERTIES, array('fb_app' => $fb_app), $props_map);
    if ($fb = fb_api_init($fb_app, FB_FBU_NO_SESSION)) {
        $props = $fb->api_client->admin_getAppProperties(array_values($props_map));
      } catch (Exception $e) {
        fb_log_exception($e, t('Failed to get application properties from Facebook'));
      }
      $cache[$fb_app->apikey] = $props;
    }
  }
  else {
    $props = $cache[$fb_app->apikey];
  }
  
  // Update $fb_app with the values we got from facebook api.
  foreach ($props_map as $key) {
    if ($props[$key]) {
      $fb_app->$key = $props[$key];
    }
  }
}

function fb_app_set_app_properties($fb_app) {
  $fb_app_data = fb_app_get_data($fb_app);
  if ($fb_app_data['fb_app']['set_app_props']) {
    $props = fb_invoke(FB_OP_SET_PROPERTIES, array('fb_app' => $fb_app), array());
    if (count($props)) {
      if ($fb = fb_api_init($fb_app, FB_FBU_NO_SESSION)) {
        try {
          $fb->api_client->admin_setAppProperties($props);
        } catch (Exception $e) {
          fb_log_exception($e, t('Failed to set application properties on Facebook'));
        }
      }
function fb_app_insert($node) {
  //drupal_set_message("fb_app_insert" . dpr($node,1));
  $fb_app = (object) $node->fb_app;
  fb_app_get_app_properties($fb_app); // Get canvas and possible other props
  $fb_app->data = serialize($node->fb_app_data);
  $fb_app->nid = $node->nid;
  db_query("INSERT INTO {fb_app} (nid, label, apikey, secret, id, canvas, require_login, create_account, unique_account, data) VALUES (%d, '%s', '%s', '%s', '%s', '%s', %d, %d, %d, '%s')",
           $node->nid, $fb_app->label, $fb_app->apikey, $fb_app->secret,
           $fb_app->id,
           $fb_app->canvas_name, $fb_app->require_login, $fb_app->create_account, 
           $fb_app->unique_account, $fb_app->data
  watchdog('fb_app', t('Created Facebook Application %label.',
                       array('%label' => $fb_app->label,
           WATCHDOG_NOTICE,
           l($node->title, 'node/'.$node->nid));
  
  fb_app_set_app_properties($fb_app); // Set callback URL, etc.
}

function fb_app_update($node) {
  $fb_app = (object) $node->fb_app;
  fb_app_get_app_properties($fb_app); // Get canvas and possible other props
  $fb_app->data = serialize($node->fb_app_data);
  $fb_app->nid = $node->nid;
  
  db_query("UPDATE {fb_app} SET label='%s', apikey='%s', secret='%s', id='%s', canvas='%s', require_login=%d, create_account=%d, unique_account=%d, data='%s' WHERE nid=%d",
           $fb_app->label, $fb_app->apikey, $fb_app->secret,
           $fb_app->id,
           $fb_app->canvas_name, $fb_app->require_login, $fb_app->create_account,
           $fb_app->unique_account, $fb_app->data,
  
  fb_app_set_app_properties($fb_app); // Set callback URL, etc.
function fb_app_delete($node) {
  db_query('DELETE FROM {fb_app} WHERE nid=%d',
           $node->nid);
  // Should we unset properties on facebook?
}


/**
 * Convenience method for other modules to attach data to the fb_app object.
 */
function fb_app_get_data(&$fb_app) {
  if (!$fb_app->fb_app_data) {
    $fb_app->fb_app_data = unserialize($fb_app->data);
  }
  return $fb_app->fb_app_data;
}

/**
 * Helper function returns a database query for all apps.
 */
function _fb_app_query_all() {
  $result = db_query("SELECT fb.*, n.title FROM {fb_app} fb INNER JOIN {node} n ON n.nid=fb.nid WHERE status=1");
  return $result;
}
function _fb_app_query_by_label($label) {
  $result = db_query("SELECT fb.*, n.title FROM {fb_app} fb INNER JOIN {node} n ON n.nid=fb.nid WHERE n.status=1 AND fb.label = '%s'", 
                     $label);
  return $result;
}

/**
 * Implementation of hook_user.
 */
function fb_app_user($op, &$edit, &$account, $category = NULL) {
  $items = array();
  if ($op == 'view') {
    $result = _fb_app_query_all();
    while ($fb_app = db_fetch_object($result)) {
      // Learn this user's FB id
      $fbu = fb_get_fbu($account->uid, $fb_app);
      if ($fbu) {
        // The drupal user is a facebook user.  Now, learn more from facebook.
        $fb = fb_api_init($fb_app, FB_FBU_ANY);
Dave Cohen's avatar
Dave Cohen committed
        if (fb_facebook_user($fb)) {
	  try {
	    $info = $fb->api_client->users_getInfo(array($fbu), 
						   array('about_me',
							 'affiliations',
							 'name',
							 'is_app_user',
							 'pic_big',
							 'profile_update_time',
							 'status',
							 ));
	  } catch (Exception $e) {
	    fb_log_exception($e, "Failed to get Facebook user info for account $fbu");
	  }
        if (count($info)) {
          $output = theme('fb_app_user_info', $fb_app, $info[0]);
          
          $items[$fb_app->label] = array('title' => $fb_app->title,
                                         'value' => $output,
                                         'class' => 'fb_app');
        }
        else
          fb_report_errors($fb);
    if (count($items))
      return array(t('Facebook') => $items);
function theme_fb_app_user_info($fb_app, $info) {
  if ($info['pic_big'])
    $output .= '<p><img src="'.$info['pic_big'].'" /></p>';
  $fb_link = l($info['name'], 'http://www.facebook.com/profile.php', NULL, 'id='.$info['uid']);
  if ($info['is_app_user'])
    $output .= '<p>' . t('!fb_link uses %title',
                         array('!fb_link' => $fb_link,
                               '%title' => $fb_app->title)) . '</p>';
    $output .= '<p>'. t('!fb_link does not use %title',
                        array('!fb_link' => $fb_link,
                              '%title' => $fb_app->title)) . '</p>';
function fb_app_token_list($type = 'all') {
  if ($type == 'all' || $type == 'fb' || $type == 'fb_app') {
    $tokens['fb_app']['fb-app-nid'] = t('Facebook application ID');
    $tokens['fb_app']['fb-app-title'] = t('Facebook application title');
    $tokens['fb_app']['fb-app-url'] = t('Facebook application URL (base path)');
  }
  return $tokens;
}

function fb_app_token_values($type = 'all', $object = NULL) {
  if ($type == 'fb_app' && $object) {
    $fb_app = $object;
    $values['fb-app-title'] = $fb_app->title;
    $values['fb-app-nid'] = $fb_app->nid;
    $values['fb-app-url'] = 'http://apps.facebook.com/'.$fb_app->canvas;