<?php

/**
 * @file
 * The Social event module.
 */

use Drupal\block\Entity\Block;
use Drupal\bootstrap\Bootstrap;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Access\AccessResultInterface;
use Drupal\Core\Access\AccessResultNeutral;
use Drupal\Core\Block\BlockPluginInterface;
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\Core\Datetime\TimeZoneFormHelper;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Link;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\Url;
use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface;
use Drupal\group\Entity\GroupContent;
use Drupal\group\GroupMembershipLoaderInterface;
use Drupal\menu_link_content\MenuLinkContentInterface;
use Drupal\node\Entity\Node;
use Drupal\node\NodeInterface;
use Drupal\social_event\Controller\SocialEventController;
use Drupal\social_event\Entity\Node\Event;
use Drupal\social_event\EventEnrollmentInterface;
use Drupal\social_event\SocialEventDateAllDay;
use Drupal\views\Plugin\views\query\QueryPluginBase;
use Drupal\views\ViewExecutable;

/**
 * Implements hook_theme().
 */
function social_event_theme(): array {
  return [
    'node__event__email_card' => [
      'base hook' => 'node',
    ],
    'event_date_advanced' => [
      'variables' => [
        'node' => NULL,
        'view_mode' => NULL,
      ],
      'file' => 'social_event.theme.inc',
    ],
    'event_location_address' => [
      'variables' => [
        'node' => NULL,
        'view_mode' => NULL,
      ],
      'file' => 'social_event.theme.inc',
    ],
    'event_enrollment_confirmation' => [
      'base hook' => 'node',
    ],
  ];
}

/**
 * Implements hook_entity_bundle_info_alter().
 *
 * Set node bundle class for "Event" node type. This allows to encapsulate
 * related to the event functionality inside an event node object.
 *
 * @see https://www.drupal.org/node/3191609
 */
function social_event_entity_bundle_info_alter(array &$bundles): void {
  if (isset($bundles['node']['event'])) {
    $bundles['node']['event']['class'] = Event::class;
  }
}

/**
 * Implements hook_preprocess_HOOK().
 */
function social_event_preprocess_event_enrollment_confirmation(array &$variables): void {
  /** @var \Drupal\node\NodeInterface $node */
  $node = $variables['node'];
  $variables['event_date'] = _social_event_format_date($node, 'hero');
  $options = [
    'attributes' => [
      'class' => [
        'btn',
        'btn-sm',
        'btn-primary',
      ],
    ],
  ];

  $who_going_url = Url::fromRoute('view.event_manage_enrollments.page_manage_enrollments', ['node' => $node->id()], $options);
  if ($who_going_url->access(\Drupal::currentUser())) {
    $variables['who_going_link'] = Link::fromTextAndUrl(t('See who else is going'), $who_going_url);
  }
}

/**
 * Alter the visibility of blocks coming from the event module.
 */
function social_event_social_core_block_visibility_path() {
  $blocks = [
    'social_page_title_block' => [
      '*/all-enrollment-requests/confirm-decline/*',
      '*/invite/email',
      '*/invite/confirm',
      '*/event-invites',
      '*/all-enrollments/add-enrollees',
    ],
    'views_block:managers-event_managers' => [
      '*/all-enrollment-requests/confirm-decline/*',
      '*/invite/email',
      '*/invite/confirm',
    ],
    'views_block:event_enrollments-event_enrollments' => [
      '*/all-enrollment-requests/confirm-decline/*',
      '*/invite/email',
      '*/invite/confirm',
    ],
  ];

  return $blocks;
}

/**
 * Implements hook_form_form_ID_alter().
 *
 * Enhance the exposed filter form of the event overview.
 */
function social_event_form_views_exposed_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  if ($form['#id'] === 'views-exposed-form-events-events-overview') {

    $form['status']['#options'][0] = t('Unpublished');
    $form['status']['#options'][1] = t('Published');

    $account_uid = \Drupal::routeMatch()->getParameter('user');
    $current_uid = \Drupal::currentUser()->id();

    if ($account_uid !== $current_uid) {
      $form['status']['#access'] = FALSE;
    }

    // Enable the reset button.
    // @todo make sure the block content refreshes on submit as well (AJAX).
    $form['actions']['reset']['#access'] = TRUE;
    // @todo make sure exposed form filtering redirects to the proper view
    // page, when views is updated.
    $form['#action'] = '/user/' . $account_uid . '/events';
  }
  if ($form['#id'] === 'views-exposed-form-group-events-page-group-events') {
    $group_from_route = _social_group_get_current_group();

    // Get group from route.
    if (!empty($group_from_route)) {
      $group_id = $group_from_route->id();
    }

    $form['actions']['reset']['#access'] = TRUE;

    // Make sure we redirect to the current group page.
    $form['#action'] = '/group/' . $group_id . '/events';
  }
  // Only on the community or explore events overview.
  if ($form['#id'] === 'views-exposed-form-upcoming-events-page-community-events') {
    // Update the filter values with better labels.
    if (!empty($form['field_enroll_method_value']) && !empty($form['field_enroll_method_value']['#options'])) {
      if (array_key_exists(1, $form['field_enroll_method_value']['#options'])) {
        $form['field_enroll_method_value']['#options'][1] = t('Open to enroll');
      }
      if (array_key_exists(2, $form['field_enroll_method_value']['#options'])) {
        $form['field_enroll_method_value']['#options'][2] = t('Request to enroll');
      }
      if (array_key_exists(3, $form['field_enroll_method_value']['#options'])) {
        $form['field_enroll_method_value']['#options'][3] = t('Invite-only');
      }
    }
  }
}

/**
 * Check if the user is allowed to manage Enrollments.
 *
 * @param \Drupal\node\NodeInterface|null $node
 *   The node the current user could be organizer of.
 * @param bool $skip_trusted_roles
 *   Should we skip CM/SM with the manage everything enrollments.
 *
 * @return bool
 *   If the user is actually a manager or organizer.
 */
