Your IP : 18.117.100.130


Current Path : /var/www/www-root/data/www/monolith-realty.ru/bitrix/modules/main/lib/
Upload File :
Current File : /var/www/www-root/data/www/monolith-realty.ru/bitrix/modules/main/lib/eventmanager.php

<?php

/**
 * Bitrix Framework
 * @package bitrix
 * @subpackage main
 * @copyright 2001-2023 Bitrix
 */

namespace Bitrix\Main;

use Bitrix\Main\Type\Collection;

class EventManager
{
	protected const CACHE_ID = 'b_module_to_module';

	/**
	 * @var EventManager
	 */
	protected static $instance;
	protected $handlers = [];
	protected $isHandlersLoaded = false;

	protected function __construct()
	{
	}

	/**
	 * @static
	 * @return EventManager
	 */
	public static function getInstance()
	{
		if (!isset(self::$instance))
		{
			$c = __CLASS__;
			self::$instance = new $c;
		}

		return self::$instance;
	}

	/**
	 * @static
	 * @param EventManager $instance
	 */
	public static function setInstance($instance)
	{
		$c = __CLASS__;
		if ($instance instanceof $c)
		{
			self::$instance = $instance;
		}
	}

	protected function addEventHandlerInternal($fromModuleId, $eventType, $callback, $includeFile, $sort, $version)
	{
		$arEvent = [
			'FROM_MODULE_ID' => $fromModuleId,
			'MESSAGE_ID' => $eventType,
			'CALLBACK' => $callback,
			'SORT' => (int)$sort,
			'FULL_PATH' => $includeFile,
			'VERSION' => $version,
			'TO_NAME' => $this->formatEventName(['CALLBACK' => $callback]),
		];

		$fromModuleId = strtoupper($fromModuleId);
		$eventType = strtoupper($eventType);

		if (!isset($this->handlers[$fromModuleId]) || !is_array($this->handlers[$fromModuleId]))
		{
			$this->handlers[$fromModuleId] = [];
		}

		$arEvents = &$this->handlers[$fromModuleId];

		if (empty($arEvents[$eventType]) || !is_array($arEvents[$eventType]))
		{
			$arEvents[$eventType] = [$arEvent];
			$iEventHandlerKey = 0;
		}
		else
		{
			$newEvents = [];
			$iEventHandlerKey = max(array_keys($arEvents[$eventType])) + 1;

			foreach ($arEvents[$eventType] as $key => $value)
			{
				if ($value['SORT'] > $arEvent['SORT'])
				{
					$newEvents[$iEventHandlerKey] = $arEvent;
				}

				$newEvents[$key] = $value;
			}
			$newEvents[$iEventHandlerKey] = $arEvent;
			$arEvents[$eventType] = $newEvents;
		}

		return $iEventHandlerKey;
	}

	public function addEventHandler($fromModuleId, $eventType, $callback, $includeFile = false, $sort = 100)
	{
		return $this->addEventHandlerInternal($fromModuleId, $eventType, $callback, $includeFile, $sort, 2);
	}

	/**
	 * @param $fromModuleId
	 * @param $eventType
	 * @param $callback
	 * @param bool $includeFile
	 * @param int $sort
	 * @return int
	 */
	public function addEventHandlerCompatible($fromModuleId, $eventType, $callback, $includeFile = false, $sort = 100)
	{
		return $this->addEventHandlerInternal($fromModuleId, $eventType, $callback, $includeFile, $sort, 1);
	}

	public function removeEventHandler($fromModuleId, $eventType, $iEventHandlerKey)
	{
		$fromModuleId = strtoupper($fromModuleId);
		$eventType = strtoupper($eventType);

		if (isset($this->handlers[$fromModuleId][$eventType][$iEventHandlerKey]))
		{
			unset($this->handlers[$fromModuleId][$eventType][$iEventHandlerKey]);
			return true;
		}

		return false;
	}

