You are here

social_event.module in Open Social 8.6

The Social event module.

File

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

/**
 * @file
 * The Social event module.
 */
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Access\AccessResultNeutral;
use Drupal\Core\Block\BlockPluginInterface;
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\Url;
use Drupal\block\Entity\Block;
use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface;
use Drupal\group\Entity\GroupContent;
use Drupal\group\GroupMembershipLoaderInterface;
use Drupal\node\Entity\Node;
use Drupal\node\NodeInterface;
use Drupal\social_event\Controller\SocialEventController;
use Drupal\social_event\EventEnrollmentInterface;
use Drupal\views\Plugin\views\query\QueryPluginBase;
use Drupal\views\ViewExecutable;

/**
 * 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';
  }
}

/**
 * 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) {
  $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')) {
    return TRUE;
  }
  if ($node === NULL) {

    /** @var \Drupal\node\NodeInterface $node */
    $node = \Drupal::routeMatch()
      ->getParameter('node');

    // Or if it's an organizer for the node.
    if (!$node instanceof NodeInterface) {
      $node = \Drupal::entityTypeManager()
        ->getStorage('node')
        ->load($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 ($node !== NULL && $node
    ->bundle() === 'event' && !$node->field_event_managers
    ->isEmpty()) {
    foreach ($node->field_event_managers
      ->getValue() as $value) {
      if ($value && $value['target_id'] === $account
        ->id()) {
        return TRUE;
      }
    }
  }
  return FALSE;
}

/**
 * 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'] == NODE_PUBLISHED || $account_uid !== $current_uid) {
      $query->where[1]['conditions'][] = [
        'field' => 'node_field_data.status',
        'value' => NODE_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 = [
    'view.event_enrollments.view_enrollments',
    'entity.node.canonical',
    'view.managers.view_managers',
    'view.manage_enrollments.page',
    'view.event_manage_enrollments.page_manage_enrollments',
  ];
  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 Node) {
      $can_show_enrollments_link = \Drupal::service('social_event.enroll')
        ->isEnabled($node);
    }
  }

  // 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
    ->getType() === 'event' && ($display
    ->getMode() === 'teaser' || $display
    ->getMode() === 'small_teaser')) {
    $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().
    $enrollmenttag = 'enrollment:' . $nid . '-' . $uid;
    $build['#cache']['tags'][] = $enrollmenttag;
    $build['#cache']['contexts'][] = 'user';
    if (empty($nid)) {
      return;
    }
    $conditions = [
      'field_account' => $uid,
      'field_event' => $nid,
    ];
    $enrollments = \Drupal::service('entity.manager')
      ->getStorage('event_enrollment')
      ->loadByProperties($conditions);

    // Redirect anonymous use to login page before enrolling to an event.
    if ($enrollment = array_pop($enrollments)) {
      $current_enrollment_status = $enrollment->field_enrollment_status->value;
      if ($current_enrollment_status === '1') {
        $build['enrolled'] = [
          '#type' => '#text_field',
          '#markup' => t('Enrolled'),
        ];
      }
    }
  }
}

/**
 * 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') {
    $variables['event_date'] = _social_event_format_date($node, $view_mode);
  }
}

/**
 * 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']['event_passed_upcoming_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.'),
    'sort' => [
      'field' => 'field_event_date_value',
      'id' => 'event_passed_upcoming_sort',
    ],
  ];
}

/**
 * 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.
 */
function _social_event_format_date($event, $view_mode) {
  $event_date = '';

  // 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 = drupal_get_user_timezone();

  // Timezone that dates should be stored in.
  $utc_timezone = DateTimeItemInterface::STORAGE_TIMEZONE;

  // Get start and end dates.
  if ($start_date_field = $event->field_event_date) {
    if (!empty($start_date_field->value)) {

      // 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($start_date_field->value, new DateTimeZone($utc_timezone));
      $start_datetime
        ->setTimezone(new DateTimeZone($timezone));
      $start_datetime = $start_datetime
        ->getTimestamp();
    }
  }
  if ($end_date_field = $event->field_event_date_end) {
    if (!empty($end_date_field->value)) {

      // 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($end_date_field->value, 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';
  if (!empty($start_datetime)) {
    $start_date = $date_formatter
      ->format($start_datetime, $date_format);

    // Default time should not be displayed.
    $start_time = $date_formatter
      ->format($start_datetime, 'custom', 'i') === '01' ? '' : $date_formatter
      ->format($start_datetime, $time_format);
    if (!empty($end_datetime)) {
      $end_date = $date_formatter
        ->format($end_datetime, $date_format);

      // Default time should not be displayed.
      $end_time = $date_formatter
        ->format($end_datetime, 'custom', 'i') === '01' ? '' : $date_formatter
        ->format($end_datetime, $time_format);
    }

    // Date are the same or there are no end date.
    if (empty($end_datetime) || $start_datetime == $end_datetime) {
      $event_date = empty($start_time) ? $start_date : "{$start_date} {$start_time}";
    }
    elseif (date(DateTimeItemInterface::DATE_STORAGE_FORMAT, $start_datetime) == date(DateTimeItemInterface::DATE_STORAGE_FORMAT, $end_datetime)) {
      $event_date = "{$start_date} {$start_time} - {$end_time}";
    }
    elseif (!empty($end_datetime)) {
      $event_date = "{$start_date} {$start_time} - {$end_date} {$end_time}";
    }
  }
  return $event_date;
}

/**
 * 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) {
  return [
    'add_event' => [
      '#type' => 'link',
      '#attributes' => [
        'title' => new TranslatableMarkup('Create New Event'),
      ],
      '#title' => new TranslatableMarkup('New Event'),
      '#weight' => 100,
    ] + Url::fromRoute('node.add', [
      'node_type' => 'event',
    ])
      ->toRenderArray(),
  ];
}

/**
 * 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 [];
  }
  return [
    'my_events' => [
      '#type' => 'link',
      '#attributes' => [
        'title' => new TranslatableMarkup('View my events'),
      ],
      '#title' => new TranslatableMarkup('My events'),
      '#weight' => 600,
    ] + Url::fromRoute('view.events.events_overview', [
      'user' => $context['user']
        ->id(),
    ])
      ->toRenderArray(),
  ];
}

/**
 * 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))) {

      // Check if event enrollment were disabled explicitly.
      if (!\Drupal::service('social_event.enroll')
        ->isEnabled($node)) {

        // 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) {
  switch ($form_id) {
    case '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;
      }
    case 'node_event_form':
      $form['event_all_day'] = [
        '#type' => 'checkbox',
        '#title' => t('All day'),
      ];

      // Set default value and fieldgroup for all day checkbox.
      if ($date = $form['field_event_date']['widget'][0]['value']['#default_value']) {
        $all_day_value = $date instanceof DrupalDateTime && social_event_date_is_all_day($date);
        $form['event_all_day']['#default_value'] = $all_day_value;
      }
      $form['#fieldgroups']['group_date_time']->children[] = 'event_all_day';
      $form['#group_children']['event_all_day'] = 'group_date_time';
      $form['#after_build'][] = 'social_event_date_after_build';
      break;

    // Hide Enroll button if enrollment is disabled for this event.
    case 'enroll_action_form':
      $node = \Drupal::routeMatch()
        ->getParameter('node');
      if (!$node instanceof NodeInterface) {
        $node = \Drupal::entityTypeManager()
          ->getStorage('node')
          ->load($node);
      }
      if ($node instanceof NodeInterface) {
        $form['enroll_for_this_event']['#access'] = \Drupal::service('social_event.enroll')
          ->isEnabled($node);
      }
      break;
  }
}

/**
 * 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) {
  $input = NestedArray::getValue($form_state
    ->getValues(), $element['#parents']);
  $form_input = $form_state
    ->getUserInput();

  // Skip default validation for required time when date is required.
  if (!empty($input['date']) && (empty($input['time']) || !empty($form_input['event_all_day']))) {
    $input['time'] = '00:01:00';
    $storage_format = DrupalDateTime::FORMAT;
    $datetime = trim($input['date'] . ' ' . $input['time']);
    $storage_timezone = new DateTimeZone(DateTimeItemInterface::STORAGE_TIMEZONE);
    $system_timezone = date_default_timezone_get();
    $zone = !empty($system_timezone) ? new DateTimeZone($system_timezone) : $storage_timezone;
    $input['object'] = DrupalDateTime::createFromFormat($storage_format, $datetime, $zone);
    if ($input['object'] instanceof DrupalDateTime) {
      $form_state
        ->setValueForElement($element, $input);
    }
  }
}

/**
 * Implements hook_field_widget_form_alter().
 */
function social_event_field_widget_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'][] = 'social_event_date_all_day_checkbox';
  }
}

/**
 * Add 'All day' checkbox to event datetime field.
 */
function social_event_date_all_day_checkbox(&$element, FormStateInterface $form_state, $date) {

  // Time field should disappear when 'All day' is checked.
  $state = [
    ':input[name="event_all_day"]' => [
      'checked' => TRUE,
    ],
  ];
  $element['time']['#states'] = [
    'invisible' => $state,
  ];
  $form_input = $form_state
    ->getUserInput();
  $date = $element['#value']['object'];
  if (!empty($form_input['op']) && isset($form_input['event_all_day'])) {
    $element['time']['#value'] = '00:01:00';
    $element['event_all_day']['#value'] = (bool) $form_input['event_all_day'];
  }
  elseif ($date instanceof DrupalDateTime && social_event_date_is_all_day($date)) {
    $element['time']['#value'] = '00:01:00';
    $element['event_all_day']['#value'] = TRUE;
  }
}

/**
 * Check if event date is all day.
 */
function social_event_date_is_all_day(DrupalDateTime $date) {
  return $date
    ->format('i') === '01';
}

/**
 * Add notification settings display on user Profile for the EO notification.
 *
 * @param array &$items
 *   An array of groups that contain a title and an array of templates that are
 *   contained in this settings group.
 * @param array $email_message_templates
 *   Message templates enabled for sending by email.
 *
 * @see activity_send_email_form_user_form_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';
  }
}

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

Functions

Namesort descending Description
social_event_activity_send_email_notifications_alter Add notification settings display on user Profile for the EO notification.
social_event_block_access Custom permission check, to see if people have access to users' events.
social_event_block_view_alter Implements hook_block_view_alter().
social_event_date_after_build Add custom validation to event date fields.
social_event_date_all_day_checkbox Add 'All day' checkbox to event datetime field.
social_event_date_is_all_day Check if event date is all day.
social_event_date_validate Set default time to the date field if time was not set.
social_event_field_widget_form_alter Implements hook_field_widget_form_alter().
social_event_flagging_insert Implements hook_ENTITY_TYPE_insert().
social_event_form_alter Implements hook_form_alter().
social_event_form_views_exposed_form_alter Implements hook_form_form_ID_alter().
social_event_manager_or_organizer Check if the user is allowed to manage Enrollments.
social_event_menu_local_tasks_alter Implements hook_menu_local_tasks_alter().
social_event_node_view_alter Implements hook_ENTITY_TYPE_view_alter().
social_event_preprocess_node Prepares variables for node templates.
social_event_social_follow_content_types_alter Implements hook_social_follow_content_types_alter().
social_event_social_user_account_header_account_links Implements hook_social_user_account_header_account_links().
social_event_social_user_account_header_create_links Implements hook_social_user_account_header_create_links().
social_event_user_delete Implements hook_ENTITY_TYPE_delete().
social_event_views_data_alter Implements hook_views_data_alter().
social_event_views_query_alter Implements hook_views_query_alter().
_social_event_format_date Formats the event start end date.