function social_event_manager_or_organizer(NodeInterface $node = NULL, $skip_trusted_roles = FALSE) {
  $social_event_manager_or_organizer = &drupal_static(__FUNCTION__);

  if (!isset($social_event_manager_or_organizer)) {
    $account = \Drupal::currentUser();

    // Allow if user has the manage everything permission.
    // We can skip this to make sure we truly only check organizer & managers
    // used for context in notifications.
    if ($skip_trusted_roles === FALSE && $account->hasPermission('manage everything enrollments')) {
      $result = TRUE;
    }

    if (!$node && !isset($result)) {
      // Get the current event node.
      $node = social_event_get_current_event();

      // If there's no node, we might be looking at an event enrollment.
      if (!$node) {
        // If we are altering / deleting an Event Enrollment check if user
        // is manager or organizer from the referenced node.
        $event_enrollment = \Drupal::routeMatch()
          ->getParameter('event_enrollment');

        if ($event_enrollment instanceof EventEnrollmentInterface) {
          $node = $event_enrollment->field_event->entity;
        }
      }
    }

    // If we now have a node we can check if there are event managers.
    if ($node instanceof NodeInterface && !isset($result) && $node->bundle() === 'event') {
      // The event owner has access.
      if ($node->getOwnerId() === $account->id()) {
        $result = TRUE;
      }

      // Check if the user is an event manager/organizer.
      if (!isset($result) && $node->hasField('field_event_managers') && !$node->field_event_managers->isEmpty()) {
        foreach ($node->field_event_managers->getValue() as $value) {
          if ($value && $value['target_id'] === $account->id()) {
            $result = TRUE;
            break;
          }
        }
      }
    }

    // No result means the user does not have access.
    if (!isset($result)) {
      $result = FALSE;
    }

    $social_event_manager_or_organizer = $result;
  }

  return $social_event_manager_or_organizer;
}

/**
 * Implements hook_views_query_alter().
 */
function social_event_views_query_alter(ViewExecutable $view, QueryPluginBase $query) {
  if ($view->id() == 'events' && $view->getDisplay()->display['id'] == 'events_overview') {
    $account_uid = \Drupal::routeMatch()->getParameter('user');
    $current_uid = \Drupal::currentUser()->id();
    if ($view->exposed_raw_input['status'] == NodeInterface::PUBLISHED || $account_uid !== $current_uid) {
      $query->where[1]['conditions'][] = [
        'field' => 'node_field_data.status',
        'value' => NodeInterface::PUBLISHED,
        'operator' => '=',
      ];
    }
  }
}

/**
 * Implements hook_block_view_alter().
 *
 * Add a title to the exposed filter block on the events overview.
 */
function social_event_block_view_alter(array &$build, BlockPluginInterface $block) {
  // @todo check out why this happens, is this is a views bug?
  if (isset($build['#plugin_id']) && $build['#plugin_id'] === 'views_exposed_filter_block:events-events_overview') {
    $build['#configuration']['label'] = $build['#configuration']['views_label'];
  }
}

/**
 * Implements hook_menu_local_tasks_alter().
 */
function social_event_menu_local_tasks_alter(&$data, $route_name) {
  $can_show_enrollments_link = FALSE;
  $routes_to_check = _social_event_menu_local_tasks_routes();

  if (in_array($route_name, $routes_to_check)) {
    $node = \Drupal::service('current_route_match')->getParameter('node');
    if (!is_null($node) && (!$node instanceof Node)) {
      $node = Node::load($node);
    }
    if ($node instanceof Event) {
      $can_show_enrollments_link = $node->isEnrollmentEnabled();
    }

  }
  // Place this here, since hiding it should happen always
  // and not only on the mentioned routes.
  if (!$can_show_enrollments_link) {
    unset($data['tabs'][0]['views_view:view.event_enrollments.view_enrollments']);
  }
}

/**
 * Implements hook_ENTITY_TYPE_view_alter().
 */
function social_event_node_view_alter(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display) {
  $current_user = \Drupal::currentUser();

  if (
    !$current_user->isAnonymous() &&
    $entity instanceof Event
  ) {
    $uid = $current_user->id();
    $nid = $entity->id();

    // Create our custom enrollment tag so we can also invalidate f.e. teasers
    // cache when people enrol. See EnrollActionForm->submitForm().
    $enrollment_tag = 'enrollment:' . $nid . '-' . $uid;
    $build['#cache']['tags'][] = $enrollment_tag;
    $build['#cache']['contexts'][] = 'user';

    if (empty($nid)) {
      return;
    }

    $storage = \Drupal::entityTypeManager()->getStorage('event_enrollment');

    // Prepare 'Enrolled' label for teasers.
    $enrolled = $storage->loadByProperties([
      'field_account' => $uid,
      'field_event' => $nid,
      'field_enrollment_status' => 1,
    ]);

    if ($enrolled) {
      $build['enrolled'] = [
        '#type' => '#text_field',
        '#markup' => t('You have enrolled'),
      ];
    }

    // See src/Entity/Node/Event::isEnrollmentEnabled().
    if ($entity->isEnrollmentEnabled()) {
      // Prepare enrollments counter for teasers.
      $enrollments_count = $storage->getQuery()
        ->condition('field_event', $nid)
        ->condition('field_enrollment_status', 1)
        ->accessCheck()
        ->count()
        ->execute();

      $build['enrollments_count'] = [
        '#type' => '#text_field',
        '#markup' => $enrollments_count,
      ];
    }

    // Get user current date and time.
    $user_time = new DateTime(
      date(DateTimeItemInterface::DATETIME_STORAGE_FORMAT),
      new DateTimeZone(date_default_timezone_get())
    );

    // Convert datetime value to UTC timestamp.
    $user_time->setTimezone(new DateTimeZone(DateTimeItemInterface::STORAGE_TIMEZONE));
    $current_date = (int) $user_time->format('U');

    // Get event start date.
    $event_start_date = new DateTime(
      $entity->get('field_event_date')->getString(),
      new DateTimeZone(DateTimeItemInterface::STORAGE_TIMEZONE)
    );
    // Convert datetime value to UTC timestamp.
    $event_start_date = $event_start_date->format('U');

    // Check if 'field_event_date_end' value is exist.
    // @todo Remove this check if we going to make this field required.
    if ($entity->get('field_event_date_end')->getString() != NULL) {
      // Get event end date.
      $event_end_date = new DateTime(
        $entity->get('field_event_date_end')->getString(),
        new DateTimeZone(DateTimeItemInterface::STORAGE_TIMEZONE)
      );
      // Convert datetime value to UTC timestamp.
      $event_end_date = $event_end_date->format('U');
    }

    $ongoing_field = [
      '#type' => '#text_field',
      '#markup' => t('Ongoing'),
    ];

    // Check if event is ongoing.
    // Output 'Ongoing' label.
    // Set max-age cache for ongoing event.
    if ($current_date < $event_start_date) {
      $build['#cache']['max-age'] = $event_start_date - $current_date;
    }
    else {
      // @todo Remove this check if 'field_event_date_end' is required.
      if (isset($event_end_date) && $current_date <= $event_end_date) {
        $build['ongoing'] = $ongoing_field;
        $build['#cache']['max-age'] = $event_end_date - $current_date;
      }
      // @todo Remove this check if 'field_event_date_end' is required.
      elseif (!isset($event_end_date)) {
        $build['ongoing'] = $ongoing_field;
      }
    }
  }
}

