Your IP : 18.217.56.115


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/hook.php

<?php
namespace Bitrix\Landing;

use Bitrix\Landing\Hook\Page;
use \Bitrix\Landing\Internals\HookDataTable as HookData;
use \Bitrix\Main\Event;
use \Bitrix\Main\EventResult;

class Hook
{
	/**
	 * If true, hook work in edit mode (form settings).
	 * @var boolean
	 */
	protected static $editMode = false;

	/**
	 * Entity type site.
	 */
	const ENTITY_TYPE_SITE = 'S';

	/**
	 * Entity type landing.
	 */
	const ENTITY_TYPE_LANDING = 'L';

	/**
	 * Dir of repository of common hooks.
	 */
	const HOOKS_PAGE_DIR = '/bitrix/modules/landing/lib/hook/page';

	/**
	 * Namespace of repoitory of common hooks (relative current).
	 */
	const HOOKS_NAMESPACE = '\\Hook\\Page\\';

	/**
	 * Handler for copy event
	 */
	protected const HOOKS_ON_COPY_HANDLER = 'onCopy';

	/**
	 * Hook codes which contains file ids.
	 */
	const HOOKS_CODES_FILES = [
		'METAOG_IMAGE',
		'BACKGROUND_PICTURE'
	];

	/**
	 * Hook codes which have visual effect
	 */
	const HOOKS_CODES_DESIGN = [
		'BACKGROUND_USE',
		'BACKGROUND_PICTURE',
		'BACKGROUND_POSITION',
		'BACKGROUND_COLOR',
		'FONTS_CODE',
		'THEME_CODE',
		'THEME_USE',
		'THEME_COLOR',
		'THEMEFONTS_USE',
		'THEMEFONTS_CODE_H',
		'THEMEFONTS_CODE',
		'THEMEFONTS_SIZE',
		'THEMEFONTS_COLOR',
		'THEMEFONTS_COLOR_H',
		'THEMEFONTS_LINE_HEIGHT',
		'THEMEFONTS_FONT_WEIGHT',
		'THEMEFONTS_FONT_WEIGHT_H',
	];

	/**
	 * Get classes from dir.
	 * @param string $dir Relative dir.
	 * @return array
	 */
	protected static function getClassesFromDir($dir)
	{
		$classes = array();

		$path = Manager::getDocRoot() . $dir;
		if (($handle = opendir($path)))
		{
			while ((($entry = readdir($handle)) !== false))
			{
				if ($entry != '.' && $entry != '..')
				{
					$classes[] = mb_strtoupper(pathinfo($entry, PATHINFO_FILENAME));
				}
			}
		}

		return $classes;
	}

	/**
	 * Get data by entity id ant type.
	 * @param int $id Entity id.
	 * @param string $type Entity type.
	 * @param boolean $asIs Return row as is.
	 * @return array
	 */
	public static function getData($id, $type, $asIs = false): array
	{
		$data = [];
		$id = (int)$id;

		if (!is_string($type))
		{
			return $data;
		}

		$res = HookData::getList([
			'select' => [
				'ID', 'HOOK', 'CODE', 'VALUE'
			],
			'filter' => [
				'ENTITY_ID' => $id,
				'=ENTITY_TYPE' => $type,
				'=PUBLIC' => self::$editMode ? 'N' : 'Y'
			],
			'order' => [
				'ID' => 'asc'
			]
		]);
		while ($row = $res->fetch())
		{
			if (!isset($data[$row['HOOK']]))
			{
				$data[$row['HOOK']] = [];
			}
			if (mb_strpos($row['VALUE'], 'serialized#') === 0)
			{
				$row['VALUE'] = unserialize(mb_substr($row['VALUE'], 11), ['allowed_classes' => false]);
			}
			$data[$row['HOOK']][$row['CODE']] = $asIs ? $row : $row['VALUE'];
		}

		return $data;
	}

