You are here

social_group.module in Open Social 8.6

The Social group module.

File

modules/social_features/social_group/social_group.module
View source
<?php

/**
 * @file
 * The Social group module.
 */
use Drupal\block\Entity\Block;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Access\AccessResultNeutral;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\HtmlCommand;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\group\Entity\Group;
use Drupal\Core\Form\FormStateInterface;
use Drupal\group\Entity\GroupContentInterface;
use Drupal\group\Entity\GroupContentType;
use Drupal\group\Entity\GroupInterface;
use Drupal\group\Entity\GroupContent;
use Drupal\group\Entity\GroupType;
use Drupal\node\NodeInterface;
use Drupal\social_group\Controller\SocialGroupController;
use Drupal\social_group\Form\SocialGroupAddForm;
use Drupal\views\ViewExecutable;
use Drupal\views\Plugin\views\cache\CachePluginBase;
use Drupal\views\Plugin\views\query\QueryPluginBase;
use Drupal\views\Plugin\views\row\EntityRow;
use Drupal\Core\Url;
use Drupal\Core\Cache\Cache;
use Drupal\image\Entity\ImageStyle;
use Drupal\Core\Block\BlockPluginInterface;
use Drupal\user\Entity\Role;
use Drupal\user\Entity\User;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\social_group\GroupContentVisibilityUpdate;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\views_bulk_operations\ViewsBulkOperationsBatch;

/**
 * Implements hook_theme().
 */
function social_group_theme() {
  return [
    'group_settings_help' => [
      'variables' => [
        'group_type' => NULL,
        'join_method' => NULL,
        'allowed_visibility' => NULL,
      ],
    ],
  ];
}

/**
 * Prepares variables for group settings help text templates.
 *
 * Default template: group-settings-help.html.twig.
 *
 * @param array $variables
 *   An associative array containing:
 *   - group_type: The group type.
 *   - join_method: The join methods.
 *   - allowed_visibility: The allowed visibilities.
 */
function template_preprocess_group_settings_help(array &$variables) {
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function social_group_form_group_public_group_edit_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  $group = $form_state
    ->getFormObject()
    ->getEntity();
  $form['group_type'] = SocialGroupAddForm::create(\Drupal::getContainer())
    ->getGroupTypeElement();
  $form['group_type']['#default_value'] = $group
    ->bundle();

  // If user doesn't have permission to change group types disable it.
  // Or if group types can't be edited due to visibility issues.
  if (!social_group_group_type_permission_check()) {
    $form['group_type']['#disabled'] = TRUE;
  }
  else {
    $form['group_type']['#prefix'] = '<div id="group-type-result"></div>';
    $group_type_element['#ajax'] = [
      'callback' => '_social_group_inform_group_type_selection',
      'effect' => 'fade',
      'event' => 'change',
    ];
  }

  // Disable all group types that can't be edited. Because they don't have
  // a visibility.
  foreach ($form['group_type']['#options'] as $type => $label) {
    if (\Drupal::service('social_group.helper_service')
      ->getDefaultGroupVisibility($type) === NULL) {
      $form['group_type'][$type] = [
        '#disabled' => TRUE,
      ];
    }
  }
  $form['#group_children']['group_type'] = 'group_content';
}

/**
 * Check if a User is able to edit a Group's GroupType.
 *
 * @return bool
 *   TRUE if a user may edit a existings groups group type.
 */
function social_group_group_type_permission_check() {
  $user = \Drupal::currentUser();

  // Get the Group object from the route.
  $group = _social_group_get_current_group();

  // Check if we have a default visibility, if we don't it must be a custom
  // group type, we need to have a visibility in order to update group content.
  // please see hook_social_group_default_visibility_alter.
  if (\Drupal::service('social_group.helper_service')
    ->getDefaultGroupVisibility($group
    ->getGroupType()
    ->id()) === NULL) {
    return FALSE;
  }

  // Otherwise return true when we are able to edit the current group type.
  return TRUE;
}

/**
 * Get group overview route.
 *
 * @return array
 *   An array with route name and parameters and group.
 */
function _social_group_get_overview_route(GroupInterface $group) {
  $route = [
    'name' => 'view.groups.page_user_groups',
    'parameters' => [
      'user' => \Drupal::currentUser()
        ->id(),
    ],
  ];
  \Drupal::moduleHandler()
    ->alter('social_group_overview_route', $route, $group);
  return $route;
}

/**
 * Form submit redirect for social groups.
 *
 * @param array $form
 *   Group add or group edit form.
 * @param \Drupal\Core\Form\FormStateInterface $form_state
 *   Form state interface.
 */
