theme configuration page.', array('@url' => url('admin/build/themes')));
break;
}
}
}
function og_menu() {
// Anon users should be able to get to the join page
$items['og/subscribe/%node'] = array(
'type' => MENU_CALLBACK,
'page callback' => 'og_subscribe',
'page arguments' => array(2),
'access callback' => 'node_access',
'access arguments' => array('view', 2),
'title' => 'Join group'
);
$items['og/opml'] = array(
'type' => MENU_CALLBACK,
'page callback' => 'og_opml',
'access callback' => 'user_is_logged_in',
'title' => 'OPML',
);
$items['og/unsubscribe/%node/%user'] = array(
'type' => MENU_CALLBACK,
'page callback' => 'drupal_get_form',
'page arguments' => array('og_confirm_unsubscribe', 2, 3),
'access callback' => 'og_menu_access_unsubscribe',
'access arguments' => array(2, 3),
'title' => 'Leave group',
);
$items['og/approve/%node/%user/%'] = array(
'type' => MENU_CALLBACK,
'page callback' => 'og_approve',
'page arguments' => array(2, 3, 4),
'access callback' => 'og_is_group_admin',
'access arguments' => array(2),
'title' => 'Approve membership request'
);
$items['og/deny/%node/%user/%'] = array(
'type' => MENU_CALLBACK,
'page callback' => 'og_deny',
'page arguments' => array(2, 3, 4),
'access callback' => 'og_is_group_admin',
'access arguments' => array(2),
'title' => 'Deny membership request',
);
$items['og/create_admin/%node/%user'] = array(
'type' => MENU_CALLBACK,
'page callback' => 'drupal_get_form',
'page arguments' => array('og_create_admin_confirm', 2, 3),
'access callback' => 'og_is_group_admin',
'access arguments' => array(2),
'title' => 'Create group administrator',
);
$items['og/delete_admin/%node/%user'] = array(
'type' => MENU_CALLBACK,
'page callback' => 'drupal_get_form',
'page arguments' => array('og_remove_admin_confirm', 2, 3),
'access callback' => 'og_is_group_admin',
'access arguments' => array(2),
'title' => 'Delete group administrator',
);
// members only and group may not be invite-only or closed
$items['og/invite/%node'] = array(
'page callback' => 'drupal_get_form',
'page arguments' => array('og_invite_form', 2),
'access callback' => 'og_menu_access_invite',
'access arguments' => array(2),
'title' => 'Send invitation',
'type' => MENU_CALLBACK,
);
$items["og/manage/%node"] = array(
'page callback' => 'og_manage',
'page arguments' => array(2),
'access callback' => 'og_is_group_member',
'access arguments' => array(2, FALSE),
'title' => 'Manage membership',
'type' => MENU_CALLBACK,
);
$items['og/activity'] = array(
'title' => 'Group activity',
'page callback' => 'og_page_activity',
'access arguments' => array('administer organic groups'),
'weight' => 4,
'type' => MENU_LOCAL_TASK,
);
$items['admin/og'] = array(
'title' => 'Organic groups',
'description' => 'Administer the suite of Organic groups modules.',
'position' => 'right',
'weight' => -5,
'page callback' => 'system_admin_menu_block_page',
'access arguments' => array('administer site configuration'),
'file' => 'system.admin.inc',
'file path' => drupal_get_path('module', 'system'),
);
$items['admin/og/og'] = array(
'page callback' => 'drupal_get_form',
'page arguments' => array('og_admin_settings'),
'title' => 'Organic groups configuration',
'access arguments' => array('administer site configuration'),
'description' => 'Configure the main Organic groups module (og).',
'file' => 'og.admin.inc',
'weight' => -5,
);
// group admin only
$items['og/users/%node/add_user'] = array(
'page callback' => 'drupal_get_form',
'title' => 'Add members',
'page arguments' => array('og_add_users', 2),
'type' => MENU_LOCAL_TASK,
'weight' => 5,
'access callback' => 'og_is_group_admin',
'access arguments' => array(2),
);
// Email tab on group node.
$items['node/%node/email'] = array(
'title' => 'E-mail',
'page callback' => 'drupal_get_form',
'page arguments' => array('og_email_form', 1),
'access callback' => 'og_is_group_admin',
'access arguments' => array(1),
'type' => MENU_LOCAL_TASK,
'weight' => 7
);
return $items;
}
function og_menu_access_unsubscribe($group_node, $account) {
global $user;
// Unsubscribee must already be a member or pending member.
if (!og_is_group_member($group_node, FALSE, $account->uid)) {
// Check pending as well.
$subs = og_get_subscriptions($account->uid, 0);
foreach ($subs as $key => $sub) {
if ($group_node->nid == $key) {
$is_member = TRUE;
break;
}
}
if (empty($is_member)) {
return FALSE;
}
}
// Only admins can remove another member
if ($account->uid != $user->uid && !og_is_group_admin($group_node)) {
return FALSE;
}
// Regular users may not unsubscribe from CLOSED groups.
if ($group_node->og_selective == OG_CLOSED && !og_is_group_admin($group_node)) {
return FALSE;
}
// Group manager may not unsubscribe
if ($group_node->uid == $account->uid) {
return FALSE;
}
// Protect private groups.
if (!node_access('view', $group_node)) {
return FALSE;
}
return TRUE;
}
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_picture($gid) {
return og_is_picture() && og_is_group_member($gid) ? TRUE : FALSE;
}
/**
* Check a user's membership in a group.
*
* @param gid
* An integer or a node object representing the group node.
* @param $include_admins
* Whether or not site admins are considered members.
* @param $uid
* Pass a user id, or pass NULL in order to check current user.
*/
function og_is_group_member($gid, $include_admins = TRUE, $uid = NULL) {
if ($uid) {
$user = user_load(array('uid' => $uid));
}
else {
global $user;
}
// Allow caller to pass in a full $node. Used by menu items.
if (is_object($gid)) {
$gid = $gid->nid;
}
$groups = array_keys($user->og_groups);
if ($include_admins) {
return user_access('administer nodes', $user) || in_array($gid, $groups) ? TRUE : FALSE;
}
else {
return in_array($gid, $groups) ? TRUE : FALSE;
}
}
/**
* Determine whether user can act as a group administrator for a given group.
*
* @param string $node
* A group node object.
* @param string $account
* A user account object. If not supplied, the current user is assumed.
* @return boolean
*/
function og_is_group_admin($node, $account = NULL) {
if (is_null($account)) {
$account = $GLOBALS['user'];
}
return og_is_group_type($node->type) && (user_access('administer nodes', $account) || !empty($account->og_groups[$node->nid]['is_admin']));
}
// An implementation of hook_theme().
function og_theme() {
return array(
'opml_icon' => array('arguments' => array('url')),
'og_format_subscriber_status' => array('arguments' => array('group')),
'og_mission' => array('template' => 'og-mission', 'arguments' => array('form' => NULL)),
);
}
/**
* Simplify $mission variable for the template
*/
function og_preprocess_og_mission(&$variables) {
$variables['mission'] = $variables['form']['#value'];
}
/**
* Make group context available to javascript for ad tags and anaytics.
*
* @return void
**/
function og_preprocess_page(&$variables) {
if ($group_node = og_get_group_context()) {
$data = array(array('og' => array('group_context' => array(
'nid' => $group_node->nid,
'title' => $group_node->title,
'type' => $group_node->type,
))));
// Copied from drupal_get_js().
$js = "\n". '\n";
$variables['scripts'] .= $js;
}
}
/**
* Enrich non group nodes with the list og groups that the node belongs to.
*
* @return void
**/
function og_preprocess_node(&$variables) {
$og_links = array();
$node = $variables['node'];
// TODO: _both is not present during node preview and when you remove all audiences.
// So group links don't curently show then.
if (og_is_group_post_type($node->type) && !empty($node->og_groups_both)) {
$current_groups = og_node_groups_distinguish($node->og_groups_both, FALSE);
foreach ($current_groups['accessible'] as $gid => $item) {
$og_links['og_'. $gid] = array('title' => $item['title'], 'href' => "node/$gid");
}
$variables['og_links']['view'] = theme('links', $og_links);
$variables['og_links']['raw'] = $og_links;
array_unshift($variables['template_files'], 'node-og-group-post');
}
elseif (og_is_group_type($node->type)) {
// This looks awful on a group node
unset($variables['submitted']);
array_unshift($variables['template_files'], 'node-og-group');
}
}
function og_theme_registry_alter(&$variables) {
// Check for og provided templates just before we use the default node.tpl.php
array_splice($variables['node']['theme paths'], 1, 0, drupal_get_path('module', 'og'));
}
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));
}
else {
$user->og_groups = array();
}
drupal_add_css(drupal_get_path('module', 'og'). '/og.css');
// Set group context and language if needed.
if ($node = og_determine_context()) {
og_set_language($node);
}
// TODOL: try to set this less often to avoid filling sessions table during spidering.
$_SESSION['og_last'] = $node;
}
/**
* Set the language for the page based on group's language. Will have no effect
* if user has set a personal language.
* @param string $node
* A group node object.
*/
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);
if ($og_language == $user_language) {
$GLOBALS['language'] = $og_language;
}
}
}
/**
* Implementation of hook_perm().
*/
function og_perm() {
return array('administer organic groups');
}
/**
* Implementation of hook_og().
*/
function og_og($op, $gid, $uid, $args) {
if (module_exists('rules')) {
if (in_array($op, array('user insert', 'user delete'))) {
$op = str_replace(' ', '_', $op);
rules_invoke_event('og_'. $op, $uid, $gid);
}
// Pending member was approved.
elseif ($op = 'user update' && $args['is_active']) {
rules_invoke_event('og_user_approved', $uid, $gid);
}
}
}
/**
* Set group context based on URL. Modules may override what we set here.
* Set a custom theme based on what group is being displayed (if any).
* Be smart about selecting the 'active' group for ambiguous urls like node/$nid
* TODOL see http://api.drupal.org/api/function/menu_get_object/6
*/
function og_determine_context() {
global $custom_theme;
$group_node = NULL; // a node object containing the 'active' group for this request
if (arg(0) == 'og' && is_numeric(arg(2))) {
$group_node = og_set_theme(arg(2));
}
elseif (arg(0) == 'node' && is_numeric(arg(1))) {
$group_node = og_set_theme(arg(1));
}
elseif (arg(0) == 'node' && arg(1) == 'add' && arg(2) == 'book' && arg(3) == 'parent') {
$group_node = og_set_theme(arg(4));
$_REQUEST['edit']['og_groups'][] = $group_node->nid; // checks the right box on node form
}
elseif (arg(0) == 'node' && arg(1) == 'add' && isset($_REQUEST['gids'])) {
$gid = intval(current($_REQUEST['gids']));
$group_node = node_load($gid);
$custom_theme = $group_node->og_theme;
}
elseif (arg(0) == 'comment' && in_array(arg(1), array('edit', 'delete')) && is_numeric(arg(2))) {
$nid = db_result(db_query('SELECT nid FROM {comments} WHERE cid = %d', arg(2)));
$group_node = og_set_theme($nid);
}
elseif (arg(0) == 'comment' && is_numeric(arg(2))) {
// comment/reply/cid/nid URL pattern.
$group_node = og_set_theme(arg(2));
}
return og_set_group_context($group_node);
}
/**
* API function for getting the group context (if any) for the current request. Used
* for things like setting current theme and breadcrumbs. This context is set during og_determine_context()
*
* @return $node object
*/
function og_get_group_context() {
return og_set_group_context();
}
/**
* API function for setting the group context for the current request. Modules may set this as needed.
* This context is originally set during og_determine_context().
*
* @return $node object
*/
function og_set_group_context($group_node = NULL) {
static $stored_group_node;
if (is_object($group_node) && og_is_group_type($group_node->type)) {
$stored_group_node = $group_node;
}
return $stored_group_node;
}
function og_set_theme($nid) {
global $custom_theme, $user;
$node = node_load(intval($nid));
if (og_is_group_type($node->type)) {
$custom_theme = $node->og_theme;
return $node;
}
elseif (isset($node->og_groups)) {
switch (count($node->og_groups)) {
case 0:
return NULL;
case 1:
$group_node = node_load($node->og_groups[0]);
$custom_theme = $group_node->og_theme;
break;
default:
// Node is in multiple groups. Preference goes to the group we showed on the prior page view (if any),
// Then to a group the current user is a member of.
if (isset($_SESSION['og_last']) && in_array($_SESSION['og_last']->nid, $node->og_groups)) {
$group_node = node_load($_SESSION['og_last']->nid);
$custom_theme = $group_node->og_theme;
}
else {
$groups = array();
// intersect the node's groups with the user's groups
if ($user->uid) {
$groups = array_intersect($node->og_groups, array_keys($user->og_groups));
}
// no user is logged in, or none of the node's groups are the user's groups
if (empty($groups)) {
$groups = $node->og_groups;
}
// use array_shift and not [0] because array_intersect preserves keys
$group_node = node_load(array_shift($groups));
$custom_theme = $group_node->og_theme;
}
}
return $group_node;
}
}
/**
* Admins may broadcast email to all their members
*
* @ingroup forms
* @param $node
* The group node.
*/
function og_email_form($form_state, $node) {
drupal_set_title(t('Send email to %group', array('%group' => $node->title)));
if (!empty($form_state['post'])) {
drupal_set_message(t('Your email will be sent to all members of this group. Please use this feature sparingly.'));
}
$form['subject'] = array(
'#type' => 'textfield',
'#title' => t('Subject'),
'#size' => 70,
'#maxlength' => 250,
'#description' => t('Enter a subject for your email.'),
'#required' => TRUE,
);
$form['body'] = array(
'#type' => 'textarea',
'#title' => t('Body'),
'#rows' => 5,
'#cols' => 90,
'#description' => t('Enter a body for your email.'),
'#required' => TRUE
);
$form['send'] = array('#type' => 'submit', '#value' => t('Send email'));
$form['gid'] = array('#type' => 'value', '#value' => $node->nid);
return $form;
}
function og_email_form_submit($form, &$form_state) {
global $user;
$node = node_load($form_state['values']['gid']);
$variables = array(
'@group' => $node->title,
'@subject' => $form_state['values']['subject'],
'@body' => $form_state['values']['body'],
'@site' => variable_get('site_name', 'drupal'),
'!url_group' => url("node/$node->nid", array('absolute' => TRUE)),
'!url_unsubscribe' => url("og/unsubscribe/$node->nid/$user->uid", array('absolute' => TRUE))
);
$from = $user->mail;
$sql = og_list_users_sql(1);
$result = db_query($sql, $form_state['values']['gid']);
while ($row = db_fetch_object($result)) {
$emails[] = $row->mail;
}
foreach ($emails as $mail) {
drupal_mail('og', 'admin_email', trim($mail), $GLOBALS['language'], $variables, $from);
}
drupal_set_message(format_plural(count($emails), '1 email sent.', '@count emails sent.'));
}
function og_manage($group_node) {
global $user;
// warn users who can't receive mail anyway
if ($txt = user_validate_mail($user->mail)) {
drupal_set_message($txt, 'error');
return '';
}
$bc = og_get_breadcrumb($group_node);
drupal_set_breadcrumb($bc);
return drupal_get_form('og_manage_form', $group_node);
}
function og_manage_form($form_state, $group) {
global $user;
// avoid double messages on form submit
if (!$form_state['post']) {
// group manager can't leave
if ($group->og_selective == OG_CLOSED) {
drupal_set_message(t('You may not leave this group because it is a closed group. You should request removal from a group administrator.'));
}
elseif ($group->uid == $user->uid) {
drupal_set_message(t('You may not leave this group because you are its owner. A site administrator can assign ownership to another user and then you may leave.'));
}
else {
$links[] = l(t('Leave this group'), "og/unsubscribe/$group->nid/$user->uid");
$form['unsubscribe'] = array('#value' => theme('item_list', $links));
}
}
$submit_button = FALSE;
// og_email can be NULL when you enable og on an existing site.
if (empty($user->og_email)) {
$user->og_email = OG_NOTIFICATION_SELECTIVE;
}
switch ($user->og_email) {
case OG_NOTIFICATION_SELECTIVE:
$form['mail_type'] = array(
'#type' => 'radios',
'#title' => t('Email notification'),
'#default_value' => $user->og_groups[$group->nid]['mail_type'],
'#options' => array(
1 => t('Enabled'),
0 => t('Disabled')
),
'#description' => t('Do you want to receive an email each time a message is posted to this group?')
);
$submit_button = TRUE;
break;
case OG_NOTIFICATION_ALWAYS:
$form['mail_type'] = array(
'#type' => 'item',
'#title' => t('Email notification'),
'#value' => t('Your user profile is configured to: Always receive email notifications.', array('!prof' => url("user/$user->uid/edit"))),
);
break;
case OG_NOTIFICATION_NEVER:
$form['mail_type'] = array(
'#type' => 'item',
'#title' => t('Email notification'),
'#value' => t('Your user profile is configured to: Never receive email notifications.', array('!prof' => url("user/$user->uid/edit")))
);
break;
}
if ($submit_button) {
$form['op'] = array('#type' => 'submit', '#value' => t('Save'));
}
$form['gid'] = array('#type' => 'value', '#value' => $group->nid);
return $form;
}
function og_manage_form_submit($form, &$form_state) {
global $user;
$passed_values = $form_state['values'];
unset($passed_values['gid'], $passed_values['op'], $passed_values['form_id'], $passed_values['form_build_id'], $passed_values['form_token']);
og_save_subscription($form_state['values']['gid'], $user->uid, $passed_values);
drupal_set_message(t('Membership saved.'));
}
/**
* Low level function for managing membership
*
* @param $gid node ID of a group
* @param $uid user ID of user
* @param $args an array with details of this membership. Possible array keys are:
is_active, is_admin, mail_type, created
*/
function og_save_subscription($gid, $uid, $args = array()) {
$sql = "SELECT COUNT(*) FROM {og_uid} WHERE nid = %d AND uid = %d";
$cnt = db_result(db_query($sql, $gid, $uid));
$time = time();
if ($cnt == 0) {
// this pattern borrowed from user_save()
$fields = array('nid', 'uid', 'created', 'changed');
$values = array($gid, $uid, isset($args['created']) ? $args['created'] : $time, $time);
unset($args['created']);
foreach ($args as $key => $value) {
$fields[] = db_escape_string($key);
$values[] = $value;
$s[] = "'%s'";
}
// TODO: consider using drupal_write_record()
db_query('INSERT INTO {og_uid} ('. implode(', ', $fields). ') VALUES (%d, %d, %d, %d, '. implode(', ', $s). ')', $values);
module_invoke_all('og', 'user insert', $gid, $uid, $args);
}
else {
$cond[] = 'changed = '. $time;
foreach ($args as $key => $value) {
$cond[] = db_escape_string($key)." = '". db_escape_string($value). "'";
}
$cond = implode(', ', $cond);
db_query("UPDATE {og_uid} SET $cond WHERE nid = %d AND uid = %d", $gid, $uid);
module_invoke_all('og', 'user update', $gid, $uid, $args);
}
}
function og_delete_subscription($gid, $uid){
$sql = "DELETE FROM {og_uid} WHERE nid = %d AND uid = %d";
db_query($sql, $gid, $uid);
module_invoke_all('og', 'user delete', $gid, $uid, array());
}
function og_approve($node, $account, $token) {
if (!og_check_token($token, $node->nid)) {
drupal_set_message(t('Bad token. You seem to have followed an invalid link.'), 'error');
drupal_access_denied();
return;
}
if (og_is_group_member($node, FALSE, $account->uid)) {
drupal_set_message(t('!name already approved to group %group.', array('!name' => theme('username', $account), '%group' => $node->title)), 'error');
return '';
}
else {
og_save_subscription($node->nid, $account->uid, array('is_active' => 1));
drupal_set_message(t('Membership request approved.'));
$variables = array(
'@title' => $node->title,
'!group_url'=> url("node/$node->nid", array('absolute' => TRUE))
);
$account = user_load(array('uid' => $account->uid));
drupal_mail('og', 'approve_user', $account->mail, $GLOBALS['language'], $variables);
drupal_goto("node/$node->nid");
}
}
function og_deny($node, $account, $token) {
if (!og_check_token($token, $node->nid)) {
drupal_set_message(t('Bad token. You seem to have followed an invalid link.'), 'error');
drupal_access_denied();
return;
}
og_delete_subscription($node->nid, $account->uid);
drupal_set_message(t('Membership request denied.'));
$variables = array(
'@title' => $node->title,
'!group_url' => url("node/$node->nid", array('absolute' => TRUE))
);
$account = user_load(array('uid' => $account->uid));
drupal_mail('og', 'deny_user', $account->mail, $GLOBALS['language'], $variables);
drupal_goto("node/$node->nid");
}
/**
* OG create admin form
*/
function og_create_admin_confirm($form_state, $node, $account) {
$form['node'] = array('#type' => 'value', '#value' => $node);
$form['account'] = array('#type' => 'value', '#value' => $account);
return confirm_form($form,
t('Are you sure you want to make %name a group administrator for the group %title?', array('%name' => $account->name, '%title' => $node->title)),
"og/users/$node->nid",
' ',
t('Confirm'),
t('Cancel'));
}
/**
* Confirm og create admin form
*/
function og_create_admin_confirm_submit($form, &$form_state) {
$account = $form_state['values']['account'];
$node = $form_state['values']['node'];
og_save_subscription($node->nid, $account->uid, array('is_admin' => 1));
drupal_set_message(t('%name was promoted to group administrator.', array('%name' => $account->name)));
$variables = array(
'@group' => $node->title,
'!group_url' => url("node/$node->nid", array('absolute' => TRUE)),
'@username' => $account->name
);
drupal_mail('og', 'new_admin', $account->mail, $GLOBALS['language'], $variables);
$form_state['#redirect'] = "og/users/$node->nid";
}
/**
* OG remove admin form
*/
function og_remove_admin_confirm($form_state, $node, $account) {
$form['gid'] = array('#type' => 'value', '#value' => $node->nid);
$form['account'] = array('#type' => 'value', '#value' => $account);
return confirm_form($form,
t('Are you sure you want to remove %name as a group administrator for the group %title?', array('%name' => $account->name, '%title' => $node->title)),
"og/users/$node->nid",
' ',
t('Remove'),
t('Cancel')
);
}
/**
* Confirm og remove admin form
*/
function og_remove_admin_confirm_submit($form, &$form_state) {
$account = $form_state['values']['account'];
$gid = $form_state['values']['gid'];
og_save_subscription($gid, $account->uid, array('is_admin' => 0));
drupal_set_message(t('%name is no longer a group administrator.', array('%name' => $account->name)));
$form_state['redirect'] = "og/users/$gid";
}
function og_invite_form($form_state, $node) {
$bc = og_get_breadcrumb($node);
drupal_set_breadcrumb($bc);
$max = variable_get('og_email_max', 10);
$form['mails'] = array(
'#type' => 'textarea',
'#title' => t('Email addresses or usernames'),
'#description' => t('Enter up to %max email addresses or usernames. Separate multiple addresses by commas or new lines. Each person will receive an invitation message from you.', array('%max' => $max))
);
$form['pmessage'] = array(
'#type' => 'textarea',
'#title' => t('Personal message'),
'#description' => t('Optional. Enter a message which will become part of the invitation email.')
);
$form['op'] = array('#type' => 'submit', '#value' => t('Send invitation'));
$form['gid'] = array ('#type' => 'value', '#value' => $node->nid);
return $form;
}
// TODOL Use #element_validate as per http://drupal.org/node/144132#element-validate
function og_invite_form_validate($form, &$form_state) {
global $user;
$max = variable_get('og_email_max', 10);
$mails = $form_state['values']['mails'];
$mails = str_replace("\n", ',', $mails);
$emails = explode(',', $mails);
if (count($emails) > $max) {
form_set_error('mails', t('You may not specify more than %max email addresses or usernames.', array('%max' => $max)));
}
elseif (in_array($user->mail, $emails)) {
form_set_error('mails', t('You may not invite yourself - @self.', array('@self' => $user->mail)));
}
else {
$valid_emails = array();
$bad = array();
foreach ($emails as $email) {
$email = trim($email);
if (empty($email)) {
continue;
}
if (valid_email_address($email)) {
$valid_emails[] = $email;
}
else {
$account = user_load(array('name' => check_plain($email)));
if ($account->mail) {
$valid_emails[] = $account->mail;
}
else {
$bad[] = $email;
}
}
}
if (count($bad)) {
form_set_error('mails', t('Invalid email address or username: @value.', array('@value' => implode(', ', $bad))));
}
else {
// Store valid e-mails so we don't have to go through that looping again on submit
$form_state['valid_emails'] = $valid_emails;
}
}
}
function og_invite_form_submit($form, &$form_state) {
$emails = $form_state['valid_emails'];
$node = node_load($form_state['values']['gid']);
$variables = array(
'@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;
foreach ($emails as $mail) {
drupal_mail('og', 'invite_user', $mail, $GLOBALS['language'], $variables, $from);
}
drupal_set_message(format_plural(count($emails), '1 invitation sent.', '@count invitations sent.'));
}
function og_subscribe($node, $uid = NULL) {
global $user;
if (is_null($uid)) {
if ($user->uid) {
$account = $user;
}
else {
drupal_set_message(t('In order to join this group, you must login or register a new account. After you have successfully done so, you will need to request membership again.'));
drupal_goto('user');
}
}
else {
$account = user_load(array('uid' => $uid));
}
if ($node->og_selective >= OG_INVITE_ONLY || $node->status == 0 || !og_is_group_type($node->type)) {
drupal_access_denied();
exit();
}
// Only admins can add another member.
if ($account->uid != $user->uid && !og_is_group_admin($node)) {
drupal_access_denied();
exit();
}
// User is already a member of this group, redirect to group homepage.
else if (isset($account->og_groups[$node->nid])) {
drupal_set_message(t('@user is already a member the group @group.', array('@user' => $account->name, '@group' => $node->title)));
drupal_goto('node/'. $node->nid);
}
else {
return drupal_get_form('og_confirm_subscribe', $node->nid, $node, $account);
}
}
/**
* Confirm og membership form
*/
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) {
$form['request'] = array(
'#type' => 'textarea',
'#title' => t('Additional details'),
'#description' => t('Add any detail which will help an administrator decide whether to approve or deny your membership request.')
);
}
else {
$form['request'] = array(
'#type' => 'value',
'#value' => '',
);
}
return confirm_form($form,
t('Are you sure you want to join the group %title?', array('%title' => $node->title)),
'node/'. $node->nid, ' ',
t('Join'), t('Cancel'));
}
/**
* Confirm og membership submit handler
*/
function og_confirm_subscribe_submit($form, &$form_state) {
$return = og_subscribe_user($form_state['values']['gid'], $form_state['values']['account'], $form_state['values']['request']);
if (!empty($return['message'])) {
drupal_set_message($return['message']);
}
$form_state['redirect'] = 'node/'. $form_state['values']['gid'];
}
/**
* Create a new membership for a given user to given group and send proper email. Edits to membership should
* go through og_save_subscription(). No access control since this is an API function.
*
* @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)
$node = node_load($gid);
switch ($node->og_selective) {
case OG_MODERATED:
$admins = array();
og_save_subscription($gid, $account->uid, array('is_active' => 0));
$sql = og_list_users_sql(1, 1, NULL);
$res = db_query($sql, $node->nid);
while ($row = db_fetch_object($res)) {
if ($row->mail) {
$admins[] = $row->mail;
}
}
if ($admins) {
$variables = array(
'@group' => $node->title,
'@username' => $account->name,
'!approve_url' => url("og/approve/$node->nid/$account->uid", array('absolute' => TRUE)),
'!group_url' => url("og/users/$node->nid", array('absolute' => TRUE)),
'@request' => $request,
);
drupal_mail('og', 'request_user', implode(', ', $admins), $GLOBALS['language'], $variables);
}
$return_value = array('type' => 'approval',
'message' => t('Membership request to the %group group awaits approval by an administrator.', array('%group' => $node->title)));
break;
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)));
break;
case OG_CLOSED:
case OG_INVITE_ONLY:
// admins can add members to these groups, but others can't.
if (og_is_group_admin($node)) {
og_save_subscription($gid, $account->uid, array('is_active' => 1));
}
else {
$return_value = array('type' => 'rejected',
'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;
}
/**
* Confirm og unsubscription form
*/
function og_confirm_unsubscribe($form_state, $group_node, $account) {
$form['group_node'] = array('#type' => 'value', '#value' => $group_node);
$form['account'] = array('#type' => 'value', '#value' => $account);
return confirm_form($form,
t('Are you sure you want to remove !name from the group %title?', array('!name' => theme('username', $account), '%title' => $group_node->title)),
'og/users/'. $group_node->nid, ' ', t('Remove'), t('Cancel'));
}
/**
* Confirm og unsubscription submit handler
*/
function og_confirm_unsubscribe_submit($form, &$form_state) {
global $user;
$group_node = $form_state['values']['group_node'];
$account = $form_state['values']['account'];
og_delete_subscription($group_node->nid, $account->uid);
drupal_set_message(t('%user removed from %group.', array('%user' => $account->name, '%group' => $group_node->title)));
if ($user->uid == $account->uid) {
return $form_state['redirect'] = 'og';
}
else {
$form_state['redirect'] = "node/". $group_node->nid;
}
}
/**
* Load all memberships for a given user.
*
* Since a user's memberships are loaded into $user object, this function is only occasionally
* useful to get group memberships for users other than the current user. Even
* then, it often makes sense to call user_load() instead of this function.
*
* @return array
**/
function og_get_subscriptions($uid, $min_is_active = 1, $reset = FALSE) {
static $subscriptions = array();
if ($reset) {
unset($subscriptions[$uid]);
}
if (!isset($subscriptions[$uid][$min_is_active])) {
list($types, $in) = og_get_sql_args();
array_unshift($types, $min_is_active);
array_unshift($types, $uid);
$sql = "SELECT n.title, n.type, n.status, ou.* FROM {og_uid} ou INNER JOIN {node} n ON ou.nid = n.nid WHERE ou.uid = %d AND ou.is_active >= %d AND n.type $in ORDER BY n.title";
$result = db_query($sql, $types);
while ($row = db_fetch_array($result)) {
$subscriptions[$uid][$min_is_active][$row['nid']] = $row;
}
if (!isset($subscriptions[$uid][$min_is_active])) {
$subscriptions[$uid][$min_is_active] = array();
}
}
return $subscriptions[$uid][$min_is_active];
}
function og_list_users_sql($min_is_active = 1, $min_is_admin = 0, $orderby='u.name ASC', $count = FALSE) {
$order = '';
if ($count) {
$fields = 'COUNT(*)';
}
else {
$fields = "u.uid, u.name, u.mail, u.picture, ou.*";
if ($orderby) {
$order = "ORDER BY $orderby";
}
}
return "SELECT $fields FROM {og_uid} ou INNER JOIN {users} u ON ou.uid = u.uid WHERE ou.nid = %d AND u.status > 0 AND ou.is_active >= $min_is_active AND ou.is_admin >= $min_is_admin $order";
}
function og_add_users($form_state, $group_node) {
$form['og_names'] = array(
'#type' => 'textarea',
'#title' => t('List of users'),
'#rows' => 5,
'#cols' => 70,
// No autocomplete b/c user_autocomplete can't handle commas like taxonomy. pls improve core.
// '#autocomplete_path' => 'user/autocomplete',
'#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['op'] = array('#type' => 'submit', '#value' => t('Add users'));
$form['gid'] = array('#type' => 'value', '#value' => $group_node->nid);
return $form;
}
// An #element_validate handler
function og_add_users_og_names_validate($form, $form_state) {
$names = explode(',', $form_state['values']['og_names']);
foreach ($names as $name) {
$account = user_load(array('name' => trim($name)));
if (isset($account->uid)) {
$accounts[] = $account;
$uids[] = $account->uid;
}
else {
$bad[] = check_plain($name);
$err = TRUE;
}
}
if (isset($err)) {
form_set_error('og_names', format_plural(count($bad), 'Unrecognized name: %bad.', 'Unrecognized names: %bad.', array('%bad' => implode(', ', $bad))));
}
}
function og_add_users_submit($form, &$form_state) {
// Safest option is to do a select, filter existing members, then insert.
$names = explode(',', $form_state['values']['og_names']);
foreach ($names as $name) {
$account = user_load(array('name' => trim($name)));
if ($account->uid) {
$accounts[] = $account;
}
}
foreach ($accounts as $account) {
og_save_subscription($form_state['values']['gid'], $account->uid, array('is_active' => 1));
}
drupal_set_message(format_plural(count($accounts), '1 user added to the group.', '@count users added to the group.'));
}
// TODOL: use Views for opml page.
function og_opml() {
$output = "\n";
$output .= "\n";
$output .= "\n";
$output .= ''. check_plain(variable_get('site_name', 'Drupal')) ."\n";
$output .= ''. gmdate('r') ."\n";
$output .= "\n";
$output .= "\n";
global $user;
foreach ($user->og_groups as $gid => $group) {
$output .= ' TRUE)) ."\" />\n";
}
$output .= "\n";
$output .= "\n";
drupal_set_header('Content-Type: text/xml; charset=utf-8');
print $output;
}
function og_page_activity() {
$sql = "SELECT oga.group_nid, node_group_nid.title, node_group_nid.uid, u.name, COUNT(*) as ncount, MAX(n.created) as ncreated, SUM(ncs.comment_count) as ccount, MAX(last_comment_timestamp) AS lct FROM {node} n INNER JOIN {og_ancestry} oga ON n.nid = oga.nid LEFT JOIN {node_comment_statistics} ncs ON n.nid = ncs.nid INNER JOIN {node} node_group_nid ON oga.group_nid = node_group_nid.nid INNER JOIN {users} u ON node_group_nid.uid = u.uid WHERE n.status = 1 GROUP BY oga.group_nid, node_group_nid.title, node_group_nid.uid, u.name";
$header = array(
array('data' => t('Title'), 'field' => 'node_group_nid.title'),
array('data' => t('Manager'), 'field' => 'u.name'),
array('data' => t('Posts'), 'field' => 'ncount'),
array('data' => t('Comments'), 'field' => 'ccount'),
array('data' => t('Age'), 'field' => 'node_group_nid.created'),
array('data' => t('Last comment'), 'field' => 'lct', 'sort' => 'asc'),
);
$result = db_query($sql. tablesort_sql($header));
while ($row = db_fetch_object($result)) {
$rows[] = array(
l($row->title, "node/$row->group_nid"),
theme('username', $row),
$row->ncount,
$row->ccount,
format_interval(time()-$row->ncreated),
format_interval(time()-$row->lct),
);
}
if (!isset($rows)) {
$rows[] = array(array('data' => t('No groups available.'), 'colspan' => 6));
}
return theme('table', $header, $rows);
}
/**
* When you view a group, you really see some facts about the group in a block and then
* lists of nodes affiliated with that group. The node list is provided by the View of
* your choice (see the variable 'og_home_page_view'). If you use og_panels.module and the group has defined
* a default home page, then that page becomes the presentation of the GHP.
*
* @return void
* Add changes to $node->content by reference.
**/
//
function og_view_group(&$node, $teaser = FALSE, $page = FALSE) {
if ($teaser || !$page) {
$node->content['og_description'] = array(
'#type' => 'item',
'#title' => t('Description'),
'#value' => $node->og_description,
);
}
else {
// See http://drupal.org/files/issues/bc-fixup-204415-50.patch for an alternate way
$bc[] = l(t('Home'), '');
$bc[] = l(t('Groups'), 'og');
drupal_set_breadcrumb($bc);
unset($node->content['body']);
$node->content['og_mission'] = array(
'#value' => $node->body, // node_prepare() already ran check_markup()
'#node' => $node,
'#weight' => -3,
'#theme' => 'og_mission',
);
}
}
function og_home_empty($node) {
global $user;
$dest = drupal_get_destination();
if (og_is_group_member($node->nid)) {
$msg = t('No posts in this group.');
}
else {
$msg = t('No public posts in this group.');
if (!$user->uid) {
$msg .= ' '. t('You must register or login and become a member in order to post messages, and view any private posts.', array('!register' => url("user/register", array('query' => $dest)), '!login' => url("user/login", array('query' => $dest))));
}
// TODOL: hide this from pending members too
elseif ($node->og_selective < OG_INVITE_ONLY) {
$msg .= ' '. t('Consider joining this group in order to view its posts.', array('!url' => url("og/subscribe/$node->nid", array('query' => $dest))));
}
}
drupal_set_message($msg);
}
function og_selective_map() {
return array(
OG_OPEN => t('Open'),
OG_MODERATED => t('Moderated'),
OG_INVITE_ONLY => t('Invite only'),
OG_CLOSED => t('Closed'),
);
}
/**
* Adds standard fields for any node configured to be a group node.
*
* @param object $node
*/
function og_group_form($node, $form_state) {
global $user;
// Set the default values for a new item. By using += rather than =, we
// only overwrite array keys that have not yet been set. It's safe to use
// on both an empty array, and an incoming array with full or partial data.
$node = (array)$node;
$node += array(
'og_description' => NULL,
'og_theme' => NULL,
'og_language' => NULL,
'nid' => NULL,
);
$node = (object)$node;
$form['og_description'] = array(
'#type' => 'textfield',
'#title' => t('Description'),
'#default_value' => $node->og_description,
'#size' => 70,
'#maxlength' => 150,
'#required' => TRUE,
'#description' => t('A brief description for the group details block and the group directory.'),
'#weight' => -4
);
$default = isset($node->nid) ? $node->og_selective : OG_OPEN;
$options = array(
t('Open - membership requests are accepted immediately.'),
t('Moderated - membership requests must be approved.'),
t('Invite only - membership must be created by an administrator.'),
t('Closed - membership is exclusively managed by an administrator.'),
);
$form['og_selective'] = array(
'#type' => 'radios',
'#title' => t('Membership requests'),
'#required' => TRUE,
'#default_value' => $default,
'#options' => $options,
'#description' => t('How should membership requests be handled in this group? When you select closed, users will not be able to join or leave.')
);
// registration checkbox
// get the visibility for normal users
$visibility = variable_get('og_visibility_registration', OG_REGISTRATION_CHOOSE_FALSE);
// admin can always choose registration checkbox - get right default
if (user_access('administer nodes')) {
$visibility = in_array($visibility, array(OG_REGISTRATION_NEVER, OG_REGISTRATION_CHOOSE_FALSE)) ? OG_REGISTRATION_CHOOSE_FALSE : OG_REGISTRATION_CHOOSE_TRUE;
}
$default = FALSE;
switch ($visibility) {
case OG_REGISTRATION_NEVER:
$form['og_register'] = array('#type' => 'value', '#value' => 0);
break;
case OG_REGISTRATION_ALWAYS:
$form['og_register'] = array('#type' => 'value', '#value' => 1);
break;
case OG_REGISTRATION_CHOOSE_TRUE:
$default = TRUE;
// fall through
case OG_REGISTRATION_CHOOSE_FALSE:
$form['og_register'] = array(
'#type' => 'checkbox',
'#title' => t('Registration form'),
'#default_value' => $node->nid ? $node->og_register : $default,
'#description' =>t('May users join this group during registration? If checked, a corresponding checkbox will be added to the registration form.'),
);
break;
}
// directory checkbox
$visibility = variable_get('og_visibility_directory', OG_DIRECTORY_CHOOSE_TRUE);
// override for admins - get right default
if (user_access('administer nodes')) {
$visibility = in_array($visibility, array(OG_DIRECTORY_NEVER, OG_DIRECTORY_CHOOSE_FALSE)) ? OG_DIRECTORY_CHOOSE_FALSE : OG_DIRECTORY_CHOOSE_TRUE;
}
$default = FALSE;
switch ($visibility) {
case OG_DIRECTORY_NEVER:
$form['og_directory'] = array('#type' => 'value', '#value' => 0);
break;
case OG_DIRECTORY_ALWAYS:
$form['og_directory'] = array('#type' => 'value', '#value' => 1);
break;
case OG_DIRECTORY_CHOOSE_TRUE:
$default = TRUE;
// fall through
case OG_DIRECTORY_CHOOSE_FALSE:
$form['og_directory'] = array(
'#type' => 'checkbox',
'#title' => t('List in groups directory'),
'#default_value' => $node->nid ? $node->og_directory : $default,
'#description' => t('Should this group appear on the list of groups page? Disabled if the group is set to private group.', array('@url' => url('og'))));
break;
}
if (module_exists('locale') && $languages = locale_language_list()) {
if (count($languages) > 1) {
$form['og_language'] = array(
'#type' => 'radios',
'#title' => t('Language'),
'#default_value' => $node->og_language,
'#options' => array('' => t('Language neutral')) + $languages,
'#description' => t('Selecting a different locale will change the interface language of the group. Users who have chosen a preferred language always see their chosen language.'),
);
}
}
if ($theme_form = system_theme_select_form(t('Selecting a different theme will change the look and feel of the group.'), isset($form_state['values']['theme']) ? $form_state['values']['theme'] : $node->og_theme, 2)) {
$theme_form['themes']['#weight'] = 8;
$form += $theme_form;
}
return $form;
}
// Returns all the group affiliations for a given node.
function og_get_node_groups($node) {
$groups = array();
if (!og_is_group_type($node->type)) {
$result = og_get_node_groups_result($node->nid);
while ($row = db_fetch_object($result)) {
$groups[$row->group_nid] = $row->title;
}
return $groups;
}
}
// The query for the get_node_groups function. Is reused in og.views.inc
function og_get_node_groups_result($nid) {
// We do not run db_rewrite_sql() here since we need to know about groups that the user cannot access as well (i.e. node edit).
$sql = "SELECT oga.group_nid, n.title FROM {node} n INNER JOIN {og_ancestry} oga ON n.nid = oga.group_nid WHERE oga.nid = %d";
return db_query($sql, $nid);
}
function og_presave_group(&$node) {
if (!empty($node->og_groups_inaccessible)) {
// Add the inaccessible groups which did not show in Audience selector
$node->og_groups = (array)$node->og_groups + $node->og_groups_inaccessible;
}
/**
* Change $node->theme to $node->og_theme so it matches node_load(). The node form uses $theme, not $og_theme.
* If author chose the default theme, then '' is written to DB and group follows changes made by site admin.
*/
if (isset($node->theme)) {
$node->og_theme = $node->theme;
}
else {
$node->og_theme = NULL;
}
// TODOL Once ancestry table uses drupal_write_record(), these are not needed.
if (!isset($node->og_public)) {
$node->og_public = TRUE;
}
if (!isset($node->og_language)) {
$node->og_language = NULL;
}
if (!isset($node->og_notification)) {
$node->og_notification = NULL;
}
if (!isset($node->og_private)) {
$node->og_private = FALSE;
}
// Normalize og_groups array
if (isset($node->og_groups)) {
$node->og_groups = array_filter($node->og_groups);
$node->og_groups = array_keys($node->og_groups);
}
// Support devel module's bulk node generation.
// Affiliate group posts with group(s). Also populate special group fields.
if (isset($node->devel_generate)) {
og_devel_generate($node);
}
}
function og_devel_generate(&$node) {
if (og_is_group_type($node->type)) {
$node->og_selective = rand(0,3);
$can_join = $node->og_selective <= OG_MODERATED;
$open_join = $node->og_selective < OG_MODERATED;
$node->og_register = $can_join ? rand(0,1) : FALSE;
$node->og_directory = $can_join ? rand(0,1) :FALSE;
$node->og_private = !$open_join ? rand(0,1) : FALSE; // Ignored if og_access not installed.
$node->og_description = devel_create_greeking(rand(1, 20), TRUE);
$node->og_notification = rand(0,1);
$node->og_language = NULL;
if (module_exists('locale') && $languages = locale_language_list()) {
if (count($languages) > 1) {
$node->og_language = array_rand($languages);
}
}
}
elseif (og_is_group_post_type($node->type)) {
$types = og_get_types('group');
$placeholders = db_placeholders($types, 'varchar');
$sql = "SELECT nid FROM {node} WHERE type IN ($placeholders) AND status = 1 ORDER BY RAND()";
$result = db_query_range($sql, $types, 0, rand(1,4));
while ($row = db_fetch_object($result)) {
$node->og_groups[] = $row->nid;
}
$node->og_public = rand(0,1); // Ignored if og_access not installed.
}
}
function og_load_group(&$node) {
$sql = 'SELECT selective AS og_selective, description AS og_description, theme AS og_theme, register AS og_register, directory AS og_directory, notification AS og_notification, language AS og_language, private AS og_private FROM {og} WHERE nid = %d';
$result = db_query($sql, $node->nid);
$node = (object) array_merge((array)$node, (array)db_fetch_array($result));
}
function og_insert_group($node) {
$sql = "INSERT INTO {og} (nid, theme, selective, description, register, directory, notification, language, private) VALUES (%d, '%s', %d, '%s', %d, %d, %d, '%s', %d)";
db_query($sql, $node->nid, $node->og_theme, $node->og_selective, $node->og_description, $node->og_register, $node->og_directory, $node->og_notification, $node->og_language, $node->og_private);
}
function og_update_group($node) {
// If an existing node becomes a group, then a row may not be present in {og} table.
// TODOH Rename fields, use drupal_write_record(), gets rid of notices when updating group.
$sql = "SELECT nid FROM {og} WHERE nid = %d";
if (db_result(db_query($sql, $node->nid))) {
$sql = "UPDATE {og} SET theme = '%s', selective = %d, register = %d, description = '%s', directory = %d, notification = %d, language = '%s', private = %d WHERE nid = %d";
db_query($sql, $node->og_theme, $node->og_selective, $node->og_register, $node->og_description, $node->og_directory, $node->og_notification, $node->og_language, $node->og_private, $node->nid);
}
else {
og_insert_group($node);
}
}
// Return a breadcrumb array for a given groupnode.
function og_get_breadcrumb($group_node) {
$bc[] = l(t('Home'), "");
$bc[] = l(t('Groups'), "og");
$bc[] = l($group_node->title, "node/$group_node->nid");
return $bc;
}
/**
* Implementation of hook_nodeapi().
*
*/
function og_nodeapi(&$node, $op, $teaser = NULL, $page = NULL) {
global $user;
switch ($op) {
case 'view':
$group_node = og_get_group_context();
if ($group_node && $page && !empty($node->og_groups)) {
$bc = og_get_breadcrumb($group_node);
drupal_set_breadcrumb($bc);
}
if (og_is_group_type($node->type)) {
og_view_group($node, $teaser, $page);
}
break;
case 'load':
if (og_is_group_type($node->type)) {
og_load_group($node);
}
if ($grps = og_get_node_groups($node)) {
// TODO: Refactor so we don't need 2 arrays.
$node->og_groups = array_keys($grps);
$node->og_groups_both = $grps;
$public = db_result(db_query_range("SELECT is_public FROM {og_ancestry} WHERE nid = %d", $node->nid, 0, 1));
$node->og_public = $public ? TRUE : FALSE;
}
break;
case 'validate':
if (og_is_group_type($node->type)) {
// Email address is required for contacting the Manager
$account = user_load(array('uid' => $node->uid));
if ($account->uid > 0 && empty($account->mail)) {
form_set_error('name', t('The group manager %name, must have an email address in his user profile.', array('%name' => $account->name, '@profile' => url("user/$node->uid/edit"))));
}
}
else {
// Ensure that a group is selected if groups are required. needed when author has no groups. In other cases, fapi does the validation
if (og_is_group_post_type($node->type) && variable_get('og_audience_required', FALSE) && !user_access('administer nodes')) {
if (!isset($node->og_groups)) {
form_set_error('title', t('You must join a group before posting on this web site.', array('@join' => url('og'))));
}
}
}
break;
case 'presave':
og_presave_group($node);
break;
case 'delete':
$sql = "DELETE FROM {og} WHERE nid = %d";
db_query($sql, $node->nid);
$sql = "DELETE FROM {og_ancestry} WHERE nid = %d";
db_query($sql, $node->nid);
$sql = "DELETE FROM {og_uid} WHERE nid = %d";
db_query($sql, $node->nid);
break;
case 'insert':
if (og_is_group_type($node->type)) {
og_insert_group($node);
// 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
if ($node->uid == $user->uid) {
$user->og_groups = og_get_subscriptions($node->uid, 1, TRUE);
}
$account = user_load(array('uid' => $node->uid));
$variables = array(
'@group' => $node->title,
'!group_url' => url("node/$node->nid", array('absolute' => TRUE)),
'@username' => $account->name,
'!invite_url' => url("og/invite/$node->nid", array('absolute' => TRUE))
);
// Alert the user that they are now the admin of the group.
drupal_mail('og', 'new_admin', $account->mail, $GLOBALS['language'], $variables);
}
else {
og_save_ancestry($node);
}
// Disable emails for now until Notifications integration is in.
$skip_notification = TRUE;
// Any module that returns TRUE from its hook_og_notify($node) will prevent sending notifications.
// og2list and og_subscriptions use this to send own notifications.
foreach(module_implements('og_notify') as $module) {
if (module_invoke($module,'og_notify',$node)) {
$skip_notification = TRUE;
break;
}
}
if (!$skip_notification && $node->status && og_is_mail_type($node->type) && $node->og_groups) {
if (module_exists('job_queue')) {
$description = t('OG: notify group members about node %nid - !link.', array('%nid' => $node->nid, '!link' => l($node->title, "node/$node->nid")));
job_queue_add('og_mail', $description, array('node', $node->nid));
}
else {
og_mail_notify('node', $node);
}
}
break;
case 'update':
if (og_is_group_type($node->type)) {
og_update_group($node);
// 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
if ($node->uid == $user->uid) {
$user->og_groups = og_get_subscriptions($user->uid, 1, TRUE);
}
}
else {
og_save_ancestry($node);
}
break;
case 'search result':
// Similar code in og_preprocess_node()
$current_groups['accessible'] = array();
if ($node->og_groups) {
$current_groups = og_node_groups_distinguish($node->og_groups_both, FALSE);
}
return format_plural(count($current_groups['accessible']), '1 group', '@count groups');
// TODOL: bad formatting. commented out.
// foreach ($current_groups['accessible'] as $gid => $item) {
// $og_links['og_'. $gid] = array('title' => $item['title'], 'href' => "node/$gid");
// }
// return theme('links', $og_links, array('class' => 'groups links'));
break;
case 'rss item':
if ($node->og_groups) {
foreach ($node->og_groups_both as $gid => $title) {
// TODO: should be absolute link. core bug.
$append['og_links'] = array('title' => $title, 'href' => "node/$gid");
$ret[] = array('key' => 'group',
'value' => check_plain($title),
'attributes' => array('domain' => url("node/$gid", array('absolute' => TRUE))));
}
$node->body .= '
';
$node->teaser .= '';
return $ret;
}
break;
}
}
function og_msgid_server() {
global $base_url;
if ($dir = str_replace("/", ".", substr(strchr(str_replace("http://", "", $base_url), "/"), 1))) {
$at = "@$dir.". $_SERVER['SERVER_NAME'];
}
else {
$at = '@'. $_SERVER['SERVER_NAME'];
}
return strtolower($at);
}
function og_form_alter(&$form, &$form_state, $form_id) {
// Add audience selection to node forms
if (isset($form['#node']) && $form_id == $form['#node']->type .'_node_form') {
$node = $form['#node'];
if (og_is_group_type($node->type)) {
$form = array_merge($form, og_group_form($node, $form_state));
// Don't trample on custom label.
if ($form['body_field']['body']['#title'] == t('Body')) {
$form['body_field']['body']['#title'] = t('Mission statement');
$form['body_field']['body']['#description'] = t('A welcome greeting for your group home page. Consider listing the group objectives and mission.');
}
$form['author']['name']['#title'] = t('Group manager');
$form['options']['sticky']['#title'] = t('Sticky at top of group home page and other lists.');
}
elseif (og_is_group_post_type($node->type)) {
if ($group_node = og_get_group_context()) {
$bc = og_get_breadcrumb($group_node);
if (isset($node->nid)) {
$bc[] = l($node->title, "node/$node->nid");
}
drupal_set_breadcrumb($bc);
}
og_form_add_og_audience($form, $form_state);
}
}
}
function og_form_node_type_form_alter(&$form, &$form_state) {
// Built in content types do not allow changes to type machine name.
if (isset($form['identity']['type']['#default_value'])) {
$usage = variable_get('og_content_type_usage_'. $form['identity']['type']['#default_value'], 'omitted');
}
else {
$usage = variable_get('og_content_type_usage_'. $form['identity']['type']['#value'], 'omitted');
}
// Persist $usage so that we can rebuild node access as needed.
$form['old_og_content_type_usage'] = array(
'#type' => 'value',
'#value' => $usage,
);
// We push to the front so we can unset() variables before they are saved.
array_unshift($form['#submit'], 'og_node_type_form_submit');
$options = og_types_map();
$form['og'] = array(
'#type' => 'fieldset',
'#title' => t('Organic groups'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
'#access' => user_access('administer organic groups'),
);
$form['og']['og_content_type_usage'] = array(
'#type' => 'radios',
'#title' => t('Organic groups usage'),
'#default_value' => $usage,
'#options' => $options,
'#description' => t('Specify how organic groups should treat nodes of this type. Nodes may behave as a group, as group posts, or may not participate in organic groups at all.'),
);
}
// Add option to migrate messages before deleting a group.
// TODO: add option to move memberships as well
function og_form_node_delete_confirm_alter(&$form, &$form_state) {
$node = node_load($form['nid']['#value']);
if (og_is_group_type($node->type)) {
og_node_delete_group_form($form);
}
elseif (og_is_group_post_type($node->type)) {
og_node_delete_nongroup_form($form);
}
}
// Rebuild node access if Usage has changed
function og_node_type_form_submit($form, &$form_state) {
$type = $form_state['values']['type'];
$var = 'og_content_type_usage';
$new = $form_state['values'][$var];
$old = $form_state['values']['old_'. $var];
if ($new != $old) {
node_access_needs_rebuild();
}
// Prevent this old variable from being saved to DB.
unset($form_state['values']['old_'. $var]);
}
// Form_alter() the node_delete form for a group
function og_node_delete_group_form(&$form) {
$options[] = t('Do nothing.');
$options[] = t("Delete all group posts which don't also belong to another group.");
if (user_access('administer nodes')) {
$options[] = t('Move all group posts to the group listed below.');
}
$form['verb'] = array(
'#type' => 'radios',
'#title' => t('Group posts'),
'#options' => $options,
'#weight' => -1,
'#description' => t('In addition to deleting this group, you may choose how to disposition the posts within it.')
);
if (user_access('administer nodes')) {
$options = og_all_groups_options();
unset($options[$form['nid']['#value']]);
$form['target'] = array(
'#type' => 'select',
'#title' => t('Target group'),
'#default_value' => 0,
'#options' => $options,
'#weight' => 0,
'#description' => t('If you chose Move all group posts above, specify a destination group.'),
);
// register a submit handler
$form['#submit'][] = 'og_node_delete_confirm_submit';
}
$form['actions']['submit']['#value'] = t('Delete group');
}
// Alter the node_delete form for a non-group.
// Redirect back to group home page after a delete.
function og_node_delete_nongroup_form(&$form) {
if ($groupnode = og_get_group_context()) {
$form['#redirect'] = "node/$groupnode->nid";
}
}
// Return all node ids belonging to a group. No access control.
// If you are retrieving for displaying, you should use code like below instead of this function:
// TODO: update this comment
// $info = views_build_view('items', $view, array($group_nid));
function og_group_child_nids($group_nid) {
$result = db_query('SELECT oga.nid FROM {og_ancestry} oga WHERE oga.group_nid = %d', $group_nid);
$child_nids = array();
while ($row = db_fetch_object($result)) {
$child_nids[] = $row->nid;
}
return $child_nids;
}
// Submit handler for node delete form. Handles deletes to group nodes.
function og_node_delete_confirm_submit($form, &$form_state) {
$deleted_group_nid = $form_state['values']['nid'];
$target_group_nid = $form_state['values']['target'];
$move_children = $form_state['values']['verb'] == 2;
$delete_orphans = $form_state['values']['verb'] == 1;
foreach (og_group_child_nids($deleted_group_nid) as $child_nid) {
$node = node_load($child_nid);
unset($node->og_groups[$deleted_group_nid]);
if ($move_children) {
// there is an array_unique() in og_save_ancestry which guards against duplicates so don't worry here.
$node->og_groups[$target_group_nid] = $target_group_nid;
}
if ($delete_orphans && count($node->og_groups) == 0) {
node_delete($node->nid);
}
else {
node_save($node);
}
}
if ($move_children) {
$form_state['redirect'] = 'node/'. $target_group_nid;
}
}
// Return an array containing all groups - suitable for a form item.
function og_all_groups_options() {
list($types, $in) = og_get_sql_args();
$sql = "SELECT n.title, n.nid FROM {node} n WHERE n.type $in AND n.status = 1 ORDER BY n.title ASC";
$result = db_query($sql, $types);
while ($row = db_fetch_object($result)) {
$options[$row->nid] = $row->title;
}
return isset($options) ? $options : array();
}
/**
* Iterate over a set of groups and separate out those that are inaccessible to the current user.
* Don't return groups of which the current user is a member, unless $exclude_joined=FALSE
* is passed. Used in og_form_add_og_audience() and node.tpl.php.
*
* @return array
* A two element array containing 'accessible' and 'inaccessible' keys.
**/
function og_node_groups_distinguish($groups_names_both, $exclude_joined = TRUE) {
global $user;
$current_groups = array('accessible' => array(), 'inaccessible' => array());
if (empty($groups_names_both)) {
// Do nothing.
}
else {
$placeholders = db_placeholders($groups_names_both);
$sql = 'SELECT n.nid FROM {node} n WHERE n.nid IN ('. $placeholders. ')';
$result = db_query(db_rewrite_sql($sql), array_keys($groups_names_both));
while ($row = db_fetch_object($result)) {
$current_groups['accessible'][$row->nid]['title'] = $groups_names_both[$row->nid];
}
foreach ($groups_names_both as $gid => $title) {
if (!in_array($gid, array_keys($current_groups['accessible']))) {
$current_groups['inaccessible'][$gid] = $gid;
}
}
if ($exclude_joined) {
// Don't return groups that the user has already joined (default).
$current_groups['accessible'] = array_diff_assoc($current_groups['accessible'], $user->og_groups);
}
}
return $current_groups;
}
/**
* Helper method to add OG audience fields to a given form. This is
* lives in a separate function from og_form_alter() so it can be shared
* by other OG contrib modules.
*/
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'];
}
$node = $form['#node'];
$required = variable_get('og_audience_required', 0) && !user_access('administer nodes');
$is_optgroup = FALSE;
// Determine the list of groups that are shown.
// Start by collecting all groups that the user is a member of.
$subs = og_get_subscriptions($user->uid);
$options = array();
foreach ($subs as $key => $val) {
$options[$key] = $val['title'];
}
if (user_access('administer nodes')) {
// Node admins see all of groups.
$all = og_all_groups_options();
$other = array_diff_assoc($all, $options);
// Use an optgroup if admin is not a member of all groups.
if ($other) {
$options = array(
t('My groups') => $options,
t('Other groups') => $other,
);
$is_optgroup = TRUE;
}
else {
$options = $all;
}
}
else {
// Classify those groups which the node already has but the author does not.
if (!isset($node->og_groups_both)) {
$node->og_groups_both = array();
}
$current_groups = og_node_groups_distinguish($node->og_groups_both);
// Put inaccessible groups in the $form so that they can persist. See og_presave_group() and og_access_alter_nongroup_form() in og_access.module
$form['og_invisible']['og_groups_inaccessible'] = array('#type' => 'value', '#value' => $current_groups['inaccessible']);
// Add the accessible groups that they node already belongs to.
if ($current_groups['accessible']) {
// Use an optgroup to distinguish between my memberships and additional groups in the Audience select.
// There is code below which assumes that $options does not have optgroups but that code is within a $simple check
// So we are OK as long as $simple does not apply to node edits.
// NOTE: If you form_alter the audience element, beware that it can sometimes be an optgroup.
foreach ($current_groups['accessible'] as $key => $val) {
$other[$key] = $val['title'];
}
$options = array(
t('My groups') => $options,
t('Other groups') => $other,
);
$is_optgroup = TRUE;
}
}
// Show read only item if we are non-admin, and in simple mode (i.e. non-checkboxes) and at least one group is in querystring
$simple = !user_access('administer organic groups') && !variable_get('og_audience_checkboxes', TRUE) && count($gids);
// determine value of audience multi-select
if (count($options) == 1 && $required) {
$gids = array_keys($options);
$gid = $gids[0];
$groups = array($gid);
// also show read only mode if user has 1 option and we are in required mode
$simple = TRUE;
}
elseif (!empty($gids)) {
// populate field from the querystring if sent
$groups = $gids;
if (!user_access('administer nodes') && $simple) {
// filter out any groups where author is not a member. we cannot rely on fapi to do this when in simple mode.
$groups = array_intersect($gids, array_keys($options));
}
}
elseif (isset($node->og_groups)) {
$groups = $node->og_groups;
}
else {
$groups = array();
}
// This is only used by og_access module right now.
$form['og_initial_groups'] = array(
'#type' => 'value',
'#value' => $groups,
);
// Emit the audience form element.
if ($simple) {
// 'simple' mode. read only.
if (count($groups)) {
foreach ($groups as $gid) {
$titles[] = $options[$gid];
$item_value = implode(', ', $titles);
}
$form['og_nodeapi']['visible']['og_groups_visible'] = array(
'#type' => 'item',
'#title' => t('Audience'),
'#value' => $item_value
);
$assoc_groups = drupal_map_assoc($groups);
// this 'value' element persists the audience value during submit process
$form['og_nodeapi']['invisible']['og_groups'] = array('#type' => 'value', '#value' => $assoc_groups);
}
}
elseif ($cnt = count($options, COUNT_RECURSIVE)) {
// show multi-select. if less than 20 choices, use checkboxes.
$type = $cnt >= 20 || $is_optgroup ? 'select' : 'checkboxes';
$form['og_nodeapi']['visible']['og_groups'] = array(
'#type' => $type,
'#title' => t('Audience'),
'#attributes' => array('class' => 'og-audience'),
'#options' => $options,
'#required' => $required,
'#description' => format_plural(count($options), 'Show this post in this group.', 'Show this post in these groups.'),
'#default_value' => $groups,
'#required' => $required,
'#multiple' => TRUE);
}
else if ($required) {
form_set_error('title', t('You must join a group before posting a %type.', array('@join' => url('og'), '%type' => node_get_types('name', $node->type))));
}
}
function og_comment($comment, $op) {
switch ($op) {
case 'publish':
$comment = (array) $comment;
// fall through
case 'insert':
// Disable emails until Notifications integration is in.
$skip_notification = TRUE;
// Any module that returns true from its hook_og_notify($node) will prevent sending notifications.
// og2list uses this to send its own notifications instead for mailing list content.
$node = node_load($comment['nid']);
foreach(module_implements('og_notify') as $module) {
if (module_invoke($module, 'og_notify', $node)) {
$skip_notification = TRUE;
break;
}
}
// remove the perm check after 5.3 is released.
if ($comment['status'] == COMMENT_PUBLISHED && !$skip_notification && og_is_mail_type($node->type) && isset($node->og_groups) && !empty($node->og_groups)) {
if (module_exists('job_queue')) {
$description = t('OG: Notify group members about comment %id on !link.', array('%id' => $comment['cid'], '!link' => l($node->title, "node/$node->nid", array('fragment' => 'comment-'. $comment['cid']))));
job_queue_add('og_mail', $description, array('comment', $comment['cid']));
}
else {
og_mail_notify('comment', (object)$comment);
}
}
break;
}
}
/**
* Send this node/comment via email to all email members. Called from og_nodeapi() and og_comment().
* Sometimes called from during cron if job_queue.module is enabled (recommended).
* TODO: this function is a bit messy. rethink.
*
* @param $type
* the object type: node or comment
* @param $id
* a node or comment object. if a non object is supplied, a load() operation is performed.
* @return
* none
*/
// TODOH: move to Notifications framework.
function og_mail_notify($type, $obj) {
if ($type == 'comment') {
if (!is_object($obj)) {
$obj = _comment_load($obj);
}
// registered user
if ($obj->uid) {
$account = user_load(array('uid' => $obj->uid));
$obj->name = $account->name;
}
else {
$obj->name = variable_get('anonymous', 'Anonymous');
}
$rendered = check_markup($obj->comment, $obj->format, FALSE);
$obj->body = $rendered;
$obj->teaser = $rendered;
$originalurl = url("node/$obj->nid", array('fragment' => "comment-$obj->cid", 'absolute' => TRUE));
$replyurl = url("comment/reply/$obj->nid/$obj->cid", array('absolute' => TRUE));
$node = node_load($obj->nid);
$obj->og_groups = $node->og_groups;
$obj->title = $node->title;
$obj->msgid = $obj->nid. '-'. $obj->cid. og_msgid_server();
$reply = $obj->nid. '-';
if ($obj->pid) {
$reply .= $obj->pid;
}
else {
$reply .= '0';
}
$obj->in_reply_to .= $reply. og_msgid_server();
$type_friendly = 'comment';
}
else {
if (!is_object($obj)) {
$obj = node_load($obj);
}
$account = user_load(array('uid' => $obj->uid));
$obj->name = $account->name;
$obj->subject = $obj->title;
// These calls to og_node_view() setup the node body and teaser, apply the
// appropriate input filters, and handle CCK fields, event fields, etc.
$obj->body = og_node_view($obj, FALSE);
$obj->teaser = og_node_view($obj, TRUE);
$obj->msgid = $obj->nid. '-0'. og_msgid_server();
$originalurl = url("node/$obj->nid", array('absolute' => TRUE));
$replyurl = url("comment/reply/$obj->nid", array('fragment' => 'comment-form', 'absolute' => TRUE));
$type_friendly = node_get_types('name', $obj);
}
// set email from variables
$variables = array(
'@user_mail' => $account->mail ? $account->mail : variable_get("site_mail", ini_get("sendmail_from")),
'@user_name' => mime_header_encode($obj->name),
'@site_mail' => variable_get("site_mail", ini_get("sendmail_from")),
'@site_name' => mime_header_encode(variable_get("site_name", 'Drupal')),
);
$from_mail = strtr(variable_get("og_email_notification_pattern", '@user_name <@site_mail>'), $variables);
$headers = array('X-Mailer' => 'Drupal - og_mail', 'Precedence' => 'list', 'Message-Id' => "<$obj->msgid>");
if ($obj->in_reply_to) {
$headers['In-Reply-To'] = "<$obj->in_reply_to>";
}
// set email body variables
$variables = array(
'@site' => variable_get('site_name', 'drupal'),
'!read_more' => $obj->readmore ? t('Read more') : t('View original'),
'!content_url' => $originalurl,
'!reply_url' => $replyurl,
'@title' => trim($obj->title),
'@subject' => trim($obj->subject),
'@node_full' => trim(og_mail_output($obj->body)),
'@node_teaser' => trim(og_mail_output($obj->teaser)),
'@username' => $obj->name
);
// Send email to selective subscribers and global subscribers.
// We use if() here in case node/comment no longer has any groups (i.e. this function can be called from cron).
if (is_array($obj->og_groups) && !empty($obj->og_groups)) {
$groups = implode(', ', $obj->og_groups);
// tricky query here. mysql returns NULL in the case of NULL != 0 so i rework this for 2 positive statements about og_email field
// ORDER BY favors newer groups to help them get ramped up. Thus, they are perceived as active.
$sql = "SELECT u.mail, u.uid, ou.nid AS gid, n.title AS group_name, n.uid AS group_uid, u.name AS group_owner, oug.og_email
FROM {og_uid} ou INNER JOIN {users} u ON ou.uid=u.uid
INNER JOIN {og_uid_global} oug ON ou.uid=oug.uid
INNER JOIN {node} n ON ou.nid=n.nid
WHERE ou.nid IN ($groups) AND (
(oug.og_email = %d AND ou.mail_type=1) OR
(oug.og_email = %d)
) AND u.status = 1 AND u.mail != '' AND ou.is_active = 1
ORDER by u.mail DESC, n.created DESC
";
$result = db_query($sql, OG_NOTIFICATION_SELECTIVE, OG_NOTIFICATION_ALWAYS);
$last_mail = '';
while ($row = db_fetch_object($result)) {
// only notify each user once. we used to do this with GROUP BY but got very hard to assure that all the selected fields came from same record.
if ($row->mail == $last_mail) {
continue;
}
$last_mail = $row->mail;
// New for D6. See http://drupal.org/node/114774#node_access_account
// TODO: check if this blocks comments properly.
if (node_access('view', $obj, user_load($row->uid))) {
// Append these group specific variables.
$variables['@group'] = $row->group_name;
$variables['!group_url'] = url("og/manage/$row->gid", array('absolute' => TRUE));
$variables['@type'] = $type_friendly;
$unsubscribe = url("og/manage/$row->gid", array('absolute' => TRUE));
$ownerurl = url("user/$row->group_uid", array('absolute' => TRUE));
$group_home = url("node/$row->gid", array('absolute' => TRUE));
$groupheaders = $headers + array(
'List-Id' => mime_header_encode($row->group_name). " <$group_home>",
'List-Unsubscribe' => "<$unsubscribe>",
'List-Owner' => mime_header_encode($row->group_owner). " <$ownerurl>",
"List-Archive" => "<$group_home>"
);
// todo no mail headers?
drupal_mail('og', 'mail', $row->mail, $GLOBALS['language'], $variables, $from_mail);
}
}
}
}
/**
* Similar to node_view() but without calling theme('node').
*
* This is needed to get the proper body or teaser for nodes (e.g. Event,
* Location, CCK node types) and to clean up the body for use in email
* notifications.
*
* @param $node
* The node object you want to view.
* @param $teaser
* Boolean to select the teaser or the body of the node.
*
* @return
* The requested data, filtered and ready for use in an e-mail.
*
* @see node_view()
* @see og_mail_notify()
*/
function og_node_view($node, $teaser = FALSE) {
// Remove the delimiter (if any) that separates the teaser from the body.
// TODO: this strips legitimate uses of '' also.
$node->body = str_replace('', '', $node->body);
// The 'view' hook can be implemented to overwrite the default function
// to display nodes.
if (node_hook($node, 'view')) {
node_invoke($node, 'view', $teaser, TRUE);
}
else {
$node = node_prepare($node, $teaser);
}
// Allow modules to change $node->body before viewing.
node_invoke_nodeapi($node, 'view', $teaser, TRUE);
return $teaser ? $node->teaser : $node->body;
}
// 2 functions ripped from mail.inc in project.module package
function og_mail_urls($url = 0) {
static $urls = array();
if ($url) {
$urls[] = strpos($url, '://') ? $url : url($url, array('absolute' => TRUE));
return count($urls);
}
return $urls;
}
// takes filtered HTML as input and transforms for email
// modified from project.module
function og_mail_output($body, $html = TRUE) {
static $i = 0;
if ($html) {
$pattern = '@]+ )*?href *= *"([^>"]+?)"[^>]*>([^<]+?)@ei';
$body = preg_replace($pattern, "'\\3 ['. og_mail_urls('\\2') .']'", $body);
$urls = og_mail_urls();
if (count($urls)) {
$body .= "\n";
for ($max = count($urls); $i < $max; $i++) {
$body .= '['. ($i + 1) .'] '. $urls[$i] ."\n";
}
}
$body = preg_replace('!?blockquote>!i', '"', $body);
$body = preg_replace('!?(em|i)>!i', '/', $body);
$body = preg_replace('!?(b|strong)>!i', '*', $body);
$body = preg_replace("@
(?!\n)@i", "\n", $body);
$body = preg_replace("@(?!\n\n)@i", "\n\n", $body);
$body = preg_replace("@(?!\n\n)@i", " #\n", $body);
$body = preg_replace("@(?!\n\n)@i", " ##\n", $body);
$body = preg_replace("@(?!\n\n)@i", " ###\n", $body);
$body = preg_replace("@(?!\n\n)@i", " ####\n", $body);
$body = preg_replace("@(li|dd)>\n?@i", "\n", $body);
$body = preg_replace("@@i", "\n\n# ", $body);
$body = preg_replace("@@i", "\n\n## ", $body);
$body = preg_replace("@@i", "\n\n### ", $body);
$body = preg_replace("@@i", "\n\n#### ", $body);
$body = preg_replace("@@i", "* ", $body);
$body = strip_tags($body);
$body = decode_entities($body);
$body = wordwrap($body, 72);
}
else {
$body = decode_entities($body);
}
return $body;
}
/**
* Implementation of hook_mail().
*/
function og_mail($key, &$message, $params) {
$language = $message['language'];
$message['subject'] .= _og_mail_text($key .'_subject', $language, $params);
$message['body'][] = _og_mail_text($key .'_body', $language, $params);
}
/**
* Define all OG email bodies
* Modelled after user.module
*/
function _og_mail_text($messageid, $language = NULL, $variables = array()) {
$langcode = isset($language) ? $language->language : NULL;
// Check if an admin setting overrides the default string.
if ($admin_setting = variable_get($messageid, FALSE)) {
return strtr($admin_setting, $variables);
}
// No override, return with default strings.
else {
switch ($messageid) {
case 'new_node_subject':
return t("@group: '@title' at @site", $variables, $langcode);
case 'new_node_body':
return t("@type '@subject' by @username\n\n@node_teaser\n\n!read_more: !content_url\nPost reply: !reply_url\n\n--\nYou are subscribed from the group '@group' at @site.\nTo manage your subscription, visit !group_url", $variables, $langcode);
case 'admin_email_subject':
return $variables['@subject'];
case 'admin_email_body':
return t("@body\n\n--\nThis message was sent by an administrator in the '@group' group at @site. To visit this group, browse to !url_group. To unsubscribe from this group, visit !url_unsubscribe", $variables, $langcode);
case 'approve_user_subject':
return t("Membership request approved for '@title'", $variables, $langcode);
case 'approve_user_body':
return t("You may now post messages in this group located at !group_url", $variables, $langcode);
case 'deny_user_subject':
return t("Membership request denied for '@title'", $variables, $langcode);
case 'deny_user_body':
return t("Sorry, your membership request was denied.", $variables, $langcode);
case 'invite_user_subject':
return t("Invitation to join the group '@group' at @site", $variables, $langcode);
case 'invite_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\n@description\nJoin: !group_url\n@body", $variables, $langcode);
case 'request_user_subject':
return t("Membership request for '@group' from '@username'", $variables, $langcode);
case 'request_user_body':
return t("To instantly approve this request, visit !approve_url.\nYou may deny this request or manage members at !group_url. \n\nPersonal message from @username:\n------------------\n\n@request", $variables, $langcode);
case 'new_admin_subject':
return t("You are now an administrator for the group '@group'", $variables, $langcode);
case 'new_admin_body':
return t("@username, you are now an administrator for the group '@group'.\n\nYou can administer this group by logging in here:\n !group_url", $variables, $langcode);
}
}
}
// Helper function for queries that need all group types.
function og_get_sql_args() {
if ($types = og_get_types('group')) {
$in = 'IN ('. db_placeholders($types, "varchar"). ')';
}
else {
$in = 'IS NULL';
}
return array($types, $in);
}
function og_user($op, $edit, &$account, $category = NULL) {
global $user;
switch ($op) {
case 'register':
$options = array();
list($types, $in) = og_get_sql_args();
// If groups are passed on querystring, just use them.
if (isset($_REQUEST['gids']) && $gids = $_REQUEST['gids']) {
$default_value = $gids;
foreach ($gids as $gid) {
$nids[] = (int)$gid;
}
$in_nids = db_placeholders($nids);
$sql = "SELECT n.nid, n.title, o.* FROM {node} n INNER JOIN {og} o ON n.nid = o.nid WHERE n.type $in AND n.status = 1 AND n.nid IN ($in_nids) ORDER BY n.title";
$result = db_query(db_rewrite_sql($sql), array_merge($types, $nids));
}
else {
$default_value = array();
// perhaps this should be a View
$result = db_query(db_rewrite_sql("SELECT n.nid, n.title, o.* FROM {node} n INNER JOIN {og} o ON n.nid = o.nid WHERE n.type $in AND n.status = 1 AND o.register = 1 ORDER BY n.title"), $types);
}
while ($group = db_fetch_object($result)) {
$options[$group->nid] = ''. t('Join %name.', array('%name' => $group->title)). "\n";
if ($group->selective) {
$options[$group->nid] .= ' '. t('(approval needed)');
}
}
if (count($options)) {
$form['og_register'] = array('#type' => 'fieldset', '#title' => t('Groups'));
$form['og_register']['og_register'] = array(
'#type' => 'checkboxes',
'#options' => $options,
'#default_value' => $default_value,
);
return $form;
}
case 'form':
if ($category == 'account' && !empty($account->og_groups)) {
$form['og_settings'] = array(
'#type' => 'fieldset',
'#title' => t('Organic groups settings'),
'#collapsible' => TRUE,
'#weight' => 4
);
$options = array(
OG_NOTIFICATION_NEVER => t('Never send email notifications. Useful when tracking activity via RSS feed instead.'),
OG_NOTIFICATION_ALWAYS => t('Always send email notifications.'),
OG_NOTIFICATION_SELECTIVE => t("Selectively send email notification based on the checkbox for each of my group's My membership page."),
);
$form['og_settings']['og_email'] = array(
'#type' => 'radios',
'#title' => t('Email notifications'),
'#options' => $options,
'#default_value' => isset($account->og_email) ? $account->og_email : variable_get('og_notification', 2),
'#description' => t('When posts are submitted into your groups, you may be notified via email.'),
);
return $form;
}
break;
case 'insert':
if (isset($edit['og_register'])) {
foreach (array_keys(array_filter($edit['og_register'])) as $gid) {
$return = og_subscribe_user($gid, $account);
if (!empty($return['message'])) {
drupal_set_message($return['message']);
}
}
}
$sql = 'INSERT INTO {og_uid_global} (uid, og_email) VALUES (%d, %d)';
db_query($sql, $account->uid, variable_get('og_notification', OG_NOTIFICATION_ALWAYS));
$account->og_email = NULL;
break;
case 'update':
if (isset($edit['og_email'])) {
$sql = 'UPDATE {og_uid_global} SET og_email=%d WHERE uid=%d';
db_query($sql, $edit['og_email'], $account->uid);
$account->og_email = NULL;
}
break;
case 'delete':
// User delete doesn't exist, but it should and will one day. See http://drupal.org/node/8
$sql = 'DELETE FROM {og_uid_global} WHERE uid=%d';
db_query($sql, $account->uid);
$sql = 'DELETE FROM {og_uid} WHERE uid=%d';
db_query($sql, $account->uid);
break;
case 'load':
$account->og_groups = og_get_subscriptions($account->uid);
$result = db_query_range("SELECT og_email FROM {og_uid_global} WHERE uid = %d", $account->uid, 0, 1);
if ($row = db_fetch_object($result)) {
$account->og_email = $row->og_email;
}
break;
case 'view':
if ($account->og_groups) {
foreach ($account->og_groups as $key => $val) {
$links[$key] = l($val['title'], "node/$key") . theme('og_format_subscriber_status', $val);
}
$account->content['summary']['groups'] = array(
'#type' => 'item',
'#title' => t('Groups'),
'#value' => theme('item_list', $links),
// Not working in 6
// '#theme' => 'item_list',
'#attributes' => array('class' => 'og_groups'),
// Only show list of groups to self and admins. TOOD: Make this configurable or doable via Views.
'#access' => $account->uid == $user->uid || user_access('administer organic groups'),
);
}
break;
}
}
function og_save_ancestry($node) {
if (og_is_group_post_type($node->type)) {
$sql = "DELETE FROM {og_ancestry} WHERE nid = %d";
db_query($sql, $node->nid);
if (isset($node->og_groups)) {
$node->og_groups = array_unique($node->og_groups);
foreach ($node->og_groups as $gid) {
$sql = "INSERT INTO {og_ancestry} (nid, group_nid, is_public) VALUES (%d, %d, %d)";
db_query($sql, $node->nid, $gid, $node->og_public);
}
}
}
}
/**
* An implementation of hook_node_type. Automatically update admin preferences when node type is renamed or removed.
*/
function og_node_type($op, $info) {
switch ($op) {
case 'delete':
variable_del('og_content_type_usage_'. $info->type);
break;
case 'update':
$usage = variable_get('og_content_type_usage_'. $info->type, 'omitted');
variable_set('og_content_type_usage_'. $info->type, $usage);
if (isset($info->old_type)) {
variable_del('og_content_type_usage_'. $info->old_type);
}
}
}
function og_types_map() {
$usages = array(
'group' => t('Group node'),
'omitted' => t('May not be posted into a group.'),
'group_post_standard_mail' => t('Standard group post (typically only author may edit). Sends email notifications.'),
'group_post_standard_nomail' => t('Standard group post (typically only author may edit). No email notification.'),
);
if (module_exists('og_access')) {
$usages['group_post_wiki_mail'] = t('Wiki group post (any group member may edit). Sends email notifications.');
$usages['group_post_wiki_nomail'] = t('Wiki group post (any group member may edit). No email notification.');
}
return $usages;
}
// Return all content types which meet a specified usage.
function og_get_types($usage) {
$types = node_get_types();
foreach ($types as $type) {
if ($usage == 'group_post') {
if (!og_is_omitted_type($type->type) && !og_is_group_type($type->type)) {
$return[$usage][] = $type->type;
}
}
else {
$type_usage = variable_get('og_content_type_usage_'. $type->type, 'omitted');
$return[$type_usage][] = $type->type;
}
}
return isset($return[$usage]) ? $return[$usage] : array();
}
// returns TRUE if node type should generate email notifications when posted to a group.
function og_is_mail_type($type) {
$usage = variable_get('og_content_type_usage_'. $type, 'omitted');
return strpos($usage, 'mail') && !strpos($usage, 'nomail') ? TRUE : FALSE;
}
// returns TRUE if node type lets all subscribers edit the node.
function og_is_wiki_type($type) {
$usage = variable_get('og_content_type_usage_'. $type, 'omitted');
return strpos($usage, 'wiki') ? TRUE : FALSE;
}
// returns TRUE if node type can be posted into a group.
function og_is_group_post_type($type) {
$usage = variable_get('og_content_type_usage_'. $type, 'omitted');
return strpos($usage, 'group_post') !== FALSE ? TRUE : FALSE;
}
function og_is_omitted_type($type) {
return variable_get('og_content_type_usage_'. $type, 'omitted') == 'omitted' ? TRUE : FALSE;
}
/**
* API function for determining whether a given node type has been designated
* by admin to behave as a group node (i.e. a container).
*
* @param string $type
* @return boolean
*/
function og_is_group_type($type) {
return variable_get('og_content_type_usage_'. $type, 'omitted') == 'group' ? TRUE : FALSE;
}
/**
* Implementation of hook_block().
*/
function og_block($op = 'list', $delta = 0, $edit = array()) {
if ($op == 'list') {
$blocks[0]['info'] = t('Group details');
$blocks[0]['cache'] = BLOCK_NO_CACHE;
// $blocks[1] used to be the album block. We do not change the numbers to not confuse people who update.
// $blocks[2] used to be the group members block. This is now served by Views. We do not change the numbers to not confuse people who update.
$blocks[3]['info'] = t('New groups');
$blocks[3]['cache'] = BLOCK_CACHE_PER_USER;
// Now provided by og_views. Please don't reuse this number 4
// $blocks[4]['info'] = t('My groups');
$blocks[5]['info'] = t('Group notifications');
$blocks[5]['cache'] = BLOCK_NO_CACHE;
// $blocks[6]['info'] = t('Group network');
// $blocks[6]['cache'] = BLOCK_CACHE_PER_USER;
// Auto-enable the group blocks for fresh installations.
// TODOL: In order for this to work, you must rehash blocks during install which has been problematic.
// $blocks[0]['status'] = 1;
// $blocks[0]['region'] = 'left';
// $blocks[0]['weight'] = -2;
// $blocks[5]['status'] = 1;
// $blocks[5]['region'] = 'left';
// $blocks[5]['weight'] = -1;
return $blocks;
}
elseif ($op == 'view') {
switch ($delta) {
case 0:
return og_block_details();
case 3:
return og_block_new();
case 5:
return og_block_notifications();
case 6:
return og_block_users_network();
}
}
elseif ($op == 'configure') {
switch ($delta) {
case 2:
$items['og_block_cnt'] = array(
'#type' => 'textfield',
'#title' => t('Maximum number of members to show'),
'#default_value' => variable_get("og_block_cnt_$delta", 10),
'#size' => 5,
);
$items['og_block_subscribers_is_admin'] = array(
'#type' => 'checkboxes',
'#title' => t('Group roles'),
'#default_value' => variable_get("og_block_subscribers_is_admin", array('members', 'admins')),
'#options' => array(
'members' => t('Standard members'),
'admins' => t('Administrators')
),
'#description' => t('You may specify which types of group members appear in the listing.'),
);
return $items;
case 3:
return array('og_block_cnt' => array('#type' => 'textfield', '#title' => t('Maximum number of groups to show'), '#default_value' => variable_get("og_block_cnt_$delta", 10), '#size' => 5, '#maxlength' => 255));
}
}
elseif ($op == 'save') {
switch ($delta) {
case 2:
if (isset($edit['og_block_subscribers_is_admin'])) {
variable_set("og_block_subscribers_is_admin", array_filter($edit['og_block_subscribers_is_admin']));
}
if (isset($edit['og_block_cnt'])) {
variable_set("og_block_cnt_$delta", $edit['og_block_cnt']);
}
break;
case 3:
if (isset($edit['og_block_cnt'])) {
variable_set("og_block_cnt_$delta", $edit['og_block_cnt']);
}
break;
}
}
}
function og_block_notifications() {
global $user;
if ($groupnode = og_get_group_context()) {
$content = t('This group offers an email subscription.', array('@email' => url('og/manage/'. $groupnode->nid)));
$block['content'] = $content;
$block['subject'] = t('Group notifications');
return $block;
}
}
/**
* Return code that emits an XML icon. TODOL: this opml icon belongs in theme.inc
*/
function theme_opml_icon($url) {
if ($image = theme('image', drupal_get_path('module', 'og'). '/images/opml-icon-16x16.png', t('OPML feed'), t('OPML feed'))) {
return ''. $image. '';
}
}
function og_block_new() {
list($types, $in) = og_get_sql_args();
$sql = "SELECT COUNT(*) FROM {node} n INNER JOIN {og} og ON n.nid = og.nid WHERE og.directory=1 AND n.type $in AND n.status = 1";
$cnt = db_result(db_query(db_rewrite_sql($sql), $types));
if ($cnt > 0) {
$max = variable_get('og_block_cnt_3', 10);
$sql = "SELECT n.nid, n.title FROM {node} n INNER JOIN {og} og ON n.nid = og.nid WHERE n.status = 1 AND n.type $in AND og.directory=1 ORDER BY nid DESC";
$result = db_query_range(db_rewrite_sql($sql), $types, 0, $max);
$output = node_title_list($result);
if ($cnt > $max) {
$output .= ''. l(t('more'), 'og', array('title' => t('Browse the newest groups.'))) .'
';
}
$block['subject'] = t('New groups');
$block['content'] = $output;
return $block;
}
}
function og_block_details() {
global $user;
// Only display group details if we have a group context.
if (($node = og_get_group_context()) && node_access('view', $node)) {
list($txt, $subscription) = og_subscriber_count_link($node);
if ($subscription == 'active' || user_access('administer nodes')) {
$links = module_invoke_all('og_create_links', $node);
// We want to open this up for OG_INVITE_ONLY but we need to handle invitation workflow much better. See http://drupal.org/node/170332
if ($node->og_selective < OG_INVITE_ONLY) {
$links['invite'] = l(t('Invite friend'), "og/invite/$node->nid");
}
$links['subscribers'] = $txt;
$links['manager'] = t('Manager: !name', array('!name' => theme('username', $node)));
// Site admins get a Join link if they are not yet subscribed.
$subscribe = isset($subscription) && og_is_group_member($node->nid, FALSE) ? l(t('My membership'), "og/manage/$node->nid") : og_subscribe_link($node);
if(isset($subscribe)) {
$links['my_membership'] = $subscribe;
}
}
elseif ($subscription == 'requested') {
$links['approval'] = t('Your membership request awaits approval.');
$links['delete'] = l(t('Delete request'), "og/unsubscribe/$node->nid/$user->uid", array('query' => 'destination=og'));
}
elseif (!$user->uid) {
$dest = drupal_get_destination();
$links['must_login'] = t('You must register/login in order to post into this group.', array('!register' => url("user/register", array('query' => $dest)), '!login' => url("user/login", array('query' => $dest))));
}
elseif ($node->og_selective < OG_INVITE_ONLY) {
$links['subscribe'] = og_subscribe_link($node);
}
elseif ($node->og_selective == OG_INVITE_ONLY) {
$links['closed'] = t('This is an invite only group. The group administrators add/remove members as needed.');
}
elseif ($node->og_selective == OG_CLOSED) {
$links['closed'] = t('This is a closed group. The group administrators add/remove members as needed.');
}
// Modify these links by reference. If you want control of the whole block, see og_block_details().
drupal_alter('og_links', $links, $node);
$block['content'] = theme('item_list', $links);
$block['subject'] = l($node->title, "node/$node->nid");
return $block;
}
}
/**
* Determine the number of active and pending members and the current user's membership state.
*
* @return array
* An array containing two strings. One for the number of members and another containing 'active' or 'requested'
*/
function og_subscriber_count_link($node) {
global $user;
$result = db_query(og_list_users_sql(0, 0, NULL), $node->nid);
$cntpending = $cntall = 0;
$subscription = '';
while ($row = db_fetch_object($result)) {
$cntall++;
if ($row->is_active == 0) {
$cntpending++;
}
if ($row->uid == $user->uid) {
if ($row->is_active) {
$subscription = 'active';
}
else {
$subscription = 'requested';
}
}
}
$txt = format_plural($cntall-$cntpending, '1 member', '@count members');
// The hyperlinked version of this text is supplied by og_views.module in alter hook.
$txt .= $cntpending ? " ($cntpending)" : '';
return array($txt, $subscription);
}
function og_subscribe_link($node) {
if ($node->og_selective == OG_MODERATED) {
$txt = t('Request membership');
}
elseif ($node->og_selective == OG_OPEN) {
$txt = t('Join');
}
if(isset($txt))
return l($txt, "og/subscribe/$node->nid", array('attributes' => array('rel' => 'nofollow'), 'query' => drupal_get_destination()));
}
// $group is an object containing a group node.
// TODO: Make this a proper menu
function og_og_create_links($group) {
global $user;
$post_types = og_get_types('group_post');
$types = node_get_types();
foreach ($post_types as $post_type) {
// We used to check for node_access(create) but then node admins would get a false positive and see node types that they could not create.
// When this becomes a proper menu in D6, we get sorting for free
$type_name = $types[$post_type]->name;
$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)),
'query' => "gids[]=$group->nid",
));
}
}
return isset($links) ? $links : array();
}
function og_is_picture() {
return variable_get('user_pictures', 0);
}
// TODOL: maybe use a custom theme('mark') here?
// Mark the current user's membership in a given group if it is pending.
function theme_og_format_subscriber_status($group) {
if (!$group['is_active']) {
return ' '. t('(pending approval)');
}
}
/**
* Implementation of hook_xmlrpc(). /*
*
*/
function og_xmlrpc() {
module_load_include('inc', 'og', 'og_xmlrpc');
return array(
array(
'og.subscribe_user',
'og_xmlrpc_subscribe_user',
array('struct', 'string', 'string', 'int', 'int'),
t('Add a user to a group.')),
array(
'og.getAllSubscribers',
'og_xmlrpc_get_all_subscribers',
array('array', 'string', 'string', 'int', 'int', 'int'),
t('All members for a given group.')),
array(
'og.getUserGroups',
'og_xmlrpc_get_user_groups',
array('array', 'string', 'string', 'int'),
t('Retrieve the group memberships for a given user.')),
);
}
/**
* Implementation of hook_token_list() for og specific tokens /*
*/
function og_token_list($type = 'all') {
if ($type == 'node' || $type == 'all') {
$tokens['node']['ogname'] = t('Title of top group');
$tokens['node']['ogname-raw'] = t('Unfiltered title of top group. WARNING - raw user input.');
$tokens['node']['og-id'] = t('ID of top group');
return $tokens;
}
}
/**
* Implementation of hook_token_values() for og specific tokens
*/
function og_token_values($type, $object = NULL) {
switch ($type) {
case 'node':
if (is_array($object->og_groups)) {
$gids = array_filter($object->og_groups);
foreach ($gids as $gid) {
$title = db_result(db_query("SELECT title FROM {node} WHERE nid = %d", $gid));
$values['ogname'] = check_plain($title);
$values['ogname-raw'] = $title;
$values['og-id'] = $gid;
break;
}
return $values;
}
break;
}
// No group info found. Return defaults.
$values['ogname'] = '';
$values['ogname-raw'] = '';
$values['og-id'] = '';
return $values;
}
function og_requirements($phase) {
$requirements = array();
// Ensure translations don't break at install time
$t = get_t();
if ($phase == 'runtime') {
$og_types = og_get_types('group');
$all_types = array_keys(node_get_types('types'));
if (!count(array_intersect($og_types, $all_types))) {
$requirements['og_group_types'] = array(
'title' => $t('Organic groups group type'),
'value' => $t('You have no node types which are acting as groups. See the notes section of the !readme_file and the content types fieldset at top of OG settings.', array('!readme_file' => og_readme(), '!settings' => url('admin/og/og'))),
'severity' => REQUIREMENT_ERROR,
);
}
if (!module_exists('job_queue')) {
$requirements['og_modules'] = array(
'title' => $t('Organic groups modules'),
'value' => $t('Organic groups works best when job_queue.module is enabled. See the integration section of the !readme_file.', array('@job_queue' => 'http://drupal.org/project/job_queue', '!readme_file' => og_readme())),
'severity' => REQUIREMENT_INFO
);
}
if (!module_exists('og_access')) {
$requirements['og_access'] = array(
'title' => $t('Organic groups access control'),
'value' => $t('Organic groups access control module is disabled. See the modules page.', array('@modules' => url('admin/build/modules'))),
'severity' => REQUIREMENT_INFO
);
}
}
return $requirements;
}
function og_readme() {
global $base_path;
// this link has to work when clean urls are disabled and drupal in subdir.
$href = drupal_get_path('module', 'og'). '/README.txt';
$link = "". t('README file'). '';
return $link;
}
/**
* Get a private token used to protect links from spoofing - CSRF.
*/
function og_get_token($nid) {
return drupal_get_token($nid);
}
/**
* Check to see if a token value matches the specified node.
*/
function og_check_token($token, $seed) {
return drupal_get_token($seed) == $token;
}