<?php

/**
 * @file
 * The Social Group Flexible Group module.
 */

use Drupal\Core\Access\AccessResult;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\ReplaceCommand;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Database\Query\AlterableInterface;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\block\Entity\Block;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\group\Entity\Group;
use Drupal\group\Entity\GroupInterface;
use Drupal\social_group\SocialGroupInterface;
use Drupal\social_group_flexible_group\FlexibleGroupContentVisibilityUpdate;
use Drupal\social_user\VerifyableUserInterface;
use Drupal\user\Entity\User;
use Drupal\views\Plugin\views\query\QueryPluginBase;
use Drupal\views\Plugin\views\row\EntityRow;
use Drupal\views\ViewExecutable;
use Drupal\views\Views;

/**
 * Provide a method to alter array of group types used in open social.
 *
 * @param array $social_group_types
 *   List of group types used in open social.
 *
 * @ingroup social_group_api
 */
function social_group_flexible_group_social_group_types_alter(array &$social_group_types) {
  $social_group_types[] = 'flexible_group';
}

/**
 * Implements hook_views_data_alter().
 *
 * @deprecated in social:11.9.0 and is removed from social:13.0.0.
 *   Use views filter "node_access" from "node" module instead.
 * @see https://www.drupal.org/project/social/issues/3358489
 */
function social_group_flexible_group_views_data_alter(array &$data): void {
  $data['node_access']['flexible_group_node_access'] = [
    'title' => t('Filter nodes, with visibility group, placed in group you are not a member of.'),
    'filter' => [
      'title' => t('Filter nodes, with visibility group, placed in group you are not a member of.'),
      'help' => t('Filter nodes, with visibility group, placed in group you are not a member of.'),
      'id' => 'flexible_group_node_access',
    ],
  ];
}

/**
 * Implements hook_form_FORM_ID_form_alter().
 */
function social_group_flexible_group_form_group_flexible_group_edit_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  // Lets remove group type from flexible group.
  // Editing is useless since the options are all in there.
  if ($form['group_type']['#disabled']) {
    // Remove all the options.
    // @see social_group_form_alter().
    unset($form['group_type']);
    unset($form['#fieldgroups']['group_settings']->children['group_type']);
    unset($form['#group_children']['group_type']);
  }
  $form['field_flexible_group_visibility']['#prefix'] =
    t('Altering the visibility options could result in changes in access for content within this group and the group itself.');

  $form['actions']['submit']['#submit'][] = '_social_flexible_group_edit_submit';
}

/**
 * Implements hook_form_alter().
 */
