social_event.module in Open Social 10.3.x
Same filename and directory in other branches
- 8.9 modules/social_features/social_event/social_event.module
- 8 modules/social_features/social_event/social_event.module
- 8.2 modules/social_features/social_event/social_event.module
- 8.3 modules/social_features/social_event/social_event.module
- 8.4 modules/social_features/social_event/social_event.module
- 8.5 modules/social_features/social_event/social_event.module
- 8.6 modules/social_features/social_event/social_event.module
- 8.7 modules/social_features/social_event/social_event.module
- 8.8 modules/social_features/social_event/social_event.module
- 10.0.x modules/social_features/social_event/social_event.module
- 10.1.x modules/social_features/social_event/social_event.module
- 10.2.x modules/social_features/social_event/social_event.module
The Social event module.
File
modules/social_features/social_event/social_event.moduleView source
<?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\AccessResultNeutral;
use Drupal\Core\Block\BlockPluginInterface;
use Drupal\Core\Datetime\DrupalDateTime;
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\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;
/**
* 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 = [
'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') {
$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'),
];
}
// Prepare enrollments counter for teasers.
$enrollments_count = $storage
->getQuery()
->condition('field_event', $nid)
->condition('field_enrollment_status', 1)
->count()
->execute();
$build['enrollments_count'] = [
'#type' => '#text_field',
'#markup' => $enrollments_count,
];
// Check if ongoing.
// Get user current date and time.
$user_time = new DateTime(date(DateTimeItemInterface::DATETIME_STORAGE_FORMAT), new DateTimeZone(date_default_timezone_get()));
// Convert to UTC.
$user_time
->setTimezone(new DateTimeZone(DateTimeItemInterface::STORAGE_TIMEZONE));
$date = $user_time
->format(DateTimeItemInterface::DATETIME_STORAGE_FORMAT);
$node_storage = \Drupal::entityTypeManager()
->getStorage('node');
$ongoing = $node_storage
->getQuery()
->condition('field_event_date', $date, '<=')
->condition('field_event_date_end', $date, '>=')
->condition('nid', $nid)
->count()
->execute();
if ($ongoing) {
$build['ongoing'] = [
'#type' => '#text_field',
'#markup' => t('Ongoing'),
];
}
}
}
/**
* 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);
$variables['#cache']['contexts'][] = 'timezone';
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 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',
],
];
$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.
*/
function _social_event_format_date($event, $view_mode) {
$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 ($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)) {
if ($date_formatter
->format($start_datetime, 'custom', 'i') === '01') {
$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 = $date_formatter
->format($start_datetime, 'custom', 'i') === '01' ? '' : $date_formatter
->format($start_datetime, $time_format);
if (!empty($end_datetime)) {
if ($date_formatter
->format($end_datetime, 'custom', 'i') === '01') {
$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 = $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 $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(system_time_zones(NULL, TRUE), $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) {
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) {
// Form alters for the event add and edit forms.
if ($form_id === 'node_event_edit_form' || $form_id === 'node_event_form') {
// Hide the default checkbox in favor of new enrollment options.
$form['field_event_enroll']['#attributes']['class'][] = 'hidden';
$form['field_event_enroll']['#states'] = [
'unchecked' => [
':input[name="field_enroll_method"]' => [
'value' => '3',
],
],
];
// 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]);
}
// Add the all day event checkbox.
$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;
}
// 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,
],
];
$form['#fieldgroups']['group_date_time']->children[] = 'event_all_day';
$form['#fieldgroups']['group_date_time']->children[] = 'timezone_indication';
$form['#group_children']['event_all_day'] = 'group_date_time';
$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) {
$form['enroll_for_this_event']['#access'] = \Drupal::service('social_event.enroll')
->isEnabled($node);
}
}
}
/**
* 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);
$input['object'] = DrupalDateTime::createFromFormat($storage_format, $datetime, $storage_timezone);
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';
}
/**
* 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';
}
}
/**
* 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();
$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 && $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_enable_enrollment[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;
}
}