Your IP : 18.188.175.197


Current Path : /home/bitrix/ext_www/home-comfort.in.ua/bitrix/modules/learning/classes/general/
Upload File :
Current File : /home/bitrix/ext_www/home-comfort.in.ua/bitrix/modules/learning/classes/general/clearnaccess.php

<?php

interface ILearnAccessInterface
{
	/**
	 * @param int operations code (taken from sum of constants with prefix OP_)
	 * @param bool flag isUseCache (can be omitted, false by default)
	 * @param string prefix for tables acronyms (can be omitted)
	 * 
	 * @return string #sql_code# for usage in "SELECT ... WHERE LESSON_ID IN (#sql_code#)"
	 * 
	 * @example
	 * $o = CLearnAccess::GetInstance ($someUserId);
	 * $sql = $o->SQLClauseForAccessibleLessons (CLearnAccess::OP_LESSON_READ + CLearnAccess::OP_LESSON_WRITE);
	 * // Selects only lessons, which are accessible by user with id = $someUserId
	 * $rc = $DB->Query ("SELECT NAME FROM b_learn_lesson WHERE ACTIVE = 'Y' AND ID IN (" . $sql . ")");
	 */
	public function SQLClauseForAccessibleLessons ($in_bitmaskOperations, $isUseCache = false, $lessonId = 0, $in_prfx = 'DEFPRFX');


	public static function GetNameForTask ($taskId);

	/**
	 * @return array of possible rights. Example of array item:
	 *			$arPossibleRights['ID'] = array(
	 *				'name'              => 'NAME',
	 *				'name_human'        => $nameUpperCase,
	 *				'sys'               => 'SYS',
	 *				'description'       => 'DESCRIPTION',
	 *				'description_human' => $descrUpperCase,
	 *				'binding'           => 'BINDING'
	 *				);
	 */
	public static function ListAllPossibleRights();


	/**
	 * This function include CR to access symbols when checks base rights.
	 * @param int bitmask of operations (constants self::OP_...)
	 * @param bool use cache
	 * 
	 * @return bool true - if there is access to given operations
	 */
	public function IsBaseAccessForCR ($in_bitmaskRequested, $isUseCache = false);


	/**
	 * @param int bitmask of operations (constants self::OP_...)
	 * @param bool use cache (false be default)
	 * @param bool does include CR to check? (false by default)
	 * 
	 * @return bool true - if there is access to given operations
	 */
	public function IsBaseAccess ($in_bitmaskRequested, $isUseCache = false, $checkForAuthor = false);


	/**
	 * @param array $arPermPairs, for example: array ('CR' => 4, 'U2' => '1', ...).
	 * All unlisted access symbols ("subjects") will be removed.
	 */
	public function SetBasePermissions ($in_arPermPairs);


	/**
	 * @return array of base for all lessons permissions
	 * @example
	 * <?php
	 * $oAccess = CLearnAccess::getInstance ($USER->GetID());
	 * $arPermPairs = $oAccess->GetLessonPermissions ($some_lesson_id);
	 * ?>
	 * $arPermPairs now contains
	 * array ('AU' => 1, 'U12' => '3', 'CR' => 2, ...)
	 */
	public function GetBasePermissions ();


	/**
	 * @return array of lesson's permissions
	 * @example
	 * <?php
	 * $oAccess = CLearnAccess::getInstance ($USER->GetID());
	 * $arPermPairs = $oAccess->GetLessonPermissions ($some_lesson_id);
	 * ?>
	 * $arPermPairs now contains
	 * array ('AU' => 1, 'U12' => '3', 'CR' => 2, ...)
	 */
	public function GetLessonPermissions ($in_lessonId);


	/**
	 * @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 = CLearnAccess::getInstance ($userId);
	 * $oAccess->SetLessonsPermissions ($arPermissions);
	 * 
	 */
	public function SetLessonsPermissions ($in_arPermissions);


	/**
	 * This function checks access rights for user to given lesson. 
	 * It's includes checks for base rights (shared for all lessons).
	 * 
	 * @return bool true - if lesson is accessible by given user for given operations.
	 */
	public function IsLessonAccessible ($in_lessonId, $in_bitmaskOperations, $isUseCache = false);