function social_group_flexible_group_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  // Exposed Filter block on the all-groups overview and search.
  if ($form['#id'] === 'views-exposed-form-newest-groups-page-all-groups' ||
    $form['#id'] === 'views-exposed-form-search-groups-page-no-value' ||
    $form['#id'] === 'views-exposed-form-search-groups-page') {
    // Add states so this is only available when flexible groups is checked.
    // Could be hidden when only flexible groups is enabled, so check that.
    // @todo remove this once everything is migrated to flexible groups.
    if (!empty($form['field_group_allowed_join_method']) &&
      !empty($form['type']['#options']) &&
      $form['type']['#type'] !== 'hidden') {
      $form['field_group_allowed_join_method']['#states'] = [
        'visible' => [
          ':input[name="type"]' => ['value' => 'flexible_group'],
        ],
      ];
    }
    if (!empty($form['field_group_type_target_id']) &&
      !empty($form['type']['#options']) &&
      $form['type']['#type'] !== 'hidden') {
      $form['field_group_type_target_id']['#states'] = [
        'visible' => [
          ':input[name="type"]' => ['value' => 'flexible_group'],
        ],
      ];
    }
    // Hide the flexible group field group type if the setting for it to be
    // required is disabled even if there are values in the vocabulary.
    if (!empty($form['field_group_type_target_id']) &&
      !\Drupal::config('social_group.settings')->get('social_group_type_required')) {
      $form['field_group_type_target_id']['#type'] = 'hidden';
    }
    // Hide the flexible group field group type if there is only the
    // "All / any" option we hide it as well.
    if (!empty($form['field_group_type_target_id']) &&
      !empty($form['field_group_type_target_id']['#options']) &&
      count($form['field_group_type_target_id']['#options']) === 1 &&
      array_key_exists('All', $form['field_group_type_target_id']['#options'])) {
      $form['field_group_type_target_id']['#type'] = 'hidden';
    }
  }
  // For adding or editing a flexible group, we alter the visibility fields.
  if ($form['#id'] === 'group-flexible-group-add-form' ||
    $form['#id'] === 'group-flexible-group-edit-form') {
    // Change the group visibility on flexible groups.
    if (!empty($form['field_group_allowed_visibility'])) {
      if (!empty($form['field_group_allowed_visibility']['widget']['#title'])) {
        $form['field_group_allowed_visibility']['widget']['#title'] = t('Group content visibility options');
      }
      if (!empty($form['field_group_allowed_visibility']['widget']['#description'])) {
        $form['field_group_allowed_visibility']['widget']['#description'] = t('Choose the visibility options allowed for the group content.');
      }
      // When referencing select lists and radio buttons in remote conditions,
      // a 'value' condition must be used.
      $form['field_group_allowed_visibility']['#states'] = [
        'visible' => [
          ':input[name="field_flexible_group_visibility"]' => [
            ['value' => 'public'],
            ['value' => 'community'],
            ['value' => 'members'],
          ],
        ],
      ];
    }
    // Change the allowed join method on flexible groups.
    if (!empty($form['field_group_allowed_join_method'])) {
      if (!empty($form['field_group_allowed_join_method']['widget']['#title'])) {
        $form['field_group_allowed_join_method']['widget']['#title'] = t('Join methods');
      }
      if (!empty($form['field_group_allowed_join_method']['widget']['#description'])) {
        $form['field_group_allowed_join_method']['widget']['#description'] = t('How can people join this group. Group managers can always add members directly, regardless of the chosen join method.');
      }
      // When referencing select lists and radio buttons in remote conditions,
      // a 'value' condition must be used.
      $form['field_group_allowed_join_method']['#states'] = [
        'visible' => [
          ':input[name="field_flexible_group_visibility"]' => [
            ['value' => 'public'],
            ['value' => 'community'],
            ['value' => 'members'],
          ],
        ],
      ];
    }
    // Only when adding a flexible group, we need to predefine the
    // content visibility and join method based on the group visibility.
    if ($form['#id'] === 'group-flexible-group-add-form') {
      // Reset default_values on the add form only.
      // So we can use state management to check / select.
      if (!empty($form['field_group_allowed_join_method']['widget']['#default_value'])) {
        $form['field_group_allowed_join_method']['widget']['#default_value'] = [];
      }
      if (!empty($form['field_group_allowed_visibility']['widget']['#default_value'])) {
        $form['field_group_allowed_visibility']['widget']['#default_value'] = [];
      }
    }

    // On the edit form, if there is no value set for the group_visibility
    // it means it was an already existing flexible group and we need to add a
    // default value so people can save it.
    if ($form['#id'] === 'group-flexible-group-edit-form' &&
      empty($form['field_flexible_group_visibility']['widget']['#default_value'])) {
      // The default is always community because we used to only have closed
      // groups (which is visible for the community) or public if the
      // content visibility has public as an option.
      $form['field_flexible_group_visibility']['widget']['#default_value'] = 'community';
      if (in_array('public', $form['field_group_allowed_visibility']['widget']['#default_value'])) {
        $form['field_flexible_group_visibility']['widget']['#default_value'] = 'public';
      }
    }

    // Disable type field if there are no terms added in 'Group type' vocab.
    // Also, check for settings by SM for enabling this field.
    $group_type_settings = \Drupal::config('social_group.settings')->get('social_group_type_required');
    if ($group_type_settings
      && isset($form['field_group_type'])
      && !empty(\Drupal::entityTypeManager()->getStorage('taxonomy_term')->loadTree('group_type'))) {
      $form['field_group_type']['widget']['#required'] = TRUE;
      // Unset the group type label for none.
      if (isset($form['field_group_type']['widget']['#options']['_none'])) {
        unset($form['field_group_type']['widget']['#options']['_none']);
      }
    }
    else {
      $form['field_group_type']['#access'] = FALSE;
    }

    // Disable the public visibility option on group create/edit content option.
    $config = \Drupal::config('entity_access_by_field.settings');
    $account = \Drupal::currentUser();
    if ($config->get('disable_public_visibility') === 1 && !$account->hasPermission('override disabled public visibility')) {
      $form['field_group_allowed_visibility']['widget']['public']['#disabled'] = TRUE;
      // Because the checkbox is disabled we also need to remove the value.
      if (($key = array_search('public', $form['field_group_allowed_visibility']['widget']["#default_value"], TRUE)) !== FALSE) {
        unset($form['field_group_allowed_visibility']['widget']["#default_value"][$key]);
      }
    }

    // Validate visibilities.
    $form['#validate'][] = '_flexible_group_visibility_validation';
    $form['#after_build'][] = 'social_group_flexible_group_flexible_group_add_after_build';
    $form['#attached']['library'][] = 'social_group_flexible_group/admin';
    $form['#attached']['library'][] = 'social_group_flexible_group/form';
  }
}

