Your IP : 18.216.176.63


Current Path : /home/bitrix/ext_www/crm.klimatlend.ua/bitrix/modules/crm/lib/volume/
Upload File :
Current File : /home/bitrix/ext_www/crm.klimatlend.ua/bitrix/modules/crm/lib/volume/base.php

<?php

namespace Bitrix\Crm\Volume;

use Bitrix\Crm;
use Bitrix\Crm\Volume;
use Bitrix\Main;
use Bitrix\Main\ORM;
use Bitrix\Main\Error;
use Bitrix\Disk;

/**
 * @implements \Bitrix\Crm\Volume\IVolumeClear
 * @implements \Bitrix\Crm\Volume\IVolumeClearFile
 * @implements \Bitrix\Crm\Volume\IVolumeClearActivity
 * @implements \Bitrix\Crm\Volume\IVolumeClearEvents
 */
abstract class Base
	implements Volume\IVolumeIndicator, Main\Errorable
{
	use Main\ErrorableImplementation;

	/** @var array Indicator list available in library. */
	protected static $indicatorTypeList = array();

	/** @var array */
	protected static $tablesInformation = array();

	/** @var array */
	protected static $userFieldInformation = array();

	/** @var array */
	protected static $maxIdRangeCache = array();

	/** @var array */
	protected $tableList = array();

	/** @var int */
	protected $ownerId = 0;

	/** @var int */
	protected $entitySize = 0;

	/** @var int */
	protected $entityCount = 0;

	/** @var int */
	protected $fileSize = 0;

	/** @var int */
	protected $fileCount = 0;

	/** @var int */
	protected $diskSize = 0;

	/** @var int */
	protected $diskCount = 0;

	/** @var int */
	protected $eventSize = 0;

	/** @var int */
	protected $eventCount = 0;

	/** @var int */
	protected $activitySize = 0;

	/** @var int */
	protected $activityCount = 0;

	/** @var array */
	protected $filter = array();

	/** @var float seconds */
	private $timeLimit = -1;

	/** @var float seconds */
	private $startTime = -1;

	/** @var boolean */
	private $timeLimitReached = false;

	/** @var int */
	private $processOffset = 0;

	/** @var int */
	private $droppedCount = 0;

	/** @var int */
	private $droppedFileCount = 0;

	/** @var int */
	private $failCount = 0;


	const ERROR_PERMISSION_DENIED = 'CRM_PERMISSION_DENIED';
	const ERROR_DELETION_FAILED = 'CRM_DELETION_FAILED';

	// limit maximum number selected entity
	const MAX_ENTITY_PER_INTERACTION = 1000;

	// limit maximum number selected files
	const MAX_FILE_PER_INTERACTION = 1000;

	/** @var array */
	protected static $filterFieldAlias = array();

	/** @var array */
	protected static $entityList = array();

	/** @var array */
	protected $dateSplitPeriod = array(1, 'months');

	/** @var false */
	protected $collectEntityRowSize = false;


	/**
	 * @return string the fully qualified name of this class.
	 */
	final public static function className()
	{
		return get_called_class();
	}

	/**
	 * @return string The short indicator name of this class.
	 */
	final public static function getIndicatorId()
	{
		return str_replace(array(__NAMESPACE__. '\\', '\\'), array('', '_'), static::className());
	}

	/**
	 * Checks if module Voximplant is available.
	 * @param string $moduleId Mmodule Id.
	 * @return boolean
	 */
	protected static function isModuleAvailable($moduleId)
	{
		static $available = array();
		if (!isset($available[$moduleId]))
		{
			$available[$moduleId] =
				Main\ModuleManager::isModuleInstalled($moduleId) &&
				Main\Loader::includeModule($moduleId);
		}

		return $available[$moduleId];
	}

	/**
	 * Gets task owner.
	 * @return \CUser
	 */
	protected function getUser()
	{
		/** @global \CUser $USER */
		global $USER;
		return $USER;
	}

	/**
	 * Checks data base structure and vipe old data.
	 * @return void
	 */
	protected function checkTemporally()
	{
		if (Crm\VolumeTmpTable::checkTemporally())
		{
			Crm\VolumeTmpTable::deleteBatch(array(
				'=INDICATOR_TYPE' => static::getIndicatorId(),
				'=OWNER_ID' => $this->getOwner(),
			));
		}
		else
		{
			Crm\VolumeTmpTable::createTemporally();
		}
	}

	/**
	 * Copy data from temporally table.
	 * @return void
	 */
	protected function copyTemporallyData()
	{
		$connection = Main\Application::getConnection();

		$keyFields = array(
			'INDICATOR_TYPE',
			'OWNER_ID',
			'DATE_CREATE',
			'STAGE_SEMANTIC_ID',
		);
		$updateFields = array(
			'ENTITY_SIZE',
			'ENTITY_COUNT',
			'FILE_SIZE',
			'FILE_COUNT',
			'DISK_SIZE',
			'DISK_COUNT',
			'EVENT_SIZE',
			'EVENT_COUNT',
			'ACTIVITY_SIZE',
			'ACTIVITY_COUNT',
		);

		$target = $connection->getSqlHelper()->quote(Crm\VolumeTable::getTableName());

		$query = Crm\VolumeTmpTable::query();
		$query
			->setSelect(array_merge($keyFields, $updateFields))
			->setFilter(array(
				'=INDICATOR_TYPE' => static::getIndicatorId(),
				'=OWNER_ID' => $this->getOwner(),
			));
		$sourceSql = $query->getQuery();

		$columns = $update = array();
		foreach ($keyFields as $field)
		{
			$field = $connection->getSqlHelper()->quote($field);
			$columns[] = $field;
		}
		foreach ($updateFields as $field)
		{
			$field = $connection->getSqlHelper()->quote($field);
			$columns[] = $field;
			$update[] = "{$target}.{$field} = {$target}.{$field} + VALUES({$field})";
		}

		$sqlIns =
			"INSERT INTO {$target} (". implode(', ', $columns). ") {$sourceSql} ".
			"ON DUPLICATE KEY UPDATE ". implode(', ', $update)
		;

		$connection->queryExecute($sqlIns);
	}

	/**
	 * Runs measure test.
	 * @return self
	 */
	public function measure()
	{
		$this
			->purify()
			->measureEntity()
			->measureFiles();

		if ($this instanceof Volume\IVolumeClearActivity || is_callable([$this, 'measureActivity']))
		{
			$this->measureActivity();
		}

		if ($this instanceof Volume\IVolumeClearEvent || is_callable([$this, 'measureEvent']))
		{
			$this->measureEvent();
		}

		return $this;
	}


	/**
	 * Deletes objects selecting by filter.
	 * @return self
	 */
	public function purify()
	{
		Crm\VolumeTable::deleteBatch(array(
			'=INDICATOR_TYPE' => static::getIndicatorId(),
			'=OWNER_ID' => $this->getOwner(),
		));

		return $this;
	}

	/**
	 * Returns total amount of objects selecting by filter.
	 * @return double[]
	 */
	public function loadTotals()
	{
		$query = Crm\VolumeTable::query();

		$filter = array(
			'=INDICATOR_TYPE' => static::getIndicatorId(),
			'=OWNER_ID' => $this->getOwner(),
			'=AGENT_LOCK' => Volume\Cleaner::TASK_STATUS_NONE,
		);

		$filterExt = $this->getFilter();
		foreach ($filterExt as $key => $value)
		{
			if (empty($value))
			{
				continue;
			}
			$filter[$key] = $value;
		}

		$query
			->setFilter($filter)
			->registerRuntimeField(new ORM\Fields\ExpressionField('CNT', 'COUNT(*)'))
			->registerRuntimeField(new ORM\Fields\ExpressionField('ENTITY_SIZE', 'SUM(ENTITY_SIZE)'))
			->registerRuntimeField(new ORM\Fields\ExpressionField('ENTITY_COUNT', 'SUM(ENTITY_COUNT)'))
			->registerRuntimeField(new ORM\Fields\ExpressionField('FILE_SIZE', 'SUM(FILE_SIZE)'))
			->registerRuntimeField(new ORM\Fields\ExpressionField('FILE_COUNT', 'SUM(FILE_COUNT)'))
			->registerRuntimeField(new ORM\Fields\ExpressionField('DISK_SIZE', 'SUM(DISK_SIZE)'))
			->registerRuntimeField(new ORM\Fields\ExpressionField('DISK_COUNT', 'SUM(DISK_COUNT)'))
			->registerRuntimeField(new ORM\Fields\ExpressionField('EVENT_SIZE', 'SUM(EVENT_SIZE)'))
			->registerRuntimeField(new ORM\Fields\ExpressionField('EVENT_COUNT', 'SUM(EVENT_COUNT)'))
			->registerRuntimeField(new ORM\Fields\ExpressionField('ACTIVITY_SIZE', 'SUM(ACTIVITY_SIZE)'))
			->registerRuntimeField(new ORM\Fields\ExpressionField('ACTIVITY_COUNT', 'SUM(ACTIVITY_COUNT)'))
			->addSelect('CNT')
			->addSelect('ENTITY_SIZE')
			->addSelect('ENTITY_COUNT')
			->addSelect('FILE_SIZE')
			->addSelect('FILE_COUNT')
			->addSelect('DISK_SIZE')
			->addSelect('DISK_COUNT')
			->addSelect('EVENT_SIZE')
			->addSelect('EVENT_COUNT')
			->addSelect('ACTIVITY_SIZE')
			->addSelect('ACTIVITY_COUNT')
		;

		$res = $query->exec();
		if ($row = $res->fetch())
		{
			$this->entitySize = (double)$row['ENTITY_SIZE'];
			$this->entityCount = (double)$row['ENTITY_COUNT'];
			$this->fileSize = (double)$row['FILE_SIZE'];
			$this->fileCount = (double)$row['FILE_COUNT'];
			$this->diskSize = (double)$row['DISK_SIZE'];
			$this->diskCount = (double)$row['DISK_COUNT'];
			$this->eventSize = (double)$row['EVENT_SIZE'];
			$this->eventCount = (double)$row['EVENT_COUNT'];
			$this->activitySize = (double)$row['ACTIVITY_SIZE'];
			$this->activityCount = (double)$row['ACTIVITY_COUNT'];
		}

		return $row;
	}


	/**
	 * Runs measure test for tables.
	 * @return self
	 */
	public function measureEntity()
	{
		self::loadTablesInformation();

		// database size
		$this->entitySize = 0;
		$this->entityCount = 0;

		if (count($this->getFilter()) > 0)
		{
			if (!$this->canBeFiltered())
			{
				// nonfiterable
				return $this;
			}

			$entityList = static::getEntityList();
			foreach ($entityList as $classEntity)
			{
				/**
				 * @var ORM\Data\DataManager $classEntity
				 */
				$query = $classEntity::query();
				$entity = $classEntity::getEntity();

				// filter
				if ($this->prepareEntityFilter($query, $entity))
				{
					$query
						->registerRuntimeField(new ORM\Fields\ExpressionField('CNT', 'COUNT(*)'))
						->addSelect('CNT');

					$res = $query->exec();
					if ($row = $res->fetch())
					{
						$table = $classEntity::getTableName();
						$avgTableRowLength = (double)self::$tablesInformation[$table]['AVG_SIZE'];
						$this->entitySize += $avgTableRowLength * (double)$row['CNT'];
					}
				}
			}
		}
		else
		{
			$tableList = $this->getTableList();
			if (count($tableList) > 0)
			{
				$this->entityCount = self::$tablesInformation[$tableList[0]]['TABLE_ROWS'];

				foreach ($tableList as $tableName)
				{
					$this->entitySize += (double)self::$tablesInformation[$tableName]['SIZE'];
				}
			}
		}

		$connection = Main\Application::getConnection();

		$data = array(
			'INDICATOR_TYPE' => static::getIndicatorId(),
			'OWNER_ID' => $this->getOwner(),
			'ENTITY_COUNT' => ($this->entityCount ? : 0),
			'ENTITY_SIZE' => ($this->entitySize ? : 0),
			'STAGE_SEMANTIC_ID' => '-',
		);

		$insert = $connection->getSqlHelper()->prepareInsert(Crm\VolumeTable::getTableName(), $data);

		$querySql = 'INSERT INTO '.$connection->getSqlHelper()->quote(Crm\VolumeTable::getTableName()). '('. $insert[0]. ') VALUES ('. $insert[1]. ')';

		Crm\VolumeTable::deleteBatch(array(
			'=INDICATOR_TYPE' => static::getIndicatorId(),
			'=OWNER_ID' => $this->getOwner(),
			'=AGENT_LOCK' => Volume\Cleaner::TASK_STATUS_NONE,
		));

		$connection->queryExecute($querySql);

		return $this;
	}

	/**
	 * Setups filter params into query.
	 * @param ORM\Query\Query $query Query.
	 * @param ORM\Entity $entity Use only this entity fields.
	 * @param string $entityAlias Table alias.
	 * @return boolean
	 */
	public function prepareEntityFilter(ORM\Query\Query $query, ORM\Entity $entity, $entityAlias = '')
	{
		$isAllValueApplied = true;

		// Samples naming:
		// EVENT.DATE_CREATE
		// DealCategory.CREATED_DATE
		// InvoiceStatusHistory.CREATED_TIME
		// ActivityTable.CREATED
		$dateCreatedAlias = array('DATE_CREATE', 'CREATED_TIME', 'CREATED_DATE', 'CREATED');

		// Samples naming:
		// DEAL.STAGE_SEMANTIC_ID
		// LEAD.STATUS_SEMANTIC_ID
		$stageSemanticAlias = array('STAGE_SEMANTIC_ID', 'STATUS_SEMANTIC_ID');

		$filter = $this->getFilter();

		foreach ($filter as $key => $value)
		{
			if (empty($value))
			{
				continue;
			}
			$key0 = trim($key, '<>!=@');
			if (mb_strlen($key) > mb_strlen($key0))
			{
				$operator = mb_substr($key, 0, mb_strlen($key) - mb_strlen($key0));
			}
			else
			{
				$operator = '=';
				if (is_array($value))
				{
					$operator = 'in';
				}
			}
			switch ($key0)
			{
				case 'STAGE_SEMANTIC_ID':
					$isApplied = false;
					foreach ($stageSemanticAlias as $aliasStageSemantic)
					{
						if ($entity->hasField($aliasStageSemantic))
						{
							$isApplied = true;
							if ($entityAlias !== '')
							{
								$aliasStageSemantic = "{$entityAlias}.{$aliasStageSemantic}";
							}
							$query->where($aliasStageSemantic, $operator, $value);
							break;
						}
					}
					if (!$isApplied)
					{
						$isAllValueApplied = false;
					}
					break;

				case 'DATE_CREATE':
					$isApplied = false;
					foreach ($dateCreatedAlias as $aliasDateCreated)
					{
						if ($entity->hasField($aliasDateCreated))
						{
							if ($entity->getField($aliasDateCreated) instanceof ORM\Fields\DatetimeField)
							{
								$isApplied = true;
								if ($entityAlias !== '')
								{
									$aliasDateCreated = "{$entityAlias}.{$aliasDateCreated}";
								}
								$query->where($aliasDateCreated, $operator, $value);
								break;
							}
						}
					}
					if (!$isApplied)
					{
						$isAllValueApplied = false;
					}
					break;


				default:
					$isApplied = $this->addFilterEntityField($query, $entity, $key, $value);
					if (!$isApplied)
					{
						$isAllValueApplied = false;
					}

			}
		}

		return $isAllValueApplied;
	}

	/**
	 * Setups filter params into query.
	 * @param ORM\Query\Query $query Query.
	 * @param ORM\Entity $entity Use only this entity fields.
	 * @param string $key Key name.
	 * @param mixed $value Value.
	 * @return boolean
	 */
	protected function addFilterEntityField(ORM\Query\Query $query, ORM\Entity $entity, $key, $value)
	{
		$isAllValueApplied = false;

		$key0 = trim($key, '<>!=');
		if (mb_strpos($key0, '.') !== false)
		{
			$key0 = mb_substr($key0, 0, mb_strpos($key0, '.'));
			if ($entity->hasField($key0))
			{
				$query->addFilter($key, $value);
				$isAllValueApplied = true;
			}
		}
		elseif ($entity->hasField($key0))
		{
			$query->addFilter($key, $value);
			$isAllValueApplied = true;
		}

		return $isAllValueApplied;
	}

	/**
	 * Runs measure test for files.
	 *
	 * @return self
	 * @throws Main\Db\SqlQueryException
	 * @throws Main\LoaderException
	 */
	public function measureFiles()
	{
		$this->fileSize = 0;
		$this->fileCount = 0;
		$this->diskSize = 0;
		$this->diskCount = 0;

		if (count($this->getFilter()) > 0 && !$this->canBeFiltered())
		{
			// nonfiterable
			return $this;
		}

		self::loadTablesInformation();

		$connection = Main\Application::getConnection();

		$source = array();

		$entityList = static::getEntityList();
		foreach ($entityList as $entityClass)
		{
			$entityUserFieldList = $this->getUserTypeFieldList($entityClass);
			/** @var array $userField */
			foreach ($entityUserFieldList as $userField)
			{
				$sql = $this->prepareUserFieldQuery($entityClass, $userField);
				if ($sql !== '')
				{
					$source[] = $sql;
				}
			}

			$diskConnector = static::getDiskConnector($entityClass);
			if ($diskConnector !== null)
			{
				$sql = $this->prepareDiskAttachedQuery($entityClass, $diskConnector);
				if ($sql !== '')
				{
					$source[] = $sql;
				}
			}

			$liveFeedConnector = static::getLiveFeedConnector($entityClass);
			if ($liveFeedConnector !== null)
			{
				$sql = $this->prepareLiveFeedQuery($entityClass, $liveFeedConnector);
				if ($sql !== '')
				{
					$source[] = $sql;
				}
			}
		}
		if (count($source) > 0)
		{

			$querySql = "
				SELECT 
					SUM(src.FILE_SIZE) as FILE_SIZE,
					SUM(src.FILE_COUNT) as FILE_COUNT,
					SUM(src.DISK_SIZE) as DISK_SIZE,
					SUM(src.DISK_COUNT) as DISK_COUNT
				FROM 
				(
					(". implode(' ) UNION ( ', $source). ")
				) src
			";

			$result = $connection->query($querySql);
			if ($row = $result->fetch())
			{
				$this->fileSize += (double)$row['FILE_SIZE'];
				$this->fileCount += (double)$row['FILE_COUNT'];
				$this->diskSize += (double)$row['DISK_SIZE'];
				$this->diskCount += (double)$row['DISK_COUNT'];
			}
		}


		$data = array(
			'INDICATOR_TYPE' => static::getIndicatorId(),
			'OWNER_ID' => $this->getOwner(),
			'STAGE_SEMANTIC_ID' => '-',
			'FILE_SIZE' => $this->fileSize,
			'FILE_COUNT' => $this->fileCount,
			'DISK_SIZE' => $this->diskSize,
			'DISK_COUNT' => $this->diskCount,
		);

		$insert = $connection->getSqlHelper()->prepareInsert(Crm\VolumeTable::getTableName(), $data);

		$querySql = 'INSERT INTO '.$connection->getSqlHelper()->quote(Crm\VolumeTable::getTableName()). '('. $insert[0]. ') VALUES ('. $insert[1]. ')';

		Crm\VolumeTable::deleteBatch(array(
			'=INDICATOR_TYPE' => static::getIndicatorId(),
			'=OWNER_ID' => $this->getOwner(),
			'=AGENT_LOCK' => Volume\Cleaner::TASK_STATUS_NONE,
		));

		$connection->queryExecute($querySql);

		return $this;
	}




	/**
	 * Tells that is is participated in the total volume.
	 * @return boolean
	 */
	public function isParticipatedTotal()
	{
		return true;
	}

	/**
	 * Returns query.
	 *
	 * @return ORM\Query\Query
	 */
	public function prepareQuery()
	{
		return null;
	}

	/**
	 * Setups filter params into query.
	 * @param ORM\Query\Query $query Query.
	 * @return boolean
	 */
	public function prepareFilter(ORM\Query\Query $query)
	{
		$filter = $this->getFilter();

		if (count($filter) > 0 && !$this->canBeFiltered())
		{
			return false;
		}

		$isAllValueApplied = true;
		foreach ($filter as $key => $value)
		{
			if (empty($value))
			{
				continue;
			}
			$key0 = trim($key, '<>!=');
			if (isset(static::$filterFieldAlias[$key0]))
			{
				$key1 = str_replace($key0, static::$filterFieldAlias[$key0], $key);
				if (is_array($value))
				{
					$query->where($key1, 'in', $value);
				}
				else
				{
					$query->addFilter($key1, $value);
				}
			}
			else
			{

				$isAllValueApplied = $isAllValueApplied && $this->addFilterEntityField($query, $query->getEntity(), $key, $value);
			}
		}

		return $isAllValueApplied;
	}


	/**
	 * Returns indicator list available in library.
	 * @return array
	 */
	final public static function getListIndicator()
	{
		if (empty(self::$indicatorTypeList))
		{
			self::loadListIndicator();
		}

		return self::$indicatorTypeList;
	}

	/**
	 * Recursively looks for indicator class files available in library.
	 * @param string $libraryPath Sub folder name inside current library.
	 * @return void
	 */
	private static function loadListIndicator($libraryPath = '')
	{
		$directory = new Main\IO\Directory(__DIR__. '/'. $libraryPath);
		$fileList = $directory->getChildren();
		foreach ($fileList as $entry)
		{
			if ($entry->isFile() && preg_match("/^(.+)\.php$/i", $entry->getName(), $parts))
			{
				$subNamespace = ($libraryPath != '' ? '\\'.$libraryPath : ''). '\\';
				/** @var Volume\IVolumeIndicator $indicatorType */
				$indicatorType = __NAMESPACE__. $subNamespace. $parts[1];
				try
				{
					$reflection = new \ReflectionClass($indicatorType);
					if (
						!$reflection->isInterface() &&
						!$reflection->isAbstract() &&
						!$reflection->isTrait() &&
						$reflection->implementsInterface(__NAMESPACE__.'\\IVolumeIndicator')
					)
					{
						self::$indicatorTypeList[$indicatorType::getIndicatorId()] = $indicatorType::className();
					}
				}
				catch(\ReflectionException $exception)
				{
				}
			}
			elseif ($entry->isDirectory())
			{
				self::loadListIndicator($entry->getName());
			}
		}
	}

	/**
	 * Constructs and returns indicator type object.
	 * @param string $indicatorId Indicator class name.
	 * @return Volume\IVolumeIndicator
	 * @throws Main\ObjectException
	 * @throws Main\ArgumentNullException
	 */
	final public static function getIndicator($indicatorId)
	{
		if (!$indicatorId)
		{
			throw new Main\ArgumentNullException('Wrong parameter indicatorTypeId');
		}

		if (mb_strpos($indicatorId, __NAMESPACE__) !== false)
		{
			$className = $indicatorId;
		}
		else
		{
			$className = __NAMESPACE__.'\\'.str_replace('_', '\\', $indicatorId);
		}

		/** @var Volume\IVolumeIndicator $indicator */
		$indicator = new $className();
		if (!$indicator instanceof Volume\IVolumeIndicator)
		{
			throw new Main\ObjectException('Return must implements '. __NAMESPACE__. '\\IVolumeIndicator interface.');
		}

		return $indicator;
	}

	/**
	 * Component action list for measure process.
	 * @param array $componentCommandAlias Command alias.
	 * @return array
	 */
	public function getActionList($componentCommandAlias)
	{
		$indicatorId = static::getIndicatorId();

		$queueList[] = array(
			'indicatorId' => $indicatorId,
			'action' => $componentCommandAlias['MEASURE_ENTITY'],
		);
		$queueList[] = array(
			'indicatorId' => $indicatorId,
			'action' => $componentCommandAlias['MEASURE_FILE'],
		);
		$queueList[] = array(
			'indicatorId' => $indicatorId,
			'action' => $componentCommandAlias['MEASURE_ACTIVITY'],
		);
		$queueList[] = array(
			'indicatorId' => $indicatorId,
			'action' => $componentCommandAlias['MEASURE_EVENT'],
		);

		return $queueList;
	}

	/**
	 * Returns date split period.
	 * @return array
	 */
	public function getDateSplitPeriod()
	{
		return $this->dateSplitPeriod;
	}

	/**
	 * Sets date split period.
	 * @param array $dateSplitPeriod Value and units.
	 * @return void
	 */
	public function setDateSplitPeriod(array $dateSplitPeriod)
	{
		$this->dateSplitPeriod = $dateSplitPeriod;
	}

	/**
	 * Can filter applied to the indicator.
	 * @return boolean
	 */
	public function canBeFiltered()
	{
		// can not be filtered
		return false;
	}

	/**
	 * Sets filter parameters.
	 * @param string $key Parameter name to filter.
	 * @param string|string[] $value Parameter value.
	 * @return $this
	 */
	public function addFilter($key, $value)
	{
		$this->filter[$key] = $value;
		return $this;
	}

	/**
	 * Replace filter parameters.
	 * @param array $filter Filter key = value pair.
	 * @return $this
	 */
	public function setFilter(array $filter)
	{
		$this->filter = $filter;
		return $this;
	}

	/**
	 * Gets filter parameter by key.
	 *
	 * @param string $key Parameter name to filter.
	 * @param mixed|null $defaultValue Default value.
	 * @param string $acceptedListModificators List of accepted filter modificator. Defaults are '=<>!'.
	 *
	 * @return mixed|null
	 */
	public function getFilterValue($key, $defaultValue = null, $acceptedListModificators = '<>!=')
	{
		if (isset($this->filter[$key]))
		{
			return $this->filter[$key];
		}

		$filter = $this->getFilter();
		foreach ($filter as $k => $value)
		{
			$k0 = trim($k, $acceptedListModificators);
			if ($k0 == $key)
			{
				return $value;
			}
		}

		return $defaultValue;
	}

	/**
	 * Removes filter parameter by key.
	 * @param string $key Parameter name to filter.
	 * @param string $acceptedListModificators List of accepted filter modificator. Defaults are '=<>!'.
	 * @return void
	 */
	public function delFilterValue($key, $acceptedListModificators = '<>!=')
	{
		if (isset($this->filter[$key]))
		{
			unset($this->filter[$key]);
		}

		$filter = $this->getFilter();
		foreach ($filter as $k => $value)
		{
			$k0 = trim($k, $acceptedListModificators);
			if ($k0 == $key)
			{
				unset($this->filter[$k]);
			}
		}
	}

	/**
	 * Gets filter parameters.
	 * @param string[] $defaultFilter Default filter set.
	 * @return array
	 */
	public function getFilter(array $defaultFilter = array())
	{
		return (!empty($this->filter) ? $this->filter : $defaultFilter);
	}


	/**
	 * Returns total volume size.
	 * @return double
	 */
	public function getTotalSize()
	{
		return (double)$this->entitySize + (double)$this->fileSize;
	}

	/**
	 * Returns total volume size of tables corresponding to indicator.
	 * @return integer
	 */
	public function getEntitySize()
	{
		return (double)$this->entitySize;
	}

	/**
	 * Returns total count of entities corresponding to indicator.
	 * @return integer
	 */
	public function getEntityCount()
	{
		return (double)$this->entityCount;
	}

	/**
	 * Returns total volume size of files corresponding to indicator.
	 * @return integer
	 */
	public function getFileSize()
	{
		return (double)$this->fileSize;
	}

	/**
	 * Returns total amount of files corresponding to indicator.
	 * @return integer
	 */
	public function getFileCount()
	{
		return (double)$this->fileCount;
	}

	/**
	 * Returns total volume size of file on disk.
	 * @return double
	 */
	public function getDiskSize()
	{
		return (double)$this->diskSize;
	}

	/**
	 * Returns total amount of files on disk.
	 * @return double
	 */
	public function getDiskCount()
	{
		return (double)$this->diskCount;
	}

	/**
	 * Returns total volume size of activities and associated files.
	 * @return integer
	 */
	public function getActivitySize()
	{
		return (double)$this->activitySize;
	}

	/**
	 * Returns total amount of activities and associated files.
	 * @return integer
	 */
	public function getActivityCount()
	{
		return (double)$this->activityCount;
	}

	/**
	 * Returns total volume size of events and associated files.
	 * @return integer
	 */
	public function getEventSize()
	{
		return (double)$this->eventSize;
	}

	/**
	 * Returns total amount of events and associated files.
	 * @return integer
	 */
	public function getEventCount()
	{
		return (double)$this->eventCount;
	}

	/**
	 * Returns title of the indicator.
	 * @return string
	 * @throws Main\NotImplementedException
	 */
	abstract public function getTitle();

	/**
	 * Returns entity list.
	 * @return string[]
	 */
	public static function getEntityList()
	{
		return static::$entityList;
	}

	/**
	 * Returns special folder list.
	 * @return Disk\Folder[]|null
	 */
	public function getSpecialFolderList()
	{
		return array();
	}

	/**
	 * Returns entity list attached to disk object.
	 * @param string $entityClass Class name of entity.
	 * @return string|null
	 */
	public static function getDiskConnector($entityClass)
	{
		return null;
	}

	/**
	 * Returns Socialnetwork log entity list attached to disk object.
	 * @param string $entityClass Class name of entity.
	 * @return string|null
	 */
	public static function getLiveFeedConnector($entityClass)
	{
		return null;
	}

	/**
	 * Load tables information.
	 *
	 * @return void
	 * @throws Main\Db\SqlQueryException
	 */
	protected static function loadTablesInformation()
	{
		if (empty(self::$tablesInformation))
		{
			$connection = Main\Application::getConnection();

			self::$tablesInformation = array();

			$querySql = "
				SELECT 
					TABLE_NAME, 
					TABLE_ROWS AS TABLE_ROWS, 
					DATA_LENGTH + INDEX_LENGTH AS SIZE, 
					case TABLE_ROWS 
						when 0 then 0 
						else round((DATA_LENGTH + INDEX_LENGTH) / TABLE_ROWS)
					end AS AVG_SIZE
				FROM information_schema.TABLES 
				WHERE 
					TABLE_SCHEMA = '".$connection->getDatabase()."'
					AND (
						TABLE_NAME LIKE 'b_crm_%' OR 
						TABLE_NAME LIKE 'b_uts_crm_%' OR
						TABLE_NAME LIKE 'b_utm_crm_%' OR
						TABLE_NAME LIKE 'b_uts_order' OR
						TABLE_NAME LIKE 'b_utm_order'
					)
			";
			$result = $connection->query($querySql);
			while ($row = $result->fetch())
			{
				self::$tablesInformation[mb_strtolower($row['TABLE_NAME'])] = $row;
			}
		}
	}


	/**
	 * Returns table list corresponding to indicator.
	 *
	 * @return string[]
	 * @throws Main\Db\SqlQueryException
	 */
	public function getTableList()
	{
		if (empty($this->tableList))
		{
			$this->tableList = array();

			self::loadTablesInformation();

			$entityList = static::getEntityList();

			foreach ($entityList as $entity)
			{
				try
				{
					$reflection = new \ReflectionClass($entity);
					if (
						!$reflection->isInterface() &&
						!$reflection->isAbstract() &&
						$reflection->isSubclassOf(ORM\Data\DataManager::class)
					)
					{
						/** @var ORM\Data\DataManager $entity */
						$this->tableList[] = $entity::getTableName();

						/** @var ORM\Data\DataManager $entity */
						$ufName = $entity::getUfId();
						if ($ufName != '')
						{
							$utmEntityTableName = 'b_utm_'.mb_strtolower($ufName);
							if (isset(self::$tablesInformation[$utmEntityTableName]))
							{
								$this->tableList[] = $utmEntityTableName;
							}

							$utsEntityTableName = 'b_uts_'.mb_strtolower($ufName);
							if (isset(self::$tablesInformation[$utsEntityTableName]))
							{
								$this->tableList[] = $utsEntityTableName;
							}
						}
					}
				}
				catch (\ReflectionException $exception)
				{
				}
			}
		}

		return $this->tableList;
	}


	/**
	 * Loads list of user fields information.
	 *
	 * @return void
	 * @throws Main\ArgumentException
	 */
	protected static function loadUserFieldInformation()
	{
		$entityList = static::getEntityList();

		foreach ($entityList as $entity)
		{
			if (isset(self::$userFieldInformation[$entity]))
			{
				continue;
			}
			self::$userFieldInformation[$entity] = false;

			try
			{
				$reflection = new \ReflectionClass($entity);
				if (
					!$reflection->isInterface() &&
					!$reflection->isAbstract() &&
					$reflection->isSubclassOf(ORM\Data\DataManager::class)
				)
				{
					/** @var ORM\Data\DataManager $entity */
					$ufName = $entity::getUfId();
					if ($ufName <> '')
					{
						$userFieldList = Main\UserFieldTable::getList(array(
							'filter' => array(
								'=ENTITY_ID' => $ufName,
							),
							'select' => array(
								'ID',
								'ENTITY_ID',
								'USER_TYPE_ID',
								'FIELD_NAME',
								'MULTIPLE',
								'XML_ID',
							),
						));
						if ($userFieldList->getSelectedRowsCount() > 0)
						{
							self::$userFieldInformation[$entity] = array();
							foreach ($userFieldList as $userField)
							{
								self::$userFieldInformation[$entity][$userField['FIELD_NAME']] = $userField;
							}
						}
					}
				}
			}
			catch (\ReflectionException $exception)
			{
			}
		}
	}


	/**
	 * Returns list of user fields corresponding to entity.
	 * @param string $entity Class name of entity.
	 * @return array
	 */
	public function getUserTypeFieldList($entity)
	{
		self::loadUserFieldInformation();

		$fields = array();

		if (isset(self::$userFieldInformation[$entity]) && is_array(self::$userFieldInformation[$entity]))
		{
			$userTypeField = array(
				\CUserTypeFile::USER_TYPE_ID,
			);
			if (self::isModuleAvailable('disk'))
			{
				$userTypeField[] = Disk\Uf\FileUserType::USER_TYPE_ID;
				$userTypeField[] = Disk\Uf\VersionUserType::USER_TYPE_ID;
			}

			foreach (self::$userFieldInformation[$entity] as $userField)
			{
				if (is_array($userField) && in_array($userField['USER_TYPE_ID'], $userTypeField))
				{
					$fields[$userField['FIELD_NAME']] = $userField;
				}
			}
		}

		return $fields;
	}


	/**
	 * Gets SQL query code to userfield table.
	 *
	 * @param string $entityClass Class name of entity.
	 * @param array $userField User field params.
	 * @param array $entityGroupField Entity fields to group by.
	 *
	 * @return string
	 */
	protected function prepareUserFieldQuery($entityClass, array $userField, array $entityGroupField = array())
	{
		/**
		 * @var ORM\Data\DataManager $entityClass
		 */
		$ufName = $entityClass::getUfId();
		if (empty($ufName))
		{
			return '';
		}

		$ufType = $userField['USER_TYPE_ID'];
		$isDiskAvailable = self::isModuleAvailable('disk');

		// need to filter by Entity
		$entityQuery = $entityClass::query();
		$entityEntity = $entityClass::getEntity();

		$entityQuery->addSelect('ID');


		// STAGE_SEMANTIC_ID
		if ($entityClass == Crm\QuoteTable::class)
		{
			Volume\Quote::registerStageField($entityQuery, '', 'STAGE_SEMANTIC_ID');
			Volume\Quote::registerStageField($entityQuery, '', 'QUOTE_STAGE_SEMANTIC_ID');
		}
		if ($entityClass == Crm\InvoiceTable::class)
		{
			Volume\Invoice::registerStageField($entityQuery, '', 'STAGE_SEMANTIC_ID');
			Volume\Invoice::registerStageField($entityQuery, '', 'INVOICE_STAGE_SEMANTIC_ID');
		}
		// DATE
		if (
			$entityClass == Crm\CompanyTable::class ||
			$entityClass == Crm\ContactTable::class
		)
		{
			$dayField = new ORM\Fields\ExpressionField(
				'DATE_CREATE_SHORT',
				'DATE(%s)',
				'DATE_CREATE'
			);
			$entityQuery->registerRuntimeField($dayField);
		}
		if (
			$entityClass == Crm\InvoiceTable::class
		)
		{
			$dayField = new ORM\Fields\ExpressionField(
				'DATE_CREATE_SHORT',
				'DATE(%s)',
				'DATE_INSERT'
			);
			$entityQuery->registerRuntimeField($dayField);
		}



		$entityFieldsSql = '';
		$entityFieldsGroupSql = '';
		$entityFields = array();
		foreach ($entityGroupField as $alias => $field)
		{
			$entityQuery->addSelect($field, $alias);
			$entityFields[] = 'entity.'. $alias;
		}

		if ($this->prepareEntityFilter($entityQuery, $entityEntity))
		{
			$entityFilterQuerySql = $entityQuery->getQuery();

			if (count($entityFields) > 0)
			{
				$entityFieldsSql = ', '.implode(', ', $entityFields);
				$entityFieldsGroupSql = 'GROUP BY '.implode(', ', $entityFields);
			}
		}
		else
		{
			// cannot filter this Entity
			return '';
		}


		$querySql = '';
		if ($userField['MULTIPLE'] === 'Y')
		{
			$ufId = $userField['ID'];
			$utmEntityTableName = 'b_utm_'.mb_strtolower($ufName);

			if (isset(self::$tablesInformation[$utmEntityTableName]))
			{
				if (
					$isDiskAvailable &&
					$ufType === Disk\Uf\FileUserType::USER_TYPE_ID
				)
				{
					$querySql = "
						SELECT 
							SUM(f.FILE_SIZE) as FILE_SIZE,
							COUNT(DISTINCT f.ID) as FILE_COUNT,
							SUM(f.FILE_SIZE) as DISK_SIZE,
							COUNT(DISTINCT f.ID) as DISK_COUNT
							{$entityFieldsSql}
						FROM
							{$utmEntityTableName} ufsrc
							INNER JOIN ( {$entityFilterQuerySql} ) entity 
								ON entity.ID = ufsrc.VALUE_ID
							INNER JOIN b_disk_attached_object attached
								ON attached.ID = ufsrc.VALUE_INT
								AND ufsrc.FIELD_ID = '{$ufId}'
							INNER JOIN b_disk_object files
								ON files.ID = attached.OBJECT_ID 
								AND files.ID = files.REAL_OBJECT_ID
								AND files.TYPE = '".Disk\Internals\ObjectTable::TYPE_FILE."'
							INNER JOIN b_file f
								ON f.ID = files.FILE_ID 
						{$entityFieldsGroupSql}  
					";
				}

				elseif (
					$isDiskAvailable &&
					$ufType === Disk\Uf\VersionUserType::USER_TYPE_ID
				)
				{
					$querySql = "
						SELECT DISTINCT
							SUM(f.FILE_SIZE) as FILE_SIZE,
							COUNT(DISTINCT f.ID) as FILE_COUNT,
							SUM(f.FILE_SIZE) as DISK_SIZE,
							COUNT(DISTINCT f.ID) as DISK_COUNT
							{$entityFieldsSql}
						FROM
							{$utmEntityTableName} ufsrc
							INNER JOIN ( {$entityFilterQuerySql} ) entity 
								ON entity.ID = ufsrc.VALUE_ID
							INNER JOIN b_disk_attached_object attached
								ON attached.ID = ufsrc.VALUE_INT
								AND ufsrc.FIELD_ID = '{$ufId}'
							INNER JOIN b_disk_version versions
								ON versions.ID = attached.VERSION_ID 
							INNER JOIN b_disk_object files
								ON files.ID = versions.OBJECT_ID
								AND files.ID = attached.OBJECT_ID 
								AND files.ID = files.REAL_OBJECT_ID
								AND files.TYPE = '".Disk\Internals\ObjectTable::TYPE_FILE."'
							INNER JOIN b_file f
								ON f.ID = versions.FILE_ID
						{$entityFieldsGroupSql}
					";
				}

				elseif (
					$ufType === \CUserTypeFile::USER_TYPE_ID
				)
				{
					$querySql = "
						SELECT 
							SUM(f.FILE_SIZE) as FILE_SIZE,
							COUNT(DISTINCT f.ID) as FILE_COUNT,
							0 as DISK_SIZE,
							0 as DISK_COUNT
							{$entityFieldsSql}
						FROM
							{$utmEntityTableName} ufsrc
							INNER JOIN ( {$entityFilterQuerySql} ) entity 
								ON entity.ID = ufsrc.VALUE_ID
							INNER JOIN b_file f
								ON f.ID = ufsrc.VALUE_INT
								AND ufsrc.FIELD_ID = '{$ufId}'
						{$entityFieldsGroupSql}
					";
				}
			}
		}
		else
		{
			$ufEntityTableFieldName = $userField['FIELD_NAME'];
			$utsEntityTableName = 'b_uts_'.mb_strtolower($ufName);

			if (isset(self::$tablesInformation[$utsEntityTableName]))
			{
				if (
					$isDiskAvailable &&
					$ufType === Disk\Uf\FileUserType::USER_TYPE_ID
				)
				{
					$querySql = "
						SELECT 
							SUM(f.FILE_SIZE) as FILE_SIZE,
							COUNT(DISTINCT f.ID) as FILE_COUNT,
							SUM(f.FILE_SIZE) as DISK_SIZE,
							COUNT(DISTINCT f.ID) as DISK_COUNT
							{$entityFieldsSql}
						FROM
							{$utsEntityTableName} ufsrc
							INNER JOIN ( {$entityFilterQuerySql} ) entity 
								ON entity.ID = ufsrc.VALUE_ID
							INNER JOIN b_disk_attached_object attached
								ON attached.ID = cast(ufsrc.{$ufEntityTableFieldName} as UNSIGNED)
								and ufsrc.{$ufEntityTableFieldName} REGEXP '^[0-9]+$'
							INNER JOIN b_disk_object files
								ON files.ID = attached.OBJECT_ID 
								AND files.ID = files.REAL_OBJECT_ID
								AND files.TYPE = '".Disk\Internals\ObjectTable::TYPE_FILE."'
							INNER JOIN b_file f
								ON f.ID = files.FILE_ID 
						{$entityFieldsGroupSql}
					";
				}

				elseif (
					$isDiskAvailable &&
					$ufType === Disk\Uf\VersionUserType::USER_TYPE_ID
				)
				{
					$querySql = "
						SELECT 
							SUM(f.FILE_SIZE) as FILE_SIZE,
							COUNT(DISTINCT f.ID) as FILE_COUNT,
							SUM(f.FILE_SIZE) as DISK_SIZE,
							COUNT(DISTINCT f.ID) as DISK_COUNT
							{$entityFieldsSql}
						FROM
							{$utsEntityTableName} ufsrc
							INNER JOIN ( {$entityFilterQuerySql} ) entity 
								ON entity.ID = ufsrc.VALUE_ID
							INNER JOIN b_disk_attached_object attached
								ON attached.ID = cast(ufsrc.{$ufEntityTableFieldName} as UNSIGNED)
								and ufsrc.{$ufEntityTableFieldName} REGEXP '^[0-9]+$'
							INNER JOIN b_disk_version versions
								ON versions.ID = attached.VERSION_ID 
							INNER JOIN b_disk_object files
								ON files.ID = versions.OBJECT_ID
								AND files.ID = attached.OBJECT_ID
								AND files.ID = files.REAL_OBJECT_ID
								AND files.TYPE = '".Disk\Internals\ObjectTable::TYPE_FILE."'
							INNER JOIN b_file f
								ON f.ID = versions.FILE_ID 
						{$entityFieldsGroupSql}
					";
				}

				elseif (
					$ufType === \CUserTypeFile::USER_TYPE_ID
				)
				{
					$querySql = "
						SELECT 
							SUM(f.FILE_SIZE) as FILE_SIZE,
							COUNT(DISTINCT f.ID) as FILE_COUNT,
							0 as DISK_SIZE,
							0 as DISK_COUNT
							{$entityFieldsSql}
						FROM
							{$utsEntityTableName} ufsrc
							INNER JOIN ( {$entityFilterQuerySql} ) entity 
								ON entity.ID = ufsrc.VALUE_ID
							INNER JOIN b_file f
								ON f.ID = cast(ufsrc.{$ufEntityTableFieldName} as UNSIGNED)
								and ufsrc.{$ufEntityTableFieldName} REGEXP '^[0-9]+$'
						{$entityFieldsGroupSql}
					";
				}
			}
		}

		return $querySql;
	}


	/**
	 * Gets SQL query code for disk attached entity.
	 *
	 * @param string $entityClass Entity class name.
	 * @param string $diskConnector Connector class name.
	 * @param array $entityGroupField Entity fields to group by.
	 *
	 * @return string
	 */
	protected function prepareDiskAttachedQuery($entityClass, $diskConnector, array $entityGroupField = array())
	{
		if (self::isModuleAvailable('disk') !== true)
		{
			return '';
		}

		/**
		 * @var ORM\Data\DataManager $entityClass
		 */
		$entityQuery = $entityClass::query();
		$entityEntity = $entityClass::getEntity();

		$entityQuery->addSelect('ID');

		// STAGE_SEMANTIC_ID
		if ($entityClass == Crm\QuoteTable::class)
		{
			Volume\Quote::registerStageField($entityQuery, '', 'QUOTE_STAGE_SEMANTIC_ID');
		}
		if ($entityClass == Crm\InvoiceTable::class)
		{
			Volume\Invoice::registerStageField($entityQuery, '', 'INVOICE_STAGE_SEMANTIC_ID');
		}
		// DATE
		if (
			$entityClass == Crm\CompanyTable::class ||
			$entityClass == Crm\ContactTable::class
		)
		{
			$dayField = new ORM\Fields\ExpressionField(
				'DATE_CREATE_SHORT',
				'DATE(%s)',
				'DATE_CREATE'
			);
			$entityQuery->registerRuntimeField($dayField);
		}
		if (
			$entityClass == Crm\InvoiceTable::class
		)
		{
			$dayField = new ORM\Fields\ExpressionField(
				'DATE_CREATE_SHORT',
				'DATE(%s)',
				'DATE_INSERT'
			);
			$entityQuery->registerRuntimeField($dayField);
		}

		$entityFieldsSql = '';
		$entityFieldsGroupSql = '';
		$entityFields = array();
		foreach ($entityGroupField as $alias => $field)
		{
			$entityQuery->addSelect($field, $alias);
			$entityFields[] = 'entity.'. $alias;
		}

		if ($this->prepareEntityFilter($entityQuery, $entityEntity))
		{
			$entityFilterQuerySql = $entityQuery->getQuery();

			if (count($entityFields) > 0)
			{
				$entityFieldsSql = ', '.implode(', ', $entityFields);
				$entityFieldsGroupSql = 'GROUP BY '.implode(', ', $entityFields);
			}
		}
		else
		{
			// cannot filter this Entity
			return '';
		}

		$attachedEntitySql = Main\Application::getConnection()->getSqlHelper()->forSql($diskConnector);
		$querySql = "
			SELECT 
				SUM(ver.SIZE) as FILE_SIZE,
				COUNT(ver.FILE_ID) as FILE_COUNT,
				SUM(ver.SIZE) as DISK_SIZE,
				COUNT(DISTINCT files.ID) as DISK_COUNT
				{$entityFieldsSql}
			FROM 
				b_disk_version ver 
				INNER JOIN b_disk_object files
					ON files.ID  = ver.OBJECT_ID
					AND files.TYPE = '".Disk\Internals\ObjectTable::TYPE_FILE."'
					AND files.ID = files.REAL_OBJECT_ID
				INNER JOIN b_disk_attached_object attached 
					ON attached.OBJECT_ID = files.ID
					AND (attached.VERSION_ID IS NULL OR attached.VERSION_ID = ver.ID)
					AND attached.ENTITY_TYPE = '{$attachedEntitySql}'
				INNER JOIN ( {$entityFilterQuerySql} ) entity 
					ON entity.ID = attached.ENTITY_ID
			{$entityFieldsGroupSql}
		";

		return $querySql;
	}

	/**
	 * Gets SQL query code for disk attache linked by social network log.
	 *
	 * @param string $entityClass Entity class name.
	 * @param string $eventEntityType Connector class name.
	 * @param array $entityGroupField Entity fields to group by.
	 *
	 * @return string
	 */
	protected function prepareLiveFeedQuery($entityClass, $eventEntityType, array $entityGroupField = array())
	{
		if (!(self::isModuleAvailable('socialnetwork') && self::isModuleAvailable('disk')))
		{
			return '';
		}

		/**
		 * @var ORM\Data\DataManager $entityClass
		 */
		$entityQuery = $entityClass::query();
		$entityEntity = $entityClass::getEntity();

		$entityQuery->addSelect('ID');

		// STAGE_SEMANTIC_ID
		if ($entityClass == Crm\QuoteTable::class)
		{
			Volume\Quote::registerStageField($entityQuery, '', 'QUOTE_STAGE_SEMANTIC_ID');
		}
		if ($entityClass == Crm\InvoiceTable::class)
		{
			Volume\Invoice::registerStageField($entityQuery, '', 'INVOICE_STAGE_SEMANTIC_ID');
		}
		// DATE
		if (
			$entityClass == Crm\CompanyTable::class ||
			$entityClass == Crm\ContactTable::class
		)
		{
			$dayField = new ORM\Fields\ExpressionField(
				'DATE_CREATE_SHORT',
				'DATE(%s)',
				'DATE_CREATE'
			);
			$entityQuery->registerRuntimeField($dayField);
		}
		if (
			$entityClass == Crm\InvoiceTable::class
		)
		{
			$dayField = new ORM\Fields\ExpressionField(
				'DATE_CREATE_SHORT',
				'DATE(%s)',
				'DATE_INSERT'
			);
			$entityQuery->registerRuntimeField($dayField);
		}

		$entityFieldsSql = '';
		$entityFieldsGroupSql = '';
		$entityFields = array();
		foreach ($entityGroupField as $alias => $field)
		{
			$entityQuery->addSelect($field, $alias);
			$entityFields[] = 'entity.'. $alias;
		}

		if ($this->prepareEntityFilter($entityQuery, $entityEntity))
		{
			$entityFilterQuerySql = $entityQuery->getQuery();

			if (count($entityFields) > 0)
			{
				$entityFieldsSql = ', '.implode(', ', $entityFields);
				$entityFieldsGroupSql = 'GROUP BY '.implode(', ', $entityFields);
			}
		}
		else
		{
			// cannot filter this Entity
			return '';
		}

		$logTable = \Bitrix\Socialnetwork\LogTable::getTableName();
		$helper = Main\Application::getConnection()->getSqlHelper();

		$attachedEntitySql = $helper->forSql(Disk\Uf\SonetLogConnector::class);
		$eventEntitySql = $helper->forSql($eventEntityType);

		$querySql = "
			SELECT 
				SUM(ver.SIZE) as FILE_SIZE,
				COUNT(ver.FILE_ID) as FILE_COUNT,
				SUM(ver.SIZE) as DISK_SIZE,
				COUNT(DISTINCT files.ID) as DISK_COUNT
				{$entityFieldsSql}
			FROM 
				b_disk_version ver 
				INNER JOIN b_disk_object files
					ON files.ID  = ver.OBJECT_ID
					AND files.TYPE = '".Disk\Internals\ObjectTable::TYPE_FILE."'
					AND files.ID = files.REAL_OBJECT_ID
				INNER JOIN b_disk_attached_object attached
					ON attached.OBJECT_ID = files.ID
					AND (attached.VERSION_ID IS NULL OR attached.VERSION_ID = ver.ID)
					AND attached.ENTITY_TYPE = '{$attachedEntitySql}'
				INNER JOIN {$logTable} live_feed_log
					ON attached.ENTITY_ID = live_feed_log.ID
					AND live_feed_log.ENTITY_TYPE = '{$eventEntitySql}'
				INNER JOIN ( {$entityFilterQuerySql} ) entity 
					ON entity.ID = live_feed_log.ENTITY_ID
			{$entityFieldsGroupSql}
		";

		return $querySql;
	}



	/**
	 * Method generates component action list for measure process.
	 *
	 * @param string $entityClass Entity class name.
	 * @param string $dateFieldAlias Date field alias.
	 * @param array $actionAliases Command alias.
	 *
	 * @return array
	 */
	protected function prepareRangeActionList($entityClass, $dateFieldAlias, $actionAliases)
	{
		$indicatorId = static::getIndicatorId();

		$queueList = array();

		$actionCommands = array(
			'MEASURE_ENTITY',
			'MEASURE_FILE',
			'MEASURE_ACTIVITY',
			'MEASURE_EVENT',
		);

		$maxIdRange = -1;
		if (isset(static::$maxIdRangeCache[$entityClass]))
		{
			$maxIdRange = static::$maxIdRangeCache[$entityClass];
		}
		else
		{
			$cache = new \CPHPCache();
			if ($cache->startDataCache(3 * 3600, "{$entityClass}:{$indicatorId}:maxIdRange", 'crm/configs/volume'))
			{
				/**
				 * @var ORM\Data\DataManager $entityClass
				 */
				$entityQuery = $entityClass::query();

				$row0 = $entityQuery
					->registerRuntimeField(new ORM\Fields\ExpressionField('CNT', 'COUNT(*)'))
					->addSelect('CNT')
					->exec()
					->fetch();

				if ($row0)
				{
					if ((int)$row0['CNT'] > 500000)
					{
						$maxIdRange = 100000;
					}
					elseif ((int)$row0['CNT'] > 100000)
					{
						$maxIdRange = 50000;
					}
				}

				static::$maxIdRangeCache[$entityClass] = $maxIdRange;

				$cache->endDataCache($maxIdRange);
			}
			else
			{
				$maxIdRange = $cache->getVars();
				static::$maxIdRangeCache[$entityClass] = $maxIdRange;
			}
		}

		if ($maxIdRange > 0)
		{
			$cache = new \CPHPCache();
			if ($cache->startDataCache(3 * 3600, "{$entityClass}:{$indicatorId}:queueList", 'crm/configs/volume'))
			{
				/**
				 * @var ORM\Data\DataManager $entityClass
				 */
				$query = $entityClass::query();

				$month = new ORM\Fields\ExpressionField('YY', "YEAR(%s)", $dateFieldAlias);
				$query->registerRuntimeField($month)->addSelect('YY');

				$month = new ORM\Fields\ExpressionField('MM', "MONTH(%s)", $dateFieldAlias);
				$query->registerRuntimeField($month)->addSelect('MM');

				$month = new ORM\Fields\ExpressionField('DD', "DAY(%s)", $dateFieldAlias);
				$query->registerRuntimeField($month)->addSelect('DD');

				$border = new ORM\Fields\ExpressionField('BRDR', 'MAX(%s)', 'ID');
				$query->registerRuntimeField($border)->addSelect('BRDR');

				$counter = new ORM\Fields\ExpressionField('CNT', 'COUNT(*)');
				$query->registerRuntimeField($counter)->addSelect('CNT');

				$query->setGroup(array('YY', 'MM', 'DD'))->setOrder(array('BRDR' => 'ASC'));

				$res = $query->exec();
				if ($row = $res->fetch())
				{
					$count = 0;
					$appendQueueList = function($range) use ($actionCommands, $actionAliases, &$queueList, $indicatorId)
					{
						foreach ($actionCommands as $command)
						{
							if (isset($actionAliases[$command]))
							{
								$queueList[] = array(
									'indicatorId' => $indicatorId,
									'action' => $actionAliases[$command],
									'range' => $range,
								);
							}
						}
					};

					$prevId = null;
					do
					{
						if (
							$row['CNT'] >= $maxIdRange ||
							($count + (int)$row['CNT']) >= $maxIdRange * 1.3 ||
							$count >= $maxIdRange
						)
						{
							$range = '';
							if ($prevId > 0)
							{
								$range .= $prevId;
							}
							$range .= '-'.(int)$row['BRDR'];

							$appendQueueList($range);

							$count = 0;
							$prevId = (int)$row['BRDR'];
							continue;
						}

						$count += (int)$row['CNT'];
					}
					while ($row = $res->fetch());

					if ($count >= 0 && $prevId > 0)
					{
						$range = $prevId.'-';

						$appendQueueList($range);
					}
				}

				$cache->endDataCache($queueList);
			}
			else
			{
				$queueList = $cache->getVars();
			}
		}
		else
		{
			foreach ($actionCommands as $command)
			{
				if (isset($actionAliases[$command]))
				{
					$queueList[] = array(
						'indicatorId' => $indicatorId,
						'action' => $actionAliases[$command],
					);
				}
			}
		}

		return $queueList;
	}


	/**
	 * Method generates component action list for measure process.
	 *
	 * @param string $entityClass Entity class name.
	 * @param string $dateFieldAlias Date field alias.
	 * @param array $actionAliases Command alias.
	 *
	 * @return array
	 */
	protected function prepareDateActionList($entityClass, $dateFieldAlias, $actionAliases)
	{
		$indicatorId = static::getIndicatorId();

		$queueList = array();

		$actionCommands = array(
			'MEASURE_ENTITY',
			'MEASURE_FILE',
			'MEASURE_ACTIVITY',
			'MEASURE_EVENT',
		);

		/**
		 * @var ORM\Data\DataManager $entityClass
		 */
		$query = $entityClass::query();

		$dateMin = new ORM\Fields\ExpressionField('DATE_MIN', "DATE_FORMAT(MIN(%s), '%%Y-%%m-%%d')", $dateFieldAlias);
		$query->registerRuntimeField($dateMin)->addSelect('DATE_MIN');

		$monthCount = new ORM\Fields\ExpressionField('MONTHS', 'TIMESTAMPDIFF(MONTH, MIN(%s), MAX(%s))', array($dateFieldAlias, $dateFieldAlias));
		$query->registerRuntimeField($monthCount)->addSelect('MONTHS');

		$res = $query->exec();
		if ($row = $res->fetch())
		{
			list($dateSplitPeriod, $dateSplitPeriodUnits) = $this->getDateSplitPeriod();

			$dateMin =  new Main\Type\DateTime($row['DATE_MIN'], 'Y-m-d');
			$months =  $row['MONTHS'];

			while ($months >= 0)
			{
				$period = $dateMin->format('Y.m');
				$dateMin->add("$dateSplitPeriod $dateSplitPeriodUnits");
				$period .= '-';
				$period .= $dateMin->format('Y.m');
				$months -= $dateSplitPeriod;

				foreach ($actionCommands as $command)
				{
					if (isset($actionAliases[$command]))
					{
						$queueList[] = array(
							'indicatorId' => $indicatorId,
							'action' => $actionAliases[$command],
							'period' => $period,
						);
					}
				}
			}
		}

		return $queueList;
	}

	/**
	 * Sets owner id.
	 * @param int $ownerId User id.
	 * @return void
	 */
	public function setOwner($ownerId)
	{
		$this->ownerId = $ownerId;
	}

	/**
	 * Gets owner id.
	 * @return int|null
	 */
	public function getOwner()
	{
		return $this->ownerId > 0 ? $this->ownerId : 0;
	}


	/**
	 * Adds an array of errors to the collection.
	 *
	 * @param Main\Error[] | Main\Error $errors Raised error.
	 * @return void
	 */
	public function collectError($errors)
	{
		if (!($this->errorCollection instanceof Main\ErrorCollection))
		{
			$this->errorCollection = new Main\ErrorCollection();
		}

		if (is_array($errors))
		{
			$this->errorCollection->add($errors);
		}
		else
		{
			$this->errorCollection->add(array($errors));
		}
	}

	/**
	 * Returns errors list.
	 *
	 * @implements Volume\IVolumeClear
	 * @return Main\Error|null
	 */
	public function getLastError()
	{
		if ($this->errorCollection instanceof Main\ErrorCollection)
		{
			$offset = $this->errorCollection->count() - 1;
			return $this->errorCollection->offsetGet($offset);
		}

		return null;
	}

	/**
	 * Returns process offset.
	 *
	 * @implements Volume\IVolumeClear
	 * @p
	 * @return int
	 */
	public function getProcessOffset()
	{
		return $this->processOffset;
	}

	/**
	 * Setup process offset.
	 *
	 * @implements Volume\IVolumeClear
	 * @param int $offset Offset position.
	 * @return void
	 */
	public function setProcessOffset($offset)
	{
		$this->processOffset = $offset;
	}

	/**
	 * Sets dropped count of entity attachments.
	 *
	 * @implements Volume\IVolumeClearFile
	 * @param int $count Amount to set.
	 * @return void
	 */
	public function setDroppedFileCount($count)
	{
		$this->droppedFileCount = $count;
	}

	/**
	 * Returns dropped count of entity attachments.
	 *
	 * @implements Volume\IVolumeClearFile
	 * @param int $count Amount to increment.
	 * @return void
	 */
	public function incrementDroppedFileCount($count = 1)
	{
		$this->droppedFileCount += $count;
	}

	/**
	 * Returns dropped count of entity attachments.
	 * @implements Volume\IVolumeClearFile
	 *
	 * @return int
	 */
	public function getDroppedFileCount()
	{
		return $this->droppedFileCount;
	}


	/**
	 * Sets dropped count of entities.
	 *
	 * @implements Volume\IVolumeClear
	 * @param int $count Amount to set.
	 * @return void
	 */
	public function setDroppedEntityCount($count)
	{
		$this->droppedCount = $count;
	}

	/**
	 * Returns dropped count of entities.
	 *
	 * @implements Volume\IVolumeClear
	 * @param int $count Amount to increment.
	 * @return void
	 */
	public function incrementDroppedEntityCount($count = 1)
	{
		$this->droppedCount += $count;
	}

	/**
	 * Returns dropped count of entities.
	 *
	 * @implements Volume\IVolumeClear
	 * @return int
	 */
	public function getDroppedEntityCount()
	{
		return $this->droppedCount;
	}

	/**
	 * Returns error count.
	 *
	 * @implements Volume\IVolumeClear
	 * @return int
	 */
	public function getFailCount()
	{
		return $this->failCount;
	}

	/**
	 * Sets error count.
	 *
	 * @implements Volume\IVolumeClear
	 * @param int $count Amount to set.
	 * @return void
	 */
	public function setFailCount($count)
	{
		$this->failCount = $count;
	}

	/**
	 * Returns error count.
	 *
	 * @implements Volume\IVolumeClearEvent
	 * @param int $count Amount to increment.
	 * @return void
	 */
	public function incrementFailCount($count = 1)
	{
		$this->failCount += $count;
	}

	/**
	 * Start up timer.
	 *
	 * @implements Volume\IVolumeClear
	 * @param int $timeLimit Time limit.
	 * @return void
	 */
	public function startTimer($timeLimit = 25)
	{
		$this->timeLimit = $timeLimit;

		if (defined('START_EXEC_TIME') && START_EXEC_TIME > 0)
		{
			$this->startTime = (int)START_EXEC_TIME;
		}
		else
		{
			$this->startTime = time();
		}
	}


	/**
	 * Tells true if time limit reached.
	 *
	 * @implements Volume\IVolumeClear
	 * @return boolean
	 */
	public function hasTimeLimitReached()
	{
		if ($this->timeLimit > 0)
		{
			if ($this->timeLimitReached)
			{
				return true;
			}
			if ((time() - $this->startTime) >= $this->timeLimit)
			{
				$this->timeLimitReached = true;

				return true;
			}
		}

		return false;
	}


	/**
	 * Returns count of files in disk folder.
	 *
	 * @param Disk\Folder $folder Disk folder to analize.
	 * @param array $filter Additional filter for file selection.
	 *
	 * @return int
	 * @throws Main\ObjectPropertyException
	 * @throws Main\SystemException
	 */
	protected function countDiskFiles(Disk\Folder $folder, $filter = array())
	{
		$count = 0;
		if (self::isModuleAvailable('disk'))
		{
			if ($folder instanceof Disk\Folder)
			{
				$filter['=STORAGE_ID'] = $folder->getStorageId();
				$filter['=PATH_CHILD.PARENT_ID'] = $folder->getId();
				$filter['=TYPE'] = Disk\Internals\ObjectTable::TYPE_FILE;

				$count = Disk\Internals\ObjectTable::getCount($filter);
			}
		}

		return $count;
	}


	/**
	 * Performs dropping entity attachments.
	 *
	 * @param Disk\Folder $folder Disk folder to analize.
	 * @param array $filter Additional filter for file selection.
	 *
	 * @return boolean
	 * @throws Main\ArgumentException
	 * @throws Main\ObjectPropertyException
	 * @throws Main\SystemException
	 */
	protected function clearDiskFiles(Disk\Folder $folder, $filter = array())
	{
		if (!self::isModuleAvailable('disk'))
		{
			return false;
		}
		if (!($folder instanceof Disk\Folder))
		{
			return false;
		}

		$filter['=STORAGE_ID'] = $folder->getStorageId();
		$filter['=PATH_CHILD.PARENT_ID'] = $folder->getId();
		$filter['=TYPE'] = Disk\Internals\ObjectTable::TYPE_FILE;

		if ($this->getProcessOffset() > 0)
		{
			$filter['>ID'] = $this->getProcessOffset();
		}

		$objectList = Disk\Internals\ObjectTable::getList(array(
			'filter' => $filter,
			'order' => array(
				'PATH_CHILD.DEPTH_LEVEL' => 'DESC',
				'ID' => 'ASC',
			),
			'limit' => static::MAX_FILE_PER_INTERACTION,
		));

		$success = true;

		foreach ($objectList as $row)
		{
			$file = Disk\BaseObject::buildFromArray($row);

			if($file instanceof Disk\File)
			{
				/** @var Disk\File $file */
				$securityContext = $this->getDiskSecurityContext($file);
				if($file->canDelete($securityContext))
				{
					if ($this->deleteDiskFile($file))
					{
						$this->incrementDroppedFileCount();
					}
					else
					{
						//$this->collectError(new Error('Deletion failed with file #'. $file->getId(), self::ERROR_DELETION_FAILED));
						$this->incrementFailCount();
					}
				}
				else
				{
					$this->collectError(new Error('Access denied to file #'. $file->getId(), self::ERROR_PERMISSION_DENIED));
					$this->incrementFailCount();
				}
			}

			$this->setProcessOffset($row['ID']);

			if ($this->hasTimeLimitReached())
			{
				$success = false;
				break;
			}
		}

		return $success;
	}

	/**
	 * Returns disk security context.
	 * @param Disk\BaseObject $object File or folder.
	 * @return Disk\Security\SecurityContext
	 */
	protected function getDiskSecurityContext($object)
	{
		static $securityContext = null;

		$userId = $this->getUser()->getId();

		if (!($securityContext instanceof Disk\Security\SecurityContext))
		{
			if (Disk\User::isCurrentUserAdmin())
			{
				$securityContext = new Disk\Security\FakeSecurityContext($userId);
			}
			else
			{
				$securityContext = $object->getStorage()->getSecurityContext($userId);
			}
		}

		return $securityContext;
	}

	/**
	 * Deletes file.
	 * @param Disk\File $file File to drop.
	 * @return boolean
	 */
	protected function deleteDiskFile(Disk\File $file)
	{
		$userId = $this->getUser()->getId();

		if(!$file->delete($userId))
		{
			$this->collectError($file->getErrors());

			return false;
		}

		return true;
	}
}