<?php

/**
 * @file
 * The Social Invite group module.
 */

use Drupal\block\Entity\Block;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Cache\RefinableCacheableDependencyInterface;
use Drupal\Core\Entity\Display\EntityFormDisplayInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Link;
use Drupal\Core\Render\BubbleableMetadata;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\TypedData\Exception\MissingDataException;
use Drupal\Core\Url;
use Drupal\ginvite\Plugin\GroupContentEnabler\GroupInvitation;
use Drupal\group\Entity\GroupContent;
use Drupal\group\Entity\GroupContentInterface;
use Drupal\group\Entity\GroupInterface;
use Drupal\group\Entity\GroupRoleInterface;
use Drupal\group\Entity\GroupTypeInterface;
use Drupal\social_group_invite\Plugin\Field\FieldFormatter\EntityReferenceLabelFormatterOverrider;

/**
 * Implements hook_menu_local_actions_alter().
 */
function social_group_invite_menu_local_actions_alter(&$local_actions) {
  // Remove "Invite members" button.
  if (isset($local_actions['ginvite.invitation.bulk'])) {
    unset($local_actions['ginvite.invitation.bulk']);
  }
  // Remove "Invite member" button.
  if (isset($local_actions['ginvite.invite_member'])) {
    unset($local_actions['ginvite.invite_member']);
  }
}

/**
 * Implements hook_theme().
 */
function social_group_invite_theme(): array {
  return [
    'invite_to_group_by_member' => [
      'variables' => [
        'url' => NULL,
        'link' => NULL,
      ],
    ],
  ];
}

/**
 * Implements hook_config_schema_info_alter().
 */
function social_group_invite_config_schema_info_alter(array &$definitions): void {
  $definitions['social_group.settings']['mapping']['group_invite'] = [
    'type' => 'mapping',
    'label' => 'Group invite settings',
    'mapping' => [
      'invite_subject' => [
        'type' => 'text',
        'label' => 'Invite subject',
      ],
      'invite_message' => [
        'type' => 'text',
        'label' => 'Invite message',
      ],
      'invite_resend_message' => [
        'type' => 'text',
        'label' => 'Invite resend message',
      ],
      'email_verification' => [
        'type' => 'boolean',
        'label' => 'Email verification status',
      ],
      'invite_by_members' => [
        'type' => 'boolean',
        'label' => 'Invite by members status',
      ],
    ],
  ];
}

/**
 * Implements hook_menu_local_tasks_alter().
 */
function social_group_invite_menu_local_tasks_alter(&$data, $route_name, RefinableCacheableDependencyInterface $cacheability) {
  // Add tasks on these route for invite Groups/Events.
  if ($route_name === 'view.social_group_user_invitations.page_1') {
    $tabs_to_remove = [
      'social_user.groups',
      'social_user.stream',
      'social_user.topics',
      'social_user.events',
      'social_profile.information',
      'profile.user_page:profile',
      'entity.user.canonical',
      'entity.user.edit_form',
    ];
    foreach ($tabs_to_remove as $task_name) {
      if (!empty($data['tabs'][0][$task_name])) {
        unset($data['tabs'][0][$task_name]);
      }
    }
  }
  // Else we remove our custom group task.
  // This will render for example on the user profile etc.
  if ($route_name !== 'view.social_group_user_invitations.page_1' &&
    $route_name !== 'view.user_event_invites.page_user_event_invites') {
    if (!empty($data['tabs'][0]['social_group_invite.user_groups'])) {
      unset($data['tabs'][0]['social_group_invite.user_groups']);
    }
  }
}

/**
 * Implements hook_block_access().
 */
function social_group_invite_block_access(Block $block, $operation, AccountInterface $account) {
  $route_name = \Drupal::routeMatch()->getRouteName();
  $routes_to_check = [
    'entity.group_content.add_form',
    'entity.group_content.delete_form',
  ];

  // Only when on the confirm page of removing or adding invites
  // we remove the block for tasks and heros,the cancel button
  // will allow users to go back.
  if (in_array($route_name, $routes_to_check)) {
    $block_id = $block->getPluginId();
    $group_content = \Drupal::routeMatch()->getParameter('group_content');

    // Only if we are sure it's a group invitation.
    if (NULL !== $group_content &&
      NULL !== $group_content->getGroupContentType() &&
      $group_content->getGroupContentType()->getContentPluginId() === 'group_invitation') {
      // This is a list of the blocks that this function cares about,
      // if we're being called for a different block we exit early.
      $hide_blocks = [
        'group_hero_block',
        'local_tasks_block',
      ];
      if (!in_array($block_id, $hide_blocks)) {
        return AccessResult::neutral();
      }
      // Remove the blocks.
      return AccessResult::forbidden();
    }
  }
}

