$questionDescription
HOTSPOT;
}
$relPath = api_get_path(WEB_CODE_PATH);
$s .= "
$answerList
";
echo <<
HOTSPOT;
} elseif ($answerType == ANNOTATION) {
global $exe_id;
$relPath = api_get_path(WEB_CODE_PATH);
if (api_is_platform_admin() || api_is_course_admin()) {
$docId = DocumentManager::get_document_id($course, '/images/'.$pictureName);
if ($docId) {
$images_folder_visibility = api_get_item_visibility(
$course,
'document',
$docId,
api_get_session_id()
);
if (!$images_folder_visibility) {
echo Display::return_message(get_lang('ChangeTheVisibilityOfTheCurrentImage'), 'warning');
}
}
if ($freeze) {
echo Display::img(
api_get_path(WEB_COURSE_PATH).$course['path'].'/document/images/'.$pictureName,
$objQuestionTmp->selectTitle(),
['width' => '600px']
);
return 0;
}
}
if (!$only_questions) {
if ($show_title) {
if ($exercise->display_category_name) {
TestCategory::displayCategoryAndTitle($objQuestionTmp->iid);
}
echo $objQuestionTmp->getTitleToDisplay($current_item, $exerciseId);
}
if ($questionRequireAuth) {
WhispeakAuthPlugin::quizQuestionAuthentify($questionId, $exercise);
return false;
}
echo '
'.$objQuestionTmp->selectDescription().'
';
}
$objAnswerTmp = new Answer($questionId);
$nbrAnswers = $objAnswerTmp->selectNbrAnswers();
unset($objAnswerTmp, $objQuestionTmp);
}
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'];
$courseDetails = api_get_course_info_by_id($row['c_id']);
$courseCode = $courseDetails['code'];
// 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_params($courseCode, $row['session_id']).'&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;
}
/**
* Get the table as array of results of exercises attempts with questions score.
*
* @return array
*/
public static function getTrackExerciseAttemptsTable(Exercise $objExercise)
{
$tblQuiz = Database::get_course_table(TABLE_QUIZ_TEST);
$tblTrackExercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
$exerciseId = $objExercise->iid;
$courseId = $objExercise->course_id;
$sessionId = (int) $objExercise->sessionId;
$questionList = $objExercise->getQuestionForTeacher(0, $objExercise->getQuestionCount());
$headers = [
get_lang('UserName'),
get_lang('Email'),
];
$extraField = new ExtraField('user');
$extraFieldValue = new ExtraFieldValue('user');
$extraFieldQuestion = new ExtraFieldValue('question');
$extraFields = $extraField->get_all(['filter = ?' => 1]);
$userExtraFields = [];
if (!empty($extraFields)) {
foreach ($extraFields as $extra) {
$headers[] = $extra['display_text'];
$userExtraFields[] = $extra['variable'];
}
}
$headersXls = $headers;
if (!empty($questionList)) {
foreach ($questionList as $questionId) {
$questionObj = Question::read($questionId);
$questionName = cut($questionObj->question, 200);
$headers[] = '
'.$questionId.' ';
$headersXls[] = $questionName;
}
}
$sql = "SELECT
te.exe_id,
te.exe_user_id
FROM
$tblTrackExercises te
INNER JOIN
$tblQuiz q ON (q.iid = te.exe_exo_id AND q.c_id = te.c_id)
WHERE
te.c_id = $courseId AND
te.session_id = $sessionId AND
te.status = '' AND
te.exe_exo_id = $exerciseId
";
$rs = Database::query($sql);
$data = [];
if (Database::num_rows($rs) > 0) {
$x = 0;
while ($row = Database::fetch_array($rs)) {
$userInfo = api_get_user_info($row['exe_user_id']);
$data[$x]['username'] = $userInfo['username'];
$data[$x]['email'] = $userInfo['email'];
if (!empty($userExtraFields)) {
foreach ($userExtraFields as $variable) {
$extra = $extraFieldValue->get_values_by_handler_and_field_variable(
$row['exe_user_id'],
$variable
);
$data[$x][$variable] = $extra['value'] ?? '';
}
}
// the questions
if (!empty($questionList)) {
foreach ($questionList as $questionId) {
$questionObj = Question::read($questionId);
$questionName = cut($questionObj->question, 200);
$questionResult = $objExercise->manage_answer(
$row['exe_id'],
$questionId,
'',
'exercise_show',
[],
false,
true,
false,
$objExercise->selectPropagateNeg()
);
$displayValue = $questionResult['score'];
$differentiation = $extraFieldQuestion->get_values_by_handler_and_field_variable($questionId, 'differentiation');
if (!empty($differentiation['value'])) {
$answerType = $questionObj->selectType();
$objAnswerTmp = new Answer($questionId, api_get_course_int_id());
$userChoice = [];
if (!empty($questionResult['correct_answer_id']) && HOT_SPOT_DELINEATION != $answerType) {
foreach ($questionResult['correct_answer_id'] as $answerId) {
$answer = $objAnswerTmp->getAnswerByAutoId($answerId);
if (!empty($answer)) {
$userChoice[] = $answer['answer'];
} else {
$answer = $objAnswerTmp->selectAnswer($answerId);
$userChoice[] = $answer;
}
}
}
if (!empty($userChoice)) {
$displayValue = implode('|', $userChoice);
}
}
$questionModalUrl = api_get_path(WEB_AJAX_PATH).'exercise.ajax.php?'.api_get_cidreq().'&a=show_question_attempt&exercise='.$exerciseId.'&question='.$questionId.'&exe_id='.$row['exe_id'];
$data[$x][$questionId] = '
'.$displayValue.' ';
}
}
$x++;
}
}
$table['headers'] = $headers;
$table['headers_xls'] = $headersXls;
$table['rows'] = $data;
return $table;
}
/**
* @param int $exeId
*
* @return array
*/
public static function get_exercise_track_exercise_info($exeId)
{
$quizTable = Database::get_course_table(TABLE_QUIZ_TEST);
$trackExerciseTable = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
$courseTable = Database::get_main_table(TABLE_MAIN_COURSE);
$exeId = (int) $exeId;
$result = [];
if (!empty($exeId)) {
$sql = " SELECT q.*, tee.*
FROM $quizTable as q
INNER JOIN $trackExerciseTable as tee
ON q.iid = tee.exe_exo_id
INNER JOIN $courseTable c
ON c.id = tee.c_id
WHERE tee.exe_id = $exeId
AND q.c_id = c.id";
$sqlResult = Database::query($sql);
if (Database::num_rows($sqlResult)) {
$result = Database::fetch_array($sqlResult, 'ASSOC');
$result['duration_formatted'] = '';
if (!empty($result['exe_duration'])) {
$time = api_format_time($result['exe_duration'], 'js');
$result['duration_formatted'] = $time;
}
}
}
return $result;
}
/**
* Validates the time control key.
*
* @param int $exercise_id
* @param int $lp_id
* @param int $lp_item_id
*
* @return bool
*/
public static function exercise_time_control_is_valid(
$exercise_id,
$lp_id = 0,
$lp_item_id = 0
) {
$course_id = api_get_course_int_id();
$exercise_id = (int) $exercise_id;
$table = Database::get_course_table(TABLE_QUIZ_TEST);
$sql = "SELECT expired_time FROM $table
WHERE iid = $exercise_id";
$result = Database::query($sql);
$row = Database::fetch_array($result, 'ASSOC');
if (!empty($row['expired_time'])) {
$current_expired_time_key = self::get_time_control_key(
$exercise_id,
$lp_id,
$lp_item_id
);
if (isset($_SESSION['expired_time'][$current_expired_time_key])) {
$current_time = time();
$expired_time = api_strtotime(
$_SESSION['expired_time'][$current_expired_time_key],
'UTC'
);
$total_time_allowed = $expired_time + 30;
if ($total_time_allowed < $current_time) {
return false;
}
return true;
}
return false;
}
return true;
}
/**
* Deletes the time control token.
*
* @param int $exercise_id
* @param int $lp_id
* @param int $lp_item_id
*/
public static function exercise_time_control_delete(
$exercise_id,
$lp_id = 0,
$lp_item_id = 0
) {
$current_expired_time_key = self::get_time_control_key(
$exercise_id,
$lp_id,
$lp_item_id
);
unset($_SESSION['expired_time'][$current_expired_time_key]);
}
/**
* Generates the time control key.
*
* @param int $exercise_id
* @param int $lp_id
* @param int $lp_item_id
*
* @return string
*/
public static function get_time_control_key(
$exercise_id,
$lp_id = 0,
$lp_item_id = 0
) {
$exercise_id = (int) $exercise_id;
$lp_id = (int) $lp_id;
$lp_item_id = (int) $lp_item_id;
return
api_get_course_int_id().'_'.
api_get_session_id().'_'.
$exercise_id.'_'.
api_get_user_id().'_'.
$lp_id.'_'.
$lp_item_id;
}
/**
* Get session time control.
*
* @param int $exercise_id
* @param int $lp_id
* @param int $lp_item_id
*
* @return int
*/
public static function get_session_time_control_key(
$exercise_id,
$lp_id = 0,
$lp_item_id = 0
) {
$return_value = 0;
$time_control_key = self::get_time_control_key(
$exercise_id,
$lp_id,
$lp_item_id
);
if (isset($_SESSION['expired_time']) && isset($_SESSION['expired_time'][$time_control_key])) {
$return_value = $_SESSION['expired_time'][$time_control_key];
}
return $return_value;
}
/**
* Gets count of exam results.
*
* @param int $exerciseId
* @param array $conditions
* @param string $courseCode
* @param bool $showSession
* @param bool $searchAllTeacherCourses
* @param int $status
*
* @return array
*/
public static function get_count_exam_results(
$exerciseId,
$conditions,
$courseCode = '',
$showSession = false,
$searchAllTeacherCourses = false,
$status = 0,
$showAttemptsInSessions = false,
$questionType = 0,
$originPending = false
) {
return self::get_exam_results_data(
null,
null,
null,
null,
$exerciseId,
$conditions,
true,
$courseCode,
$showSession,
false,
[],
false,
false,
false,
$searchAllTeacherCourses,
$status,
$showAttemptsInSessions,
$questionType,
$originPending
);
}
/**
* @param string $path
*
* @return int
*/
public static function get_count_exam_hotpotatoes_results($path)
{
return self::get_exam_results_hotpotatoes_data(
0,
0,
'',
'',
$path,
true,
''
);
}
/**
* @param int $in_from
* @param int $in_number_of_items
* @param int $in_column
* @param int $in_direction
* @param string $in_hotpot_path
* @param bool $in_get_count
* @param null $where_condition
*
* @return array|int
*/
public static function get_exam_results_hotpotatoes_data(
$in_from,
$in_number_of_items,
$in_column,
$in_direction,
$in_hotpot_path,
$in_get_count = false,
$where_condition = null
) {
$courseId = api_get_course_int_id();
// by default in_column = 1 If parameters given, it is the name of the column witch is the bdd field name
if ($in_column == 1) {
$in_column = 'firstname';
}
$in_hotpot_path = Database::escape_string($in_hotpot_path);
$in_direction = Database::escape_string($in_direction);
$in_direction = !in_array(strtolower(trim($in_direction)), ['asc', 'desc']) ? 'asc' : $in_direction;
$in_column = Database::escape_string($in_column);
$in_number_of_items = (int) $in_number_of_items;
$in_from = (int) $in_from;
$TBL_TRACK_HOTPOTATOES = Database::get_main_table(
TABLE_STATISTIC_TRACK_E_HOTPOTATOES
);
$TBL_USER = Database::get_main_table(TABLE_MAIN_USER);
$sql = "SELECT *, thp.id AS thp_id
FROM $TBL_TRACK_HOTPOTATOES thp
JOIN $TBL_USER u
ON thp.exe_user_id = u.user_id
WHERE
thp.c_id = $courseId AND
exe_name LIKE '$in_hotpot_path%'";
// just count how many answers
if ($in_get_count) {
$res = Database::query($sql);
return Database::num_rows($res);
}
// get a number of sorted results
$sql .= " $where_condition
ORDER BY `$in_column` $in_direction
LIMIT $in_from, $in_number_of_items";
$res = Database::query($sql);
$result = [];
$apiIsAllowedToEdit = api_is_allowed_to_edit();
$urlBase = api_get_path(WEB_CODE_PATH).
'exercise/hotpotatoes_exercise_report.php?action=delete&'.
api_get_cidreq().'&id=';
while ($data = Database::fetch_array($res)) {
$actions = null;
if ($apiIsAllowedToEdit) {
$url = $urlBase.$data['thp_id'].'&path='.$data['exe_name'];
$actions = Display::url(
Display::return_icon('delete.png', get_lang('Delete')),
$url
);
}
$result[] = [
'firstname' => $data['firstname'],
'lastname' => $data['lastname'],
'username' => $data['username'],
'group_name' => implode(
'
',
GroupManager::get_user_group_name($data['user_id'])
),
'exe_date' => $data['exe_date'],
'score' => $data['exe_result'].' / '.$data['exe_weighting'],
'actions' => $actions,
];
}
return $result;
}
/**
* @param string $exercisePath
* @param int $userId
* @param int $courseId
* @param int $sessionId
*
* @return array
*/
public static function getLatestHotPotatoResult(
$exercisePath,
$userId,
$courseId,
$sessionId
) {
$table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTPOTATOES);
$exercisePath = Database::escape_string($exercisePath);
$userId = (int) $userId;
$courseId = (int) $courseId;
$sql = "SELECT * FROM $table
WHERE
c_id = $courseId AND
exe_name LIKE '$exercisePath%' AND
exe_user_id = $userId
ORDER BY id
LIMIT 1";
$result = Database::query($sql);
$attempt = [];
if (Database::num_rows($result)) {
$attempt = Database::fetch_array($result, 'ASSOC');
}
return $attempt;
}
/**
* Export the pending attempts to excel.
*
* @params $values
*/
public static function exportPendingAttemptsToExcel($values)
{
$headers = [
get_lang('Course'),
get_lang('Exercise'),
get_lang('FirstName'),
get_lang('LastName'),
get_lang('LoginName'),
get_lang('Duration').' ('.get_lang('MinMinute').')',
get_lang('StartDate'),
get_lang('EndDate'),
get_lang('Score'),
get_lang('IP'),
get_lang('Status'),
get_lang('Corrector'),
get_lang('CorrectionDate'),
];
$tableXls[] = $headers;
$courseId = $values['course_id'] ?? 0;
$exerciseId = $values['exercise_id'] ?? 0;
$status = $values['status'] ?? 0;
$whereCondition = '';
if (isset($_GET['filter_by_user']) && !empty($_GET['filter_by_user'])) {
$filter_user = (int) $_GET['filter_by_user'];
if (empty($whereCondition)) {
$whereCondition .= " te.exe_user_id = '$filter_user'";
} else {
$whereCondition .= " AND te.exe_user_id = '$filter_user'";
}
}
if (isset($_GET['group_id_in_toolbar']) && !empty($_GET['group_id_in_toolbar'])) {
$groupIdFromToolbar = (int) $_GET['group_id_in_toolbar'];
if (!empty($groupIdFromToolbar)) {
if (empty($whereCondition)) {
$whereCondition .= " te.group_id = '$groupIdFromToolbar'";
} else {
$whereCondition .= " AND group_id = '$groupIdFromToolbar'";
}
}
}
if (!empty($whereCondition)) {
$whereCondition = " AND $whereCondition";
}
if (!empty($courseId)) {
$whereCondition .= " AND te.c_id = $courseId";
}
$result = ExerciseLib::get_exam_results_data(
0,
10000000,
'c_id',
'asc',
$exerciseId,
$whereCondition,
false,
null,
false,
false,
[],
false,
false,
false,
true,
$status
);
if (!empty($result)) {
foreach ($result as $attempt) {
$data = [
$attempt['course'],
$attempt['exercise'],
$attempt['firstname'],
$attempt['lastname'],
$attempt['username'],
$attempt['exe_duration'],
$attempt['start_date'],
$attempt['exe_date'],
strip_tags($attempt['score']),
$attempt['user_ip'],
strip_tags($attempt['status']),
$attempt['qualificator_fullname'],
$attempt['date_of_qualification'],
];
$tableXls[] = $data;
}
}
$fileName = get_lang('PendingAttempts').'_'.api_get_local_time();
Export::arrayToXls($tableXls, $fileName);
return true;
}
/**
* Gets exercise results.
*
* @todo this function should be moved in a library + no global calls
*
* @param int $from
* @param int $number_of_items
* @param int $column
* @param string $direction
* @param int $exercise_id
* @param null $extra_where_conditions
* @param bool $get_count
* @param string $courseCode
* @param bool $showSessionField
* @param bool $showExerciseCategories
* @param array $userExtraFieldsToAdd
* @param bool $useCommaAsDecimalPoint
* @param bool $roundValues
* @param bool $getOnlyIds
*
* @return array
*/
public static function get_exam_results_data(
$from,
$number_of_items,
$column,
$direction,
$exercise_id,
$extra_where_conditions = null,
$get_count = false,
$courseCode = null,
$showSessionField = false,
$showExerciseCategories = false,
$userExtraFieldsToAdd = [],
$useCommaAsDecimalPoint = false,
$roundValues = false,
$getOnlyIds = false,
$searchAllTeacherCourses = false,
$status = 0,
$showAttemptsInSessions = false,
$questionType = 0,
$originPending = false
) {
//@todo replace all this globals
global $filter;
$courseCode = empty($courseCode) ? api_get_course_id() : $courseCode;
$courseInfo = api_get_course_info($courseCode);
$documentPath = '';
$sessionId = api_get_session_id();
$courseId = 0;
if (!empty($courseInfo)) {
$courseId = $courseInfo['real_id'];
$documentPath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document';
}
$is_allowedToEdit =
api_is_allowed_to_edit(null, true) ||
api_is_allowed_to_edit(true) ||
api_is_drh() ||
api_is_student_boss() ||
api_is_session_admin();
$courseCondition = "c_id = $courseId";
$statusCondition = '';
$exercisesFilter = '';
$exercises_where = '';
if ($questionType == 1) {
$TBL_EXERCISES_REL_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
$TBL_EXERCISES_QUESTION = Database::get_course_table(TABLE_QUIZ_QUESTION);
$sqlExercise = "SELECT exercice_id
FROM $TBL_EXERCISES_REL_QUESTION terq
LEFT JOIN $TBL_EXERCISES_QUESTION teq
ON terq.question_id = teq.iid
WHERE teq.type in (".FREE_ANSWER.", ".ORAL_EXPRESSION.", ".ANNOTATION.", ".UPLOAD_ANSWER.")
";
$resultExerciseIds = Database::query($sqlExercise);
$exercises = Database::store_result($resultExerciseIds, 'ASSOC');
$exerciseIds = [];
foreach ($exercises as $exercise) {
$exerciseIds[] = $exercise['exercice_id'];
}
$exercises_where = " AND te.exe_exo_id IN(".implode(',', $exerciseIds).")";
$exercisesFilter = " AND exe_exo_id IN(".implode(',', $exerciseIds).")";
}
if (!empty($status)) {
switch ($status) {
case 2:
// validated
$statusCondition = ' AND revised = 1 ';
break;
case 3:
// not validated
$statusCondition = ' AND revised = 0 ';
break;
}
}
if (false === $searchAllTeacherCourses && true === api_is_teacher()) {
if (empty($courseInfo)) {
return [];
}
} elseif (false === api_is_platform_admin(true, false)) {
$courses = CourseManager::get_courses_list_by_user_id(api_get_user_id(), $showAttemptsInSessions, false, false);
if (empty($courses)) {
return [];
}
$courses = array_column($courses, 'real_id');
$is_allowedToEdit = true;
$courseCondition = "c_id IN ('".implode("', '", $courses)."') ";
}
$exercise_id = (int) $exercise_id;
$TBL_USER = Database::get_main_table(TABLE_MAIN_USER);
$TBL_EXERCICES = Database::get_course_table(TABLE_QUIZ_TEST);
$TBL_GROUP_REL_USER = Database::get_course_table(TABLE_GROUP_USER);
$TBL_GROUP = Database::get_course_table(TABLE_GROUP);
$TBL_TRACK_EXERCICES = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
$TBL_TRACK_HOTPOTATOES = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTPOTATOES);
$TBL_TRACK_ATTEMPT_RECORDING = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT_RECORDING);
$TBL_ACCESS_URL_REL_SESSION = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_SESSION);
$TBL_ACCESS_URL_REL_USER = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
$currentUrl = api_get_current_access_url_id();
$te_access_url_session_filter = " te.session_id in (select session_id from $TBL_ACCESS_URL_REL_SESSION where access_url_id = $currentUrl)";
$te_access_url_user_filter = " te.exe_user_id in (select user_id from $TBL_ACCESS_URL_REL_USER where access_url_id = $currentUrl)";
$session_id_and = '';
$sessionCondition = '';
if (!$showSessionField) {
$session_id_and = " AND te.session_id = $sessionId ";
$sessionCondition = " AND ttte.session_id = $sessionId";
}
if ($searchAllTeacherCourses) {
$session_id_and = " AND te.session_id = 0 ";
$sessionCondition = " AND ttte.session_id = 0";
}
if ($showAttemptsInSessions) {
$sessions = SessionManager::get_sessions_by_general_coach(api_get_user_id());
if (!empty($sessions)) {
$sessionIds = [];
foreach ($sessions as $session) {
$sessionIds[] = $session['id'];
}
$session_id_and = " AND te.session_id IN(".implode(',', $sessionIds).") AND $te_access_url_session_filter";
$sessionCondition = " AND ttte.session_id IN(".implode(',', $sessionIds).")";
} elseif (empty($sessionId) &&
api_get_configuration_value('show_exercise_session_attempts_in_base_course')
) {
$session_id_and = " AND (te.session_id = 0 OR $te_access_url_session_filter)";
$sessionCondition = "";
} else {
return false;
}
} elseif (empty($sessionId) &&
api_get_configuration_value('show_exercise_session_attempts_in_base_course')
) {
$session_id_and = " AND (te.session_id = 0 OR $te_access_url_session_filter)";
$sessionCondition = "";
}
if ((api_is_platform_admin() || true === api_is_session_admin()) && $originPending) {
$session_id_and = " AND (te.session_id = 0 OR $te_access_url_session_filter)";
$sessionCondition = "";
if (false !== $searchAllTeacherCourses) {
$courseCondition = "c_id is not null ";
}
}
$exercise_where = '';
$exerciseFilter = '';
if (!empty($exercise_id)) {
$exercise_where .= ' AND te.exe_exo_id = '.$exercise_id.' ';
$exerciseFilter = " AND exe_exo_id = $exercise_id ";
}
$hotpotatoe_where = '';
if (!empty($_GET['path'])) {
$hotpotatoe_path = Database::escape_string($_GET['path']);
$hotpotatoe_where .= ' AND exe_name = "'.$hotpotatoe_path.'" ';
}
// sql for chamilo-type tests for teacher / tutor view
$sql_inner_join_tbl_track_exercices = "
(
SELECT DISTINCT ttte.*, if(tr.exe_id,1, 0) as revised, tr.author as corrector, MAX(tr.insert_date) as correction_date
FROM $TBL_TRACK_EXERCICES ttte
LEFT JOIN $TBL_TRACK_ATTEMPT_RECORDING tr
ON (ttte.exe_id = tr.exe_id)
WHERE
$courseCondition
$exerciseFilter
$exercisesFilter
$sessionCondition
GROUP BY ttte.exe_id
)";
if ($is_allowedToEdit) {
//@todo fix to work with COURSE_RELATION_TYPE_RRHH in both queries
// Hack in order to filter groups
$sql_inner_join_tbl_user = '';
if (strpos($extra_where_conditions, 'group_id')) {
$sql_inner_join_tbl_user = "
(
SELECT
u.user_id,
firstname,
lastname,
official_code,
email,
username,
g.name as group_name,
g.id as group_id
FROM $TBL_USER u
INNER JOIN $TBL_GROUP_REL_USER gru
ON (gru.user_id = u.user_id AND gru.c_id= $courseId )
INNER JOIN $TBL_GROUP g
ON (gru.group_id = g.id AND g.c_id= $courseId )
)";
}
if (strpos($extra_where_conditions, 'group_all')) {
$extra_where_conditions = str_replace(
"AND ( group_id = 'group_all' )",
'',
$extra_where_conditions
);
$extra_where_conditions = str_replace(
"AND group_id = 'group_all'",
'',
$extra_where_conditions
);
$extra_where_conditions = str_replace(
"group_id = 'group_all' AND",
'',
$extra_where_conditions
);
$sql_inner_join_tbl_user = "
(
SELECT
u.user_id,
firstname,
lastname,
official_code,
email,
username,
'' as group_name,
'' as group_id
FROM $TBL_USER u
)";
$sql_inner_join_tbl_user = null;
}
if (strpos($extra_where_conditions, 'group_none')) {
$extra_where_conditions = str_replace(
"AND ( group_id = 'group_none' )",
"AND ( group_id is null )",
$extra_where_conditions
);
$extra_where_conditions = str_replace(
"AND group_id = 'group_none'",
"AND ( group_id is null )",
$extra_where_conditions
);
$sql_inner_join_tbl_user = "
(
SELECT
u.user_id,
firstname,
lastname,
official_code,
email,
username,
g.name as group_name,
g.id as group_id
FROM $TBL_USER u
LEFT OUTER JOIN $TBL_GROUP_REL_USER gru
ON ( gru.user_id = u.user_id AND gru.c_id = $courseId )
LEFT OUTER JOIN $TBL_GROUP g
ON (gru.group_id = g.id AND g.c_id = $courseId )
)";
}
// All
$is_empty_sql_inner_join_tbl_user = false;
if (empty($sql_inner_join_tbl_user)) {
$is_empty_sql_inner_join_tbl_user = true;
$sql_inner_join_tbl_user = "
(
SELECT u.user_id, firstname, lastname, email, username, ' ' as group_name, '' as group_id, official_code
FROM $TBL_USER u
WHERE u.status NOT IN(".api_get_users_status_ignored_in_reports('string').")
)";
}
$sqlFromOption = '';
$sqlWhereOption = '';
if (false === $searchAllTeacherCourses) {
$sqlFromOption = " , $TBL_GROUP_REL_USER AS gru ";
$sqlWhereOption = " AND gru.c_id = $courseId AND gru.user_id = user.user_id ";
}
$first_and_last_name = api_is_western_name_order() ? "firstname, lastname" : "lastname, firstname";
if ($get_count) {
$sql_select = 'SELECT count(te.exe_id) ';
} else {
$sql_select = "SELECT DISTINCT
user_id,
$first_and_last_name,
official_code,
ce.title,
username,
te.exe_result,
te.exe_weighting,
te.exe_date,
te.exe_id,
te.c_id,
te.session_id,
email as exemail,
te.start_date,
ce.expired_time,
steps_counter,
exe_user_id,
te.exe_duration,
te.status as completion_status,
propagate_neg,
revised,
group_name,
group_id,
orig_lp_id,
te.user_ip,
corrector,
correction_date";
}
$sql = " $sql_select
FROM $TBL_EXERCICES AS ce
INNER JOIN $sql_inner_join_tbl_track_exercices AS te
ON (te.exe_exo_id = ce.iid)
INNER JOIN $sql_inner_join_tbl_user AS user
ON (user.user_id = exe_user_id)
WHERE
te.$courseCondition
$session_id_and AND
$te_access_url_user_filter AND
ce.active <> -1 AND
ce.$courseCondition
$exercise_where
$exercises_where
$extra_where_conditions
$statusCondition
";
// sql for hotpotatoes tests for teacher / tutor view
if ($get_count) {
$hpsql_select = ' SELECT count(username) ';
} else {
$hpsql_select = " SELECT
$first_and_last_name ,
username,
official_code,
tth.exe_name,
tth.exe_result ,
tth.exe_weighting,
tth.exe_date";
}
$hpsql = " $hpsql_select
FROM
$TBL_TRACK_HOTPOTATOES tth,
$TBL_USER user
$sqlFromOption
WHERE
user.user_id=tth.exe_user_id AND
tth.$courseCondition
$hotpotatoe_where
$sqlWhereOption AND
user.status NOT IN (".api_get_users_status_ignored_in_reports('string').")
ORDER BY tth.c_id ASC, tth.exe_date DESC ";
}
if (empty($sql)) {
return false;
}
if ($get_count) {
$resx = Database::query($sql);
$rowx = Database::fetch_row($resx, 'ASSOC');
return $rowx[0];
}
$teacher_id_list = [];
if (!empty($courseCode)) {
$teacher_list = CourseManager::get_teacher_list_from_course_code($courseCode);
if (!empty($teacher_list)) {
foreach ($teacher_list as $teacher) {
$teacher_id_list[] = $teacher['user_id'];
}
}
}
$scoreDisplay = new ScoreDisplay();
$decimalSeparator = '.';
$thousandSeparator = ',';
if ($useCommaAsDecimalPoint) {
$decimalSeparator = ',';
$thousandSeparator = '';
}
$hideIp = api_get_configuration_value('exercise_hide_ip');
$listInfo = [];
// Simple exercises
if (empty($hotpotatoe_where)) {
$column = !empty($column) ? Database::escape_string($column) : null;
$from = (int) $from;
$number_of_items = (int) $number_of_items;
$direction = !in_array(strtolower(trim($direction)), ['asc', 'desc']) ? 'asc' : $direction;
if (!empty($column)) {
$sql .= " ORDER BY `$column` $direction ";
}
if (!$getOnlyIds) {
$sql .= " LIMIT $from, $number_of_items";
}
$results = [];
$resx = Database::query($sql);
while ($rowx = Database::fetch_array($resx, 'ASSOC')) {
$results[] = $rowx;
}
$clean_group_list = [];
$lp_list = [];
if (!empty($courseInfo)) {
$group_list = GroupManager::get_group_list(null, $courseInfo);
if (!empty($group_list)) {
foreach ($group_list as $group) {
$clean_group_list[$group['id']] = $group['name'];
}
}
$lp_list_obj = new LearnpathList(api_get_user_id());
$lp_list = $lp_list_obj->get_flat_list();
$oldIds = array_column($lp_list, 'lp_old_id', 'iid');
}
if (is_array($results)) {
$users_array_id = [];
$from_gradebook = false;
if (isset($_GET['gradebook']) && $_GET['gradebook'] === 'view') {
$from_gradebook = true;
}
$sizeof = count($results);
$locked = api_resource_is_locked_by_gradebook($exercise_id, LINK_EXERCISE);
$timeNow = strtotime(api_get_utc_datetime());
$courseItemList = [];
// Looping results
for ($i = 0; $i < $sizeof; $i++) {
$attempt = $results[$i];
$revised = $attempt['revised'];
$attemptSessionId = (int) $attempt['session_id'];
if (false === $searchAllTeacherCourses) {
$courseItemInfo = api_get_course_info();
$cidReq = api_get_cidreq(false).'&id_session='.$attemptSessionId;
} else {
if (isset($courseItemList[$attempt['c_id']])) {
$courseItemInfo = $courseItemList[$attempt['c_id']];
} else {
$courseItemInfo = api_get_course_info_by_id($attempt['c_id']);
$courseItemList[$attempt['c_id']] = $courseItemInfo;
}
$cidReq = 'cidReq='.$courseItemInfo['code'].'&id_session='.$attemptSessionId;
}
if ('incomplete' === $attempt['completion_status']) {
// If the exercise was incomplete, we need to determine
// if it is still into the time allowed, or if its
// allowed time has expired and it can be closed
// (it's "unclosed")
$minutes = $attempt['expired_time'];
if ($minutes == 0) {
// There's no time limit, so obviously the attempt
// can still be "ongoing", but the teacher should
// be able to choose to close it, so mark it as
// "unclosed" instead of "ongoing"
$revised = 2;
} else {
$allowedSeconds = $minutes * 60;
$timeAttemptStarted = strtotime($attempt['start_date']);
$secondsSinceStart = $timeNow - $timeAttemptStarted;
$revised = 3; // mark as "ongoing"
if ($secondsSinceStart > $allowedSeconds) {
$revised = 2; // mark as "unclosed"
}
}
}
if (4 == $status && 2 != $revised) {
// Filter by status "unclosed"
continue;
}
if (5 == $status && 3 != $revised) {
// Filter by status "ongoing"
continue;
}
if (3 == $status && in_array($revised, [1, 2, 3])) {
// Filter by status "not validated"
continue;
}
if ($from_gradebook && ($is_allowedToEdit)) {
if (in_array(
$attempt['username'].$attempt['firstname'].$attempt['lastname'],
$users_array_id
)) {
continue;
}
$users_array_id[] = $attempt['username'].$attempt['firstname'].$attempt['lastname'];
}
$lp_obj = isset($attempt['orig_lp_id']) &&
isset($lp_list[$attempt['orig_lp_id']]) ? $lp_list[$attempt['orig_lp_id']] : null;
if (empty($lp_obj)) {
// Try to get the old id (id instead of iid)
$lpNewId = isset($attempt['orig_lp_id']) &&
isset($oldIds[$attempt['orig_lp_id']]) ? $oldIds[$attempt['orig_lp_id']] : null;
if ($lpNewId) {
$lp_obj = isset($lp_list[$lpNewId]) ? $lp_list[$lpNewId] : null;
}
}
$lp_name = null;
if ($lp_obj) {
$url = api_get_path(WEB_CODE_PATH).
'lp/lp_controller.php?'.$cidReq.'&action=view&lp_id='.$attempt['orig_lp_id'];
$lp_name = Display::url(
$lp_obj['lp_name'],
$url,
['target' => '_blank']
);
}
// Add all groups by user
$group_name_list = '';
if ($is_empty_sql_inner_join_tbl_user) {
$group_list = GroupManager::get_group_ids(
api_get_course_int_id(),
$attempt['user_id']
);
foreach ($group_list as $id) {
if (isset($clean_group_list[$id])) {
$group_name_list .= $clean_group_list[$id].'
';
}
}
$attempt['group_name'] = $group_name_list;
}
$attempt['exe_duration'] = !empty($attempt['exe_duration']) ? round($attempt['exe_duration'] / 60) : 0;
$id = $attempt['exe_id'];
$dt = api_convert_and_format_date($attempt['exe_weighting']);
// we filter the results if we have the permission to
$result_disabled = 0;
if (isset($attempt['results_disabled'])) {
$result_disabled = (int) $attempt['results_disabled'];
}
if ($result_disabled == 0) {
$my_res = $attempt['exe_result'];
$my_total = $attempt['exe_weighting'];
$attempt['start_date'] = api_get_local_time($attempt['start_date']);
$attempt['exe_date'] = api_get_local_time($attempt['exe_date']);
if (!$attempt['propagate_neg'] && $my_res < 0) {
$my_res = 0;
}
$score = self::show_score(
$my_res,
$my_total,
true,
true,
false,
false,
$decimalSeparator,
$thousandSeparator,
$roundValues
);
$actions = '
';
if ($is_allowedToEdit) {
if (isset($teacher_id_list)) {
if (in_array(
$attempt['exe_user_id'],
$teacher_id_list
)) {
$actions .= Display::return_icon('teacher.png', get_lang('Teacher'));
}
}
$revisedLabel = '';
switch ($revised) {
case 0:
$actions .= "
".
Display::return_icon(
'quiz.png',
get_lang('Qualify')
);
$actions .= ' ';
$revisedLabel = Display::label(
get_lang('NotValidated'),
'info'
);
break;
case 1:
$actions .= "
".
Display::return_icon(
'edit.png',
get_lang('Edit'),
[],
ICON_SIZE_SMALL
);
$actions .= ' ';
$revisedLabel = Display::label(
get_lang('Validated'),
'success'
);
break;
case 2: //finished but not marked as such
$actions .= '
'.
Display::return_icon(
'lock.png',
get_lang('MarkAttemptAsClosed'),
[],
ICON_SIZE_SMALL
);
$actions .= ' ';
$revisedLabel = Display::label(
get_lang('Unclosed'),
'warning'
);
break;
case 3: //still ongoing
$actions .= Display::return_icon(
'clock.png',
get_lang('AttemptStillOngoingPleaseWait'),
[],
ICON_SIZE_SMALL
);
$actions .= '';
$revisedLabel = Display::label(
get_lang('Ongoing'),
'danger'
);
break;
}
if ($filter == 2) {
$actions .= '
'.
Display::return_icon(
'history.png',
get_lang('ViewHistoryChange')
).' ';
}
// Admin can always delete the attempt
if (($locked == false || api_is_platform_admin()) && !api_is_student_boss()) {
$ip = Tracking::get_ip_from_user_event(
$attempt['exe_user_id'],
api_get_utc_datetime(),
false
);
$actions .= '
'
.Display::return_icon('info.png', $ip)
.' ';
$recalculateUrl = api_get_path(WEB_CODE_PATH).'exercise/recalculate.php?'.
$cidReq.'&'.
http_build_query([
'id' => $id,
'exercise' => $exercise_id,
'user' => $attempt['exe_user_id'],
]);
$actions .= Display::url(
Display::return_icon('reload.png', get_lang('RecalculateResults')),
$recalculateUrl,
[
'data-exercise' => $exercise_id,
'data-user' => $attempt['exe_user_id'],
'data-id' => $id,
'class' => 'exercise-recalculate',
]
);
$filterByUser = isset($_GET['filter_by_user']) ? (int) $_GET['filter_by_user'] : 0;
$delete_link = '
';
$delete_link .= Display::return_icon(
'delete.png',
addslashes(get_lang('Delete'))
).' ';
if (api_is_drh() && !api_is_platform_admin()) {
$delete_link = null;
}
if (api_is_session_admin()) {
$delete_link = '';
}
if ($revised == 3) {
$delete_link = null;
}
$actions .= $delete_link;
}
} else {
$attempt_url = api_get_path(WEB_CODE_PATH).'exercise/result.php?'.$cidReq.'&id='.$attempt['exe_id'];
$attempt_link = Display::url(
get_lang('Show'),
$attempt_url,
[
'class' => 'ajax btn btn-default',
'data-title' => get_lang('Show'),
]
);
$actions .= $attempt_link;
}
$actions .= '
';
if (!empty($userExtraFieldsToAdd)) {
foreach ($userExtraFieldsToAdd as $variable) {
$extraFieldValue = new ExtraFieldValue('user');
$values = $extraFieldValue->get_values_by_handler_and_field_variable(
$attempt['user_id'],
$variable
);
if (isset($values['value'])) {
$attempt[$variable] = $values['value'];
}
}
}
$exeId = $attempt['exe_id'];
$attempt['id'] = $exeId;
$category_list = [];
if ($is_allowedToEdit) {
$sessionName = '';
$sessionStartAccessDate = '';
if (!empty($attemptSessionId)) {
$sessionInfo = api_get_session_info($attemptSessionId);
if (!empty($sessionInfo)) {
$sessionName = $sessionInfo['name'];
$sessionStartAccessDate = api_get_local_time($sessionInfo['access_start_date']);
}
}
$courseId = $courseItemInfo['real_id'];
if ($searchAllTeacherCourses) {
$attempt['course'] = $courseItemInfo['title'];
$attempt['exercise'] = $attempt['title'];
}
$objExercise = new Exercise($courseId);
if ($showExerciseCategories) {
// Getting attempt info
$exercise_stat_info = $objExercise->get_stat_track_exercise_info_by_exe_id($exeId);
if (!empty($exercise_stat_info['data_tracking'])) {
$question_list = explode(',', $exercise_stat_info['data_tracking']);
if (!empty($question_list)) {
foreach ($question_list as $questionId) {
$objQuestionTmp = Question::read($questionId, $objExercise->course);
// We're inside *one* question.
// Go through each possible answer for this question.
$result = $objExercise->manage_answer(
$exeId,
$questionId,
null,
'exercise_result',
false,
false,
true,
false,
$objExercise->selectPropagateNeg(),
null,
true
);
$my_total_score = $result['score'];
$my_total_weight = $result['weight'];
// Category report
$category_was_added_for_this_test = false;
if (isset($objQuestionTmp->category) && !empty($objQuestionTmp->category)) {
if (!isset($category_list[$objQuestionTmp->category]['score'])) {
$category_list[$objQuestionTmp->category]['score'] = 0;
}
if (!isset($category_list[$objQuestionTmp->category]['total'])) {
$category_list[$objQuestionTmp->category]['total'] = 0;
}
$category_list[$objQuestionTmp->category]['score'] += $my_total_score;
$category_list[$objQuestionTmp->category]['total'] += $my_total_weight;
$category_was_added_for_this_test = true;
}
if (isset($objQuestionTmp->category_list) &&
!empty($objQuestionTmp->category_list)
) {
foreach ($objQuestionTmp->category_list as $category_id) {
$category_list[$category_id]['score'] += $my_total_score;
$category_list[$category_id]['total'] += $my_total_weight;
$category_was_added_for_this_test = true;
}
}
// No category for this question!
if ($category_was_added_for_this_test == false) {
if (!isset($category_list['none']['score'])) {
$category_list['none']['score'] = 0;
}
if (!isset($category_list['none']['total'])) {
$category_list['none']['total'] = 0;
}
$category_list['none']['score'] += $my_total_score;
$category_list['none']['total'] += $my_total_weight;
}
}
}
}
}
foreach ($category_list as $categoryId => $result) {
$scoreToDisplay = self::show_score(
$result['score'],
$result['total'],
true,
true,
false,
false,
$decimalSeparator,
$thousandSeparator,
$roundValues
);
$attempt['category_'.$categoryId] = $scoreToDisplay;
$attempt['category_'.$categoryId.'_score_percentage'] = self::show_score(
$result['score'],
$result['total'],
true,
true,
true,
true,
$decimalSeparator,
$thousandSeparator,
$roundValues
);
$attempt['category_'.$categoryId.'_only_score'] = $result['score'];
$attempt['category_'.$categoryId.'_total'] = $result['total'];
}
$attempt['session'] = $sessionName;
$attempt['session_access_start_date'] = $sessionStartAccessDate;
$attempt['status'] = $revisedLabel;
$attempt['score'] = $score;
$attempt['qualificator_fullname'] = '';
$attempt['date_of_qualification'] = '';
if (!empty($attempt['corrector'])) {
$qualificatorAuthor = api_get_user_info($attempt['corrector']);
$attempt['qualificator_fullname'] = api_get_person_name($qualificatorAuthor['firstname'], $qualificatorAuthor['lastname']);
}
if (!empty($attempt['correction_date'])) {
$attempt['date_of_qualification'] = api_convert_and_format_date($attempt['correction_date'], DATE_TIME_FORMAT_SHORT);
}
$attempt['score_percentage'] = self::show_score(
$my_res,
$my_total,
true,
true,
true,
true,
$decimalSeparator,
$thousandSeparator,
$roundValues
);
if ($roundValues) {
$whole = floor($my_res); // 1
$fraction = $my_res - $whole; // .25
if ($fraction >= 0.5) {
$onlyScore = ceil($my_res);
} else {
$onlyScore = round($my_res);
}
} else {
$onlyScore = $scoreDisplay->format_score(
$my_res,
false,
$decimalSeparator,
$thousandSeparator
);
}
$attempt['only_score'] = $onlyScore;
if ($roundValues) {
$whole = floor($my_total); // 1
$fraction = $my_total - $whole; // .25
if ($fraction >= 0.5) {
$onlyTotal = ceil($my_total);
} else {
$onlyTotal = round($my_total);
}
} else {
$onlyTotal = $scoreDisplay->format_score(
$my_total,
false,
$decimalSeparator,
$thousandSeparator
);
}
$attempt['total'] = $onlyTotal;
$attempt['lp'] = $lp_name;
$attempt['actions'] = $actions;
if ($hideIp && isset($attempt['user_ip'])) {
unset($attempt['user_ip']);
}
$listInfo[] = $attempt;
} else {
$attempt['status'] = $revisedLabel;
$attempt['score'] = $score;
$attempt['actions'] = $actions;
if ($hideIp && isset($attempt['user_ip'])) {
unset($attempt['user_ip']);
}
$listInfo[] = $attempt;
}
}
}
}
} else {
$hpresults = [];
$res = Database::query($hpsql);
if ($res !== false) {
$i = 0;
while ($resA = Database::fetch_array($res, 'NUM')) {
for ($j = 0; $j < 6; $j++) {
$hpresults[$i][$j] = $resA[$j];
}
$i++;
}
}
// Print HotPotatoes test results.
if (is_array($hpresults)) {
for ($i = 0; $i < count($hpresults); $i++) {
$hp_title = GetQuizName($hpresults[$i][3], $documentPath);
if ($hp_title == '') {
$hp_title = basename($hpresults[$i][3]);
}
$hp_date = api_get_local_time(
$hpresults[$i][6],
null,
date_default_timezone_get()
);
$hp_result = round(($hpresults[$i][4] / ($hpresults[$i][5] != 0 ? $hpresults[$i][5] : 1)) * 100, 2);
$hp_result .= '% ('.$hpresults[$i][4].' / '.$hpresults[$i][5].')';
if ($is_allowedToEdit) {
$listInfo[] = [
$hpresults[$i][0],
$hpresults[$i][1],
$hpresults[$i][2],
'',
$hp_title,
'-',
$hp_date,
$hp_result,
'-',
];
} else {
$listInfo[] = [
$hp_title,
'-',
$hp_date,
$hp_result,
'-',
];
}
}
}
}
return $listInfo;
}
/**
* @param $score
* @param $weight
*
* @return array
*/
public static function convertScoreToPlatformSetting($score, $weight)
{
$maxNote = api_get_setting('exercise_max_score');
$minNote = api_get_setting('exercise_min_score');
if ($maxNote != '' && $minNote != '') {
if (!empty($weight) && (float) $weight !== (float) 0) {
$score = $minNote + ($maxNote - $minNote) * $score / $weight;
} else {
$score = $minNote;
}
$weight = $maxNote;
}
return ['score' => $score, 'weight' => $weight];
}
/**
* Converts the score with the exercise_max_note and exercise_min_score
* the platform settings + formats the results using the float_format function.
*
* @param float $score
* @param float $weight
* @param bool $show_percentage show percentage or not
* @param bool $use_platform_settings use or not the platform settings
* @param bool $show_only_percentage
* @param bool $hidePercentageSign hide "%" sign
* @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
*/
public static function show_score(
$score,
$weight,
$show_percentage = true,
$use_platform_settings = true,
$show_only_percentage = false,
$hidePercentageSign = false,
$decimalSeparator = '.',
$thousandSeparator = ',',
$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);
$score = $result['score'];
$weight = $result['weight'];
}
$percentage = (100 * $score) / ($weight != 0 ? $weight : 1);
// Formats values
$percentage = float_format($percentage, 1);
$score = float_format($score, 1);
$weight = float_format($weight, 1);
if ($roundValues) {
$whole = floor($percentage); // 1
$fraction = $percentage - $whole; // .25
// Formats values
if ($fraction >= 0.5) {
$percentage = ceil($percentage);
} else {
$percentage = round($percentage);
}
$whole = floor($score); // 1
$fraction = $score - $whole; // .25
if ($fraction >= 0.5) {
$score = ceil($score);
} else {
$score = round($score);
}
$whole = floor($weight); // 1
$fraction = $weight - $whole; // .25
if ($fraction >= 0.5) {
$weight = ceil($weight);
} else {
$weight = round($weight);
}
} else {
// Formats values
$percentage = float_format($percentage, 1, $decimalSeparator, $thousandSeparator);
$score = float_format($score, 1, $decimalSeparator, $thousandSeparator);
$weight = float_format($weight, 1, $decimalSeparator, $thousandSeparator);
}
if ($show_percentage) {
$percentageSign = ' %';
if ($hidePercentageSign) {
$percentageSign = '';
}
$html = $percentage."$percentageSign ($score / $weight)";
if ($show_only_percentage) {
$html = $percentage.$percentageSign;
}
} else {
if ($removeEmptyDecimals) {
if (ScoreDisplay::hasEmptyDecimals($weight)) {
$weight = round($weight);
}
}
$html = $score.' / '.$weight;
}
// Over write score
$scoreBasedInModel = self::convertScoreToModel($percentage);
if (!empty($scoreBasedInModel)) {
$html = $scoreBasedInModel;
}
// Ignore other formats and use the configuration['exercise_score_format'] value
// But also keep the round values settings.
$format = api_get_configuration_value('exercise_score_format');
if (!empty($format)) {
$html = ScoreDisplay::instance()->display_score([$score, $weight], $format);
}
return Display::span($html, ['class' => 'score_exercise']);
}
/**
* @param array $model
* @param float $percentage
*
* @return string
*/
public static function getModelStyle($model, $percentage)
{
return '
';
}
/**
* @param float $percentage value between 0 and 100
*
* @return string
*/
public static function convertScoreToModel($percentage)
{
$model = self::getCourseScoreModel();
if (!empty($model)) {
$scoreWithGrade = [];
foreach ($model['score_list'] as $item) {
if ($percentage >= $item['min'] && $percentage <= $item['max']) {
$scoreWithGrade = $item;
break;
}
}
if (!empty($scoreWithGrade)) {
return self::getModelStyle($scoreWithGrade, $percentage);
}
}
return '';
}
/**
* @return array
*/
public static function getCourseScoreModel()
{
$modelList = self::getScoreModels();
if (empty($modelList)) {
return [];
}
$courseInfo = api_get_course_info();
if (!empty($courseInfo)) {
$scoreModelId = api_get_course_setting('score_model_id');
if (-1 != $scoreModelId) {
$modelIdList = array_column($modelList['models'], 'id');
if (in_array($scoreModelId, $modelIdList)) {
foreach ($modelList['models'] as $item) {
if ($item['id'] == $scoreModelId) {
return $item;
}
}
}
}
}
return [];
}
/**
* @return array
*/
public static function getScoreModels()
{
return api_get_configuration_value('score_grade_model');
}
/**
* @param float $score
* @param float $weight
* @param string $passPercentage
*
* @return bool
*/
public static function isSuccessExerciseResult($score, $weight, $passPercentage)
{
$percentage = float_format(
($score / (0 != $weight ? $weight : 1)) * 100,
1
);
if (isset($passPercentage) && !empty($passPercentage)) {
if ($percentage >= $passPercentage) {
return true;
}
}
return false;
}
/**
* @param string $name
* @param $weight
* @param $selected
*
* @return bool
*/
public static function addScoreModelInput(
FormValidator $form,
$name,
$weight,
$selected
) {
$model = self::getCourseScoreModel();
if (empty($model)) {
return false;
}
/** @var HTML_QuickForm_select $element */
$element = $form->createElement(
'select',
$name,
get_lang('Qualification'),
[],
['class' => 'exercise_mark_select']
);
foreach ($model['score_list'] as $item) {
$i = api_number_format($item['score_to_qualify'] / 100 * $weight, 2);
$label = self::getModelStyle($item, $i);
$attributes = [
'class' => $item['css_class'],
];
if ($selected == $i) {
$attributes['selected'] = 'selected';
}
$element->addOption($label, $i, $attributes);
}
$form->addElement($element);
}
/**
* @return string
*/
public static function getJsCode()
{
// Filling the scores with the right colors.
$models = self::getCourseScoreModel();
$cssListToString = '';
if (!empty($models)) {
$cssList = array_column($models['score_list'], 'css_class');
$cssListToString = implode(' ', $cssList);
}
if (empty($cssListToString)) {
return '';
}
$js = <<
'width:40px; padding:2px 10px 0px 0px']
);
$res = $html;
}
return $res;
}
/**
* Return true if pass_pourcentage activated (we use the pass pourcentage feature
* return false if pass_percentage = 0 (we don't use the pass pourcentage feature.
*
* @param $value
*
* @return bool
* In this version, pass_percentage and show_success_message are disabled if
* pass_percentage is set to 0
*/
public static function isPassPercentageEnabled($value)
{
return $value > 0;
}
/**
* Converts a numeric value in a percentage example 0.66666 to 66.67 %.
*
* @param $value
*
* @return float Converted number
*/
public static function convert_to_percentage($value)
{
$return = '-';
if ($value != '') {
$return = float_format($value * 100, 1).' %';
}
return $return;
}
/**
* Getting all active exercises from a course from a session
* (if a session_id is provided we will show all the exercises in the course +
* all exercises in the session).
*
* @param array $course_info
* @param int $session_id
* @param bool $check_publication_dates
* @param string $search Search exercise name
* @param bool $search_all_sessions Search exercises in all sessions
* @param int 0 = only inactive exercises
* 1 = only active exercises,
* 2 = all exercises
* 3 = active <> -1
*
* @return array array with exercise data
*/
public static function get_all_exercises(
$course_info = null,
$session_id = 0,
$check_publication_dates = false,
$search = '',
$search_all_sessions = false,
$active = 2
) {
$course_id = api_get_course_int_id();
if (!empty($course_info) && !empty($course_info['real_id'])) {
$course_id = $course_info['real_id'];
}
if ($session_id == -1) {
$session_id = 0;
}
$now = api_get_utc_datetime();
$timeConditions = '';
if ($check_publication_dates) {
// Start and end are set
$timeConditions = " AND ((start_time <> '' AND start_time < '$now' AND end_time <> '' AND end_time > '$now' ) OR ";
// only start is set
$timeConditions .= " (start_time <> '' AND start_time < '$now' AND end_time is NULL) OR ";
// only end is set
$timeConditions .= " (start_time IS NULL AND end_time <> '' AND end_time > '$now') OR ";
// nothing is set
$timeConditions .= ' (start_time IS NULL AND end_time IS NULL)) ';
}
$needle_where = !empty($search) ? " AND title LIKE '?' " : '';
$needle = !empty($search) ? "%".$search."%" : '';
// Show courses by active status
$active_sql = '';
if ($active == 3) {
$active_sql = ' active <> -1 AND';
} else {
if ($active != 2) {
$active_sql = sprintf(' active = %d AND', $active);
}
}
if ($search_all_sessions == true) {
$conditions = [
'where' => [
$active_sql.' c_id = ? '.$needle_where.$timeConditions => [
$course_id,
$needle,
],
],
'order' => 'title',
];
} else {
if (empty($session_id)) {
$conditions = [
'where' => [
$active_sql.' (session_id = 0 OR session_id IS NULL) AND c_id = ? '.$needle_where.$timeConditions => [
$course_id,
$needle,
],
],
'order' => 'title',
];
} else {
$conditions = [
'where' => [
$active_sql.' (session_id = 0 OR session_id IS NULL OR session_id = ? ) AND c_id = ? '.$needle_where.$timeConditions => [
$session_id,
$course_id,
$needle,
],
],
'order' => 'title',
];
}
}
$table = Database::get_course_table(TABLE_QUIZ_TEST);
return Database::select('*', $table, $conditions);
}
/**
* Getting all exercises (active only or all)
* from a course from a session
* (if a session_id is provided we will show all the exercises in the
* course + all exercises in the session).
*
* @param array course data
* @param int session id
* @param int course c_id
* @param bool $only_active_exercises
*
* @return array array with exercise data
* modified by Hubert Borderiou
*/
public static function get_all_exercises_for_course_id(
$course_info = null,
$session_id = 0,
$course_id = 0,
$only_active_exercises = true
) {
$table = Database::get_course_table(TABLE_QUIZ_TEST);
if ($only_active_exercises) {
// Only active exercises.
$sql_active_exercises = "active = 1 AND ";
} else {
// Not only active means visible and invisible NOT deleted (-2)
$sql_active_exercises = "active IN (1, 0) AND ";
}
if ($session_id == -1) {
$session_id = 0;
}
$params = [
$session_id,
$course_id,
];
if (empty($session_id)) {
$conditions = [
'where' => ["$sql_active_exercises (session_id = 0 OR session_id IS NULL) AND c_id = ?" => [$course_id]],
'order' => 'title',
];
} else {
// All exercises
$conditions = [
'where' => ["$sql_active_exercises (session_id = 0 OR session_id IS NULL OR session_id = ? ) AND c_id = ?" => $params],
'order' => 'title',
];
}
return Database::select('*', $table, $conditions);
}
/**
* Gets the position of the score based in a given score (result/weight)
* and the exe_id based in the user list
* (NO Exercises in LPs ).
*
* @param float $my_score user score to be compared *attention*
* $my_score = score/weight and not just the score
* @param int $my_exe_id exe id of the exercise
* (this is necessary because if 2 students have the same score the one
* with the minor exe_id will have a best position, just to be fair and FIFO)
* @param int $exercise_id
* @param string $course_code
* @param int $session_id
* @param array $user_list
* @param bool $return_string
*
* @return int the position of the user between his friends in a course
* (or course within a session)
*/
public static function get_exercise_result_ranking(
$my_score,
$my_exe_id,
$exercise_id,
$course_code,
$session_id = 0,
$user_list = [],
$return_string = true,
$skipLpResults = true
) {
//No score given we return
if (is_null($my_score)) {
return '-';
}
if (empty($user_list)) {
return '-';
}
$best_attempts = [];
foreach ($user_list as $user_data) {
$user_id = $user_data['user_id'];
$best_attempts[$user_id] = self::get_best_attempt_by_user(
$user_id,
$exercise_id,
$course_code,
$session_id,
$skipLpResults
);
}
if (empty($best_attempts)) {
return 1;
} else {
$position = 1;
$my_ranking = [];
foreach ($best_attempts as $user_id => $result) {
if (!empty($result['exe_weighting']) && intval($result['exe_weighting']) != 0) {
$my_ranking[$user_id] = $result['exe_result'] / $result['exe_weighting'];
} else {
$my_ranking[$user_id] = 0;
}
}
//if (!empty($my_ranking)) {
asort($my_ranking);
$position = count($my_ranking);
if (!empty($my_ranking)) {
foreach ($my_ranking as $user_id => $ranking) {
if ($my_score >= $ranking) {
if ($my_score == $ranking && isset($best_attempts[$user_id]['exe_id'])) {
$exe_id = $best_attempts[$user_id]['exe_id'];
if ($my_exe_id < $exe_id) {
$position--;
}
} else {
$position--;
}
}
}
}
//}
$return_value = [
'position' => $position,
'count' => count($my_ranking),
];
if ($return_string) {
if (!empty($position) && !empty($my_ranking)) {
$return_value = $position.'/'.count($my_ranking);
} else {
$return_value = '-';
}
}
return $return_value;
}
}
/**
* Gets the position of the score based in a given score (result/weight) and the exe_id based in all attempts
* (NO Exercises in LPs ) old functionality by attempt.
*
* @param float user score to be compared attention => score/weight
* @param int exe id of the exercise
* (this is necessary because if 2 students have the same score the one
* with the minor exe_id will have a best position, just to be fair and FIFO)
* @param int exercise id
* @param string course code
* @param int session id
* @param bool $return_string
*
* @return int the position of the user between his friends in a course (or course within a session)
*/
public static function get_exercise_result_ranking_by_attempt(
$my_score,
$my_exe_id,
$exercise_id,
$courseId,
$session_id = 0,
$return_string = true
) {
if (empty($session_id)) {
$session_id = 0;
}
if (is_null($my_score)) {
return '-';
}
$user_results = Event::get_all_exercise_results(
$exercise_id,
$courseId,
$session_id,
false
);
$position_data = [];
if (empty($user_results)) {
return 1;
} else {
$position = 1;
$my_ranking = [];
foreach ($user_results as $result) {
if (!empty($result['exe_weighting']) && intval($result['exe_weighting']) != 0) {
$my_ranking[$result['exe_id']] = $result['exe_result'] / $result['exe_weighting'];
} else {
$my_ranking[$result['exe_id']] = 0;
}
}
asort($my_ranking);
$position = count($my_ranking);
if (!empty($my_ranking)) {
foreach ($my_ranking as $exe_id => $ranking) {
if ($my_score >= $ranking) {
if ($my_score == $ranking) {
if ($my_exe_id < $exe_id) {
$position--;
}
} else {
$position--;
}
}
}
}
$return_value = [
'position' => $position,
'count' => count($my_ranking),
];
if ($return_string) {
if (!empty($position) && !empty($my_ranking)) {
return $position.'/'.count($my_ranking);
}
}
return $return_value;
}
}
/**
* Get the best attempt in a exercise (NO Exercises in LPs ).
*
* @param int $exercise_id
* @param int $courseId
* @param int $session_id
*
* @return array
*/
public static function get_best_attempt_in_course($exercise_id, $courseId, $session_id, $skipLpResults = true)
{
$user_results = Event::get_all_exercise_results(
$exercise_id,
$courseId,
$session_id,
false,
null,
0,
$skipLpResults
);
$best_score_data = [];
$best_score = 0;
if (!empty($user_results)) {
foreach ($user_results as $result) {
if (!empty($result['exe_weighting']) &&
intval($result['exe_weighting']) != 0
) {
$score = $result['exe_result'] / $result['exe_weighting'];
if ($score >= $best_score) {
$best_score = $score;
$best_score_data = $result;
}
}
}
}
return $best_score_data;
}
/**
* Get the best score in a exercise (NO Exercises in LPs ).
*
* @param int $user_id
* @param int $exercise_id
* @param int $courseId
* @param int $session_id
*
* @return array
*/
public static function get_best_attempt_by_user(
$user_id,
$exercise_id,
$courseId,
$session_id,
$skipLpResults = true
) {
$user_results = Event::get_all_exercise_results(
$exercise_id,
$courseId,
$session_id,
false,
$user_id,
0,
$skipLpResults
);
$best_score_data = [];
$best_score = 0;
if (!empty($user_results)) {
foreach ($user_results as $result) {
if (!empty($result['exe_weighting']) && (float) $result['exe_weighting'] != 0) {
$score = $result['exe_result'] / $result['exe_weighting'];
if ($score >= $best_score) {
$best_score = $score;
$best_score_data = $result;
}
}
}
}
return $best_score_data;
}
/**
* Get average score (NO Exercises in LPs ).
*
* @param int $exerciseId
* @param int $courseId
* @param int $sessionId
*
* @return float Average score
*/
public static function get_average_score($exerciseId, $courseId, $sessionId, $groupId = 0)
{
$user_results = Event::get_all_exercise_results(
$exerciseId,
$courseId,
$sessionId,
true,
null,
$groupId
);
$avg_score = 0;
if (!empty($user_results)) {
foreach ($user_results as $result) {
if (!empty($result['exe_weighting']) && intval($result['exe_weighting']) != 0) {
$score = $result['exe_result'] / $result['exe_weighting'];
$avg_score += $score;
}
}
$avg_score = float_format($avg_score / count($user_results), 1);
}
return $avg_score;
}
/**
* Get average quiz score by course (Only exercises not added in a LP).
*
* @param int $courseId
* @param int $sessionId
*
* @return float Average score
*/
public static function get_average_score_by_course($courseId, $sessionId)
{
$user_results = Event::get_all_exercise_results_by_course(
$courseId,
$sessionId,
false
);
$avg_score = 0;
if (!empty($user_results)) {
foreach ($user_results as $result) {
if (!empty($result['exe_weighting']) && intval(
$result['exe_weighting']
) != 0
) {
$score = $result['exe_result'] / $result['exe_weighting'];
$avg_score += $score;
}
}
// We assume that all exe_weighting
$avg_score = $avg_score / count($user_results);
}
return $avg_score;
}
/**
* @param int $user_id
* @param int $courseId
* @param int $session_id
*
* @return float|int
*/
public static function get_average_score_by_course_by_user(
$user_id,
$courseId,
$session_id
) {
$user_results = Event::get_all_exercise_results_by_user(
$user_id,
$courseId,
$session_id
);
$avg_score = 0;
if (!empty($user_results)) {
foreach ($user_results as $result) {
if (!empty($result['exe_weighting']) && intval($result['exe_weighting']) != 0) {
$score = $result['exe_result'] / $result['exe_weighting'];
$avg_score += $score;
}
}
// We assume that all exe_weighting
$avg_score = ($avg_score / count($user_results));
}
return $avg_score;
}
/**
* Get average score by score (NO Exercises in LPs ).
*
* @param int $exercise_id
* @param int $courseId
* @param int $session_id
* @param int $user_count
*
* @return float Best average score
*/
public static function get_best_average_score_by_exercise(
$exercise_id,
$courseId,
$session_id,
$user_count
) {
$user_results = Event::get_best_exercise_results_by_user(
$exercise_id,
$courseId,
$session_id
);
$avg_score = 0;
if (!empty($user_results)) {
foreach ($user_results as $result) {
if (!empty($result['exe_weighting']) && intval($result['exe_weighting']) != 0) {
$score = $result['exe_result'] / $result['exe_weighting'];
$avg_score += $score;
}
}
// We asumme that all exe_weighting
if (!empty($user_count)) {
$avg_score = float_format($avg_score / $user_count, 1) * 100;
} else {
$avg_score = 0;
}
}
return $avg_score;
}
/**
* Get average score by score (NO Exercises in LPs ).
*
* @param int $exercise_id
* @param int $courseId
* @param int $session_id
*
* @return float Best average score
*/
public static function getBestScoreByExercise(
$exercise_id,
$courseId,
$session_id
) {
$user_results = Event::get_best_exercise_results_by_user(
$exercise_id,
$courseId,
$session_id
);
$avg_score = 0;
if (!empty($user_results)) {
foreach ($user_results as $result) {
if (!empty($result['exe_weighting']) && intval($result['exe_weighting']) != 0) {
$score = $result['exe_result'] / $result['exe_weighting'];
$avg_score += $score;
}
}
}
return $avg_score;
}
/**
* @param string $course_code
* @param int $session_id
*
* @return array
*/
public static function get_exercises_to_be_taken($course_code, $session_id)
{
$course_info = api_get_course_info($course_code);
$exercises = self::get_all_exercises($course_info, $session_id);
$result = [];
$now = time() + 15 * 24 * 60 * 60;
foreach ($exercises as $exercise_item) {
if (isset($exercise_item['end_time']) &&
!empty($exercise_item['end_time']) &&
api_strtotime($exercise_item['end_time'], 'UTC') < $now
) {
$result[] = $exercise_item;
}
}
return $result;
}
/**
* Get student results (only in completed exercises) stats by question.
*
* @param int $question_id
* @param int $exercise_id
* @param int $courseId
* @param int $session_id
* @param bool $onlyStudent Filter only enrolled students
*
* @return array
*/
public static function get_student_stats_by_question(
$question_id,
$exercise_id,
$courseId,
$session_id,
$onlyStudent = false
) {
$track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
$track_attempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
$courseUser = Database::get_main_table(TABLE_MAIN_COURSE_USER);
$question_id = (int) $question_id;
$exercise_id = (int) $exercise_id;
$session_id = (int) $session_id;
$courseId = (int) $courseId;
$sql = "SELECT MAX(marks) as max, MIN(marks) as min, AVG(marks) as average
FROM $track_exercises e ";
if ($onlyStudent) {
if (empty($session_id)) {
$courseCondition = "
INNER JOIN $courseUser c
ON (
e.exe_user_id = c.user_id AND
e.c_id = c.c_id AND
c.status = ".STUDENT."
AND relation_type <> 2
)";
} else {
$sessionRelCourse = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
$courseCondition = "
INNER JOIN $sessionRelCourse sc
ON (
e.exe_user_id = sc.user_id AND
e.c_id = sc.c_id AND
e.session_id = sc.session_id AND
sc.status = 0
) ";
}
$sql .= $courseCondition;
}
$sql .= "
INNER JOIN $track_attempt a
ON (
a.exe_id = e.exe_id AND
e.c_id = a.c_id AND
e.session_id = a.session_id
)
WHERE
exe_exo_id = $exercise_id AND
a.c_id = $courseId AND
e.session_id = $session_id AND
question_id = $question_id AND
e.status = ''
LIMIT 1";
$result = Database::query($sql);
$return = [];
if ($result) {
$return = Database::fetch_array($result, 'ASSOC');
}
return $return;
}
/**
* Get the correct answer count for a fill blanks question.
*
* @param int $question_id
* @param int $exercise_id
*
* @return array
*/
public static function getNumberStudentsFillBlanksAnswerCount(
$question_id,
$exercise_id
) {
$listStudentsId = [];
$listAllStudentInfo = CourseManager::get_student_list_from_course_code(
api_get_course_id(),
true
);
foreach ($listAllStudentInfo as $i => $listStudentInfo) {
$listStudentsId[] = $listStudentInfo['user_id'];
}
$listFillTheBlankResult = FillBlanks::getFillTheBlankResult(
$exercise_id,
$question_id,
$listStudentsId,
'1970-01-01',
'3000-01-01'
);
$arrayCount = [];
foreach ($listFillTheBlankResult as $resultCount) {
foreach ($resultCount as $index => $count) {
//this is only for declare the array index per answer
$arrayCount[$index] = 0;
}
}
foreach ($listFillTheBlankResult as $resultCount) {
foreach ($resultCount as $index => $count) {
$count = ($count === 0) ? 1 : 0;
$arrayCount[$index] += $count;
}
}
return $arrayCount;
}
/**
* Get the number of questions with answers.
*
* @param int $question_id
* @param int $exercise_id
* @param string $course_code
* @param int $session_id
* @param string $questionType
*
* @return int
*/
public static function get_number_students_question_with_answer_count(
$question_id,
$exercise_id,
$course_code,
$session_id,
$questionType = ''
) {
$track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
$track_attempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
$courseUser = Database::get_main_table(TABLE_MAIN_COURSE_USER);
$courseTable = Database::get_main_table(TABLE_MAIN_COURSE);
$courseUserSession = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
$question_id = intval($question_id);
$exercise_id = intval($exercise_id);
$courseId = api_get_course_int_id($course_code);
$session_id = intval($session_id);
if (in_array($questionType, [FILL_IN_BLANKS, FILL_IN_BLANKS_COMBINATION])) {
$listStudentsId = [];
$listAllStudentInfo = CourseManager::get_student_list_from_course_code(
api_get_course_id(),
true
);
foreach ($listAllStudentInfo as $i => $listStudentInfo) {
$listStudentsId[] = $listStudentInfo['user_id'];
}
$listFillTheBlankResult = FillBlanks::getFillTheBlankResult(
$exercise_id,
$question_id,
$listStudentsId,
'1970-01-01',
'3000-01-01'
);
return FillBlanks::getNbResultFillBlankAll($listFillTheBlankResult);
}
if (empty($session_id)) {
$courseCondition = "
INNER JOIN $courseUser cu
ON cu.c_id = c.id AND cu.user_id = exe_user_id";
$courseConditionWhere = " AND relation_type <> 2 AND cu.status = ".STUDENT;
} else {
$courseCondition = "
INNER JOIN $courseUserSession cu
ON (cu.c_id = c.id AND cu.user_id = e.exe_user_id AND e.session_id = cu.session_id)";
$courseConditionWhere = " AND cu.status = 0 ";
}
$sql = "SELECT DISTINCT exe_user_id
FROM $track_exercises e
INNER JOIN $track_attempt a
ON (
a.exe_id = e.exe_id AND
e.c_id = a.c_id AND
e.session_id = a.session_id
)
INNER JOIN $courseTable c
ON (c.id = a.c_id)
$courseCondition
WHERE
exe_exo_id = $exercise_id AND
a.c_id = $courseId AND
e.session_id = $session_id AND
question_id = $question_id AND
answer <> '0' AND
e.status = ''
$courseConditionWhere
";
$result = Database::query($sql);
$return = 0;
if ($result) {
$return = Database::num_rows($result);
}
return $return;
}
/**
* Get number of answers to hotspot questions.
*
* @param int $answer_id
* @param int $question_id
* @param int $exercise_id
* @param string $course_code
* @param int $session_id
*
* @return int
*/
public static function get_number_students_answer_hotspot_count(
$answer_id,
$question_id,
$exercise_id,
$course_code,
$session_id
) {
$track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
$track_hotspot = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT);
$courseUser = Database::get_main_table(TABLE_MAIN_COURSE_USER);
$courseTable = Database::get_main_table(TABLE_MAIN_COURSE);
$courseUserSession = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
$question_id = (int) $question_id;
$answer_id = (int) $answer_id;
$exercise_id = (int) $exercise_id;
$course_code = Database::escape_string($course_code);
$session_id = (int) $session_id;
if (empty($session_id)) {
$courseCondition = "
INNER JOIN $courseUser cu
ON cu.c_id = c.id AND cu.user_id = exe_user_id";
$courseConditionWhere = " AND relation_type <> 2 AND cu.status = ".STUDENT;
} else {
$courseCondition = "
INNER JOIN $courseUserSession cu
ON (cu.c_id = c.id AND cu.user_id = e.exe_user_id AND e.session_id = cu.session_id)";
$courseConditionWhere = ' AND cu.status = 0 ';
}
$sql = "SELECT DISTINCT exe_user_id
FROM $track_exercises e
INNER JOIN $track_hotspot a
ON (a.hotspot_exe_id = e.exe_id)
INNER JOIN $courseTable c
ON (hotspot_course_code = c.code)
$courseCondition
WHERE
exe_exo_id = $exercise_id AND
a.hotspot_course_code = '$course_code' AND
e.session_id = $session_id AND
hotspot_answer_id = $answer_id AND
hotspot_question_id = $question_id AND
hotspot_correct = 1 AND
e.status = ''
$courseConditionWhere
";
$result = Database::query($sql);
$return = 0;
if ($result) {
$return = Database::num_rows($result);
}
return $return;
}
/**
* @param int $answer_id
* @param int $question_id
* @param int $exercise_id
* @param int $courseId
* @param int $session_id
* @param string $question_type
* @param string $correct_answer
* @param string $current_answer
*
* @return int
*/
public static function get_number_students_answer_count(
$answer_id,
$question_id,
$exercise_id,
$courseId,
$session_id,
$question_type = null,
$correct_answer = null,
$current_answer = null
) {
$track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
$track_attempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
$courseTable = Database::get_main_table(TABLE_MAIN_COURSE);
$courseUser = Database::get_main_table(TABLE_MAIN_COURSE_USER);
$courseUserSession = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
$question_id = (int) $question_id;
$answer_id = (int) $answer_id;
$exercise_id = (int) $exercise_id;
$courseId = (int) $courseId;
$session_id = (int) $session_id;
switch ($question_type) {
case FILL_IN_BLANKS:
case FILL_IN_BLANKS_COMBINATION:
$answer_condition = '';
$select_condition = ' e.exe_id, answer ';
break;
case MATCHING:
case MATCHING_COMBINATION:
case MATCHING_DRAGGABLE:
case MATCHING_DRAGGABLE_COMBINATION:
default:
$answer_condition = " answer = $answer_id AND ";
$select_condition = ' DISTINCT exe_user_id ';
}
if (empty($session_id)) {
$courseCondition = "
INNER JOIN $courseUser cu
ON cu.c_id = c.id AND cu.user_id = exe_user_id";
$courseConditionWhere = " AND relation_type <> 2 AND cu.status = ".STUDENT;
} else {
$courseCondition = "
INNER JOIN $courseUserSession cu
ON (cu.c_id = a.c_id AND cu.user_id = e.exe_user_id AND e.session_id = cu.session_id)";
$courseConditionWhere = ' AND cu.status = 0 ';
}
$sql = "SELECT $select_condition
FROM $track_exercises e
INNER JOIN $track_attempt a
ON (
a.exe_id = e.exe_id AND
e.c_id = a.c_id AND
e.session_id = a.session_id
)
INNER JOIN $courseTable c
ON c.id = a.c_id
$courseCondition
WHERE
exe_exo_id = $exercise_id AND
a.c_id = $courseId AND
e.session_id = $session_id AND
$answer_condition
question_id = $question_id AND
e.status = ''
$courseConditionWhere
";
$result = Database::query($sql);
$return = 0;
if ($result) {
$good_answers = 0;
switch ($question_type) {
case FILL_IN_BLANKS:
case FILL_IN_BLANKS_COMBINATION:
while ($row = Database::fetch_array($result, 'ASSOC')) {
$fill_blank = self::check_fill_in_blanks(
$correct_answer,
$row['answer'],
$current_answer
);
if (isset($fill_blank[$current_answer]) && $fill_blank[$current_answer] == 1) {
$good_answers++;
}
}
return $good_answers;
break;
case MATCHING:
case MATCHING_COMBINATION:
case MATCHING_DRAGGABLE:
case MATCHING_DRAGGABLE_COMBINATION:
default:
$return = Database::num_rows($result);
}
}
return $return;
}
/**
* @param array $answer
* @param string $user_answer
*
* @return array
*/
public static function check_fill_in_blanks($answer, $user_answer, $current_answer)
{
// the question is encoded like this
// [A] B [C] D [E] F::10,10,10@1
// number 1 before the "@" means that is a switchable fill in blank question
// [A] B [C] D [E] F::10,10,10@ or [A] B [C] D [E] F::10,10,10
// means that is a normal fill blank question
// first we explode the "::"
$pre_array = explode('::', $answer);
// is switchable fill blank or not
$last = count($pre_array) - 1;
$is_set_switchable = explode('@', $pre_array[$last]);
$switchable_answer_set = false;
if (isset($is_set_switchable[1]) && $is_set_switchable[1] == 1) {
$switchable_answer_set = true;
}
$answer = '';
for ($k = 0; $k < $last; $k++) {
$answer .= $pre_array[$k];
}
// splits weightings that are joined with a comma
$answerWeighting = explode(',', $is_set_switchable[0]);
// we save the answer because it will be modified
//$temp = $answer;
$temp = $answer;
$answer = '';
$j = 0;
//initialise answer tags
$user_tags = $correct_tags = $real_text = [];
// the loop will stop at the end of the text
while (1) {
// quits the loop if there are no more blanks (detect '[')
if (($pos = api_strpos($temp, '[')) === false) {
// adds the end of the text
$answer = $temp;
$real_text[] = $answer;
break; //no more "blanks", quit the loop
}
// adds the piece of text that is before the blank
//and ends with '[' into a general storage array
$real_text[] = api_substr($temp, 0, $pos + 1);
$answer .= api_substr($temp, 0, $pos + 1);
//take the string remaining (after the last "[" we found)
$temp = api_substr($temp, $pos + 1);
// quit the loop if there are no more blanks, and update $pos to the position of next ']'
if (($pos = api_strpos($temp, ']')) === false) {
// adds the end of the text
$answer .= $temp;
break;
}
$str = $user_answer;
preg_match_all('#\[([^[]*)\]#', $str, $arr);
$str = str_replace('\r\n', '', $str);
$choices = $arr[1];
$choice = [];
$check = false;
$i = 0;
foreach ($choices as $item) {
if ($current_answer === $item) {
$check = true;
}
if ($check) {
$choice[] = $item;
$i++;
}
if ($i == 3) {
break;
}
}
$tmp = api_strrpos($choice[$j], ' / ');
if ($tmp !== false) {
$choice[$j] = api_substr($choice[$j], 0, $tmp);
}
$choice[$j] = trim($choice[$j]);
//Needed to let characters ' and " to work as part of an answer
$choice[$j] = stripslashes($choice[$j]);
$user_tags[] = api_strtolower($choice[$j]);
//put the contents of the [] answer tag into correct_tags[]
$correct_tags[] = api_strtolower(api_substr($temp, 0, $pos));
$j++;
$temp = api_substr($temp, $pos + 1);
}
$answer = '';
$real_correct_tags = $correct_tags;
$chosen_list = [];
$good_answer = [];
for ($i = 0; $i < count($real_correct_tags); $i++) {
if (!$switchable_answer_set) {
//needed to parse ' and " characters
$user_tags[$i] = stripslashes($user_tags[$i]);
if ($correct_tags[$i] == $user_tags[$i]) {
$good_answer[$correct_tags[$i]] = 1;
} elseif (!empty($user_tags[$i])) {
$good_answer[$correct_tags[$i]] = 0;
} else {
$good_answer[$correct_tags[$i]] = 0;
}
} else {
// switchable fill in the blanks
if (in_array($user_tags[$i], $correct_tags)) {
$correct_tags = array_diff($correct_tags, $chosen_list);
$good_answer[$correct_tags[$i]] = 1;
} elseif (!empty($user_tags[$i])) {
$good_answer[$correct_tags[$i]] = 0;
} else {
$good_answer[$correct_tags[$i]] = 0;
}
}
// adds the correct word, followed by ] to close the blank
$answer .= ' / '.$real_correct_tags[$i].' ]';
if (isset($real_text[$i + 1])) {
$answer .= $real_text[$i + 1];
}
}
return $good_answer;
}
/**
* It gets the number of users who finishing the exercise.
*
* @param int $exerciseId
* @param int $courseId
* @param int $sessionId
*
* @return int
*/
public static function getNumberStudentsFinishExercise(
$exerciseId,
$courseId,
$sessionId
) {
$tblTrackExercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
$tblTrackAttempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
$exerciseId = (int) $exerciseId;
$courseId = (int) $courseId;
$sessionId = (int) $sessionId;
$sql = "SELECT DISTINCT exe_user_id
FROM $tblTrackExercises e
INNER JOIN $tblTrackAttempt a
ON (a.exe_id = e.exe_id)
WHERE
exe_exo_id = $exerciseId AND
e.c_id = $courseId AND
e.session_id = $sessionId AND
status = ''";
$result = Database::query($sql);
$return = 0;
if ($result) {
$return = Database::num_rows($result);
}
return $return;
}
/**
* Return an HTML select menu with the student groups.
*
* @param string $name is the name and the id of the
* @param string $default default value for option
* @param string $onchange
*
* @return string the html code of the
*/
public static function displayGroupMenu($name, $default, $onchange = "")
{
// check the default value of option
$tabSelected = [$default => " selected='selected' "];
$res = "";
$res .= "";
$res .= "-- ".get_lang(
'AllGroups'
)." -- ";
$res .= "- ".get_lang(
'NotInAGroup'
)." - ";
$tabGroups = GroupManager::get_group_list();
$currentCatId = 0;
$countGroups = count($tabGroups);
for ($i = 0; $i < $countGroups; $i++) {
$tabCategory = GroupManager::get_category_from_group(
$tabGroups[$i]['iid']
);
if ($tabCategory['iid'] != $currentCatId) {
$res .= "".$tabCategory['title']." ";
$currentCatId = $tabCategory['iid'];
}
$res .= "".
$tabGroups[$i]['name'].
" ";
}
$res .= " ";
return $res;
}
/**
* @param int $exe_id
*/
public static function create_chat_exercise_session($exe_id)
{
if (!isset($_SESSION['current_exercises'])) {
$_SESSION['current_exercises'] = [];
}
$_SESSION['current_exercises'][$exe_id] = true;
}
/**
* @param int $exe_id
*/
public static function delete_chat_exercise_session($exe_id)
{
if (isset($_SESSION['current_exercises'])) {
$_SESSION['current_exercises'][$exe_id] = false;
}
}
/**
* Display the exercise results.
*
* @param Exercise $objExercise
* @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 = '',
$allowSignature = false,
$allowExportPdf = false,
$isExport = false
) {
$origin = api_get_origin();
$courseId = api_get_course_int_id();
$courseCode = api_get_course_id();
$sessionId = api_get_session_id();
// Getting attempt info
$exercise_stat_info = $objExercise->get_stat_track_exercise_info_by_exe_id($exeId);
// Getting question list
$question_list = [];
$studentInfo = [];
if (!empty($exercise_stat_info['data_tracking'])) {
$studentInfo = api_get_user_info($exercise_stat_info['exe_user_id']);
$question_list = explode(',', $exercise_stat_info['data_tracking']);
} else {
// Try getting the question list only if save result is off
if ($save_user_result == false) {
$question_list = $objExercise->get_validated_question_list();
}
if (in_array(
$objExercise->getFeedbackType(),
[EXERCISE_FEEDBACK_TYPE_DIRECT, EXERCISE_FEEDBACK_TYPE_POPUP]
)) {
$question_list = $objExercise->get_validated_question_list();
}
}
if ($objExercise->getResultAccess()) {
if ($objExercise->hasResultsAccess($exercise_stat_info) === false) {
echo Display::return_message(
sprintf(get_lang('YouPassedTheLimitOfXMinutesToSeeTheResults'), $objExercise->getResultsAccess())
);
return false;
}
if (!empty($objExercise->getResultAccess())) {
$url = api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq().'&exerciseId='.$objExercise->iid;
echo $objExercise->returnTimeLeftDiv();
echo $objExercise->showSimpleTimeControl(
$objExercise->getResultAccessTimeDiff($exercise_stat_info),
$url
);
}
}
$counter = 1;
$total_score = $total_weight = 0;
$exercise_content = null;
// Hide results
$show_results = false;
$show_only_score = false;
if (in_array($objExercise->results_disabled,
[
RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER,
RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS,
RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS_AND_RANKING,
]
)) {
$show_results = true;
}
if (in_array(
$objExercise->results_disabled,
[
RESULT_DISABLE_SHOW_SCORE_ONLY,
RESULT_DISABLE_SHOW_FINAL_SCORE_ONLY_WITH_CATEGORIES,
RESULT_DISABLE_RANKING,
]
)
) {
$show_only_score = true;
}
// Not display expected answer, but score, and feedback
$show_all_but_expected_answer = false;
if ($objExercise->results_disabled == RESULT_DISABLE_SHOW_SCORE_ONLY &&
$objExercise->getFeedbackType() == EXERCISE_FEEDBACK_TYPE_END
) {
$show_all_but_expected_answer = true;
$show_results = true;
$show_only_score = false;
}
$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,
])
) {
$show_only_score = true;
$show_results = true;
$numberAttempts = 0;
if ($objExercise->attempts > 0) {
$attempts = Event::getExerciseResultsByUser(
api_get_user_id(),
$objExercise->iid,
$courseId,
$sessionId,
$exercise_stat_info['orig_lp_id'],
$exercise_stat_info['orig_lp_item_id'],
'desc'
);
if ($attempts) {
$numberAttempts = count($attempts);
}
if ($save_user_result) {
$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;
$show_results = true;
$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;
}
// Check if the current attempt is the last.
/*if (false === $save_user_result && !empty($attempts)) {
$showTotalScoreAndUserChoicesInLastAttempt = false;
$position = 1;
foreach ($attempts as $attempt) {
if ($exeId == $attempt['exe_id']) {
break;
}
$position++;
}
if ($position == $objExercise->attempts) {
$showTotalScoreAndUserChoicesInLastAttempt = true;
}
}*/
}
}
if ($objExercise->results_disabled ==
RESULT_DISABLE_DONT_SHOW_SCORE_ONLY_IF_USER_FINISHES_ATTEMPTS_SHOW_ALWAYS_FEEDBACK
) {
$show_only_score = false;
$show_results = true;
$show_all_but_expected_answer = false;
$showTotalScore = false;
$showQuestionScore = false;
if ($numberAttempts >= $objExercise->attempts) {
$showTotalScore = true;
$showQuestionScore = true;
}
}
}
// When exporting to PDF hide feedback/comment/score show warning in hotspot.
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)
) {
// Shows exercise header.
echo $objExercise->showExerciseResultHeader(
$studentInfo,
$exercise_stat_info,
$save_user_result,
$allowSignature,
$allowExportPdf
);
}
// Display text when test is finished #4074 and for LP #4227
// Allows to do a remove_XSS for end text result of exercise with
// user status COURSEMANAGERLOWSECURITY BT#20194
if (true === api_get_configuration_value('exercise_result_end_text_html_strict_filtering')) {
$endOfMessage = Security::remove_XSS($objExercise->getTextWhenFinished(), COURSEMANAGERLOWSECURITY);
} else {
$endOfMessage = Security::remove_XSS($objExercise->getTextWhenFinished());
}
if (!empty($endOfMessage)) {
echo Display::div(
$endOfMessage,
['id' => 'quiz_end_message']
);
}
$question_list_answers = [];
$category_list = [];
$loadChoiceFromSession = false;
$fromDatabase = true;
$exerciseResult = null;
$exerciseResultCoordinates = null;
$delineationResults = null;
if (true === $save_user_result && in_array(
$objExercise->getFeedbackType(),
[EXERCISE_FEEDBACK_TYPE_DIRECT, EXERCISE_FEEDBACK_TYPE_POPUP]
)) {
$loadChoiceFromSession = true;
$fromDatabase = false;
$exerciseResult = Session::read('exerciseResult');
$exerciseResultCoordinates = Session::read('exerciseResultCoordinates');
$delineationResults = Session::read('hotspot_delineation_result');
$delineationResults = $delineationResults[$objExercise->iid] ?? null;
}
$countPendingQuestions = 0;
$result = [];
// Loop over all question to show results for each of them, one by one
if (!empty($question_list)) {
foreach ($question_list as $questionId) {
// Creates a temporary Question object
$objQuestionTmp = Question::read($questionId, $objExercise->course);
// This variable came from exercise_submit_modal.php
ob_start();
$choice = null;
$delineationChoice = null;
if ($loadChoiceFromSession) {
$choice = $exerciseResult[$questionId] ?? null;
$delineationChoice = $delineationResults[$questionId] ?? null;
}
// We're inside *one* question. Go through each possible answer for this question
$result = $objExercise->manage_answer(
$exeId,
$questionId,
$choice,
'exercise_result',
$exerciseResultCoordinates,
$save_user_result,
$fromDatabase,
$show_results,
$objExercise->selectPropagateNeg(),
$delineationChoice,
$showTotalScoreAndUserChoicesInLastAttempt
);
if (empty($result)) {
continue;
}
$total_score += (float) $result['score'];
$total_weight += (float) $result['weight'];
$question_list_answers[] = [
'question' => $result['open_question'],
'answer' => $result['open_answer'],
'answer_type' => $result['answer_type'],
'generated_oral_file' => $result['generated_oral_file'],
];
$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;
if (isset($objQuestionTmp->category) && !empty($objQuestionTmp->category)) {
if (!isset($category_list[$objQuestionTmp->category]['score'])) {
$category_list[$objQuestionTmp->category]['score'] = 0;
}
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;
}
if (isset($objQuestionTmp->category_list) && !empty($objQuestionTmp->category_list)) {
foreach ($objQuestionTmp->category_list as $category_id) {
$category_list[$category_id]['score'] += $my_total_score;
$category_list[$category_id]['total'] += $my_total_weight;
$category_was_added_for_this_test = true;
}
}
// No category for this question!
if ($category_was_added_for_this_test == false) {
if (!isset($category_list['none']['score'])) {
$category_list['none']['score'] = 0;
}
if (!isset($category_list['none']['total'])) {
$category_list['none']['total'] = 0;
}
$category_list['none']['score'] += $my_total_score;
$category_list['none']['total'] += $my_total_weight;
}
if ($objExercise->selectPropagateNeg() == 0 && $my_total_score < 0) {
$my_total_score = 0;
}
$comnt = null;
if ($show_results) {
$comnt = Event::get_comments($exeId, $questionId);
$teacherAudio = self::getOralFeedbackAudio(
$exeId,
$questionId,
api_get_user_id()
);
if (!empty($comnt) || $teacherAudio) {
echo ''.get_lang('Feedback').' ';
}
if (!empty($comnt)) {
echo self::getFeedbackText($comnt);
}
if ($teacherAudio) {
echo $teacherAudio;
}
}
$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) {
$score = $calculatedScore;
}
if (in_array($objQuestionTmp->type, [FREE_ANSWER, ORAL_EXPRESSION, ANNOTATION, UPLOAD_ANSWER])) {
$reviewScore = [
'score' => $my_total_score,
'comments' => Event::get_comments($exeId, $questionId),
];
$check = $objQuestionTmp->isQuestionWaitingReview($reviewScore);
if (false === $check) {
$countPendingQuestions++;
}
}
$contents = ob_get_clean();
// Hide correct answers.
if ($scorePassed && false === $objExercise->disableHideCorrectAnsweredQuestions) {
// Skip correct answers.
$hide = (int) $objExercise->getPageConfigurationAttribute('hide_correct_answered_questions');
if (1 === $hide) {
continue;
}
}
$question_content = '';
if ($show_results) {
$question_content = '';
if (false === $showQuestionScore) {
$score = [];
}
// Shows question title an description
$question_content .= $objQuestionTmp->return_header(
$objExercise,
$counter,
$score
);
}
$counter++;
$question_content .= $contents;
if ($show_results) {
$question_content .= '
';
}
$calculatedScore['question_content'] = $question_content;
$attemptResult[] = $calculatedScore;
if ($objExercise->showExpectedChoice()) {
$exercise_content .= Display::div(
Display::panel($question_content),
['class' => 'question-panel']
);
} else {
// $show_all_but_expected_answer should not happen at
// the same time as $show_results
if ($show_results && !$show_only_score) {
$exercise_content .= Display::div(
Display::panel($question_content),
['class' => 'question-panel']
);
}
}
}
}
$totalScoreText = null;
$certificateBlock = '';
if (($show_results || $show_only_score) && $showTotalScore) {
if ($result['answer_type'] == MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY) {
echo ''.get_lang('YourResults').' ';
}
$totalScoreText .= '';
if ($result['answer_type'] == MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY) {
$totalScoreText .= self::getQuestionDiagnosisRibbon(
$objExercise,
$total_score,
$total_weight,
true
);
} else {
$pluginEvaluation = QuestionOptionsEvaluationPlugin::create();
if ('true' === $pluginEvaluation->get(QuestionOptionsEvaluationPlugin::SETTING_ENABLE)) {
$formula = $pluginEvaluation->getFormulaForExercise($objExercise->selectId());
if (!empty($formula)) {
$total_score = $pluginEvaluation->getResultWithFormula($exeId, $formula);
$total_weight = $pluginEvaluation->getMaxScore();
}
}
$totalScoreText .= self::getTotalScoreRibbon(
$objExercise,
$total_score,
$total_weight,
true,
$countPendingQuestions
);
}
$totalScoreText .= '
';
if (!empty($studentInfo)) {
$certificateBlock = self::generateAndShowCertificateBlock(
$total_score,
$total_weight,
$objExercise,
$studentInfo['id'],
$courseCode,
$sessionId
);
}
}
if ($result['answer_type'] == MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY) {
$chartMultiAnswer = MultipleAnswerTrueFalseDegreeCertainty::displayStudentsChartResults(
$exeId,
$objExercise
);
echo $chartMultiAnswer;
}
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, $category_list);
}
if ($show_all_but_expected_answer) {
$exercise_content .= Display::return_message(get_lang('ExerciseWithFeedbackWithoutCorrectionComment'));
}
// Remove audio auto play from questions on results page - refs BT#7939
$exercise_content = preg_replace(
['/autoplay[\=\".+\"]+/', '/autostart[\=\".+\"]+/'],
'',
$exercise_content
);
echo $totalScoreText;
echo $certificateBlock;
// Ofaj change BT#11784
if (api_get_configuration_value('quiz_show_description_on_results_page') &&
!empty($objExercise->description)
) {
echo Display::div(Security::remove_XSS($objExercise->description), ['class' => 'exercise_description']);
}
echo $exercise_content;
if (!$show_only_score) {
echo $totalScoreText;
}
if ($save_user_result) {
// Tracking of results
if ($exercise_stat_info) {
$learnpath_id = $exercise_stat_info['orig_lp_id'];
$learnpath_item_id = $exercise_stat_info['orig_lp_item_id'];
$learnpath_item_view_id = $exercise_stat_info['orig_lp_item_view_id'];
if (api_is_allowed_to_session_edit()) {
Event::updateEventExercise(
$exercise_stat_info['exe_id'],
$objExercise->selectId(),
$total_score,
$total_weight,
$sessionId,
$learnpath_id,
$learnpath_item_id,
$learnpath_item_view_id,
$exercise_stat_info['exe_duration'],
$question_list
);
$allowStats = api_get_configuration_value('allow_gradebook_stats');
if ($allowStats) {
$objExercise->generateStats(
$objExercise->selectId(),
api_get_course_info(),
$sessionId
);
}
}
}
// Send notification at the end
if (!api_is_allowed_to_edit(null, true) &&
!api_is_excluded_user_type()
) {
$objExercise->send_mail_notification_for_exam(
'end',
$question_list_answers,
$origin,
$exeId,
$total_score,
$total_weight
);
}
}
if (in_array(
$objExercise->selectResultsDisabled(),
[RESULT_DISABLE_RANKING, RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS_AND_RANKING]
)) {
echo Display::page_header(get_lang('Ranking'), null, 'h4');
echo self::displayResultsInRanking(
$objExercise,
api_get_user_id(),
$courseId,
$sessionId
);
}
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,
];
}
/**
* It validates unique score when all user answers are correct by question.
* It is used for global questions.
*
* @param $answerType
* @param $listCorrectAnswers
* @param $exeId
* @param $questionId
* @param $questionWeighting
* @param array $choice
* @param int $nbrAnswers
*
* @return int|mixed
*/
public static function getUserQuestionScoreGlobal(
$answerType,
$listCorrectAnswers,
$exeId,
$questionId,
$questionWeighting,
$choice = [],
$nbrAnswers = 0
) {
$nbrCorrect = 0;
$nbrOptions = 0;
switch ($answerType) {
case FILL_IN_BLANKS_COMBINATION:
if (!empty($listCorrectAnswers)) {
foreach ($listCorrectAnswers['student_score'] as $idx => $val) {
if (1 === (int) $val) {
$nbrCorrect++;
}
}
$nbrOptions = (int) $listCorrectAnswers['words_count'];
}
break;
case HOT_SPOT_COMBINATION:
if (!empty($listCorrectAnswers)) {
foreach ($listCorrectAnswers as $idx => $val) {
if (1 === (int) $choice[$idx]) {
$nbrCorrect++;
}
}
} else {
// We get the user answers from database
$TBL_TRACK_HOTSPOT = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT);
$sql = "SELECT count(hotspot_id) as ct
FROM $TBL_TRACK_HOTSPOT
WHERE
hotspot_exe_id = '".Database::escape_string($exeId)."' AND
hotspot_question_id = '".Database::escape_string($questionId)."' AND
hotspot_correct = 1";
$result = Database::query($sql);
$nbrCorrect = (int) Database::result($result, 0, 0);
}
$nbrOptions = $nbrAnswers;
break;
case MATCHING_COMBINATION:
case MATCHING_DRAGGABLE_COMBINATION:
if (isset($listCorrectAnswers['form_values'])) {
if (isset($listCorrectAnswers['form_values']['correct'])) {
$nbrCorrect = count($listCorrectAnswers['form_values']['correct']);
$nbrOptions = (int) $listCorrectAnswers['form_values']['count_options'];
}
} else {
if (isset($listCorrectAnswers['from_database'])) {
if (isset($listCorrectAnswers['from_database']['correct'])) {
$nbrCorrect = count($listCorrectAnswers['from_database']['correct']);
$nbrOptions = (int) $listCorrectAnswers['from_database']['count_options'];
}
}
}
break;
}
$questionScore = 0;
if ($nbrCorrect > 0 && $nbrCorrect == $nbrOptions) {
$questionScore = $questionWeighting;
}
return $questionScore;
}
/**
* Display the ranking of results in a exercise.
*
* @param Exercise $exercise
* @param int $currentUserId
* @param int $courseId
* @param int $sessionId
*
* @return string
*/
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-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']);
$table->setHeaderContents(0, 3, get_lang('Date'), ['class' => 'text-center']);
foreach ($data as $r => $item) {
if (!isset($item[1])) {
continue;
}
$selected = $item[1]->getId() == $currentUserId;
foreach ($item as $c => $value) {
$table->setCellContents($r + 1, $c, $value);
$attrClass = '';
if (in_array($c, [0, 2])) {
$attrClass = 'text-right';
} elseif (3 == $c) {
$attrClass = 'text-center';
}
if ($selected) {
$attrClass .= ' warning';
}
$table->setCellAttributes($r + 1, $c, ['class' => $attrClass]);
}
}
return $table->toHtml();
}
/**
* Get the ranking for results in a exercise.
* Function used internally by ExerciseLib::displayResultsInRanking.
*
* @param int $exerciseId
* @param int $courseId
* @param int $sessionId
*
* @return array
*/
public static function exerciseResultsInRanking($exerciseId, $courseId, $sessionId = 0)
{
$em = Database::getManager();
$dql = 'SELECT DISTINCT te.exeUserId FROM ChamiloCoreBundle:TrackEExercises te WHERE te.exeExoId = :id AND te.cId = :cId';
$dql .= api_get_session_condition($sessionId, true, false, 'te.sessionId');
$result = $em
->createQuery($dql)
->setParameters(['id' => $exerciseId, 'cId' => $courseId])
->getScalarResult();
$data = [];
/** @var TrackEExercises $item */
foreach ($result as $item) {
$data[] = self::get_best_attempt_by_user($item['exeUserId'], $exerciseId, $courseId, $sessionId);
}
usort(
$data,
function ($a, $b) {
if ($a['exe_result'] != $b['exe_result']) {
return $a['exe_result'] > $b['exe_result'] ? -1 : 1;
}
if ($a['exe_date'] != $b['exe_date']) {
return $a['exe_date'] < $b['exe_date'] ? -1 : 1;
}
return 0;
}
);
// flags to display the same position in case of tie
$lastScore = $data[0]['exe_result'];
$position = 1;
$data = array_map(
function ($item) use (&$lastScore, &$position) {
if ($item['exe_result'] < $lastScore) {
$position++;
}
$lastScore = $item['exe_result'];
return [
$position,
api_get_user_entity($item['exe_user_id']),
self::show_score($item['exe_result'], $item['exe_weighting'], true, true, true),
api_convert_and_format_date($item['exe_date'], DATE_TIME_FORMAT_SHORT),
];
},
$data
);
return $data;
}
/**
* Get a special ribbon on top of "degree of certainty" questions (
* variation from getTotalScoreRibbon() for other question types).
*
* @param Exercise $objExercise
* @param float $score
* @param float $weight
* @param bool $checkPassPercentage
*
* @return string
*/
public static function getQuestionDiagnosisRibbon($objExercise, $score, $weight, $checkPassPercentage = false)
{
$displayChartDegree = true;
$ribbon = $displayChartDegree ? '' : '';
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 ? '
' : '';
} else {
$ribbon .= $displayChartDegree ? '
' : '';
}
if ($displayChartDegree) {
$ribbon .= '
'.get_lang('YourTotalScore').': ';
$ribbon .= self::show_score($score, $weight, false, true);
$ribbon .= ' ';
$ribbon .= '';
}
if ($checkPassPercentage) {
$ribbon .= self::showSuccessMessage(
$score,
$weight,
$objExercise->selectPassPercentage()
);
}
$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,
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;
}
}