You are here

date_recur.install in Recurring Dates Field 3.1.x

File

date_recur.install
View source
<?php

/**
 * @file
 * Install, update and uninstall functions for date_recur module.
 */
declare (strict_types=1);
use Drupal\Core\Datetime\Entity\DateFormat;
use Drupal\Core\Entity\Entity\EntityFormDisplay;
use Drupal\Core\Entity\Entity\EntityViewDisplay;
use Drupal\Core\Entity\Sql\SqlEntityStorageInterface;
use Drupal\Core\Utility\UpdateException;
use Drupal\date_recur\Entity\DateRecurInterpreter;
use Drupal\date_recur\Plugin\Field\FieldWidget\DateRecurBasicWidget;
use Drupal\field\Entity\FieldStorageConfig;

/**
 * Implements hook_requirements().
 */
function date_recur_requirements($phase) : array {
  $requirements = [];
  if (!class_exists('\\RRule\\RRule')) {
    $requirements['date_recur_dependencies'] = [
      'title' => t('Date recur'),
      'description' => \t('Date recur has unmet Composer dependencies. Read the <a href="@url">documentation</a> on how to install them.', [
        '@url' => 'https://www.drupal.org/node/2627292',
      ]),
      'severity' => \REQUIREMENT_ERROR,
    ];
  }
  return $requirements;
}

/**
 * Update date recur fields with new column schema for 'rrule' property.
 */
function date_recur_update_8201(&$sandbox) : void {

  // The process is:
  // 1. Determine all entity types with date recur fields and load their
  //    definitions.
  // 2. Update the SQL table schema for all relevant tables.
  // 3. Inform Drupal of the expected table schemas.
  // 4. Update Drupal's entity type definitions.
  $fieldType = 'date_recur';
  $schema = \Drupal::database()
    ->schema();
  $entityTypeManager = \Drupal::entityTypeManager();
  $entityFieldManager = \Drupal::service('entity_field.manager');
  $entityFieldMap = $entityFieldManager
    ->getFieldMapByFieldType($fieldType);
  $entityStorageSchemaSql = \Drupal::keyValue('entity.storage_schema.sql');

  /** @var \Drupal\Core\Entity\EntityLastInstalledSchemaRepositoryInterface $lastInstalledSchemaRepository */
  $lastInstalledSchemaRepository = \Drupal::service('entity.last_installed_schema.repository');

  // The new SQL schema for our column.
  $specification = [
    'description' => 'The repeat rule.',
    'type' => 'text',
  ];

  // Iterate over all date_recur fields for all entity types.
  foreach ($entityFieldMap as $entityTypeId => $fields) {
    $entityStorage = $entityTypeManager
      ->getStorage($entityTypeId);
    if (!$entityStorage instanceof SqlEntityStorageInterface) {
      continue;
    }
    $entityType = $entityTypeManager
      ->getDefinition($entityTypeId);

    // Loads definitions for all fields (even non date_recur).
    $entityFieldStorageDefinitions = $entityFieldManager
      ->getFieldStorageDefinitions($entityTypeId);

    /** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $tableMapping */
    $tableMapping = $entityStorage
      ->getTableMapping($entityFieldStorageDefinitions);

    // Intersect date_recur fields with storage definitions for all fields.

    /** @var \Drupal\Core\Field\FieldStorageDefinitionInterface[] $fieldDefinitions */
    $fieldDefinitions = array_intersect_key($entityFieldStorageDefinitions, $fields);

    // Iterate over all date_recur field definitions for this entity type.
    foreach ($fieldDefinitions as $fieldDefinition) {
      $fieldName = $fieldDefinition
        ->getName();
      $tables = [];
      $tables[] = $tableMapping
        ->getFieldTableName($fieldName);
      if ($entityType
        ->isRevisionable() && $fieldDefinition
        ->isRevisionable()) {
        $tables[] = $tableMapping
          ->getDedicatedRevisionTableName($fieldDefinition);
      }

      // Field type column names map to real table column names.
      $columns = $tableMapping
        ->getColumnNames($fieldName);
      $rruleColumnName = $columns['rrule'];
      foreach ($tables as $table) {

        // Change the column spec here.
        $schema
          ->changeField($table, $rruleColumnName, $rruleColumnName, $specification);
      }

      // Update the tracked entity table schema.
      $schemaKey = "{$entityTypeId}.field_schema_data.{$fieldName}";
      $fieldSchemaData = $entityStorageSchemaSql
        ->get($schemaKey);
      foreach ($fieldSchemaData as $tableName => $fieldSchema) {

        // Type is now 'text'.
        $fieldSchemaData[$tableName]['fields'][$rruleColumnName]['type'] = 'text';

        // Type previously was 'varchar', remove the length portion.
        unset($fieldSchemaData[$tableName]['fields'][$rruleColumnName]['length']);
      }
      $entityStorageSchemaSql
        ->set($schemaKey, $fieldSchemaData);

      // Update cached entity definitions for entity types with of single
      // cardinality base fields.
      if ($tableMapping
        ->allowsSharedTableStorage($fieldDefinition)) {
        $definitions = $lastInstalledSchemaRepository
          ->getLastInstalledFieldStorageDefinitions($entityTypeId);
        $definitions[$fieldName] = $fieldDefinition;
        $lastInstalledSchemaRepository
          ->setLastInstalledFieldStorageDefinitions($entityTypeId, $definitions);
      }
    }
  }
}

