<?php
/**
 * @file
 * Code for the Commons Groups feature.
 */

include_once 'commons_groups.features.inc';

/**
 * Implements hook_og_permission_alter().
 */
function commons_groups_og_permission_alter(&$perms) {
  // Rename the 'subscribe' and 'subscribe without approval' permissions to
  // better reflect their purpose in Commons.
  $perms['subscribe']['title'] = t('Contribute to the group');
  $perms['subscribe']['description'] = t('This value is set automatically based on the "Group Privacy Settings" field.');
  $perms['subscribe without approval']['title'] = t('Contribute to the group without approval');
  $perms['subscribe without approval']['description'] = t('This value is set automatically based on the "Group Privacy Settings" field.');
}

/**
 * Implements hook_form_FORM_ID_alter().
 *
 * Hides permissions that are set automatically based on the "Group Privacy
 * Settings" field.
 */
function commons_groups_form_og_ui_admin_permissions_alter(&$form, &$form_state) {
  $hidden_permissions = array('subscribe', 'subscribe without approval');
  foreach ($hidden_permissions as $permission) {
    if (isset($form['permission'][$permission])) {
      $form['permission'][$permission]['#markup'] .= ' ' . t('<strong>Disabled by the Commons Groups module.<strong>');
    }
    foreach ($form['checkboxes'] as $index => $elements) {
      if (isset($elements['#options'][$permission])) {
        unset($form['checkboxes'][$index]['#options'][$permission]);
      }
    }
  }
}

/**
 * Implements hook_ctools_plugin_directory().
 */
function commons_groups_ctools_plugin_directory($module, $plugin) {
  if ($module == 'entityreference') {
    return "plugins/entityreference/$plugin";
  }
}

/**
 * Implements hook_modules_enabled().
 *
 * Make sure the og access fields exist when og_access is enabled.
 */
function commons_groups_modules_enabled($modules) {
  if (in_array('og_access', $modules)) {
    features_revert(array('commons_groups' => array('field_base')));
    features_revert(array('commons_groups' => array('field_instance')));
  }
}

/**
 * Implements hook_help().
 * Used for the 3.2 -> 3.3 migration to warn users who have out-of-date groups
 * to make sure they update the group privacy settings.
 * See https://drupal.org/node/2059857 for more information
 */
