diff -upwr /Users/bryn/projects/ogrc3orig/modules/og_access/og_access.module ./modules/og_access/og_access.module --- /Users/bryn/projects/ogrc3orig/modules/og_access/og_access.module 2009-04-09 17:14:20.000000000 -0400 +++ ./modules/og_access/og_access.module 2009-08-11 19:43:14.000000000 -0400 @@ -1,5 +1,5 @@ type)) { - $node->og_public = (bool)db_result(db_query("SELECT og_public FROM {og_access_post} WHERE nid = %d", $node->nid)); + // Can't assume that og_access post records already exist. See http://drupal.org/node/522728. + $public_value = db_result(db_query("SELECT og_public FROM {og_access_post} WHERE nid = %d", $node->nid)); + $node->og_public = ($public_value === '0') ? FALSE : TRUE; } break; case 'insert': @@ -40,8 +42,15 @@ function og_access_nodeapi(&$node, $op, case 'update': // Save og_public. if (og_is_group_post_type($node->type)) { + // Can't assume that og_access post records already exist. See http://drupal.org/node/522728. + // TODO D7: use merge query. + if (!db_result(db_query_range("SELECT nid FROM {og_access_post} WHERE nid = %d", $node->nid, 0, 1))) { + drupal_write_record('og_access_post', $node); + } + else { drupal_write_record('og_access_post', $node, 'nid'); } + } break; case 'delete': if (og_is_group_post_type($node->type)) { @@ -97,6 +106,12 @@ function og_access_content_extra_fields( 'description' => t('Checkbox for visibility of group home page to non-members.'), 'weight' => 0, ); + + $extra['og_private_preview'] = array( + 'label' => t('Private group preview'), + 'description' => t('If this box is checked, users invited to private groups will be able to view the group in order to decide whether or not they want to join.'), + 'weight' => 0, + ); } return $extra; } @@ -138,6 +153,14 @@ function og_access_alter_group_form(&$fo '#weight' => module_exists('content') ? content_extra_field_weight($node->type, 'og_private') : 0, '#description' => t('Should this group be visible only to its members? Disabled if the group is set to List in Directory or Membership requests: open.') ); + + $form['og_private_preview'] = array( + '#type' => 'checkbox', + '#title' => t('Private group preview'), + '#default_value' => isset($node->nid) ? $node->og_private : 0, + '#weight' => module_exists('content') ? content_extra_field_weight($node->type, 'og_private_preview') : 0, + '#description' => t('If this box is checked, users invited to private groups will be able to view the group in order to decide whether or not they want to join.'), + ); break; } } @@ -245,6 +268,9 @@ function og_access_form_alter(&$form, &$ function og_access_node_grants($account, $op) { if ($op == 'view') { $grants['og_public'][] = 0; // everyone can see a public node + + $result = db_query('select group_nid from og_invite where mail=\'%s\'',$account->uid); + while($gid = db_result($result)) $grants['og_private_preview'][] = $gid; } // Subscribers get an admin or non-admin grant for each subscription @@ -299,6 +325,17 @@ function og_access_node_access_records($ 'priority' => 0, ); + if($node->og_private_preview) { + // If the group allows private previews for invited users, let those users view the group homepage. + $grants[] = array ( + 'realm' => 'og_private_preview', + 'gid' => $node->nid, + 'grant_view' => 1, + 'grant_update' => 0, + 'grant_delete' => 0, + 'priority' => 0, + ); + } } } elseif (!empty($node->og_groups)) { diff -upwr /Users/bryn/projects/ogrc3orig/modules/og_actions/og_actions.module ./modules/og_actions/og_actions.module --- /Users/bryn/projects/ogrc3orig/modules/og_actions/og_actions.module 2009-06-08 21:04:44.000000000 -0400 +++ ./modules/og_actions/og_actions.module 2009-08-11 14:19:07.000000000 -0400 @@ -33,6 +33,12 @@ function og_actions_action_info() { 'nodeapi' => array('insert', 'update'), ), ), + 'og_invite_user_action' => array( + 'type' => 'user', + 'description' => t('Invite user to the specified groups'), + 'configurable' => TRUE, + 'hooks' => array(), + ), 'og_subscribe_user_action' => array( 'type' => 'user', 'description' => t('Subscribe user to the specified groups'), @@ -274,6 +280,63 @@ function og_subscribe_user_action_submit } /** + * A configurable action to subscribe a user to specific groups. + */ +function og_invite_user_action($account, $context) { + if (isset($context['groups'])) { + require_once drupal_get_path('module', 'og') ."/og.pages.inc"; + foreach ($context['groups'] as $gid) { + $form_state = array('values' => array('mails' => $account->name, 'gid' => $gid, 'pmessage' => $context['pmessage'])); + drupal_execute('og_invite_form', $form_state, node_load($gid)); + } + watchdog('action', 'Invited user %name to groups %groups.', array('%name' => $account->name, '%groups' => implode(',', $context['groups']))); + } +} + +/** + * Configuration form for Invite User action. + */ +function og_invite_user_action_form($context) { + $form = array(); + + if (!isset($context['groups'])) { + $context['groups'] = array(); + } + + $groups = og_all_groups_options(); + if (count($groups)) { + $form['groups'] = array( + '#type' => 'select', + '#title' => t('Groups'), + '#options' => $groups, + '#description' => t('Select the groups to which this user should be invited.'), + '#default_value' => $context['groups'], + '#required' => TRUE, + '#multiple' => TRUE, + ); + $form['pmessage'] = array( + '#type' => 'textarea', + '#title' => t('Personal message'), + '#description' => t('Optional. Enter a message which will become part of the invitation email.') + ); + } + else { + drupal_set_message(t('Please create a group first.', array('!url' => url('admin/content')))); + } + return $form; +} + +/** + * Submission handler for Invite User action configuration form. + */ +function og_invite_user_action_submit($form, &$form_state) { + return array( + 'groups' => $form_state['values']['groups'], + 'pmessage' => $form_state['values']['pmessage'], + ); +} + +/** * A configurable action to unsubscribe a user from specific groups. */ function og_unsubscribe_user_action($account, $context) { diff -upwr /Users/bryn/projects/ogrc3orig/og.install ./og.install --- /Users/bryn/projects/ogrc3orig/og.install 2009-05-15 13:28:02.000000000 -0400 +++ ./og.install 2009-08-11 18:47:06.000000000 -0400 @@ -143,6 +143,24 @@ function og_schema() { ), 'primary key' => array('nid', 'group_nid'), ); + $schema['og_invite'] = array( + 'description' => 'Pending OG Invitations', + 'fields' => array( + 'mail' => array( + 'type' => 'varchar', + 'length' => 64, + 'not null' => TRUE, + 'description' => "Invited email address.", + ), + 'group_nid' => array( + 'description' => "The group's {node}.nid.", + 'type' => 'int', + 'size' => 'normal', + 'not null' => TRUE, + ), + ), + 'primary key' => array('mail', 'group_nid'), + ); return $schema; } @@ -432,6 +450,50 @@ function og_update_6203() { return $ret; } +// Add og_invite table to store emails for inviting non-users to groups. +function og_update_6204() { + $ret = array(); + + $schema['og_invite'] = array( + 'description' => 'Pending OG Invitations', + 'fields' => array( + 'mail' => array( + 'type' => 'varchar', + 'length' => 64, + 'not null' => TRUE, + 'description' => "Invited email address.", + ), + 'group_nid' => array( + 'description' => "The group's {node}.nid.", + 'type' => 'int', + 'size' => 'normal', + 'not null' => TRUE, + ), + ), + 'primary key' => array('mail', 'group_nid'), + ); + db_create_table($ret, 'og_invite', $schema['og_invite']); + return $ret; +} + +// Add private_preview field to allow invited users to preview a private group before joining +function og_update_6205() { + $ret = array(); + + $schema = array( + 'description' => 'Give invited users preview access to an otherwise private group?', + 'type' => 'int', + 'size' => 'tiny', + 'not null' => TRUE, + 'default' => 0, + ); + + + db_add_field($ret, 'og', 'og_private_preview', $schema); + return $ret; +} + + // end updates // function og_uninstall() { diff -upwr /Users/bryn/projects/ogrc3orig/og.module ./og.module --- /Users/bryn/projects/ogrc3orig/og.module 2009-06-09 07:42:46.000000000 -0400 +++ ./og.module 2009-08-11 18:32:22.000000000 -0400 @@ -1,5 +1,5 @@ 'og.pages.inc', 'page callback' => 'og_subscribe', 'page arguments' => array(2), - 'access callback' => 'node_access', - 'access arguments' => array('view', 2), + 'access callback' => 'og_menu_access_subscribe', + 'access arguments' => array(2), 'title' => 'Join group' ); @@ -258,6 +258,29 @@ function og_menu_access_invite($node) { return og_is_group_member($node) && ($node->og_selective < OG_INVITE_ONLY || og_is_group_admin($node)); } +function og_menu_access_subscribe($group_node) { + if($group_node->og_private) { + if(og_is_invited($group_node)) return TRUE; + else return FALSE; + } + else return node_access('view', $group_node); +} + +function og_is_invited($group_node) { + global $user; + + if (!isset($user->og_groups)) { + $user = user_load(array('uid' => $user->uid)); + } + + $sql = "SELECT COUNT(*) FROM {og_invite} WHERE group_nid = %d and mail = '%s'"; + $query = db_query($sql, $group_node->nid, $user->uid); + $cnt = db_result($query); + + if($cnt == 0) return FALSE; + else return TRUE; +} + /** * Check a user's membership in a group. * @@ -386,7 +409,8 @@ function og_init() { // We have to perform a load in order to assure that the $user->og_groups bits are present. global $user; if ($user->uid) { - $user = user_load(array('uid' => $user->uid)); + // $user gets modified by reference. + og_user('load', array(), $user); } else { $user->og_groups = array(); @@ -428,7 +452,8 @@ function og_set_language($node) { if ($node->og_language) { $map = language_list(); $og_language = $map[$node->og_language]; - $user_language = user_preferred_language($account, $og_language); + global $user; + $user_language = user_preferred_language($user, $og_language); if ($og_language == $user_language) { $GLOBALS['language'] = $og_language; } @@ -484,7 +509,7 @@ function og_determine_context() { $gid = intval(current($_REQUEST['gids'])); $node = node_load($gid); } - elseif (($item['map'][0] == 'og' && !empty($item['map'][2])) || $path == 'comment/reply/%') { + elseif ((!empty($item['map'][2]) && $item['map'][0] == 'og') || $path == 'comment/reply/%') { $node = menu_get_object('node', 2); } elseif ($path == 'comment/edit' || $path == 'comment/delete') { @@ -617,6 +642,7 @@ function og_set_theme($group_node) { is_active, is_admin, created. Other values are passed to hook implementations. */ function og_save_subscription($gid, $uid, $args = array()) { + if ($uid > 0) { $sql = "SELECT COUNT(*) FROM {og_uid} WHERE nid = %d AND uid = %d"; $cnt = db_result(db_query($sql, $gid, $uid)); $time = time(); @@ -638,6 +664,7 @@ function og_save_subscription($gid, $uid module_invoke_all('og', 'user update', $gid, $uid, $args); } } +} function og_delete_subscription($gid, $uid, $args = array()){ $sql = "DELETE FROM {og_uid} WHERE nid = %d AND uid = %d"; @@ -713,7 +740,15 @@ function og_mail($key, &$message, $param * @return string 'approval', 'subscribed' or 'rejected' depending on the group's configuration. **/ function og_subscribe_user($gid, $account, $request = NULL) { - // moderated groups must approve all members (selective=1) + if ($account->uid == 0) { + // Silly admins can do this. Maybe code can too when an account gets deleted. See http://drupal.org/node/434632. + $return_value = array( + 'type' => 'rejected', + 'message' => t('Membership request to the %group group was rejected; the anonymous user may not join a group.', array('%group' => $node->title)) + ); + } + else { + // Moderated groups must approve all members (selective=1). $node = node_load($gid); switch ($node->og_selective) { case OG_MODERATED: @@ -750,7 +785,7 @@ function og_subscribe_user($gid, $accoun case OG_OPEN: og_save_subscription($gid, $account->uid, array('is_active' => 1)); $return_value = array('type' => 'subscribed', - 'message' => t('You are now a member of the %group.', array('%group' => $node->title))); + 'message' => t('You are now a member of %group.', array('%group' => $node->title))); break; case OG_CLOSED: case OG_INVITE_ONLY: @@ -763,6 +798,8 @@ function og_subscribe_user($gid, $accoun 'message' => t('Membership request to the %group group was rejected, only group administrators can add users to this group.', array('%group' => $node->title))); } } + } + return $return_value; } @@ -1239,13 +1276,15 @@ function og_nodeapi(&$node, $op, $teaser case 'update': if (og_is_group_type($node->type)) { og_update_group($node); - // make sure the node owner is a full powered member + if ($node->uid > 0) { + // Make sure the node owner is a full powered member. og_save_subscription($node->nid, $node->uid, array('is_active' => 1, 'is_admin' => 1)); - // load new group into $user->og_groups so that author can get redirected to the new group + // Load new group into $user->og_groups so that author can get redirected to the new group. if ($node->uid == $user->uid) { $user->og_groups = og_get_subscriptions($user->uid, 1, TRUE); } } + } else { og_save_ancestry($node); } @@ -1323,6 +1362,11 @@ function og_form_alter(&$form, &$form_st } drupal_set_breadcrumb($bc); } + // Pass the gids if it exists in the request, and not set already by + // another module, so it can be used by og_form_add_og_audience(). + if (!empty($_GET['gids']) && empty($form_state['og_gids'])) { + $form_state['og_gids'] = $_GET['gids']; + } og_form_add_og_audience($form, $form_state); } } @@ -1599,9 +1643,9 @@ function og_node_groups_distinguish($gro */ function og_form_add_og_audience(&$form, &$form_state) { global $user; - // Determine the selected groups if FAPI doesn't tell us. - if (isset($_GET['gids'])) { - $gids = $_GET['gids']; + // Determine the selected groups if it is stored in the form_state. + if (!empty($form_state['og_gids'][0]) && empty($form_state['og_groups'])) { + $gids = explode(',', $form_state['og_gids'][0]); } $node = $form['#node']; @@ -1719,7 +1763,7 @@ function og_form_add_og_audience(&$form, '#type' => $type, '#title' => t('Audience'), '#attributes' => array('class' => 'og-audience'), - '#options' => $options, + '#options' => $type == 'checkboxes' ? array_map('filter_xss', $options) : $options, '#required' => $required, '#description' => format_plural(count($options), 'Show this post in this group.', 'Show this post in these groups.'), '#default_value' => $groups ? $groups : array(), @@ -2182,7 +2226,7 @@ function og_og_create_links($group) { $type_url_str = str_replace('_', '-', $post_type); if (module_invoke($types[$post_type]->module, 'access', 'create', $post_type, $user)) { $links["create_$post_type"] = l(t('Create !type', array('!type' => $type_name)), "node/add/$type_url_str", array( - 'title' => t('Add a new !type in this group.', array('!type' => $type_name)), + 'attributes' => array('title' => t('Add a new !type in this group.', array('!type' => $type_name))), 'query' => "gids[]=$group->nid", )); } @@ -2235,6 +2279,7 @@ function og_token_list($type = 'all') { $tokens['node']['ogname-raw'] = t('Unfiltered title of top group. WARNING - raw user input.'); $tokens['node']['og-id'] = t('ID of top group'); $tokens['node']['og-type'] = t("Type of top group"); + $tokens['node']['ogalias'] = t("URL alias for the top group."); return $tokens; } } @@ -2251,15 +2296,17 @@ function og_token_values($type, $object $values['ogname-raw'] = ''; $values['og-id'] = ''; $values['og-type'] = ''; + $values['ogalias'] = ''; if (!empty($object->og_groups) && is_array($object->og_groups)) { $gids = array_filter($object->og_groups); foreach ($gids as $gid) { $group = db_fetch_object(db_query("SELECT title, type FROM {node} WHERE nid = %d", $gid)); $values['ogname'] = check_plain($group->title); - $values['ogname-raw'] = $title; + $values['ogname-raw'] = $group->title; $values['og-id'] = $gid; $values['og-type'] = check_plain($group->type); + $values['ogalias'] = drupal_get_path_alias('node/'. $gid); break; } return $values; Only in ./: og.module.orig diff -upwr /Users/bryn/projects/ogrc3orig/og.pages.inc ./og.pages.inc --- /Users/bryn/projects/ogrc3orig/og.pages.inc 2009-06-09 21:07:27.000000000 -0400 +++ ./og.pages.inc 2009-08-11 16:13:23.000000000 -0400 @@ -214,6 +214,9 @@ function og_invite_form_validate($form, elseif (in_array($user->mail, $emails)) { form_set_error('mails', t('You may not invite yourself - @self.', array('@self' => $user->mail))); } + elseif (in_array($user->name, $emails)) { + form_set_error('mails', t('You may not invite yourself - @self.', array('@self' => $user->name))); + } else { $valid_emails = array(); $bad = array(); @@ -253,16 +256,69 @@ function og_invite_form_submit($form, &$ '@group' => $node->title, '@description' => $node->og_description, '@site' => variable_get('site_name', 'drupal'), - '!group_url' => url("og/subscribe/$node->nid", array('absolute' => TRUE)), '@body' => $form_state['values']['pmessage'], ); global $user; $from = $user->mail; + $sent = 0; foreach ($emails as $mail) { + $variables['!group_url'] = url("og/subscribe/$node->nid", array('absolute' => TRUE)); + $account = user_load(array('mail' => $mail)); + $wasinactive = FALSE; + if ($account) { + $sql = "SELECT is_active,uid FROM {og_uid} WHERE uid = %d AND nid = %d"; + $oguid = db_fetch_array(db_query($sql, $account->uid, $node->nid)); + if ($oguid['uid']) { + // user already active/pending + if($oguid['is_active']) { + drupal_set_message("@username is already a member of the group", array('@username' => $account->name)); + continue; + } else { + $wasinactive = TRUE; + drupal_set_message('@username already requested group membership', array('@username' => $account->name)); + } + } + } + + if (og_is_group_admin($node)) { + if ($account) { + if ($wasinactive) { + // user wanted to be a member, we want them to be a member--oh happy day + db_query("UPDATE {og_uid} SET is_active = 1 WHERE uid = %d and nid = %d", $account->uid, $node->nid); + drupal_set_message('@username has now been approved as a member', array('@username' => $account->name)); + } else { + $sql = "SELECT COUNT(*) FROM {og_invite} WHERE mail = '%d' AND group_nid = %d"; + $cnt = db_result(db_query($sql, $account->uid, $node->nid)); + if ($cnt) { + drupal_set_message(t('@username was already on the invitation list for this group, but will be re-invited.', array('@username' => $account->name))); + } + else { + $record = array('mail' => $account->uid, 'group_nid' => $node->nid); + $result = drupal_write_record('og_invite', $record); + } + } + } else { + $sql = "SELECT COUNT(*) FROM {og_invite} WHERE mail = '%s' AND group_nid = %d"; + $cnt = db_result(db_query($sql, $mail, $node->nid)); + if ($cnt) { + drupal_set_message(t('@email was already on the invitation list for this group, but will be re-invited.', array('@email' => $mail))); + } + else { + $record = array('mail' => $mail, 'group_nid' => $node->nid); + $result = drupal_write_record('og_invite', $record); + } + $variables['!group_url'] = url('user/register', array('absolute' => TRUE)); + } + } + // could still be here if user in there but is_active = 0: don't send another invitation if they don't need one + if ($wasinactive) { + continue; + } + $sent ++; drupal_mail('og', 'invite_user', $mail, $GLOBALS['language'], $variables, $from); } - drupal_set_message(format_plural(count($emails), '1 invitation sent.', '@count invitations sent.')); + drupal_set_message(format_plural($sent, '1 invitation sent.', '@count invitations sent.')); } function og_subscribe($node, $uid = NULL) { @@ -286,7 +342,10 @@ function og_subscribe($node, $uid = NULL $account = user_load(array('uid' => $uid)); } - if ($node->og_selective >= OG_INVITE_ONLY || $node->status == 0 || !og_is_group_type($node->type)) { + $sql = "SELECT COUNT(*) FROM {og_invite} WHERE mail = '%d' AND group_nid = %d"; + $cnt = db_result(db_query($sql, $account->uid, $node->nid)); + + if (($node->og_selective >= OG_INVITE_ONLY && !$cnt) || $node->og_selective >= OG_CLOSED || $node->status == 0 || !og_is_group_type($node->type)) { drupal_access_denied(); exit(); } @@ -312,7 +371,9 @@ function og_subscribe($node, $uid = NULL function og_confirm_subscribe($form_state, $gid, $node, $account) { $form['gid'] = array('#type' => 'value', '#value' => $gid); $form['account'] = array('#type' => 'value', '#value' => $account); - if ($node->og_selective == OG_MODERATED) { + $sql = "SELECT COUNT(*) FROM {og_invite} WHERE mail = '%d' AND group_nid = %d"; + $cnt = db_result(db_query($sql, $account->uid, $node->nid)); + if ($node->og_selective == OG_MODERATED && !$cnt) { $form['request'] = array( '#type' => 'textarea', '#title' => t('Additional details'), @@ -384,6 +445,7 @@ function og_add_users($form_state, $grou '#description' => t('Add one or more usernames in order to associate users with this group. Multiple usernames should be separated by a comma.'), '#element_validate' => array('og_add_users_og_names_validate'), ); + $form['og_names']['#description'] .= '
' . t('If you wish to use emails in addition to usernames, invite will accept a mix of both.', array( '@url' => url('og/invite/'. $group_node->nid))); $form['op'] = array('#type' => 'submit', '#value' => t('Add users')); $form['gid'] = array('#type' => 'value', '#value' => $group_node->nid); return $form;