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