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