Your IP : 18.189.14.251
<?php
namespace Bitrix\Disk;
use Bitrix\Disk\Internals\Entity\ModelSynchronizer;
use Bitrix\Disk\Internals\Error\Error;
use Bitrix\Disk\Internals\ObjectPathTable;
use Bitrix\Disk\Internals\ObjectTable;
use Bitrix\Disk\Internals\SharingTable;
use Bitrix\Disk\Security\SecurityContext;
use Bitrix\Disk\Ui\Avatar;
use Bitrix\Main\Application;
use Bitrix\Main\ArgumentTypeException;
use Bitrix\Main\Entity\AddResult;
use Bitrix\Main\Entity\Result;
use Bitrix\Main\Event;
use Bitrix\Main\InvalidOperationException;
use Bitrix\Main\Loader;
use Bitrix\Main\Localization\Loc;
use Bitrix\Main\Type\Collection;
use Bitrix\Main\Type\DateTime;
use Bitrix\Main\UserTable;
Loc::loadMessages(__FILE__);
abstract class BaseObject extends Internals\Model implements \JsonSerializable
{
const ERROR_NON_UNIQUE_NAME = 'DISK_OBJ_22000';
const ERROR_RESTORE_UNDER_LINK_WRONG_TYPE = 'DISK_OBJ_22001';
/** @var string */
protected $name;
/** @var string */
protected $label;
/** @var string */
protected $code;
/** @var string */
protected $xmlId;
/** @var int */
protected $storageId;
/** @var Storage */
protected $storage;
/** @var int */
protected $type;
/** @var int */
protected $realObjectId;
/** @var BaseObject */
protected $realObject;
/** @var int */
protected $parentId;
/** @var Folder */
protected $parent;
/** @var Document\CloudImport\Entry */
protected $lastCloudImport;
/** @var string */
protected $contentProvider;
/** @var int */
protected $deletedType;
/** @var ObjectLock */
protected $lock;
/** @var ObjectTtl */
protected $ttl;
/** @var DateTime */
protected $createTime;
/** @var DateTime */
protected $updateTime;
/** @var DateTime */
protected $syncUpdateTime;
/** @var DateTime */
protected $deleteTime;
/** @var int */
protected $createdBy;
/** @var User */
protected $createUser;
/** @var int */
protected $updatedBy;
/** @var User */
protected $updateUser;
/** @var int */
protected $deletedBy;
/** @var User */
protected $deleteUser;
/**
* Returns the fully qualified name of table class which belongs to current model.
* @throws \Bitrix\Main\NotImplementedException
* @return string
*/
public static function getTableClassName()
{
return ObjectTable::className();
}
/**
* Checks rights to change rights on current object.
* @param SecurityContext $securityContext Security context.
* @return bool
*/
public function canChangeRights(SecurityContext $securityContext)
{
return $securityContext->canChangeRights($this->id);
}
/**
* Checks rights to delete current object.
* @param SecurityContext $securityContext Security context.
* @return bool
*/
public function canDelete(SecurityContext $securityContext)
{
return $securityContext->canDelete($this->id);
}
/**
* Checks rights to mark deleted current object.
* @param SecurityContext $securityContext Security context.
* @return bool
*/
public function canMarkDeleted(SecurityContext $securityContext)
{
return $securityContext->canMarkDeleted($this->id);
}
/**
* Checks rights to move current object in new destination object.
* @param SecurityContext $securityContext Security context.
* @param BaseObject $targetObject New destination object.
* @return bool
*/
public function canMove(SecurityContext $securityContext, BaseObject $targetObject)
{
return $securityContext->canMove($this->id, $targetObject->getRealObjectId());
}
/**
* Checks rights to read current object.
* @param SecurityContext $securityContext Security context.
* @return bool
*/
public function canRead(SecurityContext $securityContext)
{
return $securityContext->canRead($this->id);
}
/**
* Checks rights to rename current object.
* @param SecurityContext $securityContext Security context.
* @return bool
*/
public function canRename(SecurityContext $securityContext)
{
return $securityContext->canRename($this->id);
}
/**
* Checks rights to restore current object from trash can.
* @param SecurityContext $securityContext Security context.
* @return bool
*/
public function canRestore(SecurityContext $securityContext)
{
return
$securityContext->canRestore($this->id) ||
(
$securityContext->canMarkDeleted($this->id) &&
$this->deletedBy == $securityContext->getUserId() &&
$this->deletedBy
)
;
}
/**
* Checks rights to share current object.
* @param SecurityContext $securityContext Security context.
* @return bool
*/
public function canShare(SecurityContext $securityContext)
{
return $securityContext->canShare($this->id);
}
/**
* Checks rights to update (content) current object.
* @param SecurityContext $securityContext Security context.
* @return bool
*/
public function canUpdate(SecurityContext $securityContext)
{
return $securityContext->canUpdate($this->id);
}
/**
* Checks rights to update current object by cloud import.
* @param SecurityContext $securityContext Security context.
* @return bool
*/
public function canUpdateByCloudImport(SecurityContext $securityContext)
{
return
$this->getContentProvider() &&
$this->getCreatedBy() == $securityContext->getUserId() &&
$securityContext->canUpdate($this->id)
;
}
/**
* Checks rights to lock (content) the object.
* @param SecurityContext $securityContext Security context.
* @return bool
*/
public function canLock(SecurityContext $securityContext)
{
return $securityContext->canUpdate($this->id);
}
/**
* Checks rights to unlock (content) the object.
* @param SecurityContext $securityContext Security context.
* @return bool
*/
public function canUnlock(SecurityContext $securityContext)
{
return $securityContext->canUpdate($this->id);
}
/**
* Returns time of create object.
* @return DateTime
*/
public function getCreateTime()
{
return $this->createTime;
}
/**
* Returns id of user, who created object.
* @return int
*/
public function getCreatedBy()
{
return $this->createdBy;
}
/**
* Returns user model, who created object.
* @return User
*/
public function getCreateUser()
{
if($this->isLoadedAttribute('createUser') && $this->createUser && $this->createdBy == $this->createUser->getId())
{
return $this->createUser;
}
$this->createUser = User::getModelForReferenceField($this->createdBy, $this->createUser);
$this->setAsLoadedAttribute('createUser');
return $this->createUser;
}
/**
* Returns time of delete object.
* @return DateTime
*/
public function getDeleteTime()
{
return $this->deleteTime;
}
/**
* Returns id of user, who deleted object.
* @return int
*/
public function getDeletedBy()
{
return $this->deletedBy;
}
/**
* Returns deleted type (@see ObjectTable).
* @return int
*/
public function getDeletedType()
{
return $this->deletedType;
}
/**
* Returns user model, who deleted object.
* @return User
*/
public function getDeleteUser()
{
if($this->isLoadedAttribute('deleteUser') && $this->deleteUser && $this->deletedBy == $this->deleteUser->getId())
{
return $this->deleteUser;
}
$this->deleteUser = User::getModelForReferenceField($this->deletedBy, $this->deleteUser);
$this->setAsLoadedAttribute('deleteUser');
return $this->deleteUser;
}
/**
* Returns real object of object.
*
* For example if object is link (@see FolderLink, @see FileLink), then method returns original object.
* @return BaseObject|Folder|File
*/
public function getRealObject()
{
if(!$this->isLink())
{
return $this;
}
if($this->isLoadedAttribute('realObject') && $this->realObject && $this->realObjectId === $this->realObject->getId())
{
return $this->realObject;
}
$this->realObject = BaseObject::loadById($this->realObjectId);
$this->setAsLoadedAttribute('realObject');
return $this->realObject;
}
/**
* Returns id of real object of object.
*
* For example if object is link (@see FolderLink, @see FileLink), then method returns id of original object.
* @return int
*/
public function getRealObjectId()
{
return $this->isLink()? $this->realObjectId : $this->id;
}
/**
* Returns true if object is moved in trash can.
* @return boolean
*/
public function isDeleted()
{
return $this->deletedType != ObjectTable::DELETED_TYPE_NONE;
}
/**
* Returns name of object.
* Be careful: the method returns name without possible trash can suffix.
* @return string
*/
public function getName()
{
if($this->label === null)
{
$this->label = $this->getNameWithoutTrashCanSuffix();
}
return $this->label;
}
/**
* Returns original name of object.
* May contains trash can suffix.
* @return string
*/
public function getOriginalName()
{
return $this->name;
}
protected function getNameWithTrashCanSuffix()
{
return Ui\Text::appendTrashCanSuffix($this->name);
}
protected function getNameWithoutTrashCanSuffix()
{
return Ui\Text::cleanTrashCanSuffix($this->name);
}
/**
* Returns code of object.
* Code is used for working with object by symbolic name.
* @return string
*/
public function getCode()
{
return $this->code;
}
/**
* Returns xml id of object.
* @return string
*/
public function getXmlId()
{
return $this->xmlId;
}
/**
* Returns id of parent object.
* If object is root, then method returns null.
* @return int
*/
public function getParentId()
{
return $this->parentId;
}
/**
* Returns content provider of object.
* Content provider determines service which provided object (for ex. Dropbox).
* @return string
*/
public function getContentProvider()
{
return $this->contentProvider;
}
/**
* Returns id of storage.
* @return int
*/
public function getStorageId()
{
return $this->storageId;
}
/**
* Returns storage model.
* @return Storage|null
*/
public function getStorage()
{
if(!$this->storageId)
{
return null;
}
if($this->isLoadedAttribute('storage') && $this->storage && $this->storageId == $this->storage->getId())
{
return $this->storage;
}
$this->storage = Storage::loadById($this->storageId, array('ROOT_OBJECT'));
$this->setAsLoadedAttribute('storage');
return $this->storage;
}
/**
* Returns type (folder or file).
* @see ObjectTable::TYPE_FOLDER, ObjectTable::TYPE_FILE.
* @return int
*/
public function getType()
{
return $this->type;
}
/**
* Returns size in bytes.
*
* @param null $filter
*
* @return int
*/
abstract public function getSize($filter = null);
/**
* Returns time of update object. If not set returns create time.
* @return DateTime
*/
public function getUpdateTime()
{
return $this->updateTime?: $this->createTime;
}
/**
* Returns sync time object.
* @return DateTime
*/
public function getSyncUpdateTime()
{
return $this->syncUpdateTime;
}
/**
* Returns id of user, who updated object.
* @return int
*/
public function getUpdatedBy()
{
return $this->updatedBy;
}
/**
* Returns user model, who created object.
* @return User
*/
public function getUpdateUser()
{
if($this->isLoadedAttribute('updateUser') && $this->updateUser && $this->updatedBy == $this->updateUser->getId())
{
return $this->updateUser;
}
$this->updateUser = User::getModelForReferenceField($this->updatedBy, $this->updateUser);
$this->setAsLoadedAttribute('updateUser');
return $this->updateUser;
}
/**
* Adds external link.
* @param array $data Data to create new external link (@see ExternalLink).
* @return bool|ExternalLink
* @throws \Bitrix\Main\ArgumentException
*/
public function addExternalLink(array $data)
{
$this->errorCollection->clear();
$data['OBJECT_ID'] = $this->id;
return ExternalLink::add($data, $this->errorCollection);
}
/**
* Returns external links by file.
* @param array $parameters Parameters.
* @return ExternalLink[]
*/
public function getExternalLinks(array $parameters = array())
{
if (!isset($parameters['filter']))
{
$parameters['filter'] = array();
}
$parameters['filter']['OBJECT_ID'] = $this->id;
if (!isset($parameters['order']))
{
$parameters['order'] = array(
'CREATE_TIME' => 'DESC',
);
}
return ExternalLink::getModelList($parameters);
}
/**
* Tells if object is a link on another object.
* @return bool
*/
public function isLink()
{
return isset($this->realObjectId) && $this->realObjectId != $this->id;
}
/**
* Returns last cloud import entry of object.
* @see Document\CloudImport\ImportManager, @see Document\CloudImport\Entry.
* @return Document\CloudImport\Entry|
*/
public function getLastCloudImportEntry()
{
if($this->lastCloudImport === null)
{
$lastCloudImport = Document\CloudImport\Entry::getModelList(array(
'filter' => array(
'OBJECT_ID' => $this->getRealObjectId(),
),
'order' => array(
'ID' => 'DESC',
),
'limit' => 1,
));
if(!$lastCloudImport)
{
return null;
}
$this->lastCloudImport = array_pop($lastCloudImport);
}
return $this->lastCloudImport;
}
/**
* Renames object.
* @param string $newName New name.
* @param bool $generateUniqueName Generates unique name for object in directory.
* @return bool
* @internal
*/
public function renameInternal($newName, $generateUniqueName = false)
{
$this->errorCollection->clear();
if(!$newName)
{
$this->errorCollection->addOne(new Error('Empty name.'));
return false;
}
if($this->name === $newName)
{
return true;
}
if($generateUniqueName)
{
$newName = $this->generateUniqueName($newName, $this->getParentId());
}
$opponentId = null;
if(!$this->isUniqueName($newName, $this->parentId, null, $opponentId))
{
if($opponentId != $this->id)
{
$this->errorCollection->add(array(new Error(Loc::getMessage('DISK_OBJECT_MODEL_ERROR_NON_UNIQUE_NAME'), self::ERROR_NON_UNIQUE_NAME)));
return false;
}
}
$oldName = $this->name;
$success = $this->update(array('NAME' => $newName, 'SYNC_UPDATE_TIME' => new DateTime()));
if(!$success)
{
return false;
}
$this->label = null;
Driver::getInstance()->getIndexManager()->changeName($this);
Driver::getInstance()->sendChangeStatusToSubscribers($this, 'quick');
$event = new Event(Driver::INTERNAL_MODULE_ID, "onAfterRenameObject", array($this, $oldName, $newName));
$event->send();
return true;
}
/**
* Renames object.
* @param string $newName New name.
* @return bool
*/
public function rename($newName)
{
$success = $this->renameInternal($newName, false);
if($success)
{
$this->changeParentUpdateTime();
}
return $success;
}
/**
* Changes field update time of parent.
* @param DateTime|null $datetime Datetime.
* @return bool
*/
protected function changeParentUpdateTime(DateTime $datetime = null, $updatedBy = null)
{
$parent = $this->getParent();
if (!$parent)
{
return false;
}
$data = [
'UPDATE_TIME' => $datetime ? : new DateTime(),
];
if ($updatedBy)
{
$data['UPDATED_BY'] = $updatedBy;
}
return $parent->update($data);
}
/**
* Changes field update time.
* @param DateTime|null $datetime Datetime.
* @return bool
*/
protected function changeSelfUpdateTime(DateTime $datetime = null)
{
return $this->update(array(
'UPDATE_TIME' => $datetime?: new DateTime(),
));
}
/**
* Changes xml id on current element.
* @param string $newXmlId New xml id.
* @return bool
*/
public function changeXmlId($newXmlId)
{
return $this->update(array('XML_ID' => $newXmlId));
}
/**
* Changes code on current element.
* @param string $newCode New code.
* @return bool
*/
public function changeCode($newCode)
{
return $this->update(array('CODE' => $newCode));
}
/**
* 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
*/
abstract public function copyTo(Folder $targetFolder, $updatedBy, $generateUniqueName = false);
/**
* Moves object to another folder.
* Support cross-storage move (mark deleted + create new)
* @param Folder $folder Destination folder.
* @param int $movedBy User id of user, which move file.
* @param bool $generateUniqueName Generates unique name for object if in destination directory.
* @return BaseObject|null
*/
public function moveTo(Folder $folder, $movedBy, $generateUniqueName = false)
{
$this->errorCollection->clear();
if($this->getId() == $folder->getId())
{
return $this;
}
if (!$this->getParentId() && $this instanceof Folder)
{
return $this;
}
$realStorageIdSource = $this->getStorageId();
$realStorageIdTarget = $folder->getRealObject()->getStorageId();
$realFolderId = $folder->getRealObject()->getId();
if($this->getParentId() == $realFolderId)
{
return $this;
}
$ancestors = ObjectPathTable::getAncestors($folder->getId());
if (in_array($this->getRealObjectId(), array_column($ancestors, 'PARENT_ID'), true))
{
$this->errorCollection[] = new Error(Loc::getMessage('DISK_OBJECT_MODEL_INVALID_MOVEMENT_TO_CHILD'));
return null;
}
$possibleNewName = $this->name;
if($generateUniqueName)
{
$possibleNewName = $this->generateUniqueName($this->name, $realFolderId);
}
$needToRename = $possibleNewName != $this->name;
if(!$this->isUniqueName($possibleNewName, $realFolderId))
{
$this->errorCollection->add(array(new Error(Loc::getMessage('DISK_OBJECT_MODEL_ERROR_NON_UNIQUE_NAME'), self::ERROR_NON_UNIQUE_NAME)));
return null;
}
$this->name = $possibleNewName;
if($needToRename)
{
$successUpdate = $this->update(array(
'NAME' => $possibleNewName
));
if(!$successUpdate)
{
return null;
}
}
//simple move
if($realStorageIdSource == $realStorageIdTarget)
{
$object = $this->moveInSameStorage($folder, $movedBy);
}
else
{
$object = $this->moveInAnotherStorage($folder, $movedBy);
}
if($object !== null)
{
$folder->changeSelfUpdateTime();
$event = new Event(Driver::INTERNAL_MODULE_ID, "onAfterMoveObject", array($this));
$event->send();
}
return $object;
}
/**
* @param Folder $folder
* @param int $movedBy
* @return $this|null
* @throws \Bitrix\Main\ArgumentException
*/
protected function moveInSameStorage(Folder $folder, $movedBy)
{
$driver = Driver::getInstance();
$subscriberManager = $driver->getSubscriberManager();
$subscribersBeforeMove = $subscriberManager->collectSubscribersSmart($this);
$realFolderId = $folder->getRealObject()->getId();
/** @var ObjectTable $tableClassName */
$tableClassName = $this->getTableClassName();
$moveResult = $tableClassName::move($this->id, $realFolderId);
if(!$moveResult->isSuccess())
{
$this->errorCollection->addFromResult($moveResult);
return null;
}
$this->setAttributesFromResult($moveResult);
$driver->getRightsManager()->setAfterMove($this);
$subscribersAfterMove = $subscriberManager->collectSubscribersSmart($this);
$driver->getDeletedLogManager()->markAfterMove(
$this,
array_unique(array_diff($subscribersBeforeMove, $subscribersAfterMove)),
$movedBy
);
//notify new subscribers (in DeletedLog we notify subscribers only missed access)
if($this instanceof Folder)
{
$driver->cleanCacheTreeBitrixDisk(array_keys($subscribersAfterMove));
}
$driver->sendChangeStatus($subscribersAfterMove);
ObjectTable::updateSyncTime($this->id, new DateTime());
$driver->sendChangeStatus($subscriberManager->collectSubscribersFromSubtree($this));
return $this;
}
/**
* Simple logic: create copy in another storage and move in trash can.
* If we have problem - stop.
* Return new object.
* @param Folder $targetFolder
* @param int $movedBy
* @return null|BaseObject
*/
protected function moveInAnotherStorage(Folder $targetFolder, $movedBy)
{
$newObject = $this->copyTo($targetFolder, $movedBy);
if(!$newObject)
{
return null;
}
$newObject->update([
'UPDATE_TIME' => $this->getUpdateTime(),
'CREATE_TIME' => $this->getCreateTime(),
]);
if($newObject->getErrors())
{
$this->errorCollection->add($newObject->getErrors());
return $newObject;
}
$rightsManager = Driver::getInstance()->getRightsManager();
$specificRights = $rightsManager->getSpecificRights($this);
$rightsManager->set($newObject, $specificRights);
$this->markDeleted($movedBy);
return $newObject;
}
/**
* Builds model from array.
* @param array $attributes Model attributes.
* @param array &$aliases Aliases.
* @internal
* @return static
*/
public static function buildFromArray(array $attributes, array &$aliases = null)
{
/** @var BaseObject $className */
$className = static::getClassNameModel($attributes);
/** @var BaseObject $model */
$model = new $className;
return $model->setAttributes($attributes, $aliases);
}
/**
* Builds model from \Bitrix\Main\Entity\Result.
* @param Result $result Query result.
* @return static
* @throws \Bitrix\Main\ArgumentException
*/
public static function buildFromResult(Result $result)
{
$data = $result->getData();
if($result instanceof AddResult)
{
$data['ID'] = $result->getId();
}
$className = static::getClassNameModel($data);
/** @var BaseObject $model */
$model = new $className;
return $model->setAttributesFromResult($result);
}
protected static function getClassNameModel(array $row)
{
if(!isset($row['ID']))
{
throw new ArgumentTypeException('Invalid ID');
}
if(!isset($row['TYPE']))
{
throw new ArgumentTypeException('Invalid TYPE');
}
if(empty($row['REAL_OBJECT_ID']) || $row['REAL_OBJECT_ID'] == $row['ID'])
{
if($row['TYPE'] == ObjectTable::TYPE_FILE)
{
return File::className();
}
return Folder::className();
}
if($row['TYPE'] == ObjectTable::TYPE_FILE)
{
return FileLink::className();
}
return FolderLink::className();
}
/**
* 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())
{
$objectData = static::getList(array(
'with' => $with,
'filter'=> $filter,
'limit' => 1,
))->fetch();
if(empty($objectData))
{
return null;
}
/** @var BaseObject $className */
$className = static::getClassNameModel($objectData);
return $className::buildFromArray($objectData);
}
/**
* Marks deleted object. It equals to move in trash can.
* @param int $deletedBy Id of user (or SystemUser::SYSTEM_USER_ID).
* @return bool
*/
abstract public function markDeleted($deletedBy);
/**
* Restores object from trash can.
* @param int $restoredBy Id of user (or SystemUser::SYSTEM_USER_ID).
* @return bool
*/
abstract public function restore($restoredBy);
/**
* Restores object under link and destroy itself.
* It's special case. @see \Bitrix\Disk\BaseObject::markDeletedInternal.
* There we put file/folder in original trash can and in trash can of user, who delete the object.
*
* @param int $restoredBy Id of user (or SystemUser::SYSTEM_USER_ID).
* @return bool
*/
protected function restoreByLinkObject($restoredBy)
{
if ($this->deletedType != ObjectTable::DELETED_TYPE_ROOT)
{
$this->errorCollection[] = new Error(
"It's not possible to restore object, which was deleted with TYPE_CHILD",
self::ERROR_RESTORE_UNDER_LINK_WRONG_TYPE
);
return false;
}
$realObject = $this->getRealObject();
if (!$realObject)
{
$this->errorCollection[] = new Error('Could not find real object of link to restore it');
return false;
}
$status = $realObject->restore($restoredBy);
$this->deleteInternal();
return $status;
}
/**
* @param int $deletedBy
* @param int $deletedType
* @throws \Bitrix\Main\ArgumentException
* @return bool
*/
protected function markDeletedInternal($deletedBy, $deletedType = ObjectTable::DELETED_TYPE_ROOT)
{
if ($this->deletedType == $deletedType)
{
return true;
}
$this->errorCollection->clear();
$subscriberManager = Driver::getInstance()->getSubscriberManager();
foreach($subscriberManager->getSharingsByObject($this) as $sharing)
{
$sharing->delete($deletedBy);
}
//with status unreplied, declined (not approved)
SharingTable::deleteByFilter(array(
'REAL_OBJECT_ID' => $this->id,
));
if ($deletedType == ObjectTable::DELETED_TYPE_CHILD)
{
$nameAfterDelete = $this->getNameWithoutTrashCanSuffix();
}
elseif ($deletedType == ObjectTable::DELETED_TYPE_ROOT)
{
//we want to delete object as root. It means it has to have unique name. In this way we clean the name,
//and after we make it unique.
$nameAfterDelete = Ui\Text::appendTrashCanSuffix($this->getNameWithoutTrashCanSuffix());
}
else
{
$nameAfterDelete = $this->getNameWithoutTrashCanSuffix();
}
if (!static::isUniqueName($nameAfterDelete, $this->getParentId(), $this->getId()))
{
$nameAfterDelete = $this->generateUniqueName($nameAfterDelete, $this->getParentId());
}
$data = [
'CODE' => null,
'NAME' => $nameAfterDelete,
'DELETED_TYPE' => $deletedType,
];
$alreadyDeleted = $this->isDeleted();
if (!$alreadyDeleted)
{
$data['DELETE_TIME'] = new DateTime();
$data['DELETED_BY'] = $deletedBy;
}
$status = $this->update($data);
if ($status)
{
$driver = Driver::getInstance();
$driver->getDeletionNotifyManager()->put($this, $deletedBy);
$driver->getIndexManager()->dropIndexByModuleSearch($this);
$event = new Event(Driver::INTERNAL_MODULE_ID, "onAfterMarkDeletedObject", array($this, $deletedBy, $deletedType));
$event->send();
}
if ($deletedType == ObjectTable::DELETED_TYPE_ROOT)
{
$this->createLinkInTrashcan($deletedBy);
}
return $status;
}
private function createLinkInTrashcan($deletedBy)
{
$userStorage = Driver::getInstance()->getStorageByUserId($deletedBy);
if (!$userStorage || $userStorage->getId() == $this->getStorageId())
{
return;
}
if ($this instanceof File)
{
$userStorage->addFileLink(
$this,
array(
'CREATED_BY' => $deletedBy,
'DELETED_BY' => $deletedBy,
'DELETED_TYPE' => ObjectTable::DELETED_TYPE_ROOT,
'DELETE_TIME' => new DateTime(),
)
);
}
elseif ($this instanceof Folder)
{
$userStorage->addFolderLink(
$this,
array(
'CREATED_BY' => $deletedBy,
'DELETED_BY' => $deletedBy,
'DELETED_TYPE' => ObjectTable::DELETED_TYPE_ROOT,
'DELETE_TIME' => new DateTime(),
)
);
}
}
/**
* Restores object from trash can.
* @param int $restoredBy Id of user (or SystemUser::SYSTEM_USER_ID).
* @return bool
* @internal
*/
public function restoreInternal($restoredBy)
{
if(!$this->isUniqueName($this->getNameWithoutTrashCanSuffix(), $this->parentId, $this->id))
{
$this->name = $this->generateUniqueName($this->getNameWithoutTrashCanSuffix(), $this->parentId);
}
/** @var ObjectTable $tableClassName */
$tableClassName = $this->getTableClassName();
$status = $this->update(array(
'NAME' => $this->getNameWithoutTrashCanSuffix(),
'DELETED_TYPE' => $tableClassName::DELETED_TYPE_NONE,
'UPDATE_TIME' => new DateTime(),
'UPDATED_BY' => $restoredBy,
));
if($status)
{
//we have to delete links which are in trashcan. It's special case. @see \Bitrix\Disk\BaseObject::markDeletedInternal.
$links = BaseObject::getModelList(array(
'filter' => array(
'REAL_OBJECT_ID' => $this->id,
'!=DELETED_TYPE' => ObjectTable::DELETED_TYPE_NONE,
)
));
foreach($links as $link)
{
if ($link instanceof FileLink)
{
$link->delete($restoredBy);
}
elseif ($link instanceof FolderLink)
{
$link->deleteTree($restoredBy);
}
}
$event = new Event(Driver::INTERNAL_MODULE_ID, "onAfterRestoreObject", array($this, $restoredBy));
$event->send();
}
return $status;
}
protected function recalculateDeletedTypeAfterRestore($restoredBy)
{
$fakeContext = Storage::getFakeSecurityContext();
$parents = $this->getParents($fakeContext, ['filter' => ['MIXED_SHOW_DELETED' => true]], SORT_ASC);
foreach ($parents as $parent)
{
if(!$parent instanceof Folder || !$parent->isDeleted())
{
continue;
}
/** @var $parent Folder */
foreach ($parent->getChildren($fakeContext, array('filter' => array('!==DELETED_TYPE' => ObjectTable::DELETED_TYPE_NONE,))) as $childPotentialRoot)
{
if($childPotentialRoot->getId() == $this->getId())
{
continue;
}
if($childPotentialRoot instanceof Folder)
{
/** @var $childPotentialRoot Folder */
$childPotentialRoot->markDeletedNonRecursiveInternal($childPotentialRoot->getDeletedBy());
}
elseif($childPotentialRoot instanceof File)
{
$childPotentialRoot->markDeletedInternal($childPotentialRoot->getDeletedBy());
}
}
}
foreach ($parents as $parent)
{
if (!$parent instanceof Folder || !$parent->isDeleted())
{
continue;
}
$parent->restoreNonRecursive($restoredBy);
}
}
/**
* Returns list parents of object.
* @param SecurityContext $securityContext Security context.
* @param array $parameters Parameters.
* @param int $orderDepthLevel Order for depth level (default asc).
* @return array|Folder[]|File[]|FileLink[]|FolderLink[]
* @throws \Bitrix\Main\ArgumentOutOfRangeException
*/
public function getParents(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_PARENT.DEPTH_LEVEL';
$parameters = Driver::getInstance()->getRightsManager()->addRightsCheck($securityContext, $parameters, array('ID', 'CREATED_BY'));
/** @var ObjectTable $tableClassName */
$tableClassName = $this->getTableClassName();
$data = $tableClassName::getAncestors($this->id, static::prepareGetListParameters($parameters))->fetchAll();
Collection::sortByColumn($data, array('DEPTH_LEVEL' => $orderDepthLevel));
$modelData = array();
foreach($data as $item)
{
$modelData[] = BaseObject::buildFromArray($item);
}
unset($item);
return $modelData;
}
/**
* Returns parent model.
* If object does not have parent, then returns null.
* @return Folder|null
* @throws \Bitrix\Main\NotImplementedException
*/
public function getParent()
{
if(!$this->parentId)
{
return null;
}
if($this->isLoadedAttribute('parent') && $this->parent && $this->parentId === $this->parent->getId())
{
return $this->parent;
}
//todo - BaseObject - knows about Folder ^( Nu i pust'
$this->parent = Folder::loadById($this->getParentId());
$this->setAsLoadedAttribute('parent');
return $this->parent;
}
/**
* Tells if object is root folder.
* @return bool
*/
public function isRoot()
{
return !$this->parentId && $this instanceof Folder && !$this->isLink();
}
/**
* Tells if name is not unique in object.
* @param string $name Name.
* @param int $underObjectId Id of parent object.
* @param null $excludeId Id which will be excluded from query.
* @param null &$opponentId Opponent object which has same name.
* @return bool
* @throws \Bitrix\Main\ArgumentException
*/
public static function isUniqueName($name, $underObjectId, $excludeId = null, &$opponentId = null)
{
$parameters = array(
'select' => array('NAME'),
'filter' => array(
'PARENT_ID' => $underObjectId,
'=NAME' => $name,
),
'limit' => 1,
);
if($excludeId !== null)
{
$parameters['filter']['!ID'] = $excludeId;
}
if(func_num_args() >= 4)
{
$parameters['select'][] = 'ID';
}
$opponent = ObjectTable::getList($parameters)->fetch();
if(!$opponent)
{
return true;
}
if(func_num_args() >= 4)
{
$opponentId = $opponent['ID'];
}
return false;
}
/**
* Appends (1), (2), etc. if name is non unique in target dir
* @param string $potentialName Potential name.
* @param int $underObjectId Id of parent object.
* @return string
*/
protected static function generateUniqueName($potentialName, $underObjectId)
{
$count = 0;
$newName = $mainPartName = $potentialName;
$suffix = null;
$mainParts = explode('.', $mainPartName);
if (count($mainParts) > 1)
{
$suffix = array_pop($mainParts);
$mainPartName = implode('.', $mainParts);
}
while (!static::isUniqueName($newName, $underObjectId))
{
$count++;
[$newName] = static::getNextGeneratedName($mainPartName, $suffix, $underObjectId, $count > 2);
if ($count > 10)
{
return static::generateFallbackName($mainPartName, $suffix);
}
}
return $newName;
}
private static function generateFallbackName($mainPartName, $suffix)
{
return implode('.', array_filter([
$mainPartName . ' ' . time(),
$suffix
]));
}
private static function getNextGeneratedName($mainPartName, $suffix, $underObjectId, $withoutLike = false)
{
$underObjectId = (int)$underObjectId;
$left = $mainPartName . ' (';
$right = ')';
if ($suffix)
{
$right = ').'. $suffix;
}
$lengthR = mb_strlen($right);
$lengthL = mb_strlen($left);
$connection = Application::getConnection();
$sqlHelper = $connection->getSqlHelper();
$filterByLike = "NAME LIKE '" . $sqlHelper->forSql($left) . "%' AND";
if ($withoutLike)
{
$filterByLike = '';
}
$sql = "
SELECT NAME FROM b_disk_object
WHERE
PARENT_ID = {$underObjectId} AND
{$filterByLike}
LEFT(NAME, {$lengthL}) = '" . $sqlHelper->forSql($left) . "' AND
MID(NAME, {$lengthL} + 1, CHAR_LENGTH(NAME) - {$lengthL} - {$lengthR}) regexp '^[1-9][[:digit:]]*$' AND
RIGHT(NAME, {$lengthR}) = '" . $sqlHelper->forSql($right) . "'
ORDER BY CHAR_LENGTH(NAME) DESC, NAME DESC
LIMIT 1;
";
$row = $connection->query($sql)->fetch();
if (!$row)
{
$newName = $mainPartName . " (1)";
if ($suffix)
{
$newName .= "." . $suffix;
}
return [
$newName,
null,
1
];
}
$counter = mb_substr($row['NAME'], $lengthL, mb_strlen($row['NAME']) - $lengthL - $lengthR);
$counter = (int)$counter + 1;
return [
$left . $counter . $right,
$row['NAME'],
$counter
];
}
protected function updateLinksAttributes(array $attr)
{
/** @var ObjectTable $tableClassName */
$tableClassName = $this->getTableClassName();
//todo don't update object with REAL_OBJECT_ID == ID. Exlucde form update. It is not necessary.
$tableClassName::updateAttributesByFilter($attr, array('REAL_OBJECT_ID' => $this->id));
ModelSynchronizer::getInstance()->trigger($this, $attr);
}
/**
* Returns all sharings where the object is as source (real_object_id).
* @return Sharing[]
* @throws \Bitrix\Main\NotImplementedException
*/
public function getSharingsAsReal()
{
/** @var Sharing[] $sharings */
$sharings = Sharing::getModelList(array(
'with' => array('LINK_OBJECT'),
'filter' => array(
'REAL_OBJECT_ID' => $this->id,
'REAL_STORAGE_ID' => $this->storageId,
'!=STATUS' => SharingTable::STATUS_IS_DECLINED,
)
));
foreach($sharings as $sharing)
{
$sharing->setAttributes(array('REAL_OBJECT' => $this));
}
unset($sharing);
return $sharings;
}
/**
* Returns all sharing where the object is as link (link_object_id).
* @return Sharing[]
* @throws \Bitrix\Main\NotImplementedException
*/
public function getSharingsAsLink()
{
/** @var Sharing[] $sharings */
$sharings = Sharing::getModelList(array(
'filter' => array(
'LINK_OBJECT_ID' => $this->id,
'LINK_STORAGE_ID' => $this->storageId,
)
));
foreach($sharings as $sharing)
{
$sharing->setAttributes(array('LINK_OBJECT' => $this));
}
unset($sharing);
return $sharings;
}
/**
* Returns list users who have sharing on this object.
* @return array
* @throws \Bitrix\Main\ArgumentException
* @throws \Bitrix\Main\LoaderException
*/
public function getMembersOfSharing()
{
$realObject = $this->getRealObject();
if(!$realObject)
{
return array();
}
$sharings = $realObject->getSharingsAsReal();
$members = array();
$membersToSharing = array();
foreach($sharings as $sharing)
{
if($sharing->isToDepartmentChild())
{
continue;
}
[$type, $id] = Sharing::parseEntityValue($sharing->getToEntity());
$members[$type][] = $id;
$membersToSharing[$type . '|' . $id] = $sharing;
}
unset($sharing);
$enabledSocialnetwork = Loader::includeModule('socialnetwork');
$entityList = array();
foreach(SharingTable::getListOfTypeValues() as $type)
{
if(empty($members[$type]))
{
continue;
}
if($type == SharingTable::TYPE_TO_USER)
{
$query = UserTable::getList(array(
'select' => array('ID', 'PERSONAL_PHOTO', 'NAME', 'LOGIN', 'LAST_NAME', 'SECOND_NAME'),
'filter' => array('ID' => array_values($members[$type])),
));
while($userRow = $query->fetch())
{
/** @var Sharing $sharing */
$sharing = $membersToSharing[$type . '|' . $userRow['ID']];
$entityList[] = array(
'sharingId' => $sharing->getId(),
'entityId' => Sharing::CODE_USER . $userRow['ID'],
'name' => \CUser::formatName('#NAME# #LAST_NAME#', array(
"NAME" => $userRow['NAME'],
"LAST_NAME" => $userRow['LAST_NAME'],
"SECOND_NAME" => $userRow['SECOND_NAME'],
"LOGIN" => $userRow['LOGIN'],
), false, false),
'right' => $sharing->getTaskName(),
'avatar' => Avatar::getPerson($userRow['PERSONAL_PHOTO']),
'type' => 'users',
);
}
}
elseif($type == SharingTable::TYPE_TO_GROUP && $enabledSocialnetwork)
{
$query = \CSocNetGroup::getList(array(), array('ID' => array_values($members[$type])), false, false, array(
'ID',
'IMAGE_ID',
'NAME'
));
while($query && $groupRow = $query->fetch())
{
/** @var Sharing $sharing */
$sharing = $membersToSharing[$type . '|' . $groupRow['ID']];
$entityList[] = array(
'sharingId' => $sharing->getId(),
'entityId' => Sharing::CODE_SOCNET_GROUP . $groupRow['ID'],
'name' => $groupRow['NAME'],
'right' => $sharing->getTaskName(),
'avatar' => Avatar::getGroup($groupRow['IMAGE_ID']),
'type' => 'groups',
);
}
}
elseif($type == SharingTable::TYPE_TO_DEPARTMENT && $enabledSocialnetwork)
{
// intranet structure
$structure = \CSocNetLogDestination::getStucture();
foreach(array_values($members[$type]) as $departmentId)
{
if(empty($structure['department']['DR' . $departmentId]))
{
continue;
}
/** @var Sharing $sharing */
$sharing = $membersToSharing[$type . '|' . $departmentId];
$entityList[] = array(
'sharingId' => $sharing->getId(),
'entityId' => Sharing::CODE_DEPARTMENT . $departmentId,
'name' => $structure['department']['DR' . $departmentId]['name'],
'right' => $sharing->getTaskName(),
'avatar' => Avatar::getDefaultGroup(),
'type' => 'department',
);
}
unset($departmentId);
}
}
unset($type);
return $entityList;
}
/**
* Returns object ttl model.
*
* @return ObjectTtl
*/
public function getTtl()
{
if($this->isLoadedAttribute('ttl'))
{
return $this->ttl;
}
$this->ttl = ObjectTtl::load(array('OBJECT_ID' => $this->id));
$this->setAsLoadedAttribute('ttl');
return $this->ttl;
}
/**
* Creates ttl object.
*
* @param int $ttl Seconds to live.
* @return ObjectTtl
*/
public function setTtl($ttl)
{
$ttl = (int)$ttl;
$deathTime = DateTime::createFromTimestamp(time() + $ttl);
$objectTtl = ObjectTtl::loadByObjectId($this->id);
if ($objectTtl)
{
if($objectTtl->getDeathTime()->getTimestamp() > $deathTime->getTimestamp())
{
$objectTtl->changeDeathTime($deathTime);
return $objectTtl;
}
else
{
return $objectTtl;
}
}
return ObjectTtl::add(
array(
'OBJECT_ID' => $this->id,
'DEATH_TIME' => $deathTime,
),
$this->errorCollection
);
}
/**
* Returns the list of pair for mapping.
* Key is field in DataManager, value is object property.
* @return array
*/
public static function getMapAttributes()
{
return array(
'ID' => 'id',
'NAME' => 'name',
'CODE' => 'code',
'XML_ID' => 'xmlId',
'STORAGE_ID' => 'storageId',
'STORAGE' => 'storage',
'TYPE' => 'type',
'REAL_OBJECT_ID' => 'realObjectId',
'REAL_OBJECT' => 'realObject',
'PARENT_ID' => 'parentId',
'PARENT' => 'parent',
'LOCK' => 'lock',
'TTL' => 'ttl',
'CONTENT_PROVIDER' => 'contentProvider',
'DELETED_TYPE' => 'deletedType',
'TYPE_FILE' => null,
'GLOBAL_CONTENT_VERSION' => null,
'FILE_ID' => null,
'SIZE' => null,
'EXTERNAL_HASH' => null,
'ETAG' => null,
'CREATE_TIME' => 'createTime',
'UPDATE_TIME' => 'updateTime',
'SYNC_UPDATE_TIME' => 'syncUpdateTime',
'DELETE_TIME' => 'deleteTime',
'CREATED_BY' => 'createdBy',
'CREATE_USER' => 'createUser',
'UPDATED_BY' => 'updatedBy',
'UPDATE_USER' => 'updateUser',
'DELETED_BY' => 'deletedBy',
'DELETE_USER' => 'deleteUser',
'HAS_SUBFOLDERS' => null,
);
}
/**
* Returns the list attributes which is connected with another models.
* @return array
*/
public static function getMapReferenceAttributes()
{
$userClassName = User::className();
$fields = User::getFieldsForSelect();
return array(
'CREATE_USER' => array(
'class' => $userClassName,
'select' => $fields,
),
'UPDATE_USER' => array(
'class' => $userClassName,
'select' => $fields,
),
'DELETE_USER' => array(
'class' => $userClassName,
'select' => $fields,
),
'REAL_OBJECT' => BaseObject::className(),
'STORAGE' => Storage::className(),
'LOCK' => ObjectLock::className(),
'TTL' => ObjectTtl::className(),
);
}
/**
* Specify data which should be serialized to JSON
* @link http://php.net/manual/en/jsonserializable.jsonserialize.php
* @return mixed data which can be serialized by <b>json_encode</b>,
* which is a value of any type other than a resource.
* @since 5.4.0
*/
public function jsonSerialize()
{
return [
'id' => (int)$this->getId(),
'name' => $this->getName(),
'createTime' => $this->getCreateTime(),
'updateTime' => $this->getUpdateTime(),
'deleteTime' => $this->getDeleteTime(),
'code' => $this->getCode(),
'xmlId' => $this->getXmlId(),
'storageId' => (int)$this->getStorageId(),
'realObjectId' => (int)$this->getRealObjectId(),
'parentId' => (int)$this->getParentId(),
'deletedType' => (int)$this->getDeletedType(),
'createdBy' => $this->getCreatedBy(),
'updatedBy' => $this->getUpdatedBy(),
'deletedBy' => $this->getDeletedBy(),
];
}
}