View source
<?php
namespace Drupal\multiversion\Entity\Index;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\KeyValueStore\KeyValueFactoryInterface;
use Drupal\multiversion\Entity\Workspace;
use Drupal\multiversion\Workspace\WorkspaceManagerInterface;
use Fhaculty\Graph\Graph;
class RevisionTreeIndex implements RevisionTreeIndexInterface {
protected $keyValueFactory;
protected $workspaceManager;
protected $indexFactory;
protected $workspaceId;
public function __construct(KeyValueFactoryInterface $key_value_factory, WorkspaceManagerInterface $workspace_manager, MultiversionIndexFactory $index_factory) {
$this->keyValueFactory = $key_value_factory;
$this->workspaceManager = $workspace_manager;
$this->indexFactory = $index_factory;
}
public function useWorkspace($id) {
$this->workspaceId = $id;
return $this;
}
public function getTree($uuid) {
$values = $this
->buildTree($uuid);
return $values['tree'];
}
public function getGraph($uuid) {
$tree = $this
->getTree($uuid);
$graph = new Graph();
$rev_ids = [];
$this
->storeNodesId($tree, $rev_ids);
$vertices = $this
->generateVertices($graph, $rev_ids);
$this
->generateEdges($vertices, $tree);
return $graph;
}
protected function storeNodesId(array $tree, array &$revision_ids) {
foreach ($tree as $value) {
$current_id = $value['#rev'];
$revision_ids[$current_id] = $current_id;
if (count($value['children'])) {
$this
->storeNodesId($value['children'], $revision_ids);
}
}
}
protected function generateEdges(array $revisions_array, array $tree, $parent = -1) {
foreach ($tree as $item) {
$current_id = $item['#rev'];
if ($parent != -1) {
$revisions_array[$parent]
->createEdgeTo($revisions_array[$current_id]);
}
if (count($item['children'])) {
$this
->generateEdges($revisions_array, $item['children'], $current_id);
}
}
}
protected function generateVertices(Graph $graph, array $revision_ids) {
foreach ($revision_ids as $id) {
$ids[] = $id;
}
return $graph
->createVertices($ids)
->getMap();
}
public function updateTree(ContentEntityInterface $entity, array $branch = []) {
if ($entity
->getEntityType()
->get('workspace') === FALSE) {
$this
->keyValueStore($entity
->uuid(), 0)
->setMultiple($branch);
}
else {
$this
->keyValueStore($entity
->uuid())
->setMultiple($branch);
}
return $this;
}
public function countRevs($uuid) {
$values = $this
->buildTree($uuid);
return count($values['default_branch']);
}
public function getDefaultRevision($uuid) {
$values = $this
->buildTree($uuid);
return $values['default_rev']['#rev'];
}
public function getDefaultBranch($uuid) {
$values = $this
->buildTree($uuid);
return $values['default_branch'];
}
public function getOpenRevisions($uuid) {
$revs = [];
$values = $this
->buildTree($uuid);
foreach ($values['open_revs'] as $rev => $element) {
$revs[$rev] = $element['#rev_info']['status'];
}
return $revs;
}
public function getConflicts($uuid) {
$revs = [];
$values = $this
->buildTree($uuid);
foreach ($values['conflicts'] as $rev => $element) {
$revs[$rev] = $element['#rev_info']['status'];
}
return $revs;
}
public static function sortRevisions(array $a, array $b) {
$a_deleted = $a['#rev_info']['status'] == 'deleted' ? TRUE : FALSE;
$b_deleted = $b['#rev_info']['status'] == 'deleted' ? TRUE : FALSE;
if ($a_deleted && !$b_deleted) {
return 1;
}
elseif (!$a_deleted && $b_deleted) {
return -1;
}
list($a_id) = explode('-', $a['#rev']);
list($b_id) = explode('-', $b['#rev']);
if ($a_id === $b_id) {
return $a['#rev'] < $b['#rev'] ? 1 : -1;
}
else {
return $a_id < $b_id ? 1 : -1;
}
}
public static function sortTree(array &$tree) {
usort($tree, [
__CLASS__,
'sortRevisions',
]);
foreach ($tree as &$element) {
if (!empty($element['children'])) {
self::sortTree($element['children']);
}
}
}
protected function keyValueStore($uuid, $workspace_id = NULL) {
if (!is_numeric($workspace_id)) {
$workspace_id = $this
->getWorkspaceId();
}
return $this->keyValueFactory
->get("multiversion.entity_index.rev.tree.{$workspace_id}.{$uuid}");
}
protected function buildTree($uuid) {
$revs = $this
->keyValueStore($uuid)
->getAll();
if (!$revs) {
$revs = $this
->keyValueStore($uuid, 0)
->getAll();
}
$keys = [];
foreach (array_keys($revs) as $rev) {
$keys[] = "{$uuid}:{$rev}";
}
$workspace = Workspace::load($this
->getWorkspaceId());
$revs_info = $this->indexFactory
->get('multiversion.entity_index.rev', $workspace)
->getMultiple($keys);
return self::doBuildTree($uuid, $revs, $revs_info);
}
protected static function doBuildTree($uuid, $revs, $revs_info, $parse = 0, &$tree = [], &$open_revs = [], &$conflicts = []) {
foreach ($revs as $rev => $parent_revs) {
foreach ($parent_revs as $parent_rev) {
if ($rev == 0) {
continue;
}
if ($parent_rev == $parse) {
if ($rev == $parse) {
throw new \InvalidArgumentException('Child and parent revision can not be the same value.');
}
$i = count($tree);
$tree[$i] = [
'#type' => 'rev',
'#uuid' => $uuid,
'#rev' => $rev,
'#rev_info' => [
'status' => isset($revs_info["{$uuid}:{$rev}"]['status']) ? $revs_info["{$uuid}:{$rev}"]['status'] : 'missing',
'default' => FALSE,
'open_rev' => FALSE,
'conflict' => FALSE,
],
'children' => [],
];
self::doBuildTree($uuid, $revs, $revs_info, $rev, $tree[$i]['children'], $open_revs, $conflicts);
if (empty($tree[$i]['children']) && $tree[$i]['#rev_info']['status'] != 'missing') {
$tree[$i]['#rev_info']['open_rev'] = TRUE;
$open_revs[$rev] = $tree[$i];
if ($tree[$i]['#rev_info']['status'] != 'deleted') {
$tree[$i]['#rev_info']['conflict'] = TRUE;
$conflicts[$rev] = $tree[$i];
}
}
}
}
}
if ($parse == 0) {
$default_rev = 0;
uasort($open_revs, [
__CLASS__,
'sortRevisions',
]);
$default_rev = reset($open_revs);
unset($conflicts[$default_rev['#rev']]);
uasort($conflicts, [
__CLASS__,
'sortRevisions',
]);
self::updateDefaultRevision($tree, $default_rev);
self::sortTree($tree);
$default_branch = [];
$rev = $default_rev['#rev'];
while ($rev != 0) {
$default_branch[$rev] = isset($revs_info["{$uuid}:{$rev}"]['status']) ? $revs_info["{$uuid}:{$rev}"]['status'] : 'missing';
$rev = $revs[$rev][0];
}
return [
'tree' => $tree,
'default_rev' => $default_rev,
'default_branch' => array_reverse($default_branch),
'open_revs' => $open_revs,
'conflicts' => $conflicts,
];
}
}
protected static function updateDefaultRevision(&$tree, $default_rev) {
foreach ($tree as &$element) {
if (isset($element['#rev']) && $element['#rev'] == $default_rev['#rev']) {
$element['#rev_info']['default'] = TRUE;
$element['#rev_info']['conflict'] = FALSE;
break;
}
if (!empty($element['children'])) {
self::updateDefaultRevision($element['children'], $default_rev);
}
}
}
protected function getWorkspaceId() {
return $this->workspaceId ?: $this->workspaceManager
->getActiveWorkspaceId();
}
}