/**
 * Prepares variables for node templates.
 *
 * @param array $variables
 *   An associative array containing:
 *   - elements: An array of elements to display in view mode.
 *   - node: The node object.
 *   - view_mode: View mode; e.g., 'full', 'teaser', etc.
 */
function social_event_preprocess_node(array &$variables) {
  $view_mode = $variables['view_mode'];
  $node = $variables['node'];

  if ($node->getType() === 'event') {
    // Date as a text.
    $variables['event_date'] = _social_event_format_date($node, $view_mode);
    $variables['#cache']['contexts'][] = 'timezone';

    // This variable contains icon as well.
    $variables['event_date_advanced'] = [
      '#theme' => 'event_date_advanced',
      '#node' => $node,
      '#view_mode' => $view_mode,
    ];

    // Contains combined with a few fields location information.
    $variables['event_location_address'] = [
      '#theme' => 'event_location_address',
      '#node' => $node,
      '#view_mode' => $view_mode,
    ];

    if ($view_mode === 'hero') {
      // Add Enroll button.
      if (empty($variables['event_enrollment'])) {
        $form = \Drupal::formBuilder()
          ->getForm('Drupal\social_event\Form\EnrollActionForm');
        $render_array = [
          'enroll_action_form' => $form,
        ];
        $variables['event_enrollment'] = $render_array;
      }
    }
  }
}

/**
 * Implements theme_preprocess_HOOK() for email.
 */
function social_event_preprocess_email(array &$variables): void {
  // Add library with styles for event.
  $variables['#attached']['library'][] = 'social_event/event';
}

/**
 * Implements hook_views_data_alter().
 */
function social_event_views_data_alter(array &$data) {
  $data['node']['event_enrolled_or_created_filter'] = [
    'title' => t('Event enrolled or created'),
    'filter' => [
      'title' => t('Event enrolled or created'),
      'help' => t('Enable events for on the user profiles.'),
      'field' => 'field_event',
      'id' => 'event_enrolled_or_created',
    ],
  ];
  $data['node__field_event_date']['event_date'] = [
    'title' => t('Event date'),
    'group' => t('Social'),
    'filter' => [
      'title' => t('Event date'),
      'help' => t('Filter events by start date and end date'),
      'id' => 'social_event_date_filter',
      'field' => 'field_event_date_value',
    ],
    'sort' => [
      'title' => t('Event sorting (passed and upcoming)'),
      'help' => t('For upcoming events sort ASC by start date and for passed events change order to DESC.'),
      'id' => 'social_event_date_sort',
      'field' => 'field_event_date_value',
    ],
  ];
}

/**
 * Implements hook_ENTITY_TYPE_insert().
 */
function social_event_flagging_insert(EntityInterface $entity) {
  $group_type_ids = \Drupal::config('social_event.settings')->get('enroll');

  if (empty($group_type_ids)) {
    return;
  }

  $current_user = \Drupal::currentUser();
  $owner = $entity->getOwnerId() == $current_user->id();
  $is_node = $entity->entity_type->value == 'node';
  $following = $entity->getFlagId() == 'follow_content';

  if (!($owner && $is_node && $following)) {
    return;
  }

  $nid = $entity->entity_id->value;
  $node = \Drupal::entityTypeManager()->getStorage('node')->load($nid);

  /** @var \Drupal\group\Entity\GroupContentInterface $groupcontent */
  foreach (GroupContent::loadByEntity($node) as $groupcontent) {
    /** @var \Drupal\group\Entity\GroupInterface $group */
    $group = $groupcontent->getGroup();

    $allowed_type = in_array($group->bundle(), $group_type_ids);
    $is_member = $group->getMember($current_user) instanceof GroupMembershipLoaderInterface;

    if ($allowed_type && !$is_member) {
      $account = \Drupal::entityTypeManager()
        ->getStorage('user')
        ->load($current_user->id());

      $group->addMember($account);
    }
  }
}

/**
 * Implements hook_ENTITY_TYPE_delete().
 */
function social_event_user_delete(EntityInterface $entity) {
  $storage = \Drupal::entityTypeManager()->getStorage('event_enrollment');
  $entities = $storage->loadByProperties(['user_id' => $entity->id()]);
  $storage->delete($entities);
}

/**
 * Formats the event start end date.
 *
 * @param \Drupal\node\NodeInterface $event
 *   Event entity.
 * @param string|null $view_mode
 *   The view mode.
 */
