protected function performSave()
{
// there are two situation here, an orthodox form submittal and a ajax one:
// - the orthodox will send a json string
// - the ajax version will send an array
$var = \Input::get($this->name);
if (is_string($var)) {
$var = json_decode($var, true);
}
$movements = [];
$subtreeId = $this->source->getKey();
// We now invert the order of movements and group/sort them by
// depth. This is done to avoid the situation where a node wants
// to become the descendant of one of its own descendants.
// This kind of sort will prevent the issue ensuring all the descendants
// are moved first.
$this->sortMovementsByDepth($var, $movements, $subtreeId);
ksort($movements);
$movements = call_user_func_array('array_merge', $movements);
$movements = Collection::make($movements)->keyBy('id');
/** @var \Baum\Extensions\Eloquent\Collection $nodes */
$root = $this->source->getRoot();
// store depth and left ot the root, to build upon when
// we will rebuild the tree.
$rootDepth = $root->depth;
$rootLeft = $root->lft;
// now we read the entire tree. We need to do that because
// of the nested set way workings: Baum provides handy methods
// to move the nodes, but they trigger an awful lot of queries.
// We'd rather read the whole tree once instead, and perform all
// the calculations in-memory.
$nodes = $root->getDescendantsAndSelf([$this->source->getKeyName(), 'lft', 'rgt', 'depth', 'parent_id']);
// the ids of all the moved elements
$movedIds = $movements->keys()->toArray();
// index the elements by primary key for speedy retrieval
$dictionary = $nodes->getDictionary();
// the elements of the bigger tree that did not change their
// parent_id
$unmoved = new \Baum\Extensions\Eloquent\Collection();
foreach ($dictionary as $n) {
if (!in_array($n->getKey(), $movedIds)) {
$unmoved[] = $n;
}
}
// the elements that were moved to a different parent
$moved = new \Baum\Extensions\Eloquent\Collection();
foreach ($movedIds as $i) {
$moved[] = $dictionary[$i];
}
// this is the column that Baum uses to order the tree
// the default is `lft`
$orderColumn = $this->source->getOrderColumnName();
// we backup the order column, because we have to mess with
// it later and we want to be able to restore it so we can
// still use `$node->isDirty()` to see if the the node needs
// to be updated or not.
foreach ($dictionary as $n) {
$n->__order = $n->{$orderColumn};
}
// what now? We put all the moved nodes before the rest of the
// tree. This way they'll be put before their unmoved siblings
// shall they exist.
$orderedNodes = $moved->merge($unmoved);
// shady stuff going on here: Baum collections build the hierarchy
// based on parent id AND the order column (lft). We thus update the
// order column with an incremental value to be sure the siblings
// order is preserved.
$order = 1;
foreach ($orderedNodes as $n) {
$n->{$orderColumn} = $order++;
if (isset($movements[$n->getKey()])) {
// is the parent_id changed? If so, let's update it
$n->parent_id = $movements[$n->getKey()]['parent_id'];
}
}
// let Baum build the new tree
$newTree = $orderedNodes->toHierarchy();
// lets restore the order column and delete the previous backup,
// so we can use `$node->isDirty` later
foreach ($dictionary as $n) {
$n->{$orderColumn} = $n->__order;
unset($n->__order);
}
// if everything worked correctly we should have a nested collection
// with only one root element. The root ID should be unchanged.
$newRoot = $newTree->first();
if ($newRoot->getKey() != $root->getKey() || count($newTree) != 1) {
throw new \LogicException("Invalid tree");
}
// now we take the new tree and recursively recalculate the left, right
// and depth fields.
$left = $rootLeft - 1;
$depth = $rootDepth;
$reindex = function ($tree, $reindex, $depth) use(&$left) {
foreach ($tree as $node) {
$left++;
$node->lft = $left;
$node->depth = $depth;
$reindex($node->getRelation('children'), $reindex, $depth + 1);
$left++;
$node->rgt = $left;
}
};
$reindex($newTree, $reindex, $depth);
// compute the changes and only save the changed ones!
$bulk = [];
foreach ($dictionary as $n) {
if ($n->isDirty()) {
$bulk[$n->getKey()] = ['lft' => $n->lft, 'rgt' => $n->rgt, 'depth' => $n->depth, 'parent_id' => $n->parent_id];
}
}
foreach ($bulk as $id => $fields) {
\DB::table($this->source->getTable())->where($this->source->getKeyName(), $id)->update($fields);
}
}