	public function GetAccessibleLessonsList($in_bitmaskOperations, $isUseCache = false);


	/**
	 * @param int id of lesson
	 * @param int bitmask of operations (constants self::OP_...)
	 * @param bool use cache (false be default)
	 * 
	 * @return array of symbols, which has access to lesson with given operation bitmask.
	 * 
	 * @example of returned array: array ('G11', 'U11', 'AU', 'CR')
	 */
	public static function GetSymbolsAccessibleToLesson ($in_lessonId, $in_bitmaskOperations, $isUseCache = false);
}


class CLearnAccess implements ILearnAccessInterface
{
	protected static $instanceOfSelf = array();
	protected static $CAccessLastUpdated = false;

	protected $userId = false;

	const OP_LESSON_READ                = 0x0001;
	const OP_LESSON_CREATE              = 0x0002;
	const OP_LESSON_WRITE               = 0x0004;
	const OP_LESSON_REMOVE              = 0x0008;
	const OP_LESSON_LINK_TO_PARENTS     = 0x0010;
	const OP_LESSON_UNLINK_FROM_PARENTS = 0x0020;
	const OP_LESSON_LINK_DESCENDANTS    = 0x0040;
	const OP_LESSON_UNLINK_DESCENDANTS  = 0x0080;
	const OP_LESSON_MANAGE_RIGHTS       = 0x0100;

	protected static $arOperations = array(
		'lesson_read'                => self::OP_LESSON_READ,
		'lesson_create'              => self::OP_LESSON_CREATE,
		'lesson_write'               => self::OP_LESSON_WRITE,
		'lesson_remove'              => self::OP_LESSON_REMOVE,
		'lesson_link_to_parents'     => self::OP_LESSON_LINK_TO_PARENTS,
		'lesson_unlink_from_parents' => self::OP_LESSON_UNLINK_FROM_PARENTS,
		'lesson_link_descendants'    => self::OP_LESSON_LINK_DESCENDANTS,
		'lesson_unlink_descendants'  => self::OP_LESSON_UNLINK_DESCENDANTS,
		'lesson_manage_rights'       => self::OP_LESSON_MANAGE_RIGHTS
		);


	// prevent creating throughs "new"
	private function __construct($in_userId)
	{
		$this->userId = self::StrictlyCastToInteger ($in_userId);
	}


	// prevent clone of object
	private function __clone()
	{
	}


	// prevent wakeup
	private function __wakeup()
	{
	}

	/**
	 * @param $in_userId
	 * @return CLearnAccess
	 */
	public static function GetInstance($in_userId)
	{
		$userId = self::StrictlyCastToInteger ($in_userId);

		if ( ! array_key_exists($userId, self::$instanceOfSelf) )
			self::$instanceOfSelf[$userId] = new CLearnAccess($userId);
		
		return (self::$instanceOfSelf[$userId]);
	}


	/**
	 * If user logged in - get hash for of access symbols for user.
	 * If user isn't logged in - get hash of access symbols for not authorized users.
	 */
	public static function GetAccessSymbolsHashForSiteUser()
	{
		global $USER;

		$userId = $USER->GetID();
		$arCodes = array();

		if ($userId > 0)
		{
			$oAccess = CLearnAccess::GetInstance ($userId);
			$arCodes = $oAccess->GetAccessCodes();
		}
		else
			$arCodes = array('G2');	// G2 - is group included all users (not authorized too)

		$hash = base64_encode (serialize($arCodes));

		return ($hash);
	}


	public static function GetNameForTask($taskId)
	{
		global $DB, $MESS;

		$rc = $DB->Query("SELECT NAME FROM b_task WHERE ID = " . (int) $taskId . " AND MODULE_ID = 'learning'");
		if ($rc === false)
		{
			throw new LearnException ('EA_SQLERROR',
				LearnException::EXC_ERR_ALL_GIVEUP);
		}

		$row = $rc->Fetch();

		if ( ! isset($row['NAME']) )
		{
			throw new LearnException ('EA_NOT_EXISTS',
				LearnException::EXC_ERR_ALL_LOGIC);
		}

		$nameUpperCase = strtoupper($row['NAME']);

		return CTask::GetLangTitle($nameUpperCase, "learning");
	}