/**
 * Custom form validation to make sure people cannot hack visibility.
 *
 * @param array $form
 *   The form element.
 * @param \Drupal\Core\Form\FormStateInterface $form_state
 *   The form state.
 *
 * @return void
 *   Void as it will trigger setErrorByName method.
 */
function _flexible_group_visibility_validation($form, &$form_state) {
  // Get visibility options.
  $visibilities = $form['field_flexible_group_visibility']['widget']['#options'];

  // Check if the values are being altered while it's disabled.
  // Check for group visibility.
  foreach ($visibilities as $visibility => $value) {
    if (isset($form['field_flexible_group_visibility']['widget'][$visibility]['#disabled'])
      && $form['field_flexible_group_visibility']['widget'][$visibility]['#disabled'] === TRUE
      && $form_state->getValue('field_flexible_group_visibility')[0]['value'] === $visibility) {
      $form_state->setErrorByName('field_flexible_group_visibility', t('@visibility visibility is not allowed', ['@visibility' => $visibility]));
    }
  }

  // Get visibility options.
  $visibilities = $form['field_group_allowed_visibility']['widget']['#options'];

  // Check if the values are being altered while it's disabled.
  // Check for group content visibility.
  foreach ($visibilities as $visibility => $value) {
    if (isset($form['field_group_allowed_visibility']['widget'][$visibility]['#disabled'])
      && $form['field_group_allowed_visibility']['widget'][$visibility]['#disabled'] === TRUE
      && in_array($visibility, array_column($form_state->getValue('field_group_allowed_visibility'), 'value'), TRUE)) {
      $form_state->setErrorByName('field_group_allowed_visibility', t('@visibility visibility is not allowed', ['@visibility' => $visibility]));
    }
  }
}

/**
 * After_build function for the flexible group add form to add default states.
 *
 * @param array $form
 *   The form element.
 * @param \Drupal\Core\Form\FormStateInterface $form_state
 *   The form state.
 *
 * @return mixed
 *   The form array containing the default values & states.
 */
