Бизнес-процессы на данный момент имеют только самую базовую поддержку расширенных прав элементов и, что часто самое неприятное, не могут изменять существующие права элемента, а только добавлять свои, если они не совпадают с уже существующими. Это существенный недостаток, так как не позволяет динамически управлять правами на документы в соответствии с заложенной вами логикой бизнес-процесса.
Например, дать доступ к документу только тем сотрудникам, которые указаны в бизнес-процессе, а остальным запретить. Или запретить изменять документ автору после какого-то этапа. И тому подобные кейзы.
Поддержка такого функционала обещается в дальнейшем, с полным набором объектов новых расширенных полномочий, но когда это будет - доподлинно не известно никому, а работать нужно уже сейчас.
Поэтому по просьбе некоторых пользователей собрал мод стандартного активити установки прав на основе своих хаков модулей. Собирал "на коленке", особо не проверяя, так что перед использованием на рабочих проектах настоятельно рекомендую всесторонне протестировать.
Теперь ближе к делу. Установка прав на бизнес-процесс и объект, над которым он происходит, производится методом DocumentService->SetPermissions(), который в свою очередь вызывает обёртку объекта для бизнес-процессов в соответствующем модуле. Для библиотеки документов и инфоблоков это класс CIBlockDocument и его соответствующий метод SetPermissions(), в нём-то и происходит обработка прав, которая нас на текущий момент не устраивает. Поэтому мы выдернем метод из класса и поместим в наше действие, где переписываем его под наши нужды.
Но так как DocumentService->SetPermissions() вызывается не непосредственно из активити, а опосредованно, через метод StateService->SetStatePermissions(), то придётся и последний выдёргивать к нам, чтобы не ломать его в модуле.
Итак, склеив и модифицировав функционал двух указанных выше методов, мы получим примерно следующее (с пояснениями "по коду дела"):
Такой вот немного заковыристый метод получился. Он вводит пять возможных способов работы с правами, вместо существующих двух (которые эквивалентны нашим новым AddPermissions и ClearPermissions). Вот эти константы:
Новый метод и наши константы мы разместим в кастомном, откопированном с оригинального SetPermissionsActivity действии, которое обзовём SetPermissionsActivityExRights.
Помимо этого, нам ещё нужно модифицировать часть, которая вызывается при исполнении действия, чтобы она обращалась к нашему новому методу в нужных нам случаях, и работала как обычно в других.
Ещё немного требуется изменить свойства действия и диалог параметров действия, но это просто, поэтому не буду заострять внимания.
Полученное действие позволяет гибко управлять правами элемента: добавлять только новые права, заменять/удалять права, установленные ранее из нашего бизнес-процесса, заменять или вовсе удалять любые права, ранее установленные или отнаследованные элементом.
Готовое действие можно взять в приложенном архиве, потом положить его в /bitrix/activities/custom и использовать.
Ещё раз повторюсь о необходимости тестирования, так как действие собирал быстро и у меня времени погонять его на данный момент нет. Использовать на собственный страх и риск.
ЗЫ. Может возникнуть вопрос, почему нет никакой обработки дублирующихся прав для операции AddPermissions - просто потому, что дубли самостоятельно откинет метод $ob->SetRights($ar), так что я не стал дублировать эту работу.
UPD: Отмечу одну, возможно, неочевидную вещь: может возникнуть вопрос, а как быть, если используется управление правами через права статусов бизнес-процесса, необходимо будет помимо установки прав непосредственно в настройках статуса дополнять этим кастомным действием установки прав? Ответ - нет, этого не требуется, потому что текущая реализация схемы установки прав из БП не позволяет изменять права, установленные не из нашего бизнес-процесса, права же, установленные из текущего бизнес-процесса ранее, успешно меняются текущим функционалом. Если подумать, отсюда следует, что нам достаточно единожды за весь бизнес-процесс применить новое кастомное действие, например, при входе в первый статус, и сбросить уже существовавшие права элемента, записать новые права из бизнес-процесса, и в дальнейшем мы сможем уже переопределять их штатными механизмами, в т.ч. и через права на статус БП.
Если поразмышлять ещё немного, то становится очевидным, что и без этого кастомного действия можно обойтись, достаточно только сбросить текущие права элемента, а затем штатными активити установить требуемые новые. Такой сброс можно осуществить, например, действием PHP-код, в котором использовать примерно следующий код из вышеприведённого примера:
Этого достаточно, а дальше уже назначаем и изменяем права элемента штатными механизмами.
Например, дать доступ к документу только тем сотрудникам, которые указаны в бизнес-процессе, а остальным запретить. Или запретить изменять документ автору после какого-то этапа. И тому подобные кейзы.
Поддержка такого функционала обещается в дальнейшем, с полным набором объектов новых расширенных полномочий, но когда это будет - доподлинно не известно никому, а работать нужно уже сейчас.
Поэтому по просьбе некоторых пользователей собрал мод стандартного активити установки прав на основе своих хаков модулей. Собирал "на коленке", особо не проверяя, так что перед использованием на рабочих проектах настоятельно рекомендую всесторонне протестировать.
Теперь ближе к делу. Установка прав на бизнес-процесс и объект, над которым он происходит, производится методом DocumentService->SetPermissions(), который в свою очередь вызывает обёртку объекта для бизнес-процессов в соответствующем модуле. Для библиотеки документов и инфоблоков это класс CIBlockDocument и его соответствующий метод SetPermissions(), в нём-то и происходит обработка прав, которая нас на текущий момент не устраивает. Поэтому мы выдернем метод из класса и поместим в наше действие, где переписываем его под наши нужды.
Но так как DocumentService->SetPermissions() вызывается не непосредственно из активити, а опосредованно, через метод StateService->SetStatePermissions(), то придётся и последний выдёргивать к нам, чтобы не ломать его в модуле.
Итак, склеив и модифицировав функционал двух указанных выше методов, мы получим примерно следующее (с пояснениями "по коду дела"):
public function SetStatePermissionsHack($documentId) { global $DB; $arStatePermissions = $this->Permission; // массив полномочий $workflowId = trim($this->GetWorkflowInstanceId()); // ИД БП $bRewrite = $this->Rewrite; // наш способ работы с правами list($moduleId, $entity, $documentType) = $this->GetDocumentType(); // дёргаем тип документа if (strlen($workflowId) <= 0) throw new Exception("workflowId"); /* === Блок работы со штатными правами БП из StateService->SetStatePermissions() === Идеологически плохой и неправильный кусок, так как база может поменяться, а активити менять за вас никто не будет. Но, выбора, в общем-то, нет */ if (in_array($bRewrite, array(self::ClearPermissions, self::ClearAllPermissions))) { $DB->Query( "DELETE FR OM b_bp_workflow_permissions ". "WH ERE WORKFLOW_ID = '".$DB->ForSql($workflowId)."' " ); } /* Как видно ниже, написан он тоже не ахти, никакой защиты от дублей и пустышек, просто фигачим всё, что есть, поэтому в правах БП часто много мусора */ foreach ($arStatePermissions as $taskId => $arUsers) { foreach ($arUsers as $user) { $DB->Query( "INS ERT INTO b_bp_workflow_permissions (WORKFLOW_ID, OBJECT_ID, PERMISSION) ". "VAL UES ('".$DB->ForSql($workflowId)."', '".$DB->ForSql($user)."', '".$DB->ForSql($taskId)."')" ); } } /* === Конец блока работы со штатными правами БП === */ $documentId = intval($documentId); if ($documentId <= 0) throw new CBPArgumentNullException("documentId"); $iblockId = intval(substr($documentType, strlen("iblock_"))); if ($iblockId <= 0) throw new CBPArgumentOutOfRangeException("documentType", $documentType); if (CIBlock::GetArrayByID($iblockId, "RIGHTS_MODE") !== "E") // нас интересуют только расширенные права return; $i = 0; $l = strlen("user_"); $arNew = array(); // Массив новых прав $arNewGroupCodes = array(); // Группкоды новых прав foreach ($arStatePermissions as $taskId => $arUsers) { foreach ($arUsers as $user) { if (!empty($user)) { $gc = null; if ($user == 'author') { $gc = "CR"; // Родная роль "Автор" инфоблоков, вместо использования конкретного пользователя, как сейчас. Позволяет гибче управлять правами в дальнейшем } elseif (substr($user,0,2) != "{=") // тупозащита для отсечения переданных данных не о юзере, а макросов переменных или параметров. игнорим, чтобы не было мусора в правах иб { $gc = ((substr($user, 0, $l) == "user_") ? "U".substr($user, $l) : "G".$user); // либо юзер, либо юзергрупп, другое не поддерживаем } if ($gc != null) { $arNew["n".$i] = array("GROUP_CODE" => $gc, "TASK_ID" => $taskId, "XML_ID" => $workflowId); // забиваем массив новых прав $arNewGroupCodes[] = $gc; // сохраняем группкод для дальнейшего парсинга старых прав $i++; } } } } $ob = new CIBlockElementRights($iblockId, $documentId); // создаём объект прав и инициализируем нашим элементом if ($bRewrite == self::ClearAllPermissions) // если нам не нужны старые права, мы их просто не извлекаем, делаем пустой массив $ar = array(); else // иначе дёргаем текущие права на элемент $ar = $ob->GetRights(); if ($bRewrite != self::AddPermissions && $bRewrite != self::ClearAllPermissions) // парсим права только если надо { foreach ($ar as $i => $arRight) { if ($bRewrite == self::ClearPermissions && $arRight["XML_ID"] == $workflowId) // если очищаем старые права данного БП unset($ar[$i]); elseif ($bRewrite == self::RewritePermissions && $arRight["XML_ID"] == $workflowId && in_array($arRight["GROUP_CODE"], $arNewGroupCodes)) // если заменяем права данного БП при совпадении группкода unset($ar[$i]); elseif ($bRewrite == self::RewriteAllPermissions && in_array($arRight["GROUP_CODE"], $arNewGroupCodes)) // если заменяем любые права при совпадении unset($ar[$i]); } } $ar = $ar+$arNew; // склеиваем массивы без потери ключей $ob->SetRights($ar); // устанавливаем права на элемент, радуемся } |
const AddPermissions = 1; // Добавляем права БП, приоритет старых прав элемента и БП const RewritePermissions = 2; // Перетираем старые права БП при совпадении, приоритет прав элемента const ClearPermissions = 3; // Удаляем старые полномочия БП, приоритет прав элемента const RewriteAllPermissions = 4;// Перетираем любые полномочия при совпадении, приоритет новых прав БП const ClearAllPermissions = 5; // Удаляем все старые полномочия |
Помимо этого, нам ещё нужно модифицировать часть, которая вызывается при исполнении действия, чтобы она обращалась к нашему новому методу в нужных нам случаях, и работала как обычно в других.
public function Execute() { // модифицируем метод, вебдав и иблоки отправляем по новому пути, всё остальное - по старому list($moduleId, $entity, $documentId) = CBPHelper::ParseDocumentId($this->GetDocumentId()); if ($moduleId == "iblock" || $moduleId == "webdav") $this->SetStatePermissionsHack($documentId); // используем нашу функцию else { $bRewriteOld = true; // стандартный тип перезаписи прав if ($this->Rewrite == self::AddPermissions) // не стираем старые права только в одном случае $bRewriteOld = false; $stateService = $this->workflow->GetService("StateService"); $stateService->SetStatePermissions($this->GetWorkflowInstanceId(), $this->Permission, $bRewriteOld); } return CBPActivityExecutionStatus::Closed; } |
Полученное действие позволяет гибко управлять правами элемента: добавлять только новые права, заменять/удалять права, установленные ранее из нашего бизнес-процесса, заменять или вовсе удалять любые права, ранее установленные или отнаследованные элементом.
Готовое действие можно взять в приложенном архиве, потом положить его в /bitrix/activities/custom и использовать.
Ещё раз повторюсь о необходимости тестирования, так как действие собирал быстро и у меня времени погонять его на данный момент нет. Использовать на собственный страх и риск.
ЗЫ. Может возникнуть вопрос, почему нет никакой обработки дублирующихся прав для операции AddPermissions - просто потому, что дубли самостоятельно откинет метод $ob->SetRights($ar), так что я не стал дублировать эту работу.
UPD: Отмечу одну, возможно, неочевидную вещь: может возникнуть вопрос, а как быть, если используется управление правами через права статусов бизнес-процесса, необходимо будет помимо установки прав непосредственно в настройках статуса дополнять этим кастомным действием установки прав? Ответ - нет, этого не требуется, потому что текущая реализация схемы установки прав из БП не позволяет изменять права, установленные не из нашего бизнес-процесса, права же, установленные из текущего бизнес-процесса ранее, успешно меняются текущим функционалом. Если подумать, отсюда следует, что нам достаточно единожды за весь бизнес-процесс применить новое кастомное действие, например, при входе в первый статус, и сбросить уже существовавшие права элемента, записать новые права из бизнес-процесса, и в дальнейшем мы сможем уже переопределять их штатными механизмами, в т.ч. и через права на статус БП.
Если поразмышлять ещё немного, то становится очевидным, что и без этого кастомного действия можно обойтись, достаточно только сбросить текущие права элемента, а затем штатными активити установить требуемые новые. Такой сброс можно осуществить, например, действием PHP-код, в котором использовать примерно следующий код из вышеприведённого примера:
list($moduleId, $entity, $documentId) = CBPHelper::ParseDocumentId($this->GetDocumentId()); // получаем ИД документа list($moduleId, $entity, $documentType) = $this->GetDocumentType(); // дёргаем тип документа $documentId = intval($documentId); if ($documentId <= 0) throw new CBPArgumentNullException("documentId"); $iblockId = intval(substr($documentType, strlen("iblock_"))); if ($iblockId <= 0) throw new CBPArgumentOutOfRangeException("documentType", $documentType); $ar = array(); $ob = new CIBlockElementRights($iblockId, $documentId); // создаём объект прав и инициализируем нашим элементом $ob->SetRights($ar); // сбрасываем все текущие права элемента |