ILIAS  release_8 Revision v8.19-1-g4e8f2f9140c
All Data Structures Namespaces Files Functions Variables Modules Pages
class.ilLOUserResults.php
Go to the documentation of this file.
1 <?php
2 
3 declare(strict_types=0);
26 {
27  protected int $course_obj_id;
28  protected int $user_id;
29 
30  public const TYPE_INITIAL = 1;
31  public const TYPE_QUALIFIED = 2;
32 
33  public const STATUS_COMPLETED = 1;
34  public const STATUS_FAILED = 2;
35 
36  protected ilDBInterface $db;
37 
38  public function __construct(int $a_course_obj_id, int $a_user_id)
39  {
40  global $DIC;
41 
42  $this->course_obj_id = $a_course_obj_id;
43  $this->user_id = $a_user_id;
44 
45  $this->db = $DIC->database();
46  }
47 
48  public static function updateResultLimit(int $a_objective_id, int $a_test_type, int $a_limit) : void
49  {
50  global $DIC;
51 
52  $db = $DIC->database();
53  $query = 'UPDATE loc_user_results ' .
54  'SET limit_perc = ' . $db->quote($a_limit, ilDBConstants::T_INTEGER) . ' ' .
55  'WHERE objective_id = ' . $db->quote($a_objective_id, ilDBConstants::T_INTEGER) . ' ' .
56  'AND type = ' . $db->quote($a_test_type, ilDBConstants::T_INTEGER);
57  $db->manipulate($query);
58  }
59 
60  public static function lookupResult(
61  int $a_course_obj_id,
62  int $a_user_id,
63  int $a_objective_id,
64  int $a_tst_type
65  ): array {
66  global $DIC;
67 
68  $ilDB = $DIC->database();
69  $query = 'SELECT * FROM loc_user_results ' .
70  'WHERE user_id = ' . $ilDB->quote($a_user_id, 'integer') . ' ' .
71  'AND course_id = ' . $ilDB->quote($a_course_obj_id, 'integer') . ' ' .
72  'AND objective_id = ' . $ilDB->quote($a_objective_id, 'integer') . ' ' .
73  'AND type = ' . $ilDB->quote($a_tst_type, 'integer');
74  $res = $ilDB->query($query);
75  $ur = array(
76  'status' => self::STATUS_FAILED,
77  'result_perc' => 0,
78  'limit_perc' => 0,
79  'tries' => 0,
80  'is_final' => 0,
81  'has_result' => false
82  );
83  while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) {
84  $ur['status'] = $row->status;
85  $ur['result_perc'] = $row->result_perc;
86  $ur['limit_perc'] = $row->limit_perc;
87  $ur['tries'] = $row->tries;
88  $ur['is_final'] = $row->is_final;
89  $ur['has_result'] = true;
90  }
91  return $ur;
92  }
93 
94  public static function resetFinalByObjective(int $a_objective_id): void
95  {
96  global $DIC;
97 
98  $db = $DIC->database();
99  $query = 'UPDATE loc_user_results ' .
100  'SET is_final = ' . $db->quote(0, 'integer') . ' ' .
101  'WHERE objective_id = ' . $db->quote($a_objective_id, 'integer');
102  $db->manipulate($query);
103  }
104 
105  protected static function isValidType(int $a_type): bool
106  {
107  return in_array($a_type, array(self::TYPE_INITIAL, self::TYPE_QUALIFIED));
108  }
109 
110  protected static function isValidStatus(int $a_status): bool
111  {
112  return in_array($a_status, array(self::STATUS_COMPLETED, self::STATUS_FAILED));
113  }
114 
115  public static function deleteResultsForUser(int $a_user_id): bool
116  {
117  global $DIC;
118 
119  $ilDB = $DIC->database();
120  if (!$a_user_id) {
121  return false;
122  }
123 
124  $ilDB->manipulate("DELETE FROM loc_user_results" .
125  " WHERE user_id = " . $ilDB->quote($a_user_id, "integer"));
126  return true;
127  }
128 
129  public static function deleteResultsForCourse(int $a_course_id): bool
130  {
131  global $DIC;
132 
133  $ilDB = $DIC->database();
134  if (!$a_course_id) {
135  return false;
136  }
137  $ilDB->manipulate("DELETE FROM loc_user_results" .
138  " WHERE course_id = " . $ilDB->quote($a_course_id, "integer"));
139  return true;
140  }
141 
142  public function delete(): void
143  {
144  $query = 'DELETE FROM loc_user_results ' .
145  'WHERE course_id = ' . $this->db->quote($this->course_obj_id, ilDBConstants::T_INTEGER) . ' ' .
146  'AND user_id = ' . $this->db->quote($this->user_id, ilDBConstants::T_INTEGER);
147  $this->db->manipulate($query);
148  }
149 
150  public static function deleteResultsFromLP(
151  int $a_course_id,
152  array $a_user_ids,
153  bool $a_remove_initial,
154  bool $a_remove_qualified,
155  array $a_objective_ids
156  ): bool {
157  global $DIC;
158 
159  $ilDB = $DIC->database();
160  if (!$a_course_id ||
161  $a_user_ids === []) {
162  return false;
163  }
164 
165  $base_sql = "DELETE FROM loc_user_results" .
166  " WHERE course_id = " . $ilDB->quote($a_course_id, "integer") .
167  " AND " . $ilDB->in("user_id", $a_user_ids, false, "integer");
168 
169  if ($a_objective_ids !== []) {
170  $base_sql .= ' AND ' . $ilDB->in('objective_id', $a_objective_ids, false, "integer");
171  }
172 
173  $sql = '';
174  if ($a_remove_initial) {
175  $sql = $base_sql .
176  " AND type = " . $ilDB->quote(self::TYPE_INITIAL, "integer");
177  $ilDB->manipulate($sql);
178  }
179 
180  if ($a_remove_qualified) {
181  $sql = $base_sql .
182  " AND type = " . $ilDB->quote(self::TYPE_QUALIFIED, "integer");
183  $ilDB->manipulate($sql);
184  }
185 
186  if ($a_objective_ids === []) {
187  $ilDB->manipulate($base_sql);
188  }
189  $ilDB->manipulate($sql);
190  return true;
191  }
192 
193  public function saveObjectiveResult(
194  int $a_objective_id,
195  int $a_type,
196  int $a_status,
197  int $a_result_percentage,
198  int $a_limit_percentage,
199  int $a_tries,
200  bool $a_is_final
201  ): bool {
202  if (!self::isValidType($a_type) ||
203  !self::isValidStatus($a_status)) {
204  return false;
205  }
206  $this->db->replace(
207  "loc_user_results",
208  array(
209  "course_id" => array("integer", $this->course_obj_id),
210  "user_id" => array("integer", $this->user_id),
211  "objective_id" => array("integer", $a_objective_id),
212  "type" => array("integer", $a_type)
213  ),
214  array(
215  "status" => array("integer", $a_status),
216  "result_perc" => array("integer", $a_result_percentage),
217  "limit_perc" => array("integer", $a_limit_percentage),
218  "tries" => array("integer", $a_tries),
219  "is_final" => array("integer", $a_is_final),
220  "tstamp" => array("integer", time()),
221  )
222  );
223  return true;
224  }
225 
226  protected function findObjectiveIds(int $a_type = 0, int $a_status = 0, ?bool $a_is_final = null): array
227  {
228  $res = array();
229  $sql = "SELECT objective_id" .
230  " FROM loc_user_results" .
231  " WHERE course_id = " . $this->db->quote($this->course_obj_id, "integer") .
232  " AND user_id = " . $this->db->quote($this->user_id, "integer");
233 
234  if ($this->isValidType($a_type)) {
235  $sql .= " AND type = " . $this->db->quote($a_type, "integer");
236  }
237  if ($this->isValidStatus($a_status)) {
238  $sql .= " AND status = " . $this->db->quote($a_status, "integer");
239  }
240  if ($a_is_final !== null) {
241  $sql .= " AND is_final = " . $this->db->quote($a_is_final, "integer");
242  }
243 
244  $set = $this->db->query($sql);
245  while ($row = $this->db->fetchAssoc($set)) {
246  $res[] = $row["objective_id"];
247  }
248 
249  return $res;
250  }
251 
252  public function getCompletedObjectiveIdsByType(int $a_type): array
253  {
254  return $this->findObjectiveIds($a_type, self::STATUS_COMPLETED);
255  }
256 
260  public function getSuggestedObjectiveIds(): array
261  {
262  return $this->findObjectiveIds(self::TYPE_INITIAL, self::STATUS_FAILED);
263  }
264 
268  public function getCompletedObjectiveIds(): array
269  {
270  $settings = ilLOSettings::getInstanceByObjId($this->course_obj_id);
271 
272  if (!$settings->isInitialTestQualifying() || !$settings->worksWithInitialTest()) {
273  return $this->findObjectiveIds(self::TYPE_QUALIFIED, self::STATUS_COMPLETED);
274  }
275 
276  // status of final final test overwrites initial qualified.
277  $completed = [];
278  if (
279  $settings->isInitialTestQualifying() &&
280  $settings->worksWithInitialTest()
281  ) {
282  $completed_candidates = array_unique(
283  array_merge(
284  $this->findObjectiveIds(self::TYPE_INITIAL, self::STATUS_COMPLETED),
285  $this->findObjectiveIds(self::TYPE_QUALIFIED, self::STATUS_COMPLETED)
286  )
287  );
288  $failed_final = $this->findObjectiveIds(self::TYPE_QUALIFIED, self::STATUS_FAILED);
289 
290  foreach ($completed_candidates as $objective_completed) {
291  if (!in_array($objective_completed, $failed_final)) {
292  $completed[] = $objective_completed;
293  }
294  }
295  return $completed;
296  }
297  return [];
298  }
299 
300  public function getFailedObjectiveIds(bool $a_is_final = true): array
301  {
302  return $this->findObjectiveIds(self::TYPE_QUALIFIED, self::STATUS_FAILED, $a_is_final);
303  }
304 
305  public function getCourseResultsForUserPresentation(): array
306  {
307  $res = [];
308  $settings = ilLOSettings::getInstanceByObjId($this->course_obj_id);
309 
310  $set = $this->db->query("SELECT *" .
311  " FROM loc_user_results" .
312  " WHERE course_id = " . $this->db->quote($this->course_obj_id, "integer") .
313  " AND user_id = " . $this->db->quote($this->user_id, "integer"));
314  while ($row = $this->db->fetchAssoc($set)) {
315  // do not read initial test results, if disabled.
316  if (
317  $row['type'] == self::TYPE_INITIAL &&
318  !$settings->worksWithInitialTest()
319  ) {
320  continue;
321  }
322 
323  $objective_id = (int) $row["objective_id"];
324  $type = (int) $row["type"];
325  unset($row["objective_id"]);
326  unset($row["type"]);
327  $res[$objective_id][$type] = $row;
328  }
329  return $res;
330  }
331 
335  public static function getObjectiveStatusForLP(int $a_user_id, int $a_obj_id, array $a_objective_ids): array
336  {
337  global $DIC;
338 
339  $ilDB = $DIC->database();
340 
341  // are initital test(s) qualifying?
342  $lo_set = ilLOSettings::getInstanceByObjId($a_obj_id);
343  $initial_qualifying = $lo_set->isInitialTestQualifying();
344 
345  // this method returns LP status codes!
346 
347  $res = array();
348 
349  $sql = "SELECT lor.objective_id, lor.user_id, lor.status, lor.is_final" .
350  " FROM loc_user_results lor" .
351  " JOIN crs_objectives cobj ON (cobj.objective_id = lor.objective_id)" .
352  " WHERE " . $ilDB->in("lor.objective_id", $a_objective_ids, false, "integer");
353  if (!$initial_qualifying) {
354  $sql .= " AND lor.type = " . $ilDB->quote(self::TYPE_QUALIFIED, "integer");
355  }
356  $sql .= " AND lor.user_id = " . $ilDB->quote($a_user_id, "integer") .
357  " AND cobj.active = " . $ilDB->quote(1, "integer") .
358  " ORDER BY lor.type"; // qualified must come last!
359  $set = $ilDB->query($sql);
360  while ($row = $ilDB->fetchAssoc($set)) {
361  switch ($row["status"]) {
362  case self::STATUS_FAILED:
363  if ($row["is_final"]) {
365  } else {
366  // #15379
368  }
369  break;
370 
371  case self::STATUS_COMPLETED:
373  break;
374 
375  default:
376  continue 2;
377  }
378 
379  // if both initial and qualified, qualified will overwrite initial
380  $res[(int) $row["objective_id"]] = $status;
381  }
382  return $res;
383  }
384 
388  public static function getSummarizedObjectiveStatusForLP(
389  int $a_obj_id,
390  array $a_objective_ids,
391  int $a_user_id = 0
392  ) {
393  global $DIC;
394 
395  $ilDB = $DIC->database();
396  // change event is NOT parsed here!
397  // are initital test(s) qualifying?
398  $lo_set = ilLOSettings::getInstanceByObjId($a_obj_id);
399  $initial_qualifying = $lo_set->isInitialTestQualifying();
400 
401  // this method returns LP status codes!
402 
403  $res = $tmp_completed = array();
404 
405  $sql = "SELECT lor.objective_id, lor.user_id, lor.status, lor.type, lor.is_final" .
406  " FROM loc_user_results lor" .
407  " JOIN crs_objectives cobj ON (cobj.objective_id = lor.objective_id)" .
408  " WHERE " . $ilDB->in("lor.objective_id", $a_objective_ids, false, "integer") .
409  " AND cobj.active = " . $ilDB->quote(1, "integer");
410  if (!$initial_qualifying) {
411  $sql .= " AND lor.type = " . $ilDB->quote(self::TYPE_QUALIFIED, "integer");
412  }
413  if ($a_user_id) {
414  $sql .= " AND lor.user_id = " . $ilDB->quote($a_user_id, "integer");
415  }
416  $sql .= " ORDER BY lor.type DESC"; // qualified must come first!
417  $set = $ilDB->query($sql);
418 
419  $has_final_result = array();
420  while ($row = $ilDB->fetchAssoc($set)) {
421  if ($row['type'] == self::TYPE_QUALIFIED) {
422  $has_final_result[$row['objective_id']] = $row['user_id'];
423  }
424 
425  $user_id = (int) $row["user_id"];
426  $status = (int) $row["status"];
427 
428  // initial tests only count if no qualified test
429  if (
430  $row["type"] == self::TYPE_INITIAL &&
431  in_array($row['user_id'], (array) ($has_final_result[(int) $row['objective_id']] ?? []))
432  ) {
433  continue;
434  }
435 
436  // user did do something
438 
439  switch ($status) {
440  case self::STATUS_COMPLETED:
441  $tmp_completed[$user_id] = ($tmp_completed[$user_id] ?? 0) + 1;
442  break;
443 
444  case self::STATUS_FAILED:
445  if ($row["is_final"]) {
446  // object is failed when at least 1 objective is failed without any tries left
448  }
449  break;
450  }
451  }
452 
453  $all_nr = count($a_objective_ids);
454  foreach ($tmp_completed as $user_id => $counter) {
455  // if used as precondition object should be completed ASAP, status can be lost on subsequent tries
456  if ($counter == $all_nr) {
458  }
459  }
460 
461  if ($a_user_id) {
462  return isset($res[$a_user_id]) ? (int) $res[$a_user_id] : null;
463  } else {
464  return $res;
465  }
466  }
467 
468  public static function hasResults(int $a_container_id, int $a_user_id): bool
469  {
470  global $DIC;
471 
472  $ilDB = $DIC->database();
473  $query = 'SELECT objective_id FROM loc_user_results ' .
474  'WHERE course_id = ' . $ilDB->quote($a_container_id, 'integer') . ' ' .
475  'AND user_id = ' . $ilDB->quote($a_user_id, 'integer');
476 
477  $res = $ilDB->query($query);
478  while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) {
479  return true;
480  }
481  return false;
482  }
483 
487  public static function getCompletionsOfUser(int $a_user_id, int $a_from_ts, int $a_to_ts): array
488  {
489  global $DIC;
490 
491  $ilDB = $DIC->database();
492  $res = [];
493  $sql = "SELECT lor.objective_id, lor.user_id, lor.status, lor.is_final, lor.tstamp, lor.course_id, cobj.title" .
494  " FROM loc_user_results lor" .
495  " JOIN crs_objectives cobj ON (cobj.objective_id = lor.objective_id)" .
496  " WHERE lor.user_id = " . $ilDB->quote($a_user_id, "integer") .
497  " AND lor.type = " . $ilDB->quote(self::TYPE_QUALIFIED, "integer") .
498  " AND lor.tstamp >= " . $ilDB->quote($a_from_ts, "integer") .
499  " AND lor.tstamp <= " . $ilDB->quote($a_to_ts, "integer") .
500  " AND lor.status = " . $ilDB->quote(self::STATUS_COMPLETED, "integer");
501 
502  $set = $ilDB->query($sql);
503  while ($row = $ilDB->fetchAssoc($set)) {
504  $res[(int) $row["objective_id"]] = $row;
505  }
506  return $res;
507  }
508 }
const LP_STATUS_COMPLETED_NUM
$res
Definition: ltiservices.php:69
array $settings
Setting values (LTI parameters, custom parameters and local parameters).
Definition: System.php:200
getFailedObjectiveIds(bool $a_is_final=true)
static getSummarizedObjectiveStatusForLP(int $a_obj_id, array $a_objective_ids, int $a_user_id=0)
static hasResults(int $a_container_id, int $a_user_id)
$type
static updateResultLimit(int $a_objective_id, int $a_test_type, int $a_limit)
getCompletedObjectiveIdsByType(int $a_type)
const LP_STATUS_IN_PROGRESS_NUM
static lookupResult(int $a_course_obj_id, int $a_user_id, int $a_objective_id, int $a_tst_type)
static isValidStatus(int $a_status)
static getCompletionsOfUser(int $a_user_id, int $a_from_ts, int $a_to_ts)
Get completed learning objectives for user and time frame.
quote($value, string $type)
static getObjectiveStatusForLP(int $a_user_id, int $a_obj_id, array $a_objective_ids)
getSuggestedObjectiveIds()
Get all objectives where the user failed the initial test.
getCompletedObjectiveIds()
Get all objectives where the user completed the qualified test.
global $DIC
Definition: feed.php:28
static deleteResultsFromLP(int $a_course_id, array $a_user_ids, bool $a_remove_initial, bool $a_remove_qualified, array $a_objective_ids)
static resetFinalByObjective(int $a_objective_id)
static isValidType(int $a_type)
$query
saveObjectiveResult(int $a_objective_id, int $a_type, int $a_status, int $a_result_percentage, int $a_limit_percentage, int $a_tries, bool $a_is_final)
static getInstanceByObjId(int $a_obj_id)
findObjectiveIds(int $a_type=0, int $a_status=0, ?bool $a_is_final=null)
static deleteResultsForUser(int $a_user_id)
manipulate(string $query)
Run a (write) Query on the database.
__construct(int $a_course_obj_id, int $a_user_id)
const LP_STATUS_FAILED_NUM
static deleteResultsForCourse(int $a_course_id)