/**
 * Implements hook_preprocess_HOOK().
 */
function social_group_invite_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);
  }

  // See function social_group_preprocess_views_view(&$variables).
  // We have to override the local actions block.
  // and render our own block instance in the view for placement.
  // hook_theme_registry_alter will ensure our hooks is invoked later.
  // That is also why hook_menu_local_actions_alter won't work.
  // Get current group so we can build correct links.
  if ($view->id() === 'group_manage_members') {
    if (_social_group_invite_current_type_enabled_invites()) {
      $entity = \Drupal::entityTypeManager()->getStorage('block')
        ->load('socialinviteactionsblock');
      if (NULL !== $entity) {
        $block_content = \Drupal::entityTypeManager()
          ->getViewBuilder('block')
          ->view($entity);
        if (!empty($block_content)) {
          $variables['header']['actions'] = $block_content;
        }
      }
    }
    else {
      $entity = \Drupal::entityTypeManager()->getStorage('block')
        ->load('socialblue_local_actions');
      $variables['header']['actions'] = \Drupal::entityTypeManager()
        ->getViewBuilder('block')
        ->view($entity);
    }
  }
  // Implement button to go back to the group for our custom view.
  elseif (
    $view->id() === 'social_group_invitations' &&
    ($group = _social_group_get_current_group()) !== NULL
  ) {
    $variables['more'] = [
      '#title' => t('Back to group'),
      '#type' => 'link',
      '#url' => Url::fromRoute('entity.group.canonical', ['group' => $group->id()]),
      '#attributes' => [
        'class' => [
          'btn',
          'btn-default',
          'btn-raised',
          'waves-effect',
        ],
      ],
    ];
  }
}

/**
 * Implements hook_preprocess_HOOK().
 */
function social_group_invite_preprocess_views_view_field(&$variables) {
  $view = $variables['view'];
  // Alter some group invite fields for better representation in the
  // custom social overview.
  if ($view->id() === 'social_group_invitations') {
    // For invite status we want to show the readable name.
    if ($variables['field']->field === 'invitation_status' && !empty($variables['row']->_entity)) {
      /** @var \Drupal\group\Entity\GroupContent $entity */
      $entity = $variables['row']->_entity;
      // If we have a invite we check it's status.
      $field_value = _social_group_get_group_invite_value($entity, 'invitation_status');
      if (NULL !== $field_value) {
        // Field values are always returned as string,
        // so lets typecast them for now.
        $current_status = (integer) $field_value;
        switch ($current_status) {
          case GroupInvitation::INVITATION_PENDING:
            $output = t('Pending reply');
            break;

          case GroupInvitation::INVITATION_ACCEPTED:
            $output = t('Accepted and joined');
            break;

          case GroupInvitation::INVITATION_REJECTED:
            $output = t('Declined');
            break;

          default:
            $output = $variables['output'];
            break;
        }

        $variables['output'] = $output;
      }
    }
    // If user accepted the invite, don't render the actions.
    if ($variables['field']->field === 'dropbutton' && !empty($variables['row']->_entity)) {
      $entity = $variables['row']->_entity;
      $field_value = _social_group_get_group_invite_value($entity, 'invitation_status');
      // Field values are always returned as string,
      // so lets typecast them for now.
      if (NULL !== $field_value) {
        $current_status = (integer) $field_value;
        if ($current_status === GroupInvitation::INVITATION_ACCEPTED) {
          $variables['output'] = '';
        }
      }
    }
  }
}

/**
 * Return a group invites field value if it exists.
 *
 * @param \Drupal\group\Entity\GroupContent $entity
 *   Group invitation.
 * @param string $field_name
 *   The field name.
 *
 * @return string|null
 *   The field value or NULL if there isn't any.
 */
