Your IP : 18.227.105.164


Current Path : /var/www/www-root/data/www.catalog.monolith-realty.ru/bitrix/modules/landing/lib/
Upload File :
Current File : /var/www/www-root/data/www.catalog.monolith-realty.ru/bitrix/modules/landing/lib/site.php

<?php
namespace Bitrix\Landing;

use \Bitrix\Main\Localization\Loc;
use \Bitrix\Main\Event;
use \Bitrix\Main\EventResult;

Loc::loadMessages(__FILE__);

class Site extends \Bitrix\Landing\Internals\BaseTable
{
	/**
	 * Internal class.
	 * @var string
	 */
	public static $internalClass = 'SiteTable';

	/**
	 * Site's ids that was pinged.
	 * @var array
	 */
	protected static $pings = [];

	/**
	 * Returns true if site exists and available.
	 * @param int $id Site id.
	 * @param bool $deleted And from recycle bin.
	 * @return bool
	 */
	public static function ping(int $id, bool $deleted = false): bool
	{
		if (array_key_exists($id, self::$pings))
		{
			return self::$pings[$id];
		}

		$filter = [
			'ID' => $id
		];
		if ($deleted)
		{
			$filter['=DELETED'] = ['Y', 'N'];
		}

		$check = Site::getList([
			'select' => [
				'ID'
			],
			'filter' => $filter
		]);
		self::$pings[$id] = (boolean) $check->fetch();

		return self::$pings[$id];
	}

	/**
	 * Removes sites id that was pinged.
	 * @param int $id Site id.
	 * @return void
	 */
	protected static function clearPing(int $id): void
	{
		if (array_key_exists($id, self::$pings))
		{
			unset(self::$pings[$id]);
		}
	}

	/**
	 * Get public url for site.
	 * @param int[]|int $id Site id or array of ids.
	 * @param boolean $full Return full site url with relative path.
	 * @param boolean $hostInclude Include host name in full path.
	 * @param boolean $previewForNotActive If true and site is not active, url will be with preview hash.
	 * @return string|array
	 */
	public static function getPublicUrl($id, bool $full = true, bool $hostInclude = true, bool $previewForNotActive = false)
	{
		$paths = [];
		$isB24 = Manager::isB24();

		$siteKeyCode = Site\Type::getKeyCode();
		$defaultPubPath = rtrim(Manager::getPublicationPath(), '/');
		$hostUrl = Domain::getHostUrl();
		$disableCloud = Manager::isCloudDisable();
		$res = self::getList(array(
			'select' => array(
				'DOMAIN_PROTOCOL' => 'DOMAIN.PROTOCOL',
				'DOMAIN_NAME' => 'DOMAIN.DOMAIN',
				'DOMAIN_ID',
				'SMN_SITE_ID',
				'CODE',
				'TYPE',
				'ACTIVE',
				'DELETED',
				'ID'
			),
			'filter' => array(
				'ID' => $id,
				'=DELETED' => ['Y', 'N'],
				'CHECK_PERMISSIONS' => 'N'
			)
		));
		while ($row = $res->fetch())
		{
			$pubPath = '';
			$isB24localVar = $isB24;

			if ($row['TYPE'] == 'SMN')
			{
				$isB24localVar = false;
			}

			if (!$isB24localVar || $disableCloud)
			{
				$pubPath = Manager::getPublicationPath(
					null,
					$row['SMN_SITE_ID'] ? $row['SMN_SITE_ID'] : null
				);
				$pubPath = rtrim($pubPath, '/');
			}

			if ($siteKeyCode == 'ID')
			{
				$row['CODE'] = '/' . $row['ID'] . '/';
			}

			// force https
			if (Manager::isHttps())
			{
				$row['DOMAIN_PROTOCOL'] = \Bitrix\Landing\Internals\DomainTable::PROTOCOL_HTTPS;
			}

			if ($row['DOMAIN_ID'])
			{
				$paths[$row['ID']] = ($hostInclude ? ($disableCloud ? $hostUrl : $row['DOMAIN_PROTOCOL'] . '://' . $row['DOMAIN_NAME']) : '') . $pubPath;
				if ($full)
				{
					if ($disableCloud && $isB24localVar)
					{
						$paths[$row['ID']] .= $row['CODE'];
					}
					else if (!$isB24localVar)
					{
						$paths[$row['ID']] .= '/';
					}
				}
			}
			else
			{
				$paths[$row['ID']] = ($hostInclude ? $hostUrl : '') . $defaultPubPath . ($full ? $row['CODE'] : '');
			}
			if ($previewForNotActive && ($row['ACTIVE'] === 'N' || $row['DELETED'] === 'Y'))
			{
				$paths[$row['ID']] .= 'preview/' . self::getPublicHash($row['ID'], $row['DOMAIN_NAME']) . '/';
			}
		}

		if (is_array($id))
		{
			return $paths;
		}
		else
		{
			return isset($paths[$id]) ? $paths[$id] : '';
		}
	}

	/**
	 * Get preview picture of the site's main page.
	 * @param int $siteId Site id.
	 * @return string
	 */
	public static function getPreview(int $siteId): string
	{
		$res = self::getList([
			'select' => [
				'LANDING_ID_INDEX'
			],
			'filter' => [
				'ID' => $siteId
			],
		]);
		if ($row = $res->fetch())
		{
			if ($row['LANDING_ID_INDEX'])
			{
				return Landing::createInstance(0)->getPreview($row['LANDING_ID_INDEX']);
			}
		}

		return Manager::getUrlFromFile('/bitrix/images/landing/nopreview.jpg');
	}

	/**
	 * Get hooks of Site.
	 * @param int $id Site id.
	 * @return array Array of Hook.
	 */
	public static function getHooks($id)
	{
		if (!Rights::hasAccessForSite($id, Rights::ACCESS_TYPES['read']))
		{
			return [];
		}

		return Hook::getForSite($id);
	}

