19declare(strict_types=1);
47 $result = $this->db->queryF(
48 'SELECT tst_result_cache.active_fi AS active_id, tst_active.user_fi AS user_id FROM tst_result_cache' . PHP_EOL
49 .
'INNER JOIN tst_active ON tst_active.active_id = tst_result_cache.active_fi' . PHP_EOL
50 .
'INNER JOIN tst_tests ON tst_tests.test_id = tst_active.test_fi' . PHP_EOL
51 .
'WHERE tst_tests.obj_fi = %s AND tst_result_cache.passed_once = 1' . PHP_EOL,
55 return $this->db->fetchAll($result);
60 return ($status = $this->
readOrQueryStatus($user_id, $test_obj_id)) !==
null && $status[
'passed'];
65 return ($status = $this->
readOrQueryStatus($user_id, $test_obj_id)) !==
null && $status[
'failed'];
70 return ($status = $this->
readOrQueryStatus($user_id, $test_obj_id)) !==
null && $status[
'finished'];
75 int $a_trigger_obj_id,
80 && $status[
'percentage'] >= $min_threshold
81 && $status[
'percentage'] <= $max_threshold;
86 $result = $this->db->queryF(
87 'SELECT tst_result_cache.*, tst_active.test_fi AS test_id FROM tst_result_cache' . PHP_EOL
88 .
'JOIN tst_active ON tst_result_cache.active_fi = tst_active.active_id' . PHP_EOL
89 .
'WHERE active_fi = %s',
94 return $this->toParticipantResult($this->db->fetchAssoc($result));
99 $attempt = $this->lookupAttempt($active_id);
100 $attempt_result = $this->fetchTestAttemptResult($active_id, $attempt);
101 if (!$attempt_result) {
106 $status = StatusOfAttempt::build(
108 $attempt_result[
'last_finished_pass'],
109 $attempt_result[
'finalized_by'],
112 $result = $this->buildTestResultObject($attempt_result);
113 $callback =
function () use ($result) {
128 [
'active_fi' => $result->getActiveId()],
133 if (is_object($process_locker)) {
134 $process_locker->executeUserTestResultUpdateLockOperation($callback);
139 $this->updateStatusCache(
140 $attempt_result[
'user_id'],
141 $attempt_result[
'test_obj_id'],
143 'passed' => $result->isPassed(),
144 'failed' => $result->isFailed(),
145 'finished' => $status->isFinished(),
146 'percentage' => $result->getPercentage(),
155 $result = $this->db->queryF(
156 "SELECT * FROM tst_pass_result WHERE active_fi = %s",
160 return $this->toTestAttemptResult($this->db->fetchAssoc($result));
167 ?
int $test_obj_id =
null,
168 bool $update_result_cache_table =
true
170 $test_result = $this->fetchTestResult($active_id, $attempt);
175 $result_object = $this->buildTestAttemptResultObject(
181 $callback =
function () use ($result_object, $attempt) {
201 if (is_object($process_locker)) {
202 $process_locker->executeUserPassResultUpdateLockOperation($callback);
207 if ($update_result_cache_table) {
208 $this->updateTestResultCache($active_id, $process_locker);
211 return $result_object;
216 if (!$status_of_attempt->isFinished()) {
217 throw new \RuntimeException(
'Status of attempt must be finished to finalize test attempt result');
220 $this->db->manipulateF(
221 'UPDATE tst_pass_result SET tstamp = %s, finalized_by = %s WHERE active_fi = %s AND pass = %s',
222 [
'integer',
'text',
'integer',
'integer'],
223 [time(), $status_of_attempt->value, $active_id, $attempt]
229 return $this->db->fetchAssoc($this->db->queryF(
230 "SELECT tst_pass_result.*, tst_active.last_finished_pass, tst_active.user_fi AS user_id, tst_tests.test_id,
231 tst_tests.obj_fi AS test_obj_id, tst_pass_result.maxpoints AS max_points, points AS reached_points,
232 tst_result_cache.passed_once AS passed_once_before
234 INNER JOIN tst_active ON tst_pass_result.active_fi = tst_active.active_id
235 INNER JOIN tst_tests ON tst_tests.test_id = tst_active.test_fi
236 LEFT JOIN tst_result_cache ON tst_result_cache.active_fi = tst_active.active_id
237 WHERE tst_pass_result.active_fi = %s AND tst_pass_result.pass = %s",
239 [$active_id, $attempt]
245 $test_attempt_result = $this->toParticipantResult($test_attempt_result_array);
247 $is_passed = $test_attempt_result->getAttempt() <= $test_attempt_result_array[
'last_finished_pass'] && $test_attempt_result->isPassed();
248 $passed_once_before = (bool) ($test_attempt_result_array[
'passed_once_before'] ??
false);
249 return $test_attempt_result->withPassedOnce($is_passed || $passed_once_before);
254 return $this->db->fetchAssoc($this->db->queryF(
255 'SELECT r.pass,' . PHP_EOL
256 .
'SUM(r.points) AS points,' . PHP_EOL
257 .
'COUNT(DISTINCT(r.question_fi)) answeredquestions,' . PHP_EOL
258 .
'pr.exam_id,' . PHP_EOL
259 .
'pr.finalized_by' . PHP_EOL
260 .
'FROM tst_test_result r' . PHP_EOL
261 .
'INNER JOIN tst_pass_result pr' . PHP_EOL
262 .
'ON r.active_fi = pr.active_fi AND r.pass = pr.pass' . PHP_EOL
263 .
'WHERE r.active_fi = %s AND r.pass = %s',
265 [$active_id, $attempt]
271 $test_result[
'active_fi'] = $active_id;
272 $test_attempt_result = $this->toTestAttemptResult($test_result);
273 $additional_data = $this->fetchAdditionalTestData($test_attempt_result->getActiveId(), $test_attempt_result->getAttempt());
275 return $test_attempt_result->withMaxPoints($additional_data[
'max_points'])
276 ->withQuestionCount($additional_data[
'question_count'])
278 $this->fetchWorkingTime($test_attempt_result->getActiveId(), $test_attempt_result->getAttempt())
282 $test_attempt_result->getActiveId(),
283 $test_attempt_result->getAttempt(),
295 $qst_set_type_result = $this->db->queryF(
296 'SELECT tst_test_settings.question_set_type FROM tst_active' . PHP_EOL
297 .
'INNER JOIN tst_tests ON tst_active.test_fi = tst_tests.test_id' . PHP_EOL
298 .
'INNER JOIN tst_test_settings ON tst_tests.settings_id = tst_test_settings.id' . PHP_EOL
299 .
'WHERE tst_active.active_id = %s',
303 $question_set_type = $qst_set_type_result->numRows() > 0
304 ? $this->db->fetchAssoc($qst_set_type_result)[
'question_set_type']
307 $result = match ($question_set_type) {
309 "SELECT tst_test_rnd_qst.pass, COUNT(tst_test_rnd_qst.question_fi) qcount, SUM(qpl_questions.points) qsum
310 FROM tst_test_rnd_qst, qpl_questions
311 WHERE tst_test_rnd_qst.question_fi = qpl_questions.question_id
312 AND tst_test_rnd_qst.active_fi = %s AND pass = %s
313 GROUP BY tst_test_rnd_qst.active_fi, tst_test_rnd_qst.pass",
315 [$active_id, $attempt]
318 "SELECT COUNT(tst_test_question.question_fi) qcount, SUM(qpl_questions.points) qsum
319 FROM tst_test_question, qpl_questions, tst_active
320 WHERE tst_test_question.question_fi = qpl_questions.question_id
321 AND tst_test_question.test_fi = tst_active.test_fi AND tst_active.active_id = %s
322 GROUP BY tst_test_question.test_fi",
326 default =>
throw new \ilTestException(
'not supported question set type: ' . $question_set_type),
329 $row = $this->db->fetchAssoc($result);
330 return is_array($row)
331 ? [
'question_count' => (
int) $row[
'qcount'],
'max_points' => (
float) $row[
'qsum']]
332 : [
'question_count' => 0,
'max_points' => 0.0];
337 $result = $this->db->queryF(
338 "SELECT started, finished FROM tst_times WHERE active_fi = %s AND pass = %s ORDER BY started",
340 [$active_id, $attempt]
344 while ($row = $this->db->fetchAssoc($result)) {
345 $time += (strtotime($row[
'finished']) - strtotime($row[
'started']));
354 $this->db->manipulate(
"DELETE FROM tst_test_result WHERE {$condition}");
355 $this->db->manipulate(
"DELETE FROM tst_pass_result WHERE {$condition}");
357 $user_ids = $this->db->fetchAll(
359 'SELECT user_fi FROM tst_active WHERE' . PHP_EOL
363 foreach ($user_ids as $row) {
364 $this->cache->delete($row[
'user_fi'] .
':' . $test_obj_id);
370 return \ilObjTest::_getResultPass($active_id);
380 $mark = $this->marks_repository
381 ->getMarkSchemaFor($row[
'test_id'])
382 ->getMatchingMark($this->calculatePercentage($row) * 100);
387 $this->ensurePositive($row[
'max_points'] ?? 0.0),
388 $this->ensurePositive($row[
'reached_points'] ?? 0.0),
390 (
int) ($row[
'tstamp'] ?? -1),
391 (
bool) ($row[
'passed_once'] ??
false),
404 $this->ensurePositive($row[
'maxpoints'] ?? 0.0),
405 $this->ensurePositive($row[
'points'] ?? 0.0),
406 (
int) ($row[
'questioncount'] ?? 0),
407 (
int) ($row[
'answeredquestions'] ?? 0),
408 (
int) ($row[
'workingtime'] ?? 0),
409 (
int) ($row[
'tstamp'] ?? -1),
410 $row[
'exam_id'] ??
'',
411 $row[
'finalized_by'] ??
'',
417 return max(0.0, (
float) $value);
425 $this->cache->set(
$user_id .
':' . $test_obj_id, $status);
433 $cached_status = $this->cache->get(
$user_id .
':' . $test_obj_id, $this->
refinery->identity());
434 if ($cached_status !==
null) {
435 return $cached_status;
438 $status = $this->db->fetchAssoc($this->db->queryF(
439 "SELECT tst_result_cache.passed, tst_result_cache.failed, (tst_active.last_finished_pass IS NOT NULL) AS finished,
440 tst_result_cache.reached_points, tst_result_cache.max_points
441 FROM tst_result_cache
442 INNER JOIN tst_active ON tst_active.active_id = tst_result_cache.active_fi
443 INNER JOIN tst_tests ON tst_tests.test_id = tst_active.test_fi
444 WHERE tst_active.user_fi = %s AND tst_tests.obj_fi = %s",
448 if ($status ===
null) {
452 $status[
'percentage'] = $this->calculatePercentage($status);
453 unset($status[
'reached_points'], $status[
'max_points']);
455 $this->updateStatusCache(
$user_id, $test_obj_id, $status);
466 $max_points = $this->ensurePositive($row[
'max_points'] ?? 0.0);
467 $reached_points = $this->ensurePositive($row[
'reached_points'] ?? 0.0);
469 return $max_points > 0 ? $reached_points / $max_points : 0.0;
get(Request $for_container)
Class ParticipantResult is a model representation of an entry in the test_result_cache table.
getTestAttemptResult(int $active_id)
lookupAttempt(int $active_id)
__construct(private readonly \ilDBInterface $db, private readonly Refinery $refinery, private readonly MarksRepository $marks_repository, Services $global_cache)
fetchTestResult(int $active_id, int $attempt)
readOrQueryStatus(int $user_id, int $test_obj_id)
reachedPercentage(int $a_usr_id, int $a_trigger_obj_id, float $min_threshold, float $max_threshold)
updateStatusCache(int $user_id, int $test_obj_id, array $status)
finalizeTestAttemptResult(int $active_id, int $attempt, StatusOfAttempt $status_of_attempt)
isPassed(int $user_id, int $test_obj_id)
updateTestAttemptResult(int $active_id, int $attempt, ?\ilAssQuestionProcessLocker $process_locker=null, ?int $test_obj_id=null, bool $update_result_cache_table=true)
getPassedParticipants(int $test_obj_id)
fetchWorkingTime(int $active_id, int $attempt)
ensurePositive(mixed $value)
updateTestResultCache(int $active_id, ?\ilAssQuestionProcessLocker $process_locker=null)
toTestAttemptResult(?array $row)
fetchAdditionalTestData(int $active_id, int $attempt)
isFailed(int $user_id, int $test_obj_id)
hasFinished(int $user_id, int $test_obj_id)
buildTestResultObject(array $test_attempt_result_array)
buildTestAttemptResultObject(int $active_id, array $test_result, ?int $test_obj_id)
removeTestResults(array $active_ids, int $test_obj_id)
calculatePercentage(array $row)
toParticipantResult(?array $row)
getTestResult(int $active_id)
fetchTestAttemptResult(int $active_id, int $attempt)
const QUESTION_SET_TYPE_RANDOM
const QUESTION_SET_TYPE_FIXED
static buildExamId($active_id, $pass, $test_obj_id=null)
return['delivery_method'=> 'php',]
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...