t('Spaces'), 'description' => t('Sets a spaces context based on prefix.'), 'callback' => 'spaces_init_context', 'example' => 'my-space', ); return $items; } /** * Context prefix provider callback */ function spaces_init_context($gid) { context_set('spaces', 'gid', $gid); } /* * Implementation of hook_menu() */ function spaces_menu($may_cache) { $items = array(); if ($may_cache) { $items[] = array( 'path' => 'admin/settings/spaces', 'title' => t('Spaces Settings'), 'description' => t('Spaces feature defaults.'), 'callback' => 'drupal_get_form', 'callback arguments' => array('spaces_features_form', 0), 'access' => user_access('administer group features'), 'type' => MENU_NORMAL_ITEM, ); $items[] = array( 'path' => 'admin/settings/spaces/default', 'title' => t('Default settings'), 'callback' => 'drupal_get_form', 'callback arguments' => array('spaces_features_form', 0), 'access' => user_access('administer group features'), 'type' => MENU_DEFAULT_LOCAL_TASK, ); $items[] = array( 'path' => 'admin/settings/spaces/debug', 'title' => t('Debug'), 'callback' => 'spaces_admin_debug', 'access' => user_access('administer group features'), 'weight' => 10, 'type' => MENU_LOCAL_TASK, ); } else { if ($_GET['q'] == 'admin/settings/spaces') { include_once(drupal_get_path('module', 'spaces') .'/spaces_admin.inc'); } if (arg(0) == 'node' && is_numeric(arg(1))) { $node = node_load(arg(1)); if (og_is_group_type($node->type)) { include_once(drupal_get_path('module', 'spaces') .'/spaces_admin.inc'); $items[] = array( 'path' => 'node/'. $node->nid .'/features', 'title' => t('Features'), 'callback' => 'drupal_get_form', 'callback arguments' => array('spaces_features_form', $node->nid), 'access' => (user_access('administer group features') || og_is_node_admin($node)), 'type' => MENU_LOCAL_TASK, 'weight' => 2); $items[] = array( 'path' => 'node/'. $node->nid .'/labels', 'title' => t('Labels'), 'callback' => 'drupal_get_form', 'callback arguments' => array('spaces_features_labels', $node->nid), 'access' => (user_access('administer group features') || og_is_node_admin($node)), 'type' => MENU_LOCAL_TASK, 'weight' => 2); $items[] = array( 'path' => 'node/'. $node->nid .'/view-headers', 'title' => t('View Headers'), 'callback' => 'drupal_get_form', 'callback arguments' => array('spaces_view_headers'), 'access' => (user_access('administer group features') || og_is_node_admin($node)), 'type' => MENU_LOCAL_TASK, ); } } // Verify that the user should be able to look into this group. if ($gid = spaces_gid()) { if (spaces_router('menu', $gid)) { $group = node_load($gid); // Member management // TODO: move into spaces_og when ready $items[] = array( 'path' => "member-list", 'callback' => 'spaces_og_wrapper', 'callback arguments' => array('member-list'), 'title' => t('Members'), 'type' => MENU_CALLBACK, 'access' => (user_access('administer group features') || og_is_node_admin($group)), ); $items[] = array( 'path' => "member-add", 'callback' => 'spaces_og_wrapper', 'callback arguments' => array('member-add'), 'title' => t('Add Members'), 'type' => MENU_CALLBACK, 'access' => (user_access('administer group features') || og_is_node_admin($group)), ); } else { drupal_access_denied(); exit; } } } return $items; } /** * Implementation of hook_block() */ function spaces_block($op = 'list', $delta = 0) { if ($op == 'list') { $blocks[1]['info'] = t('Spaces: Contextual Tools'); $blocks[2]['info'] = t('Spaces: Navigation'); return $blocks; } else if ($op == 'view') { switch ($delta) { case 1: return _spaces_block_tools(); case 2: return _spaces_block_nav(); } } } /** * Implementation of hook_context_define() * * hook_context_define provides a central method to define contextual behavior. The spaces * module extends this hook in the "spaces" key namespace. Available attributes are: * 'label', 'description', 'options', 'options_function', '#weight' * * @return * Keyed array which defines features. */ function spaces_context_define() { $items = array(); return $items; } /** * Implementation of hook_spaces_settings() */ function spaces_spaces_settings() { $items = array(); $items['spaces_home'] = array( 'label' => t('Homepage'), 'description' => t("Set the default page seen by members."), 'options' => '_spaces_homepage_options', ); return $items; } /* * Implementation of hook_perm() */ function spaces_perm() { return array('administer group features', 'view users outside groups'); } /* * Implementation of hook_form_alter() */ function spaces_form_alter($form_id, &$form) { switch ($form_id) { default: if ($form['#id'] == 'node-form' && (arg(0) .'/'. arg(1) != 'admin/content')) { // GROUP NODES if (og_is_group_type($form['#node']->type)) { _spaces_form_alter_group($form); } // GROUP ENABLED CONTENT TYPES elseif (!og_is_omitted_type($form['#node']->type)) { _spaces_form_alter_node($form); } } break; } } function _spaces_form_alter_group(&$form) { _spaces_make_hidden($form['og_selective']); _spaces_make_hidden($form['og_register']); _spaces_make_hidden($form['og_private']); _spaces_make_hidden($form['og_directory']); switch (variable_get('context_prefix_method_spaces', CONTEXT_PREFIX_PATH)) { case CONTEXT_PREFIX_PATH: $description = t('Choose a prefix path for this Spaces group. May contain only lowercase letters, numbers, dashes and underscores. e.g. "development-seed"'); break; case CONTEXT_PREFIX_SUBDOMAIN: $description = t('Enter a domain registered for this group, such as "mygroup". Do not include http://'); break; case CONTEXT_PREFIX_DOMAIN: $description = t('Enter a domain registered for this group, such as "www.example.com". Do not include http://'); break; } $form['spaces_path'] = array( '#type' => 'textfield', '#title' => t('Group Path'), '#description' => $description, '#required' => true, '#disabled' => user_access('administer group features') ? false : true, '#default_value' => spaces_group_path($form['#node']->nid) ? spaces_group_path($form['#node']->nid) : '', ); // Add group mask options if ($form['#node']) { $og_settings = array( 'og_selective' => $form['#node']->og_selective, 'og_directory' => $form['#node']->og_directory, 'og_register' => $form['#node']->og_register, 'og_private' => $form['#node']->og_private, ); $default_privacy = spaces_groupmask('check', $og_settings); } $form['spaces_groupmask'] = array( '#required' => true, '#type' => 'select', '#title' => t('Group Privacy'), '#options' => spaces_groupmask('labels'), '#description' => t('Private groups are invisible to the public and accessible only by members.
Controlled groups are visible to the public, but cannot be joined without approval.
Public groups are visible to the public and can be joined by site members.'), '#default_value' => $default_privacy ? $default_privacy : 'private', '#disabled' => user_access('administer group features') ? false : true, ); $form['#after_build'][] = 'spaces_group_after_build'; } function _spaces_form_alter_node(&$form) { global $user; // TODO: We need to present a different UI (actually, probably we don't // need one at all) if spaces_announce is enabled. We need to move this // check into spaces_announce to protect the modularity of Spaces. if (!module_exists('spaces_announce')) { // Give admins access to all group options if (user_access('administer organic groups')) { $options = array( t('My groups') => array(), t('All groups') => array(), ); $options[t('All groups')] = _spaces_og_group_options($form['#node']->type); foreach (og_get_subscriptions($user->uid) as $node) { unset($options[t('All groups')][$node['nid']]); $options[t('My groups')][$node['nid']] = $node['title']; } if (empty($options[t('All groups')])) { unset($options[t('All groups')]); } if ($options) { $default_gid = is_array($form['#node']->og_groups) ? current($form['#node']->og_groups) : spaces_gid(); $form['spaces_og'] = array( '#type' => 'fieldset', '#tree' => true, '#title' => t('Group'), ); $form['spaces_og']['gid'] = array( '#type' => 'select', '#options' => $options, '#default_value' => $default_gid, '#description' => t('Please select a group to move this post to.') ); } } } if (spaces_router('node form', $form['#node'])) { // Recurse into og_options hiding all of them. _spaces_make_hidden($form['og_nodeapi']); $form['spaces'] = array( '#title' => t('Privacy'), '#type' => 'fieldset', '#weight' => 100, ); switch ($form['#node']->og_public) { case OG_VISIBLE_GROUPONLY: $form['spaces']['#description'] = t('A post of this type is always private. Only members of this group will see it.'); break; case OG_VISIBLE_BOTH: $form['spaces']['#description'] = t('A post of this type is always public. All visitors will see it.'); break; } } else { drupal_access_denied(); exit; } } /** * Generates an array of groups that a node could potentially * be a member of based on enabled spaces features and optionally * the specified user's groups */ function _spaces_og_group_options($type, $uid = 0) { $types = spaces_content_types(); $group_options = array(); $args = array($types[$type], 0); if ($uid) { $join = "JOIN {og_uid} ogu ON ogu.nid = og.nid"; $where = "AND ogu.uid = %d AND ogu.is_active >= 1"; $args[] = $uid; } $result = db_query( "SELECT og.nid, n.title FROM {og} JOIN {node} n ON og.nid = n.nid JOIN {spaces_features} sf ON sf.gid = og.nid $join WHERE n.status = 1 AND sf.id = '%s' AND sf.value != %d $where ORDER BY n.title ASC", $args); while ($group = db_fetch_object($result)) { $group_options[$group->nid] = $group->title; } return $group_options; } /** * Set all elements in a given form to 'value'. Using value preserves the tree and prevents * The element from being rendered. */ function _spaces_make_hidden(&$form) { if (isset($form['#type'])) { $form['#type'] = 'value'; $form['#required'] = false; } if (is_array($form)) { foreach ($form as $key => $value) { if (is_array($value) && strpos($key, '#') !== 0) { _spaces_make_hidden($form[$key]); } } } } /** * Overrides og_selective and og_directory options based on whether a group is public/private */ function spaces_group_after_build($form, $form_values) { $mask = spaces_groupmask('mask'); $privacy = $form_values['spaces_groupmask']; if ($privacy && isset($mask[$privacy]['mask'])) { foreach ($mask[$privacy]['mask'] as $key => $value) { // Need to add a parents key, otherwise form_set_value() cries foul. if (!isset($form[$key]['#parents'])) { $form[$key]['#parents'] = array(); } form_set_value($form[$key], $value); } } return $form; } /** * Implentation of hook_nodeapi */ function spaces_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) { switch ($op) { case 'view': if ($a4) { if (spaces_router('node view', $node->nid) === false) { drupal_access_denied(); exit; } } break; case 'validate': if (og_is_group_type($node->type)) { if (!isset($node->spaces_path) || empty($node->spaces_path)) { form_set_error(t('Group path is required.')); } elseif (spaces_group_path($node->nid) != $node->spaces_path) { $group_context = array( 'provider' => 'spaces', 'prefix' => $node->spaces_path, 'id' => $node->nid, ); if (!context_prefix_api('validate', $group_context)) { form_set_error('spaces', t('There was an error registering the path "@path". It is either invalid or is already taken. Please choose another.', array('@path' => $node->spaces_path))); } } } break; case 'prepare': if (og_is_group_type($node->type)) { } else if ($gid = spaces_gid()) { _spaces_enforce_feature($gid, $node); } break; case 'submit': // switch node's group if specified if (!og_is_omitted_type($node->type)) { if (isset($node->spaces_og['gid']) && !in_array($node->spaces_og['gid'], $node->og_groups)) { $new_gid = $node->spaces_og['gid']; _spaces_enforce_feature($new_gid, $node); } } break; case 'insert': case 'update': // only attempt to insert or update the context path if it is different/does not exist if (og_is_group_type($node->type) && spaces_group_path($node->nid) != $node->spaces_path) { $group_context = array( 'provider' => 'spaces', 'id' => $node->nid, ); // clear out old group path if exists context_prefix_api('delete', $group_context); // insert new group path $group_context['prefix'] = $node->spaces_path; context_prefix_api('insert', $group_context); } break; case 'load': if (og_is_group_type($node->type)) { $prefix = spaces_group_path($node->nid); $node->spaces_path = $prefix ? $prefix : ''; } break; case 'delete': // Remove entries from spaces_features table when deleting group nodes. if (og_is_group_type($node->type)) { db_query('DELETE FROM {spaces_features} WHERE gid=%d', $node->nid); // Clear group's context path from context paths variable $group_context = array( 'module' => 'spaces', 'prefix' => $node->spaces_path, 'id' => $node->nid, ); context_prefix_api('delete', $group_context); } break; } } /** * Implementation of hook_user */ function spaces_user($op, &$edit, &$account, $category = NULL) { // user profile silo'ing if ($op == 'view') { global $user; if (spaces_router('user view', $account) === false) { drupal_access_denied(); exit; } if ($user->uid == $account->uid) { $links['user']['title'] = t('Edit my account'); $links['user']['href'] = 'user/'. $account->uid .'/edit'; $links['user']['custom'] = true; context_set('spaces', 'links', $links); } } // user form mods if ($op == 'form' && $category == 'account') { // Add the groups selector to the user form. $form = og_user('register', $edit, $account, $category = NULL); $form['og_register']['#weight'] = 5; $form['og_register']['og_register']['#default_value'] = array_keys($account->og_groups); return $form; } elseif ($op == 'update') { if (is_array($edit['og_register'])) { // Process groups selections. $active_groups = array_keys(array_filter($edit['og_register'])); foreach (array_diff($active_groups, array_keys($account->og_groups)) as $gid) { $return = og_subscribe_user($gid, $account); if (!empty($return['message'])) { drupal_set_message($return['message']); } } foreach (array_diff(array_keys($edit['og_register']), $active_groups) as $gid) { og_delete_subscription($gid, $account->uid); } } } } /* * Implementation of hook_post_view() */ function spaces_views_post_view($view, $items, &$output) { if ($view->build_type == 'page') { if ($label = spaces_custom_menu('label', $view->url)) { drupal_set_title($label); } $nid = variable_get("spaces_header_". $view->name ."_". spaces_gid(), FALSE); if (is_numeric($nid)) { $output = tic_nodecontent($nid). $output; } } } /** * Consolidated group context routing logic. * * @param $stage * String, can be 'menu' or 'node view'. * @param $node * Integer, for the 'menu' stage the group id for the current group. For the * 'node view' stage the node id of the node being viewed. * For the 'node form' stage it must be the node object from the form array. * * @return * boolean true if access is allowed, false otherwise. */ function spaces_router($stage, $node = null) { switch ($stage) { case 'menu': $gid = !$node ? spaces_gid() : $node; $group = node_load($gid); // Theming + routing logic if (($group->og_private != 1) || spaces_is_member($gid)) { og_set_group_context($group); // Handle theme switching since OG is not prefix aware if ($group->og_theme) { global $custom_theme; $custom_theme = $group->og_theme; } if (drupal_is_front_page()) { return spaces_goto_grouphome($gid); } else { return true; } } else { // Give anon users access to login pages. global $user; if (!$user->uid) { if ((arg(0) .'/'. arg(1) == 'user/login') || (arg(0) == 'openid')) { return true; } else { drupal_goto('user/login'); } } } break; case 'node view': $node = node_load($node); if (og_is_omitted_type($node->type)) { return true; } elseif (og_is_group_type($node->type)) { // If it's the actual GROUP node, send to group "homepage". return spaces_goto_grouphome($node->nid); } else { if (is_array($node->og_groups)) { // redirect to prefix/node/xyz if user lands on node/xyz. $node_types = spaces_content_types(); // If node type is in the feature list, check for private/public settings. if (isset($node_types[$node->type])) { if (!spaces_gid()) { // Push user to correct group context. context_prefix_goto('spaces', current($node->og_groups), $_GET['q']); } elseif (spaces_gid() && !in_array(spaces_gid(), $node->og_groups)) { // Push user to correct group context if node is in alien context. context_prefix_goto('spaces', current($node->og_groups), $_GET['q']); } else { // Node_access() has already handled the real permission check for us // here, so let the user view the node. return true; } } } elseif (user_access('administer nodes')) { drupal_set_message(t('This content is not assigned to a group and it not visible to non-administrative users.')); return true; } } break; case 'node form': if ($gid = spaces_gid()) { $features = spaces_features($gid); $content_types = spaces_content_types(); $feature = $content_types[$node->type]; $privacy = $features[$feature]; // Check that user edit post in this location. if (isset($node->nid) && !in_array($gid, $node->og_groups)) { if (!$gid) { $gid = current($node->og_groups); } if (spaces_is_member($gid)) { $path = spaces_group_path($gid); if ($dest = $_REQUEST['destination']) { unset($_REQUEST['destination']); } // Path is actually prefix. See above. context_prefix_goto('spaces', $gid, $_GET['q'], $dest ? "destination=$dest" : NULL); } } elseif (spaces_feature($feature, $gid)) { return true; } } elseif (user_access('administer nodes')) { drupal_set_message(t('This form should only be submitted within a properly configured group. Continue at your own risk.')); return true; } break; case 'user view': global $user; if (user_access('view users outside groups')) { return true; } else { $account = $node; // Push user into a group silo if not already there. if (!spaces_gid() && $account->og_groups && spaces_is_member(key($account->og_groups))) { context_prefix_goto('spaces', key($account->og_groups), 'user/'. $account->uid); } // If either the user &r profile belong to group allow access. else if (spaces_is_member(spaces_gid(), $user->uid) && spaces_is_member(spaces_gid(), $account->uid)) { return true; } } break; } // If we fall though access should be denied. return false; } /** * Given a group id redirect a user to the group home page. * * @param $gid * Integer, the nid of the group node. * * @return * Boolean, false if users should be access denied, true if user should fall * though. Generally this function will not return and the user will be * redirected. */ function spaces_goto_grouphome($gid) { if ($group_home = spaces_setting('spaces_home', $gid)) { if ($group_home == 'pass_thru') { // We may be on either the group node or the group homepage -- // on a group node we want to send the user to the actual homepage // in order to "pass through" if (arg(0) == 'node') { context_prefix_goto('spaces', $gid); } else { return true; } } else { $features = spaces_features(); // use the menu path of selected feature as homepage if (is_array($features[$group_home]->menu)) { $group_home = array_shift($features[$group_home]->menu); } // if group has specified a homepage, send to context/homepage context_prefix_goto('spaces', $gid, $group_home); } } else { if (user_access('administer group features')) { drupal_set_message(t("Please setup your group by enabling at least 1 feature and choosing a homepage setting.")); context_prefix_goto('spaces', $gid, 'node/'. $gid .'/features'); } } return false; } /* * Wrapper around context_get */ function spaces_gid() { return context_get('spaces', 'gid'); } /* * Return the group path by gid */ function spaces_group_path($gid) { static $items; if (!$items) { $items[$gid] = context_prefix_api('load', array('provider' => 'spaces', 'id' => $gid)); } return $items[$gid]['prefix'] ? $items[$gid]['prefix'] : false; } /* * Tests for user membership in group */ function spaces_is_member($gid = null, $uid = null) { global $user; $gid = !$gid ? spaces_gid() : $gid; $account = $uid ? user_load(array('uid' => $uid)) : $user; if (user_access('administer organic groups', $account)) { return true; } else if (is_array($account->og_groups) && $account->og_groups[$gid]) { return true; } return false; } /** * Retrieve available features * * @param $gid * An optional group ID -- if provided, an array of enabled features for that group will be provided. * @param $op * An operation to perform. This is only for caching purposes. Do not use this parameter -- use spaces_settings instead. * * @return * Keyed array of potential group features. */ function spaces_features($gid = -1, $op = 'features') { if ($gid == -1) { static $spaces_features; if (!isset($spaces_features)) { $spaces_features = array(); foreach (context_ui_defaults('spaces') as $feature) { if ($feature->spaces) { $spaces_features[$feature->value] = $feature; } } } return $spaces_features; } else { static $cache = array(); if (!isset($cache[$gid])) { $result = db_query('SELECT type, id, value FROM {spaces_features} WHERE gid = %d', $gid); while ($row = db_fetch_object($result)) { $cache[$gid][($row->type == 0 ? 'features' : 'settings')][$row->id] = $row->value; } } switch ($op) { case 'settings': return is_array($cache[$gid]['settings']) ? $cache[$gid]['settings'] : array(); case 'features': return is_array($cache[$gid]['features']) ? $cache[$gid]['features'] : array(); default: return is_array($cache[$gid]) ? $cache[$gid] : array(); } } } function spaces_settings($gid = -1) { if ($gid == -1) { static $settings; if (!$settings) { $settings = array(); foreach (module_implements('spaces_settings') as $module) { $function = $module .'_spaces_settings'; $settings = array_merge($settings, $function()); } } return $settings; } else { return spaces_features($gid, 'settings'); } } /** * Test if feature exists * * If in a group context we check on a group-by-group basis to see if an feature is enabled, and * that the current user has access. * * @param $feature * String, a feature to check if it is active (in the current group) * * @return * bool, true if feature is enabled. */ function spaces_feature($feature, $gid = null) { $gid = !$gid ? spaces_gid() : $gid; $features = $gid ? spaces_features($gid) : array(); if (array_key_exists($feature, $features)) { // If using OG check if user can access feature. true if feature is public or user is member of the group. global $user; if ($features[$feature] == SPACES_PUBLIC) { return true; } elseif ($features[$feature] == SPACES_PRIVATE && spaces_is_member($gid, $user->uid)) { return true; } } return false; } /** * Return the value of an spaces setting. * * @param $setting * String, a setting id to get * * @param $gid * String, An optional group id -- if unspecified, current context $gid is used. * * @return * Mixed, the value of the setting. */ function spaces_setting($setting, $gid = null) { $gid = !$gid ? spaces_gid() : $gid; $settings = $gid ? spaces_settings($gid) : array(); if (array_key_exists($setting, $settings)) { return $settings[$setting]; } else { return false; } } /** * API function that enforces OG group and privacy settings on a node. */ function _spaces_enforce_feature($gid, &$node) { $map = spaces_content_types(); $features = spaces_features($gid); if ($feature = $map[$node->type]) { if (isset($features[$feature]) && $privacy = $features[$feature]) { switch ($privacy) { case SPACES_PRIVATE: $node->og_public = OG_VISIBLE_GROUPONLY; break; case SPACES_PUBLIC: $node->og_public = OG_VISIBLE_BOTH; break; } $node->og_groups = array($gid => $gid); } } } /** * Provides a debug page to display all group privacy settings */ function spaces_admin_debug() { drupal_add_css(drupal_get_path('module', 'spaces') .'/spaces.css'); drupal_set_title('Debug spaces'); // Query $result = db_query(" SELECT sf.*, n.title FROM {spaces_features} sf JOIN {node} n ON sf.gid = n.nid WHERE n.status = 1 ORDER BY sf.gid ASC"); $spaces = array(); while ($row = db_fetch_object($result)) { $spaces[$row->gid][$row->id] = $row->value; if (!$spaces[$row->gid]['title']) { $spaces[$row->gid]['title'] = $row->title; } } // Generate list of features that have customizable options $features = array(); foreach (spaces_features() as $id => $feature) { if (is_array($feature->spaces['options'])) { $features[] = $id; } } // Display table $rows = array(); $headers = array_merge(array(t('Group ID'), t('Name')), $features); foreach ($spaces as $gid => $group) { $row = array($gid, $group['title']); foreach ($features as $feature) { $row[] = $group[$feature] ? array('data' => $group[$feature], 'class' => 'spaces-value') : array('data' => '', 'class' => 'space-value'); } $rows[] = $row; } return theme('table', $headers, $rows, array('class' => 'spaces-debug')); } /** * Provides a mask to OG settings mapping * * @param $op * 'mask' - Fetch the available masks. * 'check' - Checks the provided definition against available masks. * 'labels' - Fetch an list of avaiable masks suitalble for the forms api. * @param $og_settings * An associative array to compare to the available masks. Used with the 'check' op * * @return * Depends on the $op. For 'mask' returns the available mask definitions. For 'check' * returns the mask key is a match is found, otherwise FALSE. For 'labels' and options * array that can be used in a form. */ function spaces_groupmask($op = 'mask', $og_settings = array()) { static $spacetype_mask; if (!$spacetype_mask) { $spacetype_mask = array( 'private' => array( 'label' => t('Private'), 'limit options' => array(SPACES_PRIVATE), 'mask' => array( 'og_selective' => OG_CLOSED, 'og_directory' => OG_DIRECTORY_NEVER, 'og_register' => OG_REGISTRATION_ALWAYS, 'og_private' => defined(OG_PRIVATE_GROUPS_ALWAYS) ? OG_PRIVATE_GROUPS_ALWAYS : 1, ), ), 'controlled' => array( 'label' => t('Controlled'), 'mask' => array( 'og_selective' => variable_get('spaces_controlled_selective', OG_CLOSED), 'og_directory' => OG_DIRECTORY_ALWAYS, 'og_register' => OG_REGISTRATION_ALWAYS, 'og_private' => defined(OG_PRIVATE_GROUPS_NEVER) ? OG_PRIVATE_GROUPS_NEVER : 0, ), ), 'public' => array( 'label' => t('Public'), 'limit options' => array(SPACES_PUBLIC), 'mask' => array( 'og_selective' => OG_OPEN, 'og_directory' => OG_DIRECTORY_ALWAYS, 'og_register' => OG_REGISTRATION_ALWAYS, 'og_private' => defined(OG_PRIVATE_GROUPS_NEVER) ? OG_PRIVATE_GROUPS_NEVER : 0, ), ), ); } switch ($op) { case 'mask': return $spacetype_mask; case 'check': foreach ($spacetype_mask as $key => $type) { if ($og_settings == $type['mask']) { return $key; } } return false; case 'labels': $labels = array(); foreach ($spacetype_mask as $key => $type) { $labels[$key] = $type['label']; } return $labels; } } /** * Returns a content type => features map */ function spaces_content_types() { static $map; if (!$map) { $map = array(); $features = spaces_features(); foreach ($features as $id => $feature) { if (is_array($feature->node)) { foreach ($feature->node as $type) { $map[$type] = $id; } } } } return $map; } /** * Returns a custom label or weight for a given group & path. * * @param $path * A valid drupal path. * @param $gid * Optional group id. Will be assumed from spaces_gid() if omitted. */ function spaces_custom_menu($op = 'label', $path, $gid = null) { static $labels = array(); static $weights = array(); $gid = ($gid ? $gid : spaces_gid()); if (!isset($labels[$gid])) { $custom = db_fetch_object(db_query("SELECT labels, weights FROM {spaces_features_custom} WHERE gid = %d", $gid)); $labels[$gid] = unserialize($custom->labels); $weights[$gid] = unserialize($custom->weights); } switch ($op) { case 'label': return $labels[$gid][$path] ? $labels[$gid][$path] : false; break; case 'weight': return $weights[$gid][$path] ? $weights[$gid][$path] : false; break; } } /** * returns a keyed array of features split by public/private/group-agnostic * TODO: can this be optimized? maybe if we store our data differently : | */ function spaces_features_menu($op, $subnav = false) { static $menu; // use static cached menu if possible if (!$menu) { $menu = array( 'main' => array(), 'subnav' => array(), ); $all_features = spaces_features(); $og_features = spaces_features(spaces_gid()); // iterate through public/private features and categorize accordingly foreach (($all_features) as $feature => $f) { if ($f->menu) { $addto = array(); $item = _spaces_feature_menu_tree($f); if ($f->spaces['options']) { switch ($og_features[$feature]) { case SPACES_PRIVATE: if (spaces_is_member()) { $addto[] = 'private'; } break; case SPACES_PUBLIC: $addto[] = 'public'; break; } } else { $addto[] = 'agnostic'; } foreach ($addto as $key) { if ($item) { $menu['main'][$key][$feature] = $item; if ($item['children']) { $menu['subnav'][$key][$feature] = $item['children']; } } } } } // Sort items in each of the menu types by weight foreach ($menu['main'] as $key => $dummy) { uasort($menu['main'][$key], '_element_sort'); } } switch ($subnav) { case false: return isset($menu['main'][$op]) ? $menu['main'][$op] : array(); case true: return isset($menu['subnav'][$op]) ? $menu['subnav'][$op] : array(); } } /** * Get drupal users * * @param $exclude_system * Bool, whether to exclude system user - ie user 1 * * @return * Array of user objects, where key is uid. */ function spaces_get_users($exclude_system = true, $active_only = true, $group_only = true, $pager = 0) { $args[] = $exclude_system ? 1 : 0; $args[] = $active_only ? 1 : 0; if (($gid = spaces_gid()) && $group_only) { $join = 'JOIN {og_uid} ogu ON u.uid = ogu.uid'; $where = 'AND ogu.nid = %d'; $args[] = $gid; } if ($pager == 0) { $result = db_query("SELECT u.uid, u.name, u.mail, u.picture, u.status FROM {users} u $join WHERE u.uid > %d AND u.status >= %d $where ORDER BY name", $args); } else { $result = pager_query("SELECT u.uid, u.name, u.mail, u.picture, u.status FROM {users} u $join WHERE u.uid > %d AND u.status >= %d $where ORDER BY name", $pager, 0, null, $args); } $users = array(); while ($u = db_fetch_object($result)) { $users[$u->uid] = $u; } return $users; } /** * Recurses down the menu cache and builds a menu tree for a given feature. */ function _spaces_feature_menu_tree($feature, $item = null, $gid = null) { global $_menu; $inet_menu = array(); if (!$item) { // retrieve the menu item from cache $path = current($feature->menu); // Currently only the first menu item is supported (and its children) $mid = $_menu['path index'][$path]; $item = $_menu['items'][$mid]; if (context_get('spaces', 'feature') == $feature->value) { $inet_menu['attributes'] = array('class' => 'active'); } } // Check the item['type'] bitmask if ($item['access'] && ($item['type'] & MENU_NORMAL_ITEM)) { $inet_menu['title'] = spaces_custom_menu('label', $item['path'], $gid) ? spaces_custom_menu('label', $item['path'], $gid) : $item['title']; $inet_menu['#weight'] = spaces_custom_menu('weight', $item['path'], $gid) ? spaces_custom_menu('weight', $item['path'], $gid) : 0; $inet_menu['href'] = $item['path']; if ($item['children']) { foreach ($item['children'] as $mid) { $child = _spaces_feature_menu_tree($feature, $_menu['items'][$mid], $gid); if ($child) { $inet_menu['children'][$child['href']] = $child; } } // Children if they exist if (is_array($inet_menu['children'])) { uasort($inet_menu['children'], "_element_sort"); } } } return $inet_menu; } /** * Define options for group features. */ function _spaces_group_options() { $options = array( 0 => t('Disabled'), SPACES_PRIVATE => t('Private'), SPACES_PUBLIC => t('Public'), ); return $options; } /** * Allows a user to choose a default view for the group's homepage */ function _spaces_homepage_options() { $spaces_features = spaces_features(); foreach ($spaces_features as $feature) { if ($feature->menu && $feature->spaces['options']) { $homepage_options[$feature->value] = $feature->spaces['label']; } } $homepage_options[] = '---'; $homepage_options['pass_thru'] = t('Pass through to site homepage'); return $homepage_options; } /** * Implementation of hook_requirements */ function spaces_requirements($phase) { $requirements = array(); $t = get_t(); switch ($phase) { case 'runtime' : // Check OG settings. include(dirname(__FILE__) .'/spaces_conf.inc'); foreach ($conf as $key => $value) { if (variable_get($key, NULL) !== $value) { $requirements['spaces'] = array( 'title' => $t('Spaces Configuration'), 'description' => $t('The Spaces module requires a specific OG configureation describe in spaces_conf.inc. Please check that settings.php to verify that a line line %include exists', array('%include' => "require(dirname(__FILE__) .'/../all/modules/spaces/spaces_conf.inc');")), 'severity' => REQUIREMENT_ERROR, 'value' => $t('OG Misconfiguration'), ); } } // Check that existing node types are og enabled $types = spaces_content_types(); $existing_types = node_get_types(); foreach ($types as $type => $feature) { if (og_is_omitted_type($type) && array_key_exists($type, $existing_types)) { $requirements['spaces'] = array( 'title' => $t('Spaces Configuration'), 'description' => $t('The !type content type appear to be misconfigured.', array('!type' => l($type, 'admin/content/types/'. $type))), 'severity' => REQUIREMENT_ERROR, 'value' => $t('OG Misconfiguration'), ); return $requirements; } } $requirements['spaces'] = array( 'title' => $t('Spaces Configuration'), 'description' => t('The spaces module is installed and configured properly'), 'severity' => REQUIREMENT_OK, 'value' => $t('Installed correctly'), ); } return $requirements; } /** * Spaces OG wrapper */ function spaces_og_wrapper($op) { switch ($op) { case 'member-list': if (og_is_node_admin(node_load(spaces_gid())) && user_access('ucreate users')) { $links = context_get('spaces', 'links'); $links = $links ? $links : array(); $links['ucreate'] = array( 'title' => t('Member'), 'href' => 'member-add', ); context_set('spaces', 'links', $links); } return og_menu_check('og_list_users_page', spaces_gid()); case 'member-add': if (module_exists('ucreate')) { $form = drupal_get_form('ucreate_user_form'); } return $form; } } /** * Custom subscription link - use "join" instead of "subscribe" - make it shorter. */ function spaces_og_subscription_link() { global $user; if ($user->uid && is_array($user->og_groups)) { $gid = spaces_gid(); $node = node_load($gid); // User is a member if ($user->og_groups[$gid]) { // Do not let managers leave the group -- TODO: figure out a // better workflow for these situations. if (!og_is_node_admin($node)) { return array( 'title' => t('Leave this group'), 'href' => "og/unsubscribe/". $node->nid, 'query' => 'destination='. $_GET['q'], ); } } // User has requested membership else if (db_result(db_query("SELECT count(nid) FROM {og_uid} WHERE nid = %d AND uid = %d AND is_active = 0", $gid, $user->uid))) { return array( 'title' => t('Cancel request to join'), 'href' => "og/unsubscribe/". $node->nid, 'query' => 'destination='. $_GET['q'], ); } // User is not a member else { if ($node->og_selective == OG_MODERATED) { return array( 'title' => t('Request to join'), 'href' => "og/subscribe/". $node->nid, 'query' => 'destination='. $_GET['q'], ); } elseif ($node->og_selective == OG_OPEN) { return array( 'title' => t('Join this group'), 'href' => "og/subscribe/". $node->nid, 'query' => 'destination='. $_GET['q'], ); } } } return; } /** * THEME FUNCTIONS ==================================================== */ /** * Generates a themed set of links for node types associated with * the current active contexts. */ function theme_spaces_button() { $output = ''; $links = _context_ui_node_links(); // Perform additional logic if a spaces feature is active. if ($feature = context_get('spaces', 'feature')) { // Are we in an enabled, accessible feature? if (spaces_gid() && (!spaces_is_member() || !spaces_feature($feature))) { $features = spaces_features(); if (isset($features[$feature]->node)) { foreach ($features[$feature]->node as $type) { unset($links[$type]); } } } // We are not in a space else if (!spaces_gid()) { // strip out all OG-enabled types from $links array foreach ($links as $type => $link) { if (!og_is_omitted_type($type) && !og_is_group_type($type)) { unset($links[$type]); } } } } if (context_isset('spaces', 'links')) { $links = array_merge($links, context_get('spaces', 'links')); } foreach ($links as $link) { if ($link['custom']) { $output .= l($link['title'], $link['href'], array('class' => 'button')); } else if (!empty($link)) { $output .= l('+ '. t('Add !type', array('!type' => $link['title'])), $link['href'], array('class' => 'button')); } } return $output; }