Your IP : 3.135.213.128


Current Path : /var/www/admin_ftp_12/data/www/httpdocs/bitrix/modules/rest/lib/configuration/action/
Upload File :
Current File : /var/www/admin_ftp_12/data/www/httpdocs/bitrix/modules/rest/lib/configuration/action/import.php

<?php

namespace Bitrix\Rest\Configuration\Action;

use Bitrix\Main\ArgumentException;
use Bitrix\Main\Localization\Loc;
use Bitrix\Rest\Configuration\Notification;
use Bitrix\Rest\Configuration\Setting;
use Bitrix\Rest\Configuration\Helper;
use Bitrix\Rest\Configuration\Controller;
use Bitrix\Rest\Configuration\Manifest;
use Bitrix\Rest\Configuration\Structure;
use Bitrix\Rest\Configuration\DataProvider;
use Bitrix\Rest\AppTable;
use Bitrix\Rest\AppLogTable;
use Bitrix\Main\IO\File;

/**
 * Class Import
 * @package Bitrix\Rest\Configuration\Action
 */
class Import extends Base
{
	public const ACTION = 'import';
	public const ERROR_MANIFEST_IS_NOT_AVAILABLE = 'ERROR_MANIFEST_IS_NOT_AVAILABLE';

	private const STEP_INIT_BACKGROUND = 'init_background';
	private const STEP_START = 'start';
	private const STEP_MANIFEST = 'manifest';
	private const STEP_CLEAN = 'clean';
	private const STEP_LOAD = 'load';
	private const STEP_FINISH = 'finish';

	private const STEPS_ORDER = [
		self::STEP_START,
		self::STEP_INIT_BACKGROUND,
		self::STEP_MANIFEST,
		self::STEP_CLEAN,
		self::STEP_LOAD,
		self::STEP_FINISH,
	];

	private const COUNT_ADD_FILES_BY_STEP = 200;

	private $manifestCode;

	/**
	 * Sets uses manifest
	 * @param $code
	 *
	 * @return bool
	 */
	public function setManifestCode($code): bool
	{
		$this->manifestCode = $code;

		return true;
	}

	/**
	 * Return uses manifest
	 * @return string
	 */
	public function getManifestCode()
	{
		return $this->manifestCode ?? '';
	}

