Your IP : 108.162.216.192


Current Path : /var/www/element/data/www/revenuestory.ru/bitrix/modules/landing/lib/
Upload File :
Current File : /var/www/element/data/www/revenuestory.ru/bitrix/modules/landing/lib/history.php

<?php
namespace Bitrix\Landing;

use Bitrix\Landing\History\ActionFactory;
use Bitrix\Landing\History\Action\BaseAction;
use Bitrix\Landing\Internals\BlockTable;
use Bitrix\Landing\Internals\HistoryTable;
use Bitrix\Landing\Internals\LandingTable;
use Bitrix\Main\Type\DateTime;

class History
{
	/**
	 * Entity type landing.
	 */
	public const ENTITY_TYPE_LANDING = 'L';

	/**
	 * Entity type designer block.
	 */
	public const ENTITY_TYPE_DESIGNER_BLOCK = 'D';

	// todo: max count
	// todo: lifetime

	public const AVAILABLE_TYPES = [
		self::ENTITY_TYPE_LANDING,
		self::ENTITY_TYPE_DESIGNER_BLOCK,
	];

	/**
	 * Activity flag
	 * @var bool
	 */
	protected static bool $isActive = false;

	/**
	 * If set multiply mode some actions will connected and changed as one step
	 * @var bool
	 */
	protected static bool $isMultiply = false;

	/**
	 * ID of multiply actions group, by default - ID of first action in group
	 * @var int|null
	 */
	protected static ?int $multiplyId = null;

	/**
	 * Because multiply step is one step, need increase step just once and save value
	 * @var int|null
	 */
	protected static ?int $multiplyStep = null;

	protected int $entityId;
	protected string $entityType = self::ENTITY_TYPE_LANDING;
	protected array $stack = [];
	protected int $step = 0;
	protected array $actions = [];

	/**
	 * Enable history for all
	 * @return void
	 */
	public static function activate(): void
	{
		self::$isActive = true;
	}

	/**
	 * Disable history for all
	 * @return void
	 */
	public static function deactivate(): void
	{
		self::$isActive = false;
	}

	public static function setMultiplyMode(): void
	{
		self::$isMultiply = true;
	}

	public static function unsetMultiplyMode(): void
	{
		self::$isMultiply = false;
	}

	/**
	 * Check enable or disable global history
	 * @return bool
	 */
	public static function isActive() :bool
	{
		return self::$isActive;
	}

	/**
	 * @param int $entityId
	 * @param string $entityType - one of constants ENTITY_TYPE_
	 */
	public function __construct(int $entityId, string $entityType)
	{
		if (!in_array($entityType, self::AVAILABLE_TYPES, true))
		{
			// todo :err or null
			return;
		}

		$this->entityId = $entityId;
		$this->entityType = $entityType;

		$this->loadStack();
		$this->loadStep();
	}

	protected function loadStack(): void
	{
		// todo: maybe cache
		$this->stack = [];

		$res = HistoryTable::query()
			->addSelect('*')
			->where('ENTITY_TYPE', '=', $this->entityType)
			->where('ENTITY_ID', '=', $this->entityId)
			->setOrder(['ID' => 'ASC'])
			->exec();
		$step = 1;
		$multyId = null;
		while ($row = $res->fetch())
		{
			$row['ID'] = (int)$row['ID'];
			$row['STEP'] = $step;
			$row['ENTITY_ID'] = (int)$row['ENTITY_ID'];
			$row['MULTIPLY_ID'] = (int)$row['MULTIPLY_ID'];

			if ($row['MULTIPLY_ID'])
			{
				if ($multyId && $multyId !== $row['MULTIPLY_ID'])
				{
					$multyId = null;
				}

				if (!$multyId)
				{
					// first multiply step
					$row['ACTION_PARAMS'] = [
						[
							'ACTION' => $row['ACTION'],
							'ACTION_PARAMS' => $row['ACTION_PARAMS'],
						]
					];
					$row['ACTION'] = ActionFactory::MULTIPLY_ACTION_NAME;
					$multyId = $row['MULTIPLY_ID'];
					$row['MULTIPLY'] = [$row['MULTIPLY_ID']];
					unset($row['MULTIPLY_ID']);
					$this->stack[$step] = $row;
				}
				else
				{
					$this->stack[$step - 1]['ACTION_PARAMS'][] = [
						'ACTION' => $row['ACTION'],
						'ACTION_PARAMS' => $row['ACTION_PARAMS'],
					];
					$this->stack[$step - 1]['MULTIPLY'][] = $row['ID'];
				}
			}
			else
			{
				$multyId = null;
				$this->stack[$step] = $row;
			}

			$step++;
		}
	}

