ILIAS  trunk Revision v11.0_alpha-1713-gd8962da2f67
All Data Structures Namespaces Files Functions Variables Enumerations Enumerator Modules Pages
SubmissionManager.php
Go to the documentation of this file.
1 <?php
2 
19 declare(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(
37  InternalRepoService $repo,
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(), [
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,
80  $team_id,
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,
90  $user_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(),
190  $user_id
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,
216  $user_id
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(),
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,
247  $filename
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,
265  $user_id
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(),
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,
296  $filename
297  );
298  }
299 
300  return $success;
301  }
302 
303  protected function remainingFilesAllowed(int $user_id): int
304  {
305  $submission = new \ilExSubmission(
306  $this->assignment,
307  $user_id
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,
324  $user_id
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(),
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,
354  $filename
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 .
408  ILIAS_WEB_DIR .
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();
511  $log = $this->log;
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  {
636  $userName = \ilObjUser::_lookupName($user_id);
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) {
700  $userName = \ilObjUser::_lookupName($user_id);
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 }
const IL_CAL_DATETIME
getAllSubmissionIdsOfUser(int $user_id)
All, include print, include all from team.
deleteSubmissions(int $user_id, array $ids)
Delete submissions.
deliverFile(int $user_id, string $rid, string $filetitle="")
static lookupTitle(int $a_id)
addZipUploads(int $user_id, UploadResult $result)
static _lookupName(int $a_user_id)
lookup user name
getSubmissionsOfOwners(array $user_ids)
This is only suitable for types like text or single upload, where no teams are used.
ILIAS Exercise Team TeamManager $team
const IL_CAL_UNIX
static makeDirParents(string $a_dir)
Create a new directory and all parent directories.
__construct(InternalRepoService $repo, protected InternalDomainService $domain, protected \ilExcSubmissionStakeholder $stakeholder, protected int $ass_id)
static _lookupObjId(int $ref_id)
static getASCIIFilename(string $a_filename)
while($session_entry=$r->fetchRow(ilDBConstants::FETCHMODE_ASSOC)) return null
copySubmissionsToDir(array $user_ids, string $directory)
Should be replaced by writing into a zip directly in the future.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
getDirectoryNameFromUserData(int $user_id)
there is still a version in ilExSubmission that needs to be replaced
const CLIENT_ID
Definition: constants.php:41
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.
global $DIC
Definition: shib_login.php:22
static getAssignmentTeamMap(int $a_ass_id)
$filename
Definition: buildRTE.php:78
addUpload(int $user_id, UploadResult $result, string $filename="")
copyRidToWebDir(string $rid, string $internal_file_path)
global $lng
Definition: privfeed.php:31
getUsersWithSubmission()
Get all user ids, that have submitted something.
static _lookupType(int $id, bool $reference=false)
addLocalFile(int $user_id, string $file, string $filename="")
const ILIAS_WEB_DIR
Definition: constants.php:45