	/**
	 * Runs action step.
	 *
	 * @return bool
	 * @throws ArgumentException
	 * @throws \Bitrix\Main\ObjectPropertyException
	 * @throws \Bitrix\Main\SystemException
	 */
	public function run(): bool
	{
		$result = false;
		$data = $this->getStructureData();

		if ($this->check($data))
		{
			$this->setStatus(self::STATUS_PROCESS);
			$actionInfo = $this->getSetting()->get(Setting::SETTING_ACTION_INFO);
			if (!$actionInfo)
			{
				$actionInfo = [
					'setting' => [],
					'run' => self::STEP_START,
					'section' => [],
					'currentSection' => 0,
					'step' => 0,
				];
			}

			$this->setManifestCode($data['MANIFEST']['CODE']);
			switch ($actionInfo['run'])
			{
				case self::STEP_START:
					$actionInfo['setting'] = $this->doStart();
					$actionInfo['section'] = $actionInfo['setting']['section'];
					break;
				case self::STEP_INIT_BACKGROUND:
					$actionInfo['setting'] = $this->doInitBackground($actionInfo, $data);
					break;
				case self::STEP_MANIFEST:
					$actionInfo['setting'] = $this->doInitManifest(
						$actionInfo['setting']['next'],
						$actionInfo['step'],
						Helper::MODE_IMPORT
					);
					break;
				case self::STEP_CLEAN:
					$manifest = Manifest::get($this->getManifestCode());
					if (
						(isset($data['MANIFEST']['SKIP_CLEARING']) && $data['MANIFEST']['SKIP_CLEARING'] === 'Y')
						|| (isset($manifest['SKIP_CLEARING']) && $manifest['SKIP_CLEARING'] === 'Y')
					)
					{
						$actionInfo['setting']['finish'] = true;
					}
					else
					{
						$actionInfo['section']['next'] = $actionInfo['section']['next'] ?? null;
						$actionInfo['setting'] = $this->doClean(
							$actionInfo['section'][$actionInfo['currentSection']],
							$actionInfo['section']['next'],
							(int) $actionInfo['step']
						);
					}
					break;
				case self::STEP_LOAD:
					$actionInfo['setting']['result'] = true;
					$type = $actionInfo['section'][$actionInfo['currentSection']] ?? false;
					if ($type && isset($data[self::PROPERTY_STRUCTURE][$type][$actionInfo['step']]))
					{
						$path = $data[self::PROPERTY_STRUCTURE][$type][$actionInfo['step']];
						$http = DataProvider\Controller::getInstance()->get(DataProvider\Controller::CODE_IO);
						$content = $http->getContent($path, $actionInfo['step']);
						if (!empty($content['ERROR_CODE']))
						{
							$this->getNotificationInstance()->add(
								Loc::getMessage(
									'REST_CONFIGURATION_IMPORT_ERROR_CONTENT',
									[
										'#STEP#' => $actionInfo['step'],
									]
								),
								implode(
									'_',
									[
										$content['ERROR_CODE'],
										$actionInfo['section'][$actionInfo['currentSection']],
										$actionInfo['step'],
									]
								),
								Notification::TYPE_EXCEPTION
							);
						}
						if ($content['COUNT'] === 0)
						{
							$content['COUNT'] = count($data[self::PROPERTY_STRUCTURE][$type]);
						}

						if ($content['SANITIZE'])
						{
							$this->getNotificationInstance()->add(
								Loc::getMessage(
									'REST_CONFIGURATION_IMPORT_ERROR_SANITIZE_SHORT',
									[
										'#STEP#' => $actionInfo['step']
									]
								),
								implode(
									'_',
									[
										'SANITIZE',
										$actionInfo['currentSection'],
										$actionInfo['step'],
									]
								),
								Notification::TYPE_NOTICE
							);
						}

						if (!is_null($content['DATA']))
						{
							$actionInfo['setting']['step'] = $actionInfo['setting']['step'] ?? null;
							$actionInfo['setting'] = $this->doLoad(
								$actionInfo['setting']['step'],
								$actionInfo['section'][$actionInfo['currentSection']],
								$content
							);
						}
					}
					$actionInfo['setting']['next'] = !$actionInfo['setting']['result'];

					break;
				case self::STEP_FINISH:
					$actionInfo['setting'] = $this->doFinish();
					break;
				default:
					$this->setStatus(self::STATUS_FINISH);
					$this->unregister();
					break;
			}

			$exception = $this->getNotificationInstance()->list(
				[
					'type' => Notification::TYPE_EXCEPTION,
				]
			);
			if (!empty($exception))
			{
				$this->setStatus(self::STATUS_ERROR);
				$this->unregister();
			}
			else
			{
				$actionInfo['step']++;
				if (
					!array_key_exists('next', $actionInfo['setting'])
					|| $actionInfo['setting']['next'] === false
				)
				{
					$actionInfo['currentSection']++;
					$actionInfo['step'] = 0;
				}
				if (
					(
						!array_key_exists('finish', $actionInfo['setting'])
						&& !isset($actionInfo['section'][$actionInfo['currentSection']])
					)
					|| 	(
						isset($actionInfo['setting']['finish'])
						&& $actionInfo['setting']['finish'] === true
					)
				)
				{
					$actionInfo['setting']['finish'] = false;
					$actionInfo['currentSection'] = 0;
					$actionInfo['step'] = 0;
					$key = array_search($actionInfo['run'], self::STEPS_ORDER);
					if ($key !== false)
					{
						$key++;
					}
					$actionInfo['run'] = self::STEPS_ORDER[$key] ?? false;
				}
			}

			$result = $this->getSetting()->set(Setting::SETTING_ACTION_INFO, $actionInfo);
		}
		else
		{
			$this->setStatus(self::STATUS_ERROR);
		}

		return $result;
	}

