From 14f29e351acf9e9f49053bb8563d39f06af5077f Mon Sep 17 00:00:00 2001
From: James Williams <james.williams@computerminds.co.uk>
Date: Wed, 3 Aug 2011 15:37:59 +0100
Subject: [PATCH] Ports OG Invite Link to Drupal 7, for OG (aimed at 7.x-1.1-rc3).

Also adds ability to invite non-members. See http://drupal.org/node/1129198 and http://drupal.org/node/1097578. Based on http://drupal.org/sandbox/jameswilliams/1228942. Includes contribution by rafhuys.
---
 og_invite_link.admin.inc |   60 +++++-
 og_invite_link.info      |    5 +-
 og_invite_link.install   |   59 +++--
 og_invite_link.module    |  613 +++++++++++++++++++++++++---------------------
 og_invite_link.pages.inc |  303 ++++++++++++++++-------
 5 files changed, 639 insertions(+), 401 deletions(-)

diff --git a/og_invite_link.admin.inc b/og_invite_link.admin.inc
index d771c97..d7c742e 100644
--- a/og_invite_link.admin.inc
+++ b/og_invite_link.admin.inc
@@ -11,6 +11,15 @@
  */
 function og_invite_link_admin() {
   $form = array();
+
+  $form['og_invite_link_max_invites_per_form'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Maximum invites at a time'),
+    '#description' => t('Set the number of invites that can be sent at any one time. For no limit, set to zero or leave blank.'),
+    '#default_value' => variable_get('og_invite_link_max_invites_per_form'),
+    '#element_validate' => array('_element_validate_integer'),
+  );
+
   //Provide an option for site administrators to choose
   //how long the invitations are valid. Expired ones are
   //just purged at cron.
@@ -22,5 +31,54 @@ function og_invite_link_admin() {
     '#default_value' => variable_get('og_invite_link_expiration', 30),
     '#options' => $options,
   );
+
+  $form['og_settings']['notifications']['og_invite_link_user_subject'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Invite user notification subject'),
+    '#description' => t('The subject of the message sent to users invited to join a group. Available variables: @group, @site, !group_url, @body, @sent-to, @sent-by.'),
+    '#default_value' => _og_invite_link_mail_text('og_invite_link_user_subject'),
+    '#weight' => 15,
+  );
+  $form['og_settings']['notifications']['og_invite_link_user_body'] = array(
+    '#type' => 'textarea',
+    '#title' => t('Invite user notification body'),
+    '#rows' => 10,
+    '#description' => t('The body of the message sent to users invited to join a group. Available variables: @group, @site, !group_url, @body, @sent-to, @sent-by.'),
+    '#default_value' =>  _og_invite_link_mail_text('og_invite_link_user_body'),
+    '#weight' => 16,
+  );
+
+  $form['og_settings']['notifications']['og_invite_link_email_subject'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Invite user by email notification subject'),
+    '#description' => t('The subject of the message sent to people that are not yet site members invited to join a group. Available variables: @group, @site, !group_url, @body, @sent-to, @sent-by.'),
+    '#default_value' => _og_invite_link_mail_text('og_invite_link_email_subject'),
+    '#weight' => 17,
+  );
+  $form['og_settings']['notifications']['og_invite_link_email_body'] = array(
+    '#type' => 'textarea',
+    '#title' => t('Invite user by email notification body'),
+    '#rows' => 10,
+    '#description' => t('The body of the message sent to people that are not yet site members invited to join a group. Available variables: @group, @site, !group_url, @body, @sent-to, @sent-by.'),
+    '#default_value' => _og_invite_link_mail_text('og_invite_link_email_body'),
+    '#weight' => 18,
+  );
+
+  /*// Add a message template for notifying group admins of sent invitations
+  $form['og_settings']['notifications']['og_invite_link_admin_subject'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Group admin invite notification subject'),
+    '#description' => t('The subject of the message sent to group admins whenever a user sends out a group invitation. Available variables: @group, @site, !group_url, @body, @sent-to, @sent-by.'),
+    '#default_value' => _og_invite_link_mail_text('og_invite_link_admin_subject'),
+    '#weight' => 19,
+  );
+  $form['og_settings']['notifications']['og_invite_link_admin_body'] = array(
+    '#type' => 'textarea',
+    '#title' => t('Group admin invite notification body'),
+    '#description' => t('The body of the message sent to group admins whenever a user sends out a group invitation. Available variables: @group, @site, !group_url, @body, @sent-to, @sent-by.'),
+    '#default_value' => _og_invite_link_mail_text('og_invite_link_admin_body'),
+    '#weight' => 20,
+  );*/
+
   return system_settings_form($form);
-}
\ No newline at end of file
+}
diff --git a/og_invite_link.info b/og_invite_link.info
index fe4b060..ab6d2a8 100644
--- a/og_invite_link.info
+++ b/og_invite_link.info
@@ -1,5 +1,6 @@
 name = OG invite link
-description = Replace and improvde OG's group invitation system
+description = Replace and improve OG's group invitation system
 package = "Organic groups"
-core = 6.x
+core = 7.x
 dependencies[] = og