	/**
	 * @return array of possible rights. Example of array item:
	 *			$arPossibleRights['ID'] = array(
	 *				'name'              => 'NAME',
	 *				'name_human'        => $nameUpperCase,
	 *				'sys'               => 'SYS',
	 *				'description'       => 'DESCRIPTION',
	 *				'description_human' => $descrUpperCase,
	 *				'binding'           => 'BINDING'
	 *				);
	 */
	public static function ListAllPossibleRights()
	{
		global $DB, $MESS;

		$rc = $DB->Query("SELECT ID, NAME, SYS, DESCRIPTION, BINDING FROM b_task WHERE MODULE_ID = 'learning'");
		if ($rc === false)
		{
			throw new LearnException ('EA_SQLERROR',
				LearnException::EXC_ERR_ALL_ACCESS_DENIED
				| LearnException::EXC_ERR_ALL_GIVEUP);
		}

		$arPossibleRights = array();
		while ($row = $rc->Fetch())
		{
			$nameUpperCase = strtoupper($row['NAME']);

			$arPossibleRights[$row['ID']] = array(
				'name'              => $row['NAME'],
				'name_human'        => CTask::GetLangTitle($nameUpperCase, "learning"),
				'sys'               => $row['SYS'],
				'description'       => $row['DESCRIPTION'],
				'description_human' => CTask::GetLangDescription($nameUpperCase, "", "learning"),
				'binding'           => $row['BINDING']
			);
		}

		return ($arPossibleRights);
	}


