Your IP : 18.217.97.131


Current Path : /var/www/www-root/data/www/monolith-realty.ru/bitrix/modules/ui/lib/entityselector/
Upload File :
Current File : /var/www/www-root/data/www/monolith-realty.ru/bitrix/modules/ui/lib/entityselector/dialog.php

<?

namespace Bitrix\UI\EntitySelector;

use Bitrix\Main\Localization\Loc;
use Bitrix\Main\ORM\Fields\ExpressionField;
use Bitrix\Main\UI\EntitySelector\EntityUsageTable;

class Dialog implements \JsonSerializable
{
	protected $id;

	/** @var ItemCollection */
	protected $itemCollection;

	/** @var Tab[] */
	protected $tabs = [];

	/** @var array<string, Entity> */
	protected $entities = [];

	/** @var RecentCollection */
	protected $recentItems;

	/** @var RecentCollection */
	protected $globalRecentItems;

	/** @var PreselectedCollection */
	protected $preselectedItems;

	/** @var string */
	protected $context;

	/** @var string */
	protected $header;

	/** @var array */
	protected $headerOptions;

	/** @var string */
	protected $footer;

	/** @var array */
	protected $footerOptions;

	/** @var boolean */
	protected $clearUnavailableItems = false;

	/** @var int */
	protected int $recentItemsLimit = 50;

	protected const MAX_RECENT_ITEMS_LIMIT = 50;

	public function __construct(array $options)
	{
		if (isset($options['entities']) && is_array($options['entities']))
		{
			foreach ($options['entities'] as $entityOptions)
			{
				if (is_array($entityOptions))
				{
					$entity = Entity::create($entityOptions);
					if ($entity)
					{
						$this->addEntity($entity);
					}
				}
			}
		}

		if (isset($options['id']) && is_string($options['id']))
		{
			$this->id = $options['id'];
		}

		if (isset($options['context']) && is_string($options['context']) && strlen($options['context']) > 0)
		{
			$this->context = $options['context'];
		}

		if (isset($options['clearUnavailableItems']) && is_bool($options['clearUnavailableItems']))
		{
			$this->clearUnavailableItems = $options['clearUnavailableItems'];
		}

		$this->itemCollection = new ItemCollection();
		$this->recentItems = new RecentCollection();
		$this->globalRecentItems = new RecentCollection();
		$this->preselectedItems = new PreselectedCollection();

		if (isset($options['preselectedItems']) && is_array($options['preselectedItems']))
		{
			$this->setPreselectedItems($options['preselectedItems']);
		}

		if (isset($options['recentItemsLimit']) && is_int($options['recentItemsLimit']))
		{
			$this->recentItemsLimit = max(1, min($options['recentItemsLimit'], static::MAX_RECENT_ITEMS_LIMIT));
		}
	}

	public function getId(): ?string
	{
		return $this->id;
	}

	public function getContext(): ?string
	{
		return $this->context;
	}

	public function getItemCollection(): ItemCollection
	{
		return $this->itemCollection;
	}

	public function getCurrentUserId(): int
	{
		return is_object($GLOBALS['USER']) ? $GLOBALS['USER']->getId() : 0;
	}

	public function addItem(Item $item)
	{
		$success = $this->getItemCollection()->add($item);
		if ($success)
		{
			$this->handleItemAdd($item);
		}
	}

	public function addItems(array $items)
	{
		foreach ($items as $item)
		{
			$this->addItem($item);
		}
	}

	public function addRecentItem(Item $item)
	{
		$this->addItem($item);

		$recentItem = $this->getRecentItems()->getByItem($item);
		if (!$recentItem && $item->isAvailableInRecentTab())
		{
			$this->getRecentItems()->add(
				new RecentItem(
					[
						'id' => $item->getId(),
						'entityId' => $item->getEntityId(),
						'loaded' => true,
					]
				)
			);
		}
	}

	public function addRecentItems(array $items)
	{
		foreach ($items as $item)
		{
			$this->addRecentItem($item);
		}
	}

	public function setHeader(string $header, array $options = [])
	{
		if (strlen($header) > 0)
		{
			$this->header = $header;
			$this->headerOptions = $options;
		}
	}

	public function getHeader(): ?string
	{
		return $this->header;
	}

	public function getHeaderOptions(): ?array
	{
		return $this->headerOptions;
	}

	public function setFooter(string $footer, array $options = [])
	{
		if (strlen($footer) > 0)
		{
			$this->footer = $footer;
			$this->footerOptions = $options;
		}
	}

	public function getFooter(): ?string
	{
		return $this->footer;
	}