+dependencies[] = og_ui
diff --git a/og_invite_link.install b/og_invite_link.install
index 6791c94..29c7664 100644
--- a/og_invite_link.install
+++ b/og_invite_link.install
@@ -6,11 +6,11 @@
  */
 
 /**
- * Implementation of hook_schema();
+ * Implements hook_schema();
  */
 function og_invite_link_schema() {
-  $schema['og_invite'] = array(
-    'description' => 'Stores the requests for invited members into organic groups.',
+  $schema['og_invite_link'] = array(
+    'description' => 'Stores the requests for invited members into groups.',
     'fields' => array(
       'iid' => array(
         'type' => 'serial',
@@ -18,13 +18,13 @@ function og_invite_link_schema() {
         'not null' => TRUE
       ),
       'uid' => array(
-        'description' => 'The uid of the invited user.',
-        'type' => 'int',
-        'unsigned' => TRUE,
+        'description' => 'The uid or email of the invited user.',
+        'type' => 'varchar',
+        'length' => 255,
         'not null' => TRUE
       ),
-      'group_nid' => array(
-        'description' => 'The group node id into which the user has been invited.',
+      'gid' => array(
+        'description' => 'The group id into which the user has been invited.',
         'type' => 'int',
         'unsigned' => TRUE,
         'not null' => TRUE
@@ -35,13 +35,6 @@ function og_invite_link_schema() {
         'unsigned' => TRUE,
         'not null' => TRUE
       ),
-      'moderated' => array(
-        'description' => 'Boolean indication for if the invitation results in a moderated group acceptance.',
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 0
-      ),
       'invite_key' => array(
         'description' => 'A key used to generate a token that will be used in the invitation link sent on the mail.',
         'type' => 'varchar',
@@ -66,11 +59,12 @@ function og_invite_link_schema() {
         'description' => 'The UNIX timestamp of when the invitation has been accepted.',
         'type' => 'int',
         'unsigned' => TRUE,
-        'not null' => TRUE
+        'not null' => TRUE,
+        'default' => 0
       ),
     ),
     'indexes' => array(
-      'uid_group_nid' => array('uid', 'group_nid'),
+      'uid_gid' => array('uid', 'gid'),
     ),
     'primary key' => array('iid'),
   );
@@ -78,15 +72,28 @@ function og_invite_link_schema() {
 }
 
 /**
- * Implementation of hook_install();
- */
-function og_invite_link_install() {
-  drupal_install_schema('og_invite_link');
-}
-
-/**
- * Implementation of hook_uninstall();
+ * Implements hook_uninstall();
+ *
+ * Removes variables used by the module.
  */
 function og_invite_link_uninstall() {
-  drupal_uninstall_schema('og_invite_link');
+  global $conf;
+  $vars = array(
+    'og_invite_link_expiration',
+    'og_invite_link_admin_subject',
+    'og_invite_link_admin_body',
+    'og_invite_link_user_subject',
+    'og_invite_link_user_body',
+    'og_invite_link_email_subject',
+    'og_invite_link_email_body',
+  );
+
+  db_delete('variable')
+    ->condition('name', 'og_invite_link_*', 'LIKE')
+    ->execute();
+  cache_clear_all('variables', 'cache_bootstrap');
+
+  foreach ($vars as $var) {
+    unset($conf[$var]);
+  }
 }
diff --git a/og_invite_link.module b/og_invite_link.module
index bdf21a1..e308a29 100644
--- a/og_invite_link.module
+++ b/og_invite_link.module
@@ -1,197 +1,255 @@
 <?php
 
 /**
- * Implementation of hook_menu();
+ * Implements hook_og_permission().
+ */
+function og_invite_link_og_permission() {
+  $perms = array();
+
+  $perms['invite'] = array(
+    'title' => t('Invite users'),
+    'description' => t('Invite users to become members of the group.'),
+    'roles' => array(OG_AUTHENTICATED_ROLE),
+    'default role' => array(OG_ADMINISTRATOR_ROLE),
+  );
+
+  return $perms;
+}
+
+/**
+ * Implements hook_menu().
  */
 function og_invite_link_menu() {
-  $items['og/users/%node/invite'] = array(
+  $items['group/%/%/admin/people/invite'] = array(
     'title' => 'Invite members',
-    'access callback' => 'og_invite_link_invite_access',
-    'access arguments' => array(2),
+    'access callback' => 'og_user_access_by_entity',
+    'access arguments' => array('invite', 1, 2),
     'page callback' => 'drupal_get_form',
-    'page arguments' => array('og_invite_link_invite_page_form', 2),
+    'page arguments' => array('og_invite_link_invite_page_form', 1, 2),
     'type' => MENU_LOCAL_TASK,
     'file' => 'og_invite_link.pages.inc',
     'weight' => 8,
   );
-  $items['group/%node/join/%user/%'] = array(
+  $items['group/%/%/join/%/%'] = array(
     'title' => 'Join to group',
     'access callback' => 'og_invite_link_join_access',
-    'access arguments' => array(1, 3, 4),
+    'access arguments' => array(1, 2, 4, 5),
     'page callback' => 'og_invite_link_join',
-    'page arguments' => array(1, 3, 4),
+    'page arguments' => array(1, 2, 4, 5),
     'type' => MENU_CALLBACK,
     'file' => 'og_invite_link.pages.inc',
   );
   $items['og_invite_link/autocomplete'] = array(
-    'title' => 'Autocomplete OG invite link',
+    'title' => 'Autocomplete group invite link',
     'page callback' => 'og_invite_link_autocomplete',
-    'access arguments' => array('access user profiles'), //is this the right permission?
+    'access arguments' => array('access content'),
     'type' => MENU_CALLBACK,
     'file' => 'og_invite_link.pages.inc',
   );
-  $items['admin/og/og_invite_link'] = array(
-    'title' => 'Organic groups invitations',
+  $items['admin/config/group/og_invite_link'] = array(
+    'title' => 'Group invite settings',
     'description' => 'Configure group invitation settings',
     'page callback' => 'drupal_get_form',
     'page arguments' => array('og_invite_link_admin'),
-    'access arguments' => array('administer organic groups'),
+    'access arguments' => array('administer group'),
     'file' => 'og_invite_link.admin.inc',
   );
   return $items;
 }
 
 /**
- * Implementation of hook_menu_alter();
+ * Implements hook_menu_alter().
  */
 function og_invite_link_menu_alter(&$items) {
   // Remove OG's invite page
-  unset($items['og/invite/%node']);
+  unset($items['group/%/%/invite']);
 }
 
 /**
- * Implementation of hook_og_links_alter();
+ * Check if the user has accesss to the join group callback.
+ *
+ * @param string $entity_type
+ *   The group's entity type.
+ * @param int $etid
+ *   The group's entity ID. (e.g. node ID)
+ * @param object $account
+ *   The user object, or an email address.
+ * @param string $token
+ *   The token that has to be validated.
  */
-function og_invite_link_og_links_alter(&$links) {
+function og_invite_link_join_access($entity_type, $etid, $account, $token) {
   global $user;
+  if (is_numeric($account)) {
+    $account = user_load($account);
+    if ($account == FALSE) {
+      return FALSE;
+    }
+  }
 
-  // Remove link to OG's invite page
-  unset($links['invite']);
-
-  // Check if the user has access to the invite form
-  if ($group = og_get_group_context()) {
-    if (og_invite_link_invite_access()) {
-      $links['invite'] = l(t('Invite members'), "og/users/{$group->nid}/invite");
+  // If user is logged in, and doesn't match the UID/email in the URL
+  // then deny access
+  if ($user->uid) {
+    if (is_object($account)) {
+      if ($user->uid != $account->uid) {
+        return FALSE;
+      }
+    }
+    elseif (is_string($account)) {
+      if (empty($user->mail) || $user->mail != $account) {
+        return FALSE;
+      }
     }
   }
+
+  // All other cases will be checked on the page callback, to allow us
+  // to print helpful messages, rather than give an access denied
+
+  return TRUE;
 }
 
 /**
- * Access callback for the invite form page
- *
- * @param $group
- *   Optionally supply the group object, otherwise attempt to detect
- *   the current group
- * @param $user
- *   Optionally supply the user, otherwise use the current user
- * @return
- *   TRUE if the user can access the groups invite page, otherwise FALSE
+ * Implements hook_og_ui_get_group_admin().
  */
-function og_invite_link_invite_access($group = NULL, $user = NULL) {
-  // If needed, get the current user
-  if (!$user) {
-    global $user;
+function og_invite_link_og_ui_get_group_admin($gid) {
+  $items = array();
+  if (og_user_access($gid, 'invite')) {
+    $items['invite'] = array(
+      'title' => t('Invite people'),
+      'description' => t('Invite people to become group members.'),
+      'href' => 'admin/people/invite',
+    );
   }
+  return $items;
+}
 
-  // If needed, get the current group
-  if (!$group) {
-    if (!($group = og_get_group_context())) {
-      return FALSE;
-    }
-  }
+/**
+ * Implements hook_mail().
+ */
+function og_invite_link_mail($key, &$message, $params) {
+  static $users = array();
 
-  // Check that the group is a group
-  if (!og_is_group_type($group->type)) {
-    return FALSE;
-  }
+  switch ($key) {
+    case 'invite_user_to_group':
+      $invitation = $params['invitation'];
+      $group = $params['group'];
 
-  // See if the user is an admin of this group
-  if (og_is_group_admin($group, $user)) {
-    // Admins always have access
-    return TRUE;
-  }
+      // Load the invitation recipient
+      if (!isset($users[$invitation->uid])) {
+        $users[$invitation->uid] = user_load($invitation->uid);
+      }
+      $sent_to = $users[$invitation->uid];
 
-  // Provide access depending on the group type
-  switch ($group->og_selective) {
-    case OG_OPEN:
-    case OG_MODERATED:
-    case OG_INVITE_ONLY:
-      // Members can access the form for these group typess
-      if (og_is_group_member($group->nid, FALSE)) {
-        return TRUE;
+      // Load the invitation sender
+      if (!isset($users[$invitation->sender])) {
+        $users[$invitation->sender] = user_load($invitation->sender);
       }
+      $sent_by = $users[$invitation->sender];
+
+      // Populate the message variables
+      $variables = array(
+        '@group' => check_plain($group->label),
+        '@site' => variable_get('site_name', 'Drupal'),
+        '@body' => $params['additional_message'],
+        '@sent-to' => $sent_to->name,
+        '@sent-by' => $sent_by->name,
+        '!group_url' => url("group/{$group->entity_type}/{$group->etid}/join/{$invitation->uid}/{$invitation->token}", array('absolute' => TRUE)),
+      );
+
+      // Set the message subject and body
+      $message['subject'] = _og_invite_link_mail_text('og_invite_link_user_subject', $variables, $message['language']);
+      $message['body'][] = _og_invite_link_mail_text('og_invite_link_user_body', $variables, $message['language']);
       break;
-    case OG_CLOSED:
-      // Admins were granted access prior to this
+    case 'invite_email_to_group':
+      $invitation = $params['invitation'];
+      $group = $params['group'];
+
+      // Load the invitation sender
+      if (!isset($users[$invitation->sender])) {
+        $users[$invitation->sender] = user_load($invitation->sender);
+      }
+      $sent_by = $users[$invitation->sender];
+
+      // Populate the message variables
+      $variables = array(
+        '@group' => check_plain($group->label),
+        '@site' => variable_get('site_name', 'Drupal'),
+        '@body' => $params['additional_message'],
+        '@sent-to' => $invitation->uid,
+        '@sent-by' => $sent_by->name,
+        '!group_url' => url("group/{$group->entity_type}/{$group->etid}/join/{$invitation->uid}/{$invitation->token}", array('absolute' => TRUE)),
+      );
+
+      // Set the message subject and body
+      $message['subject'] = _og_invite_link_mail_text('og_invite_link_email_subject', $variables, $message['language']);
+      $message['body'][] = _og_invite_link_mail_text('og_invite_link_email_body', $variables, $message['language']);
       break;
   }
-
-  return FALSE;
 }
 
 /**
- * Check if the user has accesss to the join group callback
- *
- * @param object $node
- *   The group node object.
- * @param object $account
- *   The user object.
- * @param string $token
- *   The token that has to be validated.
+ * Implements hook_user_delete().
  */
-function og_invite_link_join_access($node, $account, $token) {
-  global $user;
-
-  // If user is logged in, and doesn't match the UID in the URL
-  // then deny access
-  if ($user->uid && $user->uid != $account->uid) {
-    return FALSE;
+function og_invite_link_user_delete($account) {
+  // Change all invitations for this user to be for their email, should they
+  // wish to sign up again in future.
+  if (!empty($account->mail)) {
+    db_update('og_invite_link')
+      ->fields(array(
+        'uid' => $account->mail
+      ))
+      ->condition('uid', $account->uid)
+      ->execute();
   }
+}
 
-  // All other cases will be checked on the page callback, to allow us
-  // to print helpful messages, rather than give an access denied
-
-  return TRUE;
+/**
+ * Implements hook_user_insert().
+ */
+function og_invite_link_user_insert(&$edit, $account, $category) {
+  og_invite_link_user_changed($edit, $account);
 }
 
 /**
- * Implementation of hook_mail();
+ * Act on user insertion/updating.
  */
-function og_invite_link_mail($key, &$message, $params) {
-  switch ($key) {
-    case 'invite_to_group': {
-      $variables = array(
-        '@group' => check_plain($params['group']->title),
-        '@site' => variable_get('site_name', 'Drupal'),
-        '@description' => check_plain($params['group']->og_description),
-        '@body' => $params['additional_message'],
-        '!group_url' => url('group/' . $params['group']->nid . '/join/' . $params['account']->uid . '/' . $params['invitation']->token, array('absolute' => TRUE)),
-      );
-      $message['subject'] = _og_mail_text('og_invite_user_subject', $variables, $message['language']);
-      $message['body'] = _og_mail_text('og_invite_user_body', $variables, $message['language']);
-      break;
-    }
+function og_invite_link_user_changed($edit, $account) {
+  // If new email is in the og_invite_link table, change that entry to match
+  // uid instead.
+  if (!empty($account->mail)) {
+    db_update('og_invite_link')
+      ->fields(array(
+        'uid' => $account->uid
+      ))
+      ->condition('uid', $account->mail)
+      ->execute();
   }
 }
 
 /**
- * Implementation of hook_user();
+ * Implements hook_user_update().
  */
-function og_invite_link_user($op, &$edit, &$account, $category = NULL) {
-  switch ($op) {
-    case 'delete':
-      // Remove all invitations for this user
-      db_query("DELETE FROM {og_invite} WHERE uid = %d", $account->uid);
-      break;
-  }
+function og_invite_link_user_update(&$edit, $account, $category) {
+  og_invite_link_user_changed($edit, $account);
 }
 
 /**
- * Implementation of hook_nodeapi();
+ * Implements hook_node_delete().
  */
-function og_invite_link_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
-  switch ($op) {
-    case 'delete':
-      // Remove all invitations for this group
-      if (og_is_group_type($node->type)) {
-        db_query("DELETE FROM {og_invite} WHERE group_nid = %d", $node->nid);
-      }
-      break;
+function og_invite_link_node_delete($node) {
+  // Remove all invitations for this group
+  if ($group = og_get_group('node', $node->nid)) {
+    db_delete('og_invite_link')
+      ->condition('gid', $group->gid)
+      ->execute();
   }
 }
 
 /**
- * Generates a token (a hash) used on invite links
+ * Generates a token (a hash) used on invite links.
+ *
+ * Note that although the $invitation->uid, which could either be an actual user
+ * ID or an email address, is used in generating the token, the token is tied to
+ * either the uid or email, not just the one supplied here.
  *
  * @param object $invitation
  *   The invitation object
@@ -199,30 +257,30 @@ function og_invite_link_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
  *   A token string
  */
 function og_invite_link_generate_token($invitation) {
-  return md5(md5($invitation->invite_key) . md5($invitation->group_nid . '-' . $invitation->uid . '-' . $invitation->timestamp));
+  return md5(md5($invitation->invite_key) . md5($invitation->gid . '-' . $invitation->uid . '-' . $invitation->timestamp));
 }
 
 /**
- * Sends invitation mails to a set of users for a group.
+ * Send an invitation mail to a user for a group.
  *
- * @param $user
- *   A user object or user ID
+ * @param $account
+ *   A user object or user ID ... or email address for a non-user.
  * @param object $group
- *   The group node object.
+ *   The group entity object.
  * @param string $additional_message
  *   The additional message to be put in the mail.
  */
-function og_invite_link_send_invite($user, $group, $additional_message = NULL) {
-  // Load the user account, if necessary
-  $account = is_numeric($user) ? user_load($user) : $user;
+function og_invite_link_send_invite($account, $group, $additional_message = NULL) {
+  global $language;
+  $lang = clone $language;
 
-  // Make sure we have a valid user
-  if (!is_object($account) || !$account->uid) {
-    return FALSE;
+  // Load the user account, if necessary
+  if (is_numeric($account)) {
+    $account = user_load($account);
   }
 
-  // Make sure we have a valid group
-  if (!og_is_group_type($group->type)) {
+  // Make sure we have a valid user or email
+  if ((is_object($account) && !$account->uid) || (is_string($account) && !valid_email_address($account)) || (!is_object($account) && !is_string($account))) {
     return FALSE;
   }
 
@@ -236,65 +294,75 @@ function og_invite_link_send_invite($user, $group, $additional_message = NULL) {
     'additional_message' => $additional_message,
     'invitation' => $invitation
   );
-  drupal_mail('og_invite_link', 'invite_to_group', $account->mail, user_preferred_language($account), $mail_params);
-
-  // Store an entry in watchdog
-  watchdog('og_invite_link', 'Group invite sent to !mail (user id: !user_id) for group !group', array('!mail' => $account->mail, '!user_id' => $account->uid, '!group' => $group->title), WATCHDOG_INFO);
+  if (is_string($account)) {
+    drupal_mail('og_invite_link', 'invite_email_to_group', $account, $lang, $mail_params);
+    watchdog('og_invite_link', 'Group invite sent to !mail for group !group', array('!mail' => $account, '!group' => check_plain($group->label)), WATCHDOG_INFO);
+  }
+  else {
+    drupal_mail('og_invite_link', 'invite_user_to_group', $account->mail, user_preferred_language($account), $mail_params);
+    watchdog('og_invite_link', 'Group invite sent to !mail (user id: !user_id) for group !group', array('!mail' => $account->mail, '!user_id' => $account->uid, '!group' => check_plain($group->label)), WATCHDOG_INFO);
+  }
 }
 
 /**
- * Creates an invitation that is stored in the database
+ * Creates an invitation that is stored in the database.
  *
  * @param $account
- *   The ID of the user being invited, or the user object
+ *   The ID of the user being invited, or the user object, or just an email.
  * @param $group
- *   The ID of the group the user is being invited to, or the group object
+ *   The ID of the group the user is being invited to, or the group object.
+ * @param $write
+ *   Explicitly set to FALSE if the invitation should not actually be added to
+ *   the DB. Only useful for creating invitation objects that won't be re-used.
  * @return
- *   The invitation object, if sucessful, or FALSE, if not
+ *   The invitation object, if sucessful, or FALSE, if not.
  */
-function og_invite_link_create_invitation($account, $group) {
+function og_invite_link_create_invitation($account, $group, $write = TRUE) {
   global $user;
 
   // Load the group, if needed
-  $group = is_numeric($group) ? node_load($group) : $group;
+  $group = is_numeric($group) ? og_load($group) : $group;
 
   // Create the invitation
   $invitation = new stdClass();
   // Store the group id
-  $invitation->group_nid = $group->nid;
+  $invitation->gid = $group->gid;
   // Store the user id of the invited user
   $invitation->uid = is_object($account) ? $account->uid : $account;
   // Store the user id of the sender
   $invitation->sender = $user->uid;
   // Store the time of creation
-  $invitation->timestamp = $_SERVER['REQUEST_TIME'];
+  $invitation->timestamp = REQUEST_TIME;
   // Use user_password() to generate a random key
   $invitation->invite_key = user_password();
   // Generate the token
   $invitation->token = og_invite_link_generate_token($invitation);
-  // If the user sending the invite is the group admin
-  // the invite is not moderated, meaning the invitee will
-  // automatically be accepted regardless of the group type
-  $invitation->moderated = og_is_group_admin($group, $user) ? 0 : 1;
 
   // Save it to the database
-  $success = drupal_write_record('og_invite', $invitation);
-
-  return $success ? $invitation : FALSE;
+  if ($write == TRUE) {
+    $success = drupal_write_record('og_invite_link', $invitation);
+    return $success ? $invitation : FALSE;
+  }
+  else {
+    return $invitation;
+  }
 }
 
 /**
- * Implementation of hook_cron();
+ * Implements hook_cron().
  */
 function og_invite_link_cron() {
   // Purge old pending invitations.
   if ($expiration_days = variable_get('og_invite_link_expiration', 30)) {
-    db_query("DELETE FROM {og_invite} WHERE accepted_timestamp = 0 AND timestamp < %d", $_SERVER['REQUEST_TIME'] - ($expiration_days * 86400));
+    db_delete('og_invite_link')
+      ->condition('accepted_timestamp', 0)
+      ->condition('timestamp', (REQUEST_TIME - ($expiration_days * 86400)), '<')
+      ->execute();
   }
 }
 
 /**
- * Load an invitation that matches a token
+ * Load an invitation that matches a token.
  *
  * @param $token
  *   The invitation token
@@ -302,8 +370,11 @@ function og_invite_link_cron() {
  *   An invitation object
  */
 function og_invite_link_get_invitation_by_token($token) {
-  $sql = "SELECT * from {og_invite} WHERE token = '%s'";
-  $invitation = db_fetch_object(db_query($sql, $token));
+  $invitation = db_select('og_invite_link', 'm')
+    ->fields('m')
+    ->condition('token', $token)
+    ->execute()
+    ->fetchObject();
   return $invitation ? $invitation : FALSE;
 }
 
@@ -311,124 +382,32 @@ function og_invite_link_get_invitation_by_token($token) {
  * Checks if an user is already invited into a group.
  *
  * @param int $user_id
- *   The user id to be checked.
- * @param int $group_nid
+ *   The user id to be checked (or email).
+ * @param int $gid
  *   The group id to be checked.
  */
-function og_invite_link_user_is_invited($user_id, $group_nid) {
-  $sql = "SELECT uid FROM {og_invite} WHERE uid = %d AND group_nid = %d";
-  $invitation = db_fetch_object(db_query($sql, $user_id, $group_nid));
-  return $invitation ? TRUE : FALSE;
-}
-
-/**
- * Implementation of hook_action_info();
- *
- */
-function og_invite_link_action_info() {
-  $info['og_invite_link_invite_users_action'] = array(
-    'type' => 'node',
-    'description' => t('Invite users to groups'),
-    'configurable' => TRUE,
-    'hooks' => array(
-      'any' => TRUE, // is this ok?
-    ),
-  );
-  return $info;
-}
-
-/**
- * The action callback.
- *
- */
-function og_invite_link_invite_users_action($node, $context) {
-  static $processed;
-  // Run only once the invitation process.
-  // This is needed because the view bulk operations module invokes
-  // the action for each selected row. We do not need this here, because
-  // the data in the context contains all the selected rows and the invitees,
-  // so we just invite them once.
-  if (!isset($processed)) {
-    $processed = TRUE;
-    $result = og_invite_link_invite_users_to_groups($context);
-    // Show to the user how many successful invites were sent, how many users
-    // were already a member of one the selected groups and which users were invalid.
-    // 1. Invalid users.
-    $invalid = '';
-    if (count($result['invalid'])) {
-      $invalid = implode(', ', $result['invalid']);
-    }
-    // 2. Already in group users.
-    $in_group = 0;
-    if (count($result['in_group'])) {
-      foreach ($result['in_group'] as $value) {
-        $in_group += count($value);
-      }
-    }
-    // 3. Succesful invites.
-    $succesful = 0;
-    if (count($result['invitees'])) {
-      foreach ($result['invitees'] as $value) {
-        $succesful += count($value);
-      }
-    }
-    $message_to_display = t('Invitation results:');
-    $messages[] = format_plural($succesful, '1 successful invitation sent.', '@count successful invitations sent.');
-    if ($in_group) {
-      $messages[] = format_plural($in_group, '1 user was already in one of the groups.', '@count users were already in one of the groups.');
-    }
-    if (!empty($invalid)) {
-      $messages[] = t('The following invitees are not users: !users', array('!users' => $invalid));
-    }
-    $message_to_display .= theme('item_list', $messages);
-    drupal_set_message($message_to_display);
+function og_invite_link_user_is_invited($uid, $gid) {
+  $invitation = db_select('og_invite_link', 'm')
+    ->fields('m', array('uid'))
+    ->condition('gid', $gid);
+
+  $account = FALSE;
+  if (is_numeric($uid)) {
+    $account = user_load($uid);
   }
-}
-
-/**
- * The action configuration form.
- *
- */
-function og_invite_link_invite_users_action_form($context) {
-  // The action configuration form is actually the same form that
-  // is used when inviting users on a group page.
-  module_load_include('inc', 'og_invite_link', 'og_invite_link.pages');
-  $groups = array();
-  // Iterate through each selected node and load its details
-  // so it can be passed as parameter to the invite form.
-  // This only works with vbo.
-  // @todo: make this not vbo dependent.
-  foreach ($context['selection'] as $key => $value) {
-    if ($value->nid) {
-      $groups[$value->nid] = node_load($value->nid);
-    }
+  else {
+    $account = user_load_by_mail($uid);
+  }
+  if ($account) {
+    // Try matching on uid or email.
+    $invitation = $invitation->condition(db_or()->condition('uid', $account->uid)->condition('uid', $account->mail));
+  }
+  else {
+    $invitation = $invitation->condition('uid', $uid);
   }
-  $form = og_invite_link_invite_page_form(array(), $groups);
-  unset($form['submit']);
-  return $form;
-}
-
-/**
- * Validation handler for the configuration form.
- *
- */
-function og_invite_link_invite_users_action_validate($form, &$form_state) {
-  // Just call the normal validation handler of the invite page.
-  module_load_include('inc', 'og_invite_link', 'og_invite_link.pages');
-  og_invite_link_invite_page_form_validate($form, $form_state);
-}
 
-/**
- * Submit handler for the configuration form.
- *
- */
-function og_invite_link_invite_users_action_submit($form, &$form_state) {
-  // Prepare the data into the format that the action callback needs.
-  return array(
-    'groups' => $form_state['values']['groups'],
-    'invitees' => $form_state['values']['invitees'],
-    'additional_message' => $form_state['values']['additional_message']
-  );
+  $invitation = $invitation->execute()->fetchAssoc();
+  return $invitation ? $invitation : FALSE;
 }
 
 /**
@@ -436,31 +415,57 @@ function og_invite_link_invite_users_action_submit($form, &$form_state) {
  *
  * @param array $params
  *   An associative array that contains the following keys:
- *   - groups: An array with organic group nodes.
+ *   - groups: An array with organic group entities.
  *   - invitees: A comma separated string containing the names
  *               of the users to invite
+ *   - email_invitees: A comma separated string containing emails that should be
+ *                     sent invitations.
  *   - additional_message: An additional message to be appended
  *                         to the invitation message.
  *
  *  @return An associative array with the following keys:
  *   - invitees: An array with all the successfull invitations,
- *               grouped by node id.
+ *               grouped by group id.
  *   - in_group: An array with all the users that were invited,
- *               but are already in the group(s), grouped by node id.
+ *               but are already in the group(s), grouped by group id.
  *   - invalid: An array with invalid names.
  */
 function og_invite_link_invite_users_to_groups($params) {
+  // Convert usernames to emails. We will have to load each user object soon
+  // anyway (see og_invite_link_invite_users_to_groups()), so given that they
+  // should be cached, do lots of user_load_by_name().
   if (count($params['groups'])) {
     // Extract the additional message from the parameters.
     $message = filter_xss($params['additional_message']);
     // Extract the user names to send invites to
     $names = explode(',', $params['invitees']);
+    // Extract the emails to send invites to
+    $emails = explode("\n", $params['emails']);
+    $emails = array_map('trim', $emails);
+    foreach ($emails as $key => $email) {
+      if (empty($email)) {
+        unset($emails[$key]);
+        continue;
+      }
+      if ($account = user_load_by_mail($email)) {
+        $names[] = $account->name;
+        unset($emails[$key]);
+      }
+    }
     // Track the valid invitees
     $invitees = array();
     // Track the invalid invitees
     $invalid = array();
     // Track the valid invitees that are already group members
     $in_group = array();
+    foreach ($params['groups'] as $key => $group) {
+      if (!isset($invitees[$group->gid])) {
+        $invitees[$group->gid] = array();
+      }
+      if (!isset($in_group[$group->gid])) {
+        $in_group[$group->gid] = array();
+      }
+    }
 
     // Attempt to load the users based on names, to determine which are real
     // users and which are not. We need to load each user individually instead
@@ -476,34 +481,78 @@ function og_invite_link_invite_users_to_groups($params) {
       }
 
       // Attempt to load the user and make sure he is a valid one
-      $account = user_load(array('name' => $name));
+      $account = user_load_by_name($name);
       if (!$account->status) {
         $invalid[] = check_plain($name);
         continue;
       }
       foreach ($params['groups'] as $key => $group) {
-        if (!is_array($invitees[$group->nid])) {
-          $invitees[$group->nid] = array();
-        }
-        if (!is_array($in_group[$group->nid])) {
-          $in_group[$group->nid] = array();
-        }
         // Check for duplicates
-        if (isset($invitees[$group->nid][$account->uid]) || isset($in_group[$group->nid][$account->uid])) {
+        if (isset($invitees[$group->gid][$account->uid]) || isset($in_group[$group->gid][$account->uid])) {
           continue;
         }
         // See if the user is already a member
-        // Don't use og_is_group_member() because it's horrible
-        if (isset($account->og_groups[$group->nid])) {
-          $in_group[$group->nid][$account->uid] = theme('username', $account);
+        if (og_is_member($group->gid, 'user', $account, array(OG_STATE_ACTIVE, OG_STATE_PENDING))) {
+          $in_group[$group->gid][$account->uid] = theme('username', array('account' => $account));
         }
         else {
           // Invite the user
           og_invite_link_send_invite($account, $group, $message);
-          $invitees[$group->nid][$account->uid] = theme('username', $account);
+          $invitees[$group->gid][$account->uid] = theme('username', array('account' => $account));
+        }
+      }
+    }
+
+    // Now send invitations to emails that aren't yet users...
+    foreach ($emails as $email) {
+      foreach ($params['groups'] as $key => $group) {
+        // Check for duplicates
+        if (isset($invitees[$group->gid][$email]) || isset($in_group[$group->gid][$email])) {
+          continue;
         }
+        // Invite the user
+        og_invite_link_send_invite($email, $group, $message);
+        $invitees[$group->gid][$email] = check_plain($email);
       }
     }
     return array('invitees' => $invitees, 'in_group' => $in_group, 'invalid' => $invalid);
   }
-}
\ No newline at end of file
+}
+
+/**
+ * Provide the text for invitation mail components.
+ *
+ * @param $key
+ *   The message key (site variable name)
+ * @param $variables
+ *   An array of variables for the message
+ * @param $language
+ *   The preferred language object
+ * @return
+ *   A message string
+ */
+function _og_invite_link_mail_text($key, $variables = array(), $language = NULL) {
+  $langcode = isset($language) ? (is_object($language) ? $language->language : $language) : NULL;
+
+  // See if we have a variable set for this
+  if ($message = variable_get($key)) {
+    return strtr($message, $variables);
+  }
+  // Provide defaults
+  else {
+    switch ($key) {
+      case 'og_invite_link_user_subject':
+        return t("Invitation to join the group '@group' at @site", $variables, array('langcode' => $langcode));
+      case 'og_invite_link_user_body':
+        return t("Hi. I'm a member of '@group' and I welcome you to join this group as well. Please see the link and message below.\n\n@group\nJoin: !group_url\n@body", $variables, array('langcode' => $langcode));
+      case 'og_invite_link_email_subject':
+        return t("Invitation to join the group '@group' at @site", $variables, array('langcode' => $langcode));
+      case 'og_invite_link_email_body':
+        return t("Hi. I'm a member of '@group' on @site and I welcome you to join this group as well. Please see the link and message below. You will need to register an account using @sent-to on @site.\n\n@group\nJoin: !group_url\n@body", $variables, array('langcode' => $langcode));
+      /*case 'og_invite_link_admin_subject':
+        return t("Invitation sent out for the group '@group'", $variables, array('langcode' => $langcode));
+      case 'og_invite_link_admin_body':
+        return t("A group member has sent out an invitation for the group, '@group'.\n\nSent to: @sent-to\nSent by: @sent-by\n\nAdditional message:\n@body", $variables, array('langcode' => $langcode));*/
+    }
+  }
+}
diff --git a/og_invite_link.pages.inc b/og_invite_link.pages.inc
index 42e3c35..b0da207 100644
--- a/og_invite_link.pages.inc
+++ b/og_invite_link.pages.inc
@@ -2,34 +2,53 @@
 
 /**
  * @file
- *   Contains the page callback functions for this module
+ *   Contains the page callback functions for this module.
  */
-define('OG_INVITE_LINK_MAX_INVITES_PER_FORM', 20);
 
 /**
  * Form constructor for inviting members into groups.
  */
-function og_invite_link_invite_page_form($form_state = array(), $nodes) {
+function og_invite_link_invite_page_form($form, $form_state = array(), $entity_type, $etid) {
   // Set the page title (needed because it's a local task)
-  drupal_set_title(t('Invite members to this group'));
+  drupal_set_title(t('Invite members to this group'), PASS_THROUGH);
+  og_set_breadcrumb($entity_type, $etid, array(l(t('Group'), "$entity_type/$etid/group")));
 
   $form = array();
-  // Handle the case when the form is called from the menu.
-  if (is_object($nodes)) {
-    $nodes = array($nodes);
+  // Form is only ever called from the menu now (it could have been from an
+  // action, but we've removed this.)
+  if ($group = og_get_group($entity_type, $etid)) {
+    $groups = array($group);
+  }
+  else {
+    // This entity isn't a group.
+    return MENU_NOT_FOUND;
   }
   $form['groups'] = array(
     '#type' => 'value',
-    '#value' => $nodes,
+    '#value' => $groups,
   );
   $form['invitees'] = array(
     '#type' => 'textfield',
     '#title' => t('Invitees'),
     '#maxlength' => 1024,
-    '#required' => TRUE,
+    '#required' => FALSE,
     '#autocomplete_path' => 'og_invite_link/autocomplete',
-    '#description' => t('Add one or more usernames in order to invite users in this group. Multiple usernames should be separated by a comma. A maximum of !max invites can be sent out at a time.', array('!max' => OG_INVITE_LINK_MAX_INVITES_PER_FORM)),
+    '#description' => t('Add one or more usernames in order to invite & add users to this group. Multiple usernames should be separated by a comma.'),
+  );
+  if ($max = variable_get('og_invite_link_max_invites_per_form')) {
+    $form['invitees']['#description'] .= t(' A maximum of !max invites can be sent out at a time.', array('!max' => $max));
+  }
+  $form['emails'] = array(
+    '#type' => 'textarea',
+    '#title' => t('Invite by email'),
+    '#rows' => 20,
+    '#required' => FALSE,
+    '#description' => t('Add one or more email addresses in order to invite users in this group. Place one email address on each line.'),
+    '#access' => variable_get('user_register', TRUE),
   );
+  if ($max = variable_get('og_invite_link_max_invites_per_form')) {
+    $form['emails']['#description'] .= t(' A maximum of !max invites can be sent out at a time.', array('!max' => $max));
+  }
   $form['additional_message'] = array(
     '#type' => 'textarea',
     '#title' => t('Additional message'),
@@ -43,7 +62,7 @@ function og_invite_link_invite_page_form($form_state = array(), $nodes) {
 }
 
 /**
- * Validation handler for the invite form
+ * Validation handler for the invite form.
  */
 function og_invite_link_invite_page_form_validate($form, &$form_state) {
   $invitees = array();
@@ -51,6 +70,9 @@ function og_invite_link_invite_page_form_validate($form, &$form_state) {
   // Extract the user names to send invites to
   $names = explode(',', $form_state['values']['invitees']);
 
+  // Extract the emails to send invites to
+  $emails = explode("\n", $form_state['values']['emails']);
+
   // Add them to an array, filtering out whitespace entries
   // in order to get a real count
   foreach ($names as $name) {
@@ -59,163 +81,263 @@ function og_invite_link_invite_page_form_validate($form, &$form_state) {
       $invitees[] = trim($name);
     }
   }
+  foreach ($emails as $email) {
+    $email = trim($email);
+    if (!empty($email)) {
+      $email = trim($email);
+      if (valid_email_address($email)) {
+        $invitees[] = $email;
+      }
+      else {
+        form_set_error('emails', t('@email is an invalid email address.', array('@email' => $email)));
+      }
+    }
+  }
 
   // Make sure we have no more than the max amount of invites
-  // TO DO: check also the number of groups.
-  if (count($invitees) > OG_INVITE_LINK_MAX_INVITES_PER_FORM) {
-    form_set_error('invitees', t('You can only send !max invitations at a time', array('!max' => OG_INVITE_LINK_MAX_INVITES_PER_FORM)));
+  // @todo check also the number of groups.
+  $max = variable_get('og_invite_link_max_invites_per_form', 200);
+  if (!empty($max) && count($invitees) > $max) {
+    form_set_error('invitees', t('You can only send !max invitations at a time', array('!max' => $max)));
   }
 }
 
 /**
- * Submit handler for the invite form
+ * Submit handler for the invite form.
  */
 function og_invite_link_invite_page_form_submit($form, &$form_state) {
-  $invitiation_result = og_invite_link_invite_users_to_groups(array(
+  $invitation_result = og_invite_link_invite_users_to_groups(array(
                                           'groups' => $form_state['values']['groups'],
                                           'invitees' => $form_state['values']['invitees'],
+                                          'emails' => $form_state['values']['emails'],
                                           'additional_message' => $form_state['values']['additional_message']));
   // Here we should not have more than one group.
   $group = current($form_state['values']['groups']);
   // Set a message for the invited users
-  if (!empty($invitiation_result['invitees'][$group->nid])) {
-    drupal_set_message(t('An invite has been sent to !users', array('!users' => implode(', ', $invitiation_result['invitees'][$group->nid]))));
+  if (!empty($invitation_result['invitees'][$group->gid])) {
+    drupal_set_message(t('An invite has been sent to !users', array('!users' => implode(', ', $invitation_result['invitees'][$group->gid]))));
   }
 
   // Set a message for the invitees already in the group
-  if (!empty($invitiation_result['in_group'][$group->nid])) {
-    drupal_set_message(t('The following invitees are already members: !users', array('!users' => implode(', ', $invitiation_result['in_group'][$group->nid]))), 'warning');
+  if (!empty($invitation_result['in_group'][$group->gid])) {
+    drupal_set_message(t('The following invitees are already members of the group: !users', array('!users' => implode(', ', $invitation_result['in_group'][$group->gid]))), 'warning');
   }
 
   // Set a message for the invalid invitees
-  if (!empty($invitiation_result['invalid'])) {
-    drupal_set_message(t('The following invitees are not users: !users', array('!users' => implode(', ', $invitiation_result['invalid']))), 'error');
+  if (!empty($invitation_result['invalid'])) {
+    drupal_set_message(t('The following invitees are invalid users or are blocked: !users', array('!users' => implode(', ', $invitation_result['invalid']))), 'error');
   }
 }
 
 /**
- * Page callback for joining a group
+ * Helper function to set message & redirect in the case of invalid invitations.
  *
- * @param object $group
- *   The group object
+ * @param $msg
+ *   Message. Default: 'This is not a valid invitation.'
+ * @param
+ *   Any additional arguments are passed to drupal_goto().
+ *
+ * @return
+ *   Returns MENU_NOT_FOUND.
+ */
+function _og_invite_link_invalid_invitation($msg = NULL) {
+  $args = func_get_args();
+  array_shift($args);
+  if (empty($msg)) {
+    $msg = t('This is not a valid invitation.');
+  }
+  if (empty($args[0])) {
+    $dest = '<front>';
+  }
+  drupal_set_message($msg, 'error');
+  call_user_func_array('drupal_goto', $args);
+  return MENU_NOT_FOUND;
+}
+
+/**
+ * Page callback for joining a group.
+ *
+ * @param string $entity_type
+ *   The group's entity type.
+ * @param int $etid
+ *   The group's entity ID. (e.g. node ID)
  * @param object $account
  *   The account in the invitation link
  * @param string $token
  *   The invitation token
  */
-function og_invite_link_join($group, $account, $token) {
+function og_invite_link_join($entity_type, $etid, $account, $token) {
   global $user;
+  if (is_numeric($account)) {
+    $account = user_load($account);
+    if ($account == FALSE) {
+      return _og_invite_link_invalid_invitation();
+    }
+  }
+  elseif (is_string($account) && valid_email_address($account)) {
+    $loaded = user_load_by_mail($account);
+    $account = $loaded ? $loaded : $account;
+  }
+  else {
+    return _og_invite_link_invalid_invitation();
+  }
 
   // Load the invitation from the token
   $invitation = og_invite_link_get_invitation_by_token($token);
 
   // Determine if this is a valid invitation
-  if (!$invitation) {
-    drupal_set_message(t('This invitation is not valid or has expired.'), 'error');
-    drupal_goto('<front>');
+  if ($invitation == FALSE) {
+    return _og_invite_link_invalid_invitation();
   }
-
-  // Make sure this invitation was intended for the account
-  // included in the invitation link
-  if ($account->uid != $invitation->uid) {
-    drupal_set_message(t('This is not a valid invitation.'), 'error');
-    drupal_goto('<front>');
+  // If the user is logged in, make sure the invitation is meant for them.
+  elseif (!empty($user->uid) && empty($user->mail) || ($user->uid != $invitation->uid && $user->mail != $invitation->uid)) {
+    return _og_invite_link_invalid_invitation();
   }
 
   // Make sure the invitation is meant for this group
-  if ($group->nid != $invitation->group_nid) {
-    drupal_set_message(t('This is not a valid invitation.'), 'error');
-    drupal_goto('<front>');
+  $group = og_get_group($entity_type, $etid);
+  if ($group->gid != $invitation->gid) {
+    return _og_invite_link_invalid_invitation();
   }
 
-  // If the user is logged in, make sure the invitation is meant for them
-  if ($user->uid && $user->uid != $invitation->uid) {
-    drupal_set_message(t('This is not a valid invitation.'), 'error');
-    drupal_goto('<front>');
+  // Make sure this invitation was intended for the account included in the
+  // invitation link. Note that $account has come from the URL.
+  // First, if $account is an string, check if its a different email to our
+  // logged-in user. Secondly, if $account is an object, check if the invitation
+  // is for this user's uid or email address. Finally, check if $account is
+  // anything else.
+  $valid = TRUE;
+  if (is_numeric($invitation->uid)) {
+    $valid = (is_object($account) && $account->uid == $invitation->uid);
+  }
+  elseif (is_string($invitation->uid)) {
+    if (is_object($account)) {
+      $valid = ($account->mail == $invitation->uid);
+    }
+    elseif (is_string($account)) {
+      $valid = ($account == $invitation->uid);
+    }
+    else {
+      $valid = FALSE;
+    }
+  }
+  else {
+    $valid = FALSE;
+  }
+  if ($valid == FALSE) {
+    return _og_invite_link_invalid_invitation();
   }
 
   // See if the invitation has expired
   if ($invitation->accepted_timestamp) {
-    drupal_set_message(t('This invitation has expired.'), 'error');
-    drupal_goto('<front>');
+    return _og_invite_link_invalid_invitation(t('This invitation has expired.'));
   }
 
-  // Log the user in if not yet logged in
+  // Log the user in if not yet logged in.
   if (!$user->uid) {
-    // Make sure the invited user is active and exists
-    if ($account->uid && $account->status) {
-      // Load the user in
-      $user = $account;
+    // Make sure the invited user is active and exists.
+    if (is_object($account)) {
+      if ($account->uid && $account->status) {
+        // Log the user in
+        $user = $account;
+        user_login_finalize();
+      }
+      else {
+        return _og_invite_link_invalid_invitation();
+      }
+    }
+    elseif (variable_get('user_register', TRUE)) {
+      // We will need a new account for the user with the email held by $account
+      return _og_invite_link_invalid_invitation(t('Please register to join the campaign.'), 'user/register', array('query' => drupal_get_destination()));
+    }
+    else {
+      return _og_invite_link_invalid_invitation();
     }
   }
 
   // If we don't have a logged in user by now, the invitation is invalid
+  // We probably don't need this check, but hey, what's the harm? :-)
   if (!$user->uid) {
-    drupal_set_message(t('This is not a valid invitation.'), 'error');
-    drupal_goto('<front>');
+    return _og_invite_link_invalid_invitation();
   }
 
+  // Get the group's base entity as we'll need it for redirecting.
+  $entity = og_load_entity_from_group($group->gid);
+
+  $account = clone $user;
   // See if the user is already in the group
-  $is_member = og_is_group_member($invitation->group_nid, FALSE, $invitation->uid);
-  if ($is_member) {
+  if (og_is_member($invitation->gid)) {
     drupal_set_message(t('You are already a member of this group.'));
     // Remove any pending invitations for this user and this group
-    db_query("DELETE FROM {og_invite} WHERE uid = %d AND group_nid = %d AND accepted_timestamp = 0", $invitation->uid, $invitation->group_nid, $invitation->token);
+    //db_query("DELETE FROM {og_invite_link} WHERE uid = %d AND gid = %d AND accepted_timestamp = 0", $invitation->uid, $invitation->gid, $invitation->token);
+    db_delete('og_invite_link')
+      ->condition(db_or()->condition('uid', $account->uid)->condition('uid', $account->mail))
+      ->condition('gid', $invitation->gid)
+      ->condition('accepted_timestamp', 0)
+      ->execute();
   }
   else {
     // Determine the moderation status of the invitation
-    if ($moderated = $invitation->moderated) {
-      switch ($group->og_selective) {
-        // If the group is open, no moderation needed
-        case OG_OPEN:
-          $moderated = 0;
-          break;
-
+    $state = og_user_access_by_entity('subscribe without approval', $group->entity_type, $group->etid, $account) ? OG_STATE_ACTIVE : FALSE;
+    if ($state == FALSE) {
+      $sender = user_load($invitation->sender);
+      // Approved if the inviter is allowed to just add users,
+      // or if the invitee can subscribe & the inviter can approve this.
+      // Otherwise (invitee can't subscribe & just be added, or can subscribe
+      // but can't be approved), it's an invalid invitation. In these cases,
+      // show the group page.
+      if (og_user_access_by_entity('add user', $group->entity_type, $group->etid, $sender)) {
+        $state = OG_STATE_ACTIVE;
+      }
+      elseif (og_user_access_by_entity('subscribe', $group->entity_type, $group->etid, $account)) {
+        $state = og_user_access_by_entity('approve and deny subscription', $group->entity_type, $group->etid, $sender) ? OG_STATE_ACTIVE : OG_STATE_PENDING;
+      }
+      else {
         // If moderated and the group is now closed, this is no
-        // longer a valid invitation
-        case OG_CLOSED:
-          // Alert the user
-          drupal_set_message(t('This invitation is no longer valid.'), 'error');
-          // Delete this invitation (should we?)
-          db_query("DELETE FROM {og_invite} WHERE token = '%s'", $invitation->token);
-          // Redirect home
-          drupal_goto('<front>');
-          break;
+        // longer a valid invitation. Alert the user.
+        $uri = entity_uri($group->entity_type, $entity);
+        return _og_invite_link_invalid_invitation(NULL, $uri['path'], $uri['options']);
       }
     }
 
-    // Update the invitation to mark it as accepted
-    db_query("UPDATE {og_invite} SET accepted_timestamp = %d WHERE token = '%s'", time(), $invitation->token);
+    // Update the invitation to mark it as accepted.
+    db_update('og_invite_link')
+      ->fields(array(
+        'accepted_timestamp' => REQUEST_TIME
+      ))
+      ->condition('token', $invitation->token)
+      ->execute();
 
-    // Remove any other pending invitations for this user and group
-    db_query("DELETE FROM {og_invite} WHERE uid = %d AND group_nid = %d AND token <> '%s'", $invitation->uid, $invitation->group_nid, $invitation->token);
+    // Remove any other pending invitations for this user and group/token.
+    db_delete('og_invite_link')
+      ->condition(db_or()->condition('uid', $account->uid)->condition('uid', $account->mail))
+      ->condition('gid', $invitation->gid)
+      ->condition('token', $invitation->token, '<>')
+      ->execute();
 
-    // Create a group subscription for the user
-    og_save_subscription($invitation->group_nid, $invitation->uid, array('is_active' => $moderated ? 0 : 1));
+    // Create a group subscription for the user:
+    og_group($invitation->gid, array('entity type' => 'user', 'entity' => $account, 'state' => $state));
 
     // Set a message based on the moderation status
-    if ($moderated) {
+    if ($state == OG_STATE_PENDING) {
       drupal_set_message(t('You have requested access to join the group. A group administrator must first approve your request before you can join the group.'), 'warning');
-      // Go to the home page if this group is private
-      if ($group->og_private) {
-        drupal_goto('<front>');
-      }
     }
     else {
       drupal_set_message(t('You are a member of this group now.'));
     }
   }
 
-  drupal_goto("node/{$invitation->group_nid}");
+  $uri = entity_uri($group->entity_type, $entity);
+  drupal_goto($uri['path'], $uri['options']);
 }
 
 /**
- * Helper function for autocompletion
+ * Helper function for autocompletion.
  * @see taxonomy_autocomplete()
  *
  * @todo A good thing would be to avoid returning
- * usernames that are already members in the group.
- * For that, the group id has to be sent as a parameter.
+ *   usernames that are already members in the group.
+ *   For that, the group id has to be sent as a parameter.
  */
 function og_invite_link_autocomplete($string = '') {
   // The user enters a comma-separated list of names. We only autocomplete the last name.
@@ -225,20 +347,21 @@ function og_invite_link_autocomplete($string = '') {
   $last_string = trim(array_pop($array));
   $matches = array();
   if ($last_string != '') {
-    $result = db_query_range("SELECT name FROM {users} WHERE LOWER(name) LIKE LOWER('%s%%')", $last_string, 0, 10);
+    //$result = db_query_range("SELECT name FROM {users} WHERE LOWER(name) LIKE LOWER('%s%%')", $last_string, 0, 10);
+    $result = db_select('users')->fields('users', array('name'))->condition('name', db_like($last_string) . '%', 'LIKE')->range(0, 10)->execute();
 
-    $prefix = count($array) ? implode(', ', $array) .', ' : '';
+    $prefix = count($array) ? implode(', ', $array) . ', ' : '';
 
-    while ($name = db_fetch_object($result)) {
+    foreach ($result as $name) {
       $n = $name->name;
       // Commas and quotes in usernames are special cases, so encode 'em.
       //(Although the usernames should not have such characters).
       if (strpos($name->name, ',') !== FALSE || strpos($name->name, '"') !== FALSE) {
-        $n = '"'. str_replace('"', '""', $name->name) .'"';
+        $n = '"' . str_replace('"', '""', $name->name) . '"';
       }
       $matches[$prefix . $n] = check_plain($name->name);
     }
   }
 
-  drupal_json($matches);
+  drupal_json_output($matches);
 }
-- 
1.7.1

