ILIAS  trunk Revision v11.0_alpha-3011-gc6b235a2e85
class.ilExAssignment.php
Go to the documentation of this file.
1<?php
2
28
35{
39 public const TYPE_UPLOAD = 1;
40 public const TYPE_BLOG = 2;
41 public const TYPE_PORTFOLIO = 3;
42 public const TYPE_UPLOAD_TEAM = 4;
43 public const TYPE_TEXT = 5;
44 public const TYPE_WIKI_TEAM = 6;
45
46 public const FEEDBACK_DATE_DEADLINE = 1;
47 public const FEEDBACK_DATE_SUBMISSION = 2;
48 public const FEEDBACK_DATE_CUSTOM = 3;
49
50 public const PEER_REVIEW_VALID_NONE = 1;
51 public const PEER_REVIEW_VALID_ONE = 2;
52 public const PEER_REVIEW_VALID_ALL = 3;
53
55 public const TEAMS_FORMED_BY_TUTOR = 1;
56 public const TEAMS_FORMED_BY_RANDOM = 2;
58
59 public const DEADLINE_ABSOLUTE = 0;
60 public const DEADLINE_RELATIVE = 1;
62 protected \ILIAS\Exercise\InternalGUIService $gui;
63 protected \ILIAS\Exercise\InternalDomainService $domain;
65
66 protected ilDBInterface $db;
67 protected ilLanguage $lng;
68 protected ilObjUser $user;
71
72 protected int $id = 0;
73 protected int $exc_id = 0;
74 protected int $type = 0;
75 protected ?int $start_time = null;
76 protected ?int $deadline = null;
77 protected ?int $deadline2 = null;
78 protected string $instruction = "";
79 protected string $title = "";
80 protected bool $mandatory = false;
81 protected int $order_nr = 0;
82 protected bool $peer = false; // peer review activated
83 protected int $peer_min = 0;
84 protected int $peer_unlock = 0;
85 protected int $peer_dl = 0;
86 protected int $peer_valid; // passed after submission, one or all peer feedbacks
87 protected bool $peer_file = false;
88 protected bool $peer_personal = false; // personalised peer review
89 protected ?int $peer_char = null; // minimun number of characters for peer review
90 protected bool $peer_text = false;
91 protected bool $peer_rating = false;
92 protected int $peer_crit_cat = 0;
93 protected ?string $feedback_file = null;
94 protected bool $feedback_cron = false;
95 protected int $feedback_date = 0;
96 protected int $feedback_date_custom = 0;
97 protected bool $team_tutor = false;
98 protected ?int $max_file = null;
99 protected int $portfolio_template = 0;
100 protected int $min_char_limit = 0;
101 protected int $max_char_limit = 0;
104 protected int $deadline_mode = 0;
105 protected int $relative_deadline = 0;
106 protected int $rel_deadline_last_subm = 0;
107 protected array $member_status = [];
108 protected ilLogger $log;
109 protected ?int $crit_cat = 0;
110
115 public function __construct($a_id = 0)
116 {
117 global $DIC;
118
119 $this->domain = $DIC->exercise()->internal()->domain();
120 $this->db = $DIC->database();
121 $this->lng = $DIC->language();
122 $this->user = $DIC->user();
123 $this->app_event_handler = $DIC["ilAppEventHandler"];
124 $this->types = ilExAssignmentTypes::getInstance();
125 $this->access = $DIC->access();
126 $this->domain = $DIC->exercise()->internal()->domain();
127 $this->gui = $DIC->exercise()->internal()->gui();
128
129 $this->setType(self::TYPE_UPLOAD);
130 $this->setFeedbackDate(self::FEEDBACK_DATE_DEADLINE);
131
132 $this->log = ilLoggerFactory::getLogger("exc");
133
134 if ($a_id > 0) {
135 $this->setId($a_id);
136 $this->read();
137 }
138 $this->string_transform = $DIC->refinery()
139 ->string();
140 }
141
147 public static function getInstancesByExercise(int $a_exc_id): array
148 {
149 global $DIC;
150
151 $ilDB = $DIC->database();
152
153 $set = $ilDB->query("SELECT * FROM exc_assignment " .
154 " WHERE exc_id = " . $ilDB->quote($a_exc_id, "integer") .
155 " ORDER BY order_nr");
156 $data = array();
157
158 $order_val = 10;
159 while ($rec = $ilDB->fetchAssoc($set)) {
160 // ???
161 $rec["order_val"] = $order_val;
162
163 $ass = new self();
164 $ass->initFromDB($rec);
165 $data[] = $ass;
166
167 $order_val += 10;
168 }
169
170 return $data;
171 }
172
178 public static function instructionFileGetFileOrderData(
179 array $a_file_data,
180 int $a_ass_id
181 ): array {
182 global $DIC;
183
184 $db = $DIC->database();
185 $db->setLimit(1, 0);
186
187 $result_order_val = $db->query("
188 SELECT id, order_nr
189 FROM exc_ass_file_order
190 WHERE assignment_id = {$db->quote($a_ass_id, 'integer')}
191 AND filename = {$db->quote($a_file_data['entry'], 'text')}
192 ");
193
194 $order_val = 0;
195 $order_id = 0;
196 while ($row = $db->fetchAssoc($result_order_val)) {
197 $order_val = (int) $row['order_nr'];
198 $order_id = (int) $row['id'];
199 }
200 return array($order_val, $order_id);
201 }
202
203 public function hasTeam(): bool
204 {
205 return $this->ass_type->usesTeams();
206 }
207
208 public function setId(int $a_val): void
209 {
210 $this->id = $a_val;
211 }
212
213 public function getId(): int
214 {
215 return $this->id;
216 }
217
218 public function setExerciseId(int $a_val): void
219 {
220 $this->exc_id = $a_val;
221 }
222
223 public function getExerciseId(): int
224 {
225 return $this->exc_id;
226 }
227
228 public function setStartTime(?int $a_val): void
229 {
230 $this->start_time = $a_val;
231 }
232
233 public function getStartTime(): ?int
234 {
235 return $this->start_time;
236 }
237
238 public function setDeadline(?int $a_val): void
239 {
240 $this->deadline = $a_val;
241 }
242
243 public function getDeadline(): ?int
244 {
245 return $this->deadline;
246 }
247
252 public function setDeadlineMode(int $a_val): void
253 {
254 $this->deadline_mode = $a_val;
255 }
256
257 public function getDeadlineMode(): int
258 {
259 return $this->deadline_mode;
260 }
261
262 public function setRelativeDeadline(int $a_val): void
263 {
264 $this->relative_deadline = $a_val;
265 }
266
267 public function getRelativeDeadline(): int
268 {
269 return $this->relative_deadline;
270 }
271
272 public function setRelDeadlineLastSubmission(int $a_val): void
273 {
274 $this->rel_deadline_last_subm = $a_val;
275 }
276
278 {
279 return $this->rel_deadline_last_subm;
280 }
281
282
283 // Get individual deadline (max of common or idl (team) deadline = Official Deadline)
284 public function getPersonalDeadline(int $a_user_id): int
285 {
286 $ilDB = $this->db;
287
288 $is_team = false;
289 if ($this->ass_type->usesTeams()) {
290 $team_id = ilExAssignmentTeam::getTeamId($this->getId(), $a_user_id);
291 if (!$team_id) {
292 // #0021043
293 return $this->getDeadline();
294 }
295 $a_user_id = $team_id;
296 $is_team = true;
297 }
298
299 $set = $ilDB->query("SELECT tstamp FROM exc_idl" .
300 " WHERE ass_id = " . $ilDB->quote($this->getId(), "integer") .
301 " AND member_id = " . $ilDB->quote($a_user_id, "integer") .
302 " AND is_team = " . $ilDB->quote($is_team, "integer"));
303 $row = $ilDB->fetchAssoc($set);
304
305 // use assignment deadline if no direct personal
306 return max(($row["tstamp"] ?? 0), $this->getDeadline());
307 }
308
309 // Get last/final personal deadline (of assignment)
310 public function getLastPersonalDeadline(): int
311 {
312 $ilDB = $this->db;
313
314 $set = $ilDB->query("SELECT MAX(tstamp) FROM exc_idl" .
315 " WHERE ass_id = " . $ilDB->quote($this->getId(), "integer"));
316 $row = $ilDB->fetchAssoc($set);
317 return $row["tstamp"] ?? 0;
318 }
319
320 // Set extended deadline (timestamp)
321 public function setExtendedDeadline(?int $a_val): void
322 {
323 $this->deadline2 = $a_val;
324 }
325
326 public function getExtendedDeadline(): ?int
327 {
328 return $this->deadline2;
329 }
330
331 public function setInstruction(string $a_val): void
332 {
333 $this->instruction = $a_val;
334 }
335
336 public function getInstruction(): string
337 {
338 return $this->instruction;
339 }
340
341 public function getInstructionPresentation(): string
342 {
343 $inst = $this->getInstruction();
344 if (trim($inst)) {
345 $is_html = (strlen($inst) != strlen(strip_tags($inst)));
346 if (!$is_html) {
347 $inst = nl2br(
348 $this->string_transform->makeClickable()->transform($inst)
349 );
350 }
351 }
352 $inst = $this->gui->html()->escapeCurly($inst);
353 return $inst;
354 }
355
356 public function setTitle(string $a_val): void
357 {
358 $this->title = $a_val;
359 }
360
361 public function getTitle(): string
362 {
363 return $this->title;
364 }
365
366 public function setMandatory(bool $a_val): void
367 {
368 $this->mandatory = $a_val;
369 }
370
371 public function getMandatory(): bool
372 {
373 return $this->mandatory;
374 }
375
376 public function setOrderNr(int $a_val): void
377 {
378 $this->order_nr = $a_val;
379 }
380
381 public function getOrderNr(): int
382 {
383 return $this->order_nr;
384 }
385
391 public function setType(int $a_value): void
392 {
393 if ($this->isValidType($a_value)) {
394 $this->type = $a_value;
395
396 $this->ass_type = $this->types->getById($a_value);
397
398 if ($this->ass_type->usesTeams()) {
399 $this->setPeerReview(false);
400 }
401 }
402 }
403
405 {
406 return $this->ass_type;
407 }
408
409
414 public function getType(): int
415 {
416 return $this->type;
417 }
418
419 public function isValidType(int $a_value): bool
420 {
421 return $this->types->isValidId($a_value);
422 }
423
424 public function setPeerReview(bool $a_value): void
425 {
426 $this->peer = $a_value;
427 }
428
429 public function getPeerReview(): bool
430 {
431 return $this->peer;
432 }
433
434 public function setPeerReviewMin(int $a_value): void
435 {
436 $this->peer_min = $a_value;
437 }
438
439 public function getPeerReviewMin(): int
440 {
441 return $this->peer_min;
442 }
443
444 public function setPeerReviewSimpleUnlock(int $a_value)
445 {
446 $this->peer_unlock = $a_value;
447 }
448
450 {
451 return $this->peer_unlock;
452 }
453
457 public function setPeerReviewDeadline(int $a_val): void
458 {
459 $this->peer_dl = $a_val;
460 }
461
462 public function getPeerReviewDeadline(): int
463 {
464 return $this->peer_dl;
465 }
466
472 public function setPeerReviewValid(int $a_value): void
473 {
474 $this->peer_valid = $a_value;
475 }
476
477 public function getPeerReviewValid(): int
478 {
479 return $this->peer_valid;
480 }
481
482 public function setPeerReviewRating(bool $a_val): void
483 {
484 $this->peer_rating = $a_val;
485 }
486
487 public function hasPeerReviewRating(): bool
488 {
489 return $this->peer_rating;
490 }
491
492 public function setPeerReviewText(bool $a_val): void
493 {
494 $this->peer_text = $a_val;
495 }
496
497 public function hasPeerReviewText(): bool
498 {
499 return $this->peer_text;
500 }
501
502 public function setPeerReviewFileUpload(bool $a_val): void
503 {
504 $this->peer_file = $a_val;
505 }
506
507 public function hasPeerReviewFileUpload(): bool
508 {
509 return $this->peer_file;
510 }
511
512 public function setPeerReviewPersonalized(bool $a_val): void
513 {
514 $this->peer_personal = $a_val;
515 }
516
517 public function hasPeerReviewPersonalized(): bool
518 {
519 return $this->peer_personal;
520 }
521
522 public function setPeerReviewChars(?int $a_value): void
523 {
524 $a_value = (is_numeric($a_value) && (int) $a_value > 0)
525 ? (int) $a_value
526 : null;
527 $this->peer_char = $a_value;
528 }
529
530 public function getPeerReviewChars(): ?int
531 {
532 return $this->peer_char;
533 }
534
535 public function setPeerReviewCriteriaCatalogue(?int $a_value): void
536 {
537 $this->crit_cat = $a_value;
538 }
539
541 {
542 return $this->crit_cat;
543 }
544
545 public function getPeerReviewCriteriaCatalogueItems(): array
546 {
547 if ($this->crit_cat) {
548 return ilExcCriteria::getInstancesByParentId($this->crit_cat);
549 } else {
550 $res = array();
551
552 if ($this->peer_rating) {
554 }
555
556 if ($this->peer_text) {
558 $crit = ilExcCriteria::getInstanceByType("text");
559 if ($this->peer_char) {
560 $crit->setMinChars($this->peer_char);
561 }
562 $res[] = $crit;
563 }
564
565 if ($this->peer_file) {
567 }
568
569 return $res;
570 }
571 }
572
573 public function setFeedbackFile(?string $a_value): void
574 {
575 $this->feedback_file = $a_value;
576 }
577
578 public function getFeedbackFile(): ?string
579 {
580 return $this->feedback_file;
581 }
582
586 public function setFeedbackCron(bool $a_value): void
587 {
588 $this->feedback_cron = $a_value;
589 }
590
591 public function hasFeedbackCron(): bool
592 {
593 return $this->feedback_cron;
594 }
595
596 // Set (global) feedback file availability date
597 public function setFeedbackDate(int $a_value): void
598 {
599 $this->feedback_date = $a_value;
600 }
601
602 public function getFeedbackDate(): int
603 {
604 return $this->feedback_date;
605 }
606
611 public function setFeedbackDateCustom(int $a_value): void
612 {
613 $this->feedback_date_custom = $a_value;
614 }
615
616 public function getFeedbackDateCustom(): int
617 {
618 return $this->feedback_date_custom;
619 }
620
621 // Set team management by tutor
622 public function setTeamTutor(bool $a_value): void
623 {
624 $this->team_tutor = $a_value;
625 }
626
627 public function getTeamTutor(): bool
628 {
629 return $this->team_tutor;
630 }
631
632 // Set max number of uploads
633 public function setMaxFile(?int $a_value): void
634 {
635 $this->max_file = $a_value;
636 }
637
638 public function getMaxFile(): ?int
639 {
640 return $this->max_file;
641 }
642
643 // Set portfolio template id
644 public function setPortfolioTemplateId(int $a_val): void
645 {
646 $this->portfolio_template = $a_val;
647 }
648
649 public function getPortfolioTemplateId(): int
650 {
651 return $this->portfolio_template;
652 }
653
657 public function read(): void
658 {
659 $ilDB = $this->db;
660
661 $set = $ilDB->query(
662 "SELECT * FROM exc_assignment " .
663 " WHERE id = " . $ilDB->quote($this->getId(), "integer")
664 );
665 $rec = $ilDB->fetchAssoc($set);
666
667 // #16172 - might be deleted
668 if (is_array($rec)) {
669 $this->initFromDB($rec);
670 }
671 }
672
678 protected function initFromDB(array $a_set): void
679 {
680 $this->setId((int) $a_set["id"]);
681 $this->setExerciseId((int) $a_set["exc_id"]);
682 $this->setDeadline((int) $a_set["time_stamp"]);
683 $this->setExtendedDeadline((int) $a_set["deadline2"]);
684 $this->setInstruction((string) $a_set["instruction"]);
685 $this->setTitle((string) $a_set["title"]);
686 $this->setStartTime((int) $a_set["start_time"]);
687 $this->setOrderNr((int) $a_set["order_nr"]);
688 $this->setMandatory((bool) $a_set["mandatory"]);
689 $this->setType((int) $a_set["type"]);
690 $this->setPeerReview((bool) $a_set["peer"]);
691 $this->setPeerReviewMin((int) $a_set["peer_min"]);
692 $this->setPeerReviewSimpleUnlock((int) $a_set["peer_unlock"]);
693 $this->setPeerReviewDeadline((int) $a_set["peer_dl"]);
694 $this->setPeerReviewValid((int) $a_set["peer_valid"]);
695 $this->setPeerReviewFileUpload((bool) $a_set["peer_file"]);
696 $this->setPeerReviewPersonalized((bool) $a_set["peer_prsl"]);
697 $this->setPeerReviewChars((int) $a_set["peer_char"]);
698 $this->setPeerReviewText((bool) $a_set["peer_text"]);
699 $this->setPeerReviewRating((bool) $a_set["peer_rating"]);
700 $this->setPeerReviewCriteriaCatalogue((int) $a_set["peer_crit_cat"]);
701 $this->setFeedbackFile((string) $a_set["fb_file"]);
702 $this->setFeedbackDate((int) $a_set["fb_date"]);
703 $this->setFeedbackDateCustom((int) $a_set["fb_date_custom"]);
704 $this->setFeedbackCron((bool) $a_set["fb_cron"]);
705 $this->setTeamTutor((bool) $a_set["team_tutor"]);
706 $this->setMaxFile((int) $a_set["max_file"]);
707 $this->setPortfolioTemplateId((int) $a_set["portfolio_template"]);
708 $this->setMinCharLimit((int) $a_set["min_char_limit"]);
709 $this->setMaxCharLimit((int) $a_set["max_char_limit"]);
710 $this->setDeadlineMode((int) $a_set["deadline_mode"]);
711 $this->setRelativeDeadline((int) $a_set["relative_deadline"]);
712 $this->setRelDeadlineLastSubmission((int) $a_set["rel_deadline_last_subm"]);
713 }
714
718 public function save(): void
719 {
720 $ilDB = $this->db;
721
722 if ($this->getOrderNr() == 0) {
723 $this->setOrderNr(
724 self::lookupMaxOrderNrForEx($this->getExerciseId())
725 + 10
726 );
727 }
728
729 $next_id = $ilDB->nextId("exc_assignment");
730 $ilDB->insert("exc_assignment", array(
731 "id" => array("integer", $next_id),
732 "exc_id" => array("integer", $this->getExerciseId()),
733 "time_stamp" => array("integer", $this->getDeadline()),
734 "deadline2" => array("integer", $this->getExtendedDeadline()),
735 "instruction" => array("clob", $this->getInstruction()),
736 "title" => array("text", $this->getTitle()),
737 "start_time" => array("integer", $this->getStartTime()),
738 "order_nr" => array("integer", $this->getOrderNr()),
739 "mandatory" => array("integer", $this->getMandatory()),
740 "type" => array("integer", $this->getType()),
741 "peer" => array("integer", $this->getPeerReview()),
742 "peer_min" => array("integer", $this->getPeerReviewMin()),
743 "peer_unlock" => array("integer", $this->getPeerReviewSimpleUnlock()),
744 "peer_dl" => array("integer", $this->getPeerReviewDeadline()),
745 "peer_valid" => array("integer", $this->getPeerReviewValid()),
746 "peer_file" => array("integer", $this->hasPeerReviewFileUpload()),
747 "peer_prsl" => array("integer", $this->hasPeerReviewPersonalized()),
748 "peer_char" => array("integer", $this->getPeerReviewChars()),
749 "peer_text" => array("integer", (int) $this->hasPeerReviewText()),
750 "peer_rating" => array("integer", (int) $this->hasPeerReviewRating()),
751 "peer_crit_cat" => array("integer", $this->getPeerReviewCriteriaCatalogue()),
752 "fb_file" => array("text", $this->getFeedbackFile()),
753 "fb_date" => array("integer", $this->getFeedbackDate()),
754 "fb_date_custom" => array("integer", $this->getFeedbackDateCustom()),
755 "fb_cron" => array("integer", $this->hasFeedbackCron()),
756 "team_tutor" => array("integer", $this->getTeamTutor()),
757 "max_file" => array("integer", $this->getMaxFile()),
758 "portfolio_template" => array("integer", $this->getPortfolioTemplateId()),
759 "min_char_limit" => array("integer", $this->getMinCharLimit()),
760 "max_char_limit" => array("integer", $this->getMaxCharLimit()),
761 "relative_deadline" => array("integer", $this->getRelativeDeadline()),
762 "rel_deadline_last_subm" => array("integer", $this->getRelDeadlineLastSubmission()),
763 "deadline_mode" => array("integer", $this->getDeadlineMode())
764 ));
765 $this->setId($next_id);
766 $exc = new ilObjExercise($this->getExerciseId(), false);
767 $exc->updateAllUsersStatus();
768
769 $this->domain->assignment()->instructionFiles($next_id)->createCollection();
770
771 self::createNewAssignmentRecords($next_id, $exc);
772 ilObjectSearch::raiseContentChanged($this->getExerciseId());
773 $this->handleCalendarEntries("create", $exc);
774 }
775
779 public function update(): void
780 {
781 $ilDB = $this->db;
782
783 $ilDB->update(
784 "exc_assignment",
785 array(
786 "exc_id" => array("integer", $this->getExerciseId()),
787 "time_stamp" => array("integer", $this->getDeadline()),
788 "deadline2" => array("integer", $this->getExtendedDeadline()),
789 "instruction" => array("clob", $this->getInstruction()),
790 "title" => array("text", $this->getTitle()),
791 "start_time" => array("integer", $this->getStartTime()),
792 "order_nr" => array("integer", $this->getOrderNr()),
793 "mandatory" => array("integer", $this->getMandatory()),
794 "type" => array("integer", $this->getType()),
795 "peer" => array("integer", $this->getPeerReview()),
796 "peer_min" => array("integer", $this->getPeerReviewMin()),
797 "peer_unlock" => array("integer", $this->getPeerReviewSimpleUnlock()),
798 "peer_dl" => array("integer", $this->getPeerReviewDeadline()),
799 "peer_valid" => array("integer", $this->getPeerReviewValid()),
800 "peer_file" => array("integer", $this->hasPeerReviewFileUpload()),
801 "peer_prsl" => array("integer", $this->hasPeerReviewPersonalized()),
802 "peer_char" => array("integer", $this->getPeerReviewChars()),
803 "peer_text" => array("integer", (int) $this->hasPeerReviewText()),
804 "peer_rating" => array("integer", (int) $this->hasPeerReviewRating()),
805 "peer_crit_cat" => array("integer", $this->getPeerReviewCriteriaCatalogue()),
806 "fb_file" => array("text", $this->getFeedbackFile()),
807 "fb_date" => array("integer", $this->getFeedbackDate()),
808 "fb_date_custom" => array("integer", $this->getFeedbackDateCustom()),
809 "fb_cron" => array("integer", $this->hasFeedbackCron()),
810 "team_tutor" => array("integer", $this->getTeamTutor()),
811 "max_file" => array("integer", $this->getMaxFile()),
812 "portfolio_template" => array("integer", $this->getPortfolioTemplateId()),
813 "min_char_limit" => array("integer", $this->getMinCharLimit()),
814 "max_char_limit" => array("integer", $this->getMaxCharLimit()),
815 "deadline_mode" => array("integer", $this->getDeadlineMode()),
816 "relative_deadline" => array("integer", $this->getRelativeDeadline()),
817 "rel_deadline_last_subm" => array("integer", $this->getRelDeadlineLastSubmission())
818 ),
819 array(
820 "id" => array("integer", $this->getId()),
821 )
822 );
823 $exc = new ilObjExercise($this->getExerciseId(), false);
824 $exc->updateAllUsersStatus();
825 ilObjectSearch::raiseContentChanged($this->getExerciseId());
826 $this->handleCalendarEntries("update", $exc);
827 }
828
832 public function delete(
833 ilObjExercise $exc,
834 bool $update_user_status = true
835 ): void {
836 $ilDB = $this->db;
837
838 // delete submissions
839 $exc_members = new ilExerciseMembers($exc);
840 foreach ($exc_members->getMembers() as $mem) {
841 $submission = new ilExSubmission($this, $mem);
842 $submission->deleteAllFiles();
843 }
844
845 $ilDB->manipulateF(
846 "DELETE FROM exc_usr_tutor " .
847 "WHERE ass_id = %s",
848 array("integer"),
849 array($this->getId())
850 );
851
852 // remove peer review data
853 if ($this->getPeerReview()) {
854 $peer_review = new ilExPeerReview($this);
855 $peer_review->resetPeerReviews();
856 }
857
858 $ilDB->manipulate(
859 "DELETE FROM exc_ass_file_order" .
860 " WHERE assignment_id = " . $ilDB->quote($this->getId(), 'integer')
861 );
862
863 $ilDB->manipulate(
864 "DELETE FROM exc_mem_ass_status" .
865 " WHERE ass_id = " . $ilDB->quote($this->getId(), 'integer')
866 );
867
868 $ilDB->manipulate(
869 "DELETE FROM exc_assignment WHERE " .
870 " id = " . $ilDB->quote($this->getId(), "integer")
871 );
872
873 if ($update_user_status) {
874 $exc->updateAllUsersStatus();
875 }
876
877 $this->handleCalendarEntries("delete", $exc);
878
880
881 $reminder = new ilExAssignmentReminder();
882 $reminder->deleteReminders($this->getId());
883
884 // delete teams
885 $this->domain->team()->deleteTeamsOfAssignment($this->getId());
886
887 // delete resource collections and resources
888 $this->domain->assignment()->instructionFiles($this->getId())
889 ->deleteCollection();
890 }
891
892
893 // Get assignments data of an exercise in an array
894 public static function getAssignmentDataOfExercise(int $a_exc_id): array
895 {
896 global $DIC;
897
898 $ilDB = $DIC->database();
899
900 // should be changed to self::getInstancesByExerciseId()
901
902 $set = $ilDB->query("SELECT * FROM exc_assignment " .
903 " WHERE exc_id = " . $ilDB->quote($a_exc_id, "integer") .
904 " ORDER BY order_nr");
905 $data = array();
906
907 $order_val = 10;
908 while ($rec = $ilDB->fetchAssoc($set)) {
909 $data[] = array(
910 "id" => (int) $rec["id"],
911 "exc_id" => (int) $rec["exc_id"],
912 "deadline" => (int) $rec["time_stamp"],
913 "deadline2" => (int) $rec["deadline2"],
914 "instruction" => (string) $rec["instruction"],
915 "title" => (string) $rec["title"],
916 "start_time" => (int) $rec["start_time"],
917 "order_val" => $order_val,
918 "mandatory" => (bool) $rec["mandatory"],
919 "type" => (int) $rec["type"],
920 "peer" => (bool) $rec["peer"],
921 "peer_min" => (int) $rec["peer_min"],
922 "peer_dl" => (int) $rec["peer_dl"],
923 "peer_file" => (bool) $rec["peer_file"],
924 "peer_prsl" => (bool) $rec["peer_prsl"],
925 "fb_file" => (string) $rec["fb_file"],
926 "fb_date" => (int) $rec["fb_date"],
927 "fb_cron" => (bool) $rec["fb_cron"],
928 "deadline_mode" => (int) $rec["deadline_mode"],
929 "relative_deadline" => (int) $rec["relative_deadline"],
930 "rel_deadline_last_subm" => (int) $rec["rel_deadline_last_subm"]
931 );
932 $order_val += 10;
933 }
934 return $data;
935 }
936
944 public static function cloneAssignmentsOfExercise(
945 int $a_old_exc_id,
946 int $a_new_exc_id,
947 array $a_crit_cat_map
948 ): void {
949 global $DIC;
950
951 $ass_domain = $DIC->exercise()->internal()->domain()->assignment();
952 $ass_data = self::getInstancesByExercise($a_old_exc_id);
953 foreach ($ass_data as $d) {
954 // clone assignment
955 $new_ass = new ilExAssignment();
956 $new_ass->setExerciseId($a_new_exc_id);
957 $new_ass->setTitle($d->getTitle());
958 $new_ass->setDeadline($d->getDeadline());
959 $new_ass->setExtendedDeadline($d->getExtendedDeadline());
960 $new_ass->setInstruction($d->getInstruction());
961 $new_ass->setMandatory($d->getMandatory());
962 $new_ass->setOrderNr($d->getOrderNr());
963 $new_ass->setStartTime($d->getStartTime());
964 $new_ass->setType($d->getType());
965 $new_ass->setPeerReview($d->getPeerReview());
966 $new_ass->setPeerReviewMin($d->getPeerReviewMin());
967 $new_ass->setPeerReviewDeadline($d->getPeerReviewDeadline());
968 $new_ass->setPeerReviewFileUpload($d->hasPeerReviewFileUpload());
969 $new_ass->setPeerReviewPersonalized($d->hasPeerReviewPersonalized());
970 $new_ass->setPeerReviewValid($d->getPeerReviewValid());
971 $new_ass->setPeerReviewChars($d->getPeerReviewChars());
972 $new_ass->setPeerReviewText($d->hasPeerReviewText());
973 $new_ass->setPeerReviewRating($d->hasPeerReviewRating());
974 $new_ass->setPeerReviewCriteriaCatalogue($d->getPeerReviewCriteriaCatalogue());
975 $new_ass->setPeerReviewSimpleUnlock($d->getPeerReviewSimpleUnlock());
976 $new_ass->setFeedbackFile($d->getFeedbackFile());
977 $new_ass->setFeedbackDate($d->getFeedbackDate());
978 $new_ass->setFeedbackDateCustom($d->getFeedbackDateCustom());
979 $new_ass->setFeedbackCron($d->hasFeedbackCron()); // #16295
980 $new_ass->setTeamTutor($d->getTeamTutor());
981 $new_ass->setMaxFile($d->getMaxFile());
982 $new_ass->setMinCharLimit($d->getMinCharLimit());
983 $new_ass->setMaxCharLimit($d->getMaxCharLimit());
984 $new_ass->setPortfolioTemplateId($d->getPortfolioTemplateId());
985 $new_ass->setDeadlineMode($d->getDeadlineMode());
986 $new_ass->setRelativeDeadline($d->getRelativeDeadline());
987 $new_ass->setRelDeadlineLastSubmission($d->getRelDeadlineLastSubmission());
988
989 // criteria catalogue(s)
990 if ($d->getPeerReviewCriteriaCatalogue() &&
991 array_key_exists($d->getPeerReviewCriteriaCatalogue(), $a_crit_cat_map)) {
992 $new_ass->setPeerReviewCriteriaCatalogue($a_crit_cat_map[$d->getPeerReviewCriteriaCatalogue()]);
993 }
994
995 $new_ass->save();
996
997 // clone assignment files
998 $ass_domain->instructionFiles($d->getId())->cloneTo($new_ass->getId());
999
1000 // clone global feedback file
1001 $ass_domain->sampleSolution($d->getId())->cloneTo($new_ass->getId());
1002
1003 // clone reminders
1007 $rmd_sub = new ilExAssignmentReminder($a_old_exc_id, $d->getId(), $rem_type);
1008 if ($rmd_sub->getReminderStatus()) {
1009 $new_rmd_sub = new ilExAssignmentReminder($a_new_exc_id, $new_ass->getId(), $rem_type);
1010 $new_rmd_sub->setReminderStatus($rmd_sub->getReminderStatus());
1011 $new_rmd_sub->setReminderStart($rmd_sub->getReminderStart());
1012 $new_rmd_sub->setReminderEnd($rmd_sub->getReminderEnd());
1013 $new_rmd_sub->setReminderFrequency($rmd_sub->getReminderFrequency());
1014 $new_rmd_sub->setReminderMailTemplate($rmd_sub->getReminderMailTemplate());
1015 $new_rmd_sub->save();
1016 }
1017 }
1018
1019
1020 // type specific properties
1021 $ass_type = $d->getAssignmentType();
1022 $ass_type->cloneSpecificProperties($d, $new_ass);
1023 }
1024 }
1025
1026 public function getFiles(): array
1027 {
1028 return $this->domain->assignment()->instructionFiles($this->getId())->getFiles();
1029 }
1030
1031 public function getInstructionFilesOrder(): array
1032 {
1033 // TODO IRSS: currently the ilResourceCollectionGUI does not support the order of files done by the component,
1034 // this should be implemented as well there and will follow.
1035 $ilDB = $this->db;
1036
1037 $set = $ilDB->query(
1038 "SELECT filename, order_nr, id FROM exc_ass_file_order " .
1039 " WHERE assignment_id = " . $ilDB->quote($this->getId(), "integer")
1040 );
1041
1042 $data = array();
1043 while ($rec = $ilDB->fetchAssoc($set)) {
1044 $data[$rec['filename']] = $rec;
1045 }
1046
1047 return $data;
1048 }
1049
1050 // Select the maximum order nr for an exercise
1051 public static function lookupMaxOrderNrForEx(int $a_exc_id): int
1052 {
1053 // TODO IRSS: currently the ilResourceCollectionGUI does not support the order of files done by the component,
1054 // this should be implemented as well there and will follow.
1055 global $DIC;
1056
1057 $ilDB = $DIC->database();
1058
1059 $set = $ilDB->query(
1060 "SELECT MAX(order_nr) mnr FROM exc_assignment " .
1061 " WHERE exc_id = " . $ilDB->quote($a_exc_id, "integer")
1062 );
1063 if ($rec = $ilDB->fetchAssoc($set)) {
1064 return (int) $rec["mnr"];
1065 }
1066 return 0;
1067 }
1068
1069 public static function lookupAssignmentOnline(int $a_ass_id): bool
1070 {
1071 global $DIC;
1072
1073 $ilDB = $DIC->database();
1074
1075 $query = "SELECT id FROM exc_assignment " .
1076 "WHERE start_time <= " . $ilDB->quote(time(), 'integer') . ' ' .
1077 "AND time_stamp >= " . $ilDB->quote(time(), 'integer') . ' ' .
1078 "AND id = " . $ilDB->quote($a_ass_id, 'integer');
1079 $res = $ilDB->query($query);
1080
1081 return (bool) $res->numRows();
1082 }
1083
1084 public static function lookupExerciseId(int $a_ass_id): int
1085 {
1086 global $DIC;
1087
1088 $ilDB = $DIC->database();
1089 $query = "SELECT exc_id FROM exc_assignment " .
1090 "WHERE id = " . $ilDB->quote($a_ass_id, 'integer');
1091 $res = $ilDB->fetchAssoc($ilDB->query($query));
1092
1093 return (int) ($res["exc_id"] ?? 0);
1094 }
1095
1096 private static function lookup(int $a_id, string $a_field): string
1097 {
1098 global $DIC;
1099
1100 $ilDB = $DIC->database();
1101
1102 $set = $ilDB->query(
1103 "SELECT " . $a_field . " FROM exc_assignment " .
1104 " WHERE id = " . $ilDB->quote($a_id, "integer")
1105 );
1106
1107 $rec = $ilDB->fetchAssoc($set);
1108
1109 return $rec[$a_field] ?? "";
1110 }
1111
1112 public static function lookupTitle(int $a_id): string
1113 {
1114 return self::lookup($a_id, "title");
1115 }
1116
1117 public static function lookupType(int $a_id): int
1118 {
1119 return (int) self::lookup($a_id, "type");
1120 }
1121
1122 // Save ordering of all assignments of an exercise
1123 public static function saveAssOrderOfExercise(int $a_ex_id, array $a_order): void
1124 {
1125 global $DIC;
1126
1127 $ilDB = $DIC->database();
1128
1129 asort($a_order);
1130 $nr = 10;
1131 foreach ($a_order as $k => $v) {
1132 // the check for exc_id is for security reasons. ass ids are unique.
1133 $ilDB->manipulate(
1134 "UPDATE exc_assignment SET " .
1135 " order_nr = " . $ilDB->quote($nr, "integer") .
1136 " WHERE id = " . $ilDB->quote((int) $k, "integer") .
1137 " AND exc_id = " . $ilDB->quote($a_ex_id, "integer")
1138 );
1139 $nr += 10;
1140 }
1141 }
1142
1143 // Order assignments by deadline date
1144 public static function orderAssByDeadline(int $a_ex_id): void
1145 {
1146 global $DIC;
1147 $ilDB = $DIC->database();
1148
1149 $set = $ilDB->query(
1150 "SELECT id FROM exc_assignment " .
1151 " WHERE exc_id = " . $ilDB->quote($a_ex_id, "integer") .
1152 " ORDER BY time_stamp"
1153 );
1154 $nr = 10;
1155 while ($rec = $ilDB->fetchAssoc($set)) {
1156 $ilDB->manipulate(
1157 "UPDATE exc_assignment SET " .
1158 " order_nr = " . $ilDB->quote($nr, "integer") .
1159 " WHERE id = " . $ilDB->quote($rec["id"], "integer")
1160 );
1161 $nr += 10;
1162 }
1163 }
1164
1165 // Count the number of mandatory assignments
1166 public static function countMandatory(int $a_ex_id): int
1167 {
1168 global $DIC;
1169
1170 $ilDB = $DIC->database();
1171
1172 $set = $ilDB->query(
1173 "SELECT count(*) cntm FROM exc_assignment " .
1174 " WHERE exc_id = " . $ilDB->quote($a_ex_id, "integer") .
1175 " AND mandatory = " . $ilDB->quote(1, "integer")
1176 );
1177 $rec = $ilDB->fetchAssoc($set);
1178 return (int) $rec["cntm"];
1179 }
1180
1181 // Count assignments
1182 public static function count(int $a_ex_id): int
1183 {
1184 global $DIC;
1185
1186 $ilDB = $DIC->database();
1187
1188 $set = $ilDB->query(
1189 "SELECT count(*) cntm FROM exc_assignment " .
1190 " WHERE exc_id = " . $ilDB->quote($a_ex_id, "integer")
1191 );
1192 $rec = $ilDB->fetchAssoc($set);
1193 return $rec["cntm"];
1194 }
1195
1196 // Is assignment in exercise?
1197 public static function isInExercise(int $a_ass_id, int $a_ex_id): bool
1198 {
1199 global $DIC;
1200
1201 $ilDB = $DIC->database();
1202
1203 $set = $ilDB->query(
1204 "SELECT * FROM exc_assignment " .
1205 " WHERE exc_id = " . $ilDB->quote($a_ex_id, "integer") .
1206 " AND id = " . $ilDB->quote($a_ass_id, "integer")
1207 );
1208 if ($ilDB->fetchAssoc($set)) {
1209 return true;
1210 }
1211 return false;
1212 }
1213
1214 public function getMemberListData(): array
1215 {
1216 $ilDB = $this->db;
1217
1218 $mem = array();
1219
1220 // first get list of members from member table
1221 $set = $ilDB->query("SELECT ud.usr_id, ud.lastname, ud.firstname, ud.login" .
1222 " FROM exc_members excm" .
1223 " JOIN usr_data ud ON (ud.usr_id = excm.usr_id)" .
1224 " WHERE excm.obj_id = " . $ilDB->quote($this->getExerciseId(), "integer"));
1225 while ($rec = $ilDB->fetchAssoc($set)) {
1226 $mem[$rec["usr_id"]] =
1227 array(
1228 "name" => $rec["lastname"] . ", " . $rec["firstname"],
1229 "login" => $rec["login"],
1230 "usr_id" => $rec["usr_id"],
1231 "lastname" => $rec["lastname"],
1232 "firstname" => $rec["firstname"]
1233 );
1234 }
1235
1236 // users in idl may already exist before occuring in the members db table
1237 // if user starts an assignment with relative deadline
1238 $idl = $this->getIndividualDeadlines();
1239 if (!$this->ass_type->usesTeams()) {
1240 foreach ($idl as $user_id => $v) {
1241 if (!isset($mem[$user_id])) {
1242 if (ilObjUser::_exists((int) $user_id)) {
1244 $mem[$user_id] =
1245 array(
1246 "name" => $name["lastname"] . ", " . $name["firstname"],
1247 "login" => $name["login"],
1248 "usr_id" => $user_id,
1249 "lastname" => $name["lastname"],
1250 "firstname" => $name["firstname"]
1251 );
1252 }
1253 }
1254 }
1255 }
1256
1257 $q = "SELECT * FROM exc_mem_ass_status " .
1258 "WHERE ass_id = " . $ilDB->quote($this->getId(), "integer");
1259 $set = $ilDB->query($q);
1260 while ($rec = $ilDB->fetchAssoc($set)) {
1261 if (isset($mem[$rec["usr_id"]])) {
1262 $sub = new ilExSubmission($this, $rec["usr_id"]);
1263
1264 $mem[$rec["usr_id"]]["sent_time"] = $rec["sent_time"];
1265 $mem[$rec["usr_id"]]["submission"] = $sub->getLastSubmission();
1266 $mem[$rec["usr_id"]]["status_time"] = $rec["status_time"];
1267 $mem[$rec["usr_id"]]["feedback_time"] = $rec["feedback_time"];
1268 $mem[$rec["usr_id"]]["notice"] = $rec["notice"];
1269 $mem[$rec["usr_id"]]["status"] = $rec["status"];
1270 $mem[$rec["usr_id"]]["mark"] = $rec["mark"];
1271 $mem[$rec["usr_id"]]["comment"] = $rec["u_comment"];
1272 }
1273 }
1274 return $mem;
1275 }
1276
1282 int $a_user_id,
1283 string $a_grade = ""
1284 ): ?array {
1285 global $DIC;
1286 $ilDB = $DIC->database();
1287
1288 $and_grade = "";
1289 if (in_array($a_grade, array("notgraded", "passed", "failed"))) {
1290 $and_grade = " AND status = " . $ilDB->quote($a_grade, "text");
1291 }
1292
1293 $q = "SELECT * FROM exc_mem_ass_status " .
1294 "WHERE ass_id = " . $ilDB->quote($this->getId(), "integer") .
1295 " AND usr_id = " . $ilDB->quote($a_user_id, "integer") .
1296 $and_grade;
1297
1298 $set = $ilDB->query($q);
1299
1300 $data = null;
1301 while ($rec = $ilDB->fetchAssoc($set)) {
1302 $sub = new ilExSubmission($this, $a_user_id);
1303
1304 $data["sent_time"] = $rec["sent_time"];
1305 $data["submission"] = $sub->getLastSubmission();
1306 $data["status_time"] = $rec["status_time"];
1307 $data["feedback_time"] = $rec["feedback_time"];
1308 $data["notice"] = $rec["notice"];
1309 $data["status"] = $rec["status"];
1310 $data["mark"] = $rec["mark"];
1311 $data["comment"] = $rec["u_comment"];
1312 }
1313
1314 return $data;
1315 }
1316
1317 // Create member status record for a new participant for all assignments
1318 public static function createNewUserRecords(
1319 int $a_user_id,
1320 int $a_exc_id
1321 ): void {
1322 global $DIC;
1323
1324 $ilDB = $DIC->database();
1325 $ass_domain = $DIC->exercise()->internal()->domain()->assignment();
1326
1327 $ass_data = self::getAssignmentDataOfExercise($a_exc_id);
1328 foreach ($ass_data as $ass) {
1329 //echo "-".$ass["id"]."-".$a_user_id."-";
1330 $ilDB->replace("exc_mem_ass_status", array(
1331 "ass_id" => array("integer", $ass["id"]),
1332 "usr_id" => array("integer", $a_user_id)
1333 ), array(
1334 "status" => array("text", "notgraded")
1335 ));
1336 if (!$ass_domain->tutorFeedbackFile((int) $ass["id"])->hasCollection($a_user_id)) {
1337 $ass_domain->tutorFeedbackFile((int) $ass["id"])->createCollection($a_user_id);
1338 }
1339 }
1340 }
1341
1342 // Create member status record for a new assignment for all participants
1343 public static function createNewAssignmentRecords(
1344 int $a_ass_id,
1345 ilObjExercise $a_exc
1346 ): void {
1347 global $DIC;
1348
1349 $ilDB = $DIC->database();
1350 $ass_domain = $DIC->exercise()->internal()->domain()->assignment();
1351
1352 $exmem = new ilExerciseMembers($a_exc);
1353 $mems = $exmem->getMembers();
1354
1355 foreach ($mems as $mem) {
1356 $ilDB->replace("exc_mem_ass_status", array(
1357 "ass_id" => array("integer", $a_ass_id),
1358 "usr_id" => array("integer", $mem)
1359 ), array(
1360 "status" => array("text", "notgraded")
1361 ));
1362 if (!$ass_domain->tutorFeedbackFile($a_ass_id)->hasCollection($mem)) {
1363 $ass_domain->tutorFeedbackFile($a_ass_id)->createCollection($mem);
1364 }
1365 }
1366 }
1367
1368
1373 protected function handleCalendarEntries(
1374 string $a_event,
1375 ilObjExercise $exc
1376 ): void {
1377 $ilAppEventHandler = $this->app_event_handler;
1378
1379 $dl_id = $this->getId() . "0";
1380 $fbdl_id = $this->getId() . "1";
1381
1382 $context_ids = array($dl_id, $fbdl_id);
1383 $apps = array();
1384
1385 if ($a_event != "delete") {
1386 // deadline or relative deadline given
1387 if ($this->getDeadline() || $this->getDeadlineMode() == ilExAssignment::DEADLINE_RELATIVE) {
1388 $app = new ilCalendarAppointmentTemplate($dl_id);
1389 $app->setTranslationType(ilCalendarEntry::TRANSLATION_SYSTEM);
1390 $app->setSubtitle("cal_exc_deadline");
1391 $app->setTitle($this->getTitle());
1392 $app->setFullday(false);
1393 // note: in the case of a relative deadline this will be set to 0 / 1970...)
1394 // see ilCalendarScheduleFilterExercise for appointment modification
1395 $app->setStart(new ilDateTime($this->getDeadline(), IL_CAL_UNIX));
1396
1397 $apps[] = $app;
1398 }
1399
1400 if ($this->getPeerReview() &&
1401 $this->getPeerReviewDeadline()) {
1402 $app = new ilCalendarAppointmentTemplate($fbdl_id);
1403 $app->setTranslationType(ilCalendarEntry::TRANSLATION_SYSTEM);
1404 $app->setSubtitle("cal_exc_peer_review_deadline");
1405 $app->setTitle($this->getTitle());
1406 $app->setFullday(false);
1407 $app->setStart(new ilDateTime($this->getPeerReviewDeadline(), IL_CAL_UNIX));
1408
1409 $apps[] = $app;
1410 }
1411 }
1412
1413 $ilAppEventHandler->raise(
1414 'components/ILIAS/Exercise',
1415 $a_event . 'Assignment',
1416 array(
1417 'object' => $exc,
1418 'obj_id' => $exc->getId(),
1419 'context_ids' => $context_ids,
1420 'appointments' => $apps)
1421 );
1422 }
1423
1424 public static function getPendingFeedbackNotifications(): array
1425 {
1426 global $DIC;
1427
1429 $log->debug("Get feedback notifications.");
1430
1431 $ilDB = $DIC->database();
1432
1433 $res = array();
1434
1435 $set = $ilDB->query("SELECT id,fb_file,time_stamp,deadline2,fb_date FROM exc_assignment" .
1436 " WHERE fb_cron = " . $ilDB->quote(1, "integer") .
1437 " AND (fb_date = " . $ilDB->quote(self::FEEDBACK_DATE_DEADLINE, "integer") .
1438 " AND time_stamp IS NOT NULL" .
1439 " AND time_stamp > " . $ilDB->quote(0, "integer") .
1440 " AND time_stamp < " . $ilDB->quote(time(), "integer") .
1441 " AND fb_cron_done = " . $ilDB->quote(0, "integer") .
1442 ") OR (fb_date = " . $ilDB->quote(self::FEEDBACK_DATE_CUSTOM, "integer") .
1443 " AND fb_date_custom IS NOT NULL" .
1444 " AND fb_date_custom > " . $ilDB->quote(0, "integer") .
1445 " AND fb_date_custom < " . $ilDB->quote(time(), "integer") .
1446 " AND fb_cron_done = " . $ilDB->quote(0, "integer") . ")");
1447
1448
1449
1450 while ($row = $ilDB->fetchAssoc($set)) {
1451 $log->debug("check assignment " . $row['id'] . ", fb_file " . $row["fb_file"]);
1452 if ($row['fb_date'] == self::FEEDBACK_DATE_DEADLINE) {
1453 $max = max($row['time_stamp'], $row['deadline2']);
1454 if (trim($row["fb_file"]) && $max <= time()) {
1455 $log->debug("...adding(1)");
1456 $res[] = $row["id"];
1457 }
1458 } elseif ($row['fb_date'] == self::FEEDBACK_DATE_CUSTOM) {
1459 if (trim($row["fb_file"] ?? "") && ($row['fb_date_custom'] ?? 0) <= time()) {
1460 $log->debug("...adding(2)");
1461 $res[] = $row["id"];
1462 }
1463 }
1464 }
1465
1466 return $res;
1467 }
1468
1472 public static function sendFeedbackNotifications(
1473 int $a_ass_id,
1474 ?int $a_user_id = null
1475 ): bool {
1476 global $DIC;
1477
1478 $ilDB = $DIC->database();
1480
1481 $ass = new self($a_ass_id);
1482
1483 // valid assignment?
1484 if (!$ass->hasFeedbackCron() || !$ass->getFeedbackFile()) {
1485 $log->debug("return(1)");
1486 return false;
1487 }
1488
1489 if (!$a_user_id) {
1490 // already done?
1491 $set = $ilDB->query("SELECT fb_cron_done" .
1492 " FROM exc_assignment" .
1493 " WHERE id = " . $ilDB->quote($a_ass_id, "integer"));
1494 $row = $ilDB->fetchAssoc($set);
1495 if ($row["fb_cron_done"]) {
1496 $log->debug("return(2)");
1497 return false;
1498 }
1499 }
1500
1501 $ntf = new ilSystemNotification();
1502 $ntf->setLangModules(array("exc"));
1503 $ntf->setObjId($ass->getExerciseId());
1504 $ntf->setSubjectLangId("exc_feedback_notification_subject");
1505 $ntf->setIntroductionLangId("exc_feedback_notification_body");
1506 $ntf->addAdditionalInfo("exc_assignment", $ass->getTitle());
1507 $ntf->setGotoLangId("exc_feedback_notification_link");
1508 $ntf->setReasonLangId("exc_feedback_notification_reason");
1509
1510 if (!$a_user_id) {
1511 $log->debug("send to members, cnt: " . count(ilExerciseMembers::_getMembers($ass->getExerciseId())));
1512 $ntf->sendMailAndReturnRecipients(ilExerciseMembers::_getMembers($ass->getExerciseId()));
1513
1514 $ilDB->manipulate("UPDATE exc_assignment" .
1515 " SET fb_cron_done = " . $ilDB->quote(1, "integer") .
1516 " WHERE id = " . $ilDB->quote($a_ass_id, "integer"));
1517 } else {
1518 $log->debug("send to user: " . $a_user_id);
1519 $ntf->sendMailAndReturnRecipients(array($a_user_id));
1520 }
1521
1522 return true;
1523 }
1524
1525
1526 // status
1527
1528 // like: after effective deadline (for single user), no deadline: true
1529 public function afterDeadline(): bool
1530 {
1531 $ilUser = $this->user;
1532
1533 // :TODO: always current user?
1534 $idl = $this->getPersonalDeadline($ilUser->getId()); // official deadline
1535
1536 // no deadline === true
1537 $deadline = max($this->deadline, $this->deadline2, $idl); // includes grace period
1538 return ($deadline - time() <= 0);
1539 }
1540
1541 public function afterDeadlineStrict(bool $a_include_personal = true): bool
1542 {
1543 // :TODO: this means that peer feedback, global feedback is available
1544 // after LAST personal deadline
1545 // team management is currently ignoring personal deadlines
1546 $idl = $a_include_personal
1547 ? $this->getLastPersonalDeadline()
1548 : null;
1549
1550 // no deadline === false
1551 $deadline = max($this->deadline, $this->deadline2, $idl);
1552
1553 // #18271 - afterDeadline() does not handle last personal deadline
1554 // after effective deadline of all users
1555 if ($idl && $deadline == $idl) {
1556 return ($deadline - time() <= 0);
1557 }
1558
1559 // like: after effective deadline (for single user), except: no deadline false
1560 return ($deadline > 0 &&
1561 $this->afterDeadline());
1562 }
1563
1567 public function afterCustomDate(): bool
1568 {
1569 $date_custom = $this->getFeedbackDateCustom();
1570 //if the solution will be displayed only after reach all the deadlines.
1571 //$final_deadline = $this->afterDeadlineStrict();
1572 //$dl = max($final_deadline, time());
1573 //return ($date_custom - $dl <= 0);
1574 return ($date_custom - time() <= 0);
1575 }
1576
1577 // like: before effective deadline (for all users), no deadline: true
1578 public function beforeDeadline(): bool
1579 {
1580 // no deadline === true
1581 return !$this->afterDeadlineStrict();
1582 }
1583
1584 public function notStartedYet(): bool
1585 {
1586 return (time() - $this->start_time <= 0);
1587 }
1588
1589
1590 //
1591 // FEEDBACK FILES
1592 //
1593
1597 public function handleGlobalFeedbackFileUpload(int $ass_id, array $a_file): bool
1598 {
1599 $rcid = $this->domain->assignment()->sampleSolution($ass_id)->importFromLegacyUpload($a_file);
1600 $this->setFeedbackFile($a_file["name"]);
1601 return ($rcid !== "");
1602 }
1603
1604
1605 public function getMemberStatus(?int $a_user_id = null): ilExAssignmentMemberStatus
1606 {
1607 $ilUser = $this->user;
1608
1609 if (!$a_user_id) {
1610 $a_user_id = $ilUser->getId();
1611 }
1612 if (!array_key_exists($a_user_id, $this->member_status)) {
1613 $this->member_status[$a_user_id] = new ilExAssignmentMemberStatus($this->getId(), $a_user_id);
1614 }
1615 return $this->member_status[$a_user_id];
1616 }
1617
1618 //
1619 // individual deadlines
1620 //
1621
1622 public function setIndividualDeadline(
1623 string $id,
1624 ilDateTime $date
1625 ): void {
1626 $is_team = false;
1627 if (!is_numeric($id)) {
1628 $id = substr($id, 1);
1629 $is_team = true;
1630 }
1631
1632 $idl = ilExcIndividualDeadline::getInstance($this->getId(), (int) $id, $is_team);
1633 $idl->setIndividualDeadline($date->get(IL_CAL_UNIX));
1634 $idl->save();
1635 }
1636
1637 public function getIndividualDeadlines(): array
1638 {
1639 $ilDB = $this->db;
1640
1641 $res = array();
1642
1643 $set = $ilDB->query("SELECT * FROM exc_idl" .
1644 " WHERE ass_id = " . $ilDB->quote($this->getId(), "integer"));
1645 while ($row = $ilDB->fetchAssoc($set)) {
1646 if ($row["is_team"]) {
1647 $row["member_id"] = "t" . $row["member_id"];
1648 }
1649
1650 $res[$row["member_id"]] = $row["tstamp"];
1651 }
1652
1653 return $res;
1654 }
1655
1656 public function getRequestedDeadlines(): array
1657 {
1658 $ilDB = $this->db;
1659
1660 $res = array();
1661
1662 $set = $ilDB->query("SELECT * FROM exc_idl" .
1663 " WHERE ass_id = " . $ilDB->quote($this->getId(), "integer") .
1664 " AND requested = " . $ilDB->quote(1, "integer"));
1665 while ($row = $ilDB->fetchAssoc($set)) {
1666 if ($row["is_team"]) {
1667 $row["member_id"] = "t" . $row["member_id"];
1668 }
1669
1670 $res[$row["member_id"]] = $row["requested"];
1671 }
1672
1673 return $res;
1674 }
1675
1676 public function hasActiveIDl(): bool
1677 {
1678 return (bool) ($this->getDeadline() || $this->getDeadlineMode() === self::DEADLINE_ABSOLUTE_INDIVIDUAL);
1679 }
1680
1681 public function hasReadOnlyIDl(): bool
1682 {
1683 if (!$this->ass_type->usesTeams() &&
1684 $this->getPeerReview()) {
1685 // all deadlines are read-only if we have peer feedback
1686 $peer_review = new ilExPeerReview($this);
1687 if ($peer_review->hasPeerReviewGroups()) {
1688 return true;
1689 }
1690 }
1691
1692 return false;
1693 }
1694
1696 int $a_ass_id,
1697 array $a_order
1698 ): void {
1699 global $DIC;
1700
1701 $db = $DIC->database();
1702
1703 asort($a_order, SORT_NUMERIC);
1704
1705 $nr = 10;
1706 foreach (array_keys($a_order) as $k) {
1707 // the check for exc_id is for security reasons. ass ids are unique.
1708 $db->manipulate(
1709 "UPDATE exc_ass_file_order SET " .
1710 " order_nr = " . $db->quote($nr, "integer") .
1711 " WHERE id = " . $db->quote((int) $k, "integer") .
1712 " AND assignment_id = " . $db->quote($a_ass_id, "integer")
1713 );
1714 $nr += 10;
1715 }
1716 }
1717
1718 public static function insertFileOrderNr(
1719 int $a_ass_id,
1720 string $a_filename,
1721 int $a_order_nr
1722 ): void {
1723 global $DIC;
1724 $db = $DIC->database();
1725 $id = $db->nextId("exc_ass_file_order");
1726 $db->insert(
1727 "exc_ass_file_order",
1728 [
1729 "id" => ["integer", $id],
1730 "order_nr" => ["integer", $a_order_nr],
1731 "assignment_id" => ["integer", $a_ass_id],
1732 "filename" => ["text", $a_filename]
1733 ]
1734 );
1735 }
1736
1737 // Store the order nr of a file in the database
1738 public static function instructionFileInsertOrder(
1739 string $a_filename,
1740 int $a_ass_id,
1741 int $a_order_nr = 0
1742 ): void {
1743 global $DIC;
1744
1745 $db = $DIC->database();
1746
1747 if ($a_ass_id) {
1748 //first of all check the suffix and change if necessary
1750
1751 if (self::instructionFileExistsInDb($filename, $a_ass_id) == 0) {
1752 if ($a_order_nr == 0) {
1753 $order_val = self::instructionFileOrderGetMax($a_ass_id);
1754 $order = $order_val + 10;
1755 } else {
1756 $order = $a_order_nr;
1757 }
1758
1759 $id = $db->nextID('exc_ass_file_order');
1760 $db->manipulate("INSERT INTO exc_ass_file_order " .
1761 "(id, assignment_id, filename, order_nr) VALUES (" .
1762 $db->quote($id, "integer") . "," .
1763 $db->quote($a_ass_id, "integer") . "," .
1764 $db->quote($filename, "text") . "," .
1765 $db->quote($order, "integer") .
1766 ")");
1767 }
1768 }
1769 }
1770
1774 public static function instructionFileDeleteOrder(
1775 int $a_ass_id,
1776 array $a_file
1777 ): void {
1778 global $DIC;
1779
1780 $db = $DIC->database();
1781
1782 //now its done by filename. We need to figure how to get the order id in the confirmdelete method
1783 foreach ($a_file as $v) {
1784 $db->manipulate(
1785 "DELETE FROM exc_ass_file_order " .
1786 "WHERE filename = " . $db->quote($v, 'text') .
1787 " AND assignment_id = " . $db->quote($a_ass_id, 'integer')
1788 );
1789 }
1790 }
1791
1792 public static function renameInstructionFile(
1793 string $a_old_name,
1794 string $a_new_name,
1795 int $a_ass_id
1796 ): void {
1797 global $DIC;
1798
1799 $db = $DIC->database();
1800
1801 if ($a_ass_id) {
1802 $db->manipulate(
1803 "DELETE FROM exc_ass_file_order" .
1804 " WHERE assignment_id = " . $db->quote($a_ass_id, 'integer') .
1805 " AND filename = " . $db->quote($a_new_name, 'text')
1806 );
1807
1808 $db->manipulate(
1809 "UPDATE exc_ass_file_order SET" .
1810 " filename = " . $db->quote($a_new_name, 'text') .
1811 " WHERE assignment_id = " . $db->quote($a_ass_id, 'integer') .
1812 " AND filename = " . $db->quote($a_old_name, 'text')
1813 );
1814 }
1815 }
1816
1817 public static function instructionFileExistsInDb(
1818 string $a_filename,
1819 int $a_ass_id
1820 ): int {
1821 global $DIC;
1822
1823 $db = $DIC->database();
1824
1825 if ($a_ass_id) {
1826 $result = $db->query(
1827 "SELECT id FROM exc_ass_file_order" .
1828 " WHERE assignment_id = " . $db->quote($a_ass_id, 'integer') .
1829 " AND filename = " . $db->quote($a_filename, 'text')
1830 );
1831
1832 return $db->numRows($result);
1833 }
1834
1835 return 0;
1836 }
1837
1838 public function fixInstructionFileOrdering(): void
1839 {
1840 $db = $this->db;
1841
1842 $files = array_map(function ($v) {
1843 return $v["name"];
1844 }, $this->getFiles());
1845
1846 $set = $db->query("SELECT * FROM exc_ass_file_order " .
1847 " WHERE assignment_id = " . $db->quote($this->getId(), "integer") .
1848 " ORDER BY order_nr");
1849 $order_nr = 10;
1850 $numbered_files = array();
1851 while ($rec = $db->fetchAssoc($set)) {
1852 // file exists, set correct order nr
1853 if (in_array($rec["filename"], $files)) {
1854 $db->manipulate(
1855 "UPDATE exc_ass_file_order SET " .
1856 " order_nr = " . $db->quote($order_nr, "integer") .
1857 " WHERE assignment_id = " . $db->quote($this->getId(), "integer") .
1858 " AND id = " . $db->quote($rec["id"], "integer")
1859 );
1860 $order_nr += 10;
1861 $numbered_files[] = $rec["filename"];
1862 } else { // file does not exist, delete entry
1863 $db->manipulate(
1864 "DELETE FROM exc_ass_file_order " .
1865 " WHERE assignment_id = " . $db->quote($this->getId(), "integer") .
1866 " AND id = " . $db->quote($rec["id"], "integer")
1867 );
1868 }
1869 }
1870 foreach ($files as $f) {
1871 if (!in_array($f, $numbered_files)) {
1872 self::instructionFileInsertOrder($f, $this->getId());
1873 }
1874 }
1875 }
1876
1877 public function fileAddOrder(
1878 array $a_entries = array()
1879 ): array {
1880 $this->fixInstructionFileOrdering();
1881
1882 $order = $this->getInstructionFilesOrder();
1883 foreach ($a_entries as $k => $e) {
1884 $a_entries[$k]["order_val"] = $order[$e["file"]]["order_nr"] ?? 0;
1885 $a_entries[$k]["order_id"] = $order[$e["file"]]["id"] ?? "";
1886 }
1887
1888 return $a_entries;
1889 }
1890
1891 public static function instructionFileOrderGetMax(int $a_ass_id): int
1892 {
1893 global $DIC;
1894
1895 $db = $DIC->database();
1896
1897 //get max order number
1898 $result = $db->queryF(
1899 "SELECT max(order_nr) as max_order FROM exc_ass_file_order WHERE assignment_id = %s",
1900 array('integer'),
1901 array($db->quote($a_ass_id, 'integer'))
1902 );
1903
1904 $order_val = 0;
1905 while ($row = $db->fetchAssoc($result)) {
1906 $order_val = (int) $row['max_order'];
1907 }
1908 return $order_val;
1909 }
1910
1911
1912 // Set limit minimum characters
1913 public function setMinCharLimit(int $a_val): void
1914 {
1915 $this->min_char_limit = $a_val;
1916 }
1917
1918 public function getMinCharLimit(): int
1919 {
1920 return $this->min_char_limit;
1921 }
1922
1923 // Set limit maximum characters
1924 public function setMaxCharLimit(int $a_val): void
1925 {
1926 $this->max_char_limit = $a_val;
1927 }
1928
1929 public function getMaxCharLimit(): int
1930 {
1931 return $this->max_char_limit;
1932 }
1933
1942 public function getCalculatedDeadlines(): array
1943 {
1944 $calculated_deadlines = array(
1945 "user" => array(),
1946 "team" => array()
1947 );
1948
1949 if ($this->getRelativeDeadline() && $this->getDeadlineMode() == self::DEADLINE_RELATIVE) {
1950 foreach (ilExcIndividualDeadline::getStartingTimestamps($this->getId()) as $ts) {
1951 $type = $ts["is_team"]
1952 ? "team"
1953 : "user";
1954
1955 $calculated_deadlines[$type][$ts["member_id"]] = array(
1956 "calculated_deadline" => $ts["starting_ts"] + ($this->getRelativeDeadline() * 24 * 60 * 60)
1957 );
1958 }
1959 }
1960 return $calculated_deadlines;
1961 }
1962
1963 // see bug #36253
1964 public function canParticipantReceiveFeedback(int $part_id): bool
1965 {
1966 if ($this->hasTeam()) {
1967 if (!ilExAssignmentTeam::getTeamId($this->getId(), $part_id)) {
1968 return false;
1969 }
1970 }
1971 return true;
1972 }
1973}
$id
plugin.php for ilComponentBuildPluginInfoObjectiveTest::testAddPlugins
Definition: plugin.php:23
$filename
Definition: buildRTE.php:78
Indicates that the directory is missing or not found.
Indicates that a file is missing or not found.
Indicates general problems with the input or output operations.
Definition: IOException.php:28
Stream factory which enables the user to create streams without the knowledge of the concrete class.
Definition: Streams.php:32
This exception indicates that an UI component was accepted by the JF but is not backed by a real impl...
const IL_CAL_UNIX
return true
Global event handler.
Apointment templates are used for automatic generated apointments.
@classDescription Date and time handling
get(int $a_format, string $a_format_str='', string $a_tz='')
get formatted date
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
static getTeamId(int $a_assignment_id, int $a_user_id, bool $a_create_on_demand=false)
Exercise assignment.
ILIAS Refinery String Group $string_transform
static getAssignmentDataOfExercise(int $a_exc_id)
static lookupExerciseId(int $a_ass_id)
ilAppEventHandler $app_event_handler
setTeamTutor(bool $a_value)
static lookupMaxOrderNrForEx(int $a_exc_id)
ilExAssignmentTypes $types
static saveInstructionFilesOrderOfAssignment(int $a_ass_id, array $a_order)
static lookup(int $a_id, string $a_field)
static saveAssOrderOfExercise(int $a_ex_id, array $a_order)
setPeerReviewSimpleUnlock(int $a_value)
setRelativeDeadline(int $a_val)
setPeerReviewMin(int $a_value)
ilExAssignmentTypeInterface $ass_type
static instructionFileOrderGetMax(int $a_ass_id)
static instructionFileGetFileOrderData(array $a_file_data, int $a_ass_id)
static count(int $a_ex_id)
static lookupType(int $a_id)
setDeadline(?int $a_val)
setExtendedDeadline(?int $a_val)
static cloneAssignmentsOfExercise(int $a_old_exc_id, int $a_new_exc_id, array $a_crit_cat_map)
Clone assignments of exercise.
setPeerReviewText(bool $a_val)
setPeerReviewCriteriaCatalogue(?int $a_value)
static countMandatory(int $a_ex_id)
setPeerReview(bool $a_value)
isValidType(int $a_value)
static instructionFileDeleteOrder(int $a_ass_id, array $a_file)
getType()
Get type this will most probably become an non public function in the future (or become obsolete)
static isInExercise(int $a_ass_id, int $a_ex_id)
handleCalendarEntries(string $a_event, ilObjExercise $exc)
Handle calendar entries for deadline(s)
static insertFileOrderNr(int $a_ass_id, string $a_filename, int $a_order_nr)
setStartTime(?int $a_val)
setDeadlineMode(int $a_val)
Set deadline mode.
setFeedbackCron(bool $a_value)
Toggle (global) feedback file cron.
getCalculatedDeadlines()
Get calculated deadlines for user/team members.
static renameInstructionFile(string $a_old_name, string $a_new_name, int $a_ass_id)
setPeerReviewValid(int $a_value)
Set peer review validation.
static instructionFileInsertOrder(string $a_filename, int $a_ass_id, int $a_order_nr=0)
static getInstancesByExercise(int $a_exc_id)
setPeerReviewPersonalized(bool $a_val)
static createNewUserRecords(int $a_user_id, int $a_exc_id)
getExerciseMemberAssignmentData(int $a_user_id, string $a_grade="")
Get submission data for an specific user,exercise and assignment.
static getPendingFeedbackNotifications()
initFromDB(array $a_set)
Import DB record.
__construct($a_id=0)
Constructor.
setInstruction(string $a_val)
setPortfolioTemplateId(int $a_val)
static sendFeedbackNotifications(int $a_ass_id, ?int $a_user_id=null)
setTitle(string $a_val)
const TYPE_UPLOAD
direct checks against const should be avoided, use type objects instead
static createNewAssignmentRecords(int $a_ass_id, ilObjExercise $a_exc)
getPersonalDeadline(int $a_user_id)
handleGlobalFeedbackFileUpload(int $ass_id, array $a_file)
setRelDeadlineLastSubmission(int $a_val)
ILIAS Exercise InternalDomainService $domain
getMemberStatus(?int $a_user_id=null)
setFeedbackFile(?string $a_value)
ilAccessHandler $access
ILIAS Exercise InternalGUIService $gui
setPeerReviewChars(?int $a_value)
setPeerReviewRating(bool $a_val)
setType(int $a_value)
Set type this will most probably become an non public function in the future (or become obsolete)
static orderAssByDeadline(int $a_ex_id)
setPeerReviewDeadline(int $a_val)
fileAddOrder(array $a_entries=array())
setFeedbackDateCustom(int $a_value)
Set (global) feedback file availability using a custom date.
setFeedbackDate(int $a_value)
setIndividualDeadline(string $id, ilDateTime $date)
canParticipantReceiveFeedback(int $part_id)
setMaxFile(?int $a_value)
static lookupAssignmentOnline(int $a_ass_id)
setMandatory(bool $a_val)
setPeerReviewFileUpload(bool $a_val)
static instructionFileExistsInDb(string $a_filename, int $a_ass_id)
static lookupTitle(int $a_id)
afterDeadlineStrict(bool $a_include_personal=true)
Exercise peer review.
Exercise submission //TODO: This class has many static methods related to delivered "files".
static getInstancesByParentId(int $a_parent_id)
static getInstanceByType(string $a_type)
static getStartingTimestamps(int $a_ass_id)
Get starting timestamp data for an assignment.
static getInstance(int $a_ass_id, int $a_participant_id, bool $a_is_team=false)
Class ilExerciseMembers.
static _getMembers(int $a_obj_id)
static getSafeFilename(string $a_initial_filename)
language handling
static getLogger(string $a_component_id)
Get component logger.
Component logger with individual log levels by component id.
Class ilObjExercise.
updateAllUsersStatus()
Update status of all users.
User class.
static _lookupName(int $a_user_id)
static raiseContentChanged(int $obj_id)
static _exists(int $id, bool $reference=false, ?string $type=null)
checks if an object exists in object_data
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
Interface ilAccessHandler This interface combines all available interfaces which can be called via gl...
Interface ilDBInterface.
insert(string $table_name, array $values)
nextId(string $table_name)
setLimit(int $limit, int $offset=0)
numRows(ilDBStatement $statement)
quote($value, string $type)
manipulate(string $query)
Run a (write) Query on the database.
query(string $query)
Run a (read-only) Query on the database.
fetchAssoc(ilDBStatement $statement)
queryF(string $query, array $types, array $values)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
cloneSpecificProperties(ilExAssignment $source, ilExAssignment $target)
$log
Definition: ltiresult.php:34
$res
Definition: ltiservices.php:69
global $DIC
Definition: shib_login.php:26
$q
Definition: shib_logout.php:23