' : '';
if ($checkPassPercentage) {
$passPercentage = $objExercise->selectPassPercentage();
$isSuccess = self::isSuccessExerciseResult($score, $weight, $passPercentage);
// Color the final test score if pass_percentage activated
$ribbonTotalSuccessOrError = '';
if (self::isPassPercentageEnabled($passPercentage)) {
if ($isSuccess) {
$ribbonTotalSuccessOrError = ' ribbon-total-success';
} else {
$ribbonTotalSuccessOrError = ' ribbon-total-error';
}
}
$ribbon .= $displayChartDegree ? '
' : '';
return $ribbon;
}
public static function isPassPercentageAttemptPassed($objExercise, $score, $weight)
{
$passPercentage = $objExercise->selectPassPercentage();
return self::isSuccessExerciseResult($score, $weight, $passPercentage);
}
/**
* @param float $score
* @param float $weight
* @param bool $checkPassPercentage
* @param int $countPendingQuestions
*
* @return string
*/
public static function getTotalScoreRibbon(
Exercise $objExercise,
$score,
$weight,
$checkPassPercentage = false,
$countPendingQuestions = 0
) {
$hide = (int) $objExercise->getPageConfigurationAttribute('hide_total_score');
if (1 === $hide) {
return '';
}
$passPercentage = $objExercise->selectPassPercentage();
$ribbon = '
';
if ($checkPassPercentage) {
$isSuccess = self::isSuccessExerciseResult(
$score,
$weight,
$passPercentage
);
// Color the final test score if pass_percentage activated
$class = '';
if (self::isPassPercentageEnabled($passPercentage)) {
if ($isSuccess) {
$class = ' ribbon-total-success';
} else {
$class = ' ribbon-total-error';
}
}
$ribbon .= '
';
} else {
$ribbon .= '
';
}
$ribbon .= '
'.get_lang('YourTotalScore').': ';
$ribbon .= self::show_score($score, $weight, false, true);
$ribbon .= '
';
$ribbon .= '';
if ($checkPassPercentage) {
$ribbon .= self::showSuccessMessage(
$score,
$weight,
$passPercentage
);
}
$ribbon .= '
';
if (!empty($countPendingQuestions)) {
$ribbon .= '
';
$ribbon .= Display::return_message(
sprintf(
get_lang('TempScoreXQuestionsNotCorrectedYet'),
$countPendingQuestions
),
'warning'
);
}
return $ribbon;
}
/**
* @param int $countLetter
*
* @return mixed
*/
public static function detectInputAppropriateClass($countLetter)
{
$limits = [
0 => 'input-mini',
10 => 'input-mini',
15 => 'input-medium',
20 => 'input-xlarge',
40 => 'input-xlarge',
60 => 'input-xxlarge',
100 => 'input-xxlarge',
200 => 'input-xxlarge',
];
foreach ($limits as $size => $item) {
if ($countLetter <= $size) {
return $item;
}
}
return $limits[0];
}
/**
* @param int $senderId
* @param array $course_info
* @param string $test
* @param string $url
*
* @return string
*/
public static function getEmailNotification($senderId, $course_info, $test, $url)
{
$teacher_info = api_get_user_info($senderId);
$from_name = api_get_person_name(
$teacher_info['firstname'],
$teacher_info['lastname'],
null,
PERSON_NAME_EMAIL_ADDRESS
);
$view = new Template('', false, false, false, false, false, false);
$view->assign('course_title', Security::remove_XSS($course_info['name']));
$view->assign('test_title', Security::remove_XSS($test));
$view->assign('url', $url);
$view->assign('teacher_name', $from_name);
$template = $view->get_template('mail/exercise_result_alert_body.tpl');
return $view->fetch($template);
}
/**
* @return string
*/
public static function getNotCorrectedYetText()
{
return Display::return_message(get_lang('notCorrectedYet'), 'warning');
}
/**
* @param string $message
*
* @return string
*/
public static function getFeedbackText($message)
{
return Display::return_message($message, 'warning', false);
}
/**
* Get the recorder audio component for save a teacher audio feedback.
*
* @param Template $template
* @param int $attemptId
* @param int $questionId
* @param int $userId
*
* @return string
*/
public static function getOralFeedbackForm($template, $attemptId, $questionId, $userId)
{
$template->assign('user_id', $userId);
$template->assign('question_id', $questionId);
$template->assign('directory', "/../exercises/teacher_audio/$attemptId/");
$template->assign('file_name', "{$questionId}_{$userId}");
return $template->fetch($template->get_template('exercise/oral_expression.tpl'));
}
/**
* Get the audio componen for a teacher audio feedback.
*
* @param int $attemptId
* @param int $questionId
* @param int $userId
*
* @return string
*/
public static function getOralFeedbackAudio($attemptId, $questionId, $userId)
{
$courseInfo = api_get_course_info();
$sessionId = api_get_session_id();
$groupId = api_get_group_id();
$sysCourseDir = api_get_path(SYS_COURSE_PATH).$courseInfo['path'];
$webCourseDir = api_get_path(WEB_COURSE_PATH).$courseInfo['path'];
$fileName = "{$questionId}_{$userId}".DocumentManager::getDocumentSuffix($courseInfo, $sessionId, $groupId);
$filePath = null;
$relFilePath = "/exercises/teacher_audio/$attemptId/$fileName";
if (file_exists($sysCourseDir.$relFilePath.'.ogg')) {
$filePath = $webCourseDir.$relFilePath.'.ogg';
} elseif (file_exists($sysCourseDir.$relFilePath.'.wav.wav')) {
$filePath = $webCourseDir.$relFilePath.'.wav.wav';
} elseif (file_exists($sysCourseDir.$relFilePath.'.wav')) {
$filePath = $webCourseDir.$relFilePath.'.wav';
}
if (!$filePath) {
return '';
}
return Display::tag(
'audio',
null,
['src' => $filePath]
);
}
/**
* @return array
*/
public static function getNotificationSettings()
{
$emailAlerts = [
2 => get_lang('SendEmailToTeacherWhenStudentStartQuiz'),
1 => get_lang('SendEmailToTeacherWhenStudentEndQuiz'), // default
3 => get_lang('SendEmailToTeacherWhenStudentEndQuizOnlyIfOpenQuestion'),
4 => get_lang('SendEmailToTeacherWhenStudentEndQuizOnlyIfOralQuestion'),
];
return $emailAlerts;
}
/**
* Get the additional actions added in exercise_additional_teacher_modify_actions configuration.
*
* @param int $exerciseId
* @param int $iconSize
*
* @return string
*/
public static function getAdditionalTeacherActions($exerciseId, $iconSize = ICON_SIZE_SMALL)
{
$additionalActions = api_get_configuration_value('exercise_additional_teacher_modify_actions') ?: [];
$actions = [];
foreach ($additionalActions as $additionalAction) {
$actions[] = call_user_func(
$additionalAction,
$exerciseId,
$iconSize
);
}
return implode(PHP_EOL, $actions);
}
/**
* @param int $userId
* @param int $courseId
* @param int $sessionId
*
* @throws \Doctrine\ORM\Query\QueryException
*
* @return int
*/
public static function countAnsweredQuestionsByUserAfterTime(DateTime $time, $userId, $courseId, $sessionId)
{
$em = Database::getManager();
$time = api_get_utc_datetime($time->format('Y-m-d H:i:s'), false, true);
$result = $em
->createQuery('
SELECT COUNT(ea) FROM ChamiloCoreBundle:TrackEAttempt ea
WHERE ea.userId = :user AND ea.cId = :course AND ea.sessionId = :session
AND ea.tms > :time
')
->setParameters(['user' => $userId, 'course' => $courseId, 'session' => $sessionId, 'time' => $time])
->getSingleScalarResult();
return $result;
}
/**
* @param int $userId
* @param int $numberOfQuestions
* @param int $courseId
* @param int $sessionId
*
* @throws \Doctrine\ORM\Query\QueryException
*
* @return bool
*/
public static function isQuestionsLimitPerDayReached($userId, $numberOfQuestions, $courseId, $sessionId)
{
$questionsLimitPerDay = (int) api_get_course_setting('quiz_question_limit_per_day');
if ($questionsLimitPerDay <= 0) {
return false;
}
$midnightTime = ChamiloApi::getServerMidnightTime();
$answeredQuestionsCount = self::countAnsweredQuestionsByUserAfterTime(
$midnightTime,
$userId,
$courseId,
$sessionId
);
return ($answeredQuestionsCount + $numberOfQuestions) > $questionsLimitPerDay;
}
/**
* By default, allowed types are unique-answer (and image) or multiple-answer questions.
* Types can be extended by the configuration setting "exercise_embeddable_extra_types".
*/
public static function getEmbeddableTypes(): array
{
$allowedTypes = [
UNIQUE_ANSWER,
MULTIPLE_ANSWER,
FILL_IN_BLANKS,
MATCHING,
FREE_ANSWER,
MULTIPLE_ANSWER_COMBINATION,
UNIQUE_ANSWER_NO_OPTION,
MULTIPLE_ANSWER_TRUE_FALSE,
MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE,
ORAL_EXPRESSION,
GLOBAL_MULTIPLE_ANSWER,
CALCULATED_ANSWER,
UNIQUE_ANSWER_IMAGE,
READING_COMPREHENSION,
MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY,
UPLOAD_ANSWER,
ANSWER_IN_OFFICE_DOC,
MATCHING_COMBINATION,
FILL_IN_BLANKS_COMBINATION,
MULTIPLE_ANSWER_DROPDOWN,
MULTIPLE_ANSWER_DROPDOWN_COMBINATION,
];
$defaultTypes = [UNIQUE_ANSWER, MULTIPLE_ANSWER, UNIQUE_ANSWER_IMAGE];
$types = $defaultTypes;
$extraTypes = api_get_configuration_value('exercise_embeddable_extra_types');
if (false !== $extraTypes && !empty($extraTypes['types'])) {
$types = array_merge($defaultTypes, $extraTypes['types']);
}
return array_filter(
array_unique($types),
function ($type) use ($allowedTypes) {
return in_array($type, $allowedTypes);
}
);
}
/**
* Check if an exercise complies with the requirements to be embedded in the mobile app or a video.
* By making sure it is set on one question per page, and that the exam does not have immediate feedback,
* and it only contains allowed types.
*
* @see Exercise::getEmbeddableTypes()
*/
public static function isQuizEmbeddable(array $exercise): bool
{
$exercise['iid'] = isset($exercise['iid']) ? (int) $exercise['iid'] : 0;
if (ONE_PER_PAGE != $exercise['type'] ||
in_array($exercise['feedback_type'], [EXERCISE_FEEDBACK_TYPE_DIRECT, EXERCISE_FEEDBACK_TYPE_POPUP])
) {
return false;
}
$questionRepository = Database::getManager()->getRepository(CQuizQuestion::class);
$countAll = $questionRepository->countQuestionsInExercise($exercise['iid']);
$countAllowed = $questionRepository->countEmbeddableQuestionsInExercise($exercise['iid']);
return $countAll === $countAllowed;
}
/**
* Generate a certificate linked to current quiz and.
* Return the HTML block with links to download and view the certificate.
*
* @param float $totalScore
* @param float $totalWeight
* @param int $studentId
* @param string $courseCode
* @param int $sessionId
*
* @return string
*/
public static function generateAndShowCertificateBlock(
$totalScore,
$totalWeight,
Exercise $objExercise,
$studentId,
$courseCode,
$sessionId = 0
) {
if (!api_get_configuration_value('quiz_generate_certificate_ending') ||
!self::isSuccessExerciseResult($totalScore, $totalWeight, $objExercise->selectPassPercentage())
) {
return '';
}
/** @var Category $category */
$category = Category::load(null, null, $courseCode, null, null, $sessionId, 'ORDER By id');
if (empty($category)) {
return '';
}
/** @var Category $category */
$category = $category[0];
$categoryId = $category->get_id();
$link = LinkFactory::load(
null,
null,
$objExercise->selectId(),
null,
$courseCode,
$categoryId
);
if (empty($link)) {
return '';
}
$resourceDeletedMessage = $category->show_message_resource_delete($courseCode);
if (false !== $resourceDeletedMessage || api_is_allowed_to_edit() || api_is_excluded_user_type()) {
return '';
}
$certificate = Category::generateUserCertificate($categoryId, $studentId);
if (!is_array($certificate)) {
return '';
}
return Category::getDownloadCertificateBlock($certificate);
}
/**
* @param int $exerciseId
*/
public static function getExerciseTitleById($exerciseId)
{
$em = Database::getManager();
return $em
->createQuery('SELECT cq.title
FROM ChamiloCourseBundle:CQuiz cq
WHERE cq.iid = :iid'
)
->setParameter('iid', $exerciseId)
->getSingleScalarResult();
}
/**
* @param int $exeId ID from track_e_exercises
* @param int $userId User ID
* @param int $exerciseId Exercise ID
* @param int $courseId Optional. Coure ID.
*
* @return TrackEExercises|null
*/
public static function recalculateResult($exeId, $userId, $exerciseId, $courseId = 0)
{
if (empty($userId) || empty($exerciseId)) {
return null;
}
$em = Database::getManager();
/** @var TrackEExercises $trackedExercise */
$trackedExercise = $em->getRepository('ChamiloCoreBundle:TrackEExercises')->find($exeId);
if (empty($trackedExercise)) {
return null;
}
if ($trackedExercise->getExeUserId() != $userId ||
$trackedExercise->getExeExoId() != $exerciseId
) {
return null;
}
$questionList = $trackedExercise->getDataTracking();
if (empty($questionList)) {
return null;
}
$questionList = explode(',', $questionList);
$exercise = new Exercise($courseId);
$courseInfo = $courseId ? api_get_course_info_by_id($courseId) : [];
if ($exercise->read($exerciseId) === false) {
return null;
}
$totalScore = 0;
$totalWeight = 0;
$pluginEvaluation = QuestionOptionsEvaluationPlugin::create();
$formula = 'true' === $pluginEvaluation->get(QuestionOptionsEvaluationPlugin::SETTING_ENABLE)
? $pluginEvaluation->getFormulaForExercise($exerciseId)
: 0;
if (empty($formula)) {
foreach ($questionList as $questionId) {
$question = Question::read($questionId, $courseInfo);
if (false === $question) {
continue;
}
$totalWeight += $question->selectWeighting();
// We're inside *one* question. Go through each possible answer for this question
$result = $exercise->manage_answer(
$exeId,
$questionId,
[],
'exercise_result',
[],
false,
true,
false,
$exercise->selectPropagateNeg(),
[],
[],
true
);
// Adding the new score.
$totalScore += $result['score'];
}
} else {
$totalScore = $pluginEvaluation->getResultWithFormula($exeId, $formula);
$totalWeight = $pluginEvaluation->getMaxScore();
}
$trackedExercise
->setExeResult($totalScore)
->setExeWeighting($totalWeight);
$em->persist($trackedExercise);
$em->flush();
return $trackedExercise;
}
public static function getTotalQuestionAnswered($courseId, $exerciseId, $questionId, $sessionId = 0, $groups = [], $users = [])
{
$courseId = (int) $courseId;
$exerciseId = (int) $exerciseId;
$questionId = (int) $questionId;
$sessionId = (int) $sessionId;
$attemptTable = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
$trackTable = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
$userCondition = '';
$allUsers = [];
if (!empty($groups)) {
foreach ($groups as $groupId) {
$groupUsers = GroupManager::get_users($groupId, null, null, null, false, $courseId);
if (!empty($groupUsers)) {
$allUsers = array_merge($allUsers, $groupUsers);
}
}
}
if (!empty($users)) {
$allUsers = array_merge($allUsers, $users);
}
if (!empty($allUsers)) {
$allUsers = array_map('intval', $allUsers);
$usersToString = implode("', '", $allUsers);
$userCondition = " AND user_id IN ('$usersToString') ";
}
$sessionCondition = '';
if (!empty($sessionId)) {
$sessionCondition = api_get_session_condition($sessionId, true, false, 'te.session_id');
}
$sql = "SELECT count(te.exe_id) total
FROM $attemptTable t
INNER JOIN $trackTable te
ON (te.c_id = t.c_id AND t.exe_id = te.exe_id)
WHERE
t.c_id = $courseId AND
exe_exo_id = $exerciseId AND
t.question_id = $questionId AND
status != 'incomplete'
$sessionCondition
$userCondition
";
$queryTotal = Database::query($sql);
$totalRow = Database::fetch_array($queryTotal, 'ASSOC');
$total = 0;
if ($totalRow) {
$total = (int) $totalRow['total'];
}
return $total;
}
public static function getWrongQuestionResults($courseId, $exerciseId, $sessionId = 0, $groups = [], $users = [])
{
$courseId = (int) $courseId;
$exerciseId = (int) $exerciseId;
$questionTable = Database::get_course_table(TABLE_QUIZ_QUESTION);
$attemptTable = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
$trackTable = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
$sessionCondition = '';
if (!empty($sessionId)) {
$sessionCondition = api_get_session_condition($sessionId, true, false, 'te.session_id');
}
$userCondition = '';
$allUsers = [];
if (!empty($groups)) {
foreach ($groups as $groupId) {
$groupUsers = GroupManager::get_users($groupId, null, null, null, false, $courseId);
if (!empty($groupUsers)) {
$allUsers = array_merge($allUsers, $groupUsers);
}
}
}
if (!empty($users)) {
$allUsers = array_merge($allUsers, $users);
}
if (!empty($allUsers)) {
$allUsers = array_map('intval', $allUsers);
$usersToString = implode("', '", $allUsers);
$userCondition .= " AND user_id IN ('$usersToString') ";
}
$sql = "SELECT q.question, question_id, count(q.iid) count
FROM $attemptTable t
INNER JOIN $questionTable q
ON q.iid = t.question_id
INNER JOIN $trackTable te
ON t.exe_id = te.exe_id
WHERE
t.c_id = $courseId AND
t.marks != q.ponderation AND
exe_exo_id = $exerciseId AND
status != 'incomplete'
$sessionCondition
$userCondition
GROUP BY q.iid
ORDER BY count DESC
";
$result = Database::query($sql);
return Database::store_result($result, 'ASSOC');
}
public static function getExerciseResultsCount($type, $courseId, Exercise $exercise, $sessionId = 0)
{
$courseId = (int) $courseId;
$exerciseId = (int) $exercise->iid;
$trackTable = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
$sessionCondition = '';
if (!empty($sessionId)) {
$sessionCondition = api_get_session_condition($sessionId, true, false, 'te.session_id');
}
$passPercentage = $exercise->selectPassPercentage();
$minPercentage = 100;
if (!empty($passPercentage)) {
$minPercentage = $passPercentage;
}
$selectCount = 'count(DISTINCT te.exe_id)';
$scoreCondition = '';
switch ($type) {
case 'correct_student':
$selectCount = 'count(DISTINCT te.exe_user_id)';
$scoreCondition = " AND (exe_result/exe_weighting*100) >= $minPercentage ";
break;
case 'wrong_student':
$selectCount = 'count(DISTINCT te.exe_user_id)';
$scoreCondition = " AND (exe_result/exe_weighting*100) < $minPercentage ";
break;
case 'correct':
$scoreCondition = " AND (exe_result/exe_weighting*100) >= $minPercentage ";
break;
case 'wrong':
$scoreCondition = " AND (exe_result/exe_weighting*100) < $minPercentage ";
break;
}
$sql = "SELECT $selectCount count
FROM $trackTable te
WHERE
c_id = $courseId AND
exe_exo_id = $exerciseId AND
status != 'incomplete'
$scoreCondition
$sessionCondition
";
$result = Database::query($sql);
$totalRow = Database::fetch_array($result, 'ASSOC');
$total = 0;
if ($totalRow) {
$total = (int) $totalRow['count'];
}
return $total;
}
public static function parseContent($content, $stats, Exercise $exercise, $trackInfo, $currentUserId = 0)
{
$wrongAnswersCount = $stats['failed_answers_count'];
$attemptDate = substr($trackInfo['exe_date'], 0, 10);
$exeId = $trackInfo['exe_id'];
$resultsStudentUrl = api_get_path(WEB_CODE_PATH).
'exercise/result.php?id='.$exeId.'&'.api_get_cidreq();
$resultsTeacherUrl = api_get_path(WEB_CODE_PATH).
'exercise/exercise_show.php?action=edit&id='.$exeId.'&'.api_get_cidreq(true, true, 'teacher');
$content = str_replace(
[
'((exercise_error_count))',
'((all_answers_html))',
'((all_answers_teacher_html))',
'((exercise_title))',
'((exercise_attempt_date))',
'((link_to_test_result_page_student))',
'((link_to_test_result_page_teacher))',
],
[
$wrongAnswersCount,
$stats['all_answers_html'],
$stats['all_answers_teacher_html'],
$exercise->get_formated_title(),
$attemptDate,
$resultsStudentUrl,
$resultsTeacherUrl,
],
$content
);
$currentUserId = empty($currentUserId) ? api_get_user_id() : (int) $currentUserId;
$content = AnnouncementManager::parseContent(
$currentUserId,
$content,
api_get_course_id(),
api_get_session_id()
);
return $content;
}
public static function sendNotification(
$currentUserId,
$objExercise,
$exercise_stat_info,
$courseInfo,
$attemptCountToSend,
$stats,
$statsTeacher
) {
$notifications = api_get_configuration_value('exercise_finished_notification_settings');
if (empty($notifications)) {
return false;
}
$studentId = $exercise_stat_info['exe_user_id'];
$exerciseExtraFieldValue = new ExtraFieldValue('exercise');
$wrongAnswersCount = $stats['failed_answers_count'];
$exercisePassed = $stats['exercise_passed'];
$countPendingQuestions = $stats['count_pending_questions'];
$stats['all_answers_teacher_html'] = $statsTeacher['all_answers_html'];
// If there are no pending questions (Open questions).
if (0 === $countPendingQuestions) {
/*$extraFieldData = $exerciseExtraFieldValue->get_values_by_handler_and_field_variable(
$objExercise->iid,
'signature_mandatory'
);
if ($extraFieldData && isset($extraFieldData['value']) && 1 === (int) $extraFieldData['value']) {
if (ExerciseSignaturePlugin::exerciseHasSignatureActivated($objExercise)) {
$signature = ExerciseSignaturePlugin::getSignature($studentId, $exercise_stat_info);
if (false !== $signature) {
//return false;
}
}
}*/
// Notifications.
$extraFieldData = $exerciseExtraFieldValue->get_values_by_handler_and_field_variable(
$objExercise->iid,
'notifications'
);
$exerciseNotification = '';
if ($extraFieldData && isset($extraFieldData['value'])) {
$exerciseNotification = $extraFieldData['value'];
}
$subject = sprintf(get_lang('WrongAttemptXInCourseX'), $attemptCountToSend, $courseInfo['title']);
if ($exercisePassed) {
$subject = sprintf(get_lang('ExerciseValidationInCourseX'), $courseInfo['title']);
}
if ($exercisePassed) {
$extraFieldData = $exerciseExtraFieldValue->get_values_by_handler_and_field_variable(
$objExercise->iid,
'MailSuccess'
);
} else {
$extraFieldData = $exerciseExtraFieldValue->get_values_by_handler_and_field_variable(
$objExercise->iid,
'MailAttempt'.$attemptCountToSend
);
}
// Blocking exercise.
$blockPercentageExtra = $exerciseExtraFieldValue->get_values_by_handler_and_field_variable(
$objExercise->iid,
'blocking_percentage'
);
$blockPercentage = false;
if ($blockPercentageExtra && isset($blockPercentageExtra['value']) && $blockPercentageExtra['value']) {
$blockPercentage = $blockPercentageExtra['value'];
}
if ($blockPercentage) {
$passBlock = $stats['total_percentage'] > $blockPercentage;
if (false === $passBlock) {
$extraFieldData = $exerciseExtraFieldValue->get_values_by_handler_and_field_variable(
$objExercise->iid,
'MailIsBlockByPercentage'
);
}
}
$extraFieldValueUser = new ExtraFieldValue('user');
if ($extraFieldData && isset($extraFieldData['value'])) {
$content = $extraFieldData['value'];
$content = self::parseContent($content, $stats, $objExercise, $exercise_stat_info, $studentId);
//if (false === $exercisePassed) {
if (0 !== $wrongAnswersCount) {
$content .= $stats['failed_answers_html'];
}
$sendMessage = true;
if (!empty($exerciseNotification)) {
foreach ($notifications as $name => $notificationList) {
if ($exerciseNotification !== $name) {
continue;
}
foreach ($notificationList as $notificationName => $attemptData) {
if ('student_check' === $notificationName) {
$sendMsgIfInList = isset($attemptData['send_notification_if_user_in_extra_field']) ? $attemptData['send_notification_if_user_in_extra_field'] : '';
if (!empty($sendMsgIfInList)) {
foreach ($sendMsgIfInList as $skipVariable => $skipValues) {
$userExtraFieldValue = $extraFieldValueUser->get_values_by_handler_and_field_variable(
$studentId,
$skipVariable
);
if (empty($userExtraFieldValue)) {
$sendMessage = false;
break;
} else {
$sendMessage = false;
if (isset($userExtraFieldValue['value']) &&
in_array($userExtraFieldValue['value'], $skipValues)
) {
$sendMessage = true;
break;
}
}
}
}
break;
}
}
}
}
// Send to student.
if ($sendMessage) {
MessageManager::send_message($currentUserId, $subject, $content);
}
}
if (!empty($exerciseNotification)) {
foreach ($notifications as $name => $notificationList) {
if ($exerciseNotification !== $name) {
continue;
}
foreach ($notificationList as $attemptData) {
$skipNotification = false;
$skipNotificationList = isset($attemptData['send_notification_if_user_in_extra_field']) ? $attemptData['send_notification_if_user_in_extra_field'] : [];
if (!empty($skipNotificationList)) {
foreach ($skipNotificationList as $skipVariable => $skipValues) {
$userExtraFieldValue = $extraFieldValueUser->get_values_by_handler_and_field_variable(
$studentId,
$skipVariable
);
if (empty($userExtraFieldValue)) {
$skipNotification = true;
break;
} else {
if (isset($userExtraFieldValue['value'])) {
if (!in_array($userExtraFieldValue['value'], $skipValues)) {
$skipNotification = true;
break;
}
} else {
$skipNotification = true;
break;
}
}
}
}
if ($skipNotification) {
continue;
}
$email = isset($attemptData['email']) ? $attemptData['email'] : '';
$emailList = explode(',', $email);
if (empty($emailList)) {
continue;
}
$attempts = isset($attemptData['attempts']) ? $attemptData['attempts'] : [];
foreach ($attempts as $attempt) {
$sendMessage = false;
if (isset($attempt['attempt']) && $attemptCountToSend !== (int) $attempt['attempt']) {
continue;
}
if (!isset($attempt['status'])) {
continue;
}
if ($blockPercentage && isset($attempt['is_block_by_percentage'])) {
if ($attempt['is_block_by_percentage']) {
if ($passBlock) {
continue;
}
} else {
if (false === $passBlock) {
continue;
}
}
}
switch ($attempt['status']) {
case 'passed':
if ($exercisePassed) {
$sendMessage = true;
}
break;
case 'failed':
if (false === $exercisePassed) {
$sendMessage = true;
}
break;
case 'all':
$sendMessage = true;
break;
}
if ($sendMessage) {
$attachments = [];
if (isset($attempt['add_pdf']) && $attempt['add_pdf']) {
// Get pdf content
$pdfExtraData = $exerciseExtraFieldValue->get_values_by_handler_and_field_variable(
$objExercise->iid,
$attempt['add_pdf']
);
if ($pdfExtraData && isset($pdfExtraData['value'])) {
$pdfContent = self::parseContent(
$pdfExtraData['value'],
$stats,
$objExercise,
$exercise_stat_info,
$studentId
);
@$pdf = new PDF();
$filename = get_lang('Exercise');
$cssFile = api_get_path(SYS_CSS_PATH).'themes/chamilo/default.css';
$pdfPath = @$pdf->content_to_pdf(
"$pdfContent",
file_get_contents($cssFile),
$filename,
api_get_course_id(),
'F',
false,
null,
false,
true
);
$attachments[] = ['filename' => $filename, 'path' => $pdfPath];
}
}
$content = isset($attempt['content_default']) ? $attempt['content_default'] : '';
if (isset($attempt['content'])) {
$extraFieldData = $exerciseExtraFieldValue->get_values_by_handler_and_field_variable(
$objExercise->iid,
$attempt['content']
);
if ($extraFieldData && isset($extraFieldData['value']) && !empty($extraFieldData['value'])) {
$content = $extraFieldData['value'];
}
}
if (!empty($content)) {
$content = self::parseContent(
$content,
$stats,
$objExercise,
$exercise_stat_info,
$studentId
);
$extraParameters = [];
if (api_get_configuration_value('mail_header_from_custom_course_logo') == true) {
$extraParameters = ['logo' => CourseManager::getCourseEmailPicture($courseInfo)];
}
foreach ($emailList as $email) {
if (empty($email)) {
continue;
}
api_mail_html(
null,
$email,
$subject,
$content,
null,
null,
[],
$attachments,
false,
$extraParameters,
''
);
}
}
if (isset($attempt['post_actions'])) {
foreach ($attempt['post_actions'] as $action => $params) {
switch ($action) {
case 'subscribe_student_to_courses':
foreach ($params as $code) {
CourseManager::subscribeUser($currentUserId, $code);
break;
}
break;
}
}
}
}
}
}
}
}
}
}
/**
* Delete an exercise attempt.
*
* Log the exe_id deleted with the exe_user_id related.
*
* @param int $exeId
*/
public static function deleteExerciseAttempt($exeId)
{
$exeId = (int) $exeId;
$trackExerciseInfo = self::get_exercise_track_exercise_info($exeId);
if (empty($trackExerciseInfo)) {
return;
}
$tblTrackExercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
$tblTrackAttempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
Database::query("DELETE FROM $tblTrackExercises WHERE exe_id = $exeId");
Database::query("DELETE FROM $tblTrackAttempt WHERE exe_id = $exeId");
Event::addEvent(
LOG_EXERCISE_ATTEMPT_DELETE,
LOG_EXERCISE_ATTEMPT,
$exeId,
api_get_utc_datetime()
);
Event::addEvent(
LOG_EXERCISE_ATTEMPT_DELETE,
LOG_EXERCISE_AND_USER_ID,
$exeId.'-'.$trackExerciseInfo['exe_user_id'],
api_get_utc_datetime()
);
}
public static function scorePassed($score, $total)
{
$compareResult = bccomp($score, $total, 3);
$scorePassed = 1 === $compareResult || 0 === $compareResult;
if (false === $scorePassed) {
$epsilon = 0.00001;
if (abs($score - $total) < $epsilon) {
$scorePassed = true;
}
}
return $scorePassed;
}
public static function logPingForCheckingConnection()
{
$action = $_REQUEST['a'] ?? '';
if ('ping' !== $action) {
return;
}
if (!empty(api_get_user_id())) {
return;
}
$exeId = $_REQUEST['exe_id'] ?? 0;
error_log("Exercise ping received: exe_id = $exeId. _user not found in session.");
}
public static function saveFileExerciseResultPdf(
int $exeId,
int $courseId,
int $sessionId
) {
$courseInfo = api_get_course_info_by_id($courseId);
$courseCode = $courseInfo['code'];
$cidReq = 'cidReq='.$courseCode.'&id_session='.$sessionId.'&gidReq=0&gradebook=0';
$url = api_get_path(WEB_PATH).'main/exercise/exercise_show.php?'.$cidReq.'&id='.$exeId.'&action=export&export_type=all_results';
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_COOKIE, session_id());
curl_setopt($ch, CURLOPT_AUTOREFERER, true);
curl_setopt($ch, CURLOPT_COOKIESESSION, true);
curl_setopt($ch, CURLOPT_FAILONERROR, false);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$result = curl_exec($ch);
if (false === $result) {
error_log('saveFileExerciseResultPdf error: '.curl_error($ch));
}
curl_close($ch);
}
/**
* Export all results of all exercises to a ZIP file (including one zip for each exercise).
*
* @return false|void
*/
public static function exportAllExercisesResultsZip(
int $sessionId,
int $courseId,
array $filterDates = []
) {
$exercises = self::get_all_exercises_for_course_id(
null,
$sessionId,
$courseId,
false
);
$exportOk = false;
if (!empty($exercises)) {
$exportName = 'S'.$sessionId.'-C'.$courseId.'-ALL';
$baseDir = api_get_path(SYS_ARCHIVE_PATH);
$folderName = 'pdfexport-'.$exportName;
$exportFolderPath = $baseDir.$folderName;
if (!is_dir($exportFolderPath)) {
@mkdir($exportFolderPath);
}
foreach ($exercises as $exercise) {
$exerciseId = $exercise['iid'];
self::exportExerciseAllResultsZip($sessionId, $courseId, $exerciseId, $filterDates, $exportFolderPath);
}
// If export folder is not empty will be zipped.
$isFolderPathEmpty = (file_exists($exportFolderPath) && 2 == count(scandir($exportFolderPath)));
if (is_dir($exportFolderPath) && !$isFolderPathEmpty) {
$exportOk = true;
$exportFilePath = $baseDir.$exportName.'.zip';
$zip = new \PclZip($exportFilePath);
$zip->create($exportFolderPath, PCLZIP_OPT_REMOVE_PATH, $exportFolderPath);
rmdirr($exportFolderPath);
DocumentManager::file_send_for_download($exportFilePath, true, $exportName.'.zip');
exit;
}
}
if (!$exportOk) {
Display::addFlash(
Display::return_message(
get_lang('ExportExerciseNoResult'),
'warning',
false
)
);
}
return false;
}
/**
* Export all results of *one* exercise to a ZIP file containing individual PDFs.
*
* @return false|void
*/
public static function exportExerciseAllResultsZip(
int $sessionId,
int $courseId,
int $exerciseId,
array $filterDates = [],
string $mainPath = ''
) {
$objExerciseTmp = new Exercise($courseId);
$exeResults = $objExerciseTmp->getExerciseAndResult(
$courseId,
$sessionId,
$exerciseId,
true,
$filterDates
);
$exportOk = false;
if (!empty($exeResults)) {
$exportName = 'S'.$sessionId.'-C'.$courseId.'-T'.$exerciseId;
$baseDir = api_get_path(SYS_ARCHIVE_PATH);
$folderName = 'pdfexport-'.$exportName;
$exportFolderPath = $baseDir.$folderName;
// 1. Cleans the export folder if it exists.
if (is_dir($exportFolderPath)) {
rmdirr($exportFolderPath);
}
// 2. Create the pdfs inside a new export folder path.
if (!empty($exeResults)) {
foreach ($exeResults as $exeResult) {
$exeId = (int) $exeResult['exe_id'];
ExerciseLib::saveFileExerciseResultPdf($exeId, $courseId, $sessionId);
}
}
// 3. If export folder is not empty will be zipped.
$isFolderPathEmpty = (file_exists($exportFolderPath) && 2 == count(scandir($exportFolderPath)));
if (is_dir($exportFolderPath) && !$isFolderPathEmpty) {
$exportOk = true;
$exportFilePath = $baseDir.$exportName.'.zip';
$zip = new \PclZip($exportFilePath);
$zip->create($exportFolderPath, PCLZIP_OPT_REMOVE_PATH, $exportFolderPath);
rmdirr($exportFolderPath);
if (!empty($mainPath) && file_exists($exportFilePath)) {
@rename($exportFilePath, $mainPath.'/'.$exportName.'.zip');
} else {
DocumentManager::file_send_for_download($exportFilePath, true, $exportName.'.zip');
exit;
}
}
}
if (empty($mainPath) && !$exportOk) {
Display::addFlash(
Display::return_message(
get_lang('ExportExerciseNoResult'),
'warning',
false
)
);
}
return false;
}
/**
* Get formatted feedback comments for an exam attempt.
*/
public static function getFeedbackComments(int $examId): string
{
$TBL_TRACK_ATTEMPT = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
$TBL_QUIZ_QUESTION = Database::get_course_table(TABLE_QUIZ_QUESTION);
$sql = "SELECT ta.question_id, ta.teacher_comment, q.question AS title
FROM $TBL_TRACK_ATTEMPT ta
INNER JOIN $TBL_QUIZ_QUESTION q ON ta.question_id = q.iid
WHERE ta.exe_id = $examId
AND ta.teacher_comment IS NOT NULL
AND ta.teacher_comment != ''
GROUP BY ta.question_id
ORDER BY q.position ASC, ta.id ASC";
$result = Database::query($sql);
$commentsByQuestion = [];
while ($row = Database::fetch_array($result)) {
$questionId = $row['question_id'];
$questionTitle = Security::remove_XSS($row['title']);
$comment = Security::remove_XSS(trim(strip_tags($row['teacher_comment'])));
if (!empty($comment)) {
if (!isset($commentsByQuestion[$questionId])) {
$commentsByQuestion[$questionId] = [
'title' => $questionTitle,
'comments' => [],
];
}
$commentsByQuestion[$questionId]['comments'][] = $comment;
}
}
if (empty($commentsByQuestion)) {
return "
".get_lang('NoAdditionalComments')."
";
}
$output = "
".get_lang('TeacherFeedback')."
";
$output .= "
";
foreach ($commentsByQuestion as $questionId => $data) {
$output .= "
| ".get_lang('Question')." #$questionId: ".$data['title']." |
";
foreach ($data['comments'] as $comment) {
$output .= "
| ".get_lang('Feedback').": $comment |
";
}
}
$output .= "
";
return $output;
}
public static function replaceTermsInContent(string $search, string $replace): array
{
$replacements = [
Database::get_course_table(TABLE_QUIZ_TEST) => [
'iid' => ['title', 'description', 'sound'],
],
Database::get_course_table(TABLE_QUIZ_QUESTION) => [
'iid' => ['question', 'description'],
],
Database::get_course_table(TABLE_QUIZ_ANSWER) => [
'iid' => ['answer', 'comment'],
],
Database::get_course_table(TABLE_ANNOUNCEMENT) => [
'iid' => ['title', 'content'],
],
Database::get_course_table(TABLE_ANNOUNCEMENT_ATTACHMENT) => [
'iid' => ['path', 'comment'],
],
Database::get_course_table(TABLE_ATTENDANCE) => [
'iid' => ['name', 'description', 'attendance_qualify_title'],
],
Database::get_course_table(TABLE_BLOGS) => [
'iid' => ['blog_name', 'blog_subtitle'],
],
Database::get_course_table(TABLE_BLOGS_ATTACHMENT) => [
'iid' => ['path', 'comment'],
],
Database::get_course_table(TABLE_BLOGS_COMMENTS) => [
'iid' => ['title', 'comment'],
],
Database::get_course_table(TABLE_BLOGS_POSTS) => [
'iid' => ['title', 'full_text'],
],
Database::get_course_table(TABLE_BLOGS_TASKS) => [
'iid' => ['title', 'description'],
],
Database::get_course_table(TABLE_AGENDA) => [
'iid' => ['title', 'content', 'comment'],
],
Database::get_course_table(TABLE_AGENDA_ATTACHMENT) => [
'iid' => ['path', 'comment', 'filename'],
],
Database::get_course_table(TABLE_COURSE_DESCRIPTION) => [
'iid' => ['title', 'content'],
],
Database::get_course_table(TABLE_DOCUMENT) => [
'iid' => ['path', 'comment'],
],
Database::get_course_table(TABLE_DROPBOX_FEEDBACK) => [
'iid' => ['feedback'],
],
Database::get_course_table(TABLE_DROPBOX_FILE) => [
'iid' => ['title', 'description'],
],
Database::get_course_table(TABLE_DROPBOX_POST) => [
'iid' => ['feedback'],
],
Database::get_course_table(TABLE_FORUM_ATTACHMENT) => [
'iid' => ['path', 'comment', 'filename'],
],
Database::get_course_table(TABLE_FORUM_CATEGORY) => [
'iid' => ['cat_title', 'cat_comment'],
],
Database::get_course_table(TABLE_FORUM) => [
'iid' => ['forum_title', 'forum_comment', 'forum_image'],
],
Database::get_course_table(TABLE_FORUM_POST) => [
'iid' => ['post_title', 'post_text', 'poster_name'],
],
Database::get_course_table(TABLE_FORUM_THREAD) => [
'iid' => ['thread_title', 'thread_poster_name', 'thread_title_qualify'],
],
Database::get_course_table(TABLE_GLOSSARY) => [
'iid' => ['name', 'description'],
],
Database::get_course_table(TABLE_GROUP_CATEGORY) => [
'iid' => ['title', 'description'],
],
Database::get_course_table(TABLE_GROUP) => [
'iid' => ['name', 'description', 'secret_directory'],
],
Database::get_course_table(TABLE_LINK) => [
'iid' => ['description'],
],
Database::get_course_table(TABLE_LINK_CATEGORY) => [
'iid' => ['category_title', 'description'],
],
Database::get_course_table(TABLE_LP_MAIN) => [
'iid' => ['name', 'ref', 'description', 'path', 'content_license', 'preview_image', 'theme'],
],
Database::get_course_table(TABLE_LP_CATEGORY) => [
'iid' => ['name'],
],
Database::get_course_table(TABLE_LP_ITEM) => [
'iid' => ['prerequisite', 'description', 'title', 'parameters', 'launch_data', 'terms'],
],
Database::get_course_table(TABLE_LP_ITEM_VIEW) => [
'iid' => ['suspend_data', 'lesson_location'],
],
Database::get_course_table(TABLE_NOTEBOOK) => [
'iid' => ['title', 'description'],
],
Database::get_course_table(TABLE_ONLINE_LINK) => [
'iid' => ['name'],
],
Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY) => [
'iid' => ['title', 'description'],
],
Database::get_course_table(TABLE_ROLE) => [
'iid' => ['role_name', 'role_comment'],
],
Database::get_course_table(TABLE_STUDENT_PUBLICATION) => [
'iid' => ['title', 'title_correction', 'description'],
],
Database::get_course_table(TABLE_STUDENT_PUBLICATION_ASSIGNMENT_COMMENT) => [
'iid' => ['comment', 'file'],
],
Database::get_course_table(TABLE_SURVEY) => [
'iid' => ['title', 'subtitle', 'surveythanks', 'invite_mail', 'reminder_mail', 'mail_subject', 'access_condition', 'form_fields'],
],
Database::get_course_table(TABLE_SURVEY_QUESTION_GROUP) => [
'iid' => ['name', 'description'],
],
Database::get_course_table(TABLE_SURVEY_QUESTION) => [
'iid' => ['survey_question', 'survey_question_comment'],
],
Database::get_course_table(TABLE_SURVEY_QUESTION_OPTION) => [
'iid' => ['option_text'],
],
Database::get_course_table(TABLE_THEMATIC) => [
'iid' => ['content', 'title'],
],
Database::get_course_table(TABLE_THEMATIC_ADVANCE) => [
'iid' => ['content'],
],
Database::get_course_table(TABLE_THEMATIC_PLAN) => [
'iid' => ['description'],
],
Database::get_course_table(TABLE_TOOL_LIST) => [
'iid' => ['description'],
],
Database::get_course_table(TABLE_TOOL_INTRO) => [
'iid' => ['intro_text'],
],
Database::get_course_table(TABLE_USER_INFO_DEF) => [
'iid' => ['comment'],
],
Database::get_course_table(TABLE_WIKI) => [
'iid' => ['title', 'content', 'comment', 'progress', 'linksto'],
],
Database::get_course_table(TABLE_WIKI_CONF) => [
'iid' => ['feedback1', 'feedback2', 'feedback3'],
],
Database::get_course_table(TABLE_WIKI_DISCUSS) => [
'iid' => ['comment'],
],
Database::get_main_table(TABLE_CAREER) => [
'id' => ['name', 'description'],
],
Database::get_main_table(TABLE_MAIN_CHAT) => [
'id' => ['message'],
],
Database::get_main_table(TABLE_MAIN_CLASS) => [
'id' => ['name'],
],
Database::get_main_table(TABLE_MAIN_COURSE_REQUEST) => [
'id' => ['description', 'title', 'objetives', 'target_audience'],
],
'course_type' => [
'id' => ['description'],
],
Database::get_main_table(TABLE_EVENT_EMAIL_TEMPLATE) => [
'id' => ['message', 'subject', 'event_type_name'],
],
Database::get_main_table(TABLE_GRADE_MODEL) => [
'id' => ['name', 'description'],
],
Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY) => [
'id' => ['name', 'description'],
],
Database::get_main_table(TABLE_MAIN_GRADEBOOK_CERTIFICATE) => [
'id' => ['path_certificate'],
],
Database::get_main_table(TABLE_MAIN_GRADEBOOK_EVALUATION) => [
'id' => ['description', 'name'],
],
Database::get_main_table(TABLE_MAIN_GRADEBOOK_LINKEVAL_LOG) => [
'id' => ['name', 'description'],
],
Database::get_main_table(TABLE_MAIN_LEGAL) => [
'id' => ['content', 'changes'],
],
Database::get_main_table(TABLE_MESSAGE) => [
'id' => ['content'],
],
Database::get_main_table(TABLE_MESSAGE_ATTACHMENT) => [
'id' => ['path', 'comment', 'filename'],
],
Database::get_main_table(TABLE_NOTIFICATION) => [
'id' => ['content'],
],
Database::get_main_table(TABLE_PERSONAL_AGENDA) => [
'id' => ['title', 'text'],
],
Database::get_main_table(TABLE_PROMOTION) => [
'id' => ['description'],
],
'room' => [
'id' => ['description'],
],
'sequence_condition' => [
'id' => ['description'],
],
'sequence_method' => [
'id' => ['description', 'formula'],
],
'sequence_rule' => [
'id' => ['description'],
],
'sequence_type_entity' => [
'id' => ['description'],
],
'sequence_variable' => [
'id' => ['description'],
],
Database::get_main_table(TABLE_MAIN_SESSION) => [
'id' => ['description'],
],
Database::get_main_table(TABLE_MAIN_SHARED_SURVEY) => [
'survey_id' => ['subtitle', 'surveythanks', 'intro'],
],
Database::get_main_table(TABLE_MAIN_SHARED_SURVEY_QUESTION) => [
'question_id' => ['survey_question', 'survey_question_comment'],
],
Database::get_main_table(TABLE_MAIN_SHARED_SURVEY_QUESTION_OPTION) => [
'question_option_id' => ['option_text'],
],
Database::get_main_table(TABLE_MAIN_SKILL) => [
'id' => ['name', 'description', 'criteria'],
],
Database::get_main_table(TABLE_MAIN_SKILL_PROFILE) => [
'id' => ['description'],
],
Database::get_main_table(TABLE_MAIN_SKILL_REL_USER) => [
'id' => ['argumentation'],
],
'skill_rel_user_comment' => [
'id' => ['feedback_text'],
],
Database::get_main_table(TABLE_MAIN_SYSTEM_ANNOUNCEMENTS) => [
'id' => ['content'],
],
Database::get_main_table(TABLE_MAIN_SYSTEM_CALENDAR) => [
'id' => ['content'],
],
Database::get_main_table(TABLE_MAIN_SYSTEM_TEMPLATE) => [
'id' => ['comment', 'content'],
],
Database::get_main_table(TABLE_MAIN_TEMPLATES) => [
'id' => ['description', 'image'],
],
Database::get_main_table(TABLE_TICKET_CATEGORY) => [
'id' => ['description'],
],
Database::get_main_table(TABLE_TICKET_MESSAGE) => [
'id' => ['message'],
],
Database::get_main_table(TABLE_TICKET_MESSAGE_ATTACHMENTS) => [
'id' => ['filename', 'path'],
],
Database::get_main_table(TABLE_TICKET_PRIORITY) => [
'id' => ['description'],
],
Database::get_main_table(TABLE_TICKET_PROJECT) => [
'id' => ['description'],
],
Database::get_main_table(TABLE_TICKET_STATUS) => [
'id' => ['description'],
],
Database::get_main_table(TABLE_TICKET_TICKET) => [
'id' => ['message'],
],
Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT) => [
'id' => ['answer', 'teacher_comment', 'filename'],
],
Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT_RECORDING) => [
'id' => ['teacher_comment'],
],
Database::get_main_table(TABLE_STATISTIC_TRACK_E_DEFAULT) => [
'default_id' => ['default_value'],
],
Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES) => [
'exe_id' => ['data_tracking', 'questions_to_check'],
],
Database::get_main_table(TABLE_STATISTIC_TRACK_E_ITEM_PROPERTY) => [
'id' => ['content'],
],
'track_e_open' => [
'open_id' => ['open_remote_host', 'open_agent', 'open_referer'],
],
Database::get_main_table(TABLE_TRACK_STORED_VALUES) => [
'id' => ['sv_value'],
],
Database::get_main_table(TABLE_TRACK_STORED_VALUES_STACK) => [
'id' => ['sv_value'],
],
Database::get_main_table(TABLE_MAIN_USER_API_KEY) => [
'id' => ['description'],
],
Database::get_main_table(TABLE_USERGROUP) => [
'id' => ['name', 'description', 'picture', 'url'],
],
Database::get_main_table(TABLE_MAIN_BLOCK) => [
'id' => ['name', 'description', 'path'],
],
];
if (api_get_configuration_value('attendance_allow_comments')) {
$replacements['c_attendance_result_comment'] = [
'iid' => ['comment'],
];
}
if (api_get_configuration_value('exercise_text_when_finished_failure')) {
$replacements[Database::get_course_table(TABLE_QUIZ_TEST)]['iid'][] = 'text_when_finished_failure';
}
$changes = array_map(
fn ($table) => 0,
$replacements
);
foreach ($replacements as $table => $replacement) {
foreach ($replacement as $idColumn => $columns) {
$keys = array_map(fn ($column) => "$column LIKE %?%", $columns);
$values = array_fill(0, count($columns), $search);
$result = Database::select(
[$idColumn, ...$columns],
$table,
[
'where' => [
implode(' OR ', $keys) => $values,
],
'order' => "$idColumn ASC",
]
);
foreach ($result as $row) {
$attributes = array_combine(
$columns,
array_map(
fn ($column) => preg_replace('#'.$search.'#', $replace, $row[$column]),
$columns
)
);
try {
Database::update(
$table,
$attributes,
["$idColumn = ?" => $row[$idColumn]]
);
} catch (Exception $e) {
Database::handleError($e);
}
$changes[$table]++;
}
}
}
return $changes;
}
private static function subscribeSessionWhenFinishedFailure(int $exerciseId): void
{
$failureSession = self::getSessionWhenFinishedFailure($exerciseId);
if ($failureSession) {
SessionManager::subscribeUsersToSession(
$failureSession->getId(),
[api_get_user_id()],
SESSION_VISIBLE_READ_ONLY,
false
);
}
}
}