Skip to content
Snippets Groups Projects
recurring_events.module 27.9 KiB
Newer Older
Owen Bush's avatar
Owen Bush committed
<?php

/**
 * @file
 * Contains recurring_events.module.
 */

Owen Bush's avatar
Owen Bush committed
use Drupal\Component\Utility\Html;
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\Core\Entity\EntityForm;
Owen Bush's avatar
Owen Bush committed
use Drupal\Core\Entity\EntityInterface;
Owen Bush's avatar
Owen Bush committed
use Drupal\Core\Entity\FieldableEntityInterface;
Owen Bush's avatar
Owen Bush committed
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Form\FormStateInterface;
Owen Bush's avatar
Owen Bush committed
use Drupal\Core\Link;
use Drupal\Core\Render\Element;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Url;
use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface;
use Drupal\recurring_events\Entity\EventSeries;
use Drupal\recurring_events\EventInterface;
use Drupal\recurring_events\Plugin\ComputedField\EventInstances;
Owen Bush's avatar
Owen Bush committed

/**
 * Implements hook_help().
 */
function recurring_events_help($route_name, RouteMatchInterface $route_match) {
  switch ($route_name) {
    // Main module help for the recurring_events_views module.
Owen Bush's avatar
Owen Bush committed
    case 'help.page.recurring_events':
      $text = file_get_contents(__DIR__ . '/README.md');
      if (!\Drupal::moduleHandler()->moduleExists('markdown')) {
        return '<pre>' . Html::escape($text) . '</pre>';
      }
      else {
        // Use the Markdown filter to render the README.
        $filter_manager = \Drupal::service('plugin.manager.filter');
        $settings = \Drupal::configFactory()->get('markdown.settings')->getRawData();
        $config = ['settings' => $settings];
        $filter = $filter_manager->createInstance('markdown', $config);
        return $filter->process($text, 'en');
      }
      break;
Owen Bush's avatar
Owen Bush committed
  }
}
Owen Bush's avatar
Owen Bush committed

/**
 * Implements hook_entity_operation().
 */