function _social_group_get_group_invite_value(GroupContent $entity, $field_name) {
  $field_value = NULL;
  if ($entity->hasField($field_name)) {
    try {
      /** @var \Drupal\Core\TypedData\ListInterface $field */
      $field = $entity->get($field_name)->first();
      if (NULL !== $field && !empty($field->getValue())) {
        $field_value = $field->getValue()['value'];
      }
    }
    catch (MissingDataException $e) {
    }
  }

  return $field_value;
}

/**
 * Check if current group it's group_type has invites enabled.
 *
 * @return bool
 *   TRUE if it's enabled.
 */
function _social_group_invite_current_type_enabled_invites() {
  $enabled = FALSE;
  $group = _social_group_get_current_group();
  // Check if group type has content plugin enabled.
  if ($group instanceof GroupInterface) {
    $group_type = $group->getGroupType();
    if ($group_type->hasContentPlugin('group_invitation')) {
      $enabled = TRUE;
    }
  }

  return $enabled;
}

/**
 * Implements hook_theme_registry_alter().
 */
function social_group_invite_theme_registry_alter(&$theme_registry) {
  // Unfortunately the preprocess functions aren't ordered by module weight.
  // Changing module weight doesn't work, also with dependency set to
  // social_group this should be dealt with but isnt.
  // So we enforce our preprocess after social_group.
  if (!empty($theme_registry['views_view']['preprocess functions'])) {
    $current_key = array_search('social_group_invite_preprocess_views_view', $theme_registry['views_view']['preprocess functions'], FALSE);
    unset($theme_registry['views_view']['preprocess functions'][$current_key]);
    // Give it a new key all the way at the end.
    $theme_registry['views_view']['preprocess functions'][] = 'social_group_invite_preprocess_views_view';
  }
}

/**
 * Implements hook_preprocess_HOOK().
 */
function social_group_invite_preprocess_page_title(&$variables) {
  // Add count of pending invites to the page title for a group.
  if (
    \Drupal::routeMatch()->getParameter('view_id') === 'social_group_invitations' &&
    !empty(\Drupal::routeMatch()->getParameter('group')) &&
    ($group = _social_group_get_current_group()) !== NULL
  ) {
    $loader = \Drupal::service('ginvite.invitation_loader');
    $count = count($loader->loadByProperties(['gid' => $group->id()]));
    $title = \Drupal::translation()->formatPlural($count, '1 membership invite for group: @group_name', '@count membership invites for group: @group_name', ['@group_name' => $group->label()]);
    $variables['title'] = $title;
    $variables['#cache']['tags'][] = 'group_content_list:group:' . $group->id();
    $variables['#cache']['tags'][] = 'group_content_list:plugin:group_invitation:group:' . $group->id();
  }
  // Add count of pending invites to the page title for a user.
  if (\Drupal::routeMatch()->getParameter('view_id') === 'social_group_user_invitations' &&
    !empty(\Drupal::routeMatch()->getParameter('user'))) {
    $loader = \Drupal::service('ginvite.invitation_loader');
    $count = count($loader->loadByUser());
    $translation = \Drupal::translation()->formatPlural($count, '1 group invite', '@count group invites');
    $variables['title'] = $translation;

    $user = \Drupal::routeMatch()->getParameter('user');
    if (is_string($user)) {
      $variables['#cache']['tags'][] = 'group_content_list:entity:' . $user;
      $variables['#cache']['tags'][] = 'group_content_list:plugin:group_invitation:entity:' . $user;
    }
  }
}

/**
 * Implements hook_form_alter().
 */
function social_group_invite_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  // We need to write a custom form alter, since the ginvite module
  // has it's own routes, with no group context that loads the
  // join form. Decline is done differently. Due to the fact on joining
  // users need to fill in their membership fields.
  $social_group_types = [
    'open_group',
    'closed_group',
    'public_group',
  ];

  // Also include any added optional group types.
  \Drupal::moduleHandler()->alter('social_group_types', $social_group_types);

  $join_forms = [];
  foreach ($social_group_types as $social_group_type) {
    $join_forms[] = "group_content_{$social_group_type}-group_membership_group-join_form";
  }

  // Perform alterations on joining / leaving groups.
  if (in_array($form_id, $join_forms)) {
    // Add custom redirect form submit.
    $form['actions']['submit']['#submit'][] = '_social_group_invite_action_form_submit';
  }
}

