date_recur-8.x-2.2/date_recur.install
date_recur.install
<?php
/**
* @file
* Install, update and uninstall functions for date_recur module.
*/
declare(strict_types=1);
use Drupal\Core\Database\SchemaObjectExistsException;
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\DateRecurOccurrences;
use Drupal\date_recur\Entity\DateRecurInterpreter;
use Drupal\date_recur\Plugin\Field\FieldWidget\DateRecurBasicWidget;
use Drupal\field\Entity\FieldConfig;
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, TRUE)) {
// Unset the old setting.
unset($options['settings']['timezone_override']);
$updated++;
$display->setComponent($component, $options);
}
}
if ($updated > 0) {
$display->save();
}
}
}
/**
* Updates default time zone configuration.
*/
function date_recur_update_8207(&$sandbox): void {
/** @var \Drupal\Core\Entity\EntityFieldManagerInterface $entityFieldManager */
$entityFieldManager = \Drupal::service('entity_field.manager');
$entityFieldMap = $entityFieldManager->getFieldMapByFieldType('date_recur');
// Iterate over all date_recur fields for all entity types.
/** @var \Drupal\Core\Field\FieldConfigInterface[] $drFields */
$drFields = [];
foreach ($entityFieldMap as $entityTypeId => $fields) {
foreach ($fields as $fieldName => ['bundles' => $bundles]) {
foreach ($bundles as $bundle) {
$field = FieldConfig::loadByName($entityTypeId, $bundle, $fieldName);
if ($field) {
array_push($drFields, $field);
}
}
}
}
foreach ($drFields as $fieldConfig) {
$defaultValue = $fieldConfig->getDefaultValueLiteral();
// Skip if the whole value is empty.
// Don't bother updating install functions, these will be removed.
if (empty($defaultValue)) {
continue;
}
// Don't bother updating install functions, these will be removed.
$defaultValue[0]['default_time_zone_source'] = !empty($defaultValue[0]['default_time_zone'])
? 'fixed'
: 'current_user';
$fieldConfig->setDefaultValue($defaultValue);
$fieldConfig->save();
}
}
/**
* Update date recur occurrence table schemas to add a primary key.
*/
function date_recur_update_8208(&$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.
$fieldType = 'date_recur';
$schema = \Drupal::database()->schema();
$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;
}
$entityType = $entityTypeManager->getDefinition($entityTypeId);
$primaryKey = [
$entityType->isRevisionable() ? 'revision_id' : 'entity_id',
'field_delta',
'delta',
];
// 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) {
$tableName = DateRecurOccurrences::getOccurrenceCacheStorageTableName($fieldDefinition);
try {
$schema->addPrimaryKey($tableName, $primaryKey);
}
catch (SchemaObjectExistsException $e) {
// Somehow people already have primary keys set, so ignore.
continue;
}
}
}
}