function _social_group_edit_submit_redirect(array $form, FormStateInterface $form_state) {

  // Set redirect to the group about page.
  $route_parameters = $form_state
    ->getRedirect()
    ->getRouteParameters();
  if (!empty($route_parameters['group'])) {
    $form_state
      ->setRedirect('view.group_information.page_group_about', $route_parameters);
  }
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function social_group_form_entity_access_by_field_visibility_form_alter(&$form, FormStateInterface $form_state, $form_id) {

  // Add option to disable public group creation.
  $form['#submit'][] = '_social_group_visibility_settings_submit';
}

/**
 * Form submit for visibility settings.
 *
 * @param array $form
 *   Group add or group edit form.
 * @param \Drupal\Core\Form\FormStateInterface $form_state
 *   Form state interface.
 */
function _social_group_visibility_settings_submit(array $form, FormStateInterface $form_state) {

  /** @var \Drupal\user\Entity\Role $role */
  $role = Role::load('authenticated');
  $current_permission = $role
    ->hasPermission('create public_group group');
  $disable_public_visibility = $form_state
    ->getValue('disable_public_visibility');
  if ($disable_public_visibility === 1 && $current_permission === TRUE) {
    user_role_revoke_permissions($role
      ->id(), [
      'create public_group group',
    ]);
  }
  if ($disable_public_visibility === 0 && $current_permission === FALSE) {
    user_role_grant_permissions($role
      ->id(), [
      'create public_group group',
    ]);
  }
}

/**
 * Load group content label and group label.
 *
 * @return array
 *   array of group labels.
 */
function _social_group_get_group_labels() {

  // Load the entity label from the group content being handled.
  $group_content = \Drupal::routeMatch()
    ->getParameter('group_content')
    ->label();

  // Load group name.
  $group = \Drupal::routeMatch()
    ->getParameter('group')
    ->label();
  return [
    $group_content,
    $group,
  ];
}

/**
 * Prepares variables for profile templates.
 *
 * Default template: profile.html.twig.
 *
 * @param array $variables
 *   An associative array containing:
 *   - elements: An array of elements to display in view mode.
 *   - profile: The profile object.
 *   - view_mode: View mode; e.g., 'full', 'teaser', etc.
 */
function social_group_preprocess_group(array &$variables) {

  /** @var \Drupal\group\Entity\GroupInterface $group */
  $group = $variables['group'];
  $variables['title'] = $group
    ->label();
  $variables['joined'] = FALSE;
  $variables['closed_group'] = FALSE;
  $variables['cta'] = '';
  $variables['closed_group_lock'] = $variables['secret_group_shield'] = FALSE;
  $group_type_id = $group
    ->getGroupType()
    ->id();
  if (!array_key_exists('direct', _social_group_get_join_methods($group))) {
    $variables['closed_group_lock'] = TRUE;
  }
  $variables['group_type_id'] = $group_type_id;
  $variables['group_type'] = $group
    ->getGroupType()
    ->label();
  $group_settings_help = [
    '#theme' => 'group_settings_help',
    '#group_type' => $group
      ->getGroupType()
      ->label(),
    '#join_method' => _social_group_get_join_methods($group),
    '#allowed_visibility' => _social_group_get_allowed_visibility($group),
  ];
  $variables['group_settings_help'] = \Drupal::service('renderer')
    ->renderPlain($group_settings_help);
  $account = \Drupal::currentUser();

  // Set joined to true for teaser when current logged in
  // user is member of the group.
  if ($group
    ->getMember($account)) {
    $variables['joined'] = TRUE;
    if ($group
      ->hasPermission('leave group', $account)) {
      $variables['group_operations_url'] = Url::fromRoute('entity.group.leave', [
        'group' => $group
          ->id(),
      ]);
    }
  }
  elseif ($group
    ->hasPermission('join group', $account)) {

    // @todo switch this to get URL from routes correctly.
    $variables['group_operations_url'] = Url::fromRoute('entity.group.join', [
      'group' => $group
        ->id(),
    ]);
    if ($group_type_id === 'flexible_group') {
      $join_methods = $group
        ->get('field_group_allowed_join_method')
        ->getValue();
      $direct_option = in_array('direct', array_column($join_methods, 'value'), FALSE);
      if (!$direct_option) {
        $variables['group_operations_url'] = Url::fromRoute('entity.group.join', [
          'group' => $group
            ->id(),
        ]);
        $variables['closed_group'] = TRUE;
        $variables['cta'] = t('Invitation only');
      }
    }
  }
  elseif ($group_type_id == 'closed_group' && !$group
    ->hasPermission('manage all groups', $account)) {

    // Users can only be invited.
    $variables['group_operations_url'] = Url::fromRoute('entity.group.join', [
      'group' => $group
        ->id(),
    ]);
    $variables['closed_group'] = TRUE;
    $variables['cta'] = t('Invitation only');
  }

  // Add the hero styled image.
  if ($group
    ->hasField('field_group_image') && !empty($group
    ->get('field_group_image')->entity)) {

    // Fetch image style from field info.
    $original_image_style = $variables['content']['field_group_image'][0]['#image_style'] ?? '';
    $image_style = ImageStyle::load($original_image_style);
    if ($image_style instanceof ImageStyle) {
      $variables['group_hero_styled_image_url'] = $image_style
        ->buildUrl($group
        ->get('field_group_image')->entity
        ->getFileUri());

      // Check if this style is considered small.
      $overridden_image_style = Drupal::getContainer()
        ->get('social_group.hero_image')
        ->getGroupHeroImageStyle();
      if ($overridden_image_style !== $original_image_style) {
        $variables['group_hero_styled_image_url'] = ImageStyle::load($overridden_image_style)
          ->buildUrl($group
          ->get('field_group_image')->entity
          ->getFileUri());
      }
    }
  }

  // This should be determined regardless if there's an image or not.
  if (Drupal::getContainer()
    ->get('social_group.hero_image')
    ->isSmall()) {
    $variables['group_hero_small'] = TRUE;
  }

  // Add group edit url for management.
  if ($group instanceof Group) {

    // Get the current route name to check if
    // the user is on the edit or delete page.
    $route = \Drupal::routeMatch()
      ->getRouteName();
    if (!in_array($route, [
      'entity.group.edit_form',
      'entity.group.delete_form',
    ])) {
      if ($group
        ->access('update', $account)) {
        $variables['group_edit_url'] = $group
          ->toUrl('edit-form')
          ->toString();
        $variables['#cache']['contexts'][] = 'route.name';
      }
    }
  }

  // Count number of group members.
  $group_members_count = \Drupal::service('social_group.group_members_count');
  $variables['group_members'] = $group_members_count
    ->getGroupMemberCount($group);
}

/**
 * Implements hook_preprocess_HOOK().
 */
function social_group_preprocess_group__hero(array &$variables) {

  /** @var \Drupal\group\Entity\GroupInterface $group */
  $group = $variables['group'];
  $account = \Drupal::currentUser();
  if ($group
    ->bundle() == 'public_group' && $account
    ->isAnonymous()) {
    $variables['group_operations_url'] = Url::fromRoute('user.register', [], [
      'query' => [
        'destination' => Url::fromRoute('entity.group.join', [
          'group' => $group
            ->id(),
        ])
          ->toString(),
      ],
    ])
      ->toString();
  }
  $group_settings_help = [
    '#theme' => 'group_settings_help',
    '#group_type' => $group
      ->getGroupType()
      ->label(),
    '#join_method' => _social_group_get_join_methods($group),
    '#allowed_visibility' => _social_group_get_allowed_visibility($group),
  ];
  $variables['group_settings_help'] = \Drupal::service('renderer')
    ->renderPlain($group_settings_help);
}

/**
 * Get the join methods of a group.
 *
 * @param \Drupal\group\Entity\GroupInterface $group
 *   the Group interface.
 *
 * @return array
 *   Returns join methods of a group.
 */
function _social_group_get_join_methods(GroupInterface $group) {
  $group_type = $group
    ->getGroupType();
  $group_type_id = $group_type
    ->id();
  $join_methods = [];

  // Get join method based on group type. TODO get it programmatically.
  switch ($group_type_id) {
    case 'secret_group':
    case 'closed_group':
      $join_methods = [
        'added' => t('Be added by group managers'),
      ];
      break;
    case 'public_group':
    case 'open_group':
      $join_methods = [
        'direct' => t('Free to join'),
        'added' => t('Be added by group managers'),
      ];
      break;
    case 'flexible_group':

      // Try to retrieve join methods from Group directly.
      $allowed_options = $group
        ->get('field_group_allowed_join_method')
        ->getValue();
      foreach ($allowed_options as $option) {
        $value = $option['value'];
        if ($value === 'direct') {
          $join_methods['direct'] = t('Free to join');
        }
        if ($value === 'added') {
          $join_methods['added'] = t('Be added by group managers');
        }
      }
      break;
  }
  return $join_methods;
}

/**
 * Get the allowed visibility of a group.
 *
 * @param Drupal\group\Entity\GroupInterface $group
 *   the Group interface.
 *
 * @return array
 *   Returns allowed visibility of a group.
 */
function _social_group_get_allowed_visibility(GroupInterface $group) {
  $group_type = $group
    ->getGroupType();
  $group_type_id = $group_type
    ->id();
  $allowed_visibility = [];

  // Get allowed visibility based on group type. TODO get it programmatically.
  switch ($group_type_id) {
    case 'secret_group':
    case 'closed_group':
      $allowed_visibility = [
        'group' => t('Group members'),
      ];
      break;
    case 'open_group':
      $allowed_visibility = [
        'community' => t('Community'),
      ];
      break;
    case 'public_group':
      $allowed_visibility = [
        'public' => t('Public'),
      ];
      break;
    case 'flexible_group':

      // Try to retrieve allowed visibility from Group directly.
      $allowed_options = $group
        ->get('field_group_allowed_visibility')
        ->getValue();
      foreach ($allowed_options as $option) {
        $value = $option['value'];
        if ($value === 'public') {
          $allowed_visibility['public'] = t('Public');
        }
        if ($value === 'community') {
          $allowed_visibility['community'] = t('Community');
        }
        if ($value === 'group') {
          $allowed_visibility['group'] = t('Group members');
        }
      }
      break;
  }
  return $allowed_visibility;
}

/**
 * Implements hook_entity_insert().
 *
 * On a new group insert, from the type
 * open_group or closed_group the Owner gets the group manager role by default.
 */
function social_group_group_insert(GroupInterface $group) {

  // @Todo remove this when https://www.drupal.org/node/2702743 lands and make
  // sure the settings will be implemented accordingly.
  if ($group
    ->getGroupType()
    ->id() === 'open_group' || $group
    ->getGroupType()
    ->id() === 'closed_group' || $group
    ->getGroupType()
    ->id() === 'flexible_group') {

    // Get the group owner.
    $account = $group
      ->getOwner();

    // Get membership.
    $content = $group
      ->getMember($account)
      ->getGroupContent();

    // Delete the initial created membership.
    $content
      ->delete();
    $grant_group_admin = FALSE;

    // If the user has this permission inside a group.
    if ($group
      ->hasPermission('manage all groups', $account)) {

      // Then we grant this user de Group Admin role.
      $grant_group_admin = TRUE;
    }

    // When a CM+ creates a group, he is given the group_manager role
    // alongside the group_admin role to keep the full control over the group.
    if ($grant_group_admin) {

      // Delete the initial created membership.
      $content
        ->delete();
      $plugin = $group
        ->getGroupType()
        ->getContentPlugin('group_membership');
      $values = [
        'group_roles' => [
          $group
            ->bundle() . '-group_admin',
          $group
            ->bundle() . '-group_manager',
        ],
      ];
      $group_content = GroupContent::create([
        'type' => $plugin
          ->getContentTypeConfigId(),
        'gid' => $group
          ->id(),
        'entity_id' => $group
          ->getOwnerId(),
      ] + $values);
      $group_content
        ->save();
    }
    else {

      // Create a new membership.
      $plugin = $group
        ->getGroupType()
        ->getContentPlugin('group_membership');
      $values = [
        'group_roles' => [
          $group
            ->bundle() . '-group_manager',
        ],
      ];
      $group_content = GroupContent::create([
        'type' => $plugin
          ->getContentTypeConfigId(),
        'gid' => $group
          ->id(),
        'entity_id' => $group
          ->getOwnerId(),
      ] + $values);
      $group_content
        ->save();
    }
  }
}

/**
 * Implements hook_entity_view_alter().
 */
function social_group_entity_view_alter(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display) {

  // For group entities.
  if ($entity instanceof Group) {

    // Add cache contexts for some view modes.
    switch ($build['#view_mode']) {
      case 'hero':

        // Add cache contexts for group type & permissions.
        // We need join / leave for the CTA and also update permissions
        // for the button to edit a group.
        $build['#cache']['contexts'][] = 'group.type';
        $build['#cache']['contexts'][] = 'group_membership.roles.permissions';
        $build['#cache']['contexts'][] = 'group_membership.audience';
        $build['#cache']['tags'][] = 'group_block:' . $entity
          ->id();
        break;
      case 'teaser':
        $build['#cache']['contexts'][] = 'user';
        break;
    }
  }
}

/**
 * Implements hook_form_alter().
 */
function social_group_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  $social_group_types = [
    'open_group',
    'closed_group',
    'public_group',
  ];
  \Drupal::moduleHandler()
    ->alter('social_group_types', $social_group_types);
  $join_forms = [];
  $leave_forms = [];
  $membership_add_forms = [];
  $membership_edit_forms = [];
  $membership_delete_forms = [];
  $group_forms = [];
  foreach ($social_group_types as $social_group_type) {
    $join_forms[] = "group_content_{$social_group_type}-group_membership_group-join_form";
    $leave_forms[] = "group_content_{$social_group_type}-group_membership_group-leave_form";
    $membership_add_forms[] = "group_content_{$social_group_type}-group_membership_add_form";
    $membership_edit_forms[] = "group_content_{$social_group_type}-group_membership_edit_form";
    $membership_delete_forms[] = "group_content_{$social_group_type}-group_membership_delete_form";
    $group_forms['edit'][] = "group_{$social_group_type}_edit_form";
    $group_forms['add'][] = "group_{$social_group_type}_add_form";
    $group_forms['delete'][] = "group_{$social_group_type}_delete_form";
  }
  $action_forms = array_merge($join_forms, $leave_forms, $membership_delete_forms, $membership_add_forms);
  $membership_forms = array_merge($membership_add_forms, $membership_edit_forms);

  // Perform alterations on joining / leaving groups.
  if (in_array($form_id, $membership_delete_forms)) {
    $form['actions']['submit']['#submit'][] = '_social_membership_delete_form_submit';
    list($name, $group) = _social_group_get_group_labels();

    // Give a better title, description and submit button value.
    $form['#title'] = t('Remove %name from %group', [
      '%name' => $name,
      '%group' => $group,
    ]);
    $form['actions']['submit']['#value'] = t('Remove');
    $form['description']['#markup'] = t('Are you sure you want to remove %name from %group?', [
      '%name' => $name,
      '%group' => $group,
    ]);
  }

  // Set some helpful text on the group join form now it's there.
  if (in_array($form_id, $join_forms)) {
    $form['help'] = [
      '#type' => 'item',
      '#markup' => t('By submitting this form you will become a member of the group. Please fill out any available fields to complete your membership information.'),
    ];
    $form['path']['#type'] = 'hidden';
  }

  // Perform alterations on joining / leaving groups.
  if (in_array($form_id, $action_forms)) {

    // Add cancel option on join and leave form.
    $form['actions']['cancel'] = [
      '#type' => 'submit',
      '#value' => t('Cancel'),
      '#submit' => [
        '_social_group_cancel_join_leave_form',
      ],
      '#limit_validation_errors' => [],
    ];
    $form['actions']['submit']['#submit'][] = '_social_group_action_form_submit';
  }
  if (in_array($form_id, $membership_forms) && Drupal::currentUser()
    ->id() != 1) {

    // Change titles on membership forms.
    $form['entity_id']['widget'][0]['target_id']['#title'] = t('Select a member');
    $form['group_roles']['widget']['#title'] = t('Group roles');

    // Remove the 'group_admin' role in a generic way
    // for all (future) group types.
    foreach ($form['group_roles']['widget']['#options'] as $key => $value) {

      // Hide the submission for the Group Admin role.
      if (strpos($key, 'group_admin') != FALSE) {
        unset($form['group_roles']['widget']['#options'][$key]);
      }
    }
    $form['path']['#type'] = 'hidden';
  }
  if (in_array($form_id, $membership_add_forms)) {
    $form['entity_id']['widget'][0]['target_id']['#title'] = t('Select members to add');
    $form['entity_id']['widget'][0]['target_id']['#description'] = t('To add multiple members, separate each member with a comma ( , ).');
    $form['entity_id']['widget'][0]['target_id']['#type'] = 'social_group_entity_autocomplete';
  }

  // Check if form is group content create form.
  if (isset($form['#entity_type']) && $form['#entity_type'] === 'node') {
    $group = _social_group_get_current_group();
    if (!empty($group)) {

      // Add custom submit handler just for redirect purposes.
      // We don't want to override the form::save in group.
      $form['actions']['submit']['#submit'][] = '_social_group_node_form_submit';
    }
  }
  if (in_array($form_id, $group_forms['add']) || in_array($form_id, $group_forms['edit']) || in_array($form_id, $group_forms['delete'])) {

    // Add custom submit handler just for redirect purposes.
    $form['actions']['submit']['#submit'][] = '_social_group_edit_submit_redirect';
    if (in_array($form_id, $group_forms['add']) || in_array($form_id, $group_forms['edit'])) {
      $form['path']['#type'] = 'hidden';
      $form['actions']['submit']['#value'] = t('Save');

      // Hide default title from Address field.
      if (isset($form['field_group_address'])) {
        $form['field_group_address']['widget'][0]['#title'] = '';
      }
    }
    if (in_array($form_id, $group_forms['edit'])) {
      $social_group_form = SocialGroupAddForm::create(\Drupal::getContainer());
      $group_type_element = $social_group_form
        ->getGroupTypeElement();

      // Get the current group.
      $group = _social_group_get_current_group();

      // Set the default value in the form.
      $group_type_element['#default_value'] = $group
        ->getGroupType()
        ->id();

      // If user doesn't have permission to change group types disable it.
      // Or if group types can't be edited due to visibility issues.
      if (!social_group_group_type_permission_check()) {
        $group_type_element['#disabled'] = TRUE;
      }
      else {
        $group_type_element['#prefix'] = '<div id="group-type-result"></div>';
        $group_type_element['#ajax'] = [
          'callback' => '_social_group_inform_group_type_selection',
          'effect' => 'fade',
          'event' => 'change',
        ];
      }
      $form['group_type'] = $group_type_element;
      $form['#group_children']['group_type'] = 'group_settings';

      // Disable all group types that can't be edited. Because they don't have
      // a visibility.
      foreach (array_keys($form['group_type']['#options']) as $type) {
        if (\Drupal::service('social_group.helper_service')
          ->getDefaultGroupVisibility($type) === NULL) {
          $form['group_type'][$type] = [
            '#disabled' => TRUE,
          ];
        }
      }
      $form['actions']['submit']['#submit'][] = '_social_group_type_edit_submit';
    }
    if (in_array($form_id, $group_forms['delete'])) {

      // Add custom submit handler to delete all content of the group.
      $group = _social_group_get_current_group();
      $form['description']['#markup'] = t('Are you sure you want to delete your group "@group" along with all of the posts, events and topics inside this group? This action cannot be undone.', [
        '@group' => $group
          ->label(),
      ]);
      $form['actions']['cancel'] = [
        '#type' => 'submit',
        '#value' => t('Cancel'),
        '#submit' => [
          '_social_group_cancel_join_leave_form',
        ],
        '#limit_validation_errors' => [],
      ];
      array_unshift($form['actions']['submit']['#submit'], '_social_group_delete_group');
    }
  }
}