function _social_event_format_date(NodeInterface $event, string $view_mode = NULL): string {
  $event_date = '';
  $event_config = \Drupal::config('social_event.settings');
  $add_timezone = $event_config->get('show_user_timezone');

  // This will get the users timezone, which is either set by the user
  // or defaults back to the sites timezone if the user didn't select any.
  $timezone = date_default_timezone_get();
  $user_timezone = date('T');
  if (in_array($user_timezone[0], ['+', '-'])) {
    $user_timezone = 'UTC' . $user_timezone;
  }
  // Timezone that dates should be stored in.
  $utc_timezone = DateTimeItemInterface::STORAGE_TIMEZONE;

  // Get start and end dates.
  if (!$event->get('field_event_date')->isEmpty()) {
    // Since dates are stored as UTC, we will declare our event values
    // as UTC. So we can actually calculate them back to the users timezone.
    // This is necessary because we do not store the event value as being UTC
    // so declaring it with setTimezone will result in wrong values.
    $start_datetime = new DateTime($event->get('field_event_date')->getString(), new DateTimeZone($utc_timezone));
    $start_datetime->setTimezone(new DateTimeZone($timezone));
    $start_datetime = $start_datetime->getTimestamp();
  }
  $end_datetime = NULL;
  if (!$event->get('field_event_date_end')->isEmpty()) {
    // Since dates are stored as UTC, we will declare our event values
    // as UTC. So we can actually calculate them back to the users timezone.
    // This is necessary because we do not store the event value as being UTC
    // so declaring it with setTimezone will result in wrong values.
    $end_datetime = new DateTime($event->get('field_event_date_end')->getString(), new DateTimeZone($utc_timezone));
    // We now calculate it back to the users timezone.
    $end_datetime->setTimezone(new DateTimeZone($timezone));
    $end_datetime = $end_datetime->getTimestamp();
  }

  // Get date and time formats.
  $date_formatter = \Drupal::service('date.formatter');
  $date_format = ($view_mode === 'hero') ? 'social_long_date' : 'social_medium_extended_date';
  $time_format = 'social_time';
  $all_day_event = !$event->get('field_event_all_day')->isEmpty() && $event->get('field_event_all_day')->getString() === '1';

  if (!empty($start_datetime)) {
    if ($all_day_event) {
      $start_date = $date_formatter->format($start_datetime, $date_format, '', $utc_timezone);
      $add_timezone = FALSE;
    }
    else {
      $start_date = $date_formatter->format($start_datetime, $date_format);
    }
    // Default time should not be displayed.
    $start_time = $all_day_event ? '' : $date_formatter->format($start_datetime, $time_format);

    if ($end_datetime !== NULL) {
      if ($all_day_event) {
        $end_date = $date_formatter->format($end_datetime, $date_format, '', $utc_timezone);
      }
      else {
        $end_date = $date_formatter->format($end_datetime, $date_format);
      }
      // Default time should not be displayed.
      $end_time = $all_day_event ? '' : $date_formatter->format($end_datetime, $time_format);
    }

    // Date are the same or there are no end date.
    if ($end_datetime === NULL || $start_datetime == $end_datetime) {
      $event_date = empty($start_time) ? $start_date : "$start_date $start_time";
    }
    // The date is the same, the time is different.
    elseif (date(DateTimeItemInterface::DATE_STORAGE_FORMAT, $start_datetime) == date(DateTimeItemInterface::DATE_STORAGE_FORMAT, $end_datetime)) {
      $event_date = "$start_date $start_time - $end_time";
    }
    // They are not the same day (or empty?).
    else {
      $event_date = "$start_date $start_time - $end_date $end_time";
    }
  }

  return $add_timezone ? "$event_date ($user_timezone)" : $event_date;
}

/**
 * Returns user timezone if needed.
 *
 * @param string $timezone
 *   User's timezone.
 *
 * @return string
 *   User timezone.
 */
function _social_event_user_timezone($timezone) {
  if ($timezone !== DateTimeItemInterface::STORAGE_TIMEZONE) {
    $zones = array_column(TimeZoneFormHelper::getOptionsListByRegion(), $timezone);
    $user_timezone = reset($zones);
  }
  else {
    $user_timezone = $timezone;
  }

  return t('@timezone time', ['@timezone' => $user_timezone]);
}

/**
 * Implements hook_social_user_account_header_create_links().
 *
 * Adds the "Create Event" link to the content creation menu.
 */
function social_event_social_user_account_header_create_links($context) {
  $link = Link::createFromRoute((new TranslatableMarkup('New Event')), 'node.add', [
    'node_type' => 'event',
  ]);

  return [
    'add_event' => [
      '#type' => 'link',
      '#attributes' => [
        'title' => new TranslatableMarkup('Create New Event'),
      ],
      '#weight' => 100,
      '#access' => $link->getUrl()->access(),
    ] + $link->toRenderable(),
  ];
}

/**
 * Implements hook_social_user_account_header_account_links().
 *
 * Adds the "View my events" link to the user menu.
 */
function social_event_social_user_account_header_account_links(array $context) {
  // We require a user for this link.
  if (empty($context['user']) || !($context['user'] instanceof AccountInterface)) {
    return [];
  }

  $link = Link::createFromRoute((new TranslatableMarkup('My events')), 'view.events.events_overview', [
    'user' => $context['user']->id(),
  ]);

  return [
    'my_events' => [
      '#type' => 'link',
      '#attributes' => [
        'title' => new TranslatableMarkup('View my events'),
      ],
      '#weight' => 600,
      '#access' => $link->getUrl()->access(),
    ] + $link->toRenderable(),
  ];
}

/**
 * Implements hook_ENTITY_TYPE_access() for "event_enrollment".
 */
function social_event_event_enrollment_access(EntityInterface $enrollment, string $operation, AccountInterface $account): AccessResultInterface {
  // @todo The code below should be removed or adopted after fixing https://www.drupal.org/project/social/issues/3273218
  if ($operation === 'view') {
    // Unfortunately, we can't send email message triggered by
    // "event_enrollment" actions because email send worker needs access to
    // entity "view" operation.
    // By default, users doesn't have permissions to view event enrollments.
    // As we can't change default behaviour we need at least "reduce" access
    // opportunity to viewing enrollments for owners.
    /** @var \Drupal\social_event\EventEnrollmentInterface $enrollment */
    return AccessResult::allowedIf(
      $enrollment->getEventStandaloneEnrollConfirmationStatus() &&
      $enrollment->getAccount() == $account->id()
    )->setCacheMaxAge(0);
  }

  return AccessResult::neutral();
}

/**
 * Custom permission check, to see if people have access to users' events.
 *
 * Implements hook_block_access().
 */
function social_event_block_access(Block $block, $operation, AccountInterface $account) {
  if ($operation === 'view' && ($block->getPluginId() === 'views_exposed_filter_block:events-events_overview' || $block->getPluginId() === 'views_block:events-block_events_on_profile')) {
    // Here we're going to assume by default access is not granted.
    $eventController = SocialEventController::create(\Drupal::getContainer());
    $access = $eventController->myEventAccess($account);
    // If the 'myEventAccess' 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;
  }

  if ($operation === 'view' && ($block->getPluginId() === 'views_block:event_enrollments-event_enrollments')) {
    $nid = \Drupal::routeMatch()->getRawParameter('node');
    if (!empty($nid) && $node = Node::load($nid)) {
      if (
        // Show only for event node type.
        !$node instanceof Event ||
        // Check if event enrollment were disabled explicitly.
        !$node->isEnrollmentEnabled() ||
        // Or disabled in the event.
        !$node->showEnrollments()
      ) {
        // Block should be visible for authors.
        if ($node->getOwnerId() !== $account->id()) {
          // Return forbidden, since access was not explicitly granted.
          return AccessResult::forbidden();
        }
      }
    }
  }

  // No opinion.
  return AccessResult::neutral();
}

