Bitrix-D7 23.9
 
Загрузка...
Поиск...
Не найдено
history.php
1<?php
2
3namespace Bitrix\Landing;
4
12
17{
21 public const ENTITY_TYPE_LANDING = 'L';
22
26 public const ENTITY_TYPE_DESIGNER_BLOCK = 'D';
27
28 public const AVAILABLE_TYPES = [
29 self::ENTITY_TYPE_LANDING,
30 self::ENTITY_TYPE_DESIGNER_BLOCK,
31 ];
32
37 protected static bool $isActive = false;
38
43 protected static bool $multiplyMode = false;
44
49 protected static ?int $multiplyId = null;
50
51 // todo: $multiplyId and $multiplyStep - is no static. But need getInstance method and like a singletone style
56 protected static ?int $multiplyStep = null;
57
58 protected int $entityId;
59 protected string $entityType = self::ENTITY_TYPE_LANDING;
64 protected ?int $stepRowId = null;
69 protected array $stack = [];
70 protected int $step = 0;
71 protected array $actions = [];
72
77 public static function activate(): void
78 {
79 self::$isActive = true;
80 }
81
86 public static function deactivate(): void
87 {
88 self::$isActive = false;
89 }
90
91 public static function setMultiplyMode(): void
92 {
93 self::$multiplyMode = true;
94 }
95
96 public static function unsetMultiplyMode(): void
97 {
98 self::$multiplyMode = false;
99 }
100
105 public static function isActive(): bool
106 {
107 return self::$isActive;
108 }
109
114 public function __construct(int $entityId, string $entityType)
115 {
116 if (!in_array($entityType, self::AVAILABLE_TYPES, true))
117 {
118 // todo :err or null
119 return;
120 }
121
122 $this->entityId = $entityId;
123 $this->entityType = $entityType;
124
125 $this->loadStack();
126 $this->loadStep();
127
128 if ($this->step > $this->getStackCount())
129 {
130 $this->saveStep($this->getStackCount());
131 }
132 }
133
134 protected function loadStack(): void
135 {
136 // todo: maybe cache
137 $this->stack = [];
138
139 $res = HistoryTable::query()
140 ->addSelect('*')
141 ->where('ENTITY_TYPE', '=', $this->entityType)
142 ->where('ENTITY_ID', '=', $this->entityId)
143 ->setOrder(['ID' => 'ASC'])
144 ->exec()
145 ;
146 $step = 1;
147 $multyId = null;
148 while ($row = $res->fetch())
149 {
150 $row['ID'] = (int)$row['ID'];
151 if (!is_array($row['ACTION_PARAMS']))
152 {
153 $this->fixBrokenStep($step, $row['ID']);
154 continue;
155 }
156
157 $row['STEP'] = $step;
158 $row['ENTITY_ID'] = (int)$row['ENTITY_ID'];
159 $row['MULTIPLY_ID'] = (int)$row['MULTIPLY_ID'];
160
161 if ($row['MULTIPLY_ID'])
162 {
163 if ($multyId && $multyId !== $row['MULTIPLY_ID'])
164 {
165 $multyId = null;
166 }
167
168 if (!$multyId)
169 {
170 // first multiply step
171 $row['ACTION_PARAMS'] = [
172 [
173 'ACTION' => $row['ACTION'],
174 'ACTION_PARAMS' => $row['ACTION_PARAMS'],
175 ],
176 ];
178 $multyId = $row['MULTIPLY_ID'];
179 $row['MULTIPLY'] = [$row['MULTIPLY_ID']];
180 unset($row['MULTIPLY_ID']);
181 $this->stack[$step] = $row;
182 }
183 else
184 {
185 $this->stack[$step - 1]['ACTION_PARAMS'][] = [
186 'ACTION' => $row['ACTION'],
187 'ACTION_PARAMS' => $row['ACTION_PARAMS'],
188 ];
189 $this->stack[$step - 1]['MULTIPLY'][] = $row['ID'];
190 }
191 }
192 else
193 {
194 $multyId = null;
195 $this->stack[$step] = $row;
196 }
197
198 $step++;
199 }
200 }
201
209 protected function fixBrokenStep(int $step, int $id): bool
210 {
211 $resDelete = HistoryTable::delete($id);
212 if ($resDelete->isSuccess())
213 {
214 $currentStep = $this->loadStep();
215 if ($step > $currentStep)
216 {
217 return true;
218 }
219
220 return $this->saveStep(max(--$currentStep, 0));
221 }
222
223 return false;
224 }
225
231 protected function clearBefore(int $step): bool
232 {
233 if (!isset($this->stack[$step]))
234 {
235 return false;
236 }
237
238 // if first step - can't delete nothing
239 if ($this->step <= 1)
240 {
241 return true;
242 }
243
244 // delete only before current step
245 if ($step >= $this->step)
246 {
247 $step = $this->step - 1;
248 }
249
250 for ($i = 1; $i <= $step; $i++)
251 {
252 if (!$this->deleteStep(1))
253 {
254 return false;
255 }
256 }
257
258 return true;
259 }
260
266 protected function clearAfter(int $step): bool
267 {
268 if (!isset($this->stack[$step]))
269 {
270 return false;
271 }
272
273 // if last step - can't delete nothing
274 $stackCount = $this->getStackCount();
275 if ($this->step >= $stackCount)
276 {
277 return true;
278 }
279
280 // delete only after current step
281 if ($step <= $this->step)
282 {
283 $step = $this->step + 1;
284 }
285
286 $count = $this->getStackCount();
287 for ($i = $step; $i <= $count; $i++)
288 {
289 if (!$this->deleteStep($step))
290 {
291 return false;
292 }
293 }
294
295 return true;
296 }
297
302 public function clear(): bool
303 {
304 $count = $this->getStackCount();
305 for ($i = 0; $i < $count; $i++)
306 {
307 if (!$this->deleteStep(1))
308 {
309 return false;
310 }
311 }
312
313 $this->stack = [];
314
315 return true;
316 }
317
323 protected function deleteStep(int $step): bool
324 {
325 if (!isset($this->stack[$step]))
326 {
327 return false;
328 }
329
330 $item = $this->stack[$step];
331 $action = $this->getActionForStep($item['STEP'], false);
332 if (!$action || !$action->delete())
333 {
334 return false;
335 }
336
337 if (isset($item['MULTIPLY']) && is_array($item['MULTIPLY']) && !empty($item['MULTIPLY']))
338 {
339 foreach ($item['MULTIPLY'] as $multyId)
340 {
341 $resDelete = HistoryTable::delete($multyId);
342 if (!$resDelete->isSuccess())
343 {
344 return false;
345 }
346 }
347 }
348 else
349 {
350 $resDelete = HistoryTable::delete($item['ID']);
351 if (!$resDelete->isSuccess())
352 {
353 return false;
354 }
355 }
356
357 // update stack and step
358 unset($this->stack[$step]);
359 $this->resetStackSteps();
360 if ($step <= $this->step)
361 {
362 return $this->saveStep($this->step - 1);
363 }
364
365 return true;
366 }
367
372 protected function resetStackSteps(): void
373 {
374 $newStack = [];
375 $step = 1;
376 foreach ($this->stack as $item)
377 {
378 $item['STEP'] = $step;
379 $newStack[$step] = $item;
380 $step++;
381 }
382
383 // todo: what about multiply step?
384
385 $this->stack = $newStack;
386 }
387
388
394 public function clearOld(int $days): bool
395 {
396 if ($days > 0)
397 {
398 $dateEnd = new DateTime();
399 $dateEnd->add('-' . $days . ' days');
400
401 $deleteBeforeStep = 0;
402 foreach ($this->stack as $stackItem)
403 {
404 $dateCurrent = DateTime::createFromUserTime($stackItem['DATE_CREATE']);
405 if ($dateEnd < $dateCurrent)
406 {
407 break;
408 }
409 $deleteBeforeStep = $stackItem['STEP'];
410 }
411
412 return $this->clearBefore($deleteBeforeStep);
413 }
414
415 return false;
416 }
417
418 public function getStackCount(): int
419 {
420 return count($this->stack);
421 }
422
427 protected function loadStep(): int
428 {
429 $this->step = 0;
430
431 $step = HistoryStepTable::query()
432 ->addSelect('ID')
433 ->addSelect('STEP')
434 ->where('ENTITY_ID', '=', $this->entityId)
435 ->where('ENTITY_TYPE', '=', $this->entityType)
436 ->exec()
437 ->fetch()
438 ;
439 // todo: del other entities row if exists
440 if ($step)
441 {
442 $this->stepRowId = $step['ID'];
443 $this->step = $step['STEP'];
444 }
445 else
446 {
447 $this->migrateStep();
448 }
449
450 return $this->step;
451 }
452
458 protected function saveStep(int $step): bool
459 {
460 $this->step = $step;
461
462 if ($this->stepRowId)
463 {
464 $res = HistoryStepTable::update($this->stepRowId, ['STEP' => $step]);
465 }
466 else
467 {
468 $res = HistoryStepTable::add([
469 'ENTITY_ID' => $this->entityId,
470 'ENTITY_TYPE' => $this->entityType,
471 'STEP' => $step,
472 ]);
473 }
474
475 if ($res->isSuccess())
476 {
477 $this->stepRowId = $res->getId();
478 $this->step = $step;
479
480 return true;
481 }
482
483 return false;
484 }
485
491 private function migrateStep(): void
492 {
493 $oldStep = null;
494
495 if ($this->entityType === self::ENTITY_TYPE_LANDING)
496 {
497 if (!array_key_exists('HISTORY_STEP', LandingTable::getMap()))
498 {
499 return;
500 }
501
502 $landing = LandingTable::query()
503 ->addSelect('HISTORY_STEP')
504 ->where('ID', '=', $this->entityId)
505 ->exec()
506 ->fetch()
507 ;
508 $oldStep = $landing ? $landing['HISTORY_STEP'] : null;
509 }
510
511 if ($this->entityType === self::ENTITY_TYPE_DESIGNER_BLOCK)
512 {
513 if (!array_key_exists('HISTORY_STEP_DESIGNER', BlockTable::getMap()))
514 {
515 return;
516 }
517
518 $block = BlockTable::query()
519 ->addSelect('HISTORY_STEP_DESIGNER')
520 ->where('ID', '=', $this->entityId)
521 ->exec()
522 ->fetch()
523 ;
524 $oldStep = $block ? $block['HISTORY_STEP_DESIGNER'] : null;
525 }
526
527 $isNewStepExists = HistoryStepTable::query()
528 ->addSelect('ID')
529 ->addSelect('STEP')
530 ->where('ENTITY_ID', '=', $this->entityId)
531 ->where('ENTITY_TYPE', '=', $this->entityType)
532 ->exec()
533 ->fetch()
534 ;
535
536 if ($oldStep && !$isNewStepExists)
537 {
538 $this->saveStep((int)$oldStep);
539 }
540 }
541
546 public function getJsStack(): array
547 {
548 $result = [];
549 foreach ($this->stack as $step => $stackItem)
550 {
551 $actionClass = ActionFactory::getActionClass($stackItem['ACTION']);
552 $result[$step] =
553 (is_callable([$actionClass, 'getJsCommandName']))
554 ? call_user_func([$actionClass, 'getJsCommandName'])
555 : [];
556 }
557
558 return $result;
559 }
560
565 public function getStep(): int
566 {
567 return $this->step;
568 }
569
570 public function push(string $actionName, array $params): bool
571 {
572 $actionName = strtoupper($actionName);
573
574 $action = ActionFactory::getAction($actionName);
575 if (!$action)
576 {
577 return false;
578 // todo: or err
579 }
580 $action->setParams($params);
581
582 $fields = [
583 'ENTITY_TYPE' => $this->entityType,
584 'ENTITY_ID' => $this->entityId,
585 'ACTION' => $actionName,
586 'ACTION_PARAMS' => $action->getParams(),
587 'CREATED_BY_ID' => Manager::getUserId() ?: 1,
588 'DATE_CREATE' => new DateTime,
589 ];
590
591 // check duplicates
592 if (
593 !empty($this->stack[$this->step])
594 && ActionFactory::compareSteps($this->stack[$this->step], $fields)
595 )
596 {
597 return false;
598 }
599
600 if (!$action->isNeedPush())
601 {
602 return true;
603 }
604
605 $stackCount = $this->getStackCount();
606 if ($this->step < $stackCount)
607 {
608 if (!$this->clearAfter($this->step + 1))
609 {
610 return false;
611 }
612 }
613
614 $nextStep =
615 (self::$multiplyMode && self::$multiplyStep !== null)
616 ? self::$multiplyStep
617 : $this->step + 1
618 ;
619
620 if (!$this->saveStep($nextStep))
621 {
622 return false;
623 }
624
625 self::$multiplyStep = $nextStep;
626 // todo: drop $multiplyStep after last element (when set multiply mode off)
627
628 if (self::$multiplyMode && self::$multiplyId !== null)
629 {
630 $fields['MULTIPLY_ID'] = self::$multiplyId;
631 }
632
633 $resAdd = HistoryTable::add($fields);
634
635 // save MULTIPLY_ID for first element in group
636 if (self::$multiplyMode && self::$multiplyId === null)
637 {
638 self::$multiplyId = $resAdd->getId();
639 HistoryTable::update(self::$multiplyId, [
640 'MULTIPLY_ID' => self::$multiplyId,
641 ]);
642 }
643
644 return $resAdd->isSuccess();
645 }
646
647 public function undo(): bool
648 {
649 if ($this->canUndo())
650 {
651 self::deactivate();
652 $action = $this->getActionForStep($this->step, true);
653 if ($action && $action->execute())
654 {
655 return $this->saveStep($this->step - 1);
656 }
657 }
658
659 return false;
660 }
661
662 protected function canUndo(): bool
663 {
664 return
665 $this->step > 0
666 && $this->getStackCount() > 0
667 && $this->step <= $this->getStackCount()
668 ;
669 }
670
671 public function redo(): bool
672 {
673 if ($this->canRedo())
674 {
675 self::deactivate();
676 $action = $this->getActionForStep($this->step + 1, false);
677 if ($action && $action->execute(false))
678 {
679 return $this->saveStep($this->step + 1);
680 }
681 }
682
683 return false;
684 }
685
686 protected function canRedo(): bool
687 {
688 return
689 $this->step >= 0
690 && $this->getStackCount() > 0
691 && $this->step < $this->getStackCount()
692 ;
693 }
694
700 public function getJsCommand(bool $undo = true): array
701 {
702 $action = $this->getActionForStep(
703 $undo ? $this->step : ($this->step + 1),
704 $undo
705 );
706
707 return $action ? $action->getJsCommand($undo) : [];
708 }
709
716 protected function getActionForStep(int $step, bool $undo): ?BaseAction
717 {
718 if (!isset($this->stack[$step]))
719 {
720 return null;
721 }
722
723 $stepItem = $this->stack[$step];
724 $stepId = $stepItem['ID'];
725 $direction = ActionFactory::getDirectionName($undo);
726 if (isset($this->actions[$stepId][$direction]))
727 {
728 return $this->actions[$stepId][$direction];
729 }
730
731 $params = $stepItem['ACTION_PARAMS'];
732 if ($this->entityType === self::ENTITY_TYPE_LANDING)
733 {
734 $params['lid'] = $this->entityId;
735 }
736 if ($this->entityType === self::ENTITY_TYPE_DESIGNER_BLOCK)
737 {
738 $params['blockId'] = $this->entityId;
739 }
740
741 $action = ActionFactory::getAction($stepItem['ACTION'], $undo);
742 if (!$action)
743 {
744 return null;
745 }
746
747 $action->setParams($params, true);
748 $this->actions[$stepId][$direction] = $action;
749
750 return $action;
751 }
752
753
754}
static getAction(string $actionName, ?bool $undo=false)
static getActionClass(string $actionName, ?bool $undo=false)
static compareSteps(array $prevStep, array $nextStep)
static getDirectionName(?bool $undo=false)
clearBefore(int $step)
Definition history.php:231
static int $multiplyStep
Definition history.php:56
getActionForStep(int $step, bool $undo)
Definition history.php:716
static bool $isActive
Definition history.php:37
getJsCommand(bool $undo=true)
Definition history.php:700
static int $multiplyId
Definition history.php:49
fixBrokenStep(int $step, int $id)
Definition history.php:209
static setMultiplyMode()
Definition history.php:91
clearAfter(int $step)
Definition history.php:266
const ENTITY_TYPE_DESIGNER_BLOCK
Definition history.php:26
static bool $multiplyMode
Definition history.php:43
static unsetMultiplyMode()
Definition history.php:96
__construct(int $entityId, string $entityType)
Definition history.php:114
deleteStep(int $step)
Definition history.php:323
push(string $actionName, array $params)
Definition history.php:570
static createFromUserTime($timeString)
Definition datetime.php:180