/**
 * Implements hook_form_BASE_FORM_ID_alter().
 */
function social_group_invite_form_group_form_alter(array &$form, FormStateInterface $form_state): void {
  if (!isset($form['field_group_invite_by_member'])) {
    return;
  }

  // Wrapper for the sharing/invitation group settings field.
  $form['group_sharing'] = [
    '#type' => 'container',
    '#weight' => 120,
    'label' => [
      '#type' => 'html_tag',
      '#tag' => 'label',
      '#value' => t('Sharing Options'),
      '#attributes' => [
        'class' => [
          'control-label',
        ],
      ],
    ],
    'field_group_invite_by_member' => $form['field_group_invite_by_member'],
  ];

  // Remove default field as it was added to the wrapper and to remove
  // duplicates on the form.
  unset($form['field_group_invite_by_member']);

  // Add wrapper for the sharing/invitation to the specific field group.
  if (isset($form['#fieldgroups']['group_access_permissions'])) {
    $form['#fieldgroups']['group_access_permissions']->children[] = 'group_sharing';
  }
}

/**
 * Form submit for group invite join form.
 */
function _social_group_invite_action_form_submit($form, FormStateInterface $form_state) {
  $invite_content = \Drupal::routeMatch()->getParameter('group_content');
  $group = '';

  if ($invite_content instanceof GroupContentInterface) {
    $group = $invite_content->getGroup();
  }
  elseif (is_string($invite_content)) {
    $group_content = \Drupal::entityTypeManager()->getStorage('group_content')
      ->load($invite_content);

    if ($group_content instanceof GroupContentInterface) {
      $group = $group_content->getGroup();
    }
  }

  // Redirect user back to the group they joined.
  if ($group instanceof GroupInterface) {
    $form_state->setRedirect('entity.group.canonical', [
      'group' => $group->id(),
      [],
    ]);
  }
}

/**
 * A specific Group Type Role, will get default invite permissions.
 *
 * @param \Drupal\group\Entity\GroupRoleInterface $role
 *   The role that we will add the default permissions to.
 *
 * @throws \Drupal\Core\Entity\EntityStorageException
 */
function social_group_invite_set_default_permissions_for_role_on_group_type(GroupRoleInterface $role) {
  $role->grantPermissions([
    'delete any invitation',
    'delete own invitations',
    'invite users to group',
    'view group invitations',
  ]);
  $role->save();
}

/**
 * A specific Group Type, where will assign invite permissions.
 *
 * @param \Drupal\group\Entity\GroupTypeInterface $group_type
 *   The Group type role that we will add the default permissions to.
 *
 * @throws \Drupal\Core\Entity\EntityStorageException
 */
function social_group_invite_set_default_permissions_for_group_type(GroupTypeInterface $group_type) {
  /** @var \Drupal\group\Entity\GroupRoleInterface $roles */
  $roles = $group_type->getRoles();
  // Make sure this is done for managers and admins.
  if (!empty($roles[$group_type->id() . '-group_manager'])) {
    $role = $roles[$group_type->id() . '-group_manager'];
    social_group_invite_set_default_permissions_for_role_on_group_type($role);
  }
  if (!empty($roles[$group_type->id() . '-group_admin'])) {
    $role = $roles[$group_type->id() . '-group_admin'];
    social_group_invite_set_default_permissions_for_role_on_group_type($role);
  }
}

/**
 * Implements hook_preprocess_activity().
 */
function social_group_invite_preprocess_activity(&$variables) {
  $activity = $variables['elements']['#activity'];
  $related_entity = $activity->getRelatedEntity();
  // If the related entity is a piece of GroupContent and coming from the
  // ginvite group_invitation plugin, lets link the entity to the group itself.
  if (!empty($related_entity) &&
    $related_entity instanceof GroupContent &&
    $related_entity->getGroupContentType()->getContentPluginId() === 'group_invitation') {
    $group = $related_entity->getGroup();
    if ($group instanceof GroupInterface) {
      $variables['full_url'] = $group->toUrl('canonical');
    }
  }
}