/**
 * Implements hook_form_alter().
 */
function social_event_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  // Form alters for the event add and edit forms.
  if ($form_id === 'node_event_edit_form' || $form_id === 'node_event_form') {
    // Alter visibility of enrollment methods.
    social_event_field_visibility($form, 'field_event_enroll', ['field_event_enroll_outside_group']);

    $form['field_event_enroll_outside_group']['#states'] = [
      'visible' => [
        ':input[name="groups"]' => ['!value' => '_none'],
        ':input[name="field_enroll_method"]' => ['!value' => '3'],
      ],
    ];

    // Hide invite enroll method if the submodule is not enabled.
    $moduleHandler = \Drupal::service('module_handler');
    if (!$moduleHandler->moduleExists('social_event_invite')) {
      unset($form['field_enroll_method']['widget']['#options'][3]);
    }

    // Get user timezone.
    $user_timezone = _social_event_user_timezone(date_default_timezone_get());
    $timezone_label = t('Time zone');
    $markup = '<div class="control-label margin-bottom-s">' . $timezone_label . '</div>';
    $markup .= '<div class="bg-gray-lightest btn btn-lg">' . $user_timezone . '</div>';
    // Check if user has access to change its timezone.
    $date_config = Drupal::config('system.date');
    if ($date_config->get('timezone.user.configurable')) {
      $description = t('The event date and time are set based on your timezone. @change_timezone.', [
        '@change_timezone' => Link::createFromRoute(
          t('Change your timezone here'),
          'entity.user.edit_form',
          ['user' => \Drupal::currentUser()->id()],
          [
            'attributes' => [
              'target' => '_blank',
            ],
            'fragment' => 'edit-group-locale-settings',
          ]
        )->toString(),
      ]);
    }
    else {
      $description = t("The event date and time are set based on the site's default timezone.");
    }
    $form['timezone_indication'] = [
      '#type' => 'container',
      '#weight' => 99,
      'content' => [
        '#type' => 'item',
        '#markup' => $markup,
        '#description' => $description,
      ],
    ];

    if ($form['field_event_all_day']['widget']['value']['#default_value']) {
      $start_date = &$form['field_event_date']['widget'][0]['value'];
      $start_date['#default_value']->setTimezone(new DateTimeZone(DateTimeItemInterface::STORAGE_TIMEZONE))->setTime(0, 0);
      $start_date['#date_timezone'] = DateTimeItemInterface::STORAGE_TIMEZONE;
      $end_date = &$form['field_event_date_end']['widget'][0]['value'];
      $end_date['#default_value']->setTimezone(new DateTimeZone(DateTimeItemInterface::STORAGE_TIMEZONE))->setTime(0, 0);
      $end_date['#date_timezone'] = DateTimeItemInterface::STORAGE_TIMEZONE;
    }

    if (!empty($form['field_event_send_confirmation'])) {
      // Replace title with description.
      $form['field_event_send_confirmation']['widget']['value']['#title'] = $form['field_event_send_confirmation']['widget']['value']['#description'];
      unset($form['field_event_send_confirmation']['widget']['value']['#description']);
      // And move field to an enrollment field group.
      if (!empty($form['#fieldgroups']['group_enrollment_methods'])) {
        $form['#fieldgroups']['group_enrollment_methods']->children[] = 'field_event_send_confirmation';
        $form['#group_children']['field_event_send_confirmation'] = 'group_enrollment_methods';
      }
    }

    if (!empty($form['field_hide_enrollments'])) {
      $form['field_hide_enrollments']['#states'] = [
        'visible' => [
          ':input[name="field_event_enroll[value]"]' => ['checked' => TRUE],
        ],
      ];
      // And move field to an enrollment field group.
      if (!empty($form['#fieldgroups']['group_enrollment_methods'])) {
        $form['#fieldgroups']['group_enrollment_methods']->children[] = 'field_hide_enrollments';
        $form['#group_children']['field_hide_enrollments'] = 'group_enrollment_methods';
      }
    }

    $form['#fieldgroups']['group_date_time']->children[] = 'timezone_indication';
    $form['#group_children']['timezone_indication'] = 'group_date_time';
    $form['#after_build'][] = 'social_event_date_after_build';
  }

  // Specific alter for the event edit form.
  if ($form_id === 'node_event_edit_form') {
    // Set allow event enrollment checked by default for existing events.
    $entity = $form_state->getFormObject()->getEntity();
    if ($entity->hasField('field_event_enroll') && $entity->field_event_enroll->isEmpty()) {
      $form['field_event_enroll']['widget']['value']['#default_value'] = TRUE;
    }
  }

  // Alters for the Enroll Action form.
  if ($form_id === 'enroll_action_form') {
    $node = \Drupal::routeMatch()->getParameter('node');

    if (!$node instanceof NodeInterface) {
      $node = \Drupal::entityTypeManager()->getStorage('node')->load($node);
    }

    if ($node instanceof NodeInterface) {
      $access = $node instanceof Event && $node->isEnrollmentEnabled();
      if (isset($form['enroll_wrapper'])) {
        $form['enroll_wrapper']['#access'] = $access;
      }
      else {
        $form['enroll_for_this_event']['#access'] = $access;
      }
    }
  }
}

/**
 * Add custom validation to event date fields.
 */
function social_event_date_after_build($form, &$form_state) {
  array_unshift($form['field_event_date']['widget'][0]['value']['#element_validate'], 'social_event_date_validate');
  array_unshift($form['field_event_date_end']['widget'][0]['value']['#element_validate'], 'social_event_date_validate');
  return $form;
}

/**
 * Set default time to the date field if time was not set.
 */
