ILIAS  release_7 Revision v7.30-3-g800a261c036
All Data Structures Namespaces Files Functions Variables Modules Pages
class.ilObjStudyProgramme.php
Go to the documentation of this file.
1 <?php declare(strict_types = 1);
2 
3 /* Copyright (c) 2015-2019 Richard Klees <richard.klees@concepts-and-training.de>, Stefan Hecken <stefan.hecken@concepts-and-training.de> Extended GPL, see docs/LICENSE */
4 
6 {
10  protected $parent;
11 
15  protected $children;
16 
20  protected $lp_children;
21 
25  protected $type_repository;
26 
31 
36 
41 
46 
51 
55  protected $events;
56 
57  // GLOBALS from ILIAS
58 
62  public $webdir;
63 
67  public $tree;
68 
72  public $ilUser;
73 
77  protected $db;
78 
82  protected $plugin_admin;
83 
88 
94 
99 
103  public static $study_programme_cache = null;
104 
108  protected $members_cache;
109 
114 
119  public function __construct($a_id = 0, bool $a_call_by_reference = true)
120  {
122  $this->type = "prg";
123 
124  $this->type_repository = $dic['model.Type.ilStudyProgrammeTypeRepository'];
125  $this->auto_categories_repository = $dic['model.AutoCategories.ilStudyProgrammeAutoCategoriesRepository'];
126  $this->auto_memberships_repository = $dic['model.AutoMemberships.ilStudyProgrammeAutoMembershipsRepository'];
127  $this->membersourcereader_factory = $dic['model.AutoMemberships.ilStudyProgrammeMembershipSourceReaderFactory'];
128 
129  $this->settings_repository = $dic['model.Settings.ilStudyProgrammeSettingsRepository'];
130  $this->assignment_repository = $dic['model.Assignment.ilStudyProgrammeAssignmentRepository'];
131  $this->progress_repository = $dic['model.Progress.ilStudyProgrammeProgressRepository'];
132 
133  $this->events = $dic['ilStudyProgrammeEvents'];
134 
135  parent::__construct($a_id, $a_call_by_reference);
136 
137  $this->clearParentCache();
138  $this->clearChildrenCache();
139  $this->clearLPChildrenCache();
140 
141  global $DIC;
142  $tree = $DIC['tree'];
143  $ilUser = $DIC['ilUser'];
144  $this->webdir = $DIC->filesystem()->web();
145  $this->tree = $tree;
146  $this->ilUser = $ilUser;
147  $this->db = $DIC['ilDB'];
148  $this->plugin_admin = $DIC['ilPluginAdmin'];
149  $this->lng = $DIC['lng'];
150  $this->logger = ilLoggerFactory::getLogger($this->type);
151 
152  $this->object_factory = ilObjectFactoryWrapper::singleton();
153 
154  $this->custom_icon_factory = $DIC['object.customicons.factory'];
155 
156  self::initStudyProgrammeCache();
157  }
158 
159  public static function initStudyProgrammeCache() : void
160  {
161  if (self::$study_programme_cache === null) {
162  self::$study_programme_cache = ilObjStudyProgrammeCache::singleton();
163  }
164  }
165 
169  protected function clearParentCache() : void
170  {
171  // This is not initialized, but we need null if there is no parent.
172  $this->parent = false;
173  }
174 
178  protected function clearChildrenCache() : void
179  {
180  $this->children = null;
181  }
182 
186  protected function clearLPChildrenCache() : void
187  {
188  $this->lp_children = null;
189  }
190 
191  public static function getRefIdFor(int $obj_id) : int
192  {
193  $refs = ilObject::_getAllReferences($obj_id);
194  if (count($refs) < 1) {
195  throw new ilException("Could not find ref_id for programme with obj_id $obj_id");
196  }
197  return (int) array_shift($refs);
198  }
199 
200  protected function getPrgInstanceByObjId(int $obj_id) : ilObjStudyProgramme
201  {
202  return self::getInstanceByRefId(self::getRefIdFor($obj_id));
203  }
204 
205  public static function getInstanceByObjId($obj_id) : ilObjStudyProgramme
206  {
207  return self::getInstanceByRefId(self::getRefIdFor($obj_id));
208  }
209 
210  public static function getInstanceByRefId($a_ref_id) : ilObjStudyProgramme
211  {
212  if (self::$study_programme_cache === null) {
213  self::initStudyProgrammeCache();
214  }
215  return self::$study_programme_cache->getInstanceByRefId($a_ref_id);
216  }
217 
219  {
221  }
223  {
225  }
227  {
229  }
230  protected function getTree() : ilTree
231  {
232  return $this->tree;
233  }
234  protected function getLogger() : ilComponentLogger
235  {
236  return $this->logger;
237  }
238 
244  public static function createInstance() : ilObjStudyProgramme
245  {
246  $obj = new ilObjStudyProgramme();
247  $obj->create();
248  $obj->createReference();
249  self::$study_programme_cache->addInstance($obj);
250  return $obj;
251  }
252 
254  // CRUD
256 
258  {
259  return $this->getSettingsRepository()->get($this->getId());
260  }
261 
262  public function updateSettings(ilStudyProgrammeSettings $settings) : void
263  {
264  if ($settings->getObjId() !== (int) $this->getId()) {
265  throw new Exception("The given settings-object does not belong to this programme", 1);
266  }
267  $this->getSettingsRepository()->update($settings);
268  }
269 
270  protected function deleteSettings() : void
271  {
272  $this->getSettingsRepository()->delete($this->getSettings());
273  }
274 
280  protected function deleteAssignmentsAndProgresses() : void
281  {
283  $assignment_repository->deleteAllAssignmentsForProgrammeId($this->getId());
284  $orphan_condition_field = $assignment_repository->getTableAndFieldOfAssignmentIds();
286  $progress_repository->deleteProgressesFor($this->getId());
287  $progress_repository->deleteAllOrphanedProgresses(...$orphan_condition_field);
288  }
289 
293  public function create() : int
294  {
295  $id = (int) parent::create();
296  $this->getSettingsRepository()->createFor($id);
297  return $id;
298  }
299 
303  public function update() : void
304  {
305  parent::update();
306 
307  $type_settings = $this->getSettings()->getTypeSettings();
308  // Update selection for advanced meta data of the type
309  if ($type_settings->getTypeId()) {
311  $this->getId(),
312  'prg_type',
313  $this->type_repository->getAssignedAMDRecordIdsByType($type_settings->getTypeId())
314  );
315  } else {
316  // If no type is assigned, delete relations by passing an empty array
317  ilAdvancedMDRecord::saveObjRecSelection($this->getId(), 'prg_type', array());
318  }
319  }
320 
326  public function delete() : bool
327  {
328  // always call parent delete function first!!
329  if (!parent::delete()) {
330  return false;
331  }
332 
333  $this->deleteSettings();
335  try {
336  $this->auto_categories_repository->deleteFor((int) $this->getId());
338  // This would be the case when SP is in trash (#17797)
339  }
340 
343 
344  $this->events->raise('delete', ['object' => $this, 'obj_id' => $this->getId()]);
345  return true;
346  }
347 
348  public function hasAdvancedMetadata() : bool
349  {
350  $sub_type_id = $this->getSettings()->getTypeSettings()->getTypeId();
351  if ($sub_type_id) {
352  $type = $this->type_repository->getType($sub_type_id);
353  }
354 
355  return !is_null($type) && count($this->type_repository->getAssignedAMDRecordIdsByType($type->getId(), true)) > 0;
356  }
357 
359  // GETTERS AND SETTERS
361 
365  public function getLastChange() : DateTime
366  {
367  return $this->getSettings()->getLastChange();
368  }
369 
373  public function getPoints() : int
374  {
375  return $this->getSettings()->getAssessmentSettings()->getPoints();
376  }
377 
383  public function setPoints(int $a_points) : ilObjStudyProgramme
384  {
385  $settings = $this->getSettings()->getAssessmentSettings()
386  ->withPoints($a_points);
387  $this->setAssessmentSettings($settings);
388  $this->updateLastChange();
389  return $this;
390  }
391 
392  public function getLPMode() : int
393  {
394  return $this->getSettings()->getLPMode();
395  }
396 
405  public function adjustLPMode() : void
406  {
407  // Clear caches here, there have been some changes, because this method
408  // would not have been called otherwise, and the changer just does not
409  // know if we have filled the caches already...
410  $this->clearLPChildrenCache();
411  $this->clearChildrenCache();
412 
413  if ($this->getAmountOfLPChildren() > 0) {
414  $this->settings_repository->update(
416  );
417  } else {
418  if ($this->getAmountOfChildren(true) > 0) {
419  $this->settings_repository->update(
421  );
422  } else {
423  $this->settings_repository->update(
425  );
426  }
427  }
428  }
429 
430  public function getStatus() : int
431  {
432  return $this->getSettings()->getAssessmentSettings()->getStatus();
433  }
434 
440  public function setStatus(int $a_status) : ilObjStudyProgramme
441  {
442  $settings = $this->getSettings()->getAssessmentSettings()
443  ->withStatus($a_status);
444  $this->setAssessmentSettings($settings);
445  $this->updateLastChange();
446  return $this;
447  }
448 
449  public function isActive() : bool
450  {
452  }
453 
459  public function getSubType()
460  {
461  $type_settings = $this->getSettings()->getTypeSettings();
462  if (!in_array($type_settings->getTypeId(), array("-", "0"))) {
463  $subtype_id = $type_settings->getTypeId();
464  return $this->type_repository->getType($subtype_id);
465  }
466 
467  return null;
468  }
469 
470 
472  // TREE NAVIGATION
474 
483  public static function getAllChildren(int $a_ref_id, bool $include_references = false)
484  {
485  $ret = array();
486  $root = self::getInstanceByRefId($a_ref_id);
487  $root_id = $root->getId();
488  $root->applyToSubTreeNodes(function (ilObjStudyProgramme $prg) use (&$ret, $root_id) {
489  // exclude root node of subtree.
490  if ($prg->getId() == $root_id) {
491  return;
492  }
493  $ret[] = $prg;
494  }, $include_references);
495  return $ret;
496  }
497 
498  public function getAllPrgChildren() : array
499  {
500  $ret = [];
501  $this->applyToSubTreeNodes(
502  function (ilObjStudyProgramme $prg) use (&$ret) {
503  if ($prg->getId() == $this->getId()) {
504  return;
505  }
506  $ret[] = $prg;
507  },
508  false
509  );
510  return $ret;
511  }
512 
520  public function getChildren(bool $include_references = false) : array
521  {
522  $this->throwIfNotInTree();
523 
524  if ($this->children === null) {
525  $ref_ids = $this->tree->getChildsByType($this->getRefId(), "prg");
526 
527  // apply container sorting to tree
528  $sorting = ilContainerSorting::_getInstance($this->getId());
529  $ref_ids = $sorting->sortItems(array('prg' => $ref_ids));
530  $ref_ids = $ref_ids['prg'];
531 
532  $this->children = array_map(function ($node_data) {
533  return ilObjStudyProgramme::getInstanceByRefId($node_data["child"]);
534  }, $ref_ids);
535  }
536 
537  if ($include_references && $this->reference_children === null) {
538  $this->reference_children = [];
539  $ref_child_ref_ids = $this->tree->getChildsByType($this->getRefId(), "prgr");
540  foreach ($this->children as $prg) {
541  $ref_child_ref_ids =
542  array_merge(
543  $this->tree->getChildsByType($prg->getRefId(), "prgr"),
544  $ref_child_ref_ids
545  );
546  }
547  foreach (
548  array_unique(
549  array_map(
550  function ($data) {
551  return $data['child'];
552  },
553  array_filter($ref_child_ref_ids, function ($data) {
554  return $data["deleted"] === null;
555  })
556  )
557  ) as $prg_ref_id
558  ) {
559  $this->reference_children[] =
560  (new ilObjStudyProgrammeReference($prg_ref_id))->getReferencedObject();
561  }
562  }
563  return $include_references ?
564  array_merge($this->children, $this->reference_children) :
566  }
567 
574  public function getParent() : ?ilObjStudyProgramme
575  {
576  if ($this->parent === false) {
577  $this->throwIfNotInTree();
578  $parent_data = $this->tree->getParentNodeData($this->getRefId());
579  if ($parent_data["type"] != "prg") {
580  $this->parent = null;
581  } else {
582  $this->parent = ilObjStudyProgramme::getInstanceByRefId($parent_data["ref_id"]);
583  }
584  }
585  return $this->parent;
586  }
587 
588  protected function getReferencesTo(ilObjStudyProgramme $prg) : array
589  {
590  $tree = $this->tree;
591  return array_filter(
592  array_map(
593  function ($id) {
594  return new ilObjStudyProgrammeReference(
595  array_shift(
597  )
598  );
599  },
601  ),
602  function ($prg_ref) use ($tree) {
603  return !$tree->isDeleted((int) $prg_ref->getRefId());
604  }
605  );
606  }
607 
608  public function getReferencesToSelf() : array
609  {
610  return $this->getReferencesTo($this);
611  }
612 
618  public function getParents(bool $include_references = false) : array
619  {
620  $current = $this;
621  $parents = [];
622  $queque = [$current];
623  while ($element = array_shift($queque)) {
624  $parent = $element->getParent();
625  if ($parent === null || $include_references) {
626  foreach ($this->getReferencesTo($element) as $reference) {
627  if ($this->tree->isDeleted($reference->getRefId())) {
628  continue;
629  }
630  $r_parent = $reference->getParent();
631  if (is_null($r_parent)) {
632  continue;
633  }
634  array_push($queque, $r_parent);
635  $parents[] = $r_parent;
636  }
637  continue;
638  }
639  array_push($queque, $parent);
640  $parents[] = $parent;
641  }
642  return array_reverse($parents);
643  }
644 
650  public function hasChildren(bool $include_references = false) : bool
651  {
652  return $this->getAmountOfChildren($include_references) > 0;
653  }
654 
661  public function getAmountOfChildren($include_references = false) : int
662  {
663  return count($this->getChildren($include_references));
664  }
665 
672  public function getDepth() : int
673  {
674  $cur = $this;
675  $count = 0;
676  while ($cur = $cur->getParent()) {
677  $count++;
678  }
679  return $count;
680  }
681 
688  public function getRoot()
689  {
690  $parents = $this->getParents();
691  if (count($parents) < 1) {
692  return $this;
693  }
694  return $parents[0];
695  }
696 
703  public function getLPChildren()
704  {
705  $this->throwIfNotInTree();
706 
707  if ($this->lp_children === null) {
708  $this->lp_children = array();
709 
710  $ref_ids = $this->tree->getChildsByType($this->getRefId(), "crsr");
711 
712  // apply container sorting to tree
713  $sorting = ilContainerSorting::_getInstance($this->getId());
714  $ref_ids = $sorting->sortItems(array('crs_ref' => $ref_ids));
715  $ref_ids = $ref_ids['crs_ref'];
716 
717  $lp_children = array_map(function ($node_data) {
718  $lp_obj = $this->object_factory->getInstanceByRefId($node_data["child"]);
719 
720  // filter out all StudyProgramme instances
721  return ($lp_obj instanceof $this) ? null : $lp_obj;
722  }, $ref_ids);
723 
724  $this->lp_children = array_filter($lp_children);
725  }
726  return $this->lp_children;
727  }
728 
735  public function getLPChildrenIds()
736  {
737  return array_map(function ($child) {
738  return $child->getId();
739  }, $this->getLPChildren());
740  }
741 
747  public function getAmountOfLPChildren()
748  {
749  return count($this->getLPChildren());
750  }
751 
752  public function hasLPChildren() : bool
753  {
754  return ($this->getAmountOfLPChildren() > 0);
755  }
756 
760  protected function throwIfNotInTree() : void
761  {
762  if (!$this->tree->isInTree($this->getRefId())) {
763  throw new ilStudyProgrammeTreeException("This program is not in tree.");
764  }
765  }
766 
768  // QUERIES ON SUBTREE
770 
778  public function applyToSubTreeNodes(Closure $fun, bool $include_references = false) : void
779  {
780  $this->throwIfNotInTree();
781 
782  if ($fun($this) !== false) {
783  foreach ($this->getChildren($include_references) as $child) {
784  $child->applyToSubTreeNodes($fun, $include_references);
785  }
786  }
787  }
788 
794  public function getCompletedCourses(int $a_user_id) : array
795  {
796  $node_data = $this->tree->getNodeData($this->getRefId());
797  $crsrs = $this->tree->getSubTree($node_data, true, "crsr");
798 
799  $completed_crss = array();
800  foreach ($crsrs as $ref) {
801  if (ilObject::_exists($ref['ref_id'], true) &&
802  is_null(ilObject::_lookupDeletedDate($ref['ref_id']))
803  ) {
804  $crs_id = ilContainerReference::_lookupTargetId($ref["obj_id"]);
805  if (ilLPStatus::_hasUserCompleted($crs_id, $a_user_id)) {
806  $containing_prg = self::getInstanceByRefId($ref["parent"]);
807  if ($containing_prg->isActive()) {
808  $completed_crss[] = [
809  "crs_id" => $crs_id
810  , "prg_ref_id" => $ref["parent"]
811  , "crsr_ref_id" => $ref["child"]
812  , "crsr_id" => $ref["obj_id"]
813  , "title" => ilContainerReference::_lookupTitle($ref["obj_id"])
814  ];
815  }
816  }
817  }
818  }
819  return $completed_crss;
820  }
821 
823  // TREE MANIPULATION
825 
837  {
838  $this->throwIfNotInTree();
839 
841  throw new ilStudyProgrammeTreeException("Program already contains leafs.");
842  }
843 
844  if ($this->tree->isInTree($a_prg->getRefId())) {
845  throw new ilStudyProgrammeTreeException("Other program already is in tree.");
846  }
847 
848  if ($a_prg->getRefId() === null) {
849  $a_prg->createReference();
850  }
851  $a_prg->putInTree($this->getRefId());
852  return $this;
853  }
854 
862  public function nodeInserted(ilObjStudyProgramme $a_prg)
863  {
865  throw new ilStudyProgrammeTreeException("Program already contains leafs.");
866  }
867 
869  $this->settings_repository->update(
871  );
872  }
873 
874  $this->clearChildrenCache();
875  $this->addMissingProgresses();
876  }
877 
887  public function putInTree($a_parent_ref)
888  {
889  $res = parent::putInTree($a_parent_ref);
890 
891  if (ilObject::_lookupType($a_parent_ref, true) == "prg") {
892  $par = ilObjStudyProgramme::getInstanceByRefId($a_parent_ref);
893  $par->nodeInserted($this);
894  }
895 
896  return $res;
897  }
898 
909  {
910  if ($a_prg->getParent()->getId() !== $this->getId()) {
911  throw new ilStudyProgrammeTreeException("This is no parent of the given programm.");
912  }
913 
914  if (!$a_prg->canBeRemoved()) {
915  throw new ilStudyProgrammeTreeException("The node has relevant assignments.");
916  }
917 
918  // *sigh*...
919  $node_data = $this->tree->getNodeData($a_prg->getRefId());
920  $this->tree->deleteTree($node_data);
921  $a_prg->clearParentCache();
922  $this->clearChildrenCache();
923 
924  return $this;
925  }
926 
931  public function canBeRemoved() : bool
932  {
933  foreach ($this->getProgresses() as $progress) {
934  if ($progress->getStatus() != ilStudyProgrammeProgress::STATUS_NOT_RELEVANT) {
935  return false;
936  }
937  if ($progress->getLastChangeBy() !== null) {
938  return false;
939  }
940  }
941  return true;
942  }
943 
954  {
955  $this->throwIfNotInTree();
956 
957  if ($this->hasChildren()) {
958  throw new ilStudyProgrammeTreeException("Program already contains other programm nodes.");
959  }
960 
961  if ($a_leaf->getRefId() === null) {
962  $a_leaf->createReference();
963  }
964  $a_leaf->putInTree($this->getRefId());
965  $this->clearLPChildrenCache();
966  $this->settings_repository->update(
968  );
969 
970  return $this;
971  }
972 
983  {
984  if (self::getParentId($a_leaf) !== $this->getId()) {
985  throw new ilStudyProgrammeTreeException("This is no parent of the given leaf node.");
986  }
987 
988  $node_data = $this->tree->getNodeData($a_leaf->getRefId());
989  $this->tree->deleteTree($node_data);
990  $this->clearLPChildrenCache();
991 
992  return $this;
993  }
994 
1004  public function moveTo(ilObjStudyProgramme $a_new_parent) : ilObjStudyProgramme
1005  {
1006  global $DIC;
1007  $rbacadmin = $DIC['rbacadmin'];
1008 
1009  if ($parent = $this->getParent()) {
1010 
1011  // TODO: check if there some leafs in the new parent
1012 
1013  $this->tree->moveTree($this->getRefId(), $a_new_parent->getRefId());
1014  // necessary to clean up permissions
1015  $rbacadmin->adjustMovedObjectPermissions($this->getRefId(), $parent->getRefId());
1016 
1017  // TODO: lp-progress needs to be updated
1018 
1019  // clear caches on different nodes
1020  $this->clearParentCache();
1021 
1022  $parent->clearChildrenCache();
1023  $parent->clearLPChildrenCache();
1024 
1025  $a_new_parent->clearChildrenCache();
1026  $a_new_parent->clearLPChildrenCache();
1027  }
1028 
1029  return $this;
1030  }
1031 
1033  // USER ASSIGNMENTS
1035 
1046  public function assignUser(int $usr_id, int $acting_usr_id = null) : ilStudyProgrammeAssignment
1047  {
1048  $this->members_cache = null;
1049 
1051  throw new ilException(
1052  "ilObjStudyProgramme::assignUser: Can't assign user to program '"
1053  . $this->getId() . "', since it's not in active status."
1054  );
1055  }
1056 
1057  if (is_null($acting_usr_id)) {
1058  $acting_usr_id = $this->getLoggedInUserId();
1059  }
1060 
1061  $ass = $this->assignment_repository->createFor($this->getId(), $usr_id, $acting_usr_id);
1062 
1063  $this->applyToSubTreeNodes(
1064  function (ilObjStudyProgramme $node) use ($ass, $acting_usr_id) {
1065  $progress = $node->createProgressForAssignment($ass, $acting_usr_id);
1066  $progress = $node->resetProgressToSettings($progress, $acting_usr_id);
1067  $this->getProgressRepository()->update($progress);
1068  },
1069  true
1070  );
1071 
1072  $this->events->userAssigned($ass);
1073  return $ass;
1074  }
1075 
1084  {
1085  $this->members_cache = null;
1086  if ($assignment->getRootId() != $this->getId()) {
1087  throw new ilException(
1088  "ilObjStudyProgramme::removeAssignment: Assignment '"
1089  . $assignment->getId() . "' does not belong to study "
1090  . "program '" . $this->getId() . "'."
1091  );
1092  }
1093 
1094  $this->assignment_repository->delete($assignment);
1095 
1096  $affected_node_ids = $this->progress_repository->deleteForAssignmentId($assignment->getId());
1097  foreach ($affected_node_ids as $node_obj_id) {
1098  $this->refreshLPStatus($assignment->getUserId(), $node_obj_id);
1099  }
1100 
1101  $this->events->userDeassigned($assignment);
1102  return $this;
1103  }
1104 
1108  public function hasAssignmentOf(int $a_user_id) : bool
1109  {
1110  return $this->getAmountOfAssignmentsOf($a_user_id) > 0;
1111  }
1112 
1117  public function getAmountOfAssignmentsOf(int $a_user_id) : int
1118  {
1119  return count($this->getAssignmentsOf($a_user_id));
1120  }
1121 
1129  public function getAssignmentsOf(int $a_user_id) : array
1130  {
1131  $prg_ids = $this->getIdsFromNodesOnPathFromRootToHere();
1132  $assignments = [];
1133  foreach ($prg_ids as $prg_id) {
1134  $assignments = array_merge(
1135  $assignments,
1136  $this->assignment_repository->getByUsrIdAndPrgId($a_user_id, $prg_id)
1137  );
1138  }
1139  usort($assignments, function ($a_one, $a_other) {
1140  return strcmp(
1141  $a_one->getLastChange()->format('Y-m-d'),
1142  $a_other->getLastChange()->format('Y-m-d')
1143  );
1144  });
1146  return array_map(function ($ass) use ($assignment_repository) {
1147  return $assignment_repository->getInstanceByModel($ass);
1148  }, array_values($assignments)); // use array values since we want keys 0...
1149  }
1150 
1156  public function getAssignments() : array
1157  {
1159  return array_map(function ($ass) use ($assignment_repository) {
1160  return $assignment_repository->getInstanceByModel($ass);
1161  }, array_values($this->getAssignmentsRaw())); // use array values since we want keys 0...
1162  }
1163 
1168  public function getMembers() : array
1169  {
1170  $usr_ids = [];
1171  foreach ($this->getProgresses() as $progress) {
1172  $usr_ids[] = $progress->getUserId();
1173  }
1174  return array_unique($usr_ids);
1175  }
1179  public function getLocalMembers() : array
1180  {
1181  if (!$this->members_cache) {
1182  $this->members_cache = array_map(
1183  function ($assignment) {
1184  return $assignment->getUserId();
1185  },
1186  $this->assignment_repository->getByPrgId($this->getId())
1187  );
1188  }
1189  return $this->members_cache;
1190  }
1191 
1195  public function hasAssignments() : bool
1196  {
1197  return count($this->getAssignments()) > 0;
1198  }
1199 
1204  {
1205  $this->members_cache = null;
1206  $assignments = $this->getAssignmentRepository()->getByPrgId((int) $this->getId());
1207  foreach ($assignments as $ass) {
1208  $ass->updateFromProgram();
1209  }
1210  return $this;
1211  }
1212 
1218  public function getAssignmentsOfSingleProgramForUser(int $usr_id) : array
1219  {
1220  return $this->assignment_repository->getByUsrIdAndPrgId($usr_id, $this->getId());
1221  }
1222 
1226  public function hasAssignmentsOfSingleProgramForUser(int $usr_id) : bool
1227  {
1228  return count($this->getAssignmentsOfSingleProgramForUser($usr_id)) > 0;
1229  }
1230 
1231 
1233  // USER PROGRESS
1235 
1240  {
1241  return $this->progress_repository->createFor($this->getSettings(), $ass, $acting_user);
1242  }
1243 
1250  public function getProgressesOf(int $a_user_id) : array
1251  {
1252  return $this->progress_repository->getByPrgIdAndUserId($this->getId(), $a_user_id);
1253  }
1254 
1255  public function getProgressForAssignment(int $assignment_id) : ?ilStudyProgrammeProgress
1256  {
1257  return $this->getProgressRepository()->getByPrgIdAndAssignmentId($this->getId(), $assignment_id);
1258  }
1259 
1266  public function addMissingProgresses() : void
1267  {
1268  $logger = $this->getLogger();
1272 
1273  $assignment_ids_here = array_unique(
1274  array_map(
1275  function ($progress) {
1276  return $progress->getAssignmentId();
1277  },
1278  $progress_repository->getByPrgId((int) $this->getId())
1279  )
1280  );
1281 
1282  $nodes_below = $this->getChildren(true); //include references
1283  foreach ($assignment_ids_here as $ass_id) {
1284  foreach ($nodes_below as $node) {
1285  $node_id = $node->getId();
1286  if (!$progress_repository->getByPrgIdAndAssignmentId($node_id, $ass_id)) {
1287  $new_progress = $progress_repository->createFor(
1288  $settings_repository->get($node_id),
1289  $assignment_repository->get($ass_id),
1290  null //acting user
1291  );
1292  $progress_repository->update(
1293  $new_progress->withStatus(ilStudyProgrammeProgress::STATUS_NOT_RELEVANT)
1294  );
1295 
1296  $logger->log("Added progress for assingnment $ass_id at node $node_id.");
1297  }
1298  $node->addMissingProgresses();
1299  }
1300  }
1301  }
1302 
1308  public function getProgresses() : array
1309  {
1310  return $this->progress_repository->getByPrgId($this->getId());
1311  }
1312 
1316  public function hasProgresses() : bool
1317  {
1318  return count($this->getProgresses()) > 0;
1319  }
1320 
1324  public function hasRelevantProgresses() : bool
1325  {
1326  foreach ($this->getProgresses() as $progress) {
1327  if ($progress->isRelevant()) {
1328  return true;
1329  }
1330  }
1331  return false;
1332  }
1333 
1339  public function getIdsOfUsersWithRelevantProgress() : array
1340  {
1341  $returns = array();
1342  foreach ($this->getProgresses() as $progress) {
1343  if ($progress->isRelevant()) {
1344  $returns[] = $progress->getUserId();
1345  }
1346  }
1347  return array_unique($returns);
1348  }
1349 
1355  public function getIdsOfUsersWithCompletedProgress() : array
1356  {
1357  $returns = array();
1358  foreach ($this->getProgresses() as $progress) {
1359  if ($progress->isSuccessful() && !$progress->isSuccessfulExpired()) {
1360  $returns[] = $progress->getUserId();
1361  }
1362  }
1363  return array_unique($returns);
1364  }
1365 
1371  public function getIdsOfUsersWithFailedProgress() : array
1372  {
1373  $returns = array();
1374  foreach ($this->getProgresses() as $progress) {
1375  if ($progress->isFailed() || $progress->isSuccessfulExpired()) {
1376  $returns[] = $progress->getUserId();
1377  }
1378  }
1379  return array_unique(array_diff($returns, $this->getIdsOfUsersWithCompletedProgress()));
1380  }
1381 
1382 
1390  {
1391  $returns = array();
1392  foreach ($this->getProgresses() as $progress) {
1393  if ($progress->isRelevant() && !$progress->isSuccessful()) {
1394  $returns[] = $progress->getUserId();
1395  }
1396  }
1397  return array_unique($returns);
1398  }
1399 
1400 
1402  // AUTOMATIC CONTENT CATEGORIES
1404 
1409  public function getAutomaticContentCategories() : array
1410  {
1411  return $this->auto_categories_repository->getFor($this->getId());
1412  }
1413 
1414  public function hasAutomaticContentCategories() : bool
1415  {
1416  return count($this->getAutomaticContentCategories()) > 0;
1417  }
1418 
1419 
1424  public function storeAutomaticContentCategory(int $category_ref_id) : void
1425  {
1426  $ac = $this->auto_categories_repository->create(
1427  $this->getId(),
1428  $category_ref_id
1429  );
1430  $this->auto_categories_repository->update($ac);
1431  }
1432 
1437  public function deleteAutomaticContentCategories(array $category_ids = []) : void
1438  {
1439  $this->auto_categories_repository->delete($this->getId(), $category_ids);
1440  }
1441 
1445  public function deleteAllAutomaticContentCategories() : void
1446  {
1447  $this->auto_categories_repository->deleteFor($this->getId());
1448  }
1449 
1453  public static function addCrsToProgrammes(int $crs_ref_id, int $cat_ref_id) : void
1454  {
1455  foreach (self::getProgrammesMonitoringCategory($cat_ref_id) as $prg) {
1456  $course_ref = new ilObjCourseReference();
1457  $course_ref->setTitleType(ilObjCourseReference::TITLE_TYPE_REUSE);
1458  $course_ref->setTargetRefId($crs_ref_id);
1459  $course_ref->create();
1460  $course_ref->createReference();
1461  $course_ref->putInTree($prg->getRefId());
1462  $course_ref->setPermissions($crs_ref_id);
1463  $course_ref->setTargetId(ilObject::_lookupObjectId($crs_ref_id));
1464  $course_ref->update();
1465  $lp = new ilLPObjSettings($course_ref->getId());
1466  $lp->insert();
1467  $lp->setMode($lp::LP_MODE_COURSE_REFERENCE);
1468  $lp->update(false);
1469  }
1470  }
1471 
1477  public static function removeCrsFromProgrammes(int $crs_ref_id, int $cat_ref_id)
1478  {
1479  foreach (self::getProgrammesMonitoringCategory($cat_ref_id) as $prg) {
1480  foreach ($prg->getLPChildren() as $child) {
1481  if ((int) $child->getTargetRefId() === $crs_ref_id) {
1482  $child->delete();
1483  }
1484  }
1485  }
1486  }
1487 
1492  protected static function getProgrammesMonitoringCategory(int $cat_ref_id) : array
1493  {
1494  $db = ilStudyProgrammeDIC::dic()['model.AutoCategories.ilStudyProgrammeAutoCategoriesRepository'];
1495  $programmes = array_map(
1496  function ($rec) {
1497  $prg_obj_id = (int) array_shift(array_values($rec));
1498  $prg_ref_id = (int) array_shift(ilObject::_getAllReferences($prg_obj_id));
1499  $prg = self::getInstanceByRefId($prg_ref_id);
1500  if ($prg->isAutoContentApplicable()) {
1501  return $prg;
1502  }
1503  },
1504  $db::getProgrammesFor($cat_ref_id)
1505  );
1506  return $programmes;
1507  }
1508 
1515  public function isAutoContentApplicable() : bool
1516  {
1517  $valid_status = in_array(
1518  $this->getSettings()->getAssessmentSettings()->getStatus(),
1519  [
1522  ]
1523  );
1524 
1525  $crslnk_allowed = (
1526  $this->hasLPChildren()
1527  || $this->getAmountOfChildren(true) === 0
1528  );
1529 
1530  return $valid_status && $crslnk_allowed;
1531  }
1532 
1533 
1535  // AUTOMATIC MEMBERSHIPS
1537 
1542  public function getAutomaticMembershipSources() : array
1543  {
1544  return $this->auto_memberships_repository->getFor($this->getId());
1545  }
1546 
1550  public function storeAutomaticMembershipSource(string $type, int $src_id) : void
1551  {
1552  $ams = $this->auto_memberships_repository->create($this->getId(), $type, $src_id, false);
1553  $this->auto_memberships_repository->update($ams);
1554  }
1555 
1559  public function deleteAutomaticMembershipSource(string $type, int $src_id) : void
1560  {
1561  $this->auto_memberships_repository->delete($this->getId(), $type, $src_id);
1562  }
1563 
1567  public function deleteAllAutomaticMembershipSources() : void
1568  {
1569  $this->auto_memberships_repository->deleteFor($this->getId());
1570  }
1571 
1575  public function disableAutomaticMembershipSource(string $type, int $src_id) : void
1576  {
1577  $ams = $this->auto_memberships_repository->create($this->getId(), $type, $src_id, false);
1578  $this->auto_memberships_repository->update($ams);
1579  }
1580 
1585  public function enableAutomaticMembershipSource(string $type, int $src_id, $assign_now = false) : void
1586  {
1587  if ($assign_now) {
1589  $member_ids = $this->getMembersOfMembershipSource($type, $src_id);
1590  foreach ($member_ids as $usr_id) {
1591  if (!$this->getAssignmentsOfSingleProgramForUser($usr_id)) {
1592  $this->assignUser($usr_id, $assigned_by);
1593  }
1594  }
1595  }
1596  $ams = $this->auto_memberships_repository->create($this->getId(), $type, $src_id, true);
1597  $this->auto_memberships_repository->update($ams);
1598  }
1599 
1605  protected function getMembersOfMembershipSource(string $src_type, int $src_id) : array
1606  {
1607  $source_reader = $this->membersourcereader_factory->getReaderFor($src_type, $src_id);
1608  return $source_reader->getMemberIds();
1609  }
1610 
1611 
1616  protected static function getProgrammesMonitoringMemberSource(string $src_type, int $src_id) : array
1617  {
1618  $db = ilStudyProgrammeDIC::dic()['model.AutoMemberships.ilStudyProgrammeAutoMembershipsRepository'];
1619  $programmes = array_map(
1620  function ($rec) {
1621  $prg_obj_id = (int) array_shift(array_values($rec));
1622  $prg_ref_id = (int) array_shift(ilObject::_getAllReferences($prg_obj_id));
1623  $prg = self::getInstanceByRefId($prg_ref_id);
1624  return $prg;
1625  },
1626  $db::getProgrammesFor($src_type, $src_id)
1627  );
1628  return $programmes;
1629  }
1630 
1631  public static function addMemberToProgrammes(string $src_type, int $src_id, int $usr_id) : void
1632  {
1633  foreach (self::getProgrammesMonitoringMemberSource($src_type, $src_id) as $prg) {
1634  if ($prg->isActive() &&
1635  !$prg->hasAssignmentsOfSingleProgramForUser($usr_id)) {
1636  $assigned_by = ilStudyProgrammeAutoMembershipSource::SOURCE_MAPPING[$src_type];
1637  $prg->assignUser($usr_id, $assigned_by);
1638  }
1639  }
1640  }
1641 
1642  public static function removeMemberFromProgrammes(string $src_type, int $src_id, int $usr_id) : void
1643  {
1644  $now = new DateTimeImmutable();
1645  foreach (self::getProgrammesMonitoringMemberSource($src_type, $src_id) as $prg) {
1646  foreach ($prg->getProgressesOf($usr_id) as $progress) {
1647  if ($progress->getStatus() !== ilStudyProgrammeProgress::STATUS_IN_PROGRESS) {
1648  continue;
1649  }
1650  $assignments = $prg->getAssignmentsOfSingleProgramForUser($usr_id);
1651  $next_membership_source = $prg->getApplicableMembershipSourceForUser($usr_id, $src_type);
1652 
1653  foreach ($assignments as $assignment) {
1654  if (!is_null($next_membership_source) && $next_membership_source->isEnabled()) {
1655  $new_src_type = $next_membership_source->getSourceType();
1656  $assigned_by = ilStudyProgrammeAutoMembershipSource::SOURCE_MAPPING[$new_src_type];
1657  $assignment = $assignment->withLastChange($assigned_by, $now);
1658  $prg->assignment_repository->update($assignment);
1659  break;
1660  } else {
1661  $assignment_repository = ilStudyProgrammeDIC::dic()['ilStudyProgrammeUserAssignmentDB'];
1662  $user_assignment = $assignment_repository->getInstanceByModel($assignment);
1663  $prg->removeAssignment($user_assignment);
1664  }
1665  }
1666  }
1667  }
1668  }
1669 
1673  public function getApplicableMembershipSourceForUser(int $usr_id, string $exclude_type)
1674  {
1675  foreach ($this->getAutomaticMembershipSources() as $ams) {
1676  $src_type = $ams->getSourceType();
1677  if ($src_type !== $exclude_type) {
1678  $source_members = $this->getMembersOfMembershipSource($src_type, $ams->getSourceId());
1679  if (in_array($usr_id, $source_members)) {
1680  return $ams;
1681  }
1682  }
1683  }
1684  return null;
1685  }
1686 
1688  // HELPERS
1690 
1694  protected function updateLastChange() : void
1695  {
1696  $this->getSettings()->updateLastChange();
1697  if ($parent = $this->getParent()) {
1698  $parent->updateLastChange();
1699  }
1700  $this->update();
1701  }
1702 
1709  protected function getIdsFromNodesOnPathFromRootToHere(bool $include_references = false) : array
1710  {
1711  $prg_ids = array_map(function ($par) {
1712  return $par->getId();
1713  }, $this->getParents($include_references));
1714  $prg_ids[] = $this->getId();
1715  return $prg_ids;
1716  }
1717 
1723  protected function getAssignmentsRaw() : array
1724  {
1725  $assignments = [];
1726  foreach ($this->getIdsFromNodesOnPathFromRootToHere(true) as $prg_id) {
1727  $assignments = array_merge($this->assignment_repository->getByPrgId($prg_id), $assignments);
1728  }
1729  usort(
1730  $assignments,
1731  function (ilStudyProgrammeAssignment $a_one, ilStudyProgrammeAssignment $a_other) {
1732  return strcmp(
1733  $a_one->getLastChange()->format('Y-m-d'),
1734  $a_other->getLastChange()->format('Y-m-d')
1735  );
1736  }
1737  );
1738  return $assignments;
1739  }
1740 
1745  public static function setProgressesCompletedFor(int $a_obj_id, int $a_user_id) : void
1746  {
1747  // We only use courses via crs_refs
1748  $type = ilObject::_lookupType($a_obj_id);
1749  if ($type == "crsr") {
1750  foreach (ilObject::_getAllReferences($a_obj_id) as $ref_id) {
1751  self::setProgressesCompletedIfParentIsProgrammeInLPCompletedMode((int) $ref_id, $a_obj_id, $a_user_id);
1752  }
1753  }
1754  }
1755 
1760  int $a_ref_id,
1761  int $a_obj_id,
1762  int $a_user_id
1763  ) : void {
1764  global $DIC; // TODO: replace this by a settable static for testing purpose?
1765  $tree = $DIC['tree'];
1766  $node_data = $tree->getParentNodeData($a_ref_id);
1767  if ($node_data["type"] !== "prg") {
1768  return;
1769  }
1770  self::initStudyProgrammeCache();
1771  $prg = ilObjStudyProgramme::getInstanceByRefId($node_data["child"]);
1772  if ($prg->getLPMode() != ilStudyProgrammeSettings::MODE_LP_COMPLETED) {
1773  return;
1774  }
1775 
1776  $now = new DateTimeImmutable();
1777  foreach ($prg->getProgressesOf($a_user_id) as $progress) {
1778  $progress_deadline = $progress->getDeadline();
1779  if (
1780  (is_null($progress_deadline) || $progress_deadline >= $now)
1781  && $progress->getStatus() === ilStudyProgrammeProgress::STATUS_IN_PROGRESS
1782  ) {
1783  $prg->succeed($progress->getId(), $a_obj_id);
1784  }
1785  }
1786  }
1787 
1794  protected static function getParentId(ilObject $a_object)
1795  {
1796  global $DIC;
1797  $tree = $DIC['tree'];
1798  if (!$tree->isInTree($a_object->getRefId())) {
1799  return null;
1800  }
1801 
1802  $nd = $tree->getParentNodeData($a_object->getRefId());
1803  return $nd["obj_id"];
1804  }
1805 
1806 
1807  public function updateCustomIcon() : void
1808  {
1809  $customIcon = $this->custom_icon_factory->getByObjId($this->getId(), $this->getType());
1810  $subtype = $this->getSubType();
1811 
1812  if ($subtype
1813  && $this->webdir->has($subtype->getIconPath(true))
1814  && $subtype->getIconPath(true) != $subtype->getIconPath(false)
1815  ) {
1816  $icon = $subtype->getIconPath(true);
1817  $customIcon->saveFromSourceFile($icon);
1818  } else {
1819  $customIcon->remove();
1820  }
1821  }
1822 
1824  // HOOKS
1826 
1837  public static function getCreatableSubObjects(array $a_subobjects, $a_ref_id) : array
1838  {
1839  if ($a_ref_id === null) {
1840  return $a_subobjects;
1841  }
1842 
1843  if (ilObject::_lookupType($a_ref_id, true) != "prg") {
1844  throw new ilException("Ref-Id '$a_ref_id' does not belong to a study programme object.");
1845  }
1846 
1848 
1849  $mode = $parent->getLPMode();
1850 
1851  switch ($mode) {
1853  $possible_subobjects = $a_subobjects;
1854  break;
1856  $possible_subobjects = [
1857  "prg" => $a_subobjects["prg"],
1858  "prgr" => $a_subobjects["prgr"]
1859  ];
1860  break;
1862  $possible_subobjects = ['crsr' => $a_subobjects['crsr']];
1863  break;
1864  default:
1865  throw new ilException("Undefined mode for study programme: '$mode'");
1866  }
1867 
1868  if ($parent->hasAutomaticContentCategories()) {
1869  $possible_subobjects = array_filter(
1870  $possible_subobjects,
1871  function ($subtype) {
1872  return $subtype === 'crsr';
1873  },
1874  ARRAY_FILTER_USE_KEY
1875  );
1876  }
1877  return $possible_subobjects;
1878  }
1879 
1880  public static function sendReAssignedMail(int $ref_id, int $usr_id) : bool
1881  {
1882  global $DIC;
1883  $lng = $DIC['lng'];
1884  $lng->loadLanguageModule("prg");
1885  $lng->loadLanguageModule("mail");
1886  $log = $DIC['ilLog'];
1887 
1889  $prg_should_send_mail = $prg->getSettings()->getAutoMailSettings()->getSendReAssignedMail();
1890  if (!$prg_should_send_mail) {
1891  $log->write("Send re assign mail is deactivated in study programme settings");
1892  return false;
1893  }
1894 
1895  $subject = $lng->txt("re_assigned_mail_subject");
1896  $gender = ilObjUser::_lookupGender($usr_id);
1897  $name = ilObjUser::_lookupFullname($usr_id);
1898  $body = sprintf(
1899  $lng->txt("re_assigned_mail_body"),
1900  $lng->txt("mail_salutation_" . $gender),
1901  $name,
1902  $prg->getTitle()
1903  );
1904 
1905  $send = true;
1906  $mail = new ilMail(ANONYMOUS_USER_ID);
1907  try {
1908  $mail->enqueue(
1909  ilObjUser::_lookupLogin($usr_id),
1910  '',
1911  '',
1912  $subject,
1913  $body,
1914  null
1915  );
1916  } catch (Exception $e) {
1917  $send = false;
1918  }
1919 
1920  return $send;
1921  }
1922 
1923  public static function sendInvalidateMail(int $ref_id, int $usr_id) : bool
1924  {
1925  global $DIC;
1926  $lng = $DIC['lng'];
1927  $lng->loadLanguageModule("prg");
1928  $lng->loadLanguageModule("mail");
1929 
1931 
1932  $subject = $lng->txt("invalidate_mail_subject");
1933  $gender = ilObjUser::_lookupGender($usr_id);
1934  $name = ilObjUser::_lookupFullname($usr_id);
1935  $body = sprintf(
1936  $lng->txt("invalidate_mail_body"),
1937  $lng->txt("mail_salutation_" . $gender),
1938  $name,
1939  $prg->getTitle()
1940  );
1941 
1942  $send = true;
1943  $mail = new ilMail(ANONYMOUS_USER_ID);
1944  try {
1945  $mail->enqueue(
1946  ilObjUser::_lookupLogin($usr_id),
1947  '',
1948  '',
1949  $subject,
1950  $body,
1951  null
1952  );
1953  } catch (Exception $e) {
1954  $send = false;
1955  }
1956 
1957  return $send;
1958  }
1959 
1960  public static function sendInformToReAssignMail(int $progress_id, int $usr_id) : void
1961  {
1962  global $DIC;
1963  $lng = $DIC['lng'];
1964  $log = $DIC['ilLog'];
1965  $lng->loadLanguageModule("prg");
1966  $lng->loadLanguageModule("mail");
1967 
1968  $usr_progress_db = ilStudyProgrammeDIC::dic()['ilStudyProgrammeUserProgressDB'];
1969  $usr_progress = $usr_progress_db->get($progress_id);
1970  $prg = ilObjStudyProgramme::getInstanceByObjId($usr_progress->getNodeId());
1971  $prg_should_send_mail = $prg->getSettings()->getAutoMailSettings()
1972  ->getReminderNotRestartedByUserDays() > 0;
1973 
1974  if (!$prg_should_send_mail) {
1975  $log->write("Send info to re assign mail is deactivated in study programme settings");
1976  return;
1977  }
1978 
1979  $subject = $lng->txt("info_to_re_assign_mail_subject");
1980  $gender = ilObjUser::_lookupGender($usr_id);
1981  $name = ilObjUser::_lookupFullname($usr_id);
1982  $body = sprintf(
1983  $lng->txt("info_to_re_assign_mail_body"),
1984  $lng->txt("mail_salutation_" . $gender),
1985  $name,
1986  $prg->getTitle()
1987  );
1988 
1989  $send = true;
1990  $mail = new ilMail(ANONYMOUS_USER_ID);
1991  try {
1992  $mail->enqueue(
1993  ilObjUser::_lookupLogin($usr_id),
1994  '',
1995  '',
1996  $subject,
1997  $body,
1998  null
1999  );
2000  } catch (Exception $e) {
2001  $send = false;
2002  }
2003 
2004  if ($send) {
2005  $usr_progress_db->sentExpiryInfoFor($usr_progress->getId());
2006  }
2007  }
2008 
2012  public static function sendRiskyToFailMail(int $progress_id, int $usr_id) : void
2013  {
2014  global $DIC;
2015  $lng = $DIC['lng'];
2016  $log = $DIC['ilLog'];
2017  $lng->loadLanguageModule("prg");
2018  $lng->loadLanguageModule("mail");
2019 
2020  $usr_progress_db = ilStudyProgrammeDIC::dic()['ilStudyProgrammeUserProgressDB'];
2021  $usr_progress = $usr_progress_db->get($progress_id);
2022  $prg = ilObjStudyProgramme::getInstanceByObjId($usr_progress->getNodeId());
2023  $prg_should_send_mail = $prg->getSettings()->getAutoMailSettings()
2024  ->getProcessingEndsNotSuccessfulDays() > 0;
2025 
2026  if (!$prg_should_send_mail) {
2027  $log->write("Send risky to fail mail is deactivated in study programme settings");
2028  return;
2029  }
2030 
2031  $subject = $lng->txt("risky_to_fail_mail_subject");
2032  $gender = ilObjUser::_lookupGender($usr_id);
2033  $name = ilObjUser::_lookupFullname($usr_id);
2034  $body = sprintf(
2035  $lng->txt("risky_to_fail_mail_body"),
2036  $lng->txt("mail_salutation_" . $gender),
2037  $name,
2038  $prg->getTitle()
2039  );
2040 
2041  $send = true;
2042  $mail = new ilMail(ANONYMOUS_USER_ID);
2043  try {
2044  $mail->enqueue(
2045  ilObjUser::_lookupLogin($usr_id),
2046  '',
2047  '',
2048  $subject,
2049  $body,
2050  null
2051  );
2052  } catch (Exception $e) {
2053  $send = false;
2054  }
2055 
2056  if ($send) {
2057  $usr_progress_db->sentRiskyToFailFor($usr_progress->getId());
2058  }
2059  }
2060 
2064  public function getIdsOfSuccessfulChildren(int $ass_id) : array
2065  {
2066  $children = $this->getChildren(true);
2067 
2068  $ids = array();
2069 
2070  foreach ($children as $child) {
2071  $prgrs = $child->getProgressForAssignment($ass_id);
2072  if (!$prgrs->isSuccessful()) {
2073  continue;
2074  }
2075  $ids[] = [$child->getId(), $child->getRefId()];
2076  }
2077  if (count($ids) > 0) {
2078  return $ids;
2079  }
2080 
2081  //courses:
2082  $children = $this->getTree()->getChildsByType($this->getRefId(), "crsr");
2083  $usr_id = $this->getAssignmentRepository()->get($ass_id)->getUserId();
2084  foreach ($children as $child) {
2085  if (ilObject::_exists($child['ref_id'], true) &&
2086  is_null(ilObject::_lookupDeletedDate($child['ref_id']))
2087  ) {
2088  continue;
2089  }
2090  $crs_id = ilContainerReference::_lookupTargetId($child["obj_id"]);
2091 
2092  if (ilLPStatus::_hasUserCompleted($crs_id, $usr_id)) {
2094  $ids[] = [$crs_id, $ref_id];
2095  }
2096  }
2097  return $ids;
2098  }
2099 
2100  public function getNamesOfCompletedOrAccreditedChildren(int $ass_id) : array
2101  {
2102  return array_map(
2103  function ($entry) {
2104  list($obj_id, $ref_id) = $entry;
2105  return ilObject::_lookupTitle($obj_id);
2106  },
2107  $this->getIdsOfSuccessfulChildren($ass_id)
2108  );
2109  }
2110 
2111  protected function getLoggedInUserId() : int
2112  {
2113  return (int) $this->ilUser->getId();
2114  }
2115 
2116  protected function getNow() : DateTimeImmutable
2117  {
2118  return new DateTimeImmutable();
2119  }
2120 
2122  {
2123  return $this->assignment_repository->get($progress->getAssignmentId());
2124  }
2125 
2127  {
2128  return $this->settings_repository->get($progress->getNodeId());
2129  }
2130 
2131 
2132  protected function getObjIdsOfChildren(int $node_obj_id) : array
2133  {
2134  $node_ref_id = self::getRefIdFor($node_obj_id);
2135 
2136  $prgs = $this->tree->getChildsByType($node_ref_id, "prg");
2137  $prg_ids = array_map(
2138  function ($nd) {
2139  return (int) $nd['obj_id'];
2140  },
2141  $prgs
2142  );
2143 
2144  $prg_ref_ids = [];
2145  $prg_refs = $this->tree->getChildsByType($node_ref_id, "prgr");
2146  foreach ($prg_refs as $ref) {
2147  $ref_obj = new ilObjStudyProgrammeReference((int) $ref['ref_id']);
2148  $prg_ref_ids[] = (int) $ref_obj->getReferencedObject()->getId();
2149  }
2150 
2151  return array_merge($prg_ids, $prg_ref_ids);
2152  }
2153 
2157  public function getChildrenProgress($progress) : array
2158  {
2159  $children = $this->getObjIdsOfChildren($progress->getNodeId());
2160  $ass_id = $progress->getAssignmentId();
2161 
2162  $ret = [];
2163  foreach ($children as $child_obj_id) {
2164  $progress = $this->getProgressRepository()->getByPrgIdAndAssignmentId($child_obj_id, $ass_id);
2165  $ret[] = $this->getProgressRepository()->getByPrgIdAndAssignmentId($child_obj_id, $ass_id);
2166  }
2167 
2168  return $ret;
2169  }
2170 
2172  {
2173  $assignment_id = $progress->getAssignmentId();
2174 
2175  $prg_ref_id = $this->getRefIdFor($progress->getNodeId());
2176  $parent_node = $this->tree->getParentNodeData($prg_ref_id);
2177  $parent_prg_obj_id = (int) $parent_node["obj_id"];
2178 
2179  $parent_progress = $this->progress_repository->getByPrgIdAndAssignmentId(
2180  $parent_prg_obj_id,
2181  $assignment_id
2182  );
2183 
2184  if (!$parent_progress) {
2185  //maybe by reference?
2186  $obj_ids_referencing_node = ilContainerReference::_lookupSourceIds($progress->getNodeId());
2187  foreach (ilContainerReference::_lookupSourceIds($progress->getNodeId()) as $obj_id_referencing) {
2188  foreach (ilObject::_getAllReferences($obj_id_referencing) as $ref_id_referencing) {
2189  $parent_node = $this->tree->getParentNodeData($ref_id_referencing);
2190  $parent_prg_obj_id = (int) $parent_node["obj_id"];
2191 
2192  $parent_progress = $this->progress_repository->getByPrgIdAndAssignmentId(
2193  $parent_prg_obj_id,
2194  $assignment_id
2195  );
2196  if ($parent_progress) {
2197  return $parent_progress;
2198  }
2199  }
2200  }
2201  }
2202 
2203  return $parent_progress; //...which is null here
2204  }
2205 
2206 
2208  {
2209  $sum = 0;
2210  foreach ($this->getChildrenProgress($progress) as $child_progress) {
2211  if (!is_null($child_progress) && $child_progress->isRelevant()) {
2212  $sum += $child_progress->getAmountOfPoints();
2213  }
2214  }
2215  return $sum;
2216  }
2217 
2219  {
2220  $sum = 0;
2221  $children = $this->getChildrenProgress($progress);
2222  foreach ($children as $child_progress) {
2223  if (!is_null($child_progress) && $child_progress->isSuccessful()) {
2224  $sum += $child_progress->getAmountOfPoints();
2225  }
2226  }
2227  return $sum;
2228  }
2229 
2230  protected function refreshLPStatus(int $usr_id, int $node_obj_id = null) : void
2231  {
2232  if (is_null($node_obj_id)) {
2233  $node_obj_id = $this->getId();
2234  }
2235  ilLPStatusWrapper::_updateStatus($node_obj_id, $usr_id);
2236  }
2237 
2239  {
2240  $parent_progress = $this->getParentProgress($progress);
2241  if (is_null($parent_progress)) {
2242  return $progress;
2243  }
2244  $parent_progress = $this->recalculateProgressStatus($parent_progress);
2245  $this->getProgressRepository()->update($parent_progress);
2246  return $this->updateParentProgress($parent_progress); //recurse
2247  }
2248 
2250  {
2251  if (!$progress->isRelevant()) {
2252  return $progress;
2253  }
2254  $node_settings = $this->getSettingsRepository()->get($progress->getNodeId());
2255  $completion_mode = $node_settings->getLPMode();
2256 
2257  if ($completion_mode === ilStudyProgrammeSettings::MODE_UNDEFINED) {
2258  return $progress;
2259  }
2260 
2261  $required_points = $progress->getAmountOfPoints();
2262 
2263  if ($completion_mode === ilStudyProgrammeSettings::MODE_LP_COMPLETED) {
2264  $achieved_points = 0;
2265 
2266  $node_ref = self::getRefIdFor($progress->getNodeId());
2267  $children = $this->tree->getChildsByType($node_ref, "crsr");
2268  foreach ($children as $child) {
2269  if (ilObject::_exists($child['ref_id'], true) &&
2270  is_null(ilObject::_lookupDeletedDate($child['ref_id']))
2271  ) {
2272  $crs_id = ilContainerReference::_lookupTargetId($child["obj_id"]);
2273  if (ilLPStatus::_hasUserCompleted($crs_id, $progress->getUserId())) {
2274  $achieved_points = $progress->getAmountOfPoints();
2275  break;
2276  }
2277  }
2278  }
2279  }
2280 
2281  if ($completion_mode === ilStudyProgrammeSettings::MODE_POINTS) {
2282  $achieved_points = $this->getAchievedPointsOfChildren($progress);
2283  }
2284 
2285  $progress = $progress->withCurrentAmountOfPoints($achieved_points);
2286  $successful = ($achieved_points >= $required_points);
2287 
2288  if ($successful && !$progress->isSuccessful()) {
2289  $progress = $progress
2291  ->withCompletion(null, new DateTimeImmutable());
2292 
2293  // there was a status change, so:
2294  $this->events->userSuccessful($progress);
2295  }
2296 
2297  if (!$successful && $progress->isSuccessful()) {
2298  $progress = $progress
2300  ->withCompletion(null, null)
2301  ->withValidityOfQualification(null);
2302  }
2303 
2304  return $progress;
2305  }
2306 
2307 
2308  protected function applyProgressDeadline(ilStudyProgrammeProgress $progress, int $acting_usr_id = null) : ilStudyProgrammeProgress
2309  {
2310  $today = $this->getNow();
2312  $deadline = $progress->getDeadline();
2313 
2314  if (is_null($acting_usr_id)) {
2315  $acting_usr_id = $this->getLoggedInUserId();
2316  }
2317 
2318  switch ($progress->getStatus()) {
2319 
2321  if (!is_null($deadline)
2322  && $deadline->format($format) < $today->format($format)
2323  ) {
2324  $progress = $progress->markFailed($this->getNow(), $acting_usr_id);
2325  }
2326  break;
2327 
2329  if (is_null($deadline)
2330  || $deadline->format($format) >= $today->format($format)
2331  ) {
2332  $progress = $progress->markNotFailed($this->getNow(), $acting_usr_id);
2333  }
2334  break;
2335  }
2336 
2337  return $progress;
2338  }
2339 
2340  public function markAccredited(
2341  int $progress_id,
2342  int $acting_usr_id,
2343  ilPRGMessageCollection $err_collection
2344  ) : void {
2345  $progress = $this->getProgressRepository()->get($progress_id);
2346  $new_status = $progress::STATUS_ACCREDITED;
2347 
2348  if (!$progress->isRelevant()) {
2349  $err_collection->add(false, 'will_not_modify_irrelevant_progress', $this->getProgressIdString($progress));
2350  return;
2351  }
2352  if ($progress->getStatus() === $new_status) {
2353  $err_collection->add(false, 'status_unchanged', $this->getProgressIdString($progress));
2354  return;
2355  }
2356  if (!$progress->isTransitionAllowedTo($new_status)) {
2357  $err_collection->add(false, 'status_transition_not_allowed', $this->getProgressIdString($progress));
2358  return;
2359  }
2360 
2361  $progress = $progress
2362  ->markAccredited($this->getNow(), $acting_usr_id)
2363  ->withCurrentAmountOfPoints($progress->getAmountOfPoints());
2364 
2365  if (!$progress->getValidityOfQualification()) {
2366  $progress = $this->updateProgressValidityFromSettings($progress);
2367  }
2368 
2369  $this->events->userSuccessful($progress);
2370 
2371  $this->getProgressRepository()->update($progress);
2372  $this->refreshLPStatus($progress->getUserId());
2373  $this->updateParentProgress($progress);
2374  $err_collection->add(true, 'status_changed', $this->getProgressIdString($progress));
2375  }
2376 
2377  public function unmarkAccredited(
2378  int $progress_id,
2379  int $acting_usr_id,
2380  ilPRGMessageCollection $err_collection
2381  ) : void {
2382  $progress = $this->getProgressRepository()->get($progress_id);
2383  $new_status = $progress::STATUS_IN_PROGRESS;
2384 
2385  if (!$progress->isRelevant()) {
2386  $err_collection->add(false, 'will_not_modify_irrelevant_progress', $this->getProgressIdString($progress));
2387  return;
2388  }
2389  if ($progress->getStatus() === $new_status) {
2390  $err_collection->add(false, 'status_unchanged', $this->getProgressIdString($progress));
2391  return;
2392  }
2393  if (!$progress->isTransitionAllowedTo($new_status)
2394  //special case: completion may not be revoked manually (but might be as a calculation-result of underlying progresses)
2395  || $progress->getStatus() === $progress::STATUS_COMPLETED
2396  ) {
2397  $err_collection->add(false, 'status_transition_not_allowed', $this->getProgressIdString($progress));
2398  return;
2399  }
2400 
2401  $progress = $progress
2402  ->unmarkAccredited($this->getNow(), $acting_usr_id);
2403 
2404  $achieved_points = $this->getAchievedPointsOfChildren($progress);
2405  $progress = $progress->withCurrentAmountOfPoints($achieved_points);
2406 
2407  $progress = $this->applyProgressDeadline($progress);
2408 
2409  $this->getProgressRepository()->update($progress);
2410  $this->refreshLPStatus($progress->getUserId());
2411  $this->updateParentProgress($progress);
2412  $err_collection->add(true, 'status_changed', $this->getProgressIdString($progress));
2413  }
2414 
2415  public function markFailed(int $progress_id, int $acting_usr_id) : void
2416  {
2417  $progress = $this->getProgressRepository()->get($progress_id);
2418  if (!$progress->isRelevant()) {
2419  return;
2420  }
2421  $progress = $progress->markFailed($this->getNow(), $acting_usr_id);
2422 
2423  $this->getProgressRepository()->update($progress);
2424  $this->refreshLPStatus($progress->getUserId());
2425  $this->updateParentProgress($progress);
2426  }
2427 
2428  public function markNotFailed(int $progress_id, int $acting_usr_id) : void
2429  {
2430  if (!$progress->isRelevant()) {
2431  return;
2432  }
2433  $progress = $this->getProgressRepository()->get($progress_id)
2434  ->markNotFailed($this->getNow(), $acting_usr_id);
2435 
2436  $this->getProgressRepository()->update($progress);
2437  $this->refreshLPStatus($progress->getUserId());
2438  $this->updateParentProgress($progress);
2439  }
2440 
2441  public function markNotRelevant(
2442  int $progress_id,
2443  int $acting_usr_id,
2444  ilPRGMessageCollection $err_collection
2445  ) : void {
2446  $progress = $this->getProgressRepository()->get($progress_id);
2447  if (!$progress->isRelevant()) {
2448  $err_collection->add(false, 'will_not_modify_irrelevant_progress', $this->getProgressIdString($progress));
2449  return;
2450  }
2451  if (is_null($this->getParentProgress($progress))) {
2452  $err_collection->add(false, 'will_not_set_top_progress_to_irrelevant', $this->getProgressIdString($progress));
2453  return;
2454  }
2455  if ($progress->getStatus() === ilStudyProgrammeProgress::STATUS_COMPLETED) {
2456  $err_collection->add(false, 'will_not_set_completed_progress_to_irrelevant_', $this->getProgressIdString($progress));
2457  return;
2458  }
2459 
2460  $progress = $progress
2461  ->markNotRelevant($this->getNow(), $acting_usr_id);
2462 
2463  $this->getProgressRepository()->update($progress);
2464  $this->refreshLPStatus($progress->getUserId());
2465  $this->updateParentProgress($progress);
2466  $err_collection->add(true, 'set_to_irrelevant', $this->getProgressIdString($progress));
2467  }
2468 
2469  public function markRelevant(
2470  int $progress_id,
2471  int $acting_usr_id,
2472  ilPRGMessageCollection $err_collection
2473  ) : void {
2474  $progress = $this->getProgressRepository()->get($progress_id);
2475  if ($progress->isRelevant()) {
2476  $err_collection->add(false, 'will_not_modify_relevant_progress', $this->getProgressIdString($progress));
2477  return;
2478  }
2479 
2480  $progress = $progress
2481  ->markRelevant($this->getNow(), $acting_usr_id);
2482 
2483  $progress = $this->recalculateProgressStatus($progress);
2484 
2485  $this->getProgressRepository()->update($progress);
2486  $this->refreshLPStatus($progress->getUserId());
2487  $this->updateParentProgress($progress);
2488  $err_collection->add(true, 'set_to_relevant', $this->getProgressIdString($progress));
2489  }
2490 
2491  public function invalidate(int $progress_id) : void
2492  {
2493  $progress = $this->getProgressRepository()->get($progress_id)
2494  ->invalidate();
2495 
2496  $this->getProgressRepository()->update($progress);
2497  $this->refreshLPStatus($progress->getUserId());
2498  $this->updateParentProgress($progress);
2499  }
2500 
2501  public function succeed(int $progress_id, int $triggering_obj_id) : void
2502  {
2503  $progress = $this->getProgressRepository()->get($progress_id)
2504  ->succeed($this->getNow(), $triggering_obj_id);
2505 
2506  $achieved_points = $progress->getAmountOfPoints();
2507  $progress = $progress->withCurrentAmountOfPoints($achieved_points);
2508  $progress = $this->updateProgressValidityFromSettings($progress);
2509 
2510  $this->getProgressRepository()->update($progress);
2511 
2512  $this->refreshLPStatus($progress->getUserId());
2513  $this->updateParentProgress($progress);
2514  }
2515 
2516  public function changeProgressDeadline(
2517  int $progress_id,
2518  int $acting_usr_id,
2519  ilPRGMessageCollection $err_collection,
2520  ?DateTimeImmutable $deadline
2521  ) : void {
2522  $progress = $this->getProgressRepository()->get($progress_id);
2523 
2524  if (!$progress->isRelevant()) {
2525  $err_collection->add(false, 'will_not_modify_irrelevant_progress', $this->getProgressIdString($progress));
2526  return;
2527  }
2528  if ($progress->isSuccessful()) {
2529  $err_collection->add(false, 'will_not_modify_deadline_on_successful_progress', $this->getProgressIdString($progress));
2530  return;
2531  }
2532 
2533  $progress = $progress
2534  ->withDeadline($deadline)
2535  ->withLastChange($acting_usr_id, $this->getNow())
2536  ->withIndividualModifications(true);
2537 
2538  $progress = $this->applyProgressDeadline($progress, $acting_usr_id);
2539 
2540  $this->getProgressRepository()->update($progress);
2541  $this->refreshLPStatus($progress->getUserId());
2542  $this->updateParentProgress($progress);
2543  $err_collection->add(true, 'deadline_updated', $this->getProgressIdString($progress));
2544  }
2545 
2547  int $progress_id,
2548  int $acting_usr_id,
2549  ilPRGMessageCollection $err_collection,
2550  ?DateTimeImmutable $validity
2551  ) : void {
2552  $progress = $this->getProgressRepository()->get($progress_id);
2553 
2554  if (!$progress->isRelevant()) {
2555  $err_collection->add(false, 'will_not_modify_irrelevant_progress', $this->getProgressIdString($progress));
2556  return;
2557  }
2558  if (!$progress->isSuccessful()) {
2559  $err_collection->add(false, 'will_not_modify_validity_on_non_successful_progress', $this->getProgressIdString($progress));
2560  return;
2561  }
2562 
2563  $progress = $progress
2564  ->withValidityOfQualification($validity)
2565  ->withLastChange($acting_usr_id, $this->getNow())
2566  ->withIndividualModifications(true);
2567 
2568  $this->getProgressRepository()->update($progress);
2569  $err_collection->add(true, 'validity_updated', $this->getProgressIdString($progress));
2570 
2571  //update LearningProgress, but this does not affect upper progresses
2572  $this->refreshLPStatus($progress->getUserId());
2573  }
2574 
2575  public function changeAmountOfPoints(
2576  int $progress_id,
2577  int $acting_usr_id,
2578  ilPRGMessageCollection $err_collection,
2579  ?int $points
2580  ) : void {
2581  $progress = $this->getProgressRepository()->get($progress_id);
2582 
2583  if (!$progress->isRelevant()) {
2584  $err_collection->add(false, 'will_not_modify_irrelevant_progress', $this->getProgressIdString($progress));
2585  return;
2586  }
2587  if ($progress->isSuccessful()) {
2588  $err_collection->add(false, 'will_not_modify_successful_progress', $this->getProgressIdString($progress));
2589  return;
2590  }
2591 
2592  $progress = $progress
2593  ->withAmountOfPoints($points)
2594  ->withLastChange($acting_usr_id, $this->getNow())
2595  ->withIndividualModifications(true);
2596 
2597  $progress = $this->recalculateProgressStatus($progress);
2598 
2599  $this->getProgressRepository()->update($progress);
2600  $err_collection->add(true, 'required_points_updated', $this->getProgressIdString($progress));
2601  $this->refreshLPStatus($progress->getUserId());
2602  $this->updateParentProgress($progress);
2603  }
2604 
2605  public function updatePlanFromRepository(
2606  int $progress_id,
2607  int $acting_usr_id,
2608  ilPRGMessageCollection $err_collection = null
2609  ) : void {
2610  $progress = $this->getProgressRepository()->get($progress_id);
2611  $assignment_id = $progress->getAssignmentId();
2612  $progresses = $this->getProgressRepository()->getByAssignmentId($assignment_id);
2613 
2614  $leafs = [];
2615  foreach ($progresses as $progress) {
2616  // get node for progress; settings are pulled from the node, which might not be "this"
2617  $node = $this->getPrgInstanceByObjId($progress->getNodeId());
2618  $progress = $node->updateProgressRelevanceFromSettings($progress);
2619  $progress = $node->resetProgressToSettings($progress, $acting_usr_id);
2620  $this->getProgressRepository()->update($progress);
2621 
2622  if (!$node->hasChildren(true)) {
2623  $leafs[] = [$node, $progress];
2624  }
2625  }
2626 
2627  foreach ($leafs as $leaf) {
2628  list($node, $progress) = $leaf;
2629  $progress = $this->recalculateProgressStatus($progress);
2630  $progress = $this->applyProgressDeadline($progress);
2631  $this->getProgressRepository()->update($progress);
2632  $this->refreshLPStatus($progress->getUserId(), (int) $node->getId());
2633  $this->updateParentProgress($progress);
2634  }
2635  }
2636 
2637  protected function resetProgressToSettings(
2638  ilStudyProgrammeProgress $progress,
2639  int $acting_usr_id
2641  if ($progress->isRelevant()) {
2642  $progress = $this->updateProgressValidityFromSettings($progress);
2643  $progress = $this->updateProgressDeadlineFromSettings($progress);
2644  } else {
2645  $progress = $progress
2647  ->withDeadline(null);
2648  }
2649 
2650  $progress = $progress->withAmountOfPoints($this->getPoints());
2651  $progress = $progress
2652  ->withLastChange($acting_usr_id, $this->getNow())
2653  ->withIndividualModifications(false);
2654 
2655  return $progress;
2656  }
2657 
2659  {
2660  if ($this->isActive() && !$progress->isRelevant()) {
2662  }
2663  if (!$this->isActive() && $progress->isInProgress()) {
2664  $progress = $progress->withStatus(ilStudyProgrammeProgress::STATUS_NOT_RELEVANT);
2665  }
2666 
2667  return $progress;
2668  }
2669 
2671  {
2672  $cdate = $progress->getCompletionDate();
2673  if (!$cdate
2674  || $progress->isSuccessful() === false
2675  ) {
2676  return $progress;
2677  }
2678 
2679  $settings = $this->getSettings()->getValidityOfQualificationSettings();
2680  $period = $settings->getQualificationPeriod();
2681  $date = $settings->getQualificationDate();
2682 
2683  if ($date) {
2684  $date = DateTimeImmutable::createFromMutable($date);
2685  }
2686 
2687  if ($period) {
2688  $date = $cdate->add(new DateInterval('P' . $period . 'D'));
2689  }
2690 
2691  return $progress->withValidityOfQualification($date);
2692  }
2693 
2695  {
2696  $settings = $this->getSettings()->getDeadlineSettings();
2697  $period = $settings->getDeadlinePeriod();
2698  $date = $settings->getDeadlineDate();
2699  if ($date) {
2700  $date = DateTimeImmutable::createFromMutable($date);
2701  }
2702 
2703  if ($period) {
2704  $date = $progress->getAssignmentDate();
2705  $date = $date->add(new DateInterval('P' . $period . 'D'));
2706  }
2707  return $progress->withDeadline($date);
2708  }
2709 
2710  public function canBeCompleted(ilStudyProgrammeProgress $progress) : bool
2711  {
2713  return true;
2714  }
2715  $possible_points = $this->getPossiblePointsOfRelevantChildren($progress);
2716  return $possible_points >= $progress->getAmountOfPoints();
2717  }
2718 
2722  public function statusToRepr($a_status)
2723  {
2724  $lng = $this->lng;
2725  $lng->loadLanguageModule("prg");
2726 
2728  return $lng->txt("prg_status_in_progress");
2729  }
2730  if ($a_status == ilStudyProgrammeProgress::STATUS_COMPLETED) {
2731  return $lng->txt("prg_status_completed");
2732  }
2734  return $lng->txt("prg_status_accredited");
2735  }
2737  return $lng->txt("prg_status_not_relevant");
2738  }
2739  if ($a_status == ilStudyProgrammeProgress::STATUS_FAILED) {
2740  return $lng->txt("prg_status_failed");
2741  }
2742  throw new ilException("Unknown status: '$a_status'");
2743  }
2744 
2745  protected function getProgressIdString(ilStudyProgrammeProgress $progress) : string
2746  {
2747  $username = ilObjUser::_lookupFullname($progress->getUserId());
2748  return sprintf(
2749  '%s, progress-id %s',
2750  $username,
2751  $progress->getId()
2752  );
2753  }
2754 }
setStatus(int $a_status)
Set the status of the node.
static _lookupLogin($a_user_id)
lookup login
getNamesOfCompletedOrAccreditedChildren(int $ass_id)
The interface a class has to fullfill if it should be used as leaf in a program.
updateAllAssignments()
Update all assignments to this program node.
adjustLPMode()
Adjust the lp mode to match current state of tree:
static _lookupDeletedDate($a_ref_id)
only called in ilObjectGUI::insertSavedNodes
addNode(ilObjStudyProgramme $a_prg)
Inserts another ilObjStudyProgramme in this object.
statusToRepr($a_status)
Get a user readable representation of a status.
clearLPChildrenCache()
Clear the cached lp children.
enableAutomaticMembershipSource(string $type, int $src_id, $assign_now=false)
Enable a membership source.
addLeaf(ilStudyProgrammeLeaf $a_leaf)
Insert a leaf in this object.
static addCrsToProgrammes(int $crs_ref_id, int $cat_ref_id)
Check, if a category is under surveilllance and automatically add the course.
getSubType()
Gets the SubType Object.
static sendRiskyToFailMail(int $progress_id, int $usr_id)
$data
Definition: storeScorm.php:23
storeAutomaticContentCategory(int $category_ref_id)
Store a Category with auto-content for this StudyProgramme; a category can only be referenced once (p...
assignUser(int $usr_id, int $acting_usr_id=null)
Assign a user to this node at the study program.
const ANONYMOUS_USER_ID
Definition: constants.php:25
succeed(int $progress_id, int $triggering_obj_id)
getParent()
Get the parent ilObjStudyProgramme of this object.
getIdsOfUsersWithCompletedProgress()
Get the ids of all users that have completed this programme.
isAutoContentApplicable()
AutoContent should only be available in active- or draft-mode, and only, if there is no sub-programme...
getMembers()
get usr_ids with any progress on this node
markFailed(DateTimeImmutable $date, int $acting_usr_id)
removeAssignment(ilStudyProgrammeAssignment $assignment)
Remove an assignment from this program.
static _exists($a_id, $a_reference=false, $a_type=null)
checks if an object exists in object_data
setPoints(int $a_points)
Set the amount of points.
getChildren(bool $include_references=false)
Get all ilObjStudyProgrammes that are direct children of this object.
static getProgrammesMonitoringMemberSource(string $src_type, int $src_id)
Get all StudyProgrammes monitoring this membership-source.
getAssignmentId()
Get the assignment this progress belongs to.
static _lookupFullname($a_user_id)
Lookup Full Name.
createReference()
Create a reference id for this object.
getLPChildren()
Get the leafs the study programme contains.
markNotFailed(DateTimeImmutable $date, int $acting_usr_id)
static _updateStatus($a_obj_id, $a_usr_id, $a_obj=null, $a_percentage=false, $a_force_raise=false)
Update status.
hasProgresses()
Are there any users that have a progress on this programme?
disableAutomaticMembershipSource(string $type, int $src_id)
Disable a membership source.
applyToSubTreeNodes(Closure $fun, bool $include_references=false)
Apply the given Closure to every node in the subtree starting at this object.
markRelevant(int $progress_id, int $acting_usr_id, ilPRGMessageCollection $err_collection)
static _lookupTitle($a_id)
lookup object title
markNotRelevant(int $progress_id, int $acting_usr_id, ilPRGMessageCollection $err_collection)
Component logger with individual log levels by component id.
getProgressForAssignment(int $assignment_id)
deleteAssignmentsAndProgresses()
Delete all assignments from the DB.
getAssignmentsRaw()
Get model objects for the assignments on this programm.
storeAutomaticMembershipSource(string $type, int $src_id)
Store a source to be monitored for automatic memberships.
getIdsOfUsersWithFailedProgress()
Get the ids of all users that have failed this programme.
static _lookupGender($a_user_id)
Lookup gender.
hasAssignmentOf(int $a_user_id)
Check whether user is assigned to this program or any node above.
withCurrentAmountOfPoints(int $points_cur)
Set the amount of points the user currently has achieved on this node.
getLocalMembers()
get usr_ids with assignment on this node
static saveObjRecSelection($a_obj_id, $a_sub_type="", array $a_records=null, $a_delete_before=true)
Save repository object record selection.
getAmountOfAssignmentsOf(int $a_user_id)
Get the amount of assignments a user has on this program node or any node above.
add(bool $success, string $message, string $record_identitifer)
applyProgressDeadline(ilStudyProgrammeProgress $progress, int $acting_usr_id=null)
recalculateProgressStatus(ilStudyProgrammeProgress $progress)
updatePlanFromRepository(int $progress_id, int $acting_usr_id, ilPRGMessageCollection $err_collection=null)
static sendInformToReAssignMail(int $progress_id, int $usr_id)
static _getAllReferences($a_id)
get all reference ids of object
getAchievedPointsOfChildren(ilStudyProgrammeProgress $progress)
getAmountOfLPChildren()
Get the amount of leafs, the study programme contains.
createProgressForAssignment(ilStudyProgrammeAssignment $ass, int $acting_user=null)
Create a progress on this programme for the given assignment.
static _lookupObjectId($a_ref_id)
lookup object id
getRoot()
Get the ilObjStudyProgramme that is the root node of the tree this programme is in.
static getProgrammesMonitoringCategory(int $cat_ref_id)
Get all StudyProgrammes monitoring this category.
withLastChange(int $last_change_by, DateTimeImmutable $timestamp)
static getInstanceByRefId($a_ref_id)
static removeCrsFromProgrammes(int $crs_ref_id, int $cat_ref_id)
Check, if a category is under surveilllance and automatically remove the deleted course.
static getParentId(ilObject $a_object)
Get the obj id of the parent object for the given object.
createReference()
creates reference for object
removeNode(ilObjStudyProgramme $a_prg)
Remove a node from this object.
if($format !==null) $name
Definition: metadata.php:230
hasAssignments()
Are there any assignments on this node or any node above?
changeAmountOfPoints(int $progress_id, int $acting_usr_id, ilPRGMessageCollection $err_collection, ?int $points)
getRefId()
Get the ILIAS reference id of the leaf.
getObjIdsOfChildren(int $node_obj_id)
putInTree($a_ref_id)
Put the leaf object in the repository tree under object identified by $a_ref_id.
getId()
Get the id of the progress.
getStatus()
Get the status the user has on this node.
getProgresses()
Get all progresses on this node.
$nd
Definition: error.php:12
getCompletedCourses(int $a_user_id)
Get courses in this program that the given user already completed.
Covers the persistence of settings belonging to a study programme (SP).
unmarkAccredited(int $progress_id, int $acting_usr_id, ilPRGMessageCollection $err_collection)
getProgrammeSettingsForProgress(ilStudyProgrammeProgress $progress)
foreach($_POST as $key=> $value) $res
static _lookupTitle($a_obj_id)
Overwitten from base class.
clearChildrenCache()
Clear the cached children.
getAutomaticContentCategories()
Get configuration of categories with auto-content for this StudyProgramme;.
getId()
get object id public
deleteAllAutomaticContentCategories()
Delete all configuration of categories with auto-content for this StudyProgramme;.
hasRelevantProgresses()
Are there any users that have a relevant progress on this programme?
markFailed(int $progress_id, int $acting_usr_id)
static addMemberToProgrammes(string $src_type, int $src_id, int $usr_id)
static _hasUserCompleted($a_obj_id, $a_user_id)
Lookup user object completion.
getProgressIdString(ilStudyProgrammeProgress $progress)
getParents(bool $include_references=false)
Get all parents of the node, where the root of the program comes first.
nodeInserted(ilObjStudyProgramme $a_prg)
Clears child chache and adds progress for new node.
getIdsOfUsersWithNotCompletedAndRelevantProgress()
Get the ids of all users that have not completed this programme but have a relevant progress on it...
getAssignmentsOfSingleProgramForUser(int $usr_id)
Get assignments of user to this program-node only.
getAssignmentsOf(int $a_user_id)
Get the assignments of user at this program or any node above.
global $DIC
Definition: goto.php:24
getPossiblePointsOfRelevantChildren(ilStudyProgrammeProgress $progress)
getAmountOfPoints()
Get the amount of points the user needs to achieve on the subnodes of this node.
$format
Definition: metadata.php:218
Represents one assignment of the user to a program tree.
Class ilContainer.
__construct($a_id=0, bool $a_call_by_reference=true)
ATTENTION: After using the constructor the object won&#39;t be in the cache.
static getCreatableSubObjects(array $a_subobjects, $a_ref_id)
Filter the list of possible subobjects for the objects that actually could be created on a concrete n...
hasAssignmentsOfSingleProgramForUser(int $usr_id)
Get assignments of user to this program-node only.
getType()
get object type public
static removeMemberFromProgrammes(string $src_type, int $src_id, int $usr_id)
static _lookupType($a_id, $a_reference=false)
lookup object type
static setProgressesCompletedIfParentIsProgrammeInLPCompletedMode(int $a_ref_id, int $a_obj_id, int $a_user_id)
getIdsFromNodesOnPathFromRootToHere(bool $include_references=false)
Get the ids from the nodes in the path leading from the root node of this program to this node...
static sendInvalidateMail(int $ref_id, int $usr_id)
static sendReAssignedMail(int $ref_id, int $usr_id)
getIdsOfUsersWithRelevantProgress()
Get the ids of all users that have a relevant progress at this programme.
getUserId()
Get the id of the user this progress is for.
getMembersOfMembershipSource(string $src_type, int $src_id)
Get member-ids of a certain source.
Class ilStudyProgrammeProgress.
getLastChange()
Get the timestamp of the last change on this program or sub program.
getObjId()
Get the id of the study program.
getParentProgress(ilStudyProgrammeProgress $progress)
withValidityOfQualification(DateTimeImmutable $date=null)
removeLeaf(ilStudyProgrammeLeaf $a_leaf)
Remove a leaf from this object.
updateParentProgress(ilStudyProgrammeProgress $progress)
changeProgressValidityDate(int $progress_id, int $acting_usr_id, ilPRGMessageCollection $err_collection, ?DateTimeImmutable $validity)
addMissingProgresses()
Add missing progress records for all assignments of this programm.
getProgressesOf(int $a_user_id)
Get the progresses the user has on this node.
getReferencesTo(ilObjStudyProgramme $prg)
putInTree($a_parent_ref)
Overwritten from ilObject.
static getAllChildren(int $a_ref_id, bool $include_references=false)
Get a list of all ilObjStudyProgrammes in the subtree starting at $a_ref_id.
canBeRemoved()
Check weather a node can be removed.
getApplicableMembershipSourceForUser(int $usr_id, string $exclude_type)
getAutomaticMembershipSources()
Get sources for auto-memberships.
getDepth()
Get the depth of this StudyProgramme in the tree starting at the topmost StudyProgramme (not root nod...
hasChildren(bool $include_references=false)
Does this StudyProgramme have other ilObjStudyProgrammes as children?
__construct(Container $dic, ilPlugin $plugin)
withStatus(int $status)
Set the status of this node.
getLPChildrenIds()
Get the obj-ids of the leafs the program contains.
$ret
Definition: parser.php:6
Covers the persistence of settings belonging to a study programme (SP).
getRefId()
get reference id public
$dic
Definition: result.php:13
static createInstance()
Create an instance of ilObjStudyProgramme, put in cache.
static getRefIdFor(int $obj_id)
markAccredited(int $progress_id, int $acting_usr_id, ilPRGMessageCollection $err_collection)
deleteAutomaticContentCategories(array $category_ids=[])
Delete configuration of categories with auto-content for this StudyProgramme;.
static getLogger($a_component_id)
Get component logger.
getPoints()
Get the amount of points.
static _getInstance($a_obj_id)
get instance by obj_id
markNotFailed(int $progress_id, int $acting_usr_id)
static _lookupTargetRefId($a_obj_id)
Lookup target ref_id.
static _lookupSourceIds($a_target_id)
Get ids of all container references that target the object with the given id.
resetProgressToSettings(ilStudyProgrammeProgress $progress, int $acting_usr_id)
Exception is thrown when invariants on the program tree would be violated by manipulation of tree...
getAmountOfChildren($include_references=false)
Get the amount of other StudyProgrammes this StudyProgramme has as children.
refreshLPStatus(int $usr_id, int $node_obj_id=null)
getNodeId()
Get the obj_id of the program node this progress belongs to.
moveTo(ilObjStudyProgramme $a_new_parent)
Move this tree node to a new parent.
withAmountOfPoints(int $points)
Throws when amount of points is smaller then zero.
static _lookupTargetId($a_obj_id)
lookup target id
getAssignmentForProgress(ilStudyProgrammeProgress $progress)
deleteAllAutomaticMembershipSources()
Delete all membership sources of this StudyProgramme;.
canBeCompleted(ilStudyProgrammeProgress $progress)
static setProgressesCompletedFor(int $a_obj_id, int $a_user_id)
Set all progresses to completed where the object with given id is a leaf and that belong to the user...
updateLastChange()
Update last change timestamp on this node and its parents.
clearParentCache()
Clear the cached parent to query it again at the tree.
deleteAutomaticMembershipSource(string $type, int $src_id)
Delete a membership source.
Holds information about multi-actions, mainly in context of member-assignemnts and status changes...
changeProgressDeadline(int $progress_id, int $acting_usr_id, ilPRGMessageCollection $err_collection, ?DateTimeImmutable $deadline)
updateSettings(ilStudyProgrammeSettings $settings)
getAssignments()
Get all assignments to this program or any node above.