function commons_groups_help($path, $arg) {
  if (variable_get('commons_groups_needs_update', FALSE)) {
    $message =  '<p>' . t("Drupal Commons 3.3 added a new, required field to control group privacy. Please edit your group(s) select one of the privacy options. Once all groups are
      set, an administrator can dismiss the update notice.") . '</p>';
    if ($path == 'admin/content/groups/update') {
      return $message;
    }
    elseif ($arg[0] == 'node' && $arg[2] == 'edit') {
      $node = menu_get_object();
      if($node->type == 'group' && empty($node->field_og_subscribe_settings)) {
        return $message;
      }
    }
    if (user_access('edit any group content')) {
      $message = t("Group privacy settings !updated.", array('!updated' => l('need to be updated', 'admin/content/groups/update')));
      drupal_set_message($message, 'warning', FALSE);
    }
  }
}

/**
 * Implements hook_entity_view().
 */
function commons_groups_entity_view($entity, $type, $view_mode, $langcode) {
  // Set a breadcrumb for nodes in groups. We currently assume that
  // nodes are groups.
  if ($view_mode == 'full' && !empty($entity->og_group_ref[LANGUAGE_NONE][0]['target_id']) && $type != 'user') {
    $breadcrumb = array();
    $breadcrumb[] = l(t('Home'), NULL);
    $breadcrumb[] = l(t('Groups'), 'groups');
    $group = node_load($entity->og_group_ref[LANGUAGE_NONE][0]['target_id']);
    if (node_access('view', $group)) {
      $breadcrumb[] = l($group->title, 'node/' . $group->nid);
    }
    drupal_set_breadcrumb($breadcrumb);
  }
}

/**
 * Implements hook_form_BASE_FORM_ID_alter().
 */
function commons_groups_form_node_form_alter(&$form, &$form_state, $form_id) {
  $node = $form['#node'];

  list(, , $bundle) = entity_extract_ids('node', $node);

  // Customizations to the node form for entities that are group content.
  $group_content_entity_types = commons_groups_get_group_content_entity_types();

  if (isset($group_content_entity_types['node'][$bundle])) {
    $form['actions']['submit']['#submit'][] = 'commons_groups_node_in_group_submit';
  }

  // Hide the "Group content visibility" field to simplify the node form.
  if (!empty($form['group_content_access']['#access'])) {
    $form['group_content_access']['#access'] = FALSE;
  }

  // Alter the privacy settings fields.
  $groups = og_get_all_group_bundle();

  if (isset($groups['node']) && in_array($bundle, array_keys($groups['node']))) {
    // The group privacy settings are not required.
    $form['field_og_subscribe_settings'][LANGUAGE_NONE]['#required'] = FALSE;

    if (module_exists('og_access')) {
      // Display private content checkbox only when "Joining requires approval"
      // is selected.
      $form['field_og_access_default_value']['#states'] = array(
        'visible' => array(
          ':input[name="field_og_subscribe_settings[' . LANGUAGE_NONE . ']"]' => array('value' => 'approval'),
        ),
      );

      $form['#after_build'] = array('commons_groups_form_group_node_after_build');
    }

    $form['#attached']['css'][] = drupal_get_path('module', 'commons_groups') . '/css/commons_groups.css';

    // The group access is set on commons_groups_node_presave().
    $form['group_access'][LANGUAGE_NONE]['#required'] = FALSE;
    $form['group_access']['#access'] = FALSE;
  }
}

/**
 * After build callback for the group node form.
 *
 * Display the private content checkbox inside the privacy settings field.
 */
function commons_groups_form_group_node_after_build($form, $form_state) {
  $form['field_og_subscribe_settings'][LANGUAGE_NONE]['approval']['#suffix'] = render($form['field_og_access_default_value']);

  return $form;
}

/**
 * Update the group permission field.
 *
 * @param $role
 *   The OG role object of which the permissions are being changed.
 * @param $permissions
 *   The anonymous user permissions of the group.
 */
function _commons_groups_update_group_permissions($role, $permissions) {
  $updated_roles = &drupal_static(__FUNCTION__);
  if (!empty($updated_roles[$role->rid])) {
    // Avoid updating a group subscription twice on the same request.
    return;
  }

  if (!empty($permissions['subscribe without approval'])) {
    $subscribe_type = 'anyone';
  }
  elseif (!empty($permissions['subscribe'])) {
    $subscribe_type = 'approval';
  }
  else {
    $subscribe_type = 'invitation';
  }

  $wrapper = entity_metadata_wrapper($role->group_type, $role->gid);
  if ($wrapper->field_og_subscribe_settings->value() != $subscribe_type) {
    // Mark that the group's permissions were already handled on this request,
    // to avoid saving the group entity more than once.
    $updated_roles[$role->rid] = TRUE;

    $wrapper->field_og_subscribe_settings->set($subscribe_type);
    $wrapper->save();
  }
}

/**
 * Implements hook_menu_alter().
 */
function commons_groups_menu_alter(&$items) {
  // Provide a more informative title.
  if (isset($items['node/%/group'])) {
    $items['node/%/group']['title'] = t('Administer group');
  }
}

/*
 * Implements hook_menu
 * Used with commons_groups_help and the commons groups update view to turn off
 * the warning message to update groups
 */
function commons_groups_menu() {
  $items['admin/content/groups/update/toggle'] = array(
    'title' => 'Toggle Groups Update',
    'page callback' => 'commons_groups_update_toggle',
    'access arguments' => array('edit any group content'),
    'type' => MENU_CALLBACK,
  );
  return $items;
}

/*
 * Ajax callback page to toggle the group update status to off
 * See https://drupal.org/node/2059857 for more information
 */
function commons_groups_update_toggle() {
  variable_set('commons_groups_needs_update', FALSE);
  return TRUE;
}

/**
 * Implements hook_block_info().
 */
function commons_groups_block_info() {
  $blocks['commons_groups_create_group'] = array(
    'info' => t('"Create a group" call to action'),
    'cache' => DRUPAL_NO_CACHE,
  );

  return $blocks;
}

/**
 * Implements hook_block_view().
 */
function commons_groups_block_view($delta = '') {
  $block = array();

  switch ($delta) {
    case 'commons_groups_create_group':
      if (node_access('create', 'group')) {
        $block['subject'] = NULL;
        $block['content'] = array(
          '#type' => 'link',
          '#title' => t('Create a group'),
          '#href' => 'node/add/group',
        );
      }
      break;
  }

  return $block;
}

/**
 * Implements hook_views_pre_view().
 * By default, all views should have a group_type filter that looks at only nodes.
 * This function allows those views to also show group content on the front page
 * regardless of their entity type.
 * See https://drupal.org/node/2037417 for more info.
 */
function commons_groups_views_pre_view(&$view, &$display_id, &$args) {
  // We check to see if a group id argument is set in the view, and if no arguments
  // are being passed to the view. If so, the group_type filter is irrelevant.
  if (isset($view->display_handler->options['arguments']['gid']) && empty($args)) {
    if (isset($view->display_handler->options['filters']['group_type'])) {
      $filters = $view->display_handler->get_option('filters');
      unset($filters['group_type']);
      $view->display_handler->override_option('filters', $filters);
    }
  }
}

function commons_groups_group_contributors_count_topics($group) {
  // Format the count of contributors.
  $output = '';
  $view = views_get_view('commons_contributors_group');
  if (!empty($view)) {
    $view->set_display('panel_pane_1');
    $view->set_arguments(array($group->nid));
    $view->get_total_rows = TRUE;
    $view->execute();
    // If there are no contributors with avatars, return an empty string
    // rather than displaying '0 contributors'.
    if (empty($view->total_rows)) {
      return '';
    }
    $contributors_count = $view->total_rows;
    $output .= l(format_plural($contributors_count, '1 contributor', '@count contributors'), 'node/' . $group->nid . '/contributors');
  }
  // Format the list of topics:
  if (!empty($group->field_topics[LANGUAGE_NONE])) {
    foreach ($group->field_topics[LANGUAGE_NONE] as $item) {
      $tids[] = $item['tid'];
    }
    $topics = taxonomy_term_load_multiple($tids);
    $topics_text = ' discussing the @topics ';
    $t_args = array('@topics' => format_plural(count($topics), 'topic', 'topics'));
    foreach ($topics as $topic) {
      $topics_text .= '!topic-' . $topic->tid;
      if ($topic == end($topics)) {
        $topics_text .= '.';
      }
      else {
        $topics_text .= ', ';
      }
      $t_args['!topic-' . $topic->tid] = l(t($topic->name), 'taxonomy/term/' . $topic->tid);
    }
    $output .= t($topics_text, $t_args);
  }
  return $output;
}

/* set commons_Groups form alter to happen after views bulk operations */
function commons_groups_module_implements_alter(&$implementations, $hook) {
  if ($hook == 'form_alter') {
    $group = $implementations['commons_groups'];
    unset($implementations['commons_groups']);
    $implementations['commons_groups'] = $group;
  }
}

/**
 * Implements hook_form_alter().
 */
function commons_groups_form_alter(&$form, &$form_state, $form_id) {
  if ($form_id == 'views_exposed_form' && strstr($form['#id'], 'views-exposed-form-commons-groups-directory')) {
    $form['groups-keys']['#attributes'] = array(
      'placeholder' => t('Separate keywords with commas'),
    );
  }
  if ((strstr($form_id, 'views_form_commons_group_moderation_page'))) {
    $form['select']['action::views_bulk_operations_delete_item']['#weight'] = 9;
  }
  if ($form_id == 'group_node_form' && is_null($form['nid']['#value'])) {
    $form['actions']['submit']['#submit'][] = 'commons_groups_group_submission_message';
  }
  if (in_array($form_id, array('og_ui_admin_global_permissions', 'og_ui_admin_permissions'))) {
    $group_content_entity_types = commons_groups_get_group_content_entity_types();
    if (!empty($group_content_entity_types)) {
      // @TODO: Improve this message to be more specific and/or
      // reflect these changes in the checkboxes.
      $message = 'In addition to the permissions listed here, the Commons Groups module grants non-group members the ability to post content into groups where content in the group is public.';
      drupal_set_message(t($message), 'warning');
    }
  }
  // Hide internal fields that the user should not be able to edit directly.
  if ($form_id == 'edit_profile_user_profile_form' || substr($form_id, -10) === '_node_form') {
    $internal_fields = array('field_unread_invitations', 'field_unread_messages', 'user_trusted_contacts', 'og_user_group_ref', 'group_access');
    foreach ($internal_fields as $field_name) {
      if(isset($form[$field_name])) {
        $form[$field_name]['#access'] = FALSE;
      }
    }
  }
  // Disable Trusted Contacts field if commons_trusted_contacts is disabled.
  $group_content_entity_types = commons_groups_get_group_content_entity_types();
  if (isset($form['#node']->type) && isset($group_content_entity_types['node'][$form['#node']->type])) {
    if (isset($form['og_user_group_ref']) && !module_exists('commons_trusted_contacts')) {
      $form['og_user_group_ref']['#access'] = FALSE;
    }
  }
}

/**
 * Submit handler called if the form is for a node enabled as group content.
 */
function commons_groups_node_in_group_submit(&$form, &$form_state) {
  if (isset($form_state['values']['og_group_ref'][LANGUAGE_NONE][0])) {
    $group = $form_state['values']['og_group_ref'][LANGUAGE_NONE][0]['target_id'];
    $form_state['redirect'] = 'node/' . $group;
  }
}

/**
 * Implements hook_system_info_alter().
 */
function commons_groups_system_info_alter(&$info, $file, $type) {
  // Commons Groups dynamically adds the og_group_ref field to
  // content types that request it by altering the
  // commons_groups_entity_types variable.
  // We must add a corresponding line for each field instance
  // to commons_groups.info so that Features is aware of the instance
  // and can successfully revert the field_instance component back
  // to its default state.
  if ($file->name == 'commons_groups') {
    $group_bundles = og_get_all_group_bundle();
    if (!empty($group_bundles['node'])) {
      foreach ($group_bundles['node'] as $bundle => $name) {
        // These field instances should be added to groups regardless of
        // whether og_access.module is enabled.
        $info['features']['field_instance'][] = "node-$bundle-field_og_access_default_value";
        $info['features']['field_instance'][] = "node-$bundle-field_og_subscribe_settings";
        $info['features']['field_instance'][] = "node-$bundle-og_roles_permissions";
        $info['features']['field_instance'][] = "node-$bundle-body";
        $info['features']['field_instance'][] = "node-$bundle-group_group";

        // These fields are only necessary when og_access.module is enabled.
        $info['features']['field_instance'][] = "node-$bundle-group_access";
        $info['features']['field_instance'][] = "node-$bundle-field_group_logo";

        // Add default strongarm settings.
        $info['features']['variable'][] = "comment_anonymous_$bundle";
        $info['features']['variable'][] = "comment_default_mode_$bundle";
        $info['features']['variable'][] = "comment_default_per_page_$bundle";
        $info['features']['variable'][] = "comment_form_location_$bundle";
        $info['features']['variable'][] = "comment_$bundle";
        $info['features']['variable'][] = "comment_preview_$bundle";
        $info['features']['variable'][] = "comment_subject_field_$bundle";
        $info['features']['variable'][] = "field_bundle_settings_node__$bundle";
      }
    }
    $group_content_entity_types = commons_groups_get_group_content_entity_types();
    if (!empty($group_content_entity_types)) {
      foreach ($group_content_entity_types as $entity_type => $bundles) {
        foreach (array_keys($bundles) as $bundle) {
          $info['features']['field_instance'][] = "$entity_type-$bundle-og_group_ref";
          $info['features']['field_instance'][] = "$entity_type-$bundle-group_content_access";
        }
      }
    }

    // Commons specific group variables.
    $commons_groups = commons_groups_get_group_types();
    if (isset($commons_groups['node'])) {
      foreach ($commons_groups['node'] as $bundle => $group_info) {
        $info['features']['variable'][] = "node_options_$bundle";
        $info['features']['variable'][] = "node_preview_$bundle";
        $info['features']['variable'][] = "node_submitted_$bundle";
        $info['features']['variable'][] = "og_group_manager_default_rids_node_$bundle";
      }
    }
  }

  // Dynamically adding a field instance to an entity type results in features
  // automatically detecting Commons Groups as a dependency.
  // We manually exclude the dependency in order to prevent entity type provider
  // modules from appearing overridden and to allow them to be used
  // independently of Commons Groups.
  $commons_entity_integrations = &drupal_static(__FUNCTION__);

  if (!isset($commons_entity_integrations)) {
    foreach (module_implements('commons_entity_integration') as $module) {
      $commons_entity_integrations[$module] = call_user_func($module . '_commons_entity_integration');
    }
  }

  if (isset($commons_entity_integrations[$file->name])) {
    foreach ($commons_entity_integrations[$file->name] as $entity_type => $integration) {
      foreach ($integration as $bundle => $options) {
        if (commons_groups_is_group_content($entity_type, $bundle)) {
          $info['features_exclude']['dependencies']['commons_groups'] = 'commons_groups';
        }
      }
    }
  }
}

/**
 * Implements hook_default_message_type_alter().
 */
function commons_groups_default_message_type_alter(&$defaults) {
  foreach (array('commons_activity_streams_comment_created', 'commons_activity_streams_node_created') as $name) {
    if (!empty($defaults[$name])) {
      $defaults[$name]->message_text[LANGUAGE_NONE][2] = commons_groups_message_partial_default();
    }
  }
}


/**
 * Implements hook_og_user_access_alter().
 *
 * Deny create permissions from non-members on "non-public" groups (i.e. groups
 * that don't allow joining without approval).
 */
function commons_groups_og_user_access_alter(&$perm, $context) {
  $account = $context['account'];
  $group_type = $context['group_type'];
  $group = $context['group'];

  if ($group_type != 'node') {
    return;
  }
  // The purpose of this function is to grant permissions to create content
  // in a group to non-members of the group, when the group's privacy settings
  // (field_og_subscribe_settings) is set to "Anyone can contribute".
  if (og_is_member($group_type, $group->nid, 'user', $account, array(OG_STATE_ACTIVE, OG_STATE_PENDING, OG_STATE_BLOCKED))) {
    // The user is a group member, so comply to the OG permissions.
    return;
  }

  $wrapper = entity_metadata_wrapper($group_type, $group);
  $access_create = $account->uid && $wrapper->field_og_subscribe_settings->value() == 'anyone';

  // Make sure user can view group (i.e. it's not private).
  $commons_groups_entity_types = commons_groups_get_group_content_entity_types();
  foreach (array_keys($commons_groups_entity_types['node']) as $type) {
    $perm["create $type content"] = $access_create;
  }
}


/**
 * Implements of hook_token_info().
 */
function commons_groups_token_info() {
  $types = array();
  $tokens = array();

  // Commons Groups tokens.
  $types['commons-groups'] = array(
    'name' => t('Commons Groups'),
    'description' => t('Tokens related to the Groups functionality in Drupal Commons.'),
    'needs-data' => 'node',
  );
  $tokens['commons-groups']['in-groups-text'] = array(
    'name' => t('"In groups" text'),
    'description' => t('The text (starting with "in the groups") indicating which groups a piece of content belongs to.'),
  );
  $tokens['node']['commons-groups-first-group'] = array(
    'name' => t('First Group'),
    'description' => t('First group associated with a piece of content. Useful for path aliases'),
  );
  $tokens['node']['commons-groups-group-contributors-count-topics'] = array(
    'name' => t('Commons Groups: Group contributor count and topics'),
    'description' => t('Displays text showing the number of contributors and the topics associated with a group node.'),
  );
  return array(
    'types' => $types,
    'tokens' => $tokens,
  );
}

/**
 * Implements hook_tokens().
 */
function commons_groups_tokens($type, $tokens, $data = array(), $options = array()) {
  $replacements = array();
  if ($type == 'node' && !empty($data['node'])) {
    $group = $data['node'];

    foreach ($tokens as $name => $original) {
      if ($name == 'commons-groups-group-contributors-count-topics') {
        $replacements[$original] = commons_groups_group_contributors_count_topics($group);
        return $replacements;
      }
    }
  }
  if ($type == 'commons-groups') {
    if (!empty($tokens['in-groups-text'])) {
      // Build a list of groups associated with this message.
      $text = '';
      $target_nids = array();
      $related_groups = array();
      $related_gids = array();

      // First, build an array of target nodes associated with the message.
      foreach ($data['message']->field_target_nodes[LANGUAGE_NONE] as $key => $value) {
        $target_nids[] = $value['target_id'];
      }
      // If there are no target nodes, the in-groups-text token should be empty.
      if (empty($target_nids)) {
        $replacements['[commons-groups:in-groups-text]'] = $text;
        return $replacements;
      }
      // Build a list of groups associated with the target nodes.
      // For now, we assume that the group type is node.
      foreach ($target_nids as $key => $nid) {
        $og_memberships_this_target = og_get_entity_groups('node', $nid);
        if (!empty($og_memberships_this_target['node'])) {
          $og_memberships_this_target = $og_memberships_this_target['node'];
          foreach ($og_memberships_this_target as $membership_id => $gid) {
            $related_gids[] = $gid;
          }
        }
      }
      // If no groups are associated with any of the target nodes,
      // then we have no "in the groups" text.
      if (empty($related_gids)) {
        $replacements['[commons-groups:in-groups-text]'] = '';
        return $replacements;
      }

      $related_groups = entity_load('node', $related_gids);
      // Key the array of groups in a predictable way.
      $related_groups = array_values($related_groups);
      // Generate the appropriate text depending on the number of groups
      // associated with the message:
      $replacements['[commons-groups:in-groups-text]'] = commons_groups_related_groups_text($related_groups);
      return $replacements;
    }
  }
  if ($type == 'node' && !empty($data['node'])) {
    if (!empty($tokens['commons-groups-first-group'])) {
      $group = $data['node'];
      $text = '';

      if (!empty($group->og_group_ref[LANGUAGE_NONE])) {
        $wrapper = entity_metadata_wrapper('node', $group);
        $groups = $wrapper->og_group_ref->value();

        // Return the title of the first group associated with this node.
        $first_group = array_shift($groups);

        // Use the title field by default and fall back to the node title.
        $first_group_wrapper = entity_metadata_wrapper('node', $first_group);
        $text = isset($first_group_wrapper->title_field) ? $first_group_wrapper->title_field->value() : $first_group_wrapper->label();
      }
      $replacements['[node:commons-groups-first-group]'] = $text;
      return $replacements;
    }
  }
}

function commons_groups_message_partial_default() {
  $partial = array(
    'value' => '[commons-groups:in-groups-text]',
    'format' => 'full_html',
    'safe_value' => '[commons-groups:in-groups-text]',
  );
  return $partial;
}

/**
 * Build the related-groups text for nodes.
 *
 * @param $related_groups
 *   Array of groups referenced by the node.
 *
 * @return
 *   String containing the related groups.
 */
function commons_groups_related_groups_text($related_groups) {
  // In 1 group: "in the x group"
  if (count($related_groups) == 1) {
    return t(' in the !group group', array('!group' => l($related_groups[0]->title, 'node/' . $related_groups[0]->nid)));
  }

  // In 2 groups: "in the x and y groups"
  if (count($related_groups) == 2) {
    return t(' in the !group-0 and !group-1 groups', array(
      '!group-0' => l($related_groups[0]->title, 'node/' . $related_groups[0]->nid),
      '!group-1' => l($related_groups[1]->title, 'node/' . $related_groups[1]->nid),
    ));
  }

  // In more than 2 groups: "in the x, y and z groups"
  if (count($related_groups) > 2) {
    // Separate the last group.
    $last_group = array_pop($related_groups);
    $text = ' in the ';
    // Prepare tokens for t() for each of the other groups.
    foreach ($related_groups as $key => $this_group) {
      $text .= "!group-$key, ";
      $t_args["!group-$key"] = l($this_group->title, 'node/' . $this_group->nid);
    }
    // Prepare the last group token.
    $text .= " and !group-$last_group->nid groups.";
    $t_args["!group-$last_group->nid"] = l($last_group->title, 'node/' . $last_group->nid);
    // Prepare the full text with all of the groups and their tokens:
    return t($text, $t_args);
  }
}

function commons_groups_group_submission_message($form, &$form_state) {
  if ($form_state['values']['status'] !== 1) {
    drupal_set_message(t('Thanks for your group submission! This group has entered the moderation queue and will be reviewed shortly.'));
  }
}

/**
 * Default value function for the og_group_ref reference field.
 * This function is assigned to the field with the default_value_function
 * property defined in our instances of the og_group_ref field,
 * which takes place in commons_groups_field_definition().
 */
function commons_groups_entityreference_default_value($entity_type, $entity, $field, $instance, $langcode) {

  $items = array();
  $field_name = $field['field_name'];

  if (empty($_GET[$field_name]) || !is_string($_GET[$field_name])) {
    return $items;
  }

  if (empty($instance['settings']['behaviors']['prepopulate']['status'])) {
    return $items;
  }

  $ids = explode(',', $_GET[$field_name]);
  // Check access to the provided entities.
  $target_type = $field['settings']['target_type'];
  entity_load($target_type, $ids);
  // Remove group nodes hidden by the node access system.
  foreach ($ids as $target_id) {
    $target = entity_load_single($target_type, $target_id);
    if (entity_access('view', $target_type, $target)
      && og_is_group_type($target_type, $target->type)
      && (og_user_access($target_type, $target_id, "create $entity->type content") || og_user_access($target_type, $target_id, "update any $entity->type content"))
    ) {
      $items[] = array('target_id' => $target_id);
    }
  }
  return $items;
}

/**
 * Implements hook_strongarm_alter().
 */
function commons_groups_strongarm_alter(&$items) {
  // Expose the Group content type for integration with Commons Radioactivity.
  if (isset($items['commons_radioactivity_entity_types'])) {
    $items['commons_radioactivity_entity_types']->value['node']['group'] = 1;
  }
}

function commons_groups_default_rules_configuration_alter(&$configs) {
  // Disable default OG new content notifications.
  // The language doesn't correspond to Commons' open groups model and we use
  // commons_follow and commons_follow_notify for new content notifications.
  if (isset($configs['rules_og_member_active'])) {
    $configs['rules_og_member_active']->active = FALSE;
  }
}

/**
 * Implements hook_node_presave().
 *
 * When the node's group is private, force the group content to be private.
 */
function commons_groups_node_presave($node) {
  if (!module_exists('og_access')) {
    return;
  }

  $wrapper = entity_metadata_wrapper('node', $node);

  if (og_is_group('node', $node)) {
    // Determine whether the group is private according to the subscription
    // field.
    $private = $wrapper->field_og_subscribe_settings->value() == 'invitation';
    $wrapper->{OG_ACCESS_FIELD}->set((int)$private);
    return;
  }

  if (!og_is_group_content_type('node', $node->type)) {
    return;
  }


  // Check whether any of the groups are private.
  $private = FALSE;
  foreach (array_keys(og_get_group_audience_fields('node', $node->type)) as $field) {
    if (empty($node->$field)) {
      continue;
    }

    foreach ($wrapper->$field as $group_wrapper) {
      if (empty($group_wrapper->field_og_access_default_value)) {
        continue;
      }

      if ($group_wrapper->field_og_access_default_value->value() == TRUE) {
        // Once a private group was found, there's no need to continue.
        $private = TRUE;
        break 2;
      }
    }
  }

  if ($private) {
    $wrapper->{OG_CONTENT_ACCESS_FIELD}->set(OG_CONTENT_ACCESS_PRIVATE);
  }
}

/**
 * Implements hook_node_update().
 */
function commons_groups_node_update($node) {
  $account = user_load($node->uid);
  commons_groups_first_contribution($account, $node);

  if (og_is_group('node', $node)) {
    commons_groups_set_group_permissions($node);
  }
}

/**
 * Implements hook_node_insert().
 */
function commons_groups_node_insert($node) {
  $account = user_load($node->uid);
  commons_groups_first_contribution($account, $node);

  if (og_is_group('node', $node)) {
    // When creating a new group, this hook happens before OG creates the
    // group specific roles. Therefore we create the roles here before altering
    // them in commons_groups_set_group_permissions().
    og_roles_override('node', $node->type, $node->nid);
    commons_groups_set_group_permissions($node);
  }
}

/**
 * Set the group's permissions according to field_og_subscribe_settings.
 *
 * @param $node
 *   A group node.
 */
function commons_groups_set_group_permissions($node) {
  // Avoid updating a group subscription twice on the same request.
  $updated_nodes = & drupal_static(__FUNCTION__);
  if (!empty($updated_nodes[$node->nid])) {
    return;
  }
  $updated_nodes[$node->nid] = TRUE;

  $wrapper = entity_metadata_wrapper('node', $node);
  $permission = $wrapper->field_og_subscribe_settings->value();
  $og_roles = og_roles('node', $node->type, $node->nid);
  $anon_rid = array_search(OG_ANONYMOUS_ROLE, $og_roles);

  $permissions = array(
    'subscribe' => $permission == 'approval',
    'subscribe without approval' => $permission == 'anyone',
  );

  // Check if the permissions needs to be changed.
  $changed = FALSE;
  $old_permissions = og_role_permissions(array($anon_rid => OG_ANONYMOUS_ROLE));
  foreach ($permissions as $permission => $value) {
    if (empty($old_permissions[$anon_rid][$permission]) || $old_permissions[$anon_rid][$permission] != $value) {
      $changed = TRUE;
    }
  }

  // Only change the permissions when neccessary.
  if ($changed) {
    og_role_change_permissions($anon_rid, $permissions);
  }
}

/**
 * Helper function to determine whether an entity bundle is considered group
 * content.
 *
 * @param $entity_type
 *   The entity type to check group content settings for.
 * @param $bundle
 *   The entity bundle to check group content settings for.
 *
 * @return boolean
 *   The value of the group content setting if available, FALSE otherwise.
 */
function commons_groups_is_group_content($entity_type, $bundle) {
  $commons_groups_entity_types = commons_groups_get_group_content_entity_types();

  return isset($commons_groups_entity_types[$entity_type][$bundle]['is_group_content']) ? $commons_groups_entity_types[$entity_type][$bundle]['is_group_content'] : FALSE;
}

/**
 * Returns an array of entity types that are enabled via Commons Groups.
 */
function commons_groups_get_group_content_entity_types($cache = TRUE) {
  // Find all Commons Entity integrations.
  $commons_entity_integrations = commons_entity_integration_info(NULL, $cache);
  if (empty($commons_entity_integrations)) {
    return array();
  }

  foreach ($commons_entity_integrations as $entity_type => $integration) {
    foreach ($integration as $bundle => $options) {
      if (isset($options['is_group_content']) && $options['is_group_content'] == FALSE) {
        unset($commons_entity_integrations[$entity_type][$bundle]);
      }
    }
    // If an entity type has no integrations, don't return it.
    if (empty($commons_entity_integrations[$entity_type])) {
      unset($commons_entity_integrations[$entity_type]);
    }
  }

  return $commons_entity_integrations;
}

/**
 * Returns an array of entity types that are defined as a group.
 */
function commons_groups_get_group_types() {
  // Find all Commons Entity integrations.
  $commons_groups = array();
  $commons_entity_integrations = commons_entity_integration_info();
  if (empty($commons_entity_integrations)) {
    return array();
  }

  foreach ($commons_entity_integrations as $entity_type => $integration) {
    foreach ($integration as $bundle => $options) {
      if (isset($options['is_group']) && $options['is_group'] == TRUE) {
        $commons_groups[$entity_type][$bundle] = $commons_entity_integrations[$entity_type][$bundle];
      }
    }
  }

  return $commons_groups;
}

/**
 * When a user first creates content within a group,
 * grant her the contributor role within that group.
 */
function commons_groups_first_contribution($account, $node) {
  // Find the groups that this piece of content belongs to.
  $groups = og_get_entity_groups('node', $node);
  // @todo: Make it work also with user-groups.
  if (!empty($groups['node'])) {
    $node_groups = array_values($groups['node']);
    // Find the groups that the node author belongs to.
    $account_groups = og_get_groups_by_user($account, 'node');
    if (!$account_groups) {
      $account_groups = array();
    }
    // For groups where this user is not already a member, add her to the group.
    // Anonymous users should never be added to a group automatically
    if ($account->uid == 0) {
      return;
    }
    $new_groups = array_diff($node_groups, $account_groups);
    if (!empty($new_groups)) {
      foreach ($new_groups as $new_group_nid) {
        og_group('node', $new_group_nid, array('entity' => $account->uid));
      }
    }
  }
}

/**
 * Implements hook_preprocess_node().
 */
function commons_groups_preprocess_node(&$variables) {
  $variables['user_picture'] = '';
  if (variable_get('user_pictures', 0)) {
    $node = $variables['node'];
    $account = user_load($node->uid);

    if (!empty($account->picture)) {
      // @TODO: Ideally this function would only be passed file objects, but
      // since there's a lot of legacy code that JOINs the {users} table to
      // {node} or {comments} and passes the results into this function if we
      // a numeric value in the picture field we'll assume it's a file id
      // and load it for them. Once we've got user_load_multiple() and
      // comment_load_multiple() functions the user module will be able to load
      // the picture files in mass during the object's load process.
      if (is_numeric($account->picture)) {
        $account->picture = file_load($account->picture);
      }
      if (!empty($account->picture->uri)) {
        $filepath = $account->picture->uri;
      }
    }
    elseif (variable_get('user_picture_default', '')) {
      $filepath = variable_get('user_picture_default', '');
    }

    if (isset($filepath)) {
      if (module_exists('image') && file_valid_uri($filepath)) {
        $alt = t("@user's picture", array('@user' => format_username($account)));

        $render = array(
          '#theme' => 'image_formatter',
          '#image_style' => '50x50',
          '#item' => array(
            'uri' => $filepath,
            'alt' => $alt,
          ),
          '#path' => array(
            'path' => 'user/' . $account->uid,
            'options' => array(
              'attributes' => array(
                'title' => t("View @user's profile.", array('@user' => format_username($account))),
                'class' => array('user-picture'),
              ),
            ),
          ),
        );

        // Use a unique image style for user pictures in post information.
        $variables['user_picture'] = drupal_render($render);
      }
    }
  }
}

/**
 * Implements hook_preprocess_views_view_list().
 */
function commons_groups_preprocess_views_view_list(&$variables, $hook) {
  // Change the displayed role name in the group contributors block to
  // "Organizers".
  if ($variables['view']->name == 'commons_contributors_group' && !empty($variables['title']) && $variables['title'] == 'administrator member') {
    $variables['title'] = t('Organizers');
  }
}

/**
 * Implements hook_field_access().
 */
function commons_groups_field_access($op, $field, $entity_type, $entity, $account) {
  $field_name = $field['field_name'];
  switch ($field_name) {
    case 'og_roles_permissions':
      return FALSE;

    case 'field_og_access_default_value':
      return $op == 'edit' && module_exists('og_access');

    case 'field_og_subscribe_settings':
      return $op == 'edit';
  }

  if (module_exists('og_access') && in_array($field_name, array(OG_CONTENT_ACCESS_FIELD, OG_ACCESS_FIELD))) {
    return FALSE;
  }
}

/**
 * Implements hook_field_formatter_info().
 */
function commons_groups_field_formatter_info() {
  return array(
    'commons_groups_group_subscribe' => array(
      'label' => t('Commons groups subscribe link'),
      'field types' => array('list_boolean'),
      'settings' => array(
        'field_name' => FALSE,
      ),
    ),
  );
}

/**
 * Implements hook_field_formatter_view().
 */
function commons_groups_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
  global $user;
  $account = clone $user;

  if ($display['type'] != 'commons_groups_group_subscribe') {
    return;
  }

  if (!og_is_group($entity_type, $entity)) {
    return;
  }

  if (!empty($entity->uid) && ($entity->uid == $account->uid)) {
    // User is the group manager.
    $element[0] = array('#markup' => t('You are the group manager'));
    return $element;
  }

  list($id, , $bundle) = entity_extract_ids($entity_type, $entity);

  // The user has a pending membership request. Let her know that
  // her request is pending review.
  if (og_is_member($entity_type, $id, 'user', $account, array(OG_STATE_PENDING))) {
    $element[0] = array('#markup' => '<div class="subscription-type">' . t('Your membership request is pending review by a group organizer.') . '</div>');
    return $element;
  }
  // If user is blocked, they should not be able to apply for membership.
  if (og_is_member($entity_type, $id, 'user', $account, array(OG_STATE_BLOCKED))) {
    return;
  }
  if (og_is_member($entity_type, $id, 'user', $account)) {
    // The user has an active membership.
    if (og_user_access($entity_type, $id, 'unsubscribe', $account)) {
      // The user has the permission to unsubscribe himself,
      // otherwise don't display a "Leave" link since the user can't leave
      // anyways.
      // For groups where anyone can contribute without joining, don't display
      // a "Leave" link since users never went through
      // the separate step of joining.
      if (og_is_member($entity_type, $id, 'user', $account, array(OG_STATE_ACTIVE)) && $entity->field_og_subscribe_settings[LANGUAGE_NONE][0]['value'] != 'anyone') {
        $links['title'] = t('Leave group');
        $links['href'] = "group/$entity_type/$id/unsubscribe";
      }
    }
  }
  else {
    // Check if user can subscribe to the field.
    if (empty($settings['field_name']) && $audience_field_name = og_get_best_group_audience_field('user', $account, $entity_type, $bundle)) {
      $settings['field_name'] = $audience_field_name;
    }
    if (!$settings['field_name']) {
      return;
    }

    $field_info = field_info_field($settings['field_name']);

    // Check if entity is referencable.
    if ($field_info['settings']['target_type'] != $entity_type) {
      // Group type doesn't match.
      return;
    }
    if (!empty($field_info['settings']['handler_settings']['target_bundles']) && !in_array($bundle, $field_info['settings']['handler_settings']['target_bundles'])) {
      // Bundles don't match.
      return;
    }

    if (!og_check_field_cardinality('user', $account, $settings['field_name'])) {
      $element[0] = array('#markup' => format_plural($field_info['cardinality'], 'You are already registered to another group', 'You are already registered to @count groups'));
      return $element;
    }

    $url = "group/$entity_type/$id/subscribe";
    if ($settings['field_name']) {
      $url .= '/' . $settings['field_name'];
    }
    // Set the needs update hook if we end up with a group call that is missing
    // the subscribe settings. We also check the variable first, because we
    // don't want to reset the variable cache if we don't have to.
    // See https://drupal.org/node/2059857#comment-7733465 for more info.
    if (empty($entity->field_og_subscribe_settings)) {
      if (!variable_get('commons_groups_needs_update', FALSE)) {
        variable_set('commons_groups_needs_update', TRUE);
      }
    }
    // Don't display join link on public groups.
    else {
      if ($entity->field_og_subscribe_settings[LANGUAGE_NONE][0]['value'] != 'anyone') {
        if ($entity->field_og_subscribe_settings[LANGUAGE_NONE][0]['value'] == 'approval') {
          $subscription_type = t('Moderated group');
          $links['title'] = t('Join group');
          if ($account->uid) {
            $links['href'] = $url;
          }
          else {
            $links['href'] = 'user/login';
            $links['options'] = array('query' => array('destination' => $url));
          }
        }
        else {
          $element[0] = array('#markup' => '<div class="subscription-type">' . t('Invite-only group') . '</div>');
          return $element;
        }
      }
    }
  }
  if (!empty($links['title'])) {
    $links += array('options' => array());
    $element[0] = array(
      '#type' => 'link',
      '#title' => $links['title'],
      '#href' => $links['href'],
      '#options' => $links['options'],
    );

    if (!empty($subscription_type)) {
      $element[0]['#prefix'] = '<div class="subscription-type">' . $subscription_type . '</div>';
    }

    return $element;
  }
}