function social_event_date_validate(&$element, FormStateInterface $form_state, &$complete_form) {
  $form_values = $form_state->getValues();
  $input = NestedArray::getValue($form_values, $element['#parents']);

  $all_day = (bool) $form_state->getValue(['field_event_all_day', 'value']);

  // End date should not be empty if "All day" option was enabled.
  if ($all_day) {
    if (strpos($element['#name'], 'field_event_date_end') !== FALSE) {
      if (empty($input['date'])) {
        $form_state->setError($element, t('End date is required!'));
        return;
      }
    }
  }

  // Skip default validation for required time when date is required.
  if ($all_day) {
    $input['time'] = '00:00:00';
    if (is_null($input['object']) && !empty($input['date'])) {
      $input['object'] = new DrupalDateTime($input['date'] . ' ' . $input['time'], new DateTimeZone(DateTimeItemInterface::STORAGE_TIMEZONE));
    }
    elseif ($input['object'] instanceof DrupalDateTime) {
      $input['object'] = $input['object']->setTimezone(new DateTimeZone(DateTimeItemInterface::STORAGE_TIMEZONE))->setTime(0, 0);
    }
  }
  else {
    if ($input['object'] instanceof DrupalDateTime) {
      $input['object'] = new DrupalDateTime($input['object']->format('Y-m-d H:i:s'), new DateTimeZone(date_default_timezone_get()));
    }
  }

  if ($input['object'] instanceof DrupalDateTime) {
    $form_state->setValueForElement($element, $input);
  }
}

/**
 * Implements hook_field_widget_single_element_form_alter().
 */
function social_event_field_widget_single_element_form_alter(&$element, FormStateInterface $form_state, $context) {
  $field_definition = $context['items']->getFieldDefinition();
  if ($field_definition->getName() == 'field_event_date' || $field_definition->getName() == 'field_event_date_end') {
    $element['value']['#date_time_callbacks'][] = [
      SocialEventDateAllDay::class,
      'allDayCheckboxCallback',
    ];

  }
}

/**
 * Implements hook_activity_send_email_notifications_alter().
 */
function social_event_activity_send_email_notifications_alter(array &$items, array $email_message_templates) {
  // If a activity_on_events_im_organizing template is enabled then we add it in
  // the "Message to Me" section.
  if (isset($email_message_templates['activity_on_events_im_organizing'])) {
    $items['what_manage']['templates'][] = 'activity_on_events_im_organizing';
  }

  if (isset($email_message_templates['user_was_enrolled_to_event'])) {
    $items['message_to_me']['templates'][] = 'user_was_enrolled_to_event';
  }
}

/**
 * Implements hook_social_follow_content_types_alter().
 */
function social_event_social_follow_content_types_alter(array &$types) {
  $types[] = 'event';
}

/**
 * Return the Event from a given page.
 *
 * @return \Drupal\Core\Entity\EntityInterface|null
 *   The event or NULL if nothing found.
 */
function social_event_get_current_event() {
  $event = &drupal_static(__FUNCTION__);

  if (!isset($event)) {
    $node = \Drupal::service('current_route_match')->getParameter('node');

    if ($node !== NULL && (!$node instanceof NodeInterface)) {
      $node = Node::load($node);
    }

    if ($node instanceof NodeInterface && $node->getType() === 'event') {
      $event = $node;
    }

    // If we don't have an event then we can go back to NULL.
    if (!isset($event)) {
      $event = NULL;
    }
  }

  return $event;
}

/**
 * Add conditional field visibility triggers.
 *
 * @param array $form
 *   The form that needs to be altered.
 * @param string $field_name
 *   The field that acts as a trigger.
 * @param array $fields
 *   The field that should show upon trigger.
 * @param string $state
 *   Which state do you want for the form field. Defaults to visibility.
 */
function social_event_field_visibility(array &$form, $field_name, array $fields, $state = 'visible') {
  // @todo Refactor outside of an alter when going to React.
  // Hide fields if event enrollment is disabled for event.
  $trigger = [
    ':input[name="' . $field_name . '[value]"]' => ['checked' => TRUE],
  ];

  foreach ($fields as $field) {
    $form[$field]['#states'] = [
      $state => $trigger,
    ];
  }
}

/**
 * Implements hook_entity_operation_alter().
 */
function social_event_entity_operation_alter(array &$operations, EntityInterface $entity) {
  // Check access first.
  if (!social_event_manager_or_organizer()) {
    return;
  }
  // Get the node, so we can pass it as a parameter.
  $node = \Drupal::routeMatch()->getParameter('node');
  // Check if the entity type is one of event_enrollment and that we're on the
  // correct view. Otherwise it would update all actions across the platform.
  if ($entity->getEntityTypeId() === 'event_enrollment' && \Drupal::routeMatch()->getRouteName() === 'view.event_manage_enrollment_requests.page_manage_enrollment_requests') {
    // Empty the current operations.
    $operations = [];
    // Add the "Approve" option.
    $operations['approve']['title'] = t('Approve');
    $operations['approve']['url'] = Url::fromRoute('social_event.update_enrollment_request', [
      'node' => $node,
      'event_enrollment' => $entity->id(),
      'approve' => 1,
    ]);
    // Add the "Decline" option.
    $operations['decline']['title'] = t('Decline');
    $operations['decline']['url'] = Url::fromRoute('social_event.enrollment_request_decline_form', [
      'node' => $node,
      'event_enrollment' => $entity->id(),
    ]);
  }
}

/**
 * Implements hook_ENTITY_TYPE_update().
 *
 * Delete the event enrollments request if type is changed to directly.
 */
function social_event_node_update(EntityInterface $entity) {
  if ($entity instanceof NodeInterface && $entity->getType() === 'event') {
    $enroll_state_original = $entity->original->get('field_enroll_method')->getString();
    $enroll_new_state = $entity->get('field_enroll_method')->getString();
    // If the request to enroll was turned off then delete the request.
    if ((int) $enroll_state_original === EventEnrollmentInterface::ENROLL_METHOD_REQUEST && (int) $enroll_new_state !== EventEnrollmentInterface::ENROLL_METHOD_REQUEST) {
      /** @var \Drupal\social_event\EventEnrollmentStatusHelper $enrollments */
      $enrollments = \Drupal::service('social_event.status_helper');
      /** @var \Drupal\social_event\Entity\EventEnrollment $enrollment */
      foreach ($enrollments->getAllEventEnrollments($entity->id(), TRUE) as $enrollment) {
        // Check if the field is not empty before getting the value,
        // Since (int) will otherwise return 0 and give false positives.
        if (!$enrollment->get('field_request_or_invite_status')->isEmpty() && (int) $enrollment->get('field_request_or_invite_status')->getString() === EventEnrollmentInterface::REQUEST_PENDING) {
          $enrollment->delete();
        }
      }
    }
  }
}

/**
 * Implements template_preprocess_views_view().
 */
