В рамках разработки одного сервиса в нашей компании возникла интересная задача. Возможно, решение оной и вам будет интересно.
Задача.
Предоставить доступ некоторой группе пользователей на возможность создания и редактирование курсов , уроков и глав без возможности просмотра и редактирования курсов уроков и глав не относящихся к данной группе пользователей
Проблема
В битриксе такая возможность отсутствует, так как если группе пользователей предоставить возможность просмотра модуля обучения, а к определенному курсу возможность администрирования, у данной группы пользователя появляться возможность редактирования и создания тестов и вопросов, но не появляется возможность создавать главы и уроки, вот такой абсурд
И это не говоря уже о не возможности создания курсов.
Комментарии техподдержки по данному вопросу :
Права уроков не наследуются от курсов по соображениям безопасности (т.к. у урока может быть более одного родителя и возможна эскалация привилегий за счет присоединения урока к своему курсу с заведомо нужными правами).
Решение проблемы
При попытке создания пользователем курса или урока, создавать оные от имени администратора и давать права к этому уроку или кусу группе пользователей, от которой производилась попытка создания. После же перебрасывать пользователя на редактирование созданного урока или курса.
Приступим к исполнению
Подзадача 1 Сформировать у пользователя в админке кнопки создания курсов и уроков, так как при ограничении прав, данные кнопки отсутствуют.
Код формирования кнопок вешаем на событие OnAdminContextMenuShow вызывается в функции
Кнопки формируем примерно похожими на стандартные кнопки.
Реализация. Код из bitrix/php_interface/init.php
AddEventHandler('main', 'OnAdminContextMenuShow', 'OrderDetailAdminContextMenuShow'); function OrderDetailAdminContextMenuShow(&$items){ if ($_SERVER['REQUEST_METHOD']=='GET' && $GLOBALS['APPLICATION']->GetCurPage()=='/bitrix/admin/learn_unilesson_admin.php' ) { // срабатывает функция только на страницах курсов модуля обучения if ($_GET['LESSON_PATH']){ $urlepir="learn_course_edit.php?lang=ru&LESSON_PATH=".$_GET['LESSON_PATH'];} else {$urlepir="learn_course_edit.php?lang=ru";}; //формируем ссылку для добавления курса $items[] = array( "TEXT"=>"Добавить курс", // название "LINK"=>$urlepir, // ссылка "TITLE"=>"Добавить новый курс", // подпись "ICON"=>"btn_new" // картинка ); $urlepir="learn_unilesson_edit.php?lang=ru&PARENT_LESSON_ID=".$_GET['PARENT_LESSON_ID']."&PROPOSE_RETURN_LESSON_PATH=".$_GET['LESSON_PATH']; // формируем ссылку для урока привязанного к курсу if ($_GET['PARENT_LESSON_ID']<0) {$urlepir='learn_unilesson_edit.php?lang=ru&PARENT_LESSON_ID=-1&LESSON_PATH=';} ; // формируем ссылку для урока не привязанного к курсу $items[] = array( "TEXT"=>"Добавить главу/урок", // название "LINK"=>$urlepir, // ссылка "TITLE"=>"Добавить главу/урок", // подпись "ICON"=>"btn_new" // картинка ); } } |
Подзадача 2: перехватить попытку создания курса или урока и запустить скрипт реализующий нашу идею
Перехват попытки будем осуществлять в bitrix/php_interface/admin_header.php
Скрипт будем вызывать через curl так как необходимо, чтобы он выполнился не на хите пользователя. Необходимо это потому что в скрипте будет осуществятся авторизация пользователя которому даны права администратора модуля обучения
Если запускать скрипт на хите пользователя и даже авторизовать обратно текущего пользователя после выполнения скрипта, есть вероятность что во время выполнения возникнет какая либо ошибка и обратная авторизация не произойдет. А мы не можем допустить что бы пользователь вдруг получил админский доступ к системе.
Скрипт же в свою очередь нужно поместить в укромный уголок «сайта» что бы на скрипт никто не наткнулся случайно. Потому как, все же его нужно помещать в общедоступную папку
Для примера у нас скрипт называется test.php
Возвращает он у нас id созданного урока или курса
Реализация
<? function getUrlContent($url){ //функция вызова скрипта $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; .NET CLR 1.1.4322)'); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5); curl_setopt($ch, CURLOPT_TIMEOUT, 5); $data = curl_exec($ch); $httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); return ($httpcode>=200 && $httpcode<300) ? $data : false; } global $USER; if (!$USER->IsAdmin()){ //все должно работать если текущий юзер не админ. if( $GLOBALS['APPLICATION']->GetCurPage()=='/bitrix/admin/learn_course_edit.php' and !isset($_GET['COURSE_ID']) ){ //Проверяем вызыается ли страница создания/редактирования курса и нет ли параметра указывающего что курс уже создан и его хотят отредактировать, без второй проверки редактирование курсов станет не возможным а попытка создания обернется зацикливанием $tr=getUrlContent('http://example.ru/test.php?lang=ru&PARENT_LESSON_ID='.$_GET['PARENT_LESSON_ID'].'&C=Y'); //вызываем скрипт с парметрами оповещающими о том что нужно создать именно курс с родительской привязкой $_GET['PARENT_LESSON_ID'] если таковая имеется LocalRedirect("https://example.ru/bitrix/admin/learn_course_edit.php?lang=ru&COURSE_ID=".$tr."&filter=Y&set_filter=Y"); // переадресовываем на страницу редактирования созданного курса }; if( $GLOBALS['APPLICATION']->GetCurPage()=='/bitrix/admin/learn_unilesson_edit.php' and !isset($_GET['LESSON_ID']) ){ // аналогичная проверка но уже на создание урока $tr=getUrlContent('http://example.ru/test.php?lang=ru&PROPOSE_RETURN_LESSON_PATH='.$_GET['PROPOSE_RETURN_LESSON_PATH'].'&L=Y'); //формируем параметры указывавшие на то что мы хотим создать урок и какой у урока будет родительский курс/глава LocalRedirect("https://examle.ru/bitrix/admin/learn_unilesson_edit.php?lang=ru&LESSON_ID=".$tr."&LESSON_PATH=".$tr."&filter=Y&set_filter=Y"); //переадресовываем на страницу редактирования урока }; }; ?> |
Далее переходим к реализации самого скрипта
Подзадачи скрипта
- Авторизоваться от пользователя имеющего права на создание курсов или уроков
- Создать курс или урок с нужной родительской привязкой
- Назначить права курсу или уроку той группе пользователей от которых происходила попытка создания
Возникшие проблемы
- Задание прав курсу при создании игнорируется системой, хотя задокументированы
- Задание прав созданному курсу задокументированный но не работают
- Создание урока не задокументирован
- Задания привязок к курсам главам не задокументирован
- Задание прав созданному курсу не задокументированы
Для задания прав созданному курсу пришлось расковырять админку , и удалось из нее вытащить вот такую вот конструкцию, позже все было найдено в живом описании api
$oAccess = CLearnAccess::GetInstance($USER->GetID());
$oAccess->SetLessonsPermissions (array( $IDL => $arPermPairs));
Где $IDL Это COURSE_ID курса — не путать с просто ID курса
$arPermPairs массив параметров доступа вида [«G»+ id группы]=> id уровня доступа
Для создания урока найдена не задокументированная функция
ClearnLesson->Add($arFields);
Где $arFields массив параметров урока. Внимание, некоторые параметры задавать нельзя, вываливается ошибка
Для создания привязок родителя к уроку также была найдена не задокументированная функция
CLearnLesson::RelationAdd( $rt, $ID, array("SORT"=>500));
Где $rt это id родителя (в случае с курсами это COURSE_ID)
$ID это id самого урока
Зачем там sort я не знаю но в исходниках модуля он присутствует
Для задания прав к уроку используется та же функция что и у курса
Код скрипта:
<? require_once($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/main/include/prolog_before.php"); global $USER; $USER->Authorize(1); //авторизация от пользователя имеющего права на нужныедействия if($_GET["C"]=="Y") { // если происходит попытка добавить курс if (CModule::IncludeModule("learning")) { $arFields = Array( "ACTIVE" => "Y", "NAME" => "очередной Курс", "SITE_ID" => Array("s1"), //Sites "SORT" => "100" ); $course = new CCourse; $ID = $course->Add($arFields); //добавляем курс $success = ($ID>0); if($success) { } else { if($e = $APPLICATION->GetException()); echo "Error: ".$e->GetString(); //если происходт ошибка ее неплохо было бы потом обработать } }; if (CModule::IncludeModule("learning")) { $res = CCourse::GetList( Array("SORT"=>"ASC"), Array("ID" => $ID), $bIncCnt = true ); while ($arCourse = $res->GetNext()) { $IDL=$arCourse[LESSON_ID]; // Получаем lesson_id курса } } $arPermPairs[“G”.4]=84; // формируем масив прав доступа, у вас это могут быть другие группы и другие уровни, у меня же в место этого передались группы в параметрах, но в пример это не вошло $oAccess = CLearnAccess::GetInstance($USER->GetID()); $oAccess->SetLessonsPermissions (array( $IDL => $arPermPairs)); // задаем права print_r($ID); // возвращаем id созданого курса }; if($_GET["L"]=="Y") { //если создается урок то выполняем это $pieces = explode(".", $_GET['PROPOSE_RETURN_LESSON_PATH']); $rt=end( $pieces); // получаем id к чему привязывать урок if (CModule::IncludeModule("learning")) { $COURSE_ID = $rt; $arFields = Array( "ACTIVE" => "Y", "NAME" => "Очередной урок", "DETAIL_TEXT" => "Очередной урок" ); $lesson = new CLearnLesson; $ID = $lesson->Add($arFields); $success = ($ID>0); if($success) { } else { if($e = $APPLICATION->GetException()) echo "Error: ".$e->GetString(); // опять же если ошибка то не плохо было бы обработать ее, но в примере это не рассматривается } if ($_GET['PROPOSE_RETURN_LESSON_PATH']!=""){ CLearnLesson::RelationAdd( $rt, $ID, array("SORT"=>500)); // привязываем урок к родителю если он вообще должен быть привязан }; } if (CModule::IncludeModule("learning")) { $IDL=$ID ; $arPermPairs['G'.4]=84; //также массив прав $oAccess = CLearnAccess::GetInstance($USER->GetID()); $oAccess->SetLessonsPermissions (array( $IDL => $arPermPairs)); //задаем права к уроку print_r($ID); // возвращаем id урока }; ?> |
Вот собственно и все.
Для большего понимания происходящего просьба вчитываться в комментарии в приведенном коде.
Валидность php не проверял так как правки для статьи вносил уже в редакторе
На правильность решения поставленной задачи не претендую, некоторые моменты можно и оптимизировать.
Приведенный код не рекомендуется использовать без понимания что же происходит в каждой строке.
Так же, пользуясь случаем хотелось бы спросить у уважаемых разработчиков!
Как в новом текстовом редакторе, отключить некоторые функции/кнопки для пользователей?