/**
 * Add a default value for new 'rrule_max_length' setting on date recur fields.
 *
 * Applies to attached fields only.
 */
function date_recur_update_8202(&$sandbox) : void {
  $fieldType = 'date_recur';
  $entityTypeManager = \Drupal::entityTypeManager();
  $entityFieldManager = \Drupal::service('entity_field.manager');
  $entityFieldMap = $entityFieldManager
    ->getFieldMapByFieldType($fieldType);

  // Iterate over all date_recur fields for all entity types.
  foreach ($entityFieldMap as $entityTypeId => $fields) {
    $entityStorage = $entityTypeManager
      ->getStorage($entityTypeId);
    if (!$entityStorage instanceof SqlEntityStorageInterface) {
      continue;
    }

    // Loads definitions for all fields (even non date_recur).
    $entityFieldStorageDefinitions = $entityFieldManager
      ->getFieldStorageDefinitions($entityTypeId);

    // Intersect date_recur fields with storage definitions for all fields.

    /** @var \Drupal\Core\Field\FieldStorageDefinitionInterface[] $fieldDefinitions */
    $fieldDefinitions = array_intersect_key($entityFieldStorageDefinitions, $fields);

    // Iterate over all date_recur field definitions for this entity type.
    foreach ($fieldDefinitions as $fieldDefinition) {
      if ($fieldDefinition instanceof FieldStorageConfig) {

        // Ignore base fields, etc.
        $fieldDefinition
          ->setSetting('rrule_max_length', 256);
        $fieldDefinition
          ->save();
      }
    }
  }
}

/**
 * Checks for invalid time zones in storage.
 */
function date_recur_update_8203(&$sandbox) : void {
  $timeZoneList = timezone_identifiers_list();
  $database = \Drupal::database();
  $entityTypeManager = \Drupal::entityTypeManager();
  $entityFieldManager = \Drupal::service('entity_field.manager');
  $entityFieldMap = $entityFieldManager
    ->getFieldMapByFieldType('date_recur');

  // Iterate over all date_recur fields for all entity types.
  foreach ($entityFieldMap as $entityTypeId => $fields) {
    $entityStorage = $entityTypeManager
      ->getStorage($entityTypeId);
    if (!$entityStorage instanceof SqlEntityStorageInterface) {
      continue;
    }

    // Loads definitions for all fields (even non date_recur).
    $entityFieldStorageDefinitions = $entityFieldManager
      ->getFieldStorageDefinitions($entityTypeId);

    /** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $tableMapping */
    $tableMapping = $entityStorage
      ->getTableMapping($entityFieldStorageDefinitions);

    // Intersect date_recur fields with storage definitions for all fields.

    /** @var \Drupal\Core\Field\FieldStorageDefinitionInterface[] $fieldDefinitions */
    $fieldDefinitions = array_intersect_key($entityFieldStorageDefinitions, $fields);

    // Iterate over all date_recur field definitions for this entity type.
    foreach ($fieldDefinitions as $fieldDefinition) {
      $fieldName = $fieldDefinition
        ->getName();
      $table = $tableMapping
        ->getFieldTableName($fieldName);

      // Field type column names map to real table column names.
      $columns = $tableMapping
        ->getColumnNames($fieldName);
      $timeZoneColumnName = $columns['timezone'];
      $select = $database
        ->select($table, 'field_table');
      $select
        ->addField('field_table', $timeZoneColumnName);
      $select
        ->distinct();
      $result = $select
        ->execute();
      if (!$result) {
        throw new \Exception('Problem executing query.');
      }
      $allTimeZones = $result
        ->fetchCol($timeZoneColumnName);
      $diff = array_diff($allTimeZones, $timeZoneList);
      if (count($diff)) {

        // This exception prevents further updates from happening until it is
        // resolved.
        throw new UpdateException(sprintf('Invalid time zones found for field `%s` in table `%s`: `%s`. Please resolve these invalid values manually before continuing.', $fieldName, $table, implode(', ', $diff)));
      }
    }
  }
}