	public static function GetSymbolsAccessibleToLesson ($in_lessonId, $in_bitmaskOperations, $isUseCache = false)
	{
		global $DB;
		static $cacheSymbols = array();

		if ( ! (is_int($in_bitmaskOperations) && ($in_bitmaskOperations > 0)) )
		{
			throw new LearnException ('bitmask must be an integer > 0', 
				LearnException::EXC_ERR_ALL_ACCESS_DENIED 
				| LearnException::EXC_ERR_ALL_PARAMS);
		}

		$lessonId = (int) $in_lessonId;

		$cacheKey = 'k' . $in_lessonId . '|' . $in_bitmaskOperations;

		if ( ! ($isUseCache && isset($cacheSymbols[$cacheKey])) )
		{
			$arSymbols = array();
			$sqlOperationsNames = self::ParseOperationsForSQL ($in_bitmaskOperations);

			$rc = $DB->Query(
				"SELECT TLR.SUBJECT_ID AS SYMBOLS
				FROM b_learn_rights TLR
				INNER JOIN b_task_operation TTO
					ON TTO.TASK_ID = TLR.TASK_ID
				INNER JOIN b_operation XTO
					ON XTO.ID = TTO.OPERATION_ID
				WHERE TLR.LESSON_ID = " . $lessonId . "
					AND XTO.MODULE_ID = 'learning'
					AND XTO.NAME IN (" . $sqlOperationsNames . ")

				UNION

				SELECT TLRA.SUBJECT_ID AS SYMBOLS
				FROM b_learn_rights_all TLRA
				INNER JOIN b_task_operation TTO
					ON TTO.TASK_ID = TLRA.TASK_ID
				INNER JOIN b_operation XTO
					ON XTO.ID = TTO.OPERATION_ID
				WHERE XTO.MODULE_ID = 'learning'
					AND XTO.NAME IN (" . $sqlOperationsNames . ")
				", true);

			if ($rc === false)
			{
				throw new LearnException ('EA_SQLERROR', 
					LearnException::EXC_ERR_ALL_ACCESS_DENIED 
					| LearnException::EXC_ERR_ALL_GIVEUP);
			}

			while ($row = $rc->Fetch())
				$arSymbols[] = $row['SYMBOLS'];

			$cacheSymbols[$cacheKey] = $arSymbols;
		}

		return ($cacheSymbols[$cacheKey]);
	}


	/**
	 * This function include CR to access symbols when checks base rights.
	 * @param int bitmask of operations (constants self::OP_...)
	 * @param bool use cache
	 * 
	 * @return bool true - if there is access to given operations
	 */
	public function IsBaseAccessForCR ($in_bitmaskRequested, $isUseCache = false)
	{
		return ($this->IsBaseAccess ($in_bitmaskRequested, $isUseCache, true));
	}


	/**
	 * @param int bitmask of operations (constants self::OP_...)
	 * @param bool use cache
	 * @param bool does include CR to check? (false by default)
	 * 
	 * @return bool true - if there is access to given operations
	 */
	public function IsBaseAccess ($in_bitmaskRequested, $isUseCache = false, $checkForAuthor = false)
	{
		global $USER;

		if (is_object($USER) 
			&& ( $this->userId === ((int) $USER->GetID()) ) 
			&& $USER->IsAdmin()
		)
		{
			// Admin can access anything
			return (true);
		}
		elseif (defined('CRON_MODE'))
		{
			// Under cron script anybody can access anything
			return (true);
		}

		if ( ! (is_int($in_bitmaskRequested) && ($in_bitmaskRequested > 0)) )
		{
			throw new LearnException ('bitmask must be an integer > 0', 
				LearnException::EXC_ERR_ALL_ACCESS_DENIED 
				| LearnException::EXC_ERR_ALL_PARAMS);
		}

		$bitmaskRequested = $in_bitmaskRequested;

		// access codes for user $this->userId
		$arUserAccessSymbols = $this->GetAccessCodes ($isUseCache);

		if ($checkForAuthor)
			$arUserAccessSymbols[] = 'CR';

		// bitmask of accessible operations for user
		$bitmaskBaseAccess = $this->GetBitmaskOperationsForAllLessons($arUserAccessSymbols);

		// check that all bits in $bitmaskRequested are setted in $bitmaskBaseAccess
		if ( ($bitmaskRequested & $bitmaskBaseAccess) === $bitmaskRequested )
			return (true);
		else
			return (false);
	}


	/**
	 * @param array $arPermPairs, for example: array ('CR' => 4, 'U2' => '1', ...).
	 * All unlisted access symbols ("subjects") will be removed.
	 */
	public function SetBasePermissions ($in_arPermPairs)
	{
		global $DB, $USER;

		// Check args
		if ( ! is_array($in_arPermPairs) )
		{
			throw new LearnException ('', 
				LearnException::EXC_ERR_ALL_ACCESS_DENIED 
				| LearnException::EXC_ERR_ALL_PARAMS);
		}

		// Check & escape for SQL
		$arPermPairs = array();
		foreach ($in_arPermPairs as $in_subject_id => $in_task_id)
		{
			$subject_id = $DB->ForSQL($in_subject_id);
			$task_id    = self::StrictlyCastToInteger($in_task_id);
			$arPermPairs[$subject_id] = $task_id;
		}

		// Check rights (we can access only if is admin and logged in)
		if ( ! (self::IsLoggedUserCanAccessModuleSettings() 
			&& ( ((int) $USER->GetID()) === $this->userId) ) 
		)
		{
			throw new LearnException ('', 
				LearnException::EXC_ERR_ALL_ACCESS_DENIED);
		}

		// Yes, I know - most of products on MyISAM. So, In God We Trust.
		$DB->StartTransaction();

		$rc = $DB->Query(
			"DELETE FROM b_learn_rights_all 
			WHERE 1=1", 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_all (SUBJECT_ID, TASK_ID) 
				VALUES ('" . $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();
	}


	/**
	 * @return array of base for all lessons permissions
	 * @example
	 * <?php
	 * $oAccess = CLearnAccess::getInstance ($USER->GetID());
	 * $arPermPairs = $oAccess->GetLessonPermissions ($some_lesson_id);
	 * ?>
	 * $arPermPairs now contains
	 * array ('AU' => 1, 'U12' => '3', 'CR' => 2, ...)
	 */
	public function GetBasePermissions ()
	{
		global $DB;

		$rc = $DB->Query(
			"SELECT SUBJECT_ID, TASK_ID 
			FROM b_learn_rights_all
			WHERE 1=1");

		if ($rc === false)
		{
			throw new LearnException('EA_SQLERROR', 
				LearnException::EXC_ERR_ALL_GIVEUP 
				| LearnException::EXC_ERR_ALL_ACCESS_DENIED);
		}

		$arPermPairs = array();
		while ($row = $rc->Fetch())
			$arPermPairs[$row['SUBJECT_ID']] = (int) $row['TASK_ID'];

		return ($arPermPairs);
	}


	/**
	 * @return array of lesson's permissions
	 * @example
	 * <?php
	 * $oAccess = CLearnAccess::getInstance ($USER->GetID());
	 * $arPermPairs = $oAccess->GetLessonPermissions ($some_lesson_id);
	 * ?>
	 * $arPermPairs now contains
	 * array ('AU' => 1, 'U12' => '3', 'CR' => 2, ...)
	 */
	public function GetLessonPermissions ($in_lessonId)
	{
		global $DB;

		$lessonId = self::StrictlyCastToInteger($in_lessonId);

		$rc = $DB->Query(
			"SELECT LESSON_ID, SUBJECT_ID, TASK_ID 
			FROM b_learn_rights
			WHERE LESSON_ID = " . $lessonId . "
			");

		if ($rc === false)
		{
			throw new LearnException('EA_SQLERROR', 
				LearnException::EXC_ERR_ALL_GIVEUP 
				| LearnException::EXC_ERR_ALL_ACCESS_DENIED);
		}

		$arPermPairs = array();
		while ($row = $rc->Fetch())
			$arPermPairs[$row['SUBJECT_ID']] = (int) $row['TASK_ID'];

		return ($arPermPairs);
	}


	/**
	 * @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 = CLearnAccess::getInstance ($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 
				WHERE 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) 
					VALUES (" . $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 function IsLessonAccessible ($in_lessonId, $in_bitmaskOperations, $isUseCache = false)
	{
		static $cacheArIds = array();

		$lessonId = intval($in_lessonId);
		$cacheKey = $in_bitmaskOperations."_".$lessonId;

		if ($isUseCache && array_key_exists($cacheKey, $cacheArIds))
		{
			return true;
		}

		$cacheArIds = array_merge($cacheArIds, $this->GetAccessibleLessonsList($in_bitmaskOperations, $isUseCache, $lessonId));
		return array_key_exists($cacheKey, $cacheArIds);
	}


	public function GetAccessibleLessonsList($in_bitmaskOperations, $isUseCache = false, $lessonId = 0)
	{
		global $DB;

		$sql = $this->SQLClauseForAccessibleLessons($in_bitmaskOperations, $isUseCache, $lessonId);

		$rc = $DB->Query($sql, true);

		if ($rc === false)
		{
			throw new LearnException ('EA_SQLERROR', 
				LearnException::EXC_ERR_ALL_ACCESS_DENIED 
				| LearnException::EXC_ERR_ALL_GIVEUP);
		}

		$arIds = array();
		while ($row = $rc->Fetch())
			$arIds[$in_bitmaskOperations."_".$row['LESSON_ID']] = (int) $row['LESSON_ID'];

		return ($arIds);
	}


	/**
	 * @param int operations code (taken from sum of constants with prefix OP_)
	 * @param bool flag isUseCache (can be omitted, false by default)
	 * @param string prefix for tables acronyms (can be omitted)
	 * 
	 * @return string #sql_code# for usage in "SELECT ... WHERE LESSON_ID IN (#sql_code#)"
	 * 
	 * @example
	 * $o = CLearnAccess::GetInstance ($someUserId);
	 * $sql = $o->SQLClauseForAccessibleLessons (CLearnAccess::OP_LESSON_READ + CLearnAccess::OP_LESSON_WRITE);
	 * // Selects only lessons, which are accessible by user with id = $someUserId
	 * $rc = $DB->Query ("SELECT NAME FROM b_learn_lesson WHERE ACTIVE = 'Y' AND ID IN (" . $sql . ")");
	 */
	public function SQLClauseForAccessibleLessons ($in_bitmaskOperations, $isUseCache = false, $lessonId = 0, $in_prfx = 'DEFPRFX')
	{
		global $DB;
		if ( ! (is_int($in_bitmaskOperations) && ($in_bitmaskOperations > 0)) )
		{
			throw new LearnException ('bitmask must be an integer > 0', 
				LearnException::EXC_ERR_ALL_ACCESS_DENIED 
				| LearnException::EXC_ERR_ALL_PARAMS);
		}

		$prfx   = $DB->ForSQL ($in_prfx);
		$userId = (int) $this->userId;

		// access codes for user $this->userId
		$arUserAccessSymbols = $this->GetAccessCodes ($isUseCache);

		$userAccessSymbols = 'NULL';
		// convert array to comma-separeted list for sql query (items will be escaped)
		if (count($arUserAccessSymbols) > 0)
			$userAccessSymbols = $this->Array2CommaSeparatedListForSQL ($arUserAccessSymbols);

		/**
		 * There are some operations, granted on all lessons in context of some user.
		 * So, we must adjust $in_bitmaskOperations on operations, which are already 
		 * accessible by user (in both roles: as author(CR) and as just any user(Any)).
		 * User role is unknown now, it will be known on SQL query only.
		 */
		// Get bitmask of operations granted on all lessons (any user mode)
		$bitmaskAvailOperationsForAny = $this->GetBitmaskOperationsForAllLessons($arUserAccessSymbols);
		// Get bitmask of operations granted on all lessons (user-author mode)
		$bitmaskAvailOperationsForCR  = $this->GetBitmaskOperationsForAllLessons(array_merge($arUserAccessSymbols, array('CR')));

		/**
		 * Now, switch off bits for operations, 
		 * that are available for current user 
		 * on all lessons (or all own lessons for author).
		 * Because, we must check only rights, that are not
		 * available on all lessons yet.
		 */
		$bitmaskOperationsForAny = $in_bitmaskOperations & ( ~ $bitmaskAvailOperationsForAny );
		$bitmaskOperationsForCR  = $in_bitmaskOperations & ( ~ $bitmaskAvailOperationsForCR );

		// Convert bitmasks to sql comma-separated list of operations' names
		$sqlOperationsForAny = false;
		$sqlOperationsForCR  = false;
		if ($bitmaskOperationsForAny !== 0)
			$sqlOperationsForAny = $this->ParseOperationsForSQL ($bitmaskOperationsForAny);
		if ($bitmaskOperationsForCR !== 0)
			$sqlOperationsForCR  = $this->ParseOperationsForSQL ($bitmaskOperationsForCR);

		$arSqlWhere = array();

		// Is some operations must be checked for author?
		if ($sqlOperationsForCR !== false)
			$arSqlWhere[] = "(${prfx}TLR.SUBJECT_ID = 'CR' AND ${prfx}TLL.CREATED_BY = $userId AND ${prfx}XTO.NAME IN ($sqlOperationsForCR))";
		else
			$arSqlWhere[] = "(${prfx}TLL.CREATED_BY = $userId)";	// All requested operations are permitted for author

		if ($sqlOperationsForAny !== false)
			$arSqlWhere[] = "(${prfx}TLR.SUBJECT_ID IN ($userAccessSymbols) AND ${prfx}XTO.NAME IN ($sqlOperationsForAny))";
		else
			$arSqlWhere[] = "(1=1)";	// All requested operations permitted for user $this->userId

		$sqlWhere = implode("\n OR \n", $arSqlWhere);

		$lessonId = intval($lessonId);
		if ($lessonId > 0)
		{
			$sqlWhere = "${prfx}TLL.ID={$lessonId} AND (".$sqlWhere.")";
		}

		$sql = "SELECT ${prfx}TLL.ID AS LESSON_ID
		FROM b_learn_lesson ${prfx}TLL
		LEFT OUTER JOIN b_learn_rights ${prfx}TLR
			ON ${prfx}TLL.ID = ${prfx}TLR.LESSON_ID
		LEFT OUTER JOIN b_task_operation ${prfx}TTO
			ON ${prfx}TLR.TASK_ID = ${prfx}TTO.TASK_ID
		LEFT OUTER JOIN b_operation ${prfx}XTO
			ON ${prfx}TTO.OPERATION_ID = ${prfx}XTO.ID
		WHERE
			$sqlWhere";

		return ($sql);
		
		/*
		prev version of code:

		$userAccessSymbols = $this->GetAccessCodesForSQL ($isUseCache);
		$sqlOperations     = $this->ParseOperationsForSQL ($in_bitmaskOperations);
		$prfx   = CDatabase::ForSQL ($in_prfx);
		$userId = $this->userId;

		$sql = "
		SELECT ${prfx}TLR.LESSON_ID
		FROM b_learn_rights ${prfx}TLR
		INNER JOIN b_task_operation ${prfx}TTO
			ON ${prfx}TLR.TASK_ID = ${prfx}TTO.TASK_ID
		INNER JOIN b_operation ${prfx}TO
			ON ${prfx}TTO.OPERATION_ID = ${prfx}TO.ID
		INNER JOIN b_learn_lesson ${prfx}TLL
			ON ${prfx}TLL.ID = ${prfx}TLR.LESSON_ID
		WHERE 
			TO.NAME IN ($sqlOperations)
			AND
			(
			(${prfx}TLR.SUBJECT_ID = 'CR' AND ${prfx}TLL.CREATED_BY = $userId)
			OR (TLR.SUBJECT_ID IN ($userAccessSymbols))
			)";

		return ($sql);
		*/
	}


	protected function GetBitmaskOperationsForAllLessons($arUserAccessSymbols)
	{
		global $DB;
		static $cache = array();

		if (!is_array($arUserAccessSymbols) || count($arUserAccessSymbols) < 1)
		{
			return 0;
		}

		$userAccessSymbols = $this->Array2CommaSeparatedListForSQL ($arUserAccessSymbols);
		if (isset($cache[$userAccessSymbols]))
		{
			return $cache[$userAccessSymbols];
		}

		$rc = $DB->Query (
			"SELECT XTO.NAME AS OPERATION_NAME
			FROM b_learn_rights_all TLRA
			INNER JOIN b_task_operation TTO
				ON TTO.TASK_ID = TLRA.TASK_ID
			INNER JOIN b_operation XTO
				ON XTO.ID = TTO.OPERATION_ID
			WHERE TLRA.SUBJECT_ID IN ($userAccessSymbols)", 
			true);
		if ($rc === false)
		{
			throw new LearnException ('EA_SQLERROR: ', 
				LearnException::EXC_ERR_ALL_GIVEUP 
				| LearnException::EXC_ERR_ALL_ACCESS_DENIED);
		}

		$bitmaskOperations = 0;
		while ($arData = $rc->Fetch())
		{
			if ( ! isset(self::$arOperations[$arData['OPERATION_NAME']]) )
			{
				throw new LearnException ('Unknown operation: ' . $arData['OPERATION_NAME'], 
					LearnException::EXC_ERR_ALL_LOGIC 
					| LearnException::EXC_ERR_ALL_GIVEUP
					| LearnException::EXC_ERR_ALL_ACCESS_DENIED);
			}

			$bitmaskOperations = $bitmaskOperations | self::$arOperations[$arData['OPERATION_NAME']];
		}

		$cache[$userAccessSymbols] = $bitmaskOperations;
		return ($bitmaskOperations);
	}


	/**
	 * @return string of comma-separated operations names
	 */
	protected static function ParseOperationsForSQL ($in_operations)
	{
		static $determinedCache = array();

		if ( ! (is_int($in_operations) && ($in_operations > 0)) )
			throw new LearnException ('', LearnException::EXC_ERR_ALL_PARAMS | LearnException::EXC_ERR_ALL_ACCESS_DENIED);

		$cacheKey = 'str' . $in_operations;

		if ( ! isset ($determinedCache[$cacheKey]) )
		{
			$arOperations = array();
			foreach (self::$arOperations as $operationName => $operationBitFlag)
			{
				if ($in_operations & $operationBitFlag)
				{
					$arOperations[] = $operationName;
					$in_operations -= $operationBitFlag;
				}
			}

			// Must be zero. If not => not all operations listed in self::$arOperations
			// or wrong requested value in $in_operations
			if ($in_operations !== 0)
				throw new LearnException ('', LearnException::EXC_ERR_ALL_PARAMS | LearnException::EXC_ERR_ALL_ACCESS_DENIED);

			$sql = self::Array2CommaSeparatedListForSQL ($arOperations);
			$determinedCache[$cacheKey] = $sql;
		}

		return ($determinedCache[$cacheKey]);
	}


	/**
	 * @return string of comma-separated access codes, includes AU symbol (if user is authorized)
	 */
	protected function GetAccessCodesForSQL ($isUseCache = false)
	{
		static $cache = array();

		if ($isUseCache && isset($cache['str' . $this->userId]))
			return ($cache['str' . $this->userId]);

		$arCodes = $this->GetAccessCodes ($isUseCache);
		$sql = $this->Array2CommaSeparatedListForSQL ($arCodes);

		// Cache in case when $isUseCache === false too. 
		// Because, this will refresh cache, if it exists before.
		$cache['str' . $this->userId] = $sql;

		return ($sql);
	}


	/**
	 * @return array of access codes, includes AU symbol (if user is authorized)
	 */
	protected function GetAccessCodes ($isUseCache = false)
	{
		global $USER;
		static $cache = array();
		$isNeedCAccessUpdate = true;

		if ($isUseCache)
		{
			// Cache hits?
			if (isset($cache['str' . $this->userId]))
				return ($cache['str' . $this->userId]);

			// Prevent call CAccess->UpdateCodes() multiple times per hit,
			// except long time period (three seconds) expired.
			if ( ($this->CAccessLastUpdated === false) 
				|| ( (microtime(true) - $this->CAccessLastUpdated) > 3 )
			)
			{
				$isNeedCAccessUpdate = true;
			}
			else
				$isNeedCAccessUpdate = false;
		}
		else
			$isNeedCAccessUpdate = true;

		if ($isNeedCAccessUpdate)
		{
			$oAcc = new CAccess();
			$oAcc->UpdateCodes();

			if ($isUseCache)
				$this->CAccessLastUpdated = microtime(true);

			unset ($oAcc);
		}

		$rc = CAccess::GetUserCodes($this->userId);
		if ($rc === false)
		{
			throw new LearnException('', 
				LearnException::EXC_ERR_ALL_GIVEUP 
				| LearnException::EXC_ERR_ALL_ACCESS_DENIED);
		}

		$arData = array();
		while ($arItem = $rc->Fetch())
		{
			if ( ( (int) $arItem['USER_ID'] ) !== $this->userId )
			{
				throw new LearnException('', 
					LearnException::EXC_ERR_ALL_GIVEUP 
					| LearnException::EXC_ERR_ALL_LOGIC
					| LearnException::EXC_ERR_ALL_ACCESS_DENIED);
			}

			$arData[] = $arItem['ACCESS_CODE'];
		}

		if ( is_object($USER) && ( $this->userId === ((int) $USER->GetID()) ) )
			$arData[] = 'AU';

		// Cache in case when $isUseCache === false too. 
		// Because, this will refresh cache, if it exists before.
		$cache['str' . $this->userId] = $arData;

		return ($arData);
	}


	protected static function Array2CommaSeparatedListForSQL ($in_arData)
	{
		$arData = array_map (array('CLearnAccess', 'EscapeAndAddLateralQuotes'), $in_arData);

		$sql = implode(',', $arData);

		return ($sql);
	}


	protected static function EscapeAndAddLateralQuotes ($txt)
	{
		global $DB;
		return ("'" . $DB->ForSQL($txt) . "'");
	}


	public static function IsLoggedUserCanAccessModuleSettings()
	{
		global $USER, $APPLICATION;

		if ($USER->IsAdmin() || ($APPLICATION->GetGroupRight('learning') === 'W'))
			return (true);
		else
			return (false);
	}


	protected static function StrictlyCastToInteger ($var)
	{
		if ( ! preg_match("/^[0-9]+$/", (string) $var) )
		{
			throw new LearnException(
				'EA_PARAMS: can\'t b strictly casted to integer, but expected: ' . $var, 
				LearnException::EXC_ERR_ALL_PARAMS 
				| LearnException::EXC_ERR_ALL_ACCESS_DENIED);
		}

		return ( (int) $var );
	}
}