	protected function check(array $data): bool
	{
		$result = false;
		if (!empty($data[self::PROPERTY_MANIFEST]['CODE']))
		{
			$result = true;
		}

		return $result;
	}

	/**
	 * @param string|null $next
	 * @param int $step
	 * @param string $type
	 *
	 * @return array
	 */
	public function doInitManifest(?string $next, int $step, string $type): array
	{
		$result = [
			'next' => false,
			'finish' => true,
		];
		$additionalOption = $this->getSetting()->get(Setting::SETTING_ACTION_ADDITIONAL_OPTION);
		$items = Manifest::callEventInit(
			$this->getManifestCode(),
			[
				'TYPE' => $type,
				'STEP' => $step,
				'NEXT' => $next,
				'ITEM_CODE' => '',
				'CONTEXT_USER' => $this->getContext(),
				'ADDITIONAL_OPTION' => $additionalOption,
			]
		);
		foreach ($items as $item)
		{
			$this->getNotificationInstance()->save($item);
			if ($item['NEXT'] !== false)
			{
				$result['next'] = $item['NEXT'];
				$result['finish'] = false;
			}
		}

		return $result;
	}

	/**
	 * @param $code
	 * @param $step
	 * @param $next
	 * @param bool $clearFull
	 *
	 * @return array
	 */
	public function doClean($code, $step, $next, bool $clearFull = false): array
	{
		$result = [
			'next' => false,
		];

		if ($code)
		{
			$additionalOption = $this->getSetting()->get(Setting::SETTING_ACTION_ADDITIONAL_OPTION);
			$data = Controller::callEventClear(
				[
					'CODE' => $code,
					'STEP' => $step,
					'NEXT' => $next,
					'CONTEXT' => $this->getContextEntity(),
					'CONTEXT_USER' => $this->getContext(),
					'CLEAR_FULL' => $clearFull,
					'PREFIX_NAME' => Loc::getMessage('REST_CONFIGURATION_INSTALL_CLEAR_PREFIX_NAME'),//todo: lang file
					'MANIFEST_CODE' => $this->getManifestCode(),
					'IMPORT_MANIFEST' => Manifest::get($this->getManifestCode()),
					'ADDITIONAL_OPTION' => $additionalOption,
				]
			);

			$this->getNotificationInstance()->save($data);

			if (isset($data['NEXT']))
			{
				$result['next'] = $data['NEXT'];
			}
		}

		return $result;
	}

	/**
	 * @param $step
	 * @param $code
	 * @param $content
	 *
	 * @return array
	 */
	public function doLoad($step, $code, $content): array
	{
		$result = [
			'result' => true,
		];

		if ($content['COUNT'] > $step)
		{
			$result['result'] = false;
		}

		if (!is_null($content['DATA']))
		{
			$ratio = $this->getSetting()->get(Setting::SETTING_RATIO);
			$additionalOption = $this->getSetting()->get(Setting::SETTING_ACTION_ADDITIONAL_OPTION);
			$dataList = Controller::callEventImport(
				[
					'CODE' => $code,
					'CONTENT' => $content,
					'RATIO' => $ratio,
					'CONTEXT' => $this->getContextEntity(),
					'CONTEXT_USER' => $this->getContext(),
					'MANIFEST_CODE' => $this->getManifestCode(),
					'IMPORT_MANIFEST' => Manifest::get($this->getManifestCode()),
					'ADDITIONAL_OPTION' => $additionalOption,
				]
			);

			foreach ($dataList as $data)
			{
				if (is_array($data['RATIO']))
				{
					if (empty($ratio[$code]))
					{
						$ratio[$code] = [];
					}
					foreach ($data['RATIO'] as $old => $new)
					{
						$ratio[$code][$old] = $new;
					}
				}

				$this->getNotificationInstance()->save($data);
			}

			$this->getSetting()->set(Setting::SETTING_RATIO, $ratio);
		}

		return $result;
	}

