';
$s .= Display::tag('tr', $header, ['style' => 'text-align:left;']);
// ajout de la 2eme ligne d'entĂȘte pour true/falss et les pourcentages de certitude
@@ -1453,9 +1453,10 @@ HTML;
}
$answerList .= '
-
+
';
+ }
if ($freeze) {
$relPath = api_get_path(WEB_CODE_PATH);
echo "
@@ -1481,7 +1482,6 @@ HTML;
return;
}
- }
if (!$only_questions) {
if ($show_title) {
@@ -1514,6 +1514,7 @@ HOTSPOT;
new ".(HOT_SPOT_DELINEATION == $answerType ? 'DelineationQuestion' : 'HotspotQuestion')."({
questionId: $questionId,
exerciseId: $exerciseId,
+ exeId: 0,
selector: '#question_div_' + $questionId + ' .hotspot-image',
for: 'user',
relPath: '$relPath'
@@ -1614,6 +1615,80 @@ HOTSPOT;
return $nbrAnswers;
}
+ /**
+ * Get an HTML string with the list of exercises where the given question
+ * is being used.
+ *
+ * @param int $questionId The iid of the question being observed
+ * @param int $excludeTestId If defined, exclude this (current) test from the list of results
+ *
+ * @return string An HTML string containing a div and a table
+ */
+ public static function showTestsWhereQuestionIsUsed(int $questionId, int $excludeTestId = 0)
+ {
+ $questionId = (int) $questionId;
+ $sql = "SELECT qz.title quiz_title,
+ c.title course_title,
+ s.name session_name,
+ qz.iid as quiz_id,
+ qz.c_id,
+ qz.session_id
+ FROM c_quiz qz,
+ c_quiz_rel_question qq,
+ course c,
+ session s
+ WHERE qz.c_id = c.id AND
+ (qz.session_id = s.id OR qz.session_id = 0) AND
+ qq.exercice_id = qz.iid AND ";
+ if (!empty($excludeTestId)) {
+ $excludeTestId = (int) $excludeTestId;
+ $sql .= " qz.iid != $excludeTestId AND ";
+ }
+ $sql .= " qq.question_id = $questionId
+ GROUP BY qq.iid";
+
+ $result = [];
+ $html = "";
+
+ $sqlResult = Database::query($sql);
+
+ if (Database::num_rows($sqlResult) != 0) {
+ while ($row = Database::fetch_array($sqlResult, 'ASSOC')) {
+ $tmp = [];
+ $tmp[0] = $row['course_title'];
+ $tmp[1] = $row['session_name'];
+ $tmp[2] = $row['quiz_title'];
+ // Send do other test with r=1 to reset current test session variables
+ $urlToQuiz = api_get_path(WEB_CODE_PATH).'exercise/admin.php?'.api_get_cidreq().'&exerciseId='.$row['quiz_id'].'&r=1';
+ $tmp[3] = ''.Display::return_icon('quiz.png', get_lang('Edit')).'';
+ if ((int) $row['session_id'] == 0) {
+ $tmp[1] = '-';
+ }
+
+ $result[] = $tmp;
+ }
+
+ $headers = [
+ get_lang('Course'),
+ get_lang('Session'),
+ get_lang('Quiz'),
+ get_lang('LinkToTestEdition'),
+ ];
+
+ $title = Display::div(
+ get_lang('QuestionAlsoUsedInTheFollowingTests'),
+ [
+ 'class' => 'section-title',
+ 'style' => 'margin-top: 25px; border-bottom: none',
+ ]
+ );
+
+ $html = $title.Display::table($headers, $result);
+ }
+
+ echo $html;
+ }
+
/**
* @param int $exeId
*
@@ -2558,8 +2633,8 @@ HOTSPOT;
$maxNote = api_get_setting('exercise_max_score');
$minNote = api_get_setting('exercise_min_score');
- if ('' != $maxNote && '' != $minNote) {
- if (!empty($weight) && 0 != intval($weight)) {
+ if ($maxNote != '' && $minNote != '') {
+ if (!empty($weight) && (float) $weight !== (float) 0) {
$score = $minNote + ($maxNote - $minNote) * $score / $weight;
} else {
$score = $minNote;
@@ -2583,6 +2658,7 @@ HOTSPOT;
* @param string $decimalSeparator
* @param string $thousandSeparator
* @param bool $roundValues This option rounds the float values into a int using ceil()
+ * @param bool $removeEmptyDecimals
*
* @return string an html with the score modified
*/
@@ -2595,11 +2671,14 @@ HOTSPOT;
$hidePercentageSign = false,
$decimalSeparator = '.',
$thousandSeparator = ',',
- $roundValues = false
+ $roundValues = false,
+ $removeEmptyDecimals = false
) {
if (is_null($score) && is_null($weight)) {
return '-';
}
+ $decimalSeparator = empty($decimalSeparator) ? '.' : $decimalSeparator;
+ $thousandSeparator = empty($thousandSeparator) ? ',' : $thousandSeparator;
if ($use_platform_settings) {
$result = self::convertScoreToPlatformSetting($score, $weight);
@@ -2656,6 +2735,11 @@ HOTSPOT;
$html = $percentage.$percentageSign;
}
} else {
+ if ($removeEmptyDecimals) {
+ if (ScoreDisplay::hasEmptyDecimals($weight)) {
+ $weight = round($weight);
+ }
+ }
$html = $score.' / '.$weight;
}
@@ -3322,7 +3406,7 @@ EOT;
$best_score = 0;
if (!empty($user_results)) {
foreach ($user_results as $result) {
- if (!empty($result['max_score']) && 0 != intval($result['max_score'])) {
+ if (!empty($result['max_score']) && 0 != (float) $result['max_score']) {
$score = $result['score'] / $result['max_score'];
if ($score >= $best_score) {
$best_score = $score;
@@ -4158,12 +4242,18 @@ EOT;
* @param int $exeId
* @param bool $save_user_result save users results (true) or just show the results (false)
* @param string $remainingMessage
+ * @param bool $allowSignature
+ * @param bool $allowExportPdf
+ * @param bool $isExport
*/
public static function displayQuestionListByAttempt(
$objExercise,
$exeId,
$save_user_result = false,
- $remainingMessage = ''
+ $remainingMessage = '',
+ $allowSignature = false,
+ $allowExportPdf = false,
+ $isExport = false
) {
$origin = api_get_origin();
$courseId = api_get_course_int_id();
@@ -4252,11 +4342,13 @@ EOT;
$showTotalScoreAndUserChoicesInLastAttempt = true;
$showTotalScore = true;
$showQuestionScore = true;
+ $attemptResult = [];
if (in_array(
$objExercise->results_disabled,
[
RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT,
+ RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT_NO_FEEDBACK,
RESULT_DISABLE_DONT_SHOW_SCORE_ONLY_IF_USER_FINISHES_ATTEMPTS_SHOW_ALWAYS_FEEDBACK,
])
) {
@@ -4281,6 +4373,9 @@ EOT;
$numberAttempts++;
}
$showTotalScore = false;
+ if ($objExercise->results_disabled == RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT) {
+ $showTotalScore = true;
+ }
$showTotalScoreAndUserChoicesInLastAttempt = false;
if ($numberAttempts >= $objExercise->attempts) {
$showTotalScore = true;
@@ -4288,6 +4383,15 @@ EOT;
$show_only_score = false;
$showTotalScoreAndUserChoicesInLastAttempt = true;
}
+ if ($objExercise->results_disabled == RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT_NO_FEEDBACK) {
+ $showTotalScore = true;
+ $show_results = true;
+ $show_only_score = false;
+ $showTotalScoreAndUserChoicesInLastAttempt = false;
+ if ($numberAttempts >= $objExercise->attempts) {
+ $showTotalScoreAndUserChoicesInLastAttempt = true;
+ }
+ }
}
if (RESULT_DISABLE_DONT_SHOW_SCORE_ONLY_IF_USER_FINISHES_ATTEMPTS_SHOW_ALWAYS_FEEDBACK ==
@@ -4305,6 +4409,16 @@ EOT;
}
}
+ if ($allowExportPdf && $isExport) {
+ $showTotalScore = false;
+ $showQuestionScore = false;
+ $objExercise->feedback_type = 2;
+ $objExercise->hideComment = true;
+ $objExercise->hideNoAnswer = true;
+ $objExercise->results_disabled = 0;
+ $objExercise->hideExpectedAnswer = true;
+ $show_results = true;
+ }
if ('embeddable' !== $origin &&
!empty($exercise_stat_info['exe_user_id']) &&
!empty($studentInfo)
@@ -4313,7 +4427,9 @@ EOT;
echo $objExercise->showExerciseResultHeader(
$studentInfo,
$exercise_stat_info,
- $save_user_result
+ $save_user_result,
+ $allowSignature,
+ $allowExportPdf
);
}
@@ -4327,7 +4443,6 @@ EOT;
}
$question_list_answers = [];
- $media_list = [];
$category_list = [];
$loadChoiceFromSession = false;
$fromDatabase = true;
@@ -4335,7 +4450,7 @@ EOT;
$exerciseResultCoordinates = null;
$delineationResults = null;
- if (in_array(
+ if (true === $save_user_result && in_array(
$objExercise->getFeedbackType(),
[EXERCISE_FEEDBACK_TYPE_DIRECT, EXERCISE_FEEDBACK_TYPE_POPUP]
)) {
@@ -4394,6 +4509,7 @@ EOT;
$my_total_score = $result['score'];
$my_total_weight = $result['weight'];
+ $scorePassed = self::scorePassed($my_total_score, $my_total_weight);
// Category report
$category_was_added_for_this_test = false;
@@ -4404,8 +4520,34 @@ EOT;
if (!isset($category_list[$objQuestionTmp->category]['total'])) {
$category_list[$objQuestionTmp->category]['total'] = 0;
}
+ if (!isset($category_list[$objQuestionTmp->category]['total_questions'])) {
+ $category_list[$objQuestionTmp->category]['total_questions'] = 0;
+ }
+ if (!isset($category_list[$objQuestionTmp->category]['passed'])) {
+ $category_list[$objQuestionTmp->category]['passed'] = 0;
+ }
+ if (!isset($category_list[$objQuestionTmp->category]['wrong'])) {
+ $category_list[$objQuestionTmp->category]['wrong'] = 0;
+ }
+ if (!isset($category_list[$objQuestionTmp->category]['no_answer'])) {
+ $category_list[$objQuestionTmp->category]['no_answer'] = 0;
+ }
$category_list[$objQuestionTmp->category]['score'] += $my_total_score;
$category_list[$objQuestionTmp->category]['total'] += $my_total_weight;
+ if ($scorePassed) {
+ // Only count passed if score is not empty
+ if (!empty($my_total_score)) {
+ $category_list[$objQuestionTmp->category]['passed']++;
+ }
+ } else {
+ if ($result['user_answered']) {
+ $category_list[$objQuestionTmp->category]['wrong']++;
+ } else {
+ $category_list[$objQuestionTmp->category]['no_answer']++;
+ }
+ }
+
+ $category_list[$objQuestionTmp->category]['total_questions']++;
$category_was_added_for_this_test = true;
}
@@ -4456,25 +4598,21 @@ EOT;
}
}
+ $calculatedScore = [
+ 'result' => self::show_score(
+ $my_total_score,
+ $my_total_weight,
+ false
+ ),
+ 'pass' => $scorePassed,
+ 'score' => $my_total_score,
+ 'weight' => $my_total_weight,
+ 'comments' => $comnt,
+ 'user_answered' => $result['user_answered'],
+ ];
$score = [];
if ($show_results) {
- $scorePassed = $my_total_score >= $my_total_weight;
- if (function_exists('bccomp')) {
- $compareResult = bccomp($my_total_score, $my_total_weight, 3);
- $scorePassed = 1 === $compareResult || 0 === $compareResult;
- }
- $score = [
- 'result' => self::show_score(
- $my_total_score,
- $my_total_weight,
- false
- ),
- 'pass' => $scorePassed,
- 'score' => $my_total_score,
- 'weight' => $my_total_weight,
- 'comments' => $comnt,
- 'user_answered' => $result['user_answered'],
- ];
+ $score = $calculatedScore;
}
if (in_array($objQuestionTmp->type, [FREE_ANSWER, ORAL_EXPRESSION, ANNOTATION])) {
@@ -4492,7 +4630,7 @@ EOT;
$question_content = '';
if ($show_results) {
$question_content = '';
- if (false == $showQuestionScore) {
+ if (false === $showQuestionScore) {
$score = [];
}
@@ -4508,6 +4646,8 @@ EOT;
if ($show_results) {
$question_content .= '
';
}
+ $calculatedScore['question_content'] = $question_content;
+ $attemptResult[] = $calculatedScore;
if ($objExercise->showExpectedChoice()) {
$exercise_content .= Display::div(
Display::panel($question_content),
@@ -4581,13 +4721,15 @@ EOT;
echo $chartMultiAnswer;
}
- if (!empty($category_list) && ($show_results || $show_only_score)) {
+ if (!empty($category_list) &&
+ ($show_results || $show_only_score || RESULT_DISABLE_RADAR == $objExercise->results_disabled)
+ ) {
// Adding total
$category_list['total'] = [
'score' => $total_score,
'total' => $total_weight,
];
- echo TestCategory::get_stats_table_by_attempt($objExercise->id, $category_list);
+ echo TestCategory::get_stats_table_by_attempt($objExercise, $category_list);
}
if ($show_all_but_expected_answer) {
@@ -4669,7 +4811,7 @@ EOT;
)) {
echo Display::page_header(get_lang('Ranking'), null, 'h4');
echo self::displayResultsInRanking(
- $objExercise->iId,
+ $objExercise,
api_get_user_id(),
$courseId,
$sessionId
@@ -4679,23 +4821,59 @@ EOT;
if (!empty($remainingMessage)) {
echo Display::return_message($remainingMessage, 'normal', false);
}
+ $failedAnswersCount = 0;
+ $wrongQuestionHtml = '';
+ $all = '';
+ foreach ($attemptResult as $item) {
+ if (false === $item['pass']) {
+ $failedAnswersCount++;
+ $wrongQuestionHtml .= $item['question_content'].'
';
+ }
+ $all .= $item['question_content'].'
';
+ }
+
+ $passed = self::isPassPercentageAttemptPassed(
+ $objExercise,
+ $total_score,
+ $total_weight
+ );
+
+ $percentage = 0;
+ if (!empty($total_weight)) {
+ $percentage = ($total_score / $total_weight) * 100;
+ }
+
+ return [
+ 'category_list' => $category_list,
+ 'attempts_result_list' => $attemptResult, // array of results
+ 'exercise_passed' => $passed, // boolean
+ 'total_answers_count' => count($attemptResult), // int
+ 'failed_answers_count' => $failedAnswersCount, // int
+ 'failed_answers_html' => $wrongQuestionHtml,
+ 'all_answers_html' => $all,
+ 'total_score' => $total_score,
+ 'total_weight' => $total_weight,
+ 'total_percentage' => $percentage,
+ 'count_pending_questions' => $countPendingQuestions,
+ ];
}
/**
* Display the ranking of results in a exercise.
*
- * @param int $exerciseId
+ * @param Exercise $exercise
* @param int $currentUserId
* @param int $courseId
* @param int $sessionId
*
* @return string
*/
- public static function displayResultsInRanking($exerciseId, $currentUserId, $courseId, $sessionId = 0)
+ public static function displayResultsInRanking($exercise, $currentUserId, $courseId, $sessionId = 0)
{
+ $exerciseId = $exercise->iId;
$data = self::exerciseResultsInRanking($exerciseId, $courseId, $sessionId);
- $table = new HTML_Table(['class' => 'table table-hover table-bordered']);
+ $table = new HTML_Table(['class' => 'table table-hover table-striped table-bordered']);
$table->setHeaderContents(0, 0, get_lang('Position'), ['class' => 'text-right']);
$table->setHeaderContents(0, 1, get_lang('Username'));
$table->setHeaderContents(0, 2, get_lang('Score'), ['class' => 'text-right']);
@@ -4813,12 +4991,11 @@ EOT;
$ribbon = $displayChartDegree ? '' : '';
if ($checkPassPercentage) {
- $isSuccess = self::isSuccessExerciseResult(
- $score, $weight, $objExercise->selectPassPercentage()
- );
+ $passPercentage = $objExercise->selectPassPercentage();
+ $isSuccess = self::isSuccessExerciseResult($score, $weight, $passPercentage);
// Color the final test score if pass_percentage activated
$ribbonTotalSuccessOrError = '';
- if (self::isPassPercentageEnabled($objExercise->selectPassPercentage())) {
+ if (self::isPassPercentageEnabled($passPercentage)) {
if ($isSuccess) {
$ribbonTotalSuccessOrError = ' ribbon-total-success';
} else {
@@ -4850,6 +5027,12 @@ EOT;
return $ribbon;
}
+ public static function isPassPercentageAttemptPassed($objExercise, $score, $weight)
+ {
+ $passPercentage = $objExercise->selectPassPercentage();
+
+ return self::isSuccessExerciseResult($score, $weight, $passPercentage);
+ }
/**
* @param float $score
* @param float $weight
@@ -5261,16 +5444,28 @@ EOT;
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.
*
- * @throws \Doctrine\ORM\ORMException
- * @throws \Doctrine\ORM\OptimisticLockException
- * @throws \Doctrine\ORM\TransactionRequiredException
- *
* @return TrackEExercises|null
*/
public static function recalculateResult($exeId, $userId, $exerciseId, $courseId = 0)
@@ -5360,4 +5555,510 @@ EOT;
return $trackedExercise;
}
+
+ public static function getTotalQuestionAnswered($courseId, $exerciseId, $questionId)
+ {
+ $courseId = (int) $courseId;
+ $exerciseId = (int) $exerciseId;
+ $questionId = (int) $questionId;
+
+ $attemptTable = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
+ $trackTable = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
+
+ $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'
+ ";
+ $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, $limit = 10)
+ {
+ $courseId = (int) $courseId;
+ $exerciseId = (int) $exerciseId;
+ $limit = (int) $limit;
+
+ $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');
+ }
+
+ $sql = "SELECT q.question, question_id, count(q.iid) count
+ FROM $attemptTable t
+ INNER JOIN $questionTable q
+ ON (q.c_id = t.c_id AND q.id = t.question_id)
+ INNER JOIN $trackTable te
+ ON (te.c_id = q.c_id AND 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
+ GROUP BY q.iid
+ ORDER BY count DESC
+ LIMIT $limit
+ ";
+
+ $result = Database::query($sql);
+
+ return Database::store_result($result, 'ASSOC');
+ }
+
+ public static function getExerciseResultsCount($type, $courseId, $exerciseId, $sessionId = 0)
+ {
+ $courseId = (int) $courseId;
+ $exerciseId = (int) $exerciseId;
+
+ $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');
+ }
+
+ $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 ';
+ break;
+ case 'wrong_student':
+ $selectCount = 'count(DISTINCT te.exe_user_id)';
+ $scoreCondition = ' AND exe_result != exe_weighting ';
+ break;
+ case 'correct':
+ $scoreCondition = ' AND exe_result = exe_weighting ';
+ break;
+ case 'wrong':
+ $scoreCondition = ' AND exe_result != exe_weighting ';
+ 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, $trackInfo, $currentUserId = 0)
+ {
+ $wrongAnswersCount = $stats['failed_answers_count'];
+ $attemptDate = substr($trackInfo['exe_date'], 0, 10);
+
+ $content = str_replace(
+ [
+ '((exercise_error_count))',
+ '((all_answers_html))',
+ '((exercise_title))',
+ '((exercise_attempt_date))',
+ ],
+ [
+ $wrongAnswersCount,
+ $stats['all_answers_html'],
+ $exercise->get_formated_title(),
+ $attemptDate,
+ ],
+ $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
+ ) {
+ $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'];
+
+ // 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
+ );
+ foreach ($emailList as $email) {
+ if (empty($email)) {
+ continue;
+ }
+ api_mail_html(
+ null,
+ $email,
+ $subject,
+ $content,
+ null,
+ null,
+ [],
+ $attachments
+ );
+ }
+ }
+
+ 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;
+ }
}
diff --git a/public/main/inc/lib/exercise_show_functions.lib.php b/public/main/inc/lib/exercise_show_functions.lib.php
index 38d49d853d..951cb4384e 100644
--- a/public/main/inc/lib/exercise_show_functions.lib.php
+++ b/public/main/inc/lib/exercise_show_functions.lib.php
@@ -1,16 +1,6 @@
';
+ $content = '
';
$content .= '';
$content .= '';
+ $hotspotColors[$orderColor].'">';
$content .= ' | ';
$content .= '';
$content .= "$answerId - $answer";
$content .= ' | ';
+ if (false === $exercise->hideComment) {
$content .= '';
if (!$hide_expected_answer) {
$status = Display::label(get_lang('Incorrect'), 'danger');
if ($studentChoice) {
$status = Display::label(get_lang('Correct'), 'success');
- } else {
- if (in_array($resultsDisabled, [
- RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER,
- ])
- ) {
- return '';
- }
}
$content .= $status;
+ } else {
+ $content .= ' ';
}
$content .= ' | ';
if (EXERCISE_FEEDBACK_TYPE_EXAM != $feedback_type) {
$content .= '';
if ($studentChoice) {
$content .= ''.nl2br($answerComment).'';
+ } else {
+ $content .= ' ';
}
$content .= ' | ';
} else {
$content .= ' | ';
}
+ }
$content .= '
';
echo $content;
@@ -319,6 +325,9 @@ class ExerciseShowFunctions
$showTotalScoreAndUserChoices,
$export = false
) {
+ if (true === $exercise->hideNoAnswer && empty($studentChoice)) {
+ return '';
+ }
if ($export) {
$answer = strip_tags_blacklist($answer, ['title', 'head']);
// Fix answers that contains this tags
@@ -356,6 +365,11 @@ class ExerciseShowFunctions
$hide_expected_answer = false;
}
break;
+ case RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT_NO_FEEDBACK:
+ if (false === $showTotalScoreAndUserChoices && empty($studentChoiceInt)) {
+ return '';
+ }
+ break;
}
$icon = in_array($answerType, [UNIQUE_ANSWER, UNIQUE_ANSWER_NO_OPTION]) ? 'radio' : 'checkbox';
@@ -418,6 +432,7 @@ class ExerciseShowFunctions
$showComment = true;
}
+ if (false === $exercise->hideComment) {
if ($showComment) {
echo '';
$color = 'black';
@@ -435,6 +450,7 @@ class ExerciseShowFunctions
} else {
echo ' | | ';
}
+ }
echo '';
}
@@ -486,6 +502,11 @@ class ExerciseShowFunctions
$hide_expected_answer = false;
}
break;
+ case RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT_NO_FEEDBACK:
+ if (false === $showTotalScoreAndUserChoices && empty($studentChoice)) {
+ return '';
+ }
+ break;
}
$content = '';
@@ -531,6 +552,7 @@ class ExerciseShowFunctions
$content .= '';
}
+ if (false === $exercise->hideComment) {
if (EXERCISE_FEEDBACK_TYPE_EXAM != $feedbackType) {
$content .= '';
$color = 'black';
@@ -553,6 +575,7 @@ class ExerciseShowFunctions
}
$content .= ' | ';
}
+ }
$content .= '
';
echo $content;
@@ -645,6 +668,7 @@ class ExerciseShowFunctions
'
';
+ if (false === $exercise->hideComment) {
if (EXERCISE_FEEDBACK_TYPE_EXAM != $feedbackType) {
echo '';
if (isset($newOptions[$studentChoice])) {
@@ -654,6 +678,7 @@ class ExerciseShowFunctions
} else {
echo ' | | ';
}
+ }
echo '';
}
@@ -704,6 +729,11 @@ class ExerciseShowFunctions
$hide_expected_answer = false;
}
break;
+ case RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT_NO_FEEDBACK:
+ if (false === $showTotalScoreAndUserChoices && empty($studentChoice)) {
+ return '';
+ }
+ break;
}
echo '';
@@ -750,13 +780,17 @@ class ExerciseShowFunctions
echo '';
}
+ if (false === $exercise->hideComment) {
if (EXERCISE_FEEDBACK_TYPE_EXAM != $feedbackType) {
echo '';
//@todo replace this harcoded value
- if ($studentChoice || in_array($resultsDisabled, [
+ if ($studentChoice || in_array(
+ $resultsDisabled,
+ [
RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER,
RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS_AND_RANKING,
- ])
+ ]
+ )
) {
$color = 'black';
if ($studentChoice == $answerCorrect) {
@@ -770,6 +804,7 @@ class ExerciseShowFunctions
echo ' | ';
} else {
echo ' | ';
+ }
}
echo '
';
}
diff --git a/src/CoreBundle/Entity/TrackEAttempt.php b/src/CoreBundle/Entity/TrackEAttempt.php
index e76c45d398..7bfad37c37 100644
--- a/src/CoreBundle/Entity/TrackEAttempt.php
+++ b/src/CoreBundle/Entity/TrackEAttempt.php
@@ -118,13 +118,14 @@ class TrackEAttempt
/**
* @var int
*
- * @ORM\Column(name="second_spent", type="integer")
+ * @ORM\Column(name="seconds_spent", type="integer")
*/
- protected $secondSpent;
+ protected $secondsSpent;
public function __construct()
{
- $this->secondSpent = 0;
+ $this->teacherComment = '';
+ $this->secondsSpent = 0;
}
/**
@@ -352,4 +353,16 @@ class TrackEAttempt
{
return $this->id;
}
+
+ public function getSecondsSpent(): int
+ {
+ return $this->secondsSpent;
+ }
+
+ public function setSecondsSpent(int $secondsSpent): TrackEAttempt
+ {
+ $this->secondsSpent = $secondsSpent;
+
+ return $this;
+ }
}
diff --git a/src/CoreBundle/Entity/TrackEAttemptRecording.php b/src/CoreBundle/Entity/TrackEAttemptRecording.php
index 88dc7891d6..bad68ae74d 100644
--- a/src/CoreBundle/Entity/TrackEAttemptRecording.php
+++ b/src/CoreBundle/Entity/TrackEAttemptRecording.php
@@ -269,4 +269,23 @@ class TrackEAttemptRecording
{
return $this->id;
}
+
+ /**
+ * @return int
+ */
+ public function getAnswer()
+ {
+ return $this->answer;
+ }
+
+ /**
+ * @param int $answer
+ */
+ public function setAnswer($answer): self
+ {
+ $this->answer = $answer;
+
+ return $this;
+ }
+
}
diff --git a/src/CoreBundle/Migrations/Schema/V200/Version20180904175500.php b/src/CoreBundle/Migrations/Schema/V200/Version20180904175500.php
index e11e3e975e..853350614e 100644
--- a/src/CoreBundle/Migrations/Schema/V200/Version20180904175500.php
+++ b/src/CoreBundle/Migrations/Schema/V200/Version20180904175500.php
@@ -97,8 +97,8 @@ class Version20180904175500 extends AbstractMigrationChamilo
$this->addSql('CREATE INDEX idx_track_e_attempt_tms ON track_e_attempt (tms)');
}
- if (false === $table->hasColumn('second_spent')) {
- $this->addSql('ALTER TABLE track_e_attempt ADD second_spent INT NOT NULL, CHANGE user_id user_id INT DEFAULT NULL');
+ if (false === $table->hasColumn('seconds_spent')) {
+ $this->addSql('ALTER TABLE track_e_attempt ADD seconds_spent INT NOT NULL, CHANGE user_id user_id INT DEFAULT NULL');
}
if (false === $table->hasForeignKey('FK_A89CC3B691D79BD3')) {