ILIAS  trunk Revision v12.0_alpha-1227-g7ff6d300864
TestScoring.php
Go to the documentation of this file.
1<?php
2
19declare(strict_types=1);
20
22
25use ILIAS\Test\Results\Data\Repository as TestResultRepository;
26
47{
48 private bool $preserve_manual_scores = false;
49 private int $question_id = 0;
50 private \ilTestEvaluationFactory $evaluation_factory;
51
55 protected array $question_cache = [];
56
57 public function __construct(
58 private \ilObjTest $test,
59 private \ilObjUser $scorer,
60 private \ilDBInterface $db,
61 private readonly TestResultRepository $test_result_repository
62 ) {
63 $this->evaluation_factory = new \ilTestEvaluationFactory($this->db, $this->test);
64 }
65
67 {
68 $this->preserve_manual_scores = $preserve_manual_scores;
69 }
70
71 public function getPreserveManualScores(): bool
72 {
74 }
75
76 public function getQuestionId(): int
77 {
78 return $this->question_id;
79 }
80
81 public function setQuestionId(int $question_id): void
82 {
83 $this->question_id = $question_id;
84 }
85
86 public function recalculateSolutions(): array
87 {
88 $participants = $this->evaluation_factory->getCorrectionsEvaluationData()->getParticipants();
89
90 foreach ($participants as $active_id => $userdata) {
91 if ($userdata instanceof \ilTestEvaluationUserData) {
92 $this->recalculatePasses($userdata, $active_id);
93 \ilLPStatusWrapper::_updateStatus($this->test->getId(), $userdata->getUserID());
94 }
95 }
96
97 return $participants;
98
99 }
100
101 public function recalculateSolution(int $active_id, int $pass): void
102 {
103 $user_data = $this
104 ->test
105 ->getCompleteEvaluationData()
106 ->getParticipant($active_id);
107
108 $this->recalculatePass(
109 $user_data->getPass($pass),
110 $user_data->getUserID(),
111 $active_id,
112 $pass
113 );
114 $this->test_result_repository->updateTestResultCache($active_id);
115 }
116
117 private function recalculatePasses(\ilTestEvaluationUserData $userdata, int $active_id): void
118 {
119 foreach ($userdata->getPasses() as $pass => $passdata) {
120 if ($passdata instanceof \ilTestEvaluationPassData) {
121 $this->recalculatePass($passdata, $userdata->getUserID(), $active_id, $pass);
122 }
123 }
124 $this->test_result_repository->updateTestResultCache($active_id);
125 }
126
127 private function recalculatePass(
128 \ilTestEvaluationPassData $passdata,
129 int $user_id,
130 int $active_id,
131 int $pass
132 ): void {
133 $reached_points_changed = false;
134 foreach ($passdata->getAnsweredQuestions() as $question_data) {
135 if ($this->getQuestionId() !== 0 || $this->getQuestionId() === $question_data['id']) {
136 $reached_points_changed = $reached_points_changed || $this->recalculateQuestionScore($user_id, $active_id, $pass, $question_data);
137 }
138 }
139 $this->updatePassResultsTable($active_id, $pass, $reached_points_changed);
140 }
141
142 private function recalculateQuestionScore(
143 int $user_id,
144 int $active_id,
145 int $pass,
146 array $questiondata
147 ): bool {
148 if ($this->preserve_manual_scores && $questiondata['manual'] === 1) {
149 return false;
150 }
151
152 $q_id = $questiondata['id'];
153 $this->question_cache[$q_id] ??= $this->test->createQuestionGUI('', $q_id)->getObject();
155 $question = $this->question_cache[$q_id];
156
157 $old_points = $question->getReachedPoints($active_id, $pass);
158 $reached = $question->adjustReachedPointsByScoringOptions(
159 $question->calculateReachedPoints($active_id, $pass),
160 $active_id,
161 );
162
163 return $this->updateReachedPoints(
164 $user_id,
165 $active_id,
166 $questiondata['id'],
167 $old_points,
168 $reached,
169 $question->getMaximumPoints(),
170 $pass
171 );
172 }
173
179 private function updateReachedPoints(
180 int $user_id,
181 int $active_id,
182 int $question_id,
183 float $old_points,
184 float $points,
185 float $max_points,
186 int $pass
187 ): bool {
188 // Only update the test results if necessary
189 $has_changed = $old_points !== $points;
190 if ($has_changed && $points <= $max_points) {
191 $this->db->update(
192 'tst_test_result',
193 [
194 'points' => [\ilDBConstants::T_FLOAT, $points],
195 'tstamp' => [\ilDBConstants::T_INTEGER, time()],
196 ],
197 [
198 'active_fi' => [\ilDBConstants::T_INTEGER, $active_id],
199 'question_fi' => [\ilDBConstants::T_INTEGER, $question_id],
200 'pass' => [\ilDBConstants::T_INTEGER, $pass]
201 ]
202 );
203 }
204
206 $logger = $this->test->getTestLogger();
207 if ($logger->isLoggingEnabled()) {
208 $logger->logScoringInteraction(
210 $this->test->getRefId(),
211 $question_id,
212 $this->scorer->getId(),
213 $user_id,
215 time(),
216 []
217 )
218 );
219 }
220
221 return $has_changed;
222 }
223
224 private function updatePassResultsTable(
225 int $active_id,
226 int $pass,
227 bool $reached_points_changed
228 ): void {
229 // Always update the pass result as the maximum points might have changed
230 $data = $this->test->getQuestionCountAndPointsForPassOfParticipant($active_id, $pass);
231 $values = [
232 'maxpoints' => [\ilDBConstants::T_FLOAT, $data['points']],
233 'tstamp' => [\ilDBConstants::T_INTEGER, time()],
234 ];
235
236 if ($reached_points_changed) {
237 $result = $this->db->queryF(
238 'SELECT SUM(points) reachedpoints FROM tst_test_result WHERE active_fi = %s AND pass = %s',
240 [$active_id, $pass]
241 );
242 $values['points'] = [\ilDBConstants::T_FLOAT, max($result->fetchAssoc()['reachedpoints'] ?? 0.0, 0.0)];
243 }
244
245 $this->db->update(
246 'tst_pass_result',
247 $values,
248 ['active_fi' => [\ilDBConstants::T_INTEGER, $active_id], 'pass' => [\ilDBConstants::T_INTEGER, $pass]]
249 );
250 }
251
255 public function calculateBestSolutionForTest(): string
256 {
257 $solution = '';
258 foreach ($this->test->getAllQuestions() as $question) {
260 $question_gui = $this->test->createQuestionGUI("", $question['question_id']);
261 $solution .= '<h1>' . $question_gui->getObject()->getTitleForHTMLOutput() . '</h1>';
262 $solution .= $question_gui->getSolutionOutput(0, null, true, true, false, false, true, false);
263 }
264
265 return $solution;
266 }
267
268 public function removeAllQuestionResults($question_id)
269 {
270 $query = "DELETE FROM tst_test_result WHERE question_fi = %s";
271 $this->db->manipulateF($query, ['integer'], [$question_id]);
272 }
273
278 public function updatePassAndTestResults(array $active_ids): void
279 {
280 foreach ($active_ids as $active_id) {
281 $passSelector = new \ilTestPassesSelector($this->db, $this->test);
282 $passSelector->setActiveId($active_id);
283
284 foreach ($passSelector->getExistingPasses() as $pass) {
285 $this->test_result_repository->updateTestAttemptResult(
286 $active_id,
287 $pass,
288 null,
289 null,
290 false
291 );
292 }
293
294 $this->test_result_repository->updateTestResultCache($active_id);
295 }
296 }
297
298 public function getNumManualScorings(): int
299 {
300 $query = "
301 SELECT COUNT(*) num_manual_scorings
302 FROM tst_test_result tres
303 INNER JOIN tst_active tact
304 ON tact.active_id = tres.active_fi
305 WHERE tact.test_fi = %s
306 AND tres.manual = 1
307 ";
308
309 $types = ['integer'];
310 $values = [$this->test->getTestId()];
311
312 if ($this->getQuestionId()) {
313 $query .= "
314 AND tres.question_fi = %s
315 ";
316
317 $types[] = 'integer';
318 $values[] = $this->getQuestionId();
319 }
320
321 $res = $this->db->queryF($query, $types, $values);
322
323 while ($row = $this->db->fetchAssoc($res)) {
324 return (int) $row['num_manual_scorings'];
325 }
326
327 return 0;
328 }
329}
ilTestEvaluationFactory $evaluation_factory
Definition: TestScoring.php:50
updateReachedPoints(int $user_id, int $active_id, int $question_id, float $old_points, float $points, float $max_points, int $pass)
This is an optimized version of \assQuestion::_setReachedPoints that only executes updates in the dat...
setPreserveManualScores(bool $preserve_manual_scores)
Definition: TestScoring.php:66
recalculatePasses(\ilTestEvaluationUserData $userdata, int $active_id)
updatePassAndTestResults(array $active_ids)
__construct(private \ilObjTest $test, private \ilObjUser $scorer, private \ilDBInterface $db, private readonly TestResultRepository $test_result_repository)
Definition: TestScoring.php:57
updatePassResultsTable(int $active_id, int $pass, bool $reached_points_changed)
recalculatePass(\ilTestEvaluationPassData $passdata, int $user_id, int $active_id, int $pass)
recalculateSolution(int $active_id, int $pass)
static _updateObjectiveResult(int $a_user_id, int $a_active_id, int $a_question_id)
static _updateStatus(int $a_obj_id, int $a_usr_id, ?object $a_obj=null, bool $a_percentage=false, bool $a_force_raise=false)
User class.
Interface ilDBInterface.
$res
Definition: ltiservices.php:69
if(!file_exists('../ilias.ini.php'))