	public function getFooterOptions(): ?array
	{
		return $this->footerOptions;
	}

	public function handleItemAdd(Item $item): void
	{
		$item->setDialog($this);

		$recentItem = $this->getRecentItems()->getByItem($item);
		if ($recentItem)
		{
			$recentItem->setLoaded(true);
			$recentItem->setAvailable($item->isAvailableInRecentTab());
			$item->setContextSort($recentItem->getLastUseDate());
		}

		$globalRecentItem = $this->getGlobalRecentItems()->getByItem($item);
		if ($globalRecentItem)
		{
			$globalRecentItem->setLoaded(true);
			$item->setGlobalSort($globalRecentItem->getLastUseDate());
		}

		$preselectedItem = $this->getPreselectedCollection()->getByItem($item);
		if ($preselectedItem && !$preselectedItem->getItem())
		{
			$preselectedItem->setItem($item);
		}

		foreach ($item->getChildren() as $childItem)
		{
			$this->handleItemAdd($childItem);
		}
	}

	public function getRecentItems(): RecentCollection
	{
		return $this->recentItems;
	}

	public function getGlobalRecentItems(): RecentCollection
	{
		return $this->globalRecentItems;
	}

	public function addTab(Tab $tab): void
	{
		if (!empty($tab->getId()))
		{
			$this->tabs[$tab->getId()] = $tab;
		}
	}

	/**
	 * @return Tab[]
	 */
	public function getTabs(): array
	{
		return $this->tabs;
	}

	public function getTab(string $tabId): ?Tab
	{
		return $this->tabs[$tabId] ?? null;
	}

	public function addEntity(Entity $entity)
	{
		if (!empty($entity->getId()))
		{
			$this->entities[$entity->getId()] = $entity;
		}
	}

	/**
	 * @return Entity[]
	 */
	public function getEntities(): array
	{
		return $this->entities;
	}

	/**
	 * @param string $entityId
	 *
	 * @return Entity
	 */
	public function getEntity(string $entityId): ?Entity
	{
		return $this->entities[$entityId] ?? null;
	}

	/**
	 * @internal
	 */
	public function load(): void
	{
		$entities = [];
		foreach ($this->getEntities() as $entity)
		{
			if ($entity->hasDynamicLoad())
			{
				$entities[] = $entity->getId();
			}
		}

		if (empty($entities))
		{
			return;
		}

		$this->fillRecentItems($entities);
		if ($this->getContext() !== null)
		{
			$this->fillGlobalRecentItems($entities);
		}

		foreach ($entities as $entityId)
		{
			$this->getEntity($entityId)->getProvider()->fillDialog($this);
		}

		$this->loadRecentItems();
		$this->loadPreselectedItems();
	}

	/**
	 * @internal
	 * @param SearchQuery $searchQuery
	 */
	public function doSearch(SearchQuery $searchQuery)
	{
		if (empty($searchQuery->getQueryWords()))
		{
			return;
		}

		$entities = [];
		foreach ($this->getEntities() as $entity)
		{
			$hasDynamicSearch =
				$entity->isSearchable() &&
				($entity->hasDynamicSearch() || $searchQuery->hasDynamicSearchEntity($entity->getId()))
			;

			if ($hasDynamicSearch)
			{
				$entities[] = $entity->getId();
			}
		}

		if ($this->getContext() !== null)
		{
			$this->fillRecentItems($entities);
		}

		$this->fillGlobalRecentItems($entities);
		foreach ($entities as $entityId)
		{
			$this->getEntity($entityId)->getProvider()->doSearch($searchQuery, $this);
		}
	}

	/**
	 * @internal
	 * @param Item $parentItem
	 */
	public function getChildren(Item $parentItem)
	{
		$entities = [];
		foreach ($this->getEntities() as $entity)
		{
			if ($entity->hasDynamicLoad())
			{
				$entities[] = $entity->getId();
			}
		}

		$entity = $this->getEntity($parentItem->getEntityId());
		if ($entity && $entity->hasDynamicLoad())
		{
			$this->fillGlobalRecentItems($entities);
			$entity->getProvider()->getChildren($parentItem, $this);
		}
	}

	public function setPreselectedItems(array $preselectedItems)
	{
		$this->preselectedItems->load($preselectedItems);
	}

	public function getPreselectedCollection(): PreselectedCollection
	{
		return $this->preselectedItems;
	}