	/**
	 * Get available hooks for this landing.
	 * @param int $id Entity id.
	 * @param string $type Entity type.
	 * @param array $data Data array (optional).
	 * @return Page[]
	 */
	protected static function getList($id, $type, array $data = array())
	{
		$hooks = array();
		$classDir = self::HOOKS_PAGE_DIR;
		$classNamespace = self::HOOKS_NAMESPACE;
		$excludedHooks = \Bitrix\Landing\Site\Type::getExcludedHooks();

		// first read all hooks in base dir
		foreach (self::getClassesFromDir($classDir) as $class)
		{
			if (in_array($class, $excludedHooks))
			{
				continue;
			}
			$classFull = __NAMESPACE__  . $classNamespace . $class;
			if (class_exists($classFull))
			{
				$hooks[$class] = new $classFull(
					self::$editMode,
					!($type == self::ENTITY_TYPE_SITE)
				);
			}
		}

		// sort hooks
		uasort($hooks, function($a, $b)
		{
			if ($a->getSort() == $b->getSort())
			{
				return 0;
			}
			return ($a->getSort() < $b->getSort()) ? -1 : 1;
		});

		// check custom exec
		$event = new Event('landing', 'onHookExec');
		$event->send();
		foreach ($event->getResults() as $result)
		{
			if ($result->getType() != EventResult::ERROR)
			{
				if ($customExec = $result->getModified())
				{
					foreach ((array)$customExec as $code => $itemExec)
					{
						$code = mb_strtoupper($code);
						if (isset($hooks[$code]) && is_callable($itemExec))
						{
							$hooks[$code]->setCustomExec($itemExec);
						}
					}
					unset($code, $itemExec);
				}
				unset($customExec);
			}
		}
		unset($event, $result);

		// then fill hook with data
		if (!empty($hooks) && $id > 0)
		{
			if (empty($data))
			{
				$data = self::getData($id, $type);
			}
			foreach ($hooks as $code => $hook)
			{
				if (isset($data[$code]))
				{
					$hook->setData($data[$code]);
				}
			}
		}

		return $hooks;
	}

	/**
	 * Set edit mode to true.
	 * @param bool $mode Edit mode (true by default).
	 * @return void
	 */
	public static function setEditMode(bool $mode = true): void
	{
		self::$editMode = $mode;
	}

	/**
	 * Returns edit mode state.
	 * @return bool
	 */
	public static function getEditMode(): bool
	{
		return self::$editMode;
	}

	/**
	 * Get hooks for site.
	 * @param int $id Site id.
	 * @return Page[]
	 */
	public static function getForSite($id)
	{
		if (!Landing::getEditMode())
		{
			static $hooks = [];
		}
		else
		{
			$hooks = [];
		}

		if (!array_key_exists($id, $hooks))
		{
			$hooks[$id] = self::getList($id, self::ENTITY_TYPE_SITE);
		}

		return $hooks[$id];
	}

	/**
	 * Get hooks for landing.
	 * @param int $id Landing id.
	 * @return Page[]
	 */
	public static function getForLanding($id)
	{
		if (!Landing::getEditMode())
		{
			static $hooks = [];
		}
		else
		{
			$hooks = [];
		}

		if (!array_key_exists($id, $hooks))
		{
			$hooks[$id] = self::getList($id, self::ENTITY_TYPE_LANDING);
		}

		return $hooks[$id];
	}

	/**
	 * Get row hooks for landing.
	 * @param int $id Landing id.
	 * @return array
	 */
	public static function getForLandingRow($id)
	{
		return self::getData($id, self::ENTITY_TYPE_LANDING);
	}