	/**
	 *
	 * @param $actionInfo
	 * @param $data
	 *
	 * @return array
	 */
	private function doInitBackground($actionInfo, $data): array
	{
		$isEnd = false;
		$next = $actionInfo['step'];
		$structure = new Structure($this->setting->getContext());

		if ($data[self::PROPERTY_FILES] && $data[self::PROPERTY_STRUCTURE][Helper::STRUCTURE_FILES_NAME])
		{
			$chunkList = array_chunk($data[self::PROPERTY_FILES], self::COUNT_ADD_FILES_BY_STEP);
			if (!empty($chunkList[$next]))
			{
				$structure->addFileList(
					$chunkList[$next],
					$data[self::PROPERTY_STRUCTURE][Helper::STRUCTURE_FILES_NAME]
				);
			}
			else
			{
				$isEnd = true;
			}
		}
		else
		{
			$isEnd = true;
		}

		if ($isEnd)
		{
			if (!empty($actionInfo['next']) && (int)$actionInfo['next'] === 0)
			{
				$fileName = Helper::STRUCTURE_SMALL_FILES_NAME . Helper::CONFIGURATION_FILE_EXTENSION;

				foreach ($data[self::PROPERTY_STRUCTURE] as $path)
				{
					if (is_string($path) && mb_strpos($path, $fileName) !== false)
					{
						try
						{
							$content = File::getFileContents($path);
							$structure->unpackSmallFiles($content);
						}
						catch (\Exception $e)
						{
						}
						break;
					}
				}
			}
			elseif (!empty($actionInfo['next']) && (int)$actionInfo['next'] > 0)
			{
				//one more step will load small files
				$isEnd = false;
				$next = 0;
			}
		}


		return [
			'next' => $next,
			'finish' => $isEnd,
		];
	}

	/**
	 * @param mixed|null $app
	 * @param string $mode
	 * @param array $option
	 *
	 * @return string[]
	 */
	public function doStart($app = null, string $mode = Helper::MODE_IMPORT, array $option = []): array
	{
		$result = [
			'finish' => true,
		];

		$section = Controller::getEntityCodeList();
		$result['section'] = array_values($section);

		if (!is_array($app) && $app !== null)
		{
			$app = \Bitrix\Rest\AppTable::getByClientId($app);
		}

		if (is_array($app))
		{
			$this->getSetting()->set(Setting::SETTING_APP_INFO, $app);
		}

		if (!empty($option['UNINSTALL_APP_ON_FINISH']))
		{
			$this->getSetting()->set(Setting::SETTING_UNINSTALL_APP_CODE, $option['UNINSTALL_APP_ON_FINISH']);
		}

		return $result;
	}