	public function unRegisterEventHandler($fromModuleId, $eventType, $toModuleId, $toClass = '', $toMethod = '', $toPath = '', $toMethodArg = [])
	{
		$toMethodArg = (!is_array($toMethodArg) || empty($toMethodArg) ? '' : serialize($toMethodArg));

		$con = Application::getConnection();
		$sqlHelper = $con->getSqlHelper();

		$strSql =
			"DELETE FROM b_module_to_module " .
			"WHERE FROM_MODULE_ID='" . $sqlHelper->forSql($fromModuleId) . "'" .
			"	AND MESSAGE_ID='" . $sqlHelper->forSql($eventType) . "' " .
			"	AND TO_MODULE_ID='" . $sqlHelper->forSql($toModuleId) . "' " .
			(($toClass != '') ? " AND TO_CLASS='" . $sqlHelper->forSql($toClass) . "' " : " AND (TO_CLASS='' OR TO_CLASS IS NULL) ") .
			(($toMethod != '') ? " AND TO_METHOD='" . $sqlHelper->forSql($toMethod) . "'" : " AND (TO_METHOD='' OR TO_METHOD IS NULL) ") .
			(($toPath != '' && $toPath !== 1/*controller disconnect correction*/) ? " AND TO_PATH='" . $sqlHelper->forSql($toPath) . "'" : " AND (TO_PATH='' OR TO_PATH IS NULL) ") .
			(($toMethodArg != '') ? " AND TO_METHOD_ARG='" . $sqlHelper->forSql($toMethodArg) . "'" : " AND (TO_METHOD_ARG='' OR TO_METHOD_ARG IS NULL) ");

		$con->queryExecute($strSql);

		$this->clearLoadedHandlers();
	}

	public function registerEventHandler($fromModuleId, $eventType, $toModuleId, $toClass = '', $toMethod = '', $sort = 100, $toPath = '', $toMethodArg = [])
	{
		$this->registerEventHandlerInternal($fromModuleId, $eventType, $toModuleId, $toClass, $toMethod, $sort, $toPath, $toMethodArg, 2);
	}

	public function registerEventHandlerCompatible($fromModuleId, $eventType, $toModuleId, $toClass = '', $toMethod = '', $sort = 100, $toPath = '', $toMethodArg = [])
	{
		$this->registerEventHandlerInternal($fromModuleId, $eventType, $toModuleId, $toClass, $toMethod, $sort, $toPath, $toMethodArg, 1);
	}

	protected function registerEventHandlerInternal($fromModuleId, $eventType, $toModuleId, $toClass, $toMethod, $sort, $toPath, $toMethodArg, $version)
	{
		$toMethodArg = (!is_array($toMethodArg) || empty($toMethodArg) ? '' : serialize($toMethodArg));
		$sort = intval($sort);
		$version = intval($version);

		$uniqueID = md5(mb_strtolower($fromModuleId . '.' . $eventType . '.' . $toModuleId . '.' . $toPath . '.' . $toClass . '.' . $toMethod . '.' . $toMethodArg . '.' . $version));

		$connection = Application::getConnection();
		$sqlHelper = $connection->getSqlHelper();

		$fromModuleId = $sqlHelper->forSql($fromModuleId);
		$eventType = $sqlHelper->forSql($eventType);
		$toModuleId = $sqlHelper->forSql($toModuleId);
		$toClass = $sqlHelper->forSql($toClass);
		$toMethod = $sqlHelper->forSql($toMethod);
		$toPath = $sqlHelper->forSql($toPath);
		$toMethodArg = $sqlHelper->forSql($toMethodArg);

		$fields = '(SORT, FROM_MODULE_ID, MESSAGE_ID, TO_MODULE_ID, TO_CLASS, TO_METHOD, TO_PATH, TO_METHOD_ARG, VERSION, UNIQUE_ID)';
		$values = "(" . $sort . ", '" . $fromModuleId . "', '" . $eventType . "', '" . $toModuleId . "', " . "   '" . $toClass . "', '" . $toMethod . "', '" . $toPath . "', '" . $toMethodArg . "', " . $version . ", '" . $uniqueID . "')";
		$sql = $sqlHelper->getInsertIgnore('b_module_to_module', $fields, 'VALUES ' . $values);
		$connection->queryExecute($sql);

		$this->clearLoadedHandlers();
	}