/**
 * Implements hook_preprocess_join().
 */
function social_group_invite_preprocess_join(array &$variables): void {
  $entity = $variables['entity'];

  if (!$entity instanceof GroupInterface) {
    return;
  }

  $url = Url::fromRoute('ginvite.invitation.bulk', [
    'group' => $entity->id(),
  ]);

  if (!$url->access()) {
    return;
  }

  $config = \Drupal::config('social_group.settings');

  if (!$config->get('group_invite.invite_by_members')) {
    return;
  }

  // For groups that only group members have access to, we should not show the
  // share link functionality.
  if (
    $entity->hasField('field_flexible_group_visibility') &&
    !$entity->field_flexible_group_visibility->isEmpty() &&
    $entity->field_flexible_group_visibility->value === 'members' ||
    in_array($entity->bundle(), ['closed_group', 'secret_group'])
  ) {
    $group_url = NULL;
  }
  else {
    $group_url = Url::fromRoute('view.group_information.page_group_about', [
      'group' => $entity->id(),
    ], [
      'absolute' => TRUE,
    ]);
  }

  // Show share/invite button only if member is allowed to do that.
  $variables['invite_widget'] = [
    '#theme' => 'invite_to_group_by_member',
    '#url' => $group_url,
    '#link' => Link::fromTextAndUrl(t('Invite via email'), $url)
      ->toRenderable(),
  ];
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function social_group_invite_form_social_group_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  $invite_config = \Drupal::config('social_group.settings')
    ->get('group_invite');

  $form['group_invite'] = [
    '#type' => 'details',
    '#title' => t('Group invite settings'),
    '#weight' => 30,
  ];

  // "Group invite" allows to have invite message per each group where
  // this group content type plugin is enabled.
  // Here we allow for SM to have separate invite message for each group type.
  $form['group_invite']['invite_message_tabs'] = [
    '#type' => 'vertical_tabs',
  ];
  $configurations = (array) \Drupal::entityQuery('group_content_type')
    ->condition('content_plugin', 'group_invitation')
    ->condition('status', TRUE)
    ->accessCheck(FALSE)
    ->execute();

  $default_subject = '[user:display-name] has invited you to join a group on [site:name].';
  $default_message = "Hi,<br /><br /> I would like to invite you to join my group [group:title] on [site:name].<br /><br />Kind regards,<br />[user:display-name]<br /><br />\r\n<table class=\"btn-wrapp\">\r\n\t<tbody>\r\n\t\t<tr>\r\n\t\t\t<td class=\"align-center\"><a class=\"btn-link btn-link-bg btn-link-one\" href=\"[group_content:register_link]\">View group</a></td>\r\n\t\t\t<td class=\"align-center\"><a class=\"btn-link btn-link-bg btn-link-one\" href=\"[site:url]\">About [site:name]</a></td>\r\n\t\t</tr>\r\n\t</tbody>\r\n</table>";

  foreach ($configurations as $name) {
    $config = \Drupal::configFactory()->getEditable("group.content_type.$name");

    /** @var \Drupal\group\Entity\GroupTypeInterface $group_type */
    $group_type = \Drupal::entityTypeManager()->getStorage('group_type')
      ->load($config->get('group_type'));

    $form[$name] = [
      '#type' => 'details',
      '#title' => $group_type->label(),
      '#group' => 'invite_message_tabs',
      '#tree' => TRUE,
    ];

    $form[$name]['invitation_subject'] = [
      '#type' => 'textfield',
      '#title' => t('Subject'),
      '#default_value' => $config->get('plugin_config.invitation_subject') ?? ($invite_config['invite_subject'] ?? $default_subject),
    ];

    $form[$name]['invitation_body'] = [
      '#type' => 'textarea',
      '#title' => t('Message'),
      '#default_value' => $config->get('plugin_config.invitation_body') ?? ($invite_config['invite_message'] ?? $default_message),
    ];
  }

  // Default fallback invite message.
  $form['default'] = [
    '#type' => 'details',
    '#title' => t('Default'),
    '#description' => t('If any of above is empty this will be used as fallback.'),
    '#group' => 'invite_message_tabs',
  ];
  $form['default']['invite_subject'] = [
    '#type' => 'textfield',
    '#title' => t('Subject'),
    '#default_value' => $invite_config['invite_subject'] ?? $default_subject,
    '#required' => TRUE,
  ];
  $form['default']['invite_message'] = [
    '#type' => 'textarea',
    '#title' => t('Message'),
    '#default_value' => $invite_config['invite_message'] ?? $default_message,
    '#required' => TRUE,
  ];

  // Allow SM to override invite message for resending invites.
  $form['group_invite']['override_resend'] = [
    '#type' => 'checkbox',
    '#title' => t('Override invite message when resend invites'),
    '#description' => t('If checked you will be prompted to define a message users will receive on resending invites.'),
    '#default_value' => !empty($invite_config['invite_resend_message']),
  ];
  $form['group_invite']['invite_resend_message'] = [
    '#type' => 'textarea',
    '#placeholder' => t('Type a message on resending invites'),
    '#default_value' => $invite_config['invite_resend_message'] ?? '',
    '#states' => [
      'visible' => [
        ':input[name="override_resend"]' => ['checked' => TRUE],
      ],
    ],
  ];

  $form['group_invite']['email_verification'] = [
    '#type' => 'checkbox',
    '#title' => t('Allow invited user to skip email verification'),
    '#default_value' => !empty($invite_config['email_verification']) ?? FALSE,
  ];
  $form['group_invite']['invite_by_members'] = [
    '#type' => 'checkbox',
    '#title' => t('Group managers are able to allow members send invites'),
    '#description' => t('If enabled, group managers can allow group members to send invitations to their group.'),
    '#default_value' => !empty($invite_config['invite_by_members']) ?? FALSE,
  ];

  $form['#submit'][] = '_social_group_invite_settings_submit';
}