	/**
	 * @param string $mode
	 *
	 * @return array
	 * @throws ArgumentException
	 * @throws \Bitrix\Main\ObjectPropertyException
	 * @throws \Bitrix\Main\SystemException
	 */
	public function doFinish(string $mode = Helper::MODE_IMPORT)
	{
		$result = [
			'result' => true,
			'finish' => true,
			'createItemList' => [],
			'additional' => [],
		];

		$app = $this->getSetting()->get(Setting::SETTING_APP_INFO);
		if (!empty($app['ID']))
		{
			if ($app['INSTALLED'] == AppTable::NOT_INSTALLED)
			{
				AppTable::setSkipRemoteUpdate(true);
				$updateResult = AppTable::update(
					$app['ID'],
					[
						'INSTALLED' => AppTable::INSTALLED
					]
				);
				AppTable::setSkipRemoteUpdate(false);
				AppTable::install($app['ID']);
				AppLogTable::log($app['ID'], AppLogTable::ACTION_TYPE_INSTALL);
				$result['result'] = $updateResult->isSuccess();
			}
			else
			{
				$result['result'] = true;
			}
			Helper::getInstance()->setBasicApp($this->getManifestCode(), $app['CODE']);
		}
		else
		{
			Helper::getInstance()->deleteBasicApp($this->getManifestCode());
		}

		$uninstallAppCode = $this->getSetting()->get(Setting::SETTING_UNINSTALL_APP_CODE);
		if (!empty($uninstallAppCode))
		{
			$res = AppTable::getList(
				[
					'filter' => array(
						"=CODE" => $uninstallAppCode,
						"!=STATUS" => AppTable::STATUS_LOCAL
					),
				]
			);
			if ($appInfo = $res->fetch())
			{
				$clean = true;
				$checkResult = AppTable::checkUninstallAvailability($appInfo['ID'], $clean);
				if ($checkResult->isEmpty())
				{
					$result['result'] = true;
					AppTable::uninstall($appInfo['ID'], $clean);
					$appFields = [
						'ACTIVE' => 'N',
						'INSTALLED' => 'N',
					];
					AppTable::update($appInfo['ID'], $appFields);
					AppLogTable::log($appInfo['ID'], AppLogTable::ACTION_TYPE_UNINSTALL);
				}
			}
		}

		$ratio = $this->getSetting()->get(Setting::SETTING_RATIO);
		$app = $this->getSetting()->get(Setting::SETTING_APP_INFO);
		$userId = $this->getSetting()->get(Setting::SETTING_USER_ID) ?? 0;
		$additionalOption = $this->getSetting()->get(Setting::SETTING_ACTION_ADDITIONAL_OPTION);
		$eventResult = Controller::callEventFinish(
			[
				'TYPE' => 'IMPORT',
				'CONTEXT' => $this->getContextEntity(),
				'CONTEXT_USER' => $this->getContext(),
				'RATIO' => $ratio,
				'MANIFEST_CODE' => $this->getManifestCode(),
				'IMPORT_MANIFEST' => Manifest::get($this->getManifestCode()) ?? [],
				'APP_ID' => ($app['ID'] > 0) ? $app['ID'] : 0,
				'USER_ID' => $userId,
				'ADDITIONAL_OPTION' => $additionalOption,
			]
		);

		foreach ($eventResult as $data)
		{
			if (is_array($data['CREATE_DOM_LIST']))
			{
				$result['createItemList'] = array_merge($result['createItemList'], $data['CREATE_DOM_LIST']);
			}

			if (is_array($data['ADDITIONAL']))
			{
				$result['additional'] = array_merge($result['additional'], $data['ADDITIONAL']);
			}
		}
		$result['finish'] = $result['result'];

		return $result;
	}

	protected function checkRegister($data): array
	{
		$result = [];

		if (
			!isset($data['MANIFEST']['CODE'])
			|| !Manifest::isRestImportAvailable($data['MANIFEST']['CODE'])
		)
		{
			$result = [
				'error' => static::ERROR_MANIFEST_IS_NOT_AVAILABLE,
				'error_description' => 'Manifest is not available.',
			];
		}

		return $result;
	}

	/**
	 * Returns information about current action
	 *
	 * @return array
	 */
	public function get(): array
	{
		$status = $this->getStatus();
		$result = parent::get();
		$actionInfo = $this->getSetting()->get(Setting::SETTING_ACTION_INFO);
		if ($status === self::STATUS_PROCESS || $status === self::STATUS_ERROR)
		{
			if ($actionInfo)
			{
				$result['progress'] = [
					'action' => $actionInfo['run'],
					'step' => $actionInfo['step'],
				];
				if (
					!empty($actionInfo['section'][$actionInfo['currentSection']])
					&& !empty($actionInfo['run'])
					&& $actionInfo['run'] !== self::STEP_FINISH
				)
				{
					$result['progress']['section'] = $actionInfo['section'][$actionInfo['currentSection']];
				}
			}
		}
		elseif ($status === self::STATUS_FINISH)
		{
			$result['additional'] = $actionInfo['setting']['additional'] ?? [];
			$result['createItemList'] = $actionInfo['setting']['createItemList'] ?? [];
		}

		return $result;
	}
}