ILIAS  release_9 Revision v9.13-25-g2c18ec4c24f
class.ilPRGAssignmentDBRepository.php
Go to the documentation of this file.
1 <?php
2 
19 declare(strict_types=1);
20 
26 {
27  public const ASSIGNMENT_TABLE = 'prg_usr_assignments';
28  public const ASSIGNMENT_FIELD_ID = 'id';
29  public const ASSIGNMENT_FIELD_USR_ID = 'usr_id';
30  public const ASSIGNMENT_FIELD_ROOT_PRG_ID = 'root_prg_id';
31  public const ASSIGNMENT_FIELD_LAST_CHANGE = 'last_change';
32  public const ASSIGNMENT_FIELD_LAST_CHANGE_BY = 'last_change_by';
33  public const ASSIGNMENT_FIELD_RESTART_DATE = 'restart_date';
34  public const ASSIGNMENT_FIELD_RESTARTED_ASSIGNMENT_ID = 'restarted_assignment_id';
35  public const ASSIGNMENT_FIELD_RESTART_MAIL = 'restart_mail_send';
36  public const ASSIGNMENT_FIELD_MANUALLY_ASSIGNED = 'assigned_manually';
37 
38  public const PROGRESS_TABLE = 'prg_usr_progress';
39  public const PROGRESS_FIELD_ASSIGNMENT_ID = 'assignment_id';
40  public const PROGRESS_FIELD_USR_ID = 'usr_id';
41  public const PROGRESS_FIELD_PRG_ID = 'prg_id';
42  public const PROGRESS_FIELD_POINTS = 'points';
43  public const PROGRESS_FIELD_POINTS_CUR = 'points_cur';
44  public const PROGRESS_FIELD_STATUS = 'status';
45  public const PROGRESS_FIELD_COMPLETION_BY = 'completion_by';
46  public const PROGRESS_FIELD_ASSIGNMENT_DATE = 'assignment_date';
47  public const PROGRESS_FIELD_LAST_CHANGE = 'last_change'; //'p_' .
48  public const PROGRESS_FIELD_LAST_CHANGE_BY = 'last_change_by'; //'p_' .
49  public const PROGRESS_FIELD_COMPLETION_DATE = 'completion_date';
50  public const PROGRESS_FIELD_DEADLINE = 'deadline';
51  public const PROGRESS_FIELD_VQ_DATE = 'vq_date';
52  public const PROGRESS_FIELD_INVALIDATED = 'invalidated';
53  public const PROGRESS_FIELD_MAIL_SENT_RISKYTOFAIL = 'sent_mail_risky_to_fail';
54  public const PROGRESS_FIELD_MAIL_SENT_WILLEXPIRE = 'sent_mail_expires';
55  public const PROGRESS_FIELD_IS_INDIVIDUAL = 'individual';
56 
57  public const DATE_FORMAT_ENDOFDAY = 'Y-m-d 23:59:59';
58 
59  protected array $user_data_fields;
60 
64  protected array $progresses = [];
65 
66  public function __construct(
67  protected ilDBInterface $db,
68  protected ilTree $tree,
69  protected ilStudyProgrammeSettingsRepository $settings_repo,
70  protected PRGEventsDelayed $events,
71  ilExportFieldsInfo $user_field_info
72  ) {
73  $this->user_data_fields = array_merge(
74  array_keys($user_field_info->getSelectableFieldsInfo()),
76  );
77  }
78 
79  public function getDashboardInstancesforUser(int $usr_id): array
80  {
81  $assignments = $this->getForUser($usr_id);
82  //TODO: decide, which ones are relevant for the dashboard
83  return $assignments;
84  }
85 
86  public function createFor(
87  int $prg_obj_id,
88  int $usr_id,
89  int $assigning_usr_id
90  ): ilPRGAssignment {
91  $manually = false;
92  if (ilObject::_lookupType($assigning_usr_id) === "usr") {
93  $manually = true;
94  }
95  $row = [
96  self::ASSIGNMENT_FIELD_ID => $this->nextId(),
97  self::ASSIGNMENT_FIELD_USR_ID => $usr_id,
98  self::ASSIGNMENT_FIELD_ROOT_PRG_ID => $prg_obj_id,
99  self::ASSIGNMENT_FIELD_LAST_CHANGE_BY => $assigning_usr_id,
100  self::ASSIGNMENT_FIELD_LAST_CHANGE => ilUtil::now(),
101  self::ASSIGNMENT_FIELD_RESTART_DATE => null,
102  self::ASSIGNMENT_FIELD_RESTARTED_ASSIGNMENT_ID => ilPRGAssignment::NO_RESTARTED_ASSIGNMENT,
103  self::ASSIGNMENT_FIELD_MANUALLY_ASSIGNED => $manually
104  ];
105  $this->insertAssignmentRowDB($row);
106  $this->progresses = [];
107 
108  $user_fields = array_filter(
109  $this->user_data_fields,
110  fn($f) => !str_starts_with($f, 'udf_') && $f !== 'org_units'
111  );
112 
113  $query = 'SELECT '
114  . implode(',', $user_fields)
115  . PHP_EOL
116  . 'FROM usr_data WHERE usr_id = ' . $this->db->quote($usr_id, 'integer');
117 
118  $res = $this->db->query($query);
119  $row = array_merge($row, $this->db->fetchAssoc($res));
120 
121  $ass = $this->assignmentByRow($row);
122  return $ass;
123  }
124 
125  public function store(ilPRGAssignment $assignment): void
126  {
127  $row = [
128  self::ASSIGNMENT_FIELD_ID => $assignment->getId(),
129  self::ASSIGNMENT_FIELD_USR_ID => $assignment->getUserId(),
130  self::ASSIGNMENT_FIELD_ROOT_PRG_ID => $assignment->getRootId(),
131  self::ASSIGNMENT_FIELD_LAST_CHANGE_BY => $assignment->getLastChangeBy(),
132  self::ASSIGNMENT_FIELD_LAST_CHANGE => $assignment->getLastChange()->format(ilPRGAssignment::DATE_TIME_FORMAT),
133  self::ASSIGNMENT_FIELD_RESTART_DATE => $assignment->getRestartDate() ? $assignment->getRestartDate()->format(ilPRGAssignment::DATE_TIME_FORMAT) : null,
134  self::ASSIGNMENT_FIELD_RESTARTED_ASSIGNMENT_ID => $assignment->getRestartedAssignmentId(),
135  self::ASSIGNMENT_FIELD_MANUALLY_ASSIGNED => $assignment->isManuallyAssigned()
136  ];
137  $this->updateAssignmentRowDB($row);
138  foreach ($assignment->getProgresses() as $pgs) {
139  $this->storeProgressRow(
140  $assignment->getId(),
141  $assignment->getUserId(),
142  $pgs
143  );
144  }
145 
146  $this->events->raiseCollected();
147  }
148 
149  public function delete(ilPRGAssignment $assignment): void
150  {
151  $ass_id = $assignment->getId();
152  $query = 'DELETE FROM ' . self::ASSIGNMENT_TABLE . PHP_EOL
153  . 'WHERE ' . self::ASSIGNMENT_FIELD_ID . ' = ' . $ass_id;
154  $this->db->manipulate($query);
155 
156  $query = 'DELETE FROM ' . self::PROGRESS_TABLE . PHP_EOL
157  . 'WHERE ' . self::PROGRESS_FIELD_ASSIGNMENT_ID . ' = ' . $ass_id;
158  $this->db->manipulate($query);
159  }
160 
161  public function deleteAllAssignmentsForProgrammeId(int $prg_obj_id): void
162  {
163  $query = 'DELETE FROM ' . self::ASSIGNMENT_TABLE . PHP_EOL
164  . 'WHERE ' . self::ASSIGNMENT_FIELD_ROOT_PRG_ID . '=' . $this->db->quote($prg_obj_id, 'integer');
165  $this->db->manipulate($query);
167  }
168  protected function deleteAllOrphanedProgresses(): void
169  {
170  $query = 'DELETE FROM ' . self::PROGRESS_TABLE . PHP_EOL
171  . 'WHERE ' . self::PROGRESS_FIELD_ASSIGNMENT_ID . PHP_EOL
172  . 'NOT IN (' . PHP_EOL
173  . 'SELECT ' . $this->db->quoteIdentifier(self::ASSIGNMENT_FIELD_ID)
174  . ' FROM ' . $this->db->quoteIdentifier(self::ASSIGNMENT_TABLE) . PHP_EOL
175  . ');' . PHP_EOL;
176  $this->db->manipulate($query);
177  }
178 
179  public function get(int $id): ilPRGAssignment
180  {
181  $ass = $this->read([
182  'ass.' . self::ASSIGNMENT_FIELD_ID . ' = ' . $this->db->quote($id, 'integer')
183  ]);
184 
185  return $ass->current();
186  }
187 
188  public function getForUser(int $usr_id): array
189  {
190  $assignments = array_filter(iterator_to_array(
191  $this->read([
192  'ass.' . self::ASSIGNMENT_FIELD_USR_ID . ' = ' . $this->db->quote($usr_id, 'integer')
193  ])
194  ));
195  return $assignments;
196  }
197 
198  public function getForUserOnNode(int $usr_id, int $root_prg_obj_id): array
199  {
200  $assignments = array_filter(iterator_to_array(
201  $this->read([
202  'ass.' . self::ASSIGNMENT_FIELD_USR_ID . ' = ' . $this->db->quote($usr_id, 'integer'),
203  self::ASSIGNMENT_FIELD_ROOT_PRG_ID . ' = ' . $this->db->quote($root_prg_obj_id, 'integer')
204  ])
205  ));
206  return $assignments;
207  }
208 
209  public function getAllForNodeIsContained(
210  int $prg_obj_id,
211  array $user_filter = null,
212  ilPRGAssignmentFilter $custom_filters = null
213  ): array {
214  $conditions = [
215  'pgs.' . self::PROGRESS_FIELD_PRG_ID . ' = ' . $this->db->quote($prg_obj_id, 'integer')
216  ];
217  if ($user_filter) {
218  $conditions[] = $this->db->in('ass.' . self::ASSIGNMENT_FIELD_USR_ID, $user_filter, false, 'integer');
219  }
220  if ($custom_filters) {
221  $conditions = array_merge($conditions, $custom_filters->toConditions());
222  }
223 
224  $assignments = array_filter(iterator_to_array(
225  $this->read($conditions)
226  ));
227  return $assignments;
228  }
229 
230  public function countAllForNodeIsContained(
231  int $prg_obj_id,
232  array $user_filter = null,
233  ilPRGAssignmentFilter $custom_filters = null
234  ): int {
235  $conditions = [
236  'pgs.' . self::PROGRESS_FIELD_PRG_ID . ' = ' . $this->db->quote($prg_obj_id, 'integer')
237  ];
238  if ($user_filter) {
239  $conditions[] = $this->db->in('ass.' . self::ASSIGNMENT_FIELD_USR_ID, $user_filter, false, 'integer');
240  }
241  if ($custom_filters) {
242  $conditions = array_merge($conditions, $custom_filters->toConditions());
243  }
244  return $this->count($conditions);
245  }
246 
247  public function getAllForSpecificNode(int $prg_obj_id, array $user_filter = null): array
248  {
249  $conditions = [
250  self::ASSIGNMENT_FIELD_ROOT_PRG_ID . ' = ' . $this->db->quote($prg_obj_id, 'integer')
251  ];
252  if ($user_filter) {
253  $conditions[] = $this->db->in('ass.' . self::ASSIGNMENT_FIELD_USR_ID, $user_filter, false, 'integer');
254  }
255 
256  $assignments = array_filter(iterator_to_array(
257  $this->read($conditions)
258  ));
259  return $assignments;
260  }
261 
262  public function getPassedDeadline(\DateTimeImmutable $deadline): array
263  {
264  $deadline = $this->db->quote(
265  $deadline->format(ilPRGProgress::DATE_FORMAT),
266  'text'
267  );
268 
269  $conditions = [
270  $this->db->in(
271  self::PROGRESS_FIELD_STATUS,
272  [
275  ],
276  false,
277  'integer'
278  ),
279  self::PROGRESS_FIELD_DEADLINE . ' IS NOT NULL',
280  self::PROGRESS_FIELD_DEADLINE . ' < ' . $deadline
281  ];
282 
283  $assignments = array_filter(iterator_to_array(
284  $this->read($conditions)
285  ));
286  return $assignments;
287  }
288 
289  public function getAboutToExpire(
290  array $programmes_and_due,
291  bool $discard_formerly_notified = true
292  ): array {
293  $ret = [];
294  if (count($programmes_and_due) == 0) {
295  return $ret;
296  }
297 
298  $where = [];
299  foreach ($programmes_and_due as $prg_obj_id => $due) {
300  $due = $due->format(self::DATE_FORMAT_ENDOFDAY);
301 
302  $where_clause = '('
303  . self::PROGRESS_FIELD_VQ_DATE . '<=' . $this->db->quote($due, 'text')
304  . ' AND (pgs.' . self::PROGRESS_FIELD_PRG_ID . '=' . $prg_obj_id
305  . ' OR ' . self::ASSIGNMENT_FIELD_ROOT_PRG_ID . '=' . $prg_obj_id . ')';
306 
307  if ($discard_formerly_notified) {
308  $where_clause .= ' AND ' . self::PROGRESS_FIELD_MAIL_SENT_WILLEXPIRE . ' IS NULL';
309  }
310 
311  $where_clause .= ')';
312  $where[] = $where_clause;
313  }
314 
315  $conditions = [
316  implode(' OR ', $where)
317  ];
318  $assignments = array_filter(iterator_to_array(
319  $this->read($conditions)
320  ));
321  return $assignments;
322  }
323 
324  public function getExpiredAndNotInvalidated(): array
325  {
326  $now = (new \DateTimeImmutable())->format(self::DATE_FORMAT_ENDOFDAY);
327  $conditions = [
328  $this->db->in(
329  self::PROGRESS_FIELD_STATUS,
330  [
333  ],
334  false,
335  'integer'
336  ),
337  self::PROGRESS_FIELD_VQ_DATE . ' IS NOT NULL',
338  self::PROGRESS_FIELD_VQ_DATE . ' < ' . $this->db->quote($now, 'text'),
339  self::PROGRESS_FIELD_INVALIDATED . ' = 0 ',
340  ];
341 
342  $assignments = array_filter(iterator_to_array(
343  $this->read($conditions)
344  ));
345  return $assignments;
346  }
347 
348  public function getRiskyToFail(
349  array $programmes_and_due,
350  bool $discard_formerly_notified = true
351  ): array {
352  $ret = [];
353  if (count($programmes_and_due) == 0) {
354  return $ret;
355  }
356 
357  $where = [];
358  foreach ($programmes_and_due as $prg_obj_id => $due) {
359  $due = $due->format(ilPRGProgress::DATE_FORMAT);
360 
361  $where_clause = '('
362  . self::PROGRESS_FIELD_DEADLINE . '<=' . $this->db->quote($due, 'text')
363  . 'AND (pgs.' . self::PROGRESS_FIELD_PRG_ID . '=' . $prg_obj_id
364  . ' OR ' . self::ASSIGNMENT_FIELD_ROOT_PRG_ID . '=' . $prg_obj_id . ')'
365  . ' AND ' . $this->db->in(
366  self::PROGRESS_FIELD_STATUS,
367  [
371  ],
372  true,
373  'integer'
374  );
375 
376  if ($discard_formerly_notified) {
377  $where_clause .= ' AND ' . self::PROGRESS_FIELD_MAIL_SENT_RISKYTOFAIL . ' IS NULL';
378  }
379 
380  $where_clause .= ')';
381  $where[] = $where_clause;
382  }
383 
384  $conditions = [
385  implode(' OR ', $where)
386  ];
387  $assignments = array_filter(iterator_to_array(
388  $this->read($conditions)
389  ));
390  return $assignments;
391  }
392 
393  protected function query($filter): ilDBStatement
394  {
395  $user_fields_without_udf = array_filter(
396  $this->user_data_fields,
397  static fn($field) => !str_starts_with($field, 'udf_') && $field !== 'org_units'
398  );
399 
400  $q = 'SELECT'
401  . ' ass.' . self::ASSIGNMENT_FIELD_ID . ' AS ' . self::ASSIGNMENT_FIELD_ID
402  . ', ass.' . self::ASSIGNMENT_FIELD_USR_ID . ' AS ' . self::ASSIGNMENT_FIELD_USR_ID
403  . ',' . self::ASSIGNMENT_FIELD_ROOT_PRG_ID
404  . ', ass.' . self::ASSIGNMENT_FIELD_LAST_CHANGE
405  . ', ass.' . self::ASSIGNMENT_FIELD_LAST_CHANGE_BY
406  . ',' . self::ASSIGNMENT_FIELD_RESTART_DATE
407  . ',' . self::ASSIGNMENT_FIELD_RESTARTED_ASSIGNMENT_ID
408  . ',' . self::ASSIGNMENT_FIELD_MANUALLY_ASSIGNED
409  . ',' . self::PROGRESS_FIELD_PRG_ID
410  . ',' . self::PROGRESS_FIELD_POINTS
411  . ',' . self::PROGRESS_FIELD_POINTS_CUR
412  . ',' . self::PROGRESS_FIELD_STATUS
413  . ',' . self::PROGRESS_FIELD_COMPLETION_BY
414  . ',' . self::PROGRESS_FIELD_ASSIGNMENT_DATE
415  . ', pgs.' . self::PROGRESS_FIELD_LAST_CHANGE . ' AS p_' . self::PROGRESS_FIELD_LAST_CHANGE
416  . ', pgs.' . self::PROGRESS_FIELD_LAST_CHANGE_BY . ' AS p_' . self::PROGRESS_FIELD_LAST_CHANGE_BY
417  . ',' . self::PROGRESS_FIELD_COMPLETION_DATE
418  . ',' . self::PROGRESS_FIELD_DEADLINE
419  . ',' . self::PROGRESS_FIELD_VQ_DATE
420  . ',' . self::PROGRESS_FIELD_INVALIDATED
421  . ',' . self::PROGRESS_FIELD_MAIL_SENT_RISKYTOFAIL
422  . ',' . self::PROGRESS_FIELD_MAIL_SENT_WILLEXPIRE
423  . ',' . self::PROGRESS_FIELD_IS_INDIVIDUAL
424 
425  . ', ' . implode(', ', $user_fields_without_udf)
426 
427  . ' FROM ' . self::ASSIGNMENT_TABLE . ' ass '
428  . ' JOIN ' . self::PROGRESS_TABLE . ' pgs '
429  . ' ON ass.' . self::ASSIGNMENT_FIELD_ID . ' = pgs.' . self::PROGRESS_FIELD_ASSIGNMENT_ID
430 
431 
432  . ' JOIN usr_data memberdata ON ass.usr_id = memberdata.usr_id '
433 
434  . ' WHERE TRUE AND ';
435  $q = $q . implode(' AND ', $filter);
436  $q = $q . ' ORDER BY assignment_id, ass.usr_id';
437 
438  $res = $this->db->query($q);
439  return $res;
440  }
441 
442  protected function nextId()
443  {
444  return $this->db->nextId(self::ASSIGNMENT_TABLE);
445  }
446 
447  protected function count(array $filter): int
448  {
449  $res = $this->query($filter);
450  return $this->db->numRows($res);
451  }
452 
453  protected function read(array $filter): Generator
454  {
455  $res = $this->query($filter);
456 
457  $current_ass = -1;
458  $ass = null;
459 
460  while ($row = $this->db->fetchAssoc($res)) {
461  if ($row[self::ASSIGNMENT_FIELD_ID] !== $current_ass) {
462  $current_ass = $row[self::ASSIGNMENT_FIELD_ID];
463  if (!is_null($ass)) {
464  yield $ass;
465  }
466  $this->progresses = $this->prebuildProgressesForAssingment((int) $row[self::ASSIGNMENT_FIELD_ID]);
467  $ass = $this->assignmentByRow($row); //amend all progresses based on tree
468  }
469  }
470 
471  yield $ass;
472  }
473 
474  protected function prebuildProgressesForAssingment(int $assignment_id): array
475  {
476  $q = 'SELECT * FROM ' . self::PROGRESS_TABLE
477  . ' WHERE ' . self::PROGRESS_FIELD_ASSIGNMENT_ID . ' = ' . $assignment_id;
478  $res = $this->db->query($q);
479  while ($row = $this->db->fetchAssoc($res)) {
480  $ret[$row[self::PROGRESS_FIELD_PRG_ID]] = $this->buildProgressByRow($row);
481  }
482  return $ret;
483  }
484 
485  protected function assignmentByRow(array $row): ilPRGAssignment
486  {
487  $ass = new ilPRGAssignment(
488  (int) $row[self::ASSIGNMENT_FIELD_ID],
489  (int) $row[self::ASSIGNMENT_FIELD_USR_ID]
490  );
491  $ass = $ass
492  ->withEvents($this->events)
493  ->withLastChange(
494  (int) $row[self::ASSIGNMENT_FIELD_LAST_CHANGE_BY],
495  \DateTimeImmutable::createFromFormat(
497  $row[self::ASSIGNMENT_FIELD_LAST_CHANGE]
498  )
499  )
500  ->withRestarted(
501  (int) $row[self::ASSIGNMENT_FIELD_RESTARTED_ASSIGNMENT_ID],
502  $row[self::ASSIGNMENT_FIELD_RESTART_DATE] ?
503  \DateTimeImmutable::createFromFormat(ilPRGAssignment::DATE_TIME_FORMAT, $row[self::ASSIGNMENT_FIELD_RESTART_DATE]) :
504  null
505  )
506  ->withManuallyAssigned((bool) $row[self::ASSIGNMENT_FIELD_MANUALLY_ASSIGNED]);
507 
508 
509  $root_pgs_id = (int) $row[self::ASSIGNMENT_FIELD_ROOT_PRG_ID];
510  $pgs = $this->buildProgressTreeFor($root_pgs_id);
511 
512  $user_information = $this->buildUserInformation($row);
513 
514  $ass = $ass
515  ->withProgressTree($pgs)
516  ->withUserInformation($user_information);
517  return $ass;
518  }
519 
520  protected function buildProgressTreeFor(int $node_obj_id): ilPRGProgress
521  {
522  $children = array_filter(
523  $this->tree->getChilds($this->getRefIdFor($node_obj_id)),
524  fn($c) => in_array($c['type'], ['prg', 'prgr']),
525  );
526  $children = array_map(
527  fn($c) => $c['type'] === 'prg' ? (int) $c['obj_id'] : ilContainerReference::_lookupTargetId((int) $c['obj_id']),
528  $children
529  );
530 
531  $pgss = [];
532  foreach ($children as $child_obj_id) {
533  $pgss[] = $this->buildProgressTreeFor($child_obj_id);
534  }
535 
536  if (!array_key_exists($node_obj_id, $this->progresses)) {
537  $pgs = new ilPRGProgress((int) $node_obj_id);
538  } else {
539  $pgs = $this->progresses[$node_obj_id];
540  }
541  $pgs->setSubnodes($pgss);
542  return $pgs;
543  }
544 
545  protected function getRefIdFor(int $obj_id): int
546  {
547  $refs = ilObject::_getAllReferences($obj_id);
548  if (count($refs) < 1) {
549  throw new ilException("Could not find ref_id for programme with obj_id $obj_id");
550  }
551  return (int) array_shift($refs);
552  }
553  protected function getObjIdFor(int $ref_id): int
554  {
555  return (int) ilObject::_lookupObjectId($ref_id);
556  }
557 
558  protected function buildProgressByRow(array $row): ilPRGProgress
559  {
560  $pgs = new ilPRGProgress(
561  (int) $row[self::PROGRESS_FIELD_PRG_ID],
562  (int) $row[self::PROGRESS_FIELD_STATUS]
563  );
564 
565  $pgs = $pgs
566  ->withAmountOfPoints((int) $row[self::PROGRESS_FIELD_POINTS])
567  ->withCurrentAmountOfPoints((int) $row[self::PROGRESS_FIELD_POINTS_CUR])
568  ->withAssignmentDate(
569  $row[self::PROGRESS_FIELD_ASSIGNMENT_DATE] ?
570  \DateTimeImmutable::createFromFormat(ilPRGProgress::DATE_TIME_FORMAT, $row[self::PROGRESS_FIELD_ASSIGNMENT_DATE]) :
571  null
572  )
573  ->withDeadline(
574  $row[self::PROGRESS_FIELD_DEADLINE] ?
575  \DateTimeImmutable::createFromFormat(ilPRGProgress::DATE_FORMAT, $row[self::PROGRESS_FIELD_DEADLINE]) :
576  null
577  )
578  ->withCompletion(
579  (int) $row[self::PROGRESS_FIELD_COMPLETION_BY],
580  $row[self::PROGRESS_FIELD_COMPLETION_DATE] ?
581  \DateTimeImmutable::createFromFormat(ilPRGProgress::DATE_TIME_FORMAT, $row[self::PROGRESS_FIELD_COMPLETION_DATE]) :
582  null
583  )
584  ->withLastChange(
585  (int) $row[self::PROGRESS_FIELD_LAST_CHANGE_BY],
586  $row[self::PROGRESS_FIELD_LAST_CHANGE] ?
587  \DateTimeImmutable::createFromFormat(ilPRGProgress::DATE_TIME_FORMAT, $row[self::PROGRESS_FIELD_LAST_CHANGE]) :
588  null
589  )
590  ->withValidityOfQualification(
591  $row[self::PROGRESS_FIELD_VQ_DATE] ?
592  \DateTimeImmutable::createFromFormat(ilPRGProgress::DATE_TIME_FORMAT, $row[self::PROGRESS_FIELD_VQ_DATE]) :
593  null
594  )
595  ->withIndividualModifications((bool) $row[self::PROGRESS_FIELD_IS_INDIVIDUAL])
596  ->withInvalidated((bool) $row[self::PROGRESS_FIELD_INVALIDATED]);
597 
598  return $pgs;
599  }
600 
604  protected function interimOrguLookup(int $usr_id): string
605  {
607  $orgus = array_values($orgu_repo->findAllUserAssingmentsByUserIds([$usr_id]));
608  if ($orgus) {
609  $orgu_ref_ids = array_map(
610  fn($orgu_assignment) => $orgu_assignment->getOrguId(),
611  $orgus[0]
612  );
613  $orgus = array_map(
614  fn($orgu_ref_id) => ilObject::_lookupTitle(ilObject::_lookupObjId($orgu_ref_id)),
615  $orgu_ref_ids
616  );
617  }
618  return implode(', ', $orgus);
619  }
620 
621  protected function buildUserInformation(array $row): ilPRGUserInformation
622  {
623  $udf_data = new ilUserDefinedData((int) $row[self::ASSIGNMENT_FIELD_USR_ID]);
624  $user_data_values = [];
625  foreach ($this->user_data_fields as $field) {
626  switch ($field) {
627  case 'active':
628  $user_data_values[$field] = (bool) $row[$field];
629  break;
630  case 'org_units':
631  //$user_data_values[$field] = ilObjUser::lookupOrgUnitsRepresentation((int) $row[self::ASSIGNMENT_FIELD_USR_ID]);
632  $user_data_values[$field] = $this->interimOrguLookup((int) $row[self::ASSIGNMENT_FIELD_USR_ID]);
633  break;
634  case str_starts_with($field, 'udf_'):
635  $udf_field_id = str_replace('udf_', 'f_', $field);
636  $user_data_values[$field] = $udf_data->get($udf_field_id);
637  break;
638  default:
639  $user_data_values[$field] = $row[$field];
640  }
641  }
642  return new ilPRGUserInformation(
643  $user_data_values
644  );
645  }
646 
647  protected function insertAssignmentRowDB(array $row)
648  {
649  $this->db->insert(
650  self::ASSIGNMENT_TABLE,
651  [
652  self::ASSIGNMENT_FIELD_ID => ['integer', $row[self::ASSIGNMENT_FIELD_ID]]
653  , self::ASSIGNMENT_FIELD_USR_ID => ['integer', $row[self::ASSIGNMENT_FIELD_USR_ID]]
654  , self::ASSIGNMENT_FIELD_ROOT_PRG_ID => ['integer', $row[self::ASSIGNMENT_FIELD_ROOT_PRG_ID]]
655  , self::ASSIGNMENT_FIELD_LAST_CHANGE => ['text', $row[self::ASSIGNMENT_FIELD_LAST_CHANGE]]
656  , self::ASSIGNMENT_FIELD_LAST_CHANGE_BY => ['integer', $row[self::ASSIGNMENT_FIELD_LAST_CHANGE_BY]]
657  , self::ASSIGNMENT_FIELD_RESTART_DATE => ['timestamp', $row[self::ASSIGNMENT_FIELD_RESTART_DATE]]
658  , self::ASSIGNMENT_FIELD_RESTARTED_ASSIGNMENT_ID => ['integer', $row[self::ASSIGNMENT_FIELD_RESTARTED_ASSIGNMENT_ID]]
659  , self::ASSIGNMENT_FIELD_MANUALLY_ASSIGNED => ['integer', $row[self::ASSIGNMENT_FIELD_MANUALLY_ASSIGNED]]
660  ]
661  );
662  }
663 
664  protected function updateAssignmentRowDB(array $values)
665  {
666  $q = 'UPDATE ' . self::ASSIGNMENT_TABLE
667  . ' SET'
668  . ' ' . self::ASSIGNMENT_FIELD_USR_ID . ' = ' . $this->db->quote($values[self::ASSIGNMENT_FIELD_USR_ID], 'integer')
669  . ' ,' . self::ASSIGNMENT_FIELD_ROOT_PRG_ID . ' = ' . $this->db->quote($values[self::ASSIGNMENT_FIELD_ROOT_PRG_ID], 'integer')
670  . ' ,' . self::ASSIGNMENT_FIELD_LAST_CHANGE . ' = ' . $this->db->quote($values[self::ASSIGNMENT_FIELD_LAST_CHANGE], 'text')
671  . ' ,' . self::ASSIGNMENT_FIELD_LAST_CHANGE_BY . ' = ' . $this->db->quote($values[self::ASSIGNMENT_FIELD_LAST_CHANGE_BY], 'integer')
672  . ' ,' . self::ASSIGNMENT_FIELD_RESTART_DATE . ' = ' . $this->db->quote($values[self::ASSIGNMENT_FIELD_RESTART_DATE], 'timestamp')
673  . ' ,' . self::ASSIGNMENT_FIELD_RESTARTED_ASSIGNMENT_ID . ' = ' . $this->db->quote($values[self::ASSIGNMENT_FIELD_RESTARTED_ASSIGNMENT_ID], 'integer')
674  . ' ,' . self::ASSIGNMENT_FIELD_MANUALLY_ASSIGNED . ' = ' . $this->db->quote($values[self::ASSIGNMENT_FIELD_MANUALLY_ASSIGNED], 'integer')
675  . ' WHERE ' . self::ASSIGNMENT_FIELD_ID . ' = ' . $this->db->quote($values[self::ASSIGNMENT_FIELD_ID], 'integer');
676  $this->db->manipulate($q);
677  }
678 
679  protected function storeProgressRow(
680  int $assignment_id,
681  int $usr_id,
682  ilPRGProgress $pgs
683  ) {
684  //TODO: move into type?
685  $lastchange = is_null($pgs->getLastChange()) ? 'NULL' : $this->db->quote($pgs->getLastChange()->format(ilPRGProgress::DATE_TIME_FORMAT), 'text');
686  $assign_date = is_null($pgs->getAssignmentDate()) ? 'NULL' : $this->db->quote($pgs->getAssignmentDate()->format(ilPRGProgress::DATE_TIME_FORMAT), 'text');
687  $completion_date = is_null($pgs->getCompletionDate()) ? 'NULL' : $this->db->quote($pgs->getCompletionDate()->format(ilPRGProgress::DATE_TIME_FORMAT), 'text');
688  $deadline = is_null($pgs->getDeadline()) ? 'NULL' : $this->db->quote($pgs->getDeadline()->format(ilPRGProgress::DATE_FORMAT), 'text');
689  $validity = is_null($pgs->getValidityOfQualification()) ? 'NULL' : $this->db->quote($pgs->getValidityOfQualification()->format(ilPRGProgress::DATE_FORMAT), 'text');
690  $invalidated = $pgs->isInvalidated() ? 1 : 0;
691  $individual = $pgs->hasIndividualModifications() ? 1 : 0;
692  $completion = $pgs->getCompletionBy() ?? 'NULL';
693 
694  $q = 'INSERT INTO ' . self::PROGRESS_TABLE
695  . '('
696  . self::PROGRESS_FIELD_ASSIGNMENT_ID . ','
697  . self::PROGRESS_FIELD_USR_ID . ','
698  . self::PROGRESS_FIELD_PRG_ID . ','
699 
700  . self::PROGRESS_FIELD_STATUS . ','
701  . self::PROGRESS_FIELD_POINTS . ','
702  . self::PROGRESS_FIELD_POINTS_CUR . ','
703  . self::PROGRESS_FIELD_COMPLETION_BY . ','
704  . self::PROGRESS_FIELD_LAST_CHANGE_BY . ','
705  . self::PROGRESS_FIELD_LAST_CHANGE . ','
706  . self::PROGRESS_FIELD_ASSIGNMENT_DATE . ','
707  . self::PROGRESS_FIELD_COMPLETION_DATE . ','
708  . self::PROGRESS_FIELD_DEADLINE . ','
709  . self::PROGRESS_FIELD_VQ_DATE . ','
710  . self::PROGRESS_FIELD_INVALIDATED . ','
711  . self::PROGRESS_FIELD_IS_INDIVIDUAL
712 
713  . PHP_EOL . ') VALUES (' . PHP_EOL
714  . $assignment_id
715  . ' ,' . $usr_id
716  . ' ,' . $pgs->getNodeId()
717 
718  . ' ,' . $pgs->getStatus()
719  . ' ,' . $pgs->getAmountOfPoints()
720  . ' ,' . $pgs->getCurrentAmountOfPoints()
721  . ' ,' . $completion
722  . ' ,' . $pgs->getLastChangeBy()
723  . ' ,' . $lastchange
724  . ' ,' . $assign_date
725  . ' ,' . $completion_date
726  . ' ,' . $deadline
727  . ' ,' . $validity
728  . ' ,' . $invalidated
729  . ' ,' . $individual
730  . ')' . PHP_EOL
731  . 'ON DUPLICATE KEY UPDATE' . PHP_EOL
732  . self::PROGRESS_FIELD_STATUS . '=' . $pgs->getStatus() . ','
733  . self::PROGRESS_FIELD_POINTS . '=' . $pgs->getAmountOfPoints() . ','
734  . self::PROGRESS_FIELD_POINTS_CUR . '=' . $pgs->getCurrentAmountOfPoints() . ','
735  . self::PROGRESS_FIELD_COMPLETION_BY . '=' . $completion . ','
736  . self::PROGRESS_FIELD_LAST_CHANGE_BY . '=' . $pgs->getLastChangeBy() . ','
737  . self::PROGRESS_FIELD_LAST_CHANGE . '=' . $lastchange . ','
738  . self::PROGRESS_FIELD_ASSIGNMENT_DATE . '=' . $assign_date . ','
739  . self::PROGRESS_FIELD_COMPLETION_DATE . '=' . $completion_date . ','
740  . self::PROGRESS_FIELD_DEADLINE . '=' . $deadline . ','
741  . self::PROGRESS_FIELD_VQ_DATE . '=' . $validity . ','
742  . self::PROGRESS_FIELD_INVALIDATED . '=' . $invalidated . ','
743  . self::PROGRESS_FIELD_IS_INDIVIDUAL . '=' . $individual
744  ;
745  $this->db->manipulate($q);
746  }
747 
748  public function storeExpiryInfoSentFor(ilPRGAssignment $ass): void
749  {
750  $where = [
751  self::PROGRESS_FIELD_ASSIGNMENT_ID => ['integer', $ass->getId()],
752  self::PROGRESS_FIELD_PRG_ID => ['integer', $ass->getRootId()]
753  ];
754 
755  $values = [
756  self::PROGRESS_FIELD_MAIL_SENT_WILLEXPIRE => [
757  'timestamp',
758  date('Y-m-d H:i:s')
759  ]
760  ];
761  $this->db->update(self::PROGRESS_TABLE, $values, $where);
762  }
763 
764  public function resetExpiryInfoSentFor(ilPRGAssignment $ass): void
765  {
766  $where = [
767  self::PROGRESS_FIELD_ASSIGNMENT_ID => ['integer', $ass->getId()],
768  self::PROGRESS_FIELD_PRG_ID => ['integer', $ass->getRootId()]
769  ];
770 
771  $values = [
772  self::PROGRESS_FIELD_MAIL_SENT_WILLEXPIRE => ['null', null]
773  ];
774  $this->db->update(self::PROGRESS_TABLE, $values, $where);
775  }
776 
777  public function storeRiskyToFailSentFor(ilPRGAssignment $ass): void
778  {
779  $where = [
780  self::PROGRESS_FIELD_ASSIGNMENT_ID => ['integer', $ass->getId()],
781  self::PROGRESS_FIELD_PRG_ID => ['integer', $ass->getRootId()]
782  ];
783 
784  $values = [
785  self::PROGRESS_FIELD_MAIL_SENT_RISKYTOFAIL => [
786  'timestamp',
787  date('Y-m-d H:i:s')
788  ]
789  ];
790  $this->db->update(self::PROGRESS_TABLE, $values, $where);
791  }
792 
793  public function resetRiskyToFailSentFor(ilPRGAssignment $ass): void
794  {
795  $where = [
796  self::PROGRESS_FIELD_ASSIGNMENT_ID => ['integer', $ass->getId()],
797  self::PROGRESS_FIELD_PRG_ID => ['integer', $ass->getRootId()]
798  ];
799 
800  $values = [
801  self::PROGRESS_FIELD_MAIL_SENT_RISKYTOFAIL => ['null', null]
802  ];
803  $this->db->update(self::PROGRESS_TABLE, $values, $where);
804  }
805 
806  public function getLatestAssignment(int $root_prg_obj_id, int $usr_id): ?ilPRGAssignment
807  {
808  $assignments = $this->getForUserOnNode($usr_id, $root_prg_obj_id);
809  if ($assignments === []) {
810  return null;
811  }
812  usort(
813  $assignments,
815  => $a->getProgressTree()->getAssignmentDate() <=> $b->getProgressTree()->getAssignmentDate()
816  );
817  $assignments = array_reverse($assignments);
818  return current($assignments);
819  }
820 
821  public function getLongestValidAssignment(int $root_prg_obj_id, int $usr_id): ?ilPRGAssignment
822  {
823  $assignments = $this->getForUserOnNode($usr_id, $root_prg_obj_id);
824  if ($assignments === []) {
825  return null;
826  }
827 
828  $now = new \DateTimeImmutable();
829  $valid = array_filter($assignments, fn($ass) => $ass->getProgressTree()->hasValidQualification($now));
830  if ($valid === []) {
831  return null;
832  }
833 
834  $unlimited = array_filter($valid, fn($ass) => $ass->getProgressTree()->getValidityOfQualification() === null);
835  if ($unlimited !== []) {
836  usort(
837  $unlimited,
839  => $a->getProgressTree()->getAssignmentDate() <=> $b->getProgressTree()->getAssignmentDate()
840  );
841  $unlimited = array_reverse($unlimited);
842  return current($unlimited);
843  }
844 
845  usort(
846  $valid,
848  => $a->getProgressTree()->getValidityOfQualification() <=> $b->getProgressTree()->getValidityOfQualification()
849  );
850  $valid = array_reverse($valid);
851  return current($valid);
852  }
853 
854  public function getCertificateRelevantAssignmentIds(int $prg_obj_id, int ...$usr_id): array
855  {
856  $query = 'SELECT assignment_id FROM (' . PHP_EOL
857  . 'SELECT usr_id, assignment_id, ROW_NUMBER() OVER (' . PHP_EOL
858  . 'PARTITION BY usr_id' . PHP_EOL
859  . 'ORDER BY' . PHP_EOL
860  . 'CASE WHEN vq_date IS NULL THEN 1 ELSE 0 END DESC,' . PHP_EOL
861  . 'vq_date DESC,' . PHP_EOL
862  . 'completion_date DESC' . PHP_EOL
863  . ') AS row_numbers' . PHP_EOL
864  . 'FROM prg_usr_progress' . PHP_EOL
865  . 'WHERE prg_id = ' . $this->db->quote($prg_obj_id, 'integer') . PHP_EOL
866  . 'AND ' . $this->db->in('usr_id', $usr_id, false, 'integer') . PHP_EOL
867  . 'AND ' . $this->db->in('status', [ilPRGProgress::STATUS_COMPLETED, ilPRGProgress::STATUS_ACCREDITED], false, 'integer') . PHP_EOL
868  . ') ranked ' . PHP_EOL
869  . 'WHERE row_numbers = 1';
870 
871  $res = $this->db->query($query);
872  $row = array_map(
873  fn($r) => $r['assignment_id'],
874  $this->db->fetchAll($res)
875  );
876 
877  return $row;
878  }
879 }
getForUser(int $usr_id)
get all assignments for a user
$res
Definition: ltiservices.php:69
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
getRiskyToFail(array $programmes_and_due, bool $discard_formerly_notified=true)
static _getAllReferences(int $id)
get all reference ids for object ID
$valid
getLongestValidAssignment(int $root_prg_obj_id, int $usr_id)
Get the user&#39;s assignment on a prg with the longest lasting qualification.
Additional information about a user, used in context of assignments.
getLatestAssignment(int $root_prg_obj_id, int $usr_id)
Get the user&#39;s assignment on a prg that was created last.
getAllForSpecificNode(int $prg_obj_id, array $user_filter=null)
get all assignments for all (or given) users, where the given node is the root-node of the assignment...
createFor(int $prg_obj_id, int $usr_id, int $assigning_usr_id)
getSelectableFieldsInfo(?int $a_obj_id=null)
Get selectable fields.
static now()
Return current timestamp in Y-m-d H:i:s format.
__construct(protected ilDBInterface $db, protected ilTree $tree, protected ilStudyProgrammeSettingsRepository $settings_repo, protected PRGEventsDelayed $events, ilExportFieldsInfo $user_field_info)
static _lookupObjId(int $ref_id)
A Progress is the status of a user on a single node of an assignment; it is unique by assignment_id:u...
$ref_id
Definition: ltiauth.php:67
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
static _lookupTitle(int $obj_id)
countAllForNodeIsContained(int $prg_obj_id, array $user_filter=null, ilPRGAssignmentFilter $custom_filters=null)
Count all assignments for all (or given) users, where the given node is part of the assignment...
getProgresses(array &$ret=[], ilPRGProgress $pgs=null)
static _lookupObjectId(int $ref_id)
getForUserOnNode(int $usr_id, int $root_prg_obj_id)
getPassedDeadline(\DateTimeImmutable $deadline)
storeProgressRow(int $assignment_id, int $usr_id, ilPRGProgress $pgs)
Assignments are relations of users to a PRG; They hold progress-information for (sub-)nodes of the PR...
$id
plugin.php for ilComponentBuildPluginInfoObjectiveTest::testAddPlugins
Definition: plugin.php:23
$q
Definition: shib_logout.php:21
$a
thx to https://mlocati.github.io/php-cs-fixer-configurator for the examples
static _lookupTargetId(int $a_obj_id)
getAllForNodeIsContained(int $prg_obj_id, array $user_filter=null, ilPRGAssignmentFilter $custom_filters=null)
get all assignments for all (or given) users, where the given node is part of the assignment ...
Assignments are relations of users to a PRG; They hold progress-information for (sub-)nodes of the PR...
getAboutToExpire(array $programmes_and_due, bool $discard_formerly_notified=true)
static _lookupType(int $id, bool $reference=false)
getCertificateRelevantAssignmentIds(int $prg_obj_id, int ... $usr_id)
$r