1<?php
2/* Copyright (c) 1998-2009 ILIAS open source, Extended GPL, see docs/LICENSE */
3
4/**
5* Exercise assignment
6*
7* @author Alex Killing <alex.killing@gmx.de>
8* @ingroup ModulesExercise
9*/
10class ilExAssignment
11{
12    /**
13     * @var ilDB
14     */
15    protected $db;
16
17    /**
18     * @var ilLanguage
19     */
20    protected $lng;
21
22    /**
23     * @var ilObjUser
24     */
25    protected $user;
26
27    /**
28     * @var ilAppEventHandler
29     */
30    protected $app_event_handler;
31
32    /**
33     * @deprecated direct checks against const should be avoided, use type objects instead
34     */
35    const TYPE_UPLOAD = 1;
36    /**
37     * @deprecated
38     */
39    const TYPE_BLOG = 2;
40    /**
41     * @deprecated
42     */
43    const TYPE_PORTFOLIO = 3;
44    /**
45     * @deprecated
46     */
47    const TYPE_UPLOAD_TEAM = 4;
48    /**
49     * @deprecated
50     */
51    const TYPE_TEXT = 5;
52    /**
53     * @deprecated
54     */
55    const TYPE_WIKI_TEAM = 6;
56
57    const FEEDBACK_DATE_DEADLINE = 1;
58    const FEEDBACK_DATE_SUBMISSION = 2;
59    const FEEDBACK_DATE_CUSTOM = 3;
60
61    const PEER_REVIEW_VALID_NONE = 1;
62    const PEER_REVIEW_VALID_ONE = 2;
63    const PEER_REVIEW_VALID_ALL = 3;
64
65    const TEAMS_FORMED_BY_PARTICIPANTS = 0;
66    const TEAMS_FORMED_BY_TUTOR = 1;
67    const TEAMS_FORMED_BY_RANDOM = 2;
68    const TEAMS_FORMED_BY_ASSIGNMENT = 3;
69
70    const DEADLINE_ABSOLUTE = 0;
71    const DEADLINE_RELATIVE = 1;
72
73
74    protected $id;
75    protected $exc_id;
76    protected $type;
77    protected $start_time;
78    protected $deadline;
79    protected $deadline2;
80    protected $instruction;
81    protected $title;
82    protected $mandatory;
83    protected $order_nr;
84    protected $peer;
85    protected $peer_min;
86    protected $peer_unlock;
87    protected $peer_dl;
88    protected $peer_valid;
89    protected $peer_file;
90    protected $peer_personal;
91    protected $peer_char;
92    protected $peer_text;
93    protected $peer_rating;
94    protected $peer_crit_cat;
95    protected $feedback_file;
96    protected $feedback_cron;
97    protected $feedback_date;
98    protected $feedback_date_custom;
99    protected $team_tutor = false;
100    protected $max_file;
101
102    protected $portfolio_template;
103    protected $min_char_limit;
104    protected $max_char_limit;
105
106    protected $types = null;
107
108    /**
109     * @var ilExAssignmentTypeInterface
110     */
111    protected $ass_type;
112
113    protected $deadline_mode = 0;
114    protected $relative_deadline = 0;
115    protected $starting_timestamp = null;
116
117    /**
118     * @var
119     */
120    protected $rel_deadline_last_subm;
121
122    protected $member_status = array(); // [array]
123
124    protected $log;
125
126    /**
127     * Constructor
128     */
129    public function __construct($a_id = 0)
130    {
131        global $DIC;
132
133        $this->db = $DIC->database();
134        $this->lng = $DIC->language();
135        $this->user = $DIC->user();
136        $this->app_event_handler = $DIC["ilAppEventHandler"];
137        $this->types = ilExAssignmentTypes::getInstance();
138
139        $this->setType(self::TYPE_UPLOAD);
140        $this->setFeedbackDate(self::FEEDBACK_DATE_DEADLINE);
141
142        $this->log = ilLoggerFactory::getLogger("exc");
143
144        if ($a_id > 0) {
145            $this->setId($a_id);
146            $this->read();
147        }
148    }
149
150    /**
151     * @param $a_exc_id
152     * @return ilExAssignment[]
153     */
154    public static function getInstancesByExercise($a_exc_id)
155    {
156        global $DIC;
157
158        $ilDB = $DIC->database();
159
160        $set = $ilDB->query("SELECT * FROM exc_assignment " .
161            " WHERE exc_id = " . $ilDB->quote($a_exc_id, "integer") .
162            " ORDER BY order_nr ASC");
163        $data = array();
164
165        $order_val = 10;
166        while ($rec = $ilDB->fetchAssoc($set)) {
167            // ???
168            $rec["order_val"] = $order_val;
169
170            $ass = new self();
171            $ass->initFromDB($rec);
172            $data[] = $ass;
173
174            $order_val += 10;
175        }
176
177        return $data;
178    }
179
180    /**
181     * @param array $a_file_data
182     * @param integer $a_ass_id assignment id.
183     * @return array
184     */
185    public static function instructionFileGetFileOrderData($a_file_data, $a_ass_id)
186    {
187        global $DIC;
188
189        $db = $DIC->database();
190        $db->setLimit(1, 0);
191
192        $result_order_val = $db->query("
193				SELECT id, order_nr
194				FROM exc_ass_file_order
195				WHERE assignment_id = {$db->quote($a_ass_id, 'integer')}
196				AND filename = {$db->quote($a_file_data['entry'], 'string')}
197			");
198
199        $order_val = 0;
200        $order_id = 0;
201        while ($row = $db->fetchAssoc($result_order_val)) {
202            $order_val = (int) $row['order_nr'];
203            $order_id = (int) $row['id'];
204        }
205        return array($order_val, $order_id);
206    }
207
208    public function hasTeam()
209    {
210        return $this->ass_type->usesTeams();
211    }
212
213    /**
214     * Set assignment id
215     *
216     * @param	int		assignment id
217     */
218    public function setId($a_val)
219    {
220        $this->id = $a_val;
221    }
222
223    /**
224     * Get assignment id
225     *
226     * @return	int	assignment id
227     */
228    public function getId()
229    {
230        return $this->id;
231    }
232
233    /**
234     * Set exercise id
235     *
236     * @param	int		exercise id
237     */
238    public function setExerciseId($a_val)
239    {
240        $this->exc_id = $a_val;
241    }
242
243    /**
244     * Get exercise id
245     *
246     * @return	int	exercise id
247     */
248    public function getExerciseId()
249    {
250        return $this->exc_id;
251    }
252
253    /**
254     * Set start time (timestamp)
255     *
256     * @param	int		start time (timestamp)
257     */
258    public function setStartTime($a_val)
259    {
260        $this->start_time = $a_val;
261    }
262
263    /**
264     * Get start time (timestamp)
265     *
266     * @return	int		start time (timestamp)
267     */
268    public function getStartTime()
269    {
270        return $this->start_time;
271    }
272
273    /**
274     * Set deadline (timestamp)
275     *
276     * @param	int		deadline (timestamp)
277     */
278    public function setDeadline($a_val)
279    {
280        $this->deadline = $a_val;
281    }
282
283    /**
284     * Get deadline (timestamp)
285     *
286     * @return	int		deadline (timestamp)
287     */
288    public function getDeadline()
289    {
290        return $this->deadline;
291    }
292
293    /**
294     * Set deadline mode
295     *
296     * @param int $a_val deadline mode
297     */
298    public function setDeadlineMode($a_val)
299    {
300        $this->deadline_mode = $a_val;
301    }
302
303    /**
304     * Get deadline mode
305     *
306     * @return int deadline mode
307     */
308    public function getDeadlineMode()
309    {
310        return $this->deadline_mode;
311    }
312
313    /**
314     * Set relative deadline
315     *
316     * @param int $a_val relative deadline
317     */
318    public function setRelativeDeadline($a_val)
319    {
320        $this->relative_deadline = $a_val;
321    }
322
323    /**
324     * Get relative deadline
325     *
326     * @return int relative deadline
327     */
328    public function getRelativeDeadline()
329    {
330        return $this->relative_deadline;
331    }
332
333    /**
334     * Set relative deadline last submission
335     *
336     * @param int $a_val
337     */
338    public function setRelDeadlineLastSubmission($a_val)
339    {
340        $this->rel_deadline_last_subm = $a_val;
341    }
342
343    /**
344     * Get relative deadline last submission
345     *
346     * @return int
347     */
348    public function getRelDeadlineLastSubmission()
349    {
350        return $this->rel_deadline_last_subm;
351    }
352
353
354    /**
355     * Get individual deadline (max of common or idl (team) deadline = Official Deadline)
356     * @param int $a_user_id
357     * @return int
358     */
359    public function getPersonalDeadline($a_user_id)
360    {
361        $ilDB = $this->db;
362
363        $is_team = false;
364        if ($this->ass_type->usesTeams()) {
365            $team_id = ilExAssignmentTeam::getTeamId($this->getId(), $a_user_id);
366            if (!$team_id) {
367                // #0021043
368                return $this->getDeadline();
369            }
370            $a_user_id = $team_id;
371            $is_team = true;
372        }
373
374        $set = $ilDB->query("SELECT tstamp FROM exc_idl" .
375            " WHERE ass_id = " . $ilDB->quote($this->getId(), "integer") .
376            " AND member_id = " . $ilDB->quote($a_user_id, "integer") .
377            " AND is_team = " . $ilDB->quote($is_team, "integer"));
378        $row = $ilDB->fetchAssoc($set);
379
380        // use assignment deadline if no direct personal
381        return max($row["tstamp"], $this->getDeadline());
382    }
383
384    /**
385     * Get last/final personal deadline (of assignment)
386     *
387     * @return int
388     */
389    public function getLastPersonalDeadline()
390    {
391        $ilDB = $this->db;
392
393        $set = $ilDB->query("SELECT MAX(tstamp) FROM exc_idl" .
394            " WHERE ass_id = " . $ilDB->quote($this->getId(), "integer"));
395        $row = $ilDB->fetchAssoc($set);
396        return $row["tstamp"];
397    }
398
399    /**
400     * Set extended deadline (timestamp)
401     *
402     * @param int
403     */
404    public function setExtendedDeadline($a_val)
405    {
406        if ($a_val !== null) {
407            $a_val = (int) $a_val;
408        }
409        $this->deadline2 = $a_val;
410    }
411
412    /**
413     * Get extended deadline (timestamp)
414     *
415     * @return	int
416     */
417    public function getExtendedDeadline()
418    {
419        return $this->deadline2;
420    }
421
422    /**
423     * Set instruction
424     *
425     * @param	string		instruction
426     */
427    public function setInstruction($a_val)
428    {
429        $this->instruction = $a_val;
430    }
431
432    /**
433     * Get instruction
434     *
435     * @return	string		instruction
436     */
437    public function getInstruction()
438    {
439        return $this->instruction;
440    }
441
442    /**
443     * Get instruction presentation
444     *
445     * @return string
446     */
447    public function getInstructionPresentation()
448    {
449        $inst = $this->getInstruction();
450        if (trim($inst)) {
451            $is_html = (strlen($inst) != strlen(strip_tags($inst)));
452            if (!$is_html) {
453                $inst = nl2br(ilUtil::makeClickable($inst, true));
454            }
455        }
456        return $inst;
457    }
458
459
460    /**
461     * Set title
462     *
463     * @param	string		title
464     */
465    public function setTitle($a_val)
466    {
467        $this->title = $a_val;
468    }
469
470    /**
471     * Get title
472     *
473     * @return	string	title
474     */
475    public function getTitle()
476    {
477        return $this->title;
478    }
479
480    /**
481     * Set mandatory
482     *
483     * @param	int		mandatory
484     */
485    public function setMandatory($a_val)
486    {
487        $this->mandatory = $a_val;
488    }
489
490    /**
491     * Get mandatory
492     *
493     * @return	int	mandatory
494     */
495    public function getMandatory()
496    {
497        return $this->mandatory;
498    }
499
500    /**
501     * Set order nr
502     *
503     * @param	int		order nr
504     */
505    public function setOrderNr($a_val)
506    {
507        $this->order_nr = $a_val;
508    }
509
510    /**
511     * Get order nr
512     *
513     * @return	int	order nr
514     */
515    public function getOrderNr()
516    {
517        return $this->order_nr;
518    }
519
520    /**
521     * Set type
522     *
523     * @param int $a_value
524     * @deprecated this will most probably become an non public function in the future (or become obsolete)
525     */
526    public function setType($a_value)
527    {
528        if ($this->isValidType($a_value)) {
529            $this->type = (int) $a_value;
530
531            $this->ass_type = $this->types->getById($a_value);
532
533            if ($this->ass_type->usesTeams()) {
534                $this->setPeerReview(false);
535            }
536        }
537    }
538
539    /**
540     * Get assignment type
541     *
542     * @param
543     * @return null|ilExAssignmentTypeInterface
544     */
545    public function getAssignmentType()
546    {
547        return $this->ass_type;
548    }
549
550
551    /**
552     * Get type
553     *
554     * @return int
555     * @deprecated this will most probably become an non public function in the future (or become obsolete)
556     */
557    public function getType()
558    {
559        return $this->type;
560    }
561
562    /**
563     * Is given type valid?
564     *
565     * @param int $a_value
566     * @return bool
567     */
568    public function isValidType($a_value)
569    {
570        return $this->types->isValidId($a_value);
571    }
572
573    /**
574     * Toggle peer review
575     *
576     * @param bool $a_value
577     */
578    public function setPeerReview($a_value)
579    {
580        $this->peer = (bool) $a_value;
581    }
582
583    /**
584     * Get peer review status
585     *
586     * @return bool
587     */
588    public function getPeerReview()
589    {
590        return (bool) $this->peer;
591    }
592
593    /**
594     * Set peer review minimum
595     *
596     * @param int $a_value
597     */
598    public function setPeerReviewMin($a_value)
599    {
600        $this->peer_min = (int) $a_value;
601    }
602
603    /**
604     * Get peer review minimum
605     *
606     * @return int
607     */
608    public function getPeerReviewMin()
609    {
610        return (int) $this->peer_min;
611    }
612
613    /**
614     * Set peer review simple unlock
615     *
616     * @param bool $a_value
617     */
618    public function setPeerReviewSimpleUnlock($a_value)
619    {
620        $this->peer_unlock = (bool) $a_value;
621    }
622
623    /**
624     * Get peer review simple unlock
625     *
626     * @return bool
627     */
628    public function getPeerReviewSimpleUnlock()
629    {
630        return (bool) $this->peer_unlock;
631    }
632
633    /**
634     * Set peer review deadline (timestamp)
635     *
636     * @param	int		deadline (timestamp)
637     */
638    public function setPeerReviewDeadline($a_val)
639    {
640        $this->peer_dl = $a_val;
641    }
642
643    /**
644     * Get peer review deadline (timestamp)
645     *
646     * @return	int		deadline (timestamp)
647     */
648    public function getPeerReviewDeadline()
649    {
650        return $this->peer_dl;
651    }
652
653    /**
654     * Set peer review validation
655     *
656     * @param int $a_value
657     */
658    public function setPeerReviewValid($a_value)
659    {
660        $this->peer_valid = (int) $a_value;
661    }
662
663    /**
664     * Get peer review validatiob
665     *
666     * @return int
667     */
668    public function getPeerReviewValid()
669    {
670        return (int) $this->peer_valid;
671    }
672
673    /**
674     * Set peer review rating
675     *
676     * @param	bool
677     */
678    public function setPeerReviewRating($a_val)
679    {
680        $this->peer_rating = (bool) $a_val;
681    }
682
683    /**
684     * Get peer review rating status
685     *
686     * @return	bool
687     */
688    public function hasPeerReviewRating()
689    {
690        return $this->peer_rating;
691    }
692
693    /**
694     * Set peer review text
695     *
696     * @param	bool
697     */
698    public function setPeerReviewText($a_val)
699    {
700        $this->peer_text = (bool) $a_val;
701    }
702
703    /**
704     * Get peer review text status
705     *
706     * @return	bool
707     */
708    public function hasPeerReviewText()
709    {
710        return $this->peer_text;
711    }
712
713    /**
714     * Set peer review file upload
715     *
716     * @param	bool
717     */
718    public function setPeerReviewFileUpload($a_val)
719    {
720        $this->peer_file = (bool) $a_val;
721    }
722
723    /**
724     * Get peer review file upload status
725     *
726     * @return	bool
727     */
728    public function hasPeerReviewFileUpload()
729    {
730        return $this->peer_file;
731    }
732
733    /**
734     * Set peer review personalized
735     *
736     * @param	bool
737     */
738    public function setPeerReviewPersonalized($a_val)
739    {
740        $this->peer_personal = (bool) $a_val;
741    }
742
743    /**
744     * Get peer review personalized status
745     *
746     * @return	bool
747     */
748    public function hasPeerReviewPersonalized()
749    {
750        return $this->peer_personal;
751    }
752
753    /**
754     * Set peer review minimum characters
755     *
756     * @param int $a_value
757     */
758    public function setPeerReviewChars($a_value)
759    {
760        $a_value = (is_numeric($a_value) && (int) $a_value > 0)
761            ? (int) $a_value
762            : null;
763        $this->peer_char = $a_value;
764    }
765
766    /**
767     * Get peer review minimum characters
768     *
769     * @return int
770     */
771    public function getPeerReviewChars()
772    {
773        return $this->peer_char;
774    }
775
776    /**
777     * Set peer review criteria catalogue id
778     *
779     * @param int $a_value
780     */
781    public function setPeerReviewCriteriaCatalogue($a_value)
782    {
783        $a_value = is_numeric($a_value)
784            ? (int) $a_value
785            : null;
786        $this->crit_cat = $a_value;
787    }
788
789    /**
790     * Get peer review criteria catalogue id
791     *
792     * @return int
793     */
794    public function getPeerReviewCriteriaCatalogue()
795    {
796        return $this->crit_cat;
797    }
798
799    public function getPeerReviewCriteriaCatalogueItems()
800    {
801        if ($this->crit_cat) {
802            return ilExcCriteria::getInstancesByParentId($this->crit_cat);
803        } else {
804            $res = array();
805
806            if ($this->peer_rating) {
807                $res[] = ilExcCriteria::getInstanceByType("rating");
808            }
809
810            if ($this->peer_text) {
811                $crit = ilExcCriteria::getInstanceByType("text");
812                if ($this->peer_char) {
813                    $crit->setMinChars($this->peer_char);
814                }
815                $res[] = $crit;
816            }
817
818            if ($this->peer_file) {
819                $res[] = ilExcCriteria::getInstanceByType("file");
820            }
821
822            return $res;
823        }
824    }
825
826    /**
827     * Set (global) feedback file
828     *
829     * @param string $a_value
830     */
831    public function setFeedbackFile($a_value)
832    {
833        $this->feedback_file = (string) $a_value;
834    }
835
836    /**
837     * Get (global) feedback file
838     *
839     * @return int
840     */
841    public function getFeedbackFile()
842    {
843        return (string) $this->feedback_file;
844    }
845
846    /**
847     * Toggle (global) feedback file cron
848     *
849     * @param bool $a_value
850     */
851    public function setFeedbackCron($a_value)
852    {
853        $this->feedback_cron = (string) $a_value;
854    }
855
856    /**
857     * Get (global) feedback file cron status
858     *
859     * @return int
860     */
861    public function hasFeedbackCron()
862    {
863        return (bool) $this->feedback_cron;
864    }
865
866    /**
867     * Set (global) feedback file availability date
868     *
869     * @param int $a_value
870     */
871    public function setFeedbackDate($a_value)
872    {
873        $this->feedback_date = (int) $a_value;
874    }
875
876    /**
877     * Get (global) feedback file availability date
878     *
879     * @return int
880     */
881    public function getFeedbackDate()
882    {
883        return (int) $this->feedback_date;
884    }
885
886    /**
887     * Set (global) feedback file availability using a custom date.
888     * @param int $a_value timestamp
889     */
890    public function setFeedbackDateCustom($a_value)
891    {
892        $this->feedback_date_custom = $a_value;
893    }
894
895    /**
896     * Get feedback file availability using custom date.
897     * @return string timestamp
898     */
899    public function getFeedbackDateCustom()
900    {
901        return $this->feedback_date_custom;
902    }
903
904    /**
905     * Set team management by tutor
906     *
907     * @param bool $a_value
908     */
909    public function setTeamTutor($a_value)
910    {
911        $this->team_tutor = (bool) $a_value;
912    }
913
914    /**
915     * Get team management by tutor
916     *
917     * @return bool
918     */
919    public function getTeamTutor()
920    {
921        return $this->team_tutor;
922    }
923
924    /**
925     * Set max number of uploads
926     *
927     * @param int $a_value
928     */
929    public function setMaxFile($a_value)
930    {
931        if ($a_value !== null) {
932            $a_value = (int) $a_value;
933        }
934        $this->max_file = $a_value;
935    }
936
937    /**
938     * Get max number of uploads
939     *
940     * @return bool
941     */
942    public function getMaxFile()
943    {
944        return $this->max_file;
945    }
946
947    /**
948     * Set portfolio template id
949     *
950     * @param int $a_val
951     */
952    public function setPortfolioTemplateId($a_val)
953    {
954        $this->portfolio_template = $a_val;
955    }
956
957    /**
958     * Get portfolio template id
959     *
960     * @return	int	portfolio template id
961     */
962    public function getPortfolioTemplateId()
963    {
964        return $this->portfolio_template;
965    }
966
967
968    /**
969     * Read from db
970     */
971    public function read()
972    {
973        $ilDB = $this->db;
974
975        $set = $ilDB->query(
976            "SELECT * FROM exc_assignment " .
977            " WHERE id = " . $ilDB->quote($this->getId(), "integer")
978        );
979        $rec = $ilDB->fetchAssoc($set);
980
981        // #16172 - might be deleted
982        if (is_array($rec)) {
983            $this->initFromDB($rec);
984        }
985    }
986
987    /**
988     * Import DB record
989     *
990     * @see getInstancesByExercise()
991     * @param array $a_set
992     */
993    protected function initFromDB(array $a_set)
994    {
995        $this->setId($a_set["id"]);
996        $this->setExerciseId($a_set["exc_id"]);
997        $this->setDeadline($a_set["time_stamp"]);
998        $this->setExtendedDeadline($a_set["deadline2"]);
999        $this->setInstruction($a_set["instruction"]);
1000        $this->setTitle($a_set["title"]);
1001        $this->setStartTime($a_set["start_time"]);
1002        $this->setOrderNr($a_set["order_nr"]);
1003        $this->setMandatory($a_set["mandatory"]);
1004        $this->setType($a_set["type"]);
1005        $this->setPeerReview($a_set["peer"]);
1006        $this->setPeerReviewMin($a_set["peer_min"]);
1007        $this->setPeerReviewSimpleUnlock($a_set["peer_unlock"]);
1008        $this->setPeerReviewDeadline($a_set["peer_dl"]);
1009        $this->setPeerReviewValid($a_set["peer_valid"]);
1010        $this->setPeerReviewFileUpload($a_set["peer_file"]);
1011        $this->setPeerReviewPersonalized($a_set["peer_prsl"]);
1012        $this->setPeerReviewChars($a_set["peer_char"]);
1013        $this->setPeerReviewText($a_set["peer_text"]);
1014        $this->setPeerReviewRating($a_set["peer_rating"]);
1015        $this->setPeerReviewCriteriaCatalogue($a_set["peer_crit_cat"]);
1016        $this->setFeedbackFile($a_set["fb_file"]);
1017        $this->setFeedbackDate($a_set["fb_date"]);
1018        $this->setFeedbackDateCustom($a_set["fb_date_custom"]);
1019        $this->setFeedbackCron($a_set["fb_cron"]);
1020        $this->setTeamTutor($a_set["team_tutor"]);
1021        $this->setMaxFile($a_set["max_file"]);
1022        $this->setPortfolioTemplateId($a_set["portfolio_template"]);
1023        $this->setMinCharLimit($a_set["min_char_limit"]);
1024        $this->setMaxCharLimit($a_set["max_char_limit"]);
1025        $this->setDeadlineMode($a_set["deadline_mode"]);
1026        $this->setRelativeDeadline($a_set["relative_deadline"]);
1027        $this->setRelDeadlineLastSubmission($a_set["rel_deadline_last_subm"]);
1028    }
1029
1030    /**
1031     * Save assignment
1032     */
1033    public function save()
1034    {
1035        $ilDB = $this->db;
1036
1037        if ($this->getOrderNr() == 0) {
1038            $this->setOrderNr(
1039                self::lookupMaxOrderNrForEx($this->getExerciseId())
1040                + 10
1041            );
1042        }
1043
1044        $next_id = $ilDB->nextId("exc_assignment");
1045        $ilDB->insert("exc_assignment", array(
1046            "id" => array("integer", $next_id),
1047            "exc_id" => array("integer", $this->getExerciseId()),
1048            "time_stamp" => array("integer", $this->getDeadline()),
1049            "deadline2" => array("integer", $this->getExtendedDeadline()),
1050            "instruction" => array("clob", $this->getInstruction()),
1051            "title" => array("text", $this->getTitle()),
1052            "start_time" => array("integer", $this->getStartTime()),
1053            "order_nr" => array("integer", $this->getOrderNr()),
1054            "mandatory" => array("integer", $this->getMandatory()),
1055            "type" => array("integer", $this->getType()),
1056            "peer" => array("integer", $this->getPeerReview()),
1057            "peer_min" => array("integer", $this->getPeerReviewMin()),
1058            "peer_unlock" => array("integer", $this->getPeerReviewSimpleUnlock()),
1059            "peer_dl" => array("integer", $this->getPeerReviewDeadline()),
1060            "peer_valid" => array("integer", $this->getPeerReviewValid()),
1061            "peer_file" => array("integer", $this->hasPeerReviewFileUpload()),
1062            "peer_prsl" => array("integer", $this->hasPeerReviewPersonalized()),
1063            "peer_char" => array("integer", $this->getPeerReviewChars()),
1064            "peer_text" => array("integer", (int) $this->hasPeerReviewText()),
1065            "peer_rating" => array("integer", (int) $this->hasPeerReviewRating()),
1066            "peer_crit_cat" => array("integer", $this->getPeerReviewCriteriaCatalogue()),
1067            "fb_file" => array("text", $this->getFeedbackFile()),
1068            "fb_date" => array("integer", $this->getFeedbackDate()),
1069            "fb_date_custom" => array("integer", $this->getFeedbackDateCustom()),
1070            "fb_cron" => array("integer", $this->hasFeedbackCron()),
1071            "team_tutor" => array("integer", $this->getTeamTutor()),
1072            "max_file" => array("integer", $this->getMaxFile()),
1073            "portfolio_template" => array("integer", $this->getPortFolioTemplateId()),
1074            "min_char_limit" => array("integer", $this->getMinCharLimit()),
1075            "max_char_limit" => array("integer", $this->getMaxCharLimit()),
1076            "relative_deadline" => array("integer", $this->getRelativeDeadline()),
1077            "rel_deadline_last_subm" => array("integer", (int) $this->getRelDeadlineLastSubmission()),
1078            "deadline_mode" => array("integer", $this->getDeadlineMode())
1079            ));
1080        $this->setId($next_id);
1081        $exc = new ilObjExercise($this->getExerciseId(), false);
1082        $exc->updateAllUsersStatus();
1083        self::createNewAssignmentRecords($next_id, $exc);
1084
1085        $this->handleCalendarEntries("create");
1086    }
1087
1088    /**
1089     * Update
1090     */
1091    public function update()
1092    {
1093        $ilDB = $this->db;
1094
1095        $ilDB->update(
1096            "exc_assignment",
1097            array(
1098            "exc_id" => array("integer", $this->getExerciseId()),
1099            "time_stamp" => array("integer", $this->getDeadline()),
1100            "deadline2" => array("integer", $this->getExtendedDeadline()),
1101            "instruction" => array("clob", $this->getInstruction()),
1102            "title" => array("text", $this->getTitle()),
1103            "start_time" => array("integer", $this->getStartTime()),
1104            "order_nr" => array("integer", $this->getOrderNr()),
1105            "mandatory" => array("integer", $this->getMandatory()),
1106            "type" => array("integer", $this->getType()),
1107            "peer" => array("integer", $this->getPeerReview()),
1108            "peer_min" => array("integer", $this->getPeerReviewMin()),
1109            "peer_unlock" => array("integer", $this->getPeerReviewSimpleUnlock()),
1110            "peer_dl" => array("integer", $this->getPeerReviewDeadline()),
1111            "peer_valid" => array("integer", $this->getPeerReviewValid()),
1112            "peer_file" => array("integer", $this->hasPeerReviewFileUpload()),
1113            "peer_prsl" => array("integer", $this->hasPeerReviewPersonalized()),
1114            "peer_char" => array("integer", $this->getPeerReviewChars()),
1115            "peer_text" => array("integer", (int) $this->hasPeerReviewText()),
1116            "peer_rating" => array("integer", (int) $this->hasPeerReviewRating()),
1117            "peer_crit_cat" => array("integer", $this->getPeerReviewCriteriaCatalogue()),
1118            "fb_file" => array("text", $this->getFeedbackFile()),
1119            "fb_date" => array("integer", $this->getFeedbackDate()),
1120            "fb_date_custom" => array("integer", $this->getFeedbackDateCustom()),
1121            "fb_cron" => array("integer", $this->hasFeedbackCron()),
1122            "team_tutor" => array("integer", $this->getTeamTutor()),
1123            "max_file" => array("integer", $this->getMaxFile()),
1124            "portfolio_template" => array("integer", $this->getPortFolioTemplateId()),
1125            "min_char_limit" => array("integer", $this->getMinCharLimit()),
1126            "max_char_limit" => array("integer", $this->getMaxCharLimit()),
1127            "deadline_mode" => array("integer", $this->getDeadlineMode()),
1128            "relative_deadline" => array("integer", $this->getRelativeDeadline()),
1129            "rel_deadline_last_subm" => array("integer", (int) $this->getRelDeadlineLastSubmission())
1130            ),
1131            array(
1132            "id" => array("integer", $this->getId()),
1133            )
1134        );
1135        $exc = new ilObjExercise($this->getExerciseId(), false);
1136        $exc->updateAllUsersStatus();
1137
1138        $this->handleCalendarEntries("update");
1139    }
1140
1141    /**
1142     * Delete assignment
1143     */
1144    public function delete()
1145    {
1146        $ilDB = $this->db;
1147
1148        $this->deleteGlobalFeedbackFile();
1149
1150        $ilDB->manipulate(
1151            "DELETE FROM exc_assignment WHERE " .
1152            " id = " . $ilDB->quote($this->getId(), "integer")
1153        );
1154        $exc = new ilObjExercise($this->getExerciseId(), false);
1155        $exc->updateAllUsersStatus();
1156
1157        $this->handleCalendarEntries("delete");
1158
1159        $reminder = new ilExAssignmentReminder();
1160        $reminder->deleteReminders($this->getId());
1161    }
1162
1163
1164    /**
1165     * Get assignments data of an exercise in an array
1166     */
1167    public static function getAssignmentDataOfExercise($a_exc_id)
1168    {
1169        global $DIC;
1170
1171        $ilDB = $DIC->database();
1172
1173        // should be changed to self::getInstancesByExerciseId()
1174
1175        $set = $ilDB->query("SELECT * FROM exc_assignment " .
1176            " WHERE exc_id = " . $ilDB->quote($a_exc_id, "integer") .
1177            " ORDER BY order_nr ASC");
1178        $data = array();
1179
1180        $order_val = 10;
1181        while ($rec = $ilDB->fetchAssoc($set)) {
1182            $data[] = array(
1183                "id" => $rec["id"],
1184                "exc_id" => $rec["exc_id"],
1185                "deadline" => $rec["time_stamp"],
1186                "deadline2" => $rec["deadline2"],
1187                "instruction" => $rec["instruction"],
1188                "title" => $rec["title"],
1189                "start_time" => $rec["start_time"],
1190                "order_val" => $order_val,
1191                "mandatory" => $rec["mandatory"],
1192                "type" => $rec["type"],
1193                "peer" => $rec["peer"],
1194                "peer_min" => $rec["peer_min"],
1195                "peer_dl" => $rec["peer_dl"],
1196                "peer_file" => $rec["peer_file"],
1197                "peer_prsl" => $rec["peer_prsl"],
1198                "fb_file" => $rec["fb_file"],
1199                "fb_date" => $rec["fb_date"],
1200                "fb_cron" => $rec["fb_cron"],
1201                "deadline_mode" => $rec["deadline_mode"],
1202                "relative_deadline" => $rec["relative_deadline"],
1203                "rel_deadline_last_subm" => $rec["rel_deadline_last_subm"]
1204                );
1205            $order_val += 10;
1206        }
1207        return $data;
1208    }
1209
1210    /**
1211     * Clone assignments of exercise
1212     * @param
1213     * @return
1214     */
1215    public static function cloneAssignmentsOfExercise($a_old_exc_id, $a_new_exc_id, array $a_crit_cat_map)
1216    {
1217        $ass_data = self::getInstancesByExercise($a_old_exc_id);
1218        foreach ($ass_data as $d) {
1219            // clone assignment
1220            $new_ass = new ilExAssignment();
1221            $new_ass->setExerciseId($a_new_exc_id);
1222            $new_ass->setTitle($d->getTitle());
1223            $new_ass->setDeadline($d->getDeadline());
1224            $new_ass->setExtendedDeadline($d->getExtendedDeadline());
1225            $new_ass->setInstruction($d->getInstruction());
1226            $new_ass->setMandatory($d->getMandatory());
1227            $new_ass->setOrderNr($d->getOrderNr());
1228            $new_ass->setStartTime($d->getStartTime());
1229            $new_ass->setType($d->getType());
1230            $new_ass->setPeerReview($d->getPeerReview());
1231            $new_ass->setPeerReviewMin($d->getPeerReviewMin());
1232            $new_ass->setPeerReviewDeadline($d->getPeerReviewDeadline());
1233            $new_ass->setPeerReviewFileUpload($d->hasPeerReviewFileUpload());
1234            $new_ass->setPeerReviewPersonalized($d->hasPeerReviewPersonalized());
1235            $new_ass->setPeerReviewValid($d->getPeerReviewValid());
1236            $new_ass->setPeerReviewChars($d->getPeerReviewChars());
1237            $new_ass->setPeerReviewText($d->hasPeerReviewText());
1238            $new_ass->setPeerReviewRating($d->hasPeerReviewRating());
1239            $new_ass->setPeerReviewCriteriaCatalogue($d->getPeerReviewCriteriaCatalogue());
1240            $new_ass->setPeerReviewSimpleUnlock($d->getPeerReviewSimpleUnlock());
1241            $new_ass->setFeedbackFile($d->getFeedbackFile());
1242            $new_ass->setFeedbackDate($d->getFeedbackDate());
1243            $new_ass->setFeedbackDateCustom($d->getFeedbackDateCustom());
1244            $new_ass->setFeedbackCron($d->hasFeedbackCron()); // #16295
1245            $new_ass->setTeamTutor($d->getTeamTutor());
1246            $new_ass->setMaxFile($d->getMaxFile());
1247            $new_ass->setMinCharLimit($d->getMinCharLimit());
1248            $new_ass->setMaxCharLimit($d->getMaxCharLimit());
1249            $new_ass->setPortfolioTemplateId($d->getPortfolioTemplateId());
1250            $new_ass->setDeadlineMode($d->getDeadlineMode());
1251            $new_ass->setRelativeDeadline($d->getRelativeDeadline());
1252            $new_ass->setRelDeadlineLastSubmission($d->getRelDeadlineLastSubmission());
1253
1254            // criteria catalogue(s)
1255            if ($d->getPeerReviewCriteriaCatalogue() &&
1256                array_key_exists($d->getPeerReviewCriteriaCatalogue(), $a_crit_cat_map)) {
1257                $new_ass->setPeerReviewCriteriaCatalogue($a_crit_cat_map[$d->getPeerReviewCriteriaCatalogue()]);
1258            }
1259
1260            $new_ass->save();
1261
1262
1263            // clone assignment files
1264            $old_web_storage = new ilFSWebStorageExercise($a_old_exc_id, (int) $d->getId());
1265            $new_web_storage = new ilFSWebStorageExercise($a_new_exc_id, (int) $new_ass->getId());
1266            $new_web_storage->create();
1267            if (is_dir($old_web_storage->getPath())) {
1268                ilUtil::rCopy($old_web_storage->getPath(), $new_web_storage->getPath());
1269            }
1270            $order = $d->getInstructionFilesOrder();
1271            foreach ($order as $file) {
1272                ilExAssignment::insertFileOrderNr($new_ass->getId(), $file["filename"], $file["order_nr"]);
1273            }
1274
1275            // clone global feedback file
1276            $old_storage = new ilFSStorageExercise($a_old_exc_id, (int) $d->getId());
1277            $new_storage = new ilFSStorageExercise($a_new_exc_id, (int) $new_ass->getId());
1278            $new_storage->create();
1279            if (is_dir($old_storage->getGlobalFeedbackPath())) {
1280                ilUtil::rCopy($old_storage->getGlobalFeedbackPath(), $new_storage->getGlobalFeedbackPath());
1281            }
1282
1283            // clone reminders
1284            foreach ([ilExAssignmentReminder::SUBMIT_REMINDER,
1285                      ilExAssignmentReminder::GRADE_REMINDER,
1286                      ilExAssignmentReminder::FEEDBACK_REMINDER] as $rem_type) {
1287                $rmd_sub = new ilExAssignmentReminder($a_old_exc_id, $d->getId(), $rem_type);
1288                if ($rmd_sub->getReminderStatus()) {
1289                    $new_rmd_sub = new ilExAssignmentReminder($a_new_exc_id, $new_ass->getId(), $rem_type);
1290                    $new_rmd_sub->setReminderStatus($rmd_sub->getReminderStatus());
1291                    $new_rmd_sub->setReminderStart($rmd_sub->getReminderStart());
1292                    $new_rmd_sub->setReminderEnd($rmd_sub->getReminderEnd());
1293                    $new_rmd_sub->setReminderFrequency($rmd_sub->getReminderFrequency());
1294                    $new_rmd_sub->setReminderMailTemplate($rmd_sub->getReminderMailTemplate());
1295                    $new_rmd_sub->save();
1296                }
1297            }
1298
1299
1300            // type specific properties
1301            $ass_type = $d->getAssignmentType();
1302            $ass_type->cloneSpecificProperties($d, $new_ass);
1303        }
1304    }
1305
1306    /**
1307     * Get files
1308     */
1309    public function getFiles()
1310    {
1311        $this->log->debug("getting files from class.ilExAssignment using ilFSWebStorageExercise");
1312        $storage = new ilFSWebStorageExercise($this->getExerciseId(), $this->getId());
1313        return $storage->getFiles();
1314    }
1315
1316    /**
1317     * @param $a_ass_id
1318     * @return array
1319     */
1320    public function getInstructionFilesOrder()
1321    {
1322        $ilDB = $this->db;
1323
1324        $set = $ilDB->query(
1325            "SELECT filename, order_nr, id FROM exc_ass_file_order " .
1326            " WHERE assignment_id  = " . $ilDB->quote($this->getId(), "integer")
1327        );
1328
1329        $data = array();
1330        while ($rec = $ilDB->fetchAssoc($set)) {
1331            $data[$rec['filename']] = $rec;
1332        }
1333
1334        return $data;
1335    }
1336
1337    /**
1338     * Select the maximum order nr for an exercise
1339     */
1340    public static function lookupMaxOrderNrForEx($a_exc_id)
1341    {
1342        global $DIC;
1343
1344        $ilDB = $DIC->database();
1345
1346        $set = $ilDB->query(
1347            "SELECT MAX(order_nr) mnr FROM exc_assignment " .
1348            " WHERE exc_id = " . $ilDB->quote($a_exc_id, "integer")
1349        );
1350        while ($rec = $ilDB->fetchAssoc($set)) {
1351            return (int) $rec["mnr"];
1352        }
1353        return 0;
1354    }
1355
1356    /**
1357     * Check if assignment is online
1358     * @param int $a_ass_id
1359     * @return bool
1360     */
1361    public static function lookupAssignmentOnline($a_ass_id)
1362    {
1363        global $DIC;
1364
1365        $ilDB = $DIC->database();
1366
1367        $query = "SELECT id FROM exc_assignment " .
1368            "WHERE start_time <= " . $ilDB->quote(time(), 'integer') . ' ' .
1369            "AND time_stamp >= " . $ilDB->quote(time(), 'integer') . ' ' .
1370            "AND id = " . $ilDB->quote($a_ass_id, 'integer');
1371        $res = $ilDB->query($query);
1372
1373        return $res->numRows() ? true : false;
1374    }
1375
1376    /**
1377     * Lookup excercise id for assignment id
1378     *
1379     * @param int $a_ass_id
1380     * @return int
1381     */
1382    public static function lookupExerciseId($a_ass_id)
1383    {
1384        global $DIC;
1385
1386        $ilDB = $DIC->database();
1387
1388        $query = "SELECT exc_id FROM exc_assignment " .
1389            "WHERE id = " . $ilDB->quote($a_ass_id, 'integer');
1390        $res = $ilDB->fetchAssoc($ilDB->query($query));
1391
1392        return (int) $res["exc_id"];
1393    }
1394
1395    /**
1396     * Private lookup
1397     */
1398    private static function lookup($a_id, $a_field)
1399    {
1400        global $DIC;
1401
1402        $ilDB = $DIC->database();
1403
1404        $set = $ilDB->query(
1405            "SELECT " . $a_field . " FROM exc_assignment " .
1406            " WHERE id = " . $ilDB->quote($a_id, "integer")
1407        );
1408
1409        $rec = $ilDB->fetchAssoc($set);
1410
1411        return $rec[$a_field];
1412    }
1413
1414    /**
1415     * Lookup title
1416     */
1417    public static function lookupTitle($a_id)
1418    {
1419        return self::lookup($a_id, "title");
1420    }
1421
1422    /**
1423     * Lookup type
1424     */
1425    public static function lookupType($a_id)
1426    {
1427        return self::lookup($a_id, "type");
1428    }
1429
1430    /**
1431     * Save ordering of all assignments of an exercise
1432     */
1433    public static function saveAssOrderOfExercise($a_ex_id, $a_order)
1434    {
1435        global $DIC;
1436
1437        $ilDB = $DIC->database();
1438
1439        $result_order = array();
1440        asort($a_order);
1441        $nr = 10;
1442        foreach ($a_order as $k => $v) {
1443            // the check for exc_id is for security reasons. ass ids are unique.
1444            $ilDB->manipulate(
1445                $t = "UPDATE exc_assignment SET " .
1446                " order_nr = " . $ilDB->quote($nr, "integer") .
1447                " WHERE id = " . $ilDB->quote((int) $k, "integer") .
1448                " AND exc_id = " . $ilDB->quote((int) $a_ex_id, "integer")
1449            );
1450            $nr += 10;
1451        }
1452    }
1453
1454    /**
1455     * Order assignments by deadline date
1456     */
1457    public static function orderAssByDeadline($a_ex_id)
1458    {
1459        global $DIC;
1460        $ilDB = $DIC->database();
1461
1462        $set = $ilDB->query(
1463            "SELECT id FROM exc_assignment " .
1464            " WHERE exc_id = " . $ilDB->quote($a_ex_id, "integer") .
1465            " ORDER BY time_stamp ASC"
1466        );
1467        $nr = 10;
1468        while ($rec = $ilDB->fetchAssoc($set)) {
1469            $ilDB->manipulate(
1470                "UPDATE exc_assignment SET " .
1471                " order_nr = " . $ilDB->quote($nr, "integer") .
1472                " WHERE id = " . $ilDB->quote($rec["id"], "integer")
1473            );
1474            $nr += 10;
1475        }
1476    }
1477
1478    /**
1479     * Count the number of mandatory assignments
1480     */
1481    public static function countMandatory($a_ex_id)
1482    {
1483        global $DIC;
1484
1485        $ilDB = $DIC->database();
1486
1487        $set = $ilDB->query(
1488            "SELECT count(*) cntm FROM exc_assignment " .
1489            " WHERE exc_id = " . $ilDB->quote($a_ex_id, "integer") .
1490            " AND mandatory = " . $ilDB->quote(1, "integer")
1491        );
1492        $rec = $ilDB->fetchAssoc($set);
1493        return $rec["cntm"];
1494    }
1495
1496    /**
1497     * Order assignments by deadline date
1498     */
1499    public static function count($a_ex_id)
1500    {
1501        global $DIC;
1502
1503        $ilDB = $DIC->database();
1504
1505        $set = $ilDB->query(
1506            "SELECT count(*) cntm FROM exc_assignment " .
1507            " WHERE exc_id = " . $ilDB->quote($a_ex_id, "integer")
1508        );
1509        $rec = $ilDB->fetchAssoc($set);
1510        return $rec["cntm"];
1511    }
1512
1513    /**
1514     * Is assignment in exercise?
1515     */
1516    public static function isInExercise($a_ass_id, $a_ex_id)
1517    {
1518        global $DIC;
1519
1520        $ilDB = $DIC->database();
1521
1522        $set = $ilDB->query(
1523            "SELECT * FROM exc_assignment " .
1524            " WHERE exc_id = " . $ilDB->quote($a_ex_id, "integer") .
1525            " AND id = " . $ilDB->quote($a_ass_id, "integer")
1526        );
1527        if ($rec = $ilDB->fetchAssoc($set)) {
1528            return true;
1529        }
1530        return false;
1531    }
1532
1533    ///
1534    /**
1535     * Check whether student has upload new files after tutor has
1536     * set the exercise to another than notgraded.
1537     */
1538    public static function lookupUpdatedSubmission($ass_id, $member_id)
1539    {
1540        global $DIC;
1541
1542        $ilDB = $DIC->database();
1543        $lng = $DIC->language();
1544
1545        // team upload?
1546        $user_ids = self::getTeamMembersByAssignmentId($ass_id, $member_id);
1547        if (!$user_ids) {
1548            $user_ids = array($member_id);
1549        }
1550
1551        $q = "SELECT exc_mem_ass_status.status_time, exc_returned.ts " .
1552            "FROM exc_mem_ass_status, exc_returned " .
1553            "WHERE exc_mem_ass_status.status_time < exc_returned.ts " .
1554            "AND NOT exc_mem_ass_status.status_time IS NULL " .
1555            "AND exc_returned.ass_id = exc_mem_ass_status.ass_id " .
1556            "AND exc_returned.user_id = exc_mem_ass_status.usr_id " .
1557            "AND exc_returned.ass_id=" . $ilDB->quote($ass_id, "integer") .
1558            " AND " . $ilDB->in("exc_returned.user_id", $user_ids, "", "integer");
1559
1560        $usr_set = $ilDB->query($q);
1561
1562        $array = $ilDB->fetchAssoc($usr_set);
1563
1564        if (count($array) == 0) {
1565            return 0;
1566        } else {
1567            return 1;
1568        }
1569    }
1570
1571    /**
1572     * get member list data
1573     */
1574    public function getMemberListData()
1575    {
1576        $ilDB = $this->db;
1577
1578        $mem = array();
1579
1580        // first get list of members from member table
1581        $set = $ilDB->query("SELECT ud.usr_id, ud.lastname, ud.firstname, ud.login" .
1582            " FROM exc_members excm" .
1583            " JOIN usr_data ud ON (ud.usr_id = excm.usr_id)" .
1584            " WHERE excm.obj_id = " . $ilDB->quote($this->getExerciseId(), "integer"));
1585        while ($rec = $ilDB->fetchAssoc($set)) {
1586            $mem[$rec["usr_id"]] =
1587                array(
1588                "name" => $rec["lastname"] . ", " . $rec["firstname"],
1589                "login" => $rec["login"],
1590                "usr_id" => $rec["usr_id"],
1591                "lastname" => $rec["lastname"],
1592                "firstname" => $rec["firstname"]
1593                );
1594        }
1595
1596        $q = "SELECT * FROM exc_mem_ass_status " .
1597            "WHERE ass_id = " . $ilDB->quote($this->getId(), "integer");
1598        $set = $ilDB->query($q);
1599        while ($rec = $ilDB->fetchAssoc($set)) {
1600            if (isset($mem[$rec["usr_id"]])) {
1601                $sub = new ilExSubmission($this, $rec["usr_id"]);
1602
1603                $mem[$rec["usr_id"]]["sent_time"] = $rec["sent_time"];
1604                $mem[$rec["usr_id"]]["submission"] = $sub->getLastSubmission();
1605                $mem[$rec["usr_id"]]["status_time"] = $rec["status_time"];
1606                $mem[$rec["usr_id"]]["feedback_time"] = $rec["feedback_time"];
1607                $mem[$rec["usr_id"]]["notice"] = $rec["notice"];
1608                $mem[$rec["usr_id"]]["status"] = $rec["status"];
1609                $mem[$rec["usr_id"]]["mark"] = $rec["mark"];
1610                $mem[$rec["usr_id"]]["comment"] = $rec["u_comment"];
1611            }
1612        }
1613        return $mem;
1614    }
1615
1616    /**
1617     * Get submission data for an specific user,exercise and assignment.
1618     * todo we can refactor a bit the method getMemberListData to use this and remove duplicate code.
1619     * @param $a_user_id
1620     * @param $a_grade
1621     * @return array
1622     */
1623    public function getExerciseMemberAssignmentData($a_user_id, $a_grade = "")
1624    {
1625        global $DIC;
1626        $ilDB = $DIC->database();
1627
1628        if (in_array($a_grade, array("notgraded", "passed", "failed"))) {
1629            $and_grade = " AND status = " . $ilDB->quote($a_grade, "text");
1630        }
1631
1632        $q = "SELECT * FROM exc_mem_ass_status " .
1633            "WHERE ass_id = " . $ilDB->quote($this->getId(), "integer") .
1634            " AND usr_id = " . $ilDB->quote($a_user_id, "integer") .
1635            $and_grade;
1636
1637        $set = $ilDB->query($q);
1638
1639        while ($rec = $ilDB->fetchAssoc($set)) {
1640            $sub = new ilExSubmission($this, $a_user_id);
1641
1642            $data["sent_time"] = $rec["sent_time"];
1643            $data["submission"] = $sub->getLastSubmission();
1644            $data["status_time"] = $rec["status_time"];
1645            $data["feedback_time"] = $rec["feedback_time"];
1646            $data["notice"] = $rec["notice"];
1647            $data["status"] = $rec["status"];
1648            $data["mark"] = $rec["mark"];
1649            $data["comment"] = $rec["u_comment"];
1650        }
1651
1652        return $data;
1653    }
1654
1655    /**
1656     * Create member status record for a new participant for all assignments
1657     */
1658    public static function createNewUserRecords($a_user_id, $a_exc_id)
1659    {
1660        global $DIC;
1661
1662        $ilDB = $DIC->database();
1663
1664        $ass_data = self::getAssignmentDataOfExercise($a_exc_id);
1665        foreach ($ass_data as $ass) {
1666            //echo "-".$ass["id"]."-".$a_user_id."-";
1667            $ilDB->replace("exc_mem_ass_status", array(
1668                "ass_id" => array("integer", $ass["id"]),
1669                "usr_id" => array("integer", $a_user_id)
1670                ), array(
1671                "status" => array("text", "notgraded")
1672                ));
1673        }
1674    }
1675
1676    /**
1677     * Create member status record for a new assignment for all participants
1678     */
1679    public static function createNewAssignmentRecords($a_ass_id, $a_exc)
1680    {
1681        global $DIC;
1682
1683        $ilDB = $DIC->database();
1684
1685        $exmem = new ilExerciseMembers($a_exc);
1686        $mems = $exmem->getMembers();
1687
1688        foreach ($mems as $mem) {
1689            $ilDB->replace("exc_mem_ass_status", array(
1690                "ass_id" => array("integer", $a_ass_id),
1691                "usr_id" => array("integer", $mem)
1692                ), array(
1693                "status" => array("text", "notgraded")
1694                ));
1695        }
1696    }
1697
1698    /**
1699     * Upload assignment files
1700     * (from creation form)
1701     */
1702    public function uploadAssignmentFiles($a_files)
1703    {
1704        ilLoggerFactory::getLogger("exc")->debug("upload assignment files files = ", $a_files);
1705        $storage = new ilFSWebStorageExercise($this->getExerciseId(), $this->getId());
1706        $storage->create();
1707        $storage->uploadAssignmentFiles($a_files);
1708    }
1709
1710
1711    ////
1712    //// Multi-Feedback
1713    ////
1714
1715    /**
1716     * Create member status record for a new assignment for all participants
1717     */
1718    public function sendMultiFeedbackStructureFile(ilObjExercise $exercise)
1719    {
1720        global $DIC;
1721
1722
1723        // send and delete the zip file
1724        $deliverFilename = trim(str_replace(" ", "_", $this->getTitle() . "_" . $this->getId()));
1725        $deliverFilename = ilUtil::getASCIIFilename($deliverFilename);
1726        $deliverFilename = "multi_feedback_" . $deliverFilename;
1727
1728        $exc = new ilObjExercise($this->getExerciseId(), false);
1729
1730        $cdir = getcwd();
1731
1732        // create temporary directoy
1733        $tmpdir = ilUtil::ilTempnam();
1734        ilUtil::makeDir($tmpdir);
1735        $mfdir = $tmpdir . "/" . $deliverFilename;
1736        ilUtil::makeDir($mfdir);
1737
1738        // create subfolders <lastname>_<firstname>_<id> for each participant
1739        $exmem = new ilExerciseMembers($exc);
1740        $mems = $exmem->getMembers();
1741
1742        $mems = $DIC->access()->filterUserIdsByRbacOrPositionOfCurrentUser(
1743            'edit_submissions_grades',
1744            'edit_submissions_grades',
1745            $exercise->getRefId(),
1746            $mems
1747        );
1748        foreach ($mems as $mem) {
1749            $name = ilObjUser::_lookupName($mem);
1750            $subdir = $name["lastname"] . "_" . $name["firstname"] . "_" . $name["login"] . "_" . $name["user_id"];
1751            $subdir = ilUtil::getASCIIFilename($subdir);
1752            ilUtil::makeDir($mfdir . "/" . $subdir);
1753        }
1754
1755        // create the zip file
1756        chdir($tmpdir);
1757        $tmpzipfile = $tmpdir . "/multi_feedback.zip";
1758        ilUtil::zip($tmpdir, $tmpzipfile, true);
1759        chdir($cdir);
1760
1761
1762        ilUtil::deliverFile($tmpzipfile, $deliverFilename . ".zip", "", false, true);
1763    }
1764
1765    /**
1766     * Upload multi feedback file
1767     *
1768     * @param array
1769     * @return
1770     */
1771    public function uploadMultiFeedbackFile($a_file)
1772    {
1773        $lng = $this->lng;
1774        $ilUser = $this->user;
1775
1776        if (!is_file($a_file["tmp_name"])) {
1777            throw new ilExerciseException($lng->txt("exc_feedback_file_could_not_be_uploaded"));
1778        }
1779
1780        $storage = new ilFSStorageExercise($this->getExerciseId(), $this->getId());
1781        $mfu = $storage->getMultiFeedbackUploadPath($ilUser->getId());
1782        ilUtil::delDir($mfu, true);
1783        ilUtil::moveUploadedFile($a_file["tmp_name"], "multi_feedback.zip", $mfu . "/" . "multi_feedback.zip");
1784        ilUtil::unzip($mfu . "/multi_feedback.zip", true);
1785        $subdirs = ilUtil::getDir($mfu);
1786        $subdir = "notfound";
1787        foreach ($subdirs as $s => $j) {
1788            if ($j["type"] == "dir" && substr($s, 0, 14) == "multi_feedback") {
1789                $subdir = $s;
1790            }
1791        }
1792
1793        if (!is_dir($mfu . "/" . $subdir)) {
1794            throw new ilExerciseException($lng->txt("exc_no_feedback_dir_found_in_zip"));
1795        }
1796
1797        return true;
1798    }
1799
1800    /**
1801     * Get multi feedback files (of uploader)
1802     *
1803     * @param int $a_user_id user id of uploader
1804     * @return array array of user files (keys: lastname, firstname, user_id, login, file)
1805     */
1806    public function getMultiFeedbackFiles($a_user_id = 0)
1807    {
1808        $ilUser = $this->user;
1809
1810        if ($a_user_id == 0) {
1811            $a_user_id = $ilUser->getId();
1812        }
1813
1814        $mf_files = array();
1815
1816        // get members
1817        $exc = new ilObjExercise($this->getExerciseId(), false);
1818        $exmem = new ilExerciseMembers($exc);
1819        $mems = $exmem->getMembers();
1820
1821        // read mf directory
1822        $storage = new ilFSStorageExercise($this->getExerciseId(), $this->getId());
1823        $mfu = $storage->getMultiFeedbackUploadPath($ilUser->getId());
1824
1825        // get subdir that starts with multi_feedback
1826        $subdirs = ilUtil::getDir($mfu);
1827        $subdir = "notfound";
1828        foreach ($subdirs as $s => $j) {
1829            if ($j["type"] == "dir" && substr($s, 0, 14) == "multi_feedback") {
1830                $subdir = $s;
1831            }
1832        }
1833
1834        $items = ilUtil::getDir($mfu . "/" . $subdir);
1835        foreach ($items as $k => $i) {
1836            // check directory
1837            if ($i["type"] == "dir" && !in_array($k, array(".", ".."))) {
1838                // check if valid member id is given
1839                $parts = explode("_", $i["entry"]);
1840                $user_id = (int) $parts[count($parts) - 1];
1841                if (in_array($user_id, $mems)) {
1842                    // read dir of user
1843                    $name = ilObjUser::_lookupName($user_id);
1844                    $files = ilUtil::getDir($mfu . "/" . $subdir . "/" . $k);
1845                    foreach ($files as $k2 => $f) {
1846                        // append files to array
1847                        if ($f["type"] == "file" && substr($k2, 0, 1) != ".") {
1848                            $mf_files[] = array(
1849                                "lastname" => $name["lastname"],
1850                                "firstname" => $name["firstname"],
1851                                "login" => $name["login"],
1852                                "user_id" => $name["user_id"],
1853                                "full_path" => $mfu . "/" . $subdir . "/" . $k . "/" . $k2,
1854                                "file" => $k2);
1855                        }
1856                    }
1857                }
1858            }
1859        }
1860        return $mf_files;
1861    }
1862
1863    /**
1864     * Clear multi feedback directory
1865     *
1866     * @param array
1867     * @return
1868     */
1869    public function clearMultiFeedbackDirectory()
1870    {
1871        $lng = $this->lng;
1872        $ilUser = $this->user;
1873
1874        $storage = new ilFSStorageExercise($this->getExerciseId(), $this->getId());
1875        $mfu = $storage->getMultiFeedbackUploadPath($ilUser->getId());
1876        ilUtil::delDir($mfu);
1877    }
1878
1879    /**
1880     * Save multi feedback files
1881     *
1882     * @param
1883     * @return
1884     */
1885    public function saveMultiFeedbackFiles($a_files, ilObjExercise $a_exc)
1886    {
1887        if ($this->getExerciseId() != $a_exc->getId()) {
1888            return;
1889        }
1890
1891        $fstorage = new ilFSStorageExercise($this->getExerciseId(), $this->getId());
1892        $fstorage->create();
1893
1894        $team_map = array();
1895
1896        $mf_files = $this->getMultiFeedbackFiles();
1897        foreach ($mf_files as $f) {
1898            $user_id = $f["user_id"];
1899            $file_path = $f["full_path"];
1900            $file_name = $f["file"];
1901
1902            // if checked in confirmation gui
1903            if ($a_files[$user_id][md5($file_name)] != "") {
1904                $submission = new ilExSubmission($this, $user_id);
1905                $feedback_id = $submission->getFeedbackId();
1906                $noti_rec_ids = $submission->getUserIds();
1907
1908                if ($feedback_id) {
1909                    $fb_path = $fstorage->getFeedbackPath($feedback_id);
1910                    $target = $fb_path . "/" . $file_name;
1911                    if (is_file($target)) {
1912                        unlink($target);
1913                    }
1914                    // rename file
1915                    rename($file_path, $target);
1916
1917                    if ($noti_rec_ids) {
1918                        foreach ($noti_rec_ids as $user_id) {
1919                            $member_status = $this->getMemberStatus($user_id);
1920                            $member_status->setFeedback(true);
1921                            $member_status->update();
1922                        }
1923
1924                        $a_exc->sendFeedbackFileNotification(
1925                            $file_name,
1926                            $noti_rec_ids,
1927                            (int) $this->getId()
1928                        );
1929                    }
1930                }
1931            }
1932        }
1933
1934        $this->clearMultiFeedbackDirectory();
1935    }
1936
1937
1938
1939
1940    /**
1941     * Handle calendar entries for deadline(s)
1942     *
1943     * @param string $a_event
1944     */
1945    protected function handleCalendarEntries($a_event)
1946    {
1947        $ilAppEventHandler = $this->app_event_handler;
1948
1949        $dl_id = $this->getId() . "0";
1950        $fbdl_id = $this->getId() . "1";
1951
1952        $context_ids = array($dl_id, $fbdl_id);
1953        $apps = array();
1954
1955        if ($a_event != "delete") {
1956            // deadline or relative deadline given
1957            if ($this->getDeadline() || $this->getDeadlineMode() == ilExAssignment::DEADLINE_RELATIVE) {
1958                $app = new ilCalendarAppointmentTemplate($dl_id);
1959                $app->setTranslationType(IL_CAL_TRANSLATION_SYSTEM);
1960                $app->setSubtitle("cal_exc_deadline");
1961                $app->setTitle($this->getTitle());
1962                $app->setFullday(false);
1963                // note: in the case of a relative deadline this will be set to 0 / 1970...)
1964                // see ilCalendarScheduleFilterExercise for appointment modification
1965                $app->setStart(new ilDateTime($this->getDeadline(), IL_CAL_UNIX));
1966
1967                $apps[] = $app;
1968            }
1969
1970            if ($this->getPeerReview() &&
1971                $this->getPeerReviewDeadline()) {
1972                $app = new ilCalendarAppointmentTemplate($fbdl_id);
1973                $app->setTranslationType(IL_CAL_TRANSLATION_SYSTEM);
1974                $app->setSubtitle("cal_exc_peer_review_deadline");
1975                $app->setTitle($this->getTitle());
1976                $app->setFullday(false);
1977                $app->setStart(new ilDateTime($this->getPeerReviewDeadline(), IL_CAL_UNIX));
1978
1979                $apps[] = $app;
1980            }
1981        }
1982
1983        $exc = new ilObjExercise($this->getExerciseId(), false);
1984
1985        $ilAppEventHandler->raise(
1986            'Modules/Exercise',
1987            $a_event . 'Assignment',
1988            array(
1989            'object' => $exc,
1990            'obj_id' => $exc->getId(),
1991            'context_ids' => $context_ids,
1992            'appointments' => $apps)
1993        );
1994    }
1995
1996
1997    public static function getPendingFeedbackNotifications()
1998    {
1999        global $DIC;
2000
2001        $ilDB = $DIC->database();
2002
2003        $res = array();
2004
2005        $set = $ilDB->query("SELECT id,fb_file,time_stamp,deadline2,fb_date FROM exc_assignment" .
2006            " WHERE fb_cron = " . $ilDB->quote(1, "integer") .
2007            " AND (fb_date = " . $ilDB->quote(self::FEEDBACK_DATE_DEADLINE, "integer") .
2008                " AND time_stamp IS NOT NULL" .
2009                " AND time_stamp > " . $ilDB->quote(0, "integer") .
2010                " AND time_stamp < " . $ilDB->quote(time(), "integer") .
2011                " AND fb_cron_done = " . $ilDB->quote(0, "integer") .
2012            ") OR (fb_date = " . $ilDB->quote(self::FEEDBACK_DATE_CUSTOM, "integer") .
2013                " AND fb_date_custom IS NOT NULL" .
2014                " AND fb_date_custom > " . $ilDB->quote(0, "integer") .
2015                " AND fb_date_custom < " . $ilDB->quote(time(), "integer") .
2016                " AND fb_cron_done = " . $ilDB->quote(0, "integer") . ")");
2017
2018
2019
2020        while ($row = $ilDB->fetchAssoc($set)) {
2021            if ($row['fb_date'] == self::FEEDBACK_DATE_DEADLINE) {
2022                $max = max($row['time_stamp'], $row['deadline2']);
2023                if (trim($row["fb_file"]) && $max <= time()) {
2024                    $res[] = $row["id"];
2025                }
2026            } elseif ($row['fb_date'] == self::FEEDBACK_DATE_CUSTOM) {
2027                if (trim($row["fb_file"]) && $row['fb_date_custom'] <= time()) {
2028                    $res[] = $row["id"];
2029                }
2030            }
2031        }
2032
2033        return $res;
2034    }
2035
2036    public static function sendFeedbackNotifications($a_ass_id, $a_user_id = null)
2037    {
2038        global $DIC;
2039
2040        $ilDB = $DIC->database();
2041
2042        $ass = new self($a_ass_id);
2043
2044        // valid assignment?
2045        if (!$ass->hasFeedbackCron() || !$ass->getFeedbackFile()) {
2046            return false;
2047        }
2048
2049        if (!$a_user_id) {
2050            // already done?
2051            $set = $ilDB->query("SELECT fb_cron_done" .
2052                " FROM exc_assignment" .
2053                " WHERE id = " . $ilDB->quote($a_ass_id, "integer"));
2054            $row = $ilDB->fetchAssoc($set);
2055            if ($row["fb_cron_done"]) {
2056                return false;
2057            }
2058        }
2059
2060        $ntf = new ilSystemNotification();
2061        $ntf->setLangModules(array("exc"));
2062        $ntf->setObjId($ass->getExerciseId());
2063        $ntf->setSubjectLangId("exc_feedback_notification_subject");
2064        $ntf->setIntroductionLangId("exc_feedback_notification_body");
2065        $ntf->addAdditionalInfo("exc_assignment", $ass->getTitle());
2066        $ntf->setGotoLangId("exc_feedback_notification_link");
2067        $ntf->setReasonLangId("exc_feedback_notification_reason");
2068
2069        if (!$a_user_id) {
2070            $ntf->sendMail(ilExerciseMembers::_getMembers($ass->getExerciseId()));
2071
2072            $ilDB->manipulate("UPDATE exc_assignment" .
2073                " SET fb_cron_done = " . $ilDB->quote(1, "integer") .
2074                " WHERE id = " . $ilDB->quote($a_ass_id, "integer"));
2075        } else {
2076            $ntf->sendMail(array($a_user_id));
2077        }
2078
2079        return true;
2080    }
2081
2082
2083    // status
2084
2085    public function afterDeadline()									// like: after effective deadline (for single user), no deadline: true
2086    {
2087        $ilUser = $this->user;
2088
2089        // :TODO: always current user?
2090        $idl = $this->getPersonalDeadline($ilUser->getId());		// official deadline
2091
2092        // no deadline === true
2093        $deadline = max($this->deadline, $this->deadline2, $idl);	// includes grace period
2094        return ($deadline - time() <= 0);
2095    }
2096
2097    public function afterDeadlineStrict($a_include_personal = true)
2098    {
2099        // :TODO: this means that peer feedback, global feedback is available
2100        // after LAST personal deadline
2101        // team management is currently ignoring personal deadlines
2102        $idl = (bool) $a_include_personal
2103            ? $this->getLastPersonalDeadline()
2104            : null;
2105
2106        // no deadline === false
2107        $deadline = max($this->deadline, $this->deadline2, $idl);
2108
2109        // #18271 - afterDeadline() does not handle last personal deadline
2110        if ($idl && $deadline == $idl) {								// after effective deadline of all users
2111            return ($deadline - time() <= 0);
2112        }
2113
2114        return ($deadline > 0 && 									// like: after effective deadline (for single user), except: no deadline false
2115            $this->afterDeadline());
2116    }
2117
2118    /**
2119     * @return bool return if sample solution is available using a custom date.
2120     */
2121    public function afterCustomDate()
2122    {
2123        $date_custom = $this->getFeedbackDateCustom();
2124        //if the solution will be displayed only after reach all the deadlines.
2125        //$final_deadline = $this->afterDeadlineStrict();
2126        //$dl = max($final_deadline, time());
2127        //return ($date_custom - $dl <= 0);
2128        return ($date_custom - time() <= 0);
2129    }
2130
2131    public function beforeDeadline()								// like: before effective deadline (for all users), no deadline: true
2132    {
2133        // no deadline === true
2134        return !$this->afterDeadlineStrict();
2135    }
2136
2137    public function notStartedYet()
2138    {
2139        return (time() - $this->start_time <= 0);
2140    }
2141
2142
2143    //
2144    // FEEDBACK FILES
2145    //
2146
2147    public function getGlobalFeedbackFileStoragePath()
2148    {
2149        $storage = new ilFSStorageExercise($this->getExerciseId(), $this->getId());
2150        return $storage->getGlobalFeedbackPath();
2151    }
2152
2153    public function deleteGlobalFeedbackFile()
2154    {
2155        ilUtil::delDir($this->getGlobalFeedbackFileStoragePath());
2156    }
2157
2158    public function handleGlobalFeedbackFileUpload(array $a_file)
2159    {
2160        $path = $this->getGlobalFeedbackFileStoragePath();
2161        ilUtil::delDir($path, true);
2162        if (ilUtil::moveUploadedFile($a_file["tmp_name"], $a_file["name"], $path . "/" . $a_file["name"])) {
2163            $this->setFeedbackFile($a_file["name"]);
2164            return true;
2165        }
2166        return false;
2167    }
2168
2169    public function getGlobalFeedbackFilePath()
2170    {
2171        $file = $this->getFeedbackFile();
2172        if ($file) {
2173            $path = $this->getGlobalFeedbackFileStoragePath();
2174            return $path . "/" . $file;
2175        }
2176    }
2177
2178    /**
2179     * @param int|null $a_user_id
2180     * @return \ilExAssignmentMemberStatus
2181     */
2182    public function getMemberStatus($a_user_id = null)
2183    {
2184        $ilUser = $this->user;
2185
2186        if (!$a_user_id) {
2187            $a_user_id = $ilUser->getId();
2188        }
2189        if (!array_key_exists($a_user_id, $this->member_status)) {
2190            $this->member_status[$a_user_id] = new ilExAssignmentMemberStatus($this->getId(), $a_user_id);
2191        }
2192        return $this->member_status[$a_user_id];
2193    }
2194
2195    public function recalculateLateSubmissions()
2196    {
2197        $ilDB = $this->db;
2198
2199        // see JF, 2015-05-11
2200
2201        $ext_deadline = $this->getExtendedDeadline();
2202
2203        foreach (ilExSubmission::getAllAssignmentFiles($this->exc_id, $this->getId()) as $file) {
2204            $id = $file["returned_id"];
2205            $uploaded = new ilDateTime($file["ts"], IL_CAL_DATETIME);
2206            $uploaded = $uploaded->get(IL_CAL_UNIX);
2207
2208            $deadline = $this->getPersonalDeadline($file["user_id"]);
2209            $last_deadline = max($deadline, $this->getExtendedDeadline());
2210
2211            $late = null;
2212
2213            // upload is not late anymore
2214            if ($file["late"] &&
2215                (!$last_deadline ||
2216                !$ext_deadline ||
2217                $uploaded < $deadline)) {
2218                $late = false;
2219            }
2220            // upload is now late
2221            elseif (!$file["late"] &&
2222                $ext_deadline &&
2223                $deadline &&
2224                $uploaded > $deadline) {
2225                $late = true;
2226            } elseif ($last_deadline && $uploaded > $last_deadline) {
2227                // do nothing, we do not remove submissions?
2228            }
2229
2230            if ($late !== null) {
2231                $ilDB->manipulate("UPDATE exc_returned" .
2232                    " SET late = " . $ilDB->quote($late, "integer") .
2233                    " WHERE returned_id = " . $ilDB->quote($id, "integer"));
2234            }
2235        }
2236    }
2237
2238
2239    //
2240    // individual deadlines
2241    //
2242
2243    public function setIndividualDeadline($id, ilDateTime $date)
2244    {
2245        $ilDB = $this->db;
2246
2247        $is_team = false;
2248        if (!is_numeric($id)) {
2249            $id = substr($id, 1);
2250            $is_team = true;
2251        }
2252
2253        $idl = ilExcIndividualDeadline::getInstance($this->getId(), $id, $is_team);
2254        $idl->setIndividualDeadline($date->get(IL_CAL_UNIX));
2255        $idl->save();
2256
2257        /*
2258        $ilDB->replace("exc_idl",
2259            array(
2260                "ass_id" => array("integer", $this->getId()),
2261                "member_id" => array("integer", $id),
2262                "is_team" => array("integer", $is_team)
2263            ),
2264            array(
2265                "tstamp" => array("integer", $date->get(IL_CAL_UNIX))
2266            )
2267        );*/
2268    }
2269
2270    public function getIndividualDeadlines()
2271    {
2272        $ilDB = $this->db;
2273
2274        $res = array();
2275
2276        $set = $ilDB->query("SELECT * FROM exc_idl" .
2277            " WHERE ass_id = " . $ilDB->quote($this->getId(), "integer"));
2278        while ($row = $ilDB->fetchAssoc($set)) {
2279            if ($row["is_team"]) {
2280                $row["member_id"] = "t" . $row["member_id"];
2281            }
2282
2283            $res[$row["member_id"]] = $row["tstamp"];
2284        }
2285
2286        return $res;
2287    }
2288
2289    public function hasActiveIDl()
2290    {
2291        return (bool) $this->getDeadline();
2292    }
2293
2294    public function hasReadOnlyIDl()
2295    {
2296        if (!$this->ass_type->usesTeams() &&
2297            $this->getPeerReview()) {
2298            // all deadlines are read-only if we have peer feedback
2299            $peer_review = new ilExPeerReview($this);
2300            if ($peer_review->hasPeerReviewGroups()) {
2301                return true;
2302            }
2303        }
2304
2305        return false;
2306    }
2307
2308    /**
2309     * Save ordering of instruction files for an assignment
2310     * @param int $a_ass_id assignment id
2311     * @param int $a_order order
2312     */
2313    public static function saveInstructionFilesOrderOfAssignment($a_ass_id, $a_order)
2314    {
2315        global $DIC;
2316
2317        $db = $DIC->database();
2318
2319        asort($a_order, SORT_NUMERIC);
2320
2321        $nr = 10;
2322        foreach ($a_order as $k => $v) {
2323            // the check for exc_id is for security reasons. ass ids are unique.
2324            $db->manipulate(
2325                $t = "UPDATE exc_ass_file_order SET " .
2326                " order_nr = " . $db->quote($nr, "integer") .
2327                " WHERE id = " . $db->quote((int) $k, "integer") .
2328                " AND assignment_id = " . $db->quote((int) $a_ass_id, "integer")
2329            );
2330            $nr += 10;
2331        }
2332    }
2333
2334    public static function insertFileOrderNr(int $a_ass_id, string $a_filename, int $a_order_nr)
2335    {
2336        global $DIC;
2337        $db = $DIC->database();
2338        $id = $db->nextId("exc_ass_file_order");
2339        $db->insert("exc_ass_file_order", [
2340                "id" => ["integer", $id],
2341                "order_nr" => ["integer", $a_order_nr],
2342                "assignment_id" => ["integer", $a_ass_id],
2343                "filename" => ["text", $a_filename]
2344            ]
2345        );
2346    }
2347
2348    /**
2349     * Store the file order in the database
2350     * @param string $a_filename  previously sanitized.
2351     * @param int $a_ass_id assignment id.
2352     */
2353    public static function instructionFileInsertOrder($a_filename, $a_ass_id, $a_order_nr = 0)
2354    {
2355        global $DIC;
2356
2357        $db = $DIC->database();
2358
2359        $order = 0;
2360        $order_val = 0;
2361
2362        if ($a_ass_id) {
2363            //first of all check the suffix and change if necessary
2364            $filename = ilUtil::getSafeFilename($a_filename);
2365
2366            if (!self::instructionFileExistsInDb($filename, $a_ass_id)) {
2367                if ($a_order_nr == 0) {
2368                    $order_val = self::instructionFileOrderGetMax($a_ass_id);
2369                    $order = $order_val + 10;
2370                } else {
2371                    $order = $a_order_nr;
2372                }
2373
2374                $id = $db->nextID('exc_ass_file_order');
2375                $db->manipulate("INSERT INTO exc_ass_file_order " .
2376                    "(id, assignment_id, filename, order_nr) VALUES (" .
2377                    $db->quote($id, "integer") . "," .
2378                    $db->quote($a_ass_id, "integer") . "," .
2379                    $db->quote($filename, "text") . "," .
2380                    $db->quote($order, "integer") .
2381                    ")");
2382            }
2383        }
2384    }
2385
2386    public static function instructionFileDeleteOrder($a_ass_id, $a_file)
2387    {
2388        global $DIC;
2389
2390        $db = $DIC->database();
2391
2392        //now its done by filename. We need to figure how to get the order id in the confirmdelete method
2393        foreach ($a_file as $k => $v) {
2394            $db->manipulate(
2395                "DELETE FROM exc_ass_file_order " .
2396                //"WHERE id = " . $ilDB->quote((int)$k, "integer") .
2397                "WHERE filename = " . $db->quote($v, "string") .
2398                " AND assignment_id = " . $db->quote($a_ass_id, 'integer')
2399            );
2400        }
2401    }
2402
2403    /**
2404     * @param string $a_old_name
2405     * @param string $a_new_name
2406     * @param int $a_ass_id assignment id
2407     */
2408    public static function renameInstructionFile($a_old_name, $a_new_name, $a_ass_id)
2409    {
2410        global $DIC;
2411
2412        $db = $DIC->database();
2413
2414        if ($a_ass_id) {
2415            $db->manipulate(
2416                "DELETE FROM exc_ass_file_order" .
2417                " WHERE assignment_id = " . $db->quote((int) $a_ass_id, 'integer') .
2418                " AND filename = " . $db->quote($a_new_name, 'string')
2419            );
2420
2421            $db->manipulate(
2422                "UPDATE exc_ass_file_order SET" .
2423                " filename = " . $db->quote($a_new_name, 'string') .
2424                " WHERE assignment_id = " . $db->quote((int) $a_ass_id, 'integer') .
2425                " AND filename = " . $db->quote($a_old_name, 'string')
2426            );
2427        }
2428    }
2429
2430    /**
2431     * @param $a_filename
2432     * @param $a_ass_id assignment id
2433     * @return int if the file exists or not in the DB
2434     */
2435    public static function instructionFileExistsInDb($a_filename, $a_ass_id)
2436    {
2437        global $DIC;
2438
2439        $db = $DIC->database();
2440
2441        if ($a_ass_id) {
2442            $result = $db->query(
2443                "SELECT id FROM exc_ass_file_order" .
2444                " WHERE assignment_id = " . $db->quote((int) $a_ass_id, 'integer') .
2445                " AND filename = " . $db->quote($a_filename, 'string')
2446            );
2447
2448            return $db->numRows($result);
2449        }
2450    }
2451
2452    public function fixInstructionFileOrdering()
2453    {
2454        global $DIC;
2455
2456        $db = $DIC->database();
2457
2458        $files = array_map(function ($v) {
2459            return $v["name"];
2460        }, $this->getFiles());
2461
2462        $set = $db->query("SELECT * FROM exc_ass_file_order " .
2463            " WHERE assignment_id = " . $db->quote($this->getId(), "integer") .
2464            " ORDER BY order_nr");
2465        $order_nr = 10;
2466        $numbered_files = array();
2467        while ($rec = $db->fetchAssoc($set)) {
2468            // file exists, set correct order nr
2469            if (in_array($rec["filename"], $files)) {
2470                $db->manipulate(
2471                    "UPDATE exc_ass_file_order SET " .
2472                    " order_nr = " . $db->quote($order_nr, "integer") .
2473                    " WHERE assignment_id = " . $db->quote($this->getId(), "integer") .
2474                    " AND id = " . $db->quote($rec["id"], "integer")
2475                );
2476                $order_nr += 10;
2477                $numbered_files[] = $rec["filename"];
2478            } else {	// file does not exist, delete entry
2479                $db->manipulate(
2480                    "DELETE FROM exc_ass_file_order " .
2481                    " WHERE assignment_id = " . $db->quote($this->getId(), "integer") .
2482                    " AND id = " . $db->quote($rec["id"], "integer")
2483                );
2484            }
2485        }
2486        foreach ($files as $f) {
2487            if (!in_array($f, $numbered_files)) {
2488                self::instructionFileInsertOrder($f, $this->getId());
2489            }
2490        }
2491    }
2492
2493    /**
2494     * @param array $a_entries
2495     * @param integer $a_ass_id assignment id
2496     * @return array data items
2497     */
2498    public function fileAddOrder($a_entries = array())
2499    {
2500        $this->fixInstructionFileOrdering();
2501
2502        $order = $this->getInstructionFilesOrder();
2503        foreach ($a_entries as $k => $e) {
2504            $a_entries[$k]["order_val"] = $order[$e["file"]]["order_nr"];
2505            $a_entries[$k]["order_id"] = $order[$e["file"]]["id"];
2506        }
2507
2508        return $a_entries;
2509    }
2510
2511    /**
2512     * @param int $a_ass_id assignment id
2513     * @return int
2514     */
2515    public static function instructionFileOrderGetMax($a_ass_id)
2516    {
2517        global $DIC;
2518
2519        $db = $DIC->database();
2520
2521        //get max order number
2522        $result = $db->queryF(
2523            "SELECT max(order_nr) as max_order FROM exc_ass_file_order WHERE assignment_id = %s",
2524            array('integer'),
2525            array($db->quote($a_ass_id, 'integer'))
2526        );
2527
2528        while ($row = $db->fetchAssoc($result)) {
2529            $order_val = (int) $row['max_order'];
2530        }
2531        return $order_val;
2532    }
2533
2534
2535    /**
2536     * Set limit minimum characters
2537     *
2538     * @param	int	minim limit
2539     */
2540    public function setMinCharLimit($a_val)
2541    {
2542        $this->min_char_limit = $a_val;
2543    }
2544
2545    /**
2546     * Get limit minimum characters
2547     *
2548     * @return	int minimum limit
2549     */
2550    public function getMinCharLimit()
2551    {
2552        return $this->min_char_limit;
2553    }
2554
2555    /**
2556     * Set limit maximum characters
2557     * @param int max limit
2558     */
2559    public function setMaxCharLimit($a_val)
2560    {
2561        $this->max_char_limit = $a_val;
2562    }
2563
2564    /**
2565     * get limit maximum characters
2566     * return int max limit
2567     */
2568    public function getMaxCharLimit()
2569    {
2570        return $this->max_char_limit;
2571    }
2572
2573    /**
2574     * Get calculated deadlines for user/team members. These arrays will contain no entries, if team or user
2575     * has not started the assignment yet.
2576     *
2577     * @return array[array] contains two arrays one with key "user", second with key "team", each one has
2578     * 						member id as keys and calculated deadline as value
2579     */
2580    public function getCalculatedDeadlines()
2581    {
2582        $calculated_deadlines = array(
2583            "user" => array(),
2584            "team" => array()
2585        );
2586
2587        if ($this->getRelativeDeadline() && $this->getDeadlineMode() == self::DEADLINE_RELATIVE) {
2588            foreach (ilExcIndividualDeadline::getStartingTimestamps($this->getId()) as $ts) {
2589                $type = $ts["is_team"]
2590                    ? "team"
2591                    : "user";
2592
2593                $calculated_deadlines[$type][$ts["member_id"]] = array(
2594                    "calculated_deadline" => $ts["starting_ts"] + ($this->getRelativeDeadline() * 24 * 60 * 60)
2595                );
2596            }
2597        }
2598        return $calculated_deadlines;
2599    }
2600}
2601