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