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