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