/**
 * Implements hook_views_pre_render().
 */
function commons_groups_views_pre_render(&$view) {
  // Improve the browsing widget empty text when displayed outside of a group.
  // TODO: Enable og_context and check group context instead of looking for an
  // empty first argument.
  if (empty($view->args[0]) && $view->name == 'commons_bw_all') {
    $view->display_handler->handlers['empty']['area']->options['content'] = t('Nobody has posted yet.');
  }
}

/**
 * Special Commons implementation of hook_features_rebuild().
 *
 * By default, reverting og permissions only occurs on the default rid, which is
 * 0. All groups already created will not see the new permissions.
 *
 * As an alternative, this function iterates through all of the groups and sets
 * default permissions and update the permissions map.
 *
 * @param string $module
 *   The modules whose default user permissions should be rebuild.
 */
function commons_groups_features_permission_rebuild($module, $gid) {
  module_load_include('features.inc', 'og', '/includes/og_features_role');

  if ($defaults = features_get_default('og_features_permission', $module)) {
    // Make sure the list of available group types is up to date, especially
    // when installing multiple features at once, for example from an install
    // profile or via drush.
    drupal_static_reset();

    $grant = array();
    $revoke = array();

    foreach ($defaults as $key => $details) {
      list($group_type, $bundle, $perm) = explode(':', $key);

      // Make sure the role exists for this entity.
      foreach ($details['roles'] as $role) {
        $bundle_role = _og_features_role_exists($role, $group_type, $bundle);
        if (empty($bundle_role)) {
          og_role_save(og_role_create($role, $group_type, $gid, $bundle));
        }
      }

      $roles = og_roles($group_type, $bundle, $gid);
      foreach ($roles as $rid => $rolename) {
        if (in_array($rolename, $details['roles'])) {
          $grant[$rid][] = $perm;
        }
        else {
          $revoke[$rid][] = $perm;
        }
      }
    }

    if (!empty($grant)) {
      foreach ($grant as $rid => $permissions) {
        og_role_grant_permissions($rid, $permissions);
      }
    }

    if (!empty($revoke)) {
      foreach ($revoke as $rid => $permissions) {
        og_role_revoke_permissions($rid, $permissions);
      }
    }
  }
}
