ILIAS  release_7 Revision v7.30-3-g800a261c036
class.assQuestion.php
Go to the documentation of this file.
1<?php
2/* Copyright (c) 1998-2013 ILIAS open source, Extended GPL, see docs/LICENSE */
3
4include_once "./Modules/Test/classes/inc.AssessmentConstants.php";
5
20abstract class assQuestion
21{
22 const IMG_MIME_TYPE_JPG = 'image/jpeg';
23 const IMG_MIME_TYPE_PNG = 'image/png';
24 const IMG_MIME_TYPE_GIF = 'image/gif';
25
26 protected static $allowedFileExtensionsByMimeType = array(
27 self::IMG_MIME_TYPE_JPG => array('jpg', 'jpeg'),
28 self::IMG_MIME_TYPE_PNG => array('png'),
29 self::IMG_MIME_TYPE_GIF => array('gif')
30 );
31
32 protected static $allowedCharsetsByMimeType = array(
33 self::IMG_MIME_TYPE_JPG => array('binary'),
34 self::IMG_MIME_TYPE_PNG => array('binary'),
35 self::IMG_MIME_TYPE_GIF => array('binary')
36 );
37
43 protected $id;
44
50 protected $title;
51
57 protected $comment;
58
64 protected $owner;
65
71 protected $author;
72
78 protected $question;
79
85 protected $points;
86
93
99 protected $shuffle;
100
106 protected $test_id;
107
113 protected $obj_id;
114
120 protected $ilias;
121
125 protected $tpl;
126
130 protected $lng;
131
135 protected $db;
136
140 protected $refinery;
141
148
155
161 protected $original_id;
162
168 protected $page;
169
174
178 private $arrData;
179
184
189 protected $external_id = '';
190
195
200
207
213 public $feedbackOBJ = null;
214
220 public $prevent_rte_usage = false;
221
228
235
239 protected $questionChangeListeners = array();
240
244 protected $processLocker;
245
246 public $questionActionCmd = 'handleQuestionAction';
247
251 private static $resultGateway = null;
252
256 protected $step = null;
257
258 protected $lastChange;
259
263 protected $shuffler;
264
269
270 // fau: testNav - new variable $testQuestionConfig
275 // fau.
276
280 protected $lifecycle;
281
283 'image/jpeg' => array('jpg', 'jpeg'), 'image/png' => array('png'), 'image/gif' => array('gif')
284 );
285
296 public function __construct(
297 $title = "",
298 $comment = "",
299 $author = "",
300 $owner = -1,
301 $question = ""
302 ) {
304 global $DIC;
305 $ilias = $DIC['ilias'];
306 $lng = $DIC['lng'];
307 $tpl = $DIC['tpl'];
308 $ilDB = $DIC['ilDB'];
309 $refinery = $DIC['refinery'];
310
311 $this->ilias = $ilias;
312 $this->lng = $lng;
313 $this->tpl = $tpl;
314 $this->db = $ilDB;
315 $this->refinery = $refinery;
316
317 $this->original_id = null;
318 $this->title = $title;
319 $this->comment = $comment;
320 $this->page = null;
321 $this->author = $author;
322 $this->setQuestion($question);
323 if (!$this->author) {
324 $this->author = $this->ilias->account->fullname;
325 }
326 $this->owner = $owner;
327 if ($this->owner <= 0) {
328 $this->owner = $this->ilias->account->id;
329 }
330 $this->id = -1;
331 $this->test_id = -1;
332 $this->suggested_solutions = array();
333 $this->shuffle = 1;
334 $this->nr_of_tries = 0;
335 $this->setEstimatedWorkingTime(0, 1, 0);
336 $this->arrData = array();
337 $this->setExternalId('');
338
339 $this->questionActionCmd = 'handleQuestionAction';
340
341 $this->lastChange = null;
342
343 require_once 'Services/Randomization/classes/class.ilArrayElementOrderKeeper.php';
344 $this->shuffler = new ilArrayElementOrderKeeper();
345
346 $this->lifecycle = ilAssQuestionLifecycle::getDraftInstance();
347 }
348
349 protected static $forcePassResultsUpdateEnabled = false;
350
352 {
353 self::$forcePassResultsUpdateEnabled = $forcePassResultsUpdateEnabled;
354 }
355
356 public static function isForcePassResultUpdateEnabled()
357 {
359 }
360
361 public static function isAllowedImageMimeType($mimeType)
362 {
363 return (bool) count(self::getAllowedFileExtensionsForMimeType($mimeType));
364 }
365
366 public static function fetchMimeTypeIdentifier($contentTypeString)
367 {
368 return current(explode(';', $contentTypeString));
369 }
370
371 public static function getAllowedFileExtensionsForMimeType($mimeType)
372 {
373 foreach (self::$allowedFileExtensionsByMimeType as $allowedMimeType => $extensions) {
374 $rexCharsets = implode('|', self::$allowedCharsetsByMimeType[$allowedMimeType]);
375 $rexMimeType = preg_quote($allowedMimeType, '/');
376
377 $rex = '/^' . $rexMimeType . '(;(\s)*charset=(' . $rexCharsets . '))*$/';
378
379 if (!preg_match($rex, $mimeType)) {
380 continue;
381 }
382
383 return $extensions;
384 }
385
386 return array();
387 }
388
389 public static function isAllowedImageFileExtension($mimeType, $fileExtension)
390 {
391 return in_array(
392 strtolower($fileExtension),
393 self::getAllowedFileExtensionsForMimeType($mimeType)
394 );
395 }
396
397 // hey: prevPassSolutions - question action actracted (heavy use in fileupload refactoring)
398
402 protected function getQuestionAction()
403 {
404 if (!isset($_POST['cmd']) || !isset($_POST['cmd'][$this->questionActionCmd])) {
405 return '';
406 }
407
408 if (!is_array($_POST['cmd'][$this->questionActionCmd]) || !count($_POST['cmd'][$this->questionActionCmd])) {
409 return '';
410 }
411
412 return key($_POST['cmd'][$this->questionActionCmd]);
413 }
414
419 protected function isNonEmptyItemListPostSubmission($postSubmissionFieldname)
420 {
421 if (!isset($_POST[$postSubmissionFieldname])) {
422 return false;
423 }
424
425 if (!is_array($_POST[$postSubmissionFieldname])) {
426 return false;
427 }
428
429 if (!count($_POST[$postSubmissionFieldname])) {
430 return false;
431 }
432
433 return true;
434 }
435
441 protected function ensureCurrentTestPass($active_id, $pass)
442 {
443 if (is_integer($pass) && $pass >= 0) {
444 return $pass;
445 }
446
447 return $this->lookupCurrentTestPass($active_id, $pass);
448 }
449
455 protected function lookupCurrentTestPass($active_id, $pass)
456 {
457 require_once 'Modules/Test/classes/class.ilObjTest.php';
458 return ilObjTest::_getPass($active_id);
459 }
460
465 protected function lookupTestId($active_id)
466 {
467 global $DIC; /* @var ILIAS\DI\Container $DIC */
468 $ilDB = $DIC['ilDB'];
469
470 $result = $ilDB->queryF(
471 "SELECT test_fi FROM tst_active WHERE active_id = %s",
472 array('integer'),
473 array($active_id)
474 );
475
476 while ($row = $ilDB->fetchAssoc($result)) {
477 return $row["test_fi"];
478 }
479
480 return null;
481 }
482 // hey.
483
488 protected function log($active_id, $langVar)
489 {
491 $message = $this->lng->txtlng('assessment', $langVar, ilObjAssessmentFolder::_getLogLanguage());
492 assQuestion::logAction($message, $active_id, $this->getId());
493 }
494 }
495
500 {
501 $extensions = array();
502
503 foreach (self::$allowedImageMaterialFileExtensionsByMimeType as $mimeType => $mimeExtensions) {
504 $extensions = array_merge($extensions, $mimeExtensions);
505 }
506 return array_unique($extensions);
507 }
508
512 public function getShuffler()
513 {
514 return $this->shuffler;
515 }
516
521 {
522 $this->shuffler = $shuffler;
523 }
524
529 {
530 $this->processLocker = $processLocker;
531 }
532
536 public function getProcessLocker()
537 {
539 }
540
552 public function fromXML(
553 &$item,
554 &$questionpool_id,
555 &$tst_id,
556 &$tst_object,
557 &$question_counter,
558 &$import_mapping,
559 array $solutionhints = []
560 ) {
561 include_once "./Modules/TestQuestionPool/classes/import/qti12/class." . $this->getQuestionType() . "Import.php";
562 $classname = $this->getQuestionType() . "Import";
563 $import = new $classname($this);
564 $import->fromXML($item, $questionpool_id, $tst_id, $tst_object, $question_counter, $import_mapping);
565
566 foreach ($solutionhints as $hint) {
567 $h = new ilAssQuestionHint();
568 $h->setQuestionId($import->getQuestionId());
569 $h->setIndex($hint['index']);
570 $h->setPoints($hint['points']);
571 $h->setText($hint['txt']);
572 $h->save();
573 }
574 }
575
582 public function toXML($a_include_header = true, $a_include_binary = true, $a_shuffle = false, $test_output = false, $force_image_references = false)
583 {
584 include_once "./Modules/TestQuestionPool/classes/export/qti12/class." . $this->getQuestionType() . "Export.php";
585 $classname = $this->getQuestionType() . "Export";
586 $export = new $classname($this);
587 return $export->toXML($a_include_header, $a_include_binary, $a_shuffle, $test_output, $force_image_references);
588 }
589
596 public function isComplete()
597 {
598 return false;
599 }
600
608 public function questionTitleExists($questionpool_id, $title)
609 {
610 global $DIC;
611 $ilDB = $DIC['ilDB'];
612
613 $result = $ilDB->queryF(
614 "SELECT * FROM qpl_questions WHERE obj_fi = %s AND title = %s",
615 array('integer','text'),
616 array($questionpool_id, $title)
617 );
618 return ($result->numRows() > 0) ? true : false;
619 }
620
628 public function setTitle($title = "")
629 {
630 $this->title = $title;
631 }
632
640 public function setId($id = -1)
641 {
642 $this->id = $id;
643 }
644
652 public function setTestId($id = -1)
653 {
654 $this->test_id = $id;
655 }
656
664 public function setComment($comment = "")
665 {
666 $this->comment = $comment;
667 }
668
677 {
678 $this->outputType = $outputType;
679 }
680
681
689 public function setShuffle($shuffle = true)
690 {
691 if ($shuffle) {
692 $this->shuffle = 1;
693 } else {
694 $this->shuffle = 0;
695 }
696 }
697
708 public function setEstimatedWorkingTime($hour = 0, $min = 0, $sec = 0)
709 {
710 $this->est_working_time = array("h" => (int) $hour, "m" => (int) $min, "s" => (int) $sec);
711 }
712
719 public function setEstimatedWorkingTimeFromDurationString($durationString)
720 {
721 $this->est_working_time = array(
722 'h' => (int) substr($durationString, 0, 2),
723 'm' => (int) substr($durationString, 3, 2),
724 's' => (int) substr($durationString, 6, 2)
725 );
726 }
727
735 public function keyInArray($searchkey, $array)
736 {
737 if ($searchkey) {
738 foreach ($array as $key => $value) {
739 if (strcmp($key, $searchkey) == 0) {
740 return true;
741 }
742 }
743 }
744 return false;
745 }
746
754 public function setAuthor($author = "")
755 {
756 if (!$author) {
757 $author = $this->ilias->account->fullname;
758 }
759 $this->author = $author;
760 }
761
769 public function setOwner($owner = "")
770 {
771 $this->owner = $owner;
772 }
773
781 public function getTitle()
782 {
783 return $this->title;
784 }
785
792 {
793 require_once 'Services/Utilities/classes/class.ilUtil.php';
794 return ilUtil::getASCIIFilename($this->getTitle());
795 }
796
804 public function getId()
805 {
806 return $this->id;
807 }
808
816 public function getShuffle()
817 {
818 return $this->shuffle;
819 }
820
828 public function getTestId()
829 {
830 return $this->test_id;
831 }
832
840 public function getComment()
841 {
842 return $this->comment;
843 }
844
852 public function getOutputType()
853 {
854 return $this->outputType;
855 }
856
857 public function getDescriptionForHTMLOutput() : string
858 {
859 return $this->refinery->string()->stripTags()->transform($this->comment ?? '');
860 }
861
868 public function supportsJavascriptOutput()
869 {
870 return false;
871 }
872
873 public function supportsNonJsOutput()
874 {
875 return true;
876 }
877
878 public function requiresJsSwitch()
879 {
880 return $this->supportsJavascriptOutput() && $this->supportsNonJsOutput();
881 }
882
890 public function getEstimatedWorkingTime()
891 {
892 if (!$this->est_working_time) {
893 $this->est_working_time = array("h" => 0, "m" => 0, "s" => 0);
894 }
896 }
897
905 public function getAuthor()
906 {
907 return $this->author;
908 }
909
910 public function getAuthorForHTMLOutput() : string
911 {
912 return $this->refinery->string()->stripTags()->transform($this->author);
913 }
914
922 public function getOwner()
923 {
924 return $this->owner;
925 }
926
934 public function getObjId()
935 {
936 return $this->obj_id;
937 }
938
946 public function setObjId($obj_id = 0)
947 {
948 $this->obj_id = $obj_id;
949 }
950
954 public function getLifecycle()
955 {
956 return $this->lifecycle;
957 }
958
963 {
964 $this->lifecycle = $lifecycle;
965 }
966
971 {
972 $this->external_id = $external_id;
973 }
974
978 public function getExternalId()
979 {
980 if (!strlen($this->external_id)) {
981 if ($this->getId() > 0) {
982 return 'il_' . IL_INST_ID . '_qst_' . $this->getId();
983 } else {
984 return uniqid('', true);
985 }
986 } else {
987 return $this->external_id;
988 }
989 }
990
997 public static function _getMaximumPoints($question_id)
998 {
999 global $DIC;
1000 $ilDB = $DIC['ilDB'];
1001
1002 $points = 0;
1003 $result = $ilDB->queryF(
1004 "SELECT points FROM qpl_questions WHERE question_id = %s",
1005 array('integer'),
1006 array($question_id)
1007 );
1008 if ($result->numRows() == 1) {
1009 $row = $ilDB->fetchAssoc($result);
1010 $points = $row["points"];
1011 }
1012 return $points;
1013 }
1014
1021 public static function _getQuestionInfo($question_id)
1022 {
1023 global $DIC;
1024 $ilDB = $DIC['ilDB'];
1025
1026 $result = $ilDB->queryF(
1027 "SELECT qpl_questions.*, qpl_qst_type.type_tag FROM qpl_qst_type, qpl_questions WHERE qpl_questions.question_id = %s AND qpl_questions.question_type_fi = qpl_qst_type.question_type_id",
1028 array('integer'),
1029 array($question_id)
1030 );
1031 if ($result->numRows()) {
1032 return $ilDB->fetchAssoc($result);
1033 } else {
1034 return array();
1035 }
1036 }
1037
1044 public static function _getSuggestedSolutionCount($question_id)
1045 {
1046 global $DIC;
1047 $ilDB = $DIC['ilDB'];
1048
1049 $result = $ilDB->queryF(
1050 "SELECT suggested_solution_id FROM qpl_sol_sug WHERE question_fi = %s",
1051 array('integer'),
1052 array($question_id)
1053 );
1054 return $result->numRows();
1055 }
1056
1063 public static function _getSuggestedSolutionOutput($question_id)
1064 {
1066 if (!is_object($question)) {
1067 return "";
1068 }
1069 return $question->getSuggestedSolutionOutput();
1070 }
1071
1073 {
1074 $output = array();
1075 foreach ($this->suggested_solutions as $solution) {
1076 switch ($solution["type"]) {
1077 case "lm":
1078 case "st":
1079 case "pg":
1080 case "git":
1081 array_push($output, '<a href="' . assQuestion::_getInternalLinkHref($solution["internal_link"]) . '">' . $this->lng->txt("solution_hint") . '</a>');
1082 break;
1083 case "file":
1084 $possible_texts = array_values(array_filter(array(
1085 ilUtil::prepareFormOutput($solution['value']['filename']),
1086 ilUtil::prepareFormOutput($solution['value']['name']),
1087 $this->lng->txt('tst_show_solution_suggested')
1088 )));
1089
1090 require_once 'Services/WebAccessChecker/classes/class.ilWACSignedPath.php';
1092 array_push($output, '<a href="' . ilWACSignedPath::signFile($this->getSuggestedSolutionPathWeb() . $solution["value"]["name"]) . '">' . $possible_texts[0] . '</a>');
1093 break;
1094 case "text":
1095 $solutionValue = $solution["value"];
1096 $solutionValue = $this->fixSvgToPng($solutionValue);
1097 $solutionValue = $this->fixUnavailableSkinImageSources($solutionValue);
1098 $output[] = $this->prepareTextareaOutput($solutionValue, true);
1099 break;
1100 }
1101 }
1102 return join("<br />", $output);
1103 }
1104
1113 public function &_getSuggestedSolution($question_id, $subquestion_index = 0)
1114 {
1115 global $DIC;
1116 $ilDB = $DIC['ilDB'];
1117
1118 $result = $ilDB->queryF(
1119 "SELECT * FROM qpl_sol_sug WHERE question_fi = %s AND subquestion_index = %s",
1120 array('integer','integer'),
1121 array($question_id, $subquestion_index)
1122 );
1123 if ($result->numRows() == 1) {
1124 $row = $ilDB->fetchAssoc($result);
1125 return array(
1126 "internal_link" => $row["internal_link"],
1127 "import_id" => $row["import_id"]
1128 );
1129 } else {
1130 return array();
1131 }
1132 }
1133
1139 public function getSuggestedSolutions()
1140 {
1142 }
1143
1151 public static function _getReachedPoints($active_id, $question_id, $pass = null)
1152 {
1153 global $DIC;
1154 $ilDB = $DIC['ilDB'];
1155
1156 $points = 0;
1157 if (is_null($pass)) {
1158 include_once "./Modules/TestQuestionPool/classes/class.assQuestion.php";
1159 $pass = assQuestion::_getSolutionMaxPass($question_id, $active_id);
1160 }
1161 $result = $ilDB->queryF(
1162 "SELECT * FROM tst_test_result WHERE active_fi = %s AND question_fi = %s AND pass = %s",
1163 array('integer','integer','integer'),
1164 array($active_id, $question_id, $pass)
1165 );
1166 if ($result->numRows() == 1) {
1167 $row = $ilDB->fetchAssoc($result);
1168 $points = $row["points"];
1169 }
1170 return $points;
1171 }
1172
1181 public function getReachedPoints($active_id, $pass = null)
1182 {
1183 return round(self::_getReachedPoints($active_id, $this->getId(), $pass), 2);
1184 }
1185
1192 public function getMaximumPoints()
1193 {
1194 return $this->points;
1195 }
1196
1208 final public function getAdjustedReachedPoints($active_id, $pass = null, $authorizedSolution = true)
1209 {
1210 if (is_null($pass)) {
1211 include_once "./Modules/Test/classes/class.ilObjTest.php";
1212 $pass = ilObjTest::_getPass($active_id);
1213 }
1214
1215 // determine reached points for submitted solution
1216 $reached_points = $this->calculateReachedPoints($active_id, $pass, $authorizedSolution);
1217
1218
1219
1220 // deduct points for requested hints from reached points
1221 require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionHintTracking.php';
1222 $hintTracking = new ilAssQuestionHintTracking($this->getId(), $active_id, $pass);
1223 $requestsStatisticData = $hintTracking->getRequestStatisticDataByQuestionAndTestpass();
1224 $reached_points = $reached_points - $requestsStatisticData->getRequestsPoints();
1225
1226 // adjust reached points regarding to tests scoring options
1227 $reached_points = $this->adjustReachedPointsByScoringOptions($reached_points, $active_id, $pass);
1228
1229 return $reached_points;
1230 }
1231
1241 final public function calculateResultsFromSolution($active_id, $pass = null, $obligationsEnabled = false)
1242 {
1243 global $DIC;
1244 $ilDB = $DIC['ilDB'];
1245 $ilUser = $DIC['ilUser'];
1246
1247 if (is_null($pass)) {
1248 include_once "./Modules/Test/classes/class.ilObjTest.php";
1249 $pass = ilObjTest::_getPass($active_id);
1250 }
1251
1252 // determine reached points for submitted solution
1253 $reached_points = $this->calculateReachedPoints($active_id, $pass);
1254
1255 // deduct points for requested hints from reached points
1256 require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionHintTracking.php';
1257 $questionHintTracking = new ilAssQuestionHintTracking($this->getId(), $active_id, $pass);
1258 $requestsStatisticData = $questionHintTracking->getRequestStatisticDataByQuestionAndTestpass();
1259 $reached_points = $reached_points - $requestsStatisticData->getRequestsPoints();
1260
1261 // adjust reached points regarding to tests scoring options
1262 $reached_points = $this->adjustReachedPointsByScoringOptions($reached_points, $active_id, $pass);
1263
1264 if ($obligationsEnabled && ilObjTest::isQuestionObligatory($this->getId())) {
1265 $isAnswered = $this->isAnswered($active_id, $pass);
1266 } else {
1267 $isAnswered = true;
1268 }
1269
1270 if (is_null($reached_points)) {
1271 $reached_points = 0;
1272 }
1273
1274 // fau: testNav - check for existing authorized solution to know if a result record should be written
1275 $existingSolutions = $this->lookupForExistingSolutions($active_id, $pass);
1276
1277 $this->getProcessLocker()->executeUserQuestionResultUpdateOperation(function () use ($ilDB, $active_id, $pass, $reached_points, $requestsStatisticData, $isAnswered, $existingSolutions) {
1278 $query = "
1279 DELETE FROM tst_test_result
1280
1281 WHERE active_fi = %s
1282 AND question_fi = %s
1283 AND pass = %s
1284 ";
1285
1286 $types = array('integer', 'integer', 'integer');
1287 $values = array($active_id, $this->getId(), $pass);
1288
1289 if ($this->getStep() !== null) {
1290 $query .= "
1291 AND step = %s
1292 ";
1293
1294 $types[] = 'integer';
1295 $values[] = $this->getStep();
1296 }
1297 $ilDB->manipulateF($query, $types, $values);
1298
1299 if ($existingSolutions['authorized']) {
1300 $next_id = $ilDB->nextId("tst_test_result");
1301 $fieldData = array(
1302 'test_result_id' => array('integer', $next_id),
1303 'active_fi' => array('integer', $active_id),
1304 'question_fi' => array('integer', $this->getId()),
1305 'pass' => array('integer', $pass),
1306 'points' => array('float', $reached_points),
1307 'tstamp' => array('integer', time()),
1308 'hint_count' => array('integer', $requestsStatisticData->getRequestsCount()),
1309 'hint_points' => array('float', $requestsStatisticData->getRequestsPoints()),
1310 'answered' => array('integer', $isAnswered)
1311 );
1312
1313 if ($this->getStep() !== null) {
1314 $fieldData['step'] = array('integer', $this->getStep());
1315 }
1316
1317 $ilDB->insert('tst_test_result', $fieldData);
1318 }
1319 });
1320 // fau.
1321
1322 include_once("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
1323
1326 sprintf(
1327 $this->lng->txtlng(
1328 "assessment",
1329 "log_user_answered_question",
1331 ),
1332 $reached_points
1333 ),
1334 $active_id,
1335 $this->getId()
1336 );
1337 }
1338
1339 // update test pass results
1340 self::_updateTestPassResults($active_id, $pass, $obligationsEnabled, $this->getProcessLocker());
1341
1342 // Update objective status
1343 include_once 'Modules/Course/classes/class.ilCourseObjectiveResult.php';
1344 ilCourseObjectiveResult::_updateObjectiveResult($ilUser->getId(), $active_id, $this->getId());
1345 }
1346
1355 final public function persistWorkingState($active_id, $pass = null, $obligationsEnabled = false, $authorized = true)
1356 {
1357 if (!$this->validateSolutionSubmit() && !$this->savePartial()) {
1358 return false;
1359 }
1360
1361 $saveStatus = false;
1362
1363 $this->getProcessLocker()->executePersistWorkingStateLockOperation(function () use ($active_id, $pass, $authorized, $obligationsEnabled, &$saveStatus) {
1364 if ($pass === null) {
1365 require_once 'Modules/Test/classes/class.ilObjTest.php';
1366 $pass = ilObjTest::_getPass($active_id);
1367 }
1368
1369 $saveStatus = $this->saveWorkingData($active_id, $pass, $authorized);
1370
1371 if ($authorized) {
1372 // fau: testNav - remove an intermediate solution if the authorized solution is saved
1373 // the intermediate solution would set the displayed question status as "editing ..."
1374 $this->removeIntermediateSolution($active_id, $pass);
1375 // fau.
1376 $this->calculateResultsFromSolution($active_id, $pass, $obligationsEnabled);
1377 }
1378 });
1379
1380 return $saveStatus;
1381 }
1382
1386 final public function persistPreviewState(ilAssQuestionPreviewSession $previewSession)
1387 {
1388 $this->savePreviewData($previewSession);
1389 return $this->validateSolutionSubmit();
1390 }
1391
1392 public function validateSolutionSubmit()
1393 {
1394 return true;
1395 }
1396
1406 abstract public function saveWorkingData($active_id, $pass = null, $authorized = true);
1407
1408 protected function savePreviewData(ilAssQuestionPreviewSession $previewSession)
1409 {
1410 $previewSession->setParticipantsSolution($this->getSolutionSubmit());
1411 }
1412
1414 public static function _updateTestResultCache($active_id, ilAssQuestionProcessLocker $processLocker = null)
1415 {
1416 global $DIC;
1417 $ilDB = $DIC['ilDB'];
1418
1419 include_once "./Modules/Test/classes/class.ilObjTest.php";
1420 include_once "./Modules/Test/classes/class.assMarkSchema.php";
1421
1422 $pass = ilObjTest::_getResultPass($active_id);
1423
1424 if ($pass !== null) {
1425 $query = "
1426 SELECT tst_pass_result.*
1427 FROM tst_pass_result
1428 WHERE active_fi = %s
1429 AND pass = %s
1430 ";
1431
1432 $result = $ilDB->queryF(
1433 $query,
1434 array('integer', 'integer'),
1435 array($active_id, $pass)
1436 );
1437
1438 $row_result = $ilDB->fetchAssoc($result);
1439
1440 $max = $row_result['maxpoints'];
1441 $reached = $row_result['points'];
1442
1443 $obligationsAnswered = (int) $row_result['obligations_answered'];
1444
1445 include_once "./Modules/Test/classes/class.assMarkSchema.php";
1446
1447 $percentage = (!$max) ? 0 : ($reached / $max) * 100.0;
1448
1449 $mark = ASS_MarkSchema::_getMatchingMarkFromActiveId($active_id, $percentage);
1450
1451 $isPassed = ($mark["passed"] ? 1 : 0);
1452 $isFailed = (!$mark["passed"] ? 1 : 0);
1453
1454 $userTestResultUpdateCallback = function () use (
1455 $ilDB,
1456 $active_id,
1457 $pass,
1458 $max,
1459 $reached,
1460 $isFailed,
1461 $isPassed,
1462 $obligationsAnswered,
1463 $row_result,
1464 $mark
1465 ) {
1466 $passedOnceBefore = 0;
1467 $query = "SELECT passed_once FROM tst_result_cache WHERE active_fi = %s";
1468 $res = $ilDB->queryF($query, array('integer'), array($active_id));
1469 while ($row = $ilDB->fetchAssoc($res)) {
1470 $passedOnceBefore = (int) $row['passed_once'];
1471 }
1472
1473 $passedOnce = (int) ($isPassed || $passedOnceBefore);
1474
1475 $ilDB->manipulateF(
1476 "DELETE FROM tst_result_cache WHERE active_fi = %s",
1477 array('integer'),
1478 array($active_id)
1479 );
1480
1481 $ilDB->insert('tst_result_cache', array(
1482 'active_fi' => array('integer', $active_id),
1483 'pass' => array('integer', strlen($pass) ? $pass : 0),
1484 'max_points' => array('float', strlen($max) ? $max : 0),
1485 'reached_points' => array('float', strlen($reached) ? $reached : 0),
1486 'mark_short' => array('text', strlen($mark["short_name"]) ? $mark["short_name"] : " "),
1487 'mark_official' => array('text', strlen($mark["official_name"]) ? $mark["official_name"] : " "),
1488 'passed_once' => array('integer', $passedOnce),
1489 'passed' => array('integer', $isPassed),
1490 'failed' => array('integer', $isFailed),
1491 'tstamp' => array('integer', time()),
1492 'hint_count' => array('integer', $row_result['hint_count']),
1493 'hint_points' => array('float', $row_result['hint_points']),
1494 'obligations_answered' => array('integer', $obligationsAnswered)
1495 ));
1496 };
1497
1498 if (is_object($processLocker)) {
1499 $processLocker->executeUserTestResultUpdateLockOperation($userTestResultUpdateCallback);
1500 } else {
1501 $userTestResultUpdateCallback();
1502 }
1503 }
1504 }
1505
1507 public static function _updateTestPassResults($active_id, $pass, $obligationsEnabled = false, ilAssQuestionProcessLocker $processLocker = null, $test_obj_id = null)
1508 {
1509 global $DIC;
1510 $ilDB = $DIC['ilDB'];
1511
1512 include_once "./Modules/Test/classes/class.ilObjTest.php";
1513
1514 if (self::getResultGateway() !== null) {
1515 $data = self::getResultGateway()->getQuestionCountAndPointsForPassOfParticipant($active_id, $pass);
1516 $time = self::getResultGateway()->getWorkingTimeOfParticipantForPass($active_id, $pass);
1517 } else {
1520 }
1521
1522
1523 // update test pass results
1524
1525 $result = $ilDB->queryF(
1526 "
1527 SELECT SUM(points) reachedpoints,
1528 SUM(hint_count) hint_count,
1529 SUM(hint_points) hint_points,
1530 COUNT(DISTINCT(question_fi)) answeredquestions
1531 FROM tst_test_result
1532 WHERE active_fi = %s
1533 AND pass = %s
1534 ",
1535 array('integer','integer'),
1536 array($active_id, $pass)
1537 );
1538
1539 if ($result->numRows() > 0) {
1540 if ($obligationsEnabled) {
1541 $query = '
1542 SELECT answered answ
1543 FROM tst_test_question
1544 INNER JOIN tst_active
1545 ON active_id = %s
1546 AND tst_test_question.test_fi = tst_active.test_fi
1547 LEFT JOIN tst_test_result
1548 ON tst_test_result.active_fi = %s
1549 AND tst_test_result.pass = %s
1550 AND tst_test_question.question_fi = tst_test_result.question_fi
1551 WHERE obligatory = 1';
1552
1553 $result_obligatory = $ilDB->queryF(
1554 $query,
1555 array('integer','integer','integer'),
1556 array($active_id, $active_id, $pass)
1557 );
1558
1559 $obligations_answered = 1;
1560
1561 while ($row_obligatory = $ilDB->fetchAssoc($result_obligatory)) {
1562 if (!(int) $row_obligatory['answ']) {
1563 $obligations_answered = 0;
1564 break;
1565 }
1566 }
1567 } else {
1568 $obligations_answered = 1;
1569 }
1570
1571 $row = $ilDB->fetchAssoc($result);
1572
1573 if ($row['reachedpoints'] === null) {
1574 $row['reachedpoints'] = 0;
1575 }
1576 if ($row['hint_count'] === null) {
1577 $row['hint_count'] = 0;
1578 }
1579 if ($row['hint_points'] === null) {
1580 $row['hint_points'] = 0;
1581 }
1582
1583 $exam_identifier = ilObjTest::buildExamId($active_id, $pass, $test_obj_id);
1584
1585 $updatePassResultCallback = function () use ($ilDB, $data, $active_id, $pass, $row, $time, $obligations_answered, $exam_identifier) {
1586
1588 $ilDB->replace(
1589 'tst_pass_result',
1590 array(
1591 'active_fi' => array('integer', $active_id),
1592 'pass' => array('integer', strlen($pass) ? $pass : 0)),
1593 array(
1594 'points' => array('float', $row['reachedpoints'] ? $row['reachedpoints'] : 0),
1595 'maxpoints' => array('float', $data['points']),
1596 'questioncount' => array('integer', $data['count']),
1597 'answeredquestions' => array('integer', $row['answeredquestions']),
1598 'workingtime' => array('integer', $time),
1599 'tstamp' => array('integer', time()),
1600 'hint_count' => array('integer', $row['hint_count']),
1601 'hint_points' => array('float', $row['hint_points']),
1602 'obligations_answered' => array('integer', $obligations_answered),
1603 'exam_id' => array('text', $exam_identifier)
1604 )
1605 );
1606 };
1607
1608 if (is_object($processLocker)) {
1609 $processLocker->executeUserPassResultUpdateLockOperation($updatePassResultCallback);
1610 } else {
1611 $updatePassResultCallback();
1612 }
1613 }
1614
1616
1617 return array(
1618 'active_fi' => $active_id,
1619 'pass' => $pass,
1620 'points' => ($row["reachedpoints"]) ? $row["reachedpoints"] : 0,
1621 'maxpoints' => $data["points"],
1622 'questioncount' => $data["count"],
1623 'answeredquestions' => $row["answeredquestions"],
1624 'workingtime' => $time,
1625 'tstamp' => time(),
1626 'hint_count' => $row['hint_count'],
1627 'hint_points' => $row['hint_points'],
1628 'obligations_answered' => $obligations_answered,
1629 'exam_id' => $exam_identifier
1630 );
1631 }
1632
1640 public static function logAction($logtext = "", $active_id = "", $question_id = "")
1641 {
1642 $original_id = "";
1643 if (strlen($question_id)) {
1644 $original_id = self::_getOriginalId($question_id);
1645 }
1646
1647 require_once 'Modules/Test/classes/class.ilObjAssessmentFolder.php';
1648 require_once 'Modules/Test/classes/class.ilObjTest.php';
1649
1651 $GLOBALS['DIC']['ilUser']->getId(),
1653 $logtext,
1654 $question_id,
1656 );
1657 }
1658
1666 public function moveUploadedMediaFile($file, $name)
1667 {
1668 $mediatempdir = CLIENT_WEB_DIR . "/assessment/temp";
1669 if (!@is_dir($mediatempdir)) {
1670 ilUtil::createDirectory($mediatempdir);
1671 }
1672 $temp_name = tempnam($mediatempdir, $name . "_____");
1673 $temp_name = str_replace("\\", "/", $temp_name);
1674 @unlink($temp_name);
1675 if (!ilUtil::moveUploadedFile($file, $name, $temp_name)) {
1676 return false;
1677 } else {
1678 return $temp_name;
1679 }
1680 }
1681
1688 {
1689 return CLIENT_WEB_DIR . "/assessment/$this->obj_id/$this->id/solution/";
1690 }
1691
1698 public function getJavaPath()
1699 {
1700 return CLIENT_WEB_DIR . "/assessment/$this->obj_id/$this->id/java/";
1701 }
1702
1709 public function getImagePath($question_id = null, $object_id = null)
1710 {
1711 if ($question_id === null) {
1712 $question_id = $this->id;
1713 }
1714
1715 if ($object_id === null) {
1716 $object_id = $this->obj_id;
1717 }
1718
1719 return $this->buildImagePath($question_id, $object_id);
1720 }
1721
1722 public function buildImagePath($questionId, $parentObjectId)
1723 {
1724 return CLIENT_WEB_DIR . "/assessment/{$parentObjectId}/{$questionId}/images/";
1725 }
1726
1733 public function getFlashPath()
1734 {
1735 return CLIENT_WEB_DIR . "/assessment/$this->obj_id/$this->id/flash/";
1736 }
1737
1744 public function getJavaPathWeb()
1745 {
1746 $relative_path = "assessment/$this->obj_id/$this->id/java/";
1747 return ilObjTest::getDataWebPath($relative_path);
1748 }
1749
1756 {
1757 $relative_path = "assessment/$this->obj_id/$this->id/solution/";
1758 return ilObjTest::getDataWebPath($relative_path);
1759 }
1760
1769 public function getImagePathWeb()
1770 {
1771 if (!$this->export_image_path) {
1772 $relative_path = "assessment/$this->obj_id/$this->id/images/";
1773 return ilObjTest::getDataWebPath($relative_path);
1774 } else {
1776 }
1777 }
1778
1785 public function getFlashPathWeb()
1786 {
1787 $relative_path = "assessment/$this->obj_id/$this->id/flash/";
1788 return ilObjTest::getDataWebPath($relative_path);
1789 }
1790
1791 // hey: prevPassSolutions - accept and prefer intermediate only from current pass
1792 public function getTestOutputSolutions($activeId, $pass)
1793 {
1794 // hey: refactored identifiers
1795 if ($this->getTestPresentationConfig()->isSolutionInitiallyPrefilled()) {
1796 // hey.
1797 return $this->getSolutionValues($activeId, $pass, true);
1798 }
1799
1800 return $this->getUserSolutionPreferingIntermediate($activeId, $pass);
1801 }
1802 // hey.
1803
1804 public function getUserSolutionPreferingIntermediate($active_id, $pass = null)
1805 {
1806 $solution = $this->getSolutionValues($active_id, $pass, false);
1807
1808 if (!count($solution)) {
1809 $solution = $this->getSolutionValues($active_id, $pass, true);
1810 }
1811
1812 return $solution;
1813 }
1814
1818 public function getSolutionValues($active_id, $pass = null, $authorized = true)
1819 {
1820 global $DIC;
1821 $ilDB = $DIC['ilDB'];
1822
1823 if (is_null($pass)) {
1824 $pass = $this->getSolutionMaxPass($active_id);
1825 }
1826
1827 if ($this->getStep() !== null) {
1828 $query = "
1829 SELECT *
1830 FROM tst_solutions
1831 WHERE active_fi = %s
1832 AND question_fi = %s
1833 AND pass = %s
1834 AND step = %s
1835 AND authorized = %s
1836 ORDER BY solution_id";
1837
1838 $result = $ilDB->queryF(
1839 $query,
1840 array('integer', 'integer', 'integer', 'integer', 'integer'),
1841 array($active_id, $this->getId(), $pass, $this->getStep(), (int) $authorized)
1842 );
1843 } else {
1844 $query = "
1845 SELECT *
1846 FROM tst_solutions
1847 WHERE active_fi = %s
1848 AND question_fi = %s
1849 AND pass = %s
1850 AND authorized = %s
1851 ORDER BY solution_id
1852 ";
1853
1854 $result = $ilDB->queryF(
1855 $query,
1856 array('integer', 'integer', 'integer', 'integer'),
1857 array($active_id, $this->getId(), $pass, (int) $authorized)
1858 );
1859 }
1860
1861 $values = array();
1862
1863 while ($row = $ilDB->fetchAssoc($result)) {
1864 $values[] = $row;
1865 }
1866
1867 return $values;
1868 }
1869
1876 public function isInUse($question_id = "")
1877 {
1878 global $DIC;
1879 $ilDB = $DIC['ilDB'];
1880
1881 if ($question_id < 1) {
1882 $question_id = $this->getId();
1883 }
1884 $result = $ilDB->queryF(
1885 "SELECT COUNT(qpl_questions.question_id) question_count FROM qpl_questions, tst_test_question WHERE qpl_questions.original_id = %s AND qpl_questions.question_id = tst_test_question.question_fi",
1886 array('integer'),
1887 array($question_id)
1888 );
1889 $row = $ilDB->fetchAssoc($result);
1890 $count = $row["question_count"];
1891
1892 $result = $ilDB->queryF(
1893 "
1894 SELECT tst_active.test_fi
1895 FROM qpl_questions
1896 INNER JOIN tst_test_rnd_qst ON tst_test_rnd_qst.question_fi = qpl_questions.question_id
1897 INNER JOIN tst_active ON tst_active.active_id = tst_test_rnd_qst.active_fi
1898 WHERE qpl_questions.original_id = %s
1899 GROUP BY tst_active.test_fi",
1900 array('integer'),
1901 array($question_id)
1902 );
1903 $count += $result->numRows();
1904
1905 return $count;
1906 }
1907
1914 public function isClone($question_id = "")
1915 {
1916 global $DIC;
1917 $ilDB = $DIC['ilDB'];
1918
1919 if ($question_id < 1) {
1920 $question_id = $this->id;
1921 }
1922 $result = $ilDB->queryF(
1923 "SELECT original_id FROM qpl_questions WHERE question_id = %s",
1924 array('integer'),
1925 array($question_id)
1926 );
1927 $row = $ilDB->fetchAssoc($result);
1928 return ($row["original_id"] > 0) ? true : false;
1929 }
1930
1937 public function pcArrayShuffle($array)
1938 {
1939 $keys = array_keys($array);
1940 shuffle($keys);
1941 $result = array();
1942 foreach ($keys as $key) {
1943 $result[$key] = $array[$key];
1944 }
1945 return $result;
1946 }
1947
1953 public static function getQuestionTypeFromDb($question_id)
1954 {
1955 global $DIC;
1956 $ilDB = $DIC['ilDB'];
1957
1958 $result = $ilDB->queryF(
1959 "SELECT qpl_qst_type.type_tag FROM qpl_qst_type, qpl_questions WHERE qpl_questions.question_id = %s AND qpl_questions.question_type_fi = qpl_qst_type.question_type_id",
1960 array('integer'),
1961 array($question_id)
1962 );
1963 $data = $ilDB->fetchAssoc($result);
1964 return $data["type_tag"];
1965 }
1966
1973 public function getAdditionalTableName()
1974 {
1975 return "";
1976 }
1977
1984 public function getAnswerTableName()
1985 {
1986 return "";
1987 }
1988
1995 public function deleteAnswers($question_id)
1996 {
1997 global $DIC;
1998 $ilDB = $DIC['ilDB'];
1999 $answer_table_name = $this->getAnswerTableName();
2000
2001 if (!is_array($answer_table_name)) {
2002 $answer_table_name = array($answer_table_name);
2003 }
2004
2005 foreach ($answer_table_name as $table) {
2006 if (strlen($table)) {
2007 $affectedRows = $ilDB->manipulateF(
2008 "DELETE FROM $table WHERE question_fi = %s",
2009 array('integer'),
2010 array($question_id)
2011 );
2012 }
2013 }
2014 }
2015
2022 public function deleteAdditionalTableData($question_id)
2023 {
2024 global $DIC;
2025 $ilDB = $DIC['ilDB'];
2026
2027 $additional_table_name = $this->getAdditionalTableName();
2028
2029 if (!is_array($additional_table_name)) {
2030 $additional_table_name = array($additional_table_name);
2031 }
2032
2033 foreach ($additional_table_name as $table) {
2034 if (strlen($table)) {
2035 $affectedRows = $ilDB->manipulateF(
2036 "DELETE FROM $table WHERE question_fi = %s",
2037 array('integer'),
2038 array($question_id)
2039 );
2040 }
2041 }
2042 }
2043
2050 protected function deletePageOfQuestion($question_id)
2051 {
2052 include_once "./Modules/TestQuestionPool/classes/class.ilAssQuestionPage.php";
2053 $page = new ilAssQuestionPage($question_id);
2054 $page->delete();
2055 return true;
2056 }
2057
2064 public function delete($question_id)
2065 {
2066 global $DIC;
2067 $ilDB = $DIC['ilDB'];
2068 $ilLog = $DIC['ilLog'];
2069
2070 if ($question_id < 1) {
2071 return true;
2072 } // nothing to do
2073
2074 $result = $ilDB->queryF(
2075 "SELECT obj_fi FROM qpl_questions WHERE question_id = %s",
2076 array('integer'),
2077 array($question_id)
2078 );
2079 if ($result->numRows() == 1) {
2080 $row = $ilDB->fetchAssoc($result);
2081 $obj_id = $row["obj_fi"];
2082 } else {
2083 return true; // nothing to do
2084 }
2085 try {
2086 $this->deletePageOfQuestion($question_id);
2087 } catch (Exception $e) {
2088 $ilLog->write("EXCEPTION: Could not delete page of question $question_id: $e");
2089 return false;
2090 }
2091
2092 $affectedRows = $ilDB->manipulateF(
2093 "DELETE FROM qpl_questions WHERE question_id = %s",
2094 array('integer'),
2095 array($question_id)
2096 );
2097 if ($affectedRows == 0) {
2098 return false;
2099 }
2100
2101 try {
2102 $this->deleteAdditionalTableData($question_id);
2103 $this->deleteAnswers($question_id);
2104 $this->feedbackOBJ->deleteGenericFeedbacks($question_id, $this->isAdditionalContentEditingModePageObject());
2105 $this->feedbackOBJ->deleteSpecificAnswerFeedbacks($question_id, $this->isAdditionalContentEditingModePageObject());
2106 } catch (Exception $e) {
2107 $ilLog->write("EXCEPTION: Could not delete additional table data of question $question_id: $e");
2108 return false;
2109 }
2110
2111 try {
2112 // delete the question in the tst_test_question table (list of test questions)
2113 $affectedRows = $ilDB->manipulateF(
2114 "DELETE FROM tst_test_question WHERE question_fi = %s",
2115 array('integer'),
2116 array($question_id)
2117 );
2118 } catch (Exception $e) {
2119 $ilLog->write("EXCEPTION: Could not delete delete question $question_id from a test: $e");
2120 return false;
2121 }
2122
2123 try {
2124 // delete suggested solutions contained in the question
2125 $affectedRows = $ilDB->manipulateF(
2126 "DELETE FROM qpl_sol_sug WHERE question_fi = %s",
2127 array('integer'),
2128 array($question_id)
2129 );
2130 } catch (Exception $e) {
2131 $ilLog->write("EXCEPTION: Could not delete suggested solutions of question $question_id: $e");
2132 return false;
2133 }
2134
2135 try {
2136 $directory = CLIENT_WEB_DIR . "/assessment/" . $obj_id . "/$question_id";
2137 if (preg_match("/\d+/", $obj_id) and preg_match("/\d+/", $question_id) and is_dir($directory)) {
2138 include_once "./Services/Utilities/classes/class.ilUtil.php";
2139 ilUtil::delDir($directory);
2140 }
2141 } catch (Exception $e) {
2142 $ilLog->write("EXCEPTION: Could not delete question file directory $directory of question $question_id: $e");
2143 return false;
2144 }
2145
2146 try {
2147 include_once("./Services/MediaObjects/classes/class.ilObjMediaObject.php");
2148 $mobs = ilObjMediaObject::_getMobsOfObject("qpl:html", $question_id);
2149 // remaining usages are not in text anymore -> delete them
2150 // and media objects (note: delete method of ilObjMediaObject
2151 // checks whether object is used in another context; if yes,
2152 // the object is not deleted!)
2153 foreach ($mobs as $mob) {
2154 ilObjMediaObject::_removeUsage($mob, "qpl:html", $question_id);
2155 if (ilObjMediaObject::_exists($mob)) {
2156 $mob_obj = new ilObjMediaObject($mob);
2157 $mob_obj->delete();
2158 }
2159 }
2160 } catch (Exception $e) {
2161 $ilLog->write("EXCEPTION: Error deleting the media objects of question $question_id: $e");
2162 return false;
2163 }
2164
2165 require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionHintTracking.php';
2166 ilAssQuestionHintTracking::deleteRequestsByQuestionIds(array($question_id));
2167
2168 require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionHintList.php';
2170
2171 require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionSkillAssignmentList.php';
2172 $assignmentList = new ilAssQuestionSkillAssignmentList($ilDB);
2173 $assignmentList->setParentObjId($obj_id);
2174 $assignmentList->setQuestionIdFilter($question_id);
2175 $assignmentList->loadFromDb();
2176 foreach ($assignmentList->getAssignmentsByQuestionId($question_id) as $assignment) {
2177 /* @var ilAssQuestionSkillAssignment $assignment */
2178 $assignment->deleteFromDb();
2179 }
2180
2182
2183 try {
2184 // update question count of question pool
2185 include_once "./Modules/TestQuestionPool/classes/class.ilObjQuestionPool.php";
2187 } catch (Exception $e) {
2188 $ilLog->write("EXCEPTION: Error updating the question pool question count of question pool " . $this->getObjId() . " when deleting question $question_id: $e");
2189 return false;
2190 }
2191
2192 $this->notifyQuestionDeleted($this);
2193
2194 return true;
2195 }
2196
2197 private function deleteTaxonomyAssignments()
2198 {
2199 require_once 'Services/Taxonomy/classes/class.ilObjTaxonomy.php';
2200 require_once 'Services/Taxonomy/classes/class.ilTaxNodeAssignment.php';
2201 $taxIds = ilObjTaxonomy::getUsageOfObject($this->getObjId());
2202
2203 foreach ($taxIds as $taxId) {
2204 $taxNodeAssignment = new ilTaxNodeAssignment('qpl', $this->getObjId(), 'quest', $taxId);
2205 $taxNodeAssignment->deleteAssignmentsOfItem($this->getId());
2206 }
2207 }
2208
2212 public function getTotalAnswers()
2213 {
2214 return $this->_getTotalAnswers($this->id);
2215 }
2216
2223 public function _getTotalAnswers($a_q_id)
2224 {
2225 global $DIC;
2226 $ilDB = $DIC['ilDB'];
2227
2228 // get all question references to the question id
2229 $result = $ilDB->queryF(
2230 "SELECT question_id FROM qpl_questions WHERE original_id = %s OR question_id = %s",
2231 array('integer','integer'),
2232 array($a_q_id, $a_q_id)
2233 );
2234 if ($result->numRows() == 0) {
2235 return 0;
2236 }
2237 $found_id = array();
2238 while ($row = $ilDB->fetchAssoc($result)) {
2239 array_push($found_id, $row["question_id"]);
2240 }
2241
2242 $result = $ilDB->query("SELECT * FROM tst_test_result WHERE " . $ilDB->in('question_fi', $found_id, false, 'integer'));
2243
2244 return $result->numRows();
2245 }
2246
2247
2254 public static function _getTotalRightAnswers($a_q_id)
2255 {
2256 global $DIC;
2257 $ilDB = $DIC['ilDB'];
2258 $result = $ilDB->queryF(
2259 "SELECT question_id FROM qpl_questions WHERE original_id = %s OR question_id = %s",
2260 array('integer','integer'),
2261 array($a_q_id, $a_q_id)
2262 );
2263 if ($result->numRows() == 0) {
2264 return 0;
2265 }
2266 $found_id = array();
2267 while ($row = $ilDB->fetchAssoc($result)) {
2268 array_push($found_id, $row["question_id"]);
2269 }
2270 $result = $ilDB->query("SELECT * FROM tst_test_result WHERE " . $ilDB->in('question_fi', $found_id, false, 'integer'));
2271 $answers = array();
2272 while ($row = $ilDB->fetchAssoc($result)) {
2273 $reached = $row["points"];
2274 include_once "./Modules/TestQuestionPool/classes/class.assQuestion.php";
2275 $max = assQuestion::_getMaximumPoints($row["question_fi"]);
2276 array_push($answers, array("reached" => $reached, "max" => $max));
2277 }
2278 $max = 0.0;
2279 $reached = 0.0;
2280 foreach ($answers as $key => $value) {
2281 $max += $value["max"];
2282 $reached += $value["reached"];
2283 }
2284 if ($max > 0) {
2285 return $reached / $max;
2286 } else {
2287 return 0;
2288 }
2289 }
2290
2296 public static function _getTitle($a_q_id)
2297 {
2298 global $DIC;
2299 $ilDB = $DIC['ilDB'];
2300 $result = $ilDB->queryF(
2301 "SELECT title FROM qpl_questions WHERE question_id = %s",
2302 array('integer'),
2303 array($a_q_id)
2304 );
2305 if ($result->numRows() == 1) {
2306 $row = $ilDB->fetchAssoc($result);
2307 return $row["title"];
2308 } else {
2309 return "";
2310 }
2311 }
2312
2318 public static function _getQuestionText($a_q_id)
2319 {
2320 global $DIC;
2321 $ilDB = $DIC['ilDB'];
2322 $result = $ilDB->queryF(
2323 "SELECT question_text FROM qpl_questions WHERE question_id = %s",
2324 array('integer'),
2325 array($a_q_id)
2326 );
2327 if ($result->numRows() == 1) {
2328 $row = $ilDB->fetchAssoc($result);
2329 return $row["question_text"];
2330 } else {
2331 return "";
2332 }
2333 }
2334
2335 public static function isFileAvailable($file)
2336 {
2337 if (!file_exists($file)) {
2338 return false;
2339 }
2340
2341 if (!is_file($file)) {
2342 return false;
2343 }
2344
2345 if (!is_readable($file)) {
2346 return false;
2347 }
2348
2349 return true;
2350 }
2351
2352 public function copyXHTMLMediaObjectsOfQuestion($a_q_id)
2353 {
2354 include_once("./Services/MediaObjects/classes/class.ilObjMediaObject.php");
2355 $mobs = ilObjMediaObject::_getMobsOfObject("qpl:html", $a_q_id);
2356 foreach ($mobs as $mob) {
2357 ilObjMediaObject::_saveUsage($mob, "qpl:html", $this->getId());
2358 }
2359 }
2360
2362 {
2363 include_once("./Services/MediaObjects/classes/class.ilObjMediaObject.php");
2364 $mobs = ilObjMediaObject::_getMobsOfObject("qpl:html", $this->getId());
2365 foreach ($mobs as $mob) {
2366 ilObjMediaObject::_saveUsage($mob, "qpl:html", $this->original_id);
2367 }
2368 }
2369
2373 public function createPageObject()
2374 {
2375 $qpl_id = $this->getObjId();
2376
2377 include_once "./Modules/TestQuestionPool/classes/class.ilAssQuestionPage.php";
2378 $this->page = new ilAssQuestionPage(0);
2379 $this->page->setId($this->getId());
2380 $this->page->setParentId($qpl_id);
2381 $this->page->setXMLContent("<PageObject><PageContent>" .
2382 "<Question QRef=\"il__qst_" . $this->getId() . "\"/>" .
2383 "</PageContent></PageObject>");
2384 $this->page->create();
2385 }
2386
2387 public function copyPageOfQuestion($a_q_id)
2388 {
2389 if ($a_q_id > 0) {
2390 include_once "./Modules/TestQuestionPool/classes/class.ilAssQuestionPage.php";
2391 $page = new ilAssQuestionPage($a_q_id);
2392
2393 $xml = str_replace("il__qst_" . $a_q_id, "il__qst_" . $this->id, $page->getXMLContent());
2394 $this->page->setXMLContent($xml);
2395 $this->page->updateFromXML();
2396 }
2397 }
2398
2399 public function getPageOfQuestion()
2400 {
2401 include_once "./Modules/TestQuestionPool/classes/class.ilAssQuestionPage.php";
2402 $page = new ilAssQuestionPage($this->id);
2403 return $page->getXMLContent();
2404 }
2405
2411 public static function _getQuestionType($question_id)
2412 {
2413 global $DIC;
2414 $ilDB = $DIC['ilDB'];
2415
2416 if ($question_id < 1) {
2417 return "";
2418 }
2419 $result = $ilDB->queryF(
2420 "SELECT type_tag FROM qpl_questions, qpl_qst_type WHERE qpl_questions.question_id = %s AND qpl_questions.question_type_fi = qpl_qst_type.question_type_id",
2421 array('integer'),
2422 array($question_id)
2423 );
2424 if ($result->numRows() == 1) {
2425 $data = $ilDB->fetchAssoc($result);
2426 return $data["type_tag"];
2427 } else {
2428 return "";
2429 }
2430 }
2431
2439 public static function _getQuestionTitle($question_id)
2440 {
2441 global $DIC;
2442 $ilDB = $DIC['ilDB'];
2443
2444 if ($question_id < 1) {
2445 return "";
2446 }
2447
2448 $result = $ilDB->queryF(
2449 "SELECT title FROM qpl_questions WHERE qpl_questions.question_id = %s",
2450 array('integer'),
2451 array($question_id)
2452 );
2453 if ($result->numRows() == 1) {
2454 $data = $ilDB->fetchAssoc($result);
2455 return $data["title"];
2456 } else {
2457 return "";
2458 }
2459 }
2460
2462 {
2463 $this->original_id = $original_id;
2464 }
2465
2466 public function getOriginalId()
2467 {
2468 return $this->original_id;
2469 }
2470
2471 protected static $imageSourceFixReplaceMap = array(
2472 'ok.svg' => 'ok.png', 'not_ok.svg' => 'not_ok.png',
2473 'checkbox_checked.svg' => 'checkbox_checked.png',
2474 'checkbox_unchecked.svg' => 'checkbox_unchecked.png',
2475 'radiobutton_checked.svg' => 'radiobutton_checked.png',
2476 'radiobutton_unchecked.svg' => 'radiobutton_unchecked.png'
2477 );
2478
2479 public function fixSvgToPng($imageFilenameContainingString)
2480 {
2481 $needles = array_keys(self::$imageSourceFixReplaceMap);
2482 $replacements = array_values(self::$imageSourceFixReplaceMap);
2483 return str_replace($needles, $replacements, $imageFilenameContainingString);
2484 }
2485
2486
2487 public function fixUnavailableSkinImageSources($html)
2488 {
2489 $matches = null;
2490 if (preg_match_all('/src="(.*?)"/m', $html, $matches)) {
2491 $sources = $matches[1];
2492
2493 $needleReplacementMap = array();
2494
2495 foreach ($sources as $src) {
2496 $file = ilUtil::removeTrailingPathSeparators(ILIAS_ABSOLUTE_PATH) . DIRECTORY_SEPARATOR . $src;
2497
2498 if (file_exists($file)) {
2499 continue;
2500 }
2501
2502 $levels = explode(DIRECTORY_SEPARATOR, $src);
2503 if (count($levels) < 5 || $levels[0] != 'Customizing' || $levels[2] != 'skin') {
2504 continue;
2505 }
2506
2507 $component = '';
2508
2509 if ($levels[4] == 'Modules' || $levels[4] == 'Services') {
2510 $component = $levels[4] . DIRECTORY_SEPARATOR . $levels[5];
2511 }
2512
2513 $needleReplacementMap[$src] = ilUtil::getImagePath(basename($src), $component);
2514 }
2515
2516 if (count($needleReplacementMap)) {
2517 $html = str_replace(array_keys($needleReplacementMap), array_values($needleReplacementMap), $html);
2518 }
2519 }
2520
2521 return $html;
2522 }
2523
2530 public function loadFromDb($question_id)
2531 {
2532 global $DIC;
2533 $ilDB = $DIC['ilDB'];
2534
2535 $result = $ilDB->queryF(
2536 "SELECT external_id FROM qpl_questions WHERE question_id = %s",
2537 array("integer"),
2538 array($question_id)
2539 );
2540 if ($result->numRows() == 1) {
2541 $data = $ilDB->fetchAssoc($result);
2542 $this->external_id = $data['external_id'];
2543 }
2544
2545 $result = $ilDB->queryF(
2546 "SELECT * FROM qpl_sol_sug WHERE question_fi = %s",
2547 array('integer'),
2548 array($this->getId())
2549 );
2550 $this->suggested_solutions = array();
2551 if ($result->numRows()) {
2552 include_once("./Services/RTE/classes/class.ilRTE.php");
2553 while ($row = $ilDB->fetchAssoc($result)) {
2554 $value = (is_array(unserialize($row["value"]))) ? unserialize($row["value"]) : ilRTE::_replaceMediaObjectImageSrc($row["value"], 1);
2555 $this->suggested_solutions[$row["subquestion_index"]] = array(
2556 "type" => $row["type"],
2557 "value" => $value,
2558 "internal_link" => $row["internal_link"],
2559 "import_id" => $row["import_id"]
2560 );
2561 }
2562 }
2563 }
2564
2571 public function createNewQuestion($a_create_page = true)
2572 {
2573 global $DIC;
2574 $ilDB = $DIC['ilDB'];
2575 $ilUser = $DIC['ilUser'];
2576
2577 $complete = "0";
2578 $estw_time = $this->getEstimatedWorkingTime();
2579 $estw_time = sprintf("%02d:%02d:%02d", $estw_time['h'], $estw_time['m'], $estw_time['s']);
2580 $obj_id = ($this->getObjId() <= 0) ? (ilObject::_lookupObjId((strlen($_GET["ref_id"])) ? $_GET["ref_id"] : $_POST["sel_qpl"])) : $this->getObjId();
2581 if ($obj_id > 0) {
2582 if ($a_create_page) {
2583 $tstamp = 0;
2584 } else {
2585 // question pool must not try to purge
2586 $tstamp = time();
2587 }
2588
2589 $next_id = $ilDB->nextId('qpl_questions');
2590 $affectedRows = $ilDB->insert("qpl_questions", array(
2591 "question_id" => array("integer", $next_id),
2592 "question_type_fi" => array("integer", $this->getQuestionTypeID()),
2593 "obj_fi" => array("integer", $obj_id),
2594 "title" => array("text", null),
2595 "description" => array("text", null),
2596 "author" => array("text", $this->getAuthor()),
2597 "owner" => array("integer", $ilUser->getId()),
2598 "question_text" => array("clob", null),
2599 "points" => array("float", 0),
2600 "nr_of_tries" => array("integer", $this->getDefaultNrOfTries()), // #10771
2601 "working_time" => array("text", $estw_time),
2602 "complete" => array("text", $complete),
2603 "created" => array("integer", time()),
2604 "original_id" => array("integer", null),
2605 "tstamp" => array("integer", $tstamp),
2606 "external_id" => array("text", $this->getExternalId()),
2607 'add_cont_edit_mode' => array('text', $this->getAdditionalContentEditingMode())
2608 ));
2609 $this->setId($next_id);
2610
2611 if ($a_create_page) {
2612 // create page object of question
2613 $this->createPageObject();
2614 }
2615 }
2616
2617 $this->notifyQuestionCreated();
2618
2619 return $this->getId();
2620 }
2621
2623 {
2624 global $DIC;
2625 $ilDB = $DIC['ilDB'];
2626
2627 $estw_time = $this->getEstimatedWorkingTime();
2628 $estw_time = sprintf("%02d:%02d:%02d", $estw_time['h'], $estw_time['m'], $estw_time['s']);
2629
2630 // cleanup RTE images which are not inserted into the question text
2631 include_once("./Services/RTE/classes/class.ilRTE.php");
2632 if ($this->getId() == -1) {
2633 // Neuen Datensatz schreiben
2634 $next_id = $ilDB->nextId('qpl_questions');
2635 $affectedRows = $ilDB->insert("qpl_questions", array(
2636 "question_id" => array("integer", $next_id),
2637 "question_type_fi" => array("integer", $this->getQuestionTypeID()),
2638 "obj_fi" => array("integer", $this->getObjId()),
2639 "title" => array("text", $this->getTitle()),
2640 "description" => array("text", $this->getComment()),
2641 "author" => array("text", $this->getAuthor()),
2642 "owner" => array("integer", $this->getOwner()),
2643 "question_text" => array("clob", ilRTE::_replaceMediaObjectImageSrc($this->getQuestion(), 0)),
2644 "points" => array("float", $this->getMaximumPoints()),
2645 "working_time" => array("text", $estw_time),
2646 "nr_of_tries" => array("integer", $this->getNrOfTries()),
2647 "created" => array("integer", time()),
2648 "original_id" => array("integer", ($original_id) ? $original_id : null),
2649 "tstamp" => array("integer", time()),
2650 "external_id" => array("text", $this->getExternalId()),
2651 'add_cont_edit_mode' => array('text', $this->getAdditionalContentEditingMode())
2652 ));
2653 $this->setId($next_id);
2654 // create page object of question
2655 $this->createPageObject();
2656 } else {
2657 // Vorhandenen Datensatz aktualisieren
2658 $affectedRows = $ilDB->update("qpl_questions", array(
2659 "obj_fi" => array("integer", $this->getObjId()),
2660 "title" => array("text", $this->getTitle()),
2661 "description" => array("text", $this->getComment()),
2662 "author" => array("text", $this->getAuthor()),
2663 "question_text" => array("clob", ilRTE::_replaceMediaObjectImageSrc($this->getQuestion(), 0)),
2664 "points" => array("float", $this->getMaximumPoints()),
2665 "nr_of_tries" => array("integer", $this->getNrOfTries()),
2666 "working_time" => array("text", $estw_time),
2667 "tstamp" => array("integer", time()),
2668 'complete' => array('integer', $this->isComplete()),
2669 "external_id" => array("text", $this->getExternalId())
2670 ), array(
2671 "question_id" => array("integer", $this->getId())
2672 ));
2673 }
2674 }
2675
2682 public function saveToDb($original_id = "")
2683 {
2684 global $DIC;
2685
2686 $this->updateSuggestedSolutions();
2687
2688 // remove unused media objects from ILIAS
2689 $this->cleanupMediaObjectUsage();
2690
2691 $complete = "0";
2692 if ($this->isComplete()) {
2693 $complete = "1";
2694 }
2695
2696 $DIC->database()->update('qpl_questions', array(
2697 'tstamp' => array('integer', time()),
2698 'owner' => array('integer', ($this->getOwner() <= 0 ? $this->ilias->account->id : $this->getOwner())),
2699 'complete' => array('integer', $complete),
2700 'lifecycle' => array('text', $this->getLifecycle()->getIdentifier()),
2701 ), array(
2702 'question_id' => array('integer', $this->getId())
2703 ));
2704
2705 // update question count of question pool
2706 include_once "./Modules/TestQuestionPool/classes/class.ilObjQuestionPool.php";
2708
2709 $this->notifyQuestionEdited($this);
2710 }
2711
2715 public function setNewOriginalId($newId)
2716 {
2717 self::saveOriginalId($this->getId(), $newId);
2718 }
2719
2720 public static function saveOriginalId($questionId, $originalId)
2721 {
2722 $query = "UPDATE qpl_questions SET tstamp = %s, original_id = %s WHERE question_id = %s";
2723
2724 $GLOBALS['DIC']['ilDB']->manipulateF(
2725 $query,
2726 array('integer','integer', 'text'),
2727 array(time(), $originalId, $questionId)
2728 );
2729 }
2730
2731 public static function resetOriginalId($questionId)
2732 {
2733 $query = "UPDATE qpl_questions SET tstamp = %s, original_id = NULL WHERE question_id = %s";
2734
2735 $GLOBALS['DIC']['ilDB']->manipulateF(
2736 $query,
2737 array('integer', 'text'),
2738 array(time(), $questionId)
2739 );
2740 }
2741
2745 protected function onDuplicate($originalParentId, $originalQuestionId, $duplicateParentId, $duplicateQuestionId)
2746 {
2747 $this->duplicateSuggestedSolutionFiles($originalParentId, $originalQuestionId);
2748
2749 // duplicate question feeback
2750 $this->feedbackOBJ->duplicateFeedback($originalQuestionId, $duplicateQuestionId);
2751
2752 // duplicate question hints
2753 $this->duplicateQuestionHints($originalQuestionId, $duplicateQuestionId);
2754
2755 // duplicate skill assignments
2756 $this->duplicateSkillAssignments($originalParentId, $originalQuestionId, $duplicateParentId, $duplicateQuestionId);
2757 }
2758
2759 protected function beforeSyncWithOriginal($origQuestionId, $dupQuestionId, $origParentObjId, $dupParentObjId)
2760 {
2761 }
2762
2763 protected function afterSyncWithOriginal($origQuestionId, $dupQuestionId, $origParentObjId, $dupParentObjId)
2764 {
2765 // sync question feeback
2766 $this->feedbackOBJ->syncFeedback($origQuestionId, $dupQuestionId);
2767 }
2768
2772 protected function onCopy($sourceParentId, $sourceQuestionId, $targetParentId, $targetQuestionId)
2773 {
2774 $this->copySuggestedSolutionFiles($sourceParentId, $sourceQuestionId);
2775
2776 // duplicate question feeback
2777 $this->feedbackOBJ->duplicateFeedback($sourceQuestionId, $targetQuestionId);
2778
2779 // duplicate question hints
2780 $this->duplicateQuestionHints($sourceQuestionId, $targetQuestionId);
2781
2782 // duplicate skill assignments
2783 $this->duplicateSkillAssignments($sourceParentId, $sourceQuestionId, $targetParentId, $targetQuestionId);
2784 }
2785
2790 {
2791 global $DIC;
2792 $ilDB = $DIC['ilDB'];
2793 // delete the links in the qpl_sol_sug table
2794 $affectedRows = $ilDB->manipulateF(
2795 "DELETE FROM qpl_sol_sug WHERE question_fi = %s",
2796 array('integer'),
2797 array($this->getId())
2798 );
2799 // delete the links in the int_link table
2800 include_once "./Services/Link/classes/class.ilInternalLink.php";
2802 $this->suggested_solutions = array();
2804 }
2805
2813 public function getSuggestedSolution($subquestion_index = 0)
2814 {
2815 if (array_key_exists($subquestion_index, $this->suggested_solutions)) {
2816 return $this->suggested_solutions[$subquestion_index];
2817 } else {
2818 return array();
2819 }
2820 }
2821
2830 public function getSuggestedSolutionTitle($subquestion_index = 0)
2831 {
2832 if (array_key_exists($subquestion_index, $this->suggested_solutions)) {
2833 $title = $this->suggested_solutions[$subquestion_index]["internal_link"];
2834 // TO DO: resolve internal link an get link type and title
2835 } else {
2836 $title = "";
2837 }
2838 return $title;
2839 }
2840
2850 public function setSuggestedSolution($solution_id = "", $subquestion_index = 0, $is_import = false)
2851 {
2852 if (strcmp($solution_id, "") != 0) {
2853 $import_id = "";
2854 if ($is_import) {
2855 $import_id = $solution_id;
2856 $solution_id = $this->_resolveInternalLink($import_id);
2857 }
2858 $this->suggested_solutions[$subquestion_index] = array(
2859 "internal_link" => $solution_id,
2860 "import_id" => $import_id
2861 );
2862 }
2863 }
2864
2868 protected function duplicateSuggestedSolutionFiles($parent_id, $question_id)
2869 {
2870 global $DIC;
2871 $ilLog = $DIC['ilLog'];
2872
2873 foreach ($this->suggested_solutions as $index => $solution) {
2874 if (strcmp($solution["type"], "file") == 0) {
2875 $filepath = $this->getSuggestedSolutionPath();
2876 $filepath_original = str_replace(
2877 "/{$this->obj_id}/{$this->id}/solution",
2878 "/$parent_id/$question_id/solution",
2879 $filepath
2880 );
2881 if (!file_exists($filepath)) {
2882 ilUtil::makeDirParents($filepath);
2883 }
2884 $filename = $solution["value"]["name"];
2885 if (strlen($filename)) {
2886 if (!copy($filepath_original . $filename, $filepath . $filename)) {
2887 $ilLog->write("File could not be duplicated!!!!", $ilLog->ERROR);
2888 $ilLog->write("object: " . print_r($this, true), $ilLog->ERROR);
2889 }
2890 }
2891 }
2892 }
2893 }
2894
2899 {
2900 global $DIC;
2901 $ilLog = $DIC['ilLog'];
2902
2903 $filepath = $this->getSuggestedSolutionPath();
2904 $filepath_original = str_replace("/$this->id/solution", "/$original_id/solution", $filepath);
2905 ilUtil::delDir($filepath_original);
2906 foreach ($this->suggested_solutions as $index => $solution) {
2907 if (strcmp($solution["type"], "file") == 0) {
2908 if (!file_exists($filepath_original)) {
2909 ilUtil::makeDirParents($filepath_original);
2910 }
2911 $filename = $solution["value"]["name"];
2912 if (strlen($filename)) {
2913 if (!@copy($filepath . $filename, $filepath_original . $filename)) {
2914 $ilLog->write("File could not be duplicated!!!!", $ilLog->ERROR);
2915 $ilLog->write("object: " . print_r($this, true), $ilLog->ERROR);
2916 }
2917 }
2918 }
2919 }
2920 }
2921
2922 protected function copySuggestedSolutionFiles($source_questionpool_id, $source_question_id)
2923 {
2924 global $DIC;
2925 $ilLog = $DIC['ilLog'];
2926
2927 foreach ($this->suggested_solutions as $index => $solution) {
2928 if (strcmp($solution["type"], "file") == 0) {
2929 $filepath = $this->getSuggestedSolutionPath();
2930 $filepath_original = str_replace("/$this->obj_id/$this->id/solution", "/$source_questionpool_id/$source_question_id/solution", $filepath);
2931 if (!file_exists($filepath)) {
2932 ilUtil::makeDirParents($filepath);
2933 }
2934 $filename = $solution["value"]["name"];
2935 if (strlen($filename)) {
2936 if (!copy($filepath_original . $filename, $filepath . $filename)) {
2937 $ilLog->write("File could not be copied!!!!", $ilLog->ERROR);
2938 $ilLog->write("object: " . print_r($this, true), $ilLog->ERROR);
2939 }
2940 }
2941 }
2942 }
2943 }
2944
2948 public function updateSuggestedSolutions($original_id = "")
2949 {
2950 global $DIC;
2951 $ilDB = $DIC['ilDB'];
2952
2953 $id = (strlen($original_id) && is_numeric($original_id)) ? $original_id : $this->getId();
2954 include_once "./Services/Link/classes/class.ilInternalLink.php";
2955 $affectedRows = $ilDB->manipulateF(
2956 "DELETE FROM qpl_sol_sug WHERE question_fi = %s",
2957 array('integer'),
2958 array($id)
2959 );
2961 include_once("./Services/RTE/classes/class.ilRTE.php");
2962 foreach ($this->suggested_solutions as $index => $solution) {
2963 $next_id = $ilDB->nextId('qpl_sol_sug');
2965 $ilDB->insert(
2966 'qpl_sol_sug',
2967 array(
2968 'suggested_solution_id' => array( 'integer', $next_id ),
2969 'question_fi' => array( 'integer', $id ),
2970 'type' => array( 'text', $solution['type'] ),
2971 'value' => array( 'clob', ilRTE::_replaceMediaObjectImageSrc((is_array($solution['value'])) ? serialize($solution[ 'value' ]) : $solution['value'], 0) ),
2972 'internal_link' => array( 'text', $solution['internal_link'] ),
2973 'import_id' => array( 'text', null ),
2974 'subquestion_index' => array( 'integer', $index ),
2975 'tstamp' => array( 'integer', time() ),
2976 )
2977 );
2978 if (preg_match("/il_(\d*?)_(\w+)_(\d+)/", $solution["internal_link"], $matches)) {
2979 ilInternalLink::_saveLink("qst", $id, $matches[2], $matches[3], $matches[1]);
2980 }
2981 }
2982 if (strlen($original_id) && is_numeric($original_id)) {
2984 }
2985 $this->cleanupMediaObjectUsage();
2986 }
2987
2997 public function saveSuggestedSolution($type, $solution_id = "", $subquestion_index = 0, $value = "")
2998 {
2999 global $DIC;
3000 $ilDB = $DIC['ilDB'];
3001
3002 $affectedRows = $ilDB->manipulateF(
3003 "DELETE FROM qpl_sol_sug WHERE question_fi = %s AND subquestion_index = %s",
3004 array("integer", "integer"),
3005 array(
3006 $this->getId(),
3007 $subquestion_index
3008 )
3009 );
3010
3011 $next_id = $ilDB->nextId('qpl_sol_sug');
3012 include_once("./Services/RTE/classes/class.ilRTE.php");
3014 $affectedRows = $ilDB->insert(
3015 'qpl_sol_sug',
3016 array(
3017 'suggested_solution_id' => array( 'integer', $next_id ),
3018 'question_fi' => array( 'integer', $this->getId() ),
3019 'type' => array( 'text', $type ),
3020 'value' => array( 'clob', ilRTE::_replaceMediaObjectImageSrc((is_array($value)) ? serialize($value) : $value, 0) ),
3021 'internal_link' => array( 'text', $solution_id ),
3022 'import_id' => array( 'text', null ),
3023 'subquestion_index' => array( 'integer', $subquestion_index ),
3024 'tstamp' => array( 'integer', time() ),
3025 )
3026 );
3027 if ($affectedRows == 1) {
3028 $this->suggested_solutions[$subquestion_index] = array(
3029 "type" => $type,
3030 "value" => $value,
3031 "internal_link" => $solution_id,
3032 "import_id" => ""
3033 );
3034 }
3035 $this->cleanupMediaObjectUsage();
3036 }
3037
3038 public function _resolveInternalLink($internal_link)
3039 {
3040 if (preg_match("/il_(\d+)_(\w+)_(\d+)/", $internal_link, $matches)) {
3041 switch ($matches[2]) {
3042 case "lm":
3043 $resolved_link = ilLMObject::_getIdForImportId($internal_link);
3044 break;
3045 case "pg":
3046 $resolved_link = ilInternalLink::_getIdForImportId("PageObject", $internal_link);
3047 break;
3048 case "st":
3049 $resolved_link = ilInternalLink::_getIdForImportId("StructureObject", $internal_link);
3050 break;
3051 case "git":
3052 $resolved_link = ilInternalLink::_getIdForImportId("GlossaryItem", $internal_link);
3053 break;
3054 case "mob":
3055 $resolved_link = ilInternalLink::_getIdForImportId("MediaObject", $internal_link);
3056 break;
3057 }
3058 if (strcmp($resolved_link, "") == 0) {
3059 $resolved_link = $internal_link;
3060 }
3061 } else {
3062 $resolved_link = $internal_link;
3063 }
3064 return $resolved_link;
3065 }
3066
3067 public function _resolveIntLinks($question_id)
3068 {
3069 global $DIC;
3070 $ilDB = $DIC['ilDB'];
3071 $resolvedlinks = 0;
3072 $result = $ilDB->queryF(
3073 "SELECT * FROM qpl_sol_sug WHERE question_fi = %s",
3074 array('integer'),
3075 array($question_id)
3076 );
3077 if ($result->numRows()) {
3078 while ($row = $ilDB->fetchAssoc($result)) {
3079 $internal_link = $row["internal_link"];
3080 include_once "./Modules/TestQuestionPool/classes/class.assQuestion.php";
3081 $resolved_link = assQuestion::_resolveInternalLink($internal_link);
3082 if (strcmp($internal_link, $resolved_link) != 0) {
3083 // internal link was resolved successfully
3084 $affectedRows = $ilDB->manipulateF(
3085 "UPDATE qpl_sol_sug SET internal_link = %s WHERE suggested_solution_id = %s",
3086 array('text','integer'),
3087 array($resolved_link, $row["suggested_solution_id"])
3088 );
3089 $resolvedlinks++;
3090 }
3091 }
3092 }
3093 if ($resolvedlinks) {
3094 // there are resolved links -> reenter theses links to the database
3095
3096 // delete all internal links from the database
3097 include_once "./Services/Link/classes/class.ilInternalLink.php";
3098 ilInternalLink::_deleteAllLinksOfSource("qst", $question_id);
3099
3100 $result = $ilDB->queryF(
3101 "SELECT * FROM qpl_sol_sug WHERE question_fi = %s",
3102 array('integer'),
3103 array($question_id)
3104 );
3105 if ($result->numRows()) {
3106 while ($row = $ilDB->fetchAssoc($result)) {
3107 if (preg_match("/il_(\d*?)_(\w+)_(\d+)/", $row["internal_link"], $matches)) {
3108 ilInternalLink::_saveLink("qst", $question_id, $matches[2], $matches[3], $matches[1]);
3109 }
3110 }
3111 }
3112 }
3113 }
3114
3115 public static function _getInternalLinkHref($target = "")
3116 {
3117 global $DIC;
3118 $ilDB = $DIC['ilDB'];
3119 $linktypes = array(
3120 "lm" => "LearningModule",
3121 "pg" => "PageObject",
3122 "st" => "StructureObject",
3123 "git" => "GlossaryItem",
3124 "mob" => "MediaObject"
3125 );
3126 $href = "";
3127 if (preg_match("/il__(\w+)_(\d+)/", $target, $matches)) {
3128 $type = $matches[1];
3129 $target_id = $matches[2];
3130 include_once "./Services/Utilities/classes/class.ilUtil.php";
3131 switch ($linktypes[$matches[1]]) {
3132 case "LearningModule":
3133 $href = "./goto.php?target=" . $type . "_" . $target_id;
3134 break;
3135 case "PageObject":
3136 case "StructureObject":
3137 $href = "./goto.php?target=" . $type . "_" . $target_id;
3138 break;
3139 case "GlossaryItem":
3140 $href = "./goto.php?target=" . $type . "_" . $target_id;
3141 break;
3142 case "MediaObject":
3143 $href = "./ilias.php?baseClass=ilLMPresentationGUI&obj_type=" . $linktypes[$type] . "&cmd=media&ref_id=" . $_GET["ref_id"] . "&mob_id=" . $target_id;
3144 break;
3145 }
3146 }
3147 return $href;
3148 }
3149
3157 public static function _getOriginalId($question_id)
3158 {
3159 global $DIC;
3160 $ilDB = $DIC['ilDB'];
3161 $result = $ilDB->queryF(
3162 "SELECT * FROM qpl_questions WHERE question_id = %s",
3163 array('integer'),
3164 array($question_id)
3165 );
3166 if ($result->numRows() > 0) {
3167 $row = $ilDB->fetchAssoc($result);
3168 if ($row["original_id"] > 0) {
3169 return $row["original_id"];
3170 } else {
3171 return $row["question_id"];
3172 }
3173 } else {
3174 return "";
3175 }
3176 }
3177
3178 public static function originalQuestionExists($questionId)
3179 {
3180 global $DIC;
3181 $ilDB = $DIC['ilDB'];
3182
3183 $query = "
3184 SELECT COUNT(dupl.question_id) cnt
3185 FROM qpl_questions dupl
3186 INNER JOIN qpl_questions orig
3187 ON orig.question_id = dupl.original_id
3188 WHERE dupl.question_id = %s
3189 ";
3190
3191 $res = $ilDB->queryF($query, array('integer'), array($questionId));
3192 $row = $ilDB->fetchAssoc($res);
3193
3194 return $row['cnt'] > 0;
3195 }
3196
3197 public function syncWithOriginal()
3198 {
3199 global $DIC;
3200 $ilDB = $DIC['ilDB'];
3201
3202 if (!$this->getOriginalId()) {
3203 return;
3204 }
3205
3206 $originalObjId = self::lookupOriginalParentObjId($this->getOriginalId());
3207
3208 if (!$originalObjId) {
3209 return;
3210 }
3211
3212 $id = $this->getId();
3213 $objId = $this->getObjId();
3214 $original = $this->getOriginalId();
3215
3216 $this->beforeSyncWithOriginal($original, $id, $originalObjId, $objId);
3217
3218 $this->setId($original);
3219 $this->setOriginalId(null);
3220 $this->setObjId($originalObjId);
3221
3222 $this->saveToDb();
3223
3224 $this->deletePageOfQuestion($original);
3225 $this->createPageObject();
3226 $this->copyPageOfQuestion($id);
3227
3228 $this->setId($id);
3229 $this->setOriginalId($original);
3230 $this->setObjId($objId);
3231
3232 $this->updateSuggestedSolutions($original);
3234
3235 $this->afterSyncWithOriginal($original, $id, $originalObjId, $objId);
3236 $this->syncHints();
3237 }
3238
3246 public function _questionExists($question_id)
3247 {
3248 global $DIC;
3249 $ilDB = $DIC['ilDB'];
3250
3251 if ($question_id < 1) {
3252 return false;
3253 }
3254
3255 $result = $ilDB->queryF(
3256 "SELECT question_id FROM qpl_questions WHERE question_id = %s",
3257 array('integer'),
3258 array($question_id)
3259 );
3260 if ($result->numRows() == 1) {
3261 return true;
3262 } else {
3263 return false;
3264 }
3265 }
3266
3274 public function _questionExistsInPool($question_id)
3275 {
3276 global $DIC;
3277 $ilDB = $DIC['ilDB'];
3278
3279 if ($question_id < 1) {
3280 return false;
3281 }
3282
3283 $result = $ilDB->queryF(
3284 "SELECT question_id FROM qpl_questions INNER JOIN object_data ON obj_fi = obj_id WHERE question_id = %s AND type = 'qpl'",
3285 array('integer'),
3286 array($question_id)
3287 );
3288 if ($result->numRows() == 1) {
3289 return true;
3290 } else {
3291 return false;
3292 }
3293 }
3294
3302 public static function _instanciateQuestion($question_id)
3303 {
3304 return self::_instantiateQuestion($question_id);
3305 }
3306
3311 public static function _instantiateQuestion($question_id)
3312 {
3313 global $DIC;
3314 $ilCtrl = $DIC['ilCtrl'];
3315 $ilDB = $DIC['ilDB'];
3316 $lng = $DIC['lng'];
3317
3318 if (strcmp($question_id, "") != 0) {
3319 $question_type = assQuestion::_getQuestionType($question_id);
3320 if (!strlen($question_type)) {
3321 return null;
3322 }
3323 assQuestion::_includeClass($question_type);
3324 $objectClassname = self::getObjectClassNameByQuestionType($question_type);
3325 $question = new $objectClassname();
3326 $question->loadFromDb($question_id);
3327
3328 $feedbackObjectClassname = self::getFeedbackClassNameByQuestionType($question_type);
3329 $question->feedbackOBJ = new $feedbackObjectClassname($question, $ilCtrl, $ilDB, $lng);
3330
3331 return $question;
3332 }
3333 }
3334
3341 public function getPoints()
3342 {
3343 if (strcmp($this->points, "") == 0) {
3344 return 0;
3345 } else {
3346 return $this->points;
3347 }
3348 }
3349
3350
3357 public function setPoints($a_points)
3358 {
3359 $this->points = $a_points;
3360 }
3361
3368 public function getSolutionMaxPass($active_id)
3369 {
3370 return self::_getSolutionMaxPass($this->getId(), $active_id);
3371 }
3372
3379 public static function _getSolutionMaxPass($question_id, $active_id)
3380 {
3381 /* include_once "./Modules/Test/classes/class.ilObjTest.php";
3382 $pass = ilObjTest::_getPass($active_id);
3383 return $pass;*/
3384
3385 // the following code was the old solution which added the non answered
3386 // questions of a pass from the answered questions of the previous pass
3387 // with the above solution, only the answered questions of the last pass are counted
3388 global $DIC;
3389 $ilDB = $DIC['ilDB'];
3390
3391 $result = $ilDB->queryF(
3392 "SELECT MAX(pass) maxpass FROM tst_test_result WHERE active_fi = %s AND question_fi = %s",
3393 array('integer','integer'),
3394 array($active_id, $question_id)
3395 );
3396 if ($result->numRows() == 1) {
3397 $row = $ilDB->fetchAssoc($result);
3398 return $row["maxpass"];
3399 } else {
3400 return 0;
3401 }
3402 }
3403
3412 public static function _isWriteable($question_id, $user_id)
3413 {
3414 global $DIC;
3415 $ilDB = $DIC['ilDB'];
3416
3417 if (($question_id < 1) || ($user_id < 1)) {
3418 return false;
3419 }
3420
3421 $result = $ilDB->queryF(
3422 "SELECT obj_fi FROM qpl_questions WHERE question_id = %s",
3423 array('integer'),
3424 array($question_id)
3425 );
3426 if ($result->numRows() == 1) {
3427 $row = $ilDB->fetchAssoc($result);
3428 $qpl_object_id = $row["obj_fi"];
3429 include_once "./Modules/TestQuestionPool/classes/class.ilObjQuestionPool.php";
3430 return ilObjQuestionPool::_isWriteable($qpl_object_id, $user_id);
3431 } else {
3432 return false;
3433 }
3434 }
3435
3442 public static function _isUsedInRandomTest($question_id = "")
3443 {
3444 global $DIC;
3445 $ilDB = $DIC['ilDB'];
3446
3447 if ($question_id < 1) {
3448 return 0;
3449 }
3450 $result = $ilDB->queryF(
3451 "SELECT test_random_question_id FROM tst_test_rnd_qst WHERE question_fi = %s",
3452 array('integer'),
3453 array($question_id)
3454 );
3455 return $result->numRows();
3456 }
3457
3469 abstract public function calculateReachedPoints($active_id, $pass = null, $authorizedSolution = true, $returndetails = false);
3470
3471 public function deductHintPointsFromReachedPoints(ilAssQuestionPreviewSession $previewSession, $reachedPoints)
3472 {
3473 global $DIC;
3474
3475 $hintTracking = new ilAssQuestionPreviewHintTracking($DIC->database(), $previewSession);
3476 $requestsStatisticData = $hintTracking->getRequestStatisticData();
3477 $reachedPoints = $reachedPoints - $requestsStatisticData->getRequestsPoints();
3478
3479 return $reachedPoints;
3480 }
3481
3483 {
3484 $reachedPoints = $this->calculateReachedPointsForSolution($previewSession->getParticipantsSolution());
3485 $reachedPoints = $this->deductHintPointsFromReachedPoints($previewSession, $reachedPoints);
3486
3487 return $this->ensureNonNegativePoints($reachedPoints);
3488 }
3489
3491 {
3492 return $points > 0 ? $points : 0;
3493 }
3494
3496 {
3497 $reachedPoints = $this->calculateReachedPointsFromPreviewSession($previewSession);
3498
3499 if ($reachedPoints < $this->getMaximumPoints()) {
3500 return false;
3501 }
3502
3503 return true;
3504 }
3505
3506
3517 final public function adjustReachedPointsByScoringOptions($points, $active_id, $pass = null)
3518 {
3519 include_once "./Modules/Test/classes/class.ilObjTest.php";
3520 $count_system = ilObjTest::_getCountSystem($active_id);
3521 if ($count_system == 1) {
3522 if (abs($this->getMaximumPoints() - $points) > 0.0000000001) {
3523 $points = 0;
3524 }
3525 }
3526 $score_cutting = ilObjTest::_getScoreCutting($active_id);
3527 if ($score_cutting == 0) {
3528 if ($points < 0) {
3529 $points = 0;
3530 }
3531 }
3532 return $points;
3533 }
3534
3543 public static function _isWorkedThrough($active_id, $question_id, $pass = null)
3544 {
3545 return self::lookupResultRecordExist($active_id, $question_id, $pass);
3546
3547 // oldschool "workedthru"
3548
3549 global $DIC;
3550 $ilDB = $DIC['ilDB'];
3551
3552 $points = 0;
3553 if (is_null($pass)) {
3554 include_once "./Modules/TestQuestionPool/classes/class.assQuestion.php";
3555 $pass = assQuestion::_getSolutionMaxPass($question_id, $active_id);
3556 }
3557 $result = $ilDB->queryF(
3558 "SELECT solution_id FROM tst_solutions WHERE active_fi = %s AND question_fi = %s AND pass = %s",
3559 array('integer','integer','integer'),
3560 array($active_id, $question_id, $pass)
3561 );
3562 if ($result->numRows()) {
3563 return true;
3564 } else {
3565 return false;
3566 }
3567 }
3568
3576 public static function _areAnswered($a_user_id, $a_question_ids)
3577 {
3578 global $DIC;
3579 $ilDB = $DIC['ilDB'];
3580
3581 $res = $ilDB->queryF(
3582 "SELECT DISTINCT(question_fi) FROM tst_test_result JOIN tst_active " .
3583 "ON (active_id = active_fi) " .
3584 "WHERE " . $ilDB->in('question_fi', $a_question_ids, false, 'integer') .
3585 " AND user_fi = %s",
3586 array('integer'),
3587 array($a_user_id)
3588 );
3589 return ($res->numRows() == count($a_question_ids)) ? true : false;
3590 }
3591
3600 public function isHTML($a_text)
3601 {
3602 return ilUtil::isHTML($a_text);
3603 }
3604
3611 public function prepareTextareaOutput($txt_output, $prepare_for_latex_output = false, $omitNl2BrWhenTextArea = false)
3612 {
3613 include_once "./Services/Utilities/classes/class.ilUtil.php";
3614 return ilUtil::prepareTextareaOutput($txt_output, $prepare_for_latex_output, $omitNl2BrWhenTextArea);
3615 }
3616
3624 public function QTIMaterialToString($a_material)
3625 {
3626 $result = "";
3627 for ($i = 0; $i < $a_material->getMaterialCount(); $i++) {
3628 $material = $a_material->getMaterial($i);
3629 if (strcmp($material["type"], "mattext") == 0) {
3630 $result .= $material["material"]->getContent();
3631 }
3632 if (strcmp($material["type"], "matimage") == 0) {
3633 $matimage = $material["material"];
3634 if (preg_match("/(il_([0-9]+)_mob_([0-9]+))/", $matimage->getLabel(), $matches)) {
3635 // import an mediaobject which was inserted using tiny mce
3636 if (!is_array($_SESSION["import_mob_xhtml"])) {
3637 $_SESSION["import_mob_xhtml"] = array();
3638 }
3639 array_push($_SESSION["import_mob_xhtml"], array("mob" => $matimage->getLabel(), "uri" => $matimage->getUri()));
3640 }
3641 }
3642 }
3643 return $result;
3644 }
3645
3654 public function addQTIMaterial(&$a_xml_writer, $a_material, $close_material_tag = true, $add_mobs = true)
3655 {
3656 include_once "./Services/RTE/classes/class.ilRTE.php";
3657 include_once("./Services/MediaObjects/classes/class.ilObjMediaObject.php");
3658
3659 $a_xml_writer->xmlStartTag("material");
3660 $attrs = array(
3661 "texttype" => "text/plain"
3662 );
3663 if ($this->isHTML($a_material)) {
3664 $attrs["texttype"] = "text/xhtml";
3665 }
3666 $a_xml_writer->xmlElement("mattext", $attrs, ilRTE::_replaceMediaObjectImageSrc($a_material, 0));
3667 if ($add_mobs) {
3668 $mobs = ilObjMediaObject::_getMobsOfObject("qpl:html", $this->getId());
3669 foreach ($mobs as $mob) {
3670 $moblabel = "il_" . IL_INST_ID . "_mob_" . $mob;
3671 if (strpos($a_material, "mm_$mob") !== false) {
3672 if (ilObjMediaObject::_exists($mob)) {
3673 $mob_obj = new ilObjMediaObject($mob);
3674 $imgattrs = array(
3675 "label" => $moblabel,
3676 "uri" => "objects/" . "il_" . IL_INST_ID . "_mob_" . $mob . "/" . $mob_obj->getTitle()
3677 );
3678 }
3679 $a_xml_writer->xmlElement("matimage", $imgattrs, null);
3680 }
3681 }
3682 }
3683 if ($close_material_tag) {
3684 $a_xml_writer->xmlEndTag("material");
3685 }
3686 }
3687
3688 public function buildHashedImageFilename($plain_image_filename, $unique = false)
3689 {
3690 $extension = "";
3691
3692 if (preg_match("/.*\.(png|jpg|gif|jpeg)$/i", $plain_image_filename, $matches)) {
3693 $extension = "." . $matches[1];
3694 }
3695
3696 if ($unique) {
3697 $plain_image_filename = uniqid($plain_image_filename . microtime(true));
3698 }
3699
3700 $hashed_filename = md5($plain_image_filename) . $extension;
3701
3702 return $hashed_filename;
3703 }
3704
3715 public static function _setReachedPoints($active_id, $question_id, $points, $maxpoints, $pass, $manualscoring, $obligationsEnabled)
3716 {
3717 global $DIC;
3718 $ilDB = $DIC['ilDB'];
3719 $refinery = $DIC['refinery'];
3720
3721 $float_trafo = $refinery->kindlyTo()->float();
3722 try {
3723 $points = $float_trafo->transform($points);
3724 } catch (ILIAS\Refinery\ConstraintViolationException $e) {
3725 return false;
3726 }
3727
3728 if ($points <= $maxpoints) {
3729 if (is_null($pass)) {
3730 $pass = assQuestion::_getSolutionMaxPass($question_id, $active_id);
3731 }
3732
3733 // retrieve the already given points
3734 $old_points = 0;
3735 $result = $ilDB->queryF(
3736 "SELECT points FROM tst_test_result WHERE active_fi = %s AND question_fi = %s AND pass = %s",
3737 array('integer','integer','integer'),
3738 array($active_id, $question_id, $pass)
3739 );
3740 $manual = ($manualscoring) ? 1 : 0;
3741 $rowsnum = $result->numRows();
3742 if ($rowsnum) {
3743 $row = $ilDB->fetchAssoc($result);
3744 $old_points = $row["points"];
3745 if ($old_points != $points) {
3746 $affectedRows = $ilDB->manipulateF(
3747 "UPDATE tst_test_result SET points = %s, manual = %s, tstamp = %s WHERE active_fi = %s AND question_fi = %s AND pass = %s",
3748 array('float', 'integer', 'integer', 'integer', 'integer', 'integer'),
3749 array($points, $manual, time(), $active_id, $question_id, $pass)
3750 );
3751 }
3752 } else {
3753 $next_id = $ilDB->nextId('tst_test_result');
3754 $affectedRows = $ilDB->manipulateF(
3755 "INSERT INTO tst_test_result (test_result_id, active_fi, question_fi, points, pass, manual, tstamp) VALUES (%s, %s, %s, %s, %s, %s, %s)",
3756 array('integer', 'integer','integer', 'float', 'integer', 'integer','integer'),
3757 array($next_id, $active_id, $question_id, $points, $pass, $manual, time())
3758 );
3759 }
3760
3761 if (self::isForcePassResultUpdateEnabled() || $old_points != $points || !$rowsnum) {
3762 assQuestion::_updateTestPassResults($active_id, $pass, $obligationsEnabled);
3763 // finally update objective result
3764 include_once "./Modules/Test/classes/class.ilObjTest.php";
3765 include_once './Modules/Course/classes/class.ilCourseObjectiveResult.php';
3767
3768 include_once("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
3770 global $DIC;
3771 $lng = $DIC['lng'];
3772 $ilUser = $DIC['ilUser'];
3773 include_once "./Modules/Test/classes/class.ilObjTestAccess.php";
3774 $username = ilObjTestAccess::_getParticipantData($active_id);
3775 assQuestion::logAction(sprintf($lng->txtlng("assessment", "log_answer_changed_points", ilObjAssessmentFolder::_getLogLanguage()), $username, $old_points, $points, $ilUser->getFullname() . " (" . $ilUser->getLogin() . ")"), $active_id, $question_id);
3776 }
3777 }
3778
3779 return true;
3780 } else {
3781 return false;
3782 }
3783 }
3784
3792 public function getQuestion()
3793 {
3794 return $this->question;
3795 }
3796
3804 public function setQuestion($question = "")
3805 {
3806 $this->question = $question;
3807 }
3808
3809 public function getQuestionForHTMLOutput() : string
3810 {
3811 return $this->purifyAndPrepareTextAreaOutput($this->question);
3812 }
3813
3814 protected function purifyAndPrepareTextAreaOutput(string $content) : string
3815 {
3816 $purified_content = $this->getHtmlQuestionContentPurifier()->purify($content);
3818 || !(new ilSetting('advanced_editing'))->get('advanced_editing_javascript_editor') === 'tinymce') {
3819 $purified_content = nl2br($purified_content);
3820 }
3821 return $this->prepareTextareaOutput(
3822 $purified_content,
3823 true,
3824 true
3825 );
3826 }
3827
3833 abstract public function getQuestionType();
3834
3843 public function getQuestionTypeID()
3844 {
3845 global $DIC;
3846 $ilDB = $DIC['ilDB'];
3847
3848 $result = $ilDB->queryF(
3849 "SELECT question_type_id FROM qpl_qst_type WHERE type_tag = %s",
3850 array('text'),
3851 array($this->getQuestionType())
3852 );
3853 if ($result->numRows() == 1) {
3854 $row = $ilDB->fetchAssoc($result);
3855 return $row["question_type_id"];
3856 }
3857 return 0;
3858 }
3859
3860 public function syncHints()
3861 {
3862 global $DIC;
3863 $ilDB = $DIC['ilDB'];
3864
3865 // delete hints of the original
3866 $ilDB->manipulateF(
3867 "DELETE FROM qpl_hints WHERE qht_question_fi = %s",
3868 array('integer'),
3869 array($this->original_id)
3870 );
3871
3872 // get hints of the actual question
3873 $result = $ilDB->queryF(
3874 "SELECT * FROM qpl_hints WHERE qht_question_fi = %s",
3875 array('integer'),
3876 array($this->getId())
3877 );
3878
3879 // save hints to the original
3880 if ($result->numRows()) {
3881 while ($row = $ilDB->fetchAssoc($result)) {
3882 $next_id = $ilDB->nextId('qpl_hints');
3884 $ilDB->insert(
3885 'qpl_hints',
3886 array(
3887 'qht_hint_id' => array('integer', $next_id),
3888 'qht_question_fi' => array('integer', $this->original_id),
3889 'qht_hint_index' => array('integer', $row["qht_hint_index"]),
3890 'qht_hint_points' => array('integer', $row["qht_hint_points"]),
3891 'qht_hint_text' => array('text', $row["qht_hint_text"]),
3892 )
3893 );
3894 }
3895 }
3896 }
3897
3902 protected function getRTETextWithMediaObjects()
3903 {
3904 // must be called in parent classes. add additional RTE text in the parent
3905 // classes and call this method to add the standard RTE text
3906 $collected = $this->getQuestion();
3907 $collected .= $this->feedbackOBJ->getGenericFeedbackContent($this->getId(), false);
3908 $collected .= $this->feedbackOBJ->getGenericFeedbackContent($this->getId(), true);
3909 $collected .= $this->feedbackOBJ->getAllSpecificAnswerFeedbackContents($this->getId());
3910
3911 foreach ($this->suggested_solutions as $solution_array) {
3912 $collected .= $solution_array["value"];
3913 }
3914
3915 require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionHintList.php';
3916 $questionHintList = ilAssQuestionHintList::getListByQuestionId($this->getId());
3917 foreach ($questionHintList as $questionHint) {
3918 /* @var $questionHint ilAssQuestionHint */
3919 $collected .= $questionHint->getText();
3920 }
3921
3922 return $collected;
3923 }
3924
3929 public function cleanupMediaObjectUsage()
3930 {
3931 $combinedtext = $this->getRTETextWithMediaObjects();
3932 include_once("./Services/RTE/classes/class.ilRTE.php");
3933 ilRTE::_cleanupMediaObjectUsage($combinedtext, "qpl:html", $this->getId());
3934 }
3935
3941 public function &getInstances()
3942 {
3943 global $DIC;
3944 $ilDB = $DIC['ilDB'];
3945
3946 $result = $ilDB->queryF(
3947 "SELECT question_id FROM qpl_questions WHERE original_id = %s",
3948 array("integer"),
3949 array($this->getId())
3950 );
3951 $instances = array();
3952 $ids = array();
3953 while ($row = $ilDB->fetchAssoc($result)) {
3954 array_push($ids, $row["question_id"]);
3955 }
3956 foreach ($ids as $question_id) {
3957 // check non random tests
3958 $result = $ilDB->queryF(
3959 "SELECT tst_tests.obj_fi FROM tst_tests, tst_test_question WHERE tst_test_question.question_fi = %s AND tst_test_question.test_fi = tst_tests.test_id",
3960 array("integer"),
3961 array($question_id)
3962 );
3963 while ($row = $ilDB->fetchAssoc($result)) {
3964 $instances[$row['obj_fi']] = ilObject::_lookupTitle($row['obj_fi']);
3965 }
3966 // check random tests
3967 $result = $ilDB->queryF(
3968 "SELECT tst_tests.obj_fi FROM tst_tests, tst_test_rnd_qst, tst_active WHERE tst_test_rnd_qst.active_fi = tst_active.active_id AND tst_test_rnd_qst.question_fi = %s AND tst_tests.test_id = tst_active.test_fi",
3969 array("integer"),
3970 array($question_id)
3971 );
3972 while ($row = $ilDB->fetchAssoc($result)) {
3973 $instances[$row['obj_fi']] = ilObject::_lookupTitle($row['obj_fi']);
3974 }
3975 }
3976 include_once "./Modules/Test/classes/class.ilObjTest.php";
3977 foreach ($instances as $key => $value) {
3978 $instances[$key] = array("obj_id" => $key, "title" => $value, "author" => ilObjTest::_lookupAuthor($key), "refs" => ilObject::_getAllReferences($key));
3979 }
3980 return $instances;
3981 }
3982
3983 public static function _needsManualScoring($question_id)
3984 {
3985 include_once "./Modules/Test/classes/class.ilObjAssessmentFolder.php";
3987 $questiontype = assQuestion::_getQuestionType($question_id);
3988 if (in_array($questiontype, $scoring)) {
3989 return true;
3990 } else {
3991 return false;
3992 }
3993 }
3994
4002 public function getActiveUserData($active_id)
4003 {
4004 global $DIC;
4005 $ilDB = $DIC['ilDB'];
4006 $result = $ilDB->queryF(
4007 "SELECT * FROM tst_active WHERE active_id = %s",
4008 array('integer'),
4009 array($active_id)
4010 );
4011 if ($result->numRows()) {
4012 $row = $ilDB->fetchAssoc($result);
4013 return array("user_id" => $row["user_fi"], "test_id" => $row["test_fi"]);
4014 } else {
4015 return array();
4016 }
4017 }
4018
4026 public static function _includeClass($question_type, $gui = 0)
4027 {
4028 if (self::isCoreQuestionType($question_type)) {
4029 self::includeCoreClass($question_type, $gui);
4030 } else {
4031 self::includePluginClass($question_type, $gui);
4032 }
4033 }
4034
4035 public static function getGuiClassNameByQuestionType($questionType)
4036 {
4037 return $questionType . 'GUI';
4038 }
4039
4040 public static function getObjectClassNameByQuestionType($questionType)
4041 {
4042 return $questionType;
4043 }
4044
4045 public static function getFeedbackClassNameByQuestionType($questionType)
4046 {
4047 return str_replace('ass', 'ilAss', $questionType) . 'Feedback';
4048 }
4049
4050 public static function isCoreQuestionType($questionType)
4051 {
4052 $guiClassName = self::getGuiClassNameByQuestionType($questionType);
4053 return file_exists("Modules/TestQuestionPool/classes/class.{$guiClassName}.php");
4054 }
4055
4056 public static function includeCoreClass($questionType, $withGuiClass)
4057 {
4058 if ($withGuiClass) {
4059 $guiClassName = self::getGuiClassNameByQuestionType($questionType);
4060 require_once "Modules/TestQuestionPool/classes/class.{$guiClassName}.php";
4061
4062 // object class is included by gui classes constructor
4063 } else {
4064 $objectClassName = self::getObjectClassNameByQuestionType($questionType);
4065 require_once "Modules/TestQuestionPool/classes/class.{$objectClassName}.php";
4066 }
4067
4068 $feedbackClassName = self::getFeedbackClassNameByQuestionType($questionType);
4069 require_once "Modules/TestQuestionPool/classes/feedback/class.{$feedbackClassName}.php";
4070 }
4071
4072 public static function includePluginClass($questionType, $withGuiClass)
4073 {
4074 global $DIC;
4075 $ilPluginAdmin = $DIC['ilPluginAdmin'];
4076
4077 $classes = array(
4078 self::getObjectClassNameByQuestionType($questionType),
4079 self::getFeedbackClassNameByQuestionType($questionType)
4080 );
4081
4082 if ($withGuiClass) {
4083 $classes[] = self::getGuiClassNameByQuestionType($questionType);
4084 }
4085
4086 $pl_names = $ilPluginAdmin->getActivePluginsForSlot(IL_COMP_MODULE, "TestQuestionPool", "qst");
4087 foreach ($pl_names as $pl_name) {
4088 $pl = ilPlugin::getPluginObject(IL_COMP_MODULE, "TestQuestionPool", "qst", $pl_name);
4089 if (strcmp($pl->getQuestionType(), $questionType) == 0) {
4090 foreach ($classes as $class) {
4091 $pl->includeClass("class.{$class}.php");
4092 }
4093
4094 break;
4095 }
4096 }
4097 }
4098
4105 public static function _getQuestionTypeName($type_tag)
4106 {
4107 if (file_exists("./Modules/TestQuestionPool/classes/class." . $type_tag . ".php")) {
4108 global $DIC;
4109 $lng = $DIC['lng'];
4110 return $lng->txt($type_tag);
4111 } else {
4112 global $DIC;
4113 $ilPluginAdmin = $DIC['ilPluginAdmin'];
4114 $pl_names = $ilPluginAdmin->getActivePluginsForSlot(IL_COMP_MODULE, "TestQuestionPool", "qst");
4115 foreach ($pl_names as $pl_name) {
4116 $pl = ilPlugin::getPluginObject(IL_COMP_MODULE, "TestQuestionPool", "qst", $pl_name);
4117 if (strcmp($pl->getQuestionType(), $type_tag) == 0) {
4118 return $pl->getQuestionTypeTranslation();
4119 }
4120 }
4121 }
4122 return "";
4123 }
4124
4134 public static function &_instanciateQuestionGUI($question_id)
4135 {
4136 return self::instantiateQuestionGUI($question_id);
4137 }
4138
4146 public static function instantiateQuestionGUI($a_question_id)
4147 {
4148 global $DIC;
4149 $ilCtrl = $DIC['ilCtrl'];
4150 $ilDB = $DIC['ilDB'];
4151 $lng = $DIC['lng'];
4152 $ilUser = $DIC['ilUser'];
4153
4154 if (strcmp($a_question_id, "") != 0) {
4155 $question_type = assQuestion::_getQuestionType($a_question_id);
4156
4157 assQuestion::_includeClass($question_type, 1);
4158
4159 $question_type_gui = self::getGuiClassNameByQuestionType($question_type);
4160 $question_gui = new $question_type_gui();
4161 $question_gui->object->loadFromDb($a_question_id);
4162
4163 $feedbackObjectClassname = self::getFeedbackClassNameByQuestionType($question_type);
4164 $question_gui->object->feedbackOBJ = new $feedbackObjectClassname($question_gui->object, $ilCtrl, $ilDB, $lng);
4165
4166 $assSettings = new ilSetting('assessment');
4167 require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionProcessLockerFactory.php';
4168 $processLockerFactory = new ilAssQuestionProcessLockerFactory($assSettings, $ilDB);
4169 $processLockerFactory->setQuestionId($question_gui->object->getId());
4170 $processLockerFactory->setUserId($ilUser->getId());
4171 include_once("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
4172 $processLockerFactory->setAssessmentLogEnabled(ilObjAssessmentFolder::_enabledAssessmentLogging());
4173 $question_gui->object->setProcessLocker($processLockerFactory->getLocker());
4174 } else {
4175 global $DIC;
4176 $ilLog = $DIC['ilLog'];
4177 $ilLog->write('Instantiate question called without question id. (instantiateQuestionGUI@assQuestion)', $ilLog->WARNING);
4178 return null;
4179 }
4180 return $question_gui;
4181 }
4182
4193 public function setExportDetailsXLS($worksheet, $startrow, $active_id, $pass)
4194 {
4195 $worksheet->setFormattedExcelTitle($worksheet->getColumnCoord(0) . $startrow, $this->lng->txt($this->getQuestionType()));
4196 $worksheet->setFormattedExcelTitle($worksheet->getColumnCoord(1) . $startrow, $this->getTitle());
4197
4198 return $startrow;
4199 }
4200
4204 public function __get($value)
4205 {
4206 switch ($value) {
4207 case "id":
4208 return $this->getId();
4209 break;
4210 case "title":
4211 return $this->getTitle();
4212 break;
4213 case "comment":
4214 return $this->getComment();
4215 break;
4216 case "owner":
4217 return $this->getOwner();
4218 break;
4219 case "author":
4220 return $this->getAuthor();
4221 break;
4222 case "question":
4223 return $this->getQuestion();
4224 break;
4225 case "points":
4226 return $this->getPoints();
4227 break;
4228 case "est_working_time":
4229 return $this->getEstimatedWorkingTime();
4230 break;
4231 case "shuffle":
4232 return $this->getShuffle();
4233 break;
4234 case "test_id":
4235 return $this->getTestId();
4236 break;
4237 case "obj_id":
4238 return $this->getObjId();
4239 break;
4240 case "ilias":
4241 return $this->ilias;
4242 break;
4243 case "tpl":
4244 return $this->tpl;
4245 break;
4246 case "page":
4247 return $this->page;
4248 break;
4249 case "outputType":
4250 return $this->getOutputType();
4251 break;
4252 case "suggested_solutions":
4253 return $this->getSuggestedSolutions();
4254 break;
4255 case "original_id":
4256 return $this->getOriginalId();
4257 break;
4258 default:
4259 if (array_key_exists($value, $this->arrData)) {
4260 return $this->arrData[$value];
4261 } else {
4262 return null;
4263 }
4264 break;
4265 }
4266 }
4267
4271 public function __set($key, $value)
4272 {
4273 switch ($key) {
4274 case "id":
4275 $this->setId($value);
4276 break;
4277 case "title":
4278 $this->setTitle($value);
4279 break;
4280 case "comment":
4281 $this->setComment($value);
4282 break;
4283 case "owner":
4284 $this->setOwner($value);
4285 break;
4286 case "author":
4287 $this->setAuthor($value);
4288 break;
4289 case "question":
4290 $this->setQuestion($value);
4291 break;
4292 case "points":
4293 $this->setPoints($value);
4294 break;
4295 case "est_working_time":
4296 if (is_array($value)) {
4297 $this->setEstimatedWorkingTime($value["h"], $value["m"], $value["s"]);
4298 }
4299 break;
4300 case "shuffle":
4301 $this->setShuffle($value);
4302 break;
4303 case "test_id":
4304 $this->setTestId($value);
4305 break;
4306 case "obj_id":
4307 $this->setObjId($value);
4308 break;
4309 case "outputType":
4310 $this->setOutputType($value);
4311 break;
4312 case "original_id":
4313 $this->setOriginalId($value);
4314 break;
4315 case "page":
4316 $this->page = &$value;
4317 break;
4318 default:
4319 $this->arrData[$key] = $value;
4320 break;
4321 }
4322 }
4323
4324 public function getNrOfTries()
4325 {
4326 return (int) $this->nr_of_tries;
4327 }
4328
4329 public function setNrOfTries($a_nr_of_tries)
4330 {
4331 $this->nr_of_tries = $a_nr_of_tries;
4332 }
4333
4334 public function setExportImagePath($a_path)
4335 {
4336 $this->export_image_path = (string) $a_path;
4337 }
4338
4339 public static function _questionExistsInTest($question_id, $test_id)
4340 {
4341 global $DIC;
4342 $ilDB = $DIC['ilDB'];
4343
4344 if ($question_id < 1) {
4345 return false;
4346 }
4347
4348 $result = $ilDB->queryF(
4349 "SELECT question_fi FROM tst_test_question WHERE question_fi = %s AND test_fi = %s",
4350 array('integer', 'integer'),
4351 array($question_id, $test_id)
4352 );
4353 if ($result->numRows() == 1) {
4354 return true;
4355 } else {
4356 return false;
4357 }
4358 }
4359
4366 public function formatSAQuestion($a_q)
4367 {
4368 return $this->getSelfAssessmentFormatter()->format($a_q);
4369 }
4370
4374 protected function getSelfAssessmentFormatter()
4375 {
4376 require_once 'Modules/TestQuestionPool/classes/questions/class.ilAssSelfAssessmentQuestionFormatter.php';
4377 return new \ilAssSelfAssessmentQuestionFormatter();
4378 }
4379
4380 // scorm2004-start ???
4381
4387 public function setPreventRteUsage($a_val)
4388 {
4389 $this->prevent_rte_usage = $a_val;
4390 }
4391
4397 public function getPreventRteUsage()
4398 {
4400 }
4401
4406 {
4407 $this->lmMigrateQuestionTypeGenericContent($migrator);
4408 $this->lmMigrateQuestionTypeSpecificContent($migrator);
4409 $this->saveToDb();
4410
4411 $this->feedbackOBJ->migrateContentForLearningModule($migrator, $this->getId());
4412 }
4413
4418 {
4419 $this->setQuestion($migrator->migrateToLmContent($this->getQuestion()));
4420 }
4421
4426 {
4427 // overwrite if any question type specific content except feedback needs to be migrated
4428 }
4429
4435 public function setSelfAssessmentEditingMode($a_selfassessmenteditingmode)
4436 {
4437 $this->selfassessmenteditingmode = $a_selfassessmenteditingmode;
4438 }
4439
4446 {
4448 }
4449
4455 public function setDefaultNrOfTries($a_defaultnroftries)
4456 {
4457 $this->defaultnroftries = $a_defaultnroftries;
4458 }
4459
4465 public function getDefaultNrOfTries()
4466 {
4467 return (int) $this->defaultnroftries;
4468 }
4469
4470 // scorm2004-end ???
4471
4477 public static function lookupParentObjId($questionId)
4478 {
4479 global $DIC;
4480 $ilDB = $DIC['ilDB'];
4481
4482 $query = "SELECT obj_fi FROM qpl_questions WHERE question_id = %s";
4483
4484 $res = $ilDB->queryF($query, array('integer'), array((int) $questionId));
4485 $row = $ilDB->fetchAssoc($res);
4486
4487 return $row['obj_fi'];
4488 }
4489
4500 public static function lookupOriginalParentObjId($originalQuestionId)
4501 {
4502 return self::lookupParentObjId($originalQuestionId);
4503 }
4504
4505 protected function duplicateQuestionHints($originalQuestionId, $duplicateQuestionId)
4506 {
4507 require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionHintList.php';
4508 $hintIds = ilAssQuestionHintList::duplicateListForQuestion($originalQuestionId, $duplicateQuestionId);
4509
4511 require_once 'Modules/TestQuestionPool/classes/class.ilAssHintPage.php';
4512
4513 foreach ($hintIds as $originalHintId => $duplicateHintId) {
4514 $originalPageObject = new ilAssHintPage($originalHintId);
4515 $originalXML = $originalPageObject->getXMLContent();
4516
4517 $duplicatePageObject = new ilAssHintPage();
4518 $duplicatePageObject->setId($duplicateHintId);
4519 $duplicatePageObject->setParentId($this->getId());
4520 $duplicatePageObject->setXMLContent($originalXML);
4521 $duplicatePageObject->createFromXML();
4522 }
4523 }
4524 }
4525
4526 protected function duplicateSkillAssignments($srcParentId, $srcQuestionId, $trgParentId, $trgQuestionId)
4527 {
4528 global $DIC;
4529 $ilDB = $DIC['ilDB'];
4530
4531 require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionSkillAssignmentList.php';
4532 $assignmentList = new ilAssQuestionSkillAssignmentList($ilDB);
4533 $assignmentList->setParentObjId($srcParentId);
4534 $assignmentList->setQuestionIdFilter($srcQuestionId);
4535 $assignmentList->loadFromDb();
4536
4537 foreach ($assignmentList->getAssignmentsByQuestionId($srcQuestionId) as $assignment) {
4538 /* @var ilAssQuestionSkillAssignment $assignment */
4539
4540 $assignment->setParentObjId($trgParentId);
4541 $assignment->setQuestionId($trgQuestionId);
4542 $assignment->saveToDb();
4543 }
4544 }
4545
4546 public function syncSkillAssignments($srcParentId, $srcQuestionId, $trgParentId, $trgQuestionId)
4547 {
4548 global $DIC;
4549 $ilDB = $DIC['ilDB'];
4550
4551 require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionSkillAssignmentList.php';
4552 $assignmentList = new ilAssQuestionSkillAssignmentList($ilDB);
4553 $assignmentList->setParentObjId($trgParentId);
4554 $assignmentList->setQuestionIdFilter($trgQuestionId);
4555 $assignmentList->loadFromDb();
4556
4557 foreach ($assignmentList->getAssignmentsByQuestionId($trgQuestionId) as $assignment) {
4558 /* @var ilAssQuestionSkillAssignment $assignment */
4559
4560 $assignment->deleteFromDb();
4561 }
4562
4563 $this->duplicateSkillAssignments($srcParentId, $srcQuestionId, $trgParentId, $trgQuestionId);
4564 }
4565
4578 public function isAnswered($active_id, $pass = null)
4579 {
4580 return true;
4581 }
4582
4595 public static function isObligationPossible($questionId)
4596 {
4597 return false;
4598 }
4599
4600 public function isAutosaveable()
4601 {
4602 return true;
4603 }
4604
4617 protected static function getNumExistingSolutionRecords($activeId, $pass, $questionId)
4618 {
4619 global $DIC;
4620 $ilDB = $DIC['ilDB'];
4621
4622 $query = "
4623 SELECT count(active_fi) cnt
4624
4625 FROM tst_solutions
4626
4627 WHERE active_fi = %s
4628 AND question_fi = %s
4629 AND pass = %s
4630 ";
4631
4632 $res = $ilDB->queryF(
4633 $query,
4634 array('integer','integer','integer'),
4635 array($activeId, $questionId, $pass)
4636 );
4637
4638 $row = $ilDB->fetchAssoc($res);
4639
4640 return (int) $row['cnt'];
4641 }
4642
4650 {
4652 }
4653
4661 {
4663 require_once 'Modules/TestQuestionPool/exceptions/class.ilTestQuestionPoolException.php';
4664 throw new ilTestQuestionPoolException('invalid additional content editing mode given: ' . $additinalContentEditingMode);
4665 }
4666
4667 $this->additinalContentEditingMode = $additinalContentEditingMode;
4668 }
4669
4677 {
4679 }
4680
4688 public function isValidAdditionalContentEditingMode($additionalContentEditingMode)
4689 {
4690 if (in_array($additionalContentEditingMode, $this->getValidAdditionalContentEditingModes())) {
4691 return true;
4692 }
4693
4694 return false;
4695 }
4696
4704 {
4705 return array(
4706 self::ADDITIONAL_CONTENT_EDITING_MODE_RTE,
4707 self::ADDITIONAL_CONTENT_EDITING_MODE_IPE
4708 );
4709 }
4710
4715 {
4716 $this->questionChangeListeners[] = $listener;
4717 }
4718
4723 {
4725 }
4726
4727 private function notifyQuestionCreated()
4728 {
4729 foreach ($this->getQuestionChangeListeners() as $listener) {
4730 $listener->notifyQuestionCreated($this);
4731 }
4732 }
4733
4734 private function notifyQuestionEdited()
4735 {
4736 foreach ($this->getQuestionChangeListeners() as $listener) {
4737 $listener->notifyQuestionEdited($this);
4738 }
4739 }
4740
4741 private function notifyQuestionDeleted()
4742 {
4743 foreach ($this->getQuestionChangeListeners() as $listener) {
4744 $listener->notifyQuestionDeleted($this);
4745 }
4746 }
4747
4752 {
4753 require_once 'Services/Html/classes/class.ilHtmlPurifierFactory.php';
4754 return ilHtmlPurifierFactory::_getInstanceByType('qpl_usersolution');
4755 }
4756
4758 {
4759 return ilHtmlPurifierFactory::_getInstanceByType('qpl_usersolution');
4760 }
4761
4762 protected function buildQuestionDataQuery()
4763 {
4764 return "
4765 SELECT qpl_questions.*,
4766 {$this->getAdditionalTableName()}.*
4767 FROM qpl_questions
4768 LEFT JOIN {$this->getAdditionalTableName()}
4769 ON {$this->getAdditionalTableName()}.question_fi = qpl_questions.question_id
4770 WHERE qpl_questions.question_id = %s
4771 ";
4772 }
4773
4775 {
4776 $this->lastChange = $lastChange;
4777 }
4778
4779 public function getLastChange()
4780 {
4781 return $this->lastChange;
4782 }
4783
4794 protected function getCurrentSolutionResultSet($active_id, $pass, $authorized = true)
4795 {
4796 global $DIC;
4797 $ilDB = $DIC['ilDB'];
4798
4799 if ($this->getStep() !== null) {
4800 $query = "
4801 SELECT *
4802 FROM tst_solutions
4803 WHERE active_fi = %s
4804 AND question_fi = %s
4805 AND pass = %s
4806 AND step = %s
4807 AND authorized = %s
4808 ";
4809
4810 return $ilDB->queryF(
4811 $query,
4812 array('integer', 'integer', 'integer', 'integer', 'integer'),
4813 array($active_id, $this->getId(), $pass, $this->getStep(), (int) $authorized)
4814 );
4815 } else {
4816 $query = "
4817 SELECT *
4818 FROM tst_solutions
4819 WHERE active_fi = %s
4820 AND question_fi = %s
4821 AND pass = %s
4822 AND authorized = %s
4823 ";
4824
4825 return $ilDB->queryF(
4826 $query,
4827 array('integer', 'integer', 'integer', 'integer'),
4828 array($active_id, $this->getId(), $pass, (int) $authorized)
4829 );
4830 }
4831 }
4832
4839 protected function removeSolutionRecordById($solutionId)
4840 {
4841 global $DIC;
4842 $ilDB = $DIC['ilDB'];
4843
4844 return $ilDB->manipulateF(
4845 "DELETE FROM tst_solutions WHERE solution_id = %s",
4846 array('integer'),
4847 array($solutionId)
4848 );
4849 }
4850
4851 // hey: prevPassSolutions - selected file reuse, copy solution records
4858 protected function getSolutionRecordById($solutionId)
4859 {
4860 global $DIC; /* @var ILIAS\DI\Container $DIC */
4861 $ilDB = $DIC['ilDB'];
4862
4863 $res = $ilDB->queryF(
4864 "SELECT * FROM tst_solutions WHERE solution_id = %s",
4865 array('integer'),
4866 array($solutionId)
4867 );
4868
4869 while ($row = $ilDB->fetchAssoc($res)) {
4870 return $row;
4871 }
4872 }
4873 // hey.
4874
4883 public function removeIntermediateSolution($active_id, $pass)
4884 {
4885 $this->getProcessLocker()->executeUserSolutionUpdateLockOperation(function () use ($active_id, $pass) {
4886 $this->removeCurrentSolution($active_id, $pass, false);
4887 });
4888 }
4889
4898 public function removeCurrentSolution($active_id, $pass, $authorized = true)
4899 {
4900 global $DIC;
4901 $ilDB = $DIC['ilDB'];
4902
4903 if ($this->getStep() !== null) {
4904 $query = "
4905 DELETE FROM tst_solutions
4906 WHERE active_fi = %s
4907 AND question_fi = %s
4908 AND pass = %s
4909 AND step = %s
4910 AND authorized = %s
4911 ";
4912
4913 return $ilDB->manipulateF(
4914 $query,
4915 array('integer', 'integer', 'integer', 'integer', 'integer'),
4916 array($active_id, $this->getId(), $pass, $this->getStep(), (int) $authorized)
4917 );
4918 } else {
4919 $query = "
4920 DELETE FROM tst_solutions
4921 WHERE active_fi = %s
4922 AND question_fi = %s
4923 AND pass = %s
4924 AND authorized = %s
4925 ";
4926
4927 return $ilDB->manipulateF(
4928 $query,
4929 array('integer', 'integer', 'integer', 'integer'),
4930 array($active_id, $this->getId(), $pass, (int) $authorized)
4931 );
4932 }
4933 }
4934
4935 // fau: testNav - add timestamp as parameter to saveCurrentSolution
4947 public function saveCurrentSolution($active_id, $pass, $value1, $value2, $authorized = true, $tstamp = null)
4948 {
4949 global $DIC;
4950 $ilDB = $DIC['ilDB'];
4951
4952 $next_id = $ilDB->nextId("tst_solutions");
4953
4954 $fieldData = array(
4955 "solution_id" => array("integer", $next_id),
4956 "active_fi" => array("integer", $active_id),
4957 "question_fi" => array("integer", $this->getId()),
4958 "value1" => array("clob", $value1),
4959 "value2" => array("clob", $value2),
4960 "pass" => array("integer", $pass),
4961 "tstamp" => array("integer", isset($tstamp) ? $tstamp : time()),
4962 'authorized' => array('integer', (int) $authorized)
4963 );
4964
4965 if ($this->getStep() !== null) {
4966 $fieldData['step'] = array("integer", $this->getStep());
4967 }
4968
4969 return $ilDB->insert("tst_solutions", $fieldData);
4970 }
4971 // fau.
4972
4983 public function updateCurrentSolution($solutionId, $value1, $value2, $authorized = true)
4984 {
4985 global $DIC;
4986 $ilDB = $DIC['ilDB'];
4987
4988 $fieldData = array(
4989 "value1" => array("clob", $value1),
4990 "value2" => array("clob", $value2),
4991 "tstamp" => array("integer", time()),
4992 'authorized' => array('integer', (int) $authorized)
4993 );
4994
4995 if ($this->getStep() !== null) {
4996 $fieldData['step'] = array("integer", $this->getStep());
4997 }
4998
4999 return $ilDB->update("tst_solutions", $fieldData, array(
5000 'solution_id' => array('integer', $solutionId)
5001 ));
5002 }
5003
5004 // fau: testNav - added parameter to keep the timestamp (default: false)
5005 public function updateCurrentSolutionsAuthorization($activeId, $pass, $authorized, $keepTime = false)
5006 {
5007 global $DIC;
5008 $ilDB = $DIC['ilDB'];
5009
5010 $fieldData = array(
5011 'authorized' => array('integer', (int) $authorized)
5012 );
5013
5014 if (!$keepTime) {
5015 $fieldData['tstamp'] = array('integer', time());
5016 }
5017
5018 $whereData = array(
5019 'question_fi' => array('integer', $this->getId()),
5020 'active_fi' => array('integer', $activeId),
5021 'pass' => array('integer', $pass)
5022 );
5023
5024 if ($this->getStep() !== null) {
5025 $whereData['step'] = array("integer", $this->getStep());
5026 }
5027
5028 return $ilDB->update('tst_solutions', $fieldData, $whereData);
5029 }
5030 // fau.
5031
5032 // hey: prevPassSolutions - motivation slowly decreases on imagemap
5034 protected static function getKeyValuesImplosionSeparator()
5035 {
5037 }
5038 public static function implodeKeyValues($keyValues)
5039 {
5040 return implode(self::getKeyValuesImplosionSeparator(), $keyValues);
5041 }
5042 public static function explodeKeyValues($keyValues)
5043 {
5044 return explode(self::getKeyValuesImplosionSeparator(), $keyValues);
5045 }
5046
5047 protected function deleteDummySolutionRecord($activeId, $passIndex)
5048 {
5049 foreach ($this->getSolutionValues($activeId, $passIndex, false) as $solutionRec) {
5050 if (0 == strlen($solutionRec['value1']) && 0 == strlen($solutionRec['value2'])) {
5051 $this->removeSolutionRecordById($solutionRec['solution_id']);
5052 }
5053 }
5054 }
5055
5056 protected function isDummySolutionRecord($solutionRecord)
5057 {
5058 return !strlen($solutionRecord['value1']) && !strlen($solutionRecord['value2']);
5059 }
5060
5061 protected function deleteSolutionRecordByValues($activeId, $passIndex, $authorized, $matchValues)
5062 {
5063 global $DIC; /* @var ILIAS\DI\Container $DIC */
5064 $ilDB = $DIC['ilDB'];
5065
5066 $types = array("integer", "integer", "integer", "integer");
5067 $values = array($activeId, $this->getId(), $passIndex, (int) $authorized);
5068 $valuesCondition = array();
5069
5070 foreach ($matchValues as $valueField => $value) {
5071 switch ($valueField) {
5072 case 'value1':
5073 case 'value2':
5074 $valuesCondition[] = "{$valueField} = %s";
5075 $types[] = 'text';
5076 $values[] = $value;
5077 break;
5078
5079 default:
5080 require_once 'Modules/TestQuestionPool/exceptions/class.ilTestQuestionPoolException.php';
5081 throw new ilTestQuestionPoolException('invalid value field given: ' . $valueField);
5082 }
5083 }
5084
5085 $valuesCondition = implode(' AND ', $valuesCondition);
5086
5087 $query = "
5088 DELETE FROM tst_solutions
5089 WHERE active_fi = %s
5090 AND question_fi = %s
5091 AND pass = %s
5092 AND authorized = %s
5093 AND $valuesCondition
5094 ";
5095
5096 if ($this->getStep() !== null) {
5097 $query .= " AND step = %s ";
5098 $types[] = 'integer';
5099 $values[] = $this->getStep();
5100 }
5101
5102 $ilDB->manipulateF($query, $types, $values);
5103 }
5104
5105 protected function duplicateIntermediateSolutionAuthorized($activeId, $passIndex)
5106 {
5107 foreach ($this->getSolutionValues($activeId, $passIndex, false) as $rec) {
5108 $this->saveCurrentSolution($activeId, $passIndex, $rec['value1'], $rec['value2'], true, $rec['tstamp']);
5109 }
5110 }
5111
5112 protected function forceExistingIntermediateSolution($activeId, $passIndex, $considerDummyRecordCreation)
5113 {
5114 $intermediateSolution = $this->getSolutionValues($activeId, $passIndex, false);
5115
5116 if (!count($intermediateSolution)) {
5117 // make the authorized solution intermediate (keeping timestamps)
5118 // this keeps the solution_ids in synch with eventually selected in $_POST['deletefiles']
5119 $this->updateCurrentSolutionsAuthorization($activeId, $passIndex, false, true);
5120
5121 // create a backup as authorized solution again (keeping timestamps)
5122 $this->duplicateIntermediateSolutionAuthorized($activeId, $passIndex);
5123
5124 if ($considerDummyRecordCreation) {
5125 // create an additional dummy record to indicate the existence of an intermediate solution
5126 // even if all entries are deleted from the intermediate solution later
5127 $this->saveCurrentSolution($activeId, $passIndex, null, null, false, null);
5128 }
5129 }
5130 }
5131 // hey.
5132
5136 public static function setResultGateway($resultGateway)
5137 {
5138 self::$resultGateway = $resultGateway;
5139 }
5140
5144 public static function getResultGateway()
5145 {
5146 return self::$resultGateway;
5147 }
5148
5152 public function setStep($step)
5153 {
5154 $this->step = $step;
5155 }
5156
5160 public function getStep()
5161 {
5162 return $this->step;
5163 }
5164
5170 public static function sumTimesInISO8601FormatH_i_s_Extended($time1, $time2)
5171 {
5174 return gmdate('H:i:s', $time);
5175 }
5176
5182 {
5183 $sec = 0;
5184 $time_array = explode(':', $time);
5185 if (sizeof($time_array) == 3) {
5186 $sec += $time_array[0] * 3600;
5187 $sec += $time_array[1] * 60;
5188 $sec += $time_array[2];
5189 }
5190 return $sec;
5191 }
5192
5193 public function toJSON()
5194 {
5195 return json_encode(array());
5196 }
5197
5198 abstract public function duplicate($for_test = true, $title = "", $author = "", $owner = "", $testObjId = null);
5199
5200 // hey: prevPassSolutions - check for authorized solution
5201 public function intermediateSolutionExists($active_id, $pass)
5202 {
5203 $solutionAvailability = $this->lookupForExistingSolutions($active_id, $pass);
5204 return (bool) $solutionAvailability['intermediate'];
5205 }
5206 public function authorizedSolutionExists($active_id, $pass)
5207 {
5208 $solutionAvailability = $this->lookupForExistingSolutions($active_id, $pass);
5209 return (bool) $solutionAvailability['authorized'];
5210 }
5212 {
5213 $solutionAvailability = $this->lookupForExistingSolutions($active_id, $pass);
5214 return (bool) $solutionAvailability['authorized'] || (bool) $solutionAvailability['intermediate'];
5215 }
5216 // hey.
5217
5223 protected function lookupMaxStep($active_id, $pass)
5224 {
5226 global $DIC;
5227 $ilDB = $DIC['ilDB'];
5228
5229 $res = $ilDB->queryF(
5230 "SELECT MAX(step) max_step FROM tst_solutions WHERE active_fi = %s AND pass = %s AND question_fi = %s",
5231 array("integer", "integer", "integer"),
5232 array($active_id, $pass, $this->getId())
5233 );
5234
5235 $row = $ilDB->fetchAssoc($res);
5236
5237 $maxStep = $row['max_step'];
5238
5239 return $maxStep;
5240 }
5241
5242 // fau: testNav - new function lookupForExistingSolutions
5249 public function lookupForExistingSolutions($activeId, $pass)
5250 {
5252 global $DIC;
5253 $ilDB = $DIC['ilDB'];
5254
5255 $return = array(
5256 'authorized' => false,
5257 'intermediate' => false
5258 );
5259
5260 $query = "
5261 SELECT authorized, COUNT(*) cnt
5262 FROM tst_solutions
5263 WHERE active_fi = %s
5264 AND question_fi = %s
5265 AND pass = %s
5266 ";
5267
5268 if ($this->getStep() !== null) {
5269 $query .= " AND step = " . $ilDB->quote((int) $this->getStep(), 'integer') . " ";
5270 }
5271
5272 $query .= "
5273 GROUP BY authorized
5274 ";
5275
5276 $result = $ilDB->queryF($query, array('integer', 'integer', 'integer'), array($activeId, $this->getId(), $pass));
5277
5278 while ($row = $ilDB->fetchAssoc($result)) {
5279 if ($row['authorized']) {
5280 $return['authorized'] = $row['cnt'] > 0;
5281 } else {
5282 $return['intermediate'] = $row['cnt'] > 0;
5283 }
5284 }
5285 return $return;
5286 }
5287 // fau.
5288
5289 public function isAddableAnswerOptionValue($qIndex, $answerOptionValue)
5290 {
5291 return false;
5292 }
5293
5294 public function addAnswerOptionValue($qIndex, $answerOptionValue, $points)
5295 {
5296 }
5297
5299 {
5300 global $DIC; /* @var ILIAS\DI\Container $DIC */
5301
5302 $query = "DELETE FROM tst_solutions WHERE question_fi = %s";
5303
5304 $DIC->database()->manipulateF($query, array('integer'), array($this->getId()));
5305 }
5306
5307 public function removeExistingSolutions($activeId, $pass)
5308 {
5309 global $DIC;
5310 $ilDB = $DIC['ilDB'];
5311
5312 $query = "
5313 DELETE FROM tst_solutions
5314 WHERE active_fi = %s
5315 AND question_fi = %s
5316 AND pass = %s
5317 ";
5318
5319 if ($this->getStep() !== null) {
5320 $query .= " AND step = " . $ilDB->quote((int) $this->getStep(), 'integer') . " ";
5321 }
5322
5323 return $ilDB->manipulateF(
5324 $query,
5325 array('integer', 'integer', 'integer'),
5326 array($activeId, $this->getId(), $pass)
5327 );
5328 }
5329
5330 public function resetUsersAnswer($activeId, $pass)
5331 {
5332 $this->removeExistingSolutions($activeId, $pass);
5333 $this->removeResultRecord($activeId, $pass);
5334
5335 $this->log($activeId, "log_user_solution_willingly_deleted");
5336
5337 self::_updateTestPassResults(
5338 $activeId,
5339 $pass,
5341 $this->getProcessLocker(),
5342 $this->getTestId()
5343 );
5344 }
5345
5346 public function removeResultRecord($activeId, $pass)
5347 {
5348 global $DIC;
5349 $ilDB = $DIC['ilDB'];
5350
5351 $query = "
5352 DELETE FROM tst_test_result
5353 WHERE active_fi = %s
5354 AND question_fi = %s
5355 AND pass = %s
5356 ";
5357
5358 if ($this->getStep() !== null) {
5359 $query .= " AND step = " . $ilDB->quote((int) $this->getStep(), 'integer') . " ";
5360 }
5361
5362 return $ilDB->manipulateF(
5363 $query,
5364 array('integer', 'integer', 'integer'),
5365 array($activeId, $this->getId(), $pass)
5366 );
5367 }
5368
5369 public static function missingResultRecordExists($activeId, $pass, $questionIds)
5370 {
5371 global $DIC;
5372 $ilDB = $DIC['ilDB'];
5373
5374 $IN_questionIds = $ilDB->in('question_fi', $questionIds, false, 'integer');
5375
5376 $query = "
5377 SELECT COUNT(*) cnt
5378 FROM tst_test_result
5379 WHERE active_fi = %s
5380 AND pass = %s
5381 AND $IN_questionIds
5382 ";
5383
5384 $row = $ilDB->fetchAssoc($ilDB->queryF(
5385 $query,
5386 array('integer', 'integer'),
5387 array($activeId, $pass)
5388 ));
5389
5390 return $row['cnt'] < count($questionIds);
5391 }
5392
5393 public static function getQuestionsMissingResultRecord($activeId, $pass, $questionIds)
5394 {
5395 global $DIC;
5396 $ilDB = $DIC['ilDB'];
5397
5398 $IN_questionIds = $ilDB->in('question_fi', $questionIds, false, 'integer');
5399
5400 $query = "
5401 SELECT question_fi
5402 FROM tst_test_result
5403 WHERE active_fi = %s
5404 AND pass = %s
5405 AND $IN_questionIds
5406 ";
5407
5408 $res = $ilDB->queryF(
5409 $query,
5410 array('integer', 'integer'),
5411 array($activeId, $pass)
5412 );
5413
5414 $questionsHavingResultRecord = array();
5415
5416 while ($row = $ilDB->fetchAssoc($res)) {
5417 $questionsHavingResultRecord[] = $row['question_fi'];
5418 }
5419
5420 $questionsMissingResultRecordt = array_diff(
5421 $questionIds,
5422 $questionsHavingResultRecord
5423 );
5424
5425 return $questionsMissingResultRecordt;
5426 }
5427
5428 public static function lookupResultRecordExist($activeId, $questionId, $pass)
5429 {
5430 global $DIC;
5431 $ilDB = $DIC['ilDB'];
5432
5433 $query = "
5434 SELECT COUNT(*) cnt
5435 FROM tst_test_result
5436 WHERE active_fi = %s
5437 AND question_fi = %s
5438 AND pass = %s
5439 ";
5440
5441 $row = $ilDB->fetchAssoc($ilDB->queryF($query, array('integer', 'integer', 'integer'), array($activeId, $questionId, $pass)));
5442
5443 return $row['cnt'] > 0;
5444 }
5445
5450 public function fetchValuePairsFromIndexedValues(array $indexedValues)
5451 {
5452 $valuePairs = array();
5453
5454 foreach ($indexedValues as $value1 => $value2) {
5455 $valuePairs[] = array('value1' => $value1, 'value2' => $value2);
5456 }
5457
5458 return $valuePairs;
5459 }
5460
5465 public function fetchIndexedValuesFromValuePairs(array $valuePairs)
5466 {
5467 $indexedValues = array();
5468
5469 foreach ($valuePairs as $valuePair) {
5470 $indexedValues[ $valuePair['value1'] ] = $valuePair['value2'];
5471 }
5472
5473 return $indexedValues;
5474 }
5475
5480 {
5482 }
5483
5488 {
5489 $this->obligationsToBeConsidered = $obligationsToBeConsidered;
5490 }
5491
5492 public function updateTimestamp()
5493 {
5494 global $DIC;
5495 $ilDB = $DIC['ilDB'];
5496
5497 $ilDB->manipulateF(
5498 "UPDATE qpl_questions SET tstamp = %s WHERE question_id = %s",
5499 array('integer', 'integer'),
5500 array(time(), $this->getId())
5501 );
5502 }
5503
5504 // fau: testNav - new function getTestQuestionConfig()
5505 // hey: prevPassSolutions - get caching independent from configuration (config once)
5506 // renamed: getTestPresentationConfig() -> does the caching
5507 // completed: extracted instance building
5508 // avoids configuring cached instances on every access
5509 // allows a stable reconfigure of the instance from outside
5514
5520 {
5521 if ($this->testQuestionConfigInstance === null) {
5522 $this->testQuestionConfigInstance = $this->buildTestPresentationConfig();
5523 }
5524
5526 }
5527
5536 protected function buildTestPresentationConfig()
5537 {
5538 include_once('Modules/TestQuestionPool/classes/class.ilTestQuestionConfig.php');
5539 return new ilTestQuestionConfig();
5540 }
5541 // hey.
5542 // fau.
5543
5544 public function savePartial()
5545 {
5546 return false;
5547 }
5548
5549 /* doubles isInUse? */
5550 public function isInActiveTest() : bool
5551 {
5552 $query = 'SELECT user_fi FROM tst_active ' . PHP_EOL
5553 . 'JOIN tst_test_question ON tst_test_question.test_fi = tst_active.test_fi ' . PHP_EOL
5554 . 'JOIN qpl_questions ON qpl_questions.question_id = tst_test_question.question_fi ' . PHP_EOL
5555 . 'WHERE qpl_questions.obj_fi = ' . $this->db->quote($this->getObjId(), 'integer');
5556
5557 $res = $this->db->query($query);
5558 return $res->numRows() > 0;
5559 }
5560}
$result
if(!defined('PATH_SEPARATOR')) $GLOBALS['_PEAR_default_error_mode']
Definition: PEAR.php:64
$filename
Definition: buildRTE.php:89
$_GET["client_id"]
$_POST["username"]
$_SESSION["AccountId"]
An exception for terminatinating execution or to throw for unit testing.
const IL_COMP_MODULE
return true
Flag indicating whether or not HTTP headers will be sent when outputting captcha image/audio.
Abstract basic class which is to be extended by the concrete assessment question type classes.
getTotalAnswers()
get total number of answers
$export_image_path
(Web) Path to images
moveUploadedMediaFile($file, $name)
Move an uploaded media file to an public accessible temp dir to present it.
isNonEmptyItemListPostSubmission($postSubmissionFieldname)
getCurrentSolutionResultSet($active_id, $pass, $authorized=true)
Get a restulset for the current user solution for a this question by active_id and pass.
static includePluginClass($questionType, $withGuiClass)
getSolutionValues($active_id, $pass=null, $authorized=true)
Loads solutions of a given user from the database an returns it.
static getFeedbackClassNameByQuestionType($questionType)
setPreventRteUsage($a_val)
Set prevent rte usage.
removeResultRecord($activeId, $pass)
static _getOriginalId($question_id)
Returns the original id of a question.
const KEY_VALUES_IMPLOSION_SEPARATOR
setProcessLocker($processLocker)
formatSAQuestion($a_q)
Format self assessment question.
setShuffle($shuffle=true)
Sets the shuffle flag.
setId($id=-1)
Sets the id of the assQuestion object.
keyInArray($searchkey, $array)
returns TRUE if the key occurs in an array
setOriginalId($original_id)
static _instantiateQuestion($question_id)
const ADDITIONAL_CONTENT_EDITING_MODE_RTE
constant for additional content editing mode "default"
getQuestionTypeID()
Returns the question type of the question.
static _getReachedPoints($active_id, $question_id, $pass=null)
Returns the points, a learner has reached answering the question.
setObjId($obj_id=0)
Set the object id of the container object.
static sumTimesInISO8601FormatH_i_s_Extended($time1, $time2)
static instantiateQuestionGUI($a_question_id)
Creates an instance of a question gui with a given question id.
static isAllowedImageFileExtension($mimeType, $fileExtension)
setExportDetailsXLS($worksheet, $startrow, $active_id, $pass)
Creates an Excel worksheet for the detailed cumulated results of this question.
copySuggestedSolutionFiles($source_questionpool_id, $source_question_id)
getReachedPoints($active_id, $pass=null)
Returns the points, a learner has reached answering the question This is the fast way to get the poin...
static originalQuestionExists($questionId)
beforeSyncWithOriginal($origQuestionId, $dupQuestionId, $origParentObjId, $dupParentObjId)
static isObligationPossible($questionId)
returns boolean wether it is possible to set this question type as obligatory or not considering the ...
static setForcePassResultUpdateEnabled($forcePassResultsUpdateEnabled)
getSolutionMaxPass($active_id)
Returns the maximum pass a users question solution.
setSuggestedSolution($solution_id="", $subquestion_index=0, $is_import=false)
Sets a suggested solution for the question.
const ADDITIONAL_CONTENT_EDITING_MODE_IPE
constant for additional content editing mode "pageobject"
static _getMaximumPoints($question_id)
Returns the maximum points, a learner can reach answering the question.
saveQuestionDataToDb($original_id="")
QTIMaterialToString($a_material)
Reads an QTI material tag an creates a text string.
syncSuggestedSolutionFiles($original_id)
Syncs the files of a suggested solution if the question is synced.
static _getSolutionMaxPass($question_id, $active_id)
Returns the maximum pass a users question solution.
isClone($question_id="")
Checks whether the question is a clone of another question or not.
static fetchMimeTypeIdentifier($contentTypeString)
fixUnavailableSkinImageSources($html)
deletePageOfQuestion($question_id)
Deletes the page object of a question with a given ID.
lmMigrateQuestionTypeGenericContent(ilAssSelfAssessmentMigrator $migrator)
duplicateSkillAssignments($srcParentId, $srcQuestionId, $trgParentId, $trgQuestionId)
savePreviewData(ilAssQuestionPreviewSession $previewSession)
persistWorkingState($active_id, $pass=null, $obligationsEnabled=false, $authorized=true)
persists the working state for current testactive and testpass
deleteSolutionRecordByValues($activeId, $passIndex, $authorized, $matchValues)
isComplete()
Returns true, if a question is complete for use.
addAnswerOptionValue($qIndex, $answerOptionValue, $points)
log($active_id, $langVar)
static _getSuggestedSolutionCount($question_id)
Returns the number of suggested solutions associated with a question.
adjustReachedPointsByScoringOptions($points, $active_id, $pass=null)
Adjust the given reached points by checks for all special scoring options in the test container.
isInUse($question_id="")
Checks whether the question is in use or not.
static resetOriginalId($questionId)
static _areAnswered($a_user_id, $a_question_ids)
Checks if an array of question ids is answered by an user or not.
isPreviewSolutionCorrect(ilAssQuestionPreviewSession $previewSession)
cleanupMediaObjectUsage()
synchronises appearances of media objects in the question with media object usage table
deleteDummySolutionRecord($activeId, $passIndex)
getId()
Gets the id of the assQuestion object.
static saveOriginalId($questionId, $originalId)
saveCurrentSolution($active_id, $pass, $value1, $value2, $authorized=true, $tstamp=null)
fetchValuePairsFromIndexedValues(array $indexedValues)
lmMigrateQuestionTypeSpecificContent(ilAssSelfAssessmentMigrator $migrator)
getObjId()
Get the object id of the container object.
isDummySolutionRecord($solutionRecord)
questionTitleExists($questionpool_id, $title)
Returns TRUE if the question title exists in the database.
supportsJavascriptOutput()
Returns true if the question type supports JavaScript output.
static _getQuestionTypeName($type_tag)
Return the translation for a given question type tag.
getSuggestedSolutionPath()
Returns the path for a suggested solution.
removeExistingSolutions($activeId, $pass)
setTitle($title="")
Sets the title string of the assQuestion object.
static _getTitle($a_q_id)
Returns the title of a question.
lookupTestId($active_id)
getOwner()
Gets the creator/owner ID of the assQuestion object.
getAdditionalContentEditingMode()
getter for additional content editing mode for this question
static _isUsedInRandomTest($question_id="")
Checks whether the question is used in a random test or not.
getSolutionRecordById($solutionId)
isHTML($a_text)
Checks if a given string contains HTML or not.
buildTestPresentationConfig()
build basic test question configuration instance
setObligationsToBeConsidered($obligationsToBeConsidered)
persistPreviewState(ilAssQuestionPreviewSession $previewSession)
persists the preview state for current user and question
static $forcePassResultsUpdateEnabled
addQuestionChangeListener(ilQuestionChangeListener $listener)
onCopy($sourceParentId, $sourceQuestionId, $targetParentId, $targetQuestionId)
Will be called when a question is copied (into another question pool)
duplicateIntermediateSolutionAuthorized($activeId, $passIndex)
authorizedOrIntermediateSolutionExists($active_id, $pass)
addQTIMaterial(&$a_xml_writer, $a_material, $close_material_tag=true, $add_mobs=true)
Creates a QTI material tag from a plain text or xhtml text.
resetUsersAnswer($activeId, $pass)
setOwner($owner="")
Sets the creator/owner ID of the assQuestion object.
getJavaPath()
Returns the image path for web accessable images of a question.
createNewQuestion($a_create_page=true)
Creates a new question without an owner when a new question is created This assures that an ID is giv...
migrateContentForLearningModule(ilAssSelfAssessmentMigrator $migrator)
getAdditionalTableName()
Returns the name of the additional question data table in the database.
setEstimatedWorkingTime($hour=0, $min=0, $sec=0)
Sets the estimated working time of a question from given hour, minute and second.
fetchIndexedValuesFromValuePairs(array $valuePairs)
getSuggestedSolutionTitle($subquestion_index=0)
Returns the title of a suggested solution at a given subquestion_index.
static getObjectClassNameByQuestionType($questionType)
deductHintPointsFromReachedPoints(ilAssQuestionPreviewSession $previewSession, $reachedPoints)
static $allowedImageMaterialFileExtensionsByMimeType
setExportImagePath($a_path)
isValidAdditionalContentEditingMode($additionalContentEditingMode)
returns the fact wether the passed additional content mode is valid or not
calculateReachedPoints($active_id, $pass=null, $authorizedSolution=true, $returndetails=false)
Returns the points, a learner has reached answering the question.
toXML($a_include_header=true, $a_include_binary=true, $a_shuffle=false, $test_output=false, $force_image_references=false)
Returns a QTI xml representation of the question.
setEstimatedWorkingTimeFromDurationString($durationString)
Sets the estimated working time of a question from a given datetime string.
getSuggestedSolutionPathWeb()
Returns the web path for a suggested solution.
calculateReachedPointsFromPreviewSession(ilAssQuestionPreviewSession $previewSession)
setNewOriginalId($newId)
buildImagePath($questionId, $parentObjectId)
static explodeKeyValues($keyValues)
getFlashPath()
Returns the image path for web accessable flash files of a question.
__get($value)
Object getter.
static logAction($logtext="", $active_id="", $question_id="")
Logs an action into the Test&Assessment log.
getImagePath($question_id=null, $object_id=null)
Returns the image path for web accessable images of a question.
removeCurrentSolution($active_id, $pass, $authorized=true)
getSuggestedSolution($subquestion_index=0)
Returns a suggested solution for a given subquestion index.
calculateResultsFromSolution($active_id, $pass=null, $obligationsEnabled=false)
Calculates the question results from a previously saved question solution.
buildHashedImageFilename($plain_image_filename, $unique=false)
updateCurrentSolution($solutionId, $value1, $value2, $authorized=true)
removeSolutionRecordById($solutionId)
static getNumExistingSolutionRecords($activeId, $pass, $questionId)
returns the number of existing solution records for the given test active / pass and given question i...
static implodeKeyValues($keyValues)
static convertISO8601FormatH_i_s_ExtendedToSeconds($time)
duplicateSuggestedSolutionFiles($parent_id, $question_id)
Duplicates the files of a suggested solution if the question is duplicated.
setAuthor($author="")
Sets the authors name of the assQuestion object.
$arrData
Associative array to store properties.
static isFileAvailable($file)
getQuestionType()
Returns the question type of the question.
deleteSuggestedSolutions()
Deletes all suggestes solutions in the database.
getPoints()
Returns the maximum available points for the question.
getOutputType()
Gets the output type.
ensureCurrentTestPass($active_id, $pass)
static isCoreQuestionType($questionType)
getRTETextWithMediaObjects()
Collects all text in the question which could contain media objects which were created with the Rich ...
getActiveUserData($active_id)
Returns the user id and the test id for a given active id.
static _updateTestResultCache($active_id, ilAssQuestionProcessLocker $processLocker=null)
@TODO Move this to a proper place.
fromXML(&$item, &$questionpool_id, &$tst_id, &$tst_object, &$question_counter, &$import_mapping, array $solutionhints=[])
Receives parameters from a QTI parser and creates a valid ILIAS question object.
setExternalId($external_id)
getValidAdditionalContentEditingModes()
getter for valid additional content editing modes
static _getSuggestedSolutionOutput($question_id)
Returns the output of the suggested solution.
static _needsManualScoring($question_id)
prepareTextareaOutput($txt_output, $prepare_for_latex_output=false, $omitNl2BrWhenTextArea=false)
Prepares a string for a text area output in tests.
intermediateSolutionExists($active_id, $pass)
_getTotalAnswers($a_q_id)
get number of answers for question id (static) note: do not use $this inside this method
static _questionExistsInTest($question_id, $test_id)
authorizedSolutionExists($active_id, $pass)
getJavaPathWeb()
Returns the web image path for web accessable java applets of a question.
onDuplicate($originalParentId, $originalQuestionId, $duplicateParentId, $duplicateQuestionId)
Will be called when a question is duplicated (inside a question pool or for insertion in a test)
getTestId()
Gets the test id of the assQuestion object.
setShuffler(ilArrayElementShuffler $shuffler)
__set($key, $value)
Object setter.
getTestOutputSolutions($activeId, $pass)
removeIntermediateSolution($active_id, $pass)
_resolveIntLinks($question_id)
static _getInternalLinkHref($target="")
getSelfAssessmentEditingMode()
Get Self-Assessment Editing Mode.
static $allowedCharsetsByMimeType
getTitleFilenameCompliant()
returns the object title prepared to be used as a filename
copyPageOfQuestion($a_q_id)
static isForcePassResultUpdateEnabled()
static lookupOriginalParentObjId($originalQuestionId)
returns the parent object id for given original question id (should be a qpl id, but theoretically it...
_resolveInternalLink($internal_link)
saveWorkingData($active_id, $pass=null, $authorized=true)
Saves the learners input of the question to the database.
getTestPresentationConfig()
Get the test question configuration (initialised once)
static & _instanciateQuestionGUI($question_id)
Creates an instance of a question gui with a given question id.
_questionExistsInPool($question_id)
Returns true if the question already exists in the database and is assigned to a question pool.
setOutputType($outputType=OUTPUT_HTML)
Sets the output type.
static lookupParentObjId($questionId)
@global ilDBInterface $ilDB
forceExistingIntermediateSolution($activeId, $passIndex, $considerDummyRecordCreation)
static _getTotalRightAnswers($a_q_id)
get number of answers for question id (static) note: do not use $this inside this method
static _getQuestionText($a_q_id)
Returns question text.
static $imageSourceFixReplaceMap
static getGuiClassNameByQuestionType($questionType)
getDefaultNrOfTries()
Get Default Nr of Tries.
purifyAndPrepareTextAreaOutput(string $content)
static _setReachedPoints($active_id, $question_id, $points, $maxpoints, $pass, $manualscoring, $obligationsEnabled)
Sets the points, a learner has reached answering the question Additionally objective results are upda...
setSelfAssessmentEditingMode($a_selfassessmenteditingmode)
Set Self-Assessment Editing Mode.
static $allowedFileExtensionsByMimeType
static setResultGateway($resultGateway)
getShuffle()
Gets the shuffle flag.
duplicate($for_test=true, $title="", $author="", $owner="", $testObjId=null)
getFlashPathWeb()
Returns the web image path for web accessable flash applications of a question.
static includeCoreClass($questionType, $withGuiClass)
createPageObject()
create page object of question
static missingResultRecordExists($activeId, $pass, $questionIds)
static getKeyValuesImplosionSeparator()
getUserSolutionPreferingIntermediate($active_id, $pass=null)
copyXHTMLMediaObjectsOfQuestion($a_q_id)
getSuggestedSolutions()
Return the suggested solutions.
static _isWorkedThrough($active_id, $question_id, $pass=null)
Returns true if the question was worked through in the given pass Worked through means that the user ...
& _getSuggestedSolution($question_id, $subquestion_index=0)
Returns a suggested solution for a given subquestion index.
getAnswerTableName()
Returns the name of the answer table in the database.
isAddableAnswerOptionValue($qIndex, $answerOptionValue)
setLifecycle(ilAssQuestionLifecycle $lifecycle)
fixSvgToPng($imageFilenameContainingString)
syncSkillAssignments($srcParentId, $srcQuestionId, $trgParentId, $trgQuestionId)
updateCurrentSolutionsAuthorization($activeId, $pass, $authorized, $keepTime=false)
static getQuestionsMissingResultRecord($activeId, $pass, $questionIds)
deleteAdditionalTableData($question_id)
Deletes datasets from the additional question table in the database.
static _includeClass($question_type, $gui=0)
Include the php class file for a given question type.
getTitle()
Gets the title string of the assQuestion object.
static _getQuestionTitle($question_id)
Returns the question title of a question with a given id.
setPoints($a_points)
Sets the maximum available points for the question.
afterSyncWithOriginal($origQuestionId, $dupQuestionId, $origParentObjId, $dupParentObjId)
isAdditionalContentEditingModePageObject()
isser for additional "pageobject" content editing mode
duplicateQuestionHints($originalQuestionId, $duplicateQuestionId)
setComment($comment="")
Sets the comment string of the assQuestion object.
static _getQuestionInfo($question_id)
Returns question information from the database.
getAdjustedReachedPoints($active_id, $pass=null, $authorizedSolution=true)
returns the reached points ...
getComment()
Gets the comment string of the assQuestion object.
setTestId($id=-1)
Sets the test id of the assQuestion object.
getAuthor()
Gets the authors name of the assQuestion object.
static getQuestionTypeFromDb($question_id)
get question type for question id
setNrOfTries($a_nr_of_tries)
$nr_of_tries
Number of tries.
static _getQuestionType($question_id)
Returns the question type of a question with a given id.
getQuestion()
Gets the question string of the question object.
setAdditionalContentEditingMode($additinalContentEditingMode)
setter for additional content editing mode for this question
static isAllowedImageMimeType($mimeType)
isAnswered($active_id, $pass=null)
returns boolean wether the question is answered during test pass or not
saveToDb($original_id="")
Saves the question to the database.
static _instanciateQuestion($question_id)
Creates an instance of a question with a given question id.
static _isWriteable($question_id, $user_id)
Returns true if the question is writeable by a certain user.
_questionExists($question_id)
Returns true if the question already exists in the database.
pcArrayShuffle($array)
Shuffles the values of a given array.
setQuestion($question="")
Sets the question string of the question object.
getImagePathWeb()
Returns the web image path for web accessable images of a question.
getMaximumPoints()
Returns the maximum points, a learner can reach answering the question.
loadFromDb($question_id)
Loads the question from the database.
static getAllowedFileExtensionsForMimeType($mimeType)
static lookupResultRecordExist($activeId, $questionId, $pass)
setLastChange($lastChange)
static getResultGateway()
& getInstances()
Gets all instances of the question.
static getAllowedImageMaterialFileExtensions()
lookupCurrentTestPass($active_id, $pass)
getEstimatedWorkingTime()
Gets the estimated working time of a question.
getPreventRteUsage()
Get prevent rte usage.
deleteAnswers($question_id)
Deletes datasets from answers tables.
setDefaultNrOfTries($a_defaultnroftries)
Set Default Nr of Tries.
ensureNonNegativePoints($points)
Assessment hint page object.
static deleteHintsByQuestionIds($questionIds)
Deletes all question hints relating to questions included in given question ids.
static duplicateListForQuestion($originalQuestionId, $duplicateQuestionId)
duplicates a hint list from given original question id to given duplicate question id and returns an ...
static getListByQuestionId($questionId)
instantiates a question hint list for the passed question id
Question page object.
static _updateObjectiveResult($a_user_id, $a_active_id, $a_question_id)
static _getInstanceByType(string $type)
Factory method for creating purifier instances.
static _getIdForImportId($a_import_id)
get current object id for import id (static)
static _addLog($user_id, $object_id, $logtext, $question_id="", $original_id="", $test_only=false, $test_ref_id=null)
Add an assessment log entry.
static _getLogLanguage()
retrieve the log language for assessment logging
static _getManualScoringTypes()
Retrieve the manual scoring settings as type strings.
static _enabledAssessmentLogging()
check wether assessment logging is enabled or not
Class ilObjMediaObject.
static _saveUsage($a_mob_id, $a_type, $a_id, $a_usage_hist_nr=0, $a_lang="-")
Save usage of mob within another container (e.g.
static _getMobsOfObject($a_type, $a_id, $a_usage_hist_nr=0, $a_lang="-")
get mobs of object
static _removeUsage($a_mob_id, $a_type, $a_id, $a_usage_hist_nr=0, $a_lang="-")
Remove usage of mob in another container.
static _exists($a_id, $a_reference=false, $a_type=null)
checks wether a lm content object with specified id exists or not
static _isWriteable($object_id, $user_id)
Returns true, if the question pool is writeable by a given user.
static _updateQuestionCount($object_id)
Updates the number of available questions for a question pool in the database.
static getUsageOfObject($a_obj_id, $a_include_titles=false)
Get usage of object.
static _getParticipantData($active_id)
Retrieves a participant name from active id.
static _getResultPass($active_id)
Retrieves the pass number that should be counted for a given user.
static _getObjectIDFromActiveID($active_id)
Returns the ILIAS test object id for a given active id.
static getDataWebPath(string $relative_path='')
This is originally a fix for https://mantis.ilias.de/view.php?id=35707; in general,...
static _getCountSystem($active_id)
Gets the count system for the calculation of points.
static _lookupAuthor($obj_id)
Gets the authors name of the ilObjTest object.
static _getQuestionCountAndPointsForPassOfParticipant($active_id, $pass)
static _getPass($active_id)
Retrieves the actual pass of a given user for a given test.
static buildExamId($active_id, $pass, $test_obj_id=null)
static _getWorkingTimeOfParticipantForPass($active_id, $pass)
Returns the complete working time in seconds for a test participant.
static _getUserIdFromActiveId($active_id)
static _getScoreCutting($active_id)
Determines if the score of a question should be cut at 0 points or the score of the whole test.
static isQuestionObligatory($question_id)
checks wether the question with given id is marked as obligatory or not
static _lookupObjId($a_id)
static _lookupTitle($a_id)
lookup object title
static _getAllReferences($a_id)
get all reference ids of object
static getPluginObject(string $a_ctype, string $a_cname, string $a_slot_id, string $a_pname)
static _replaceMediaObjectImageSrc($a_text, $a_direction=0, $nic=IL_INST_ID)
Replaces image source from mob image urls with the mob id or replaces mob id with the correct image s...
static _cleanupMediaObjectUsage($a_text, $a_usage_type, $a_usage_id)
Synchronises appearances of media objects in $a_text with media object usage table.
ILIAS Setting Class.
Taxonomy node <-> item assignment.
Test Question configuration.
static moveUploadedFile($a_file, $a_name, $a_target, $a_raise_errors=true, $a_mode="move_uploaded")
move uploaded file
static delDir($a_dir, $a_clean_only=false)
removes a dir and all its content (subdirs and files) recursively
static isHTML($a_text)
Checks if a given string contains HTML or not.
static prepareTextareaOutput($txt_output, $prepare_for_latex_output=false, $omitNl2BrWhenTextArea=false)
Prepares a string for a text area output where latex code may be in it If the text is HTML-free,...
static getASCIIFilename($a_filename)
convert utf8 to ascii filename
static makeDirParents($a_dir)
Create a new directory and all parent directories.
static getImagePath($img, $module_path="", $mode="output", $offline=false)
get image path (for images located in a template directory)
static removeTrailingPathSeparators($path)
static prepareFormOutput($a_str, $a_strip=false)
prepares string output for html forms @access public
static createDirectory($a_dir, $a_mod=0755)
create directory
static signFile($path_to_file)
static setTokenMaxLifetimeInSeconds($token_max_lifetime_in_seconds)
comment()
Definition: comment.php:2
const CLIENT_WEB_DIR
Definition: constants.php:45
const IL_INST_ID
Definition: constants.php:38
global $DIC
Definition: goto.php:24
$target_id
Definition: goto.php:51
$mobs
Definition: imgupload.php:54
$ilUser
Definition: imgupload.php:18
const OUTPUT_HTML
const OUTPUT_JAVASCRIPT
if($format !==null) $name
Definition: metadata.php:230
$index
Definition: metadata.php:128
$i
Definition: metadata.php:24
$keys
Definition: metadata.php:187
$xml
Definition: metadata.php:332
__construct(Container $dic, ilPlugin $plugin)
@inheritDoc
Class ChatMainBarProvider \MainMenu\Provider.
redirection script todo: (a better solution should control the processing via a xml file)
$query
$type
foreach($_POST as $key=> $value) $res
global $ilDB
$data
Definition: storeScorm.php:23
$message
Definition: xapiexit.php:14
$objId
Definition: xapitoken.php:39