	/**
	 * Get version of site for updater
	 * @param $siteId
	 * @return int
	 */
	public static function getVersion($siteId): int
	{
		static $versions;

		if (isset($versions[$siteId]))
		{
			return $versions[$siteId];
		}

		$resSite = self::getList([
			'select' => [
				'VERSION'
			],
			'filter' => [
				'=ID' => $siteId
			]
		]);
		if ($site = $resSite->fetch())
		{
			$versions[$siteId] = (int)$site['VERSION'];
		}
		else
		{
			$versions[$siteId] = 0;
		}

		return $versions[$siteId];
	}


	/**
	 * Get additional fields of Landing.
	 * @param int $id Landing id.
	 * @return array Array of Field.
	 */
	public static function getAdditionalFields($id)
	{
		$fields = array();

		// now we can get additional fields only from hooks
		foreach (self::getHooks($id) as $hook)
		{
			$fields += $hook->getPageFields();
		}

		return $fields;
	}

	/**
	 * Save additional fields for Site.
	 * @param int $id Site id.
	 * @param array $data Data array.
	 * @return void
	 */
	public static function saveAdditionalFields($id, array $data)
	{
		// now we can get additional fields only from hooks
		Hook::saveForSite($id, $data);
	}

	/**
	 * Get existed site types.
	 * @return array
	 */
	public static function getTypes()
	{
		static $types = null;

		if ($types !== null)
		{
			return $types;
		}

		$types = array(
			'PAGE' => Loc::getMessage('LANDING_TYPE_PAGE'),
			'STORE' => Loc::getMessage('LANDING_TYPE_STORE'),
			'SMN' => Loc::getMessage('LANDING_TYPE_SMN'),
			'KNOWLEDGE' => Loc::getMessage('LANDING_TYPE_KNOWLEDGE'),
			'GROUP' => Loc::getMessage('LANDING_TYPE_GROUP')
		);

		return $types;
	}

	/**
	 * Get default site type.
	 * @return string
	 */
	public static function getDefaultType()
	{
		return 'PAGE';
	}

	/**
	 * Delete site by id.
	 * @param int $id Site id.
	 * @param bool $pagesDelete Delete all pages before.
	 * @return \Bitrix\Main\Result
	 */
	public static function delete($id, $pagesDelete = false)
	{
		// first delete all pages if you want
		if ($pagesDelete)
		{
			$res = Landing::getList([
				'select' => [
					'ID', 'FOLDER_ID'
				],
				'filter' => [
					'SITE_ID' => $id,
					'=DELETED' => ['Y', 'N']
				]
			]);
			while ($row = $res->fetch())
			{
				if ($row['FOLDER_ID'])
				{
					Landing::update($row['ID'], [
						'FOLDER_ID' => 0
					]);
				}
				$resDel = Landing::delete($row['ID'], true);
				if (!$resDel->isSuccess())
				{
					return $resDel;
				}
			}
		}

		// delete site
		$result = parent::delete($id);
		self::clearPing($id);

		return $result;
	}

	/**
	 * Mark site as deleted.
	 * @param int $id Site id.
	 * @return \Bitrix\Main\Result
	 */
	public static function markDelete($id)
	{
		$event = new Event('landing', 'onBeforeSiteRecycle', array(
			'id' => $id,
			'delete' => 'Y'
		));
		$event->send();

		foreach ($event->getResults() as $result)
		{
			if ($result->getType() == EventResult::ERROR)
			{
				$return = new \Bitrix\Main\Result;
				foreach ($result->getErrors() as $error)
				{
					$return->addError(
						$error
					);
				}
				return $return;
			}
		}

		if (($currentScope = Site\Type::getCurrentScopeId()))
		{
			Agent::addUniqueAgent('clearRecycleScope', [$currentScope]);
		}

		$res = parent::update($id, array(
			'DELETED' => 'Y'
		));
		self::clearPing($id);

		return $res;
	}

	/**
	 * Mark site as restored.
	 * @param int $id Site id.
	 * @return \Bitrix\Main\Result
	 */
	public static function markUnDelete($id)
	{
		$event = new Event('landing', 'onBeforeSiteRecycle', array(
			'id' => $id,
			'delete' => 'N'
		));
		$event->send();

		foreach ($event->getResults() as $result)
		{
			if ($result->getType() == EventResult::ERROR)
			{
				$return = new \Bitrix\Main\Result;
				foreach ($result->getErrors() as $error)
				{
					$return->addError(
						$error
					);
				}
				return $return;
			}
		}

		$res = parent::update($id, array(
			'DELETED' => 'N'
		));
		self::clearPing($id);

		return $res;
	}

	/**
	 * Copy site without site's pages.
	 * @param int $siteId Site id.
	 * @return \Bitrix\Main\Result
	 */
	public static function copy($siteId)
	{
		$siteId = intval($siteId);
		$result = new \Bitrix\Main\Result;
		$error = new Error;

		$siteRow = Site::getList([
			'filter' => [
				'ID' => $siteId
			]
		])->fetch();

		if (!$siteRow)
		{
			$error->addError(
				'SITE_NOT_FOUND',
				Loc::getMessage('LANDING_COPY_ERROR_SITE_NOT_FOUND')
			);
		}
		else
		{
			$result = Site::add([
				'CODE' => $siteRow['CODE'],
				'ACTIVE' => 'N',
				'TITLE' => $siteRow['TITLE'],
				'XML_ID' => $siteRow['XML_ID'],
				'DESCRIPTION' => $siteRow['DESCRIPTION'],
				'TYPE' => $siteRow['TYPE'],
				'SMN_SITE_ID' => $siteRow['SMN_SITE_ID'],
				'LANG' => $siteRow['LANG']
			]);

			if ($result->isSuccess())
			{
				// copy hook data
				Hook::copySite(
					$siteId,
					$result->getId()
				);
				// copy files
				File::copySiteFiles(
					$siteId,
					$result->getId()
				);
			}
		}

		if (!$error->isEmpty())
		{
			$result->addError($error->getFirstError());
		}

		return $result;
	}

