/** * Cleanup publish prohibitions to be orphaned on relation remove. * * @param int $parentLessonId of relation to be removed * @param int $childLessonId of relation to be removed */ protected static function PublishProhibitionPurge_OnBeforeRelationRemove($in_parentLessonId, $in_childLessonId) { global $DB; /* We must remove publish prohibition for all lessons-descendants of $in_childLessonId, in context of courses-ancestors of $in_parentLessonId, that are will lost link (path). General version of algorithm: 1) Get list of all descendants of $in_childLessonId (include $in_childLessonId itself). 2) Get list of publish prohibitions for lessons from step 1. 3) Checks every prohibition, that prohibited lesson still have path to courseLessonId in context of which lesson is prohibited. Remove prohibition, when check failed. Optimized version of algorithm: 1) Get list of all ancectors (that are courses) of $in_parentLessonId (include $in_parentLessonId itself). EXPLAINATION: when DeleteRecursiveLikeHardlinks() function will work, relations will be removed from top to bottom mainly. It means, that if we will get list descendants on each step - it will be too many lessons. So, we get ancestors instead. 2) Get list of publish prohibitions in context of courses from step 1. 3) Checks every prohibition, that prohibited lesson still have path to courseLessonId in context of which lesson is prohibited. Remove prohibition, when check failed. One more optimization: In optimized algorithm, we shouldn't exclude non-courses from ancestors list on step 1, because, there is no non-courses can be in table b_learn_publish_prohibition. So, if we can do "SELECT * FROM b_learn_publish_prohibition WHERE COURSE_LESSON_ID IN (...list of all ancestors...)" and result will be as expected when ancesotrs list includes only courses. I'm sure, DB engine will do this job more fast, than my PHP-script excludes non-courses. And one more optimization: In step 1 of optimized algorithm we can limit tree of ancestors at $in_childLessonId (in case, when tree of ancestors are cycled). EXPLAINATION: $in_childLessonId will lost relation to parent lesson ($in_parentLessonId) only. It means, that all descendants of $in_childLessonId (include $in_childLessonId itself) will not lost link (path) to other immediate parents of $in_childLessonId and to $in_childLessonId itself. So we don't need to check descendsnts in context of $in_childLessonId or it's ancestors (except $in_parentLessonId and it's ancestros). About checking that lesson after relation remove still have path (link) to some course: 1) Get all ancestors of lesson with method self::GetListOfAncestors($lessonId, false, false, $arIgnoreEdges). It will return ancestors in case, when all edges from $arIgnoreEdges is interpreted as non-existing. 2) If course-lesson among this ancestors, that link will be still exists after relation removing. This steps will be perfomed for every pair of finded prohibitions. There is probability that prohibited lesson will be in few courses. We can optimize steps by caching ancestors for prohibited lessons. In spite of that probability is not good in general case, we should use cache, because cache hit can save very-very much time. And caching itself don't gives overhead for processor, it's only overheads RAM, but a little. So, final algorithm: 1) Get list of all ancectors of $in_parentLessonId (include $in_parentLessonId itself). Stop cycling BEFORE $in_childLessonId. 2) Get list of publish prohibitions in context of courses from step 1. 3) Checks every prohibition, that prohibited lesson still have path to courseLessonId in context of which lesson is prohibited. Remove prohibition, when check failed. */ // 1) Get list of all ancectors of $in_parentLessonId (include $in_parentLessonId itself). // Stop cycling BEFORE $in_childLessonId. $arAncestors = self::GetListOfAncestors($in_parentLessonId, false, $in_childLessonId); $arAncestors[] = (int) $in_parentLessonId; // include $in_parentLessonId itself // convert ids to int $arAncestorsInt = array(); foreach ($arAncestors as $ancestroId) { $arAncestorsInt[] = (int) $ancestroId; } // 2) Get list of publish prohibitions in context of courses from step 1. $rc = $DB->Query("SELECT COURSE_LESSON_ID, PROHIBITED_LESSON_ID\n\t\t\tFROM b_learn_publish_prohibition\n\t\t\tWHERE COURSE_LESSON_ID IN (" . implode(',', $arAncestorsInt) . ")", true); if ($rc === false) { throw new LearnException('EA_SQLERROR', LearnException::EXC_ERR_ALL_GIVEUP); } // This relation will be removed, so must be ignoredm when determine // future ancestors (after relation removing) $arIgnoreEdges = array(array('PARENT_LESSON' => (int) $in_parentLessonId, 'CHILD_LESSON' => (int) $in_childLessonId)); $arCache_ancestorsOfLesson = array(); while ($arData = $rc->Fetch()) { $prohibitedLessonId = (int) $arData['PROHIBITED_LESSON_ID']; $contextLessonId = (int) $arData['COURSE_LESSON_ID']; // Precache future ancestors (after relation removing) // for lesson, if they are not precached yet. if (!isset($arCache_ancestorsOfLesson[$prohibitedLessonId])) { $arCache_ancestorsOfLesson[$prohibitedLessonId] = self::GetListOfAncestors($prohibitedLessonId, false, false, $arIgnoreEdges); } // Will prohibited lesson lost link to course $contextLessonId? if (!in_array($contextLessonId, $arCache_ancestorsOfLesson[$prohibitedLessonId], true)) { // Yes, this lesson will not in subpathes of $contextLessonId, // so accorded publish prohibition must be removed. self::PublishProhibitionSetTo($prohibitedLessonId, $contextLessonId, false); } } CLearnCacheOfLessonTreeComponent::MarkAsDirty(); }
if (CLearnPath::IsUrlencodedPath($CHAPTER_ID)) { $oTmp = new CLearnPath(); $oTmp->ImportUrlencoded($CHAPTER_ID); $CHAPTER_ID = (int) $oTmp->GetBottom(); } elseif (substr($CHAPTER_ID, 0, 1) === '0') { $CHAPTER_ID = (int) substr($CHAPTER_ID, 1); } else { $CHAPTER_ID = (int) CLearnLesson::LessonIdByChapterId($CHAPTER_ID); } } else { $CHAPTER_ID = false; } $lessonCount = 0; $lessonCurrent = 0; // Get Course Content $arContents = CLearnCacheOfLessonTreeComponent::GetData($arParams['COURSE_ID']); foreach ($arContents as $arContent) { if ($arContent["TYPE"] == "CH") { $itemURL = CComponentEngine::MakePathFromTemplate($arParams["CHAPTER_DETAIL_TEMPLATE"], array("CHAPTER_ID" => '0' . $arContent["ID"], "COURSE_ID" => $arParams["COURSE_ID"])); if ($CHAPTER_ID == $arContent["ID"]) { $arContent["SELECTED"] = true; } else { $arContent["SELECTED"] = false; } $arContent["CHAPTER_OPEN"] = $arContent["SELECTED"]; } elseif ($CHAPTER_ID > 0 && $CHAPTER_ID == $arContent["ID"]) { $itemURL = CComponentEngine::MakePathFromTemplate($arParams["LESSON_DETAIL_TEMPLATE"], array("LESSON_ID" => $arContent["ID"], "COURSE_ID" => $arParams["COURSE_ID"])); $arContent["SELECTED"] = true; } else { $itemURL = CComponentEngine::MakePathFromTemplate($arParams["LESSON_DETAIL_TEMPLATE"], array("LESSON_ID" => $arContent["ID"], "COURSE_ID" => $arParams["COURSE_ID"])); /*$selftestURL = CComponentEngine::MakePathFromTemplate($arParams["SELF_TEST_TEMPLATE"],
/** * @param array of permissions. * @example * $arPermissions = array( * 1437 => array('CR' => 2, 'AU' => 1), // lesson_id = 1437, task_id = 2 and 1 * 1258 => array('AU' => 1), // lesson_id = 1258, task_id = 1 * 178 => array() // for this lesson will be cleaned all rights * ); * $userId = $USER->GetID(); * $oAccess = new CLearnAccess ($userId); * $oAccess->SetLessonsPermissions ($arPermissions); * */ public function SetLessonsPermissions($in_arPermissions) { global $DB; // Check args if (!is_array($in_arPermissions)) { throw new LearnException('', LearnException::EXC_ERR_ALL_ACCESS_DENIED | LearnException::EXC_ERR_ALL_PARAMS); } // First request for rights will not use cache (this will refresh cache) $isUseCacheForRights = false; $arPermissions = array(); foreach ($in_arPermissions as $in_lessonId => $arPermPairs) { if (!is_array($arPermPairs)) { throw new LearnException('', LearnException::EXC_ERR_ALL_ACCESS_DENIED | LearnException::EXC_ERR_ALL_PARAMS); } $lesson_id = self::StrictlyCastToInteger($in_lessonId); // Ensure, that for all requested lessons there is rights for changing rights. if (!$this->IsLessonAccessible($lesson_id, self::OP_LESSON_MANAGE_RIGHTS, $isUseCacheForRights)) { throw new LearnException('', LearnException::EXC_ERR_ALL_ACCESS_DENIED); } $isUseCacheForRights = true; // use cache for every next request for rights // Check params & escape for SQL $arPermissions[$lesson_id] = array(); foreach ($arPermPairs as $in_subject_id => $in_task_id) { $subject_id = $DB->ForSQL($in_subject_id); $task_id = self::StrictlyCastToInteger($in_task_id); $arPermissions[$lesson_id][$subject_id] = $task_id; } } // Yes, I know - most of products on MyISAM. So, In God We Trust. $DB->StartTransaction(); // Process setting permissions foreach ($arPermissions as $lesson_id => $arPermPairs) { $subject_id = $arPerm[0]; $task_id = $arPerm[1]; $rc = $DB->Query("DELETE FROM b_learn_rights \n\t\t\t\tWHERE LESSON_ID = {$lesson_id}", true); if ($rc === false) { $DB->Rollback(); throw new LearnException('EA_SQLERROR', LearnException::EXC_ERR_ALL_ACCESS_DENIED | LearnException::EXC_ERR_ALL_GIVEUP); } foreach ($arPermPairs as $subject_id => $task_id) { // All data already escaped above! $rc = $DB->Query("INSERT INTO b_learn_rights (LESSON_ID, SUBJECT_ID, TASK_ID) \n\t\t\t\t\tVALUES (" . $lesson_id . ", '" . $subject_id . "', " . $task_id . ")", true); if ($rc === false) { $DB->Rollback(); throw new LearnException('EA_SQLERROR', LearnException::EXC_ERR_ALL_ACCESS_DENIED | LearnException::EXC_ERR_ALL_GIVEUP); } } } // Amen $DB->Commit(); CLearnCacheOfLessonTreeComponent::MarkAsDirty(); }
public static function OnGroupDelete($GROUP_ID) { global $DB; $rc = $DB->Query("DELETE FROM b_learn_rights WHERE SUBJECT_ID='G" . (int) $GROUP_ID . "'", true) && $DB->Query("DELETE FROM b_learn_rights_all WHERE SUBJECT_ID='G" . (int) $GROUP_ID . "'", true); CLearnCacheOfLessonTreeComponent::MarkAsDirty(); return $rc; }