	protected function loadStep(): void
	{
		$this->step = 0;

		if ($this->entityType === self::ENTITY_TYPE_LANDING)
		{
			$block = Internals\LandingTable::query()
				->addSelect('HISTORY_STEP')
				->where('ID', '=', $this->entityId)
				->exec()
				->fetch()
			;
			$this->step = $block['HISTORY_STEP'] ?? 0;
		}

		if ($this->entityType === self::ENTITY_TYPE_DESIGNER_BLOCK)
		{
			$block = Internals\BlockTable::query()
				->addSelect('HISTORY_STEP_DESIGNER')
				->where('ID', '=', $this->entityId)
				->exec()
				->fetch()
			;
			$this->step = $block['HISTORY_STEP_DESIGNER'] ?? 0;
		}
	}

	public function getStackCount(): int
	{
		return count($this->stack);
	}

	/**
	 * Get current step
	 * @return int
	 */
	public function getCurrentStep(): int
	{
		return $this->step;
	}

	public function push(string $actionName, array $params): bool
	{
		$actionName = strtoupper($actionName);

		$action = ActionFactory::getAction($actionName);
		if (!$action)
		{
			return false;
			// todo: or err
		}
		$action->setParams($params);

		$fields = [
			'ENTITY_TYPE' => $this->entityType,
			'ENTITY_ID' => $this->entityId,
			'ACTION' => $actionName,
			'ACTION_PARAMS' => $action->getParams(),
			'CREATED_BY_ID' => Manager::getUserId() ?: 1,
			'DATE_CREATE' => new DateTime,
		];

		// check duplicates
		if (
			!empty($this->stack[$this->step])
			&& ActionFactory::compareSteps($this->stack[$this->step], $fields)
		)
		{
			return false;
		}

		if (!$action->isNeedPush())
		{
			return true;
		}

		$stackCount = $this->getStackCount();
		if ($this->step < $stackCount)
		{
			for ($i = $this->step + 1; $i <= $stackCount; $i++)
			{
				$this->deleteStackItem($this->stack[$i]);
			}
		}

		// todo: check landing exists, check success

		$newStep =
			(self::$isMultiply && self::$multiplyStep !== null)
				? self::$multiplyStep
				: ++$this->step
		;
		$resUpdate = false;
		// todo: refactor
		if ($this->entityType === self::ENTITY_TYPE_LANDING)
		{
			$resUpdate = LandingTable::update($this->entityId, [
				'HISTORY_STEP' => ($newStep),
			]);
		}
		if ($this->entityType === self::ENTITY_TYPE_DESIGNER_BLOCK)
		{
			$resUpdate = BlockTable::update($this->entityId, [
				'HISTORY_STEP_DESIGNER' => ($newStep),
			]);
		}
		$this->step = $newStep;
		self::$multiplyStep = $newStep;

		if ($resUpdate && $resUpdate->isSuccess())
		{
			if (self::$isMultiply && self::$multiplyId !== null)
			{
				$fields['MULTIPLY_ID'] = self::$multiplyId;
			}

			$resAdd = HistoryTable::add($fields);

			if (self::$isMultiply && self::$multiplyId === null)
			{
				self::$multiplyId = $resAdd->getId();
				HistoryTable::update(self::$multiplyId, [
					'MULTIPLY_ID' => self::$multiplyId,
				]);
			}

			return $resAdd->isSuccess();
		}

		return false;

		// todo: max limit
	}