function social_group_flexible_group_flexible_group_add_after_build(array $form, FormStateInterface $form_state) {
  $group_types = ['flexible_group'];
  \Drupal::moduleHandler()->alter('social_group_settings', $group_types);

  foreach ($group_types as $group_type) {
    $form_ids[] = 'group-' . str_replace('_', '-', $group_type) . '-add-form';
  }
  // Add states for content visibility based on group visibility.
  // Only on the add page. On edit we want users to be able to consciously
  // decide what allowed visibility to select.
  if (
    in_array($form['#id'], $form_ids ?? []) &&
    isset($form['field_flexible_group_visibility']['widget']['#options'], $form['field_group_allowed_visibility']['widget']['#options'])
  ) {
    // If group visibility is public. All content visibility is selected.
    $form['field_group_allowed_visibility']['widget']['public']['#states'] = [
      'checked' => [
        ':input[name="field_flexible_group_visibility"]' => [
          ['value' => 'public'],
        ],
      ],
      'unchecked' => [
        ':input[name="field_flexible_group_visibility"]' => [
          ['value' => 'community'],
          ['value' => 'members'],
        ],
      ],
    ];
    // If group visibility is community. Communit & Group members are selected.
    $form['field_group_allowed_visibility']['widget']['community']['#states'] = [
      'checked' => [
        ':input[name="field_flexible_group_visibility"]' => [
          ['value' => 'public'],
          ['value' => 'community'],
        ],
      ],
      'unchecked' => [
        ':input[name="field_flexible_group_visibility"]' => [
          ['value' => 'members'],
        ],
      ],
    ];
    // If group visibility is group. Only group members are selected.
    $form['field_group_allowed_visibility']['widget']['group']['#states'] = [
      'checked' => [
        ':input[name="field_flexible_group_visibility"]' => [
          ['value' => 'public'],
          ['value' => 'community'],
          ['value' => 'members'],
        ],
      ],
    ];
  }

  return $form;
}

/**
 * Implements hook_ENTITY_TYPE_access().
 */
function social_group_flexible_group_group_access(EntityInterface $entity, $operation, AccountInterface $account) {
  $result = AccessResult::neutral();
  // Write custom access checks based on the new group visibility field.
  // If group visibility doesn't exist we can skip this.
  /** @var \Drupal\social_group\SocialGroupInterface $entity */
  if ($operation !== 'view' || !$entity->hasField('field_flexible_group_visibility')) {
    return $result;
  }
  // If user has administer groups access we can skip this.
  if ($account->hasPermission('manage all groups')) {
    return $result;
  }

  // If group visibility value doesn't exist we can skip.
  if ($entity->field_flexible_group_visibility->isEmpty()) {
    return $result;
  }

  switch ($entity->field_flexible_group_visibility->value) {
    // If group visibility exists and public is selected, we can skip.
    case 'public':
      return $result;

    // If group visibility exists and community or members is selected, check
    // if user is logged.
    case 'community':
      return AccessResult::forbiddenIf($account->isAnonymous())
        ->cachePerUser()
        ->addCacheableDependency($entity);

    // If group visibility exists and members only is selected, we need to check
    // if user is logged in and is a member of the group.
    case 'members':
      $not_a_member = !$entity->hasMember($account) || $account->isAnonymous();

      return AccessResult::forbiddenIf($not_a_member)
        ->cachePerPermissions()
        ->cachePerUser()
        ->addCacheableDependency($entity)
        ->addCacheableDependency($account);
  }

  return $result;
}

/**
 * Implements template_preprocess_form_element().
 */
function social_group_flexible_group_preprocess_form_element(&$variables) {
  // Make sure our flexible group visibility field renders icons in the radio
  // labels. We add a new property to the form element label render array.
  // This is used in FormElementLabel.php to add data for templating.
  if (!isset($variables['element']['#type'])) {
    return;
  }

  if ($variables['element']['#type'] === 'radio' &&
    !empty($variables['name']) &&
    $variables['name'] === 'field_group_allowed_join_method') {
    $variables['label']['#render_icon'] = TRUE;
    $variables['attributes']['class'][] = 'inline-item';
  }
  if ($variables['element']['#type'] === 'radio' &&
    !empty($variables['name']) &&
    $variables['name'] === 'field_flexible_group_visibility') {
    $variables['label']['#render_icon'] = TRUE;
    $variables['attributes']['class'][] = 'inline-item';
  }
  if ($variables['element']['#type'] === 'checkbox' &&
    !empty($variables['name']) &&
    strpos($variables['name'], 'field_group_allowed_visibility') !== FALSE) {
    $variables['label']['#render_icon'] = TRUE;
    $variables['attributes']['class'][] = 'inline-item';
  }
}

