ILIAS  trunk Revision v11.0_alpha-3011-gc6b235a2e85
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
54 protected array $question_cache = [];
55
56 public function __construct(
57 private \ilObjTest $test,
58 private \ilObjUser $scorer,
59 private \ilDBInterface $db,
60 private readonly TestResultRepository $test_result_repository
61 ) {
62 }
63
65 {
66 $this->preserve_manual_scores = $preserve_manual_scores;
67 }
68
69 public function getPreserveManualScores(): bool
70 {
72 }
73
74 public function getQuestionId(): int
75 {
76 return $this->question_id;
77 }
78
79 public function setQuestionId(int $question_id): void
80 {
81 $this->question_id = $question_id;
82 }
83
84 public function recalculateSolutions(): array
85 {
86 $factory = new \ilTestEvaluationFactory($this->db, $this->test);
87 $participants = $factory->getCorrectionsEvaluationData()->getParticipants();
88
89 foreach ($participants as $active_id => $userdata) {
90 if (is_object($userdata) && is_array($userdata->getPasses())) {
91 $this->recalculatePasses($userdata, $active_id);
93 $this->test->getId(),
94 $userdata->getUserID()
95 );
96 }
97 }
98
99 return $participants;
100
101 }
102
103 public function recalculateSolution(int $active_id, int $pass): void
104 {
105 $user_data = $this
106 ->test
107 ->getCompleteEvaluationData()
108 ->getParticipant($active_id);
109
110 $this->recalculatePass(
111 $user_data->getPass($pass),
112 $user_data->getUserID(),
113 $active_id,
114 $pass
115 );
116 $this->test_result_repository->updateTestResultCache($active_id);
117 }
118
119 private function recalculatePasses(\ilTestEvaluationUserData $userdata, int $active_id): void
120 {
121 $passes = $userdata->getPasses();
122 foreach ($passes as $pass => $passdata) {
123 if (is_object($passdata)) {
124 $this->recalculatePass($passdata, $userdata->getUserID(), $active_id, $pass);
125 }
126 }
127 $this->test_result_repository->updateTestResultCache($active_id);
128 }
129
130 private function recalculatePass(
131 \ilTestEvaluationPassData $passdata,
132 int $user_id,
133 int $active_id,
134 int $pass
135 ): void {
136 $questions = $passdata->getAnsweredQuestions();
137 $reached_points_changed = false;
138 foreach ($questions as $question_data) {
139 if (!$this->getQuestionId() || $this->getQuestionId() === $question_data['id']) {
140 $reached_points_changed = $reached_points_changed || $this->recalculateQuestionScore($user_id, $active_id, $pass, $question_data);
141 }
142 }
143 $this->updatePassResultsTable($active_id, $pass, $reached_points_changed);
144 }
145
146 private function recalculateQuestionScore(
147 int $user_id,
148 int $active_id,
149 int $pass,
150 array $questiondata
151 ): bool {
152 if ($this->preserve_manual_scores === true && $questiondata['manual'] === 1) {
153 return false;
154 }
155
156 $q_id = $questiondata['id'];
157 if (!isset($this->question_cache[$q_id])) {
158 $this->question_cache[$q_id] = $this->test->createQuestionGUI('', $q_id)->getObject();
159 }
160 $question = $this->question_cache[$q_id];
161
162 $old_points = $question->getReachedPoints($active_id, $pass);
163 $reached = $question->adjustReachedPointsByScoringOptions(
164 $question->calculateReachedPoints($active_id, $pass),
165 $active_id,
166 );
167
168 return $this->updateReachedPoints(
169 $user_id,
170 $active_id,
171 $questiondata['id'],
172 $old_points,
173 $reached,
174 $question->getMaximumPoints(),
175 $pass
176 );
177 }
178
184 private function updateReachedPoints(
185 int $user_id,
186 int $active_id,
187 int $question_id,
188 float $old_points,
189 float $points,
190 float $max_points,
191 int $pass
192 ): bool {
193 // Only update the test results if necessary
194 $has_changed = $old_points !== $points;
195 if ($has_changed && $points <= $max_points) {
196 $this->db->update(
197 'tst_test_result',
198 [
199 'points' => [\ilDBConstants::T_FLOAT, $points],
200 'tstamp' => [\ilDBConstants::T_INTEGER, time()],
201 ],
202 [
203 'active_fi' => [\ilDBConstants::T_INTEGER, $active_id],
204 'question_fi' => [\ilDBConstants::T_INTEGER, $question_id],
205 'pass' => [\ilDBConstants::T_INTEGER, $pass]
206 ]
207 );
208 }
209
211 $logger = $this->test->getTestLogger();
212 if ($logger->isLoggingEnabled()) {
213 $logger->logScoringInteraction(
215 $this->test->getRefId(),
216 $question_id,
217 $this->scorer->getId(),
218 $user_id,
220 time(),
221 []
222 )
223 );
224 }
225
226 return $has_changed;
227 }
228
229 private function updatePassResultsTable(
230 int $active_id,
231 int $pass,
232 bool $reached_points_changed
233 ): void {
234 // Always update the pass result as the maximum points might have changed
235 $data = $this->test->getQuestionCountAndPointsForPassOfParticipant($active_id, $pass);
236 $values = [
237 'maxpoints' => [\ilDBConstants::T_FLOAT, $data['points']],
238 'tstamp' => [\ilDBConstants::T_INTEGER, time()],
239 ];
240
241 if ($reached_points_changed) {
242 $result = $this->db->queryF(
243 'SELECT SUM(points) reachedpoints FROM tst_test_result WHERE active_fi = %s AND pass = %s',
245 [$active_id, $pass]
246 );
247 $values['points'] = [\ilDBConstants::T_FLOAT, $result->fetchAssoc()['reachedpoints'] ?? 0.0];
248 }
249
250 $this->db->update(
251 'tst_pass_result',
252 $values,
253 ['active_fi' => [\ilDBConstants::T_INTEGER, $active_id], 'pass' => [\ilDBConstants::T_INTEGER, $pass]]
254 );
255 }
256
260 public function calculateBestSolutionForTest(): string
261 {
262 $solution = '';
263 foreach ($this->test->getAllQuestions() as $question) {
265 $question_gui = $this->test->createQuestionGUI("", $question['question_id']);
266 $solution .= '<h1>' . $question_gui->getObject()->getTitleForHTMLOutput() . '</h1>';
267 $solution .= $question_gui->getSolutionOutput(0, null, true, true, false, false, true, false);
268 }
269
270 return $solution;
271 }
272
273 public function removeAllQuestionResults($question_id)
274 {
275 $query = "DELETE FROM tst_test_result WHERE question_fi = %s";
276 $this->db->manipulateF($query, ['integer'], [$question_id]);
277 }
278
283 public function updatePassAndTestResults(array $active_ids): void
284 {
285 foreach ($active_ids as $active_id) {
286 $passSelector = new \ilTestPassesSelector($this->db, $this->test);
287 $passSelector->setActiveId($active_id);
288
289 foreach ($passSelector->getExistingPasses() as $pass) {
290 $this->test_result_repository->updateTestAttemptResult(
291 $active_id,
292 $pass,
293 null,
294 null,
295 false
296 );
297 }
298
299 $this->test_result_repository->updateTestResultCache($active_id);
300 }
301 }
302
303 public function getNumManualScorings(): int
304 {
305 $query = "
306 SELECT COUNT(*) num_manual_scorings
307 FROM tst_test_result tres
308 INNER JOIN tst_active tact
309 ON tact.active_id = tres.active_fi
310 WHERE tact.test_fi = %s
311 AND tres.manual = 1
312 ";
313
314 $types = ['integer'];
315 $values = [$this->test->getTestId()];
316
317 if ($this->getQuestionId()) {
318 $query .= "
319 AND tres.question_fi = %s
320 ";
321
322 $types[] = 'integer';
323 $values[] = $this->getQuestionId();
324 }
325
326 $res = $this->db->queryF($query, $types, $values);
327
328 while ($row = $this->db->fetchAssoc($res)) {
329 return (int) $row['num_manual_scorings'];
330 }
331
332 return 0;
333 }
334}
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:64
recalculateQuestionScore(int $user_id, int $active_id, int $pass, array $questiondata)
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:56
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)
return true
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'))