1<?php
2/* Copyright (c) 1998-2009 ILIAS open source, Extended GPL, see docs/LICENSE */
3
4/**
5 * Exercise submission
6 * //TODO: This class has to much static methods related to delivered "files". Extract them to classes.
7 *
8 * @author Jörg Lützenkirchen <luetzenkirchen@leifos.com>
9 * @ingroup ModulesExercise
10 */
11class ilExSubmission
12{
13    const TYPE_FILE = "File";
14    const TYPE_OBJECT = "Object";	// Blogs in WSP/Portfolio
15    const TYPE_TEXT = "Text";
16    const TYPE_REPO_OBJECT = "RepoObject";	// Wikis
17
18    /**
19     * @var ilObjUser
20     */
21    protected $user;
22
23    /**
24     * @var ilDB
25     */
26    protected $db;
27
28    /**
29     * @var ilLanguage
30     */
31    protected $lng;
32
33    /**
34     * @var ilCtrl
35     */
36    protected $ctrl;
37
38    protected $assignment; // [ilExAssignment]
39    protected $user_id; // [int]
40    protected $team; // [ilExAssignmentTeam]
41    protected $peer_review; // [ilExPeerReview]
42    protected $is_tutor; // [bool]
43    protected $public_submissions; // [bool]
44
45    /**
46     * @var ilExAssignmentTypeInterface
47     */
48    protected $ass_type;
49
50    /**
51     * @var ilExAssignmentTypes
52     */
53    protected $ass_types;
54
55    public function __construct(ilExAssignment $a_ass, $a_user_id, ilExAssignmentTeam $a_team = null, $a_is_tutor = false, $a_public_submissions = false)
56    {
57        global $DIC;
58
59        $this->user = $DIC->user();
60        $this->db = $DIC->database();
61        $this->lng = $DIC->language();
62        $this->ctrl = $DIC->ctrl();
63        $ilUser = $DIC->user();
64
65        $this->assignment = $a_ass;
66        $this->ass_type = $this->assignment->getAssignmentType();
67        $this->ass_types = ilExAssignmentTypes::getInstance();
68
69        $this->user_id = $a_user_id;
70        $this->is_tutor = (bool) $a_is_tutor;
71        $this->public_submissions = (bool) $a_public_submissions;
72
73        $this->state = ilExcAssMemberState::getInstanceByIds($a_ass->getId(), $a_user_id);
74
75        if ($a_ass->hasTeam()) {
76            if (!$a_team) {
77                $this->team = ilExAssignmentTeam::getInstanceByUserId($this->assignment->getId(), $this->user_id);
78            } else {
79                $this->team = $a_team;
80            }
81        }
82
83        if ($this->assignment->getPeerReview()) {
84            $this->peer_review = new ilExPeerReview($this->assignment);
85        }
86    }
87
88    public function getSubmissionType()
89    {
90        return $this->assignment->getAssignmentType()->getSubmissionType();
91        /*switch($this->assignment->getType())
92        {
93            case ilExAssignment::TYPE_UPLOAD_TEAM:
94            case ilExAssignment::TYPE_UPLOAD:
95                return "File";
96
97            case ilExAssignment::TYPE_BLOG:
98            case ilExAssignment::TYPE_PORTFOLIO:
99                return "Object";
100
101            case ilExAssignment::TYPE_TEXT:
102                return "Text";
103        };*/
104    }
105
106
107    /**
108     * @return \ilExAssignment
109     */
110    public function getAssignment()
111    {
112        return $this->assignment;
113    }
114
115    /**
116     * @return \ilExAssignmentTeam
117     */
118    public function getTeam()
119    {
120        return $this->team;
121    }
122
123    /**
124     * @return \ilExPeerReview
125     */
126    public function getPeerReview()
127    {
128        return $this->peer_review;
129    }
130
131    public function validatePeerReviews()
132    {
133        $res = array();
134        foreach ($this->getUserIds() as $user_id) {
135            $valid = true;
136
137            // no peer review == valid
138            if ($this->peer_review) {
139                $valid = $this->peer_review->isFeedbackValidForPassed($user_id);
140            }
141
142            $res[$user_id] = $valid;
143        }
144        return $res;
145    }
146
147    public function getUserId()
148    {
149        return $this->user_id;
150    }
151
152    public function getUserIds()
153    {
154        if ($this->team &&
155            !$this->hasNoTeamYet()) {
156            return $this->team->getMembers();
157        }
158
159        // if has no team currently there still might be uploads attached
160        return array($this->user_id);
161    }
162
163    public function getFeedbackId()
164    {
165        if ($this->team) {
166            return "t" . $this->team->getId();
167        } else {
168            return $this->getUserId();
169        }
170    }
171
172    public function hasSubmitted()
173    {
174        return (bool) sizeof($this->getFiles(null, true));
175    }
176
177    public function getSelectedObject()
178    {
179        $files = $this->getFiles();
180        if (sizeof($files)) {
181            return array_pop($files);
182        }
183    }
184
185    public function canSubmit()
186    {
187        return ($this->isOwner() &&
188            $this->state->isSubmissionAllowed());
189    }
190
191    public function canView()
192    {
193        $ilUser = $this->user;
194
195        if ($this->canSubmit() ||
196            $this->isTutor() ||
197            $this->isInTeam() ||
198            $this->public_submissions) {
199            return true;
200        }
201
202        // #16115
203        if ($this->peer_review) {
204            // peer review givers may view peer submissions
205            foreach ($this->peer_review->getPeerReviewsByPeerId($this->getUserId()) as $giver) {
206                if ($giver["giver_id"] == $ilUser->getId()) {
207                    return true;
208                }
209            }
210        }
211
212        return false;
213    }
214
215    public function isTutor()
216    {
217        return $this->is_tutor;
218    }
219
220    public function hasNoTeamYet()
221    {
222        if ($this->assignment->hasTeam() &&
223            !$this->team->getId()) {
224            return true;
225        }
226        return false;
227    }
228
229    public function isInTeam($a_user_id = null)
230    {
231        $ilUser = $this->user;
232
233        if (!$a_user_id) {
234            $a_user_id = $ilUser->getId();
235        }
236        return in_array($a_user_id, $this->getUserIds());
237    }
238
239    public function isOwner()
240    {
241        $ilUser = $this->user;
242
243        return ($ilUser->getId() == $this->getUserId());
244    }
245
246    public function hasPeerReviewAccess()
247    {
248        return ($this->peer_review &&
249            $this->peer_review->hasPeerReviewAccess($this->user_id));
250    }
251
252    public function canAddFile()
253    {
254        if (!$this->canSubmit()) {
255            return false;
256        }
257
258        $max = $this->getAssignment()->getMaxFile();
259        if ($max &&
260            $max <= sizeof($this->getFiles())) {
261            return false;
262        }
263
264        return true;
265    }
266
267
268    //
269    // FILES
270    //
271
272    protected function isLate()
273    {
274        $dl = $this->state->getOfficialDeadline();
275        //$dl = $this->assignment->getPersonalDeadline($this->getUserId());
276        return ($dl && $dl < time());
277    }
278
279    protected function initStorage()
280    {
281        return new ilFSStorageExercise($this->assignment->getExerciseId(), $this->assignment->getId());
282    }
283
284    /**
285     * Get storage id
286     *
287     * @return int
288     */
289    protected function getStorageId()
290    {
291        if ($this->ass_type->isSubmissionAssignedToTeam()) {
292            $storage_id = $this->getTeam()->getId();
293        } else {
294            $storage_id = $this->getUserId();
295        }
296        return $storage_id;
297    }
298
299
300    /**
301     * Save submitted file of user
302     */
303    public function uploadFile($a_http_post_files, $unzip = false)
304    {
305        $ilDB = $this->db;
306
307        if (!$this->canAddFile()) {
308            return false;
309        }
310
311        if ($this->ass_type->isSubmissionAssignedToTeam()) {
312            $team_id = $this->getTeam()->getId();
313            $user_id = 0;
314            if ($team_id == 0) {
315                return false;
316            }
317        } else {
318            $team_id = 0;
319            $user_id = $this->getUserId();
320        }
321        $storage_id = $this->getStorageId();
322
323        $deliver_result = $this->initStorage()->uploadFile($a_http_post_files, $storage_id, $unzip);
324
325        if ($deliver_result) {
326            $next_id = $ilDB->nextId("exc_returned");
327            $query = sprintf(
328                "INSERT INTO exc_returned " .
329                             "(returned_id, obj_id, user_id, filename, filetitle, mimetype, ts, ass_id, late, team_id) " .
330                             "VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)",
331                $ilDB->quote($next_id, "integer"),
332                $ilDB->quote($this->assignment->getExerciseId(), "integer"),
333                $ilDB->quote($user_id, "integer"),
334                $ilDB->quote($deliver_result["fullname"], "text"),
335                $ilDB->quote(ilFileUtils::getValidFilename($a_http_post_files["name"]), "text"),
336                $ilDB->quote($deliver_result["mimetype"], "text"),
337                $ilDB->quote(ilUtil::now(), "timestamp"),
338                $ilDB->quote($this->assignment->getId(), "integer"),
339                $ilDB->quote($this->isLate(), "integer"),
340                $ilDB->quote($team_id, "integer")
341            );
342            $ilDB->manipulate($query);
343
344            if ($this->team) {
345                $this->team->writeLog(
346                    ilExAssignmentTeam::TEAM_LOG_ADD_FILE,
347                    $a_http_post_files["name"]
348                );
349            }
350
351            return true;
352        }
353        return false;
354    }
355
356    /**
357    * processes errorhandling etc for uploaded archive
358    * @param string $tmpFile path and filename to uploaded file
359    */
360    public function processUploadedZipFile($fileTmp)
361    {
362        $lng = $this->lng;
363
364        // Create unzip-directory
365        $newDir = ilUtil::ilTempnam();
366        ilUtil::makeDir($newDir);
367
368        $success = true;
369
370        try {
371            ilFileUtils::processZipFile($newDir, $fileTmp, false);
372            ilFileUtils::recursive_dirscan($newDir, $filearray);
373
374            // #18441 - check number of files in zip
375            $max_num = $this->assignment->getMaxFile();
376            if ($max_num) {
377                $current_num = sizeof($this->getFiles());
378                $zip_num = sizeof($filearray["file"]);
379                if ($current_num + $zip_num > $max_num) {
380                    $success = false;
381                    ilUtil::sendFailure($lng->txt("exc_upload_error") . " [Zip1]", true);
382                }
383            }
384
385            if ($success) {
386                foreach ($filearray["file"] as $key => $filename) {
387                    $a_http_post_files["name"] = ilFileUtils::utf8_encode($filename);
388                    $a_http_post_files["type"] = "other";
389                    $a_http_post_files["tmp_name"] = $filearray["path"][$key] . "/" . $filename;
390                    $a_http_post_files["error"] = 0;
391                    $a_http_post_files["size"] = filesize($filearray["path"][$key] . "/" . $filename);
392
393                    if (!$this->uploadFile($a_http_post_files, true)) {
394                        $success = false;
395                        ilUtil::sendFailure($lng->txt("exc_upload_error") . " [Zip2]", true);
396                    }
397                }
398            }
399        } catch (ilFileUtilsException $e) {
400            $success = false;
401            ilUtil::sendFailure($e->getMessage());
402        }
403
404        ilUtil::delDir($newDir);
405        return $success;
406    }
407
408    public static function getAllAssignmentFiles($a_exc_id, $a_ass_id)
409    {
410        global $DIC;
411
412        $ilDB = $DIC->database();
413
414        $storage = new ilFSStorageExercise($a_exc_id, $a_ass_id);
415        $path = $storage->getAbsoluteSubmissionPath();
416
417        $ass_type = ilExAssignmentTypes::getInstance()->getById(ilExAssignment::lookupType($a_ass_id));
418
419        $query = "SELECT * FROM exc_returned WHERE ass_id = " .
420            $ilDB->quote($a_ass_id, "integer");
421
422        $res = $ilDB->query($query);
423        while ($row = $ilDB->fetchAssoc($res)) {
424            if ($ass_type->isSubmissionAssignedToTeam()) {
425                $storage_id = $row["team_id"];
426            } else {
427                $storage_id = $row["user_id"];
428            }
429
430            $row["timestamp"] = $row["ts"];
431            $row["filename"] = $path . "/" . $storage_id . "/" . basename($row["filename"]);
432            $delivered[] = $row;
433        }
434
435        return $delivered ? $delivered : array();
436    }
437
438    public static function getAssignmentFilesByUsers(int $a_exc_id, int $a_ass_id, array $a_users) : array
439    {
440        global $DIC;
441
442        $ilDB = $DIC->database();
443
444        $storage = new ilFSStorageExercise($a_exc_id, $a_ass_id);
445        $path = $storage->getAbsoluteSubmissionPath();
446
447        $ass_type = ilExAssignmentTypes::getInstance()->getById(ilExAssignment::lookupType($a_ass_id));
448
449        $query = "SELECT * FROM exc_returned WHERE ass_id = " .
450            $ilDB->quote($a_ass_id, "integer") .
451            " AND user_id IN (" . implode(',', $a_users) . ")";
452
453        $res = $ilDB->query($query);
454        while ($row = $ilDB->fetchAssoc($res)) {
455            if ($ass_type->isSubmissionAssignedToTeam()) {
456                $storage_id = $row["team_id"];
457            } else {
458                $storage_id = $row["user_id"];
459            }
460
461            $row["timestamp"] = $row["ts"];
462            $row["filename"] = $path . "/" . $storage_id . "/" . basename($row["filename"]);
463            $delivered[] = $row;
464        }
465
466        return $delivered ? $delivered : array();
467    }
468
469    /**
470     * Get submission items (not only files)
471     * @todo this also returns non-file entries, rename this, see dev.txt.php
472     * @param array|null $a_file_ids
473     * @param bool $a_only_valid
474     * @param null $a_min_timestamp
475     * @return array
476     */
477    public function getFiles(array $a_file_ids = null, $a_only_valid = false, $a_min_timestamp = null)
478    {
479        $ilDB = $this->db;
480
481        $sql = "SELECT * FROM exc_returned" .
482            " WHERE ass_id = " . $ilDB->quote($this->getAssignment()->getId(), "integer");
483
484        $sql .= " AND " . $this->getTableUserWhere(true);
485
486
487        if ($a_file_ids) {
488            $sql .= " AND " . $ilDB->in("returned_id", $a_file_ids, false, "integer");
489        }
490
491        if ($a_min_timestamp) {
492            $sql .= " AND ts > " . $ilDB->quote($a_min_timestamp, "timestamp");
493        }
494
495        $result = $ilDB->query($sql);
496
497        $delivered_files = array();
498        if ($ilDB->numRows($result)) {
499            $path = $this->initStorage()->getAbsoluteSubmissionPath();
500
501            while ($row = $ilDB->fetchAssoc($result)) {
502                // blog/portfolio/text submissions
503                if ($a_only_valid &&
504                    !$row["filename"] &&
505                    !(trim($row["atext"]))) {
506                    continue;
507                }
508
509                $row["owner_id"] = $row["user_id"];
510                $row["timestamp"] = $row["ts"];
511                $row["timestamp14"] = substr($row["ts"], 0, 4) .
512                    substr($row["ts"], 5, 2) . substr($row["ts"], 8, 2) .
513                    substr($row["ts"], 11, 2) . substr($row["ts"], 14, 2) .
514                    substr($row["ts"], 17, 2);
515
516                if ($this->getAssignment()->getAssignmentType()->isSubmissionAssignedToTeam()) {
517                    $storage_id = $row["team_id"];
518                } else {
519                    $storage_id = $row["user_id"];
520                }
521
522
523                $row["filename"] = $path .
524                    "/" . $storage_id . "/" . basename($row["filename"]);
525
526                // see 22301, 22719
527                if (is_file($row["filename"]) || (!$this->assignment->getAssignmentType()->usesFileUpload())) {
528                    array_push($delivered_files, $row);
529                }
530            }
531        }
532
533        return $delivered_files;
534    }
535
536    /**
537     * Check how much files have been uploaded by the learner
538     * after the last download of the tutor.
539     * @param tutor integer
540     * @return array
541     */
542    public function lookupNewFiles($a_tutor = null)
543    {
544        $ilDB = $this->db;
545        $ilUser = $this->user;
546
547        $tutor = ($a_tutor)
548            ? $a_tutor
549            : $ilUser->getId();
550
551        $where = " AND " . $this->getTableUserWhere(true);
552
553        $q = "SELECT exc_returned.returned_id AS id " .
554            "FROM exc_usr_tutor, exc_returned " .
555            "WHERE exc_returned.ass_id = exc_usr_tutor.ass_id " .
556            " AND exc_returned.user_id = exc_usr_tutor.usr_id " .
557            " AND exc_returned.ass_id = " . $ilDB->quote($this->getAssignment()->getId(), "integer") .
558            $where .
559            " AND exc_usr_tutor.tutor_id = " . $ilDB->quote($tutor, "integer") .
560            " AND exc_usr_tutor.download_time < exc_returned.ts ";
561
562        $new_up_set = $ilDB->query($q);
563
564        $new_up = array();
565        while ($new_up_rec = $ilDB->fetchAssoc($new_up_set)) {
566            $new_up[] = $new_up_rec["id"];
567        }
568
569        return $new_up;
570    }
571
572    /**
573     * Get exercise from submission id (used in ilObjMediaObject)
574     *
575     * @param int $a_returned_id
576     * @return int
577     */
578    public static function lookupExerciseIdForReturnedId($a_returned_id)
579    {
580        global $DIC;
581
582        $ilDB = $DIC->database();
583
584        $set = $ilDB->query("SELECT obj_id" .
585            " FROM exc_returned" .
586            " WHERE returned_id = " . $ilDB->quote($a_returned_id, "integer"));
587        $row = $ilDB->fetchAssoc($set);
588        return (int) $row["obj_id"];
589    }
590
591    /**
592     * Check if given file was assigned
593     *
594     * Used in Blog/Portfolio
595     *
596     * @param int $a_user_id
597     * @param string $a_filetitle
598     */
599    public static function findUserFiles($a_user_id, $a_filetitle)
600    {
601        global $DIC;
602
603        $ilDB = $DIC->database();
604
605        $set = $ilDB->query("SELECT obj_id, ass_id" .
606            " FROM exc_returned" .
607            " WHERE user_id = " . $ilDB->quote($a_user_id, "integer") .
608            " AND filetitle = " . $ilDB->quote($a_filetitle, "text"));
609        $res = array();
610        while ($row = $ilDB->fetchAssoc($set)) {
611            $res[$row["ass_id"]] = $row;
612        }
613        return $res;
614    }
615
616    public function deleteAllFiles()
617    {
618        $files = array();
619        foreach ($this->getFiles() as $item) {
620            $files[] = $item["returned_id"];
621        }
622        if (sizeof($files)) {
623            $this->deleteSelectedFiles($files);
624        }
625    }
626
627    /**
628    * Deletes already delivered files
629    * @param array $file_id_array An array containing database ids of the delivered files
630    * @param numeric $user_id The database id of the user
631    * @access	public
632    */
633    public function deleteSelectedFiles(array $file_id_array)
634    {
635        $ilDB = $this->db;
636
637
638        $where = " AND " . $this->getTableUserWhere(true);
639
640
641        if (!sizeof($file_id_array)) {
642            return;
643        }
644
645        if (count($file_id_array)) {
646            $result = $ilDB->query("SELECT * FROM exc_returned" .
647                " WHERE " . $ilDB->in("returned_id", $file_id_array, false, "integer") .
648                $where);
649
650            if ($ilDB->numRows($result)) {
651                $result_array = array();
652                while ($row = $ilDB->fetchAssoc($result)) {
653                    $row["timestamp"] = $row["ts"];
654                    array_push($result_array, $row);
655                }
656
657                // delete the entries in the database
658                $ilDB->manipulate("DELETE FROM exc_returned" .
659                    " WHERE " . $ilDB->in("returned_id", $file_id_array, false, "integer") .
660                    $where);
661
662                // delete the files
663                $path = $this->initStorage()->getAbsoluteSubmissionPath();
664                foreach ($result_array as $key => $value) {
665                    if ($value["filename"]) {
666                        if ($this->team) {
667                            $this->team->writeLog(
668                                ilExAssignmentTeam::TEAM_LOG_REMOVE_FILE,
669                                $value["filetitle"]
670                            );
671                        }
672
673                        if ($this->getAssignment()->getAssignmentType()->isSubmissionAssignedToTeam()) {
674                            $storage_id = $value["team_id"];
675                        } else {
676                            $storage_id = $value["user_id"];
677                        }
678
679                        $filename = $path . "/" . $storage_id . "/" . basename($value["filename"]);
680                        if (file_exists($filename)) {
681                            unlink($filename);
682                        }
683                    }
684                }
685            }
686        }
687    }
688
689    /**
690     * Delete all delivered files of user
691     *
692     * @param int $a_exc_id excercise id
693     * @param int $a_user_id user id
694     */
695    public static function deleteUser($a_exc_id, $a_user_id)
696    {
697        foreach (ilExAssignment::getInstancesByExercise($a_exc_id) as $ass) {
698            $submission = new self($ass, $a_user_id);
699            $submission->deleteAllFiles();
700
701            // remove from any team
702            $team = $submission->getTeam();
703            if ($team) {
704                $team->removeTeamMember($a_user_id);
705            }
706
707            // #14900
708            $member_status = $ass->getMemberStatus($a_user_id);
709            $member_status->setStatus("notgraded");
710            $member_status->update();
711        }
712    }
713
714    protected function getLastDownloadTime(array $a_user_ids)
715    {
716        $ilDB = $this->db;
717        $ilUser = $this->user;
718
719        $q = "SELECT download_time FROM exc_usr_tutor WHERE " .
720            " ass_id = " . $ilDB->quote($this->getAssignment()->getId(), "integer") . " AND " .
721            $ilDB->in("usr_id", $a_user_ids, "", "integer") . " AND " .
722            " tutor_id = " . $ilDB->quote($ilUser->getId(), "integer");
723        $lu_set = $ilDB->query($q);
724        $lu_rec = $ilDB->fetchAssoc($lu_set);
725        return $lu_rec["download_time"];
726    }
727
728    public function downloadFiles(array $a_file_ids = null, $a_only_new = false, $a_peer_review_mask_filename = false)
729    {
730        $ilUser = $this->user;
731        $lng = $this->lng;
732
733        $user_ids = $this->getUserIds();
734        $is_team = $this->assignment->hasTeam();
735
736        // get last download time
737        $download_time = null;
738        if ($a_only_new) {
739            $download_time = $this->getLastDownloadTime($user_ids);
740        }
741
742        if ($this->is_tutor) {
743            $this->updateTutorDownloadTime();
744        }
745
746        if ($a_peer_review_mask_filename) {
747            // process peer review sequence id
748            $peer_id = null;
749            foreach ($this->peer_review->getPeerReviewsByGiver($ilUser->getId()) as $idx => $item) {
750                if ($item["peer_id"] == $this->getUserId()) {
751                    $peer_id = $idx + 1;
752                    break;
753                }
754            }
755
756            // this will remove personal info from zip-filename
757            $is_team = true;
758        }
759
760        $files = $this->getFiles($a_file_ids, false, $download_time);
761        if ($files) {
762            if (sizeof($files) == 1) {
763                $file = array_pop($files);
764
765                switch ($this->assignment->getType()) {
766                    case ilExAssignment::TYPE_BLOG:
767                    case ilExAssignment::TYPE_PORTFOLIO:
768                        $file["filetitle"] = ilObjUser::_lookupName($file["user_id"]);
769                        $file["filetitle"] = ilObject::_lookupTitle($this->assignment->getExerciseId()) . " - " .
770                            $this->assignment->getTitle() . " - " .
771                            $file["filetitle"]["firstname"] . " " .
772                            $file["filetitle"]["lastname"] . " (" .
773                            $file["filetitle"]["login"] . ").zip";
774                        break;
775
776                    // @todo: generalize
777                    case ilExAssignment::TYPE_WIKI_TEAM:
778                        $file["filetitle"] = ilObject::_lookupTitle($this->assignment->getExerciseId()) . " - " .
779                            $this->assignment->getTitle() . " (Team " . $this->getTeam()->getId() . ").zip";
780                        break;
781
782                    default:
783                        break;
784                }
785
786                if ($a_peer_review_mask_filename) {
787                    $suffix = array_pop(explode(".", $file["filetitle"]));
788                    $file["filetitle"] = $this->assignment->getTitle() . "_peer" . $peer_id . "." . $suffix;
789                } elseif ($file["late"]) {
790                    $file["filetitle"] = $lng->txt("exc_late_submission") . " - " .
791                        $file["filetitle"];
792                }
793
794                $this->downloadSingleFile($file["user_id"], $file["filename"], $file["filetitle"], $file["team_id"]);
795            } else {
796                $array_files = array();
797                foreach ($files as $seq => $file) {
798                    if ($this->assignment->getAssignmentType()->isSubmissionAssignedToTeam()) {
799                        $storage_id = $file["team_id"];
800                    } else {
801                        $storage_id = $file["user_id"];
802                    }
803
804                    $src = basename($file["filename"]);
805                    if ($a_peer_review_mask_filename) {
806                        $suffix = array_pop(explode(".", $src));
807                        $tgt = $this->assignment->getTitle() . "_peer" . $peer_id .
808                            "_" . (++$seq) . "." . $suffix;
809
810                        $array_files[$storage_id][] = array(
811                            "src" => $src,
812                            "tgt" => $tgt
813                        );
814                    } else {
815                        $array_files[$storage_id][] = array(
816                            "src" => $src,
817                            "late" => $file["late"]
818                        );
819                    }
820                }
821
822                $this->downloadMultipleFiles(
823                    $array_files,
824                    ($is_team ? null : $this->getUserId()),
825                    $is_team
826                );
827            }
828        } else {
829            return false;
830        }
831
832        return true;
833    }
834
835    // Update the timestamp of the last download of current user (=tutor)
836    public function updateTutorDownloadTime()
837    {
838        $ilUser = $this->user;
839        $ilDB = $this->db;
840
841        $exc_id = $this->assignment->getExerciseId();
842        $ass_id = $this->assignment->getId();
843
844        foreach ($this->getUserIds() as $user_id) {
845            $ilDB->manipulateF(
846                "DELETE FROM exc_usr_tutor " .
847                "WHERE ass_id = %s AND usr_id = %s AND tutor_id = %s",
848                array("integer", "integer", "integer"),
849                array($ass_id, $user_id, $ilUser->getId())
850            );
851
852            $ilDB->manipulateF(
853                "INSERT INTO exc_usr_tutor (ass_id, obj_id, usr_id, tutor_id, download_time) VALUES " .
854                "(%s, %s, %s, %s, %s)",
855                array("integer", "integer", "integer", "integer", "timestamp"),
856                array($ass_id, $exc_id, $user_id, $ilUser->getId(), ilUtil::now())
857            );
858        }
859    }
860
861    protected function downloadSingleFile($a_user_id, $filename, $filetitle, $a_team_id)
862    {
863        if ($this->ass_type->isSubmissionAssignedToTeam()) {
864            $storage_id = $a_team_id;
865        } else {
866            $storage_id = $a_user_id;
867        }
868
869        $filename = $this->initStorage()->getAbsoluteSubmissionPath() .
870            "/" . $storage_id . "/" . basename($filename);
871
872        ilUtil::deliverFile($filename, $filetitle);
873    }
874
875    protected function downloadMultipleFiles($a_filenames, $a_user_id, $a_multi_user = false)
876    {
877        $lng = $this->lng;
878
879        $path = $this->initStorage()->getAbsoluteSubmissionPath();
880
881        $cdir = getcwd();
882
883        $zip = PATH_TO_ZIP;
884        $tmpdir = ilUtil::ilTempnam();
885        $tmpfile = ilUtil::ilTempnam();
886        $tmpzipfile = $tmpfile . ".zip";
887
888        ilUtil::makeDir($tmpdir);
889        chdir($tmpdir);
890
891        $assTitle = ilExAssignment::lookupTitle($this->assignment->getId());
892        $deliverFilename = str_replace(" ", "_", $assTitle);
893        if ($a_user_id > 0 && !$a_multi_user) {
894            $userName = ilObjUser::_lookupName($a_user_id);
895            $deliverFilename .= "_" . $userName["lastname"] . "_" . $userName["firstname"];
896        } else {
897            $deliverFilename .= "_files";
898        }
899        $orgDeliverFilename = trim($deliverFilename);
900        $deliverFilename = ilUtil::getASCIIFilename($orgDeliverFilename);
901        ilUtil::makeDir($tmpdir . "/" . $deliverFilename);
902        chdir($tmpdir . "/" . $deliverFilename);
903
904        //copy all files to a temporary directory and remove them afterwards
905        $parsed_files = $duplicates = array();
906        foreach ($a_filenames as $storage_id => $files) {
907            $pathname = $path . "/" . $storage_id;
908
909            foreach ($files as $filename) {
910                // peer review masked filenames, see deliverReturnedFiles()
911                if (isset($filename["tgt"])) {
912                    $newFilename = $filename["tgt"];
913                    $filename = $filename["src"];
914                } else {
915                    $late = $filename["late"];
916                    $filename = $filename["src"];
917
918                    // remove timestamp
919                    $newFilename = trim($filename);
920                    $pos = strpos($newFilename, "_");
921                    if ($pos !== false) {
922                        $newFilename = substr($newFilename, $pos + 1);
923                    }
924                    // #11070
925                    $chkName = strtolower($newFilename);
926                    if (array_key_exists($chkName, $duplicates)) {
927                        $suffix = strrpos($newFilename, ".");
928                        $newFilename = substr($newFilename, 0, $suffix) .
929                            " (" . (++$duplicates[$chkName]) . ")" .
930                            substr($newFilename, $suffix);
931                    } else {
932                        $duplicates[$chkName] = 1;
933                    }
934
935                    if ($late) {
936                        $newFilename = $lng->txt("exc_late_submission") . " - " .
937                            $newFilename;
938                    }
939                }
940
941                $newFilename = ilUtil::getASCIIFilename($newFilename);
942                $newFilename = $tmpdir . DIRECTORY_SEPARATOR . $deliverFilename . DIRECTORY_SEPARATOR . $newFilename;
943                // copy to temporal directory
944                $oldFilename = $pathname . DIRECTORY_SEPARATOR . $filename;
945                if (!copy($oldFilename, $newFilename)) {
946                    echo 'Could not copy ' . $oldFilename . ' to ' . $newFilename;
947                }
948                touch($newFilename, filectime($oldFilename));
949                $parsed_files[] = ilUtil::escapeShellArg($deliverFilename . DIRECTORY_SEPARATOR . basename($newFilename));
950            }
951        }
952
953        chdir($tmpdir);
954        $zipcmd = $zip . " " . ilUtil::escapeShellArg($tmpzipfile) . " " . join(" ", $parsed_files);
955
956        exec($zipcmd);
957        ilUtil::delDir($tmpdir);
958
959        chdir($cdir);
960        ilUtil::deliverFile($tmpzipfile, $orgDeliverFilename . ".zip", "", false, true);
961        exit;
962    }
963
964    /**
965     * Download all submitted files of an assignment (all user)
966     * @param $a_ass ilExAssignment
967     * @param	$members		array of user names, key is user id
968     * @param $to_path string
969     * @throws ilExerciseException
970     * @return void
971     */
972    public static function downloadAllAssignmentFiles(ilExAssignment $a_ass, array $members, $to_path)
973    {
974        global $DIC;
975
976        $lng = $DIC->language();
977
978        $storage = new ilFSStorageExercise($a_ass->getExerciseId(), $a_ass->getId());
979        $storage->create();
980
981        ksort($members);
982        //$savepath = $this->getExercisePath() . "/" . $this->obj_id . "/";
983        $savepath = $storage->getAbsoluteSubmissionPath();
984        $cdir = getcwd();
985
986
987        // important check: if the directory does not exist
988        // ILIAS stays in the current directory (echoing only a warning)
989        // and the zip command below archives the whole ILIAS directory
990        // (including the data directory) and sends a mega file to the user :-o
991        if (!is_dir($savepath)) {
992            return;
993        }
994        // Safe mode fix
995        //		chdir($this->getExercisePath());
996
997        $tmpdir = $storage->getTempPath();
998        chdir($tmpdir);
999        $zip = PATH_TO_ZIP;
1000
1001        // check free diskspace
1002        $dirsize = 0;
1003        foreach (array_keys($members) as $id) {
1004            $directory = $savepath . DIRECTORY_SEPARATOR . $id;
1005            $dirsize += ilUtil::dirsize($directory);
1006        }
1007        if ($dirsize > disk_free_space($tmpdir)) {
1008            return -1;
1009        }
1010
1011        $ass_type = $a_ass->getType();
1012
1013        // copy all member directories to the temporary folder
1014        // switch from id to member name and append the login if the member name is double
1015        // ensure that no illegal filenames will be created
1016        // remove timestamp from filename
1017        if ($a_ass->hasTeam()) {
1018            $team_dirs = array();
1019            $team_map = ilExAssignmentTeam::getAssignmentTeamMap($a_ass->getId());
1020        }
1021        foreach ($members as $id => $item) {
1022            $user = $item["name"];
1023            $user_files = $item["files"];
1024            $sourcedir = $savepath . DIRECTORY_SEPARATOR . $id;
1025            if (!is_dir($sourcedir)) {
1026                continue;
1027            }
1028
1029            // group by teams
1030            $team_dir = "";
1031            if (is_array($team_map) &&
1032                array_key_exists($id, $team_map)) {
1033                $team_id = $team_map[$id];
1034                if (!array_key_exists($team_id, $team_dirs)) {
1035                    $team_dir = $lng->txt("exc_team") . " " . $team_id;
1036                    ilUtil::makeDir($team_dir);
1037                    $team_dirs[$team_id] = $team_dir;
1038                }
1039                $team_dir = $team_dirs[$team_id] . DIRECTORY_SEPARATOR;
1040            }
1041
1042            if ($a_ass->getAssignmentType()->isSubmissionAssignedToTeam()) {
1043                $targetdir = $team_dir . ilUtil::getASCIIFilename(
1044                    $item["name"]
1045                );
1046                if ($targetdir == "") {
1047                    continue;
1048                }
1049            } else {
1050                $targetdir = self::getDirectoryNameFromUserData($id);
1051                if ($a_ass->getAssignmentType()->usesTeams()) {
1052                    $targetdir = $team_dir . $targetdir;
1053                }
1054            }
1055            ilUtil::makeDir($targetdir);
1056
1057            $sourcefiles = scandir($sourcedir);
1058            $duplicates = array();
1059            foreach ($sourcefiles as $sourcefile) {
1060                if ($sourcefile == "." || $sourcefile == "..") {
1061                    continue;
1062                }
1063
1064                $targetfile = trim(basename($sourcefile));
1065                $pos = strpos($targetfile, "_");
1066                if ($pos !== false) {
1067                    $targetfile = substr($targetfile, $pos + 1);
1068                }
1069
1070                if ($a_ass->getAssignmentType()->getSubmissionType() == self::TYPE_REPO_OBJECT) {
1071                    $obj_id = ilObject::_lookupObjId($targetfile);
1072                    $obj_type = ilObject::_lookupType($obj_id);
1073                    $targetfile = $obj_type . "_" . $obj_id . ".zip";
1074                }
1075
1076
1077                // #14536
1078                if (array_key_exists($targetfile, $duplicates)) {
1079                    $suffix = strrpos($targetfile, ".");
1080                    $targetfile = substr($targetfile, 0, $suffix) .
1081                        " (" . (++$duplicates[$targetfile]) . ")" .
1082                        substr($targetfile, $suffix);
1083                } else {
1084                    $duplicates[$targetfile] = 1;
1085                }
1086
1087                // late submission?
1088                if (is_array($user_files)) {	// see #23900
1089                    foreach ($user_files as $file) {
1090                        if (basename($file["filename"]) == $sourcefile) {
1091                            if ($file["late"]) {
1092                                $targetfile = $lng->txt("exc_late_submission") . " - " .
1093                                    $targetfile;
1094                            }
1095                            break;
1096                        }
1097                    }
1098                }
1099
1100                $targetfile = ilUtil::getASCIIFilename($targetfile);
1101                $targetfile = $targetdir . DIRECTORY_SEPARATOR . $targetfile;
1102                $sourcefile = $sourcedir . DIRECTORY_SEPARATOR . $sourcefile;
1103
1104                if (!copy($sourcefile, $targetfile)) {
1105                    throw new ilExerciseException("Could not copy " . basename($sourcefile) . " to '" . $targetfile . "'.");
1106                } else {
1107                    // preserve time stamp
1108                    touch($targetfile, filectime($sourcefile));
1109
1110                    // blogs and portfolios are stored as zip and have to be unzipped
1111                    if ($ass_type == ilExAssignment::TYPE_PORTFOLIO ||
1112                        $ass_type == ilExAssignment::TYPE_BLOG) {
1113                        ilUtil::unzip($targetfile);
1114                        unlink($targetfile);
1115                    }
1116                }
1117            }
1118        }
1119        $tmpzipfile = ilUtil::getASCIIFilename($lng->txt("exc_ass_submission_zip")) . ".zip";
1120        // Safe mode fix
1121        $zipcmd = $zip . " -r " . ilUtil::escapeShellArg($tmpzipfile) . " .";
1122        exec($zipcmd);
1123        //$path_final_zip_file = $to_path.DIRECTORY_SEPARATOR."Submissions/".$tmpzipfile;
1124        $path_final_zip_file = $to_path . DIRECTORY_SEPARATOR . $tmpzipfile;
1125
1126        if (file_exists($tmpdir . DIRECTORY_SEPARATOR . $tmpzipfile)) {
1127            copy($tmpzipfile, $path_final_zip_file);
1128            ilUtil::delDir($tmpdir);
1129
1130            //unzip the submissions zip file.(decided to unzip to allow the excel link the files more obvious when blog/portfolio)
1131            chdir($to_path);
1132            //TODO Bug in ilUtil -> if flat unzip fails. We can get rid of creating Submissions directory
1133            //ilUtil::unzip($path_final_zip_file,FALSE, TRUE);
1134            ilUtil::unzip($path_final_zip_file);
1135            unlink($path_final_zip_file);
1136        }
1137
1138        chdir($cdir);
1139    }
1140
1141
1142    /**
1143     * Get user/team where clause
1144     *
1145     * @param
1146     * @return
1147     */
1148    public function getTableUserWhere($a_team_mode = false)
1149    {
1150        $ilDB = $this->db;
1151
1152        if ($this->getAssignment()->getAssignmentType()->isSubmissionAssignedToTeam()) {
1153            $team_id = $this->getTeam()->getId();
1154            $where = " team_id = " . $ilDB->quote($team_id, "integer") . " ";
1155        } else {
1156            if ($a_team_mode) {
1157                $where = " " . $ilDB->in("user_id", $this->getUserIds(), "", "integer") . " ";
1158            } else {
1159                $where = " user_id = " . $ilDB->quote($this->getUserId(), "integer");
1160            }
1161        }
1162        return $where;
1163    }
1164
1165
1166    /**
1167     * TODO -> get rid of getTableUserWhere and move to repository class
1168     * Get the date of the last submission of a user for the assignment
1169     *
1170     * @return	mixed	false or mysql timestamp of last submission
1171     */
1172    public function getLastSubmission()
1173    {
1174        $ilDB = $this->db;
1175
1176        $ilDB->setLimit(1);
1177
1178        $q = "SELECT obj_id,user_id,ts FROM exc_returned" .
1179            " WHERE ass_id = " . $ilDB->quote($this->assignment->getId(), "integer") .
1180            " AND " . $this->getTableUserWhere(true) .
1181            " AND (filename IS NOT NULL OR atext IS NOT NULL)" .
1182            " AND ts IS NOT NULL" .
1183            " ORDER BY ts DESC";
1184        $usr_set = $ilDB->query($q);
1185        $array = $ilDB->fetchAssoc($usr_set);
1186        return ilUtil::getMySQLTimestamp($array["ts"]);
1187    }
1188
1189    /**
1190     * TODO -> get rid of getTableUserWhere and move to repository class
1191     * Get a mysql timestamp from the last HTML view opening.
1192     */
1193    public function getLastOpeningHTMLView()
1194    {
1195        $this->db->setLimit(1);
1196
1197        $q = "SELECT web_dir_access_time FROM exc_returned" .
1198            " WHERE ass_id = " . $this->db->quote($this->assignment->getId(), "integer") .
1199            " AND (filename IS NOT NULL OR atext IS NOT NULL)" .
1200            " AND web_dir_access_time IS NOT NULL" .
1201            " AND " . $this->getTableUserWhere(true) .
1202            " ORDER BY web_dir_access_time DESC";
1203
1204        $res = $this->db->query($q);
1205
1206        $data = $this->db->fetchAssoc($res);
1207
1208        return ilUtil::getMySQLTimestamp($data["web_dir_access_time"]);
1209    }
1210
1211
1212    //
1213    // OBJECTS
1214    //
1215
1216    /**
1217     * Add personal resource or repository object (ref_id) to assigment
1218     *
1219     * @param int $a_wsp_id
1220     * @param string $a_text
1221     * @return int
1222     * @throws ilExerciseException
1223     */
1224    public function addResourceObject($a_wsp_id, $a_text = null)
1225    {
1226        $ilDB = $this->db;
1227
1228        if ($this->getAssignment()->getAssignmentType()->isSubmissionAssignedToTeam()) {
1229            $user_id = 0;
1230            $team_id = $this->getTeam()->getId();
1231        } else {
1232            $user_id = $this->getUserId();
1233            $team_id = 0;
1234        }
1235
1236        // repository objects must be unique in submissions
1237        // the same repo object cannot be used in different submissions or even different assignment/exercises
1238        // why? -> the access handling would fail, since the access depends e.g. on teams or even phase of the
1239        // assignment
1240        if ($this->getAssignment()->getAssignmentType()->getSubmissionType() == ilExSubmission::TYPE_REPO_OBJECT) {
1241            $repos_ass_type_ids = $this->ass_types->getIdsForSubmissionType(ilExSubmission::TYPE_REPO_OBJECT);
1242            $subs = $this->getSubmissionsForFilename($a_wsp_id, $repos_ass_type_ids);
1243            if (count($subs) > 0) {
1244                throw new ilExerciseException("Repository object $a_wsp_id is already assigned to another assignment.");
1245            }
1246        }
1247
1248        $next_id = $ilDB->nextId("exc_returned");
1249        $query = sprintf(
1250            "INSERT INTO exc_returned " .
1251                         "(returned_id, obj_id, user_id, filetitle, ass_id, ts, atext, late, team_id) " .
1252                         "VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)",
1253            $ilDB->quote($next_id, "integer"),
1254            $ilDB->quote($this->assignment->getExerciseId(), "integer"),
1255            $ilDB->quote($user_id, "integer"),
1256            $ilDB->quote($a_wsp_id, "text"),
1257            $ilDB->quote($this->assignment->getId(), "integer"),
1258            $ilDB->quote(ilUtil::now(), "timestamp"),
1259            $ilDB->quote($a_text, "text"),
1260            $ilDB->quote($this->isLate(), "integer"),
1261            $ilDB->quote($team_id, "integer")
1262        );
1263        $ilDB->manipulate($query);
1264
1265        return $next_id;
1266    }
1267
1268    /**
1269     * Remove personal resource to assigment
1270     *
1271     * @param int $a_returned_id
1272     */
1273    public function deleteResourceObject($a_returned_id)
1274    {
1275        $ilDB = $this->db;
1276
1277        $ilDB->manipulate("DELETE FROM exc_returned" .
1278            " WHERE obj_id = " . $ilDB->quote($this->assignment->getExerciseId(), "integer") .
1279            " AND " . $this->getTableUserWhere(false) .
1280            " AND ass_id = " . $ilDB->quote($this->assignment->getId(), "integer") .
1281            " AND returned_id = " . $ilDB->quote($a_returned_id, "integer"));
1282    }
1283
1284    /**
1285     * Handle text assignment submissions
1286     *
1287     * @param string $a_text
1288     * @return int
1289     */
1290    public function updateTextSubmission($a_text)
1291    {
1292        $ilDB = $this->db;
1293
1294        $files = $this->getFiles();
1295
1296        // no text = remove submission
1297        if (!trim($a_text)) {
1298            $this->deleteAllFiles();
1299            return;
1300        }
1301
1302        if (!$files) {
1303            return $this->addResourceObject("TEXT", $a_text);
1304        } else {
1305            $files = array_shift($files);
1306            $id = $files["returned_id"];
1307            if ($id) {
1308                $ilDB->manipulate("UPDATE exc_returned" .
1309                    " SET atext = " . $ilDB->quote($a_text, "text") .
1310                    ", ts = " . $ilDB->quote(ilUtil::now(), "timestamp") .
1311                    ", late = " . $ilDB->quote($this->isLate(), "integer") .
1312                    " WHERE returned_id = " . $ilDB->quote($id, "integer"));
1313                return $id;
1314            }
1315        }
1316    }
1317
1318    //
1319    // GUI helper
1320    //
1321
1322    // :TODO:
1323
1324    public function getDownloadedFilesInfoForTableGUIS($a_parent_obj, $a_parent_cmd = null)
1325    {
1326        $lng = $this->lng;
1327        $ilCtrl = $this->ctrl;
1328
1329        $result = array();
1330        $result["files"]["count"] = "---";
1331
1332        // submission:
1333        // see if files have been resubmmited after solved
1334        $last_sub = $this->getLastSubmission();
1335        if ($last_sub) {
1336            $last_sub = ilDatePresentation::formatDate(new ilDateTime($last_sub, IL_CAL_DATETIME));
1337        } else {
1338            $last_sub = "---";
1339        }
1340        /* #13741 - status_time has been reduced to grading (mark/status)
1341        if (self::lookupUpdatedSubmission($a_ass_id, $a_user_id) == 1)
1342        {
1343            $last_sub = "<b>".$last_sub."</b>";
1344        }
1345        */
1346        $result["last_submission"]["txt"] = $lng->txt("exc_last_submission");
1347        $result["last_submission"]["value"] = $last_sub;
1348
1349        // #16994
1350        $ilCtrl->setParameterByClass("ilexsubmissionfilegui", "member_id", $this->getUserId());
1351
1352        // assignment type specific
1353        switch ($this->assignment->getType()) {
1354            case ilExAssignment::TYPE_UPLOAD_TEAM:
1355                // data is merged by team - see above
1356                // fallthrough
1357
1358            case ilExAssignment::TYPE_UPLOAD:
1359                $all_files = $this->getFiles();
1360                $late_files = 0;
1361                foreach ($all_files as $file) {
1362                    if ($file["late"]) {
1363                        $late_files++;
1364                    }
1365                }
1366
1367                // nr of submitted files
1368                $result["files"]["txt"] = $lng->txt("exc_files_returned");
1369                if ($late_files) {
1370                    $result["files"]["txt"] .= ' - <span class="warning">' . $lng->txt("exc_late_submission") . " (" . $late_files . ")</span>";
1371                }
1372                $sub_cnt = count($all_files);
1373                $new = $this->lookupNewFiles();
1374                if (count($new) > 0) {
1375                    $sub_cnt .= " " . sprintf($lng->txt("cnt_new"), count($new));
1376                }
1377
1378                $result["files"]["count"] = $sub_cnt;
1379
1380                // download command
1381                if ($sub_cnt > 0) {
1382                    $result["files"]["download_url"] =
1383                        $ilCtrl->getLinkTargetByClass("ilexsubmissionfilegui", "downloadReturned");
1384
1385                    if (count($new) <= 0) {
1386                        $result["files"]["download_txt"] = $lng->txt("exc_tbl_action_download_files");
1387                    } else {
1388                        $result["files"]["download_txt"] = $lng->txt("exc_tbl_action_download_all_files");
1389                    }
1390
1391                    // download new files only
1392                    if (count($new) > 0) {
1393                        $result["files"]["download_new_url"] =
1394                            $ilCtrl->getLinkTargetByClass("ilexsubmissionfilegui", "downloadNewReturned");
1395
1396                        $result["files"]["download_new_txt"] = $lng->txt("exc_tbl_action_download_new_files");
1397                    }
1398                }
1399                break;
1400
1401            case ilExAssignment::TYPE_BLOG:
1402                $result["files"]["txt"] = $lng->txt("exc_blog_returned");
1403                $blogs = $this->getFiles();
1404                if ($blogs) {
1405                    $blogs = array_pop($blogs);
1406                    if ($blogs && substr($blogs["filename"], -1) != "/") {
1407                        if ($blogs["late"]) {
1408                            $result["files"]["txt"] .= ' - <span class="warning">' . $lng->txt("exc_late_submission") . "</span>";
1409                        }
1410
1411                        $result["files"]["count"] = 1;
1412
1413                        $result["files"]["download_url"] =
1414                            $ilCtrl->getLinkTargetByClass("ilexsubmissionfilegui", "downloadReturned");
1415
1416                        $result["files"]["download_txt"] = $lng->txt("exc_tbl_action_download_files");
1417                    }
1418                }
1419                break;
1420
1421            case ilExAssignment::TYPE_PORTFOLIO:
1422                $result["files"]["txt"] = $lng->txt("exc_portfolio_returned");
1423                $portfolios = $this->getFiles();
1424                if ($portfolios) {
1425                    $portfolios = array_pop($portfolios);
1426                    if ($portfolios && substr($portfolios["filename"], -1) != "/") {
1427                        if ($portfolios["late"]) {
1428                            $result["files"]["txt"] .= ' - <span class="warning">' . $lng->txt("exc_late_submission") . "</span>";
1429                        }
1430
1431                        $result["files"]["count"] = 1;
1432
1433                        $result["files"]["download_url"] =
1434                            $ilCtrl->getLinkTargetByClass("ilexsubmissionfilegui", "downloadReturned");
1435
1436                        $result["files"]["download_txt"] = $lng->txt("exc_tbl_action_download_files");
1437                    }
1438                }
1439                break;
1440
1441            case ilExAssignment::TYPE_TEXT:
1442                $result["files"]["txt"] = $lng->txt("exc_files_returned_text");
1443                $files = $this->getFiles();
1444                if ($files) {
1445                    $result["files"]["count"] = 1;
1446
1447                    $files = array_shift($files);
1448                    if (trim($files["atext"])) {
1449                        if ($files["late"]) {
1450                            $result["files"]["txt"] .= ' - <span class="warning">' . $lng->txt("exc_late_submission") . "</span>";
1451                        }
1452
1453                        $result["files"]["download_url"] =
1454                            $ilCtrl->getLinkTargetByClass("ilexsubmissiontextgui", "showAssignmentText");
1455
1456                        $result["files"]["download_txt"] = $lng->txt("exc_tbl_action_text_assignment_show");
1457                    }
1458                }
1459                break;
1460
1461            case ilExAssignment::TYPE_WIKI_TEAM:
1462                $result["files"]["txt"] = $lng->txt("exc_wiki_returned");
1463                $objs = $this->getFiles();
1464                if ($objs) {
1465                    $objs = array_pop($objs);
1466                    if ($objs && substr($objs["filename"], -1) != "/") {
1467                        if ($objs["late"]) {
1468                            $result["files"]["txt"] .= ' - <span class="warning">' . $lng->txt("exc_late_submission") . "</span>";
1469                        }
1470
1471                        $result["files"]["count"] = 1;
1472
1473                        $result["files"]["download_url"] =
1474                            $ilCtrl->getLinkTargetByClass("ilexsubmissionfilegui", "downloadReturned");
1475
1476                        $result["files"]["download_txt"] = $lng->txt("exc_tbl_action_download_files");
1477                    }
1478                }
1479                break;
1480        }
1481
1482        $ilCtrl->setParameterByClass("ilexsubmissionfilegui", "member_id", "");
1483
1484        return $result;
1485    }
1486
1487    /**
1488     * Get assignment return entries for a filename
1489     *
1490     * @param string $a_filename
1491     * @param int[] $a_assignment_types
1492     * @return array
1493     */
1494    public static function getSubmissionsForFilename($a_filename, $a_assignment_types = array())
1495    {
1496        global $DIC;
1497
1498        $db = $DIC->database();
1499
1500        $query = "SELECT * FROM exc_returned r LEFT JOIN exc_assignment a" .
1501            " ON (r.ass_id = a.id) " .
1502            " WHERE r.filetitle = " . $db->quote($a_filename, "string");
1503
1504        if (is_array($a_assignment_types) && count($a_assignment_types) > 0) {
1505            $query .= " AND " . $db->in("a.type", $a_assignment_types, false, "integer");
1506        }
1507
1508        $set = $db->query($query);
1509        $rets = array();
1510        while ($rec = $db->fetchAssoc($set)) {
1511            $rets[] = $rec;
1512        }
1513
1514
1515        return $rets;
1516    }
1517
1518    /*
1519     * @param $a_user_id
1520     * @return string
1521     */
1522    public static function getDirectoryNameFromUserData($a_user_id)
1523    {
1524        $userName = ilObjUser::_lookupName($a_user_id);
1525        $targetdir = ilUtil::getASCIIFilename(
1526            trim($userName["lastname"]) . "_" .
1527            trim($userName["firstname"]) . "_" .
1528            trim($userName["login"]) . "_" .
1529            $userName["user_id"]
1530        );
1531
1532        return $targetdir;
1533    }
1534
1535    /**
1536     * @param $a_exercise_id
1537     * @param $a_ass_id
1538     * @return array
1539     */
1540    public static function getAssignmentParticipants(int $a_exercise_id, int $a_ass_id) : array
1541    {
1542        global $DIC;
1543
1544        $ilDB = $DIC->database();
1545
1546        $participants = array();
1547        $query = "SELECT user_id FROM exc_returned WHERE ass_id = " .
1548            $ilDB->quote($a_ass_id, "integer") .
1549            " AND obj_id = " .
1550            $ilDB->quote($a_exercise_id, "integer");
1551
1552        $res = $ilDB->query($query);
1553
1554        while ($row = $ilDB->fetchAssoc($res)) {
1555            $participants[] = $row['user_id'];
1556        }
1557
1558        return $participants;
1559    }
1560}
1561