function social_event_preprocess_views_view(&$variables) {
  if ($variables['view']->id() === 'event_manage_enrollment_requests') {
    $node_id = \Drupal::routeMatch()->getParameter('node');
    // Implement custom button to go back to the enroll request overview.
    $variables['more'] = [
      '#title' => t('Back to event'),
      '#type' => 'link',
      '#url' => Url::fromRoute('entity.node.canonical', ['node' => (int) $node_id]),
      '#attributes' => [
        'class' => [
          'btn',
          'btn-default',
          'btn-raised',
          'waves-effect',
        ],
      ],
    ];
  }
  // Add a custom cache tag to the user events overview page.
  if ($variables['view']->current_display === 'events_overview' && $variables['view']->id() === 'events') {
    $account_id = \Drupal::routeMatch()->getParameter('user');
    $variables['#cache']['tags'][] = 'event_content_list:user:' . $account_id;
  }
}

/**
 * Implements hook_preprocess_page().
 */
function social_event_preprocess_page(array &$variables) {
  /** @var \Drupal\Core\Routing\AdminContext $admin_context */
  $admin_context = \Drupal::service('router.admin_context');
  // Don't add the modal for admin pages.
  if ($admin_context->isAdminRoute()) {
    return;
  }

  // Make sure we do the expensive checks only after we see that there's
  // something in the URL for nodes.
  if (\Drupal::routeMatch()->getRouteName() === 'entity.node.canonical' && \Drupal::request()->query->get('requested-enrollment')) {
    // Get current event and user id.
    $node = social_event_get_current_event();
    $uid = \Drupal::currentUser()->id();

    // Only show the modal if the request method is set on this node
    // and if the 'requested-enrollment' parameter is TRUE.
    if ($node instanceof Node && (int) $node->get('field_enroll_method')->value === EventEnrollmentInterface::ENROLL_METHOD_REQUEST) {
      // If there is a max of enrollments to this event and it's reached we
      // don't want users to be able to use the modal dialog to request to
      // enroll.
      if (\Drupal::hasService('social_event_max_enroll.service') &&
        \Drupal::service('social_event_max_enroll.service')->isEnabled($node) &&
        !\Drupal::service('social_event_max_enroll.service')->getEnrollmentsLeft($node)) {
        return;
      }

      // Query to see if the user is already enrolled,
      // has already requested or is already invited.
      $query = \Drupal::entityTypeManager()
        ->getStorage('event_enrollment')
        ->getQuery()
        ->accessCheck();
      $query
        ->condition('field_event.target_id', $node->id())
        ->condition('field_account.target_id', $uid)
        ->condition('field_request_or_invite_status.value', [
          EventEnrollmentInterface::REQUEST_APPROVED,
          EventEnrollmentInterface::INVITE_INVITED,
          EventEnrollmentInterface::INVITE_PENDING_REPLY,
          EventEnrollmentInterface::INVITE_ACCEPTED_AND_JOINED,
          EventEnrollmentInterface::INVITE_INVALID_OR_EXPIRED,
        ], 'IN');
      $queryResult = (bool) $query->execute();
      if ($queryResult === FALSE) {
        $url = Url::fromRoute('social_event.request_enroll_dialog', ['node' => $node->id()]);
        $link = Link::fromTextAndUrl('', $url)->toRenderable();
        $link['#attributes'] = [
          'id' => 'modal-trigger',
          'class' => [
            'use-ajax',
            'hidden',
          ],
        ];
        // Attach link and libraries.
        $variables['page']['content']['eventEnrollmentRequest'] = $link;
        $variables['page']['content']['eventEnrollmentRequest']['#attached']['library'] = [
          'core/drupal.dialog.ajax',
          'social_event/modal',
        ];
      }
    }
  }
}

/**
 * Implements hook_preprocess_field().
 */
function social_event_preprocess_field(&$variables) {
  $formatter = $variables['element']['#formatter'];
  if (in_array($formatter, ['address_plain', 'address_default'])) {
    $entity = $variables['element']['#object'];

    if ($entity instanceof NodeInterface && $entity->getType() === 'event') {
      $social_event_settings = \Drupal::config('social_event.settings');
      $address_visibility_settings = $social_event_settings->get('address_visibility_settings');
      if (isset($address_visibility_settings['street_code_private']) && !empty($address_visibility_settings['street_code_private'])) {
        if (!_social_event_get_enrollment_status($entity)) {
          switch ($formatter) {
            case 'address_plain':
              if (isset($variables['items'][0]['content']['#address_line1'])) {
                $variables['items'][0]['content']['#address_line1'] = NULL;
              }
              if (isset($variables['items'][0]['content']['#postal_code'])) {
                $variables['items'][0]['content']['#postal_code'] = NULL;
              }
              break;

            case 'address_default':
              if (isset($variables['items'][0]['content']['address_line1']['#value'])) {
                $variables['items'][0]['content']['address_line1']['#value'] = '';
              }
              if (isset($variables['items'][0]['content']['postal_code']['#value'])) {
                $variables['items'][0]['content']['postal_code']['#value'] = '';
              }
              break;
          }
        }
      }
    }
  }
}

/**
 * Callback to get enrollment status from current user.
 *
 * @param \Drupal\node\NodeInterface $event
 *   Event entity.
 *
 * @return bool
 *   Enrolment status.
 *
 * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
 * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
 */
function _social_event_get_enrollment_status(NodeInterface $event) {
  $enrollments = \Drupal::entityTypeManager()->getStorage('event_enrollment')->loadByProperties([
    'field_account' => \Drupal::currentUser()->id(),
    'field_event' => $event->id(),
  ]);

  $status = TRUE;
  if ($enrollment = array_pop($enrollments)) {
    $enrollment_status = $enrollment->field_enrollment_status->value;
    if ($enrollment_status === '0') {
      $status = FALSE;
    }
  }
  else {
    $status = FALSE;
  }

  return $status;
}

/**
 * Change views filters order.
 *
 * @param string $view_config
 *   The views config name.
 * @param string $display
 *   The views display id.
 */
function _social_event_views_update($view_config, $display) {
  // Get views config.
  $config = \Drupal::configFactory()
    ->getEditable($view_config);

  // Get views config array.
  $data = $config->getRawData();

  $filters = $data['display'][$display]['display_options']['filters'];

  // Reorder filters.
  $new_filters['event_date'] = $filters['event_date'];
  unset($filters['event_date']);
  $new_filters = array_merge($new_filters, $filters);

  // Save new configs.
  $data['display'][$display]['display_options']['filters'] = $new_filters;
  $config->setData($data)->save();
}

