ILIAS  release_9 Revision v9.13-25-g2c18ec4c24f
ilManScoringRecalculateReachedPoints.php
Go to the documentation of this file.
1 <?php
2 
19 declare(strict_types=1);
20 
21 namespace ILIAS\Test\Setup;
22 
23 use ILIAS\Setup;
25 
27 {
28  private const TABLE_NAME = 'tst_manual_fb';
29  private const MIGRATION_ALLREADY_RUN_SETTING = 'assessment_man_scoring_fix_run';
30  private const START_DATE = 1714521600;
31 
32  private \ilDBInterface $db;
33  private \ilSetting $settings;
35  private bool $migration_already_run;
36 
37  public function getLabel(): string
38  {
39  return 'Update test attempt results on manual scoring after 1st of May 2024. We do this in one step to avoid creating tables and this might take a while.';
40  }
41 
43  {
44  return 1;
45  }
46 
47  public function getPreconditions(Environment $environment): array
48  {
49  return [
50  new \ilDatabaseInitializedObjective(),
51  new \ilSettingsFactoryExistsObjective()
52  ];
53  }
54 
55  public function prepare(Environment $environment): void
56  {
57  $this->db = $environment->getResource(Environment::RESOURCE_DATABASE);
60  ->settingsFor('assessment');
61  $this->manual_scoring_enabled = $this->settings->get('assessment_manual_scoring', '') !== '';
62  $this->migration_already_run = $this->settings->get(self::MIGRATION_ALLREADY_RUN_SETTING, '0') === '1';
63  }
64 
68  public function step(Environment $environment): void
69  {
70  if (!$this->manual_scoring_enabled) {
71  return;
72  }
73 
74  $result = $this->db->query(
75  'SELECT DISTINCT(active_fi), pass, question_set_type, (SELECT MAX(tstamp) FROM '
76  . self::TABLE_NAME . ' ms2 WHERE ms1.active_fi = ms2.active_fi) tstamp FROM '
77  . self::TABLE_NAME . ' ms1 '
78  . 'INNER JOIN tst_active ta ON ms1.active_fi = ta.active_id '
79  . 'INNER JOIN tst_tests tt ON ta.test_fi = tt.test_id '
80  . 'WHERE tt.question_set_type = "' . \ilObjTest::QUESTION_SET_TYPE_RANDOM . '"'
81  );
82 
83  while (($row = $this->db->fetchObject($result))) {
84  if ($row->tstamp <= self::START_DATE) {
85  continue;
86  }
87 
88  $this->updateTestPassResults($row->active_fi, $row->pass);
89  }
90 
91  $this->settings->set(self::MIGRATION_ALLREADY_RUN_SETTING, '1');
92  }
93 
94  public function getRemainingAmountOfSteps(): int
95  {
96  if (!$this->manual_scoring_enabled || $this->migration_already_run) {
97  return 0;
98  }
99 
100  return 1;
101  }
102 
103  private function updateTestPassResults(
104  int $active_id,
105  int $pass
106  ): void {
107  $result = $this->db->queryF(
108  'SELECT SUM(points) reachedpoints,
109  SUM(hint_count) hint_count,
110  SUM(hint_points) hint_points,
111  COUNT(DISTINCT(question_fi)) answeredquestions
112  FROM tst_test_result
113  WHERE active_fi = %s
114  AND pass = %s',
115  ['integer','integer'],
116  [$active_id, $pass]
117  );
118 
119  $row = $this->db->fetchAssoc($result);
120 
121  if ($row['reachedpoints'] === null
122  || $row['reachedpoints'] < 0.0) {
123  $row['reachedpoints'] = 0.0;
124  }
125  if ($row['hint_count'] === null) {
126  $row['hint_count'] = 0;
127  }
128  if ($row['hint_points'] === null) {
129  $row['hint_points'] = 0.0;
130  }
131 
132  $data = $this->getQuestionCountAndPointsForPassOfParticipant($active_id, $pass);
133 
134  $this->db->replace(
135  'tst_pass_result',
136  [
137  'active_fi' => ['integer', $active_id],
138  'pass' => ['integer', $pass]
139  ],
140  [
141  'points' => ['float', $row['reachedpoints']],
142  'maxpoints' => ['float', $data['points']],
143  'questioncount' => ['integer', $data['count']],
144  'answeredquestions' => ['integer', $row['answeredquestions']],
145  'tstamp' => ['integer', time()],
146  'hint_count' => ['integer', $row['hint_count']],
147  'hint_points' => ['float', $row['hint_points']]
148  ]
149  );
150 
151  $this->updateTestResultCache($active_id, $pass);
152  }
153 
154  private function updateTestResultCache(int $active_id, int $pass): void
155  {
156  $query = '
157  SELECT tst_pass_result.*,
158  tst_active.last_finished_pass,
159  tst_active.test_fi
160  FROM tst_pass_result
161  INNER JOIN tst_active
162  on tst_pass_result.active_fi = tst_active.active_id
163  WHERE active_fi = %s
164  AND pass = %s
165  ';
166 
167  $result = $this->db->queryF(
168  $query,
169  ['integer','integer'],
170  [$active_id, $pass]
171  );
172 
173  $test_pass_result_row = $this->db->fetchAssoc($result);
174 
175  if (!is_array($test_pass_result_row)) {
176  $test_pass_result_row = [];
177  }
178  $max = (float) ($test_pass_result_row['maxpoints'] ?? 0);
179  $reached = (float) ($test_pass_result_row['points'] ?? 0);
180  $percentage = ($max <= 0.0 || $reached <= 0.0) ? 0 : ($reached / $max) * 100.0;
181  $obligations_answered = (int) ($test_pass_result_row['obligations_answered'] ?? 1);
182 
183  $mark_schema = new \ASS_MarkSchema($this->db, new class ('en') extends \ilLanguage {
184  public function __construct(string $lc)
185  {
186  }
187  }, 0);
188  $mark_schema->loadFromDb($test_pass_result_row['test_fi']);
189  $mark = $mark_schema->getMatchingMark($percentage);
190  $is_passed = $pass <= $test_pass_result_row['last_finished_pass'] && $mark->getPassed();
191 
192  $hint_count = $test_pass_result_row['hint_count'] ?? 0;
193  $hint_points = $test_pass_result_row['hint_points'] ?? 0.0;
194 
195  $passed_once_before = 0;
196  $query = 'SELECT passed_once FROM tst_result_cache WHERE active_fi = %s';
197  $res = $this->db->queryF($query, ['integer'], [$active_id]);
198  while ($passed_once_result_row = $this->db->fetchAssoc($res)) {
199  $passed_once_before = (int) $passed_once_result_row['passed_once'];
200  }
201 
202  $passed_once = (int) ($is_passed || $passed_once_before);
203 
204  $this->db->manipulateF(
205  'DELETE FROM tst_result_cache WHERE active_fi = %s',
206  ['integer'],
207  [$active_id]
208  );
209 
210  if ($reached < 0.0) {
211  $reached = 0.0;
212  }
213 
214  $mark_short_name = $mark->getShortName();
215  if ($mark_short_name === '') {
216  $mark_short_name = ' ';
217  }
218 
219  $mark_official_name = $mark->getOfficialName();
220  if ($mark_official_name === '') {
221  $mark_official_name = ' ';
222  }
223 
224  $this->db->insert(
225  'tst_result_cache',
226  [
227  'active_fi' => ['integer', $active_id],
228  'pass' => ['integer', $pass ?? 0],
229  'max_points' => ['float', $max],
230  'reached_points' => ['float', $reached],
231  'mark_short' => ['text', $mark_short_name],
232  'mark_official' => ['text', $mark_official_name],
233  'passed_once' => ['integer', $passed_once],
234  'passed' => ['integer', (int) $is_passed],
235  'failed' => ['integer', (int) !$is_passed],
236  'tstamp' => ['integer', time()],
237  'hint_count' => ['integer', $hint_count],
238  'hint_points' => ['float', $hint_points],
239  'obligations_answered' => ['integer', $obligations_answered]
240  ]
241  );
242  }
243 
244  private function getQuestionCountAndPointsForPassOfParticipant($active_id, $pass): array
245  {
246  $res = $this->db->queryF(
247  "
248  SELECT tst_test_rnd_qst.pass,
249  COUNT(tst_test_rnd_qst.question_fi) qcount,
250  SUM(qpl_questions.points) qsum
251 
252  FROM tst_test_rnd_qst,
253  qpl_questions
254 
255  WHERE tst_test_rnd_qst.question_fi = qpl_questions.question_id
256  AND tst_test_rnd_qst.active_fi = %s
257  AND pass = %s
258 
259  GROUP BY tst_test_rnd_qst.active_fi,
260  tst_test_rnd_qst.pass
261  ",
262  ['integer', 'integer'],
263  [$active_id, $pass]
264  );
265 
266  $row = $this->db->fetchAssoc($res);
267 
268  if (is_array($row)) {
269  return ["count" => $row["qcount"], "points" => $row["qsum"]];
270  }
271 
272  return ["count" => 0, "points" => 0];
273  }
274 }
$res
Definition: ltiservices.php:69
const QUESTION_SET_TYPE_RANDOM
A migration is a potentially long lasting operation that can be broken into discrete steps...
Definition: Migration.php:28
prepare(Environment $environment)
Prepare the migration by means of some environment.
getPreconditions(Environment $environment)
Objectives the migration depend on.
__construct()
Constructor setup ILIAS global object public.
Definition: class.ilias.php:62
getResource(string $id)
Consumers of this method should check if the result is what they expect, e.g.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
getDefaultAmountOfStepsPerRun()
Tell the default amount of steps to be executed for one run of the migration.
An environment holds resources to be used in the setup process.
Definition: Environment.php:27
getRemainingAmountOfSteps()
Count up how many "things" need to be migrated.