ILIAS  trunk Revision v12.0_alpha-377-g3641b37b9db
SubmissionManager.php
Go to the documentation of this file.
1<?php
2
19declare(strict_types=1);
20
22
27
29{
30 protected \ilExAssignmentTypeInterface $type;
31 protected \ILIAS\Exercise\Team\TeamManager $team;
32 protected \ilLogger $log;
34 protected \ilExAssignment $assignment;
35
36 public function __construct(
38 protected InternalDomainService $domain,
39 protected \ilExcSubmissionStakeholder $stakeholder,
40 protected int $ass_id
41 ) {
42 $this->assignment = $domain->assignment()->getAssignment($ass_id);
43 $this->type = $this->assignment->getAssignmentType();
44 $this->repo = $repo->submission();
45 $this->log = $this->domain->logger()->exc();
46 $this->team = $domain->team();
47 }
48
49 public function countSubmissionsOfUser(int $user_id): int
50 {
51 return count(iterator_to_array(
52 $this->getSubmissionsOfUser($user_id)
53 ));
54 }
55
60 public function getSubmissionsOfUser(
61 int $user_id,
62 ?array $submit_ids = null,
63 bool $only_valid = false,
64 ?string $min_timestamp = null,
65 bool $print_versions = false
66 ): \Generator {
67 $type_uses_print_versions = in_array($this->assignment->getType(), [
68 \ilExAssignment::TYPE_BLOG,
69 \ilExAssignment::TYPE_PORTFOLIO,
70 \ilExAssignment::TYPE_WIKI_TEAM
71 ], true);
72 $type_uses_uploads = $this->type->usesFileUpload();
73 if ($this->type->isSubmissionAssignedToTeam()) {
74 $team_id = $this->team->getTeamForMember($this->ass_id, $user_id);
75 if (((int) $team_id) !== 0) {
76 yield from $this->repo->getSubmissionsOfTeam(
77 $this->ass_id,
78 $type_uses_uploads,
79 $type_uses_print_versions,
81 $submit_ids,
82 $only_valid,
83 $min_timestamp,
84 $print_versions
85 );
86 }
87 } else {
88 $user_ids = $this->team->getTeamMemberIdsOrUserId(
89 $this->ass_id,
91 );
92 yield from $this->repo->getSubmissionsOfUsers(
93 $this->ass_id,
94 $type_uses_uploads,
95 $type_uses_print_versions,
96 $user_ids,
97 $submit_ids,
98 $only_valid,
99 $min_timestamp,
100 $print_versions
101 );
102 }
103 yield from [];
104 }
105
110 public function getSubmissionsOfOwners(array $user_ids): \Generator
111 {
112 $type_uses_uploads = $this->type->usesFileUpload();
113 $type_uses_print_versions = in_array($this->assignment->getType(), [
117 ], true);
118 yield from $this->repo->getSubmissionsOfUsers(
119 $this->ass_id,
120 $type_uses_uploads,
121 $type_uses_print_versions,
122 $user_ids
123 );
124 }
125
126 public function recalculateLateSubmissions(): void
127 {
128 // see JF, 2015-05-11
129 $assigment = $this->assignment;
130
131 $ext_deadline = $assigment->getExtendedDeadline();
132
133 foreach ($this->getAllAssignmentFiles() as $file) {
134 $id = $file["returned_id"];
135 $uploaded = new \ilDateTime($file["ts"], IL_CAL_DATETIME);
136 $uploaded = $uploaded->get(IL_CAL_UNIX);
137
138 $deadline = $assigment->getPersonalDeadline($file["user_id"]);
139 $last_deadline = max($deadline, $assigment->getExtendedDeadline());
140
141 $late = null;
142
143 // upload is not late anymore
144 if ($file["late"] &&
145 (!$last_deadline ||
146 !$ext_deadline ||
147 $uploaded < $deadline)) {
148 $late = false;
149 }
150 // upload is now late
151 elseif (!$file["late"] &&
152 $ext_deadline &&
153 $deadline &&
154 $uploaded > $deadline) {
155 $late = true;
156 }
157
158 if ($late !== null) {
159 $this->repo->updateLate(
160 $id,
161 $late
162 );
163 }
164 }
165 }
166
170 public function getAllAssignmentFiles(): array
171 {
172 $assignment = $this->assignment;
173
174 $delivered = [];
175 foreach ($this->repo->getAllEntriesOfAssignment($assignment->getId()) as $row) {
176 $row["timestamp"] = $row["ts"];
177 $delivered[] = $row;
178 }
179
180 return $delivered;
181 }
182
184 int $user_id = 0
185 ): int {
186 $ass = $this->assignment;
187 return $this->repo->getMaxAmountOfSubmittedFiles(
188 $ass->getExerciseId(),
189 $ass->getId(),
191 );
192 }
193
198 public function getUsersWithSubmission(): array
199 {
200 return $this->repo->getUsersWithSubmission(
201 $this->ass_id
202 );
203 }
204
205 public function addLocalFile(
206 int $user_id,
207 string $file,
208 string $filename = ""
209 ): bool {
210
211 if ($filename === "") {
212 $filename = basename($file);
213 }
214 $submission = new \ilExSubmission(
215 $this->assignment,
217 );
218
219 if (!$submission->canAddFile()) {
220 return false;
221 }
222
223 if ($this->type->isSubmissionAssignedToTeam()) {
224 $team_id = $submission->getTeam()->getId();
225 $user_id = 0;
226 if ($team_id === 0) {
227 return false;
228 }
229 } else {
230 $team_id = 0;
231 }
232 $success = $this->repo->addLocalFile(
233 $this->assignment->getExerciseId(),
234 $this->ass_id,
235 $user_id,
236 $team_id,
237 $file,
238 $filename,
239 $submission->isLate(),
240 $this->stakeholder
241 );
242
243 if ($success && $team_id > 0) {
244 $this->domain->team()->writeLog(
245 $team_id,
248 );
249 }
250
251 return $success;
252 }
253
254 public function addUpload(
255 int $user_id,
256 UploadResult $result,
257 string $filename = ""
258 ): bool {
259
260 if ($filename === "") {
261 $filename = $result->getName();
262 }
263 $submission = new \ilExSubmission(
264 $this->assignment,
266 );
267
268 if (!$submission->canAddFile()) {
269 return false;
270 }
271
272 if ($this->type->isSubmissionAssignedToTeam()) {
273 $team_id = $submission->getTeam()->getId();
274 $user_id = 0;
275 if ($team_id === 0) {
276 return false;
277 }
278 } else {
279 $team_id = 0;
280 }
281 $success = $this->repo->addUpload(
282 $this->assignment->getExerciseId(),
283 $this->ass_id,
284 $user_id,
285 $team_id,
286 $result,
287 $filename,
288 $submission->isLate(),
289 $this->stakeholder
290 );
291
292 if ($success && $team_id > 0) {
293 $this->domain->team()->writeLog(
294 $team_id,
297 );
298 }
299
300 return $success;
301 }
302
303 protected function remainingFilesAllowed(int $user_id): int
304 {
305 $submission = new \ilExSubmission(
306 $this->assignment,
308 );
309 $max_file = $submission->getAssignment()->getMaxFile();
310 if ($max_file === 0) {
311 return -1;
312 }
313 $cnt_sub = $this->countSubmissionsOfUser($user_id);
314 $max_file -= $cnt_sub;
315 return max($max_file, 0);
316 }
317
318 public function addZipUploads(
319 int $user_id,
320 UploadResult $result
321 ): bool {
322 $submission = new \ilExSubmission(
323 $this->assignment,
325 );
326 if (!$submission->canAddFile()) {
327 return false;
328 }
329 if ($this->type->isSubmissionAssignedToTeam()) {
330 $team_id = $submission->getTeam()->getId();
331 $user_id = 0;
332 if ($team_id === 0) {
333 return false;
334 }
335 } else {
336 $team_id = 0;
337 }
338 $filenames = $this->repo->addZipUpload(
339 $this->assignment->getExerciseId(),
340 $this->ass_id,
341 $user_id,
342 $team_id,
343 $result,
344 $submission->isLate(),
345 $this->stakeholder,
346 $this->remainingFilesAllowed($user_id)
347 );
348 $this->log->debug("99");
349 if ($team_id > 0) {
350 foreach ($filenames as $filename) {
351 $this->domain->team()->writeLog(
352 $team_id,
355 );
356 }
357 }
358
359 return count($filenames) > 0;
360 }
361
362 public function deliverFile(
363 int $user_id,
364 string $rid,
365 string $filetitle = ""
366 ): void {
367 $this->repo->deliverFile(
368 $this->ass_id,
369 $user_id,
370 $rid,
371 $filetitle
372 );
373 }
374
375 public function copyRidToWebDir(
376 string $rid,
377 string $internal_file_path
378 ): ?string {
379 global $DIC;
380
381 $web_filesystem = $DIC->filesystem()->web();
382
383 $internal_dirs = dirname($internal_file_path);
384 $zip_file = basename($internal_file_path);
385
386 if ($rid !== "") {
387 //$this->log->debug("internal file path: " . $internal_file_path);
388 if ($web_filesystem->hasDir($internal_dirs)) {
389 $web_filesystem->deleteDir($internal_dirs);
390 }
391 $web_filesystem->createDir($internal_dirs);
392 if ($web_filesystem->has($internal_file_path)) {
393 $web_filesystem->delete($internal_file_path);
394 }
395 if (!$web_filesystem->has($internal_file_path)) {
396 //$this->log->debug("writing: " . $internal_file_path);
397 $stream = $this->repo->getStream(
398 $this->ass_id,
399 $rid
400 );
401 if (!is_null($stream)) {
402 $web_filesystem->writeStream($internal_file_path, $stream);
403
404 return ILIAS_ABSOLUTE_PATH .
405 DIRECTORY_SEPARATOR .
406 "public" .
407 DIRECTORY_SEPARATOR .
409 DIRECTORY_SEPARATOR .
410 CLIENT_ID .
411 DIRECTORY_SEPARATOR .
412 $internal_dirs .
413 DIRECTORY_SEPARATOR .
414 $zip_file;
415 }
416 }
417 }
418
419 return null;
420 }
421
425 public function deleteSubmissions(int $user_id, array $ids): void
426 {
427 if (count($ids) == 0) {
428 return;
429 }
430 $all = $this->getAllSubmissionIdsOfUser($user_id);
431 foreach ($ids as $id) {
432 // this ensures, the ids belong to user submissions at all
433 if (!in_array($id, $all)) {
434 continue;
435 }
436 $s = $this->repo->getById($id);
437 if (is_null($s)) { // #45974
438 continue;
439 }
440 $this->repo->delete(
441 $id,
442 $this->stakeholder
443 );
444 $team_id = $this->team->getTeamForMember($this->ass_id, $user_id);
445 if ($team_id && $team_id > 0) {
446 $this->team->writeLog(
447 $team_id,
449 $s->getTitle()
450 );
451 }
452 }
453 }
454
458 protected function getAllSubmissionIdsOfUser(int $user_id): array
459 {
460 $subs = [];
461 foreach ($this->getSubmissionsOfUser($user_id) as $s) {
462 $subs[] = $s->getId();
463 }
464 foreach ($this->getSubmissionsOfUser($user_id, null, false, null, true) as $s) {
465 $subs[] = $s->getId();
466 }
467 return $subs;
468 }
469
470 public function deleteAllSubmissionsOfUser(int $user_id): void
471 {
472 $this->deleteSubmissions(
473 $user_id,
474 $this->getAllSubmissionIdsOfUser($user_id)
475 );
476 }
477
481 public function copySubmissionsToDir(
482 array $user_ids,
483 string $directory
484 ): void {
485 $members = [];
486 foreach ($user_ids as $member_id) {
487 $submission = new \ilExSubmission($this->assignment, $member_id);
488 $submission->updateTutorDownloadTime();
489
490 // get member object (ilObjUser)
491 if ($this->domain->profile()->exists($member_id)) {
492 // adding file metadata
493 foreach ($this->getSubmissionsOfUser($member_id) as $sub) {
494 $members[$sub->getUserId()]["files"][$sub->getId()] = $sub;
495 }
496
497 $name = \ilObjUser::_lookupName($member_id);
498 $members[$member_id]["name"] = $name["firstname"] . " " . $name["lastname"];
499 }
500 }
501 $this->copySubmissionFilesToDir($members, $directory);
502 }
503
504 protected function copySubmissionFilesToDir(
505 array $members,
506 string $to_path
507 ): void {
508 global $DIC;
509
510 $zip_archive = $DIC->archives()->zip([]);
511 $ass = $this->assignment;
512
513 $lng = $this->domain->lng();
515
516
517 ksort($members);
518 //$savepath = $storage->getAbsoluteSubmissionPath();
519 //$cdir = getcwd();
520
521 // important check: if the directory does not exist
522 // ILIAS stays in the current directory (echoing only a warning)
523 // and the zip command below archives the whole ILIAS directory
524 // (including the data directory) and sends a mega file to the user :-o
525 // if (!is_dir($savepath)) {
526 // return;
527 // }
528 // Safe mode fix
529 // chdir($this->getExercisePath());
530
531 // $tmpdir = $storage->getTempPath();
532 // chdir($tmpdir);
533 // $zip = PATH_TO_ZIP;
534
535 //$ass_type = $a_ass->getType();
536
537 // copy all member directories to the temporary folder
538 // switch from id to member name and append the login if the member name is double
539 // ensure that no illegal filenames will be created
540 // remove timestamp from filename
541 $team_map = null;
542 $team_dirs = null;
543 if ($ass->hasTeam()) {
544 $team_dirs = array();
545 $team_map = \ilExAssignmentTeam::getAssignmentTeamMap($ass->getId());
546 }
547 foreach ($members as $id => $item) {
548 $user_files = $item["files"] ?? [];
549
550 // group by teams
551 $team_dir = "";
552 if (is_array($team_map) &&
553 array_key_exists($id, $team_map)) {
554 $team_id = $team_map[$id];
555 if (!array_key_exists($team_id, $team_dirs)) {
556 $team_dir = $lng->txt("exc_team") . " " . $team_id;
557 $team_dirs[$team_id] = $team_dir;
558 }
559 $team_dir = $team_dirs[$team_id] . DIRECTORY_SEPARATOR;
560 }
561
562 if ($ass->getAssignmentType()->isSubmissionAssignedToTeam()) {
563 $targetdir = $team_dir . \ilFileUtils::getASCIIFilename(
564 $item["name"]
565 );
566 if ($targetdir == "") {
567 continue;
568 }
569 } else {
570 $targetdir = $this->getDirectoryNameFromUserData($id);
571 if ($ass->getAssignmentType()->usesTeams()) {
572 $targetdir = $team_dir . $targetdir;
573 }
574 }
575
576 $log->debug("Creation target directory: " . $targetdir);
577
578 $duplicates = [];
579
581 foreach ($user_files as $sub) {
582 $targetfile = $sub->getTitle();
583
584 // rename repo objects
585 if ($this->type->getSubmissionType() === \ilExSubmission::TYPE_REPO_OBJECT) {
586 $obj_id = \ilObject::_lookupObjId((int) $targetfile);
587 $obj_type = \ilObject::_lookupType($obj_id);
588 $targetfile = $obj_type . "_" . $obj_id . ".zip";
589 }
590
591 // handle duplicates
592 if (array_key_exists($targetfile, $duplicates)) {
593 $suffix = strrpos($targetfile, ".");
594 $targetfile = substr($targetfile, 0, $suffix) .
595 " (" . (++$duplicates[$targetfile]) . ")" .
596 substr($targetfile, $suffix);
597 } else {
598 $duplicates[$targetfile] = 1;
599 }
600
601 // add late submission info
602 if ($sub->getLate()) { // see #23900
603 $targetfile = $lng->txt("exc_late_submission") . " - " .
604 $targetfile;
605 }
606
607 // normalize and add directory
608 $targetfile = \ilFileUtils::getASCIIFilename($targetfile);
609 //$targetfile = $targetdir . DIRECTORY_SEPARATOR . $targetfile;
610
611 /*
612 $zip_archive->addStream(
613 $this->repo->getStream($ass->getId(), $sub->getRid()),
614 $targetfile
615 );*/
616
617 $stream = $this->repo->getStream($ass->getId(), $sub->getRid());
618
619 // add file to directory (no zip see end of the function)
620 $dir = $to_path . DIRECTORY_SEPARATOR . $targetdir;
622 $file = $dir . DIRECTORY_SEPARATOR . $targetfile;
623 file_put_contents(
624 $file,
625 $stream->getContents()
626 );
627
628 // unzip blog/portfolio
629
630 }
631 }
632 }
633
637 protected function getDirectoryNameFromUserData(int $user_id): string
638 {
640 return \ilFileUtils::getASCIIFilename(
641 trim($userName["lastname"]) . "_" .
642 trim($userName["firstname"]) . "_" .
643 trim($userName["login"]) . "_" .
644 $userName["user_id"]
645 );
646 }
647
648 public function deliverSubmissions(
649 array $subs,
650 int $user_id,
651 bool $peer_review_mask_filename = false,
652 int $peer_id = 0
653 ): void {
654 global $DIC;
655
656 $filenames = array();
657 $seq = 0;
658 $is_team = $this->type->usesTeams() || $peer_review_mask_filename;
660 foreach ($subs as $sub) {
661 if ($this->type->isSubmissionAssignedToTeam()) {
662 $storage_id = $sub->getTeamId();
663 } else {
664 $storage_id = $sub->getUserId();
665 }
666
667 $src = $sub->getTitle();
668 if ($peer_review_mask_filename) {
669 $src_a = explode(".", $src);
670 $suffix = array_pop($src_a);
671 $tgt = $this->assignment->getTitle() . "_peer" . $peer_id .
672 "_" . (++$seq) . "." . $suffix;
673
674 $filenames[$storage_id][] = array(
675 "src" => $src,
676 "tgt" => $tgt,
677 "rid" => $sub->getRid()
678 );
679 } else {
680 $filenames[$storage_id][] = array(
681 "src" => $src,
682 "late" => $sub->getLate(),
683 "rid" => $sub->getRid()
684 );
685 }
686 }
687
688 if ($is_team) {
689 $multi_user = true;
690 $user_id = null;
691 } else {
692 $multi_user = false;
693 }
694
695 $lng = $this->domain->lng();
696
697 $zip = $DIC->archives()->zip([]);
698
699
700 $assTitle = \ilExAssignment::lookupTitle($this->assignment->getId());
701 $deliverFilename = str_replace(" ", "_", $assTitle);
702 if ($user_id > 0 && !$multi_user) {
704 $deliverFilename .= "_" . $userName["lastname"] . "_" . $userName["firstname"];
705 } else {
706 $deliverFilename .= "_files";
707 }
708 $orgDeliverFilename = trim($deliverFilename);
709 $deliverFilename = \ilFileUtils::getASCIIFilename($orgDeliverFilename);
710
711 //copy all files to a temporary directory and remove them afterwards
712 $parsed_files = $duplicates = array();
713 foreach ($filenames as $files) {
714
715 foreach ($files as $file) {
716 // peer review masked filenames, see deliverReturnedFiles()
717 if (isset($file["tgt"])) {
718 $newFilename = $file["tgt"];
719 $filename = $file["src"];
720 } else {
721 $late = $file["late"];
722 $filename = $file["src"];
723
724 // remove timestamp
725 $newFilename = trim($filename);
726 $pos = strpos($newFilename, "_");
727 if ($pos !== false) {
728 $newFilename = substr($newFilename, $pos + 1);
729 }
730 // #11070
731 $chkName = strtolower($newFilename);
732 if (array_key_exists($chkName, $duplicates)) {
733 $suffix = strrpos($newFilename, ".");
734 $newFilename = substr($newFilename, 0, $suffix) .
735 " (" . (++$duplicates[$chkName]) . ")" .
736 substr($newFilename, $suffix);
737 } else {
738 $duplicates[$chkName] = 1;
739 }
740
741 if ($late) {
742 $newFilename = $lng->txt("exc_late_submission") . " - " .
743 $newFilename;
744 }
745 }
746
747 $newFilename = \ilFileUtils::getASCIIFilename($newFilename);
748 $newFilename = $deliverFilename . DIRECTORY_SEPARATOR . $newFilename;
749 $zip->addStream(
750 $this->repo->getStream(
751 $this->ass_id,
752 $file["rid"]
753 ),
754 $newFilename
755 );
756
757 /*
758 $parsed_files[] = ilShellUtil::escapeShellArg(
759 $deliverFilename . DIRECTORY_SEPARATOR . basename($newFilename)
760 );*/
761 }
762 }
763
764 // todo: move to gui
765 $http_util = $DIC->exercise()->internal()->gui()->httpUtil();
766 $http_util->deliverStream(
767 $zip->get(),
768 $orgDeliverFilename . ".zip",
769 "application/zip"
770 );
771 }
772
773}
$id
plugin.php for ilComponentBuildPluginInfoObjectiveTest::testAddPlugins
Definition: plugin.php:23
$filename
Definition: buildRTE.php:78
addZipUploads(int $user_id, UploadResult $result)
copyRidToWebDir(string $rid, string $internal_file_path)
__construct(InternalRepoService $repo, protected InternalDomainService $domain, protected \ilExcSubmissionStakeholder $stakeholder, protected int $ass_id)
getAllSubmissionIdsOfUser(int $user_id)
All, include print, include all from team.
deleteSubmissions(int $user_id, array $ids)
Delete submissions.
getDirectoryNameFromUserData(int $user_id)
there is still a version in ilExSubmission that needs to be replaced
getUsersWithSubmission()
Get all user ids, that have submitted something.
deliverFile(int $user_id, string $rid, string $filetitle="")
getSubmissionsOfUser(int $user_id, ?array $submit_ids=null, bool $only_valid=false, ?string $min_timestamp=null, bool $print_versions=false)
Note: this includes submissions of other team members, if user is in a team.
copySubmissionsToDir(array $user_ids, string $directory)
Should be replaced by writing into a zip directly in the future.
addLocalFile(int $user_id, string $file, string $filename="")
ILIAS Exercise Team TeamManager $team
getSubmissionsOfOwners(array $user_ids)
This is only suitable for types like text or single upload, where no teams are used.
addUpload(int $user_id, UploadResult $result, string $filename="")
const IL_CAL_UNIX
const IL_CAL_DATETIME
return true
static getAssignmentTeamMap(int $a_ass_id)
Exercise assignment.
static lookupTitle(int $a_id)
Exercise submission //TODO: This class has many static methods related to delivered "files".
static makeDirParents(string $a_dir)
Create a new directory and all parent directories.
static getASCIIFilename(string $a_filename)
static _lookupName(int $a_user_id)
static _lookupType(int $id, bool $reference=false)
static _lookupObjId(int $ref_id)
const CLIENT_ID
Definition: constants.php:41
const ILIAS_WEB_DIR
Definition: constants.php:45
The base interface for all filesystem streams.
Definition: FileStream.php:32
$log
Definition: ltiresult.php:34
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
global $lng
Definition: privfeed.php:31
if(!file_exists('../ilias.ini.php'))
global $DIC
Definition: shib_login.php:26