l(t('theme configuration page'), 'admin/build/themes'))); break; } } } function og_menu() { // anon users should be able to get to the join page $items['og/subscribe'] = array( 'type' => MENU_CALLBACK, 'page callback' => 'og_subscribe', 'access callback' => TRUE, 'title' => 'Join group' ); $items['og/opml'] = array( 'type' => MENU_CALLBACK, 'page callback' => 'og_opml', 'access callback' => 'user_is_logged_in', 'title' => t('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' => t('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), 'title' => t('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, ); // This exists because I can't easily restrict access based on pictures_enabled using Views. $items['og/users/%/faces'] = array( 'title' => t('Faces'), 'page callback' => 'views_page', 'page arguments' => array('og_members_faces', 'default', 2), 'type' => MENU_LOCAL_TASK, 'access callback' => 'og_menu_access_picture', 'access arguments' => array(2), ); // 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' => t('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 ); // Group feed $items['node/%node/feed'] = array( 'page callback' => 'og_feed', 'page arguments' => array(1), 'title callback' => 'node_page_title', 'title arguments' => array(1), 'type' => MENU_CALLBACK, 'access callback' => 'node_access', 'access arguments' => array('view', 1), ); return $items; } function og_menu_access_unsubscribe($group_node, $account) { global $user; if ($account->uid != $user->uid && !og_is_group_admin($group_node)) { // only admins can remove another member return FALSE; } if ($group_node->og_selective == OG_CLOSED && !og_is_group_admin($group_node)) { return FALSE; } if ($group_node->uid == $account->uid) { 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 current user's membership in a group. Site admins always return TRUE. **/ function og_is_group_member($gid) { 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); return user_access('administer nodes') || in_array($gid, $groups) ? TRUE : FALSE; } /* * Determine whether user can act as a group administrator for a given group. */ function og_is_group_admin($node, $account = NULL) { return og_is_group_type($node->type) && node_access('update', $node, $account); } // 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']; } /** * 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 group links don't curently show there. if (og_is_group_post_type($node->type) && $node->build_mode != NODE_BUILD_PREVIEW) { $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) { $variables['node']['theme paths'][] = 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'); if (module_exists('workflow_ng')) { module_load_include('inc', 'og', 'og_workflow_ng'); } // Set group context and language if needed. if ($node = og_determine_context()) { og_set_language($node); } $_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'); } /** * 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; } else { switch (!empty($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 (!$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) { $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", array('absolute' => TRUE)) ); global $user; $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.', array('%closed' => t('closed')))); } 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; switch ($user->og_email) { // og_email can be NULL when you enable og on an existing site. case NULL: 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 personal 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 personal 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 (in_array($node->nid, array_keys($account->og_groups))) { 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 %ga.', array('%name' => $account->name, '%ga' => t('group administrator')))); $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 %ga.', array('%name' => $account->name, '%ga' => t('group administrator')))); $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:') .' '. 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.')); } // TODOL move access control to menu function og_subscribe($gid, $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)); } $node = node_load($gid); if ($node->og_selective >= OG_INVITE_ONLY || $node->status == 0) { 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', $gid, $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 occassionally * 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 ($account->uid) { $accounts[] = $account; $uids[] = $account->uid; } else { $bad[] = check_plain($name); $err = TRUE; } } if ($err) { form_set_error('og_names', t('Unrecognized %names:', array('%names' => format_plural(count($bad), 'name', 'names'))) .' '. 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), ); } 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 admin/og/og). 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' => check_markup($node->body, $node->format, FALSE), '#node' => $node, '#weight' => -3, '#theme' => 'og_mission', ); $view = views_get_view(variable_get('og_home_page_view', 'og_ghp_ron')); if ($view->disabled) { // Do nothing. Assume the group node type handles homepage, or theme layer - i.e. node-.tpl.php } else { $view->is_cacheable = FALSE; // $view->url = 'node'; // for pager $view->set_arguments(array($node->nid)); $view->set_display('default'); $built = $view->render(); // Views will set either total_rows or num_rows depending on whether View has a pager. if (@$view->total_rows || @$view->num_rows || @$view->page_empty) { $node->content['view'] = array('#value' => $built); } elseif (empty($_POST)) { // Use this default empty text unless overridden by View (which is a usually not a good idea. This text is smart) og_home_empty($node); } drupal_set_title(filter_xss_admin($view->build_info['title'])); } } } function og_home_empty($node) { global $user; $dest = drupal_get_destination(); if (in_array($node->nid, array_keys($user->og_groups))) { $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; $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 !page? Disabled if the group is set to private group.', array('!page' => l(t('list of groups page'), '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' => $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 how we node_load(). the node form uses $theme and not og_theme // If author chose the default theme, then '' is written to the DB and group will follow any change 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(), this is not needed. if (!isset($node->og_public)) { $node->og_public = TRUE; } // 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 profile.', array('%name' => $account->name, '!profile' => url("user/$node->uid/edit")))); } // Must be authored in the default input format or else admins might not get their admin perms // If Body field is omitted, there may not yet be a $node->format if (isset($node->format) && $node->format != filter_resolve_format(FILTER_FORMAT_DEFAULT)) { form_set_error('body', t('You must use the default input format when authoring a group node.')); } } 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 before posting on this web site.', array('!join' => l(t('join a group'), '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); } $skip_notification = FALSE; // 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 .= '
'. theme('links', $append). '
'; $node->teaser .= '
'. theme('links', $append). '
'; 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, ); $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. // TODOL: 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(); } } // 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: // $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 $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'); // 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. $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; } else { $is_optgroup = FALSE; } } // 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 ($node->nid || $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 before posting a %type.', array('!join' => l(t('join a group'), 'og'), '%type' => node_get_types('name', $node->type)))); } } function og_comment($comment, $op) { switch ($op) { case 'publish': $comment = (array) $comment; // fall through case 'insert': $skip_notification = FALSE; // 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('!!i', '"', $body); $body = preg_replace('!!i', '/', $body); $body = preg_replace('!!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("@\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 (is_array($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); 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; } /** * Menu callback. Render group feed. */ function og_feed($node) { $view = views_get_view(variable_get('og_home_page_view', 'og_ghp_ron')); $view->is_cacheable = FALSE; $view->set_display('feed'); // TODOL: not working $view->override_path = "node/$node->nid"; $view->set_arguments(array($node->nid)); $view->display['feed']->display_options['style_options']['description'] = $node->og_description; print $view->render(); } /** * 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. $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 a !groupfeed and an !email.', array('!groupfeed' => l(t('RSS feed'), "node/$groupnode->nid/feed"), '!email' => l(t('email subscription'), 'og/manage/'. $groupnode->nid))); // NOTE: See og.css for styling specific to these lists $content .= ' '. t('Or subscribe to these personalized, sitewide feeds:'); $inline = array('class' => 'links inline'); if ($user->uid) { $l1[] = array('title' => t('Feed'), 'href' => 'group/myunread/feed'); $l1[] = array('title' => t('Page'), 'href' => 'group/myunread'); $links['my_unread'] = t('My unread:') .' '. theme('links', $l1, $inline); $l2[] = array('title' => t('Feed'), 'href' => 'group/mytracker/feed'); $l2[] = array('title' => t('Page'), 'href' => 'group/mytracker'); $links['my_group'] = t('My group:') .' '. theme('links', $l2, $inline); } $l3[] = array('title' => t('Feed'), 'href' => 'group/tracker/feed'); $l3[] = array('title' => t('Page'), 'href' => 'group/tracker'); $links['all_posts'] = array('data' => t('All posts:') .' '. theme('links', $l3, $inline)); $content .= theme('item_list', $links); $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'). '/opml.gif', t('OPML file'), t('OPML file'))) { 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 .= ''; } $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)) { $suffix = ''; 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:') .' '. theme('username', $node); $subscribe = isset($subscription) ? l(t('My membership'), "og/manage/$node->nid") : og_subscribe_link($node); if(isset($subscribe)) { $links['my_membership'] = $subscribe; } if (module_exists('search') && user_access('search content')) { $suffix = drupal_get_form('og_search_form', $node); } } elseif ($subscription == 'requested') { $links['approval'] = t('Your membership request awaits approval.'); $links['delete'] = l(t('Delete request'), "og/unsubscribe/$node->nid", 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 group. The group administrators add/remove members as needed.', array('@invite' => t('invite only'))); } elseif ($node->og_selective == OG_CLOSED) { $links['closed'] = t('This is a @closed group. The group administrators add/remove members as needed.', array('@closed' => t('closed'))); } // Modify these links by reference. If you want control of the whole block, see og_block_details(). drupal_alter('og_links', $links); $block['content'] = theme('item_list', $links). $suffix; $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'); $txt = og_is_picture() ? l($txt, "og/users/$node->nid/faces") : l($txt, "og/users/$node->nid"); $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; 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/$post_type", array( 'title' => t('Add a new !s in this group.', array('!s' => $type_name)), 'query' => "gids[]=$group->nid", )); } } return $links ? $links : array(); } function og_search_form($form_state, $group) { $form_state['keys'] = array( '#type' => 'textfield', '#title' => '', '#description' => '', '#size' => 19, '#maxlength' => 255, ); $form_state['submit'] = array( '#type' => 'submit', '#value' => t('Search'), ); $form_state['#pre_render'][] = 'og_search_form_pre_render'; $form_state['#method'] = 'get'; $form_state['#action'] = url("og/search/$group->nid"); return $form_state; } // Clean up query string like Views does. function og_search_form_pre_render($form_state) { // unset($form_state['form_build_id'], $form_state['form_token'], $form_state['form_id']); return $form_state; } 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 and the Content types fieldset at top of OG settings.', array('!readme' => 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.', array('!job_queue' => l('job_queue.module', 'http://drupal.org/project/job_queue'), '!readme' => 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.', array('!settings' => l($t('modules page'), '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; }