	/**
	 * Get full data for site with pages.
	 * @param int $siteForExport Site id.
	 * @param array $params Some params.
	 * @return array
	 */
	public static function fullExport($siteForExport, $params = array())
	{
		$version = 3;//used in demo/class.php
		$siteForExport = intval($siteForExport);
		$tplsXml = array();
		$export = array();
		$editMode = isset($params['edit_mode']) && $params['edit_mode'] === 'Y';

		Landing::setEditMode($editMode);
		Hook::setEditMode($editMode);

		if (!is_array($params))
		{
			$params = array();
		}
		$params['hooks_files'] = Hook::HOOKS_CODES_FILES;

		if (isset($params['scope']))
		{
			Site\Type::setScope($params['scope']);
		}

		// check params
		if (
			!isset($params['hooks_disable']) ||
			!is_array($params['hooks_disable'])
		)
		{
			$params['hooks_disable'] = array();
		}
		if (
			isset($params['code']) &&
			preg_match('/[^a-z0-9]/i', $params['code'])
		)
		{
			throw new \Bitrix\Main\Config\ConfigurationException(
				Loc::getMessage('LANDING_EXPORT_ERROR')
			);
		}
		// additional hooks for disable
		$params['hooks_disable'][] = 'B24BUTTON_CODE';
		$params['hooks_disable'][] = 'FAVICON_PICTURE';
		// get all templates
		$res = Template::getList(array(
			'select' => array(
				'ID', 'XML_ID'
			)
		));
		while ($row = $res->fetch())
		{
			$tplsXml[$row['ID']] = $row['XML_ID'];
		}
		// gets pages count
		$res = Landing::getList(array(
			'select' => array(
				'CNT'
			),
			'filter' => array(
				'SITE_ID' => $siteForExport
			),
			'runtime' => array(
				new \Bitrix\Main\Entity\ExpressionField(
					'CNT', 'COUNT(*)'
				)
			)
		));
		if ($pagesCount = $res->fetch())
		{
			$pagesCount = $pagesCount['CNT'];
		}
		else
		{
			return array();
		}
		// get all pages from the site
		$res = Landing::getList(array(
			'select' => array(
				'ID',
				'CODE',
				'RULE',
				'TITLE',
				'DESCRIPTION',
				'TPL_ID',
				'FOLDER_ID',
				'SITE_ID',
				'SITE_CODE' => 'SITE.CODE',
				'SITE_TYPE' => 'SITE.TYPE',
				'SITE_TPL_ID' => 'SITE.TPL_ID',
				'SITE_TITLE' => 'SITE.TITLE',
				'SITE_DESCRIPTION' => 'SITE.DESCRIPTION',
				'LANDING_ID_INDEX' => 'SITE.LANDING_ID_INDEX',
				'LANDING_ID_404' => 'SITE.LANDING_ID_404'
			),
			'filter' => array(
				'SITE_ID' => $siteForExport,
				//'=ACTIVE' => 'Y',
				//'=SITE.ACTIVE' => 'Y'
			),
			'order' => array(
				'ID' => 'asc'
			)
		));
		if (!($row = $res->fetch()))
		{
			return array();
		}
		do
		{
			if (empty($export))
			{
				$export = array(
					'charset' => SITE_CHARSET,
					'code' => isset($params['code'])
								? $params['code']
								: trim($row['SITE_CODE'], '/'),
					'code_mainpage' => '',
					'site_code' => $row['SITE_CODE'],
					'name' => isset($params['name'])
								? $params['name']
								: $row['SITE_TITLE'],
					'description' => isset($params['description'])
								? $params['description']
								: $row['SITE_DESCRIPTION'],
					'preview' => isset($params['preview'])
								? $params['preview']
								: '',
					'preview2x' => isset($params['preview2x'])
								? $params['preview2x']
								: '',
					'preview3x' => isset($params['preview3x'])
								? $params['preview3x']
								: '',
					'preview_url' => isset($params['preview_url'])
								? $params['preview_url']
								: '',
					'show_in_list' => 'Y',
					'type' => mb_strtolower($row['SITE_TYPE']),
					'version' => $version,
					'fields' => array(
						'ADDITIONAL_FIELDS' => array(),
						'TITLE' => isset($params['name'])
								? $params['name']
								: $row['SITE_TITLE'],
						'LANDING_ID_INDEX' => $row['LANDING_ID_INDEX'],
						'LANDING_ID_404' => $row['LANDING_ID_404']
					),
					'layout' => array(),
					'folders' => array(),
					'syspages' => array(),
					'items' => array()
				);
				// site tpl
				if ($row['SITE_TPL_ID'])
				{
					$export['layout'] = array(
						'code' => $tplsXml[$row['SITE_TPL_ID']],
						'ref' => TemplateRef::getForSite($row['SITE_ID'])
					);
				}
				// sys pages
				foreach (Syspage::get($siteForExport) as $syspage)
				{
					$export['syspages'][$syspage['TYPE']] = $syspage['LANDING_ID'];
				}
				// site hooks
				$hookFields = &$export['fields']['ADDITIONAL_FIELDS'];
				foreach (Hook::getForSite($row['SITE_ID']) as $hookCode => $hook)
				{
					if ($hookCode == 'SETTINGS')
					{
						continue;
					}
					foreach ($hook->getFields() as $fCode => $field)
					{
						$hookCodeFull = $hookCode . '_' . $fCode;
						if (!in_array($hookCodeFull, $params['hooks_disable']))
						{
							$hookFields[$hookCodeFull] = $field->getValue();
							if (!$hookFields[$hookCodeFull])
							{
								unset($hookFields[$hookCodeFull]);
							}
							else if (
								in_array($hookCodeFull, $params['hooks_files']) &&
								intval($hookFields[$hookCodeFull]) > 0
							)
							{
								$hookFields['~' . $hookCodeFull] = $hookFields[$hookCodeFull];
								$hookFields[$hookCodeFull] = File::getFilePath(
									$hookFields[$hookCodeFull]
								);
								if ($hookFields[$hookCodeFull])
								{
									$hookFields[$hookCodeFull] = Manager::getUrlFromFile(
										$hookFields[$hookCodeFull]
									);
								}
							}
						}
					}
				}
				unset($hookFields);
			}
			// fill one page
			$export['items'][$row['ID']] = array(
				'old_id' => $row['ID'],
				'code' => $pagesCount > 1
							? $export['code'] . '/' . $row['CODE']
							: $export['code'],
				'name' => (isset($params['name']) && $pagesCount == 1)
							? $params['name']
							: $row['TITLE'],
				'description' => (isset($params['description']) && $pagesCount == 1)
							? $params['description']
							: $row['DESCRIPTION'],
				'preview' => (isset($params['preview']) && $pagesCount == 1)
							? $params['preview']
							: '',
				'preview2x' => (isset($params['preview2x']) && $pagesCount == 1)
							? $params['preview2x']
							: '',
				'preview3x' => (isset($params['preview3x']) && $pagesCount == 1)
							? $params['preview3x']
							: '',
				'preview_url' => (isset($params['preview_url']) && $pagesCount == 1)
							? $params['preview_url']
							: '',
				'show_in_list' => ($pagesCount == 1) ? 'Y' : 'N',
				'type' => mb_strtolower($row['SITE_TYPE']),
				'version' => $version,
				'fields' => array(
					'TITLE' => (isset($params['name']) && $pagesCount == 1)
							? $params['name']
							: $row['TITLE'],
					'RULE' => $row['RULE'],
					'ADDITIONAL_FIELDS' => array(),
				),
				'layout' => $row['TPL_ID']
					? array(
						'code' => $tplsXml[$row['TPL_ID']],
						'ref' => TemplateRef::getForLanding($row['ID'])
					)
					: array(),
				'items' => array()
			);
			// special code for index page
			if (
				$pagesCount > 1 &&
				$row['LANDING_ID_INDEX'] == $row['ID']
			)
			{
				$export['code_mainpage'] = $row['CODE'];
			}
			// special pages
			if ($row['LANDING_ID_INDEX'] == $row['ID'])
			{
				$export['fields']['LANDING_ID_INDEX'] = $export['items'][$row['ID']]['code'];
			}
			if ($row['LANDING_ID_404'] == $row['ID'])
			{
				$export['fields']['LANDING_ID_404'] = $export['items'][$row['ID']]['code'];
			}
			// page hooks
			$hookFields = &$export['items'][$row['ID']]['fields']['ADDITIONAL_FIELDS'];
			foreach (Hook::getForLanding($row['ID']) as $hookCode => $hook)
			{
				if ($hookCode == 'SETTINGS')
				{
					continue;
				}
				foreach ($hook->getFields() as $fCode => $field)
				{
					$hookCodeFull = $hookCode . '_' . $fCode;
					if (!in_array($hookCodeFull, $params['hooks_disable']))
					{
						$hookFields[$hookCodeFull] = $field->getValue();
						if (!$hookFields[$hookCodeFull])
						{
							unset($hookFields[$hookCodeFull]);
						}
						else if (
							in_array($hookCodeFull, $params['hooks_files']) &&
							intval($hookFields[$hookCodeFull]) > 0
						)
						{
							$hookFields['~' . $hookCodeFull] = $hookFields[$hookCodeFull];
							$hookFields[$hookCodeFull] = File::getFilePath(
								$hookFields[$hookCodeFull]
							);
							if ($hookFields[$hookCodeFull])
							{
								$hookFields[$hookCodeFull] = Manager::getUrlFromFile(
									$hookFields[$hookCodeFull]
								);
							}
						}
					}
				}
			}
			unset($hookFields);
			// folders
			if ($row['FOLDER_ID'])
			{
				if (!isset($export['folders'][$row['FOLDER_ID']]))
				{
					$export['folders'][$row['FOLDER_ID']] = array();
				}
				$export['folders'][$row['FOLDER_ID']][] = $row['ID'];
			}
			// fill page with blocks
			$landing = Landing::createInstance($row['ID']);
			if ($landing->exist())
			{
				foreach ($landing->getBlocks() as $block)
				{
					if (!$block->isActive())
					{
						continue;
					}
					// repo blocks
					$repoBlock = array();
					if ($block->getRepoId())
					{
						$repoBlock = Repo::getBlock(
							$block->getRepoId()
						);
						if ($repoBlock)
						{
							$repoBlock = array(
								'app_code' => $repoBlock['block']['app_code'],
								'xml_id' => $repoBlock['block']['xml_id']
							);
						}
					}
					$exportBlock = $block->export();
					$exportItem = array(
						'old_id' => $block->getId(),
						'code' => $block->getCode(),
						'access' => $block->getAccess(),
						'anchor' => $block->getLocalAnchor(),
						'repo_block' => $repoBlock,
						'cards' => $exportBlock['cards'],
						'nodes' => $exportBlock['nodes'],
						'menu' => $exportBlock['menu'],
						'style' => array_map(static function ($style){
							if (is_array($style) && isset($style['classList']))
							{
								$style = $style['classList'];
							}
							return $style;
						}, $exportBlock['style']),
						'attrs' => $exportBlock['attrs'],
						'dynamic' => $exportBlock['dynamic']
					);
					foreach ($exportItem as $key => $item)
					{
						if (!$item)
						{
							unset($exportItem[$key]);
						}
					}
					$export['items'][$row['ID']]['items']['#block' . $block->getId()] = $exportItem;
				}
			}
		}
		while ($row = $res->fetch());

		if ($export['code_mainpage'])
		{
			$export['code'] = $export['code'] . '/' . $export['code_mainpage'];
		}
		unset($export['code_mainpage']);

		$pages = $export['items'];
		$export['items'] = array();

		// prepare for export tpls
		if (isset($export['layout']['ref']))
		{
			foreach ($export['layout']['ref'] as &$lid)
			{
				if (isset($pages[$lid]))
				{
					$lid = $pages[$lid]['code'];
				}
			}
			unset($lid);
		}
		// ... folders
		$nCount = 0;
		foreach ($export['folders'] as $folderId => $folderPages)
		{
			$export['folders']['n' . $nCount] = [];
			foreach ($folderPages as $pageId)
			{
				if (isset($pages[$pageId]))
				{
					$export['folders']['n' . $nCount][] = $pages[$pageId]['code'];
				}
			}
			unset($export['folders'][$folderId]);
			$nCount++;
		}
		foreach ($export['folders'] as $folderId => $folderPages)
		{
			$export['folders'][$folderPages[0]] = $folderPages;
			unset($export['folders'][$folderId]);
		}
		// ... syspages
		foreach ($export['syspages'] as &$lid)
		{
			if (isset($pages[$lid]))
			{
				$lid = $pages[$lid]['code'];
			}
		}
		unset($lid);
		// ... pages
		foreach ($pages as $page)
		{
			if (isset($page['layout']['ref']))
			{
				foreach ($page['layout']['ref'] as &$lid)
				{
					if (isset($pages[$lid]))
					{
						$lid = $pages[$lid]['code'];
					}
				}
				unset($lid);
			}
			$export['items'][$page['code']] = $page;
		}

		return $export;
	}