/**
 * Sets settings for invite mail message.
 */
function _social_group_invite_settings_submit($form, FormStateInterface $form_state) {
  \Drupal::configFactory()->getEditable('social_group.settings')
    ->set('group_invite.invite_subject', $form_state->getValue('invite_subject'))
    ->set('group_invite.invite_message', $form_state->getValue('invite_message'))
    ->set('group_invite.invite_resend_message', (bool) $form_state->getValue('override_resend')
      ? $form_state->getValue('invite_resend_message')
      : ''
    )
    ->set('group_invite.email_verification', $form_state->getValue('email_verification'))
    ->set('group_invite.invite_by_members', $form_state->getValue('invite_by_members'))
    ->save();

  // Update invite messages per group type.
  $configurations = (array) \Drupal::entityQuery('group_content_type')
    ->condition('content_plugin', 'group_invitation')
    ->condition('status', TRUE)
    ->accessCheck(FALSE)
    ->execute();

  foreach ($configurations as $name) {
    $config = \Drupal::configFactory()->getEditable("group.content_type.$name");
    $subject = $form_state->getValue([$name, 'invitation_subject']);
    if ($config->get('plugin_config.invitation_subject') !== $subject) {
      $config->set('plugin_config.invitation_subject', $subject);
      $config->save();
    }
    $body = $form_state->getValue([$name, 'invitation_body']);
    if ($config->get('plugin_config.invitation_body') !== $body) {
      $config->set('plugin_config.invitation_body', $body);
      $config->save();
    }
  }
}

/**
 * Implements hook_tokens_alter().
 */
