You are here

spaces_og.module in Spaces 7.3

File

spaces_og/spaces_og.module
View source
<?php

/**
 * Implements hook_ctools_plugin_api().
 */
function spaces_og_ctools_plugin_api($module, $api) {
  if ($module == "spaces" && $api == "spaces") {
    return array(
      "version" => 3,
    );
  }
  else {
    if ($module == 'spaces' && $api == 'plugins') {
      return array(
        'version' => 3,
      );
    }
  }
}

/**
 * Implements hook_enable().
 */
function spaces_og_enable() {

  // Configure purl, grab OG method, set to path if not configured.
  $purl_types = variable_get('purl_types', array());
  if (!variable_get('purl_method_spaces_og', FALSE)) {
    $purl_types['path'] = 'path';
    variable_set('purl_types', $purl_types);
    variable_set('purl_method_spaces_og', 'path');
  }

  // The next 2 lines are for Simpletest, it won't have it otherwise.
  // variable_get up top retrieves the settings from the host, while
  // variable_set here writes into the test site. As a result,
  // variable_get('purl_method_spaces_og') will retrieve the value of the host
  // site. If it is present in the host site, the initial configuration is not
  // being executed and the tests fail.
  variable_set('purl_types', $purl_types);
  variable_set('purl_method_spaces_og', variable_get('purl_method_spaces_og', FALSE));

  // Weight spaces_og to be after OG
  $weight = db_query("SELECT weight FROM {system} WHERE type = :type AND name = :name", array(
    ':type' => 'module',
    ':name' => 'og',
  ))
    ->fetchField();

  // TODO Please review the conversion of this statement to the D7 database API syntax.

  /* db_query("UPDATE {system} SET weight = %d WHERE name = 'spaces_og'", ($weight + 1)) */
  db_update('system')
    ->fields(array(
    'weight' => $weight + 1,
  ))
    ->condition('name', 'spaces_og')
    ->execute();
}

/**
 * Implements hook_purl_provider().
 */
function spaces_og_purl_provider() {
  $items = array();
  $items["spaces_og"] = array(
    'name' => t('Group space'),
    'description' => t('Sets a space as the active space.'),
    'callback' => 'spaces_init_space',
    'callback arguments' => array(
      'og',
    ),
    'example' => 'my-space',
  );
  return $items;
}

/**
 * Implements hook_spaces_plugins().
 */
function spaces_og_spaces_plugins() {
  $plugins = array();
  $plugins['space_og'] = array(
    'handler' => array(
      'path' => drupal_get_path('module', 'spaces_og') . '/plugins',
      'file' => 'space_og.inc',
      'class' => 'space_og',
      'parent' => 'space_type_purl',
    ),
  );
  return $plugins;
}

/**
 * Implements hook_spaces_registry().
 */
function spaces_og_spaces_registry() {
  return array(
    'types' => array(
      'og' => array(
        'title' => t('Group space'),
        'plugin' => 'space_og',
        'path' => 'node/%node',
      ),
    ),
  );
}

/**
 * Implements hook_spaces_presets().
 */
