Current Path : /var/www/axolotl/data/www/yar.axolotls.ru/bitrix/modules/disk/lib/ |
Current File : /var/www/axolotl/data/www/yar.axolotls.ru/bitrix/modules/disk/lib/rightsmanager.php |
<?php namespace Bitrix\Disk; use Bitrix\Disk\Internals\Error\Error; use Bitrix\Disk\Internals\Error\ErrorCollection; use Bitrix\Disk\Internals\Error\IErrorable; use Bitrix\Disk\Internals\ObjectPathTable; use Bitrix\Disk\Internals\ObjectTable; use Bitrix\Disk\Internals\Rights\SetupSession; use Bitrix\Disk\Internals\Rights\Table\TmpSimpleRight; use Bitrix\Disk\Internals\RightTable; use Bitrix\Disk\Internals\SimpleRightTable; use Bitrix\Main\Application; use Bitrix\Main\Entity\ExpressionField; use Bitrix\Main\Entity\Query; use Bitrix\Main\SystemException; use Bitrix\Main\Type\Collection; class RightsManager implements IErrorable { const ERROR_COULD_NOT_SET_NEGATIVE_RIGHT = 'DISK_RIGHTS_22002'; const OP_READ = 'disk_read'; const OP_ADD = 'disk_add'; const OP_EDIT = 'disk_edit'; const OP_SHARING = 'disk_sharing'; const OP_DELETE = 'disk_delete'; const OP_DESTROY = 'disk_destroy'; const OP_RESTORE = 'disk_restore'; const OP_SETTINGS = 'disk_settings'; const OP_RIGHTS = 'disk_rights'; const OP_START_BP = 'disk_start_bp'; const OP_CREATE_WF = 'disk_create_wf'; const TASK_READ = 'disk_access_read'; const TASK_EDIT = 'disk_access_edit'; const TASK_ADD = 'disk_access_add'; const TASK_SHARING = 'disk_access_sharing'; const TASK_FULL = 'disk_access_full'; const DOMAIN_SHARING = 'share'; const DOMAIN_BIZPROC = 'bp'; const DOMAIN_BASE = null; /** @var ErrorCollection */ protected $errorCollection; /** @var array */ protected $accessTasks; /** @var array */ private $operationsByTask = array(); public function __construct() { $this->errorCollection = new ErrorCollection; } /** * Rewrite if exists. * @param \Bitrix\Disk\BaseObject|BaseObject $object * @param array $rights * @throws \Bitrix\Main\SystemException * @throws \Bitrix\Main\ArgumentException * @return bool */ public function set(BaseObject $object, array $rights) { $this->checkUseInternalsRightsOnStorage($object, $rights); $this->errorCollection->clear(); foreach($rights as &$right) { if (empty($right['OBJECT_ID'])) { $right['OBJECT_ID'] = $object->getId(); } if (!isset($right['NEGATIVE'])) { $right['NEGATIVE'] = 0; } } unset($right); $rights = $this->uniqualizeRightsOnObject($rights); $rights = $this->cleanWrongNegativeRights($object, $rights); $this->deleteInternal($object); if(!$this->insertRightsInternal($object, $rights)) { return false; } $simpleBuilder = new SimpleReBuilder($object, $rights); $simpleBuilder->run(); Driver::getInstance()->getIndexManager()->recalculateRights($object); return true; } /** * BaseObject is leaf (does not have children) * @param \Bitrix\Disk\BaseObject|BaseObject $object * @param array $rights * @throws \Bitrix\Main\SystemException * @return bool */ public function setAsNewLeaf(BaseObject $object, array $rights) { $this->checkUseInternalsRightsOnStorage($object, $rights); $this->errorCollection->clear(); foreach($rights as &$right) { if (empty($right['OBJECT_ID'])) { $right['OBJECT_ID'] = $object->getId(); } if (!isset($right['NEGATIVE'])) { $right['NEGATIVE'] = 0; } } $rights = $this->uniqualizeRightsOnObject($rights); $rights = $this->cleanWrongNegativeRights($object, $rights); if(!$this->insertRightsInternal($object, $rights)) { return false; } $simpleBuilder = new SimpleReBuilder($object, $rights); $simpleBuilder->runAsNewLeaf(); Driver::getInstance()->getIndexManager()->recalculateRights($object); return true; } private function validateNegativeRights(BaseObject $object, array $rights) { $negativeRights = array(); foreach ($rights as $right) { if (!empty($right['NEGATIVE'])) { $negativeRights[$right['TASK_ID'] . '-' . $right['ACCESS_CODE']] = true; } } if ($negativeRights) { foreach ($this->getParentsRights($object->getId()) as $right) { if (empty($right['NEGATIVE']) && isset($negativeRights[$right['TASK_ID'] . '-' . $right['ACCESS_CODE']])) { unset($negativeRights[$right['TASK_ID'] . '-' . $right['ACCESS_CODE']]); } } } return count($negativeRights) == 0; } /** * Recalculate rights after move * @param \Bitrix\Disk\BaseObject|BaseObject $object * @return bool */ public function setAfterMove(BaseObject $object) { $this->errorCollection->clear(); $existsRights = $this->getSpecificRights($object); $rights = $this->cleanWrongNegativeRights($object, $this->uniqualizeRightsOnObject($existsRights)); $this->deleteInternal($object); if(!$this->insertRightsInternal($object, $rights)) { return false; } $simpleBuilder = new SimpleReBuilder($object, $rights); $simpleBuilder->run(); Driver::getInstance()->getIndexManager()->recalculateRights($object); return true; } /** * Use only after move object. * @param \Bitrix\Disk\BaseObject|BaseObject $object * @param array $rights * @return array */ private function cleanWrongNegativeRights(BaseObject $object, array $rights) { $dataSetOfRights = new DataSetOfRights($rights); $negativeRights = $dataSetOfRights->filterNegative(); if ($negativeRights->isEmpty()) { //there is only positive rights return $rights; } $validNegativeRights = []; $dataSetOfParentRights = new DataSetOfRights($this->getParentsRights($object->getId())); $positiveParentRights = $dataSetOfParentRights->filterPositive(); foreach ($negativeRights as $negativeRight) { if ($positiveParentRights->isExists([ 'ACCESS_CODE' => $negativeRight['ACCESS_CODE'], 'TASK_ID' => $negativeRight['TASK_ID'], ])) { $validNegativeRights[] = $negativeRight; } } return array_merge($dataSetOfRights->filterPositive()->toArray(), $validNegativeRights); } public function getSpecificRights(BaseObject $object) { return RightTable::getList(array( 'filter' => array( 'OBJECT_ID' => $object->getId(), ), ))->fetchAll(); } public function getAllListNormalizeRights(BaseObject $object) { $query = new Query(RightTable::getEntity()); $rights = $query ->setSelect(array('*', 'DEPTH_LEVEL' => 'PATH_PARENT.DEPTH_LEVEL',)) ->setFilter(array( 'PATH_PARENT.OBJECT_ID' => $object->getId(), )) ->exec() ->fetchAll() ; Collection::sortByColumn($rights, array('DEPTH_LEVEL' => SORT_DESC)); return $this->uniqualizeRightsOnObject($rights); } public function getAllListNormalizeRightsForUserId(BaseObject $object, $userId) { $query = new Query(RightTable::getEntity()); $rights = $query ->setSelect(array('*', 'DEPTH_LEVEL' => 'PATH_PARENT.DEPTH_LEVEL',)) ->setFilter(array( 'PATH_PARENT.OBJECT_ID' => $object->getId(), 'USER_ACCESS.USER_ID' => $userId, )) ->exec() ->fetchAll() ; Collection::sortByColumn($rights, array('DEPTH_LEVEL' => SORT_DESC)); return $this->uniqualizeRightsOnObject($rights); } public function delete(BaseObject $object) { if(!$this->deleteInternal($object)) { return false; } return $this->set($object, array()); } public function deleteByDomain(BaseObject $object, $domain) { if(empty($domain)) { return false; } if(!$this->deleteInternal($object, $domain)) { return false; } if(Application::getConnection()->getAffectedRowsCount() <= 0) { //todo we don't need to recalc rights return true; } return $this->append($object, array()); } public function append(BaseObject $object, array $rights) { return $this->set($object, array_merge($this->getSpecificRights($object), $rights)); } public function revokeByAccessCodes(BaseObject $object, array $accessCodes) { if (empty($accessCodes)) { return true; } $rights = $this->getSpecificRights($object); $isUnset = false; foreach ($rights as $id => $right) { if (in_array($right['ACCESS_CODE'], $accessCodes)) { unset($rights[$id]); $isUnset = true; } } if (!$isUnset) { return true; } return $this->set($object, $rights); } /** * Resets simple rights on the object. * Notice: it will be process for long time. * @param BaseObject $object File or Folder. * @return void */ public function resetSimpleRights(BaseObject $object) { $rights = $this->getSpecificRights($object); $simpleBuilder = new SimpleReBuilder($object, $rights); $simpleBuilder->run(); } private function isEqual(array $right1, array $right2) { return $right1['TASK_ID'] == $right2['TASK_ID'] && $right1['NEGATIVE'] == $right2['NEGATIVE'] && $right1['ACCESS_CODE'] === $right2['ACCESS_CODE'] ; } private function isOpposite(array $right1, array $right2) { return $right1['TASK_ID'] == $right2['TASK_ID'] && $right1['NEGATIVE'] != $right2['NEGATIVE'] && $right1['ACCESS_CODE'] === $right2['ACCESS_CODE'] ; } /** * Remove non-unique rights. * And drop pair negative + positive on same task_id + access_code * @param array $rights * @return array */ private function uniqualizeRightsOnObject(array $rights) { $idToDelete = array(); $rights = array_values($rights); foreach ($rights as $i => $right) { foreach(array_slice($rights, $i+1, null, true) as $j => $upRight) { if($this->isOpposite($right, $upRight)) { $idToDelete[$j] = $j; $idToDelete[$i] = $i; } } unset($upRight); } unset($right); foreach($idToDelete as $id) { unset($rights[$id]); } unset($id); $byKey = array(); foreach($rights as $right) { $uniqueKey = $right['ACCESS_CODE'] . '-' . $right['TASK_ID'] . '-' . $right['NEGATIVE']; if(!isset($byKey[$uniqueKey])) { $byKey[$uniqueKey] = $right; } } unset($right); return array_values($byKey); } private function deleteInternal(BaseObject $object, $domain = null) { $filter = array('OBJECT_ID' => $object->getId()); if($domain !== null) { $filter['DOMAIN'] = $domain; } RightTable::deleteBatch($filter); return true; } public function generateDomain($domain, $id) { return $domain . '-' . $id; } public function getSharingDomain($id) { return $this->generateDomain(self::DOMAIN_SHARING, $id); } public function getIdBySharingDomain($domain) { return substr($domain, strlen(self::DOMAIN_SHARING . '-')); } public function getBizProcDomain($id) { return $this->generateDomain(self::DOMAIN_BIZPROC, $id); } /** * Getting array of errors. * @return Error[] */ public function getErrors() { return $this->errorCollection->toArray(); } /** * Getting array of errors with the necessary code. * @param string $code Code of error. * @return Error[] */ public function getErrorsByCode($code) { return $this->errorCollection->getErrorsByCode($code); } /** * Getting once error with the necessary code. * @param string $code Code of error. * @return Error */ public function getErrorByCode($code) { return $this->errorCollection->getErrorByCode($code); } public function getOperationsByTask($taskId) { if(!isset($this->operationsByTask[$taskId])) { /** @noinspection PhpDynamicAsStaticMethodCallInspection */ $this->operationsByTask[$taskId] = \CTask::getOperations($taskId, true); } return $this->operationsByTask[$taskId]; } public function containsOperationInTask($operationName, $taskId) { return in_array($operationName, $this->getOperationsByTask($taskId)); } public function listOperations() { static $operations = null; if($operations !== null) { return $operations; } $refClass = new \ReflectionClass($this); foreach($refClass->getConstants() as $name => $value) { if(substr($name, 0, 3) === 'OP_') { $operations[$value] = $value; } } return $operations; } public function getParentsRights($objectId) { $query = new Query(RightTable::getEntity()); $rights = $query ->setSelect(array('*', 'DEPTH_LEVEL' => 'PATH_PARENT.DEPTH_LEVEL',)) ->setFilter(array( 'PATH_PARENT.OBJECT_ID' => $objectId, '!PATH_PARENT.PARENT_ID' => $objectId, )) ->exec() ->fetchAll() ; Collection::sortByColumn($rights, array('DEPTH_LEVEL' => SORT_DESC)); return $rights; } /** * Get rights for all descendants by objectId. * @param $objectId * @return array */ public function getDescendantsRights($objectId) { $query = new Query(RightTable::getEntity()); $query ->setSelect(array('*', 'DEPTH_LEVEL' => 'PATH_CHILD.DEPTH_LEVEL',)) ->setFilter(array( 'PATH_CHILD.PARENT_ID' => $objectId, '!PATH_CHILD.OBJECT_ID' => $objectId, )) ; return $query->exec()->fetchAll(); } /** * Get rights for direct children by objectId. * @param $objectId * @return array */ public function getChildrenRights($objectId) { $query = new Query(RightTable::getEntity()); $query ->setSelect(array('*')) ->setFilter(array( 'PATH_CHILD.PARENT_ID' => $objectId, 'PATH_CHILD.DEPTH_LEVEL' => 1, )) ; return $query->exec()->fetchAll(); } /** * Get all operations by object (with specific and inherited) * @param $objectId * @param $userId * @return array */ public function getUserOperationsByObject($objectId, $userId) { return $this->reformatRightsToOperations( $this->getUserRightsByObject($objectId, $userId) ); } public function getUserOperationsForChildren($parentId, $userId, array $restrictIds = []) { $parentId = (int)$parentId; $userId = (int)$userId; $rightsByObjectId = []; if ($restrictIds) { $restrictIds[] = $parentId; } $needToLoadByLink = $this->appendChildRightsForChildren($parentId, $userId, $restrictIds, $rightsByObjectId); if($needToLoadByLink) { $this->appendChildRightsForConnectedChildren($parentId, $userId, $restrictIds, $rightsByObjectId); } $this->appendChildCrRightsForChildren($parentId, $userId, $restrictIds, $rightsByObjectId); $operations = []; foreach ($rightsByObjectId as $objectId => $rights) { $operations[$objectId] = $this->reformatRightsToOperations($rights); } return $operations; } private function appendChildRightsForChildren($parentId, $userId, array $restrictIds, array &$rightsByObjectId) { $restrictById = ''; $restrictIds = array_filter(array_map('intval', $restrictIds)); if ($restrictIds) { $restrictById = ' AND o.ID IN (' . implode(',', $restrictIds) . ')'; } $query = Application::getConnection()->query(" SELECT op.NAME, r.TASK_ID, r.DOMAIN, r.NEGATIVE, o.REAL_OBJECT_ID O_REAL_OBJECT_ID, o.ID O_OBJECT_ID, r.ACCESS_CODE FROM b_disk_right r INNER JOIN b_disk_object_path p ON p.PARENT_ID = r.OBJECT_ID INNER JOIN b_disk_object o ON p.OBJECT_ID = o.ID INNER JOIN b_task_operation task_op ON r.TASK_ID = task_op.TASK_ID INNER JOIN b_operation op ON task_op.OPERATION_ID = op.ID INNER JOIN b_user_access uaccess ON r.ACCESS_CODE = uaccess.ACCESS_CODE WHERE o.PARENT_ID = {$parentId} AND uaccess.USER_ID = {$userId} {$restrictById} " ); $needToLoadByLink = false; while ($row = $query->fetch()) { $rightsByObjectId[$row['O_OBJECT_ID']][] = array( 'ACCESS_CODE' => $row['ACCESS_CODE'], 'NAME' => $row['NAME'], 'TASK_ID' => $row['TASK_ID'], 'DOMAIN' => $row['DOMAIN'], 'NEGATIVE' => $row['NEGATIVE'], 'REAL_OBJECT_ID' => $row['O_REAL_OBJECT_ID'], 'OBJECT_ID' => $row['O_OBJECT_ID'], ); if ($row['O_REAL_OBJECT_ID'] != $row['O_OBJECT_ID']) { $needToLoadByLink = true; } } return $needToLoadByLink; } private function appendChildRightsForConnectedChildren($parentId, $userId, array $restrictIds, &$rightsByObjectId) { $restrictById = ''; $restrictIds = array_filter(array_map('intval', $restrictIds)); if ($restrictIds) { $restrictById = ' AND o.ID IN (' . implode(',', $restrictIds) . ')'; } $query = Application::getConnection()->query(" SELECT op.NAME, r.TASK_ID, r.DOMAIN, r.NEGATIVE, o.REAL_OBJECT_ID O_REAL_OBJECT_ID, o.ID O_OBJECT_ID, r.ACCESS_CODE FROM b_disk_right r INNER JOIN b_disk_object_path p ON p.PARENT_ID = r.OBJECT_ID INNER JOIN b_disk_object o ON p.OBJECT_ID = o.REAL_OBJECT_ID INNER JOIN b_task_operation task_op ON r.TASK_ID = task_op.TASK_ID INNER JOIN b_operation op ON task_op.OPERATION_ID = op.ID INNER JOIN b_user_access uaccess ON r.ACCESS_CODE = uaccess.ACCESS_CODE WHERE o.PARENT_ID = {$parentId} AND uaccess.USER_ID = {$userId} AND o.ID <> o.REAL_OBJECT_ID {$restrictById} " ); while ($row = $query->fetch()) { $rightsByObjectId[$row['O_REAL_OBJECT_ID']][] = array( 'ACCESS_CODE' => $row['ACCESS_CODE'], 'NAME' => $row['NAME'], 'TASK_ID' => $row['TASK_ID'], 'DOMAIN' => $row['DOMAIN'], 'NEGATIVE' => $row['NEGATIVE'], 'REAL_OBJECT_ID' => $row['O_REAL_OBJECT_ID'], 'OBJECT_ID' => $row['O_OBJECT_ID'], ); } } private function appendChildCrRightsForChildren($parentId, $userId, array $restrictIds, &$rightsByObjectId) { $restrictById = ''; $restrictIds = array_filter(array_map('intval', $restrictIds)); if ($restrictIds) { $restrictById = ' AND o.ID IN (' . implode(',', $restrictIds) . ')'; } $query = Application::getConnection()->query(" SELECT op.NAME, r.TASK_ID, r.DOMAIN, r.NEGATIVE, o.REAL_OBJECT_ID O_REAL_OBJECT_ID, o.ID O_OBJECT_ID FROM b_disk_right r INNER JOIN b_disk_object_path p ON p.PARENT_ID = r.OBJECT_ID INNER JOIN b_disk_object o ON p.OBJECT_ID = o.REAL_OBJECT_ID INNER JOIN b_task_operation task_op ON r.TASK_ID = task_op.TASK_ID INNER JOIN b_operation op ON task_op.OPERATION_ID = op.ID WHERE o.PARENT_ID = {$parentId} AND o.CREATED_BY = {$userId} AND r.ACCESS_CODE = 'CR' {$restrictById} " ); while ($row = $query->fetch()) { $rightsByObjectId[$row['O_OBJECT_ID']][] = array( 'ACCESS_CODE' => 'CR', 'NAME' => $row['NAME'], 'TASK_ID' => $row['TASK_ID'], 'DOMAIN' => $row['DOMAIN'], 'NEGATIVE' => $row['NEGATIVE'], 'REAL_OBJECT_ID' => $row['O_REAL_OBJECT_ID'], 'OBJECT_ID' => $row['O_OBJECT_ID'], ); if ($row['O_REAL_OBJECT_ID'] != $row['O_OBJECT_ID']) { $rightsByObjectId[$row['O_REAL_OBJECT_ID']][] = array( 'ACCESS_CODE' => 'CR', 'NAME' => $row['NAME'], 'TASK_ID' => $row['TASK_ID'], 'DOMAIN' => $row['DOMAIN'], 'NEGATIVE' => $row['NEGATIVE'], 'REAL_OBJECT_ID' => $row['O_REAL_OBJECT_ID'], 'OBJECT_ID' => $row['O_OBJECT_ID'], ); } } } /** * Get tasks associated with module * @return array */ public function getTasks() { $this->loadAccessTasks(); return $this->accessTasks; } public function isValidTaskName($taskName) { return in_array($taskName, array_column($this->getTasks(), 'NAME'), true); } public function getTaskById($taskId) { $this->loadAccessTasks(); if(isset($this->accessTasks[$taskId])) { return $this->accessTasks[$taskId]; } return null; } public function getTaskIdByName($name) { $this->loadAccessTasks(); foreach($this->accessTasks as $task) { if(isset($task['NAME']) && $task['NAME'] == $name) { return $task['ID']; } } unset($task); return null; } public function getTaskNameById($taskId) { $this->loadAccessTasks(); if(isset($this->accessTasks[$taskId])) { return $this->accessTasks[$taskId]['NAME']; } return null; } public function getTaskTitleByName($name) { $this->loadAccessTasks(); foreach($this->accessTasks as $task) { if(isset($task['NAME']) && $task['NAME'] == $name) { return $task['TITLE']; } } return null; } /** * This is specific function for Sharing model. And you can't use this to compare rights task in another contexts. * We don't check operations in task, Compare only pseudo-names. * @param $taskName1 * @param $taskName2 * @return int Returns < 0 if $taskName1 is less than $taskName2; > 0 if $taskName1 is greater than $taskName2, and 0 if they are equal. * @internal */ public function pseudoCompareTaskName($taskName1, $taskName2) { switch($taskName1) { case 'disk_access_read': RightsManager::TASK_READ; $taskName1Pos = 2; break; case 'disk_access_add': RightsManager::TASK_ADD; $taskName1Pos = 3; break; case 'disk_access_edit': RightsManager::TASK_EDIT; $taskName1Pos = 4; break; case 'disk_access_full': RightsManager::TASK_FULL; $taskName1Pos = 5; break; default: //unknown task names $taskName1Pos = -1; } switch($taskName2) { case 'disk_access_read': RightsManager::TASK_READ; $taskName2Pos = 2; break; case 'disk_access_add': RightsManager::TASK_ADD; $taskName2Pos = 3; break; case 'disk_access_edit': RightsManager::TASK_EDIT; $taskName2Pos = 4; break; case 'disk_access_full': RightsManager::TASK_FULL; $taskName2Pos = 5; break; default: //unknown task names $taskName2Pos = -1; } if($taskName1Pos == $taskName2Pos) { return 0; } return $taskName1Pos > $taskName2Pos? 1 : -1; } /** * This is specific function for Sharing model. And you can't use this in another contexts. * @param BaseObject $object * @param $userId * @return null|string * @internal */ public function getPseudoMaxTaskByObjectForUser(BaseObject $object, $userId) { $maxTaskName = null; foreach($this->getAllListNormalizeRightsForUserId($object->getRealObject(), $userId) as $rightOnObject) { if(empty($rightOnObject['NEGATIVE'])) { $taskName = $this->getTaskNameById($rightOnObject['TASK_ID']); if($taskName && ($this->pseudoCompareTaskName($taskName, $maxTaskName) > 0)) { $maxTaskName = $taskName; } } } unset($rightOnObject); return $maxTaskName; } protected function loadAccessTasks() { if($this->accessTasks) { return $this; } $this->accessTasks = array(); /** @noinspection PhpUndefinedClassInspection */ $query = \CTask::getList(array('ID' => 'asc'), array('MODULE_ID' => 'disk',)); while($task = $query->fetch()) { $this->accessTasks[$task['ID']] = $task; } return $this; } /** * @param \Bitrix\Disk\BaseObject|BaseObject $object * @param array $rights * @return bool */ private function insertRightsInternal(BaseObject $object, array $rights) { $rightsToInsert = array(); foreach ($rights as $right) { if (isset($right['NEGATIVE'])) { $right['NEGATIVE'] = (int)$right['NEGATIVE']; } else { $right['NEGATIVE'] = 0; //default value @see \Bitrix\Disk\Internals\RightTable } if (!isset($right['DOMAIN'])) { $right['DOMAIN'] = null; } $rightsToInsert[] = array( 'OBJECT_ID' => $object->getId(), 'TASK_ID' => $right['TASK_ID'], 'ACCESS_CODE' => $right['ACCESS_CODE'], 'DOMAIN' => $right['DOMAIN'], 'NEGATIVE' => $right['NEGATIVE'], ); } RightTable::insertBatch($rightsToInsert); return true; } private function checkUseInternalsRightsOnStorage(BaseObject $object, $rights) { $storageModel = Storage::loadById($object->getStorageId()); if($storageModel && !empty($rights) && !$storageModel->isUseInternalRights()) { throw new SystemException('Attempt to set the rights, but not to use the internal rights.'); } } /** * @param array $rights * @return array */ private function reformatRightsToOperations(array $rights) { $operations = array(); foreach($rights as $right) { $key = $right['TASK_ID'] . '-' . $right['ACCESS_CODE']; if(!empty($right['NEGATIVE'])) { if(isset($operations[$key])) { unset($operations[$key]); } continue; } if(!isset($key)) { $operations[$key] = array(); } $operations[$key][] = $right['NAME']; } unset($right); $operationNames = array(); foreach($operations as $key => $item) { $operationNames = array_merge($operationNames, array_values($item)); } unset($key); $values = array_values(array_unique($operationNames)); //https://bugs.php.net/bug.php?id=29972 return $values? array_combine($values, $values) : array(); } /** * @param $objectId * @param $userId * @return array * @throws \Bitrix\Main\ArgumentOutOfRangeException */ private function getUserRightsByObject($objectId, $userId) { $query = new Query(RightTable::getEntity()); $rights = $query ->setSelect(array( 'ACCESS_CODE', 'TASK_ID', 'NEGATIVE', 'NAME' => 'TASK_OPERATION.OPERATION.NAME', 'DEPTH_LEVEL' => 'PATH_PARENT.DEPTH_LEVEL', )) ->setFilter(array( 'PATH_PARENT.OBJECT_ID' => $objectId, 'USER_ACCESS.USER_ID' => $userId, ))->exec()->fetchAll(); $query = new Query(RightTable::getEntity()); $query ->setSelect( array( 'ACCESS_CODE', 'TASK_ID', 'NEGATIVE', 'NAME' => 'TASK_OPERATION.OPERATION.NAME', 'DEPTH_LEVEL' => 'PATH_PARENT.DEPTH_LEVEL', ) ) ->setFilter( array( 'LOGIC' => 'OR', array( 'PATH_PARENT.OBJECT_ID' => $objectId, '=ACCESS_CODE' => 'CR', ), array( 'PATH_PARENT.OBJECT_ID' => $objectId, '=ACCESS_CODE' => 'AU', ) ) ) ; $creatorRights = array(); foreach($query->exec()->fetchAll() as $additionalRight) { if($additionalRight['ACCESS_CODE'] === 'AU') { $rights[] = $additionalRight; } elseif($additionalRight['ACCESS_CODE'] === 'CR') { $creatorRights[] = $additionalRight; } } if($creatorRights) { $query = new Query(ObjectTable::getEntity()); $query ->setSelect(array('CREATED_BY')) ->addFilter('=ID', $objectId) ; $creatorData = $query->exec()->fetch(); if($creatorData && $creatorData['CREATED_BY'] == $userId) { $rights = array_merge($rights, $creatorRights); } } Collection::sortByColumn($rights, array('DEPTH_LEVEL' => SORT_DESC)); return $rights; } /** * Add to parameters rights check by security context for use in getList. * @param Security\SecurityContext $securityContext * @param array $parameters * @param array $specificColumns List of columns to use in $securityContext->getSqlExpressionForList. * @return array */ public function addRightsCheck(Security\SecurityContext $securityContext, array $parameters, array $specificColumns) { if(!isset($parameters['filter'])) { $parameters['filter'] = array(); } if(!isset($parameters['runtime'])) { $parameters['runtime'] = array(); } $parameters['runtime'][] = new ExpressionField('RIGHTS_CHECK', 'CASE WHEN ' . $securityContext->getSqlExpressionForList('%1$s', '%2$s') . ' THEN 1 ELSE 0 END', $specificColumns, array('data_type' => 'boolean',) ); $parameters['filter']['=RIGHTS_CHECK'] = true; return $parameters; } } final class SimpleReBuilder { const SCENARIO_AS_NEW_LEAF = 'leaf'; const SCENARIO_FULL_RECALC = 'recalc'; const SCENARIO_SKIP = 'skip'; /** @var \Bitrix\Disk\BaseObject */ protected $object; /** @var array */ protected $specificRights; /** @var array */ protected $simpleRights; /** @var array */ protected $simpleRightsFromParent; /** @var array */ protected $parentRights; /** @var SetupSession */ protected $setupSession; /** @var string */ protected $scenario = self::SCENARIO_FULL_RECALC; public function __construct(BaseObject $object, array $specificRights) { $this->object = $object; $this->specificRights = $specificRights; $this->setupSession = SetupSession::register($this->object->getId()); } /** * Set rights and calculate simple rights with children. */ public function run() { $this->scenario = self::SCENARIO_FULL_RECALC; $this->runByOnceObject(); $this->fillChildren(); $this->finalize(); } /** * Set rights and calculate simple rights without operation with children. */ public function runAsNewLeaf() { $this->scenario = self::SCENARIO_AS_NEW_LEAF; $this->runByOnceObject(); $this->finalize(); } public function finalize() { if($this->scenario === self::SCENARIO_SKIP) { return; } $this->setupSession->finish(); $this->scenario = self::SCENARIO_SKIP; } private function runByOnceObject() { $this->simpleRights = $this->uniqualizeSimpleRights( $this->getNewSimpleRight( $this->specificRights, $this->getParentRights(), $this->getSimpleRightsFromParent() )); $items = array(); foreach($this->simpleRights as $right) { $items[] = array( 'OBJECT_ID' => $this->object->getId(), 'ACCESS_CODE' => $right['ACCESS_CODE'], ); } unset($right); TmpSimpleRight::insertBatchBySessionId($items, $this->setupSession->getId()); } private function uniqualizeSimpleRights(array $rights) { $byKey = array(); foreach ($rights as $right) { if(!isset($byKey[$right['ACCESS_CODE']])) { $byKey[$right['ACCESS_CODE']] = $right; } } unset($right); return array_values($byKey); } private function getParentRights() { if($this->parentRights !== null) { return $this->parentRights; } $this->parentRights = Driver::getInstance()->getRightsManager()->getParentsRights($this->object->getId()); Collection::sortByColumn($this->parentRights, array('DEPTH_LEVEL' => SORT_DESC)); return $this->parentRights; } private function getNewSimpleRight(array $specificRights, array $parentRights, array $parentSimpleRights) { list($canRead, $mayCannotRead) = $this->splitRightsByReadable($specificRights); if(count($mayCannotRead)) { //analyze negative + positive read rights. Clean pair $rightsManager = Driver::getInstance()->getRightsManager(); $readableRightsFromParents = array(); foreach($parentRights as $parentRight) { if(!$rightsManager->containsOperationInTask($rightsManager::OP_READ, $parentRight['TASK_ID'])) { continue; } $key = $parentRight['TASK_ID'] . '-' . $parentRight['ACCESS_CODE']; if(empty($parentRight['NEGATIVE'])) { $readableRightsFromParents[$key] = $parentRight; continue; } elseif(isset($readableRightsFromParents[$key])) { unset($readableRightsFromParents[$key]); } } unset($parentRight); //clean parent read right if we have negative right on this task_id + access_code foreach($readableRightsFromParents as $i => $right) { if(isset($mayCannotRead[$right['ACCESS_CODE']])) { foreach($mayCannotRead[$right['ACCESS_CODE']] as $cannotReadRight) { if($cannotReadRight['TASK_ID'] == $right['TASK_ID']) { unset($readableRightsFromParents[$i]); break; } } unset($cannotReadRight); } } unset($right); //in $readableRightsFromParents stay only readable rights by task and access code. $simpleRights = $readableRightsFromParents; } else { $simpleRights = $parentSimpleRights; } foreach($canRead as $accessCode => $rights) { $simpleRights = array_merge($simpleRights, $rights); } unset($accessCode, $rights); return $simpleRights; } private function getSimpleRightsFromParent() { if($this->simpleRightsFromParent !== null) { return $this->simpleRightsFromParent; } if($this->object->getParentId()) { $this->simpleRightsFromParent = SimpleRightTable::getList(array('filter' => array( 'OBJECT_ID' => $this->object->getParentId(), )))->fetchAll(); } else { $this->simpleRightsFromParent = array(); } return $this->simpleRightsFromParent; } private function splitRightsByReadable(array $specificRights) { $rightsManager = Driver::getInstance()->getRightsManager(); $canRead = $cannotRead = array(); foreach ($specificRights as $right) { if($rightsManager->containsOperationInTask($rightsManager::OP_READ, $right['TASK_ID'])) { if(empty($right['NEGATIVE'])) { if(!isset($canRead[$right['ACCESS_CODE']])) { $canRead[$right['ACCESS_CODE']] = array(); } $canRead[$right['ACCESS_CODE']][] = $right; } else { if(!isset($cannotRead[$right['ACCESS_CODE']])) { $cannotRead[$right['ACCESS_CODE']] = array(); } $cannotRead[$right['ACCESS_CODE']][] = $right; } } } unset($right); return array($canRead, $cannotRead); } private function fillChildren() { if($this->object instanceof File) { return; } $specificRightsByObjectId = array( $this->object->getId() => $this->specificRights, ); //store all rights on object (all inherited rights) $inheritedRightsByObjectId = array( $this->object->getId() => $this->getParentRights(), ); $childrenRights = Driver::getInstance()->getRightsManager()->getDescendantsRights($this->object->getId()); if(!$childrenRights) { TmpSimpleRight::fillDescendants($this->object->getId(), $this->setupSession->getId()); return; } //store all specific rights on object foreach ($childrenRights as $right) { if(!isset($specificRightsByObjectId[$right['OBJECT_ID']])) { $specificRightsByObjectId[$right['OBJECT_ID']] = array(); } $specificRightsByObjectId[$right['OBJECT_ID']][] = $right; } unset($right, $childrenRights); $simpleRightsByObjectId = array( $this->object->getId() => $this->simpleRights, ); $query = ObjectTable::getDescendants($this->object->getId(), array('select' => array('ID', 'PARENT_ID'))); while($object = $query->fetch()) { //specific rights on object if(!isset($specificRightsByObjectId[$object['ID']])) { $specificRightsByObjectId[$object['ID']] = array(); } if(!isset($inheritedRightsByObjectId[$object['ID']])) { $inheritedRightsByObjectId[$object['ID']] = array(); } if(!isset($simpleRightsByObjectId[$object['PARENT_ID']])) { $simpleRightsByObjectId[$object['PARENT_ID']] = array(); } if(isset($inheritedRightsByObjectId[$object['PARENT_ID']])) { $inheritedRightsByObjectId[$object['ID']] = array_merge( $inheritedRightsByObjectId[$object['PARENT_ID']], ($specificRightsByObjectId[$object['PARENT_ID']]?: array()) ); } else { $inheritedRightsByObjectId[$object['PARENT_ID']] = array(); } $simpleRightsByObjectId[$object['ID']] = $this->uniqualizeSimpleRights( $this->getNewSimpleRight( $specificRightsByObjectId[$object['ID']], $inheritedRightsByObjectId[$object['ID']], $simpleRightsByObjectId[$object['PARENT_ID']] )); $items = array(); foreach($simpleRightsByObjectId[$object['ID']] as $right) { $items[] = array( 'OBJECT_ID' => $object['ID'], 'ACCESS_CODE' => $right['ACCESS_CODE'], ); } unset($right); TmpSimpleRight::insertBatchBySessionId($items, $this->setupSession->getId()); } unset($object); } } final class RightsSetter implements Internals\Error\IErrorable { /** @var ErrorCollection */ protected $errorCollection; /** @var \Bitrix\Main\DB\Connection */ protected $connection; /** @var \Bitrix\Main\Db\SqlHelper */ protected $sqlHelper; /** @var BaseObject */ protected $object; /** @var array */ protected $parentRights; /** @var array */ protected $rights = array(); /** @var array */ protected $currentRightsOnObject = array(); /** * Constructor RightsSetter. * @param BaseObject $object * @param array $rights */ public function __construct(BaseObject $object, array $rights) { $this->object = $object; $this->rights = $rights; $this->connection = Application::getConnection(); $this->sqlHelper = Application::getConnection()->getSqlHelper(); } public function run() { foreach($this->rights as $right) { $this->appendOne($right); } unset($right); } private function appendOne(array $right) { $hadOppositeRight = false; foreach($this->getCurrentRightsOnObject() as $currentRight) { if($this->isEqual($currentRight, $right)) { return true; } if($this->isOpposite($currentRight, $right)) { $hadOppositeRight = true; RightTable::delete($currentRight['ID']); } } unset($currentRight); return empty($right['NEGATIVE'])? $this->appendOnePositive($right, $hadOppositeRight) : $this->appendOneNegative($right, $hadOppositeRight); } private function isEqual(array $right1, array $right2) { return $right1['TASK_ID'] == $right2['TASK_ID'] && $right1['NEGATIVE'] == $right2['NEGATIVE'] && $right1['ACCESS_CODE'] == $right2['ACCESS_CODE'] ; } private function isOpposite(array $right1, array $right2) { return $right1['TASK_ID'] == $right2['TASK_ID'] && $right1['NEGATIVE'] != $right2['NEGATIVE'] && $right1['ACCESS_CODE'] == $right2['ACCESS_CODE'] ; } private function appendOnePositive(array $right, $hadOppositeRight = false) { //May we have to add record to b_disk_right in final. $right['OBJECT_ID'] = $this->object->getId(); $result = RightTable::add($right); if (!$result->isSuccess()) { $this->errorCollection->addFromResult($result); return false; } $rightsManager = Driver::getInstance()->getRightsManager(); if(!$rightsManager->containsOperationInTask($rightsManager::OP_READ, $right['TASK_ID'])) { return true; } $conflictRightsInSubTree = $this->getConflictRightsInSubTree($right['ACCESS_CODE'], $right['TASK_ID']); $accessCode = $this->sqlHelper->forSql($right['ACCESS_CODE']); if(empty($conflictRightsInSubTree)) { //we have to add simple right to all descendants and to current OBJECT_ID $this->connection->queryExecute(" INSERT INTO b_disk_simple_right (OBJECT_ID, ACCESS_CODE) SELECT OBJECT_ID, '{$accessCode}' FROM b_disk_object_path WHERE PARENT_ID = {$this->object->getId()} "); } else { $objectIds = array(); foreach($conflictRightsInSubTree as $conflictRight) { $objectIds[] = $conflictRight['OBJECT_ID']; } unset($conflictRight); //we have to add simple right to all descendants and to current OBJECT_ID without nodes with conflict rights in path. //when I write conflict I mean right with same ACCESS_CODE and same TASK_ID $this->connection->queryExecute(" INSERT INTO b_disk_simple_right (OBJECT_ID, ACCESS_CODE) SELECT OBJECT_ID, '{$accessCode}' FROM b_disk_object_path WHERE PARENT_ID = {$this->object->getId()} AND NOT EXISTS( SELECT 'x' FROM b_disk_object_path pp WHERE pp.OBJECT_ID = p.OBJECT_ID AND pp.PARENT_ID IN (" . implode(',', $objectIds) . ") ) "); } $this->removeDuplicates($this->object->getId()); return true; } private function removeDuplicates($parentObjectId) { } private function hasAlreadySimpleRight($accessCode) { $objectId = (int)$this->object->getId(); $accessCode = $this->sqlHelper->forSql($accessCode); return (bool)$this->connection->query(" SELECT 1 FROM b_disk_simple_right WHERE OBJECT_ID = {$objectId} AND ACCESS_CODE = '{$accessCode}' ")->fetch(); } private function getConflictRightsInSubTree($accessCode, $taskId) { $objectId = (int)$this->object->getId(); $accessCode = $this->sqlHelper->forSql($accessCode); $accessCodeForSql = $this->sqlHelper->forSql($accessCode); $taskId = (int)$taskId; $rights = $this->connection->query(" SELECT r.OBJECT_ID, r.NEGATIVE, r.DOMAIN FROM b_disk_right r INNER JOIN b_disk_object_path p ON p.OBJECT_ID = r.OBJECT_ID WHERE p.PARENT_ID = {$objectId} AND r.ACCESS_CODE = '{$accessCodeForSql}' AND r.TASK_ID = {$taskId} ")->fetchAll(); foreach($rights as $i => $right) { $rights[$i]['ACCESS_CODE'] = $accessCode; $rights[$i]['TASK_ID'] = $taskId; } unset($right); return $rights; } private function splitNegativeAndPositive(array $rights) { $negative = $positive = array(); foreach($rights as $i => $right) { if(empty($right['NEGATIVE'])) { $positive[] = $right; } else { $negative[] = $right; } } unset($right); return array($negative, $positive); } private function appendOneNegative(array $right, $hadOppositeRight = false) { $isValidNegaviteRight = $this->validateNegaviteRight($right); if(!$isValidNegaviteRight && !$hadOppositeRight) { $this->errorCollection->addOne(new Error('Invalid negative right')); return false; } //we don't have to add negative right. We must only delete old simple rights. condition($hadOppositeRight && !$isValidNegaviteRight) if($isValidNegaviteRight) { //May we have to add record to b_disk_right in final. $right['OBJECT_ID'] = $this->object->getId(); $result = RightTable::add($right); if (!$result->isSuccess()) { $this->errorCollection->addFromResult($result); return false; } } $rightsManager = Driver::getInstance()->getRightsManager(); if(!$rightsManager->containsOperationInTask($rightsManager::OP_READ, $right['TASK_ID'])) { return true; } if(!$this->hasAlreadySimpleRight($right['ACCESS_CODE'])) { //below we already have negative rights, which deleted simple rights. return true; } //need to delete simple rights from descendants $conflictRightsInSubTree = $this->getConflictRightsInSubTree($right['ACCESS_CODE'], $right['TASK_ID']); $accessCode = $this->sqlHelper->forSql($right['ACCESS_CODE']); if(empty($conflictRightsInSubTree)) { //we have to destroy simple right from all descendants and from current OBJECT_ID $this->connection->queryExecute(" DELETE simple FROM b_disk_simple_right simple INNER JOIN b_disk_object_path p ON p.OBJECT_ID = simple.OBJECT_ID WHERE p.PARENT_ID = {$this->object->getId()} AND simple.ACCESS_CODE = '{$accessCode}' "); } else { $objectIds = array(); foreach($conflictRightsInSubTree as $conflictRight) { $objectIds[] = $conflictRight['OBJECT_ID']; } unset($conflictRight); //we have to destroy simple right from all descendants and from current OBJECT_ID without nodes with conflict rights in path. $this->connection->queryExecute(" DELETE simple FROM b_disk_simple_right simple INNER JOIN b_disk_object_path p ON p.OBJECT_ID = simple.OBJECT_ID WHERE p.PARENT_ID = {$this->object->getId()} AND simple.ACCESS_CODE = '{$accessCode}' AND NOT EXISTS( SELECT 'x' FROM b_disk_object_path pp WHERE pp.OBJECT_ID = p.OBJECT_ID AND pp.PARENT_ID IN (" . implode(',', $objectIds) . ") ) "); } return true; } private function deleteOnePositive(array $right) { foreach($this->getParentRights() as $parentRight) { if($this->isEqual($parentRight, $right)) { //we don't have to clean negative rights in subtree and recalculate simple right, when in under-tree exists similar right. $result = RightTable::delete($right['ID']); if($result->isSuccess()) { $this->errorCollection->addFromResult($result); return false; } return true; } } //we don't find right in under-tree. /** * 1. Delete all negative rights in sub-tree before positive rights. * r.NEGATIVE = 1 AND ... r_p.NEGATIVE=0 AND r_p.DEPTH_LEVEL > r.DEPTH_LEVEL * 2. Delete all simple rights in sub-tree before positive rights. */ return true; } private function deleteOneNegative(array $right) { $result = RightTable::delete($right['ID']); if($result->isSuccess()) { $this->errorCollection->addFromResult($result); return false; } $firstNegative = false; foreach($this->getParentRights() as $parentRight) { if($this->isEqual($parentRight, $right)) { $firstNegative = true; break; } if($this->isOpposite($parentRight, $right)) { $firstNegative = false; break; } } if($firstNegative) { //parent negative is like current negative right ($right) return true; } /** * Insert all simple rights in sub-tree before positive rights and negative rights. */ return true; } private function updateOnePositive(array $oldRight, array $newRight) { $rightsManager = Driver::getInstance()->getRightsManager(); if(!$rightsManager->containsOperationInTask($rightsManager::OP_READ, $oldRight['TASK_ID'])) { //simple appendOnePositive return true; } if(!$rightsManager->containsOperationInTask($rightsManager::OP_READ, $newRight['TASK_ID'])) { //? delete old right? return true; } //shame. It is false. } private function getCurrentRightsOnObject() { if($this->currentRightsOnObject !== null) { return $this->currentRightsOnObject; } $this->currentRightsOnObject = Driver::getInstance()->getRightsManager()->getSpecificRights($this->object); return $this->currentRightsOnObject; } private function getParentRights() { if($this->parentRights !== null) { return $this->parentRights; } $this->parentRights = Driver::getInstance()->getRightsManager()->getParentsRights($this->object->getId()); Collection::sortByColumn($this->parentRights, array('DEPTH_LEVEL' => SORT_DESC)); return $this->parentRights; } private function validateNegaviteRight(array $potentialRight) { foreach($this->getParentRights() as $parentRight) { if($this->isOpposite($parentRight, $potentialRight)) { return true; } } return false; } /** * Getting array of errors. * @return Error[] */ public function getErrors() { return $this->errorCollection->toArray(); } /** * Getting array of errors with the necessary code. * @param string $code Code of error. * @return Error[] */ public function getErrorsByCode($code) { return $this->errorCollection->getErrorsByCode($code); } /** * Getting once error with the necessary code. * @param string $code Code of error. * @return Error[] */ public function getErrorByCode($code) { return $this->errorCollection->getErrorByCode($code); } } /** * Class SimpleRightBuilder * @package Bitrix\Disk * @internal */ final class SimpleRightBuilder { /** @var \Bitrix\Main\DB\Connection */ private $connection; /** @var \Bitrix\Main\Db\SqlHelper */ private $sqlHelper; private $objectId; /** * SimpleRightBuilder constructor. * @param int $objectId Object id. Root of storage. */ public function __construct($objectId) { $this->objectId = $objectId; $this->connection = Application::getConnection(); $this->sqlHelper = Application::getConnection()->getSqlHelper(); } /** * Runs simple right builder. * @return void */ public function run() { $this ->cleanTree() ->fillTree() ->workWithNegativeNodes() ; } /** * Works with nodes which have negative rights. * * We have to get all negative nodes in subtree and order by DEPTH_LEVEL ASC. * Then we go from each negative node up and calculate opportunity to read this object by ACCESS_CODE. * If we have positive right on different TASK_ID in subtree, then we can't delete simple rights from subtree. * If we don't have positive right on different TASK_ID in subtree, then we delete simple rights from subtree * before we find another positive rights with same ACCESS_CODE. * * @return $this * @throws \Bitrix\Main\ArgumentOutOfRangeException */ private function workWithNegativeNodes() { $negativeNodes = $this->connection->query(" SELECT r.ACCESS_CODE, r.TASK_ID, r.OBJECT_ID, p.DEPTH_LEVEL FROM b_disk_right r INNER JOIN b_disk_object_path p ON p.OBJECT_ID = r.OBJECT_ID WHERE p.PARENT_ID = {$this->objectId} AND r.NEGATIVE = 1 ")->fetchAll(); $rightsManager = Driver::getInstance()->getRightsManager(); Collection::sortByColumn($negativeNodes, array('DEPTH_LEVEL' => SORT_ASC)); foreach($negativeNodes as $negativeNode) { $nodeObject = BaseObject::buildFromArray(array( 'ID' => $negativeNode['OBJECT_ID'], 'TYPE' => ObjectTable::TYPE_FOLDER, )); $runClean = true; foreach($rightsManager->getAllListNormalizeRights($nodeObject) as $right) { if($right['ACCESS_CODE'] !== $negativeNode['ACCESS_CODE']) { continue; } //the right goes from parent if( !empty($right['NEGATIVE']) && $right['OBJECT_ID'] != $negativeNode['OBJECT_ID'] && $right['TASK_ID'] == $negativeNode['TASK_ID'] ) { $runClean = false; break; } if(!empty($right['NEGATIVE'])) { continue; } if($rightsManager->containsOperationInTask($rightsManager::OP_READ, $right['TASK_ID'])) { $runClean = false; break; } } unset($right); if(!$runClean) { //the node and all sub-elements inherit OP_READ from another positive right. continue; } $this->deleteSimpleRightFromSubTree($negativeNode['OBJECT_ID'], $negativeNode['ACCESS_CODE']); } unset($negativeNode); return $this; } /** * Deletes simple rights from subtree of object by ACCESS_CODE. * Skips nodes affected by positive right with same ACCESS_CODE. * @param int $objectId Object id. * @param string $accessCode Access code. * @return void */ private function deleteSimpleRightFromSubTree($objectId, $accessCode) { //need to delete simple rights from descendants $objectIds = $this->getConflictRightsInSubTree($objectId, $accessCode); $objectId = (int)$objectId; $accessCode = $this->sqlHelper->forSql($accessCode); if(!$objectIds) { //we have to destroy simple right from all descendants and from current OBJECT_ID $this->connection->queryExecute(" DELETE simple FROM b_disk_simple_right simple INNER JOIN b_disk_object_path p ON p.OBJECT_ID = simple.OBJECT_ID WHERE p.PARENT_ID = {$objectId} AND simple.ACCESS_CODE = '{$accessCode}' "); } else { //we have to destroy simple right from all descendants and from current OBJECT_ID without nodes with conflict rights in path. $this->connection->queryExecute(" DELETE simple FROM b_disk_simple_right simple INNER JOIN b_disk_object_path p ON p.OBJECT_ID = simple.OBJECT_ID WHERE p.PARENT_ID = {$objectId} AND simple.ACCESS_CODE = '{$accessCode}' AND NOT EXISTS( SELECT 'x' FROM b_disk_object_path pp WHERE pp.OBJECT_ID = p.OBJECT_ID AND pp.PARENT_ID IN (" . implode(',', $objectIds) . ") ) "); } } /** * Returns list of id of objects which act by positive rights on descendants objects by the same ACCESS_CODE. * @param int $objectId Object id. * @param string $accessCode Access code. * @return array */ private function getConflictRightsInSubTree($objectId, $accessCode) { $objectId = (int)$objectId; $accessCode = $this->sqlHelper->forSql($accessCode); $rights = $this->connection->query(" SELECT r.OBJECT_ID FROM b_disk_right r INNER JOIN b_disk_object_path p ON p.OBJECT_ID = r.OBJECT_ID WHERE p.PARENT_ID = {$objectId} AND r.NEGATIVE = 0 AND r.ACCESS_CODE = '{$accessCode}' ")->fetchAll(); $ids = array(); foreach($rights as $i => $right) { $ids[] = $right['OBJECT_ID']; } unset($right); return $ids; } /** * Deletes all simple rights from subtree. * @return $this */ private function cleanTree() { $this->connection->queryExecute(" DELETE r FROM b_disk_simple_right r INNER JOIN b_disk_object_path p ON p.OBJECT_ID = r.OBJECT_ID WHERE p.PARENT_ID = {$this->objectId} "); return $this; } /** * Fills subtree by simple rights. * Any positive rights, which contains operation OP_READ, will fill subtree. It's unnecessary has node negative rights * and not. * @return $this */ private function fillTree() { if($this->hasAllTasksWithOperationRead()) { $this->connection->queryExecute(" INSERT INTO b_disk_simple_right (OBJECT_ID, ACCESS_CODE) SELECT DISTINCT pathchild.OBJECT_ID, r.ACCESS_CODE FROM b_disk_object_path path INNER JOIN b_disk_right r ON r.OBJECT_ID = path.OBJECT_ID INNER JOIN b_disk_object_path pathchild ON pathchild.PARENT_ID = r.OBJECT_ID WHERE path.PARENT_ID = {$this->objectId} AND r.NEGATIVE = 0 "); } else { $ids = array(); foreach($this->getTasksWithOperationRead() as $task) { $ids[] = (int)$task['ID']; } unset($task); if(!$ids) { return $this; } $this->connection->queryExecute(" INSERT INTO b_disk_simple_right (OBJECT_ID, ACCESS_CODE) SELECT DISTINCT pathchild.OBJECT_ID, r.ACCESS_CODE FROM b_disk_object_path path INNER JOIN b_disk_right r ON r.OBJECT_ID = path.OBJECT_ID INNER JOIN b_disk_object_path pathchild ON pathchild.PARENT_ID = r.OBJECT_ID WHERE path.PARENT_ID = {$this->objectId} AND r.NEGATIVE = 0 AND r.TASK_ID IN (" . implode(', ', $ids) .") "); } return $this; } /** * Returns tasks which contains operation OP_READ. * @return array */ private function getTasksWithOperationRead() { $rightsManager = Driver::getInstance()->getRightsManager(); $tasks = $rightsManager->getTasks(); $readableTasks = array(); foreach($tasks as $task) { if($rightsManager->containsOperationInTask($rightsManager::OP_READ, $task['ID'])) { $readableTasks[$task['ID']] = $task; } } unset($task); return $readableTasks; } /** * Tells true if all tasks in module disk have operation OP_READ. * @return bool */ private function hasAllTasksWithOperationRead() { $rightsManager = Driver::getInstance()->getRightsManager(); $tasks = $rightsManager->getTasks(); return count($tasks) === count($this->getTasksWithOperationRead()); } } final class DataSetOfRights extends \Bitrix\Disk\Type\DataSet { public function filterByTaskId($taskId) { return $this->filterByField('TASK_ID', $taskId); } public function filterByAccessCode($accessCode) { return $this->filterByField('ACCESS_CODE', $accessCode); } public function filterNegative() { return $this->filterByField('NEGATIVE', true); } public function filterPositive() { return $this->filterByCallback(function($item) { return empty($item['NEGATIVE']); }); } }