	/**
	 * @internal
	 */
	public function loadPreselectedItems($preselectedMode = true): void
	{
		if ($this->getPreselectedCollection()->count() < 1)
		{
			return;
		}

		foreach ($this->getPreselectedCollection()->getItems() as $entityId => $preselectedItems)
		{
			$unloadedIds = [];
			$entity = $this->getEntity($entityId) ?? Entity::create(['id' => $entityId]);
			foreach ($preselectedItems as $preselectedItem)
			{
				// Entity doesn't exist
				if (!$entity && $preselectedMode)
				{
					$this->addItem(self::createHiddenItem($preselectedItem->getId(), $entityId));
				}
				else if (!$preselectedItem->isLoaded())
				{
					$unloadedIds[] = $preselectedItem->getId();
				}
			}

			if ($entity && !empty($unloadedIds))
			{
				$availableItems = [];
				$items =
					$preselectedMode
					? $entity->getProvider()->getPreselectedItems($unloadedIds)
					: $entity->getProvider()->getItems($unloadedIds)
				;

				foreach ($items as $item)
				{
					$availableItems[$item->getId()] = $item;
				}

				foreach ($unloadedIds as $unloadedId)
				{
					$item = $availableItems[$unloadedId] ?? null;
					if ($item)
					{
						$this->addItem($item);
					}
					else if ($preselectedMode)
					{
						$this->addItem(self::createHiddenItem($unloadedId, $entityId));
					}
				}
			}
		}
	}

	public function shouldClearUnavailableItems(): bool
	{
		return $this->clearUnavailableItems;
	}

	public static function createHiddenItem($id, $entityId): Item
	{
		return new Item([
			'id' => $id,
			'entityId' => $entityId,
			'title' => Loc::getMessage("UI_SELECTOR_HIDDEN_ITEM_TITLE"),
			'hidden' => true,
			'deselectable' => false,
			'searchable' => false,
			'saveable' => false,
			'link' => '',
			'avatar' => '',
			'availableInRecentTab' => false
		]);
	}

	/**
	 * @deprecated
	 * @see Dialog::getPreselectedCollection()
	 */
	public static function getSelectedItems(array $ids, array $options = []): ItemCollection
	{
		return self::getItemsInternal($ids, $options, true);
	}

	public static function getPreselectedItems(array $ids, array $options = []): ItemCollection
	{
		return self::getItemsInternal($ids, $options, true);
	}

	public static function getItems(array $ids, array $options = []): ItemCollection
	{
		return self::getItemsInternal($ids, $options, false);
	}

	private static function getItemsInternal(array $ids, array $options = [], $preselectedMode = true): ItemCollection
	{
		$isAssocArray = array_keys($options) !== range(0, count($options) - 1);
		$dialogOptions = $isAssocArray ? $options : ['entities' => $options];

		$dialog = new self($dialogOptions);
		$dialog->setPreselectedItems($ids);
		$dialog->loadPreselectedItems($preselectedMode);
		$dialog->applyFilters();

		return $dialog->getItemCollection();
	}

	public function saveRecentItems(array $recentItems)
	{
		if ($this->getContext() === null)
		{
			return;
		}

		foreach ($recentItems as $recentItemOptions)
		{
			if (!is_array($recentItemOptions))
			{
				continue;
			}

			$recentItem = new Item($recentItemOptions);
			$entity = $this->getEntity($recentItem->getEntityId());

			if ($entity)
			{
				$entity->getProvider()->handleBeforeItemSave($recentItem);
				if ($recentItem->isSaveable())
				{
					EntityUsageTable::merge([
						'USER_ID' => $GLOBALS['USER']->getId(),
						'CONTEXT' => $this->getContext(),
						'ENTITY_ID' => $recentItem->getEntityId(),
						'ITEM_ID' => $recentItem->getId()
					]);
				}
			}
		}
	}

	private function fillRecentItems(array $entities)
	{
		if (empty($entities))
		{
			return;
		}

		if ($this->getContext() === null)
		{
			$usages = $this->getGlobalUsages($entities, $this->recentItemsLimit);
			while ($usage = $usages->fetch())
			{
				$this->getRecentItems()->add(
					new RecentItem(
						[
							'id' => $usage['ITEM_ID'],
							'entityId' => $usage['ENTITY_ID'],
							'lastUseDate' => $usage['MAX_LAST_USE_DATE']->getTimestamp()
						]
					)
				);
			}
		}
		else
		{
			$usages = $this->getContextUsages($entities, $this->recentItemsLimit);
			foreach ($usages as $usage)
			{
				$this->getRecentItems()->add(
					new RecentItem(
						[
							'id' => $usage->getItemId(),
							'entityId' => $usage->getEntityId(),
							'lastUseDate' => $usage->getLastUseDate()->getTimestamp()
						]
					)
				);
			}
		}
	}