function spaces_og_spaces_presets() {
  $export = array();
  $spaces_presets = new stdClass();
  $spaces_presets->disabled = FALSE;

  /* Edit this to true to make a default spaces_presets disabled initially */
  $spaces_presets->api_version = 3;
  $spaces_presets->name = 'og_controlled';
  $spaces_presets->title = 'Controlled group';
  $spaces_presets->description = 'All users may view public content from this group. Users must request to join this group.';
  $spaces_presets->space_type = 'og';
  $spaces_presets->value = array(
    'variable' => array(
      'spaces_og_selective' => '1',
      'spaces_og_register' => 1,
      'spaces_og_directory' => 1,
      'spaces_og_private' => 0,
    ),
  );
  $export['og_controlled'] = $spaces_presets;
  $spaces_presets = new stdClass();
  $spaces_presets->disabled = FALSE;

  /* Edit this to true to make a default spaces_presets disabled initially */
  $spaces_presets->api_version = 3;
  $spaces_presets->name = 'og_private';
  $spaces_presets->title = 'Private group';
  $spaces_presets->description = 'Only members will be able to access this group. Membership is strictly managed by admins.';
  $spaces_presets->space_type = 'og';
  $spaces_presets->value = array(
    'variable' => array(
      'spaces_features' => array(),
      'spaces_preset_og' => 'og_private',
      'spaces_og_selective' => '3',
      'spaces_og_register' => 0,
      'spaces_og_directory' => 0,
      'spaces_og_private' => 1,
    ),
    'context' => array(),
  );
  $export['og_private'] = $spaces_presets;
  $spaces_presets = new stdClass();
  $spaces_presets->disabled = FALSE;

  /* Edit this to true to make a default spaces_presets disabled initially */
  $spaces_presets->api_version = 3;
  $spaces_presets->name = 'og_public';
  $spaces_presets->title = 'Public group';
  $spaces_presets->description = 'All users may view public content from this group. User may join this group at will.';
  $spaces_presets->space_type = 'og';
  $spaces_presets->value = array(
    'variable' => array(
      'spaces_features' => array(),
      'spaces_og_selective' => '0',
      'spaces_og_register' => 1,
      'spaces_og_directory' => 1,
      'spaces_og_private' => 0,
    ),
  );
  $export['og_public'] = $spaces_presets;
  return $export;
}

/**
 * Implements hook_menu().
 */
function spaces_og_menu() {
  $items = array();
  $items['user/%user/edit/groups'] = array(
    'file' => 'spaces_og.pages.inc',
    'title' => 'Groups',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'spaces_og_user_groups_form',
      1,
    ),
    'access callback' => 'user_edit_access',
    'access arguments' => array(
      1,
    ),
    'type' => MENU_LOCAL_TASK,
  );
  if (module_exists('ucreate')) {
    $items["og/users/%node/ucreate"] = array(
      'file' => 'spaces_og.pages.inc',
      'type' => MENU_LOCAL_TASK,
      'title' => 'Add new account',
      'page callback' => 'spaces_og_ucreate',
      'page arguments' => array(
        2,
      ),
      'access callback' => 'spaces_access_admin',
      'access arguments' => array(),
      'weight' => 1,
    );
  }
  return $items;
}

/**
 * Implements hook_menu_alter().
 */
function spaces_og_menu_alter(&$items) {
  if (isset($items['node/%node/features'])) {
    $items['node/%node/features']['access callback'] = 'spaces_og_access_admin';
    $items['node/%node/features']['access arguments'] = array(
      1,
    );
  }
  if (isset($items['node/%node/overrides'])) {
    $items['node/%node/overrides']['access callback'] = 'spaces_og_access_admin';
    $items['node/%node/overrides']['access arguments'] = array(
      1,
    );
  }
}

/**
 * Access callback for OG administrative items under node/%.
 * Prevents items from showing up under node types that are not groups.
 */
function spaces_og_access_admin($node) {
  return og_is_group_type('node', $node->type) ? spaces_access_admin() : FALSE;
}

/**
 * Implements hook_user_categories().
 */
function spaces_og_user_categories() {
  $data = array();
  $data[] = array(
    'name' => 'groups',
    'title' => t('Groups'),
    'weight' => 10,
  );
  return $data;
}

/**
 * Implements hook_permission().
 */
function spaces_og_permission() {
  return array(
    'view users outside groups' => array(
      'title' => t('view users outside groups'),
      'description' => t('Allows group members to see other users outside of their own groups.'),
    ),
    'manage group space' => array(
      'title' => t('manage group space'),
      'description' => 'Manage group space. e.g. features for this group.',
    ),
  );
}

/**
 * Implements hook_node_load().
 */
function spaces_og_node_load($nodes, $types) {
  foreach ($types as $type) {
    if (og_is_group_type('node', $type)) {
      foreach ($nodes as $node) {
        if (og_is_group_type('node', $node->type)) {
          $modifier = purl_load(array(
            'id' => isset($node->nid) ? $node->nid : NULL,
            'provider' => 'spaces_og',
          ));
          $node->purl = $modifier['value'];
        }
      }
    }
  }
}

/**
 * Implements hook_node_insert().
 */
function spaces_og_node_insert($node) {
  spaces_og_node_update($node, 'insert');
}

/**
 * Implements hook_node_update().
 */