/**
 * Update view and form display configurations.
 */
function date_recur_update_8204(&$sandbox) : void {

  /** @var \Drupal\Core\Entity\Display\EntityFormDisplayInterface[] $displays */
  $displays = EntityFormDisplay::loadMultiple();
  foreach ($displays as $display) {
    $components = $display
      ->getComponents();
    foreach ($components as $component => $options) {
      $updated = FALSE;
      $type = $options['type'] ?? NULL;
      if ($type === 'date_recur_default_widget') {

        // Change the ID.
        $options['type'] = 'date_recur_basic_widget';

        // No settings to update.
        $updated = TRUE;
      }

      // Interactive widget did not change ID or settings.
      if ($updated) {
        $display
          ->setComponent($component, $options);
      }
    }
    $display
      ->save();
  }

  /** @var \Drupal\Core\Entity\Display\EntityViewDisplayInterface[] $displays */
  $displays = EntityViewDisplay::loadMultiple();
  foreach ($displays as $display) {
    $components = $display
      ->getComponents();
    foreach ($components as $component => $options) {
      $updated = FALSE;
      $type = $options['type'] ?? NULL;
      if ($type === 'date_recur_default_formatter') {

        // Option: show_next. Unchanged.
        // Option: count_per_item. Unchanged.
        // Option: occurrence_format_type. Unchanged.
        // Option: same_end_date_format_type. Unchanged.
        // Change the ID.
        $options['type'] = 'date_recur_basic_formatter';

        // Option: show_rrule. Changed to 'interpreter'.
        if (isset($options['show_rrule'])) {
          unset($options['show_rrule']);
        }
        $updated = TRUE;
      }
      if ($updated) {
        $display
          ->setComponent($component, $options);
      }
    }
    $display
      ->save();
  }
}

/**
 * Add a default interpreter.
 */
function date_recur_update_8205(&$sandbox) : void {
  if (DateFormat::load('long') && !DateRecurInterpreter::load('default_interpreter')) {
    $configFactory = \Drupal::configFactory();
    $interpreter = $configFactory
      ->getEditable('date_recur.interpreter.default_interpreter');
    $interpreter
      ->setData([
      'id' => 'default_interpreter',
      'label' => 'Default interpreter',
      'plugin' => 'rl',
      'settings' => [
        'show_start_date' => TRUE,
        'show_until' => TRUE,
        'date_format' => 'long',
        'show_infinite' => TRUE,
      ],
    ]);
    $interpreter
      ->save(TRUE);
  }
}

/**
 * Removes default time zone setting from widgets extending basic widget.
 */
function date_recur_update_8206(&$sandbox) : void {

  // Get widgets implementing the same class as basic widget.

  /** @var \Drupal\Core\Field\WidgetPluginManager $fieldWidgetPluginManager */
  $basicWidgetClass = DateRecurBasicWidget::class;
  $basicWidgetDerivativeWidgetIds = [];
  $basicWidgetDerivativeWidgetIds[] = 'date_recur_basic_widget';
  $fieldWidgetPluginManager = \Drupal::service('plugin.manager.field.widget');
  foreach ($fieldWidgetPluginManager
    ->getDefinitions() as $widgetId => $definition) {
    $class = $definition['class'];
    if (class_exists($class) && (new \ReflectionClass($class))
      ->isSubclassOf($basicWidgetClass)) {
      $basicWidgetDerivativeWidgetIds[] = $widgetId;
    }
  }

  // Find form displays using any of the widgets extending basic widget.

  /** @var \Drupal\Core\Entity\Display\EntityFormDisplayInterface[] $displays */
  $displays = EntityFormDisplay::loadMultiple();
  foreach ($displays as $display) {
    $updated = 0;
    $components = $display
      ->getComponents();
    foreach ($components as $component => $options) {
      $widgetId = $options['type'] ?? NULL;
      if (in_array($widgetId, $basicWidgetDerivativeWidgetIds)) {

        // Unset the old setting.
        unset($options['settings']['timezone_override']);
        $updated++;
        $display
          ->setComponent($component, $options);
      }
    }
    if ($updated > 0) {
      $display
        ->save();
    }
  }
}

Functions

Namesort descending Description
date_recur_requirements Implements hook_requirements().
date_recur_update_8201 Update date recur fields with new column schema for 'rrule' property.
date_recur_update_8202 Add a default value for new 'rrule_max_length' setting on date recur fields.
date_recur_update_8203 Checks for invalid time zones in storage.
date_recur_update_8204 Update view and form display configurations.
date_recur_update_8205 Add a default interpreter.
date_recur_update_8206 Removes default time zone setting from widgets extending basic widget.