	private function fillGlobalRecentItems(array $entities)
	{
		if (empty($entities))
		{
			return;
		}

		$usages = $this->getGlobalUsages($entities);
		while ($usage = $usages->fetch())
		{
			$this->getGlobalRecentItems()->add(
				new RecentItem(
					[
						'id' => $usage['ITEM_ID'],
						'entityId' => $usage['ENTITY_ID'],
						'lastUseDate' => $usage['MAX_LAST_USE_DATE']->getTimestamp()
					]
				)
			);
		}
	}

	private function getContextUsages(array $entities, int $limit = 50)
	{
		return EntityUsageTable::getList(
			[
				'select' => ['*'],
				'filter' => [
					'=USER_ID' => $this->getCurrentUserId(),
					'=CONTEXT' => $this->getContext(),
					'@ENTITY_ID' => $entities
				],
				'limit' => $limit,
				'order' => [
					'LAST_USE_DATE' => 'DESC'
				]
			]
		)->fetchCollection();
	}

	private function getGlobalUsages(array $entities, int $limit = 200)
	{
		$query = EntityUsageTable::query();
		$query->setSelect(['ENTITY_ID', 'ITEM_ID', 'MAX_LAST_USE_DATE']);
		$query->setGroup(['ENTITY_ID', 'ITEM_ID']);
		$query->where('USER_ID', $this->getCurrentUserId());
		$query->whereIn('ENTITY_ID', $entities);

		if ($this->getContext() !== null)
		{
			$query->whereNot('CONTEXT', $this->getContext());
		}

		$query->registerRuntimeField(new ExpressionField('MAX_LAST_USE_DATE', 'MAX(%s)', 'LAST_USE_DATE'));
		$query->setOrder(['MAX_LAST_USE_DATE' => 'desc']);
		$query->setLimit($limit);

		return $query->exec();
	}

	private function loadRecentItems()
	{
		foreach ($this->getEntities() as $entity)
		{
			$unloadedIds = [];
			$unavailableIds = [];
			$recentItems = $this->getRecentItems()->getEntityItems($entity->getId());
			foreach ($recentItems as $recentItem)
			{
				if (!$recentItem->isAvailable())
				{
					$unavailableIds[] = $recentItem->getId();
				}
				else if (!$recentItem->isLoaded())
				{
					$unloadedIds[] = $recentItem->getId();
				}
			}

			if (!empty($unloadedIds))
			{
				$availableItems = [];
				$items = $entity->getProvider()->getItems($unloadedIds);
				foreach ($items as $item)
				{
					if ($item instanceof Item)
					{
						$availableItems[$item->getId()] = $item;
					}
				}

				foreach ($unloadedIds as $unloadedId)
				{
					$item = $availableItems[$unloadedId] ?? null;
					if ($item && $item->isAvailableInRecentTab())
					{
						$this->addRecentItem($item);
					}
					else
					{
						$unavailableIds[] = $unloadedId;
					}
				}
			}

			if ($this->getContext() !== null && $this->shouldClearUnavailableItems() && !empty($unavailableIds))
			{
				EntityUsageTable::deleteByFilter([
					'=USER_ID' => $this->getCurrentUserId(),
					'=CONTEXT' => $this->getContext(),
					'=ENTITY_ID' => $entity->getId(),
					'@ITEM_ID' => $unavailableIds
				]);
			}
		}
	}

	public function applyFilters(): void
	{
		foreach ($this->getEntities() as $entity)
		{
			$items = $this->getItemCollection()->getEntityItems($entity->getId());
			if (empty($items))
			{
				continue;
			}

			$filters = $entity->getFilters();
			foreach ($filters as $filter)
			{
				$filter->apply($items, $this);
			}
		}
	}

	/**
	 * @internal
	 */
	public function getAjaxData(): array
	{
		$this->applyFilters();

		return $this->jsonSerialize();
	}

	public function jsonSerialize()
	{
		$json = [
			'id' => $this->getId(),
			'items' => $this->getItemCollection(),
			'tabs' => array_values($this->getTabs()),
			'entities' => array_values($this->getEntities()),
		];

		if ($this->getHeader())
		{
			$json['header'] = $this->getHeader();
			$json['headerOptions'] = $this->getHeaderOptions();
		}

		if ($this->getFooter())
		{
			$json['footer'] = $this->getFooter();
			$json['footerOptions'] = $this->getFooterOptions();
		}

		if ($this->getRecentItems()->count() > 0)
		{
			$json['recentItems'] = $this->getRecentItems();
		}

		if ($this->getPreselectedCollection()->count() > 0)
		{
			$json['preselectedItems'] = $this->getPreselectedCollection();
		}

		return $json;
	}
}