function recurring_events_entity_operation(EntityInterface $entity) {
  $operations = [];
  if ($entity->getEntityTypeId() == 'eventseries' || $entity->getEntityTypeId() == 'eventinstance') {
    if ($entity->access('clone')) {
      $operations['clone'] = [
        'title' => t('Clone'),
        'weight' => 50,
        'url' => $entity->toUrl('clone-form'),
      ];
    }
Owen Bush's avatar
Owen Bush committed
  }

  if ($entity->getEntityTypeId() == 'eventseries') {
    if ($entity->access('update')) {
      $operations['add_instance'] = [
        'title' => t('Add Instance'),
        'weight' => 50,
        'url' => $entity->toUrl('add-instance-form'),
      ];
    }
Owen Bush's avatar
Owen Bush committed
  return $operations;
}

/**
 * Implements hook_theme().
 */
function recurring_events_theme() {
  $theme = [];

  $theme['eventinstance'] = [
    'render element' => 'elements',
    'template' => 'eventinstance',
  ];

  $theme['eventseries'] = [
    'render element' => 'elements',
    'template' => 'eventseries',
  ];

  $theme['eventseries_add_list'] = [
    'variables' => ['content' => NULL],
  ];

  return $theme;
}

/**
 * Implements template_preprocess_entity().
 */
function template_preprocess_eventinstance(array &$variables) {
  // Set the eventinstance object to be accessible in the template.
  $variables['eventinstance'] = $variables['elements']['#eventinstance'];

Owen Bush's avatar
Owen Bush committed
  // Set a class on the eventinstance to differentiate between view modes.
  $variables['view_mode'] = $variables['elements']['#view_mode'];
  $variables['attributes']['class'][] = 'eventinstance-' . $variables['view_mode'];

  // Allow field groups to be rendered too.
  foreach (Element::children($variables['elements']) as $key) {
    $variables['content'][$key] = $variables['elements'][$key];
  }
}

/**
 * Implements template_preprocess_entity().
 */
function template_preprocess_eventseries(array &$variables) {
  // Set the eventseries object to be accessible in the template.
  $variables['eventseries'] = $variables['elements']['#eventseries'];

Owen Bush's avatar
Owen Bush committed
  // Set a class on the eventseries to differentiate between view modes.
  $variables['view_mode'] = $variables['elements']['#view_mode'];
  $variables['attributes']['class'][] = 'eventseries-' . $variables['view_mode'];

  // Allow field groups to be rendered too.
  foreach (Element::children($variables['elements']) as $key) {
    $variables['content'][$key] = $variables['elements'][$key];
  }
}
/**
 * Prepares variables for list of available eventseries type templates.
 *
 * Default template: eventseries-add-list.html.twig.
 *
 * @param array $variables
 *   An associative array containing:
 *   - content: An array of eventseries types.
 */
function template_preprocess_eventseries_add_list(array &$variables) {
  $variables['types'] = [];
  if (!empty($variables['content'])) {
    foreach ($variables['content'] as $type) {
      $variables['types'][$type->id()] = [
        'type' => $type->id(),
        'add_link' => Link::fromTextAndUrl($type->label(), new Url('entity.eventseries.add_form', ['eventseries_type' => $type->id()])),
        'description' => [
          '#markup' => $type->getDescription(),
        ],
      ];
    }
  }
}

/**
 * Implements hook_ENTITY_TYPE_insert().
 */
function recurring_events_eventseries_insert(EntityInterface $entity) {
  if (\Drupal::isConfigSyncing()) {
    return;
  }
  /** @var \Drupal\recurring_events\EventCreationService $creation_service */
  $creation_service = \Drupal::service('recurring_events.event_creation_service');
  $instances = $creation_service->createInstances($entity);
  foreach ($instances as $instance) {
    $instance->set('eventseries_id', $entity->id());
    $instance->setNewRevision(FALSE);
    $creation_service->configureDefaultInheritances($instance, $entity->id());
    $creation_service->updateInstanceStatus($instance, $entity);
/**
 * Implements hook_ENTITY_TYPE_translation_insert().
 */
function recurring_events_eventseries_translation_insert(EntityInterface $translation) {
  if (\Drupal::isConfigSyncing()) {
    return;
  }
  $creation_service = \Drupal::service('recurring_events.event_creation_service');
  $creation_service->createInstances($translation);

  $instances = $translation->event_instances->referencedEntities();
  if (!empty($instances)) {
    foreach ($instances as $instance) {
      if ($instance->hasTranslation($translation->language()->getId())) {
        $instance = $instance->getTranslation($translation->language()->getId());
      }
      $instance->set('eventseries_id', $translation->id());
      $instance->setNewRevision(FALSE);

      $creation_service->configureDefaultInheritances($instance, $translation->id());
      $creation_service->updateInstanceStatus($instance, $translation);

      $instance->save();
    }
  }
}

/**
 * Implements hook_ENTITY_TYPE_insert().
 */
function recurring_events_field_inheritance_insert(EntityInterface $entity) {
  if (\Drupal::isConfigSyncing()) {
    return;
  }
  if ($entity->sourceEntityType() === 'eventseries' && $entity->destinationEntityType() === 'eventinstance') {
    $creation_service = \Drupal::service('recurring_events.event_creation_service');
    $bundle = $entity->destinationEntityBundle();

    $instances = \Drupal::entityTypeManager()->getStorage('eventinstance')->loadByProperties(['type' => $bundle]);
    if (!empty($instances)) {
      foreach ($instances as $instance) {
        $creation_service->addNewDefaultInheritance($instance, $entity);
      }
    }
  }
}

/**
 * Implements hook_ENTITY_TYPE_update().
 */
function recurring_events_field_inheritance_update(EntityInterface $entity) {
  if ($entity->sourceEntityType() === 'eventseries' && $entity->destinationEntityType() === 'eventinstance') {
    $creation_service = \Drupal::service('recurring_events.event_creation_service');
    $bundle = $entity->destinationEntityBundle();

    $instances = \Drupal::entityTypeManager()->getStorage('eventinstance')->loadByProperties(['type' => $bundle]);
    if (!empty($instances)) {
      foreach ($instances as $instance) {
        $creation_service->addNewDefaultInheritance($instance, $entity);
/**
 * Implements hook_ENTITY_TYPE_update().
 */
function recurring_events_eventseries_update(EntityInterface $entity) {
  $original = $entity->original;
  $moderated = $entity->hasField('moderation_state');
  $creation_service = \Drupal::service('recurring_events.event_creation_service');

  $date_changes = $creation_service->checkForOriginalRecurConfigChanges($entity, $original);
  // If the eventseries is being published then
  // there may be date recurrence changes that need to be converted into new
  // eventinstance entities.
  if ($date_changes) {
    if ($entity->isPublished() || !$moderated) {
      if ($entity->isDefaultTranslation()) {
        $plugin_manager = \Drupal::service('plugin.manager.event_instance_creator');
        $config = \Drupal::config('recurring_events.eventseries.config');
        $active_plugin = $plugin_manager->createInstance($config->get('creator_plugin'), []);
        \Drupal::moduleHandler()->alter('recurring_events_event_instance_creator_plugin', $active_plugin, $plugin_manager, $entity);
        $active_plugin->processInstances($entity);
    // Get a fresh version of the series to get the updated instances.
    $storage = \Drupal::entityTypeManager()->getStorage('eventseries');
    $storage->resetCache([$entity->id()]);
    $entity = $entity->load($entity->id());
  $instances = $entity->event_instances->referencedEntities();
  if (!empty($instances)) {
    foreach ($instances as $instance) {
      $status_updated = $creation_service->updateInstanceStatus($instance, $entity);
      if ($status_updated) {
        $updated_statuses++;
        $instance->save();
      }
      else {
        $skipped_statuses++;
      }
    \Drupal::messenger()->addMessage(t('Successfully updated @success instance statuses. Skipped @skipped instances due to status or workflow mismatch with series.', [
      '@success' => $updated_statuses,
      '@skipped' => $skipped_statuses,
    ]));
/**
 * Implements hook_ENTITY_TYPE_insert().
 */
function recurring_events_eventseries_type_insert(EntityInterface $entity) {
  if (\Drupal::isConfigSyncing()) {
    return;
  }
  // Event series types are tied to event instance types, and optionally
  // registrant types. Therefore, we need to create equivalent instance and
  // registrant types every time an event series type is created.
  $series_type_label = $entity->label();
  $series_type_description = $entity->getDescription();
  $series_type_id = $entity->id();

  $instance_types = \Drupal::entityTypeManager()->getStorage('eventinstance_type')->load($series_type_id);
  if (empty($instance_types)) {
    $instance_type = \Drupal::entityTypeManager()->getStorage('eventinstance_type')->create([
      'id' => $series_type_id,
      'label' => $series_type_label,
      'description' => $series_type_description,
    ]);
    $instance_type->save();

    $instance_title = \Drupal::entityTypeManager()->getStorage('field_inheritance')->create([
      'id' => 'title',
      'label' => 'Title',
      'type' => 'inherit',
      'sourceEntityType' => 'eventseries',
      'sourceEntityBundle' => $series_type_id,
      'sourceField' => 'title',
      'destinationEntityType' => 'eventinstance',
      'destinationEntityBundle' => $series_type_id,
      'plugin' => 'default_inheritance',
    ]);
    $instance_title->save();

    $instance_description = \Drupal::entityTypeManager()->getStorage('field_inheritance')->create([
      'id' => 'description',
      'label' => 'Description',
      'type' => 'append',
      'sourceEntityType' => 'eventseries',
      'sourceEntityBundle' => $series_type_id,
      'sourceField' => 'body',
      'destinationEntityType' => 'eventinstance',
      'destinationEntityBundle' => $series_type_id,
      'destinationField' => 'body',
      'plugin' => 'default_inheritance',
    ]);
    $instance_description->save();
  // Ensure that field_inheritance is enabled for the new instance bundle.
  $config = \Drupal::configFactory()->getEditable('field_inheritance.config');
  $data = $config->getRawData();
  $included_bundles = $data['included_bundles'];
  $included_bundles = explode(',', $included_bundles);
  $included_bundles[] = 'eventinstance:' . $series_type_id;
  sort($included_bundles);
  $data['included_bundles'] = implode(',', $included_bundles);
  $config->setData($data)->save();

  if (\Drupal::moduleHandler()->moduleExists('recurring_events_registration')) {
    $registrant_types = \Drupal::entityTypeManager()->getStorage('registrant_type')->load($series_type_id);
    if (empty($registrant_types)) {
      $registrant_type = \Drupal::entityTypeManager()->getStorage('registrant_type')->create([
        'id' => $series_type_id,
        'label' => $series_type_label,
        'description' => $series_type_description,
      ]);
      $registrant_type->save();
    }
  }

  \Drupal::cache('menu')->invalidateAll();
  \Drupal::service('plugin.manager.menu.link')->rebuild();
}

 * Implements hook_ENTITY_TYPE_predelete().
function recurring_events_eventseries_predelete(EntityInterface $entity) {
  // Only delete instances if we're deleting the default translation of the
  // series.
  if ($entity->isDefaultTranslation()) {
    $instances = $entity->event_instances->referencedEntities();

    // Allow other modules to react prior to deleting all instances after a
    // date configuration change.
    \Drupal::moduleHandler()->invokeAll('recurring_events_pre_delete_instances', [$entity]);

    // Loop through all instances and remove them.
    foreach ($instances as $instance) {
      $instance->delete();
    }

    // Allow other modules to react after deleting all instances after a date
    // configuration change.
    \Drupal::moduleHandler()->invokeAll('recurring_events_post_delete_instances', [$entity]);
  }
}

/**
 * Implements hook_ENTITY_TYPE_delete().
 */
function recurring_events_eventseries_type_delete(EntityInterface $entity) {
  // Event series types are tied to event instance types, and optionally
  // registrant types. Therefore, we need to delete equivalent instance and
  // registrant types every time an event series type is deleted. We also must
  // delete all inheritances that use these types as either the source or the
  // destination.
  $query = \Drupal::entityQuery('field_inheritance');
  $and_destination = $query->andConditionGroup()
    ->condition('destinationEntityType', 'eventseries')
    ->condition('destinationEntityBundle', $entity->id());
  $and_source = $query->andConditionGroup()
    ->condition('sourceEntityType', 'eventseries')
    ->condition('sourceEntityBundle', $entity->id());
  $or = $query->orConditionGroup()
    ->condition($and_destination)
    ->condition($and_source);
  $query->condition($or);
  $inherited_field_ids = $query->accessCheck(FALSE)->execute();

  if (!empty($inherited_field_ids)) {
    $inherited_fields = \Drupal::entityTypeManager()->getStorage('field_inheritance')->loadMultiple($inherited_field_ids);
    foreach ($inherited_fields as $field) {
      $field->delete();
    }
  }
  $instance_type = \Drupal::entityTypeManager()->getStorage('eventinstance_type')->load($entity->id());
    $query = \Drupal::entityQuery('field_inheritance');
    $and_destination = $query->andConditionGroup()
      ->condition('destinationEntityType', 'eventinstance')
      ->condition('destinationEntityBundle', $instance_type->id());
    $and_source = $query->andConditionGroup()
      ->condition('sourceEntityType', 'eventinstance')
      ->condition('sourceEntityBundle', $instance_type->id());
    $or = $query->orConditionGroup()
      ->condition($and_destination)
      ->condition($and_source);
    $query->condition($or);
    $inherited_field_ids = $query->accessCheck(FALSE)->execute();

    if (!empty($inherited_field_ids)) {
      $inherited_fields = \Drupal::entityTypeManager()->getStorage('field_inheritance')->loadMultiple($inherited_field_ids);
      foreach ($inherited_fields as $field) {
        $field->delete();
      }
    }
    $instance_type->delete();
  }

  if (\Drupal::moduleHandler()->moduleExists('recurring_events_registration')) {
    $registrant_type = \Drupal::entityTypeManager()->getStorage('registrant_type')->load($entity->id());
      $query = \Drupal::entityQuery('field_inheritance');
      $and_destination = $query->andConditionGroup()
        ->condition('destinationEntityType', 'registrant')
        ->condition('destinationEntityBundle', $registrant_type->id());
      $and_source = $query->andConditionGroup()
        ->condition('sourceEntityType', 'registrant')
        ->condition('sourceEntityBundle', $registrant_type->id());
      $or = $query->orConditionGroup()
        ->condition($and_destination)
        ->condition($and_source);
      $query->condition($or);
      $inherited_field_ids = $query->accessCheck(FALSE)->execute();

      if (!empty($inherited_field_ids)) {
        $inherited_fields = \Drupal::entityTypeManager()->getStorage('field_inheritance')->loadMultiple($inherited_field_ids);
        foreach ($inherited_fields as $field) {
          $field->delete();
        }
      }
      $registrant_type->delete();
    }
  }
}

/**
 * Implements hook_entity_operation_alter().
 */
function recurring_events_entity_operation_alter(array &$operations, EntityInterface $entity) {
  if ($entity->getEntityTypeId() == 'eventinstance_type') {
    if (!empty($operations['delete'])) {
      unset($operations['delete']);
    }
  }
}

/**
 * Implements hook_form_alter().
 */
function recurring_events_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  $form_object = $form_state->getFormObject();
  if (!empty($form_object) && $form_object instanceof EntityForm) {
    $entity = $form_state->getFormObject()->getEntity();
    if (!empty($entity) && $entity instanceof FieldableEntityInterface && strpos($form['#id'], 'delete') === FALSE) {
      $entity_type = $entity->getEntityTypeId();
      $bundle = $entity->bundle();
      if ($entity_type == 'eventinstance') {
        $series = $entity->getEventSeries();
        $inherited_field_ids = \Drupal::entityQuery('field_inheritance')
          ->condition('destinationEntityType', $entity_type)
          ->condition('destinationEntityBundle', $bundle)
          ->execute();

        if (!empty($inherited_field_ids)) {
          $state_key = $entity->getEntityTypeId() . ':' . $entity->uuid();
          $state = \Drupal::keyValue('field_inheritance');
          $state_values = $state->get($state_key);

          $inherited_fields = \Drupal::entityTypeManager()->getStorage('field_inheritance')->loadMultiple($inherited_field_ids);
          if (!isset($state_values['enabled'])) {
            $form['field_inheritance']['field_inheritance_enable']['#default_value'] = TRUE;
          }
          foreach ($inherited_fields as $field) {
            if (!isset($state_values[$field->idWithoutTypeAndBundle()]) && !isset($state_values[$field->idWithoutTypeAndBundle()]['skip'])) {
              $form['field_inheritance']['fields']['field_inheritance_' . $field->idWithoutTypeAndBundle()]['field_inheritance_field_entity_' . $field->idWithoutTypeAndBundle()]['#default_value'] = $series;
            }
          }
        }
      }
    }
  }
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function recurring_events_form_content_moderation_entity_moderation_form_alter(&$form, FormStateInterface $form_state) {
  $entity = $form_state->get('entity');
  if ($entity instanceof EventInterface && $entity->getEntityTypeId() === 'eventseries') {
    $original = \Drupal::entityTypeManager()->getStorage('eventseries')->load($entity->id());
    $creation_service = \Drupal::service('recurring_events.event_creation_service');
    if ($creation_service->checkForOriginalRecurConfigChanges($entity, $original)) {

      // Show a table when viewing a revision displaying any date recurrence
      // differences to alert users before they publish the revision.
      $diff_array = $creation_service->buildDiffArray($original, NULL, $entity);
      if (!empty($diff_array)) {
        $form['diff'] = [
          '#type' => 'container',
          '#weight' => -99,
        ];

        $form['diff']['diff_title'] = [
          '#type' => '#markup',
          '#prefix' => '<h2>',
          '#markup' => t('Revision Date Changes'),
          '#suffix' => '</h2>',
        ];

        $form['diff']['diff_message'] = [
          '#type' => '#markup',
          '#prefix' => '<p>',
          '#markup' => t('Recurrence configuration has been changed in this revision, as a result if you choose to publish this revision all instances will be removed and recreated. This action cannot be undone.'),
          '#suffix' => '</p>',
        ];

        $form['diff']['table'] = [
          '#type' => 'table',
          '#header' => [
            t('Data'),
            t('Stored'),
            t('Overridden'),
          ],
          '#rows' => $diff_array,
        ];
      }
    }
  }
}

/**
 * Implements hook_recurring_events_event_instances_pre_create_alter().
 */
function recurring_events_recurring_events_event_instances_pre_create_alter(array &$event_instances, EventSeries $event) {
  $config = \Drupal::config('recurring_events.eventseries.config');

  $messenger = \Drupal::messenger();

  $global_exclude = $global_include = [];
  $event_exclude = $event_include = [];

  $exclude_config = \Drupal::entityTypeManager()->getStorage('excluded_dates')->loadMultiple();
  $include_config = \Drupal::entityTypeManager()->getStorage('included_dates')->loadMultiple();

  if (!empty($exclude_config)) {
    foreach ($exclude_config as $date_config) {
      $global_exclude[] = [
        'value' => $date_config->start(),
        'end_value' => $date_config->end(),
      ];
    }
  }

  if (!empty($include_config)) {
    foreach ($include_config as $date_config) {
      $global_include[] = [
        'value' => $date_config->start(),
        'end_value' => $date_config->end(),
      ];
    }
  }

  if ($config->get('excludes')) {
    if (!empty($event->excluded_dates)) {
      $event_exclude = $event->excluded_dates->getValue();
    }
  }

  if ($config->get('includes')) {
    if (!empty($event->included_dates)) {
      $event_include = $event->included_dates->getValue();
    }
  }

  $exclude = array_merge($global_exclude, $event_exclude);
  $include = array_merge($global_include, $event_include);

  if (!empty($exclude)) {
    foreach ($event_instances as $key => $dates) {
      $start = $dates['start_date']->getTimestamp();
      $end = $dates['end_date']->getTimestamp();

      foreach ($exclude as $date) {
        $exclude_start = DrupalDateTime::createFromFormat(DateTimeItemInterface::DATETIME_STORAGE_FORMAT, $date['value'] . 'T00:00:00');
        $exclude_start = $exclude_start->getTimestamp();
        $excluded_end = DrupalDateTime::createFromFormat(DateTimeItemInterface::DATETIME_STORAGE_FORMAT, $date['end_value'] . 'T23:59:59');
        $excluded_end = $excluded_end->getTimestamp();

        if ($start >= $exclude_start && $start <= $excluded_end) {
          $messenger->addMessage(t('Skipping excluded date: @start_date - @end_date', [
            '@start_date' => $dates['start_date']->format($config->get('date_format')),
            '@end_date' => $dates['end_date']->format($config->get('date_format')),
          ]));
          unset($event_instances[$key]);
          break;
        }

        if ($end >= $exclude_start && $end <= $excluded_end) {
          $messenger->addMessage(t('Skipping excluded date: @start_date - @end_date', [
            '@start_date' => $dates['start_date']->format($config->get('date_format')),
            '@end_date' => $dates['end_date']->format($config->get('date_format')),
          ]));
          unset($event_instances[$key]);
          break;
        }
      }
    }
  }

  if (!empty($include)) {
    foreach ($event_instances as $key => $dates) {
      $include_event = FALSE;
      $start = $dates['start_date']->getTimestamp();
      $end = $dates['end_date']->getTimestamp();

      for ($x = 0; $x <= (count($include) - 1); $x++) {
        $included_start = DrupalDateTime::createFromFormat(DateTimeItemInterface::DATETIME_STORAGE_FORMAT, $include[$x]['value'] . 'T00:00:00');
        $included_start = $included_start->getTimestamp();
        $included_end = DrupalDateTime::createFromFormat(DateTimeItemInterface::DATETIME_STORAGE_FORMAT, $include[$x]['end_value'] . 'T23:59:59');
        $included_end = $included_end->getTimestamp();

        if ($start >= $included_start && $start <= $included_end && $end >= $included_start && $end <= $included_end) {
          $include_event = TRUE;
          // This event is in the inclusion range, so move on to the next one.
          break;
        }
      if (!$include_event) {
        $messenger->addMessage(t('Skipping non-included date: @start_date - @end_date', [
          '@start_date' => $dates['start_date']->format($config->get('date_format')),
          '@end_date' => $dates['end_date']->format($config->get('date_format')),
        ]));
        unset($event_instances[$key]);

/**
 * Implements callback_allowed_values_function().
 */
function recurring_events_allowed_values_function(FieldStorageDefinitionInterface $definition, FieldableEntityInterface $entity = NULL) {
  $values = ['custom' => t('Custom/Single Event')];
  $fields = \Drupal::service('entity_field.manager')->getBaseFieldDefinitions('eventseries');
  foreach ($fields as $field) {
    $field_definition = \Drupal::service('plugin.manager.field.field_type')->getDefinition($field->getType());
    $class = new \ReflectionClass($field_definition['class']);
    if ($class->implementsInterface('\Drupal\recurring_events\RecurringEventsFieldTypeInterface')) {
      $values[$field->getName()] = $field->getLabel();
    }
  }
  return $values;
}

/**
 * Implements hook_recurring_events_recur_field_types_alter().
 */
function recurring_events_recurring_events_recur_field_types_alter(&$fields) {
  // Enable/disable the recur field types based on the eventseries settings.
  $config = \Drupal::config('recurring_events.eventseries.config');
  $enabled_fields = explode(',', $config->get('enabled_fields'));

  foreach ($fields as $field_name => $field_label) {
    if (array_search($field_name, $enabled_fields) === FALSE) {
      unset($fields[$field_name]);
    }
  }
}

/**
 * Implements hook_theme_suggestions_HOOK().
 */
function recurring_events_theme_suggestions_eventseries(array $variables) {
  $suggestions = [];
  $entity = $variables['elements']['#eventseries'];
  $sanitized_view_mode = strtr($variables['elements']['#view_mode'], '.', '_');

  $suggestions[] = 'eventseries__' . $sanitized_view_mode;
  $suggestions[] = 'eventseries__' . $entity->bundle();
  $suggestions[] = 'eventseries__' . $entity->bundle() . '__' . $sanitized_view_mode;
  $suggestions[] = 'eventseries__' . $entity->id();
  $suggestions[] = 'eventseries__' . $entity->id() . '__' . $sanitized_view_mode;
  return $suggestions;
}

/**
 * Implements hook_theme_suggestions_HOOK().
 */
function recurring_events_theme_suggestions_eventinstance(array $variables) {
  $suggestions = [];
  $entity = $variables['elements']['#eventinstance'];
  $sanitized_view_mode = strtr($variables['elements']['#view_mode'], '.', '_');

  $suggestions[] = 'eventinstance__' . $sanitized_view_mode;
  $suggestions[] = 'eventinstance__' . $entity->bundle();
  $suggestions[] = 'eventinstance__' . $entity->bundle() . '__' . $sanitized_view_mode;
  $suggestions[] = 'eventinstance__' . $entity->id();
  $suggestions[] = 'eventinstance__' . $entity->id() . '__' . $sanitized_view_mode;
  return $suggestions;

/**
 * Implements hook_entity_base_field_info_alter().
 */
function recurring_events_entity_base_field_info_alter(&$fields, EntityTypeInterface $entity_type) {
  if ($entity_type->id() === 'eventseries') {
    $fields['event_instances'] = BaseFieldDefinition::create('entity_reference')
      ->setName('event_instances')
      ->setLabel(t('Event Instances'))
      ->setDescription('The event instances for this event.')
      ->setTargetEntityTypeId('eventseries')
      ->setReadOnly(TRUE)
      ->setComputed(TRUE)
      ->setSetting('target_type', 'eventinstance')
      ->setDisplayConfigurable('view', TRUE)
      ->setDisplayOptions('view', [
        'label' => 'above',
        'weight' => 0,
      ])
      ->setDisplayConfigurable('form', FALSE)
      ->setClass(EventInstances::class)
      ->setProvider('recurring_events');