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