	/**
	 * Get md5 hash for site, using http host.
	 * @param int $id Site id.
	 * @param string $domain Domain name for this site.
	 * @return string
	 */
	public static function getPublicHash($id, $domain = null)
	{
		static $hashes = [];
		static $domains = [];

		if (isset($hashes[$id]))
		{
			return $hashes[$id];
		}

		$hash = [];

		if (Manager::isB24())
		{
			$hash[] = Manager::getHttpHost();
		}
		else
		{
			// detect domain
			if ($domain === null)
			{
				if (!isset($domains[$id]))
				{
					$domains[$id] = '';
					$res = self::getList(array(
						'select' => array(
							'SITE_DOMAIN' => 'DOMAIN.DOMAIN'
						),
						'filter' => array(
							'ID' => $id
						)
					));
					if ($row = $res->fetch())
					{
						$domains[$id] = $row['SITE_DOMAIN'];
					}
				}
				$domain = $domains[$id];
			}
			$hash[] = $domain;
		}

		if (Manager::isB24())
		{
			$hash[] = rtrim(Manager::getPublicationPath($id), '/');
		}
		else
		{
			$hash[] = $id;
			$hash[] = LICENSE_KEY;
		}

		$hashes[$id] = md5(implode('', $hash));

		return $hashes[$id];
	}

