Skip to content

Instantly share code, notes, and snippets.

@acbramley
Created January 24, 2025 01:28
Show Gist options
  • Save acbramley/53578b18b27466f1b508aedb12907b5e to your computer and use it in GitHub Desktop.
Save acbramley/53578b18b27466f1b508aedb12907b5e to your computer and use it in GitHub Desktop.
Third party settings legacyAdditionalModuleKey fix
<?php
declare(strict_types=1);
/**
* @file
* Post update hooks.
*/
use Drupal\my_profile\MyProfileUpdateUtils;
use Drupal\Core\Entity\Query\QueryInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\layout_builder\Plugin\SectionStorage\OverridesSectionStorage;
use Drupal\node\Entity\Node;
/**
* Removes legacyAdditionalModuleKey from LB sections.
*/
function my_profile_profile_post_update_remove_legacy_key(array &$sandbox): TranslatableMarkup {
$process = static function (Node $node): void {
$uuid = \Drupal::service('uuid');
$resave_node = FALSE;
$layout = $node->get(OverridesSectionStorage::FIELD_NAME);
/** @var \Drupal\layout_builder\Section[] $sections */
$sections = $layout->getSections();
foreach ($sections as $section) {
// Get all components for the current section.
$components = $section->getComponents();
foreach ($components as $component) {
// Get all variables of the current component.
$object_vars = get_object_vars($component);
// Validate: If there are no variables, then there certainly will not
// be any legacyAdditionalModuleKey variable.
if (empty($object_vars)) {
continue;
}
// We cannot simply use isset() because that does not work with binary
// strings like $object_vars["\x00*\x00legacyAdditionalModuleKey"].
// Instead, convert to json string and see if that contains
// legacyAdditionalModuleKey.
$json = json_encode($object_vars);
// Validate.
if (!str_contains($json, '\u0000*\u0000legacyAdditionalModuleKey')) {
continue;
}
// If we are here, the $component has a legacyAdditionalModuleKey
// variable. To fix it, we convert the object to array and then create
// the object again from that array.
// This gets rid of the legacyAdditionalModuleKey.
$component_array = $component->toArray();
$component_array['uuid'] = $uuid->generate();
$new_component = $component->fromArray($component_array);
$section->insertAfterComponent($component->getUuid(), $new_component);
$section->removeComponent($component->getUuid());
$resave_node = TRUE;
}
if ($resave_node) {
$node->setSyncing(TRUE)->save();
}
}
};
return MyProfileUpdateUtils::entityMigration(
entityTypeClass: Node::class,
sandbox: $sandbox,
process: $process,
query: static fn (QueryInterface $query) => $query
->condition('layout_builder__layout.section', '%legacyAdditionalModuleKey%', 'LIKE'),
revisions: TRUE,
);
}
<?php
declare(strict_types=1);
namespace Drupal\my_profile;
use Drupal\Core\Entity\RevisionableStorageInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
/**
* Utility functions for post update hooks.
*/
final class MyProfileUpdateUtils {
/**
* Helper function to run entity migrations.
*
* @param class-string<T> $entityTypeClass
* The entity type class. The entity type's ID must be an auto-incrementing
* positive-only integer.
* @param array $sandbox
* The postupdate sandbox.
* @param callable $process
* A callable to apply for each entity.
* @param callable|null $query
* A callable to modify the processed entities. This must not change between
* iterations. Leave NULL if processing all entities.
* @param int $iterationSize
* Number of entities to process per batch.
* @param bool $revisions
* Whether to update entity revisions.
*
* @phpstan-param callable(T $entity): void $process
* @phpstan-param null|callable(\Drupal\Core\Entity\Query\QueryInterface $query): (void|\Drupal\Core\Entity\Query\QueryInterface) $query
*
* @return \Drupal\Core\StringTranslation\TranslatableMarkup
* The message for the current iteration.
*
* @template T of \Drupal\Core\Entity\EntityInterface
*/
public static function entityMigration(string $entityTypeClass, array &$sandbox, callable $process, ?callable $query = NULL, int $iterationSize = 50, bool $revisions = FALSE): TranslatableMarkup {
// Setup.
$entityTypeRepo = \Drupal::service('entity_type.repository');
$storage = \Drupal::entityTypeManager()->getStorage($entityTypeRepo->getEntityTypeFromClass($entityTypeClass));
if (!isset($sandbox['total'])) {
$all = $storage->getQuery();
$all->accessCheck(FALSE);
if ($query) {
$query($all);
}
if ($revisions) {
$all->allRevisions();
}
$ids = $all->execute();
$sandbox['ids'] = $revisions ? \array_keys($ids) : $ids;
$sandbox['total'] = \count($sandbox['ids']);
$sandbox['progress'] = 0;
}
$ids = \array_splice($sandbox['ids'], 0, $iterationSize);
if ($revisions) {
\assert($storage instanceof RevisionableStorageInterface);
$entities = $storage->loadMultipleRevisions($ids);
}
else {
$entities = $storage->loadMultiple($ids);
}
foreach ($entities as $entity) {
$process($entity);
}
$storage->resetCache($ids);
$sandbox['progress'] += \count($ids);
$sandbox['#finished'] = empty($sandbox['total']) ? 1 : ($sandbox['progress'] / $sandbox['total']);
return new TranslatableMarkup('Processed @entity_type_id entities (@count/@total): @ids', [
'@ids' => \implode(', ', $ids),
'@count' => $sandbox['progress'],
'@total' => $sandbox['total'],
'@entity_type_id' => $storage->getEntityTypeId(),
]);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment