Your IP : 18.227.26.244


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

<?php

namespace Bitrix\Disk;

use Bitrix\Disk\Internals\Error\Error;
use Bitrix\Disk\Internals\Error\ErrorCollection;
use Bitrix\Disk\Internals\FileTable;
use Bitrix\Disk\Internals\FolderTable;
use Bitrix\Disk\Internals\ObjectTable;
use Bitrix\Disk\Internals\RightTable;
use Bitrix\Disk\Internals\SharingTable;
use Bitrix\Disk\Internals\SimpleRightTable;
use Bitrix\Disk\Internals\VersionTable;
use Bitrix\Disk\ProxyType\Group;
use Bitrix\Disk\Security\SecurityContext;
use Bitrix\Main\DB\SqlQueryException;
use Bitrix\Main\Entity\ExpressionField;
use Bitrix\Main\Entity\Query;
use Bitrix\Main\Event;
use Bitrix\Main\InvalidOperationException;
use Bitrix\Main\Loader;
use Bitrix\Main\Localization\Loc;
use Bitrix\Main\ObjectException;
use Bitrix\Main\Type\Collection;
use Bitrix\Main\Type\DateTime;
use CFile;

Loc::loadMessages(__FILE__);

class Folder extends BaseObject
{
	const ERROR_COULD_NOT_DELETE_WITH_CODE = 'DISK_FOLDER_22001';
	const ERROR_COULD_NOT_SAVE_FILE        = 'DISK_FOLDER_22002';

	const CODE_FOR_CREATED_FILES  = SpecificFolder::CODE_FOR_CREATED_FILES;
	const CODE_FOR_SAVED_FILES    = SpecificFolder::CODE_FOR_SAVED_FILES;
	const CODE_FOR_UPLOADED_FILES = SpecificFolder::CODE_FOR_UPLOADED_FILES;

	/** @var bool */
	protected $hasSubFolders;

	/**
	 * Gets the fully qualified name of table class which belongs to current model.
	 * @throws \Bitrix\Main\NotImplementedException
	 * @return string
	 */
	public static function getTableClassName()
	{
		return FolderTable::className();
	}

	/**
	 * Tells if the folder has sub-folders.
	 * Property {hasSubFolders} fills by using field HAS_SUBFOLDERS in select.
	 * @return boolean
	 */
	public function hasSubFolders()
	{
		return (bool)$this->hasSubFolders;
	}

	/**
	 * Checks rights to add object to current object.
	 * @param SecurityContext $securityContext Security context.
	 * @return bool
	 */
	public function canAdd(SecurityContext $securityContext)
	{
		return $securityContext->canAdd($this->id);
	}

	/**
	 * Pre-loads all operations for children.
	 * @internal
	 * @param SecurityContext $securityContext Security context.
	 */
	public function preloadOperationsForChildren(SecurityContext $securityContext)
	{
		$securityContext->preloadOperationsForChildren($this->id);
	}

	public function preloadOperationsForSpecifiedObjects(array $ids, SecurityContext $securityContext)
	{
		if (!$ids)
		{
			return;
		}

		$securityContext->preloadOperationsForSpecifiedObjects($this->id, $ids);
	}

	/**
	 * Adds row to entity table, fills error collection and builds model.
	 * @param array           $data Data.
	 * @param ErrorCollection $errorCollection Error collection.
	 * @return \Bitrix\Disk\Internals\Model|static|null
	 * @throws \Bitrix\Main\NotImplementedException
	 * @internal
	 */
	public static function add(array $data, ErrorCollection $errorCollection)
	{
		/** @var Folder $folder */
		$folder = parent::add($data, $errorCollection);
		if($folder && !$folder->isDeleted())
		{
			$driver = Driver::getInstance();
			$driver->getIndexManager()->indexFolder($folder);
			$driver->sendChangeStatusToSubscribers($folder);
		}

		return $folder;
	}

	/**
	 * Returns once model by specific filter.
	 * @param array $filter Filter.
	 * @param array $with List of eager loading.
	 * @throws \Bitrix\Main\NotImplementedException
	 * @return static
	 */
	public static function load(array $filter, array $with = array())
	{
		$filter['TYPE'] = ObjectTable::TYPE_FOLDER;

		return parent::load($filter, $with);
	}

	protected static function getClassNameModel(array $row)
	{
		$classNameModel = parent::getClassNameModel($row);
		if(
			$classNameModel === static::className() ||
			is_subclass_of($classNameModel, static::className()) ||
			in_array(static::className(), class_parents($classNameModel)) //5.3.9
		)
		{
			return $classNameModel;
		}

		throw new ObjectException('Could not to get non subclass of ' . static::className());
	}

	/**
	 * Tells if folder is root. It means folder does not have parent folder.
	 *
	 * @return bool
	 */
	public function isRoot()
	{
		return !$this->parentId;
	}

	/**
	 * Uploads new file to folder.
	 * @param array $fileArray Structure like $_FILES.
	 * @param array $data Contains additional fields (CREATED_BY, NAME, etc).
	 * @param array $rights Rights (@see \Bitrix\Disk\RightsManager).
	 * @param bool  $generateUniqueName Generates unique name for object in directory.
	 * @throws \Bitrix\Main\ArgumentException
	 * @return File|null
	 */
	public function uploadFile(array $fileArray, array $data, array $rights = array(), $generateUniqueName = false)
	{
		$this->errorCollection->clear();

		$data['NAME'] = $this->resolveFileName($fileArray, $data);

		$this->checkRequiredInputParams($data, array(
			'NAME', 'CREATED_BY'
		));

		if(!isset($fileArray['MODULE_ID']))
		{
			$fileArray['MODULE_ID'] = Driver::INTERNAL_MODULE_ID;
		}

		if(empty($fileArray['type']))
		{
			$fileArray['type'] = '';
		}

		$fileArray['type'] = TypeFile::normalizeMimeType($fileArray['type'], $data['NAME']);

		/** @noinspection PhpDynamicAsStaticMethodCallInspection */
		$fileId = CFile::saveFile($fileArray, Driver::INTERNAL_MODULE_ID, true, true);
		if(!$fileId)
		{
			$this->errorCollection->add(array(new Error(Loc::getMessage("DISK_FOLDER_MODEL_ERROR_COULD_NOT_SAVE_FILE"), self::ERROR_COULD_NOT_SAVE_FILE)));
			return null;
		}
		/** @var array $fileArray */
		/** @noinspection PhpDynamicAsStaticMethodCallInspection */
		$fileArray = CFile::getFileArray($fileId);

		$data['NAME'] = Ui\Text::correctFilename($data['NAME']);
		$fileModel = $this->addFile(array(
			'NAME' => $data['NAME'],
			'FILE_ID' => $fileId,
			'PREVIEW_ID' => isset($data['PREVIEW_ID'])? $data['PREVIEW_ID'] : null,
			'CONTENT_PROVIDER' => isset($data['CONTENT_PROVIDER'])? $data['CONTENT_PROVIDER'] : null,
			'SIZE' => !isset($data['SIZE'])? $fileArray['FILE_SIZE'] : $data['SIZE'],
			'CREATED_BY' => $data['CREATED_BY'],
			'UPDATE_TIME' => isset($data['UPDATE_TIME'])? $data['UPDATE_TIME'] : null,
			'CODE' => isset($data['CODE'])? $data['CODE'] : null,
		), $rights, $generateUniqueName);

		if(!$fileModel)
		{
			CFile::delete($fileId);
			return null;
		}
		return $fileModel;
	}

	private function resolveFileName(array $fileArray, array $data)
	{
		if (!empty($data['NAME']))
		{
			return $data['NAME'];
		}

		if (!empty($fileArray['name']))
		{
			return $fileArray['name'];
		}

		return null;
	}

	/**
	 * Creates blank file (size 0 byte) in folder.
	 * @param array $data Contains additional fields (CREATED_BY, NAME, MIME_TYPE).
	 * @param array $rights Rights (@see \Bitrix\Disk\RightsManager).
	 * @param bool  $generateUniqueName Generates unique name for object in directory.
	 * @return File|null
	 * @throws \Bitrix\Main\ArgumentException
	 */
	public function addBlankFile(array $data, array $rights = array(), $generateUniqueName = false)
	{
		$this->errorCollection->clear();

		$this->checkRequiredInputParams($data, array(
			'NAME', 'CREATED_BY', 'MIME_TYPE'
		));

		return $this->uploadFile(array(
			'name' => $data['NAME'],
			'content' => '',
			'type' => $data['MIME_TYPE'],
		), array(
			'NAME' => $data['NAME'],
			'SIZE' => isset($data['SIZE'])? $data['SIZE'] : null, //for im. We should show future!
			'CREATED_BY' => $data['CREATED_BY'],
		), $rights, $generateUniqueName);
	}

	/**
	 * Adds file in folder.
	 * @param array $data Contains additional fields (CREATED_BY, NAME, etc).
	 * @param array $rights Rights (@see \Bitrix\Disk\RightsManager).
	 * @param bool  $generateUniqueName Generates unique name for object in directory.
	 * @throws \Bitrix\Main\ArgumentException
	 * @return null|static|File
	 */
	public function addFile(array $data, array $rights = array(), $generateUniqueName = false)
	{
		$this->errorCollection->clear();

		$this->checkRequiredInputParams($data, array(
			'NAME'
		));

		if($generateUniqueName)
		{
			$data['NAME'] = $this->generateUniqueName($data['NAME'], $this->id);
		}

		if(!$this->isUniqueName($data['NAME'], $this->id))
		{
			$this->errorCollection->add(array(new Error(Loc::getMessage('DISK_FOLDER_MODEL_ERROR_NON_UNIQUE_NAME'), self::ERROR_NON_UNIQUE_NAME)));
			return null;
		}

		$data['PARENT_ID'] = $this->id;
		$data['STORAGE_ID'] = $this->storageId;
		$data['PARENT'] = $this;

		/** @var File $fileModel */
		$fileModel = $this->processAdd($data, $this->errorCollection, $generateUniqueName);
		if ($fileModel)
		{
			Driver::getInstance()->getRightsManager()->setAsNewLeaf($fileModel, $rights);
		}

		return $fileModel;
	}

	private function isDuplicateKeyError(SqlQueryException $exception)
	{
		return mb_strpos($exception->getDatabaseMessage(), '(1062)') !== false;
	}

	private function processAdd(array $data, ErrorCollection $errorCollection, $generateUniqueName = false, $countStepsToGenerateName = 0)
	{
		try
		{
			$fileModel = File::add($data, $errorCollection);
			if(!$fileModel)
			{
				return null;
			}
		}
		catch (SqlQueryException $exception)
		{
			if ($generateUniqueName && $this->isDuplicateKeyError($exception))
			{
				$countStepsToGenerateName++;
				if ($countStepsToGenerateName > 10)
				{
					throw new InvalidOperationException(
						"Too many attempts ({$countStepsToGenerateName}) to generate unique name {$data['NAME']}"
					);
				}

				$data['NAME'] = $this->generateUniqueName($data['NAME'], $this->id);

				return $this->processAdd(
					$data,
					$errorCollection,
					$generateUniqueName,
					$countStepsToGenerateName
				);
			}

			throw $exception;
		}

		return $fileModel;
	}

	/**
	 * Adds link on file in folder.
	 * @param File  $sourceFile Source file.
	 * @param array $data Contains additional fields (CREATED_BY, NAME, etc).
	 * @param array $rights Rights (@see \Bitrix\Disk\RightsManager).
	 * @param bool  $generateUniqueName Generates unique name for object in directory.
	 * @throws \Bitrix\Main\ArgumentException
	 * @return File|null
	 */
	public function addFileLink(File $sourceFile, array $data, array $rights = array(), $generateUniqueName = false)
	{
		$this->errorCollection->clear();

		$data = $this->prepareDataForAddLink($sourceFile, $data, $generateUniqueName);
		if(!$data)
		{
			return null;
		}
		$data['SIZE'] = $sourceFile->getSize();

		/** @var FileLink $fileLinkModel */
		$fileLinkModel = FileLink::add($data, $this->errorCollection);
		if(!$fileLinkModel)
		{
			return null;
		}

		$driver = Driver::getInstance();
		$driver->getRightsManager()->setAsNewLeaf($fileLinkModel, $rights);

		if (!$fileLinkModel->isDeleted())
		{
			$driver->getIndexManager()->indexFile($fileLinkModel);

//			$this->notifySonetGroup($fileLinkModel);
		}

		return $fileLinkModel;
	}

	private function notifySonetGroup(File $fileModel)
	{
		static 	$folderUrlList = array();

		//todo create NotifyManager, which provides notify (not only group)
		if(!Loader::includeModule('socialnetwork'))
		{
			return;
		}

		$storage = $fileModel->getStorage();
		$proxyType = $storage->getProxyType();
		if(!$proxyType instanceof Group)
		{
			return;
		}

		$groupId = (int)$storage->getEntityId();
		if($groupId <= 0)
		{
			return;
		}

		$urlManager = Driver::getInstance()->getUrlManager();
		$fileUrl = $urlManager->getPathFileDetail($fileModel);
		$fileCreatedBy = $fileModel->getCreatedBy();
		$fileName = $fileModel->getName();
		$storageId = $storage->getId();

		$res = ObjectTable::getList(array(
			'filter' => array(
				'STORAGE_ID' => $storageId,
				'CREATED_BY' => $fileCreatedBy,
				'>UPDATE_TIME' => DateTime::createFromTimestamp(time() - 600), // for index
				'>CREATE_TIME' => DateTime::createFromTimestamp(time() - 600),
				'=TYPE' => ObjectTable::TYPE_FILE,
				'!=FILE_ID' => $fileModel->getFileId()
			),
			'select' => array('ID'),
			'limit' => 1
		));

		if (!($res->fetch()))
		{
			$message = Loc::getMessage('DISK_FOLDER_MODEL_IM_NEW_FILE', array(
				'#TITLE#' => '<a href="#URL#" class="bx-notifier-item-action">'.$fileName.'</a>',
			));
			$messageOut = Loc::getMessage('DISK_FOLDER_MODEL_IM_NEW_FILE', array(
					'#TITLE#' => $fileName
				)).' (#URL#)';

			$url = $urlManager->encodeUrn($fileUrl);
		}
		else
		{
			$message = Loc::getMessage('DISK_FOLDER_MODEL_IM_NEW_FILE2', array(
				'#LINK_FOLDER_START#' => '<a href="#URL#" class="bx-notifier-item-action">',
				'#LINK_END#' => '</a>'
			));
			$messageOut = Loc::getMessage('DISK_FOLDER_MODEL_IM_NEW_FILE2', array(
					'#LINK_FOLDER_START#' => '',
					'#LINK_END#' => ''
				)).' (#URL#)';

			if (!array_key_exists($storageId, $folderUrlList))
			{
				$folderUrlList[$storageId] = $proxyType->getBaseUrlFolderList();
			}

			$url = $folderUrlList[$storageId];
		}

		/** @noinspection PhpDynamicAsStaticMethodCallInspection */
		\CSocNetSubscription::notifyGroup(array(
			'LOG_ID' => false,
			'GROUP_ID' => array($groupId),
			'NOTIFY_MESSAGE' => '',
			'NOTIFY_TAG' => 'DISK_GROUP|'.$groupId.'|'.$fileCreatedBy,
			'FROM_USER_ID' => $fileCreatedBy,
			'URL' => $url,
			'MESSAGE' => $message,
			'MESSAGE_OUT' => $messageOut,
			'EXCLUDE_USERS' => array($fileCreatedBy)
		));
	}

	/**
	 * Adds sub-folder in folder.
	 * @param array $data Contains additional fields (CREATED_BY, NAME, etc).
	 * @param array $rights Rights (@see \Bitrix\Disk\RightsManager).
	 * @param bool  $generateUniqueName Generates unique name for object in directory.
	 * @throws \Bitrix\Main\ArgumentException
	 * @return null|static|Folder
	 */
	public function addSubFolder(array $data, array $rights = array(), $generateUniqueName = false)
	{
		$this->errorCollection->clear();

		$this->checkRequiredInputParams($data, array(
			'NAME'
		));

		if($generateUniqueName)
		{
			$data['NAME'] = $this->generateUniqueName($data['NAME'], $this->id);
		}

		if(!$this->isUniqueName($data['NAME'], $this->id))
		{
			$this->errorCollection->add(array(new Error(Loc::getMessage('DISK_FOLDER_MODEL_ERROR_NON_UNIQUE_NAME'), self::ERROR_NON_UNIQUE_NAME)));
			return null;
		}

		$data['PARENT_ID'] = $this->id;
		$data['STORAGE_ID'] = $this->storageId;

		$folderModel = Folder::add($data, $this->errorCollection);
		if(!$folderModel)
		{
			return null;
		}
		$this->changeSelfUpdateTime();
		Driver::getInstance()->getRightsManager()->setAsNewLeaf($folderModel, $rights);

		return $folderModel;
	}

	/**
	 * Adds link on folder in folder.
	 * @param Folder $sourceFolder Original folder.
	 * @param array $data Contains additional fields (CREATED_BY, NAME, etc).
	 * @param array $rights Rights (@see \Bitrix\Disk\RightsManager).
	 * @param bool  $generateUniqueName Generates unique name for object in directory.
	 * @throws \Bitrix\Main\ArgumentException
	 * @return FolderLink|null
	 */
	public function addSubFolderLink(Folder $sourceFolder, array $data, array $rights = array(), $generateUniqueName = false)
	{
		$this->errorCollection->clear();

		$data = $this->prepareDataForAddLink($sourceFolder, $data, $generateUniqueName);
		if(!$data)
		{
			return null;
		}

		/** @var FolderLink $fileLinkModel */
		$fileLinkModel = FolderLink::add($data, $this->errorCollection);
		if(!$fileLinkModel)
		{
			return null;
		}
		$this->changeSelfUpdateTime();
		Driver::getInstance()->getRightsManager()->setAsNewLeaf($fileLinkModel, $rights);

		return $fileLinkModel;
	}

	private function prepareDataForAddLink(BaseObject $object, array $data, $generateUniqueName = false)
	{
		if(empty($data['NAME']))
		{
			$data['NAME'] = $object->getName();
		}

		$this->checkRequiredInputParams($data, array(
			'NAME'
		));

		if($generateUniqueName)
		{
			$data['NAME'] = $this->generateUniqueName($data['NAME'], $this->id);
		}

		if (isset($data['DELETED_TYPE']) && $data['DELETED_TYPE'] == ObjectTable::DELETED_TYPE_ROOT)
		{
			$data['NAME'] = Ui\Text::appendTrashCanSuffix($data['NAME']);
		}

		if(!$this->isUniqueName($data['NAME'], $this->id))
		{
			$this->errorCollection->add(array(new Error(Loc::getMessage('DISK_FOLDER_MODEL_ERROR_NON_UNIQUE_NAME'), self::ERROR_NON_UNIQUE_NAME)));
			return null;
		}

		$data['PARENT_ID'] = $this->id;
		$data['STORAGE_ID'] = $this->storageId;
		$data['REAL_OBJECT_ID'] = $object->getRealObject()->getId();

		return $data;
	}

	/**
	 * Copies object to target folder.
	 * @param Folder $targetFolder Target folder.
	 * @param int    $updatedBy Id of user.
	 * @param bool   $generateUniqueName Generates unique name for object in directory.
	 * @return BaseObject|null
	 */
	public function copyTo(Folder $targetFolder, $updatedBy, $generateUniqueName = false)
	{
		$this->errorCollection->clear();

		if($this->getId() == $targetFolder->getId())
		{
			return $this;
		}

		$newRoot = $this->copyToInternal($targetFolder, $updatedBy, $generateUniqueName);
		if(!$newRoot)
		{
			return null;
		}
		$mapParentToNewParent = array(
			$this->getId() => $newRoot,
		);

		$parameters = array(
			'select' => array(
				'*',
				'DEPTH_LEVEL' => 'PATH_CHILD.DEPTH_LEVEL',
			),
			'filter' => array(
				'DELETED_TYPE' => FolderTable::DELETED_TYPE_NONE,
				'PATH_CHILD.PARENT_ID' => $this->id,
			),
			'order' => array('DEPTH_LEVEL' => 'ASC')
		);

		$objectIterator = FolderTable::getList(static::prepareGetListParameters($parameters));
		foreach ($objectIterator as $objectRow)
		{
			if ($objectRow['ID'] == $this->id)
			{
				continue;
			}

			$item = BaseObject::buildFromArray($objectRow);
			if(!isset($mapParentToNewParent[$item->getParentId()]))
			{
				return null;
			}

			/** @var Folder $newParentFolder */
			$newParentFolder = $mapParentToNewParent[$item->getParentId()];
			if($item instanceof File)
			{
				/** @var \Bitrix\Disk\File $item */
				$item->copyTo($newParentFolder, $updatedBy, $generateUniqueName);
			}
			elseif ($item instanceof Folder)
			{
				/** @var \Bitrix\Disk\Folder $item */
				$newFolder = $item->copyToInternal($newParentFolder, $updatedBy, $generateUniqueName);
				if(!$newFolder)
				{
					continue;
				}
				$mapParentToNewParent[$item->getId()] = $newFolder;
			}
		}

		return $newRoot;
	}

	protected function copyToInternal(Folder $targetFolder, $updatedBy, $generateUniqueName = false)
	{
		$newFolder = $targetFolder->addSubFolder(array(
			'NAME' => $this->getName(),
			'CREATED_BY' => $updatedBy,
		), array(), $generateUniqueName);

		if(!$newFolder)
		{
			$this->errorCollection->add($targetFolder->getErrors());
			return null;
		}
		return $newFolder;
	}

	/**
	 * Gets all descendants objects by the folder.
	 * @param SecurityContext $securityContext Security context.
	 * @param array           $parameters Parameters.
	 * @param int             $orderDepthLevel Order for depth level (default asc).
	 * @throws \Bitrix\Main\ArgumentOutOfRangeException
	 * @return BaseObject[]
	 */
	public function getDescendants(SecurityContext $securityContext, array $parameters = array(), $orderDepthLevel = SORT_ASC)
	{
		if(!isset($parameters['filter']))
		{
			$parameters['filter'] = array();
		}
		if(!isset($parameters['select']))
		{
			$parameters['select'] = array('*');
		}

		if(!empty($parameters['filter']['MIXED_SHOW_DELETED']))
		{
			unset($parameters['filter']['DELETED_TYPE'], $parameters['filter']['MIXED_SHOW_DELETED']);
		}
		elseif (
			!array_key_exists('DELETED_TYPE', $parameters['filter']) &&
			!array_key_exists('!DELETED_TYPE', $parameters['filter']) &&
			!array_key_exists('!=DELETED_TYPE', $parameters['filter']) &&
			!array_key_exists('!==DELETED_TYPE', $parameters['filter'])
		)
		{
			$parameters['filter']['DELETED_TYPE'] = ObjectTable::DELETED_TYPE_NONE;
		}
		$parameters['select']['DEPTH_LEVEL'] = 'PATH_CHILD.DEPTH_LEVEL';
		$parameters = Driver::getInstance()->getRightsManager()->addRightsCheck($securityContext, $parameters, array('ID', 'CREATED_BY'));

		$data = FolderTable::getDescendants($this->id, static::prepareGetListParameters($parameters))->fetchAll();
		if($orderDepthLevel !== null)
		{
			Collection::sortByColumn($data, array('DEPTH_LEVEL' => $orderDepthLevel));
		}

		$modelData = array();
		foreach($data as $item)
		{
			$modelData[] = BaseObject::buildFromArray($item);
		}
		unset($item);

		return $modelData;
	}

	/**
	 * Gets direct children (files, folders).
	 * @param SecurityContext $securityContext Security context.
	 * @param array           $parameters Parameters.
	 * @return BaseObject[]
	 */
	public function getChildren(SecurityContext $securityContext, array $parameters = array())
	{
		if(!isset($parameters['filter']))
		{
			$parameters['filter'] = array();
		}
		if(!empty($parameters['filter']['MIXED_SHOW_DELETED']))
		{
			unset($parameters['filter']['DELETED_TYPE'], $parameters['filter']['MIXED_SHOW_DELETED']);
		}
		elseif (
			!array_key_exists('DELETED_TYPE', $parameters['filter']) &&
			!array_key_exists('!DELETED_TYPE', $parameters['filter']) &&
			!array_key_exists('!=DELETED_TYPE', $parameters['filter']) &&
			!array_key_exists('!==DELETED_TYPE', $parameters['filter'])
		)
		{
			$parameters['filter']['DELETED_TYPE'] = ObjectTable::DELETED_TYPE_NONE;
		}
		$parameters = Driver::getInstance()->getRightsManager()->addRightsCheck($securityContext, $parameters, array('ID', 'CREATED_BY'));

		$modelData = array();
		$query = FolderTable::getChildren($this->id, static::prepareGetListParameters($parameters));
		while($item = $query->fetch())
		{
			$modelData[] = BaseObject::buildFromArray($item);
		}

		return $modelData;
	}

	/**
	 * Returns first child by filter.
	 * @param array $filter Filter.
	 * @param array $with List of eager loading.
	 *
	 * @return Folder|File|BaseObject
	 */
	public function getChild(array $filter, array $with = array())
	{
		$filter['PARENT_ID'] = $this->id;

		return BaseObject::load($filter, $with);
	}

	/**
	 * Counts size of all version under folder. Possible to use security context and filter.
	 * @param SecurityContext|null $securityContext Security context.
	 * @param array                $filter Filter.
	 * @return null|int
	 */
	public function countSizeOfVersions(SecurityContext $securityContext = null, array $filter = array())
	{
		$filter = array(
			'WITH_SYMLINKS' => false,
			'WITH_DELETED' => true,
		);

		$query = new Query(VersionTable::getEntity());
		$query
			->registerRuntimeField('', new ExpressionField('FILE_SIZE', 'SUM(SIZE)'))
			->addSelect('FILE_SIZE')
			->addFilter('=PATH_CHILD.PARENT_ID', $this->id)
		;

		$result = $query->exec();
		$row = $result->fetch();
		if(isset($row['FILE_SIZE']))
		{
			return $row['FILE_SIZE'];
		}

		return null;
	}

	/**
	 * Counts size of head version under folder. Possible to use security context and filter.
	 * @param SecurityContext|null $securityContext Security context.
	 * @param array                $filter Filter.
	 * @return null|int
	 */
	public function countSizeOfFiles(SecurityContext $securityContext = null, array $filter = array())
	{
		return $this->countSizeHeadOfVersions($securityContext, $filter);
	}

	/**
	 * Counts size of head version under folder. Possible to use security context and filter.
	 * @param SecurityContext|null $securityContext Security context.
	 * @param array                $filter Filter.
	 * @return null|int
	 */
	public function countSizeHeadOfVersions(SecurityContext $securityContext = null, array $filter = array())
	{
		$filter = array(
			'WITH_SYMLINKS' => false,
			'WITH_DELETED' => true,
		);

		$query = new Query(FolderTable::getEntity());
		$query
			->registerRuntimeField('', new ExpressionField('FILE_SIZE', 'SUM(SIZE)'))
			->addSelect('FILE_SIZE')
			->addFilter('=PATH_CHILD.PARENT_ID', $this->id)
		;

		$result = $query->exec();
		$row = $result->fetch();
		if(isset($row['FILE_SIZE']))
		{
			return $row['FILE_SIZE'];
		}

		return null;
	}

	/**
	 * Marks deleted object. It equals to move in trash can.
	 * @param int $deletedBy Id of user (or SystemUser::SYSTEM_USER_ID).
	 * @return bool
	 */
	public function markDeleted($deletedBy)
	{
		if ($this->deletedType == ObjectTable::DELETED_TYPE_ROOT)
		{
			return true;
		}

		$this->errorCollection->clear();

		$driver = Driver::getInstance();
		$driver->getSubscriberManager()->preloadSharingsForSubtree($this);

		$parameters = array(
			'select' => array(
				'*',
				'DEPTH_LEVEL' => 'PATH_CHILD.DEPTH_LEVEL',
			),
			'filter' => array(
				'PATH_CHILD.PARENT_ID' => $this->id,
			),
			'order' => array('DEPTH_LEVEL' => 'DESC')
		);

		$success = true;
		$objectIterator = FolderTable::getList(static::prepareGetListParameters($parameters));
		foreach ($objectIterator as $objectRow)
		{
			if ($objectRow['ID'] == $this->id)
			{
				//to modify current object. Don't make another instance of $this->id
				$object = $this;
			}
			else
			{
				$object = BaseObject::buildFromArray($objectRow);
			}

			/** @var Folder|File */
			if($object instanceof Folder)
			{
				$deleteType = ObjectTable::DELETED_TYPE_CHILD;
				if ($objectRow['ID'] == $this->id)
				{
					$deleteType = ObjectTable::DELETED_TYPE_ROOT;
				}

				/** @see \Bitrix\Disk\Folder::markDeletedNonRecursiveInternal */
				$success = $object->markDeletedNonRecursiveInternal($deletedBy, $deleteType);
			}
			elseif($object instanceof File)
			{
				$success = $object->markDeletedInternal($deletedBy, ObjectTable::DELETED_TYPE_CHILD);
			}
		}

		$driver->getDeletedLogManager()->finalize();
		$driver->getDeletionNotifyManager()->send();

		return $success;
	}

	protected function markDeletedNonRecursiveInternal($deletedBy, $deletedType = ObjectTable::DELETED_TYPE_ROOT)
	{
		$alreadyDeleted = $this->isDeleted();
		$success = parent::markDeletedInternal($deletedBy, $deletedType);
		if ($success && !$alreadyDeleted)
		{
			Driver::getInstance()->getDeletedLogManager()->mark($this, $deletedBy);
		}

		return $success;
	}

	/**
	 * Restores object from trash can.
	 * @param int $restoredBy Id of user (or SystemUser::SYSTEM_USER_ID).
	 * @return bool
	 */
	public function restore($restoredBy)
	{
		if (!$this->isDeleted())
		{
			return true;
		}

		$this->errorCollection->clear();

		$parameters = array(
			'select' => array(
				'*',
				'DEPTH_LEVEL' => 'PATH_CHILD.DEPTH_LEVEL',
			),
			'filter' => array(
				'PATH_CHILD.PARENT_ID' => $this->id,
				'!==DELETED_TYPE' => ObjectTable::DELETED_TYPE_NONE,
			),
			'order' => array('DEPTH_LEVEL' => 'DESC')
		);

		$objectIterator = FolderTable::getList(static::prepareGetListParameters($parameters));
		foreach ($objectIterator as $objectRow)
		{
			if ($objectRow['ID'] == $this->id)
			{
				continue;
			}

			$object = BaseObject::buildFromArray($objectRow);

			/** @var Folder|File */
			if($object instanceof Folder)
			{
				/** @see \Bitrix\Disk\Folder::restoreNonRecursive */
				$object->restoreNonRecursive($restoredBy);
			}
			elseif($object instanceof File)
			{
				$object->restoreInternal($restoredBy);
			}
		}

		$needRecalculate = $this->deletedType == ObjectTable::DELETED_TYPE_CHILD;
		$statusRestoreNonRecursive = $this->restoreInternal($restoredBy);
		if($statusRestoreNonRecursive && $needRecalculate)
		{
			$this->recalculateDeletedTypeAfterRestore($restoredBy);
		}

		if($statusRestoreNonRecursive)
		{
			Driver::getInstance()->sendChangeStatusToSubscribers($this);
		}

		return $statusRestoreNonRecursive;
	}

	protected function restoreNonRecursive($restoredBy)
	{
		return parent::restoreInternal($restoredBy);
	}

	/**
	 * Deletes folder and all descendants objects.
	 * @param int $deletedBy Id of user (or SystemUser::SYSTEM_USER_ID).
	 * @throws \Bitrix\Main\ArgumentException
	 * @throws \Bitrix\Main\ArgumentNullException
	 * @return bool
	 */
	public function deleteTree($deletedBy)
	{
		$this->errorCollection->clear();

		$parameters = array(
			'select' => array(
				'*',
				'DEPTH_LEVEL' => 'PATH_CHILD.DEPTH_LEVEL',
			),
			'filter' => array(
				'PATH_CHILD.PARENT_ID' => $this->id,
			),
			'order' => array('DEPTH_LEVEL' => 'DESC')
		);

		$success = true;
		$objectIterator = FolderTable::getList(static::prepareGetListParameters($parameters));
		foreach ($objectIterator as $objectRow)
		{
			if ($objectRow['ID'] == $this->id)
			{
				//to modify current object. Don't make another instance of $this->id
				$object = $this;
			}
			else
			{
				$object = BaseObject::buildFromArray($objectRow);
			}

			/** @var Folder|File */
			if($object instanceof Folder)
			{
				/** @see \Bitrix\Disk\Folder::deleteNonRecursive */
				$success = $object->deleteNonRecursive($deletedBy);
			}
			elseif($object instanceof File)
			{
				/** @see \Bitrix\Disk\File::delete */
				$success = $object->delete($deletedBy);
			}
		}

		return $success;
	}

	protected function deleteNonRecursive($deletedBy)
	{
		foreach($this->getSharingsAsReal() as $sharing)
		{
			$sharing->delete($deletedBy);
		}

		//with status unreplied, declined (not approved)
		$success = SharingTable::deleteByFilter(array(
			'REAL_OBJECT_ID' => $this->id,
		));

		if(!$success)
		{
			return false;
		}

		SimpleRightTable::deleteBatch(array('OBJECT_ID' => $this->id));

		$success = RightTable::deleteByFilter(array(
			'OBJECT_ID' => $this->id,
		));

		if(!$success)
		{
			return false;
		}
		Driver::getInstance()->getIndexManager()->dropIndex($this);
		Driver::getInstance()->getDeletedLogManager()->mark($this, $deletedBy);

		$resultDelete = FolderTable::delete($this->id);
		if(!$resultDelete->isSuccess())
		{
			return false;
		}

		if(!$this->isLink())
		{
			//todo potential - very hard operation.
			foreach(Folder::getModelList(array('filter' => array('REAL_OBJECT_ID' => $this->id, '!=REAL_OBJECT_ID' => $this->id))) as $link)
			{
				$link->deleteTree($deletedBy);
			}
			unset($link);
		}

		$event = new Event(Driver::INTERNAL_MODULE_ID, "onAfterDeleteFolder", array($this->getId(), $deletedBy));
		$event->send();

		return true;
	}