/**
 * Function that gives information to a CM+ when they edit a Group Type.
 *
 * @return \Drupal\Core\Ajax\AjaxResponse
 *   An ajax command containing the informative text.
 */
function _social_group_inform_group_type_selection() {
  $text = t('Please note that changing the group type will also change the
  visibility of the group content and the way users can join the group.');
  drupal_set_message($text, 'info');
  $alert = [
    '#type' => 'status_messages',
  ];
  $ajax_response = new AjaxResponse();
  $ajax_response
    ->addCommand(new HtmlCommand('#group-type-result', $alert));
  return $ajax_response;
}

/**
 * Form submit for removing members from a group so we can clear caches.
 *
 * @throws \Drupal\Core\Entity\EntityStorageException
 */
function _social_group_type_edit_submit($form, FormStateInterface $form_state) {

  // Check if the group_type changed.
  // Can be empty for flexible groups.
  $group = _social_group_get_current_group();
  if (!empty($form['group_type'])) {
    $default_type = $form['group_type']['#default_value'];
    $new_type = $form_state
      ->getValue('group_type');

    // Update the Group entity and all it's content when the type changed.
    if ($new_type !== $default_type) {

      // Update the default visibility of all the content.
      GroupContentVisibilityUpdate::batchUpdateGroupContentVisibility($group, $new_type);
    }
  }

  // Make sure we clear cache tags accordingly.
  $cache_tags = _social_group_cache_tags($group);
  foreach ($cache_tags as $cache_tag) {
    Cache::invalidateTags([
      $cache_tag,
    ]);
  }
}

/**
 * Form submit for removing members from a group so we can clear caches.
 */
function _social_membership_delete_form_submit($form, FormStateInterface $form_state) {
  $group = _social_group_get_current_group();
  if (is_object($group)) {

    // Invalidate cache tags.
    $cache_tags = _social_group_cache_tags($group);
    foreach ($cache_tags as $cache_tag) {
      Cache::invalidateTags([
        $cache_tag,
      ]);
    }
  }
}

/**
 * Form submit for group join / leave form.
 */
function _social_group_action_form_submit($form, FormStateInterface $form_state) {
  $group = _social_group_get_current_group();
  if (is_object($group)) {

    // Invalidate cache tags.
    $cache_tags = _social_group_cache_tags($group);
    foreach ($cache_tags as $cache_tag) {
      Cache::invalidateTags([
        $cache_tag,
      ]);
    }

    // Get form that was submitted.
    $complete_form = $form_state
      ->getCompleteForm();

    // For multiple Group Members that are valid we add multiple entities.
    if ($complete_form['#form_id'] == 'group_content_' . $group
      ->bundle() . '-group_membership_add_form') {

      // We passed the validation.
      // Let's create the Group Content for all our members just like
      // Group does it for creating a Group and adding its creator as member.
      $values = [
        'group_roles' => $form_state
          ->getValue('group_roles'),
      ];
      foreach ($form_state
        ->getValue('entity_id_new') as $key => $uid) {
        $group
          ->addMember(User::load($uid['target_id']), $values);
      }
    }
    if (in_array($complete_form['#form_id'], [
      'group_content_' . $group
        ->bundle() . '-group_membership_group-join_form',
      'group_content_' . $group
        ->bundle() . '-group_membership_add_form',
    ])) {

      // Set redirect to group home page.
      $form_state
        ->setRedirect('entity.group.canonical', [
        'group' => $group
          ->id(),
        [],
      ]);
    }
    else {

      // Set redirect to the Group overview page
      // when a user saves their profile.
      $route = _social_group_get_overview_route($group);
      $form_state
        ->setRedirect($route['name'], $route['parameters']);
    }
  }
}

/**
 * This function checks if the user should get the admin role within a group.
 *
 * Also check if the user has already a membership.
 * If so, update it with the new role.
 */
function _social_group_grant_admin_role($uid, $gid) {
  $account = User::load($uid);
  $group = Group::load($gid);

  // Must be a valid account AND a valid group.
  if ($account instanceof User && $group instanceof Group) {

    // Must have manage all groups permission.
    // Otherwise normal flow will be fine.
    if (!$account
      ->hasPermission('manage all groups')) {
      return;
    }

    // Check if the user is already a member in the group
    // (could be in update mode here).

    /** @var \Drupal\group\GroupMembership $membership */
    $admin_role = $group
      ->bundle() . '-group_admin';
    $membership = $group
      ->getMember($account);

    // Check what roles are there.
    $roles = [];
    foreach ($membership
      ->getGroupContent()->group_roles as $group_role_ref) {
      $roles[] = $group_role_ref->target_id;
    }

    // No admin? Add it.
    if (!in_array($admin_role, $roles)) {
      $membership
        ->getGroupContent()->group_roles[] = [
        'target_id' => $admin_role,
      ];
      $membership
        ->getGroupContent()
        ->save();
    }
  }
}

/**
 * When creating a new group membership.
 *
 * @param \Drupal\group\Entity\GroupContentInterface $group_content
 *   The group content.
 */
function social_group_group_content_insert(GroupContentInterface $group_content) {
  if ($group_content
    ->getEntity()
    ->getEntityTypeId() == 'user') {
    if ($group_content
      ->bundle() == $group_content
      ->getGroup()
      ->bundle() . '-group_membership') {
      _social_group_grant_admin_role($group_content
        ->getEntity()
        ->id(), $group_content
        ->getGroup()
        ->id());
    }
  }
}

/**
 * When updating a group membership.
 *
 * @param \Drupal\group\Entity\GroupContentInterface $group_content
 *   The group content.
 */
function social_group_group_content_update(GroupContentInterface $group_content) {
  if ($group_content
    ->getEntity()
    ->getEntityTypeId() == 'user') {
    if ($group_content
      ->bundle() == $group_content
      ->getGroup()
      ->bundle() . '-group_membership') {
      _social_group_grant_admin_role($group_content
        ->getEntity()
        ->id(), $group_content
        ->getGroup()
        ->id());
    }
  }
}

/**
 * Form submit for membership edit form.
 *
 * @param array $form
 *   Membership edit form.
 * @param \Drupal\Core\Form\FormStateInterface $form_state
 *   Form state interface.
 */
function _social_group_membership_edit_form_submit(array $form, FormStateInterface $form_state) {
  $group = _social_group_get_current_group();
  if (is_object($group)) {

    // Set redirect to the Group overview page when a user saves their profile.
    $form_state
      ->setRedirect('entity.group_content.collection', [
      'group' => $group
        ->id(),
      [],
    ]);
  }
}

/**
 * Form cancel for join and leave form.
 *
 * @param array $form
 *   Join and leave form.
 * @param \Drupal\Core\Form\FormStateInterface $form_state
 *   Form state interface.
 */
function _social_group_cancel_join_leave_form(array $form, FormStateInterface $form_state) {

  // Get form that was submitted.
  $group = _social_group_get_current_group();
  if (is_object($group)) {

    // Set redirect only for cancel buttons. Just go back to the group page.
    $form_state
      ->setRedirect('entity.group.canonical', [
      'group' => $group
        ->id(),
      [],
    ]);
  }
}

/**
 * Form submit for group content create form.
 *
 * @param array $form
 *   Group node form.
 * @param \Drupal\Core\Form\FormStateInterface $form_state
 *   Form state interface.
 */
function _social_group_node_form_submit(array $form, FormStateInterface $form_state) {
  $nid = $form_state
    ->getValue('nid');

  // Set redirect.
  $form_state
    ->setRedirect('entity.node.canonical', [
    'node' => $nid,
  ]);
}

/**
 * Alter the visibility field within groups.
 *
 * Implements hook_field_widget_form_alter().
 */
function social_group_field_widget_form_alter(&$element, FormStateInterface $form_state, $context) {

  /** @var \Drupal\Core\Field\FieldDefinitionInterface $field_definition */
  $field_definition = $context['items']
    ->getFieldDefinition();

  // Unset the public options on visibility field.
  if ($field_definition
    ->getType() == 'entity_access_field') {
    $default_value = $context['items']
      ->getValue();
    if (isset($default_value[0]['value'])) {
      $element['#default_value'] = $default_value[0]['value'];
    }
    else {
      $default_visibility = \Drupal::configFactory()
        ->get('entity_access_by_field.settings')
        ->get('default_visibility');
      $element['#default_value'] = $default_visibility;
    }
    $entity = $form_state
      ->getFormObject()
      ->getEntity();
    $current_group = _social_group_get_current_group();
    if (!empty($current_group)) {
      $group_type = $current_group
        ->getGroupType();
      $group_type_id = $group_type
        ->id();
      if (!$entity
        ->id()) {
        $element['#default_value'] = \Drupal::service('social_group.helper_service')
          ->getDefaultGroupVisibility($group_type_id);
      }
    }
    else {
      $group_type_id = NULL;

      // This is not a group so lets disable this visibility option.
      $element['group']['#disabled'] = TRUE;
    }
    $visibility_options = social_group_get_allowed_visibility_options_per_group_type($group_type_id, NULL, $entity, $current_group);
    foreach ($visibility_options as $visibility => $allowed) {
      $element[$visibility]['#disabled'] = !$allowed;
    }
  }
}

/**
 * Implements hook_views_post_render().
 *
 * Alter "Group Members" views. Replace user IDs with profile teasers.
 */
function social_group_views_post_render(ViewExecutable $view, &$output, CachePluginBase $cache) {
  if ($view
    ->id() == 'group_members') {
    if (!empty($output['#rows'][0]['#rows'])) {
      foreach ($output['#rows'][0]['#rows'] as $key => $row) {

        // Get Group membership content entity.
        $group_content = $row['#group_content'];

        // Get Profile.
        $user_profile = _social_group_get_member_profile($group_content);
        if ($user_profile) {

          // Use teaser for page and small_teaser for block.
          $view_mode = $view->current_display === 'block_newest_members' ? 'small_teaser' : 'teaser';

          // Replace output with profile teaser.
          $output['#rows'][0]['#rows'][$key] = \Drupal::entityTypeManager()
            ->getViewBuilder('profile')
            ->view($user_profile, $view_mode);
        }
        else {

          // Remove output if user don't have profile (admin).
          unset($output['#rows'][0]['#rows'][$key]);
        }
      }
    }
  }
}

/**
 * Implements hook_menu_local_tasks_alter().
 */
function social_group_menu_local_tasks_alter(&$data, $route_name) {

  // Rename Group "Related Entities" tab.
  if (isset($data['tabs'][0]['group.content']['#link'])) {
    $data['tabs'][0]['group.content']['#link']['title'] = t('Manage members');
    $data['tabs'][0]['group.content']['#weight'] = 70;
  }

  // Remove the default View tab.
  if (isset($data['tabs'][0]['group.view'])) {
    unset($data['tabs'][0]['group.view']);
  }

  // Remove Edit tab. Edit will always go through Floating Edit Button.
  if (isset($data['tabs'][0]['group.edit_form'])) {
    unset($data['tabs'][0]['group.edit_form']);
  }
  $user = \Drupal::currentUser();

  // Get the Group object from the route.
  $group = Drupal::routeMatch()
    ->getParameter('group');
  if ($group instanceof GroupInterface) {

    /** @var \Drupal\group\Entity\GroupTypeInterface $group_type */
    $group_type = $group
      ->getGroupType()
      ->id();

    // Check if it's a closed group.
    if ($group_type === 'closed_group') {

      // And if the user is not user 1.
      if ($user
        ->id() !== 1) {
        if ($user
          ->hasPermission('manage all groups')) {
          return;
        }

        // If the user is not an member of this group.
        if (!$group
          ->getMember($user)) {

          // Disable these local tasks.
          $data['tabs'][0]['group.view'] = [];
          if (!$group
            ->hasPermission('administer members', $user)) {
            $data['tabs'][0]['group.content'] = [];
          }
          $data['tabs'][0]['social_group.events'] = [];
          $data['tabs'][0]['social_group.topics'] = [];
        }
      }
    }
  }
}

/**
 * Implements hook_local_tasks_alter().
 */
function social_group_local_tasks_alter(&$local_tasks) {

  // Remove local delete task from group page.
  unset($local_tasks['group.delete_form']);
  $local_tasks['group.content']['route_name'] = 'view.group_manage_members.page_group_manage_members';
  $local_tasks['group.content']['weight'] = 70;
}

/**
 * Implements hook_menu_local_actions_alter().
 */
function social_group_menu_local_actions_alter(&$local_actions) {

  // Remove "Add Relationship" button.
  if (isset($local_actions['group_content.add_page'])) {
    unset($local_actions['group_content.add_page']);
  }

  // Remove Create new entity in group.
  if (isset($local_actions['group_content.create_page'])) {
    unset($local_actions['group_content.create_page']);
  }
}

/**
 * Implements hook_entity_type_alter().
 */
function social_group_entity_type_alter(array &$entity_types) {

  /* @var $entity_types \Drupal\Core\Entity\EntityTypeInterface[] */
  $entity_types['group']
    ->setListBuilderClass('Drupal\\social_group\\Controller\\SocialGroupListBuilder');
}

/**
 * Return user profile by given group membership content.
 *
 * @param \Drupal\group\Entity\GroupContent $group_content
 *   Group content entity.
 *
 * @return \Drupal\profile\Entity\Profile
 *   Returns the Profile entity for the member.
 */
function _social_group_get_member_profile(GroupContent $group_content) {
  $user_profile = NULL;

  // Get User entity.
  $user_entity = $group_content
    ->getEntity();
  if (!empty($user_entity)) {

    // Get Profile storage.
    $storage = \Drupal::entityTypeManager()
      ->getStorage('profile');
    if (!empty($storage)) {

      // Get Profile entity.
      $user_profile = $storage
        ->loadByUser($user_entity, 'profile');
    }
  }
  return $user_profile;
}

/**
 * Get current Group entity from the route.
 *
 * @return \Drupal\group\Entity\GroupInterface
 *   Returns the group object.
 */
function _social_group_get_current_group($node = NULL) {
  $cache =& drupal_static(__FUNCTION__, []);

  // For the same $node input, within the same request the return is always
  // the same.
  $nid = NULL;
  if (is_null($node)) {
    $nid = -1;
  }
  elseif ($node instanceof NodeInterface) {
    $nid = $node
      ->id();
  }

  // If we have a cache key and it has a value, we're done early.
  if (!is_null($nid) && isset($cache[$nid])) {

    // Translate FALSE (so isset works) back to NULL.
    return $cache[$nid] ?: NULL;
  }
  $group = \Drupal::routeMatch()
    ->getParameter('group');
  if (!is_object($group) && !is_null($group)) {
    $group = \Drupal::entityTypeManager()
      ->getStorage('group')
      ->load($group);
  }
  else {
    $node = is_object($node) ? $node : \Drupal::routeMatch()
      ->getParameter('node');
    if (is_object($node) && !is_null($node)) {
      $node_entity = [
        'target_type' => 'node',
        'target_id' => $node
          ->id(),
      ];
      $gid_from_entity = \Drupal::service('social_group.helper_service')
        ->getGroupFromEntity($node_entity);
      if ($gid_from_entity !== NULL) {
        $group = \Drupal::entityTypeManager()
          ->getStorage('group')
          ->load($gid_from_entity);
      }
    }
  }

  // If we have a cache key we store the value.
  if (!is_null($nid)) {

    // Translate NULL to FALSE so that isset works.
    $cache[$nid] = isset($group) ? $group : FALSE;
  }
  return $group;
}

/**
 * Get group cache tags.
 *
 * @param \Drupal\group\Entity\GroupInterface $group
 *   The GroupInterface.
 *
 * @return array
 *   An array of cache tags related to groups.
 */
function _social_group_cache_tags(GroupInterface $group) {

  // Group views.
  $tags = [
    'group_list',
    'group_content_list',
    'group_view',
    'group_content_view',
  ];

  // Add cache tags that are based on id.
  $tags[] = 'group_hero:' . $group
    ->id();

  // Add cache tags for the blocks.
  $tags[] = 'group_block:' . $group
    ->id();
  $current_user = \Drupal::currentUser();
  if ($group_membership = $group
    ->getMember($current_user)) {
    $group_content = $group_membership
      ->getGroupContent();
    $tags[] = 'group_content:' . $group_content
      ->id();
  }
  return $tags;
}

/**
 * Implements hook_block_view_BASE_BLOCK_ID_alter().
 *
 * Add Group cache context for system "Tabs" block.
 */
function social_group_block_view_local_tasks_block_alter(array &$build, BlockPluginInterface $block) {
  $build['#cache']['contexts'][] = 'group_membership.roles.permissions';
}

/**
 * Implements hook_preprocess_HOOK().
 */
function social_group_preprocess_profile(&$variables) {
  $group = _social_group_get_current_group();
  if ($group instanceof Group && $variables['elements']['#view_mode'] == 'teaser') {
    $account = $variables['elements']['#profile']
      ->get('uid')->entity;
    if (!($member = $group
      ->getMember($account))) {
      return;
    }
    $roles = $member
      ->getRoles();
    if (isset($roles[$group
      ->bundle() . '-group_manager'])) {
      $variables['badges'] = [
        [
          'label' => t('Group manager'),
          'classes' => [
            'badge-secondary teaser__badge',
          ],
        ],
      ];
    }
  }
}

/**
 * Implements hook_preprocess_HOOK().
 */
function social_group_preprocess_views_view(&$variables) {

  /** @var \Drupal\views\ViewExecutable $view */
  $view =& $variables['view'];
  if ($view
    ->id() === 'group_manage_members') {
    $entity = \Drupal::entityTypeManager()
      ->getStorage('block')
      ->load('socialblue_local_actions');
    $variables['header']['actions'] = \Drupal::entityTypeManager()
      ->getViewBuilder('block')
      ->view($entity);
  }
}

/**
 * Implements hook_block_access().
 *
 * Check if the user is viewing a closed_group, check if the user is a member.
 * If the user is not a member, the user has no access to view this block.
 */
function social_group_block_access(Block $block, $operation, AccountInterface $account) {
  $block_id = $block
    ->getPluginId();

  // This is a list of the blocks that this function cares about, if we're being
  // called for a different block we exit early.
  $managed_blocks = [
    'views_exposed_filter_block:newest_groups-page_all_groups',
    'views_block:groups-block_user_groups',
    'views_block:upcoming_events-upcoming_events_group',
    'views_block:latest_topics-group_topics_block',
  ];
  if (!in_array($block_id, $managed_blocks)) {
    return AccessResult::neutral();
  }
  if ($block
    ->getPluginId() == 'views_exposed_filter_block:newest_groups-page_all_groups' && $operation == 'view' && $account
    ->isAnonymous()) {
    $group_types = 0;

    /** @var \Drupal\group\Entity\GroupTypeInterface $group_type */
    foreach (GroupType::loadMultiple() as $group_type) {
      $group_types += (int) $group_type
        ->getAnonymousRole()
        ->hasPermission('view group');
    }
    if ($group_types < 2) {
      return AccessResult::forbidden();
    }
  }
  $group = _social_group_get_current_group();
  $user = Drupal::currentUser();

  // Check if there is a group set and if its an closed group.
  if ($group && $group
    ->getGroupType()
    ->id() == 'closed_group' && $account
    ->id() != 1) {
    if ($account
      ->hasPermission('manage all groups')) {
      return AccessResult::neutral();
    }
    elseif (!$group
      ->getMember($user)) {

      // If it is closed and the current user is not an member of this group,
      // then he is not allowed to see these blocks.
      $forbidden_blocks = [
        'views_block:upcoming_events-upcoming_events_group',
        'views_block:latest_topics-group_topics_block',
      ];
      foreach ($forbidden_blocks as $forbidden_block) {
        if ($operation == 'view' && $block
          ->getPluginId() == $forbidden_block) {
          return AccessResult::forbidden();
        }
      }
    }
  }

  // Check if we're alloweed to view the joined groups block.
  if ($operation === 'view' && $block
    ->getPluginId() === 'views_block:groups-block_user_groups') {

    // Here we're going to assume by default access is not granted.
    $groupController = SocialGroupController::create(\Drupal::getContainer());
    $access = $groupController
      ->myGroupAccess($account);

    // If the 'myGroupAccess' returns 'AccessResultNeutral', we have to assume
    // that access must be denied.
    if ($access instanceof AccessResultNeutral) {

      // Return forbidden, since access was not explicitly granted.
      return AccessResult::forbidden();
    }
    return $access;
  }
  return AccessResult::neutral();
}

/**
 * Delete the group and all of its content.
 */
function _social_group_delete_group() {

  // Get the group.
  if ($group = _social_group_get_current_group()) {
    $group_content_types = GroupContentType::loadByEntityTypeId('node');
    $group_content_types = array_keys($group_content_types);

    // Get all the node's related to the current group.
    $query = \Drupal::database()
      ->select('group_content_field_data', 'gcfd');
    $query
      ->addField('gcfd', 'entity_id');
    $query
      ->condition('gcfd.gid', $group
      ->id());
    $query
      ->condition('gcfd.type', $group_content_types, 'IN');
    $query
      ->execute()
      ->fetchAll();
    $entity_ids = $query
      ->execute()
      ->fetchAllAssoc('entity_id');

    // Store all the node ids.
    $nids = array_keys($entity_ids);

    // Get all the posts from this group.
    $query = \Drupal::database()
      ->select('post__field_recipient_group', 'pfrg');
    $query
      ->addField('pfrg', 'entity_id');
    $query
      ->condition('pfrg.field_recipient_group_target_id', $group
      ->id());
    $query
      ->execute()
      ->fetchAll();
    $post_ids = $query
      ->execute()
      ->fetchAllAssoc('entity_id');

    // Store all the post entity ids.
    $posts = array_keys($post_ids);

    // Pass the $nids and $posts as 2 parameters in the operations.
    // See /social_group/src/Controller/DeleteGroup.php for further process.
    $batch = [
      'title' => t('Deleting the group and all the content within the group...'),
      'init_message' => t("Preparing to delete the group and all it\\'s topic\\'s, event\\'s and post\\'s..."),
      'operations' => [
        [
          '\\Drupal\\social_group\\Controller\\DeleteGroup::deleteGroupAndContent',
          [
            $nids,
            $posts,
          ],
        ],
      ],
      'finished' => '\\Drupal\\social_group\\Controller\\DeleteGroup::deleteGroupAndContentFinishedCallback',
    ];
    batch_set($batch);
  }
}

/**
 * Get all open groups.
 */
function social_group_get_all_open_groups() {
  $query = Drupal::service('entity.query')
    ->get('group')
    ->condition('type', 'open_group');
  return $query
    ->execute();
}

/**
 * Get all open groups.
 */
function social_group_get_all_groups() {
  $query = Drupal::service('entity.query')
    ->get('group')
    ->sort('type');
  return $query
    ->execute();
}

/**
 * Get all group memberships for a certain user.
 *
 * @param int $uid
 *   The UID for which we fetch the groups he is member of.
 *
 * @return array
 *   List of group IDs the user is member of.
 *
 * @deprecated in Open Social 4.2 and will be removed in one of the next major
 *   updates.
 *   This function is moved to the service social_group.helper_service, use
 *   getAllGroupsForUser() instead.
 *
 * @see https://www.drupal.org/node/3026220
 */
function social_group_get_all_group_members($uid) {
  @trigger_error('social_group_get_all_group_members() is deprecated in Open Social 4.2 and will be removed in
  one of the next major updates. This function is moved to the service social_group.helper_service, use
  getAllGroupsForUser() instead. See https://www.drupal.org/node/3026220.', E_USER_DEPRECATED);
  $groups =& drupal_static(__FUNCTION__);

  // Get the memberships for the user if they aren't known yet.
  if (!isset($groups[$uid])) {
    $group_content_types = GroupContentType::loadByEntityTypeId('user');
    $group_content_types = array_keys($group_content_types);
    $query = \Drupal::database()
      ->select('group_content_field_data', 'gcfd');
    $query
      ->addField('gcfd', 'gid');
    $query
      ->condition('gcfd.entity_id', $uid);
    $query
      ->condition('gcfd.type', $group_content_types, 'IN');
    $query
      ->execute()
      ->fetchAll();
    $group_ids = $query
      ->execute()
      ->fetchAllAssoc('gid');
    $groups[$uid] = array_keys($group_ids);
  }
  return $groups[$uid];
}

/**
 * Implements hook_field_info_alter().
 */
function social_group_field_info_alter(&$info) {
  if (isset($info['groups'])) {
    $info['groups']['default widget'] = 'social_group_selector_widget';
  }
}

/**
 * Implements hook_entity_base_field_info_alter().
 */
function social_group_entity_base_field_info_alter(&$fields, EntityTypeInterface $entity_type) {
  $entity_types = [
    'node',
  ];
  if (in_array($entity_type
    ->id(), $entity_types)) {
    if (isset($fields['groups'])) {

      /* @var \Drupal\Core\Field\BaseFieldDefinition $fields['groups'] */
      $fields['groups']
        ->setLabel(t('Group'));
      $fields['groups']
        ->setDisplayOptions('form', [
        'type' => 'options_select',
        'settings' => [],
        'weight' => 16,
      ]);
      $fields['groups']
        ->setDisplayConfigurable('form', TRUE);
      $fields['groups']
        ->setDescription('');
    }
  }
}

/**
 * Implements hook_form_FORM_ID_alter().
 *
 * Make sure the correct group is saved when group is selected on node form.
 */
function social_group_form_node_form_alter(&$form, FormStateInterface $form_state) {
  if (isset($form['#entity_type']) && $form['#entity_type'] === 'node') {
    if (isset($form['groups'])) {
      $change_fieldgroup_titles = [
        'group_topic_visibility',
        'group_event_visibility',
      ];
      foreach ($change_fieldgroup_titles as $fieldgroup_title) {
        if (isset($form['#fieldgroups'][$fieldgroup_title])) {
          $form['#fieldgroups'][$fieldgroup_title]->label = (string) t('Group and visibility');
        }
      }

      // Lets remove the original submit function in favor of this submit.
      foreach ($form['actions']['submit']['#submit'] as $submit_key => $submit_function) {
        if ($submit_function === 'group_content_entity_submit') {
          unset($form['actions']['submit']['#submit'][$submit_key]);
        }
      }

      /* @var \Drupal\node\Entity\Node $node */
      $node = $form_state
        ->getFormObject()
        ->getEntity();

      // Store if the node is new or not.
      $form['is_new'] = [
        '#type' => 'value',
        '#value' => $node
          ->isNew(),
      ];
      $form['actions']['submit']['#submit'][] = 'social_group_save_group_from_node';
    }
  }
}

/**
 * Form submit to save the group from a node form.
 *
 * @param array $form
 *   Node add or node edit form.
 * @param \Drupal\Core\Form\FormStateInterface $form_state
 *   Form state interface.
 */
function social_group_save_group_from_node(array $form, FormStateInterface $form_state) {

  /* @var \Drupal\node\Entity\Node $node */
  $node = $form_state
    ->getFormObject()
    ->getEntity();

  // Check if the created node is new or updated.
  $is_new = NULL !== $form_state
    ->getValue('is_new') ? $form_state
    ->getValue('is_new') : FALSE;
  $original_groups = [];
  $groups_to_add = [];
  $groups_to_remove = [];
  foreach ($form_state
    ->getValue('groups') as $new_group_key => $new_group) {
    $groups_to_add[$new_group['target_id']] = $new_group['target_id'];
  }

  // The node already exist so lets also change the logic accordingly.
  if ($form['#form_id'] === 'node_' . $node
    ->bundle() . '_edit_form') {
    $original_groups = $form['groups']['widget']['#default_value'];
    foreach ($original_groups as $original_group_key => $original_group) {
      if (!in_array($original_group, $groups_to_add)) {
        $groups_to_remove[$original_group] = $original_group;
      }
      else {
        unset($groups_to_add[$original_group]);
      }
    }
  }

  // Now make sure the relevant GroupContent is removed or added.
  $setGroupsForNodeService = \Drupal::service('social_group.set_groups_for_node_service');
  $setGroupsForNodeService
    ->setGroupsForNode($node, $groups_to_remove, $groups_to_add, $original_groups, $is_new);
}

/**
 * Get the allowed visibility options for a given group type.
 *
 * @param string|null $group_type_id
 *   The group type. Can be NULL to get visibility when it is not in a group.
 * @param \Drupal\Core\Session\AccountInterface|null $account
 *   The account object that may have impact on the visibility options.
 *
 * @return array
 *   An array of visibility options for the given group type.
 */
function social_group_get_allowed_visibility_options_per_group_type($group_type_id, AccountInterface $account = NULL, $entity = NULL, $group = NULL) {

  // Get the logged in user.
  if ($account === NULL) {
    $account = \Drupal::currentUser();
  }
  $visibility_options = [];
  $visibility_options['public'] = FALSE;
  $visibility_options['community'] = TRUE;
  $visibility_options['group'] = FALSE;
  switch ($group_type_id) {
    case 'closed_group':
      $visibility_options['community'] = FALSE;
      $visibility_options['group'] = TRUE;
      break;
    case 'public_group':
      $visibility_options['public'] = TRUE;
      $visibility_options['community'] = FALSE;
      break;
    case 'open_group':
      $visibility_options['public'] = FALSE;
      $visibility_options['community'] = TRUE;
      $visibility_options['group'] = FALSE;
      break;
    case 'secret_group':
      $visibility_options['public'] = FALSE;
      $visibility_options['community'] = FALSE;
      $visibility_options['group'] = TRUE;
      break;
    case 'flexible_group':
      if (empty($group)) {
        $group = _social_group_get_current_group();
      }
      if ($group !== NULL) {
        $visibility_options['public'] = FALSE;
        $visibility_options['community'] = FALSE;
        $visibility_options['group'] = FALSE;

        // Try to retrieve allowed options from Group directly.
        $allowed_options = $group
          ->get('field_group_allowed_visibility')
          ->getValue();
        foreach ($allowed_options as $option) {
          $value = $option['value'];
          $visibility_options[$value] = TRUE;
        }
      }
      break;
    default:
      $config = \Drupal::config('entity_access_by_field.settings');
      $visibility_options['public'] = TRUE;
      if ($config
        ->get('disable_public_visibility') === 1 && !$account
        ->hasPermission('override disabled public visibility')) {

        // This is a new entity.
        if (!$entity) {
          $visibility_options['public'] = FALSE;
        }
        else {

          /** @var \Drupal\node\Entity\Node $entity */
          if ($entity
            ->hasField('field_content_visibility')) {
            $current_visibility = $entity
              ->get('field_content_visibility')
              ->getString();
            if ($current_visibility !== 'public') {
              $visibility_options['public'] = FALSE;
            }
          }
        }
      }
      $visibility_options['community'] = TRUE;
      $visibility_options['group'] = FALSE;
      break;
  }
  \Drupal::moduleHandler()
    ->alter('social_group_allowed_visibilities', $visibility_options, $group_type_id);
  return $visibility_options;
}

/**
 * Implements hook_views_query_alter().
 */
function social_group_views_query_alter(ViewExecutable $view, QueryPluginBase $query) {
  if (\Drupal::currentUser()
    ->isAuthenticated()) {
    return;
  }
  if (empty($view->rowPlugin) || !$view->rowPlugin instanceof EntityRow || $view->rowPlugin
    ->getEntityTypeId() != 'group') {
    return;
  }
  $found = FALSE;
  foreach ($query->where as &$conditions) {
    foreach ($conditions['conditions'] as &$condition) {
      if ($condition['field'] == 'groups_field_data.type') {
        $found = TRUE;
        break 2;
      }
    }
  }
  if (!$found) {
    unset($condition);
    $condition = [
      'field' => 'groups_field_data.type',
      'value' => [],
      'operator' => 'not in',
    ];
  }
  $hiddens = $condition['operator'] == 'not in';

  // Define the variable as array otherwise it can be interpreted as string
  // And breaking the code below.
  $condition['value'] = [];

  /** @var \Drupal\group\Entity\GroupTypeInterface $group_type */
  foreach (GroupType::loadMultiple() as $group_type) {
    $new = !in_array($group_type
      ->id(), $condition['value']);
    $permissions = $group_type
      ->getAnonymousRole()
      ->getPermissions();
    $hidden = !in_array('view group', $permissions);
    if ($new && $hiddens == $hidden) {
      $condition['value'][] = $group_type
        ->id();
    }
  }
  if (!$found) {
    if (isset($conditions)) {
      $conditions['conditions'][] = $condition;
    }
    else {
      $query->where[] = [
        'conditions' => [
          $condition,
        ],
        'type' => 'AND',
      ];
    }
  }
}

/**
 * Implements hook_user_cancel_methods_alter().
 */
function social_group_user_cancel_methods_alter(&$methods) {
  $methods['user_cancel_reassign']['title'] .= ' ' . t('Reassign your groups to the super administrator.');
}

/**
 * Implements hook_social_user_account_header_create_links().
 *
 * Adds the "Create Group" link to the content creation menu.
 * When the user can only create groups of one type, the user skips the
 * group type selection page.
 */
function social_group_social_user_account_header_create_links($context) {

  // Create url to create a group.
  $route_add_group = Url::fromRoute('entity.group.add_page');

  // If we have a user to work with, we can check if we should redirect him to
  // the create group overview or a single group.
  // If we do not have a user we'll default to the overview.
  if (!empty($context['user'])) {

    /** @var \Drupal\Core\Session\AccountInterface $user */
    $account = $context['user'];
    $user_can_create_groups = [];

    // Get all available group types.
    foreach (GroupType::loadMultiple() as $group_type) {

      // When the user has permission to create a group of the current type, add
      // this to the create group array.
      if ($account
        ->hasPermission('create ' . $group_type
        ->id() . ' group')) {
        $user_can_create_groups[$group_type
          ->id()] = $group_type;
      }
      if (count($user_can_create_groups) > 1) {
        break;
      }
    }

    // There's just one group this user can create.
    if (count($user_can_create_groups) === 1) {

      // When there is only one group allowed, add create the url to create a
      // group of this type.

      /** @var \Drupal\group\Entity\Group $allowed_group_type */
      $allowed_group_type = reset($user_can_create_groups);
      $route_add_group = Url::fromRoute('entity.group.add_form', [
        'group_type' => $allowed_group_type
          ->id(),
      ]);
    }
  }
  return [
    'add_group' => [
      '#type' => 'link',
      '#attributes' => [
        'title' => new TranslatableMarkup('Create New Group'),
      ],
      '#title' => new TranslatableMarkup('New Group'),
      '#weight' => 500,
    ] + $route_add_group
      ->toRenderArray(),
  ];
}

/**
 * Implements hook_social_user_account_header_items().
 *
 * Adds the My Groups button to the account header block if it's enabled by a
 * site manager.
 */
function social_group_social_user_account_header_items(array $context) {
  if (\Drupal::config('social_user.navigation.settings')
    ->get('display_my_groups_icon') !== 1) {
    return [];
  }

  // We can't create a "My Groups" link without user.
  if (empty($context['user'])) {
    return [];
  }
  return [
    'groups' => [
      '#type' => 'account_header_element',
      '#wrapper_attributes' => [
        'class' => [
          'desktop',
        ],
      ],
      '#title' => new TranslatableMarkup('My Groups'),
      '#url' => Url::fromRoute('view.groups.page_user_groups', [
        'user' => $context['user']
          ->id(),
      ]),
      '#icon' => 'group',
      '#label' => new TranslatableMarkup('My Groups'),
    ],
  ];
}

/**
 * Implements hook_social_user_account_header_account_links().
 *
 * Adds the "View my groups" link to the user menu.
 */
function social_group_social_user_account_header_account_links(array $context) {

  // We require a user for this link.
  if (empty($context['user']) || !$context['user'] instanceof AccountInterface) {
    return [];
  }
  return [
    'my_groups' => [
      '#type' => 'link',
      '#attributes' => [
        'title' => new TranslatableMarkup('View my groups'),
      ],
      '#title' => new TranslatableMarkup('My groups'),
      '#weight' => 800,
    ] + Url::fromRoute('view.groups.page_user_groups', [
      'user' => $context['user']
        ->id(),
    ])
      ->toRenderArray(),
  ];
}

/**
 * Determine whether a user can see groups of a given type as an outsider.
 *
 * @param string $type
 *   The group type to check.
 * @param \Drupal\Core\Session\AccountInterface $account
 *   The user to check for.
 *
 * @return bool
 *   Whether the user is allowed to view groups of the given type.
 */
function social_group_can_view_groups_of_type(string $type, AccountInterface $account) : bool {
  $group_type = GroupType::load($type);

  // If the group type doesn't exist then the user can't see the group type.
  if ($group_type === FALSE) {
    return FALSE;
  }

  // Anonymous users have a special role.
  if ($account
    ->isAnonymous()) {

    /** @var \Drupal\group\Entity\GroupRoleInterface $role */
    return $group_type
      ->getAnonymousRole()
      ->hasPermission('view group');
  }

  // The service that keeps platform roles in sync with group roles for
  // outsiders.

  /** @var \Drupal\group\GroupRoleSynchronizerInterface $group_role_synchroniser */
  $group_role_synchroniser = \Drupal::service('group_role.synchronizer');

  /** @var \Drupal\group\Entity\GroupRoleInterface[] $group_roles */
  $group_roles = $group_type
    ->getRoles();
  $user_roles = $account
    ->getRoles(TRUE);

  // Check each role this user has whether its group counterpart has the correct
  // permission.
  foreach ($user_roles as $role) {
    $group_role = $group_role_synchroniser
      ->getGroupRoleId($type, $role);
    if ($group_roles[$group_role]
      ->hasPermission('view group')) {
      return TRUE;
    }
  }

  // The user has no role that can view the group type.
  return FALSE;
}

/**
 * Implements hook_entity_operation_alter().
 */
function social_group_entity_operation_alter(array &$operations, EntityInterface $entity) {
  if ($entity
    ->getEntityTypeId() === 'group_content') {
    $titles = [
      'edit' => t('Edit'),
      'delete' => t('Remove'),
      'view' => t('View'),
    ];
    foreach ($operations as $key => &$operation) {
      if (isset($titles[$key])) {
        $operation['title'] = $titles[$key];
      }
    }
  }
}

/**
 * Implements hook_views_plugins_field_alter().
 */
function social_group_views_plugins_field_alter(array &$plugins) {
  $plugins['views_bulk_operations_bulk_form']['class'] = 'Drupal\\social_group\\Plugin\\views\\field\\SocialGroupViewsBulkOperationsBulkForm';
}

/**
 * Implements hook_default_route_group_tabs_alter().
 */
function social_group_social_group_default_route_tabs_alter(&$tabs) {

  // Unset some tabs created by group.
  unset($tabs['group.view'], $tabs['group.edit_form'], $tabs['group.content']);
}

/**
 * Implements hook_batch_alter().
 */
function social_group_batch_alter(&$batch) {
  if (!isset($batch['source_url'])) {
    return;
  }
  $actions = [
    'social_group_members_export_member_action',
    'social_group_change_member_role_action',
    'social_group_delete_group_content_action',
    'social_group_send_email_action',
  ];

  /** @var \Drupal\Core\Url $url */
  $url =& $batch['source_url'];
  if ($url
    ->getRouteName() === 'social_group_gvbo.views_bulk_operations.confirm' || $url
    ->getRouteName() === 'views_bulk_operations.confirm' || $url
    ->getRouteName() === 'views_bulk_operations.execute_batch' || $url
    ->getRouteName() === 'social_group_gvbo.views_bulk_operations.execute_configurable') {

    // Get the action ID.
    $action_id = _social_group_get_action_id($batch);
    $batch['sets'][0]['results']['action'] = $action_id;
    if (in_array($action_id, $actions, TRUE)) {
      $batch['sets'][0]['finished'] = '_social_group_action_batch_finish';
    }
  }
}

/**
 * Function to get the action id of a batch.
 *
 * @param array $batch
 *   The batch array.
 *
 * @return string
 *   Returns the batch action id.
 */
function _social_group_get_action_id(array &$batch) {

  /** @var \Drupal\Core\Form\FormStateInterface $form_state */
  $form_state =& $batch['form_state'];
  $action_id = '';
  if ($form_state instanceof FormStateInterface) {
    $data = $form_state
      ->get('views_bulk_operations');
    $action_id = $data['action_id'];
  }
  else {
    foreach ($batch['sets'][0]['operations'] as $operations) {
      if (empty($operations) || !is_array($operations)) {
        break;
      }
      foreach ($operations as $operation) {
        if (empty($operation) || !is_array($operation)) {
          break;
        }
        foreach ($operation as $items) {
          if (empty($items) || !is_array($items)) {
            break;
          }
          if (!empty($items['action_id'])) {
            $action_id = $items['action_id'];
            break;
          }
        }
      }
    }
  }
  return $action_id;
}

/**
 * Action batch finished callback.
 *
 * @param bool $success
 *   Was the process successfull?
 * @param array $results
 *   Batch process results array.
 * @param array $operations
 *   Performed operations array.
 */
function _social_group_action_batch_finish($success, array $results, array $operations) {

  // When we do a bulk action on all the items in a view, across multiple pages,
  // the saveList function needs to be called. So after pre-populating the list
  // the actual action is performed on the entities.
  if (!empty($results['view_id']) && !empty($results['display_id'])) {
    ViewsBulkOperationsBatch::saveList(TRUE, $results, $operations);
    return;
  }
  $operations = array_count_values($results['operations']);
  $results_count = 0;
  foreach ($operations as $count) {
    $results_count += $count;
  }
  $hook = 'social_group_action_' . $results['action'] . '_finish';
  foreach (\Drupal::moduleHandler()
    ->getImplementations($hook) as $module) {
    $function = $module . '_' . $hook;
    $messages = $function($success);
    if (is_array($messages)) {
      $fields = 0;
      foreach ($messages as $type => $message) {
        if (($type === 'singular' || $type === 'plural') && !empty($message) && is_string($message)) {
          $fields++;
        }
      }
      if ($fields === 2) {
        $message = \Drupal::translation()
          ->formatPlural($results_count, $messages['singular'], $messages['plural']);
        $type = $success ? MessengerInterface::TYPE_STATUS : MessengerInterface::TYPE_WARNING;
        \Drupal::messenger()
          ->addMessage($message, $type);
      }
    }
  }
}

/**
 * Implements hook_social_event_action_ACTION_ID_finish().
 */
function social_group_social_group_action_social_group_members_export_member_action_finish($success) {
  if ($success) {
    return [
      'singular' => '1 selected member has been exported successfully',
      'plural' => '@count selected members have been exported successfully',
    ];
  }
  return [
    'singular' => '1 selected member has not been exported',
    'plural' => '@count selected members have not been exported',
  ];
}

/**
 * Implements hook_social_event_action_ACTION_ID_finish().
 */
function social_group_social_group_action_social_group_delete_group_content_action_finish($success) {
  if ($success) {
    return [
      'singular' => '1 selected member has been removed successfully',
      'plural' => '@count selected members have been removed successfully',
    ];
  }
  return [
    'singular' => '1 selected member has not been removed successfully',
    'plural' => '@count selected members have been removed successfully',
  ];
}

/**
 * Implements hook_social_event_action_ACTION_ID_finish().
 */
function social_group_social_group_action_social_group_change_member_role_action_finish($success) {
  if ($success) {
    return [
      'singular' => 'The role of 1 selected member has been changed successfully',
      'plural' => 'The role of @count selected members have been changed successfully',
    ];
  }
  return [
    'singular' => 'The role of 1 selected member has not been changed successfully',
    'plural' => 'The role of @count selected members have not been changed successfully',
  ];
}

/**
 * Implements hook_social_event_action_ACTION_ID_finish().
 */
function social_group_social_group_action_social_group_send_email_action_finish($success) {
  if ($success) {
    return [
      'singular' => 'Your email has been sent to 1 selected member successfully',
      'plural' => 'Your email has been sent to @count selected members successfully',
    ];
  }
  return [
    'singular' => 'Your email has not been sent to 1 selected member successfully',
    'plural' => 'Your email has not been sent to @count selected members successfully',
  ];
}

Functions

Namesort descending Description
social_group_batch_alter Implements hook_batch_alter().
social_group_block_access Implements hook_block_access().
social_group_block_view_local_tasks_block_alter Implements hook_block_view_BASE_BLOCK_ID_alter().
social_group_can_view_groups_of_type Determine whether a user can see groups of a given type as an outsider.
social_group_entity_base_field_info_alter Implements hook_entity_base_field_info_alter().
social_group_entity_operation_alter Implements hook_entity_operation_alter().
social_group_entity_type_alter Implements hook_entity_type_alter().
social_group_entity_view_alter Implements hook_entity_view_alter().
social_group_field_info_alter Implements hook_field_info_alter().
social_group_field_widget_form_alter Alter the visibility field within groups.
social_group_form_alter Implements hook_form_alter().
social_group_form_entity_access_by_field_visibility_form_alter Implements hook_form_FORM_ID_alter().
social_group_form_group_public_group_edit_form_alter Implements hook_form_FORM_ID_alter().
social_group_form_node_form_alter Implements hook_form_FORM_ID_alter().
social_group_get_allowed_visibility_options_per_group_type Get the allowed visibility options for a given group type.
social_group_get_all_groups Get all open groups.
social_group_get_all_group_members Deprecated Get all group memberships for a certain user.
social_group_get_all_open_groups Get all open groups.
social_group_group_content_insert When creating a new group membership.
social_group_group_content_update When updating a group membership.
social_group_group_insert Implements hook_entity_insert().
social_group_group_type_permission_check Check if a User is able to edit a Group's GroupType.
social_group_local_tasks_alter Implements hook_local_tasks_alter().
social_group_menu_local_actions_alter Implements hook_menu_local_actions_alter().
social_group_menu_local_tasks_alter Implements hook_menu_local_tasks_alter().
social_group_preprocess_group Prepares variables for profile templates.
social_group_preprocess_group__hero Implements hook_preprocess_HOOK().
social_group_preprocess_profile Implements hook_preprocess_HOOK().
social_group_preprocess_views_view Implements hook_preprocess_HOOK().
social_group_save_group_from_node Form submit to save the group from a node form.
social_group_social_group_action_social_group_change_member_role_action_finish Implements hook_social_event_action_ACTION_ID_finish().
social_group_social_group_action_social_group_delete_group_content_action_finish Implements hook_social_event_action_ACTION_ID_finish().
social_group_social_group_action_social_group_members_export_member_action_finish Implements hook_social_event_action_ACTION_ID_finish().
social_group_social_group_action_social_group_send_email_action_finish Implements hook_social_event_action_ACTION_ID_finish().
social_group_social_group_default_route_tabs_alter Implements hook_default_route_group_tabs_alter().
social_group_social_user_account_header_account_links Implements hook_social_user_account_header_account_links().
social_group_social_user_account_header_create_links Implements hook_social_user_account_header_create_links().
social_group_social_user_account_header_items Implements hook_social_user_account_header_items().
social_group_theme Implements hook_theme().
social_group_user_cancel_methods_alter Implements hook_user_cancel_methods_alter().
social_group_views_plugins_field_alter Implements hook_views_plugins_field_alter().
social_group_views_post_render Implements hook_views_post_render().
social_group_views_query_alter Implements hook_views_query_alter().
template_preprocess_group_settings_help Prepares variables for group settings help text templates.
_social_group_action_batch_finish Action batch finished callback.
_social_group_action_form_submit Form submit for group join / leave form.
_social_group_cache_tags Get group cache tags.
_social_group_cancel_join_leave_form Form cancel for join and leave form.
_social_group_delete_group Delete the group and all of its content.
_social_group_edit_submit_redirect Form submit redirect for social groups.
_social_group_get_action_id Function to get the action id of a batch.
_social_group_get_allowed_visibility Get the allowed visibility of a group.
_social_group_get_current_group Get current Group entity from the route.
_social_group_get_group_labels Load group content label and group label.
_social_group_get_join_methods Get the join methods of a group.
_social_group_get_member_profile Return user profile by given group membership content.
_social_group_get_overview_route Get group overview route.
_social_group_grant_admin_role This function checks if the user should get the admin role within a group.
_social_group_inform_group_type_selection Function that gives information to a CM+ when they edit a Group Type.
_social_group_membership_edit_form_submit Form submit for membership edit form.
_social_group_node_form_submit Form submit for group content create form.
_social_group_type_edit_submit Form submit for removing members from a group so we can clear caches.
_social_group_visibility_settings_submit Form submit for visibility settings.
_social_membership_delete_form_submit Form submit for removing members from a group so we can clear caches.