ILIAS  trunk Revision v11.0_alpha-3011-gc6b235a2e85
class.assFileUpload.php
Go to the documentation of this file.
1<?php
2
19declare(strict_types=1);
20
26
39{
40 public const REUSE_FILES_TBL_POSTVAR = 'reusefiles';
41 public const DELETE_FILES_TBL_POSTVAR = 'deletefiles';
42
43 protected const HAS_SPECIFIC_FEEDBACK = false;
44
46 private \ILIAS\ResourceStorage\Services $irss;
47 private \ILIAS\FileDelivery\Services $file_delivery;
48 private \ILIAS\FileUpload\FileUpload $file_upload;
49
50 protected ?int $maxsize = null;
51
52 protected string $allowedextensions = '';
53
54 private ?string $current_cmd;
55
57 protected $completion_by_submission = false;
58
72 public function __construct(
73 string $title = '',
74 string $comment = '',
75 string $author = '',
76 int $owner = -1,
77 string $question = ''
78 ) {
81 global $DIC;
82 $this->irss = $DIC->resourceStorage();
83 $this->file_delivery = $DIC->fileDelivery();
84 $this->file_upload = $DIC['upload'];
85 $this->current_cmd = $DIC['ilCtrl']->getCmd();
86 $local_dic = QuestionPoolDIC::dic();
87 $this->participant_repository = $local_dic['participant_repository'];
88 }
89
95 public function isComplete(): bool
96 {
97 if (
98 strlen($this->title)
99 && ($this->author)
100 && ($this->question)
101 && ($this->getMaximumPoints() >= 0)
102 && is_numeric($this->getMaximumPoints())) {
103 return true;
104 }
105 return false;
106 }
107
111 public function saveToDb(?int $original_id = null): void
112 {
115 parent::saveToDb();
116 }
117
119 {
120 $this->db->manipulateF(
121 'DELETE FROM ' . $this->getAdditionalTableName() . ' WHERE question_fi = %s',
122 ['integer'],
123 [$this->getId()]
124 );
125 $this->db->manipulateF(
126 'INSERT INTO ' . $this->getAdditionalTableName(
127 ) . ' (question_fi, maxsize, allowedextensions, compl_by_submission) VALUES (%s, %s, %s, %s)',
128 ['integer', 'float', 'text', 'integer' ],
129 [
130 $this->getId(),
131 $this->getMaxSize(),
132 (strlen($this->getAllowedExtensions())) ? $this->getAllowedExtensions() : null,
134 ]
135 );
136 }
137
138 public function loadFromDb(int $question_id): void
139 {
140
141 $result = $this->db->queryF(
142 'SELECT qpl_questions.*, ' . $this->getAdditionalTableName()
143 . '.* FROM qpl_questions LEFT JOIN ' . $this->getAdditionalTableName()
144 . ' ON ' . $this->getAdditionalTableName()
145 . '.question_fi = qpl_questions.question_id WHERE qpl_questions.question_id = %s',
146 ['integer'],
147 [$question_id]
148 );
149 if ($result->numRows() == 1) {
150 $data = $this->db->fetchAssoc($result);
151 $this->setId($question_id);
152 $this->setTitle((string) $data['title']);
153 $this->setComment((string) $data['description']);
154 $this->setNrOfTries($data['nr_of_tries']);
155 $this->setOriginalId($data['original_id']);
156 $this->setObjId($data['obj_fi']);
157 $this->setAuthor($data['author']);
158 $this->setOwner($data['owner']);
159 $this->setPoints($data['points']);
160
161 $this->setQuestion(ilRTE::_replaceMediaObjectImageSrc((string) $data['question_text'], 1));
162 $this->setMaxSize(($data['maxsize'] ?? null) ? (int) $data['maxsize'] : null);
163 $this->setAllowedExtensions($data['allowedextensions'] ?? '');
164 $this->setCompletionBySubmission($data['compl_by_submission'] == 1 ? true : false);
165
166 try {
167 $this->setLifecycle(ilAssQuestionLifecycle::getInstance($data['lifecycle']));
170 }
171
172 try {
173 $this->setAdditionalContentEditingMode($data['add_cont_edit_mode']);
175 }
176 }
177 parent::loadFromDb($question_id);
178 }
179
180 public function getMaximumPoints(): float
181 {
182 return $this->getPoints();
183 }
184
185 public function calculateReachedPoints(
186 int $active_id,
187 ?int $pass = null,
188 bool $authorized_solution = true
189 ): float {
191 return 0.0;
192 }
193
194 if ($pass === null) {
195 $pass = $this->getSolutionMaxPass($active_id);
196 }
197
198 $result = $this->getCurrentSolutionResultSet($active_id, $pass, $authorized_solution);
199
200 while ($data = $this->db->fetchAssoc($result)) {
201 if ($this->isDummySolutionRecord($data)) {
202 continue;
203 }
204
205 return $this->getPoints();
206 }
207
208 return 0.0;
209 }
210
211 protected function calculateReachedPointsForSolution(?array $user_solution): float
212 {
213 if ($this->isCompletionBySubmissionEnabled()
214 && is_array($user_solution)
215 && $user_solution !== []) {
216 return $this->getPoints();
217 }
218
219 return 0.0;
220 }
221
227 public function checkUpload(): bool
228 {
229 $this->lng->loadLanguageModule('form');
230
231 foreach (
232 $this->file_upload->getResults() as $upload_result
233 ) { // only one supported at the moment, but we check all
234 if (!$upload_result->isOK()) {
235 $this->tpl->setOnScreenMessage('failure', $upload_result->getStatus()->getMessage(), true);
236 return false;
237 }
238
239 // check file size
240 $size_bytes = $upload_result->getSize();
241 if ($size_bytes > $this->getMaxFilesizeInBytes()) {
242 $this->tpl->setOnScreenMessage('failure', $this->lng->txt('form_msg_file_size_exceeds'), true);
243 return false;
244 }
245
246 // check suffixes
247 if (count($this->getAllowedExtensionsArray())) {
248 $filename_arr = pathinfo($upload_result->getName());
249 $suffix = $filename_arr['extension'] ?? '';
250 $mimetype = $upload_result->getMimeType();
251 if ($suffix === '') {
252 $this->tpl->setOnScreenMessage('failure', $this->lng->txt('form_msg_file_missing_file_ext'), true);
253 return false;
254 }
255
256 if (!in_array(strtolower($suffix), $this->getAllowedExtensionsArray(), true)) {
257 $this->tpl->setOnScreenMessage('failure', $this->lng->txt('form_msg_file_wrong_file_type'), true);
258 return false;
259 }
260 }
261 // virus handling already done in upload-service
262 }
263 return true;
264 }
265
269 public function getFileUploadPath($test_id, $active_id, $question_id = null): string
270 {
271 if (is_null($question_id)) {
272 $question_id = $this->getId();
273 }
274 return CLIENT_WEB_DIR . "/assessment/tst_{$test_id}/{$active_id}/{$question_id}/files/";
275 }
276
280 protected function getPreviewFileUploadPath($userId): string
281 {
282 return CLIENT_WEB_DIR . "/assessment/qst_preview/{$userId}/{$this->getId()}/fileuploads/";
283 }
284
290 public function getFileUploadPathWeb($test_id, $active_id, $question_id = null)
291 {
292 if (is_null($question_id)) {
293 $question_id = $this->getId();
294 }
296 . "/assessment/tst_{$test_id}/{$active_id}/{$question_id}/files/";
297 return str_replace(
300 $webdir
301 );
302 }
303
304 protected function getPreviewFileUploadPathWeb(int $user_id): string
305 {
307 . "/assessment/qst_preview/{$user_id}/{$this->getId()}/fileuploads/";
308 return str_replace(
311 $webdir
312 );
313 }
314
315 public function getUploadedFiles(
316 int $active_id,
317 ?int $pass = null,
318 bool $authorized = true
319 ): array {
320 if (is_null($pass)) {
321 $pass = $this->getSolutionMaxPass($active_id);
322 }
323 // fau: testNav - check existing value1 because the intermediate solution will have a dummy entry
324 $result = $this->db->queryF(
325 'SELECT * FROM tst_solutions WHERE active_fi = %s '
326 . 'AND question_fi = %s AND pass = %s AND authorized = %s '
327 . 'AND value1 IS NOT NULL ORDER BY tstamp',
328 ['integer', 'integer', 'integer', 'integer'],
329 [$active_id, $this->getId(), $pass, (int) $authorized]
330 );
331 // fau.
332 $found = [];
333
334 while ($data = $this->db->fetchAssoc($result)) {
335 array_push($found, $data);
336 }
337
338 return $found;
339 }
340
341 public function getPreviewFileUploads(ilAssQuestionPreviewSession $previewSession): array
342 {
343 if ($previewSession->getParticipantsSolution() === false || $previewSession->getParticipantsSolution() === null) {
344 return [];
345 }
346
347 return $previewSession->getParticipantsSolution();
348 }
349
355 public function getUploadedFilesForWeb($active_id, $pass): array
356 {
357 $found = $this->getUploadedFiles($active_id, $pass);
358 $result = $this->db->queryF(
359 'SELECT test_fi FROM tst_active WHERE active_id = %s',
360 ['integer'],
361 [$active_id]
362 );
363 if ($result->numRows() == 1) {
364 $row = $this->db->fetchAssoc($result);
365 $test_id = $row['test_fi'];
366 $path = $this->getFileUploadPathWeb($test_id, $active_id);
367 foreach ($found as $idx => $data) {
368 // depending on whether the files are already stored in the IRSS or not, the files are compiled differently here.
369 // this can be removed with ILIAs 10 and switched exclusively to the IRSS variant.
370 // We recommend then to revise the whole handling of files
371
372 if ($data['value2'] === 'rid') {
373 $rid = $this->irss->manage()->find($data['value1']);
374 if ($rid === null) {
375 continue;
376 }
377 $revision = $this->irss->manage()->getCurrentRevision($rid);
378 $stream = $this->irss->consume()->stream($rid)->getStream();
379 $url = $this->file_delivery->buildTokenURL(
380 $stream,
381 $revision->getTitle(),
382 Disposition::ATTACHMENT,
383 $this->current_user->getId(),
384 1
385 );
386
387 $path = (string) $url;
388 $found[$idx]['webpath'] = $path;
389 $found[$idx]['value2'] = $revision->getTitle();
390 } else {
391 $found[$idx]['webpath'] = $path;
392 }
393 }
394 }
395 return $found;
396 }
397
398 // fau: testNav new function deleteUnusedFiles()
405 protected function deleteUnusedFiles(array $rids_to_delete, $test_id, $active_id, $pass): void
406 {
407 // Remove Resources from IRSS
408 if ($rids_to_delete !== []) {
409 foreach ($rids_to_delete as $rid_to_delete) {
410 $rid_to_delete = $this->irss->manage()->find($rid_to_delete);
411 if ($rid_to_delete === null) {
412 continue;
413 }
414 $this->irss->manage()->remove(
415 $rid_to_delete,
417 );
418 }
419 }
420
421 // Legacy implementation for not yet migrated files
422
423 // read all solutions (authorized and intermediate) from all steps
424 $step = $this->getStep();
425 $this->setStep(null);
426 $solutions = array_merge(
427 $this->getSolutionValues($active_id, $pass, true),
428 $this->getSolutionValues($active_id, $pass, false)
429 );
430 $this->setStep($step);
431
432 // get the used files from these solutions
433 $used_files = [];
434 foreach ($solutions as $solution) {
435 $used_files[] = $solution['value1'];
436 }
437
438 // read the existing files for user and pass
439 // delete all files that are not used in the solutions
440 $uploadPath = $this->getFileUploadPath($test_id, $active_id);
441 if (is_dir($uploadPath) && is_readable($uploadPath)) {
442 $iter = new \RegexIterator(new \DirectoryIterator($uploadPath), '/^file_' . $active_id . '_' . $pass . '_(.*)/');
443 foreach ($iter as $file) {
445 if ($file->isFile() && !in_array($file->getFilename(), $used_files)) {
446 unlink($file->getPathname());
447 }
448 }
449 }
450 }
451 // fau.
452
453 protected function deletePreviewFileUploads($userId, $userSolution, $files)
454 {
455 foreach ($files as $name) {
456 if (isset($userSolution[$name])) {
457 unset($userSolution[$name]);
458 @unlink($this->getPreviewFileUploadPath($userId) . $name);
459 }
460 }
461
462 return $userSolution;
463 }
464
465 public function getMaxFilesizeAsString(): string
466 {
467 $size = $this->getMaxFilesizeInBytes();
468 if ($size < 1024) {
469 return sprintf('%d Bytes', $size);
470 }
471
472 if ($size < 1024 * 1024) {
473 return sprintf('%.1f KB', $size / 1024);
474 }
475
476 return sprintf('%.1f MB', $size / 1024 / 1024);
477 }
478
479 protected function getMaxFilesizeInBytes(): int
480 {
481 if ($this->getMaxSize() > 0) {
482 return $this->getMaxSize();
483 }
484
485 return $this->determineMaxFilesize();
486 }
487
488
489 public function determineMaxFilesize(): int
490 {
491 $upload_max_filesize = ini_get('upload_max_filesize');
492 $post_max_size = ini_get('post_max_size');
493
494 //convert from short-string representation to "real" bytes
495 $multiplier_a = [ "K" => 1024, "M" => 1024 * 1024, "G" => 1024 * 1024 * 1024 ];
496 $umf_parts = preg_split(
497 "/(\d+)([K|G|M])/",
498 $upload_max_filesize,
499 -1,
500 PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY
501 );
502 $pms_parts = preg_split(
503 "/(\d+)([K|G|M])/",
504 $post_max_size,
505 -1,
506 PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY
507 );
508
509 if (count($umf_parts) === 2) {
510 $upload_max_filesize = $umf_parts[0] * $multiplier_a[$umf_parts[1]];
511 }
512
513 if (count($pms_parts) === 2) {
514 $post_max_size = $pms_parts[0] * $multiplier_a[$pms_parts[1]];
515 }
516
517 // use the smaller one as limit
518 $max_filesize = min($upload_max_filesize, $post_max_size);
519
520 if (!$max_filesize) {
521 $max_filesize = max($upload_max_filesize, $post_max_size);
522 return $max_filesize;
523 }
524
525 return $max_filesize;
526 }
527
528 public function saveWorkingData(
529 int $active_id,
530 ?int $pass = null,
531 bool $authorized = true
532 ): bool {
533 if ($pass === null || $pass < 0) {
534 $pass = \ilObjTest::_getPass($active_id);
535 }
536
537 $test_id = $this->participant_repository->lookupTestIdByActiveId($active_id);
538
539 try {
540 $upload_handling_required = $this->current_cmd !== 'submitSolution'
541 && $this->current_cmd !== 'showInstantResponse'
542 && !$this->isFileDeletionAction()
543 && $this->isFileUploadAvailable()
544 && $this->checkUpload();
545 } catch (IllegalStateException $e) {
546 $this->tpl->setOnScreenMessage('failure', $e->getMessage(), true);
547 return false;
548 }
549
550 $rid = null;
551
552 if ($upload_handling_required) {
553 // upload new file to storage
554 $upload_results = $this->file_upload->getResults();
555 $upload_result = end($upload_results); // only one supported at the moment
556 $rid = $this->irss->manage()->upload(
557 $upload_result,
559 );
560 }
561
562 // RIDS to delete
563 // Unfortunately, at the moment it is not possible to delete the files from the IRSS, because the process takes
564 // place within the ProcessLocker and the IRSS tables cannot be used. we have to remove them after the lock.
565 // therefore we store the rids to delete in an array for later deletion.
566 $rids_to_delete = $this->resolveRIDStoDelete();
567
568 $this->getProcessLocker()->executeUserSolutionUpdateLockOperation(
569 function () use ($upload_handling_required, $active_id, $pass, $authorized, $rid) {
570 if ($authorized === false) {
571 $this->forceExistingIntermediateSolution($active_id, $pass, true);
572 }
573
574 if ($this->isFileDeletionAction()) {
575 if ($this->isFileDeletionSubmitAvailable()) {
576 $delete_files = $this->questionpool_request->intArray(self::DELETE_FILES_TBL_POSTVAR);
577
578 foreach ($delete_files as $solution_id) {
579 $this->removeSolutionRecordById($solution_id);
580 }
581 } else {
582 $this->tpl->setOnScreenMessage('info', $this->lng->txt('no_checkbox'), true);
583 }
584 } else {
585 if ($this->isFileReuseHandlingRequired()) {
586 $reuse_files = $this->questionpool_request->intArray(self::REUSE_FILES_TBL_POSTVAR);
587
588 foreach ($reuse_files as $solutionId) {
589 $solution = $this->getSolutionRecordById($solutionId);
590
591 $this->saveCurrentSolution(
592 $active_id,
593 $pass,
594 $solution['value1'],
595 $solution['value2'],
596 false,
597 $solution['tstamp']
598 );
599 }
600 }
601
602 if ($upload_handling_required && $rid !== null) {
603 $revision = $this->irss->manage()->getCurrentRevision($rid);
604 $this->saveCurrentSolution(
605 $active_id,
606 $pass,
607 $rid->serialize(),
608 'rid',
609 false,
610 time()
611 );
612 }
613 }
614
615 if ($authorized === true && $this->intermediateSolutionExists($active_id, $pass)) {
616 // remove the dummy record of the intermediate solution
617 $this->deleteDummySolutionRecord($active_id, $pass);
618
619 // delete the authorized solution and make the intermediate solution authorized (keeping timestamps)
620 $this->removeCurrentSolution($active_id, $pass, true);
621 $this->updateCurrentSolutionsAuthorization($active_id, $pass, true, true);
622 }
623
624 }
625 );
626
627 $this->deleteUnusedFiles(
628 $rids_to_delete,
629 $test_id,
630 $active_id,
631 $pass
632 );
633
634 return true;
635 }
636
637 protected function resolveRIDStoDelete(): array
638 {
639 $rids_to_delete = [];
640 if ($this->isFileDeletionAction() && $this->isFileDeletionSubmitAvailable()) {
641 $delete_files = $this->questionpool_request->intArray(self::DELETE_FILES_TBL_POSTVAR);
642
643 $res = $this->db->query(
644 "SELECT value1 FROM tst_solutions WHERE value2 = 'rid' AND " . $this->db->in(
645 'solution_id',
646 $delete_files,
647 false,
648 'integer'
649 )
650 );
651 while ($d = $this->db->fetchAssoc($res)) {
652 $rids_to_delete[] = $d['value1'];
653 }
654 }
655 return $rids_to_delete;
656 }
657
658 protected function removeSolutionRecordById(int $solution_id): int
659 {
660 return parent::removeSolutionRecordById($solution_id);
661 }
662
664 int $active_id,
665 ?int $pass = null
666 ): array {
667 $solution = $this->getSolutionValues($active_id, $pass, false);
668
669 if (!count($solution)) {
670 $solution = $this->getSolutionValues($active_id, $pass, true);
671 } else {
672 $cleaned = [];
673 foreach ($solution as $row) {
674 if (!empty($row['value1'])) {
675 $cleaned[] = $row;
676 }
677 }
678 $solution = $cleaned;
679 }
680
681 return $solution;
682 }
683
684 public function removeIntermediateSolution(int $active_id, int $pass): void
685 {
686 parent::removeIntermediateSolution($active_id, $pass);
687
688 $test_id = $this->participant_repository->lookupTestIdByActiveId($active_id);
689 if ($test_id !== -1) {
690 // TODO: This can be removed with ILIAS 10
691 $this->deleteUnusedFiles([], $test_id, $active_id, $pass);
692 }
693 }
694
695
696 protected function savePreviewData(ilAssQuestionPreviewSession $previewSession): void
697 {
698 $userSolution = $previewSession->getParticipantsSolution();
699
700 if (!is_array($userSolution)) {
701 $userSolution = [];
702 }
703
704 // hey: prevPassSolutions - readability spree - get a chance to understand the code
705 if ($this->isFileDeletionAction()) {
706 // hey.
707 // hey: prevPassSolutions - readability spree - get a chance to understand the code
708 if ($this->isFileDeletionSubmitAvailable()) {
709 // hey.
710 $delete_files = $this->questionpool_request->strArray(self::DELETE_FILES_TBL_POSTVAR);
711
712 $userSolution = $this->deletePreviewFileUploads($previewSession->getUserId(), $userSolution, $delete_files);
713 } else {
714 $this->tpl->setOnScreenMessage('info', $this->lng->txt('no_checkbox'), true);
715 }
716 } else {
717 // hey: prevPassSolutions - readability spree - get a chance to understand the code
718 try {
719 $fileUploadAvailable = $this->current_cmd !== 'instantResponse'
720 && $this->isFileUploadAvailable();
721 } catch (IllegalStateException $e) {
722 $this->tpl->setOnScreenMessage('failure', $e->getMessage(), true);
723 return;
724 }
725 if ($fileUploadAvailable) {
726 // hey.
727 if ($this->checkUpload()) {
728 if (!@file_exists($this->getPreviewFileUploadPath($previewSession->getUserId()))) {
729 ilFileUtils::makeDirParents($this->getPreviewFileUploadPath($previewSession->getUserId()));
730 }
731
732 $version = time();
733 $filename_arr = pathinfo($_FILES['upload']['name']);
734 $extension = $filename_arr['extension'];
735 $newfile = 'file_' . md5($_FILES['upload']['name']) . '_' . $version . '.' . $extension;
737 $_FILES['upload']['tmp_name'],
738 $_FILES['upload']['name'],
739 $this->getPreviewFileUploadPath($previewSession->getUserId()) . $newfile
740 );
741
742 $userSolution[$newfile] = [
743 'solution_id' => $newfile,
744 'value1' => $newfile,
745 'value2' => $_FILES['upload']['name'],
746 'tstamp' => $version,
747 'webpath' => $this->getPreviewFileUploadPathWeb($previewSession->getUserId())
748 ];
749 }
750 }
751 }
752
753 $previewSession->setParticipantsSolution($userSolution);
754 }
755
756 public function getQuestionType(): string
757 {
758 return 'assFileUpload';
759 }
760
761 public function getAdditionalTableName(): string
762 {
763 return 'qpl_qst_fileupload';
764 }
765
766 public function getAnswerTableName(): string
767 {
768 return '';
769 }
770
774 public function deleteAnswers($question_id): void
775 {
776 }
777
782 public function getRTETextWithMediaObjects(): string
783 {
784 return parent::getRTETextWithMediaObjects();
785 }
786
787 public function getBestSolution($active_id, $pass): array
788 {
789 $user_solution = [];
790 return $user_solution;
791 }
792
793 public function getMaxSize(): ?int
794 {
795 return $this->maxsize;
796 }
797
798 public function setMaxSize(?int $value): void
799 {
800 $this->maxsize = $value;
801 }
802
803 public function getAllowedExtensionsArray(): array
804 {
805 if ($this->allowedextensions === '') {
806 return [];
807 }
808
809 return array_filter(array_map('trim', explode(',', $this->allowedextensions)));
810 }
811
812 public function getAllowedExtensions(): string
813 {
814 return $this->allowedextensions;
815 }
816
817 public function setAllowedExtensions(string $a_value): void
818 {
819 $this->allowedextensions = strtolower(trim($a_value));
820 }
821
822 public function hasFileUploads(int $test_id): bool
823 {
824 $query = '
825 SELECT tst_solutions.solution_id
826 FROM tst_solutions, tst_active, qpl_questions
827 WHERE tst_solutions.active_fi = tst_active.active_id
828 AND tst_solutions.question_fi = qpl_questions.question_id
829 AND tst_solutions.question_fi = %s AND tst_active.test_fi = %s
830 AND tst_solutions.value1 is not null';
831 $result = $this->db->queryF(
832 $query,
833 ['integer', 'integer'],
834 [$this->getId(), $test_id]
835 );
836 if ($result->numRows() > 0) {
837 return true;
838 }
839
840 return false;
841 }
842
843 public function deliverFileUploadZIPFile(int $ref_id, int $test_id, string $test_title): void
844 {
845 $exporter = new ilAssFileUploadUploadsExporter(
846 $this->db,
847 $this->lng,
848 $ref_id,
849 $test_id
850 );
851
852 $exporter->setTestTitle($test_title);
853 $exporter->setQuestion($this);
854
855 $exporter->buildAndDownload();
856 }
857
858 public function isCompletionBySubmissionEnabled(): bool
859 {
860 return $this->completion_by_submission;
861 }
862
863 public function setCompletionBySubmission(bool $bool): assFileUpload
864 {
865 $this->completion_by_submission = (bool) $bool;
866 return $this;
867 }
868
870 {
871 return parent::buildTestPresentationConfig()
873 }
874
875 protected function isFileDeletionAction(): bool
876 {
877 return $this->getQuestionAction() == assFileUploadGUI::DELETE_FILES_ACTION;
878 }
879
880 protected function isFileDeletionSubmitAvailable(): bool
881 {
882 return $this->isNonEmptyItemListPostSubmission(self::DELETE_FILES_TBL_POSTVAR);
883 }
884
885 protected function isFileReuseSubmitAvailable(): bool
886 {
887 return $this->isNonEmptyItemListPostSubmission(self::REUSE_FILES_TBL_POSTVAR);
888 }
889
890 protected function isFileReuseHandlingRequired(): bool
891 {
892 if (!$this->getTestPresentationConfig()->isPreviousPassSolutionReuseAllowed()) {
893 return false;
894 }
895
896 if (!$this->isFileReuseSubmitAvailable()) {
897 return false;
898 }
899
900 return true;
901 }
902
906 protected function isFileUploadAvailable(): bool
907 {
908 if (!$this->file_upload->hasBeenProcessed()) {
909 $this->file_upload->process();
910 }
911 return $this->file_upload->hasUploads();
912 }
913
914 public function toLog(AdditionalInformationGenerator $additional_info): array
915 {
916 return [
917 AdditionalInformationGenerator::KEY_QUESTION_TYPE => (string) $this->getQuestionType(),
918 AdditionalInformationGenerator::KEY_QUESTION_TITLE => $this->getTitleForHTMLOutput(),
919 AdditionalInformationGenerator::KEY_QUESTION_TEXT => $this->formatSAQuestion($this->getQuestion()),
920 AdditionalInformationGenerator::KEY_QUESTION_REACHABLE_POINTS => $this->getPoints(),
921 AdditionalInformationGenerator::KEY_QUESTION_UPLOAD_MAXSIZE => $this->getMaxFilesizeAsString(),
922 AdditionalInformationGenerator::KEY_QUESTION_UPLOAD_ALLOWED_EXTENSIONS => $this->getAllowedExtensionsArray(),
923 AdditionalInformationGenerator::KEY_QUESTION_UPLOAD_COMPLETION_BY_SUBMISSION => $additional_info->getEnabledDisabledTagForBool($this->isCompletionBySubmissionEnabled()),
924 AdditionalInformationGenerator::KEY_FEEDBACK => [
925 AdditionalInformationGenerator::KEY_QUESTION_FEEDBACK_ON_INCOMPLETE => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(), false)),
926 AdditionalInformationGenerator::KEY_QUESTION_FEEDBACK_ON_COMPLETE => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(), true))
927 ]
928 ];
929 }
930
931 protected function solutionValuesToLog(
932 AdditionalInformationGenerator $additional_info,
933 array $solution_values
934 ): array {
935 return array_map(
936 static fn(array $v): string => "{$v['value1']} - {$v['value2']}",
937 $solution_values
938 );
939 }
940
941 public function solutionValuesToText(array $solution_values): array
942 {
943 return array_map(
944 static fn(array $v): string => "{$v['value1']} - {$v['value2']}",
945 $solution_values
946 );
947 }
948
949 public function getCorrectSolutionForTextOutput(int $active_id, int $pass): string
950 {
951 return '';
952 }
953}
$version
Definition: plugin.php:24
Class for file upload questions.
ParticipantRepository $participant_repository
toLog(AdditionalInformationGenerator $additional_info)
MUST return an array of the question settings that can be stored in the log.
deleteAnswers($question_id)
setCompletionBySubmission(bool $bool)
setAllowedExtensions(string $a_value)
solutionValuesToLog(AdditionalInformationGenerator $additional_info, array $solution_values)
MUST convert the given solution values into an array or a string that can be stored in the log.
ILIAS FileDelivery Services $file_delivery
ILIAS ResourceStorage Services $irss
calculateReachedPoints(int $active_id, ?int $pass=null, bool $authorized_solution=true)
saveToDb(?int $original_id=null)
Saves a assFileUpload object to a database.
getFileUploadPathWeb($test_id, $active_id, $question_id=null)
Returns the file upload path for web accessible files of a question.
getUploadedFiles(int $active_id, ?int $pass=null, bool $authorized=true)
deletePreviewFileUploads($userId, $userSolution, $files)
getRTETextWithMediaObjects()
Collects all text in the question which could contain media objects which were created with the Rich ...
solutionValuesToText(array $solution_values)
MUST convert the given solution values into text.
saveWorkingData(int $active_id, ?int $pass=null, bool $authorized=true)
deliverFileUploadZIPFile(int $ref_id, int $test_id, string $test_title)
removeSolutionRecordById(int $solution_id)
saveAdditionalQuestionDataToDb()
Saves a record to the question types additional data table.
getCorrectSolutionForTextOutput(int $active_id, int $pass)
removeIntermediateSolution(int $active_id, int $pass)
setMaxSize(?int $value)
getBestSolution($active_id, $pass)
getPreviewFileUploadPath($userId)
Returns the filesystem path for file uploads.
isComplete()
Returns true, if the question is complete for use.
hasFileUploads(int $test_id)
getPreviewFileUploadPathWeb(int $user_id)
calculateReachedPointsForSolution(?array $user_solution)
getFileUploadPath($test_id, $active_id, $question_id=null)
Returns the filesystem path for file uploads.
ILIAS FileUpload FileUpload $file_upload
savePreviewData(ilAssQuestionPreviewSession $previewSession)
getPreviewFileUploads(ilAssQuestionPreviewSession $previewSession)
getUserSolutionPreferingIntermediate(int $active_id, ?int $pass=null)
loadFromDb(int $question_id)
getUploadedFilesForWeb($active_id, $pass)
Returns the web accessible uploaded files for an active user in a given pass.
checkUpload()
Check file upload.
setOriginalId(?int $original_id)
setId(int $id=-1)
setAdditionalContentEditingMode(?string $additionalContentEditingMode)
setQuestion(string $question="")
getCurrentSolutionResultSet(int $active_id, int $pass, bool $authorized=true)
setAuthor(string $author="")
setComment(string $comment="")
setObjId(int $obj_id=0)
getSolutionMaxPass(int $active_id)
setOwner(int $owner=-1)
setNrOfTries(int $a_nr_of_tries)
setLifecycle(ilAssQuestionLifecycle $lifecycle)
setTitle(string $title="")
isDummySolutionRecord(array $solutionRecord)
saveQuestionDataToDb(?int $original_id=null)
setPoints(float $points)
static makeDirParents(string $a_dir)
Create a new directory and all parent directories.
static removeTrailingPathSeparators(string $path)
static moveUploadedFile(string $a_file, string $a_name, string $a_target, bool $a_raise_errors=true, string $a_mode="move_uploaded")
move uploaded file
static _getPass($active_id)
Retrieves the actual pass of a given user for a given test.
static _replaceMediaObjectImageSrc(string $a_text, int $a_direction=0, string $nic='')
Replaces image source from mob image urls with the mob id or replaces mob id with the correct image s...
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
setFormChangeDetectionEnabled($enableFormChangeDetection)
Set if the detection of form changes is enabled.
const CLIENT_WEB_DIR
Definition: constants.php:47
return['delivery_method'=> 'php',]
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
$ref_id
Definition: ltiauth.php:66
$path
Definition: ltiservices.php:30
$res
Definition: ltiservices.php:69
__construct(Container $dic, ilPlugin $plugin)
@inheritDoc
if(!file_exists('../ilias.ini.php'))
global $DIC
Definition: shib_login.php:26
PREG_SPLIT_NO_EMPTY PREG_SPLIT_DELIM_CAPTURE
$url
Definition: shib_logout.php:68