	protected function formatEventName($arEvent)
	{
		$strName = '';
		if (isset($arEvent['CALLBACK']))
		{
			if (is_array($arEvent['CALLBACK']))
			{
				$strName .= (is_object($arEvent['CALLBACK'][0]) ? get_class($arEvent['CALLBACK'][0]) : $arEvent['CALLBACK'][0]) . '::' . $arEvent['CALLBACK'][1];
			}
			elseif (is_callable($arEvent['CALLBACK']))
			{
				$strName .= 'callable';
			}
			else
			{
				$strName .= $arEvent['CALLBACK'];
			}
		}
		else
		{
			$strName .= $arEvent['TO_CLASS'] . '::' . $arEvent['TO_METHOD'];
		}
		if (!empty($arEvent['TO_MODULE_ID']))
		{
			$strName .= ' (' . $arEvent['TO_MODULE_ID'] . ')';
		}
		return $strName;
	}

	protected function loadEventHandlers()
	{
		$cache = Application::getInstance()->getManagedCache();

		if ($cache->read(3600, self::CACHE_ID, self::CACHE_ID))
		{
			$rawEvents = $cache->get(self::CACHE_ID);

			if (!is_array($rawEvents))
			{
				$rawEvents = [];
			}
		}
		else
		{
			$con = Application::getConnection();

			$rs = $con->query("
				SELECT FROM_MODULE_ID, MESSAGE_ID, SORT, TO_MODULE_ID, TO_PATH,
					TO_CLASS, TO_METHOD, TO_METHOD_ARG, VERSION
				FROM b_module_to_module m2m
					INNER JOIN b_module m ON (m2m.TO_MODULE_ID = m.ID)
				ORDER BY SORT
			");

			$rawEvents = $rs->fetchAll();

			$cache->set(self::CACHE_ID, $rawEvents);
		}

		$handlers = $this->handlers;
		$hasHandlers = !empty($this->handlers);

		foreach ($rawEvents as $ar)
		{
			$ar['TO_NAME'] = $this->formatEventName([
				'TO_MODULE_ID' => $ar['TO_MODULE_ID'],
				'TO_CLASS' => $ar['TO_CLASS'],
				'TO_METHOD' => $ar['TO_METHOD'],
			]);
			$ar['FROM_MODULE_ID'] = strtoupper($ar['FROM_MODULE_ID']);
			$ar['MESSAGE_ID'] = strtoupper($ar['MESSAGE_ID']);
			if ($ar['TO_METHOD_ARG'] != '')
			{
				$ar['TO_METHOD_ARG'] = unserialize($ar['TO_METHOD_ARG'], ['allowed_classes' => false]);
			}
			else
			{
				$ar['TO_METHOD_ARG'] = [];
			}

			$this->handlers[$ar['FROM_MODULE_ID']][$ar['MESSAGE_ID']][] = [
				'SORT' => (int)$ar['SORT'],
				'TO_MODULE_ID' => $ar['TO_MODULE_ID'],
				'TO_PATH' => $ar['TO_PATH'],
				'TO_CLASS' => $ar['TO_CLASS'],
				'TO_METHOD' => $ar['TO_METHOD'],
				'TO_METHOD_ARG' => $ar['TO_METHOD_ARG'],
				'VERSION' => $ar['VERSION'],
				'TO_NAME' => $ar['TO_NAME'],
				'FROM_DB' => true,
			];
		}

		if ($hasHandlers)
		{
			// need to re-sort because of AddEventHandler() calls (before loadEventHandlers)
			foreach (array_keys($handlers) as $moduleId)
			{
				foreach (array_keys($handlers[$moduleId]) as $event)
				{
					Collection::sortByColumn(
						$this->handlers[$moduleId][$event],
						['SORT' => SORT_ASC],
						'',
						null,
						true
					);
				}
			}
		}

		$this->isHandlersLoaded = true;
	}

	public function clearLoadedHandlers()
	{
		$managedCache = Application::getInstance()->getManagedCache();
		$managedCache->clean(self::CACHE_ID, self::CACHE_ID);

		foreach ($this->handlers as $module => $types)
		{
			foreach ($types as $type => $events)
			{
				foreach ($events as $i => $event)
				{
					if (isset($event['FROM_DB']) && $event['FROM_DB'])
					{
						unset($this->handlers[$module][$type][$i]);
					}
				}
			}
		}
		$this->isHandlersLoaded = false;
	}

	public function findEventHandlers($eventModuleId, $eventType, array $filter = null)
	{
		if (!$this->isHandlersLoaded)
		{
			$this->loadEventHandlers();
		}

		$eventModuleId = strtoupper($eventModuleId);
		$eventType = strtoupper($eventType);

		if (!isset($this->handlers[$eventModuleId]) || !isset($this->handlers[$eventModuleId][$eventType]))
		{
			return [];
		}

		$handlers = $this->handlers[$eventModuleId][$eventType];
		if (!is_array($handlers))
		{
			return [];
		}

		if (is_array($filter) && !empty($filter))
		{
			$handlersTmp = $handlers;
			$handlers = [];
			foreach ($handlersTmp as $handler)
			{
				if (isset($handler['TO_MODULE_ID']) && in_array($handler['TO_MODULE_ID'], $filter))
				{
					$handlers[] = $handler;
				}
			}
		}

		return $handlers;
	}

	public function send(Event $event)
	{
		$handlers = $this->findEventHandlers($event->getModuleId(), $event->getEventType(), $event->getFilter());
		foreach ($handlers as $handler)
		{
			$this->sendToEventHandler($handler, $event);
		}
	}

	protected function sendToEventHandler(array $handler, Event $event)
	{
		try
		{
			$result = true;
			$includeResult = true;

			$event->addDebugInfo($handler);

			if (!empty($handler['TO_MODULE_ID']) && ($handler['TO_MODULE_ID'] != 'main'))
			{
				$result = Loader::includeModule($handler['TO_MODULE_ID']);
			}
			elseif (!empty($handler['TO_PATH']))
			{
				$path = ltrim($handler['TO_PATH'], '/');
				if (($path = Loader::getLocal($path)) !== false)
				{
					$includeResult = include_once($path);
				}
			}
			elseif (!empty($handler['FULL_PATH']) && IO\File::isFileExists($handler['FULL_PATH']))
			{
				$includeResult = include_once($handler['FULL_PATH']);
			}

			$event->addDebugInfo($result);

			if ($result)
			{
				if (!empty($handler['TO_METHOD_ARG']) && is_array($handler['TO_METHOD_ARG']))
				{
					$args = $handler['TO_METHOD_ARG'];
				}
				else
				{
					$args = [];
				}

				if ($handler['VERSION'] > 1)
				{
					$args[] = $event;
				}
				else
				{
					$args = array_merge($args, array_values($event->getParameters()));
				}

				$callback = null;
				if (isset($handler['CALLBACK']))
				{
					$callback = $handler['CALLBACK'];
				}
				elseif (!empty($handler['TO_CLASS']) && !empty($handler['TO_METHOD']) && class_exists($handler['TO_CLASS']))
				{
					$callback = [$handler['TO_CLASS'], $handler['TO_METHOD']];
				}

				if ($callback != null)
				{
					$result = call_user_func_array($callback, $args);
				}
				else
				{
					$result = $includeResult;
				}

				if (($result != null) && !($result instanceof EventResult))
				{
					$result = new EventResult(EventResult::UNDEFINED, $result, $handler['TO_MODULE_ID'] ?? null);
				}

				$event->addDebugInfo($result);

				if ($result != null)
				{
					$event->addResult($result);
				}
			}
		}
		catch (\Exception $ex)
		{
			if ($event->isDebugOn())
			{
				$event->addException($ex);
			}
			else
			{
				throw $ex;
			}
		}
	}
}