/**
 * Implements hook_preprocess_HOOK().
 */
function social_group_flexible_group_preprocess_fieldset(array &$variables): void {
  // Make sure our flexible group visibility field renders a tooltip, since
  // this field is rendered as fieldset with legend and radios as children
  // we need to do it in this preprocess.
  $element = $variables['element'];

  if (empty($element['#field_name'])) {
    return;
  }

  $fields = [
    'field_flexible_group_visibility' => [
      'title' => t('Group Visibility'),
      'hook' => 'social_group_group_visibility_description',
    ],
    'field_group_allowed_visibility' => [
      'title' => t('Group content visibility'),
      'hook' => 'social_group_content_visibility_description',
    ],
  ];

  if (!isset($fields[$element['#field_name']])) {
    return;
  }

  /** @var \Drupal\social_group\SocialGroupHelperServiceInterface $helper */
  $helper = \Drupal::service('social_group.helper_service');

  $description = '';

  foreach ($element['#options'] as $key => $label) {
    $description .= $helper->description(
      $key,
      $fields[$element['#field_name']]['hook'],
    );
  }

  // Render a specific tooltip based on a field name and description.
  // This is done in the fieldset, next to the <legend>.
  $variables['popover'] = social_group_render_tooltip(
    $element['#field_name'],
    $fields[$element['#field_name']]['title'],
    $description,
  );
}

/**
 * Implements hook_social_group_join_method_usage().
 */
function social_group_flexible_group_social_group_join_method_usage(): array {
  return [
    [
      'entity_type' => 'group',
      'bundle' => 'flexible_group',
      'field' => 'field_group_allowed_join_method',
    ],
  ];
}

/**
 * Implements hook_social_tagging_type_alter().
 */
function social_group_flexible_group_social_tagging_type_alter(array &$items): void {
  $items['group']['sets'][] = [
    'bundles' => ['flexible_group'],
    'group' => 'additional_details',
    'label' => t('Tags'),
  ];
}

/**
 * Custom form submit handler for editing a flexible group.
 *
 * @param array $form
 *   The form array.
 * @param \Drupal\Core\Form\FormStateInterface $form_state
 *   Form state.
 *
 * @throws \Drupal\Core\Entity\EntityStorageException
 */