	/**
	 * Clear row(s) from table, and do action delete processing
	 * @param array $item
	 * @return bool
	 */
	protected function deleteStackItem(array $item): bool
	{
		$action = $this->getActionForStep($item['STEP'], true);
		if (!$action || !$action->delete())
		{
			return false;
		}

		if (is_array($item['MULTIPLY']) && !empty($item['MULTIPLY']))
		{
			foreach ($item['MULTIPLY'] as $multyId)
			{
				$resDelete = HistoryTable::delete($multyId);
			}
		}
		else
		{
			$resDelete = HistoryTable::delete($item['ID']);
		}

		// todo: del from stack

		if ($resDelete->isSuccess())
		{
			unset($this->stack[$item['STEP']]);

			return true;
		}

		return false;
	}

	public function undo(): bool
	{
		// todo :canundo?

		self::deactivate();
		$action = $this->getActionForStep($this->step, true);
		if ($action && $action->execute())
		{
			// todo: what if return false?
			if ($this->entityType === self::ENTITY_TYPE_LANDING)
			{
				$resUpdate = LandingTable::update($this->entityId, [
					'HISTORY_STEP' => (--$this->step)
				]);

				return $resUpdate->isSuccess();
			}

			if ($this->entityType === self::ENTITY_TYPE_DESIGNER_BLOCK)
			{
				$resUpdate = BlockTable::update($this->entityId, [
					'HISTORY_STEP_DESIGNER' => (--$this->step)
				]);

				return $resUpdate->isSuccess();
			}
		}

		return false;
	}


	public function redo(): bool
	{
		// todo :canundo?

		self::deactivate();
		$action = $this->getActionForStep($this->step, false);
		if ($action && $action->execute(false))
		{
			if ($this->entityType === self::ENTITY_TYPE_LANDING)
			{
				$resUpdate = LandingTable::update($this->entityId, [
					'HISTORY_STEP' => (++$this->step)
				]);

				return $resUpdate->isSuccess();
			}

			if ($this->entityType === self::ENTITY_TYPE_DESIGNER_BLOCK)
			{
				$resUpdate = BlockTable::update($this->entityId, [
					'HISTORY_STEP_DESIGNER' => (++$this->step)
				]);

				return $resUpdate->isSuccess();
			}
		}

		return false;
	}

	/**
	 * Get params for JS command for frontend changes
	 * @param bool $undo
	 * @return array
	 */
	public function getJsCommand(bool $undo = true): array
	{
		$action = $this->getActionForStep($this->step, $undo);

   		return $action ? $action->getJsCommand($undo) : [];
	}

	/**
	 * Create and save in stack action object by step number
	 * @param int $step
	 * @param bool $undo
	 * @return BaseAction|null
	 */
	protected function getActionForStep(int $step, bool $undo): ?BaseAction
	{
		$step = $undo ? $step : ++$step;
		$action = $this->actions[$step];
		if (!$action)
		{
			$current = $this->stack[$step];
			$params = $current['ACTION_PARAMS'];
			if ($this->entityType === self::ENTITY_TYPE_LANDING)
			{
				$params['lid'] = $this->entityId;
			}
			if ($this->entityType === self::ENTITY_TYPE_DESIGNER_BLOCK)
			{
				$params['blockId'] = $this->entityId;
			}

			$action = ActionFactory::getAction($current['ACTION'], $undo);
			if (!$action)
			{
				return null;
			}
			$action->setParams($params, true);
			$this->actions[$step] = $action;
		}

		return $action;
	}

	public function clear(): bool
	{
		// save stack because steps will be deleted in process
		$stackSave = $this->stack;
		foreach ($stackSave as $stackItem)
		{
			if (!$this->deleteStackItem($stackItem))
			{
				return false;
			}

		}

		unset($stackSave);

		$this->step = 0;
		if ($this->entityType === self::ENTITY_TYPE_LANDING)
		{
			$resUpdate = LandingTable::update($this->entityId, [
				'HISTORY_STEP' => 0,
			]);

			return $resUpdate->isSuccess();
		}

		if ($this->entityType === self::ENTITY_TYPE_DESIGNER_BLOCK)
		{
			$resUpdate = BlockTable::update($this->entityId, [
				'HISTORY_STEP_DESIGNER' => 0,
			]);

			return $resUpdate->isSuccess();
		}

		return false;
	}
}