ILIAS  release_9 Revision v9.13-25-g2c18ec4c24f
class.ilCourseObjectiveResult.php
Go to the documentation of this file.
1 <?php
18 declare(strict_types=0);
19 
25 {
26  public const IL_OBJECTIVE_STATUS_EMPTY = 'empty';
27  public const IL_OBJECTIVE_STATUS_PRETEST = 'pretest';
28  public const IL_OBJECTIVE_STATUS_FINAL = 'final';
29  public const IL_OBJECTIVE_STATUS_NONE = 'none';
30  public const IL_OBJECTIVE_STATUS_FINISHED = 'finished';
31  public const IL_OBJECTIVE_STATUS_PRETEST_NON_SUGGEST = 'pretest_non_suggest';
32 
33  private int $user_id;
34 
35  protected ilDBInterface $db;
36  protected \ILIAS\TestQuestionPool\QuestionInfoService $questioninfo;
37 
38  public function __construct(int $a_usr_id)
39  {
40  global $DIC;
41 
42  $this->db = $DIC->database();
43  $this->questioninfo = $DIC->testQuestionPool()->questionInfo();
44 
45  $this->user_id = $a_usr_id;
46  }
47 
48  public function getUserId(): int
49  {
50  return $this->user_id;
51  }
52 
53  public function getAccomplished(int $a_crs_id): array
54  {
55  return ilCourseObjectiveResult::_getAccomplished($this->getUserId(), $a_crs_id);
56  }
57 
58  public static function _getAccomplished(int $a_user_id, int $a_crs_id): array
59  {
60  global $DIC;
61 
62  $ilDB = $DIC->database();
63 
65  if (!is_array($objectives)) {
66  return array();
67  }
68  $query = "SELECT objective_id FROM crs_objective_status " .
69  "WHERE " . $ilDB->in('objective_id', $objectives, false, 'integer') . ' ' .
70  "AND user_id = " . $ilDB->quote($a_user_id, 'integer') . " ";
71  $res = $ilDB->query($query);
72  $accomplished = [];
73  while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) {
74  $accomplished[] = (int) $row->objective_id;
75  }
76  return $accomplished;
77  }
78 
79  public function getSuggested(int $a_crs_id, string $a_status = self::IL_OBJECTIVE_STATUS_FINAL): array
80  {
81  return ilCourseObjectiveResult::_getSuggested($this->getUserId(), $a_crs_id, $a_status);
82  }
83 
84  public static function _getSuggested(
85  int $a_user_id,
86  int $a_crs_id,
87  string $a_status = self::IL_OBJECTIVE_STATUS_FINAL
88  ): array {
89  global $DIC;
90 
91  $ilDB = $DIC->database();
92 
94  $finished = $suggested = [];
95  if (
96  $a_status == self::IL_OBJECTIVE_STATUS_FINAL ||
97  $a_status == self::IL_OBJECTIVE_STATUS_FINISHED
98  ) {
99  // check finished
100  $query = "SELECT objective_id FROM crs_objective_status " .
101  "WHERE " . $ilDB->in('objective_id', $objectives, false, 'integer') . " " .
102  "AND user_id = " . $ilDB->quote($a_user_id, 'integer') . " ";
103  $res = $ilDB->query($query);
104  while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) {
105  $finished[] = (int) $row->objective_id;
106  }
107  } else {
108  // Pretest
109  $query = "SELECT objective_id FROM crs_objective_status_p " .
110  "WHERE " . $ilDB->in('objective_id', $objectives, false, 'integer') . ' ' .
111  "AND user_id = " . $ilDB->quote($a_user_id, 'integer');
112  $res = $ilDB->query($query);
113  while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) {
114  $finished[] = (int) $row->objective_id;
115  }
116  }
117  foreach ($objectives as $objective_id) {
118  if (!in_array($objective_id, $finished)) {
119  $suggested[] = $objective_id;
120  }
121  }
122  return $suggested;
123  }
124 
125  public static function getSuggestedQuestions(int $a_usr_id, int $a_crs_id): array
126  {
127  $qsts = [];
128  foreach (self::_getSuggested($a_usr_id, $a_crs_id) as $objective_id) {
129  $obj = new ilCourseObjectiveQuestion($objective_id);
130  foreach ($obj->getFinalTestQuestions() as $qst) {
131  $qsts[] = $qst['question_id'];
132  }
133  }
134  return $qsts;
135  }
136 
137  protected function resetTestForUser(ilObjTest $a_test, int $a_user_id): void
138  {
139  // #15038
140  $test_lp = ilTestLP::getInstance($a_test->getId());
141  $test_lp->resetLPDataForUserIds(array($a_user_id));
142 
143  // #15205 - see ilObjTestGUI::confirmDeleteSelectedUserDataObject()
144  $active_id = $a_test->getActiveIdOfUser($a_user_id);
145  if ($active_id) {
146  $a_test->removeTestActives(array($active_id));
147  }
148  }
149 
150  public function reset(int $a_course_id): void
151  {
152  $assignments = ilLOTestAssignments::getInstance($a_course_id);
153  foreach (array_merge(
154  $assignments->getAssignmentsByType(ilLOSettings::TYPE_TEST_INITIAL),
155  $assignments->getAssignmentsByType(ilLOSettings::TYPE_TEST_QUALIFIED)
156  )
157  as $assignment) {
158  $tst = ilObjectFactory::getInstanceByRefId($assignment->getTestRefId(), false);
159  if ($tst instanceof ilObjTest) {
160  global $DIC;
161 
162  $lng = $DIC['lng'];
163 
164  $participantData = new ilTestParticipantData($this->db, $lng);
165  $participantData->setUserIdsFilter(array($this->getUserId()));
166  $participantData->load($tst->getTestId());
167  $tst->removeTestResults($participantData);
168  }
169  }
170 
171  $initial = ilLOSettings::getInstanceByObjId($a_course_id)->getInitialTest();
172  $initial_tst = ilObjectFactory::getInstanceByRefId($initial, false);
173  if ($initial_tst instanceof ilObjTest) {
174  $this->resetTestForUser($initial_tst, $this->getUserId());
175  }
176 
177  $qualified = ilLOSettings::getInstanceByObjId($a_course_id)->getQualifiedTest();
178  $qualified_tst = ilObjectFactory::getInstanceByRefId($qualified, false);
179  if ($qualified_tst instanceof ilObjTest) {
180  $this->resetTestForUser($qualified_tst, $this->getUserId());
181  }
182 
183  $objectives = ilCourseObjective::_getObjectiveIds($a_course_id, false);
184 
185  if ($objectives !== []) {
186  $query = "DELETE FROM crs_objective_status " .
187  "WHERE " . $this->db->in('objective_id', $objectives, false, 'integer') . ' ' .
188  "AND user_id = " . $this->db->quote($this->getUserId(), 'integer') . " ";
189  $res = $this->db->manipulate($query);
190 
191  $query = "DELETE FROM crs_objective_status_p " .
192  "WHERE " . $this->db->in('objective_id', $objectives, false, 'integer') . ' ' .
193  "AND user_id = " . $this->db->quote($this->getUserId(), ilDBConstants::T_INTEGER) . "";
194  $res = $this->db->manipulate($query);
195 
196  $query = "DELETE FROM loc_user_results " .
197  "WHERE " . $this->db->in('objective_id', $objectives, false, 'integer') . ' ' .
198  "AND user_id = " . $this->db->quote($this->getUserId(), ilDBConstants::T_INTEGER) . "";
199  }
200  // update/reset LP for course
201  ilLPStatusWrapper::_updateStatus($a_course_id, $this->getUserId());
202  }
203 
204  public function getStatus(int $a_course_id): string
205  {
206  $objective_ids = ilCourseObjective::_getObjectiveIds($a_course_id, true);
208  $accomplished = $this->getAccomplished($a_course_id);
209  $suggested = $this->getSuggested($a_course_id);
210 
211  if ($objective_ids === []) {
212  return self::IL_OBJECTIVE_STATUS_EMPTY;
213  }
214 
215  if (count($accomplished) == count($objective_ids)) {
216  return self::IL_OBJECTIVE_STATUS_FINISHED;
217  }
218 
219  $all_pretest_answered = false;
220  $all_final_answered = false;
221  foreach ($objectives as $data) {
222  if ($this->questioninfo->areQuestionsAnsweredByUser($this->getUserId(), $data['questions'])) {
223  if ($data['tst_status']) {
224  $all_final_answered = true;
225  } else {
226  $all_pretest_answered = true;
227  }
228  }
229  }
230  if ($all_final_answered) {
231  return self::IL_OBJECTIVE_STATUS_FINAL;
232  }
233  if ($all_pretest_answered && $suggested === []) {
234  return self::IL_OBJECTIVE_STATUS_PRETEST_NON_SUGGEST;
235  } elseif ($all_pretest_answered) {
236  return self::IL_OBJECTIVE_STATUS_PRETEST;
237  }
238  return self::IL_OBJECTIVE_STATUS_NONE;
239  }
240 
241  public function hasAccomplishedObjective(int $a_objective_id): bool
242  {
243  $query = "SELECT status FROM crs_objective_status " .
244  "WHERE objective_id = " . $this->db->quote($a_objective_id, 'integer') . " " .
245  "AND user_id = " . $this->db->quote($this->getUserId(), 'integer') . "";
246 
247  $res = $this->db->query($query);
248  while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) {
249  return true;
250  }
251  return false;
252  }
253 
254  public function readStatus(int $a_crs_id): void
255  {
256  $objective_ids = ilCourseObjective::_getObjectiveIds($a_crs_id, true);
259  }
260 
261  public static function _updateObjectiveResult(int $a_user_id, int $a_active_id, int $a_question_id): void
262  {
263  // find all objectives this question is assigned to
264  if (!$objectives = self::_readAssignedObjectivesOfQuestion($a_question_id)) {
265  // no objectives found. TODO user has passed a test. After that questions of that test are assigned to an objective.
266  // => User has not passed
267  return;
268  }
269  self::_updateObjectiveStatus($a_user_id, $objectives);
270  }
271 
272  public static function _readAssignedObjectivesOfQuestion(int $a_question_id): array
273  {
274  global $DIC;
275 
276  $ilDB = $DIC['ilDB'];
277 
278  // get all objtives and questions this current question is assigned to
279  $query = "SELECT q2.question_id qid,q2.objective_id ob FROM crs_objective_qst q1, " .
280  "crs_objective_qst q2 " .
281  "WHERE q1.question_id = " . $ilDB->quote($a_question_id, 'integer') . " " .
282  "AND q1.objective_id = q2.objective_id ";
283 
284  $res = $ilDB->query($query);
285  $objectives = array();
286  while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) {
287  $objectives['all_objectives'][(int) $row->ob] = (int) $row->ob;
288  $objectives['all_questions'][(int) $row->qid] = (int) $row->qid;
289  }
290  if (count($objectives) === 0) {
291  return [];
292  }
293  $objectives['objectives'] = self::_readAssignedObjectives($objectives['all_objectives']);
294  return $objectives;
295  }
296 
297  public static function _readAssignedObjectives(array $a_all_objectives): array
298  {
299  global $DIC;
300 
301  $ilDB = $DIC['ilDB'];
302 
303  // Read necessary points
304  $query = "SELECT t.objective_id obj,t.ref_id ref, question_id,tst_status,tst_limit " .
305  "FROM crs_objective_tst t JOIN crs_objective_qst q " .
306  "ON (t.objective_id = q.objective_id AND t.ref_id = q.ref_id) " .
307  "WHERE " . $ilDB->in('t.objective_id', $a_all_objectives, false, 'integer');
308 
309  $res = $ilDB->query($query);
310  $objectives = array();
311  while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) {
312  $objectives[$row->obj . "_" . $row->tst_status]['questions'][(int) $row->question_id] = (int) $row->question_id;
313  $objectives[$row->obj . "_" . $row->tst_status]['tst_status'] = (int) $row->tst_status;
314  $objectives[$row->obj . "_" . $row->tst_status]['tst_limit'] = (int) $row->tst_limit;
315  $objectives[$row->obj . "_" . $row->tst_status]['objective_id'] = (int) $row->obj;
316  }
317  return $objectives;
318  }
319 
320  public static function _updateObjectiveStatus(int $a_user_id, array $objectives): bool
321  {
322  global $DIC;
323 
324  $ilDB = $DIC['ilDB'];
325  $ilUser = $DIC['ilUser'];
326 
327  if (
328  !count($objectives['all_questions']) ||
329  !count($objectives['all_objectives'])) {
330  return false;
331  }
332  // Read reachable points
333  $query = "SELECT question_id,points FROM qpl_questions " .
334  "WHERE " . $ilDB->in('question_id', (array) $objectives['all_questions'], false, 'integer');
335  $res = $ilDB->query($query);
336  while ($row = $ilDB->fetchAssoc($res)) {
337  $objectives['all_question_points'][(int) $row['question_id']]['max_points'] = (float) $row['points'];
338  }
339  // Read reached points
340  $query = "SELECT question_fi, MAX(points) as reached FROM tst_test_result " .
341  "JOIN tst_active ON (active_id = active_fi) " .
342  "WHERE user_fi = " . $ilDB->quote($a_user_id, 'integer') . " " .
343  "AND " . $ilDB->in('question_fi', (array) $objectives['all_questions'], false, 'integer') . " " .
344  "GROUP BY question_fi,user_fi";
345  $res = $ilDB->query($query);
346  while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) {
347  $objectives['all_question_points'][$row->question_fi]['reached_points'] = (float) $row->reached;
348  }
349 
350  // Check accomplished
351  $fullfilled = array();
352  $pretest = array();
353  foreach ($objectives['objectives'] as $data) {
354  // objective does not allow to change status
355  if (ilCourseObjectiveResult::__isFullfilled($objectives['all_question_points'], $data)) {
356  // Status 0 means pretest fullfilled, status 1 means final test fullfilled
357  if ($data['tst_status']) {
358  $fullfilled[] = array($data['objective_id'], $ilUser->getId(), $data['tst_status']);
359  } else {
360  $pretest[] = array($data['objective_id'], $ilUser->getId());
361  }
362  }
363  }
364  if ($fullfilled !== []) {
365  foreach ($fullfilled as $fullfilled_arr) {
366  $ilDB->replace(
367  'crs_objective_status',
368  array(
369  'objective_id' => array('integer', $fullfilled_arr[0]),
370  'user_id' => array('integer', $fullfilled_arr[1])
371  ),
372  array(
373  'status' => array('integer', $fullfilled_arr[2])
374  )
375  );
376  }
377  ilCourseObjectiveResult::__updatePassed($a_user_id, $objectives['all_objectives']);
378  }
379  if ($pretest !== []) {
380  foreach ($pretest as $pretest_arr) {
381  $ilDB->replace(
382  'crs_objective_status_p',
383  array(
384  'objective_id' => array('integer', $pretest_arr[0]),
385  'user_id' => array('integer', $pretest_arr[1])
386  ),
387  array()
388  );
389  }
390  }
391  return true;
392  }
393 
394  public static function __isFullfilled(array $question_points, array $objective_data): bool
395  {
396  global $DIC;
397 
398  if (!is_array($objective_data['questions'])) {
399  return false;
400  }
401  $max_points = 0;
402  $reached_points = 0;
403  foreach ($objective_data['questions'] as $question_id) {
404  if (array_key_exists($question_id, $question_points)) {
405  $max_points += $question_points[$question_id]['max_points'];
406  $reached_points += $question_points[$question_id]['reached_points'] ?? 0;
407  } else {
408  $DIC->logger()->crs()->warning('stale question in course objective assignment table id ' . $question_id);
409  }
410  }
411  if (!$max_points) {
412  return false;
413  }
414  return $reached_points >= $objective_data['tst_limit'];
415  }
416 
417  protected static function __updatePassed(int $a_user_id, array $objective_ids): void
418  {
419  global $DIC;
420 
421  $ilDB = $DIC['ilDB'];
422 
423  $passed = array();
424 
425  $query = "SELECT COUNT(t1.crs_id) num,t1.crs_id FROM crs_objectives t1 " .
426  "JOIN crs_objectives t2 WHERE t1.crs_id = t2.crs_id and " .
427  $ilDB->in('t1.objective_id', $objective_ids, false, 'integer') . " " .
428  "GROUP BY t1.crs_id";
429  $res = $ilDB->query($query);
430  $crs_ids = array();
431  while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) {
432  $query = "SELECT COUNT(cs.objective_id) num_passed FROM crs_objective_status cs " .
433  "JOIN crs_objectives co ON cs.objective_id = co.objective_id " .
434  "WHERE crs_id = " . $ilDB->quote($row->crs_id, 'integer') . " " .
435  "AND user_id = " . $ilDB->quote($a_user_id, 'integer') . " ";
436 
437  $user_res = $ilDB->query($query);
438  while ($user_row = $user_res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) {
439  if ((int) $user_row->num_passed === (int) $row->num) {
440  $passed[] = $row->crs_id;
441  }
442  }
443  $crs_ids[(int) $row->crs_id] = (int) $row->crs_id;
444  }
445  if ($passed !== []) {
446  foreach ($passed as $crs_id) {
447  $members = ilCourseParticipants::_getInstanceByObjId($crs_id);
448  $members->updatePassed($a_user_id, true);
449  }
450  }
451 
452  // update tracking status
453  foreach ($crs_ids as $cid) {
454  ilLPStatusWrapper::_updateStatus($cid, $a_user_id);
455  }
456  }
457 }
$res
Definition: ltiservices.php:69
static _getAccomplished(int $a_user_id, int $a_crs_id)
static _getObjectiveIds(int $course_id, bool $a_activated_only=false)
getActiveIdOfUser($user_id="", $anonymous_id="")
Gets the active id of a given user.
hasAccomplishedObjective(int $a_objective_id)
static _readAssignedObjectivesOfQuestion(int $a_question_id)
static __isFullfilled(array $question_points, array $objective_data)
global $DIC
Definition: feed.php:28
static _updateObjectiveStatus(int $a_user_id, array $objectives)
$objectives
static _getSuggested(int $a_user_id, int $a_crs_id, string $a_status=self::IL_OBJECTIVE_STATUS_FINAL)
static _getInstanceByObjId(int $a_obj_id)
$lng
getSuggested(int $a_crs_id, string $a_status=self::IL_OBJECTIVE_STATUS_FINAL)
static getInstanceByRefId(int $ref_id, bool $stop_on_error=true)
get an instance of an Ilias object by reference id
static _readAssignedObjectives(array $a_all_objectives)
removeTestActives($activeIds)
static getInstanceByObjId(int $a_obj_id)
ILIAS TestQuestionPool QuestionInfoService $questioninfo
resetTestForUser(ilObjTest $a_test, int $a_user_id)
static getInstance(int $a_container_id)
static _updateObjectiveResult(int $a_user_id, int $a_active_id, int $a_question_id)
static __updatePassed(int $a_user_id, array $objective_ids)
static getInstance(int $obj_id)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
static getSuggestedQuestions(int $a_usr_id, int $a_crs_id)
static _updateStatus(int $a_obj_id, int $a_usr_id, ?object $a_obj=null, bool $a_percentage=false, bool $a_force_raise=false)