	/**
	 * Switch domains between two sites. Returns true on success.
	 * @param int $siteId1 First site id.
	 * @param int $siteId2 Second site id.
	 * @return bool
	 */
	public static function switchDomain(int $siteId1, int $siteId2): bool
	{
		return \Bitrix\Landing\Internals\SiteTable::switchDomain($siteId1, $siteId2);
	}

	/**
	 * Sets new random domain to site. Actual for Bitrix24 only.
	 * @param int $siteId Site id.
	 * @return bool
	 */
	public static function randomizeDomain(int $siteId): bool
	{
		return \Bitrix\Landing\Internals\SiteTable::randomizeDomain($siteId);
	}

	/**
	 * Creates site by template code.
	 * @param string $code Template code.
	 * @param string $type Site type.
	 * @param mixed $additional Data for landing.demo select.
	 * @return \Bitrix\Main\Entity\AddResult
	 */
	public static function addByTemplate(string $code, string $type, $additional = null): \Bitrix\Main\Entity\AddResult
	{
		$result = new \Bitrix\Main\Entity\AddResult;

		$componentName = 'bitrix:landing.demo';
		$className = \CBitrixComponent::includeComponentClass($componentName);
		/** @var \LandingSiteDemoComponent $demoCmp */
		$demoCmp = new $className;
		$demoCmp->initComponent($componentName);
		$demoCmp->arParams = [
			'TYPE' => $type,
			'DISABLE_REDIRECT' => 'Y'
		];
		$res = $demoCmp->actionSelect($code, $additional);

		if ($res)
		{
			$resSite = self::getList([
				'select' => [
					'ID'
				],
				'filter' => [
					'=TYPE' => $type
				],
				'order' => [
					'ID' => 'desc'
				]
			]);
			if ($rowSite = $resSite->fetch())
			{
				$result->setId($rowSite['ID']);
			}
		}
		else
		{
			foreach ($demoCmp->getErrors() as $code => $title)
			{
				$result->addError(new \Bitrix\Main\Error($title, $code));
			}
		}

		return $result;
	}

	/**
	 * Copies folders from one site to another without pages.
	 * @param int $fromSite Source site id.
	 * @param int $toSite Destination site id.
	 * @param array $folderMap External references old<>new ids.
	 * @return \Bitrix\Main\Result
	 */
	public static function copyFolders(int $fromSite, int $toSite, array &$folderMap = []): \Bitrix\Main\Result
	{
		$result = new \Bitrix\Main\Result();
		$fromSiteAccess = Site::ping($fromSite) && Rights::hasAccessForSite($fromSite, Rights::ACCESS_TYPES['read']);
		$toSiteAccess = Site::ping($toSite) && Rights::hasAccessForSite($toSite, Rights::ACCESS_TYPES['edit']);

		if ($fromSiteAccess && $toSiteAccess)
		{
			Landing::disableCheckUniqueAddress();

			$childrenExist = false;
			$res = Folder::getList([
				'filter' => [
					'SITE_ID' => $fromSite
				]
			]);
			while ($row = $res->fetch())
			{
				$oldId = $row['ID'];
				unset($row['ID']);

				if ($row['PARENT_ID'])
				{
					$childrenExist = true;
				}
				else
				{
					unset($row['PARENT_ID']);
				}

				if ($row['INDEX_ID'])
				{
					unset($row['INDEX_ID']);
				}

				$row['SITE_ID'] = $toSite;
				$resAdd = Folder::add($row);
				$folderMap[$oldId] = $resAdd->isSuccess() ? $resAdd->getId() : null;
			}

			// update child-parent
			if ($childrenExist)
			{
				$res = Folder::getList([
					'select' => [
						'ID', 'PARENT_ID'
					],
					'filter' => [
						'SITE_ID' => $toSite,
						'!PARENT_ID' => false
					]
				]);
				while ($row = $res->fetch())
				{
					Folder::update($row['ID'], [
						'PARENT_ID' => $folderMap[$row['PARENT_ID']] ?: null
					]);
				}
			}

			Landing::enableCheckUniqueAddress();
		}
		else
		{
			$result->addError(new \Bitrix\Main\Error(
				Loc::getMessage('LANDING_COPY_ERROR_SITE_NOT_FOUND'),
				'ACCESS_DENIED'
			));
		}
		return $result;
	}

