ILIAS  trunk Revision v11.0_alpha-3011-gc6b235a2e85
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 $this->repo->delete(
438 $id,
439 $this->stakeholder
440 );
441 $team_id = $this->team->getTeamForMember($this->ass_id, $user_id);
442 if ($team_id && $team_id > 0) {
443 $this->team->writeLog(
444 $team_id,
446 $s->getTitle()
447 );
448 }
449 }
450 }
451
455 protected function getAllSubmissionIdsOfUser(int $user_id): array
456 {
457 $subs = [];
458 foreach ($this->getSubmissionsOfUser($user_id) as $s) {
459 $subs[] = $s->getId();
460 }
461 foreach ($this->getSubmissionsOfUser($user_id, null, false, null, true) as $s) {
462 $subs[] = $s->getId();
463 }
464 return $subs;
465 }
466
467 public function deleteAllSubmissionsOfUser(int $user_id): void
468 {
469 $this->deleteSubmissions(
470 $user_id,
471 $this->getAllSubmissionIdsOfUser($user_id)
472 );
473 }
474
478 public function copySubmissionsToDir(
479 array $user_ids,
480 string $directory
481 ) {
482 $members = [];
483 foreach ($user_ids as $member_id) {
484 $submission = new \ilExSubmission($this->assignment, $member_id);
485 $submission->updateTutorDownloadTime();
486
487 // get member object (ilObjUser)
488 if ($this->domain->profile()->exists($member_id)) {
489 // adding file metadata
490 foreach ($this->getSubmissionsOfUser($member_id) as $sub) {
491 $members[$sub->getUserId()]["files"][$sub->getId()] = $sub;
492 }
493
494 $name = \ilObjUser::_lookupName($member_id);
495 $members[$member_id]["name"] = $name["firstname"] . " " . $name["lastname"];
496 }
497 }
498 $this->copySubmissionFilesToDir($members, $directory);
499 }
500
501 protected function copySubmissionFilesToDir(
502 array $members,
503 string $to_path
504 ): void {
505 global $DIC;
506
507 $zip_archive = $DIC->archives()->zip([]);
508 $ass = $this->assignment;
509
510 $lng = $this->domain->lng();
512
513
514 ksort($members);
515 //$savepath = $storage->getAbsoluteSubmissionPath();
516 //$cdir = getcwd();
517
518 // important check: if the directory does not exist
519 // ILIAS stays in the current directory (echoing only a warning)
520 // and the zip command below archives the whole ILIAS directory
521 // (including the data directory) and sends a mega file to the user :-o
522 // if (!is_dir($savepath)) {
523 // return;
524 // }
525 // Safe mode fix
526 // chdir($this->getExercisePath());
527
528 // $tmpdir = $storage->getTempPath();
529 // chdir($tmpdir);
530 // $zip = PATH_TO_ZIP;
531
532 //$ass_type = $a_ass->getType();
533
534 // copy all member directories to the temporary folder
535 // switch from id to member name and append the login if the member name is double
536 // ensure that no illegal filenames will be created
537 // remove timestamp from filename
538 $team_map = null;
539 $team_dirs = null;
540 if ($ass->hasTeam()) {
541 $team_dirs = array();
542 $team_map = \ilExAssignmentTeam::getAssignmentTeamMap($ass->getId());
543 }
544 foreach ($members as $id => $item) {
545 $user_files = $item["files"] ?? [];
546
547 // group by teams
548 $team_dir = "";
549 if (is_array($team_map) &&
550 array_key_exists($id, $team_map)) {
551 $team_id = $team_map[$id];
552 if (!array_key_exists($team_id, $team_dirs)) {
553 $team_dir = $lng->txt("exc_team") . " " . $team_id;
554 $team_dirs[$team_id] = $team_dir;
555 }
556 $team_dir = $team_dirs[$team_id] . DIRECTORY_SEPARATOR;
557 }
558
559 if ($ass->getAssignmentType()->isSubmissionAssignedToTeam()) {
560 $targetdir = $team_dir . \ilFileUtils::getASCIIFilename(
561 $item["name"]
562 );
563 if ($targetdir == "") {
564 continue;
565 }
566 } else {
567 $targetdir = $this->getDirectoryNameFromUserData($id);
568 if ($ass->getAssignmentType()->usesTeams()) {
569 $targetdir = $team_dir . $targetdir;
570 }
571 }
572
573 $log->debug("Creation target directory: " . $targetdir);
574
575 $duplicates = [];
576
578 foreach ($user_files as $sub) {
579 $targetfile = $sub->getTitle();
580
581 // rename repo objects
582 if ($this->type->getSubmissionType() === \ilExSubmission::TYPE_REPO_OBJECT) {
583 $obj_id = \ilObject::_lookupObjId((int) $targetfile);
584 $obj_type = \ilObject::_lookupType($obj_id);
585 $targetfile = $obj_type . "_" . $obj_id . ".zip";
586 }
587
588 // handle duplicates
589 if (array_key_exists($targetfile, $duplicates)) {
590 $suffix = strrpos($targetfile, ".");
591 $targetfile = substr($targetfile, 0, $suffix) .
592 " (" . (++$duplicates[$targetfile]) . ")" .
593 substr($targetfile, $suffix);
594 } else {
595 $duplicates[$targetfile] = 1;
596 }
597
598 // add late submission info
599 if ($sub->getLate()) { // see #23900
600 $targetfile = $lng->txt("exc_late_submission") . " - " .
601 $targetfile;
602 }
603
604 // normalize and add directory
605 $targetfile = \ilFileUtils::getASCIIFilename($targetfile);
606 //$targetfile = $targetdir . DIRECTORY_SEPARATOR . $targetfile;
607
608 /*
609 $zip_archive->addStream(
610 $this->repo->getStream($ass->getId(), $sub->getRid()),
611 $targetfile
612 );*/
613
614 $stream = $this->repo->getStream($ass->getId(), $sub->getRid());
615
616 // add file to directory (no zip see end of the function)
617 $dir = $to_path . DIRECTORY_SEPARATOR . $targetdir;
619 $file = $dir . DIRECTORY_SEPARATOR . $targetfile;
620 file_put_contents(
621 $file,
622 $stream->getContents()
623 );
624
625 // unzip blog/portfolio
626
627 }
628 }
629 }
630
634 protected function getDirectoryNameFromUserData(int $user_id): string
635 {
637 return \ilFileUtils::getASCIIFilename(
638 trim($userName["lastname"]) . "_" .
639 trim($userName["firstname"]) . "_" .
640 trim($userName["login"]) . "_" .
641 $userName["user_id"]
642 );
643 }
644
645 public function deliverSubmissions(
646 array $subs,
647 int $user_id,
648 bool $peer_review_mask_filename = false,
649 int $peer_id = 0
650 ): void {
651 global $DIC;
652
653 $filenames = array();
654 $seq = 0;
655 $is_team = $this->type->usesTeams() || $peer_review_mask_filename;
657 foreach ($subs as $sub) {
658 if ($this->type->isSubmissionAssignedToTeam()) {
659 $storage_id = $sub->getTeamId();
660 } else {
661 $storage_id = $sub->getUserId();
662 }
663
664 $src = $sub->getTitle();
665 if ($peer_review_mask_filename) {
666 $src_a = explode(".", $src);
667 $suffix = array_pop($src_a);
668 $tgt = $this->assignment->getTitle() . "_peer" . $peer_id .
669 "_" . (++$seq) . "." . $suffix;
670
671 $filenames[$storage_id][] = array(
672 "src" => $src,
673 "tgt" => $tgt,
674 "rid" => $sub->getRid()
675 );
676 } else {
677 $filenames[$storage_id][] = array(
678 "src" => $src,
679 "late" => $sub->getLate(),
680 "rid" => $sub->getRid()
681 );
682 }
683 }
684
685 if ($is_team) {
686 $multi_user = true;
687 $user_id = null;
688 } else {
689 $multi_user = false;
690 }
691
692 $lng = $this->domain->lng();
693
694 $zip = $DIC->archives()->zip([]);
695
696
697 $assTitle = \ilExAssignment::lookupTitle($this->assignment->getId());
698 $deliverFilename = str_replace(" ", "_", $assTitle);
699 if ($user_id > 0 && !$multi_user) {
701 $deliverFilename .= "_" . $userName["lastname"] . "_" . $userName["firstname"];
702 } else {
703 $deliverFilename .= "_files";
704 }
705 $orgDeliverFilename = trim($deliverFilename);
706 $deliverFilename = \ilFileUtils::getASCIIFilename($orgDeliverFilename);
707
708 //copy all files to a temporary directory and remove them afterwards
709 $parsed_files = $duplicates = array();
710 foreach ($filenames as $files) {
711
712 foreach ($files as $file) {
713 // peer review masked filenames, see deliverReturnedFiles()
714 if (isset($file["tgt"])) {
715 $newFilename = $file["tgt"];
716 $filename = $file["src"];
717 } else {
718 $late = $file["late"];
719 $filename = $file["src"];
720
721 // remove timestamp
722 $newFilename = trim($filename);
723 $pos = strpos($newFilename, "_");
724 if ($pos !== false) {
725 $newFilename = substr($newFilename, $pos + 1);
726 }
727 // #11070
728 $chkName = strtolower($newFilename);
729 if (array_key_exists($chkName, $duplicates)) {
730 $suffix = strrpos($newFilename, ".");
731 $newFilename = substr($newFilename, 0, $suffix) .
732 " (" . (++$duplicates[$chkName]) . ")" .
733 substr($newFilename, $suffix);
734 } else {
735 $duplicates[$chkName] = 1;
736 }
737
738 if ($late) {
739 $newFilename = $lng->txt("exc_late_submission") . " - " .
740 $newFilename;
741 }
742 }
743
744 $newFilename = \ilFileUtils::getASCIIFilename($newFilename);
745 $newFilename = $deliverFilename . DIRECTORY_SEPARATOR . $newFilename;
746 $zip->addStream(
747 $this->repo->getStream(
748 $this->ass_id,
749 $file["rid"]
750 ),
751 $newFilename
752 );
753
754 /*
755 $parsed_files[] = ilShellUtil::escapeShellArg(
756 $deliverFilename . DIRECTORY_SEPARATOR . basename($newFilename)
757 );*/
758 }
759 }
760
761 // todo: move to gui
762 $http_util = $DIC->exercise()->internal()->gui()->httpUtil();
763 $http_util->deliverStream(
764 $zip->get(),
765 $orgDeliverFilename . ".zip",
766 "application/zip"
767 );
768 }
769
770}
$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