function spaces_og_node_update($node, $op = 'update') {
  if (og_is_group_type('node', $node->type)) {
    $space = spaces_load('og', $node->nid, TRUE);
    if ($space) {

      // Save preset values.
      if (!empty($node->spaces_preset_og)) {
        $space->controllers->variable
          ->set('spaces_preset_og', $node->spaces_preset_og);
      }

      // Configure OG directly from space object as node_save() may be
      // called from outside of actual group space.
      $selective = $space->controllers->variable
        ->get('spaces_og_selective');
      $node->og_selective = $selective === NULL ? OG_OPEN : $selective;
      $register = $space->controllers->variable
        ->get('spaces_og_register');
      $node->og_register = $register === NULL ? TRUE : $register;
      $directory = $space->controllers->variable
        ->get('spaces_og_directory');
      $node->og_directory = $directory === NULL ? TRUE : $directory;
      $private = $space->controllers->variable
        ->get('spaces_og_private');
      $field_langauge = field_language('node', $node, 'group_access');
      if ($op == 'update' && $private != $node->group_access[$field_langauge][0]['value']) {

        // Privacy has changed, update og_public flags, and queue a rebuid.
        db_query("UPDATE {og_access_post} og_p\n          INNER JOIN {og_ancestry} og_a ON og_p.nid = og_a.nid\n          INNER JOIN {node} n ON og_p.nid = n.nid\n          SET og_public = %d WHERE og_a.group_nid = %d", !$private, $node->nid);
        node_access_needs_rebuild(TRUE);
      }
      $node->og_private = $private === NULL ? FALSE : $private;

      // TODO determine the D7 equivalent of og_update_group().
      // og_update_group($node);
    }

    // Save PURL modifier.
    $modifier = array(
      'provider' => 'spaces_og',
      'id' => $node->nid,
    );
    if (is_array($node->purl) && !empty($node->purl['value'])) {
      $modifier['value'] = $node->purl['value'];
      purl_save($modifier);
    }
    else {
      if (empty($node->purl)) {
        $modifier['value'] = "{$node->type}-{$node->nid}";
        purl_save($modifier);
      }
    }
  }
}

/**
 * Implements hook_node_delete().
 */
function spaces_og_node_delete($node) {
  if (og_is_group_type('node', $node->type)) {
    spaces_delete('og', $node->nid);
    purl_delete(array(
      'provider' => 'spaces_og',
      'id' => $node->nid,
    ));
  }
}

/**
 * Implements hook_node_validate().
 *
 * Enforce groups for OG. OG enforces audiences via required form
 * elements that Spaces removes.
 * @see og_form_add_og_audience()
 * @see _spaces_og_form_alter_node()
 */
function spaces_og_node_validate($node, $form, &$form_state) {
  if (variable_get('og_group_type_' . $node->type, 'omitted') != 'omitted') {

    // TODO verify that this sufficiently replaces the og_is_omitted_type() function.
    if (variable_get('og_audience_required', FALSE) && empty($node->og_groups)) {
      if (isset($node->spaces_og_audience) && (int) $node->spaces_og_audience !== 0) {
        $node->og_groups[$node->spaces_og_audience] = $node->spaces_og_audience;
      }
      else {

        // @todo This error will leave none 'administer nodes' users in a
        // dead end. Ideally, access should be restricted on the menu
        // level.
        form_set_error('spaces_og_audience', t('This content type must be posted in a group.'));
      }
    }
  }
}

/**
 * Implements hook_node_prepare().
 */
function spaces_og_node_prepare($node) {
  spaces_og_node_presave($node);
}

/**
 * Implements hook_node_presave().
 */
function spaces_og_node_presave($node) {
  if (variable_get('og_group_type_' . $node->type, 'omitted') != 'omitted') {

    // TODO verify that this sufficiently replaces the og_is_omitted_type() function.
    $space = spaces_get_space();
    if ($space && $space->type == 'og') {
      _spaces_og_enforce_privacy($space->id, $node);
    }
    else {
      if (!empty($node->og_groups)) {
        _spaces_og_enforce_privacy(current($node->og_groups), $node);
      }
    }
    if (isset($node->spaces_og_audience) && (int) $node->spaces_og_audience !== 0) {
      $node->og_groups[$node->spaces_og_audience] = $node->spaces_og_audience;
    }
  }
}

