You are here

spaces.module in Spaces 5

File

spaces.module
View source
<?php

/**
 * Documentation of occurances of drupal_goto for the purposes of spaces routing.
 * 
 * spaces_router 
 *   - runs on hook_menu
 * 
 * nodeapi 
 *   - direct call to drupal_goto
 *   - spaces_router
 * 
 * form_alter 
 *   - when editing an alien node.
 *
 */
include_once drupal_get_path('module', 'spaces') . '/spaces_block.inc';
include_once drupal_get_path('module', 'spaces') . '/spaces_views.inc';
define('SPACES_ARCHIVE_TIMESTAMP', 60 * 60 * 24 * 14);

// 2 weeks
define('SPACES_PRIVATE', 1);
define('SPACES_PUBLIC', 2);
define('SPACES_PRIVATE_DEFAULT', 3);
define('SPACES_PUBLIC_DEFAULT', 4);

/*
 * Implementation of hook_context_prefix_provider()
 */
function spaces_context_prefix_provider() {
  $items = array();
  $items['spaces'] = array(
    'name' => 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);
        }
        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('<strong>Private</strong> groups are invisible to the public and accessible only by members.<br/><strong>Controlled</strong> groups are visible to the public, but cannot be joined without approval.<br/><strong>Public</strong> 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 <strong>private</strong>. Only members of this group will see it.');
        break;
      case OG_VISIBLE_BOTH:
        $form['spaces']['#description'] = t('A post of this type is always <strong>public</strong>. 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\n    FROM {og}\n      JOIN {node} n ON og.nid = n.nid\n      JOIN {spaces_features} sf ON sf.gid = og.nid\n      {$join}\n    WHERE n.status = 1\n      AND sf.id = '%s'\n      AND sf.value != %d\n      {$where}\n    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);
        }
        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("\n    SELECT sf.*, n.title\n    FROM {spaces_features} sf\n    JOIN {node} n ON sf.gid = n.nid\n    WHERE n.status = 1\n    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'],
        );
      }
    }
    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'],
        );
      }
      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]);
        }
      }
    }
    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;
}

Functions

Namesort descending Description
spaces_admin_debug Provides a debug page to display all group privacy settings
spaces_block Implementation of hook_block()
spaces_content_types Returns a content type => features map
spaces_context_define Implementation of hook_context_define()
spaces_context_prefix_provider
spaces_custom_menu Returns a custom label or weight for a given group & path.
spaces_feature Test if feature exists
spaces_features Retrieve available features
spaces_features_menu returns a keyed array of features split by public/private/group-agnostic TODO: can this be optimized? maybe if we store our data differently : |
spaces_form_alter
spaces_get_users Get drupal users
spaces_gid
spaces_goto_grouphome Given a group id redirect a user to the group home page.
spaces_groupmask Provides a mask to OG settings mapping
spaces_group_after_build Overrides og_selective and og_directory options based on whether a group is public/private
spaces_group_path
spaces_init_context Context prefix provider callback
spaces_is_member
spaces_menu
spaces_nodeapi Implentation of hook_nodeapi
spaces_og_subscription_link Custom subscription link - use "join" instead of "subscribe" - make it shorter.
spaces_og_wrapper Spaces OG wrapper
spaces_perm
spaces_requirements Implementation of hook_requirements
spaces_router Consolidated group context routing logic.
spaces_setting Return the value of an spaces setting.
spaces_settings
spaces_spaces_settings Implementation of hook_spaces_settings()
spaces_user Implementation of hook_user
spaces_views_post_view
theme_spaces_button Generates a themed set of links for node types associated with the current active contexts.
_spaces_enforce_feature API function that enforces OG group and privacy settings on a node.
_spaces_feature_menu_tree Recurses down the menu cache and builds a menu tree for a given feature.
_spaces_form_alter_group
_spaces_form_alter_node
_spaces_group_options Define options for group features.
_spaces_homepage_options Allows a user to choose a default view for the group's homepage
_spaces_make_hidden Set all elements in a given form to 'value'. Using value preserves the tree and prevents The element from being rendered.
_spaces_og_group_options 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

Constants