function _social_flexible_group_edit_submit(array $form, FormStateInterface $form_state) {
  // Check if the visibility changed.
  $default_visibility = $form['field_group_allowed_visibility']['widget']['#default_value'];
  $new_visibility = $form_state->getValue('field_group_allowed_visibility');

  $changed_visibility = [];
  // If there was a visibility that we don't have anymore after editting
  // all the content that was inside the group with this visibility
  // will get the lowest visibility that is still checked.
  foreach ($default_visibility as $key => $option) {
    if (array_search($option, array_column($new_visibility, 'value')) === FALSE) {
      $changed_visibility[] = $option;
    }
  }

  // So there is now a visibility setting we don't support anymore
  // after editing. Make sure we update all the content that has this
  // to the next best optin.
  if (!empty($changed_visibility)) {
    $group = _social_group_get_current_group();

    if ($group instanceof GroupInterface) {
      // Update the default visibility of all the content.
      FlexibleGroupContentVisibilityUpdate::batchUpdateGroupContentVisibility($group, $changed_visibility, $new_visibility);

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

/**
 * Implements hook_form_FORM_ID_alter().
 */
function social_group_flexible_group_form_social_group_add_alter(&$form, FormStateInterface $form_state, $form_id) {
  // Rearrange all the options so flexible is last
  // we will need the space for the configuration.
  $type_options = $form['group_settings']['group_type']['#options'];
  if (!empty($type_options['flexible_group'])) {
    $flexible = $type_options['flexible_group'];
    unset($form['group_settings']['group_type']['#options']['flexible_group']);
    $form['group_settings']['group_type']['#options']['flexible_group'] = $flexible;
  }
}

/**
 * Check if a user can join the group directly.
 *
 * @param \Drupal\group\Entity\GroupInterface $group
 *   The group we are checking.
 *
 * @return bool
 *   TRUE when users can join.
 */
function social_group_flexible_group_can_join_directly(GroupInterface $group) {
  $join_methods = $group->get('field_group_allowed_join_method')->getValue();

  if (!in_array('direct', array_column($join_methods, 'value'), FALSE)) {
    return FALSE;
  }

  return TRUE;
}

/**
 * Check if a user can be added to a group.
 *
 * @param \Drupal\social_group\SocialGroupInterface $group
 *   The group we are checking.
 *
 * @return bool
 *   TRUE when users can join.
 */
function social_group_flexible_group_can_be_added(SocialGroupInterface $group): bool {
  return in_array(
    'added',
    array_column(
      $group->get('field_group_allowed_join_method')->getValue(),
      'value',
    ),
  );
}

/**
 * Check if public is enabled as visibility options.
 *
 * @param \Drupal\group\Entity\GroupInterface $group
 *   The group we are checking.
 *
 * @return bool
 *   TRUE when users can join.
 */
function social_group_flexible_group_public_enabled(GroupInterface $group) {
  $visibility_options = $group->get('field_flexible_group_visibility')->getValue();

  if (!in_array('public', array_column($visibility_options, 'value'), FALSE)) {
    return FALSE;
  }

  return TRUE;
}

/**
 * Check if community is enabled as visibility options.
 *
 * @param \Drupal\group\Entity\GroupInterface $group
 *   The group we are checking.
 *
 * @return bool
 *   TRUE when users can join.
 */
function social_group_flexible_group_community_enabled(GroupInterface $group) {
  $visibility_options = $group->get('field_flexible_group_visibility')->getValue();

  if (!in_array('community', array_column($visibility_options, 'value'), FALSE)) {
    return FALSE;
  }

  return TRUE;
}

/**
 * Check if members is enabled as visibility options.
 *
 * @param \Drupal\group\Entity\Group $group
 *   The group we are checking.
 *
 * @return bool
 *   TRUE when users can join.
 */
function social_group_flexible_group_members_enabled(Group $group) {
  $visibility_options = $group->get('field_flexible_group_visibility')->getValue();

  if (!in_array('group', array_column($visibility_options, 'value'), FALSE)) {
    return FALSE;
  }

  return TRUE;
}

/**
 * Implements hook_menu_local_actions_alter().
 */
function social_group_flexible_group_menu_local_actions_alter(array &$local_actions): void {
  $account = \Drupal::currentUser();

  // Remove the social_group add member action on the membership overview if we
  // can't add members directly. SM+ can still add members though.
  if (
    ($group = _social_group_get_current_group()) !== NULL &&
    $group->getGroupType()->id() === 'flexible_group' &&
    !social_group_flexible_group_can_be_added($group) &&
    !$account->hasPermission('manage all groups') &&
    !$group->hasPermission('administer members', $account)
  ) {
    unset($local_actions['social_group.add_member']);
  }
}

/**
 * Determine whether a user can see flexible groups as outsider.
 *
 * @param \Drupal\social_group\SocialGroupInterface $group
 *   The group we are checking.
 * @param \Drupal\Core\Session\AccountInterface $account
 *   The user to check for.
 *
 * @return bool
 *   Whether the user is allowed to view this flexible groups.
 */
function social_group_flexible_group_can_view_flexible_groups(
  SocialGroupInterface $group,
  AccountInterface $account
): bool {
  // Users who can manage all can manage everything.
  if ($account->hasPermission('manage all groups')) {
    return TRUE;
  }

  // If User is a member it can see it.
  if ($group->hasMember($account)) {
    return TRUE;
  }

  // Outsiders can only see groups that have
  // public / community enabled as visibility.
  if ($account->isAuthenticated()
    && !social_group_flexible_group_community_enabled($group)
    && !social_group_flexible_group_public_enabled($group)) {
    return FALSE;
  }

  return TRUE;
}

/**
 * Implements hook_views_query_alter().
 *
 * Hide flexible groups everywhere when the current user cant see it.
 */
function social_group_flexible_group_views_query_alter(ViewExecutable $view, QueryPluginBase $query) {
  if (empty($view->rowPlugin) || !($view->rowPlugin instanceof EntityRow) || $view->rowPlugin->getEntityTypeId() !== 'group') {
    return;
  }

  $account_id = \Drupal::currentUser()->id();
  $account = User::load($account_id);
  assert($account instanceof VerifyableUserInterface);

  if (!$account->isAnonymous()) {
    // Don't trigger page cache, this will cache it for AN
    // but with LU data.
    // Dynamic page cache handles this.
    \Drupal::service('page_cache_kill_switch')->trigger();
  }

  // Don't check, they can see it all.
  if ($account->hasPermission('manage all groups')) {
    return;
  }

  // Let's build our join with the allowed visibility data.
  $configuration = [
    'type'       => 'LEFT',
    'table'      => 'group__field_flexible_group_visibility',
    'field'      => 'entity_id',
    'left_table' => 'groups_field_data',
    'left_field' => 'id',
    'operator'   => '=',
  ];

  $alias = 'groups_field_flexible_group_visibility';

  /** @var \Drupal\views\Plugin\views\query\Sql $query */
  $join = Views::pluginManager('join')->createInstance('standard', $configuration);
  $rel = $query->addRelationship($alias, $join, 'groups_field_data');
  $query->addTable('group__field_flexible_group_visibility', $rel, $join, $alias);

  /** @var \Drupal\views\Plugin\views\query\Sql $query */
  $current_where = count($query->where);

  // Make sure we add one new group with a where clause.
  $new_where = $current_where + 1;

  // We need to add our group by using a query tag.
  // Otherwise views doesn't accept it.
  $query->addTag('flexible_group_by');

  // Get all LU groups.
  $my_groups = \Drupal::service('social_group.helper_service')
    ->getAllGroupsForUser($account_id);

  // Get all hidden groups.
  $hidden_groups = \Drupal::entityTypeManager()->getStorage('group')->loadByProperties([
    'field_flexible_group_visibility' => 'members',
  ]);

  // Get all hidden groups that the current user is not a member of
  // and remove them from showing in the view.
  $ids = array_diff(array_keys($hidden_groups), $my_groups);

  // Anonymous or unverified users should only see 'public' groups.
  if ($account->isAnonymous() || !$account->isVerified()) {
    $community_groups = \Drupal::entityTypeManager()->getStorage('group')->loadByProperties([
      'field_flexible_group_visibility' => 'community',
    ]);
    $ids = array_merge($ids, array_keys($community_groups));

    // Add context so for AN it will have a different cache.
    $view->element['#cache']['contexts'][] = 'user.roles:anonymous';
  }

  if ($ids) {
    $query->addWhere($new_where, 'groups_field_data.id', $ids, 'NOT IN');
  }

  // Make sure this block gets refreshed for a user if the group membership
  // changed for this user.
  $view->element['#cache']['contexts'][] = 'user';
  $view->element['#cache']['tags'][] = 'group_content_list:plugin:group_membership:entity:' . $account->id();
}

/**
 * Implements hook_query_TAG_alter().
 */
function social_group_flexible_group_query_flexible_group_by_alter(AlterableInterface $query) {
  $query->distinct();
}

/**
 * Implements hook_block_access().
 */
function social_group_flexible_group_block_access(Block $block, $operation, AccountInterface $account) {
  // This is a list of the blocks that this function cares about, if we're being
  // called for a different block we exit early.
  $block_id = $block->getPluginId();
  $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',
    'views_block:group_managers-block_list_managers',
  ];
  // We don't care for other blocks.
  if (!in_array($block_id, $managed_blocks, FALSE)) {
    return AccessResult::neutral();
  }

  $group = _social_group_get_current_group();
  // We don't care about other group types in here.
  if ($group && $group->getGroupType()->id() === 'flexible_group') {
    // Only when users cant join directly, add the managers block
    // so they know who to contact.
    if ($operation === 'view' &&
      social_group_flexible_group_can_join_directly($group) &&
      $block->getPluginId() === 'views_block:group_managers-block_list_managers') {
      return AccessResult::forbidden();
    }

    // All users with permissions can see the rest.
    if ($account->hasPermission('manage all groups')) {
      return AccessResult::neutral();
    }

    if (
      !$group->hasMember($account) &&
      !social_group_flexible_group_community_enabled($group) &&
      !social_group_flexible_group_public_enabled($group)
    ) {
      // If it is flexible and the current user is not an member of this group,
      // and content visibility is not public and also not community
      // hide it.
      $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();
        }
      }
    }
  }

  return AccessResult::neutral();
}

