View source
<?php
namespace Drupal\entity_hierarchy\Storage;
use Drupal\Component\Utility\Number;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
class TreeRebuilder {
protected $entityTypeManager;
public function __construct(EntityTypeManagerInterface $entityTypeManager) {
$this->entityTypeManager = $entityTypeManager;
}
public function getRebuildTasks($field_name, $entity_type_id) {
$batch = [
'title' => new TranslatableMarkup('Rebuilding tree for field @field, @entity_type_id ...', [
'@field' => $field_name,
'@entity_type_id' => $entity_type_id,
]),
'operations' => [
[
[
static::class,
'removeTable',
],
[
$field_name,
$entity_type_id,
],
],
],
'finished' => [
static::class,
'batchFinished',
],
];
$entityType = $this->entityTypeManager
->getDefinition($entity_type_id);
$idKey = $entityType
->getKey('id');
$query = $this->entityTypeManager
->getStorage($entity_type_id)
->getAggregateQuery()
->groupBy("{$field_name}.target_id")
->groupBy("{$field_name}.weight")
->groupBy($idKey)
->sort("{$field_name}.target_id")
->sort("{$field_name}.weight")
->exists($field_name)
->accessCheck(FALSE);
$records = $query
->execute();
$sorted = $this
->treeSort($field_name, $records, $idKey);
foreach ($sorted as $entity_id => $entry) {
$batch['operations'][] = [
[
static::class,
'rebuildTree',
],
[
$field_name,
$entity_type_id,
$entity_id,
],
];
}
return $batch;
}
public static function removeTable($field_name, $entity_type_id) {
\Drupal::database()
->schema()
->dropTable(\Drupal::service('entity_hierarchy.nested_set_storage_factory')
->getTableName($field_name, $entity_type_id, FALSE));
}
protected function sortItems(array $a, array $b) {
$a_path = (string) $a['materialized_path'];
$b_path = (string) $b['materialized_path'];
if ($a_path === $b_path) {
return 0;
}
return $a_path < $b_path ? -1 : 1;
}
public static function rebuildTree($field_name, $entity_type_id, $entity_id, &$context) {
$entity = \Drupal::entityTypeManager()
->getStorage($entity_type_id)
->load($entity_id);
$entity
->get($field_name)
->postSave(TRUE);
self::debug(sprintf('Rebuilt %s', $entity_id));
$context['results'][] = $entity_id;
}
public static function batchFinished($success, $results, $operations) {
if ($success) {
$message = new TranslatableMarkup('Finished rebuilding tree, @count items were processed.', [
'@count' => count($results),
]);
\Drupal::messenger()
->addMessage($message);
}
else {
$error_operation = reset($operations);
$message = new TranslatableMarkup('An error occurred while processing %error_operation with arguments: @arguments', [
'%error_operation' => implode('::', $error_operation[0]),
'@arguments' => print_r($error_operation[1], TRUE),
]);
\Drupal::messenger()
->addMessage($message, 'error');
}
}
protected function treeSort($field_name, array $records, $idKey) {
$items = [];
$weights = [];
$sets = [];
foreach ($records as $ix => $item) {
$parent = $item["{$field_name}_target_id"];
$sets[$parent][] = $item[$idKey];
$items[$item[$idKey]] = $parent;
}
foreach (array_keys($sets) as $parent) {
if (!isset($items[$parent])) {
$items[$parent] = 0;
$sets[0][] = $parent;
}
}
$flipped_sets = array_map(function (array $items) {
return array_flip($items);
}, $sets);
foreach ($items as $id => $parent) {
$flipped = $flipped_sets[$parent];
if (isset($weights[$id])) {
continue;
}
$weights[$id] = [
$flipped[$id],
];
if (!isset($weights[$parent]) && isset($items[$parent])) {
$this
->buildThread($weights, $items, $parent, $items[$parent], $flipped_sets);
}
if (isset($weights[$parent])) {
$weights[$id] = array_merge($weights[$parent], $weights[$id]);
}
}
$sorted = array_map(function (array $item) {
return [
'materialized_path' => implode('.', array_map([
Number::class,
'intToAlphadecimal',
], $item)),
];
}, $weights);
uasort($sorted, [
$this,
'sortItems',
]);
return array_diff_key($sorted, array_flip($sets[0]));
}
protected function buildThread(array &$weights, array $items, $id, $parent, array $flipped_sets) {
$flipped = $flipped_sets[$parent];
$weights[$id] = [
$flipped[$id],
];
if (isset($items[$parent])) {
$next_parent = $items[$parent];
$flipped = $flipped_sets[$next_parent];
$weights[$parent] = [
$flipped[$parent],
];
if (!isset($weights[$next_parent]) && isset($items[$next_parent])) {
$this
->buildThread($weights, $items, $next_parent, $items[$next_parent], $flipped_sets);
}
if (isset($weights[$next_parent])) {
$weights[$parent] = array_merge($weights[$next_parent], $weights[$parent]);
}
$weights[$id] = array_merge($weights[$parent], $weights[$id]);
}
}
protected static function debug($message) {
\Drupal::logger($message);
}
}