ILIAS  release_8 Revision v8.24
class.assFileUpload.php
Go to the documentation of this file.
1<?php
2
20
21require_once './Modules/Test/classes/inc.AssessmentConstants.php';
22
35{
36 // hey: prevPassSolutions - support reusing selected files
37 public const REUSE_FILES_TBL_POSTVAR = 'reusefiles';
38 public const DELETE_FILES_TBL_POSTVAR = 'deletefiles';
39 // hey.
40
41 protected const HAS_SPECIFIC_FEEDBACK = false;
42
43 protected ?int $maxsize = null;
44
46
47 private ?string $current_cmd;
48
50 protected $completion_by_submission = false;
51
52 private \ILIAS\FileUpload\FileUpload $file_upload;
53
67 public function __construct(
68 $title = "",
69 $comment = "",
70 $author = "",
71 $owner = -1,
72 $question = ""
73 ) {
76 global $DIC;
77 $this->file_upload = $DIC['upload'];
78 $this->current_cmd = $DIC['ilCtrl']->getCmd();
79 }
80
86 public function isComplete(): bool
87 {
88 if (
89 strlen($this->title)
90 && ($this->author)
91 && ($this->question)
92 && ($this->getMaximumPoints() >= 0)
93 && is_numeric($this->getMaximumPoints())) {
94 return true;
95 }
96 return false;
97 }
98
102 public function saveToDb($original_id = ""): void
103 {
104 if ($original_id == '') {
105 $this->saveQuestionDataToDb();
106 } else {
108 }
109
111 parent::saveToDb();
112 }
113
115 {
116 global $DIC;
117 $ilDB = $DIC['ilDB'];
118 $ilDB->manipulateF(
119 "DELETE FROM " . $this->getAdditionalTableName() . " WHERE question_fi = %s",
120 array( "integer" ),
121 array( $this->getId() )
122 );
123 $ilDB->manipulateF(
124 "INSERT INTO " . $this->getAdditionalTableName(
125 ) . " (question_fi, maxsize, allowedextensions, compl_by_submission) VALUES (%s, %s, %s, %s)",
126 array( "integer", "float", "text", "integer" ),
127 array(
128 $this->getId(),
129 $this->getMaxSize(),
130 (strlen($this->getAllowedExtensions())) ? $this->getAllowedExtensions() : null,
132 )
133 );
134 }
135
141 public function loadFromDb($question_id): void
142 {
143 global $DIC;
144 $ilDB = $DIC['ilDB'];
145 $result = $ilDB->queryF(
146 "SELECT qpl_questions.*, " . $this->getAdditionalTableName() . ".* FROM qpl_questions LEFT JOIN " . $this->getAdditionalTableName() . " ON " . $this->getAdditionalTableName() . ".question_fi = qpl_questions.question_id WHERE qpl_questions.question_id = %s",
147 array("integer"),
148 array($question_id)
149 );
150 if ($result->numRows() == 1) {
151 $data = $ilDB->fetchAssoc($result);
152 $this->setId($question_id);
153 $this->setTitle((string) $data["title"]);
154 $this->setComment((string) $data["description"]);
155 $this->setNrOfTries($data['nr_of_tries']);
156 $this->setOriginalId($data["original_id"]);
157 $this->setObjId($data["obj_fi"]);
158 $this->setAuthor($data["author"]);
159 $this->setOwner($data["owner"]);
160 $this->setPoints($data["points"]);
161
162 $this->setQuestion(ilRTE::_replaceMediaObjectImageSrc((string) $data["question_text"], 1));
163 $this->setMaxSize(($data["maxsize"] ?? null) ? (int) $data["maxsize"] : null);
164 $this->setAllowedExtensions($data["allowedextensions"]);
165 $this->setCompletionBySubmission($data['compl_by_submission'] == 1 ? true : false);
166
167 try {
168 $this->setLifecycle(ilAssQuestionLifecycle::getInstance($data['lifecycle']));
171 }
172
173 try {
174 $this->setAdditionalContentEditingMode($data['add_cont_edit_mode']);
176 }
177 }
178 parent::loadFromDb($question_id);
179 }
180
184 public function duplicate(bool $for_test = true, string $title = "", string $author = "", string $owner = "", $testObjId = null): int
185 {
186 if ($this->id <= 0) {
187 // The question has not been saved. It cannot be duplicated
188 return -1;
189 }
190 // duplicate the question in database
191 $this_id = $this->getId();
192 $thisObjId = $this->getObjId();
193
194 $clone = $this;
196 $clone->id = -1;
197
198 if ((int) $testObjId > 0) {
199 $clone->setObjId($testObjId);
200 }
201
202 if ($title) {
203 $clone->setTitle($title);
204 }
205
206 if ($author) {
207 $clone->setAuthor($author);
208 }
209 if ($owner) {
210 $clone->setOwner($owner);
211 }
212
213 if ($for_test) {
214 $clone->saveToDb($original_id);
215 } else {
216 $clone->saveToDb();
217 }
218
219 // copy question page content
220 $clone->copyPageOfQuestion($this_id);
221 // copy XHTML media objects
222 $clone->copyXHTMLMediaObjectsOfQuestion($this_id);
223
224 $clone->onDuplicate($thisObjId, $this_id, $clone->getObjId(), $clone->getId());
225
226 return $clone->id;
227 }
228
232 public function copyObject($target_questionpool_id, $title = ""): int
233 {
234 if ($this->getId() <= 0) {
235 throw new RuntimeException('The question has not been saved. It cannot be duplicated');
236 }
237 // duplicate the question in database
238 $clone = $this;
240 $clone->id = -1;
241 $source_questionpool_id = $this->getObjId();
242 $clone->setObjId($target_questionpool_id);
243 if ($title) {
244 $clone->setTitle($title);
245 }
246 $clone->saveToDb();
247
248 // copy question page content
249 $clone->copyPageOfQuestion($original_id);
250 // copy XHTML media objects
251 $clone->copyXHTMLMediaObjectsOfQuestion($original_id);
252
253 $clone->onCopy($source_questionpool_id, $original_id, $clone->getObjId(), $clone->getId());
254
255 return $clone->id;
256 }
257
258 public function createNewOriginalFromThisDuplicate($targetParentId, $targetQuestionTitle = ""): int
259 {
260 if ($this->getId() <= 0) {
261 throw new RuntimeException('The question has not been saved. It cannot be duplicated');
262 }
263
264 $sourceQuestionId = $this->id;
265 $sourceParentId = $this->getObjId();
266
267 // duplicate the question in database
268 $clone = $this;
269 $clone->id = -1;
270
271 $clone->setObjId($targetParentId);
272
273 if ($targetQuestionTitle) {
274 $clone->setTitle($targetQuestionTitle);
275 }
276
277 $clone->saveToDb();
278 // copy question page content
279 $clone->copyPageOfQuestion($sourceQuestionId);
280 // copy XHTML media objects
281 $clone->copyXHTMLMediaObjectsOfQuestion($sourceQuestionId);
282
283 $clone->onCopy($sourceParentId, $sourceQuestionId, $clone->getObjId(), $clone->getId());
284
285 return $clone->id;
286 }
287
293 public function getMaximumPoints(): float
294 {
295 return $this->getPoints();
296 }
297
308 public function calculateReachedPoints($active_id, $pass = null, $authorizedSolution = true, $returndetails = false)
309 {
310 if ($returndetails) {
311 throw new ilTestException('return details not implemented for ' . __METHOD__);
312 }
313
314 if ($this->isCompletionBySubmissionEnabled()) {
315 if (is_null($pass)) {
316 $pass = $this->getSolutionMaxPass($active_id);
317 }
318
319 global $DIC;
320
321 $result = $this->getCurrentSolutionResultSet($active_id, $pass, $authorizedSolution);
322
323 while ($data = $DIC->database()->fetchAssoc($result)) {
324 if ($this->isDummySolutionRecord($data)) {
325 continue;
326 }
327
328 return $this->getPoints();
329 }
330 }
331
332 return 0;
333 }
334
335 protected function calculateReachedPointsForSolution($userSolution)
336 {
337 if ($this->isCompletionBySubmissionEnabled() &&
338 is_array($userSolution) &&
339 count($userSolution)) {
340 return $this->getPoints();
341 }
342
343 return 0;
344 }
345
351 public function checkUpload(): bool
352 {
353 $this->lng->loadLanguageModule("form");
354 // remove trailing '/'
355 $_FILES["upload"]["name"] = rtrim($_FILES["upload"]["name"], '/');
356
357 $filename = $_FILES["upload"]["name"];
358 $filename_arr = pathinfo($_FILES["upload"]["name"]);
359 $suffix = $filename_arr["extension"] ?? '';
360 $mimetype = $_FILES["upload"]["type"];
361 $size_bytes = $_FILES["upload"]["size"];
362 $temp_name = $_FILES["upload"]["tmp_name"];
363 $error = $_FILES["upload"]["error"];
364
365 if ($size_bytes > $this->getMaxFilesizeInBytes()) {
366 $this->tpl->setOnScreenMessage('failure', $this->lng->txt("form_msg_file_size_exceeds"), true);
367 return false;
368 }
369
370 // error handling
371 if ($error > 0) {
372 switch ($error) {
373 case UPLOAD_ERR_FORM_SIZE:
374 case UPLOAD_ERR_INI_SIZE:
375 $this->tpl->setOnScreenMessage('failure', $this->lng->txt("form_msg_file_size_exceeds"), true);
376 return false;
377 break;
378
379 case UPLOAD_ERR_PARTIAL:
380 $this->tpl->setOnScreenMessage('failure', $this->lng->txt("form_msg_file_partially_uploaded"), true);
381 return false;
382 break;
383
384 case UPLOAD_ERR_NO_FILE:
385 $this->tpl->setOnScreenMessage('failure', $this->lng->txt("form_msg_file_no_upload"), true);
386 return false;
387 break;
388
389 case UPLOAD_ERR_NO_TMP_DIR:
390 $this->tpl->setOnScreenMessage('failure', $this->lng->txt("form_msg_file_missing_tmp_dir"), true);
391 return false;
392 break;
393
394 case UPLOAD_ERR_CANT_WRITE:
395 $this->tpl->setOnScreenMessage('failure', $this->lng->txt("form_msg_file_cannot_write_to_disk"), true);
396 return false;
397 break;
398
399 case UPLOAD_ERR_EXTENSION:
400 $this->tpl->setOnScreenMessage('failure', $this->lng->txt("form_msg_file_upload_stopped_ext"), true);
401 return false;
402 break;
403 }
404 }
405
406 // check suffixes
407 if (count($this->getAllowedExtensionsArray())) {
408 if (!strlen($suffix)) {
409 $this->tpl->setOnScreenMessage('failure', $this->lng->txt("form_msg_file_missing_file_ext"), true);
410 return false;
411 }
412
413 if (!in_array(strtolower($suffix), $this->getAllowedExtensionsArray())) {
414 $this->tpl->setOnScreenMessage('failure', $this->lng->txt("form_msg_file_wrong_file_type"), true);
415 return false;
416 }
417 }
418
419 // virus handling
420 if (strlen($temp_name)) {
421 $vir = ilVirusScanner::virusHandling($temp_name, $filename);
422 if ($vir[0] == false) {
423 $this->tpl->setOnScreenMessage('failure', $this->lng->txt("form_msg_file_virus_found") . "<br />" . $vir[1], true);
424 return false;
425 }
426 }
427 return true;
428 }
429
433 public function getFileUploadPath($test_id, $active_id, $question_id = null): string
434 {
435 if (is_null($question_id)) {
436 $question_id = $this->getId();
437 }
438 return CLIENT_WEB_DIR . "/assessment/tst_$test_id/$active_id/$question_id/files/";
439 }
440
444 protected function getPreviewFileUploadPath($userId): string
445 {
446 return CLIENT_WEB_DIR . "/assessment/qst_preview/$userId/{$this->getId()}/fileuploads/";
447 }
448
454 public function getFileUploadPathWeb($test_id, $active_id, $question_id = null)
455 {
456 if (is_null($question_id)) {
457 $question_id = $this->getId();
458 }
459 $webdir = ilFileUtils::removeTrailingPathSeparators(CLIENT_WEB_DIR) . "/assessment/tst_$test_id/$active_id/$question_id/files/";
460 return str_replace(
463 $webdir
464 );
465 }
466
470 protected function getPreviewFileUploadPathWeb($userId)
471 {
472 $webdir = ilFileUtils::removeTrailingPathSeparators(CLIENT_WEB_DIR) . "/assessment/qst_preview/$userId/{$this->getId()}/fileuploads/";
473 return str_replace(
476 $webdir
477 );
478 }
479
485 public function getUploadedFiles($active_id, $pass = null, $authorized = true): array
486 {
487 global $DIC;
488 $ilDB = $DIC['ilDB'];
489
490 if (is_null($pass)) {
491 $pass = $this->getSolutionMaxPass($active_id);
492 }
493 // fau: testNav - check existing value1 because the intermediate solution will have a dummy entry
494 $result = $ilDB->queryF(
495 "SELECT * FROM tst_solutions WHERE active_fi = %s AND question_fi = %s AND pass = %s AND authorized = %s AND value1 IS NOT NULL ORDER BY tstamp",
496 array("integer", "integer", "integer", 'integer'),
497 array($active_id, $this->getId(), $pass, (int) $authorized)
498 );
499 // fau.
500 $found = array();
501
502 while ($data = $ilDB->fetchAssoc($result)) {
503 array_push($found, $data);
504 }
505
506 return $found;
507 }
508
509 public function getPreviewFileUploads(ilAssQuestionPreviewSession $previewSession): array
510 {
511 if ($previewSession->getParticipantsSolution() === false || $previewSession->getParticipantsSolution() === null) {
512 return [];
513 }
514
515 return $previewSession->getParticipantsSolution();
516 }
517
523 public function getUploadedFilesForWeb($active_id, $pass): array
524 {
525 global $DIC;
526 $ilDB = $DIC['ilDB'];
527
528 $found = $this->getUploadedFiles($active_id, $pass);
529 $result = $ilDB->queryF(
530 "SELECT test_fi FROM tst_active WHERE active_id = %s",
531 array('integer'),
532 array($active_id)
533 );
534 if ($result->numRows() == 1) {
535 $row = $ilDB->fetchAssoc($result);
536 $test_id = $row["test_fi"];
537 $path = $this->getFileUploadPathWeb($test_id, $active_id);
538 foreach ($found as $idx => $data) {
539 $found[$idx]['webpath'] = $path;
540 }
541 }
542 return $found;
543 }
544
550 protected function deleteUploadedFiles($files, $test_id, $active_id, $authorized): void
551 {
552 global $DIC;
553 $ilDB = $DIC['ilDB'];
554
555 $pass = null;
556 $active_id = null;
557 foreach ($files as $solution_id) {
558 $result = $ilDB->queryF(
559 "SELECT * FROM tst_solutions WHERE solution_id = %s AND authorized = %s",
560 array("integer", 'integer'),
561 array($solution_id, (int) $authorized)
562 );
563 if ($result->numRows() == 1) {
564 $data = $ilDB->fetchAssoc($result);
565 $pass = $data['pass'];
566 $active_id = $data['active_fi'];
567 @unlink($this->getFileUploadPath($test_id, $active_id) . $data['value1']);
568 }
569 }
570 foreach ($files as $solution_id) {
571 $affectedRows = $ilDB->manipulateF(
572 "DELETE FROM tst_solutions WHERE solution_id = %s AND authorized = %s",
573 array("integer", 'integer'),
574 array($solution_id, $authorized)
575 );
576 }
577 }
578
579 // fau: testNav new function deleteUnusedFiles()
586 protected function deleteUnusedFiles($test_id, $active_id, $pass): void
587 {
588 // read all solutions (authorized and intermediate) from all steps
589 $step = $this->getStep();
590 $this->setStep(null);
591 $solutions = array_merge(
592 $this->getSolutionValues($active_id, $pass, true),
593 $this->getSolutionValues($active_id, $pass, false)
594 );
595 $this->setStep($step);
596
597 // get the used files from these solutions
598 $used_files = array();
599 foreach ($solutions as $solution) {
600 $used_files[] = $solution['value1'];
601 }
602
603 // read the existing files for user and pass
604 // delete all files that are not used in the solutions
605 $uploadPath = $this->getFileUploadPath($test_id, $active_id);
606 if (is_dir($uploadPath) && is_readable($uploadPath)) {
607 $iter = new \RegexIterator(new \DirectoryIterator($uploadPath), '/^file_' . $active_id . '_' . $pass . '_(.*)/');
608 foreach ($iter as $file) {
610 if ($file->isFile() && !in_array($file->getFilename(), $used_files)) {
611 unlink($file->getPathname());
612 }
613 }
614 }
615 }
616 // fau.
617
618 protected function deletePreviewFileUploads($userId, $userSolution, $files)
619 {
620 foreach ($files as $name) {
621 if (isset($userSolution[$name])) {
622 unset($userSolution[$name]);
623 @unlink($this->getPreviewFileUploadPath($userId) . $name);
624 }
625 }
626
627 return $userSolution;
628 }
629
635 public function getMaxFilesizeAsString(): string
636 {
637 $size = $this->getMaxFilesizeInBytes();
638 if ($size < 1024) {
639 $max_filesize = sprintf("%d Bytes", $size);
640 } elseif ($size < 1024 * 1024) {
641 $max_filesize = sprintf("%.1f KB", $size / 1024);
642 } else {
643 $max_filesize = sprintf("%.1f MB", $size / 1024 / 1024);
644 }
645
646 return $max_filesize;
647 }
648
649 public function getMaxFilesizeInBytes(): int
650 {
651 if ($this->getMaxSize() > 0) {
652 return $this->getMaxSize();
653 }
654
655 // get the value for the maximal uploadable filesize from the php.ini (if available)
656 $umf = get_cfg_var("upload_max_filesize");
657 // get the value for the maximal post data from the php.ini (if available)
658 $pms = get_cfg_var("post_max_size");
659
660 //convert from short-string representation to "real" bytes
661 $multiplier_a = array("K" => 1024, "M" => 1024 * 1024, "G" => 1024 * 1024 * 1024);
662
663 $umf_parts = preg_split("/(\d+)([K|G|M])/", $umf, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
664 $pms_parts = preg_split("/(\d+)([K|G|M])/", $pms, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
665
666 if (count($umf_parts) === 2) {
667 $umf = $umf_parts[0] * $multiplier_a[$umf_parts[1]];
668 }
669 if (count($pms_parts) === 2) {
670 $pms = $pms_parts[0] * $multiplier_a[$pms_parts[1]];
671 }
672
673 // use the smaller one as limit
674 $max_filesize = min($umf, $pms);
675
676 if (!$max_filesize) {
677 $max_filesize = max($umf, $pms);
678 }
679
680 return (int) $max_filesize;
681 }
682
688 public function saveWorkingData($active_id, $pass = null, $authorized = true): bool
689 {
690 $pass = $this->ensureCurrentTestPass($active_id, $pass);
691 $test_id = $this->lookupTestId($active_id);
692
693 try {
694 $upload_handling_required = $this->current_cmd !== 'submitSolution'
695 && $this->current_cmd !== 'showInstantResponse'
696 && !$this->isFileDeletionAction()
697 && $this->isFileUploadAvailable()
698 && $this->checkUpload();
699 } catch (IllegalStateException $e) {
700 $this->tpl->setOnScreenMessage('failure', $e->getMessage(), true);
701 return false;
702 }
703 $upload_file_data = [];
704
705 if ($upload_handling_required) {
706 if (!@file_exists($this->getFileUploadPath($test_id, $active_id))) {
708 }
709
710 $upload_file_data ['solution_file_versioning_upload_ts'] = time();
711 $filename_arr = pathinfo($_FILES["upload"]["name"]);
712 $extension = $filename_arr["extension"];
713 $new_file_name = "file_" . $active_id . "_" . $pass . "_" . $upload_file_data ['solution_file_versioning_upload_ts'] . "." . $extension;
714 $upload_file_data['new_file_name'] = ilFileUtils::getValidFilename($new_file_name);
715
716 try {
718 $_FILES["upload"]["tmp_name"],
719 $_FILES["upload"]["name"],
720 $this->getFileUploadPath($test_id, $active_id) . $upload_file_data['new_file_name']
721 );
722 } catch (ilException $e) {
723 $this->tpl->setOnScreenMessage('failure', $e->getMessage(), true);
724 return false;
725 }
726 }
727
728 $entered_values = false;
729
730 $this->getProcessLocker()->executeUserSolutionUpdateLockOperation(function () use (&$entered_values, $upload_handling_required, $upload_file_data, $test_id, $active_id, $pass, $authorized) {
731 if ($authorized == false) {
732 $this->forceExistingIntermediateSolution($active_id, $pass, true);
733 }
734
735 if ($this->isFileDeletionAction()) {
736 if ($this->isFileDeletionSubmitAvailable()) {
737 foreach ($_POST[self::DELETE_FILES_TBL_POSTVAR] as $solution_id) {
738 $this->removeSolutionRecordById($solution_id);
739 }
740 } else {
741 $this->tpl->setOnScreenMessage('info', $this->lng->txt('no_checkbox'), true);
742 }
743 } else {
744 if ($this->isFileReuseHandlingRequired()) {
745 foreach ($_POST[self::REUSE_FILES_TBL_POSTVAR] as $solutionId) {
746 $solution = $this->getSolutionRecordById($solutionId);
747
748 $this->saveCurrentSolution(
749 $active_id,
750 $pass,
751 $solution['value1'],
752 $solution['value2'],
753 false,
754 $solution['tstamp']
755 );
756 }
757 }
758
759 if ($upload_handling_required) {
760 $dispoFilename = ilFileUtils::getValidFilename($_FILES['upload']['name']);
761
762 $this->saveCurrentSolution(
763 $active_id,
764 $pass,
765 $upload_file_data['new_file_name'],
766 $dispoFilename,
767 false,
768 $upload_file_data ['solution_file_versioning_upload_ts']
769 );
770
771 $entered_values = true;
772 }
773 }
774
775 if ($authorized == true && $this->intermediateSolutionExists($active_id, $pass)) {
776 // remove the dummy record of the intermediate solution
777 $this->deleteDummySolutionRecord($active_id, $pass);
778
779 // delete the authorized solution and make the intermediate solution authorized (keeping timestamps)
780 $this->removeCurrentSolution($active_id, $pass, true);
781 $this->updateCurrentSolutionsAuthorization($active_id, $pass, true, true);
782 }
783
784 $this->deleteUnusedFiles($test_id, $active_id, $pass);
785 });
786
787 if ($entered_values) {
789 assQuestion::logAction($this->lng->txtlng(
790 "assessment",
791 "log_user_entered_values",
793 ), $active_id, $this->getId());
794 }
795 } else {
797 assQuestion::logAction($this->lng->txtlng(
798 "assessment",
799 "log_user_not_entered_values",
801 ), $active_id, $this->getId());
802 }
803 }
804
805 return true;
806 }
807
812 public function getUserSolutionPreferingIntermediate($active_id, $pass = null): array
813 {
814 $solution = $this->getSolutionValues($active_id, $pass, false);
815
816 if (!count($solution)) {
817 $solution = $this->getSolutionValues($active_id, $pass, true);
818 } else {
819 $cleaned = array();
820 foreach ($solution as $row) {
821 if (!empty($row['value1'])) {
822 $cleaned[] = $row;
823 }
824 }
825 $solution = $cleaned;
826 }
827
828 return $solution;
829 }
830 // fau.
831
832 public function removeIntermediateSolution(int $active_id, int $pass): void
833 {
834 parent::removeIntermediateSolution($active_id, $pass);
835
836 $test_id = $this->lookupTestId($active_id);
837 if ($test_id !== -1) {
838 $this->deleteUnusedFiles($test_id, $active_id, $pass);
839 }
840 }
841
842
843 protected function savePreviewData(ilAssQuestionPreviewSession $previewSession): void
844 {
845 $userSolution = $previewSession->getParticipantsSolution();
846
847 if (!is_array($userSolution)) {
848 $userSolution = array();
849 }
850
851 // hey: prevPassSolutions - readability spree - get a chance to understand the code
852 if ($this->isFileDeletionAction()) {
853 // hey.
854 // hey: prevPassSolutions - readability spree - get a chance to understand the code
855 if ($this->isFileDeletionSubmitAvailable()) {
856 // hey.
857 $userSolution = $this->deletePreviewFileUploads($previewSession->getUserId(), $userSolution, $_POST['deletefiles']);
858 } else {
859 $this->tpl->setOnScreenMessage('info', $this->lng->txt('no_checkbox'), true);
860 }
861 } else {
862 try {
863 $fileUploadAvailable = $this->current_cmd !== 'instantResponse'
864 && $this->isFileUploadAvailable();
865 } catch (IllegalStateException $e) {
866 $this->tpl->setOnScreenMessage('failure', $e->getMessage(), true);
867 return;
868 }
869 // hey: prevPassSolutions - readability spree - get a chance to understand the code
870 if ($fileUploadAvailable) {
871 // hey.
872 if ($this->checkUpload()) {
873 if (!@file_exists($this->getPreviewFileUploadPath($previewSession->getUserId()))) {
875 }
876
877 $version = time();
878 $filename_arr = pathinfo($_FILES["upload"]["name"]);
879 $extension = $filename_arr["extension"];
880 $newfile = "file_" . md5($_FILES["upload"]["name"]) . "_" . $version . "." . $extension;
881 try {
883 $_FILES["upload"]["tmp_name"],
884 $_FILES["upload"]["name"],
885 $this->getPreviewFileUploadPath($previewSession->getUserId()) . $newfile
886 );
887 } catch (ilException $e) {
888 $this->tpl->setOnScreenMessage('failure', $e->getMessage(), true);
889 return;
890 }
891
892
893 $userSolution[$newfile] = array(
894 'solution_id' => $newfile,
895 'value1' => $newfile,
896 'value2' => $_FILES['upload']['name'],
897 'tstamp' => $version,
898 'webpath' => $this->getPreviewFileUploadPathWeb($previewSession->getUserId())
899 );
900 }
901 }
902 }
903
904 $previewSession->setParticipantsSolution($userSolution);
905 }
906
915 protected function handleSubmission($active_id, $pass, $obligationsAnswered, $authorized): void
916 {
917 if (!$authorized) {
918 return;
919 }
920
921 if ($this->isCompletionBySubmissionEnabled()) {
922 $maxpoints = assQuestion::_getMaximumPoints($this->getId());
923
924 if ($this->getUploadedFiles($active_id, $pass, $authorized)) {
925 $points = $maxpoints;
926 } else {
927 // fau: testNav - don't set reached points if no file is available
928 return;
929 // fau.
930 }
931
932 assQuestion::_setReachedPoints($active_id, $this->getId(), $points, $maxpoints, $pass, true, $obligationsAnswered);
933
935 ilObjTest::_getObjectIDFromActiveID((int) $active_id),
936 ilObjTestAccess::_getParticipantId((int) $active_id)
937 );
938 }
939 }
940
941 public function getQuestionType(): string
942 {
943 return "assFileUpload";
944 }
945
946 public function getAdditionalTableName(): string
947 {
948 return "qpl_qst_fileupload";
949 }
950
951 public function getAnswerTableName(): string
952 {
953 return "";
954 }
955
959 public function deleteAnswers($question_id): void
960 {
961 }
962
967 public function getRTETextWithMediaObjects(): string
968 {
969 $text = parent::getRTETextWithMediaObjects();
970 return $text;
971 }
972
976 public function setExportDetailsXLS(ilAssExcelFormatHelper $worksheet, int $startrow, int $active_id, int $pass): int
977 {
978 parent::setExportDetailsXLS($worksheet, $startrow, $active_id, $pass);
979
980 $i = 1;
981 $solutions = $this->getSolutionValues($active_id, $pass);
982 foreach ($solutions as $solution) {
983 $worksheet->setCell($startrow + $i, 0, $this->lng->txt("result"));
984 $worksheet->setBold($worksheet->getColumnCoord(0) . ($startrow + $i));
985 if (strlen($solution["value1"])) {
986 $worksheet->setCell($startrow + $i, 2, $solution["value1"]);
987 $worksheet->setCell($startrow + $i, 3, $solution["value2"]);
988 }
989 $i++;
990 }
991
992 return $startrow + $i + 1;
993 }
994
1007 public function fromXML($item, int $questionpool_id, ?int $tst_id, &$tst_object, int &$question_counter, array $import_mapping, array &$solutionhints = []): array
1008 {
1009 $import = new assFileUploadImport($this);
1010 return $import->fromXML($item, $questionpool_id, $tst_id, $tst_object, $question_counter, $import_mapping);
1011 }
1012
1013 public function toXML($a_include_header = true, $a_include_binary = true, $a_shuffle = false, $test_output = false, $force_image_references = false): string
1014 {
1015 $export = new assFileUploadExport($this);
1016 return $export->toXML($a_include_header, $a_include_binary, $a_shuffle, $test_output, $force_image_references);
1017 }
1018
1019 public function getBestSolution($active_id, $pass): array
1020 {
1021 $user_solution = array();
1022 return $user_solution;
1023 }
1024
1025 public function getMaxSize(): ?int
1026 {
1027 return $this->maxsize;
1028 }
1029
1030 public function setMaxSize(?int $a_value): void
1031 {
1032 $this->maxsize = $a_value;
1033 }
1034
1035 public function getAllowedExtensionsArray(): array
1036 {
1037 if (strlen($this->allowedextensions)) {
1038 return array_filter(array_map('trim', explode(",", $this->allowedextensions)));
1039 }
1040 return array();
1041 }
1042
1043 public function getAllowedExtensions(): string
1044 {
1046 }
1047
1053 public function setAllowedExtensions($a_value): void
1054 {
1055 $this->allowedextensions = strtolower(trim($a_value));
1056 }
1057
1063 public function hasFileUploads($test_id): bool
1064 {
1065 global $DIC;
1066 $ilDB = $DIC['ilDB'];
1067 $query = "
1068 SELECT tst_solutions.solution_id
1069 FROM tst_solutions, tst_active, qpl_questions
1070 WHERE tst_solutions.active_fi = tst_active.active_id
1071 AND tst_solutions.question_fi = qpl_questions.question_id
1072 AND tst_solutions.question_fi = %s AND tst_active.test_fi = %s
1073 AND tst_solutions.value1 is not null";
1074 $result = $ilDB->queryF(
1075 $query,
1076 array("integer", "integer"),
1077 array($this->getId(), $test_id)
1078 );
1079 if ($result->numRows() > 0) {
1080 return true;
1081 } else {
1082 return false;
1083 }
1084 }
1085
1093 public function deliverFileUploadZIPFile($ref_id, $test_id, $test_title): void
1094 {
1095 global $DIC;
1096 $ilDB = $DIC['ilDB'];
1097 $lng = $DIC['lng'];
1098
1099 $exporter = new ilAssFileUploadUploadsExporter($ilDB, $lng);
1100
1101 $exporter->setRefId($ref_id);
1102 $exporter->setTestId($test_id);
1103 $exporter->setTestTitle($test_title);
1104 $exporter->setQuestion($this);
1105
1106 $exporter->build();
1107
1109 $exporter->getFinalZipFilePath(),
1110 $exporter->getDispoZipFileName(),
1111 $exporter->getZipFileMimeType(),
1112 false,
1113 true
1114 );
1115 }
1116
1117 public function isCompletionBySubmissionEnabled(): bool
1118 {
1120 }
1121
1128 {
1129 $this->completion_by_submission = (bool) $bool;
1130 return $this;
1131 }
1132
1133 public function isAnswered(int $active_id, int $pass): bool
1134 {
1135 $numExistingSolutionRecords = assQuestion::getNumExistingSolutionRecords($active_id, $pass, $this->getId());
1136
1137 return $numExistingSolutionRecords > 0;
1138 }
1139
1140 public static function isObligationPossible(int $questionId): bool
1141 {
1142 return true;
1143 }
1144
1145 public function isAutosaveable(): bool
1146 {
1147 return false;
1148 }
1149
1151 {
1152 return parent::buildTestPresentationConfig()
1154 }
1155
1156 protected function isFileDeletionAction(): bool
1157 {
1159 }
1160
1161 protected function isFileDeletionSubmitAvailable(): bool
1162 {
1163 return $this->isNonEmptyItemListPostSubmission(self::DELETE_FILES_TBL_POSTVAR);
1164 }
1165
1166 protected function isFileReuseSubmitAvailable(): bool
1167 {
1168 return $this->isNonEmptyItemListPostSubmission(self::REUSE_FILES_TBL_POSTVAR);
1169 }
1170
1171 protected function isFileReuseHandlingRequired(): bool
1172 {
1173 if (!$this->getTestPresentationConfig()->isPreviousPassSolutionReuseAllowed()) {
1174 return false;
1175 }
1176
1177 if (!$this->isFileReuseSubmitAvailable()) {
1178 return false;
1179 }
1180
1181 return true;
1182 }
1183
1187 protected function isFileUploadAvailable(): bool
1188 {
1189 if (!$this->file_upload->hasUploads()) {
1190 return false;
1191 }
1192
1193 if (!$this->file_upload->hasBeenProcessed()) {
1194 $this->file_upload->process();
1195 }
1196
1197 return true;
1198 }
1199}
$version
Definition: plugin.php:24
$filename
Definition: buildRTE.php:78
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...
Class for file upload questions.
deleteAnswers($question_id)
setMaxSize(?int $a_value)
getMaximumPoints()
Returns the maximum points, a learner can reach answering the question.
getUserSolutionPreferingIntermediate($active_id, $pass=null)
calculateReachedPoints($active_id, $pass=null, $authorizedSolution=true, $returndetails=false)
Returns the points, a learner has reached answering the question.
hasFileUploads($test_id)
Checks if file uploads exist for a given test and the original id of the question.
deleteUploadedFiles($files, $test_id, $active_id, $authorized)
Delete uploaded files.
createNewOriginalFromThisDuplicate($targetParentId, $targetQuestionTitle="")
getPreviewFileUploadPathWeb($userId)
Returns the filesystem path for file uploads.
getFileUploadPathWeb($test_id, $active_id, $question_id=null)
Returns the file upload path for web accessible files of a question.
setExportDetailsXLS(ilAssExcelFormatHelper $worksheet, int $startrow, int $active_id, int $pass)
{}
deletePreviewFileUploads($userId, $userSolution, $files)
saveToDb($original_id="")
Saves a assFileUpload object to a database.
isAnswered(int $active_id, int $pass)
getUploadedFiles($active_id, $pass=null, $authorized=true)
Returns the uploaded files for an active user in a given pass.
copyObject($target_questionpool_id, $title="")
Copies an assFileUpload object.
getRTETextWithMediaObjects()
Collects all text in the question which could contain media objects which were created with the Rich ...
saveAdditionalQuestionDataToDb()
Saves a record to the question types additional data table.
calculateReachedPointsForSolution($userSolution)
removeIntermediateSolution(int $active_id, int $pass)
deliverFileUploadZIPFile($ref_id, $test_id, $test_title)
Generates a ZIP file containing all file uploads for a given test and the original id of the question...
handleSubmission($active_id, $pass, $obligationsAnswered, $authorized)
This method is called after an user submitted one or more files.
getBestSolution($active_id, $pass)
saveWorkingData($active_id, $pass=null, $authorized=true)
@access public
getPreviewFileUploadPath($userId)
Returns the filesystem path for file uploads.
isComplete()
Returns true, if the question is complete for use.
fromXML($item, int $questionpool_id, ?int $tst_id, &$tst_object, int &$question_counter, array $import_mapping, array &$solutionhints=[])
Creates a question from a QTI file.
getMaxFilesizeAsString()
Return the maximum allowed file size as string.
setAllowedExtensions($a_value)
Set allowed file extensions.
setCompletionBySubmission($bool)
Enabled/Disable completion by submission.
getFileUploadPath($test_id, $active_id, $question_id=null)
Returns the filesystem path for file uploads.
toXML($a_include_header=true, $a_include_binary=true, $a_shuffle=false, $test_output=false, $force_image_references=false)
ILIAS FileUpload FileUpload $file_upload
savePreviewData(ilAssQuestionPreviewSession $previewSession)
loadFromDb($question_id)
Loads a assFileUpload object from a database.
getPreviewFileUploads(ilAssQuestionPreviewSession $previewSession)
duplicate(bool $for_test=true, string $title="", string $author="", string $owner="", $testObjId=null)
Duplicates an assFileUpload.
getQuestionType()
Returns the question type of the question.
getUploadedFilesForWeb($active_id, $pass)
Returns the web accessible uploaded files for an active user in a given pass.
checkUpload()
Check file upload.
static isObligationPossible(int $questionId)
buildTestPresentationConfig()
build basic test question configuration instance
Abstract basic class which is to be extended by the concrete assessment question type classes.
lookupTestId(int $active_id)
@refactor Move to ilObjTest or similar
static _getMaximumPoints(int $question_id)
Returns the maximum points, a learner can reach answering the question.
float $points
The maximum available points for the question.
setOriginalId(?int $original_id)
string $question
The question text.
static logAction(string $logtext, int $active_id, int $question_id)
setId(int $id=-1)
updateCurrentSolutionsAuthorization(int $activeId, int $pass, bool $authorized, bool $keepTime=false)
isNonEmptyItemListPostSubmission(string $postSubmissionFieldname)
setAdditionalContentEditingMode(?string $additionalContentEditingMode)
saveCurrentSolution(int $active_id, int $pass, $value1, $value2, bool $authorized=true, $tstamp=0)
getSolutionValues($active_id, $pass=null, bool $authorized=true)
Loads solutions of a given user from the database an returns it.
getSolutionRecordById(int $solutionId)
setQuestion(string $question="")
getCurrentSolutionResultSet(int $active_id, int $pass, bool $authorized=true)
__construct(string $title="", string $comment="", string $author="", int $owner=-1, string $question="")
assQuestion constructor
static _getOriginalId(int $question_id)
saveQuestionDataToDb(int $original_id=-1)
setAuthor(string $author="")
deleteDummySolutionRecord(int $activeId, int $passIndex)
ensureCurrentTestPass(int $active_id, int $pass)
int $test_id
The database id of a test in which the question is contained.
setComment(string $comment="")
setObjId(int $obj_id=0)
removeSolutionRecordById(int $solutionId)
forceExistingIntermediateSolution(int $activeId, int $passIndex, bool $considerDummyRecordCreation)
getSolutionMaxPass(int $active_id)
setOwner(int $owner=-1)
setNrOfTries(int $a_nr_of_tries)
setLifecycle(ilAssQuestionLifecycle $lifecycle)
static getNumExistingSolutionRecords(int $activeId, int $pass, int $questionId)
setTitle(string $title="")
removeCurrentSolution(int $active_id, int $pass, bool $authorized=true)
isDummySolutionRecord(array $solutionRecord)
intermediateSolutionExists(int $active_id, int $pass)
static _setReachedPoints(int $active_id, int $question_id, float $points, float $maxpoints, int $pass, bool $manualscoring, bool $obligationsEnabled)
Sets the points, a learner has reached answering the question Additionally objective results are upda...
setPoints(float $points)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
setCell($a_row, $a_col, $a_value, $datatype=null)
setBold(string $a_coords)
Set cell(s) to bold.
getColumnCoord(int $a_col)
Get column "name" from number.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
static deliverFileLegacy(string $a_file, ?string $a_filename=null, ?string $a_mime=null, ?bool $isInline=false, ?bool $removeAfterDelivery=false, ?bool $a_exit_after=true)
static makeDirParents(string $a_dir)
Create a new directory and all parent directories.
static getValidFilename(string $a_filename)
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 _updateStatus(int $a_obj_id, int $a_usr_id, ?object $a_obj=null, bool $a_percentage=false, bool $a_force_raise=false)
static _getParticipantId($active_id)
Get user id for active id.
static _getObjectIDFromActiveID($active_id)
Returns the ILIAS test object id for a given active id.
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...
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.
static virusHandling(string $a_file, string $a_orig_name='', bool $a_clean=true)
const CLIENT_WEB_DIR
Definition: constants.php:47
global $DIC
Definition: feed.php:28
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:67
$path
Definition: ltiservices.php:32
if($format !==null) $name
Definition: metadata.php:247
$i
Definition: metadata.php:41
__construct(Container $dic, ilPlugin $plugin)
@inheritDoc
$query