	/**
	 * Creates folder into the site.
	 * @param int $siteId Site id.
	 * @param array $fields Folder's fields.
	 * @return \Bitrix\Main\Entity\AddResult
	 */
	public static function addFolder(int $siteId, array $fields): \Bitrix\Main\Entity\AddResult
	{
		if (self::ping($siteId) && Rights::hasAccessForSite($siteId, Rights::ACCESS_TYPES['edit']))
		{
			$fields['SITE_ID'] = $siteId;
			$result = Folder::add($fields);
		}
		else
		{
			$result = new \Bitrix\Main\Entity\AddResult;
			$result->addError(new \Bitrix\Main\Error(
				Loc::getMessage('LANDING_COPY_ERROR_SITE_NOT_FOUND'),
				'ACCESS_DENIED'
			));
		}
		return $result;
	}

	/**
	 * Updates folder of the site.
	 * @param int $siteId Site id.
	 * @param int $folderId Folder id.
	 * @param array $fields Folder's fields.
	 * @return \Bitrix\Main\Entity\UpdateResult
	 */
	public static function updateFolder(int $siteId, int $folderId, array $fields): \Bitrix\Main\Entity\UpdateResult
	{
		if (self::ping($siteId) && Rights::hasAccessForSite($siteId, Rights::ACCESS_TYPES['edit']))
		{
			$fields['SITE_ID'] = $siteId;
			$result = Folder::update($folderId, $fields);
		}
		else
		{
			$result = new \Bitrix\Main\Entity\UpdateResult;
			$result->addError(new \Bitrix\Main\Error(
				Loc::getMessage('LANDING_COPY_ERROR_SITE_NOT_FOUND'),
				'ACCESS_DENIED'
			));
		}

		return $result;
	}

	/**
	 * Public all folder's breadcrumb.
	 * @param int $folderId Folder id.
	 * @param bool $mark Publication / depublication.
	 * @return \Bitrix\Main\Result
	 */
	public static function publicationFolder(int $folderId, bool $mark = true): \Bitrix\Main\Result
	{
		$wasPublic = false;
		$result = new \Bitrix\Main\Result;
		$siteId = self::getFolder($folderId)['SITE_ID'] ?? null;

		if ($siteId && self::ping($siteId) && Rights::hasAccessForSite($siteId, Rights::ACCESS_TYPES['public']))
		{
			$wasPublic = true;
			$breadCrumbs = Folder::getBreadCrumbs($folderId);
			if (!$breadCrumbs)
			{
				$wasPublic = false;
			}
			$char = $mark ? 'Y' : 'N';
			foreach ($breadCrumbs as $folder)
			{
				if ($folder['ACTIVE'] === $char)
				{
					continue;
				}
				if ($folder['DELETED'] === 'Y')
				{
					$result->addError(new \Bitrix\Main\Error(
						Loc::getMessage('LANDING_COPY_ERROR_FOLDER_NOT_FOUND'),
						'ACCESS_DENIED'
					));
					return $result;
				}
				$res = Folder::update($folder['ID'], [
					'ACTIVE' => $char
				]);
				if (!$res->isSuccess())
				{
					$wasPublic = false;
				}
			}
		}

		if (!$wasPublic)
		{
			$result->addError(new \Bitrix\Main\Error(
				Loc::getMessage('LANDING_COPY_ERROR_FOLDER_NOT_FOUND'),
				'ACCESS_DENIED'
			));
		}

		return $result;
	}

	/**
	 * Moves folder.
	 * @param int $folderId Current folder id.
	 * @param int|null $toFolderId Destination folder id (or null for root folder of current folder's site).
	 * @param int|null $toSiteId Destination site id (if different from current).
	 * @return \Bitrix\Main\Result
	 */
	public static function moveFolder(int $folderId, ?int $toFolderId, ?int $toSiteId = null): \Bitrix\Main\Result
	{
		$returnError = function()
		{
			$result = new \Bitrix\Main\Result;
			$result->addError(new \Bitrix\Main\Error(
				Loc::getMessage('LANDING_COPY_ERROR_FOLDER_NOT_FOUND'),
				'ACCESS_DENIED'
			));
			return $result;
		};

		$folder = Folder::getList([
			'filter' => [
				'ID' => $folderId
			]
		])->fetch();
		if ($folder)
		{
			// move to another site
			if ($toSiteId && (int)$folder['SITE_ID'] !== $toSiteId)
			{
				// check access to another site
				$hasRightFrom = Rights::hasAccessForSite($folder['SITE_ID'], Rights::ACCESS_TYPES['delete']);
				$hasRightTo = Rights::hasAccessForSite($toSiteId, Rights::ACCESS_TYPES['edit']);
				if (!$hasRightFrom || !$hasRightTo)
				{
					return $returnError();
				}

				// check another site folder if specified
				$toFolder = null;
				if ($toFolderId)
				{
					$toFolder = Folder::getList([
						'filter' => [
							'ID' => $toFolderId,
							'SITE_ID' => $toSiteId
						]
					])->fetch();
					if (!$toFolder)
					{
						return $returnError();
					}
				}

				// move folder
				$res = Folder::update($folderId, [
					'SITE_ID' => $toSiteId,
					'PARENT_ID' => $toFolder['ID'] ?? null
				]);
				if ($res->isSuccess())
				{
					Folder::changeSiteIdRecursive($folderId, $toSiteId);
				}

				return $res;
			}

			$willBeRoot = !$toFolderId;

			// check destination folder
			$toFolder = null;
			if ($toFolderId)
			{
				$toFolder = Folder::getList([
					'filter' => [
						'ID' => $toFolderId
					]
				])->fetch();
				if (!$toFolder)
				{
					return $returnError();
				}
			}
			if (!$toFolder)
			{
				$toFolder = $folder;
			}
			// check restriction to move to itself
			if (!$willBeRoot)
			{
				$breadCrumbs = Folder::getBreadCrumbs($toFolder['ID'], $toFolder['SITE_ID']);
				for ($i = 0, $c = count($breadCrumbs); $i < $c; $i++)
				{
					if ($breadCrumbs[$i]['ID'] === $folder['ID'])
					{
						$result = new \Bitrix\Main\Result;
						$result->addError(new \Bitrix\Main\Error(
							Loc::getMessage('LANDING_COPY_ERROR_MOVE_RESTRICTION'),
							'MOVE_RESTRICTION'
						));
						return $result;
					}
				}
			}
			// check access and update then
			$hasRightFrom = Rights::hasAccessForSite($folder['SITE_ID'], Rights::ACCESS_TYPES['delete']);
			$hasRightTo = Rights::hasAccessForSite($toFolder['SITE_ID'], Rights::ACCESS_TYPES['edit']);
			if ($hasRightFrom && $hasRightTo)
			{
				return Folder::update($folderId, [
					'SITE_ID' => $toFolder['SITE_ID'],
					'PARENT_ID' => !$willBeRoot ? $toFolder['ID'] : null
				]);
			}
		}

		return $returnError();
	}

