ILIAS  release_6 Revision v6.24-5-g0c8bfefb3b8
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
143
150
156 protected $original_id;
157
163 protected $page;
164
169
173 private $arrData;
174
179
184 protected $external_id = '';
185
190
195
202
208 public $feedbackOBJ = null;
209
215 public $prevent_rte_usage = false;
216
223
230
234 protected $questionChangeListeners = array();
235
239 protected $processLocker;
240
241 public $questionActionCmd = 'handleQuestionAction';
242
246 private static $resultGateway = null;
247
251 protected $step = null;
252
253 protected $lastChange;
254
258 protected $shuffler;
259
264
265 // fau: testNav - new variable $testQuestionConfig
270 // fau.
271
275 protected $lifecycle;
276
278 'image/jpeg' => array('jpg', 'jpeg'), 'image/png' => array('png'), 'image/gif' => array('gif')
279 );
280
291 public function __construct(
292 $title = "",
293 $comment = "",
294 $author = "",
295 $owner = -1,
296 $question = ""
297 ) {
298 global $DIC;
299 $ilias = $DIC['ilias'];
300 $lng = $DIC['lng'];
301 $tpl = $DIC['tpl'];
302 $ilDB = $DIC['ilDB'];
303
304 $this->ilias = $ilias;
305 $this->lng = $lng;
306 $this->tpl = $tpl;
307 $this->db = $ilDB;
308
309 $this->original_id = null;
310 $this->title = $title;
311 $this->comment = $comment;
312 $this->page = null;
313 $this->author = $author;
314 $this->setQuestion($question);
315 if (!$this->author) {
316 $this->author = $this->ilias->account->fullname;
317 }
318 $this->owner = $owner;
319 if ($this->owner <= 0) {
320 $this->owner = $this->ilias->account->id;
321 }
322 $this->id = -1;
323 $this->test_id = -1;
324 $this->suggested_solutions = array();
325 $this->shuffle = 1;
326 $this->nr_of_tries = 0;
327 $this->setEstimatedWorkingTime(0, 1, 0);
328 $this->arrData = array();
329 $this->setExternalId('');
330
331 $this->questionActionCmd = 'handleQuestionAction';
332
333 $this->lastChange = null;
334
335 require_once 'Services/Randomization/classes/class.ilArrayElementOrderKeeper.php';
336 $this->shuffler = new ilArrayElementOrderKeeper();
337
338 $this->lifecycle = ilAssQuestionLifecycle::getDraftInstance();
339 }
340
341 protected static $forcePassResultsUpdateEnabled = false;
342
344 {
345 self::$forcePassResultsUpdateEnabled = $forcePassResultsUpdateEnabled;
346 }
347
348 public static function isForcePassResultUpdateEnabled()
349 {
351 }
352
353 public static function isAllowedImageMimeType($mimeType)
354 {
355 return (bool) count(self::getAllowedFileExtensionsForMimeType($mimeType));
356 }
357
358 public static function fetchMimeTypeIdentifier($contentTypeString)
359 {
360 return current(explode(';', $contentTypeString));
361 }
362
363 public static function getAllowedFileExtensionsForMimeType($mimeType)
364 {
365 foreach (self::$allowedFileExtensionsByMimeType as $allowedMimeType => $extensions) {
366 $rexCharsets = implode('|', self::$allowedCharsetsByMimeType[$allowedMimeType]);
367 $rexMimeType = preg_quote($allowedMimeType, '/');
368
369 $rex = '/^' . $rexMimeType . '(;(\s)*charset=(' . $rexCharsets . '))*$/';
370
371 if (!preg_match($rex, $mimeType)) {
372 continue;
373 }
374
375 return $extensions;
376 }
377
378 return array();
379 }
380
381 public static function isAllowedImageFileExtension($mimeType, $fileExtension)
382 {
383 return in_array(
384 strtolower($fileExtension),
385 self::getAllowedFileExtensionsForMimeType($mimeType)
386 );
387 }
388
389 // hey: prevPassSolutions - question action actracted (heavy use in fileupload refactoring)
390
394 protected function getQuestionAction()
395 {
396 if (!isset($_POST['cmd']) || !isset($_POST['cmd'][$this->questionActionCmd])) {
397 return '';
398 }
399
400 if (!is_array($_POST['cmd'][$this->questionActionCmd]) || !count($_POST['cmd'][$this->questionActionCmd])) {
401 return '';
402 }
403
404 return key($_POST['cmd'][$this->questionActionCmd]);
405 }
406
411 protected function isNonEmptyItemListPostSubmission($postSubmissionFieldname)
412 {
413 if (!isset($_POST[$postSubmissionFieldname])) {
414 return false;
415 }
416
417 if (!is_array($_POST[$postSubmissionFieldname])) {
418 return false;
419 }
420
421 if (!count($_POST[$postSubmissionFieldname])) {
422 return false;
423 }
424
425 return true;
426 }
427
433 protected function ensureCurrentTestPass($active_id, $pass)
434 {
435 if (is_integer($pass) && $pass >= 0) {
436 return $pass;
437 }
438
439 return $this->lookupCurrentTestPass($active_id, $pass);
440 }
441
447 protected function lookupCurrentTestPass($active_id, $pass)
448 {
449 require_once 'Modules/Test/classes/class.ilObjTest.php';
450 return ilObjTest::_getPass($active_id);
451 }
452
457 protected function lookupTestId($active_id)
458 {
459 global $DIC; /* @var ILIAS\DI\Container $DIC */
460 $ilDB = $DIC['ilDB'];
461
462 $result = $ilDB->queryF(
463 "SELECT test_fi FROM tst_active WHERE active_id = %s",
464 array('integer'),
465 array($active_id)
466 );
467
468 while ($row = $ilDB->fetchAssoc($result)) {
469 return $row["test_fi"];
470 }
471
472 return null;
473 }
474 // hey.
475
480 protected function log($active_id, $langVar)
481 {
483 $message = $this->lng->txtlng('assessment', $langVar, ilObjAssessmentFolder::_getLogLanguage());
484 assQuestion::logAction($message, $active_id, $this->getId());
485 }
486 }
487
492 {
493 $extensions = array();
494
495 foreach (self::$allowedImageMaterialFileExtensionsByMimeType as $mimeType => $mimeExtensions) {
496 $extensions = array_merge($extensions, $mimeExtensions);
497 }
498 return array_unique($extensions);
499 }
500
504 public function getShuffler()
505 {
506 return $this->shuffler;
507 }
508
513 {
514 $this->shuffler = $shuffler;
515 }
516
521 {
522 $this->processLocker = $processLocker;
523 }
524
528 public function getProcessLocker()
529 {
531 }
532
544 public function fromXML(&$item, &$questionpool_id, &$tst_id, &$tst_object, &$question_counter, &$import_mapping)
545 {
546 include_once "./Modules/TestQuestionPool/classes/import/qti12/class." . $this->getQuestionType() . "Import.php";
547 $classname = $this->getQuestionType() . "Import";
548 $import = new $classname($this);
549 $import->fromXML($item, $questionpool_id, $tst_id, $tst_object, $question_counter, $import_mapping);
550 }
551
558 public function toXML($a_include_header = true, $a_include_binary = true, $a_shuffle = false, $test_output = false, $force_image_references = false)
559 {
560 include_once "./Modules/TestQuestionPool/classes/export/qti12/class." . $this->getQuestionType() . "Export.php";
561 $classname = $this->getQuestionType() . "Export";
562 $export = new $classname($this);
563 return $export->toXML($a_include_header, $a_include_binary, $a_shuffle, $test_output, $force_image_references);
564 }
565
572 public function isComplete()
573 {
574 return false;
575 }
576
584 public function questionTitleExists($questionpool_id, $title)
585 {
586 global $DIC;
587 $ilDB = $DIC['ilDB'];
588
589 $result = $ilDB->queryF(
590 "SELECT * FROM qpl_questions WHERE obj_fi = %s AND title = %s",
591 array('integer','text'),
592 array($questionpool_id, $title)
593 );
594 return ($result->numRows() > 0) ? true : false;
595 }
596
604 public function setTitle($title = "")
605 {
606 $this->title = $title;
607 }
608
616 public function setId($id = -1)
617 {
618 $this->id = $id;
619 }
620
628 public function setTestId($id = -1)
629 {
630 $this->test_id = $id;
631 }
632
640 public function setComment($comment = "")
641 {
642 $this->comment = $comment;
643 }
644
653 {
654 $this->outputType = $outputType;
655 }
656
657
665 public function setShuffle($shuffle = true)
666 {
667 if ($shuffle) {
668 $this->shuffle = 1;
669 } else {
670 $this->shuffle = 0;
671 }
672 }
673
684 public function setEstimatedWorkingTime($hour = 0, $min = 0, $sec = 0)
685 {
686 $this->est_working_time = array("h" => (int) $hour, "m" => (int) $min, "s" => (int) $sec);
687 }
688
695 public function setEstimatedWorkingTimeFromDurationString($durationString)
696 {
697 $this->est_working_time = array(
698 'h' => (int) substr($durationString, 0, 2),
699 'm' => (int) substr($durationString, 3, 2),
700 's' => (int) substr($durationString, 6, 2)
701 );
702 }
703
711 public function keyInArray($searchkey, $array)
712 {
713 if ($searchkey) {
714 foreach ($array as $key => $value) {
715 if (strcmp($key, $searchkey) == 0) {
716 return true;
717 }
718 }
719 }
720 return false;
721 }
722
730 public function setAuthor($author = "")
731 {
732 if (!$author) {
733 $author = $this->ilias->account->fullname;
734 }
735 $this->author = $author;
736 }
737
745 public function setOwner($owner = "")
746 {
747 $this->owner = $owner;
748 }
749
757 public function getTitle()
758 {
759 return $this->title;
760 }
761
768 {
769 require_once 'Services/Utilities/classes/class.ilUtil.php';
770 return ilUtil::getASCIIFilename($this->getTitle());
771 }
772
780 public function getId()
781 {
782 return $this->id;
783 }
784
792 public function getShuffle()
793 {
794 return $this->shuffle;
795 }
796
804 public function getTestId()
805 {
806 return $this->test_id;
807 }
808
816 public function getComment()
817 {
818 return $this->comment;
819 }
820
828 public function getOutputType()
829 {
830 return $this->outputType;
831 }
832
839 public function supportsJavascriptOutput()
840 {
841 return false;
842 }
843
844 public function supportsNonJsOutput()
845 {
846 return true;
847 }
848
849 public function requiresJsSwitch()
850 {
851 return $this->supportsJavascriptOutput() && $this->supportsNonJsOutput();
852 }
853
861 public function getEstimatedWorkingTime()
862 {
863 if (!$this->est_working_time) {
864 $this->est_working_time = array("h" => 0, "m" => 0, "s" => 0);
865 }
867 }
868
876 public function getAuthor()
877 {
878 return $this->author;
879 }
880
888 public function getOwner()
889 {
890 return $this->owner;
891 }
892
900 public function getObjId()
901 {
902 return $this->obj_id;
903 }
904
912 public function setObjId($obj_id = 0)
913 {
914 $this->obj_id = $obj_id;
915 }
916
920 public function getLifecycle()
921 {
922 return $this->lifecycle;
923 }
924
929 {
930 $this->lifecycle = $lifecycle;
931 }
932
937 {
938 $this->external_id = $external_id;
939 }
940
944 public function getExternalId()
945 {
946 if (!strlen($this->external_id)) {
947 if ($this->getId() > 0) {
948 return 'il_' . IL_INST_ID . '_qst_' . $this->getId();
949 } else {
950 return uniqid('', true);
951 }
952 } else {
953 return $this->external_id;
954 }
955 }
956
963 public static function _getMaximumPoints($question_id)
964 {
965 global $DIC;
966 $ilDB = $DIC['ilDB'];
967
968 $points = 0;
969 $result = $ilDB->queryF(
970 "SELECT points FROM qpl_questions WHERE question_id = %s",
971 array('integer'),
972 array($question_id)
973 );
974 if ($result->numRows() == 1) {
975 $row = $ilDB->fetchAssoc($result);
976 $points = $row["points"];
977 }
978 return $points;
979 }
980
987 public static function _getQuestionInfo($question_id)
988 {
989 global $DIC;
990 $ilDB = $DIC['ilDB'];
991
992 $result = $ilDB->queryF(
993 "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",
994 array('integer'),
995 array($question_id)
996 );
997 if ($result->numRows()) {
998 return $ilDB->fetchAssoc($result);
999 } else {
1000 return array();
1001 }
1002 }
1003
1010 public static function _getSuggestedSolutionCount($question_id)
1011 {
1012 global $DIC;
1013 $ilDB = $DIC['ilDB'];
1014
1015 $result = $ilDB->queryF(
1016 "SELECT suggested_solution_id FROM qpl_sol_sug WHERE question_fi = %s",
1017 array('integer'),
1018 array($question_id)
1019 );
1020 return $result->numRows();
1021 }
1022
1029 public static function _getSuggestedSolutionOutput($question_id)
1030 {
1032 if (!is_object($question)) {
1033 return "";
1034 }
1035 return $question->getSuggestedSolutionOutput();
1036 }
1037
1039 {
1040 $output = array();
1041 foreach ($this->suggested_solutions as $solution) {
1042 switch ($solution["type"]) {
1043 case "lm":
1044 case "st":
1045 case "pg":
1046 case "git":
1047 array_push($output, '<a href="' . assQuestion::_getInternalLinkHref($solution["internal_link"]) . '">' . $this->lng->txt("solution_hint") . '</a>');
1048 break;
1049 case "file":
1050 $possible_texts = array_values(array_filter(array(
1051 ilUtil::prepareFormOutput($solution['value']['filename']),
1052 ilUtil::prepareFormOutput($solution['value']['name']),
1053 $this->lng->txt('tst_show_solution_suggested')
1054 )));
1055
1056 require_once 'Services/WebAccessChecker/classes/class.ilWACSignedPath.php';
1058 array_push($output, '<a href="' . ilWACSignedPath::signFile($this->getSuggestedSolutionPathWeb() . $solution["value"]["name"]) . '">' . $possible_texts[0] . '</a>');
1059 break;
1060 case "text":
1061 $solutionValue = $solution["value"];
1062 $solutionValue = $this->fixSvgToPng($solutionValue);
1063 $solutionValue = $this->fixUnavailableSkinImageSources($solutionValue);
1064 $output[] = $this->prepareTextareaOutput($solutionValue, true);
1065 break;
1066 }
1067 }
1068 return join("<br />", $output);
1069 }
1070
1079 public function &_getSuggestedSolution($question_id, $subquestion_index = 0)
1080 {
1081 global $DIC;
1082 $ilDB = $DIC['ilDB'];
1083
1084 $result = $ilDB->queryF(
1085 "SELECT * FROM qpl_sol_sug WHERE question_fi = %s AND subquestion_index = %s",
1086 array('integer','integer'),
1087 array($question_id, $subquestion_index)
1088 );
1089 if ($result->numRows() == 1) {
1090 $row = $ilDB->fetchAssoc($result);
1091 return array(
1092 "internal_link" => $row["internal_link"],
1093 "import_id" => $row["import_id"]
1094 );
1095 } else {
1096 return array();
1097 }
1098 }
1099
1105 public function getSuggestedSolutions()
1106 {
1108 }
1109
1117 public static function _getReachedPoints($active_id, $question_id, $pass = null)
1118 {
1119 global $DIC;
1120 $ilDB = $DIC['ilDB'];
1121
1122 $points = 0;
1123 if (is_null($pass)) {
1124 include_once "./Modules/TestQuestionPool/classes/class.assQuestion.php";
1125 $pass = assQuestion::_getSolutionMaxPass($question_id, $active_id);
1126 }
1127 $result = $ilDB->queryF(
1128 "SELECT * FROM tst_test_result WHERE active_fi = %s AND question_fi = %s AND pass = %s",
1129 array('integer','integer','integer'),
1130 array($active_id, $question_id, $pass)
1131 );
1132 if ($result->numRows() == 1) {
1133 $row = $ilDB->fetchAssoc($result);
1134 $points = $row["points"];
1135 }
1136 return $points;
1137 }
1138
1147 public function getReachedPoints($active_id, $pass = null)
1148 {
1149 return round(self::_getReachedPoints($active_id, $this->getId(), $pass), 2);
1150 }
1151
1158 public function getMaximumPoints()
1159 {
1160 return $this->points;
1161 }
1162
1174 final public function getAdjustedReachedPoints($active_id, $pass = null, $authorizedSolution = true)
1175 {
1176 if (is_null($pass)) {
1177 include_once "./Modules/Test/classes/class.ilObjTest.php";
1178 $pass = ilObjTest::_getPass($active_id);
1179 }
1180
1181 // determine reached points for submitted solution
1182 $reached_points = $this->calculateReachedPoints($active_id, $pass, $authorizedSolution);
1183
1184
1185
1186 // deduct points for requested hints from reached points
1187 require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionHintTracking.php';
1188 $hintTracking = new ilAssQuestionHintTracking($this->getId(), $active_id, $pass);
1189 $requestsStatisticData = $hintTracking->getRequestStatisticDataByQuestionAndTestpass();
1190 $reached_points = $reached_points - $requestsStatisticData->getRequestsPoints();
1191
1192 // adjust reached points regarding to tests scoring options
1193 $reached_points = $this->adjustReachedPointsByScoringOptions($reached_points, $active_id, $pass);
1194
1195 return $reached_points;
1196 }
1197
1207 final public function calculateResultsFromSolution($active_id, $pass = null, $obligationsEnabled = false)
1208 {
1209 global $DIC;
1210 $ilDB = $DIC['ilDB'];
1211 $ilUser = $DIC['ilUser'];
1212
1213 if (is_null($pass)) {
1214 include_once "./Modules/Test/classes/class.ilObjTest.php";
1215 $pass = ilObjTest::_getPass($active_id);
1216 }
1217
1218 // determine reached points for submitted solution
1219 $reached_points = $this->calculateReachedPoints($active_id, $pass);
1220
1221 // deduct points for requested hints from reached points
1222 require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionHintTracking.php';
1223 $questionHintTracking = new ilAssQuestionHintTracking($this->getId(), $active_id, $pass);
1224 $requestsStatisticData = $questionHintTracking->getRequestStatisticDataByQuestionAndTestpass();
1225 $reached_points = $reached_points - $requestsStatisticData->getRequestsPoints();
1226
1227 // adjust reached points regarding to tests scoring options
1228 $reached_points = $this->adjustReachedPointsByScoringOptions($reached_points, $active_id, $pass);
1229
1230 if ($obligationsEnabled && ilObjTest::isQuestionObligatory($this->getId())) {
1231 $isAnswered = $this->isAnswered($active_id, $pass);
1232 } else {
1233 $isAnswered = true;
1234 }
1235
1236 if (is_null($reached_points)) {
1237 $reached_points = 0;
1238 }
1239
1240 // fau: testNav - check for existing authorized solution to know if a result record should be written
1241 $existingSolutions = $this->lookupForExistingSolutions($active_id, $pass);
1242
1243 $this->getProcessLocker()->executeUserQuestionResultUpdateOperation(function () use ($ilDB, $active_id, $pass, $reached_points, $requestsStatisticData, $isAnswered, $existingSolutions) {
1244 $query = "
1245 DELETE FROM tst_test_result
1246
1247 WHERE active_fi = %s
1248 AND question_fi = %s
1249 AND pass = %s
1250 ";
1251
1252 $types = array('integer', 'integer', 'integer');
1253 $values = array($active_id, $this->getId(), $pass);
1254
1255 if ($this->getStep() !== null) {
1256 $query .= "
1257 AND step = %s
1258 ";
1259
1260 $types[] = 'integer';
1261 $values[] = $this->getStep();
1262 }
1263 $ilDB->manipulateF($query, $types, $values);
1264
1265 if ($existingSolutions['authorized']) {
1266 $next_id = $ilDB->nextId("tst_test_result");
1267 $fieldData = array(
1268 'test_result_id' => array('integer', $next_id),
1269 'active_fi' => array('integer', $active_id),
1270 'question_fi' => array('integer', $this->getId()),
1271 'pass' => array('integer', $pass),
1272 'points' => array('float', $reached_points),
1273 'tstamp' => array('integer', time()),
1274 'hint_count' => array('integer', $requestsStatisticData->getRequestsCount()),
1275 'hint_points' => array('float', $requestsStatisticData->getRequestsPoints()),
1276 'answered' => array('integer', $isAnswered)
1277 );
1278
1279 if ($this->getStep() !== null) {
1280 $fieldData['step'] = array('integer', $this->getStep());
1281 }
1282
1283 $ilDB->insert('tst_test_result', $fieldData);
1284 }
1285 });
1286 // fau.
1287
1288 include_once("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
1289
1292 sprintf(
1293 $this->lng->txtlng(
1294 "assessment",
1295 "log_user_answered_question",
1297 ),
1298 $reached_points
1299 ),
1300 $active_id,
1301 $this->getId()
1302 );
1303 }
1304
1305 // update test pass results
1306 self::_updateTestPassResults($active_id, $pass, $obligationsEnabled, $this->getProcessLocker());
1307
1308 // Update objective status
1309 include_once 'Modules/Course/classes/class.ilCourseObjectiveResult.php';
1310 ilCourseObjectiveResult::_updateObjectiveResult($ilUser->getId(), $active_id, $this->getId());
1311 }
1312
1321 final public function persistWorkingState($active_id, $pass = null, $obligationsEnabled = false, $authorized = true)
1322 {
1323 if (!$this->validateSolutionSubmit() && !$this->savePartial()) {
1324 return false;
1325 }
1326
1327 $saveStatus = false;
1328
1329 $this->getProcessLocker()->executePersistWorkingStateLockOperation(function () use ($active_id, $pass, $authorized, $obligationsEnabled, &$saveStatus) {
1330
1331 if ($pass === null) {
1332 require_once 'Modules/Test/classes/class.ilObjTest.php';
1333 $pass = ilObjTest::_getPass($active_id);
1334 }
1335
1336 $saveStatus = $this->saveWorkingData($active_id, $pass, $authorized);
1337
1338 if ($authorized) {
1339 // fau: testNav - remove an intermediate solution if the authorized solution is saved
1340 // the intermediate solution would set the displayed question status as "editing ..."
1341 $this->removeIntermediateSolution($active_id, $pass);
1342 // fau.
1343 $this->calculateResultsFromSolution($active_id, $pass, $obligationsEnabled);
1344 }
1345 });
1346
1347 return $saveStatus;
1348 }
1349
1353 final public function persistPreviewState(ilAssQuestionPreviewSession $previewSession)
1354 {
1355 $this->savePreviewData($previewSession);
1356 return $this->validateSolutionSubmit();
1357 }
1358
1359 public function validateSolutionSubmit()
1360 {
1361 return true;
1362 }
1363
1373 abstract public function saveWorkingData($active_id, $pass = null, $authorized = true);
1374
1375 protected function savePreviewData(ilAssQuestionPreviewSession $previewSession)
1376 {
1377 $previewSession->setParticipantsSolution($this->getSolutionSubmit());
1378 }
1379
1381 public static function _updateTestResultCache($active_id, ilAssQuestionProcessLocker $processLocker = null)
1382 {
1383 global $DIC;
1384 $ilDB = $DIC['ilDB'];
1385
1386 include_once "./Modules/Test/classes/class.ilObjTest.php";
1387 include_once "./Modules/Test/classes/class.assMarkSchema.php";
1388
1389 $pass = ilObjTest::_getResultPass($active_id);
1390
1391 $query = "
1392 SELECT tst_pass_result.*
1393 FROM tst_pass_result
1394 WHERE active_fi = %s
1395 AND pass = %s
1396 ";
1397
1398 $result = $ilDB->queryF(
1399 $query,
1400 array('integer','integer'),
1401 array($active_id, $pass)
1402 );
1403
1404 $row = $ilDB->fetchAssoc($result);
1405
1406 $max = $row['maxpoints'];
1407 $reached = $row['points'];
1408
1409 $obligationsAnswered = (int) $row['obligations_answered'];
1410
1411 include_once "./Modules/Test/classes/class.assMarkSchema.php";
1412
1413 $percentage = (!$max) ? 0 : ($reached / $max) * 100.0;
1414
1415 $mark = ASS_MarkSchema::_getMatchingMarkFromActiveId($active_id, $percentage);
1416
1417 $isPassed = ($mark["passed"] ? 1 : 0);
1418 $isFailed = (!$mark["passed"] ? 1 : 0);
1419
1420 $userTestResultUpdateCallback = function () use ($ilDB, $active_id, $pass, $max, $reached, $isFailed, $isPassed, $obligationsAnswered, $row, $mark) {
1421 $passedOnceBefore = 0;
1422 $query = "SELECT passed_once FROM tst_result_cache WHERE active_fi = %s";
1423 $res = $ilDB->queryF($query, array('integer'), array($active_id));
1424 while ($row = $ilDB->fetchAssoc($res)) {
1425 $passedOnceBefore = (int) $row['passed_once'];
1426 }
1427
1428 $passedOnce = (int) ($isPassed || $passedOnceBefore);
1429
1430 $ilDB->manipulateF(
1431 "DELETE FROM tst_result_cache WHERE active_fi = %s",
1432 array('integer'),
1433 array($active_id)
1434 );
1435
1436 $ilDB->insert('tst_result_cache', array(
1437 'active_fi' => array('integer', $active_id),
1438 'pass' => array('integer', strlen($pass) ? $pass : 0),
1439 'max_points' => array('float', strlen($max) ? $max : 0),
1440 'reached_points' => array('float', strlen($reached) ? $reached : 0),
1441 'mark_short' => array('text', strlen($mark["short_name"]) ? $mark["short_name"] : " "),
1442 'mark_official' => array('text', strlen($mark["official_name"]) ? $mark["official_name"] : " "),
1443 'passed_once' => array('integer', $passedOnce),
1444 'passed' => array('integer', $isPassed),
1445 'failed' => array('integer', $isFailed),
1446 'tstamp' => array('integer', time()),
1447 'hint_count' => array('integer', $row['hint_count']),
1448 'hint_points' => array('float', $row['hint_points']),
1449 'obligations_answered' => array('integer', $obligationsAnswered)
1450 ));
1451 };
1452
1453 if (is_object($processLocker)) {
1454 $processLocker->executeUserTestResultUpdateLockOperation($userTestResultUpdateCallback);
1455 } else {
1456 $userTestResultUpdateCallback();
1457 }
1458 }
1459
1461 public static function _updateTestPassResults($active_id, $pass, $obligationsEnabled = false, ilAssQuestionProcessLocker $processLocker = null, $test_obj_id = null)
1462 {
1463 global $DIC;
1464 $ilDB = $DIC['ilDB'];
1465
1466 include_once "./Modules/Test/classes/class.ilObjTest.php";
1467
1468 if (self::getResultGateway() !== null) {
1469 $data = self::getResultGateway()->getQuestionCountAndPointsForPassOfParticipant($active_id, $pass);
1470 $time = self::getResultGateway()->getWorkingTimeOfParticipantForPass($active_id, $pass);
1471 } else {
1474 }
1475
1476
1477 // update test pass results
1478
1479 $result = $ilDB->queryF(
1480 "
1481 SELECT SUM(points) reachedpoints,
1482 SUM(hint_count) hint_count,
1483 SUM(hint_points) hint_points,
1484 COUNT(DISTINCT(question_fi)) answeredquestions
1485 FROM tst_test_result
1486 WHERE active_fi = %s
1487 AND pass = %s
1488 ",
1489 array('integer','integer'),
1490 array($active_id, $pass)
1491 );
1492
1493 if ($result->numRows() > 0) {
1494 if ($obligationsEnabled) {
1495 $query = '
1496 SELECT answered answ
1497 FROM tst_test_question
1498 INNER JOIN tst_active
1499 ON active_id = %s
1500 AND tst_test_question.test_fi = tst_active.test_fi
1501 LEFT JOIN tst_test_result
1502 ON tst_test_result.active_fi = %s
1503 AND tst_test_result.pass = %s
1504 AND tst_test_question.question_fi = tst_test_result.question_fi
1505 WHERE obligatory = 1';
1506
1507 $result_obligatory = $ilDB->queryF(
1508 $query,
1509 array('integer','integer','integer'),
1510 array($active_id, $active_id, $pass)
1511 );
1512
1513 $obligations_answered = 1;
1514
1515 while ($row_obligatory = $ilDB->fetchAssoc($result_obligatory)) {
1516 if (!(int) $row_obligatory['answ']) {
1517 $obligations_answered = 0;
1518 break;
1519 }
1520 }
1521 } else {
1522 $obligations_answered = 1;
1523 }
1524
1525 $row = $ilDB->fetchAssoc($result);
1526
1527 if ($row['reachedpoints'] === null) {
1528 $row['reachedpoints'] = 0;
1529 }
1530 if ($row['hint_count'] === null) {
1531 $row['hint_count'] = 0;
1532 }
1533 if ($row['hint_points'] === null) {
1534 $row['hint_points'] = 0;
1535 }
1536
1537 $exam_identifier = ilObjTest::buildExamId($active_id, $pass, $test_obj_id);
1538
1539 $updatePassResultCallback = function () use ($ilDB, $data, $active_id, $pass, $row, $time, $obligations_answered, $exam_identifier) {
1540
1542 $ilDB->replace(
1543 'tst_pass_result',
1544 array(
1545 'active_fi' => array('integer', $active_id),
1546 'pass' => array('integer', strlen($pass) ? $pass : 0)),
1547 array(
1548 'points' => array('float', $row['reachedpoints'] ? $row['reachedpoints'] : 0),
1549 'maxpoints' => array('float', $data['points']),
1550 'questioncount' => array('integer', $data['count']),
1551 'answeredquestions' => array('integer', $row['answeredquestions']),
1552 'workingtime' => array('integer', $time),
1553 'tstamp' => array('integer', time()),
1554 'hint_count' => array('integer', $row['hint_count']),
1555 'hint_points' => array('float', $row['hint_points']),
1556 'obligations_answered' => array('integer', $obligations_answered),
1557 'exam_id' => array('text', $exam_identifier)
1558 )
1559 );
1560 };
1561
1562 if (is_object($processLocker)) {
1563 $processLocker->executeUserPassResultUpdateLockOperation($updatePassResultCallback);
1564 } else {
1565 $updatePassResultCallback();
1566 }
1567 }
1568
1570
1571 return array(
1572 'active_fi' => $active_id,
1573 'pass' => $pass,
1574 'points' => ($row["reachedpoints"]) ? $row["reachedpoints"] : 0,
1575 'maxpoints' => $data["points"],
1576 'questioncount' => $data["count"],
1577 'answeredquestions' => $row["answeredquestions"],
1578 'workingtime' => $time,
1579 'tstamp' => time(),
1580 'hint_count' => $row['hint_count'],
1581 'hint_points' => $row['hint_points'],
1582 'obligations_answered' => $obligations_answered,
1583 'exam_id' => $exam_identifier
1584 );
1585 }
1586
1594 public static function logAction($logtext = "", $active_id = "", $question_id = "")
1595 {
1596 $original_id = "";
1597 if (strlen($question_id)) {
1598 $original_id = self::_getOriginalId($question_id);
1599 }
1600
1601 require_once 'Modules/Test/classes/class.ilObjAssessmentFolder.php';
1602 require_once 'Modules/Test/classes/class.ilObjTest.php';
1603
1605 $GLOBALS['DIC']['ilUser']->getId(),
1607 $logtext,
1608 $question_id,
1610 );
1611 }
1612
1620 public function moveUploadedMediaFile($file, $name)
1621 {
1622 $mediatempdir = CLIENT_WEB_DIR . "/assessment/temp";
1623 if (!@is_dir($mediatempdir)) {
1624 ilUtil::createDirectory($mediatempdir);
1625 }
1626 $temp_name = tempnam($mediatempdir, $name . "_____");
1627 $temp_name = str_replace("\\", "/", $temp_name);
1628 @unlink($temp_name);
1629 if (!ilUtil::moveUploadedFile($file, $name, $temp_name)) {
1630 return false;
1631 } else {
1632 return $temp_name;
1633 }
1634 }
1635
1642 {
1643 return CLIENT_WEB_DIR . "/assessment/$this->obj_id/$this->id/solution/";
1644 }
1645
1652 public function getJavaPath()
1653 {
1654 return CLIENT_WEB_DIR . "/assessment/$this->obj_id/$this->id/java/";
1655 }
1656
1663 public function getImagePath($question_id = null, $object_id = null)
1664 {
1665 if ($question_id === null) {
1666 $question_id = $this->id;
1667 }
1668
1669 if ($object_id === null) {
1670 $object_id = $this->obj_id;
1671 }
1672
1673 return $this->buildImagePath($question_id, $object_id);
1674 }
1675
1676 public function buildImagePath($questionId, $parentObjectId)
1677 {
1678 return CLIENT_WEB_DIR . "/assessment/{$parentObjectId}/{$questionId}/images/";
1679 }
1680
1687 public function getFlashPath()
1688 {
1689 return CLIENT_WEB_DIR . "/assessment/$this->obj_id/$this->id/flash/";
1690 }
1691
1698 public function getJavaPathWeb()
1699 {
1700 include_once "./Services/Utilities/classes/class.ilUtil.php";
1701 $webdir = ilUtil::removeTrailingPathSeparators(CLIENT_WEB_DIR) . "/assessment/$this->obj_id/$this->id/java/";
1702 return str_replace(ilUtil::removeTrailingPathSeparators(ILIAS_ABSOLUTE_PATH), ilUtil::removeTrailingPathSeparators(ILIAS_HTTP_PATH), $webdir);
1703 }
1704
1711 {
1712 include_once "./Services/Utilities/classes/class.ilUtil.php";
1713 $webdir = ilUtil::removeTrailingPathSeparators(CLIENT_WEB_DIR) . "/assessment/$this->obj_id/$this->id/solution/";
1714 return str_replace(ilUtil::removeTrailingPathSeparators(ILIAS_ABSOLUTE_PATH), ilUtil::removeTrailingPathSeparators(ILIAS_HTTP_PATH), $webdir);
1715 }
1716
1725 public function getImagePathWeb()
1726 {
1727 if (!$this->export_image_path) {
1728 include_once "./Services/Utilities/classes/class.ilUtil.php";
1729 $webdir = ilUtil::removeTrailingPathSeparators(CLIENT_WEB_DIR) . "/assessment/$this->obj_id/$this->id/images/";
1730 return str_replace(ilUtil::removeTrailingPathSeparators(ILIAS_ABSOLUTE_PATH), ilUtil::removeTrailingPathSeparators(ILIAS_HTTP_PATH), $webdir);
1731 } else {
1733 }
1734 }
1735
1742 public function getFlashPathWeb()
1743 {
1744 include_once "./Services/Utilities/classes/class.ilUtil.php";
1745 $webdir = ilUtil::removeTrailingPathSeparators(CLIENT_WEB_DIR) . "/assessment/$this->obj_id/$this->id/flash/";
1746 return str_replace(ilUtil::removeTrailingPathSeparators(ILIAS_ABSOLUTE_PATH), ilUtil::removeTrailingPathSeparators(ILIAS_HTTP_PATH), $webdir);
1747 }
1748
1749 // hey: prevPassSolutions - accept and prefer intermediate only from current pass
1750 public function getTestOutputSolutions($activeId, $pass)
1751 {
1752 // hey: refactored identifiers
1753 if ($this->getTestPresentationConfig()->isSolutionInitiallyPrefilled()) {
1754 // hey.
1755 return $this->getSolutionValues($activeId, $pass, true);
1756 }
1757
1758 return $this->getUserSolutionPreferingIntermediate($activeId, $pass);
1759 }
1760 // hey.
1761
1762 public function getUserSolutionPreferingIntermediate($active_id, $pass = null)
1763 {
1764 $solution = $this->getSolutionValues($active_id, $pass, false);
1765
1766 if (!count($solution)) {
1767 $solution = $this->getSolutionValues($active_id, $pass, true);
1768 }
1769
1770 return $solution;
1771 }
1772
1776 public function getSolutionValues($active_id, $pass = null, $authorized = true)
1777 {
1778 global $DIC;
1779 $ilDB = $DIC['ilDB'];
1780
1781 if (is_null($pass)) {
1782 $pass = $this->getSolutionMaxPass($active_id);
1783 }
1784
1785 if ($this->getStep() !== null) {
1786 $query = "
1787 SELECT *
1788 FROM tst_solutions
1789 WHERE active_fi = %s
1790 AND question_fi = %s
1791 AND pass = %s
1792 AND step = %s
1793 AND authorized = %s
1794 ORDER BY solution_id";
1795
1796 $result = $ilDB->queryF(
1797 $query,
1798 array('integer', 'integer', 'integer', 'integer', 'integer'),
1799 array($active_id, $this->getId(), $pass, $this->getStep(), (int) $authorized)
1800 );
1801 } else {
1802 $query = "
1803 SELECT *
1804 FROM tst_solutions
1805 WHERE active_fi = %s
1806 AND question_fi = %s
1807 AND pass = %s
1808 AND authorized = %s
1809 ORDER BY solution_id
1810 ";
1811
1812 $result = $ilDB->queryF(
1813 $query,
1814 array('integer', 'integer', 'integer', 'integer'),
1815 array($active_id, $this->getId(), $pass, (int) $authorized)
1816 );
1817 }
1818
1819 $values = array();
1820
1821 while ($row = $ilDB->fetchAssoc($result)) {
1822 $values[] = $row;
1823 }
1824
1825 return $values;
1826 }
1827
1834 public function isInUse($question_id = "")
1835 {
1836 global $DIC;
1837 $ilDB = $DIC['ilDB'];
1838
1839 if ($question_id < 1) {
1840 $question_id = $this->getId();
1841 }
1842 $result = $ilDB->queryF(
1843 "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",
1844 array('integer'),
1845 array($question_id)
1846 );
1847 $row = $ilDB->fetchAssoc($result);
1848 $count = $row["question_count"];
1849
1850 $result = $ilDB->queryF(
1851 "
1852 SELECT tst_active.test_fi
1853 FROM qpl_questions
1854 INNER JOIN tst_test_rnd_qst ON tst_test_rnd_qst.question_fi = qpl_questions.question_id
1855 INNER JOIN tst_active ON tst_active.active_id = tst_test_rnd_qst.active_fi
1856 WHERE qpl_questions.original_id = %s
1857 GROUP BY tst_active.test_fi",
1858 array('integer'),
1859 array($question_id)
1860 );
1861 $count += $result->numRows();
1862
1863 return $count;
1864 }
1865
1872 public function isClone($question_id = "")
1873 {
1874 global $DIC;
1875 $ilDB = $DIC['ilDB'];
1876
1877 if ($question_id < 1) {
1878 $question_id = $this->id;
1879 }
1880 $result = $ilDB->queryF(
1881 "SELECT original_id FROM qpl_questions WHERE question_id = %s",
1882 array('integer'),
1883 array($question_id)
1884 );
1885 $row = $ilDB->fetchAssoc($result);
1886 return ($row["original_id"] > 0) ? true : false;
1887 }
1888
1895 public function pcArrayShuffle($array)
1896 {
1897 $keys = array_keys($array);
1898 shuffle($keys);
1899 $result = array();
1900 foreach ($keys as $key) {
1901 $result[$key] = $array[$key];
1902 }
1903 return $result;
1904 }
1905
1911 public static function getQuestionTypeFromDb($question_id)
1912 {
1913 global $DIC;
1914 $ilDB = $DIC['ilDB'];
1915
1916 $result = $ilDB->queryF(
1917 "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",
1918 array('integer'),
1919 array($question_id)
1920 );
1921 $data = $ilDB->fetchAssoc($result);
1922 return $data["type_tag"];
1923 }
1924
1931 public function getAdditionalTableName()
1932 {
1933 return "";
1934 }
1935
1942 public function getAnswerTableName()
1943 {
1944 return "";
1945 }
1946
1953 public function deleteAnswers($question_id)
1954 {
1955 global $DIC;
1956 $ilDB = $DIC['ilDB'];
1957 $answer_table_name = $this->getAnswerTableName();
1958
1959 if (!is_array($answer_table_name)) {
1960 $answer_table_name = array($answer_table_name);
1961 }
1962
1963 foreach ($answer_table_name as $table) {
1964 if (strlen($table)) {
1965 $affectedRows = $ilDB->manipulateF(
1966 "DELETE FROM $table WHERE question_fi = %s",
1967 array('integer'),
1968 array($question_id)
1969 );
1970 }
1971 }
1972 }
1973
1980 public function deleteAdditionalTableData($question_id)
1981 {
1982 global $DIC;
1983 $ilDB = $DIC['ilDB'];
1984
1985 $additional_table_name = $this->getAdditionalTableName();
1986
1987 if (!is_array($additional_table_name)) {
1988 $additional_table_name = array($additional_table_name);
1989 }
1990
1991 foreach ($additional_table_name as $table) {
1992 if (strlen($table)) {
1993 $affectedRows = $ilDB->manipulateF(
1994 "DELETE FROM $table WHERE question_fi = %s",
1995 array('integer'),
1996 array($question_id)
1997 );
1998 }
1999 }
2000 }
2001
2008 protected function deletePageOfQuestion($question_id)
2009 {
2010 include_once "./Modules/TestQuestionPool/classes/class.ilAssQuestionPage.php";
2011 $page = new ilAssQuestionPage($question_id);
2012 $page->delete();
2013 return true;
2014 }
2015
2022 public function delete($question_id)
2023 {
2024 global $DIC;
2025 $ilDB = $DIC['ilDB'];
2026 $ilLog = $DIC['ilLog'];
2027
2028 if ($question_id < 1) {
2029 return true;
2030 } // nothing to do
2031
2032 $result = $ilDB->queryF(
2033 "SELECT obj_fi FROM qpl_questions WHERE question_id = %s",
2034 array('integer'),
2035 array($question_id)
2036 );
2037 if ($result->numRows() == 1) {
2038 $row = $ilDB->fetchAssoc($result);
2039 $obj_id = $row["obj_fi"];
2040 } else {
2041 return true; // nothing to do
2042 }
2043 try {
2044 $this->deletePageOfQuestion($question_id);
2045 } catch (Exception $e) {
2046 $ilLog->write("EXCEPTION: Could not delete page of question $question_id: $e");
2047 return false;
2048 }
2049
2050 $affectedRows = $ilDB->manipulateF(
2051 "DELETE FROM qpl_questions WHERE question_id = %s",
2052 array('integer'),
2053 array($question_id)
2054 );
2055 if ($affectedRows == 0) {
2056 return false;
2057 }
2058
2059 try {
2060 $this->deleteAdditionalTableData($question_id);
2061 $this->deleteAnswers($question_id);
2062 $this->feedbackOBJ->deleteGenericFeedbacks($question_id, $this->isAdditionalContentEditingModePageObject());
2063 $this->feedbackOBJ->deleteSpecificAnswerFeedbacks($question_id, $this->isAdditionalContentEditingModePageObject());
2064 } catch (Exception $e) {
2065 $ilLog->write("EXCEPTION: Could not delete additional table data of question $question_id: $e");
2066 return false;
2067 }
2068
2069 try {
2070 // delete the question in the tst_test_question table (list of test questions)
2071 $affectedRows = $ilDB->manipulateF(
2072 "DELETE FROM tst_test_question WHERE question_fi = %s",
2073 array('integer'),
2074 array($question_id)
2075 );
2076 } catch (Exception $e) {
2077 $ilLog->write("EXCEPTION: Could not delete delete question $question_id from a test: $e");
2078 return false;
2079 }
2080
2081 try {
2082 // delete suggested solutions contained in the question
2083 $affectedRows = $ilDB->manipulateF(
2084 "DELETE FROM qpl_sol_sug WHERE question_fi = %s",
2085 array('integer'),
2086 array($question_id)
2087 );
2088 } catch (Exception $e) {
2089 $ilLog->write("EXCEPTION: Could not delete suggested solutions of question $question_id: $e");
2090 return false;
2091 }
2092
2093 try {
2094 $directory = CLIENT_WEB_DIR . "/assessment/" . $obj_id . "/$question_id";
2095 if (preg_match("/\d+/", $obj_id) and preg_match("/\d+/", $question_id) and is_dir($directory)) {
2096 include_once "./Services/Utilities/classes/class.ilUtil.php";
2097 ilUtil::delDir($directory);
2098 }
2099 } catch (Exception $e) {
2100 $ilLog->write("EXCEPTION: Could not delete question file directory $directory of question $question_id: $e");
2101 return false;
2102 }
2103
2104 try {
2105 include_once("./Services/MediaObjects/classes/class.ilObjMediaObject.php");
2106 $mobs = ilObjMediaObject::_getMobsOfObject("qpl:html", $question_id);
2107 // remaining usages are not in text anymore -> delete them
2108 // and media objects (note: delete method of ilObjMediaObject
2109 // checks whether object is used in another context; if yes,
2110 // the object is not deleted!)
2111 foreach ($mobs as $mob) {
2112 ilObjMediaObject::_removeUsage($mob, "qpl:html", $question_id);
2113 if (ilObjMediaObject::_exists($mob)) {
2114 $mob_obj = new ilObjMediaObject($mob);
2115 $mob_obj->delete();
2116 }
2117 }
2118 } catch (Exception $e) {
2119 $ilLog->write("EXCEPTION: Error deleting the media objects of question $question_id: $e");
2120 return false;
2121 }
2122
2123 require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionHintTracking.php';
2124 ilAssQuestionHintTracking::deleteRequestsByQuestionIds(array($question_id));
2125
2126 require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionHintList.php';
2128
2129 require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionSkillAssignmentList.php';
2130 $assignmentList = new ilAssQuestionSkillAssignmentList($ilDB);
2131 $assignmentList->setParentObjId($obj_id);
2132 $assignmentList->setQuestionIdFilter($question_id);
2133 $assignmentList->loadFromDb();
2134 foreach ($assignmentList->getAssignmentsByQuestionId($question_id) as $assignment) {
2135 /* @var ilAssQuestionSkillAssignment $assignment */
2136 $assignment->deleteFromDb();
2137 }
2138
2140
2141 try {
2142 // update question count of question pool
2143 include_once "./Modules/TestQuestionPool/classes/class.ilObjQuestionPool.php";
2145 } catch (Exception $e) {
2146 $ilLog->write("EXCEPTION: Error updating the question pool question count of question pool " . $this->getObjId() . " when deleting question $question_id: $e");
2147 return false;
2148 }
2149
2150 $this->notifyQuestionDeleted($this);
2151
2152 return true;
2153 }
2154
2155 private function deleteTaxonomyAssignments()
2156 {
2157 require_once 'Services/Taxonomy/classes/class.ilObjTaxonomy.php';
2158 require_once 'Services/Taxonomy/classes/class.ilTaxNodeAssignment.php';
2159 $taxIds = ilObjTaxonomy::getUsageOfObject($this->getObjId());
2160
2161 foreach ($taxIds as $taxId) {
2162 $taxNodeAssignment = new ilTaxNodeAssignment('qpl', $this->getObjId(), 'quest', $taxId);
2163 $taxNodeAssignment->deleteAssignmentsOfItem($this->getId());
2164 }
2165 }
2166
2170 public function getTotalAnswers()
2171 {
2172 return $this->_getTotalAnswers($this->id);
2173 }
2174
2181 public function _getTotalAnswers($a_q_id)
2182 {
2183 global $DIC;
2184 $ilDB = $DIC['ilDB'];
2185
2186 // get all question references to the question id
2187 $result = $ilDB->queryF(
2188 "SELECT question_id FROM qpl_questions WHERE original_id = %s OR question_id = %s",
2189 array('integer','integer'),
2190 array($a_q_id, $a_q_id)
2191 );
2192 if ($result->numRows() == 0) {
2193 return 0;
2194 }
2195 $found_id = array();
2196 while ($row = $ilDB->fetchAssoc($result)) {
2197 array_push($found_id, $row["question_id"]);
2198 }
2199
2200 $result = $ilDB->query("SELECT * FROM tst_test_result WHERE " . $ilDB->in('question_fi', $found_id, false, 'integer'));
2201
2202 return $result->numRows();
2203 }
2204
2205
2212 public static function _getTotalRightAnswers($a_q_id)
2213 {
2214 global $DIC;
2215 $ilDB = $DIC['ilDB'];
2216 $result = $ilDB->queryF(
2217 "SELECT question_id FROM qpl_questions WHERE original_id = %s OR question_id = %s",
2218 array('integer','integer'),
2219 array($a_q_id, $a_q_id)
2220 );
2221 if ($result->numRows() == 0) {
2222 return 0;
2223 }
2224 $found_id = array();
2225 while ($row = $ilDB->fetchAssoc($result)) {
2226 array_push($found_id, $row["question_id"]);
2227 }
2228 $result = $ilDB->query("SELECT * FROM tst_test_result WHERE " . $ilDB->in('question_fi', $found_id, false, 'integer'));
2229 $answers = array();
2230 while ($row = $ilDB->fetchAssoc($result)) {
2231 $reached = $row["points"];
2232 include_once "./Modules/TestQuestionPool/classes/class.assQuestion.php";
2233 $max = assQuestion::_getMaximumPoints($row["question_fi"]);
2234 array_push($answers, array("reached" => $reached, "max" => $max));
2235 }
2236 $max = 0.0;
2237 $reached = 0.0;
2238 foreach ($answers as $key => $value) {
2239 $max += $value["max"];
2240 $reached += $value["reached"];
2241 }
2242 if ($max > 0) {
2243 return $reached / $max;
2244 } else {
2245 return 0;
2246 }
2247 }
2248
2254 public static function _getTitle($a_q_id)
2255 {
2256 global $DIC;
2257 $ilDB = $DIC['ilDB'];
2258 $result = $ilDB->queryF(
2259 "SELECT title FROM qpl_questions WHERE question_id = %s",
2260 array('integer'),
2261 array($a_q_id)
2262 );
2263 if ($result->numRows() == 1) {
2264 $row = $ilDB->fetchAssoc($result);
2265 return $row["title"];
2266 } else {
2267 return "";
2268 }
2269 }
2270
2276 public static function _getQuestionText($a_q_id)
2277 {
2278 global $DIC;
2279 $ilDB = $DIC['ilDB'];
2280 $result = $ilDB->queryF(
2281 "SELECT question_text FROM qpl_questions WHERE question_id = %s",
2282 array('integer'),
2283 array($a_q_id)
2284 );
2285 if ($result->numRows() == 1) {
2286 $row = $ilDB->fetchAssoc($result);
2287 return $row["question_text"];
2288 } else {
2289 return "";
2290 }
2291 }
2292
2293 public static function isFileAvailable($file)
2294 {
2295 if (!file_exists($file)) {
2296 return false;
2297 }
2298
2299 if (!is_file($file)) {
2300 return false;
2301 }
2302
2303 if (!is_readable($file)) {
2304 return false;
2305 }
2306
2307 return true;
2308 }
2309
2310 public function copyXHTMLMediaObjectsOfQuestion($a_q_id)
2311 {
2312 include_once("./Services/MediaObjects/classes/class.ilObjMediaObject.php");
2313 $mobs = ilObjMediaObject::_getMobsOfObject("qpl:html", $a_q_id);
2314 foreach ($mobs as $mob) {
2315 ilObjMediaObject::_saveUsage($mob, "qpl:html", $this->getId());
2316 }
2317 }
2318
2320 {
2321 include_once("./Services/MediaObjects/classes/class.ilObjMediaObject.php");
2322 $mobs = ilObjMediaObject::_getMobsOfObject("qpl:html", $this->getId());
2323 foreach ($mobs as $mob) {
2324 ilObjMediaObject::_saveUsage($mob, "qpl:html", $this->original_id);
2325 }
2326 }
2327
2331 public function createPageObject()
2332 {
2333 $qpl_id = $this->getObjId();
2334
2335 include_once "./Modules/TestQuestionPool/classes/class.ilAssQuestionPage.php";
2336 $this->page = new ilAssQuestionPage(0);
2337 $this->page->setId($this->getId());
2338 $this->page->setParentId($qpl_id);
2339 $this->page->setXMLContent("<PageObject><PageContent>" .
2340 "<Question QRef=\"il__qst_" . $this->getId() . "\"/>" .
2341 "</PageContent></PageObject>");
2342 $this->page->create();
2343 }
2344
2345 public function copyPageOfQuestion($a_q_id)
2346 {
2347 if ($a_q_id > 0) {
2348 include_once "./Modules/TestQuestionPool/classes/class.ilAssQuestionPage.php";
2349 $page = new ilAssQuestionPage($a_q_id);
2350
2351 $xml = str_replace("il__qst_" . $a_q_id, "il__qst_" . $this->id, $page->getXMLContent());
2352 $this->page->setXMLContent($xml);
2353 $this->page->updateFromXML();
2354 }
2355 }
2356
2357 public function getPageOfQuestion()
2358 {
2359 include_once "./Modules/TestQuestionPool/classes/class.ilAssQuestionPage.php";
2360 $page = new ilAssQuestionPage($this->id);
2361 return $page->getXMLContent();
2362 }
2363
2369 public static function _getQuestionType($question_id)
2370 {
2371 global $DIC;
2372 $ilDB = $DIC['ilDB'];
2373
2374 if ($question_id < 1) {
2375 return "";
2376 }
2377 $result = $ilDB->queryF(
2378 "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",
2379 array('integer'),
2380 array($question_id)
2381 );
2382 if ($result->numRows() == 1) {
2383 $data = $ilDB->fetchAssoc($result);
2384 return $data["type_tag"];
2385 } else {
2386 return "";
2387 }
2388 }
2389
2397 public static function _getQuestionTitle($question_id)
2398 {
2399 global $DIC;
2400 $ilDB = $DIC['ilDB'];
2401
2402 if ($question_id < 1) {
2403 return "";
2404 }
2405
2406 $result = $ilDB->queryF(
2407 "SELECT title FROM qpl_questions WHERE qpl_questions.question_id = %s",
2408 array('integer'),
2409 array($question_id)
2410 );
2411 if ($result->numRows() == 1) {
2412 $data = $ilDB->fetchAssoc($result);
2413 return $data["title"];
2414 } else {
2415 return "";
2416 }
2417 }
2418
2420 {
2421 $this->original_id = $original_id;
2422 }
2423
2424 public function getOriginalId()
2425 {
2426 return $this->original_id;
2427 }
2428
2429 protected static $imageSourceFixReplaceMap = array(
2430 'ok.svg' => 'ok.png', 'not_ok.svg' => 'not_ok.png',
2431 'checkbox_checked.svg' => 'checkbox_checked.png',
2432 'checkbox_unchecked.svg' => 'checkbox_unchecked.png',
2433 'radiobutton_checked.svg' => 'radiobutton_checked.png',
2434 'radiobutton_unchecked.svg' => 'radiobutton_unchecked.png'
2435 );
2436
2437 public function fixSvgToPng($imageFilenameContainingString)
2438 {
2439 $needles = array_keys(self::$imageSourceFixReplaceMap);
2440 $replacements = array_values(self::$imageSourceFixReplaceMap);
2441 return str_replace($needles, $replacements, $imageFilenameContainingString);
2442 }
2443
2444
2445 public function fixUnavailableSkinImageSources($html)
2446 {
2447 $matches = null;
2448 if (preg_match_all('/src="(.*?)"/m', $html, $matches)) {
2449 $sources = $matches[1];
2450
2451 $needleReplacementMap = array();
2452
2453 foreach ($sources as $src) {
2454 $file = ilUtil::removeTrailingPathSeparators(ILIAS_ABSOLUTE_PATH) . DIRECTORY_SEPARATOR . $src;
2455
2456 if (file_exists($file)) {
2457 continue;
2458 }
2459
2460 $levels = explode(DIRECTORY_SEPARATOR, $src);
2461 if (count($levels) < 5 || $levels[0] != 'Customizing' || $levels[2] != 'skin') {
2462 continue;
2463 }
2464
2465 $component = '';
2466
2467 if ($levels[4] == 'Modules' || $levels[4] == 'Services') {
2468 $component = $levels[4] . DIRECTORY_SEPARATOR . $levels[5];
2469 }
2470
2471 $needleReplacementMap[$src] = ilUtil::getImagePath(basename($src), $component);
2472 }
2473
2474 if (count($needleReplacementMap)) {
2475 $html = str_replace(array_keys($needleReplacementMap), array_values($needleReplacementMap), $html);
2476 }
2477 }
2478
2479 return $html;
2480 }
2481
2488 public function loadFromDb($question_id)
2489 {
2490 global $DIC;
2491 $ilDB = $DIC['ilDB'];
2492
2493 $result = $ilDB->queryF(
2494 "SELECT external_id FROM qpl_questions WHERE question_id = %s",
2495 array("integer"),
2496 array($question_id)
2497 );
2498 if ($result->numRows() == 1) {
2499 $data = $ilDB->fetchAssoc($result);
2500 $this->external_id = $data['external_id'];
2501 }
2502
2503 $result = $ilDB->queryF(
2504 "SELECT * FROM qpl_sol_sug WHERE question_fi = %s",
2505 array('integer'),
2506 array($this->getId())
2507 );
2508 $this->suggested_solutions = array();
2509 if ($result->numRows()) {
2510 include_once("./Services/RTE/classes/class.ilRTE.php");
2511 while ($row = $ilDB->fetchAssoc($result)) {
2512 $value = (is_array(unserialize($row["value"]))) ? unserialize($row["value"]) : ilRTE::_replaceMediaObjectImageSrc($row["value"], 1);
2513 $this->suggested_solutions[$row["subquestion_index"]] = array(
2514 "type" => $row["type"],
2515 "value" => $value,
2516 "internal_link" => $row["internal_link"],
2517 "import_id" => $row["import_id"]
2518 );
2519 }
2520 }
2521 }
2522
2529 public function createNewQuestion($a_create_page = true)
2530 {
2531 global $DIC;
2532 $ilDB = $DIC['ilDB'];
2533 $ilUser = $DIC['ilUser'];
2534
2535 $complete = "0";
2536 $estw_time = $this->getEstimatedWorkingTime();
2537 $estw_time = sprintf("%02d:%02d:%02d", $estw_time['h'], $estw_time['m'], $estw_time['s']);
2538 $obj_id = ($this->getObjId() <= 0) ? (ilObject::_lookupObjId((strlen($_GET["ref_id"])) ? $_GET["ref_id"] : $_POST["sel_qpl"])) : $this->getObjId();
2539 if ($obj_id > 0) {
2540 if ($a_create_page) {
2541 $tstamp = 0;
2542 } else {
2543 // question pool must not try to purge
2544 $tstamp = time();
2545 }
2546
2547 $next_id = $ilDB->nextId('qpl_questions');
2548 $affectedRows = $ilDB->insert("qpl_questions", array(
2549 "question_id" => array("integer", $next_id),
2550 "question_type_fi" => array("integer", $this->getQuestionTypeID()),
2551 "obj_fi" => array("integer", $obj_id),
2552 "title" => array("text", null),
2553 "description" => array("text", null),
2554 "author" => array("text", $this->getAuthor()),
2555 "owner" => array("integer", $ilUser->getId()),
2556 "question_text" => array("clob", null),
2557 "points" => array("float", 0),
2558 "nr_of_tries" => array("integer", $this->getDefaultNrOfTries()), // #10771
2559 "working_time" => array("text", $estw_time),
2560 "complete" => array("text", $complete),
2561 "created" => array("integer", time()),
2562 "original_id" => array("integer", null),
2563 "tstamp" => array("integer", $tstamp),
2564 "external_id" => array("text", $this->getExternalId()),
2565 'add_cont_edit_mode' => array('text', $this->getAdditionalContentEditingMode())
2566 ));
2567 $this->setId($next_id);
2568
2569 if ($a_create_page) {
2570 // create page object of question
2571 $this->createPageObject();
2572 }
2573 }
2574
2575 $this->notifyQuestionCreated();
2576
2577 return $this->getId();
2578 }
2579
2581 {
2582 global $DIC;
2583 $ilDB = $DIC['ilDB'];
2584
2585 $estw_time = $this->getEstimatedWorkingTime();
2586 $estw_time = sprintf("%02d:%02d:%02d", $estw_time['h'], $estw_time['m'], $estw_time['s']);
2587
2588 // cleanup RTE images which are not inserted into the question text
2589 include_once("./Services/RTE/classes/class.ilRTE.php");
2590 if ($this->getId() == -1) {
2591 // Neuen Datensatz schreiben
2592 $next_id = $ilDB->nextId('qpl_questions');
2593 $affectedRows = $ilDB->insert("qpl_questions", array(
2594 "question_id" => array("integer", $next_id),
2595 "question_type_fi" => array("integer", $this->getQuestionTypeID()),
2596 "obj_fi" => array("integer", $this->getObjId()),
2597 "title" => array("text", $this->getTitle()),
2598 "description" => array("text", $this->getComment()),
2599 "author" => array("text", $this->getAuthor()),
2600 "owner" => array("integer", $this->getOwner()),
2601 "question_text" => array("clob", ilRTE::_replaceMediaObjectImageSrc($this->getQuestion(), 0)),
2602 "points" => array("float", $this->getMaximumPoints()),
2603 "working_time" => array("text", $estw_time),
2604 "nr_of_tries" => array("integer", $this->getNrOfTries()),
2605 "created" => array("integer", time()),
2606 "original_id" => array("integer", ($original_id) ? $original_id : null),
2607 "tstamp" => array("integer", time()),
2608 "external_id" => array("text", $this->getExternalId()),
2609 'add_cont_edit_mode' => array('text', $this->getAdditionalContentEditingMode())
2610 ));
2611 $this->setId($next_id);
2612 // create page object of question
2613 $this->createPageObject();
2614 } else {
2615 // Vorhandenen Datensatz aktualisieren
2616 $affectedRows = $ilDB->update("qpl_questions", array(
2617 "obj_fi" => array("integer", $this->getObjId()),
2618 "title" => array("text", $this->getTitle()),
2619 "description" => array("text", $this->getComment()),
2620 "author" => array("text", $this->getAuthor()),
2621 "question_text" => array("clob", ilRTE::_replaceMediaObjectImageSrc($this->getQuestion(), 0)),
2622 "points" => array("float", $this->getMaximumPoints()),
2623 "nr_of_tries" => array("integer", $this->getNrOfTries()),
2624 "working_time" => array("text", $estw_time),
2625 "tstamp" => array("integer", time()),
2626 'complete' => array('integer', $this->isComplete()),
2627 "external_id" => array("text", $this->getExternalId())
2628 ), array(
2629 "question_id" => array("integer", $this->getId())
2630 ));
2631 }
2632 }
2633
2640 public function saveToDb($original_id = "")
2641 {
2642 global $DIC;
2643
2644 $this->updateSuggestedSolutions();
2645
2646 // remove unused media objects from ILIAS
2647 $this->cleanupMediaObjectUsage();
2648
2649 $complete = "0";
2650 if ($this->isComplete()) {
2651 $complete = "1";
2652 }
2653
2654 $DIC->database()->update('qpl_questions', array(
2655 'tstamp' => array('integer', time()),
2656 'owner' => array('integer', ($this->getOwner() <= 0 ? $this->ilias->account->id : $this->getOwner())),
2657 'complete' => array('integer', $complete),
2658 'lifecycle' => array('text', $this->getLifecycle()->getIdentifier()),
2659 ), array(
2660 'question_id' => array('integer', $this->getId())
2661 ));
2662
2663 // update question count of question pool
2664 include_once "./Modules/TestQuestionPool/classes/class.ilObjQuestionPool.php";
2666
2667 $this->notifyQuestionEdited($this);
2668 }
2669
2673 public function setNewOriginalId($newId)
2674 {
2675 self::saveOriginalId($this->getId(), $newId);
2676 }
2677
2678 public static function saveOriginalId($questionId, $originalId)
2679 {
2680 $query = "UPDATE qpl_questions SET tstamp = %s, original_id = %s WHERE question_id = %s";
2681
2682 $GLOBALS['DIC']['ilDB']->manipulateF(
2683 $query,
2684 array('integer','integer', 'text'),
2685 array(time(), $originalId, $questionId)
2686 );
2687 }
2688
2689 public static function resetOriginalId($questionId)
2690 {
2691 $query = "UPDATE qpl_questions SET tstamp = %s, original_id = NULL WHERE question_id = %s";
2692
2693 $GLOBALS['DIC']['ilDB']->manipulateF(
2694 $query,
2695 array('integer', 'text'),
2696 array(time(), $questionId)
2697 );
2698 }
2699
2703 protected function onDuplicate($originalParentId, $originalQuestionId, $duplicateParentId, $duplicateQuestionId)
2704 {
2705 $this->duplicateSuggestedSolutionFiles($originalParentId, $originalQuestionId);
2706
2707 // duplicate question feeback
2708 $this->feedbackOBJ->duplicateFeedback($originalQuestionId, $duplicateQuestionId);
2709
2710 // duplicate question hints
2711 $this->duplicateQuestionHints($originalQuestionId, $duplicateQuestionId);
2712
2713 // duplicate skill assignments
2714 $this->duplicateSkillAssignments($originalParentId, $originalQuestionId, $duplicateParentId, $duplicateQuestionId);
2715 }
2716
2717 protected function beforeSyncWithOriginal($origQuestionId, $dupQuestionId, $origParentObjId, $dupParentObjId)
2718 {
2719 }
2720
2721 protected function afterSyncWithOriginal($origQuestionId, $dupQuestionId, $origParentObjId, $dupParentObjId)
2722 {
2723 // sync question feeback
2724 $this->feedbackOBJ->syncFeedback($origQuestionId, $dupQuestionId);
2725 }
2726
2730 protected function onCopy($sourceParentId, $sourceQuestionId, $targetParentId, $targetQuestionId)
2731 {
2732 $this->copySuggestedSolutionFiles($sourceParentId, $sourceQuestionId);
2733
2734 // duplicate question feeback
2735 $this->feedbackOBJ->duplicateFeedback($sourceQuestionId, $targetQuestionId);
2736
2737 // duplicate question hints
2738 $this->duplicateQuestionHints($sourceQuestionId, $targetQuestionId);
2739
2740 // duplicate skill assignments
2741 $this->duplicateSkillAssignments($sourceParentId, $sourceQuestionId, $targetParentId, $targetQuestionId);
2742 }
2743
2748 {
2749 global $DIC;
2750 $ilDB = $DIC['ilDB'];
2751 // delete the links in the qpl_sol_sug table
2752 $affectedRows = $ilDB->manipulateF(
2753 "DELETE FROM qpl_sol_sug WHERE question_fi = %s",
2754 array('integer'),
2755 array($this->getId())
2756 );
2757 // delete the links in the int_link table
2758 include_once "./Services/Link/classes/class.ilInternalLink.php";
2760 $this->suggested_solutions = array();
2762 }
2763
2771 public function getSuggestedSolution($subquestion_index = 0)
2772 {
2773 if (array_key_exists($subquestion_index, $this->suggested_solutions)) {
2774 return $this->suggested_solutions[$subquestion_index];
2775 } else {
2776 return array();
2777 }
2778 }
2779
2788 public function getSuggestedSolutionTitle($subquestion_index = 0)
2789 {
2790 if (array_key_exists($subquestion_index, $this->suggested_solutions)) {
2791 $title = $this->suggested_solutions[$subquestion_index]["internal_link"];
2792 // TO DO: resolve internal link an get link type and title
2793 } else {
2794 $title = "";
2795 }
2796 return $title;
2797 }
2798
2808 public function setSuggestedSolution($solution_id = "", $subquestion_index = 0, $is_import = false)
2809 {
2810 if (strcmp($solution_id, "") != 0) {
2811 $import_id = "";
2812 if ($is_import) {
2813 $import_id = $solution_id;
2814 $solution_id = $this->_resolveInternalLink($import_id);
2815 }
2816 $this->suggested_solutions[$subquestion_index] = array(
2817 "internal_link" => $solution_id,
2818 "import_id" => $import_id
2819 );
2820 }
2821 }
2822
2826 protected function duplicateSuggestedSolutionFiles($parent_id, $question_id)
2827 {
2828 global $DIC;
2829 $ilLog = $DIC['ilLog'];
2830
2831 foreach ($this->suggested_solutions as $index => $solution) {
2832 if (strcmp($solution["type"], "file") == 0) {
2833 $filepath = $this->getSuggestedSolutionPath();
2834 $filepath_original = str_replace(
2835 "/{$this->obj_id}/{$this->id}/solution",
2836 "/$parent_id/$question_id/solution",
2837 $filepath
2838 );
2839 if (!file_exists($filepath)) {
2840 ilUtil::makeDirParents($filepath);
2841 }
2842 $filename = $solution["value"]["name"];
2843 if (strlen($filename)) {
2844 if (!copy($filepath_original . $filename, $filepath . $filename)) {
2845 $ilLog->write("File could not be duplicated!!!!", $ilLog->ERROR);
2846 $ilLog->write("object: " . print_r($this, true), $ilLog->ERROR);
2847 }
2848 }
2849 }
2850 }
2851 }
2852
2857 {
2858 global $DIC;
2859 $ilLog = $DIC['ilLog'];
2860
2861 $filepath = $this->getSuggestedSolutionPath();
2862 $filepath_original = str_replace("/$this->id/solution", "/$original_id/solution", $filepath);
2863 ilUtil::delDir($filepath_original);
2864 foreach ($this->suggested_solutions as $index => $solution) {
2865 if (strcmp($solution["type"], "file") == 0) {
2866 if (!file_exists($filepath_original)) {
2867 ilUtil::makeDirParents($filepath_original);
2868 }
2869 $filename = $solution["value"]["name"];
2870 if (strlen($filename)) {
2871 if (!@copy($filepath . $filename, $filepath_original . $filename)) {
2872 $ilLog->write("File could not be duplicated!!!!", $ilLog->ERROR);
2873 $ilLog->write("object: " . print_r($this, true), $ilLog->ERROR);
2874 }
2875 }
2876 }
2877 }
2878 }
2879
2880 protected function copySuggestedSolutionFiles($source_questionpool_id, $source_question_id)
2881 {
2882 global $DIC;
2883 $ilLog = $DIC['ilLog'];
2884
2885 foreach ($this->suggested_solutions as $index => $solution) {
2886 if (strcmp($solution["type"], "file") == 0) {
2887 $filepath = $this->getSuggestedSolutionPath();
2888 $filepath_original = str_replace("/$this->obj_id/$this->id/solution", "/$source_questionpool_id/$source_question_id/solution", $filepath);
2889 if (!file_exists($filepath)) {
2890 ilUtil::makeDirParents($filepath);
2891 }
2892 $filename = $solution["value"]["name"];
2893 if (strlen($filename)) {
2894 if (!copy($filepath_original . $filename, $filepath . $filename)) {
2895 $ilLog->write("File could not be copied!!!!", $ilLog->ERROR);
2896 $ilLog->write("object: " . print_r($this, true), $ilLog->ERROR);
2897 }
2898 }
2899 }
2900 }
2901 }
2902
2906 public function updateSuggestedSolutions($original_id = "")
2907 {
2908 global $DIC;
2909 $ilDB = $DIC['ilDB'];
2910
2911 $id = (strlen($original_id) && is_numeric($original_id)) ? $original_id : $this->getId();
2912 include_once "./Services/Link/classes/class.ilInternalLink.php";
2913 $affectedRows = $ilDB->manipulateF(
2914 "DELETE FROM qpl_sol_sug WHERE question_fi = %s",
2915 array('integer'),
2916 array($id)
2917 );
2919 include_once("./Services/RTE/classes/class.ilRTE.php");
2920 foreach ($this->suggested_solutions as $index => $solution) {
2921 $next_id = $ilDB->nextId('qpl_sol_sug');
2923 $ilDB->insert(
2924 'qpl_sol_sug',
2925 array(
2926 'suggested_solution_id' => array( 'integer', $next_id ),
2927 'question_fi' => array( 'integer', $id ),
2928 'type' => array( 'text', $solution['type'] ),
2929 'value' => array( 'clob', ilRTE::_replaceMediaObjectImageSrc((is_array($solution['value'])) ? serialize($solution[ 'value' ]) : $solution['value'], 0) ),
2930 'internal_link' => array( 'text', $solution['internal_link'] ),
2931 'import_id' => array( 'text', null ),
2932 'subquestion_index' => array( 'integer', $index ),
2933 'tstamp' => array( 'integer', time() ),
2934 )
2935 );
2936 if (preg_match("/il_(\d*?)_(\w+)_(\d+)/", $solution["internal_link"], $matches)) {
2937 ilInternalLink::_saveLink("qst", $id, $matches[2], $matches[3], $matches[1]);
2938 }
2939 }
2940 if (strlen($original_id) && is_numeric($original_id)) {
2942 }
2943 $this->cleanupMediaObjectUsage();
2944 }
2945
2955 public function saveSuggestedSolution($type, $solution_id = "", $subquestion_index = 0, $value = "")
2956 {
2957 global $DIC;
2958 $ilDB = $DIC['ilDB'];
2959
2960 $affectedRows = $ilDB->manipulateF(
2961 "DELETE FROM qpl_sol_sug WHERE question_fi = %s AND subquestion_index = %s",
2962 array("integer", "integer"),
2963 array(
2964 $this->getId(),
2965 $subquestion_index
2966 )
2967 );
2968
2969 $next_id = $ilDB->nextId('qpl_sol_sug');
2970 include_once("./Services/RTE/classes/class.ilRTE.php");
2972 $affectedRows = $ilDB->insert(
2973 'qpl_sol_sug',
2974 array(
2975 'suggested_solution_id' => array( 'integer', $next_id ),
2976 'question_fi' => array( 'integer', $this->getId() ),
2977 'type' => array( 'text', $type ),
2978 'value' => array( 'clob', ilRTE::_replaceMediaObjectImageSrc((is_array($value)) ? serialize($value) : $value, 0) ),
2979 'internal_link' => array( 'text', $solution_id ),
2980 'import_id' => array( 'text', null ),
2981 'subquestion_index' => array( 'integer', $subquestion_index ),
2982 'tstamp' => array( 'integer', time() ),
2983 )
2984 );
2985 if ($affectedRows == 1) {
2986 $this->suggested_solutions[$subquestion_index] = array(
2987 "type" => $type,
2988 "value" => $value,
2989 "internal_link" => $solution_id,
2990 "import_id" => ""
2991 );
2992 }
2993 $this->cleanupMediaObjectUsage();
2994 }
2995
2996 public function _resolveInternalLink($internal_link)
2997 {
2998 if (preg_match("/il_(\d+)_(\w+)_(\d+)/", $internal_link, $matches)) {
2999 switch ($matches[2]) {
3000 case "lm":
3001 $resolved_link = ilLMObject::_getIdForImportId($internal_link);
3002 break;
3003 case "pg":
3004 $resolved_link = ilInternalLink::_getIdForImportId("PageObject", $internal_link);
3005 break;
3006 case "st":
3007 $resolved_link = ilInternalLink::_getIdForImportId("StructureObject", $internal_link);
3008 break;
3009 case "git":
3010 $resolved_link = ilInternalLink::_getIdForImportId("GlossaryItem", $internal_link);
3011 break;
3012 case "mob":
3013 $resolved_link = ilInternalLink::_getIdForImportId("MediaObject", $internal_link);
3014 break;
3015 }
3016 if (strcmp($resolved_link, "") == 0) {
3017 $resolved_link = $internal_link;
3018 }
3019 } else {
3020 $resolved_link = $internal_link;
3021 }
3022 return $resolved_link;
3023 }
3024
3025 public function _resolveIntLinks($question_id)
3026 {
3027 global $DIC;
3028 $ilDB = $DIC['ilDB'];
3029 $resolvedlinks = 0;
3030 $result = $ilDB->queryF(
3031 "SELECT * FROM qpl_sol_sug WHERE question_fi = %s",
3032 array('integer'),
3033 array($question_id)
3034 );
3035 if ($result->numRows()) {
3036 while ($row = $ilDB->fetchAssoc($result)) {
3037 $internal_link = $row["internal_link"];
3038 include_once "./Modules/TestQuestionPool/classes/class.assQuestion.php";
3039 $resolved_link = assQuestion::_resolveInternalLink($internal_link);
3040 if (strcmp($internal_link, $resolved_link) != 0) {
3041 // internal link was resolved successfully
3042 $affectedRows = $ilDB->manipulateF(
3043 "UPDATE qpl_sol_sug SET internal_link = %s WHERE suggested_solution_id = %s",
3044 array('text','integer'),
3045 array($resolved_link, $row["suggested_solution_id"])
3046 );
3047 $resolvedlinks++;
3048 }
3049 }
3050 }
3051 if ($resolvedlinks) {
3052 // there are resolved links -> reenter theses links to the database
3053
3054 // delete all internal links from the database
3055 include_once "./Services/Link/classes/class.ilInternalLink.php";
3056 ilInternalLink::_deleteAllLinksOfSource("qst", $question_id);
3057
3058 $result = $ilDB->queryF(
3059 "SELECT * FROM qpl_sol_sug WHERE question_fi = %s",
3060 array('integer'),
3061 array($question_id)
3062 );
3063 if ($result->numRows()) {
3064 while ($row = $ilDB->fetchAssoc($result)) {
3065 if (preg_match("/il_(\d*?)_(\w+)_(\d+)/", $row["internal_link"], $matches)) {
3066 ilInternalLink::_saveLink("qst", $question_id, $matches[2], $matches[3], $matches[1]);
3067 }
3068 }
3069 }
3070 }
3071 }
3072
3073 public static function _getInternalLinkHref($target = "")
3074 {
3075 global $DIC;
3076 $ilDB = $DIC['ilDB'];
3077 $linktypes = array(
3078 "lm" => "LearningModule",
3079 "pg" => "PageObject",
3080 "st" => "StructureObject",
3081 "git" => "GlossaryItem",
3082 "mob" => "MediaObject"
3083 );
3084 $href = "";
3085 if (preg_match("/il__(\w+)_(\d+)/", $target, $matches)) {
3086 $type = $matches[1];
3087 $target_id = $matches[2];
3088 include_once "./Services/Utilities/classes/class.ilUtil.php";
3089 switch ($linktypes[$matches[1]]) {
3090 case "LearningModule":
3091 $href = "./goto.php?target=" . $type . "_" . $target_id;
3092 break;
3093 case "PageObject":
3094 case "StructureObject":
3095 $href = "./goto.php?target=" . $type . "_" . $target_id;
3096 break;
3097 case "GlossaryItem":
3098 $href = "./goto.php?target=" . $type . "_" . $target_id;
3099 break;
3100 case "MediaObject":
3101 $href = "./ilias.php?baseClass=ilLMPresentationGUI&obj_type=" . $linktypes[$type] . "&cmd=media&ref_id=" . $_GET["ref_id"] . "&mob_id=" . $target_id;
3102 break;
3103 }
3104 }
3105 return $href;
3106 }
3107
3115 public static function _getOriginalId($question_id)
3116 {
3117 global $DIC;
3118 $ilDB = $DIC['ilDB'];
3119 $result = $ilDB->queryF(
3120 "SELECT * FROM qpl_questions WHERE question_id = %s",
3121 array('integer'),
3122 array($question_id)
3123 );
3124 if ($result->numRows() > 0) {
3125 $row = $ilDB->fetchAssoc($result);
3126 if ($row["original_id"] > 0) {
3127 return $row["original_id"];
3128 } else {
3129 return $row["question_id"];
3130 }
3131 } else {
3132 return "";
3133 }
3134 }
3135
3136 public static function originalQuestionExists($questionId)
3137 {
3138 global $DIC;
3139 $ilDB = $DIC['ilDB'];
3140
3141 $query = "
3142 SELECT COUNT(dupl.question_id) cnt
3143 FROM qpl_questions dupl
3144 INNER JOIN qpl_questions orig
3145 ON orig.question_id = dupl.original_id
3146 WHERE dupl.question_id = %s
3147 ";
3148
3149 $res = $ilDB->queryF($query, array('integer'), array($questionId));
3150 $row = $ilDB->fetchAssoc($res);
3151
3152 return $row['cnt'] > 0;
3153 }
3154
3155 public function syncWithOriginal()
3156 {
3157 global $DIC;
3158 $ilDB = $DIC['ilDB'];
3159
3160 if (!$this->getOriginalId()) {
3161 return;
3162 }
3163
3164 $originalObjId = self::lookupOriginalParentObjId($this->getOriginalId());
3165
3166 if (!$originalObjId) {
3167 return;
3168 }
3169
3170 $id = $this->getId();
3171 $objId = $this->getObjId();
3172 $original = $this->getOriginalId();
3173
3174 $this->beforeSyncWithOriginal($original, $id, $originalObjId, $objId);
3175
3176 $this->setId($original);
3177 $this->setOriginalId(null);
3178 $this->setObjId($originalObjId);
3179
3180 $this->saveToDb();
3181
3182 $this->deletePageOfQuestion($original);
3183 $this->createPageObject();
3184 $this->copyPageOfQuestion($id);
3185
3186 $this->setId($id);
3187 $this->setOriginalId($original);
3188 $this->setObjId($objId);
3189
3190 $this->updateSuggestedSolutions($original);
3192
3193 $this->afterSyncWithOriginal($original, $id, $originalObjId, $objId);
3194 $this->syncHints();
3195 }
3196
3204 public function _questionExists($question_id)
3205 {
3206 global $DIC;
3207 $ilDB = $DIC['ilDB'];
3208
3209 if ($question_id < 1) {
3210 return false;
3211 }
3212
3213 $result = $ilDB->queryF(
3214 "SELECT question_id FROM qpl_questions WHERE question_id = %s",
3215 array('integer'),
3216 array($question_id)
3217 );
3218 if ($result->numRows() == 1) {
3219 return true;
3220 } else {
3221 return false;
3222 }
3223 }
3224
3232 public function _questionExistsInPool($question_id)
3233 {
3234 global $DIC;
3235 $ilDB = $DIC['ilDB'];
3236
3237 if ($question_id < 1) {
3238 return false;
3239 }
3240
3241 $result = $ilDB->queryF(
3242 "SELECT question_id FROM qpl_questions INNER JOIN object_data ON obj_fi = obj_id WHERE question_id = %s AND type = 'qpl'",
3243 array('integer'),
3244 array($question_id)
3245 );
3246 if ($result->numRows() == 1) {
3247 return true;
3248 } else {
3249 return false;
3250 }
3251 }
3252
3260 public static function _instanciateQuestion($question_id)
3261 {
3262 return self::_instantiateQuestion($question_id);
3263 }
3264
3269 public static function _instantiateQuestion($question_id)
3270 {
3271 global $DIC;
3272 $ilCtrl = $DIC['ilCtrl'];
3273 $ilDB = $DIC['ilDB'];
3274 $lng = $DIC['lng'];
3275
3276 if (strcmp($question_id, "") != 0) {
3277 $question_type = assQuestion::_getQuestionType($question_id);
3278 if (!strlen($question_type)) {
3279 return null;
3280 }
3281 assQuestion::_includeClass($question_type);
3282 $objectClassname = self::getObjectClassNameByQuestionType($question_type);
3283 $question = new $objectClassname();
3284 $question->loadFromDb($question_id);
3285
3286 $feedbackObjectClassname = self::getFeedbackClassNameByQuestionType($question_type);
3287 $question->feedbackOBJ = new $feedbackObjectClassname($question, $ilCtrl, $ilDB, $lng);
3288
3289 return $question;
3290 }
3291 }
3292
3299 public function getPoints()
3300 {
3301 if (strcmp($this->points, "") == 0) {
3302 return 0;
3303 } else {
3304 return $this->points;
3305 }
3306 }
3307
3308
3315 public function setPoints($a_points)
3316 {
3317 $this->points = $a_points;
3318 }
3319
3326 public function getSolutionMaxPass($active_id)
3327 {
3328 return self::_getSolutionMaxPass($this->getId(), $active_id);
3329 }
3330
3337 public static function _getSolutionMaxPass($question_id, $active_id)
3338 {
3339 /* include_once "./Modules/Test/classes/class.ilObjTest.php";
3340 $pass = ilObjTest::_getPass($active_id);
3341 return $pass;*/
3342
3343 // the following code was the old solution which added the non answered
3344 // questions of a pass from the answered questions of the previous pass
3345 // with the above solution, only the answered questions of the last pass are counted
3346 global $DIC;
3347 $ilDB = $DIC['ilDB'];
3348
3349 $result = $ilDB->queryF(
3350 "SELECT MAX(pass) maxpass FROM tst_test_result WHERE active_fi = %s AND question_fi = %s",
3351 array('integer','integer'),
3352 array($active_id, $question_id)
3353 );
3354 if ($result->numRows() == 1) {
3355 $row = $ilDB->fetchAssoc($result);
3356 return $row["maxpass"];
3357 } else {
3358 return 0;
3359 }
3360 }
3361
3370 public static function _isWriteable($question_id, $user_id)
3371 {
3372 global $DIC;
3373 $ilDB = $DIC['ilDB'];
3374
3375 if (($question_id < 1) || ($user_id < 1)) {
3376 return false;
3377 }
3378
3379 $result = $ilDB->queryF(
3380 "SELECT obj_fi FROM qpl_questions WHERE question_id = %s",
3381 array('integer'),
3382 array($question_id)
3383 );
3384 if ($result->numRows() == 1) {
3385 $row = $ilDB->fetchAssoc($result);
3386 $qpl_object_id = $row["obj_fi"];
3387 include_once "./Modules/TestQuestionPool/classes/class.ilObjQuestionPool.php";
3388 return ilObjQuestionPool::_isWriteable($qpl_object_id, $user_id);
3389 } else {
3390 return false;
3391 }
3392 }
3393
3400 public static function _isUsedInRandomTest($question_id = "")
3401 {
3402 global $DIC;
3403 $ilDB = $DIC['ilDB'];
3404
3405 if ($question_id < 1) {
3406 return 0;
3407 }
3408 $result = $ilDB->queryF(
3409 "SELECT test_random_question_id FROM tst_test_rnd_qst WHERE question_fi = %s",
3410 array('integer'),
3411 array($question_id)
3412 );
3413 return $result->numRows();
3414 }
3415
3427 abstract public function calculateReachedPoints($active_id, $pass = null, $authorizedSolution = true, $returndetails = false);
3428
3429 public function deductHintPointsFromReachedPoints(ilAssQuestionPreviewSession $previewSession, $reachedPoints)
3430 {
3431 global $DIC;
3432
3433 $hintTracking = new ilAssQuestionPreviewHintTracking($DIC->database(), $previewSession);
3434 $requestsStatisticData = $hintTracking->getRequestStatisticData();
3435 $reachedPoints = $reachedPoints - $requestsStatisticData->getRequestsPoints();
3436
3437 return $reachedPoints;
3438 }
3439
3441 {
3442 $reachedPoints = $this->calculateReachedPointsForSolution($previewSession->getParticipantsSolution());
3443 $reachedPoints = $this->deductHintPointsFromReachedPoints($previewSession, $reachedPoints);
3444
3445 return $this->ensureNonNegativePoints($reachedPoints);
3446 }
3447
3449 {
3450 return $points > 0 ? $points : 0;
3451 }
3452
3454 {
3455 $reachedPoints = $this->calculateReachedPointsFromPreviewSession($previewSession);
3456
3457 if ($reachedPoints < $this->getMaximumPoints()) {
3458 return false;
3459 }
3460
3461 return true;
3462 }
3463
3464
3475 final public function adjustReachedPointsByScoringOptions($points, $active_id, $pass = null)
3476 {
3477 include_once "./Modules/Test/classes/class.ilObjTest.php";
3478 $count_system = ilObjTest::_getCountSystem($active_id);
3479 if ($count_system == 1) {
3480 if (abs($this->getMaximumPoints() - $points) > 0.0000000001) {
3481 $points = 0;
3482 }
3483 }
3484 $score_cutting = ilObjTest::_getScoreCutting($active_id);
3485 if ($score_cutting == 0) {
3486 if ($points < 0) {
3487 $points = 0;
3488 }
3489 }
3490 return $points;
3491 }
3492
3501 public static function _isWorkedThrough($active_id, $question_id, $pass = null)
3502 {
3503 return self::lookupResultRecordExist($active_id, $question_id, $pass);
3504
3505 // oldschool "workedthru"
3506
3507 global $DIC;
3508 $ilDB = $DIC['ilDB'];
3509
3510 $points = 0;
3511 if (is_null($pass)) {
3512 include_once "./Modules/TestQuestionPool/classes/class.assQuestion.php";
3513 $pass = assQuestion::_getSolutionMaxPass($question_id, $active_id);
3514 }
3515 $result = $ilDB->queryF(
3516 "SELECT solution_id FROM tst_solutions WHERE active_fi = %s AND question_fi = %s AND pass = %s",
3517 array('integer','integer','integer'),
3518 array($active_id, $question_id, $pass)
3519 );
3520 if ($result->numRows()) {
3521 return true;
3522 } else {
3523 return false;
3524 }
3525 }
3526
3534 public static function _areAnswered($a_user_id, $a_question_ids)
3535 {
3536 global $DIC;
3537 $ilDB = $DIC['ilDB'];
3538
3539 $res = $ilDB->queryF(
3540 "SELECT DISTINCT(question_fi) FROM tst_test_result JOIN tst_active " .
3541 "ON (active_id = active_fi) " .
3542 "WHERE " . $ilDB->in('question_fi', $a_question_ids, false, 'integer') .
3543 " AND user_fi = %s",
3544 array('integer'),
3545 array($a_user_id)
3546 );
3547 return ($res->numRows() == count($a_question_ids)) ? true : false;
3548 }
3549
3558 public function isHTML($a_text)
3559 {
3560 return ilUtil::isHTML($a_text);
3561 }
3562
3569 public function prepareTextareaOutput($txt_output, $prepare_for_latex_output = false, $omitNl2BrWhenTextArea = false)
3570 {
3571 include_once "./Services/Utilities/classes/class.ilUtil.php";
3572 return ilUtil::prepareTextareaOutput($txt_output, $prepare_for_latex_output, $omitNl2BrWhenTextArea);
3573 }
3574
3582 public function QTIMaterialToString($a_material)
3583 {
3584 $result = "";
3585 for ($i = 0; $i < $a_material->getMaterialCount(); $i++) {
3586 $material = $a_material->getMaterial($i);
3587 if (strcmp($material["type"], "mattext") == 0) {
3588 $result .= $material["material"]->getContent();
3589 }
3590 if (strcmp($material["type"], "matimage") == 0) {
3591 $matimage = $material["material"];
3592 if (preg_match("/(il_([0-9]+)_mob_([0-9]+))/", $matimage->getLabel(), $matches)) {
3593 // import an mediaobject which was inserted using tiny mce
3594 if (!is_array($_SESSION["import_mob_xhtml"])) {
3595 $_SESSION["import_mob_xhtml"] = array();
3596 }
3597 array_push($_SESSION["import_mob_xhtml"], array("mob" => $matimage->getLabel(), "uri" => $matimage->getUri()));
3598 }
3599 }
3600 }
3601 return $result;
3602 }
3603
3612 public function addQTIMaterial(&$a_xml_writer, $a_material, $close_material_tag = true, $add_mobs = true)
3613 {
3614 include_once "./Services/RTE/classes/class.ilRTE.php";
3615 include_once("./Services/MediaObjects/classes/class.ilObjMediaObject.php");
3616
3617 $a_xml_writer->xmlStartTag("material");
3618 $attrs = array(
3619 "texttype" => "text/plain"
3620 );
3621 if ($this->isHTML($a_material)) {
3622 $attrs["texttype"] = "text/xhtml";
3623 }
3624 $a_xml_writer->xmlElement("mattext", $attrs, ilRTE::_replaceMediaObjectImageSrc($a_material, 0));
3625 if ($add_mobs) {
3626 $mobs = ilObjMediaObject::_getMobsOfObject("qpl:html", $this->getId());
3627 foreach ($mobs as $mob) {
3628 $moblabel = "il_" . IL_INST_ID . "_mob_" . $mob;
3629 if (strpos($a_material, "mm_$mob") !== false) {
3630 if (ilObjMediaObject::_exists($mob)) {
3631 $mob_obj = new ilObjMediaObject($mob);
3632 $imgattrs = array(
3633 "label" => $moblabel,
3634 "uri" => "objects/" . "il_" . IL_INST_ID . "_mob_" . $mob . "/" . $mob_obj->getTitle()
3635 );
3636 }
3637 $a_xml_writer->xmlElement("matimage", $imgattrs, null);
3638 }
3639 }
3640 }
3641 if ($close_material_tag) {
3642 $a_xml_writer->xmlEndTag("material");
3643 }
3644 }
3645
3646 public function buildHashedImageFilename($plain_image_filename, $unique = false)
3647 {
3648 $extension = "";
3649
3650 if (preg_match("/.*\.(png|jpg|gif|jpeg)$/i", $plain_image_filename, $matches)) {
3651 $extension = "." . $matches[1];
3652 }
3653
3654 if ($unique) {
3655 $plain_image_filename = uniqid($plain_image_filename . microtime(true));
3656 }
3657
3658 $hashed_filename = md5($plain_image_filename) . $extension;
3659
3660 return $hashed_filename;
3661 }
3662
3673 public static function _setReachedPoints($active_id, $question_id, $points, $maxpoints, $pass, $manualscoring, $obligationsEnabled)
3674 {
3675 global $DIC;
3676 $ilDB = $DIC['ilDB'];
3677
3678 if ($points <= $maxpoints) {
3679 if (is_null($pass)) {
3680 $pass = assQuestion::_getSolutionMaxPass($question_id, $active_id);
3681 }
3682
3683 // retrieve the already given points
3684 $old_points = 0;
3685 $result = $ilDB->queryF(
3686 "SELECT points FROM tst_test_result WHERE active_fi = %s AND question_fi = %s AND pass = %s",
3687 array('integer','integer','integer'),
3688 array($active_id, $question_id, $pass)
3689 );
3690 $manual = ($manualscoring) ? 1 : 0;
3691 $rowsnum = $result->numRows();
3692 if ($rowsnum) {
3693 $row = $ilDB->fetchAssoc($result);
3694 $old_points = $row["points"];
3695 if ($old_points != $points) {
3696 $affectedRows = $ilDB->manipulateF(
3697 "UPDATE tst_test_result SET points = %s, manual = %s, tstamp = %s WHERE active_fi = %s AND question_fi = %s AND pass = %s",
3698 array('float', 'integer', 'integer', 'integer', 'integer', 'integer'),
3699 array($points, $manual, time(), $active_id, $question_id, $pass)
3700 );
3701 }
3702 } else {
3703 $next_id = $ilDB->nextId('tst_test_result');
3704 $affectedRows = $ilDB->manipulateF(
3705 "INSERT INTO tst_test_result (test_result_id, active_fi, question_fi, points, pass, manual, tstamp) VALUES (%s, %s, %s, %s, %s, %s, %s)",
3706 array('integer', 'integer','integer', 'float', 'integer', 'integer','integer'),
3707 array($next_id, $active_id, $question_id, $points, $pass, $manual, time())
3708 );
3709 }
3710
3711 if (self::isForcePassResultUpdateEnabled() || $old_points != $points || !$rowsnum) {
3712 assQuestion::_updateTestPassResults($active_id, $pass, $obligationsEnabled);
3713 // finally update objective result
3714 include_once "./Modules/Test/classes/class.ilObjTest.php";
3715 include_once './Modules/Course/classes/class.ilCourseObjectiveResult.php';
3717
3718 include_once("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
3720 global $DIC;
3721 $lng = $DIC['lng'];
3722 $ilUser = $DIC['ilUser'];
3723 include_once "./Modules/Test/classes/class.ilObjTestAccess.php";
3724 $username = ilObjTestAccess::_getParticipantData($active_id);
3725 assQuestion::logAction(sprintf($lng->txtlng("assessment", "log_answer_changed_points", ilObjAssessmentFolder::_getLogLanguage()), $username, $old_points, $points, $ilUser->getFullname() . " (" . $ilUser->getLogin() . ")"), $active_id, $question_id);
3726 }
3727 }
3728
3729 return true;
3730 } else {
3731 return false;
3732 }
3733 }
3734
3742 public function getQuestion()
3743 {
3744 return $this->question;
3745 }
3746
3754 public function setQuestion($question = "")
3755 {
3756 $this->question = $question;
3757 if (!is_null($question) && $question !== '') {
3758 $this->question = $this->getHtmlQuestionContentPurifier()->purify($question);
3759 }
3760 }
3761
3767 abstract public function getQuestionType();
3768
3777 public function getQuestionTypeID()
3778 {
3779 global $DIC;
3780 $ilDB = $DIC['ilDB'];
3781
3782 $result = $ilDB->queryF(
3783 "SELECT question_type_id FROM qpl_qst_type WHERE type_tag = %s",
3784 array('text'),
3785 array($this->getQuestionType())
3786 );
3787 if ($result->numRows() == 1) {
3788 $row = $ilDB->fetchAssoc($result);
3789 return $row["question_type_id"];
3790 }
3791 return 0;
3792 }
3793
3794 public function syncHints()
3795 {
3796 global $DIC;
3797 $ilDB = $DIC['ilDB'];
3798
3799 // delete hints of the original
3800 $ilDB->manipulateF(
3801 "DELETE FROM qpl_hints WHERE qht_question_fi = %s",
3802 array('integer'),
3803 array($this->original_id)
3804 );
3805
3806 // get hints of the actual question
3807 $result = $ilDB->queryF(
3808 "SELECT * FROM qpl_hints WHERE qht_question_fi = %s",
3809 array('integer'),
3810 array($this->getId())
3811 );
3812
3813 // save hints to the original
3814 if ($result->numRows()) {
3815 while ($row = $ilDB->fetchAssoc($result)) {
3816 $next_id = $ilDB->nextId('qpl_hints');
3818 $ilDB->insert(
3819 'qpl_hints',
3820 array(
3821 'qht_hint_id' => array('integer', $next_id),
3822 'qht_question_fi' => array('integer', $this->original_id),
3823 'qht_hint_index' => array('integer', $row["qht_hint_index"]),
3824 'qht_hint_points' => array('integer', $row["qht_hint_points"]),
3825 'qht_hint_text' => array('text', $row["qht_hint_text"]),
3826 )
3827 );
3828 }
3829 }
3830 }
3831
3836 protected function getRTETextWithMediaObjects()
3837 {
3838 // must be called in parent classes. add additional RTE text in the parent
3839 // classes and call this method to add the standard RTE text
3840 $collected = $this->getQuestion();
3841 $collected .= $this->feedbackOBJ->getGenericFeedbackContent($this->getId(), false);
3842 $collected .= $this->feedbackOBJ->getGenericFeedbackContent($this->getId(), true);
3843 $collected .= $this->feedbackOBJ->getAllSpecificAnswerFeedbackContents($this->getId());
3844
3845 foreach ($this->suggested_solutions as $solution_array) {
3846 $collected .= $solution_array["value"];
3847 }
3848
3849 require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionHintList.php';
3850 $questionHintList = ilAssQuestionHintList::getListByQuestionId($this->getId());
3851 foreach ($questionHintList as $questionHint) {
3852 /* @var $questionHint ilAssQuestionHint */
3853 $collected .= $questionHint->getText();
3854 }
3855
3856 return $collected;
3857 }
3858
3863 public function cleanupMediaObjectUsage()
3864 {
3865 $combinedtext = $this->getRTETextWithMediaObjects();
3866 include_once("./Services/RTE/classes/class.ilRTE.php");
3867 ilRTE::_cleanupMediaObjectUsage($combinedtext, "qpl:html", $this->getId());
3868 }
3869
3875 public function &getInstances()
3876 {
3877 global $DIC;
3878 $ilDB = $DIC['ilDB'];
3879
3880 $result = $ilDB->queryF(
3881 "SELECT question_id FROM qpl_questions WHERE original_id = %s",
3882 array("integer"),
3883 array($this->getId())
3884 );
3885 $instances = array();
3886 $ids = array();
3887 while ($row = $ilDB->fetchAssoc($result)) {
3888 array_push($ids, $row["question_id"]);
3889 }
3890 foreach ($ids as $question_id) {
3891 // check non random tests
3892 $result = $ilDB->queryF(
3893 "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",
3894 array("integer"),
3895 array($question_id)
3896 );
3897 while ($row = $ilDB->fetchAssoc($result)) {
3898 $instances[$row['obj_fi']] = ilObject::_lookupTitle($row['obj_fi']);
3899 }
3900 // check random tests
3901 $result = $ilDB->queryF(
3902 "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",
3903 array("integer"),
3904 array($question_id)
3905 );
3906 while ($row = $ilDB->fetchAssoc($result)) {
3907 $instances[$row['obj_fi']] = ilObject::_lookupTitle($row['obj_fi']);
3908 }
3909 }
3910 include_once "./Modules/Test/classes/class.ilObjTest.php";
3911 foreach ($instances as $key => $value) {
3912 $instances[$key] = array("obj_id" => $key, "title" => $value, "author" => ilObjTest::_lookupAuthor($key), "refs" => ilObject::_getAllReferences($key));
3913 }
3914 return $instances;
3915 }
3916
3917 public static function _needsManualScoring($question_id)
3918 {
3919 include_once "./Modules/Test/classes/class.ilObjAssessmentFolder.php";
3921 $questiontype = assQuestion::_getQuestionType($question_id);
3922 if (in_array($questiontype, $scoring)) {
3923 return true;
3924 } else {
3925 return false;
3926 }
3927 }
3928
3936 public function getActiveUserData($active_id)
3937 {
3938 global $DIC;
3939 $ilDB = $DIC['ilDB'];
3940 $result = $ilDB->queryF(
3941 "SELECT * FROM tst_active WHERE active_id = %s",
3942 array('integer'),
3943 array($active_id)
3944 );
3945 if ($result->numRows()) {
3946 $row = $ilDB->fetchAssoc($result);
3947 return array("user_id" => $row["user_fi"], "test_id" => $row["test_fi"]);
3948 } else {
3949 return array();
3950 }
3951 }
3952
3960 public static function _includeClass($question_type, $gui = 0)
3961 {
3962 if (self::isCoreQuestionType($question_type)) {
3963 self::includeCoreClass($question_type, $gui);
3964 } else {
3965 self::includePluginClass($question_type, $gui);
3966 }
3967 }
3968
3969 public static function getGuiClassNameByQuestionType($questionType)
3970 {
3971 return $questionType . 'GUI';
3972 }
3973
3974 public static function getObjectClassNameByQuestionType($questionType)
3975 {
3976 return $questionType;
3977 }
3978
3979 public static function getFeedbackClassNameByQuestionType($questionType)
3980 {
3981 return str_replace('ass', 'ilAss', $questionType) . 'Feedback';
3982 }
3983
3984 public static function isCoreQuestionType($questionType)
3985 {
3986 $guiClassName = self::getGuiClassNameByQuestionType($questionType);
3987 return file_exists("Modules/TestQuestionPool/classes/class.{$guiClassName}.php");
3988 }
3989
3990 public static function includeCoreClass($questionType, $withGuiClass)
3991 {
3992 if ($withGuiClass) {
3993 $guiClassName = self::getGuiClassNameByQuestionType($questionType);
3994 require_once "Modules/TestQuestionPool/classes/class.{$guiClassName}.php";
3995
3996 // object class is included by gui classes constructor
3997 } else {
3998 $objectClassName = self::getObjectClassNameByQuestionType($questionType);
3999 require_once "Modules/TestQuestionPool/classes/class.{$objectClassName}.php";
4000 }
4001
4002 $feedbackClassName = self::getFeedbackClassNameByQuestionType($questionType);
4003 require_once "Modules/TestQuestionPool/classes/feedback/class.{$feedbackClassName}.php";
4004 }
4005
4006 public static function includePluginClass($questionType, $withGuiClass)
4007 {
4008 global $DIC;
4009 $ilPluginAdmin = $DIC['ilPluginAdmin'];
4010
4011 $classes = array(
4012 self::getObjectClassNameByQuestionType($questionType),
4013 self::getFeedbackClassNameByQuestionType($questionType)
4014 );
4015
4016 if ($withGuiClass) {
4017 $classes[] = self::getGuiClassNameByQuestionType($questionType);
4018 }
4019
4020 $pl_names = $ilPluginAdmin->getActivePluginsForSlot(IL_COMP_MODULE, "TestQuestionPool", "qst");
4021 foreach ($pl_names as $pl_name) {
4022 $pl = ilPlugin::getPluginObject(IL_COMP_MODULE, "TestQuestionPool", "qst", $pl_name);
4023 if (strcmp($pl->getQuestionType(), $questionType) == 0) {
4024 foreach ($classes as $class) {
4025 $pl->includeClass("class.{$class}.php");
4026 }
4027
4028 break;
4029 }
4030 }
4031 }
4032
4039 public static function _getQuestionTypeName($type_tag)
4040 {
4041 if (file_exists("./Modules/TestQuestionPool/classes/class." . $type_tag . ".php")) {
4042 global $DIC;
4043 $lng = $DIC['lng'];
4044 return $lng->txt($type_tag);
4045 } else {
4046 global $DIC;
4047 $ilPluginAdmin = $DIC['ilPluginAdmin'];
4048 $pl_names = $ilPluginAdmin->getActivePluginsForSlot(IL_COMP_MODULE, "TestQuestionPool", "qst");
4049 foreach ($pl_names as $pl_name) {
4050 $pl = ilPlugin::getPluginObject(IL_COMP_MODULE, "TestQuestionPool", "qst", $pl_name);
4051 if (strcmp($pl->getQuestionType(), $type_tag) == 0) {
4052 return $pl->getQuestionTypeTranslation();
4053 }
4054 }
4055 }
4056 return "";
4057 }
4058
4068 public static function &_instanciateQuestionGUI($question_id)
4069 {
4070 return self::instantiateQuestionGUI($question_id);
4071 }
4072
4080 public static function instantiateQuestionGUI($a_question_id)
4081 {
4082 global $DIC;
4083 $ilCtrl = $DIC['ilCtrl'];
4084 $ilDB = $DIC['ilDB'];
4085 $lng = $DIC['lng'];
4086 $ilUser = $DIC['ilUser'];
4087
4088 if (strcmp($a_question_id, "") != 0) {
4089 $question_type = assQuestion::_getQuestionType($a_question_id);
4090
4091 assQuestion::_includeClass($question_type, 1);
4092
4093 $question_type_gui = self::getGuiClassNameByQuestionType($question_type);
4094 $question_gui = new $question_type_gui();
4095 $question_gui->object->loadFromDb($a_question_id);
4096
4097 $feedbackObjectClassname = self::getFeedbackClassNameByQuestionType($question_type);
4098 $question_gui->object->feedbackOBJ = new $feedbackObjectClassname($question_gui->object, $ilCtrl, $ilDB, $lng);
4099
4100 $assSettings = new ilSetting('assessment');
4101 require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionProcessLockerFactory.php';
4102 $processLockerFactory = new ilAssQuestionProcessLockerFactory($assSettings, $ilDB);
4103 $processLockerFactory->setQuestionId($question_gui->object->getId());
4104 $processLockerFactory->setUserId($ilUser->getId());
4105 include_once("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
4106 $processLockerFactory->setAssessmentLogEnabled(ilObjAssessmentFolder::_enabledAssessmentLogging());
4107 $question_gui->object->setProcessLocker($processLockerFactory->getLocker());
4108 } else {
4109 global $DIC;
4110 $ilLog = $DIC['ilLog'];
4111 $ilLog->write('Instantiate question called without question id. (instantiateQuestionGUI@assQuestion)', $ilLog->WARNING);
4112 return null;
4113 }
4114 return $question_gui;
4115 }
4116
4127 public function setExportDetailsXLS($worksheet, $startrow, $active_id, $pass)
4128 {
4129 $worksheet->setFormattedExcelTitle($worksheet->getColumnCoord(0) . $startrow, $this->lng->txt($this->getQuestionType()));
4130 $worksheet->setFormattedExcelTitle($worksheet->getColumnCoord(1) . $startrow, $this->getTitle());
4131
4132 return $startrow;
4133 }
4134
4138 public function __get($value)
4139 {
4140 switch ($value) {
4141 case "id":
4142 return $this->getId();
4143 break;
4144 case "title":
4145 return $this->getTitle();
4146 break;
4147 case "comment":
4148 return $this->getComment();
4149 break;
4150 case "owner":
4151 return $this->getOwner();
4152 break;
4153 case "author":
4154 return $this->getAuthor();
4155 break;
4156 case "question":
4157 return $this->getQuestion();
4158 break;
4159 case "points":
4160 return $this->getPoints();
4161 break;
4162 case "est_working_time":
4163 return $this->getEstimatedWorkingTime();
4164 break;
4165 case "shuffle":
4166 return $this->getShuffle();
4167 break;
4168 case "test_id":
4169 return $this->getTestId();
4170 break;
4171 case "obj_id":
4172 return $this->getObjId();
4173 break;
4174 case "ilias":
4175 return $this->ilias;
4176 break;
4177 case "tpl":
4178 return $this->tpl;
4179 break;
4180 case "page":
4181 return $this->page;
4182 break;
4183 case "outputType":
4184 return $this->getOutputType();
4185 break;
4186 case "suggested_solutions":
4187 return $this->getSuggestedSolutions();
4188 break;
4189 case "original_id":
4190 return $this->getOriginalId();
4191 break;
4192 default:
4193 if (array_key_exists($value, $this->arrData)) {
4194 return $this->arrData[$value];
4195 } else {
4196 return null;
4197 }
4198 break;
4199 }
4200 }
4201
4205 public function __set($key, $value)
4206 {
4207 switch ($key) {
4208 case "id":
4209 $this->setId($value);
4210 break;
4211 case "title":
4212 $this->setTitle($value);
4213 break;
4214 case "comment":
4215 $this->setComment($value);
4216 break;
4217 case "owner":
4218 $this->setOwner($value);
4219 break;
4220 case "author":
4221 $this->setAuthor($value);
4222 break;
4223 case "question":
4224 $this->setQuestion($value);
4225 break;
4226 case "points":
4227 $this->setPoints($value);
4228 break;
4229 case "est_working_time":
4230 if (is_array($value)) {
4231 $this->setEstimatedWorkingTime($value["h"], $value["m"], $value["s"]);
4232 }
4233 break;
4234 case "shuffle":
4235 $this->setShuffle($value);
4236 break;
4237 case "test_id":
4238 $this->setTestId($value);
4239 break;
4240 case "obj_id":
4241 $this->setObjId($value);
4242 break;
4243 case "outputType":
4244 $this->setOutputType($value);
4245 break;
4246 case "original_id":
4247 $this->setOriginalId($value);
4248 break;
4249 case "page":
4250 $this->page = &$value;
4251 break;
4252 default:
4253 $this->arrData[$key] = $value;
4254 break;
4255 }
4256 }
4257
4258 public function getNrOfTries()
4259 {
4260 return (int) $this->nr_of_tries;
4261 }
4262
4263 public function setNrOfTries($a_nr_of_tries)
4264 {
4265 $this->nr_of_tries = $a_nr_of_tries;
4266 }
4267
4268 public function setExportImagePath($a_path)
4269 {
4270 $this->export_image_path = (string) $a_path;
4271 }
4272
4273 public static function _questionExistsInTest($question_id, $test_id)
4274 {
4275 global $DIC;
4276 $ilDB = $DIC['ilDB'];
4277
4278 if ($question_id < 1) {
4279 return false;
4280 }
4281
4282 $result = $ilDB->queryF(
4283 "SELECT question_fi FROM tst_test_question WHERE question_fi = %s AND test_fi = %s",
4284 array('integer', 'integer'),
4285 array($question_id, $test_id)
4286 );
4287 if ($result->numRows() == 1) {
4288 return true;
4289 } else {
4290 return false;
4291 }
4292 }
4293
4300 public function formatSAQuestion($a_q)
4301 {
4302 return $this->getSelfAssessmentFormatter()->format($a_q);
4303 }
4304
4308 protected function getSelfAssessmentFormatter()
4309 {
4310 require_once 'Modules/TestQuestionPool/classes/questions/class.ilAssSelfAssessmentQuestionFormatter.php';
4311 return new \ilAssSelfAssessmentQuestionFormatter();
4312 }
4313
4314 // scorm2004-start ???
4315
4321 public function setPreventRteUsage($a_val)
4322 {
4323 $this->prevent_rte_usage = $a_val;
4324 }
4325
4331 public function getPreventRteUsage()
4332 {
4334 }
4335
4340 {
4341 $this->lmMigrateQuestionTypeGenericContent($migrator);
4342 $this->lmMigrateQuestionTypeSpecificContent($migrator);
4343 $this->saveToDb();
4344
4345 $this->feedbackOBJ->migrateContentForLearningModule($migrator, $this->getId());
4346 }
4347
4352 {
4353 $this->setQuestion($migrator->migrateToLmContent($this->getQuestion()));
4354 }
4355
4360 {
4361 // overwrite if any question type specific content except feedback needs to be migrated
4362 }
4363
4369 public function setSelfAssessmentEditingMode($a_selfassessmenteditingmode)
4370 {
4371 $this->selfassessmenteditingmode = $a_selfassessmenteditingmode;
4372 }
4373
4380 {
4382 }
4383
4389 public function setDefaultNrOfTries($a_defaultnroftries)
4390 {
4391 $this->defaultnroftries = $a_defaultnroftries;
4392 }
4393
4399 public function getDefaultNrOfTries()
4400 {
4401 return (int) $this->defaultnroftries;
4402 }
4403
4404 // scorm2004-end ???
4405
4411 public static function lookupParentObjId($questionId)
4412 {
4413 global $DIC;
4414 $ilDB = $DIC['ilDB'];
4415
4416 $query = "SELECT obj_fi FROM qpl_questions WHERE question_id = %s";
4417
4418 $res = $ilDB->queryF($query, array('integer'), array((int) $questionId));
4419 $row = $ilDB->fetchAssoc($res);
4420
4421 return $row['obj_fi'];
4422 }
4423
4434 public static function lookupOriginalParentObjId($originalQuestionId)
4435 {
4436 return self::lookupParentObjId($originalQuestionId);
4437 }
4438
4439 protected function duplicateQuestionHints($originalQuestionId, $duplicateQuestionId)
4440 {
4441 require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionHintList.php';
4442 $hintIds = ilAssQuestionHintList::duplicateListForQuestion($originalQuestionId, $duplicateQuestionId);
4443
4445 require_once 'Modules/TestQuestionPool/classes/class.ilAssHintPage.php';
4446
4447 foreach ($hintIds as $originalHintId => $duplicateHintId) {
4448 $originalPageObject = new ilAssHintPage($originalHintId);
4449 $originalXML = $originalPageObject->getXMLContent();
4450
4451 $duplicatePageObject = new ilAssHintPage();
4452 $duplicatePageObject->setId($duplicateHintId);
4453 $duplicatePageObject->setParentId($this->getId());
4454 $duplicatePageObject->setXMLContent($originalXML);
4455 $duplicatePageObject->createFromXML();
4456 }
4457 }
4458 }
4459
4460 protected function duplicateSkillAssignments($srcParentId, $srcQuestionId, $trgParentId, $trgQuestionId)
4461 {
4462 global $DIC;
4463 $ilDB = $DIC['ilDB'];
4464
4465 require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionSkillAssignmentList.php';
4466 $assignmentList = new ilAssQuestionSkillAssignmentList($ilDB);
4467 $assignmentList->setParentObjId($srcParentId);
4468 $assignmentList->setQuestionIdFilter($srcQuestionId);
4469 $assignmentList->loadFromDb();
4470
4471 foreach ($assignmentList->getAssignmentsByQuestionId($srcQuestionId) as $assignment) {
4472 /* @var ilAssQuestionSkillAssignment $assignment */
4473
4474 $assignment->setParentObjId($trgParentId);
4475 $assignment->setQuestionId($trgQuestionId);
4476 $assignment->saveToDb();
4477 }
4478 }
4479
4480 public function syncSkillAssignments($srcParentId, $srcQuestionId, $trgParentId, $trgQuestionId)
4481 {
4482 global $DIC;
4483 $ilDB = $DIC['ilDB'];
4484
4485 require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionSkillAssignmentList.php';
4486 $assignmentList = new ilAssQuestionSkillAssignmentList($ilDB);
4487 $assignmentList->setParentObjId($trgParentId);
4488 $assignmentList->setQuestionIdFilter($trgQuestionId);
4489 $assignmentList->loadFromDb();
4490
4491 foreach ($assignmentList->getAssignmentsByQuestionId($trgQuestionId) as $assignment) {
4492 /* @var ilAssQuestionSkillAssignment $assignment */
4493
4494 $assignment->deleteFromDb();
4495 }
4496
4497 $this->duplicateSkillAssignments($srcParentId, $srcQuestionId, $trgParentId, $trgQuestionId);
4498 }
4499
4512 public function isAnswered($active_id, $pass = null)
4513 {
4514 return true;
4515 }
4516
4529 public static function isObligationPossible($questionId)
4530 {
4531 return false;
4532 }
4533
4534 public function isAutosaveable()
4535 {
4536 return true;
4537 }
4538
4551 protected static function getNumExistingSolutionRecords($activeId, $pass, $questionId)
4552 {
4553 global $DIC;
4554 $ilDB = $DIC['ilDB'];
4555
4556 $query = "
4557 SELECT count(active_fi) cnt
4558
4559 FROM tst_solutions
4560
4561 WHERE active_fi = %s
4562 AND question_fi = %s
4563 AND pass = %s
4564 ";
4565
4566 $res = $ilDB->queryF(
4567 $query,
4568 array('integer','integer','integer'),
4569 array($activeId, $questionId, $pass)
4570 );
4571
4572 $row = $ilDB->fetchAssoc($res);
4573
4574 return (int) $row['cnt'];
4575 }
4576
4584 {
4586 }
4587
4595 {
4597 require_once 'Modules/TestQuestionPool/exceptions/class.ilTestQuestionPoolException.php';
4598 throw new ilTestQuestionPoolException('invalid additional content editing mode given: ' . $additinalContentEditingMode);
4599 }
4600
4601 $this->additinalContentEditingMode = $additinalContentEditingMode;
4602 }
4603
4611 {
4613 }
4614
4622 public function isValidAdditionalContentEditingMode($additionalContentEditingMode)
4623 {
4624 if (in_array($additionalContentEditingMode, $this->getValidAdditionalContentEditingModes())) {
4625 return true;
4626 }
4627
4628 return false;
4629 }
4630
4638 {
4639 return array(
4640 self::ADDITIONAL_CONTENT_EDITING_MODE_DEFAULT,
4641 self::ADDITIONAL_CONTENT_EDITING_MODE_PAGE_OBJECT
4642 );
4643 }
4644
4649 {
4650 $this->questionChangeListeners[] = $listener;
4651 }
4652
4657 {
4659 }
4660
4661 private function notifyQuestionCreated()
4662 {
4663 foreach ($this->getQuestionChangeListeners() as $listener) {
4664 $listener->notifyQuestionCreated($this);
4665 }
4666 }
4667
4668 private function notifyQuestionEdited()
4669 {
4670 foreach ($this->getQuestionChangeListeners() as $listener) {
4671 $listener->notifyQuestionEdited($this);
4672 }
4673 }
4674
4675 private function notifyQuestionDeleted()
4676 {
4677 foreach ($this->getQuestionChangeListeners() as $listener) {
4678 $listener->notifyQuestionDeleted($this);
4679 }
4680 }
4681
4686 {
4687 require_once 'Services/Html/classes/class.ilHtmlPurifierFactory.php';
4688 return ilHtmlPurifierFactory::_getInstanceByType('qpl_usersolution');
4689 }
4690
4692 {
4693 return ilHtmlPurifierFactory::_getInstanceByType('qpl_usersolution');
4694 }
4695
4696 protected function buildQuestionDataQuery()
4697 {
4698 return "
4699 SELECT qpl_questions.*,
4700 {$this->getAdditionalTableName()}.*
4701 FROM qpl_questions
4702 LEFT JOIN {$this->getAdditionalTableName()}
4703 ON {$this->getAdditionalTableName()}.question_fi = qpl_questions.question_id
4704 WHERE qpl_questions.question_id = %s
4705 ";
4706 }
4707
4709 {
4710 $this->lastChange = $lastChange;
4711 }
4712
4713 public function getLastChange()
4714 {
4715 return $this->lastChange;
4716 }
4717
4728 protected function getCurrentSolutionResultSet($active_id, $pass, $authorized = true)
4729 {
4730 global $DIC;
4731 $ilDB = $DIC['ilDB'];
4732
4733 if ($this->getStep() !== null) {
4734 $query = "
4735 SELECT *
4736 FROM tst_solutions
4737 WHERE active_fi = %s
4738 AND question_fi = %s
4739 AND pass = %s
4740 AND step = %s
4741 AND authorized = %s
4742 ";
4743
4744 return $ilDB->queryF(
4745 $query,
4746 array('integer', 'integer', 'integer', 'integer', 'integer'),
4747 array($active_id, $this->getId(), $pass, $this->getStep(), (int) $authorized)
4748 );
4749 } else {
4750 $query = "
4751 SELECT *
4752 FROM tst_solutions
4753 WHERE active_fi = %s
4754 AND question_fi = %s
4755 AND pass = %s
4756 AND authorized = %s
4757 ";
4758
4759 return $ilDB->queryF(
4760 $query,
4761 array('integer', 'integer', 'integer', 'integer'),
4762 array($active_id, $this->getId(), $pass, (int) $authorized)
4763 );
4764 }
4765 }
4766
4773 protected function removeSolutionRecordById($solutionId)
4774 {
4775 global $DIC;
4776 $ilDB = $DIC['ilDB'];
4777
4778 return $ilDB->manipulateF(
4779 "DELETE FROM tst_solutions WHERE solution_id = %s",
4780 array('integer'),
4781 array($solutionId)
4782 );
4783 }
4784
4785 // hey: prevPassSolutions - selected file reuse, copy solution records
4792 protected function getSolutionRecordById($solutionId)
4793 {
4794 global $DIC; /* @var ILIAS\DI\Container $DIC */
4795 $ilDB = $DIC['ilDB'];
4796
4797 $res = $ilDB->queryF(
4798 "SELECT * FROM tst_solutions WHERE solution_id = %s",
4799 array('integer'),
4800 array($solutionId)
4801 );
4802
4803 while ($row = $ilDB->fetchAssoc($res)) {
4804 return $row;
4805 }
4806 }
4807 // hey.
4808
4817 public function removeIntermediateSolution($active_id, $pass)
4818 {
4819 $this->getProcessLocker()->executeUserSolutionUpdateLockOperation(function () use ($active_id, $pass) {
4820 $this->removeCurrentSolution($active_id, $pass, false);
4821 });
4822 }
4823
4832 public function removeCurrentSolution($active_id, $pass, $authorized = true)
4833 {
4834 global $DIC;
4835 $ilDB = $DIC['ilDB'];
4836
4837 if ($this->getStep() !== null) {
4838 $query = "
4839 DELETE FROM tst_solutions
4840 WHERE active_fi = %s
4841 AND question_fi = %s
4842 AND pass = %s
4843 AND step = %s
4844 AND authorized = %s
4845 ";
4846
4847 return $ilDB->manipulateF(
4848 $query,
4849 array('integer', 'integer', 'integer', 'integer', 'integer'),
4850 array($active_id, $this->getId(), $pass, $this->getStep(), (int) $authorized)
4851 );
4852 } else {
4853 $query = "
4854 DELETE FROM tst_solutions
4855 WHERE active_fi = %s
4856 AND question_fi = %s
4857 AND pass = %s
4858 AND authorized = %s
4859 ";
4860
4861 return $ilDB->manipulateF(
4862 $query,
4863 array('integer', 'integer', 'integer', 'integer'),
4864 array($active_id, $this->getId(), $pass, (int) $authorized)
4865 );
4866 }
4867 }
4868
4869 // fau: testNav - add timestamp as parameter to saveCurrentSolution
4881 public function saveCurrentSolution($active_id, $pass, $value1, $value2, $authorized = true, $tstamp = null)
4882 {
4883 global $DIC;
4884 $ilDB = $DIC['ilDB'];
4885
4886 $next_id = $ilDB->nextId("tst_solutions");
4887
4888 $fieldData = array(
4889 "solution_id" => array("integer", $next_id),
4890 "active_fi" => array("integer", $active_id),
4891 "question_fi" => array("integer", $this->getId()),
4892 "value1" => array("clob", $value1),
4893 "value2" => array("clob", $value2),
4894 "pass" => array("integer", $pass),
4895 "tstamp" => array("integer", isset($tstamp) ? $tstamp : time()),
4896 'authorized' => array('integer', (int) $authorized)
4897 );
4898
4899 if ($this->getStep() !== null) {
4900 $fieldData['step'] = array("integer", $this->getStep());
4901 }
4902
4903 return $ilDB->insert("tst_solutions", $fieldData);
4904 }
4905 // fau.
4906
4917 public function updateCurrentSolution($solutionId, $value1, $value2, $authorized = true)
4918 {
4919 global $DIC;
4920 $ilDB = $DIC['ilDB'];
4921
4922 $fieldData = array(
4923 "value1" => array("clob", $value1),
4924 "value2" => array("clob", $value2),
4925 "tstamp" => array("integer", time()),
4926 'authorized' => array('integer', (int) $authorized)
4927 );
4928
4929 if ($this->getStep() !== null) {
4930 $fieldData['step'] = array("integer", $this->getStep());
4931 }
4932
4933 return $ilDB->update("tst_solutions", $fieldData, array(
4934 'solution_id' => array('integer', $solutionId)
4935 ));
4936 }
4937
4938 // fau: testNav - added parameter to keep the timestamp (default: false)
4939 public function updateCurrentSolutionsAuthorization($activeId, $pass, $authorized, $keepTime = false)
4940 {
4941 global $DIC;
4942 $ilDB = $DIC['ilDB'];
4943
4944 $fieldData = array(
4945 'authorized' => array('integer', (int) $authorized)
4946 );
4947
4948 if (!$keepTime) {
4949 $fieldData['tstamp'] = array('integer', time());
4950 }
4951
4952 $whereData = array(
4953 'question_fi' => array('integer', $this->getId()),
4954 'active_fi' => array('integer', $activeId),
4955 'pass' => array('integer', $pass)
4956 );
4957
4958 if ($this->getStep() !== null) {
4959 $whereData['step'] = array("integer", $this->getStep());
4960 }
4961
4962 return $ilDB->update('tst_solutions', $fieldData, $whereData);
4963 }
4964 // fau.
4965
4966 // hey: prevPassSolutions - motivation slowly decreases on imagemap
4968 protected static function getKeyValuesImplosionSeparator()
4969 {
4971 }
4972 public static function implodeKeyValues($keyValues)
4973 {
4974 return implode(self::getKeyValuesImplosionSeparator(), $keyValues);
4975 }
4976 public static function explodeKeyValues($keyValues)
4977 {
4978 return explode(self::getKeyValuesImplosionSeparator(), $keyValues);
4979 }
4980
4981 protected function deleteDummySolutionRecord($activeId, $passIndex)
4982 {
4983 foreach ($this->getSolutionValues($activeId, $passIndex, false) as $solutionRec) {
4984 if (0 == strlen($solutionRec['value1']) && 0 == strlen($solutionRec['value2'])) {
4985 $this->removeSolutionRecordById($solutionRec['solution_id']);
4986 }
4987 }
4988 }
4989
4990 protected function isDummySolutionRecord($solutionRecord)
4991 {
4992 return !strlen($solutionRecord['value1']) && !strlen($solutionRecord['value2']);
4993 }
4994
4995 protected function deleteSolutionRecordByValues($activeId, $passIndex, $authorized, $matchValues)
4996 {
4997 global $DIC; /* @var ILIAS\DI\Container $DIC */
4998 $ilDB = $DIC['ilDB'];
4999
5000 $types = array("integer", "integer", "integer", "integer");
5001 $values = array($activeId, $this->getId(), $passIndex, (int) $authorized);
5002 $valuesCondition = array();
5003
5004 foreach ($matchValues as $valueField => $value) {
5005 switch ($valueField) {
5006 case 'value1':
5007 case 'value2':
5008 $valuesCondition[] = "{$valueField} = %s";
5009 $types[] = 'text';
5010 $values[] = $value;
5011 break;
5012
5013 default:
5014 require_once 'Modules/TestQuestionPool/exceptions/class.ilTestQuestionPoolException.php';
5015 throw new ilTestQuestionPoolException('invalid value field given: ' . $valueField);
5016 }
5017 }
5018
5019 $valuesCondition = implode(' AND ', $valuesCondition);
5020
5021 $query = "
5022 DELETE FROM tst_solutions
5023 WHERE active_fi = %s
5024 AND question_fi = %s
5025 AND pass = %s
5026 AND authorized = %s
5027 AND $valuesCondition
5028 ";
5029
5030 if ($this->getStep() !== null) {
5031 $query .= " AND step = %s ";
5032 $types[] = 'integer';
5033 $values[] = $this->getStep();
5034 }
5035
5036 $ilDB->manipulateF($query, $types, $values);
5037 }
5038
5039 protected function duplicateIntermediateSolutionAuthorized($activeId, $passIndex)
5040 {
5041 foreach ($this->getSolutionValues($activeId, $passIndex, false) as $rec) {
5042 $this->saveCurrentSolution($activeId, $passIndex, $rec['value1'], $rec['value2'], true, $rec['tstamp']);
5043 }
5044 }
5045
5046 protected function forceExistingIntermediateSolution($activeId, $passIndex, $considerDummyRecordCreation)
5047 {
5048 $intermediateSolution = $this->getSolutionValues($activeId, $passIndex, false);
5049
5050 if (!count($intermediateSolution)) {
5051 // make the authorized solution intermediate (keeping timestamps)
5052 // this keeps the solution_ids in synch with eventually selected in $_POST['deletefiles']
5053 $this->updateCurrentSolutionsAuthorization($activeId, $passIndex, false, true);
5054
5055 // create a backup as authorized solution again (keeping timestamps)
5056 $this->duplicateIntermediateSolutionAuthorized($activeId, $passIndex);
5057
5058 if ($considerDummyRecordCreation) {
5059 // create an additional dummy record to indicate the existence of an intermediate solution
5060 // even if all entries are deleted from the intermediate solution later
5061 $this->saveCurrentSolution($activeId, $passIndex, null, null, false, null);
5062 }
5063 }
5064 }
5065 // hey.
5066
5070 public static function setResultGateway($resultGateway)
5071 {
5072 self::$resultGateway = $resultGateway;
5073 }
5074
5078 public static function getResultGateway()
5079 {
5080 return self::$resultGateway;
5081 }
5082
5086 public function setStep($step)
5087 {
5088 $this->step = $step;
5089 }
5090
5094 public function getStep()
5095 {
5096 return $this->step;
5097 }
5098
5104 public static function sumTimesInISO8601FormatH_i_s_Extended($time1, $time2)
5105 {
5108 return gmdate('H:i:s', $time);
5109 }
5110
5116 {
5117 $sec = 0;
5118 $time_array = explode(':', $time);
5119 if (sizeof($time_array) == 3) {
5120 $sec += $time_array[0] * 3600;
5121 $sec += $time_array[1] * 60;
5122 $sec += $time_array[2];
5123 }
5124 return $sec;
5125 }
5126
5127 public function toJSON()
5128 {
5129 return json_encode(array());
5130 }
5131
5132 abstract public function duplicate($for_test = true, $title = "", $author = "", $owner = "", $testObjId = null);
5133
5134 // hey: prevPassSolutions - check for authorized solution
5135 public function intermediateSolutionExists($active_id, $pass)
5136 {
5137 $solutionAvailability = $this->lookupForExistingSolutions($active_id, $pass);
5138 return (bool) $solutionAvailability['intermediate'];
5139 }
5140 public function authorizedSolutionExists($active_id, $pass)
5141 {
5142 $solutionAvailability = $this->lookupForExistingSolutions($active_id, $pass);
5143 return (bool) $solutionAvailability['authorized'];
5144 }
5146 {
5147 $solutionAvailability = $this->lookupForExistingSolutions($active_id, $pass);
5148 return (bool) $solutionAvailability['authorized'] || (bool) $solutionAvailability['intermediate'];
5149 }
5150 // hey.
5151
5157 protected function lookupMaxStep($active_id, $pass)
5158 {
5160 global $DIC;
5161 $ilDB = $DIC['ilDB'];
5162
5163 $res = $ilDB->queryF(
5164 "SELECT MAX(step) max_step FROM tst_solutions WHERE active_fi = %s AND pass = %s AND question_fi = %s",
5165 array("integer", "integer", "integer"),
5166 array($active_id, $pass, $this->getId())
5167 );
5168
5169 $row = $ilDB->fetchAssoc($res);
5170
5171 $maxStep = $row['max_step'];
5172
5173 return $maxStep;
5174 }
5175
5176 // fau: testNav - new function lookupForExistingSolutions
5183 public function lookupForExistingSolutions($activeId, $pass)
5184 {
5186 global $DIC;
5187 $ilDB = $DIC['ilDB'];
5188
5189 $return = array(
5190 'authorized' => false,
5191 'intermediate' => false
5192 );
5193
5194 $query = "
5195 SELECT authorized, COUNT(*) cnt
5196 FROM tst_solutions
5197 WHERE active_fi = %s
5198 AND question_fi = %s
5199 AND pass = %s
5200 ";
5201
5202 if ($this->getStep() !== null) {
5203 $query .= " AND step = " . $ilDB->quote((int) $this->getStep(), 'integer') . " ";
5204 }
5205
5206 $query .= "
5207 GROUP BY authorized
5208 ";
5209
5210 $result = $ilDB->queryF($query, array('integer', 'integer', 'integer'), array($activeId, $this->getId(), $pass));
5211
5212 while ($row = $ilDB->fetchAssoc($result)) {
5213 if ($row['authorized']) {
5214 $return['authorized'] = $row['cnt'] > 0;
5215 } else {
5216 $return['intermediate'] = $row['cnt'] > 0;
5217 }
5218 }
5219 return $return;
5220 }
5221 // fau.
5222
5223 public function isAddableAnswerOptionValue($qIndex, $answerOptionValue)
5224 {
5225 return false;
5226 }
5227
5228 public function addAnswerOptionValue($qIndex, $answerOptionValue, $points)
5229 {
5230 }
5231
5233 {
5234 global $DIC; /* @var ILIAS\DI\Container $DIC */
5235
5236 $query = "DELETE FROM tst_solutions WHERE question_fi = %s";
5237
5238 $DIC->database()->manipulateF($query, array('integer'), array($this->getId()));
5239 }
5240
5241 public function removeExistingSolutions($activeId, $pass)
5242 {
5243 global $DIC;
5244 $ilDB = $DIC['ilDB'];
5245
5246 $query = "
5247 DELETE FROM tst_solutions
5248 WHERE active_fi = %s
5249 AND question_fi = %s
5250 AND pass = %s
5251 ";
5252
5253 if ($this->getStep() !== null) {
5254 $query .= " AND step = " . $ilDB->quote((int) $this->getStep(), 'integer') . " ";
5255 }
5256
5257 return $ilDB->manipulateF(
5258 $query,
5259 array('integer', 'integer', 'integer'),
5260 array($activeId, $this->getId(), $pass)
5261 );
5262 }
5263
5264 public function resetUsersAnswer($activeId, $pass)
5265 {
5266 $this->removeExistingSolutions($activeId, $pass);
5267 $this->removeResultRecord($activeId, $pass);
5268
5269 $this->log($activeId, "log_user_solution_willingly_deleted");
5270
5271 self::_updateTestPassResults(
5272 $activeId,
5273 $pass,
5275 $this->getProcessLocker(),
5276 $this->getTestId()
5277 );
5278 }
5279
5280 public function removeResultRecord($activeId, $pass)
5281 {
5282 global $DIC;
5283 $ilDB = $DIC['ilDB'];
5284
5285 $query = "
5286 DELETE FROM tst_test_result
5287 WHERE active_fi = %s
5288 AND question_fi = %s
5289 AND pass = %s
5290 ";
5291
5292 if ($this->getStep() !== null) {
5293 $query .= " AND step = " . $ilDB->quote((int) $this->getStep(), 'integer') . " ";
5294 }
5295
5296 return $ilDB->manipulateF(
5297 $query,
5298 array('integer', 'integer', 'integer'),
5299 array($activeId, $this->getId(), $pass)
5300 );
5301 }
5302
5303 public static function missingResultRecordExists($activeId, $pass, $questionIds)
5304 {
5305 global $DIC;
5306 $ilDB = $DIC['ilDB'];
5307
5308 $IN_questionIds = $ilDB->in('question_fi', $questionIds, false, 'integer');
5309
5310 $query = "
5311 SELECT COUNT(*) cnt
5312 FROM tst_test_result
5313 WHERE active_fi = %s
5314 AND pass = %s
5315 AND $IN_questionIds
5316 ";
5317
5318 $row = $ilDB->fetchAssoc($ilDB->queryF(
5319 $query,
5320 array('integer', 'integer'),
5321 array($activeId, $pass)
5322 ));
5323
5324 return $row['cnt'] < count($questionIds);
5325 }
5326
5327 public static function getQuestionsMissingResultRecord($activeId, $pass, $questionIds)
5328 {
5329 global $DIC;
5330 $ilDB = $DIC['ilDB'];
5331
5332 $IN_questionIds = $ilDB->in('question_fi', $questionIds, false, 'integer');
5333
5334 $query = "
5335 SELECT question_fi
5336 FROM tst_test_result
5337 WHERE active_fi = %s
5338 AND pass = %s
5339 AND $IN_questionIds
5340 ";
5341
5342 $res = $ilDB->queryF(
5343 $query,
5344 array('integer', 'integer'),
5345 array($activeId, $pass)
5346 );
5347
5348 $questionsHavingResultRecord = array();
5349
5350 while ($row = $ilDB->fetchAssoc($res)) {
5351 $questionsHavingResultRecord[] = $row['question_fi'];
5352 }
5353
5354 $questionsMissingResultRecordt = array_diff(
5355 $questionIds,
5356 $questionsHavingResultRecord
5357 );
5358
5359 return $questionsMissingResultRecordt;
5360 }
5361
5362 public static function lookupResultRecordExist($activeId, $questionId, $pass)
5363 {
5364 global $DIC;
5365 $ilDB = $DIC['ilDB'];
5366
5367 $query = "
5368 SELECT COUNT(*) cnt
5369 FROM tst_test_result
5370 WHERE active_fi = %s
5371 AND question_fi = %s
5372 AND pass = %s
5373 ";
5374
5375 $row = $ilDB->fetchAssoc($ilDB->queryF($query, array('integer', 'integer', 'integer'), array($activeId, $questionId, $pass)));
5376
5377 return $row['cnt'] > 0;
5378 }
5379
5384 public function fetchValuePairsFromIndexedValues(array $indexedValues)
5385 {
5386 $valuePairs = array();
5387
5388 foreach ($indexedValues as $value1 => $value2) {
5389 $valuePairs[] = array('value1' => $value1, 'value2' => $value2);
5390 }
5391
5392 return $valuePairs;
5393 }
5394
5399 public function fetchIndexedValuesFromValuePairs(array $valuePairs)
5400 {
5401 $indexedValues = array();
5402
5403 foreach ($valuePairs as $valuePair) {
5404 $indexedValues[ $valuePair['value1'] ] = $valuePair['value2'];
5405 }
5406
5407 return $indexedValues;
5408 }
5409
5414 {
5416 }
5417
5422 {
5423 $this->obligationsToBeConsidered = $obligationsToBeConsidered;
5424 }
5425
5426 public function updateTimestamp()
5427 {
5428 global $DIC;
5429 $ilDB = $DIC['ilDB'];
5430
5431 $ilDB->manipulateF(
5432 "UPDATE qpl_questions SET tstamp = %s WHERE question_id = %s",
5433 array('integer', 'integer'),
5434 array(time(), $this->getId())
5435 );
5436 }
5437
5438 // fau: testNav - new function getTestQuestionConfig()
5439 // hey: prevPassSolutions - get caching independent from configuration (config once)
5440 // renamed: getTestPresentationConfig() -> does the caching
5441 // completed: extracted instance building
5442 // avoids configuring cached instances on every access
5443 // allows a stable reconfigure of the instance from outside
5448
5454 {
5455 if ($this->testQuestionConfigInstance === null) {
5456 $this->testQuestionConfigInstance = $this->buildTestPresentationConfig();
5457 }
5458
5460 }
5461
5470 protected function buildTestPresentationConfig()
5471 {
5472 include_once('Modules/TestQuestionPool/classes/class.ilTestQuestionConfig.php');
5473 return new ilTestQuestionConfig();
5474 }
5475 // hey.
5476// fau.
5477
5478 public function savePartial()
5479 {
5480 return false;
5481 }
5482}
$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
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)
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.
static _getMaximumPoints($question_id)
Returns the maximum points, a learner can reach answering the question.
saveQuestionDataToDb($original_id="")
fromXML(&$item, &$questionpool_id, &$tst_id, &$tst_object, &$question_counter, &$import_mapping)
Receives parameters from a QTI parser and creates a valid ILIAS question object.
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.
const ADDITIONAL_CONTENT_EDITING_MODE_DEFAULT
constant for additional content editing mode "default"
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.
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.
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)
const ADDITIONAL_CONTENT_EDITING_MODE_PAGE_OBJECT
constant for additional content editing mode "pageobject"
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)
__construct( $title="", $comment="", $author="", $owner=-1, $question="")
assQuestion constructor
$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 _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
$target_id
Definition: goto.php:49
global $ilCtrl
Definition: ilias.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
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
$mobs
$ilUser
Definition: imgupload.php:18
$message
Definition: xapiexit.php:14
$DIC
Definition: xapitoken.php:46
$objId
Definition: xapitoken.php:41