/**
 * Sets dynamic allowed values for the group type icon field.
 */
function social_group_flexible_group_type_allowed_values_function(FieldStorageConfig $definition, ContentEntityInterface $entity = NULL, $cacheable) {
  $theme_path = \Drupal::service('extension.list.theme')->getPath('socialbase');
  $icon_files = \Drupal::service('file_system')->scanDirectory(DRUPAL_ROOT . '/' . $theme_path . '/components/06-libraries/icons/source', '/^icon-.*\.svg$/', ['recurse' => FALSE]);

  $options = [];
  foreach ($icon_files as $file) {
    $options[$file->name] = $file->name;
  }

  return $options;
}

/**
 * Sets the default value for the group type icon field.
 */
function social_group_flexible_group_type_default_value_function(ContentEntityInterface $entity, FieldDefinitionInterface $definition) {
  return [
    ['value' => 'icon-event'],
  ];
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function social_group_flexible_group_form_taxonomy_term_group_type_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  if (!isset($form['field_group_type_icon'])) {
    return;
  }

  $form['#attached']['library'][] = 'social_group_flexible_group/admin';

  $default_icon = $form['field_group_type_icon']['widget']['#default_value'][0];

  $form['field_group_type_icon']['preview'] = [
    '#type' => 'inline_template',
    '#template' => '<div class="icon-svg-wrapper" data-href="#' . $default_icon . '"><svg class="selected-svg"><use xlink:href="#' . $default_icon . '"></use></svg>{{ svg_icons|raw }}</div>',
    '#context' => [
      'svg_icons' => file_get_contents(\Drupal::service('extension.list.theme')->getPath('socialbase') . '/assets/icons/icons.svg'),
    ],
  ];

  $form['field_group_type_icon']['widget']['#ajax'] = [
    'callback' => '_social_group_flexible_group_type_change_topic_icon_ajax',
    'event' => 'change',
  ];
}

/**
 * Ajax callback to change group type icon.
 */
function _social_group_flexible_group_type_change_topic_icon_ajax(array $form, FormStateInterface $form_state) {
  $response = new AjaxResponse();

  $icon = $form_state->getValue('field_group_type_icon')[0]['value'];
  $replaced = [
    '#type' => 'inline_template',
    '#template' => '<div class="icon-svg-wrapper" data-href="#' . $icon . '"><svg class="selected-svg"><use xlink:href="#' . $icon . '"></use></svg>{{ svg_icons|raw }}</div>',
    '#context' => [
      'svg_icons' => file_get_contents(\Drupal::service('extension.list.theme')->getPath('socialbase') . '/assets/icons/icons.svg'),
    ],
  ];

  $response->addCommand(new ReplaceCommand('.icon-svg-wrapper', $replaced));

  return $response;
}