function social_group_invite_tokens_alter(array &$replacements, array $context, BubbleableMetadata $bubbleable_metadata) {
  if (
    !empty($replacements) &&
    isset($context["data"]["group"]) &&
    $context["type"] === 'group_content'
  ) {
    /** @var \Drupal\group\Entity\GroupContentInterface $group_content */
    $group_content = $context["data"]["group_content"];
    $group_content_id = $group_content->get('id')->getString();

    // We are using a destination for use with the register link.
    // This destination is only used when user's don't have an account yet.
    $destination = 'destination=/social-group-invite/' . $group_content_id . '/accept';
    $group_invite_url = Url::fromRoute('social_group_invite.invitation.accept', ['group_content' => $group_content_id], ['absolute' => TRUE])->toString();

    // For groups that only group members have access to, we should change the
    // destination to the user's invites page.
    $group = $group_content->getGroup();
    if (
      ($group->hasField('field_flexible_group_visibility') &&
      !$group->get('field_flexible_group_visibility')->isEmpty() &&
      $group->get('field_flexible_group_visibility')->getValue()[0]['value'] === 'members') ||
      in_array($group->bundle(), ['closed_group', 'secret_group'])
    ) {

      // Update the destination for use with the register link.
      $url = Url::fromRoute('social_core.my_invites')->toString();
      $destination = 'destination=' . $url;
      $group_invite_url = Url::fromRoute('social_core.my_invites', [], ['absolute' => TRUE])->toString();
    }
    // Attach destination to accept invite. So, after sign up/sign step the
    // invite will be accepted and user  will be redirected to the invited page.
    if (
      isset($context["tokens"]["register_link"]) &&
      $context["tokens"]["register_link"] === '[group_content:register_link]'
    ) {
      // For new users for register_link token.
      $link = $replacements["[group_content:register_link]"];
      $replacements["[group_content:register_link]"] = "{$link}&{$destination}";
    }
    if (
      isset($context["tokens"]["my_invitations_link"]) &&
      $context["tokens"]["my_invitations_link"] === '[group_content:my_invitations_link]'
    ) {
      // For existing users for my_invitations_link token.
      $replacements["[group_content:my_invitations_link]"] = $group_invite_url;
    }
  }
}

/**
 * Implements hook_entity_form_display_alter().
 */
function social_group_invite_entity_form_display_alter(EntityFormDisplayInterface $form_display, array $context): void {
  if ($form_display->id() !== 'group.flexible_group.default') {
    return;
  }
  $group_settings = \Drupal::config('social_group.settings');

  // The Group manager is able to allow members to share group or invite to it
  // only if SM+ is enabled this feature.
  if (
    !$form_display->getComponent('field_group_invite_by_member') &&
    (bool) $group_settings->get('group_invite.invite_by_members')
  ) {
    $form_display->setComponent('field_group_invite_by_member', [
      'type' => 'boolean_checkbox',
      'region' => 'content',
      'settings' => [
        'display_label' => TRUE,
      ],
      'weight' => 99,
      'third_party_settings' => [],
    ]);
  }
}

/**
 * Implements hook_entity_operation_alter().
 */
function social_group_invite_entity_operation_alter(array &$operations, EntityInterface $entity): void {
  if (\Drupal::routeMatch()->getRouteName() !== 'view.social_group_invitations.page_1') {
    return;
  }

  /** @var \Drupal\group\Entity\GroupContentInterface $entity */
  if ($entity->getEntityTypeId() !== 'group_content') {
    return;
  }

  if ($entity->getContentPlugin()->getPluginId() === 'group_invitation') {
    // Hide "view" operation as a redundant on this page.
    unset($operations['view']);
    // Rename default operation.
    if (!empty($operations['delete'])) {
      $operations['delete']['title'] = t('Remove invitation');
    }
    // Add a new "resend invite" operation.
    $operations['resend-invite'] = [
      'title' => t('Send reminder'),
      'weight' => 1,
      'url' => Url::fromRoute('social_group_invite.resend_invite',
        [
          'group' => $entity->getGroup()->id(),
          'group_content' => $entity->id(),
        ]
      ),
    ];
  }
}

/**
 * Implements hook_preprocess_HOOK() for "page".
 */
function social_group_invite_preprocess_page(array &$variables): void {
  if (in_array(\Drupal::routeMatch()->getRouteName(), [
    'social_group_invite.resend_invite',
    'social_group_invite.views_bulk_operations.confirm',
  ])) {
    // Hide redundant region on the page.
    $variables['page']['complementary_top'] = [];
  }
}

/**
 * Implements hook_field_formatter_info_alter().
 */
function social_group_invite_field_formatter_info_alter(array &$info): void {
  // If a user has been invited to the group to which user does not have an
  // access, the user cannot see the group title on the group invite page.
  // The problem is default group title rendered through the
  // "entity_reference_label" field formatter checks access to the entity.
  // Altered class to skip this check for the group entity on the group invites
  // page.
  if (isset($info['entity_reference_label'])) {
    $info['entity_reference_label']['class'] = EntityReferenceLabelFormatterOverrider::class;
  }
}