/**
 * Implements hook_form_alter() for ctools_export_ui_edit_item().
 */
function spaces_og_form_ctools_export_ui_edit_item_form_alter(&$form, $form_state) {
  if (isset($form_state['item'], $form_state['item']->table, $form_state['item']->space_type) && $form_state['item']->space_type === 'og') {
    module_load_include('inc', 'spaces_og', 'spaces_og.pages');
    _spaces_og_form_spaces_preset_editor_alter($form, $form_state);
  }
}

/**
 * Implements hook_form_alter() for node_delete_confirm().
 */
function spaces_og_form_node_delete_confirm_alter(&$form, $form_state) {
  module_load_include('inc', 'spaces_og', 'spaces_og.pages');
  $node = node_load($form['nid']['#value']);
  if (og_is_group_type('node', $node->type)) {
    $form['#submit'][] = '_spaces_og_node_delete_confirm_submit';
  }
  elseif (og_is_group_content_type('node', $node->type)) {
    $space = spaces_get_space();
    if ($space && $space->type == 'og') {
      $preset_val = $space->controllers->variable
        ->get('site_frontpage', 'preset');
      $space_val = $space->controllers->variable
        ->get('site_frontpage', 'space');
      if (!empty($preset_val) || !empty($space_val)) {
        $form_state['#redirect'] = url('<front>', array(
          'absolute' => TRUE,
          'purl' => array(
            'provider' => 'spaces_og',
            'id' => $space->id,
          ),
        ));
      }
    }
  }
}

/**
 * Implements hook_form_alter().
 */
function spaces_og_form_alter(&$form, $form_state, $form_id) {
  if (!empty($form['#node_edit_form']) && arg(0) . '/' . arg(1) != 'admin/content') {

    // Group nodes
    if (og_is_group_type('node', $form['type']['#value'])) {
      module_load_include('inc', 'spaces_og', 'spaces_og.pages');
      _spaces_og_form_alter_group($form, $form_state);
    }
    elseif (variable_get('og_group_type_' . $form['type']['#value'], 'omitted') != 'omitted') {
      module_load_include('inc', 'spaces_og', 'spaces_og.pages');
      _spaces_og_form_alter_node($form, $form_state);
    }
  }
}

/**
 * Group form submit handler.
 */
function spaces_og_form_group_submit($form, &$form_state) {
  if (!empty($form_state['nid']) && ($space = spaces_load('og', $form_state['nid']))) {
    $preset_val = $space->controllers->variable
      ->get('site_frontpage', 'preset');
    $space_val = $space->controllers->variable
      ->get('site_frontpage', 'space');
    if (!empty($preset_val) || !empty($space_val)) {
      purl_goto($space->controllers->variable
        ->get('site_frontpage'), array(
        'purl' => array(
          'provider' => 'spaces_og',
          'id' => $space->id,
        ),
      ));
    }
    else {
      purl_goto("node/{$space->id}", array(
        'purl' => array(
          'provider' => 'spaces_og',
          'id' => $space->id,
        ),
      ));
    }
  }
}

/**
 * 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) && $space = spaces_get_space()) {
    $gid = $space->id;
    $node = node_load($gid);

    // User is a member
    if (isset($user->og_groups[$gid])) {
      // Do not let managers leave the group -- TODO: figure out a
      // better workflow for these situations.
      if (!og_is_group_admin($node) && $node->og_selective != OG_CLOSED) {
        return array(
          'title' => t('Leave this group'),
          'href' => "og/unsubscribe/{$node->nid}/{$user->uid}",
          'query' => 'destination=' . $_GET['q'],
        );
      }
    }
    // User has requested membership
    else if (db_query("SELECT count(nid) FROM {og_uid} WHERE nid = :nid AND uid = :uid AND is_active = :is_active", array(':nid' => $gid, ':uid' => $user->uid, ':is_active' => 0))->fetchField()) {
      return array(
        'title' => t('Cancel request to join'),
        'href' => "og/unsubscribe/{$node->nid}/{$user->uid}",
        '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;
  */
}

/**
 * Enforces OG group and privacy settings on a node.
 */