/**
 * Returns a description array for the field_enroll_method options.
 *
 * @return string
 *   The render array containing the description.
 */
function social_event_enroll_method_description($key) {
  $description = '';

  // We need it to be specified otherwise we can't build the markup.
  if (empty($key)) {
    return $description;
  }

  // Add explanatory descriptive text.
  switch ($key) {
    case EventEnrollmentInterface::ENROLL_METHOD_JOIN:
      $description = '<p><strong>' . t('Open to enroll')->render() . '</strong>';
      $description .= ' - ' . t('users can enroll for this event without approval')->render();
      $description .= '</p>';
      break;

    case EventEnrollmentInterface::ENROLL_METHOD_REQUEST:
      $description = '<p><strong>' . t('Request to enroll')->render() . '</strong>';
      $description .= ' - ' . t('users can "request to enroll" for this event which event organisers approve/decline')->render();
      $description .= '</p>';
      break;

    case EventEnrollmentInterface::ENROLL_METHOD_INVITE:
      $description = '<strong>' . t('Invite-only')->render() . '</strong>';
      $description .= ' - ' . t('users can only enroll for this event if they are added/invited by event organisers')->render();
      $description .= '</p>';
      break;
  }

  // Allow modules to provide their own markup for a given key in the
  // event enroll method #options array.
  \Drupal::moduleHandler()->alter('social_event_enroll_method_description', $key, $description);

  return $description;
}

/**
 * Returns a new popover tooltip based on a descriptive text and field name.
 *
 * @param string $field_name
 *   The field name to create a unique id for.
 * @param string $data_title
 *   The title of the pop-up.
 * @param string $description
 *   A string containing the description, this needs to be rendered markup.
 *
 * @return array
 *   The render array containing the tooltip.
 */
function social_event_render_tooltip($field_name, $data_title, $description) {
  $build = [];
  $id = Html::getUniqueId('tooltip-' . $field_name);
  $build['toggle'] = [
    '#type' => 'link',
    '#url' => Url::fromUserInput("#$id"),
    '#icon' => Bootstrap::glyphicon('question-sign'),
    '#attributes' => [
      'class' => ['icon-before'],
      'data-toggle' => 'popover',
      'data-container' => 'body',
      'data-html' => 'true',
      'data-placement' => 'bottom',
      'data-title' => $data_title ?: '',
    ],
  ];
  $build['requirements'] = [
    '#type' => 'container',
    '#theme_wrappers' => ['container__file_upload_help'],
    '#attributes' => [
      'id' => $id,
      'class' => ['hidden', 'help-block'],
      'aria-hidden' => 'true',
    ],
  ];
  // As documented in Render API, Note that the value is passed through
  // \Drupal\Component\Utility\Xss::filterAdmin(), which strips known XSS
  // vectors while allowing a permissive list of HTML tags.
  $build['requirements']['descriptions'] = [
    '#markup' => $description,
    '#allowed_tags' => [
      'strong',
      'span',
      'svg',
      'p',
      'div',
      'em',
      'img',
      'a',
      'span',
      'use',
    ],
  ];

  $variables['popover'] = $build;

  return $build;
}

/**
 * Implements template_preprocess_form_element().
 */
function social_event_preprocess_fieldset(&$variables) {
  // Make sure our event enroll method 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'])) {
    if ($element['#field_name'] === 'field_enroll_method') {
      $description = '';
      foreach ($element['#options'] as $key => $label) {
        $description .= social_event_enroll_method_description($key);
      }

      // Render a specific tooltip based on a field name and description.
      // This is done in the fieldset, next to the <legend>.
      $variables['popover'] = social_event_render_tooltip('field_enroll_method', t('Enroll method'), $description);
    }
  }
}

/**
 * Implements hook_field_group_form_process_build_alter().
 */
function social_event_field_group_form_process_build_alter(array &$element, FormStateInterface $form_state, &$complete_form) {
  if ($field_group = &$element['group_enrollment_methods']) {
    $field_group['#states']['visible'] = [
      ':input[name="field_event_enroll[value]"]' => ['checked' => TRUE],
    ];
    $field_group['#title'] = '';
  }

  $event_settings = \Drupal::config('social_event.settings');
  if (
    isset($element['group_enrollment']) &&
    $event_settings->get('disable_event_enroll')
  ) {
    $element['group_enrollment']['#access'] = FALSE;
  }
}

/**
 * Implements hook_social_core_default_main_menu_links_alter().
 */
function social_event_social_core_default_main_menu_links_alter(array &$links) {
  $link = \Drupal::entityTypeManager()->getStorage('menu_link_content')
    ->loadByProperties([
      'title' => 'All events',
      'menu_name' => 'main',
      'link' => 'internal:/community-events',
    ]);
  $link = end($link);

  // Ensure that the end() doesn't return FALSE, and we have link instance.
  if ($link instanceof MenuLinkContentInterface) {
    $links[] = $link;
  }
}

/**
 * Implements hook_social_core_title_alter().
 */
function social_event_social_core_title_alter(array &$titles): void {
  foreach (['node', 'group_content'] as $entity_type) {
    $titles[$entity_type]['bundles']['event'] = t('an');
  }
}

/**
 * Implements hook_social_tagging_type_alter().
 */
function social_event_social_tagging_type_alter(array &$items): void {
  $items['node']['sets'][0]['bundles'][] = 'event';
}

/**
 * Implements hook_preprocess_HOOK() for "html".
 */
function social_event_preprocess_html(array &$variables): void {
  // Make event icons to be available for themes.
  $icons = file_get_contents(\Drupal::service('extension.list.module')->getPath('social_event') . '/assets/icons/icons.svg');

  // Add a new render array to page_bottom so the icons get added to the page.
  $variables['page_bottom']['event_icons'] = [
    '#type' => 'inline_template',
    '#template' => '<span class="hidden">' . $icons . '</span>',
  ];
}

/**
 * Array of routes where the special event local tasks are displayed.
 *
 * See social_event_menu_local_tasks_alter().
 */
function _social_event_menu_local_tasks_routes(): array {
  $routes = [
    'view.event_enrollments.view_enrollments',
    'entity.node.canonical',
    'view.managers.view_managers',
    'view.manage_enrollments.page',
    'view.event_manage_enrollments.page_manage_enrollments',
  ];

  \Drupal::moduleHandler()->invokeAll('social_event_menu_local_tasks_routes_alter', [&$routes]);

  return $routes;
}