	/**
	 * Copy data for entity.
	 * @param int $from From entity id.
	 * @param int $to To entity id.
	 * @param string $type Entity type.
	 * @param bool $publication It's not copy, but publication.
	 * @return void
	 */
	protected static function copy($from, $to, $type, $publication = false)
	{
		$from = (int)$from;
		$to = (int)$to;
		$data = self::getData($from, $type);
		$existData = [];

		$classDir = self::HOOKS_PAGE_DIR;
		$classNamespace = self::HOOKS_NAMESPACE;
		$excludedHooks = \Bitrix\Landing\Site\Type::getExcludedHooks();

		// first read all hooks in base dir
		foreach (self::getClassesFromDir($classDir) as $class)
		{
			if (in_array($class, $excludedHooks, true))
			{
				continue;
			}
			$classFull = __NAMESPACE__  . $classNamespace . $class;
			if (
				isset($data[$class])
				&& method_exists($classFull, self::HOOKS_ON_COPY_HANDLER)
			)
			{
				$handler = self::HOOKS_ON_COPY_HANDLER;
				if ($preparedData = $classFull::$handler($data[$class], $from, $type, $publication))
				{
					$data[$class] = $preparedData;
				}
			}
		}

		// collect exist data
		if ($data)
		{
			$res = HookData::getList([
				'select' => [
					'ID', 'HOOK', 'CODE'
				],
				'filter' => [
					'ENTITY_ID' => $to,
					'=ENTITY_TYPE' => $type,
					'=PUBLIC' => $publication ? 'Y' : 'N'
				]
			]);
			while ($row = $res->fetch())
			{
				$existData[$row['HOOK'] . '_' . $row['CODE']] = $row['ID'];
			}
		}

		// update existing keys or add new
		foreach ($data as $hookCode => $items)
		{
			foreach ($items as $code => $value)
			{
				$existKey = $hookCode . '_' . $code;
				if (is_array($value))
				{
					$value = 'serialized#' . serialize($value);
				}
				if (array_key_exists($existKey, $existData))
				{
					HookData::update($existData[$existKey], [
						'VALUE' => $value
					]);
					unset($existData[$existKey]);
				}
				else
				{
					HookData::add([
						'ENTITY_ID' => $to,
						'ENTITY_TYPE' => $type,
						'HOOK' => $hookCode,
						'CODE' => $code,
						'VALUE' => $value,
						'PUBLIC' => $publication ? 'Y' : 'N'
					]);
				}
			}
		}

		// delete unused data
		if ($existData)
		{
			foreach ($existData as $delId)
			{
				HookData::delete($delId);
			}
		}
	}

	/**
	 * Copy data for site.
	 * @param int $from From site id.
	 * @param int $to To site id.
	 * @return void
	 */
	public static function copySite($from, $to)
	{
		$originalEditMode = self::$editMode;
		if (!self::$editMode)
		{
			self::$editMode = true;
		}
		self::copy($from, $to, self::ENTITY_TYPE_SITE);
		self::$editMode = $originalEditMode;
	}

	/**
	 * Copy data for landing.
	 * @param int $from From landing id.
	 * @param int $to To landing id.
	 * @return void
	 */
	public static function copyLanding($from, $to)
	{
		$originalEditMode = self::$editMode;
		if (!self::$editMode)
		{
			self::$editMode = true;
		}
		self::copy($from, $to, self::ENTITY_TYPE_LANDING);
		self::$editMode = $originalEditMode;
	}

	/**
	 * Publication data for site.
	 * @param int $siteId Site id.
	 * @return void
	 */
	public static function publicationSite($siteId)
	{
		self::copy($siteId, $siteId, self::ENTITY_TYPE_SITE, true);
	}

	/**
	 * Publication data for landing.
	 * @param int $lid Landing id.
	 * @return void
	 */
	public static function publicationLanding($lid)
	{
		self::copy($lid, $lid, self::ENTITY_TYPE_LANDING, true);
	}

	/**
	 * In disable autobulication option we must skip hooks, then required page publication.
	 * @param $siteId
	 * @return void
	 */
	public static function publicationSiteWithSkipNeededPublication($siteId): void
	{
		self::publicationWithSkipNeededPublication($siteId, self::ENTITY_TYPE_SITE);
	}

	/**
	 * In disable autobulication option we must skip hooks, then required page publication.
	 * @param $landingId
	 * @return void
	 */
	public static function publicationLandingWithSkipNeededPublication($landingId): void
	{
		self::publicationWithSkipNeededPublication($landingId, self::ENTITY_TYPE_LANDING);
	}

