Bitrix-D7 23.9
 
Загрузка...
Поиск...
Не найдено
tree.php
1<?php
12namespace Bitrix\Sale\Location;
13
14use Bitrix\Main;
18
22
23Loc::loadMessages(__FILE__);
24
25abstract class Tree extends Entity\DataManager
26{
28 const SORT_FREE_AFTER = 2;
29 const SORT_HOLE_SIZE = 10;
31
32 const BLOCK_INSERT_MTU = 9999;
33
34 const SPACE_ADD = 1;
35 const SPACE_REMOVE = 2;
36
37 public static function add(array $data)
38 {
39 return self::addExtended($data);
40 }
41
46 public static function addExtended(array $data, array $additional = array())
47 {
48 $rebalance = !isset($additional['REBALANCE']) || $additional['REBALANCE'] !== false;
49
50 // determine LEFT_MARGIN, RIGHT_MARGIN and DEPTH_LEVEL
51 if($data['PARENT_ID'] = intval($data['PARENT_ID']))
52 {
53 // if we have PARENT_ID set, just use it`s info
54 $node = self::getNodeInfo($data['PARENT_ID']);
55
56 $needResort = true;
57
58 $data['LEFT_MARGIN'] = $node['RIGHT_MARGIN'];
59 $data['RIGHT_MARGIN'] = $node['RIGHT_MARGIN'] + 1;
60 $data['DEPTH_LEVEL'] = $node['DEPTH_LEVEL'] + 1;
61 $data['PARENT_ID'] = $node['ID'];
62 }
63 else
64 {
65 // otherwise, we assume we have "virtual root node", that has LEFT_MARGIN == 0 and RIGHT_MARGIN == +INFINITY
66 // it allows us to have actually a forest, not a tree
67
68 $rm = self::getMaxMargin();
69 $needResort = false;
70
71 $data['LEFT_MARGIN'] = $rm > 1 ? $rm + 1 : 1;
72 $data['RIGHT_MARGIN'] = $rm > 1 ? $rm + 2 : 2;
73
74 $data['DEPTH_LEVEL'] = 1;
75 $data['PARENT_ID'] = 0;
76 }
77
78 // process insert options: INSERT_AFTER and INSERT_BEFORE
79 //self::processInsertInstruction($data);
80
81 $addResult = parent::add($data);
82
83 if($addResult->isSuccess() && $needResort && $rebalance)
84 self::rebalance($node, $addResult->getId());
85
86 return $addResult;
87 }
88
89 protected static function rebalance($node, $id)
90 {
91 self::manageFreeSpace($node['RIGHT_MARGIN'], 2, self::SPACE_ADD, $id);
92 }
93
94 // we must guarantee tree integrity in any situation, so make low-level checking to prevent walking around
95 public static function checkFields(Entity\Result $result, $primary, array $data)
96 {
97 parent::checkFields($result, $primary, $data);
98
99 if(!($result instanceof Entity\UpdateResult)) // work out only when update()
100 return;
101
102 foreach (static::getEntity()->getFields() as $field)
103 {
104 if($field->getName() == 'PARENT_ID' && mb_strlen($data['PARENT_ID']))
105 {
106 //it cant be parent for itself
107 if(intval($primary['ID']) == intval($data['PARENT_ID']))
108 {
109 $result->addError(new Entity\FieldError(
110 $field,
111 Loc::getMessage('SALE_LOCATION_TREE_ENTITY_CANNOT_MOVE_STRAIGHT_TO_ITSELF_EXCEPTION'),
112 Entity\FieldError::INVALID_VALUE
113 ));
114 }
115 else
116 {
117 try
118 {
119 $node = self::getNodeInfo($primary['ID']);
120 $nodeDst = self::getNodeInfo($data['PARENT_ID']);
121
122 // new parent cannot belong to node subtree
123 if($node['PARENT_ID'] != $nodeDst['ID'])
124 {
125 if($nodeDst['LEFT_MARGIN'] >= $node['LEFT_MARGIN'] && $nodeDst['RIGHT_MARGIN'] <= $node['RIGHT_MARGIN'])
126 {
127 $result->addError(new Entity\FieldError(
128 $field,
129 Loc::getMessage('SALE_LOCATION_TREE_ENTITY_CANNOT_MOVE_TO_ITSELF_EXCEPTION'),
130 Entity\FieldError::INVALID_VALUE
131 ));
132 }
133 }
134
135 }
136 catch(Main\SystemException $e)
137 {
138 }
139 }
140 }
141 }
142 }
143
144 public static function update($primary, array $data)
145 {
146 return self::update($primary, $data);
147 }
148
153 public static function updateExtended($primary, array $data, array $additional = array())
154 {
155 $rebalance = !isset($additional['REBALANCE']) || $additional['REBALANCE'] !== false;
156 $node = self::getNodeInfo($primary);
157
158 if(isset($data['PARENT_ID']) && !mb_strlen($data['PARENT_ID']))
159 $data['PARENT_ID'] = 0;
160
161 $updResult = parent::update($primary, $data);
162
163 // if we have 'PARENT_ID' key in $data, and it was changed, we should relocate subtree
164 if($updResult->isSuccess() && isset($data['PARENT_ID']) && (intval($node['PARENT_ID']) != intval($data['PARENT_ID'])) && $rebalance)
165 self::moveSubtree($primary, $data['PARENT_ID']);
166
167 return $updResult;
168 }
169
170 public static function delete($primary)
171 {
172 static::deleteExtended($primary);
173 }
174
187 public static function deleteExtended($primary, array $additional = array()) // here also could be an implementation of CHILDREN_REATTACH
188 {
189 $rebalance = !isset($additional['REBALANCE']) || $additional['REBALANCE'] !== false;
190 $deleteSubtree = !isset($additional['DELETE_SUBTREE']) || $additional['DELETE_SUBTREE'] !== false;
191
192 if($deleteSubtree)
193 {
194 // it means we want to delete not only the following node, but the whole subtree that belongs to it
195 // note that with this option set to Y tree structure integrity will be compromised
196
197 $node = self::getNodeInfo($primary);
198 if(intval($node['ID']))
199 {
200 static::checkNodeThrowException($node);
201 // low-level
202 Main\HttpApplication::getConnection()->query('delete from '.static::getTableName().' where LEFT_MARGIN > '.$node['LEFT_MARGIN'].' and RIGHT_MARGIN < '.$node['RIGHT_MARGIN']);
203
204 // and also remove free spece, if needed
205 if($rebalance)
206 {
207 self::manageFreeSpace(
208 $node['RIGHT_MARGIN'],
209 ($node['RIGHT_MARGIN'] - $node['LEFT_MARGIN']) + 1,
210 self::SPACE_REMOVE
211 );
212 }
213 }
214 else
215 {
216 throw new Tree\NodeNotFoundException(false, array('INFO' => array('ID' => $primary)));
217 }
218 }
219
220 return parent::delete($primary);
221 }
222
228 public static function getSubtreeRangeSqlForNode($primary, $node = array())
229 {
230 $primary = Assert::expectIntegerPositive($primary, '$primary');
231
232 if(empty($node))
233 {
234 $node = self::getNodeInfo($primary);
235 if(!intval($node['ID']))
236 {
237 throw new Tree\NodeNotFoundException(false, array('INFO' => array('ID' => $primary)));
238 }
239 }
240
241 static::checkNodeThrowException($node);
242
243 $query = new Main\Entity\Query(static::getEntity());
244 $query->setSelect(array('ID'));
245 $query->setFilter(array(
246 '>LEFT_MARGIN' => $node['LEFT_MARGIN'],
247 '<RIGHT_MARGIN' => $node['RIGHT_MARGIN']
248 ));
249
250 return $query->getQuery();
251 }
252
253 public static function checkIntegrity()
254 {
255 return !self::getList([
256 'select' => ['ID'],
257 'filter' => [
258 'LOGIC' => 'OR',
259 ['LEFT_MARGIN' => false],
260 ['RIGHT_MARGIN' => false]
261 ],
262 'limit' => 1
263 ])->fetch();
264 }
265
266 public static function checkNodeIsParentOfNodeById($primary, $childPrimary, $behaviour = array('CHECK_DIRECT' => false))
267 {
268 $primary = Assert::expectIntegerPositive($primary, '$primary');
269 $childPrimary = Assert::expectIntegerPositive($childPrimary, '$childPrimary');
270
271 return static::checkNodeIsParentOfNodeByCondition(array('=ID' => $primary), array('=ID' => $childPrimary), $behaviour);
272 }
273
274 protected static function checkNodeIsParentOfNodeByCondition($parentNodeFilter, $nodeFilter, $behaviour = array('CHECK_DIRECT' => false))
275 {
276 $parent = static::getList(array('filter' => $parentNodeFilter, 'limit' => 1))->fetch();
277 $child = static::getList(array('filter' => $nodeFilter, 'limit' => 1))->fetch();
278
279 if(!intval($parent['ID']))
280 throw new Main\SystemException('Node being checked not found');
281 if(!intval($child['ID']))
282 throw new Main\SystemException('Child node not found');
283
284 if($behaviour['CHECK_DIRECT'])
285 return $parent['ID'] == $child['PARENT_ID'];
286
287 return $parent['LEFT_MARGIN'] < $child['LEFT_MARGIN'] && $parent['RIGHT_MARGIN'] > $child['RIGHT_MARGIN'];
288 }
289
290 // recalc left_margin & right_margin in the whole tree
291 // strongly recommened to invoke only inside a transaction
292 public static function resort($dontCareEvents = false)
293 {
294 $edges = array();
295 $nodes = array();
296
297 $res = parent::getList(array('select' => array('ID', 'PARENT_ID', 'LEFT_MARGIN', 'RIGHT_MARGIN')));
298 while($item = $res->Fetch())
299 {
300 $nodes[$item['ID']] = array(
301 'LEFT_MARGIN' => $item['LEFT_MARGIN'],
302 'RIGHT_MARGIN' => $item['RIGHT_MARGIN']
303 );
304
305 if(!intval($item['PARENT_ID']))
306 $edges['ROOT'][] = $item['ID'];
307 else
308 $edges[$item['PARENT_ID']][] = $item['ID'];
309 }
310
311 // walk tree in-deep to obtain correct margins
312 self::walkTreeInDeep('ROOT', $edges, $nodes, 0, 0, $dontCareEvents);
313
314 // now massively insert new values into the database, using a temporal table
315 $tabName = 'b_sale_location_temp_'.rand(99, 9999);
316 $entityTableName = static::getTableName();
317
318 $dbConnection = Main\HttpApplication::getConnection();
319
320 $dbConnection->query("create table ".$tabName." (
321 ID ".Helper::getSqlForDataType('int').",
322 LEFT_MARGIN ".Helper::getSqlForDataType('int').",
323 RIGHT_MARGIN ".Helper::getSqlForDataType('int').",
324 DEPTH_LEVEL ".Helper::getSqlForDataType('int')."
325 )");
326
327 $handle = new BlockInserter(array(
328 'tableName' => $tabName,
329 'exactFields' => array(
330 'ID' => array('data_type' => 'integer'),
331 'LEFT_MARGIN' => array('data_type' => 'integer'),
332 'RIGHT_MARGIN' => array('data_type' => 'integer'),
333 'DEPTH_LEVEL' => array('data_type' => 'integer'),
334 ),
335 'parameters' => array(
336 'mtu' => self::BLOCK_INSERT_MTU
337 )
338 ));
339 foreach($nodes as $id => $node)
340 {
341 $node['ID'] = $id;
342 $handle->insert($node);
343 }
344 $handle->flush();
345
346 // merge temp table with location table
347 Helper::mergeTables($entityTableName, $tabName, array(
348 'LEFT_MARGIN' => 'LEFT_MARGIN',
349 'RIGHT_MARGIN' => 'RIGHT_MARGIN',
350 'DEPTH_LEVEL' => 'DEPTH_LEVEL'
351 ), array('ID' => 'ID'));
352
353 $dbConnection->query("drop table {$tabName}");
354 }
355
356 public static function getPathToNode($primary, $parameters, $behaviour = array('SHOW_LEAF' => true))
357 {
358 $primary = Assert::expectIntegerPositive($primary, '$primary');
359 if(!is_array($behaviour))
360 $behaviour = array();
361 if(!isset($behaviour['SHOW_LEAF']))
362 $behaviour['SHOW_LEAF'] = true;
363
364 return self::getPathToNodeByCondition(array('ID' => $primary), $parameters, $behaviour);
365 }
366
375 public static function getPathToNodeByCondition($filter, $parameters = array(), $behaviour = array('SHOW_LEAF' => true))
376 {
377 $filter = Assert::expectNotEmptyArray($filter, '$filter');
378
379 if(!is_array($behaviour))
380 $behaviour = array();
381 if(!isset($behaviour['SHOW_LEAF']))
382 $behaviour['SHOW_LEAF'] = true;
383
384 if(empty($parameters))
385 $parameters = array();
386
387 // todo: try to do this job in a single query with join. Speed profit?
388
389 $node = self::getList(array('filter' => $filter, 'limit' => 1))->fetch();
390 if(!isset($node['ID']))
391 throw new Main\SystemException(Loc::getMessage('SALE_LOCATION_TREE_ENTITY_NODE_NOT_FOUND_EXCEPTION'));
392
393 $parameters['filter']['<=LEFT_MARGIN'] = intval($node['LEFT_MARGIN']);
394 $parameters['filter']['>=RIGHT_MARGIN'] = intval($node['RIGHT_MARGIN']);
395
396 if(!$behaviour['SHOW_LEAF'])
397 $parameters['filter']['!=ID'] = $node['ID'];
398
399 $parameters['order'] = array(
400 'LEFT_MARGIN' => 'asc'
401 );
402
403 return self::getList($parameters);
404 }
405
406 public static function getPathToMultipleNodes($nodeInfo = array(), $parameters = array(), $behaviour = array('SHOW_LEAF' => true))
407 {
408 Assert::expectNotEmptyArray($nodeInfo, '$nodeInfo');
409
410 if(!is_array($behaviour))
411 $behaviour = array();
412 if(!isset($behaviour['SHOW_LEAF']))
413 $behaviour['SHOW_LEAF'] = true;
414
415 if(empty($parameters))
416 $parameters = array();
417
418 if(is_array($parameters['select']))
419 $originSelect = $parameters['select'];
420 else
421 $originSelect = array();
422
423 $parameters['order'] = array(
424 'LEFT_MARGIN' => 'asc'
425 );
426 $parameters['select'][] = 'ID';
427 $parameters['select'][] = 'PARENT_ID';
428
429 $filter = array();
430 foreach($nodeInfo as $node)
431 {
432 Assert::expectNotEmptyArray($node, '$nodeInfo[]');
433 $node['ID'] = Assert::expectIntegerPositive($node['ID'], '$nodeInfo[][ID]');
434 $node['LEFT_MARGIN'] = Assert::expectIntegerNonNegative($node['LEFT_MARGIN'], '$nodeInfo[][LEFT_MARGIN]');
435 $node['RIGHT_MARGIN'] = Assert::expectIntegerPositive($node['RIGHT_MARGIN'], '$nodeInfo[][RIGHT_MARGIN]');
436
437 $filter[] = array(
438 '<=LEFT_MARGIN' => intval($node['LEFT_MARGIN']),
439 '>=RIGHT_MARGIN' => intval($node['RIGHT_MARGIN'])
440 );
441
442 if(!$behaviour['SHOW_LEAF'])
443 $filter['!=ID'] = $node['ID'];
444 }
445 $filter['LOGIC'] = 'OR';
446
447 $parameters['filter'][] = $filter;
448
449 $res = self::getList($parameters);
450
451 $index = array();
452
453 while($item = $res->Fetch())
454 {
455 $index[$item['ID']] = array(
456 'NODE' => $item,
457 'PARENT_ID' => $item['PARENT_ID']
458 );
459 }
460
461 $depthLimit = count($index);
462
463 $pathes = array();
464 foreach($nodeInfo as $node)
465 {
466 $path = array();
467 $id = $node['ID'];
468
469 $i = 0;
470 while($id)
471 {
472 if($i >= $depthLimit) // there is a cycle or smth like, anyway this is abnormal situation
473 break;
474
475 if(!isset($index[$id])) // non-existing element in the chain. strange, abort
476 break;
477
478 $resultNode = $index[$id]['NODE'];
479 if(!in_array('PARENT_ID', $originSelect))
480 unset($resultNode['PARENT_ID']);
481 if(!in_array('ID', $originSelect))
482 unset($resultNode['ID']);
483 $path[$id] = $resultNode;
484
485 $id = intval($index[$id]['PARENT_ID']);
486
487 $i++;
488 }
489
490 $pathes[$node['ID']] = array(
491 'ID' => $node['ID'],
492 'PATH' => $path
493 );
494 }
495
496 return new DB\ArrayResult($pathes);
497 }
498
499 public static function getDeepestCommonParent($nodeInfo = array(), $parameters = array())
500 {
501 Assert::expectNotEmptyArray($nodeInfo, '$nodeInfo');
502
503 $filter = array();
504
505 $min = false;
506 $max = false;
507 foreach($nodeInfo as $node)
508 {
509 Assert::expectNotEmptyArray($node, '$nodeInfo[]');
510 $node['LEFT_MARGIN'] = Assert::expectIntegerNonNegative($node['LEFT_MARGIN'], '$nodeInfo[][LEFT_MARGIN]');
511 $node['RIGHT_MARGIN'] = Assert::expectIntegerPositive($node['RIGHT_MARGIN'], '$nodeInfo[][RIGHT_MARGIN]');
512
513 if($min === false || $node['LEFT_MARGIN'] < $min)
514 $min = $node['LEFT_MARGIN'];
515
516 if($max === false || $node['RIGHT_MARGIN'] > $max)
517 $max = $node['RIGHT_MARGIN'];
518 }
519
520 if(empty($parameters))
521 $parameters = array();
522
523 if(!is_array($parameters['order']))
524 $parameters['order'] = array();
525
526 $parameters['filter']['<LEFT_MARGIN'] = $min;
527 $parameters['filter']['>RIGHT_MARGIN'] = $max;
528
529 $parameters['order'] = array_merge(array(
530 'LEFT_MARGIN' => 'desc',
531 'RIGHT_MARGIN' => 'asc'
532 ), $parameters['order']);
533
534 $parameters['limit'] = 1;
535
536 return static::getList($parameters);
537 }
538
539 public static function getChildren($primary, $parameters = array())
540 {
541 if(empty($parameters))
542 $parameters = array();
543
544 if($primary = intval($primary)) // here $primary might be unset: in this case we take the first level of a tree
545 {
546 $node = self::getNodeInfo($primary);
547
548 $parameters['filter']['>=LEFT_MARGIN'] = intval($node['LEFT_MARGIN']);
549 $parameters['filter']['<=RIGHT_MARGIN'] = intval($node['RIGHT_MARGIN']);
550 $parameters['filter']['!=ID'] = $primary;
551 $parameters['filter']['DEPTH_LEVEL'] = intval($node['DEPTH_LEVEL']) + 1;
552 }
553 else
554 $parameters['filter']['DEPTH_LEVEL'] = 1;
555
556 return self::getList($parameters);
557 }
558
562 public static function getSubTree($primary, $parameters = array())
563 {
564 if(empty($parameters))
565 $parameters = array();
566
567 if($primary = intval($primary)) // here $primary might be unset: if so, get the whole tree
568 {
569 $node = self::getNodeInfo($primary);
570
571 $parameters['filter']['>=LEFT_MARGIN'] = intval($node['LEFT_MARGIN']);
572 $parameters['filter']['<=RIGHT_MARGIN'] = intval($node['RIGHT_MARGIN']);
573 }
574
575 if(!is_array($parameters['order']) || empty($parameters['order']))
576 $parameters['order'] = array('LEFT_MARGIN' => 'asc');
577
578 return self::getList($parameters);
579 }
580
588 public static function getParentTree($primary, $parameters = array(), $behaviour = array('SHOW_CHILDREN' => true, 'START_FROM' => false))
589 {
590 $primary = Assert::expectIntegerPositive($primary, '$primary');
591
592 if(!is_array($behaviour))
593 $behaviour = array();
594 if(!isset($behaviour['SHOW_CHILDREN']))
595 $behaviour['SHOW_CHILDREN'] = true;
596 if(!isset($behaviour['START_FROM']))
597 $behaviour['START_FROM'] = false;
598
599 if(empty($parameters))
600 $parameters = array();
601
602 $startFrom = intval($behaviour['START_FROM']);
603 $showChildren = $behaviour['SHOW_CHILDREN'];
604
605 if(!$startFrom)
606 {
607 $conditions[] = array(
608 'DEPTH_LEVEL' => 1
609 );
610 }
611
612 // todo: combine (1) and (2) in one query, check perfomance change
613
614 // (1)
615 $res = self::getPathToNode($primary, array(
616 'select' => array('ID')
617 ));
618
619 $started = !$startFrom;
620 while($item = $res->Fetch())
621 {
622 if($item['ID'] == $startFrom)
623 $started = true;
624
625 if(!$started)
626 continue;
627
628 if(!$showChildren && $item['ID'] == $primary)
629 continue;
630
631 $conditions[] = array(
632 'PARENT_ID' => $item['ID']
633 );
634 }
635
636 $conditions['LOGIC'] = 'OR';
637
638 $parameters['filter'][] = $conditions;
639
640 if(!is_array($parameters['order']) || empty($parameters['order']))
641 $parameters['order'] = array('LEFT_MARGIN' => 'asc');
642
643 // (2)
644 return self::getList($parameters);
645 }
646
650
658 protected final static function moveSubtree($primary, $primaryDst)
659 {
660 $node = self::getNodeInfo($primary);
661
662 if(!($primaryDst = intval($primaryDst))) // move to root
663 {
664 $rm = self::getMaxMargin();
665
666 $lDst = $rm + 1;
667 $rDst = $rm + 2;
668 $dDst = 0;
669 }
670 else
671 {
672 $nodeDst = self::getNodeInfo($primaryDst);
673
674 $lDst = intval($nodeDst['LEFT_MARGIN']);
675 $rDst = intval($nodeDst['RIGHT_MARGIN']);
676 $dDst = intval($nodeDst['DEPTH_LEVEL']);
677 }
678
679 $lSub = intval($node['LEFT_MARGIN']);
680 $rSub = intval($node['RIGHT_MARGIN']);
681 $dSub = intval($node['DEPTH_LEVEL']);
682
683 $tableName = static::getTableName();
684
685 $sql = "update ".$tableName." set
686
687 DEPTH_LEVEL =
688 case
689 when
690 LEFT_MARGIN between {$lSub} and {$rSub}
691 then
692 DEPTH_LEVEL + ".($dDst - $dSub + 1)."
693
694 else
695 DEPTH_LEVEL
696
697 end, ";
698
699 // DO NOT switch the column update order in the code below, it WILL NOT work correctly
700
701 // subtree moves upwards along it`s path
702 if ($lDst < $lSub && $rDst > $rSub && $dDst < ($dSub - 1))
703 {
704 $sql .= "
705
706 RIGHT_MARGIN =
707 case
708 when
709 RIGHT_MARGIN between ".($rSub + 1)." and ".($rDst - 1)."
710 then
711 RIGHT_MARGIN - ".($rSub - $lSub + 1)."
712
713 when
714 LEFT_MARGIN between ".$lSub." and ".$rSub."
715 then
716 RIGHT_MARGIN + ".((($rDst - $rSub - $dSub + $dDst) / 2) * 2 + $dSub - $dDst - 1)."
717
718 else RIGHT_MARGIN
719 end,
720
721 LEFT_MARGIN =
722 case
723 when
724 LEFT_MARGIN between ".($rSub + 1)." and ".($rDst - 1)."
725 then
726 LEFT_MARGIN - ".($rSub - $lSub + 1)."
727
728 when
729 LEFT_MARGIN between ".$lSub." and ".$rSub."
730 then
731 LEFT_MARGIN + ".((($rDst - $rSub - $dSub + $dDst) / 2) * 2 + $dSub - $dDst - 1)."
732
733 else
734 LEFT_MARGIN
735 end
736
737 where LEFT_MARGIN between ".($lDst + 1)." and ".($rDst - 1);
738 }
739 elseif($lDst < $lSub) // subtree moves to the left of it`s path (to the left branch)
740 {
741 $sql .= "
742
743 LEFT_MARGIN =
744 case
745 when
746 LEFT_MARGIN between ".$rDst." and ".($lSub-1)."
747 then
748 LEFT_MARGIN + ".($rSub - $lSub + 1)."
749
750 when
751 LEFT_MARGIN between ".$lSub." and ".$rSub."
752 then
753 LEFT_MARGIN - ".($lSub - $rDst)."
754
755 else
756 LEFT_MARGIN
757 end,
758
759 RIGHT_MARGIN =
760 case
761 when
762 RIGHT_MARGIN between ".$rDst." and ".$lSub."
763 then
764 RIGHT_MARGIN + ".($rSub - $lSub + 1)."
765
766 when
767 RIGHT_MARGIN between ".$lSub." and ".$rSub."
768 then
769 RIGHT_MARGIN - ".($lSub - $rDst)."
770
771 else
772 RIGHT_MARGIN
773 end
774
775 where LEFT_MARGIN between ".$lDst." and ".$rSub." or RIGHT_MARGIN between ".$lDst." and ".$rSub;
776 }
777 else // subtree moves to the right of it`s path (to the right branch)
778 {
779 $sql .= "
780
781 LEFT_MARGIN =
782 case
783 when
784 LEFT_MARGIN between ".$rSub." and ".$rDst."
785 then
786 LEFT_MARGIN - ".($rSub - $lSub + 1)."
787
788 when
789 LEFT_MARGIN between ".$lSub." and ".$rSub."
790 then
791 LEFT_MARGIN + ".($rDst - $rSub - 1)."
792
793 else
794 LEFT_MARGIN
795 end,
796
797 RIGHT_MARGIN =
798 case
799 when
800 RIGHT_MARGIN between ".($rSub + 1)." and ".($rDst - 1)."
801 then RIGHT_MARGIN - ".($rSub - $lSub + 1)."
802
803 when
804 RIGHT_MARGIN between ".$lSub." and ".$rSub."
805 then RIGHT_MARGIN + ".($rDst - $rSub - 1)."
806
807 else RIGHT_MARGIN
808 end
809
810 where LEFT_MARGIN between ".$lSub." and ".$rDst." or RIGHT_MARGIN between ".$lSub." and ".$rDst;
811 }
812
813 Main\HttpApplication::getConnection()->query($sql);
814 }
815
816 protected final static function processInsertInstruction(&$data)
817 {
818 $data['INSERT_AFTER'] = intval($data['INSERT_AFTER']);
819 $data['INSERT_BEFORE'] = intval($data['INSERT_BEFORE']);
820
821 if($data['INSERT_AFTER'] || $data['INSERT_BEFORE'])
822 {
823 $neighbourId = $data['INSERT_BEFORE'] ? $data['INSERT_BEFORE'] : $data['INSERT_AFTER'];
824
825 $sort = self::makeSortSpace(
826 $neighbourId,
827 ($data['INSERT_BEFORE'] ? self::SORT_FREE_BEFORE : self::SORT_FREE_AFTER),
828 $data['PARENT_ID'],
829 $data['SORT'] ?? false
830 );
831
832 unset($data['INSERT_AFTER']);
833 unset($data['INSERT_BEFORE']);
834
835 if($sort != false)
836 $data['SORT'] = $sort;
837 }
838 }
839
840 protected final static function manageFreeSpace($right, $length = 2, $op = self::SPACE_ADD, $exceptId = false)
841 {
842 if($length <= 1 || $right <= 0)
843 return;
844
845 // LEFT_MARGIN & RIGHT_MARGIN are system fields, user should not know about them ever, so no orm events needed to be fired on update of them
846
847 $sign = $op == self::SPACE_ADD ? '+' : '-';
848
849 $tableName = static::getTableName();
850 $exceptId = intval($exceptId);
851
852 $query = "update {$tableName} set
853 LEFT_MARGIN = case when LEFT_MARGIN > {$right} then LEFT_MARGIN {$sign} {$length} else LEFT_MARGIN end,
854 RIGHT_MARGIN = case when RIGHT_MARGIN >= {$right} then RIGHT_MARGIN {$sign} {$length} else RIGHT_MARGIN end
855 where RIGHT_MARGIN >= {$right}".($exceptId ? " and ID <> {$exceptId}" : "");
856
857 $shifted = Main\HttpApplication::getConnection()->query($query);
858
859 if(!$shifted)
860 throw new Main\SystemException('Query failed: managing free space in a tree', 0, __FILE__, __LINE__); // SaleTreeSystemException
861 }
862
863 // act in assumption sort field is always defined for each node and also it`s value positive signed
864 protected final static function makeSortSpace($primary, $direction, $primaryParent, $knownSort = false)
865 {
866 $primary = Assert::expectIntegerPositive($primary, '$primary');
867 $primaryParent = Assert::expectIntegerPositive($primary, '$primaryParent');
868
869 $nodeFound = false;
870 $sorts = array();
871
872 $nextNodeId = false;
873 $prevNodeId = false;
874
875 $prev = false;
876 $res = self::getChildren($primaryParent, array('select' => array('ID', 'SORT', 'CODE'), 'order' => array('SORT' => 'asc')));
877 while($item = $res->Fetch())
878 {
879 if($nodeFound && !$nextNodeId)
880 $nextNodeId = $item['ID'];
881
882 if($item['ID'] == $primary)
883 {
884 $nodeFound = true;
885 $prevNodeId = $prev;
886 }
887
888 $sorts[$item['ID']] = $item['SORT'];
889
890 $prev = $item['ID'];
891 }
892
893 // no node exists or they are not neighbours
894 if(!$nodeFound)
895 return false;
896
897 // add extra items
898 if(!$prevNodeId)
899 {
900 $sorts = array('FH' => 0) + $sorts;
901 $prevNodeId = 'FH';
902 }
903 if(!$nextNodeId)
904 {
905 $sorts['FT'] = PHP_INT_MAX;
906 $nextNodeId = 'FT';
907 }
908
909 // handle some obvious situations
910 if($direction == self::SORT_FREE_BEFORE)
911 {
912 if($knownSort && ($knownSort < $sorts[$prevNodeId]) && ($knownSort < $sorts[$primary]))
913 return $knownSort; // its okay, current sort fits
914
915 // inequation above is not true, but there is free space between nodes
916 if($sorts[$primary] - $sorts[$prevNodeId] > 1)
917 return $sorts[$prevNodeId] + 1;
918
919 $startShift = $primary;
920 $return = $sorts[$prevNodeId] + self::SORT_HOLE_SIZE_HALF;
921 }
922 else
923 {
924 if($knownSort && ($knownSort < $sorts[$primary]) && ($knownSort < $sorts[$nextNodeId]))
925 return $knownSort; // its okay, current sort fits
926
927 // inequation above is not true, but there is free space between nodes
928 if($sorts[$nextNodeId] - $sorts[$primary] > 1)
929 return $sorts[$primary] + 1;
930
931 $startShift = $nextNodeId;
932 $return = $sorts[$primary] + self::SORT_HOLE_SIZE_HALF;
933 }
934
935 // .. or else we forced to make a hole
936 $begin = false;
937 $shift = $sorts[$startShift] + self::SORT_HOLE_SIZE;
938
939 foreach($sorts as $id => $sVal)
940 {
941 if($id == $startShift)
942 $begin = true;
943
944 if($begin && $sVal <= $shift)
945 {
946 $shift = $sVal + self::SORT_HOLE_SIZE;
947 parent::update($id, array('SORT' => $shift));
948 }
949 }
950
951 return $return;
952 }
953
954 // in-deep tree walk
955 protected final static function walkTreeInDeep($primary, $edges, &$nodes, $margin, $depth = 0, $dontCareEvents = false)
956 {
957 $lMargin = $margin;
958
959 if(empty($edges[$primary]))
960 $rMargin = $margin + 1;
961 else
962 {
963 $offset = $margin + 1;
964 foreach($edges[$primary] as $sNode)
965 $offset = self::walkTreeInDeep($sNode, $edges, $nodes, $offset, $depth+1, $dontCareEvents);
966
967 $rMargin = $offset;
968 }
969
970 // update !
971 if($primary != 'ROOT')
972 {
973 $nodes[$primary]['LEFT_MARGIN'] = intval($lMargin);
974 $nodes[$primary]['RIGHT_MARGIN'] = intval($rMargin);
975 $nodes[$primary]['DEPTH_LEVEL'] = $depth;
976 }
977
978 return $rMargin + 1;
979 }
980
981 protected static function applyRestrictions(&$data)
982 {
983 unset($data['LEFT_MARGIN']);
984 unset($data['RIGHT_MARGIN']);
985 unset($data['DEPTH_LEVEL']);
986 }
987
988 protected static function getNodeInfo($primary)
989 {
990 $primary = Assert::expectIntegerPositive($primary, '$primary');
991
992 $node = self::getById($primary)->fetch();
993 if(!isset($node['ID']))
994 {
995 throw new Main\SystemException(Loc::getMessage('SALE_LOCATION_TREE_ENTITY_NODE_NOT_FOUND_EXCEPTION'));
996 }
997
998 return $node;
999 }
1000
1001 protected static function getMaxMargin()
1002 {
1003 $tableName = static::getTableName();
1004
1005 // todo: write it in orm way
1006 $res = Main\HttpApplication::getConnection()->query("select A.RIGHT_MARGIN from {$tableName} A order by A.RIGHT_MARGIN desc")->fetch();
1007 return intval($res['RIGHT_MARGIN']);
1008 }
1009
1010 public static function mergeRelationsFromTemporalTable($temporalTabName, $additinalFlds = array(), $fldMap = array())
1011 {
1012 $dbConnection = Main\HttpApplication::getConnection();
1013 $dbHelper = $dbConnection->getSqlHelper();
1014
1015 $temporalTabName = Assert::expectStringNotNull($temporalTabName, false, 'Name of temporal table must be a non-zero length string');
1016 $temporalTabName = $dbHelper->forSql($temporalTabName);
1017
1018 $entityTableName = static::getTableName();
1019
1020 if(!is_array($additinalFlds))
1021 $additinalFlds = array();
1022
1023 $additinalFlds = array_merge(array('LEFT_MARGIN', 'RIGHT_MARGIN', 'DEPTH_LEVEL'), $additinalFlds);
1024
1025 $fldReplace = array();
1026 foreach($additinalFlds as &$fld)
1027 {
1028 $fld = $dbHelper->forSql($fld);
1029 $fldReplace[$fld] = is_array($fldMap) && isset($fldMap[$fld]) ? $dbHelper->forSql($fldMap[$fld]) : $fld;
1030 }
1031
1032 $idReplace = is_array($fldMap) && isset($fldMap['ID']) ? $dbHelper->forSql($fldMap['ID']) : 'ID';
1033
1034 if($dbConnection->getType() == 'mysql')
1035 {
1036 $sql = 'update '.$entityTableName.', '.$temporalTabName.' set ';
1037 $additFldCnt = count($additinalFlds);
1038
1039 for($i = 0; $i < $additFldCnt; $i++)
1040 {
1041 $sql .= $entityTableName.'.'.$additinalFlds[$i].' = '.$temporalTabName.'.'.$fldReplace[$additinalFlds[$i]].($i == count($additinalFlds) - 1 ? '' : ', ');
1042 }
1043
1044 $sql .= ' where '.$entityTableName.'.ID = '.$temporalTabName.'.'.$idReplace;
1045 }
1046 elseif($dbConnection->getType() == 'mssql')
1047 {
1048 $sql = 'update '.$entityTableName.' set ';
1049 $additFldCnt = count($additinalFlds);
1050
1051 for($i = 0; $i < $additFldCnt; $i++)
1052 {
1053 $sql .= $additinalFlds[$i].' = '.$temporalTabName.'.'.$fldReplace[$additinalFlds[$i]].($i == count($additinalFlds) - 1 ? '' : ', ');
1054 }
1055
1056 $sql .= ' from '.$entityTableName.' join '.$temporalTabName.' on '.$entityTableName.'.ID = '.$temporalTabName.'.'.$idReplace;
1057 }
1058 elseif($dbConnection->getType() == 'oracle')
1059 {
1060 // update tab1 set (aa,bb) = (select aa,bb from tab2 where tab2.id = tab1.id)
1061
1062 $sql = 'update '.$entityTableName.' set ('.
1063 implode(', ', $additinalFlds).
1064 ') = (select '.
1065 implode(', ', $fldReplace).
1066 ' from '.$temporalTabName.' where '.$entityTableName.'.ID = '.$temporalTabName.'.'.$idReplace.')';
1067 }
1068
1069 $dbConnection->query($sql);
1070 }
1071
1072 public static function getCountByFilter($filter = array())
1073 {
1074 $params = array(
1075 'runtime' => array(
1076 'CNT' => array(
1077 'data_type' => 'integer',
1078 'expression' => array('COUNT(*)')
1079 )
1080 ),
1081 'select' => array('CNT')
1082 );
1083
1084 if(is_array($filter))
1085 $params['filter'] = $filter;
1086
1087 $res = static::getList($params)->fetch();
1088
1089 return intval($res['CNT']);
1090 }
1091
1092 protected static function checkNodeThrowException($node)
1093 {
1094 // left margin MAY be equal to zero, right margin MAY NOT
1095 if(!is_numeric($node['LEFT_MARGIN']) || (int) $node['LEFT_MARGIN'] < 0 || !intval($node['RIGHT_MARGIN']) || !intval($node['ID']))
1096 {
1098 false,
1099 array(
1100 'INFO' => array(
1101 'ID' => $node['ID'],
1102 'CODE' => $node['CODE'],
1103 'LEFT_MARGIN' => $node['LEFT_MARGIN'],
1104 'RIGHT_MARGIN' => $node['RIGHT_MARGIN']
1105 )));
1106 }
1107 }
1108
1109 // deprecated
1110 protected static function checkNodeIsParentOfNodeByFilters($parentNodeFilter, $nodeFilter, $behaviour = array('CHECK_DIRECT' => false))
1111 {
1112 return static::checkNodeIsParentOfNodeByCondition($parentNodeFilter, $nodeFilter, $behaviour);
1113 }
1114}
static loadMessages($file)
Definition loc.php:64
static getMessage($code, $replace=null, $language=null)
Definition loc.php:29
static getPathToNode($primary, $parameters, $behaviour=array('SHOW_LEAF'=> true))
Definition tree.php:356
static getDeepestCommonParent($nodeInfo=array(), $parameters=array())
Definition tree.php:499
static updateExtended($primary, array $data, array $additional=array())
Definition tree.php:153
static checkNodeIsParentOfNodeByFilters($parentNodeFilter, $nodeFilter, $behaviour=array('CHECK_DIRECT'=> false))
Definition tree.php:1110
static walkTreeInDeep($primary, $edges, &$nodes, $margin, $depth=0, $dontCareEvents=false)
Definition tree.php:955
static getParentTree($primary, $parameters=array(), $behaviour=array('SHOW_CHILDREN'=> true, 'START_FROM'=> false))
Definition tree.php:588
static applyRestrictions(&$data)
Definition tree.php:981
static getSubtreeRangeSqlForNode($primary, $node=array())
Definition tree.php:228
static processInsertInstruction(&$data)
Definition tree.php:816
static getNodeInfo($primary)
Definition tree.php:988
static resort($dontCareEvents=false)
Definition tree.php:292
static makeSortSpace($primary, $direction, $primaryParent, $knownSort=false)
Definition tree.php:864
static mergeRelationsFromTemporalTable($temporalTabName, $additinalFlds=array(), $fldMap=array())
Definition tree.php:1010
static rebalance($node, $id)
Definition tree.php:89
static checkNodeIsParentOfNodeById($primary, $childPrimary, $behaviour=array('CHECK_DIRECT'=> false))
Definition tree.php:266
static getChildren($primary, $parameters=array())
Definition tree.php:539
static moveSubtree($primary, $primaryDst)
PROTECTED.
Definition tree.php:658
static add(array $data)
Definition tree.php:37
static getPathToMultipleNodes($nodeInfo=array(), $parameters=array(), $behaviour=array('SHOW_LEAF'=> true))
Definition tree.php:406
static checkNodeThrowException($node)
Definition tree.php:1092
static getCountByFilter($filter=array())
Definition tree.php:1072
static checkFields(Entity\Result $result, $primary, array $data)
Definition tree.php:95
static addExtended(array $data, array $additional=array())
Definition tree.php:46
static checkNodeIsParentOfNodeByCondition($parentNodeFilter, $nodeFilter, $behaviour=array('CHECK_DIRECT'=> false))
Definition tree.php:274
static getPathToNodeByCondition($filter, $parameters=array(), $behaviour=array('SHOW_LEAF'=> true))
Definition tree.php:375
static deleteExtended($primary, array $additional=array())
Definition tree.php:187
static update($primary, array $data)
Definition tree.php:144
static manageFreeSpace($right, $length=2, $op=self::SPACE_ADD, $exceptId=false)
Definition tree.php:840
static getSubTree($primary, $parameters=array())
Definition tree.php:562