Post update functions for Paragraphs.
<?php
/**
* @file
* Post update functions for Paragraphs.
*/
use Drupal\Core\Database\Database;
use Drupal\Core\Database\Query\Condition;
use Drupal\Core\Entity\Sql\SqlEntityStorageInterface;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Site\Settings;
use Drupal\field\Entity\FieldStorageConfig;
/**
* Set the parent id, type and field name to the already created paragraphs.
*
* @param $sandbox
*/
function paragraphs_post_update_set_paragraphs_parent_fields(&$sandbox) {
// Don't execute the function if paragraphs_update_8003() was already executed
// which used to do the same.
$module_schema = \Drupal::service('update.update_hook_registry')
->getInstalledVersion('paragraphs');
// The state entry 'paragraphs_update_8003_placeholder' is used in order to
// indicate that the placeholder paragraphs_update_8003() function has been
// executed, so this function needs to be executed as well. If the non
// placeholder version of paragraphs_update_8003() got executed already, the
// state won't be set and we skip this update.
if ($module_schema >= 8003 && !\Drupal::state()
->get('paragraphs_update_8003_placeholder', FALSE)) {
return;
}
if (!isset($sandbox['current_paragraph_field_id'])) {
$paragraph_field_ids = [];
// Get all the entity reference revisions fields.
$map = \Drupal::service('entity_field.manager')
->getFieldMapByFieldType('entity_reference_revisions');
foreach ($map as $entity_type_id => $info) {
foreach ($info as $name => $data) {
if (FieldStorageConfig::loadByName($entity_type_id, $name)
->getSetting('target_type') == 'paragraph') {
$paragraph_field_ids[] = "{$entity_type_id}.{$name}";
}
}
}
if (!$paragraph_field_ids) {
// There are no paragraph fields. Return before initializing the sandbox.
return;
}
// Initialize the sandbox.
$sandbox['current_paragraph_field_id'] = 0;
$sandbox['paragraph_field_ids'] = $paragraph_field_ids;
$sandbox['max'] = count($paragraph_field_ids);
$sandbox['progress'] = 0;
}
/** @var \Drupal\field\FieldStorageConfigInterface $field_storage */
$field_storage = FieldStorageConfig::load($sandbox['paragraph_field_ids'][$sandbox['current_paragraph_field_id']]);
// For revisionable entity types, we load and update all revisions.
$target_entity_type = \Drupal::entityTypeManager()
->getDefinition($field_storage
->getTargetEntityTypeId());
if ($target_entity_type
->isRevisionable()) {
$revision_id = $target_entity_type
->getKey('revision');
$entity_ids = \Drupal::entityQuery($field_storage
->getTargetEntityTypeId())
->condition($field_storage
->getName(), NULL, 'IS NOT NULL')
->range($sandbox['progress'], Settings::get('paragraph_limit', 50))
->allRevisions()
->sort($revision_id, 'ASC')
->accessCheck(FALSE)
->execute();
}
else {
$id = $target_entity_type
->getKey('id');
$entity_ids = \Drupal::entityQuery($field_storage
->getTargetEntityTypeId())
->condition($field_storage
->getName(), NULL, 'IS NOT NULL')
->range($sandbox['progress'], Settings::get('paragraph_limit', 50))
->sort($id, 'ASC')
->accessCheck(FALSE)
->execute();
}
foreach ($entity_ids as $revision_id => $entity_id) {
// For revisionable entity types, we load a specific revision otherwise load
// the entity.
if ($target_entity_type
->isRevisionable()) {
$host_entity = \Drupal::entityTypeManager()
->getStorage($field_storage
->getTargetEntityTypeId())
->loadRevision($revision_id);
}
else {
$host_entity = \Drupal::entityTypeManager()
->getStorage($field_storage
->getTargetEntityTypeId())
->load($entity_id);
}
foreach ($host_entity
->get($field_storage
->getName()) as $field_item) {
// Skip broken and already updated references (e.g. Nested paragraphs).
if ($field_item->entity && empty($field_item->entity->parent_type->value)) {
// Set the parent fields and save, ensure that no new revision is
// created.
$field_item->entity->parent_type = $field_storage
->getTargetEntityTypeId();
$field_item->entity->parent_id = $host_entity
->id();
$field_item->entity->parent_field_name = $field_storage
->getName();
$field_item->entity
->setNewRevision(FALSE);
$field_item->entity
->save();
}
}
}
// Continue with the next paragraph_field_id when the loaded entities are less
// than paragraph_limit.
if (count($entity_ids) < Settings::get('paragraph_limit', 50)) {
$sandbox['current_paragraph_field_id']++;
$sandbox['progress'] = 0;
}
else {
$sandbox['progress'] += Settings::get('paragraph_limit', 50);
}
// Update #finished, 1 if the whole update has finished.
$sandbox['#finished'] = empty($sandbox['max']) ? 1 : $sandbox['current_paragraph_field_id'] / $sandbox['max'];
}
/**
* Update the parent fields with revisionable data.
*/
function paragraphs_post_update_rebuild_parent_fields(array &$sandbox) {
$database = \Drupal::database();
$entity_type_manager = \Drupal::entityTypeManager();
/** @var \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager */
$entity_field_manager = \Drupal::service('entity_field.manager');
$entity_definition_update_manager = \Drupal::entityDefinitionUpdateManager();
$paragraph_revisions_data_table = $entity_type_manager
->getDefinition('paragraph')
->getRevisionDataTable();
$paragraph_storage = $entity_type_manager
->getStorage('paragraph');
if (!isset($sandbox['current_index'])) {
$entity_reference_revisions_fields = $entity_field_manager
->getFieldMapByFieldType('entity_reference_revisions');
$paragraph_field_ids = [];
foreach ($entity_reference_revisions_fields as $entity_type_id => $fields) {
// Skip non-revisionable entity types.
$entity_type_definition = $entity_definition_update_manager
->getEntityType($entity_type_id);
if (!$entity_type_definition || !$entity_type_definition
->isRevisionable()) {
continue;
}
// Skip non-SQL entity storage implementations.
$storage = $entity_type_manager
->getStorage($entity_type_id);
if (!$storage instanceof SqlEntityStorageInterface) {
continue;
}
$storage_definitions = $entity_field_manager
->getFieldStorageDefinitions($entity_type_id);
$storage_definitions = array_intersect_key($storage_definitions, $fields);
// Process the fields that reference paragraphs.
$storage_definitions = array_filter($storage_definitions, function (FieldStorageDefinitionInterface $field_storage) {
return $field_storage
->getSetting('target_type') === 'paragraph';
});
foreach ($storage_definitions as $field_name => $field_storage) {
// Get the field revision table name.
$table_mapping = $storage
->getTableMapping();
$column_names = $table_mapping
->getColumnNames($field_name);
$revision_column = $column_names['target_revision_id'];
if ($field_storage instanceof BaseFieldDefinition && $field_storage
->getCardinality() === 1) {
$field_revision_table = $storage
->getRevisionDataTable() ?: $storage
->getRevisionTable();
$entity_id_column = $entity_type_definition
->getKey('id');
}
else {
$field_revision_table = $table_mapping
->getDedicatedRevisionTableName($field_storage);
$entity_id_column = 'entity_id';
}
// Build a data array of the needed data to do the query.
$data = [
'entity_type_id' => $entity_type_id,
'field_name' => $field_name,
'revision_table' => $field_revision_table,
'entity_id_column' => $entity_id_column,
'revision_column' => $revision_column,
'langcode_column' => $entity_type_definition
->getKey('langcode'),
];
// Nested paragraphs must be updated first.
if ($entity_type_id === 'paragraph') {
array_unshift($paragraph_field_ids, $data);
}
else {
$paragraph_field_ids[] = $data;
}
}
}
if (empty($paragraph_field_ids)) {
// There are no paragraph fields. Return before initializing the sandbox.
return;
}
// Initialize the sandbox.
$sandbox['current_index'] = 0;
$sandbox['paragraph_field_ids'] = $paragraph_field_ids;
$sandbox['max'] = count($paragraph_field_ids);
$sandbox['max_revision_id'] = NULL;
}
$current_field = $sandbox['paragraph_field_ids'][$sandbox['current_index']];
$revision_column = !empty($current_field['revision_column']) ? $current_field['revision_column'] : $current_field['field_name'] . '_target_revision_id';
$entity_id_column = $current_field['entity_id_column'];
$entity_type_id = $current_field['entity_type_id'];
$field_name = $current_field['field_name'];
// Select the field values from the revision of the parent entity type.
$query = $database
->select($current_field['revision_table'], 'f');
// Join tables by paragraph revision IDs.
$query
->innerJoin($paragraph_revisions_data_table, 'p', "f.{$revision_column} = p.revision_id");
$query
->fields('f', [
$entity_id_column,
$revision_column,
]);
// Select paragraphs with at least one wrong parent field.
$or_group = new Condition('OR');
// Only use CAST if the db driver is Postgres.
if (Database::getConnection()
->databaseType() == 'pgsql') {
$or_group
->where("CAST(p.parent_id as TEXT) <> CAST(f.{$entity_id_column} as TEXT)");
}
else {
$or_group
->where("p.parent_id <> f.{$entity_id_column}");
}
$or_group
->condition('p.parent_type', $entity_type_id, '<>');
$or_group
->condition('p.parent_field_name', $field_name, '<>');
$query
->condition($or_group);
// Match the langcode so we can deal with revisions translations.
if (!empty($current_field['langcode_column'])) {
$query
->where('p.langcode = f.' . $current_field['langcode_column']);
}
// Order the query by revision ID and limit the number of results.
$query
->orderBy('p.revision_id');
// Only check the revisions that are not already processed.
if ($sandbox['max_revision_id']) {
$query
->condition('p.revision_id', $sandbox['max_revision_id'], '>');
}
// Limit the number of processed paragraphs per run.
$query
->range(0, Settings::get('paragraph_limit', 100));
$results = $query
->execute()
->fetchAll();
// Update the parent fields of the identified paragraphs revisions.
foreach ($results as $result) {
/** @var \Drupal\paragraphs\ParagraphInterface $revision */
$revision = $paragraph_storage
->loadRevision($result->{$revision_column});
if ($revision) {
$revision
->set('parent_id', $result->{$entity_id_column});
$revision
->set('parent_type', $entity_type_id);
$revision
->set('parent_field_name', $field_name);
$revision
->save();
}
}
// Continue with the next element in case we processed all the paragraphs
// assigned to the current paragraph field.
if (count($results) < Settings::get('paragraph_limit', 100)) {
$sandbox['current_index']++;
$sandbox['max_revision_id'] = NULL;
}
else {
$last_revision_result = end($results);
$sandbox['max_revision_id'] = $last_revision_result->{$revision_column};
}
// Update finished key if the whole update has finished.
$sandbox['#finished'] = empty($sandbox['max']) ? 1 : $sandbox['current_index'] / $sandbox['max'];
}
Name | Description |
---|---|
paragraphs_post_update_rebuild_parent_fields | Update the parent fields with revisionable data. |
paragraphs_post_update_set_paragraphs_parent_fields | Set the parent id, type and field name to the already created paragraphs. |