function _spaces_og_enforce_privacy($gid, &$node) {
  if ($group = node_load($gid)) {
    if ($group->og_private) {
      $node->og_public = OG_VISIBLE_GROUPONLY;
    }
    else {
      $node->og_public = OG_VISIBLE_BOTH;
    }
    $node->og_groups = array(
      $gid => $gid,
    );
    return TRUE;
  }
  return FALSE;
}

/**
 * Implements hook_context_page_reaction().
 * Allows Spaces OG to set the breadcrumb just prior to theme('page')
 * taking over.
 */
function spaces_og_context_page_reaction() {
  $space = spaces_get_space();
  if ($space && $space->type === 'og') {
    $item = menu_get_item();

    // Only set the breadcrumb if user has access to this page.
    if ($item['access']) {

      // TODO, og_get_breadcrumb() no longer exists, but may not be needed.
      // $bc = og_get_breadcrumb('node', $space->group);
      $bc[0] = l(t('Home'), '', array(
        'purl' => array(
          'disabled' => TRUE,
        ),
      ));
      drupal_alter('spaces_og_breadcrumb', $bc);
      drupal_set_breadcrumb($bc);
    }
  }
}

/**
 * Implements hook_db_rewrite_sql().
 */
function spaces_og_db_rewrite_sql($query, $primary_table, $primary_field, $args) {
  switch ($primary_table) {
    case 'taxonomy_term_data':
    case 'td':
    case 't':

      // This check prevents rewrites of queries run against single nodes
      // (e.g. node_load()) and rewrites in the admin section of the site.
      if (arg(0) !== 'admin' && strpos($query, 'r.vid = %d') === FALSE) {
        $return = array();
        $return['distinct'] = 1;
        $return['join'] = "\n          LEFT JOIN {taxonomy_term_node} spaces_og_tn ON {$primary_table}.{$primary_field} = spaces_og_tn.tid\n          LEFT JOIN {taxonomy_vocabulary} spaces_og_v ON {$primary_table}.vid = spaces_og_v.vid\n          LEFT JOIN {og_ancestry} spaces_og_oga ON spaces_og_tn.nid = spaces_og_oga.nid\n          LEFT JOIN {node} spaces_og_n ON spaces_og_oga.group_nid = spaces_og_n.nid";

        // Show tags that haven't been used or are on non-og enabled nodes.
        $return['where'] = "(spaces_og_oga.group_nid IS NULL)";

        // Allow any terms that do not belong to a freetagging vocab.
        $return['where'] .= " OR (spaces_og_v.tags = 0)";

        // Allow any tags that belong to nodes in a user's groups.
        global $user;
        if (!empty($user->og_groups)) {
          $groups = array_keys($user->og_groups);
          $groups = implode(', ', $groups);
          $return['where'] .= " OR (spaces_og_oga.group_nid IN ({$groups}) AND spaces_og_n.status = 1)";
        }
        return $return;
      }
      break;
    case 'users':
    case 'u':

      // This check prevents rewrites of queries run to retrive single users,
      // (e.g. user_load()) and rewrites in the admin sectin of the site.
      if (arg(0) !== 'admin' && !user_access('view users outside groups') && strpos($query, 'uid = %d') === FALSE) {
        $return = array();
        $return['distinct'] = 1;
        $return['join'] = "\n          LEFT JOIN {og_uid} spaces_og_ogu ON {$primary_table}.{$primary_field} = spaces_og_ogu.uid\n          JOIN {node} spaces_og_n ON spaces_og_ogu.nid = spaces_og_n.nid";

        // Filter by user's groups.
        global $user;
        if (!empty($user->og_groups)) {
          $groups = array_keys($user->og_groups);
          $groups = implode(', ', $groups);
          $return['where'] = "\n            (spaces_og_ogu.nid IN ({$groups}) AND spaces_og_n.status = 1) OR\n            ({$primary_table}.{$primary_field} = {$user->uid})";
        }
        else {
          $return['where'] = "FALSE OR ({$primary_table}.{$primary_field} = {$user->uid})";
        }
        return $return;
      }
      break;
  }
}

/**
 * Implements hook_views_api().
 */
