From 76d8c115c0b5848256bcee44cab1a5f0d1c22e43 Mon Sep 17 00:00:00 2001 From: Govind Maloo Date: Wed, 12 Jul 2023 06:24:46 +0530 Subject: [PATCH] [CIVIC-1375, CIVIC-1378, CIVIC-1379] Added update hooks for multiple fields. (#1088) --- .../civictheme/civictheme.post_update.php | 307 ++++++++++++++++++ .../civictheme/src/CivicthemeUpdateHelper.php | 234 +++++++++++++ .../Update/CivicthemeUpdatePathFilledTest.php | 23 ++ 3 files changed, 564 insertions(+) create mode 100644 docroot/themes/contrib/civictheme/src/CivicthemeUpdateHelper.php diff --git a/docroot/themes/contrib/civictheme/civictheme.post_update.php b/docroot/themes/contrib/civictheme/civictheme.post_update.php index 46a7ca46b..79e4e757d 100644 --- a/docroot/themes/contrib/civictheme/civictheme.post_update.php +++ b/docroot/themes/contrib/civictheme/civictheme.post_update.php @@ -6,6 +6,9 @@ */ use Drupal\civictheme\CivicthemeConstants; +use Drupal\civictheme\CivicthemeUpdateHelper; +use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\StringTranslation\TranslatableMarkup; use Drupal\node\Entity\Node; require_once 'includes/utilities.inc'; @@ -112,3 +115,307 @@ function _civictheme_update_vertical_spacing(Node $node) { return $changed; } + +/** + * Update fields machine name and migrate content. + * + * Field renamed from 'field_c_p_column_count' to 'field_c_p_list_column_count' + * and 'field_c_p_fill_width' to 'field_c_p_list_fill_width'. + */ +function civictheme_post_update_rename_list_fields(&$sandbox) { + // New field configs. + $new_field_configs = [ + 'field.storage.paragraph.field_c_p_list_fill_width' => 'field_storage_config', + 'field.field.paragraph.civictheme_manual_list.field_c_p_list_fill_width' => 'field_config', + 'field.field.paragraph.civictheme_automated_list.field_c_p_list_fill_width' => 'field_config', + 'field.storage.paragraph.field_c_p_list_column_count' => 'field_storage_config', + 'field.field.paragraph.civictheme_manual_list.field_c_p_list_column_count' => 'field_config', + 'field.field.paragraph.civictheme_automated_list.field_c_p_list_column_count' => 'field_config', + ]; + + // Old field configs to remove. + $old_field_configs = [ + 'field.storage.paragraph.field_c_p_fill_width' => 'field_storage_config', + 'field.field.paragraph.civictheme_manual_list.field_c_p_fill_width' => 'field_config', + 'field.field.paragraph.civictheme_automated_list.field_c_p_fill_width' => 'field_config', + 'field.storage.paragraph.field_c_p_column_count' => 'field_storage_config', + 'field.field.paragraph.civictheme_manual_list.field_c_p_column_count' => 'field_config', + 'field.field.paragraph.civictheme_automated_list.field_c_p_column_count' => 'field_config', + ]; + + // Form diplay config per bundle. + $form_display_config = [ + 'civictheme_manual_list' => [ + 'field_c_p_list_column_count' => [ + 'type' => 'options_select', + 'weight' => 5, + 'region' => 'content', + 'settings' => [], + 'third_party_settings' => [], + ], + 'field_c_p_list_fill_width' => [ + 'type' => 'boolean_checkbox', + 'weight' => 6, + 'region' => 'content', + 'settings' => [ + 'display_label' => TRUE, + ], + 'third_party_settings' => [], + ], + ], + 'civictheme_automated_list' => [ + 'field_c_p_list_column_count' => [ + 'type' => 'options_select', + 'weight' => 5, + 'region' => 'content', + 'settings' => [], + 'third_party_settings' => [], + ], + 'field_c_p_list_fill_width' => [ + 'type' => 'options_select', + 'weight' => 5, + 'region' => 'content', + 'settings' => [ + 'display_label' => TRUE, + ], + 'third_party_settings' => [], + ], + ], + ]; + + // Form diplay group config per bundle. + $form_display_group_config = [ + 'civictheme_manual_list' => [ + 'group_columns' => [ + 'field_c_p_list_column_count', + 'field_c_p_list_fill_width', + ], + ], + 'civictheme_automated_list' => [ + 'group_columns' => [ + 'field_c_p_list_column_count', + 'field_c_p_list_fill_width', + ], + ], + ]; + + // Obtain configuration from yaml files. + $config_path = \Drupal::service('extension.list.theme')->getPath('civictheme') . '/config/install'; + $bundles = ['civictheme_manual_list', 'civictheme_automated_list']; + + $field_mapping = [ + 'field_c_p_fill_width' => 'field_c_p_list_fill_width', + 'field_c_p_column_count' => 'field_c_p_list_column_count', + ]; + + return \Drupal::classResolver(CivicthemeUpdateHelper::class)->update( + $sandbox, + 'paragraph', + $bundles, + // Start callback. + function (CivicthemeUpdateHelper $helper) use ($config_path, $new_field_configs) { + $helper->createConfigs($new_field_configs, $config_path); + }, + // Process callback. + function (CivicthemeUpdateHelper $helper, EntityInterface $entity) use (&$sandbox, $field_mapping) { + $helper->updateFieldContent($sandbox, $entity, $field_mapping); + }, + // Finished callback. + function (CivicthemeUpdateHelper $helper) use (&$sandbox, $old_field_configs, $form_display_config, $form_display_group_config) { + $helper->deleteConfig($sandbox, $old_field_configs); + + if ($sandbox['#finished'] >= 1) { + // Update form display setting. + foreach ($form_display_config as $bundle => $config) { + $helper->updateFormDisplay('paragraph', $bundle, $config, $form_display_group_config[$bundle]); + } + + $paragraph_types = array_keys($form_display_config); + $log = new TranslatableMarkup("Content from field 'field_c_p_column_count' was moved to 'field_c_p_list_column_count'. Content from field 'field_c_p_fill_width' was moved to 'field_c_p_list_fill_width'. + The 'field_c_p_column_count' and 'field_c_p_fill_width' were removed from %paragraph_types paragraph types. Please re-export your site configuration. \n", [ + '%paragraph_types' => implode(', ', $paragraph_types), + ]); + \Drupal::logger('update')->info(strip_tags($log)); + + // Returning log messge to diplay on review log screen after upgrade. + return $log; + } + }, + ); +} + +/** + * Replace Summary(field_c_p_summary) field to Content(field_c_p_content) field. + */ +function civictheme_post_update_replace_summary(&$sandbox) { + // New field configs. + $new_field_configs = [ + 'field.storage.paragraph.field_c_p_content' => 'field_storage_config', + 'field.field.paragraph.civictheme_attachment.field_c_p_content' => 'field_config', + 'field.field.paragraph.civictheme_callout.field_c_p_content' => 'field_config', + 'field.field.paragraph.civictheme_next_step.field_c_p_content' => 'field_config', + 'field.field.paragraph.civictheme_promo.field_c_p_content' => 'field_config', + ]; + + // Old field configs to remove. + $old_field_configs = [ + 'field.field.paragraph.civictheme_attachment.field_c_p_summary' => 'field_config', + 'field.field.paragraph.civictheme_callout.field_c_p_summary' => 'field_config', + 'field.field.paragraph.civictheme_next_step.field_c_p_summary' => 'field_config', + 'field.field.paragraph.civictheme_promo.field_c_p_summary' => 'field_config', + ]; + + $field_settings = [ + 'type' => 'string_textarea', + 'weight' => 1, + 'region' => 'content', + 'settings' => [ + 'rows' => 5, + 'placeholder' => '', + ], + 'third_party_settings' => [], + ]; + + $form_display_config = [ + 'civictheme_attachment' => [ + 'field_c_p_content' => $field_settings + ['weight' => 2], + ], + 'civictheme_callout' => [ + 'field_c_p_content' => $field_settings, + ], + 'civictheme_next_step' => [ + 'field_c_p_content' => $field_settings, + ], + 'civictheme_promo' => [ + 'field_c_p_content' => $field_settings, + ], + ]; + + // Obtain configuration from yaml files. + $config_path = \Drupal::service('extension.list.theme')->getPath('civictheme') . '/config/install'; + $bundles = array_keys($form_display_config); + + $field_mapping = [ + 'field_c_p_summary' => 'field_c_p_content', + ]; + + return \Drupal::classResolver(CivicthemeUpdateHelper::class)->update( + $sandbox, + 'paragraph', + $bundles, + // Start callback. + function (CivicthemeUpdateHelper $helper) use ($config_path, $new_field_configs) { + $helper->createConfigs($new_field_configs, $config_path); + }, + // Process callback. + function (CivicthemeUpdateHelper $helper, EntityInterface $entity) use (&$sandbox, $field_mapping) { + $helper->updateFieldContent($sandbox, $entity, $field_mapping); + }, + // Finished callback. + function (CivicthemeUpdateHelper $helper) use (&$sandbox, $old_field_configs, $form_display_config) { + $helper->deleteConfig($sandbox, $old_field_configs); + + if ($sandbox['#finished']) { + // Updated form display setting. + foreach ($form_display_config as $bundle => $config) { + $helper->updateFormDisplay('paragraph', $bundle, $config); + } + + $paragraph_types = array_keys($form_display_config); + $log = new TranslatableMarkup("Content from field 'field_c_p_summary' was moved to 'field_c_p_content'. The 'field_c_p_summary' field was removed from %paragraph_types paragraph types. + Please re-export your site configuration.\n", [ + '%paragraph_types' => implode(', ', $paragraph_types), + ]); + \Drupal::logger('update')->info(strip_tags($log)); + + // Returning log messge to diplay on review log screen after upgrade. + return $log; + } + }, + ); +} + +/** + * Rename date field machine name in Event content type. + * + * From field_c_n_date to field_c_n_date_range and migrated content. + */ +function civictheme_post_update_replace_date(&$sandbox) { + // New field configs. + $new_field_configs = [ + 'field.storage.node.field_c_n_date_range' => 'field_storage_config', + 'field.field.node.civictheme_event.field_c_n_date_range' => 'field_config', + ]; + + // Old field configs to remove. + $old_field_configs = [ + 'field.storage.node.field_c_n_date' => 'field_storage_config', + 'field.field.node.civictheme_event.field_c_n_date' => 'field_config', + ]; + + // Form display config. + $form_display_config = [ + 'civictheme_event' => [ + 'field_c_n_date_range' => [ + 'type' => 'daterange_default', + 'weight' => 13, + 'region' => 'content', + 'settings' => [], + 'third_party_settings' => [], + ], + ], + ]; + + // Form difplay group config. + $form_display_group_config = [ + 'civictheme_event' => [ + 'group_event' => [ + 'field_c_n_date_range', + ], + ], + ]; + + // Obtain configuration from yaml files. + $config_path = \Drupal::service('extension.list.theme')->getPath('civictheme') . '/config/install'; + $bundles = array_keys($form_display_config); + + $field_mapping = [ + 'field_c_n_date' => 'field_c_n_date_range', + ]; + + // Call the helper to migrate fields. + return \Drupal::classResolver(CivicthemeUpdateHelper::class)->update( + $sandbox, + 'node', + $bundles, + // Start callback. + function (CivicthemeUpdateHelper $helper) use ($config_path, $new_field_configs) { + $helper->createConfigs($new_field_configs, $config_path); + }, + // Process callback. + function (CivicthemeUpdateHelper $helper, EntityInterface $entity) use (&$sandbox, $field_mapping) { + $helper->updateFieldContent($sandbox, $entity, $field_mapping); + }, + // Finished callback. + function (CivicthemeUpdateHelper $helper) use (&$sandbox, $old_field_configs, $form_display_config, $form_display_group_config) { + $helper->deleteConfig($sandbox, $old_field_configs); + + if ($sandbox['#finished']) { + // Updated form display setting. + foreach ($form_display_config as $bundle => $config) { + $helper->updateFormDisplay('node', $bundle, $config, $form_display_group_config[$bundle]); + } + + $entity_types = array_keys($form_display_config); + $log = new TranslatableMarkup("Content from field 'field_c_n_date' was moved to 'field_c_n_date_range'. The 'field_c_n_date_range' field was removed from %entity_types node types. + Please re-export your site configuration.\n", [ + '%entity_types' => implode(', ', $entity_types), + ]); + \Drupal::logger('update')->info(strip_tags($log)); + + // Returning log messge to diplay on review log screen after upgrade. + return $log; + } + }, + ); +} diff --git a/docroot/themes/contrib/civictheme/src/CivicthemeUpdateHelper.php b/docroot/themes/contrib/civictheme/src/CivicthemeUpdateHelper.php new file mode 100644 index 000000000..84e43ece9 --- /dev/null +++ b/docroot/themes/contrib/civictheme/src/CivicthemeUpdateHelper.php @@ -0,0 +1,234 @@ +entityTypeManager = $entity_type_manager; + $this->logger = $logger; + $this->batchSize = $batch_size; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('entity_type.manager'), + $container->get('logger.factory')->get('action'), + self::BATCH_SIZE + ); + } + + /** + * Updates configuration entities as part of a Drupal update. + * + * @param array $sandbox + * Stores information for batch updates. + * @param string $entity_type + * Entity type to load. + * @param array $entity_bundles + * Entity build to filter entities. + * @param callable $start_callback + * Start callback function to call whne batch initialise. + * @param callable $process_callback + * Process callback to process entity. + * @param callable $finished_callback + * Finish callback called when batch finished. + */ + public function update(array &$sandbox, $entity_type, array $entity_bundles, callable $start_callback, callable $process_callback, callable $finished_callback) { + $storage = $this->entityTypeManager->getStorage($entity_type); + + // If the sandbox is empty, initialize it. + if (!isset($sandbox['entities'])) { + $sandbox['batch'] = 0; + $sandbox['current_entity'] = 0; + // Query to fetch all the manual_list paragraph ids. + $query = $storage->getQuery()->accessCheck(FALSE) + ->condition('type', $entity_bundles, 'in'); + $sandbox['entities'] = $query->execute(); + $sandbox['max'] = count($sandbox['entities']); + + $sandbox['results']['processed'] = []; + $sandbox['results']['updated'] = []; + $sandbox['results']['skipped'] = []; + + // Start callback. + call_user_func($start_callback, $this); + } + + $sandbox['batch']++; + + /** @var \Drupal\Core\Entity\EntityInterface $entity */ + $entities = $storage->loadMultiple(array_splice($sandbox['entities'], 0, $this->batchSize)); + + foreach ($entities as $entity) { + $sandbox['results']['processed'][] = $entity->id(); + $sandbox['current_enity'] = $entity; + // Process entity. + call_user_func($process_callback, $this, $entity); + } + + $sandbox['#finished'] = empty($sandbox['entities']) ? 1 : ($sandbox['max'] - count($sandbox['entities'])) / $sandbox['max']; + + if ($sandbox['#finished'] >= 1) { + // Finiished callback. + $log = call_user_func($finished_callback, $this); + + $log = new TranslatableMarkup("%finished\n
Update results ran in %batches batch(es):\n
Processed: %processed %processed_ids\n
Updated: %updated %updated_ids\n
Skipped: %skipped %skipped_ids\n
", [ + '%finished' => $log, + '%batches' => $sandbox['batch'], + '%processed' => count($sandbox['results']['processed']), + '%processed_ids' => count($sandbox['results']['processed']) ? '(' . implode(', ', $sandbox['results']['processed']) . ')' : '', + '%updated' => count($sandbox['results']['updated']), + '%updated_ids' => count($sandbox['results']['updated']) ? '(' . implode(', ', $sandbox['results']['updated']) . ')' : '', + '%skipped' => count($sandbox['results']['skipped']), + '%skipped_ids' => count($sandbox['results']['skipped']) ? '(' . implode(', ', $sandbox['results']['skipped']) . ')' : '', + ]); + $this->logger->info($log); + + return $log; + } + } + + /** + * Updated required field configs. + */ + public function createConfigs(array $configs, $config_path) { + $source = new FileStorage($config_path); + + // Check if field already exported in config/sync. + foreach ($configs as $config => $type) { + /** @var \Drupal\Core\Config\Entity\ConfigEntityStorageInterface */ + $storage = $this->entityTypeManager->getStorage($type); + $config_read = $source->read($config); + $id = substr($config, strpos($config, '.', 6) + 1); + if ($storage->load($id) == NULL) { + $config_entity = $storage->createFromStorageRecord($config_read); + $config_entity->save(); + } + } + } + + /** + * Delete field configs after content update. + */ + public function deleteConfig($sandbox, $configs) { + if ($sandbox['#finished'] >= 1) { + // Check if field already exported to config/sync. + foreach ($configs as $config => $type) { + $storage = $this->entityTypeManager->getStorage($type); + $id = substr($config, strpos($config, '.', 6) + 1); + $config_read = $storage->load($id); + if ($config_read != NULL) { + $config_read->delete(); + } + } + } + } + + /** + * Update field data for a given Paragraph. + */ + public function updateFieldContent(&$sandbox, FieldableEntityInterface $entity, array $mappings) { + $changed = FALSE; + + foreach ($mappings as $old_field => $new_field) { + // Update fill width field value. + if ($entity->hasField($new_field) && !is_null(civictheme_get_field_value($entity, $old_field, TRUE))) { + $entity->{$new_field} = civictheme_get_field_value($entity, $old_field, TRUE); + $changed = TRUE; + } + } + + if ($changed) { + $entity->save(); + $sandbox['results']['updated'][] = $entity->id(); + return $changed; + } + + $sandbox['results']['skipped'][] = $entity->id(); + + return $changed; + } + + /** + * Update form and group display. + */ + public function updateFormDisplay($entity_type, $bundle, array $field_config, array $group_config = NULL) { + /** @var \Drupal\Core\Entity\Display\EntityFormDisplayInterface $form_display */ + $form_display = $this->entityTypeManager + ->getStorage('entity_form_display') + ->load($entity_type . '.' . $bundle . '.default'); + + if ($form_display) { + foreach ($field_config as $field => $replacements) { + $component = $form_display->getComponent($field); + $component = $component ? array_replace_recursive($component, $replacements) : $replacements; + $form_display->setComponent($field, $component); + + // Update the field groups. + if ($group_config) { + $field_group = $form_display->getThirdPartySettings('field_group'); + + foreach ($group_config as $group_name => $group_config) { + if (!empty($field_group[$group_name]['children'])) { + $field_group[$group_name]['children'] = array_merge($field_group[$group_name]['children'], $group_config); + $form_display->setThirdPartySetting('field_group', $group_name, $field_group[$group_name]); + } + } + } + } + $form_display->save(); + } + } + +} diff --git a/docroot/themes/contrib/civictheme/tests/src/Functional/Update/CivicthemeUpdatePathFilledTest.php b/docroot/themes/contrib/civictheme/tests/src/Functional/Update/CivicthemeUpdatePathFilledTest.php index a8fb8a035..b9d7f42b8 100644 --- a/docroot/themes/contrib/civictheme/tests/src/Functional/Update/CivicthemeUpdatePathFilledTest.php +++ b/docroot/themes/contrib/civictheme/tests/src/Functional/Update/CivicthemeUpdatePathFilledTest.php @@ -65,6 +65,29 @@ public function testUpdates() { $this->assertSession()->pageTextContains('Processed: 145'); $this->assertSession()->pageTextContains('Updated: 9'); $this->assertSession()->pageTextContains('Skipped: 136'); + + // Assertions for civictheme_post_update_rename_list_fields(). + $this->assertSession()->pageTextContains('Update rename_list_fields'); + $this->assertSession()->pageTextContains("Content from field 'field_c_p_column_count' was moved to 'field_c_p_list_column_count'"); + $this->assertSession()->pageTextContains("Content from field 'field_c_p_fill_width' was moved to 'field_c_p_list_fill_width'."); + $this->assertSession()->pageTextContains('Update results ran'); + $this->assertSession()->pageTextContains('Processed: 24'); + $this->assertSession()->pageTextContains('Updated: 24'); + + // Assertions for civictheme_post_update_replace_summary(). + $this->assertSession()->pageTextContains('Update replace_summary'); + $this->assertSession()->pageTextContains("Content from field 'field_c_p_summary' was moved to 'field_c_p_content'"); + $this->assertSession()->pageTextContains('Update results ran'); + $this->assertSession()->pageTextContains('Processed: 48'); + $this->assertSession()->pageTextContains('Updated: 39'); + $this->assertSession()->pageTextContains('Skipped: 9'); + + // Assertions for civictheme_post_update_replace_date(). + $this->assertSession()->pageTextContains('Update replace_date'); + $this->assertSession()->pageTextContains("Content from field 'field_c_n_date' was moved to 'field_c_n_date_range'"); + $this->assertSession()->pageTextContains('Update results ran'); + $this->assertSession()->pageTextContains('Processed: 0'); + $this->assertSession()->pageTextContains('Updated: 0'); } }