	protected static function publicationWithSkipNeededPublication($id, $type): void
	{
		$editModeBack = self::$editMode;
		self::$editMode = false;
		$publicData = self::getData($id, $type, true);
		self::$editMode = $editModeBack;

		if ($type === self::ENTITY_TYPE_SITE)
		{
			self::publicationSite($id);
		}
		if ($type === self::ENTITY_TYPE_LANDING)
		{
			self::publicationLanding($id);
		}
		$data = self::getData($id, $type, true);

		// return previously public values
		$needClearCache = false;
		foreach (self::getList($id, $type) as $hook)
		{
			if ($hook->isNeedPublication())
			{
				$fieldsToDelete = [];
				if (isset($publicData[$hook->getCode()]))
				{
					foreach ($data[$hook->getCode()] as $fieldCode => $field)
					{
						if (!isset($publicData[$hook->getCode()][$fieldCode]))
						{
							$fieldsToDelete[$fieldCode] = $field;
						}
						elseif ($publicData[$hook->getCode()][$fieldCode]['VALUE'] !== $field['VALUE'])
						{
							$needClearCache = true;
							HookData::update($field['ID'],
								[
									'VALUE' => $field['VALUE'],
								]
							);
						}
					}
				}
				else
				{
					$fieldsToDelete = $data[$hook->getCode()] ?? [];
				}

				// del if not exists in public
				if (!empty($fieldsToDelete))
				{
					$needClearCache = true;
					foreach ($fieldsToDelete as $fieldCode => $field)
					{
						$res = HookData::getList([
							'select' => ['ID'],
							'filter' => [
								'ENTITY_ID' => $id,
								'=ENTITY_TYPE' => $type,
								'=HOOK' => $hook->getCode(),
								'=CODE' => $fieldCode,
								'=PUBLIC' => 'Y'
							]
						]);
						if ($row = $res->fetch())
						{
							HookData::delete($row['ID']);
						}
					}
				}
			}
		}

		// drop public cache
		if ($needClearCache)
		{
			if ($type === self::ENTITY_TYPE_SITE)
			{
				$landings = Landing::getList([
					'select' => ['ID'],
					'filter' => [
						'SITE_ID' => $id,
						'=PUBLIC' => 'Y',
						'=DELETED' => 'N',
					],
				]);
				while ($landing = $landings->fetch())
				{
					Landing::update($landing['ID'], ['PUBLIC' => 'N']);
				}
			}
			if ($type === self::ENTITY_TYPE_LANDING)
			{
				Landing::update($id, ['PUBLIC' => 'N']);
			}
		}
	}

	/**
	 * Prepare data for save in hooks.
	 * @param array $data Input data.
	 * @return array
	 */
	protected static function prepareData(array $data)
	{
		$newData = array();

		foreach ($data as $code => $val)
		{
			if (mb_strpos($code, '_') !== false)
			{
				$codeHook = mb_substr($code, 0, mb_strpos($code, '_'));
				$codeVal = mb_substr($code, mb_strpos($code, '_') + 1);
				if (!isset($newData[$codeHook]))
				{
					$newData[$codeHook] = array();
				}
				$newData[$codeHook][$codeVal] = $val;
			}
		}

		return $newData;
	}

	/**
	 * Set data hooks for entity.
	 * @param int $id Entity id.
	 * @param string $type Entity type.
	 * @param array $data Data array.
	 * @return void
	 */
	protected static function saveData($id, $type, array $data)
	{
		$data = self::prepareData($data);
		$hooks = self::getList($id, $type, $data);
		$dataSave = self::getData($id, $type, true);

		// get hooks with new new data (not saved yet)
		foreach ($hooks as $hook)
		{
			$hookLocked = $hook->isLocked();
			$codeHook = $hook->getCode();
			// modify $dataSave ...
			foreach ($hook->getFields() as $field)
			{
				$codeVal = $field->getCode();
				if ($hookLocked && !$field->isEmptyValue())
				{
					continue;
				}
				if (!isset($data[$codeHook][$codeVal]))
				{
					continue;
				}
				// ... for changed
				if (isset($dataSave[$codeHook][$codeVal]))
				{
					$dataSave[$codeHook][$codeVal]['CHANGED'] = true;
					$dataSave[$codeHook][$codeVal]['VALUE'] = $field->getValue();
				}
				// ... and new fields
				else
				{
					if (!isset($dataSave[$codeHook]))
					{
						$dataSave[$codeHook] = array();
					}
					$dataSave[$codeHook][$codeVal] = array(
						'HOOK' => $codeHook,
						'CODE' => $codeVal,
						'VALUE' => $field->getValue()
					);
				}
				if (is_array($dataSave[$codeHook][$codeVal]['VALUE']))
				{
					$dataSave[$codeHook][$codeVal]['VALUE'] = 'serialized#' . serialize(
						$dataSave[$codeHook][$codeVal]['VALUE']
					);
				}
			}
		}

		// now save the data
		foreach ($dataSave as $codeHook => $dataHook)
		{
			foreach ($dataHook as $code => $row)
			{
				if (
					is_array($row['VALUE']) && empty($row['VALUE'])
					||
					!is_array($row['VALUE']) && trim($row['VALUE']) == ''
				)
				{
					if (isset($row['ID']))
					{
						HookData::delete($row['ID']);
					}
				}
				else
				{
					if (!isset($row['ID']))
					{
						$row['ENTITY_ID'] = $id;
						$row['ENTITY_TYPE'] = $type;
						HookData::add($row);
					}
					elseif (isset($row['CHANGED']) && $row['CHANGED'])
					{
						$updId = $row['ID'];
						unset($row['ID'], $row['CHANGED']);
						HookData::update($updId, $row);
					}
				}
			}
		}
	}

