ILIAS  release_8 Revision v8.19
All Data Structures Namespaces Files Functions Variables Modules Pages
class.assFileUpload.php
Go to the documentation of this file.
1 <?php
2 
20 
21 require_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 
45  protected $allowedextensions;
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,
131  (int) $this->isCompletionBySubmissionEnabled()
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']);
175  } catch (ilTestQuestionPoolException $e) {
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(
461  ilFileUtils::removeTrailingPathSeparators(ILIAS_ABSOLUTE_PATH),
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(
474  ilFileUtils::removeTrailingPathSeparators(ILIAS_ABSOLUTE_PATH),
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  {
1045  return $this->allowedextensions;
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 }
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...
getPreviewFileUploadPathWeb($userId)
Returns the filesystem path for file uploads.
calculateReachedPoints($active_id, $pass=null, $authorizedSolution=true, $returndetails=false)
Returns the points, a learner has reached answering the question.
getSolutionValues($active_id, $pass=null, bool $authorized=true)
Loads solutions of a given user from the database an returns it.
setFormChangeDetectionEnabled($enableFormChangeDetection)
Set if the detection of form changes is enabled.
ILIAS FileUpload FileUpload $file_upload
setNrOfTries(int $a_nr_of_tries)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
savePreviewData(ilAssQuestionPreviewSession $previewSession)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
saveWorkingData($active_id, $pass=null, $authorized=true)
public
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...
createNewOriginalFromThisDuplicate($targetParentId, $targetQuestionTitle="")
setAllowedExtensions($a_value)
Set allowed file extensions.
deleteUploadedFiles($files, $test_id, $active_id, $authorized)
Delete uploaded files.
static _getParticipantId($active_id)
Get user id for active id.
setMaxSize(?int $a_value)
hasFileUploads($test_id)
Checks if file uploads exist for a given test and the original id of the question.
getBestSolution($active_id, $pass)
updateCurrentSolutionsAuthorization(int $activeId, int $pass, bool $authorized, bool $keepTime=false)
Abstract basic class which is to be extended by the concrete assessment question type classes...
setOwner(int $owner=-1)
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.
duplicate(bool $for_test=true, string $title="", string $author="", string $owner="", $testObjId=null)
Duplicates an assFileUpload.
static virusHandling(string $a_file, string $a_orig_name='', bool $a_clean=true)
getRTETextWithMediaObjects()
Collects all text in the question which could contain media objects which were created with the Rich ...
getColumnCoord(int $a_col)
Get column "name" from number.
isDummySolutionRecord(array $solutionRecord)
__construct(string $title="", string $comment="", string $author="", int $owner=-1, string $question="")
assQuestion constructor
static _getOriginalId(int $question_id)
static getValidFilename(string $a_filename)
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...
static getNumExistingSolutionRecords(int $activeId, int $pass, int $questionId)
isComplete()
Returns true, if the question is complete for use.
isAnswered(int $active_id, int $pass)
deletePreviewFileUploads($userId, $userSolution, $files)
setCell($a_row, $a_col, $a_value, $datatype=null)
copyObject($target_questionpool_id, $title="")
Copies an assFileUpload object.
getFileUploadPathWeb($test_id, $active_id, $question_id=null)
Returns the file upload path for web accessible files of a question.
static makeDirParents(string $a_dir)
Create a new directory and all parent directories.
static _getObjectIDFromActiveID($active_id)
Returns the ILIAS test object id for a given active id.
setComment(string $comment="")
Class for file upload questions.
handleSubmission($active_id, $pass, $obligationsAnswered, $authorized)
This method is called after an user submitted one or more files.
float $points
The maximum available points for the question.
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...
$path
Definition: ltiservices.php:32
static removeTrailingPathSeparators(string $path)
setExportDetailsXLS(ilAssExcelFormatHelper $worksheet, int $startrow, int $active_id, int $pass)
{}
saveToDb($original_id="")
Saves a assFileUpload object to a database.
static deliverFileLegacy(string $a_file, ?string $a_filename=null, ?string $a_mime=null, ?bool $isInline=false, ?bool $removeAfterDelivery=false, ?bool $a_exit_after=true)
deleteDummySolutionRecord(int $activeId, int $passIndex)
lookupTestId(int $active_id)
Move to ilObjTest or similar
getMaximumPoints()
Returns the maximum points, a learner can reach answering the question.
global $DIC
Definition: feed.php:28
if($format !==null) $name
Definition: metadata.php:247
saveCurrentSolution(int $active_id, int $pass, $value1, $value2, bool $authorized=true, $tstamp=0)
setBold(string $a_coords)
Set cell(s) to bold.
$ref_id
Definition: ltiauth.php:67
ensureCurrentTestPass(int $active_id, int $pass)
getUserSolutionPreferingIntermediate($active_id, $pass=null)
static logAction(string $logtext, int $active_id, int $question_id)
removeSolutionRecordById(int $solutionId)
getMaxFilesizeAsString()
Return the maximum allowed file size as string.
checkUpload()
Check file upload.
calculateReachedPointsForSolution($userSolution)
intermediateSolutionExists(int $active_id, int $pass)
getPreviewFileUploads(ilAssQuestionPreviewSession $previewSession)
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...
$query
const CLIENT_WEB_DIR
Definition: constants.php:47
static moveUploadedFile(string $a_file, string $a_name, string $a_target, bool $a_raise_errors=true, string $a_mode="move_uploaded")
move uploaded file
removeIntermediateSolution(int $active_id, int $pass)
setPoints(float $points)
setObjId(int $obj_id=0)
saveAdditionalQuestionDataToDb()
Saves a record to the question types additional data table.
string $question
The question text.
static isObligationPossible(int $questionId)
isNonEmptyItemListPostSubmission(string $postSubmissionFieldname)
getUploadedFilesForWeb($active_id, $pass)
Returns the web accessible uploaded files for an active user in a given pass.
$filename
Definition: buildRTE.php:78
int $test_id
The database id of a test in which the question is contained.
static _getMaximumPoints(int $question_id)
Returns the maximum points, a learner can reach answering the question.
toXML($a_include_header=true, $a_include_binary=true, $a_shuffle=false, $test_output=false, $force_image_references=false)
saveQuestionDataToDb(int $original_id=-1)
getPreviewFileUploadPath($userId)
Returns the filesystem path for file uploads.
getSolutionMaxPass(int $active_id)
removeCurrentSolution(int $active_id, int $pass, bool $authorized=true)
getFileUploadPath($test_id, $active_id, $question_id=null)
Returns the filesystem path for file uploads.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
setId(int $id=-1)
__construct(Container $dic, ilPlugin $plugin)
setOriginalId(?int $original_id)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
setTitle(string $title="")
setLifecycle(ilAssQuestionLifecycle $lifecycle)
loadFromDb($question_id)
Loads a assFileUpload object from a database.
getCurrentSolutionResultSet(int $active_id, int $pass, bool $authorized=true)
getUploadedFiles($active_id, $pass=null, $authorized=true)
Returns the uploaded files for an active user in a given pass.
forceExistingIntermediateSolution(int $activeId, int $passIndex, bool $considerDummyRecordCreation)
setCompletionBySubmission($bool)
Enabled/Disable completion by submission.
setAuthor(string $author="")
getSolutionRecordById(int $solutionId)
setAdditionalContentEditingMode(?string $additionalContentEditingMode)
$version
Definition: plugin.php:24
deleteAnswers($question_id)
static _updateStatus(int $a_obj_id, int $a_usr_id, ?object $a_obj=null, bool $a_percentage=false, bool $a_force_raise=false)
$i
Definition: metadata.php:41
setQuestion(string $question="")