	/**
	 * Returns folder's list of site.
	 * @param int $siteId Site id.
	 * @param array $filter Folder's filter.
	 * @return array
	 */
	public static function getFolders(int $siteId, array $filter = []): array
	{
		if (!Rights::hasAccessForSite($siteId, Rights::ACCESS_TYPES['read']))
		{
			return [];
		}

		if (!isset($filter['DELETED']) && !isset($filter['=DELETED']))
		{
			$filter['=DELETED'] = 'N';
		}

		$folders = [];
		$filter['SITE_ID'] = $siteId;
		$res = Folder::getList([
			'filter' => $filter,
			'order' => [
				'DATE_MODIFY' => 'desc'
			]
		]);
		while ($row = $res->fetch())
		{
			$folders[$row['ID']] = $row;
		}
		return $folders;
	}

	/**
	 * Returns folder's info.
	 * @param int $folderId Folder id.
	 * @param string $accessLevel Access level to folder.
	 * @return array|null
	 */
	public static function getFolder(int $folderId, string $accessLevel = Rights::ACCESS_TYPES['read']): ?array
	{
		$folder = Folder::getList([
			'filter' => [
				'ID' => $folderId
			]
		])->fetch();

		if ($folder)
		{
			if (!Rights::hasAccessForSite($folder['SITE_ID'], $accessLevel))
			{
				return null;
			}
		}

		return is_array($folder) ? $folder : null;
	}

	/**
	 * Mark folder as deleted.
	 * @param int $id Folder id.
	 * @return \Bitrix\Main\Result
	 */
	public static function markFolderDelete(int $id): \Bitrix\Main\Result
	{
		$folder = self::getFolder($id);

		if (!$folder || !Rights::hasAccessForSite($folder['SITE_ID'], Rights::ACCESS_TYPES['delete']))
		{
			$result = new \Bitrix\Main\Entity\AddResult;
			$result->addError(new \Bitrix\Main\Error(
				Loc::getMessage('LANDING_COPY_ERROR_FOLDER_NOT_FOUND'),
				'ACCESS_DENIED'
			));
			return $result;
		}

		// disable delete if folder (or aby sub folders) contains area
		$res = Landing::getList([
			'select' => ['ID'],
			'filter' => [
				'FOLDER_ID' => [$id, ...Folder::getSubFolderIds($id)],
				'!==AREAS.ID' => null,
			],
		]);
		if ($res->fetch())
		{
			$result = new \Bitrix\Main\Entity\AddResult;
			$result->addError(new \Bitrix\Main\Error(
				Loc::getMessage('LANDING_DELETE_FOLDER_ERROR_CONTAINS_AREAS'),
				'FOLDER_CONTAINS_AREAS'
			));
			return $result;
		}

		$event = new Event('landing', 'onBeforeFolderRecycle', [
			'id' => $id,
			'delete' => 'Y'
		]);
		$event->send();

		foreach ($event->getResults() as $result)
		{
			if ($result->getType() == EventResult::ERROR)
			{
				$return = new \Bitrix\Main\Result;
				foreach ($result->getErrors() as $error)
				{
					$return->addError(
						$error
					);
				}
				return $return;
			}
		}

		if (($currentScope = Site\Type::getCurrentScopeId()))
		{
			Agent::addUniqueAgent('clearRecycleScope', [$currentScope]);
		}

		return Folder::update($id, [
			'DELETED' => 'Y'
		]);
	}

	/**
	 * Mark folder as restored.
	 * @param int $id Folder id.
	 * @return \Bitrix\Main\Result
	 */
	public static function markFolderUnDelete(int $id): \Bitrix\Main\Result
	{
		$folder = self::getFolder($id);

		if (!$folder || !Rights::hasAccessForSite($folder['SITE_ID'], Rights::ACCESS_TYPES['delete']))
		{
			$result = new \Bitrix\Main\Entity\AddResult;
			$result->addError(new \Bitrix\Main\Error(
				Loc::getMessage('LANDING_COPY_ERROR_FOLDER_NOT_FOUND'),
				'ACCESS_DENIED'
			));
			return $result;
		}

		$event = new Event('landing', 'onBeforeFolderRecycle', array(
			'id' => $id,
			'delete' => 'N'
		));
		$event->send();

		foreach ($event->getResults() as $result)
		{
			if ($result->getType() == EventResult::ERROR)
			{
				$return = new \Bitrix\Main\Result;
				foreach ($result->getErrors() as $error)
				{
					$return->addError(
						$error
					);
				}
				return $return;
			}
		}

		return Folder::update($id, array(
			'DELETED' => 'N'
		));
	}

	/**
	 * Tries to add page to the all menu on the site.
	 * Detects blocks with menu-manifests only.
	 * @param int $siteId Site id.
	 * @param array $data Landing data ([ID, TITLE]).
	 * @return void
	 */
	public static function addLandingToMenu(int $siteId, array $data): void
	{
		Landing::setEditMode();
		$res = Landing::getList([
			'select' => [
				'ID'
			],
			'filter' => [
				'SITE_ID' => $siteId,
				'!==AREAS.ID' => null
			],
		]);
		while ($row = $res->fetch())
		{
			$landing = Landing::createInstance($row['ID']);
			if ($landing->exist())
			{
				foreach ($landing->getBlocks() as $block)
				{
					$manifest = $block->getManifest();
					if (isset($manifest['menu']))
					{
						foreach ($manifest['menu'] as $menuSelector => $foo)
						{
							$block->updateNodes([
								$menuSelector => [
									[
										'text' => $data['TITLE'],
										'href' => '#landing' . $data['ID']
									]
								]
							], ['appendMenu' => true]);
							$block->save();
							break 2;
						}
					}
				}
			}
		}
	}