	/**
	 * Returns size of all files with only head version.
	 * Without symbolic links.
	 *
	 * @param null $filter
	 *
	 * @return int|null
	 */
	public function getSize($filter = null)
	{
		$query = new Query(FileTable::getEntity());
		$query
			->registerRuntimeField('', new ExpressionField('FILE_SIZE', 'SUM(SIZE)'))
			->addSelect('FILE_SIZE')
			->addFilter('=PATH_CHILD.PARENT_ID', $this->getRealObjectId())
			->addFilter('=STORAGE_ID', $this->getStorageId())
			->addFilter('=TYPE', FileTable::TYPE)
			->addFilter('=DELETED_TYPE', FileTable::DELETED_TYPE_NONE)
		;

		$result = $query->exec();
		$row = $result->fetch();
		if (isset($row['FILE_SIZE']))
		{
			return $row['FILE_SIZE'];
		}

		return null;
	}


	/**
	 * Returns the list of pair for mapping.
	 * Key is field in DataManager, value is object property.
	 * @return array
	 */
	public static function getMapAttributes()
	{
		static $shelve = null;
		if($shelve !== null)
		{
			return $shelve;
		}

		$shelve = array_merge(parent::getMapAttributes(), array(
			'HAS_SUBFOLDERS' => 'hasSubFolders',
		));

		return $shelve;
	}
}

/**
 * Class SpecificFolder
 * @package Bitrix\Disk
 * @internal
 */
final class SpecificFolder
{
	const CODE_FOR_CREATED_FILES  = 'FOR_CREATED_FILES';
	const CODE_FOR_SAVED_FILES    = 'FOR_SAVED_FILES';
	const CODE_FOR_UPLOADED_FILES = 'FOR_UPLOADED_FILES';
	const CODE_FOR_RECORDED_FILES = 'FOR_RECORDED_FILES';

	const CODE_FOR_IMPORT_DROPBOX  = 'FOR_DROPBOX_FILES';
	const CODE_FOR_IMPORT_ONEDRIVE = 'FOR_ONEDRIVE_FILES';
	const CODE_FOR_IMPORT_GDRIVE   = 'FOR_GDRIVE_FILES';
	const CODE_FOR_IMPORT_BOX      = 'FOR_BOX_FILES';
	const CODE_FOR_IMPORT_YANDEX   = 'FOR_YANDEXDISK_FILES';

	/**
	 * Gets name for specific folder by code. If code is invalid, then return null.
	 * @param string $code Code of specific folder.
	 * @return null|string
	 */
	public static function getName($code)
	{
		$codes = static::getCodes();
		if(!isset($codes[$code]))
		{
			return null;
		}
		return Loc::getMessage("DISK_FOLDER_SPECIFIC_{$code}_NAME");
	}

	/**
	 * Gets specific folder in storage by code. If folder does not exist, creates it.
	 * @param Storage $storage Target storage.
	 * @param string $code Code of specific folder.
	 * @return Folder|null|static
	 */
	public static function getFolder(Storage $storage, $code)
	{
		$folder = Folder::load(array(
			'=CODE' => $code,
			'STORAGE_ID' => $storage->getId(),
		));
		if($folder)
		{
			return $folder;
		}

		return static::createFolder($storage, $code);
	}

	protected static function createFolder(Storage $storage, $code)
	{
		$name = static::getName($code);
		if(!$name)
		{
			return null;
		}

		if($storage->getProxyType() instanceof ProxyType\User)
		{
			$createdBy = $storage->getEntityId();
		}
		else
		{
			$createdBy = SystemUser::SYSTEM_USER_ID;
		}

		if(static::shouldBeUnderUploadedFolder($code))
		{
			$folderForUploadedFiles = $storage->getFolderForUploadedFiles();
			if(!$folderForUploadedFiles)
			{
				return null;
			}
			return $folderForUploadedFiles->addSubFolder(array(
				'NAME' => $name,
				'CODE' => $code,
				'CREATED_BY' => $createdBy
			), array(), true);
		}

		return $storage->addFolder(array(
			'NAME' => $name,
			'CODE' => $code,
			'CREATED_BY' => $createdBy
		), array(), true);
	}

	protected static function shouldBeUnderUploadedFolder($code)
	{
		return
			static::CODE_FOR_IMPORT_DROPBOX === $code ||
			static::CODE_FOR_IMPORT_ONEDRIVE === $code ||
			static::CODE_FOR_IMPORT_YANDEX === $code ||
			static::CODE_FOR_IMPORT_BOX === $code ||
			static::CODE_FOR_IMPORT_GDRIVE === $code;
	}

	protected static function getCodes()
	{
		static $codes = null;
		if($codes !== null)
		{
			return $codes;
		}
		$refClass = new \ReflectionClass(__CLASS__);
		foreach($refClass->getConstants() as $name => $value)
		{
			if(mb_substr($name, 0, 4) === 'CODE')
			{
				$codes[$value] = $value;
			}
		}
		unset($name, $value);

        return $codes;
	}
}