	/**
	 * Index hook's content for entities.
	 * @param int $id Entity id.
	 * @param string $type Entity type.
	 * @return void
	 */
	protected static function indexContent($id, $type)
	{
		$id = intval($id);

		if ($type == self::ENTITY_TYPE_LANDING)
		{
			$class = '\Bitrix\Landing\Landing';
		}

		if (!isset($class))
		{
			return;
		}

		// base fields
		$searchContent = $class::getList([
			'select' => [
				'TITLE', 'DESCRIPTION'
			],
			'filter' => [
				'ID' => $id,
				'=DELETED' => ['Y', 'N'],
				'=SITE.DELETED' => ['Y', 'N']
			]
		])->fetch();
		if (!$searchContent)
		{
			return;
		}

		$searchContent = array_values($searchContent);

		// hook fields
		foreach (self::getList($id, $type) as $hook)
		{
			foreach ($hook->getFields() as $field)
			{
				if ($field->isSearchable())
				{
					$searchContent[] = $field->getValue();
				}
			}
		}

		$searchContent = array_unique($searchContent);
		$searchContent = $searchContent ? implode(' ', $searchContent) : '';
		$searchContent = trim($searchContent);

		if ($searchContent)
		{
			$res = $class::update($id, [
				'SEARCH_CONTENT' => $searchContent
			]);
			$res->isSuccess();
		}
	}

	/**
	 * Set data hooks for site.
	 * @param int $id Site id.
	 * @param array $data Data array.
	 * @return void
	 */
	public static function saveForSite(int $id, array $data): void
	{
		$check = Site::getList([
			'select' => [
				'ID'
			],
			'filter' => [
				'ID' => $id
			]
		])->fetch();
		if ($check)
		{
			$editModeBack = self::$editMode;
			self::$editMode = true;
			self::saveData($id, self::ENTITY_TYPE_SITE, $data);
			if (Manager::getOption('public_hook_on_save') === 'Y')
			{
				self::publicationSiteWithSkipNeededPublication($id);
			}
			self::$editMode = $editModeBack;
		}
	}

	/**
	 * Get hooks for landing.
	 * @param int $id Landing id.
	 * @param array $data Data array.
	 * @return void
	 */
	public static function saveForLanding(int $id, array $data): void
	{
		$check = Landing::getList([
			'select' => [
				'ID'
			],
			'filter' => [
				'ID' => $id
			]
		])->fetch();
		if ($check)
		{
			$editModeBack = self::$editMode;
			self::$editMode = true;
			self::saveData($id, self::ENTITY_TYPE_LANDING, $data);
			self::indexContent($id, self::ENTITY_TYPE_LANDING);
			if (Manager::getOption('public_hook_on_save') === 'Y')
			{
				self::publicationLandingWithSkipNeededPublication($id);
			}
			self::$editMode = $editModeBack;
		}
	}

	/**
	 * Index hook's content for landing.
	 * @param int $id Landing id.
	 * @return void
	 */
	public static function indexLanding($id)
	{
		self::indexContent($id, self::ENTITY_TYPE_LANDING);
	}

	/**
	 * Delete data hooks for entity.
	 * @param int $id Entity id.
	 * @param string $type Entity type.
	 * @return void
	 */
	protected static function deleteData($id, $type)
	{
		$id = intval($id);

		$res = HookData::getList(array(
			'select' => array(
				'ID'
			),
			'filter' => array(
				'ENTITY_ID' => $id,
				'=ENTITY_TYPE' => $type
			)
		));
		while ($row = $res->fetch())
		{
			HookData::delete($row['ID']);
		}
	}

	/**
	 * Delete data hooks for site.
	 * @param int $id Landing id.
	 * @return void
	 */
	public static function deleteForSite($id)
	{
		self::deleteData($id, self::ENTITY_TYPE_SITE);
	}

	/**
	 * Delete data hooks for landing.
	 * @param int $id Landing id.
	 * @return void
	 */
	public static function deleteForLanding($id)
	{
		self::deleteData($id, self::ENTITY_TYPE_LANDING);
	}
}