	/**
	 * Change modified user and date for the site.
	 * @param int $id Site id.
	 * @return void
	 */
	public static function touch(int $id): void
	{
		static $touched = [];

		if (isset($touched[$id]))
		{
			return;
		}

		$touched[$id] = true;

		self::update($id, [
			'TOUCH' => 'Y'
		]);
	}

	/**
	 * Makes site public.
	 * @param int $id Site id.
	 * @param bool $mark Mark.
	 * @return \Bitrix\Main\Result
	 */
	public static function publication(int $id, bool $mark = true): \Bitrix\Main\Result
	{
		$return = new \Bitrix\Main\Result;

		if ($mark)
		{
			$verificationError = new Error();
			if (!Mutator::checkSiteVerification($id, $verificationError))
			{
				$return->addError($verificationError->getFirstError());
				return $return;
			}
		}

		// work with pages
		$res = Landing::getList([
			'select' => [
				'ID', 'ACTIVE', 'PUBLIC'
			],
			'filter' => [
				'SITE_ID' => $id,
				[
					'LOGIC' => 'OR',
					['FOLDER_ID' => null],
					['!FOLDER_ID' => Folder::getFolderIdsForSite($id, ['=DELETED' => 'Y']) ?: [-1]]
				]
			]
		]);
		while ($row = $res->fetch())
		{
			if ($row['ACTIVE'] != 'Y')
			{
				$row['PUBLIC'] = 'N';
			}
			if ($row['PUBLIC'] == 'Y')
			{
				continue;
			}
			$landing = Landing::createInstance($row['ID'], [
				'skip_blocks' => true
			]);

			if ($mark)
			{
				$resPublication = $landing->publication();
			}
			else
			{
				$resPublication = $landing->unpublic();
			}

			if (!$resPublication)
			{
				if (!$landing->getError()->isEmpty())
				{
					$error = $landing->getError()->getFirstError();
					$return->addError(new \Bitrix\Main\Error(
						$error->getMessage(),
						$error->getCode()
					));
					return $return;
				}
			}
		}

		$res = Folder::getList([
			'select' => [
				'ID'
			],
			'filter' => [
				'SITE_ID' => $id,
				'=ACTIVE' => $mark ? 'N' : 'Y',
				'=DELETED' => 'N'
			]
		]);
		while ($row = $res->fetch())
		{
			Folder::update($row['ID'], [
				'ACTIVE' => $mark ? 'Y' : 'N'
			]);
		}

		return parent::update($id, [
			'ACTIVE' => $mark ? 'Y' : 'N'
		]);
	}

	/**
	 * Marks site unpublic.
	 * @param int $id Site id.
	 * @return \Bitrix\Main\Result
	 */
	public static function unpublic(int $id): \Bitrix\Main\Result
	{
		return self::publication($id, false);
	}

	/**
	 * Returns site id by template code.
	 * @param string $tplCode Template code.
	 * @return int|null
	 */
	public static function getSiteIdByTemplate(string $tplCode): ?int
	{
		$site = \Bitrix\Landing\Site::getList([
			'select' => [
				'ID'
			],
			'filter' => [
				'=TPL_CODE' => $tplCode
			],
			'order' => [
				'ID' => 'desc'
			]
		])->fetch();

		return $site['ID'] ?? null;
	}

	/**
	 * Event handler for check existing pages of main module's site.
	 * @param string $siteId Main site id.
	 * @return bool
	 */
	public static function onBeforeMainSiteDelete($siteId)
	{
		$res = Landing::getList(array(
			'select' => array(
				'ID'
			),
			'filter' => array(
				'=SITE.SMN_SITE_ID' => $siteId,
				'CHECK_PERMISSIONS' => 'N'
			)
		));

		if ($res->fetch())
		{
			Manager::getApplication()->throwException(
				Loc::getMessage('LANDING_CLB_ERROR_DELETE_SMN'),
				'ERROR_DELETE_SMN'
			);
			return false;
		}

		return true;
	}

	/**
	 * Event handler for delete pages of main module's site.
	 * @param string $siteId Main site id.
	 * @return void
	 */
	public static function onMainSiteDelete($siteId)
	{
		Rights::setOff();

		$realSiteId = null;
		// delete pages
		$res = Landing::getList(array(
			'select' => array(
				'ID', 'SITE_ID'
			),
			'filter' => array(
				'=SITE.SMN_SITE_ID' => $siteId,
				'=SITE.DELETED' => ['Y', 'N'],
				'=DELETED' => ['Y', 'N']
			)
		));
		while ($row = $res->fetch())
		{
			$realSiteId = $row['SITE_ID'];
			Landing::delete($row['ID'], true);
		}
		// detect site
		if (!$realSiteId)
		{
			$res = self::getList(array(
				'select' => array(
					'ID'
				),
				'filter' => array(
					'=SMN_SITE_ID' => $siteId,
					'=DELETED' => ['Y', 'N']
				)
			));
			if ($row = $res->fetch())
			{
				$realSiteId = $row['ID'];
			}
		}
		// and delete site
		if ($realSiteId)
		{
			self::delete($realSiteId);
		}

		Rights::setOn();
	}

	/**
	 * Change type for the site.
	 * @param int $id Site id.
	 * @param string $type Type.
	 * @return void
	 */
	public static function changeType(int $id, string $type): void
	{
		if (self::getTypes()[$type] ?? null)
		{
			parent::update($id, array(
				'TYPE' => $type
			));
		}
	}

	/**
	 * Change code for the site.
	 * @param int $id Site id.
	 * @param string $code Code.
	 * @return void
	 */
	public static function changeCode(int $id, string $code): void
	{
		parent::update($id, array(
			'CODE' => $code
		));
	}
}