function spaces_og_views_api() {
  return array(
    'api' => 2,
    'path' => drupal_get_path('module', 'spaces_og') . '/views',
  );
}

/**
 * Implementation of hook_spaces_get_space_from_object().
 */
function spaces_og_spaces_get_space_from_object($type, $object) {
  if ($type == 'node') {
    if (og_is_group_type('node', $object->type)) {
      return spaces_load('og', $object->nid);
    }
    else {
      if (og_is_group_content_type('node', $object->type)) {
        if (isset($object->{OG_AUDIENCE_FIELD}[LANGUAGE_NONE][0]['gid'])) {
          return spaces_load('og', og_load($object->{OG_AUDIENCE_FIELD}[LANGUAGE_NONE][0]['gid'])->etid);
        }
      }
    }
  }
}

/**
 * Implements hook_og_context_negotiation_info().
 */
function spaces_og_og_context_negotiation_info() {
  $providers = array();
  $providers['spaces_og'] = array(
    'name' => t('Spaces OG'),
    'description' => t("Determine context by checking spaces."),
    'callback' => 'spaces_og_og_context_handler',
  );
  return $providers;
}

/**
 * Callback function.
 * Invoked by OG Context Negotiation
 * Return the active group retrvied by spaces.
 */
function spaces_og_og_context_handler() {
  $gids = array();
  $space = spaces_get_space();
  if (isset($space->type) && $space->type == 'og') {
    $gids[] = $space->og->gid;
  }
  return $gids;
}

/**
 * Implements hook_query_TAG_alter().
 */
function spaces_og_query_user_autocomplete_spaces_og_alter(QueryAlterableInterface $query) {
  if (!user_access('view users outside groups')) {
    $query
      ->join('og_membership', 'ogm', 'u.uid = ogm.etid AND ogm.entity_type = :type', array(
      ':type' => 'user',
    ));
    $query
      ->condition('gid', reset(spaces_og_og_context_handler()));
  }
}

Functions

Namesort descending Description
spaces_og_access_admin Access callback for OG administrative items under node/%. Prevents items from showing up under node types that are not groups.
spaces_og_context_page_reaction Implements hook_context_page_reaction(). Allows Spaces OG to set the breadcrumb just prior to theme('page') taking over.
spaces_og_ctools_plugin_api Implements hook_ctools_plugin_api().
spaces_og_db_rewrite_sql Implements hook_db_rewrite_sql().
spaces_og_enable Implements hook_enable().
spaces_og_form_alter Implements hook_form_alter().
spaces_og_form_ctools_export_ui_edit_item_form_alter Implements hook_form_alter() for ctools_export_ui_edit_item().
spaces_og_form_group_submit Group form submit handler.
spaces_og_form_node_delete_confirm_alter Implements hook_form_alter() for node_delete_confirm().
spaces_og_menu Implements hook_menu().
spaces_og_menu_alter Implements hook_menu_alter().
spaces_og_node_delete Implements hook_node_delete().
spaces_og_node_insert Implements hook_node_insert().
spaces_og_node_load Implements hook_node_load().
spaces_og_node_prepare Implements hook_node_prepare().
spaces_og_node_presave Implements hook_node_presave().
spaces_og_node_update Implements hook_node_update().
spaces_og_node_validate Implements hook_node_validate().
spaces_og_og_context_handler Callback function. Invoked by OG Context Negotiation Return the active group retrvied by spaces.
spaces_og_og_context_negotiation_info Implements hook_og_context_negotiation_info().
spaces_og_permission Implements hook_permission().
spaces_og_purl_provider Implements hook_purl_provider().
spaces_og_query_user_autocomplete_spaces_og_alter Implements hook_query_TAG_alter().
spaces_og_spaces_get_space_from_object Implementation of hook_spaces_get_space_from_object().
spaces_og_spaces_plugins Implements hook_spaces_plugins().
spaces_og_spaces_presets Implements hook_spaces_presets().
spaces_og_spaces_registry Implements hook_spaces_registry().
spaces_og_subscription_link Custom subscription link - use "join" instead of "subscribe" - make it shorter.
spaces_og_user_categories Implements hook_user_categories().
spaces_og_views_api Implements hook_views_api().
_spaces_og_enforce_privacy Enforces OG group and privacy settings on a node.