ILIAS  release_5-4 Revision v5.4.26-12-gabc799a52e6
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
273 'image/jpeg' => array('jpg', 'jpeg'), 'image/png' => array('png'), 'image/gif' => array('gif')
274 );
275
286 public function __construct(
287 $title = "",
288 $comment = "",
289 $author = "",
290 $owner = -1,
291 $question = ""
292 ) {
293 global $DIC;
294 $ilias = $DIC['ilias'];
295 $lng = $DIC['lng'];
296 $tpl = $DIC['tpl'];
297 $ilDB = $DIC['ilDB'];
298
299 $this->ilias = $ilias;
300 $this->lng = $lng;
301 $this->tpl = $tpl;
302 $this->db = $ilDB;
303
304 $this->original_id = null;
305 $this->title = $title;
306 $this->comment = $comment;
307 $this->page = null;
308 $this->author = $author;
309 $this->setQuestion($question);
310 if (!$this->author) {
311 $this->author = $this->ilias->account->fullname;
312 }
313 $this->owner = $owner;
314 if ($this->owner <= 0) {
315 $this->owner = $this->ilias->account->id;
316 }
317 $this->id = -1;
318 $this->test_id = -1;
319 $this->suggested_solutions = array();
320 $this->shuffle = 1;
321 $this->nr_of_tries = 0;
322 $this->setEstimatedWorkingTime(0, 1, 0);
323 $this->arrData = array();
324 $this->setExternalId('');
325
326 $this->questionActionCmd = 'handleQuestionAction';
327
328 $this->lastChange = null;
329
330 require_once 'Services/Randomization/classes/class.ilArrayElementOrderKeeper.php';
331 $this->shuffler = new ilArrayElementOrderKeeper();
332 }
333
334 protected static $forcePassResultsUpdateEnabled = false;
335
337 {
338 self::$forcePassResultsUpdateEnabled = $forcePassResultsUpdateEnabled;
339 }
340
341 public static function isForcePassResultUpdateEnabled()
342 {
344 }
345
346 public static function isAllowedImageMimeType($mimeType)
347 {
348 return (bool) count(self::getAllowedFileExtensionsForMimeType($mimeType));
349 }
350
351 public static function fetchMimeTypeIdentifier($contentTypeString)
352 {
353 return current(explode(';', $contentTypeString));
354 }
355
356 public static function getAllowedFileExtensionsForMimeType($mimeType)
357 {
358 foreach (self::$allowedFileExtensionsByMimeType as $allowedMimeType => $extensions) {
359 $rexCharsets = implode('|', self::$allowedCharsetsByMimeType[$allowedMimeType]);
360 $rexMimeType = preg_quote($allowedMimeType, '/');
361
362 $rex = '/^' . $rexMimeType . '(;(\s)*charset=(' . $rexCharsets . '))*$/';
363
364 if (!preg_match($rex, $mimeType)) {
365 continue;
366 }
367
368 return $extensions;
369 }
370
371 return array();
372 }
373
374 public static function isAllowedImageFileExtension($mimeType, $fileExtension)
375 {
376 return in_array(
377 strtolower($fileExtension),
378 self::getAllowedFileExtensionsForMimeType($mimeType)
379 );
380 }
381
382 // hey: prevPassSolutions - question action actracted (heavy use in fileupload refactoring)
383
387 protected function getQuestionAction()
388 {
389 if (!isset($_POST['cmd']) || !isset($_POST['cmd'][$this->questionActionCmd])) {
390 return '';
391 }
392
393 if (!is_array($_POST['cmd'][$this->questionActionCmd]) || !count($_POST['cmd'][$this->questionActionCmd])) {
394 return '';
395 }
396
397 return key($_POST['cmd'][$this->questionActionCmd]);
398 }
399
404 protected function isNonEmptyItemListPostSubmission($postSubmissionFieldname)
405 {
406 if (!isset($_POST[$postSubmissionFieldname])) {
407 return false;
408 }
409
410 if (!is_array($_POST[$postSubmissionFieldname])) {
411 return false;
412 }
413
414 if (!count($_POST[$postSubmissionFieldname])) {
415 return false;
416 }
417
418 return true;
419 }
420
426 protected function ensureCurrentTestPass($active_id, $pass)
427 {
428 if (is_integer($pass) && $pass >= 0) {
429 return $pass;
430 }
431
432 return $this->lookupCurrentTestPass($active_id, $pass);
433 }
434
440 protected function lookupCurrentTestPass($active_id, $pass)
441 {
442 require_once 'Modules/Test/classes/class.ilObjTest.php';
443 return ilObjTest::_getPass($active_id);
444 }
445
450 protected function lookupTestId($active_id)
451 {
452 global $DIC; /* @var ILIAS\DI\Container $DIC */
453 $ilDB = $DIC['ilDB'];
454
455 $result = $ilDB->queryF(
456 "SELECT test_fi FROM tst_active WHERE active_id = %s",
457 array('integer'),
458 array($active_id)
459 );
460
461 while ($row = $ilDB->fetchAssoc($result)) {
462 return $row["test_fi"];
463 }
464
465 return null;
466 }
467 // hey.
468
473 protected function log($active_id, $langVar)
474 {
476 $message = $this->lng->txtlng('assessment', $langVar, ilObjAssessmentFolder::_getLogLanguage());
477 assQuestion::logAction($message, $active_id, $this->getId());
478 }
479 }
480
485 {
486 $extensions = array();
487
488 foreach (self::$allowedImageMaterialFileExtensionsByMimeType as $mimeType => $mimeExtensions) {
489 $extensions = array_merge($extensions, $mimeExtensions);
490 }
491 return array_unique($extensions);
492 }
493
497 public function getShuffler()
498 {
499 return $this->shuffler;
500 }
501
506 {
507 $this->shuffler = $shuffler;
508 }
509
514 {
515 $this->processLocker = $processLocker;
516 }
517
521 public function getProcessLocker()
522 {
524 }
525
537 public function fromXML(&$item, &$questionpool_id, &$tst_id, &$tst_object, &$question_counter, &$import_mapping)
538 {
539 include_once "./Modules/TestQuestionPool/classes/import/qti12/class." . $this->getQuestionType() . "Import.php";
540 $classname = $this->getQuestionType() . "Import";
541 $import = new $classname($this);
542 $import->fromXML($item, $questionpool_id, $tst_id, $tst_object, $question_counter, $import_mapping);
543 }
544
551 public function toXML($a_include_header = true, $a_include_binary = true, $a_shuffle = false, $test_output = false, $force_image_references = false)
552 {
553 include_once "./Modules/TestQuestionPool/classes/export/qti12/class." . $this->getQuestionType() . "Export.php";
554 $classname = $this->getQuestionType() . "Export";
555 $export = new $classname($this);
556 return $export->toXML($a_include_header, $a_include_binary, $a_shuffle, $test_output, $force_image_references);
557 }
558
565 public function isComplete()
566 {
567 return false;
568 }
569
577 public function questionTitleExists($questionpool_id, $title)
578 {
579 global $DIC;
580 $ilDB = $DIC['ilDB'];
581
582 $result = $ilDB->queryF(
583 "SELECT * FROM qpl_questions WHERE obj_fi = %s AND title = %s",
584 array('integer','text'),
585 array($questionpool_id, $title)
586 );
587 return ($result->numRows() > 0) ? true : false;
588 }
589
597 public function setTitle($title = "")
598 {
599 $this->title = $title;
600 }
601
609 public function setId($id = -1)
610 {
611 $this->id = $id;
612 }
613
621 public function setTestId($id = -1)
622 {
623 $this->test_id = $id;
624 }
625
633 public function setComment($comment = "")
634 {
635 $this->comment = $comment;
636 }
637
646 {
647 $this->outputType = $outputType;
648 }
649
650
658 public function setShuffle($shuffle = true)
659 {
660 if ($shuffle) {
661 $this->shuffle = 1;
662 } else {
663 $this->shuffle = 0;
664 }
665 }
666
677 public function setEstimatedWorkingTime($hour = 0, $min = 0, $sec = 0)
678 {
679 $this->est_working_time = array("h" => (int) $hour, "m" => (int) $min, "s" => (int) $sec);
680 }
681
688 public function setEstimatedWorkingTimeFromDurationString($durationString)
689 {
690 $this->est_working_time = array(
691 'h' => (int) substr($durationString, 0, 2),
692 'm' => (int) substr($durationString, 3, 2),
693 's' => (int) substr($durationString, 6, 2)
694 );
695 }
696
704 public function keyInArray($searchkey, $array)
705 {
706 if ($searchkey) {
707 foreach ($array as $key => $value) {
708 if (strcmp($key, $searchkey) == 0) {
709 return true;
710 }
711 }
712 }
713 return false;
714 }
715
723 public function setAuthor($author = "")
724 {
725 if (!$author) {
726 $author = $this->ilias->account->fullname;
727 }
728 $this->author = $author;
729 }
730
738 public function setOwner($owner = "")
739 {
740 $this->owner = $owner;
741 }
742
750 public function getTitle()
751 {
752 return $this->title;
753 }
754
761 {
762 require_once 'Services/Utilities/classes/class.ilUtil.php';
763 return ilUtil::getASCIIFilename($this->getTitle());
764 }
765
773 public function getId()
774 {
775 return $this->id;
776 }
777
785 public function getShuffle()
786 {
787 return $this->shuffle;
788 }
789
797 public function getTestId()
798 {
799 return $this->test_id;
800 }
801
809 public function getComment()
810 {
811 return $this->comment;
812 }
813
821 public function getOutputType()
822 {
823 return $this->outputType;
824 }
825
832 public function supportsJavascriptOutput()
833 {
834 return false;
835 }
836
837 public function supportsNonJsOutput()
838 {
839 return true;
840 }
841
842 public function requiresJsSwitch()
843 {
844 return $this->supportsJavascriptOutput() && $this->supportsNonJsOutput();
845 }
846
854 public function getEstimatedWorkingTime()
855 {
856 if (!$this->est_working_time) {
857 $this->est_working_time = array("h" => 0, "m" => 0, "s" => 0);
858 }
860 }
861
869 public function getAuthor()
870 {
871 return $this->author;
872 }
873
881 public function getOwner()
882 {
883 return $this->owner;
884 }
885
893 public function getObjId()
894 {
895 return $this->obj_id;
896 }
897
905 public function setObjId($obj_id = 0)
906 {
907 $this->obj_id = $obj_id;
908 }
909
914 {
915 $this->external_id = $external_id;
916 }
917
921 public function getExternalId()
922 {
923 if (!strlen($this->external_id)) {
924 if ($this->getId() > 0) {
925 return 'il_' . IL_INST_ID . '_qst_' . $this->getId();
926 } else {
927 return uniqid('', true);
928 }
929 } else {
930 return $this->external_id;
931 }
932 }
933
940 public static function _getMaximumPoints($question_id)
941 {
942 global $DIC;
943 $ilDB = $DIC['ilDB'];
944
945 $points = 0;
946 $result = $ilDB->queryF(
947 "SELECT points FROM qpl_questions WHERE question_id = %s",
948 array('integer'),
949 array($question_id)
950 );
951 if ($result->numRows() == 1) {
952 $row = $ilDB->fetchAssoc($result);
953 $points = $row["points"];
954 }
955 return $points;
956 }
957
964 public static function _getQuestionInfo($question_id)
965 {
966 global $DIC;
967 $ilDB = $DIC['ilDB'];
968
969 $result = $ilDB->queryF(
970 "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",
971 array('integer'),
972 array($question_id)
973 );
974 if ($result->numRows()) {
975 return $ilDB->fetchAssoc($result);
976 } else {
977 return array();
978 }
979 }
980
987 public static function _getSuggestedSolutionCount($question_id)
988 {
989 global $DIC;
990 $ilDB = $DIC['ilDB'];
991
992 $result = $ilDB->queryF(
993 "SELECT suggested_solution_id FROM qpl_sol_sug WHERE question_fi = %s",
994 array('integer'),
995 array($question_id)
996 );
997 return $result->numRows();
998 }
999
1006 public static function _getSuggestedSolutionOutput($question_id)
1007 {
1009 if (!is_object($question)) {
1010 return "";
1011 }
1012 return $question->getSuggestedSolutionOutput();
1013 }
1014
1016 {
1017 $output = array();
1018 foreach ($this->suggested_solutions as $solution) {
1019 switch ($solution["type"]) {
1020 case "lm":
1021 case "st":
1022 case "pg":
1023 case "git":
1024 array_push($output, '<a href="' . assQuestion::_getInternalLinkHref($solution["internal_link"]) . '">' . $this->lng->txt("solution_hint") . '</a>');
1025 break;
1026 case "file":
1027 $possible_texts = array_values(array_filter(array(
1028 ilUtil::prepareFormOutput($solution['value']['filename']),
1029 ilUtil::prepareFormOutput($solution['value']['name']),
1030 $this->lng->txt('tst_show_solution_suggested')
1031 )));
1032
1033 require_once 'Services/WebAccessChecker/classes/class.ilWACSignedPath.php';
1035 array_push($output, '<a href="' . ilWACSignedPath::signFile($this->getSuggestedSolutionPathWeb() . $solution["value"]["name"]) . '">' . $possible_texts[0] . '</a>');
1036 break;
1037 case "text":
1038 $solutionValue = $solution["value"];
1039 $solutionValue = $this->fixSvgToPng($solutionValue);
1040 $solutionValue = $this->fixUnavailableSkinImageSources($solutionValue);
1041 $output[] = $this->prepareTextareaOutput($solutionValue, true);
1042 break;
1043 }
1044 }
1045 return join("<br />", $output);
1046 }
1047
1056 public function &_getSuggestedSolution($question_id, $subquestion_index = 0)
1057 {
1058 global $DIC;
1059 $ilDB = $DIC['ilDB'];
1060
1061 $result = $ilDB->queryF(
1062 "SELECT * FROM qpl_sol_sug WHERE question_fi = %s AND subquestion_index = %s",
1063 array('integer','integer'),
1064 array($question_id, $subquestion_index)
1065 );
1066 if ($result->numRows() == 1) {
1067 $row = $ilDB->fetchAssoc($result);
1068 return array(
1069 "internal_link" => $row["internal_link"],
1070 "import_id" => $row["import_id"]
1071 );
1072 } else {
1073 return array();
1074 }
1075 }
1076
1082 public function getSuggestedSolutions()
1083 {
1085 }
1086
1094 public static function _getReachedPoints($active_id, $question_id, $pass = null)
1095 {
1096 global $DIC;
1097 $ilDB = $DIC['ilDB'];
1098
1099 $points = 0;
1100 if (is_null($pass)) {
1101 include_once "./Modules/TestQuestionPool/classes/class.assQuestion.php";
1102 $pass = assQuestion::_getSolutionMaxPass($question_id, $active_id);
1103 }
1104 $result = $ilDB->queryF(
1105 "SELECT * FROM tst_test_result WHERE active_fi = %s AND question_fi = %s AND pass = %s",
1106 array('integer','integer','integer'),
1107 array($active_id, $question_id, $pass)
1108 );
1109 if ($result->numRows() == 1) {
1110 $row = $ilDB->fetchAssoc($result);
1111 $points = $row["points"];
1112 }
1113 return $points;
1114 }
1115
1124 public function getReachedPoints($active_id, $pass = null)
1125 {
1126 return round(self::_getReachedPoints($active_id, $this->getId(), $pass), 2);
1127 }
1128
1135 public function getMaximumPoints()
1136 {
1137 return $this->points;
1138 }
1139
1151 final public function getAdjustedReachedPoints($active_id, $pass = null, $authorizedSolution = true)
1152 {
1153 if (is_null($pass)) {
1154 include_once "./Modules/Test/classes/class.ilObjTest.php";
1155 $pass = ilObjTest::_getPass($active_id);
1156 }
1157
1158 // determine reached points for submitted solution
1159 $reached_points = $this->calculateReachedPoints($active_id, $pass, $authorizedSolution);
1160
1161
1162
1163 // deduct points for requested hints from reached points
1164 require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionHintTracking.php';
1165 $hintTracking = new ilAssQuestionHintTracking($this->getId(), $active_id, $pass);
1166 $requestsStatisticData = $hintTracking->getRequestStatisticDataByQuestionAndTestpass();
1167 $reached_points = $reached_points - $requestsStatisticData->getRequestsPoints();
1168
1169 // adjust reached points regarding to tests scoring options
1170 $reached_points = $this->adjustReachedPointsByScoringOptions($reached_points, $active_id, $pass);
1171
1172 return $reached_points;
1173 }
1174
1184 final public function calculateResultsFromSolution($active_id, $pass = null, $obligationsEnabled = false)
1185 {
1186 global $DIC;
1187 $ilDB = $DIC['ilDB'];
1188 $ilUser = $DIC['ilUser'];
1189
1190 if (is_null($pass)) {
1191 include_once "./Modules/Test/classes/class.ilObjTest.php";
1192 $pass = ilObjTest::_getPass($active_id);
1193 }
1194
1195 // determine reached points for submitted solution
1196 $reached_points = $this->calculateReachedPoints($active_id, $pass);
1197
1198 // deduct points for requested hints from reached points
1199 require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionHintTracking.php';
1200 $questionHintTracking = new ilAssQuestionHintTracking($this->getId(), $active_id, $pass);
1201 $requestsStatisticData = $questionHintTracking->getRequestStatisticDataByQuestionAndTestpass();
1202 $reached_points = $reached_points - $requestsStatisticData->getRequestsPoints();
1203
1204 // adjust reached points regarding to tests scoring options
1205 $reached_points = $this->adjustReachedPointsByScoringOptions($reached_points, $active_id, $pass);
1206
1207 if ($obligationsEnabled && ilObjTest::isQuestionObligatory($this->getId())) {
1208 $isAnswered = $this->isAnswered($active_id, $pass);
1209 } else {
1210 $isAnswered = true;
1211 }
1212
1213 if (is_null($reached_points)) {
1214 $reached_points = 0;
1215 }
1216
1217 // fau: testNav - check for existing authorized solution to know if a result record should be written
1218 $existingSolutions = $this->lookupForExistingSolutions($active_id, $pass);
1219
1220 $this->getProcessLocker()->executeUserQuestionResultUpdateOperation(function () use ($ilDB, $active_id, $pass, $reached_points, $requestsStatisticData, $isAnswered, $existingSolutions) {
1221 $query = "
1222 DELETE FROM tst_test_result
1223
1224 WHERE active_fi = %s
1225 AND question_fi = %s
1226 AND pass = %s
1227 ";
1228
1229 $types = array('integer', 'integer', 'integer');
1230 $values = array($active_id, $this->getId(), $pass);
1231
1232 if ($this->getStep() !== null) {
1233 $query .= "
1234 AND step = %s
1235 ";
1236
1237 $types[] = 'integer';
1238 $values[] = $this->getStep();
1239 }
1240 $ilDB->manipulateF($query, $types, $values);
1241
1242 if ($existingSolutions['authorized']) {
1243 $next_id = $ilDB->nextId("tst_test_result");
1244 $fieldData = array(
1245 'test_result_id' => array('integer', $next_id),
1246 'active_fi' => array('integer', $active_id),
1247 'question_fi' => array('integer', $this->getId()),
1248 'pass' => array('integer', $pass),
1249 'points' => array('float', $reached_points),
1250 'tstamp' => array('integer', time()),
1251 'hint_count' => array('integer', $requestsStatisticData->getRequestsCount()),
1252 'hint_points' => array('float', $requestsStatisticData->getRequestsPoints()),
1253 'answered' => array('integer', $isAnswered)
1254 );
1255
1256 if ($this->getStep() !== null) {
1257 $fieldData['step'] = array('integer', $this->getStep());
1258 }
1259
1260 $ilDB->insert('tst_test_result', $fieldData);
1261 }
1262 });
1263 // fau.
1264
1265 include_once("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
1266
1269 sprintf(
1270 $this->lng->txtlng(
1271 "assessment",
1272 "log_user_answered_question",
1274 ),
1275 $reached_points
1276 ),
1277 $active_id,
1278 $this->getId()
1279 );
1280 }
1281
1282 // update test pass results
1283 self::_updateTestPassResults($active_id, $pass, $obligationsEnabled, $this->getProcessLocker());
1284
1285 // Update objective status
1286 include_once 'Modules/Course/classes/class.ilCourseObjectiveResult.php';
1287 ilCourseObjectiveResult::_updateObjectiveResult($ilUser->getId(), $active_id, $this->getId());
1288 }
1289
1298 final public function persistWorkingState($active_id, $pass = null, $obligationsEnabled = false, $authorized = true)
1299 {
1300 if (!$this->validateSolutionSubmit() && !$this->savePartial()) {
1301 return false;
1302 }
1303
1304 $saveStatus = false;
1305
1306 $this->getProcessLocker()->executePersistWorkingStateLockOperation(function () use ($active_id, $pass, $authorized, $obligationsEnabled, &$saveStatus) {
1307
1308 if ($pass === null) {
1309 require_once 'Modules/Test/classes/class.ilObjTest.php';
1310 $pass = ilObjTest::_getPass($active_id);
1311 }
1312
1313 $saveStatus = $this->saveWorkingData($active_id, $pass, $authorized);
1314
1315 if ($authorized) {
1316 // fau: testNav - remove an intermediate solution if the authorized solution is saved
1317 // the intermediate solution would set the displayed question status as "editing ..."
1318 $this->removeIntermediateSolution($active_id, $pass);
1319 // fau.
1320 $this->calculateResultsFromSolution($active_id, $pass, $obligationsEnabled);
1321 }
1322 });
1323
1324 return $saveStatus;
1325 }
1326
1330 final public function persistPreviewState(ilAssQuestionPreviewSession $previewSession)
1331 {
1332 $this->savePreviewData($previewSession);
1333 return $this->validateSolutionSubmit();
1334 }
1335
1336 public function validateSolutionSubmit()
1337 {
1338 return true;
1339 }
1340
1350 abstract public function saveWorkingData($active_id, $pass = null, $authorized = true);
1351
1352 protected function savePreviewData(ilAssQuestionPreviewSession $previewSession)
1353 {
1354 $previewSession->setParticipantsSolution($this->getSolutionSubmit());
1355 }
1356
1358 public static function _updateTestResultCache($active_id, ilAssQuestionProcessLocker $processLocker = null)
1359 {
1360 global $DIC;
1361 $ilDB = $DIC['ilDB'];
1362
1363 include_once "./Modules/Test/classes/class.ilObjTest.php";
1364 include_once "./Modules/Test/classes/class.assMarkSchema.php";
1365
1366 $pass = ilObjTest::_getResultPass($active_id);
1367
1368 $query = "
1369 SELECT tst_pass_result.*
1370 FROM tst_pass_result
1371 WHERE active_fi = %s
1372 AND pass = %s
1373 ";
1374
1375 $result = $ilDB->queryF(
1376 $query,
1377 array('integer','integer'),
1378 array($active_id, $pass)
1379 );
1380
1381 $row = $ilDB->fetchAssoc($result);
1382
1383 $max = $row['maxpoints'];
1384 $reached = $row['points'];
1385
1386 $obligationsAnswered = (int) $row['obligations_answered'];
1387
1388 include_once "./Modules/Test/classes/class.assMarkSchema.php";
1389
1390 $percentage = (!$max) ? 0 : ($reached / $max) * 100.0;
1391
1392 $mark = ASS_MarkSchema::_getMatchingMarkFromActiveId($active_id, $percentage);
1393
1394 $isPassed = ($mark["passed"] ? 1 : 0);
1395 $isFailed = (!$mark["passed"] ? 1 : 0);
1396
1397 $userTestResultUpdateCallback = function () use ($ilDB, $active_id, $pass, $max, $reached, $isFailed, $isPassed, $obligationsAnswered, $row, $mark) {
1398 $passedOnceBefore = 0;
1399 $query = "SELECT passed_once FROM tst_result_cache WHERE active_fi = %s";
1400 $res = $ilDB->queryF($query, array('integer'), array($active_id));
1401 while ($row = $ilDB->fetchAssoc($res)) {
1402 $passedOnceBefore = (int) $row['passed_once'];
1403 }
1404
1405 $passedOnce = (int) ($isPassed || $passedOnceBefore);
1406
1407 $ilDB->manipulateF(
1408 "DELETE FROM tst_result_cache WHERE active_fi = %s",
1409 array('integer'),
1410 array($active_id)
1411 );
1412
1413 $ilDB->insert('tst_result_cache', array(
1414 'active_fi' => array('integer', $active_id),
1415 'pass' => array('integer', strlen($pass) ? $pass : 0),
1416 'max_points' => array('float', strlen($max) ? $max : 0),
1417 'reached_points' => array('float', strlen($reached) ? $reached : 0),
1418 'mark_short' => array('text', strlen($mark["short_name"]) ? $mark["short_name"] : " "),
1419 'mark_official' => array('text', strlen($mark["official_name"]) ? $mark["official_name"] : " "),
1420 'passed_once' => array('integer', $passedOnce),
1421 'passed' => array('integer', $isPassed),
1422 'failed' => array('integer', $isFailed),
1423 'tstamp' => array('integer', time()),
1424 'hint_count' => array('integer', $row['hint_count']),
1425 'hint_points' => array('float', $row['hint_points']),
1426 'obligations_answered' => array('integer', $obligationsAnswered)
1427 ));
1428 };
1429
1430 if (is_object($processLocker)) {
1431 $processLocker->executeUserTestResultUpdateLockOperation($userTestResultUpdateCallback);
1432 } else {
1433 $userTestResultUpdateCallback();
1434 }
1435 }
1436
1438 public static function _updateTestPassResults($active_id, $pass, $obligationsEnabled = false, ilAssQuestionProcessLocker $processLocker = null, $test_obj_id = null)
1439 {
1440 global $DIC;
1441 $ilDB = $DIC['ilDB'];
1442
1443 include_once "./Modules/Test/classes/class.ilObjTest.php";
1444
1445 if (self::getResultGateway() !== null) {
1446 $data = self::getResultGateway()->getQuestionCountAndPointsForPassOfParticipant($active_id, $pass);
1447 $time = self::getResultGateway()->getWorkingTimeOfParticipantForPass($active_id, $pass);
1448 } else {
1451 }
1452
1453
1454 // update test pass results
1455
1456 $result = $ilDB->queryF(
1457 "
1458 SELECT SUM(points) reachedpoints,
1459 SUM(hint_count) hint_count,
1460 SUM(hint_points) hint_points,
1461 COUNT(DISTINCT(question_fi)) answeredquestions
1462 FROM tst_test_result
1463 WHERE active_fi = %s
1464 AND pass = %s
1465 ",
1466 array('integer','integer'),
1467 array($active_id, $pass)
1468 );
1469
1470 if ($result->numRows() > 0) {
1471 if ($obligationsEnabled) {
1472 $query = '
1473 SELECT answered answ
1474 FROM tst_test_question
1475 INNER JOIN tst_active
1476 ON active_id = %s
1477 AND tst_test_question.test_fi = tst_active.test_fi
1478 LEFT JOIN tst_test_result
1479 ON tst_test_result.active_fi = %s
1480 AND tst_test_result.pass = %s
1481 AND tst_test_question.question_fi = tst_test_result.question_fi
1482 WHERE obligatory = 1';
1483
1484 $result_obligatory = $ilDB->queryF(
1485 $query,
1486 array('integer','integer','integer'),
1487 array($active_id, $active_id, $pass)
1488 );
1489
1490 $obligations_answered = 1;
1491
1492 while ($row_obligatory = $ilDB->fetchAssoc($result_obligatory)) {
1493 if (!(int) $row_obligatory['answ']) {
1494 $obligations_answered = 0;
1495 break;
1496 }
1497 }
1498 } else {
1499 $obligations_answered = 1;
1500 }
1501
1502 $row = $ilDB->fetchAssoc($result);
1503
1504 if ($row['reachedpoints'] === null) {
1505 $row['reachedpoints'] = 0;
1506 }
1507 if ($row['hint_count'] === null) {
1508 $row['hint_count'] = 0;
1509 }
1510 if ($row['hint_points'] === null) {
1511 $row['hint_points'] = 0;
1512 }
1513
1514 $exam_identifier = ilObjTest::buildExamId($active_id, $pass, $test_obj_id);
1515
1516 $updatePassResultCallback = function () use ($ilDB, $data, $active_id, $pass, $row, $time, $obligations_answered, $exam_identifier) {
1517
1519 $ilDB->replace(
1520 'tst_pass_result',
1521 array(
1522 'active_fi' => array('integer', $active_id),
1523 'pass' => array('integer', strlen($pass) ? $pass : 0)),
1524 array(
1525 'points' => array('float', $row['reachedpoints'] ? $row['reachedpoints'] : 0),
1526 'maxpoints' => array('float', $data['points']),
1527 'questioncount' => array('integer', $data['count']),
1528 'answeredquestions' => array('integer', $row['answeredquestions']),
1529 'workingtime' => array('integer', $time),
1530 'tstamp' => array('integer', time()),
1531 'hint_count' => array('integer', $row['hint_count']),
1532 'hint_points' => array('float', $row['hint_points']),
1533 'obligations_answered' => array('integer', $obligations_answered),
1534 'exam_id' => array('text', $exam_identifier)
1535 )
1536 );
1537 };
1538
1539 if (is_object($processLocker)) {
1540 $processLocker->executeUserPassResultUpdateLockOperation($updatePassResultCallback);
1541 } else {
1542 $updatePassResultCallback();
1543 }
1544 }
1545
1547
1548 return array(
1549 'active_fi' => $active_id,
1550 'pass' => $pass,
1551 'points' => ($row["reachedpoints"]) ? $row["reachedpoints"] : 0,
1552 'maxpoints' => $data["points"],
1553 'questioncount' => $data["count"],
1554 'answeredquestions' => $row["answeredquestions"],
1555 'workingtime' => $time,
1556 'tstamp' => time(),
1557 'hint_count' => $row['hint_count'],
1558 'hint_points' => $row['hint_points'],
1559 'obligations_answered' => $obligations_answered,
1560 'exam_id' => $exam_identifier
1561 );
1562 }
1563
1571 public static function logAction($logtext = "", $active_id = "", $question_id = "")
1572 {
1573 $original_id = "";
1574 if (strlen($question_id)) {
1575 $original_id = self::_getOriginalId($question_id);
1576 }
1577
1578 require_once 'Modules/Test/classes/class.ilObjAssessmentFolder.php';
1579 require_once 'Modules/Test/classes/class.ilObjTest.php';
1580
1582 $GLOBALS['DIC']['ilUser']->getId(),
1584 $logtext,
1585 $question_id,
1587 );
1588 }
1589
1597 public function moveUploadedMediaFile($file, $name)
1598 {
1599 $mediatempdir = CLIENT_WEB_DIR . "/assessment/temp";
1600 if (!@is_dir($mediatempdir)) {
1601 ilUtil::createDirectory($mediatempdir);
1602 }
1603 $temp_name = tempnam($mediatempdir, $name . "_____");
1604 $temp_name = str_replace("\\", "/", $temp_name);
1605 @unlink($temp_name);
1606 if (!ilUtil::moveUploadedFile($file, $name, $temp_name)) {
1607 return false;
1608 } else {
1609 return $temp_name;
1610 }
1611 }
1612
1619 {
1620 return CLIENT_WEB_DIR . "/assessment/$this->obj_id/$this->id/solution/";
1621 }
1622
1629 public function getJavaPath()
1630 {
1631 return CLIENT_WEB_DIR . "/assessment/$this->obj_id/$this->id/java/";
1632 }
1633
1640 public function getImagePath($question_id = null, $object_id = null)
1641 {
1642 if ($question_id === null) {
1643 $question_id = $this->id;
1644 }
1645
1646 if ($object_id === null) {
1647 $object_id = $this->obj_id;
1648 }
1649
1650 return $this->buildImagePath($question_id, $object_id);
1651 }
1652
1653 public function buildImagePath($questionId, $parentObjectId)
1654 {
1655 return CLIENT_WEB_DIR . "/assessment/{$parentObjectId}/{$questionId}/images/";
1656 }
1657
1664 public function getFlashPath()
1665 {
1666 return CLIENT_WEB_DIR . "/assessment/$this->obj_id/$this->id/flash/";
1667 }
1668
1675 public function getJavaPathWeb()
1676 {
1677 include_once "./Services/Utilities/classes/class.ilUtil.php";
1678 $webdir = ilUtil::removeTrailingPathSeparators(CLIENT_WEB_DIR) . "/assessment/$this->obj_id/$this->id/java/";
1679 return str_replace(ilUtil::removeTrailingPathSeparators(ILIAS_ABSOLUTE_PATH), ilUtil::removeTrailingPathSeparators(ILIAS_HTTP_PATH), $webdir);
1680 }
1681
1688 {
1689 include_once "./Services/Utilities/classes/class.ilUtil.php";
1690 $webdir = ilUtil::removeTrailingPathSeparators(CLIENT_WEB_DIR) . "/assessment/$this->obj_id/$this->id/solution/";
1691 return str_replace(ilUtil::removeTrailingPathSeparators(ILIAS_ABSOLUTE_PATH), ilUtil::removeTrailingPathSeparators(ILIAS_HTTP_PATH), $webdir);
1692 }
1693
1702 public function getImagePathWeb()
1703 {
1704 if (!$this->export_image_path) {
1705 include_once "./Services/Utilities/classes/class.ilUtil.php";
1706 $webdir = ilUtil::removeTrailingPathSeparators(CLIENT_WEB_DIR) . "/assessment/$this->obj_id/$this->id/images/";
1707 return str_replace(ilUtil::removeTrailingPathSeparators(ILIAS_ABSOLUTE_PATH), ilUtil::removeTrailingPathSeparators(ILIAS_HTTP_PATH), $webdir);
1708 } else {
1710 }
1711 }
1712
1719 public function getFlashPathWeb()
1720 {
1721 include_once "./Services/Utilities/classes/class.ilUtil.php";
1722 $webdir = ilUtil::removeTrailingPathSeparators(CLIENT_WEB_DIR) . "/assessment/$this->obj_id/$this->id/flash/";
1723 return str_replace(ilUtil::removeTrailingPathSeparators(ILIAS_ABSOLUTE_PATH), ilUtil::removeTrailingPathSeparators(ILIAS_HTTP_PATH), $webdir);
1724 }
1725
1726 // hey: prevPassSolutions - accept and prefer intermediate only from current pass
1727 public function getTestOutputSolutions($activeId, $pass)
1728 {
1729 // hey: refactored identifiers
1730 if ($this->getTestPresentationConfig()->isSolutionInitiallyPrefilled()) {
1731 // hey.
1732 return $this->getSolutionValues($activeId, $pass, true);
1733 }
1734
1735 return $this->getUserSolutionPreferingIntermediate($activeId, $pass);
1736 }
1737 // hey.
1738
1739 public function getUserSolutionPreferingIntermediate($active_id, $pass = null)
1740 {
1741 $solution = $this->getSolutionValues($active_id, $pass, false);
1742
1743 if (!count($solution)) {
1744 $solution = $this->getSolutionValues($active_id, $pass, true);
1745 }
1746
1747 return $solution;
1748 }
1749
1753 public function getSolutionValues($active_id, $pass = null, $authorized = true)
1754 {
1755 global $DIC;
1756 $ilDB = $DIC['ilDB'];
1757
1758 if (is_null($pass)) {
1759 $pass = $this->getSolutionMaxPass($active_id);
1760 }
1761
1762 if ($this->getStep() !== null) {
1763 $query = "
1764 SELECT *
1765 FROM tst_solutions
1766 WHERE active_fi = %s
1767 AND question_fi = %s
1768 AND pass = %s
1769 AND step = %s
1770 AND authorized = %s
1771 ORDER BY solution_id";
1772
1773 $result = $ilDB->queryF(
1774 $query,
1775 array('integer', 'integer', 'integer', 'integer', 'integer'),
1776 array($active_id, $this->getId(), $pass, $this->getStep(), (int) $authorized)
1777 );
1778 } else {
1779 $query = "
1780 SELECT *
1781 FROM tst_solutions
1782 WHERE active_fi = %s
1783 AND question_fi = %s
1784 AND pass = %s
1785 AND authorized = %s
1786 ORDER BY solution_id
1787 ";
1788
1789 $result = $ilDB->queryF(
1790 $query,
1791 array('integer', 'integer', 'integer', 'integer'),
1792 array($active_id, $this->getId(), $pass, (int) $authorized)
1793 );
1794 }
1795
1796 $values = array();
1797
1798 while ($row = $ilDB->fetchAssoc($result)) {
1799 $values[] = $row;
1800 }
1801
1802 return $values;
1803 }
1804
1811 public function isInUse($question_id = "")
1812 {
1813 global $DIC;
1814 $ilDB = $DIC['ilDB'];
1815
1816 if ($question_id < 1) {
1817 $question_id = $this->getId();
1818 }
1819 $result = $ilDB->queryF(
1820 "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",
1821 array('integer'),
1822 array($question_id)
1823 );
1824 $row = $ilDB->fetchAssoc($result);
1825 $count = $row["question_count"];
1826
1827 $result = $ilDB->queryF(
1828 "
1829 SELECT tst_active.test_fi
1830 FROM qpl_questions
1831 INNER JOIN tst_test_rnd_qst ON tst_test_rnd_qst.question_fi = qpl_questions.question_id
1832 INNER JOIN tst_active ON tst_active.active_id = tst_test_rnd_qst.active_fi
1833 WHERE qpl_questions.original_id = %s
1834 GROUP BY tst_active.test_fi",
1835 array('integer'),
1836 array($question_id)
1837 );
1838 $count += $result->numRows();
1839
1840 return $count;
1841 }
1842
1849 public function isClone($question_id = "")
1850 {
1851 global $DIC;
1852 $ilDB = $DIC['ilDB'];
1853
1854 if ($question_id < 1) {
1855 $question_id = $this->id;
1856 }
1857 $result = $ilDB->queryF(
1858 "SELECT original_id FROM qpl_questions WHERE question_id = %s",
1859 array('integer'),
1860 array($question_id)
1861 );
1862 $row = $ilDB->fetchAssoc($result);
1863 return ($row["original_id"] > 0) ? true : false;
1864 }
1865
1872 public function pcArrayShuffle($array)
1873 {
1874 $keys = array_keys($array);
1875 shuffle($keys);
1876 $result = array();
1877 foreach ($keys as $key) {
1878 $result[$key] = $array[$key];
1879 }
1880 return $result;
1881 }
1882
1888 public static function getQuestionTypeFromDb($question_id)
1889 {
1890 global $DIC;
1891 $ilDB = $DIC['ilDB'];
1892
1893 $result = $ilDB->queryF(
1894 "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",
1895 array('integer'),
1896 array($question_id)
1897 );
1898 $data = $ilDB->fetchAssoc($result);
1899 return $data["type_tag"];
1900 }
1901
1908 public function getAdditionalTableName()
1909 {
1910 return "";
1911 }
1912
1919 public function getAnswerTableName()
1920 {
1921 return "";
1922 }
1923
1930 public function deleteAnswers($question_id)
1931 {
1932 global $DIC;
1933 $ilDB = $DIC['ilDB'];
1934 $answer_table_name = $this->getAnswerTableName();
1935
1936 if (!is_array($answer_table_name)) {
1937 $answer_table_name = array($answer_table_name);
1938 }
1939
1940 foreach ($answer_table_name as $table) {
1941 if (strlen($table)) {
1942 $affectedRows = $ilDB->manipulateF(
1943 "DELETE FROM $table WHERE question_fi = %s",
1944 array('integer'),
1945 array($question_id)
1946 );
1947 }
1948 }
1949 }
1950
1957 public function deleteAdditionalTableData($question_id)
1958 {
1959 global $DIC;
1960 $ilDB = $DIC['ilDB'];
1961
1962 $additional_table_name = $this->getAdditionalTableName();
1963
1964 if (!is_array($additional_table_name)) {
1965 $additional_table_name = array($additional_table_name);
1966 }
1967
1968 foreach ($additional_table_name as $table) {
1969 if (strlen($table)) {
1970 $affectedRows = $ilDB->manipulateF(
1971 "DELETE FROM $table WHERE question_fi = %s",
1972 array('integer'),
1973 array($question_id)
1974 );
1975 }
1976 }
1977 }
1978
1985 protected function deletePageOfQuestion($question_id)
1986 {
1987 include_once "./Modules/TestQuestionPool/classes/class.ilAssQuestionPage.php";
1988 $page = new ilAssQuestionPage($question_id);
1989 $page->delete();
1990 return true;
1991 }
1992
1999 public function delete($question_id)
2000 {
2001 global $DIC;
2002 $ilDB = $DIC['ilDB'];
2003 $ilLog = $DIC['ilLog'];
2004
2005 if ($question_id < 1) {
2006 return true;
2007 } // nothing to do
2008
2009 $result = $ilDB->queryF(
2010 "SELECT obj_fi FROM qpl_questions WHERE question_id = %s",
2011 array('integer'),
2012 array($question_id)
2013 );
2014 if ($result->numRows() == 1) {
2015 $row = $ilDB->fetchAssoc($result);
2016 $obj_id = $row["obj_fi"];
2017 } else {
2018 return true; // nothing to do
2019 }
2020 try {
2021 $this->deletePageOfQuestion($question_id);
2022 } catch (Exception $e) {
2023 $ilLog->write("EXCEPTION: Could not delete page of question $question_id: $e");
2024 return false;
2025 }
2026
2027 $affectedRows = $ilDB->manipulateF(
2028 "DELETE FROM qpl_questions WHERE question_id = %s",
2029 array('integer'),
2030 array($question_id)
2031 );
2032 if ($affectedRows == 0) {
2033 return false;
2034 }
2035
2036 try {
2037 $this->deleteAdditionalTableData($question_id);
2038 $this->deleteAnswers($question_id);
2039 $this->feedbackOBJ->deleteGenericFeedbacks($question_id, $this->isAdditionalContentEditingModePageObject());
2040 $this->feedbackOBJ->deleteSpecificAnswerFeedbacks($question_id, $this->isAdditionalContentEditingModePageObject());
2041 } catch (Exception $e) {
2042 $ilLog->write("EXCEPTION: Could not delete additional table data of question $question_id: $e");
2043 return false;
2044 }
2045
2046 try {
2047 // delete the question in the tst_test_question table (list of test questions)
2048 $affectedRows = $ilDB->manipulateF(
2049 "DELETE FROM tst_test_question WHERE question_fi = %s",
2050 array('integer'),
2051 array($question_id)
2052 );
2053 } catch (Exception $e) {
2054 $ilLog->write("EXCEPTION: Could not delete delete question $question_id from a test: $e");
2055 return false;
2056 }
2057
2058 try {
2059 // delete suggested solutions contained in the question
2060 $affectedRows = $ilDB->manipulateF(
2061 "DELETE FROM qpl_sol_sug WHERE question_fi = %s",
2062 array('integer'),
2063 array($question_id)
2064 );
2065 } catch (Exception $e) {
2066 $ilLog->write("EXCEPTION: Could not delete suggested solutions of question $question_id: $e");
2067 return false;
2068 }
2069
2070 try {
2071 $directory = CLIENT_WEB_DIR . "/assessment/" . $obj_id . "/$question_id";
2072 if (preg_match("/\d+/", $obj_id) and preg_match("/\d+/", $question_id) and is_dir($directory)) {
2073 include_once "./Services/Utilities/classes/class.ilUtil.php";
2074 ilUtil::delDir($directory);
2075 }
2076 } catch (Exception $e) {
2077 $ilLog->write("EXCEPTION: Could not delete question file directory $directory of question $question_id: $e");
2078 return false;
2079 }
2080
2081 try {
2082 include_once("./Services/MediaObjects/classes/class.ilObjMediaObject.php");
2083 $mobs = ilObjMediaObject::_getMobsOfObject("qpl:html", $question_id);
2084 // remaining usages are not in text anymore -> delete them
2085 // and media objects (note: delete method of ilObjMediaObject
2086 // checks whether object is used in another context; if yes,
2087 // the object is not deleted!)
2088 foreach ($mobs as $mob) {
2089 ilObjMediaObject::_removeUsage($mob, "qpl:html", $question_id);
2090 if (ilObjMediaObject::_exists($mob)) {
2091 $mob_obj = new ilObjMediaObject($mob);
2092 $mob_obj->delete();
2093 }
2094 }
2095 } catch (Exception $e) {
2096 $ilLog->write("EXCEPTION: Error deleting the media objects of question $question_id: $e");
2097 return false;
2098 }
2099
2100 require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionHintTracking.php';
2101 ilAssQuestionHintTracking::deleteRequestsByQuestionIds(array($question_id));
2102
2103 require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionHintList.php';
2105
2106 require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionSkillAssignmentList.php';
2107 $assignmentList = new ilAssQuestionSkillAssignmentList($ilDB);
2108 $assignmentList->setParentObjId($obj_id);
2109 $assignmentList->setQuestionIdFilter($question_id);
2110 $assignmentList->loadFromDb();
2111 foreach ($assignmentList->getAssignmentsByQuestionId($question_id) as $assignment) {
2112 /* @var ilAssQuestionSkillAssignment $assignment */
2113 $assignment->deleteFromDb();
2114 }
2115
2117
2118 try {
2119 // update question count of question pool
2120 include_once "./Modules/TestQuestionPool/classes/class.ilObjQuestionPool.php";
2122 } catch (Exception $e) {
2123 $ilLog->write("EXCEPTION: Error updating the question pool question count of question pool " . $this->getObjId() . " when deleting question $question_id: $e");
2124 return false;
2125 }
2126
2127 $this->notifyQuestionDeleted($this);
2128
2129 return true;
2130 }
2131
2132 private function deleteTaxonomyAssignments()
2133 {
2134 require_once 'Services/Taxonomy/classes/class.ilObjTaxonomy.php';
2135 require_once 'Services/Taxonomy/classes/class.ilTaxNodeAssignment.php';
2136 $taxIds = ilObjTaxonomy::getUsageOfObject($this->getObjId());
2137
2138 foreach ($taxIds as $taxId) {
2139 $taxNodeAssignment = new ilTaxNodeAssignment('qpl', $this->getObjId(), 'quest', $taxId);
2140 $taxNodeAssignment->deleteAssignmentsOfItem($this->getId());
2141 }
2142 }
2143
2147 public function getTotalAnswers()
2148 {
2149 return $this->_getTotalAnswers($this->id);
2150 }
2151
2158 public function _getTotalAnswers($a_q_id)
2159 {
2160 global $DIC;
2161 $ilDB = $DIC['ilDB'];
2162
2163 // get all question references to the question id
2164 $result = $ilDB->queryF(
2165 "SELECT question_id FROM qpl_questions WHERE original_id = %s OR question_id = %s",
2166 array('integer','integer'),
2167 array($a_q_id, $a_q_id)
2168 );
2169 if ($result->numRows() == 0) {
2170 return 0;
2171 }
2172 $found_id = array();
2173 while ($row = $ilDB->fetchAssoc($result)) {
2174 array_push($found_id, $row["question_id"]);
2175 }
2176
2177 $result = $ilDB->query("SELECT * FROM tst_test_result WHERE " . $ilDB->in('question_fi', $found_id, false, 'integer'));
2178
2179 return $result->numRows();
2180 }
2181
2182
2189 public static function _getTotalRightAnswers($a_q_id)
2190 {
2191 global $DIC;
2192 $ilDB = $DIC['ilDB'];
2193 $result = $ilDB->queryF(
2194 "SELECT question_id FROM qpl_questions WHERE original_id = %s OR question_id = %s",
2195 array('integer','integer'),
2196 array($a_q_id, $a_q_id)
2197 );
2198 if ($result->numRows() == 0) {
2199 return 0;
2200 }
2201 $found_id = array();
2202 while ($row = $ilDB->fetchAssoc($result)) {
2203 array_push($found_id, $row["question_id"]);
2204 }
2205 $result = $ilDB->query("SELECT * FROM tst_test_result WHERE " . $ilDB->in('question_fi', $found_id, false, 'integer'));
2206 $answers = array();
2207 while ($row = $ilDB->fetchAssoc($result)) {
2208 $reached = $row["points"];
2209 include_once "./Modules/TestQuestionPool/classes/class.assQuestion.php";
2210 $max = assQuestion::_getMaximumPoints($row["question_fi"]);
2211 array_push($answers, array("reached" => $reached, "max" => $max));
2212 }
2213 $max = 0.0;
2214 $reached = 0.0;
2215 foreach ($answers as $key => $value) {
2216 $max += $value["max"];
2217 $reached += $value["reached"];
2218 }
2219 if ($max > 0) {
2220 return $reached / $max;
2221 } else {
2222 return 0;
2223 }
2224 }
2225
2231 public static function _getTitle($a_q_id)
2232 {
2233 global $DIC;
2234 $ilDB = $DIC['ilDB'];
2235 $result = $ilDB->queryF(
2236 "SELECT title FROM qpl_questions WHERE question_id = %s",
2237 array('integer'),
2238 array($a_q_id)
2239 );
2240 if ($result->numRows() == 1) {
2241 $row = $ilDB->fetchAssoc($result);
2242 return $row["title"];
2243 } else {
2244 return "";
2245 }
2246 }
2247
2253 public static function _getQuestionText($a_q_id)
2254 {
2255 global $DIC;
2256 $ilDB = $DIC['ilDB'];
2257 $result = $ilDB->queryF(
2258 "SELECT question_text FROM qpl_questions WHERE question_id = %s",
2259 array('integer'),
2260 array($a_q_id)
2261 );
2262 if ($result->numRows() == 1) {
2263 $row = $ilDB->fetchAssoc($result);
2264 return $row["question_text"];
2265 } else {
2266 return "";
2267 }
2268 }
2269
2270 public static function isFileAvailable($file)
2271 {
2272 if (!file_exists($file)) {
2273 return false;
2274 }
2275
2276 if (!is_file($file)) {
2277 return false;
2278 }
2279
2280 if (!is_readable($file)) {
2281 return false;
2282 }
2283
2284 return true;
2285 }
2286
2287 public function copyXHTMLMediaObjectsOfQuestion($a_q_id)
2288 {
2289 include_once("./Services/MediaObjects/classes/class.ilObjMediaObject.php");
2290 $mobs = ilObjMediaObject::_getMobsOfObject("qpl:html", $a_q_id);
2291 foreach ($mobs as $mob) {
2292 ilObjMediaObject::_saveUsage($mob, "qpl:html", $this->getId());
2293 }
2294 }
2295
2297 {
2298 include_once("./Services/MediaObjects/classes/class.ilObjMediaObject.php");
2299 $mobs = ilObjMediaObject::_getMobsOfObject("qpl:html", $this->getId());
2300 foreach ($mobs as $mob) {
2301 ilObjMediaObject::_saveUsage($mob, "qpl:html", $this->original_id);
2302 }
2303 }
2304
2308 public function createPageObject()
2309 {
2310 $qpl_id = $this->getObjId();
2311
2312 include_once "./Modules/TestQuestionPool/classes/class.ilAssQuestionPage.php";
2313 $this->page = new ilAssQuestionPage(0);
2314 $this->page->setId($this->getId());
2315 $this->page->setParentId($qpl_id);
2316 $this->page->setXMLContent("<PageObject><PageContent>" .
2317 "<Question QRef=\"il__qst_" . $this->getId() . "\"/>" .
2318 "</PageContent></PageObject>");
2319 $this->page->create();
2320 }
2321
2322 public function copyPageOfQuestion($a_q_id)
2323 {
2324 if ($a_q_id > 0) {
2325 include_once "./Modules/TestQuestionPool/classes/class.ilAssQuestionPage.php";
2326 $page = new ilAssQuestionPage($a_q_id);
2327
2328 $xml = str_replace("il__qst_" . $a_q_id, "il__qst_" . $this->id, $page->getXMLContent());
2329 $this->page->setXMLContent($xml);
2330 $this->page->updateFromXML();
2331 }
2332 }
2333
2334 public function getPageOfQuestion()
2335 {
2336 include_once "./Modules/TestQuestionPool/classes/class.ilAssQuestionPage.php";
2337 $page = new ilAssQuestionPage($this->id);
2338 return $page->getXMLContent();
2339 }
2340
2346 public static function _getQuestionType($question_id)
2347 {
2348 global $DIC;
2349 $ilDB = $DIC['ilDB'];
2350
2351 if ($question_id < 1) {
2352 return "";
2353 }
2354 $result = $ilDB->queryF(
2355 "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",
2356 array('integer'),
2357 array($question_id)
2358 );
2359 if ($result->numRows() == 1) {
2360 $data = $ilDB->fetchAssoc($result);
2361 return $data["type_tag"];
2362 } else {
2363 return "";
2364 }
2365 }
2366
2374 public static function _getQuestionTitle($question_id)
2375 {
2376 global $DIC;
2377 $ilDB = $DIC['ilDB'];
2378
2379 if ($question_id < 1) {
2380 return "";
2381 }
2382
2383 $result = $ilDB->queryF(
2384 "SELECT title FROM qpl_questions WHERE qpl_questions.question_id = %s",
2385 array('integer'),
2386 array($question_id)
2387 );
2388 if ($result->numRows() == 1) {
2389 $data = $ilDB->fetchAssoc($result);
2390 return $data["title"];
2391 } else {
2392 return "";
2393 }
2394 }
2395
2397 {
2398 $this->original_id = $original_id;
2399 }
2400
2401 public function getOriginalId()
2402 {
2403 return $this->original_id;
2404 }
2405
2406 protected static $imageSourceFixReplaceMap = array(
2407 'ok.svg' => 'ok.png', 'not_ok.svg' => 'not_ok.png',
2408 'checkbox_checked.svg' => 'checkbox_checked.png',
2409 'checkbox_unchecked.svg' => 'checkbox_unchecked.png',
2410 'radiobutton_checked.svg' => 'radiobutton_checked.png',
2411 'radiobutton_unchecked.svg' => 'radiobutton_unchecked.png'
2412 );
2413
2414 public function fixSvgToPng($imageFilenameContainingString)
2415 {
2416 $needles = array_keys(self::$imageSourceFixReplaceMap);
2417 $replacements = array_values(self::$imageSourceFixReplaceMap);
2418 return str_replace($needles, $replacements, $imageFilenameContainingString);
2419 }
2420
2421
2423 {
2424 $matches = null;
2425 if (preg_match_all('/src="(.*?)"/m', $html, $matches)) {
2426 $sources = $matches[1];
2427
2428 $needleReplacementMap = array();
2429
2430 foreach ($sources as $src) {
2431 $file = ilUtil::removeTrailingPathSeparators(ILIAS_ABSOLUTE_PATH) . DIRECTORY_SEPARATOR . $src;
2432
2433 if (file_exists($file)) {
2434 continue;
2435 }
2436
2437 $levels = explode(DIRECTORY_SEPARATOR, $src);
2438 if (count($levels) < 5 || $levels[0] != 'Customizing' || $levels[2] != 'skin') {
2439 continue;
2440 }
2441
2442 $component = '';
2443
2444 if ($levels[4] == 'Modules' || $levels[4] == 'Services') {
2445 $component = $levels[4] . DIRECTORY_SEPARATOR . $levels[5];
2446 }
2447
2448 $needleReplacementMap[$src] = ilUtil::getImagePath(basename($src), $component);
2449 }
2450
2451 if (count($needleReplacementMap)) {
2452 $html = str_replace(array_keys($needleReplacementMap), array_values($needleReplacementMap), $html);
2453 }
2454 }
2455
2456 return $html;
2457 }
2458
2465 public function loadFromDb($question_id)
2466 {
2467 global $DIC;
2468 $ilDB = $DIC['ilDB'];
2469
2470 $result = $ilDB->queryF(
2471 "SELECT external_id FROM qpl_questions WHERE question_id = %s",
2472 array("integer"),
2473 array($question_id)
2474 );
2475 if ($result->numRows() == 1) {
2476 $data = $ilDB->fetchAssoc($result);
2477 $this->external_id = $data['external_id'];
2478 }
2479
2480 $result = $ilDB->queryF(
2481 "SELECT * FROM qpl_sol_sug WHERE question_fi = %s",
2482 array('integer'),
2483 array($this->getId())
2484 );
2485 $this->suggested_solutions = array();
2486 if ($result->numRows()) {
2487 include_once("./Services/RTE/classes/class.ilRTE.php");
2488 while ($row = $ilDB->fetchAssoc($result)) {
2489 $value = (is_array(unserialize($row["value"]))) ? unserialize($row["value"]) : ilRTE::_replaceMediaObjectImageSrc($row["value"], 1);
2490 $this->suggested_solutions[$row["subquestion_index"]] = array(
2491 "type" => $row["type"],
2492 "value" => $value,
2493 "internal_link" => $row["internal_link"],
2494 "import_id" => $row["import_id"]
2495 );
2496 }
2497 }
2498 }
2499
2506 public function createNewQuestion($a_create_page = true)
2507 {
2508 global $DIC;
2509 $ilDB = $DIC['ilDB'];
2510 $ilUser = $DIC['ilUser'];
2511
2512 $complete = "0";
2513 $estw_time = $this->getEstimatedWorkingTime();
2514 $estw_time = sprintf("%02d:%02d:%02d", $estw_time['h'], $estw_time['m'], $estw_time['s']);
2515 $obj_id = ($this->getObjId() <= 0) ? (ilObject::_lookupObjId((strlen($_GET["ref_id"])) ? $_GET["ref_id"] : $_POST["sel_qpl"])) : $this->getObjId();
2516 if ($obj_id > 0) {
2517 if ($a_create_page) {
2518 $tstamp = 0;
2519 } else {
2520 // question pool must not try to purge
2521 $tstamp = time();
2522 }
2523
2524 $next_id = $ilDB->nextId('qpl_questions');
2525 $affectedRows = $ilDB->insert("qpl_questions", array(
2526 "question_id" => array("integer", $next_id),
2527 "question_type_fi" => array("integer", $this->getQuestionTypeID()),
2528 "obj_fi" => array("integer", $obj_id),
2529 "title" => array("text", null),
2530 "description" => array("text", null),
2531 "author" => array("text", $this->getAuthor()),
2532 "owner" => array("integer", $ilUser->getId()),
2533 "question_text" => array("clob", null),
2534 "points" => array("float", 0),
2535 "nr_of_tries" => array("integer", $this->getDefaultNrOfTries()), // #10771
2536 "working_time" => array("text", $estw_time),
2537 "complete" => array("text", $complete),
2538 "created" => array("integer", time()),
2539 "original_id" => array("integer", null),
2540 "tstamp" => array("integer", $tstamp),
2541 "external_id" => array("text", $this->getExternalId()),
2542 'add_cont_edit_mode' => array('text', $this->getAdditionalContentEditingMode())
2543 ));
2544 $this->setId($next_id);
2545
2546 if ($a_create_page) {
2547 // create page object of question
2548 $this->createPageObject();
2549 }
2550 }
2551
2552 $this->notifyQuestionCreated();
2553
2554 return $this->getId();
2555 }
2556
2558 {
2559 global $DIC;
2560 $ilDB = $DIC['ilDB'];
2561
2562 $estw_time = $this->getEstimatedWorkingTime();
2563 $estw_time = sprintf("%02d:%02d:%02d", $estw_time['h'], $estw_time['m'], $estw_time['s']);
2564
2565 // cleanup RTE images which are not inserted into the question text
2566 include_once("./Services/RTE/classes/class.ilRTE.php");
2567 if ($this->getId() == -1) {
2568 // Neuen Datensatz schreiben
2569 $next_id = $ilDB->nextId('qpl_questions');
2570 $affectedRows = $ilDB->insert("qpl_questions", array(
2571 "question_id" => array("integer", $next_id),
2572 "question_type_fi" => array("integer", $this->getQuestionTypeID()),
2573 "obj_fi" => array("integer", $this->getObjId()),
2574 "title" => array("text", $this->getTitle()),
2575 "description" => array("text", $this->getComment()),
2576 "author" => array("text", $this->getAuthor()),
2577 "owner" => array("integer", $this->getOwner()),
2578 "question_text" => array("clob", ilRTE::_replaceMediaObjectImageSrc($this->getQuestion(), 0)),
2579 "points" => array("float", $this->getMaximumPoints()),
2580 "working_time" => array("text", $estw_time),
2581 "nr_of_tries" => array("integer", $this->getNrOfTries()),
2582 "created" => array("integer", time()),
2583 "original_id" => array("integer", ($original_id) ? $original_id : null),
2584 "tstamp" => array("integer", time()),
2585 "external_id" => array("text", $this->getExternalId()),
2586 'add_cont_edit_mode' => array('text', $this->getAdditionalContentEditingMode())
2587 ));
2588 $this->setId($next_id);
2589 // create page object of question
2590 $this->createPageObject();
2591 } else {
2592 // Vorhandenen Datensatz aktualisieren
2593 $affectedRows = $ilDB->update("qpl_questions", array(
2594 "obj_fi" => array("integer", $this->getObjId()),
2595 "title" => array("text", $this->getTitle()),
2596 "description" => array("text", $this->getComment()),
2597 "author" => array("text", $this->getAuthor()),
2598 "question_text" => array("clob", ilRTE::_replaceMediaObjectImageSrc($this->getQuestion(), 0)),
2599 "points" => array("float", $this->getMaximumPoints()),
2600 "nr_of_tries" => array("integer", $this->getNrOfTries()),
2601 "working_time" => array("text", $estw_time),
2602 "tstamp" => array("integer", time()),
2603 'complete' => array('integer', $this->isComplete()),
2604 "external_id" => array("text", $this->getExternalId())
2605 ), array(
2606 "question_id" => array("integer", $this->getId())
2607 ));
2608 }
2609 }
2610
2617 public function saveToDb($original_id = "")
2618 {
2619 global $DIC;
2620 $ilDB = $DIC['ilDB'];
2621
2622 $this->updateSuggestedSolutions();
2623
2624 // remove unused media objects from ILIAS
2625 $this->cleanupMediaObjectUsage();
2626
2627 $complete = "0";
2628 if ($this->isComplete()) {
2629 $complete = "1";
2630 }
2631
2632 // update the question time stamp and completion status
2633 $affectedRows = $ilDB->manipulateF(
2634 "UPDATE qpl_questions SET tstamp = %s, owner = %s, complete = %s WHERE question_id = %s",
2635 array('integer','integer', 'integer','text'),
2636 array(time(), ($this->getOwner() <= 0) ? $this->ilias->account->id : $this->getOwner(), $complete, $this->getId())
2637 );
2638
2639 // update question count of question pool
2640 include_once "./Modules/TestQuestionPool/classes/class.ilObjQuestionPool.php";
2642
2643 $this->notifyQuestionEdited($this);
2644 }
2645
2649 public function setNewOriginalId($newId)
2650 {
2651 self::saveOriginalId($this->getId(), $newId);
2652 }
2653
2654 public static function saveOriginalId($questionId, $originalId)
2655 {
2656 $query = "UPDATE qpl_questions SET tstamp = %s, original_id = %s WHERE question_id = %s";
2657
2658 $GLOBALS['DIC']['ilDB']->manipulateF(
2659 $query,
2660 array('integer','integer', 'text'),
2661 array(time(), $originalId, $questionId)
2662 );
2663 }
2664
2665 public static function resetOriginalId($questionId)
2666 {
2667 $query = "UPDATE qpl_questions SET tstamp = %s, original_id = NULL WHERE question_id = %s";
2668
2669 $GLOBALS['DIC']['ilDB']->manipulateF(
2670 $query,
2671 array('integer', 'text'),
2672 array(time(), $questionId)
2673 );
2674 }
2675
2679 protected function onDuplicate($originalParentId, $originalQuestionId, $duplicateParentId, $duplicateQuestionId)
2680 {
2681 $this->duplicateSuggestedSolutionFiles($originalParentId, $originalQuestionId);
2682
2683 // duplicate question feeback
2684 $this->feedbackOBJ->duplicateFeedback($originalQuestionId, $duplicateQuestionId);
2685
2686 // duplicate question hints
2687 $this->duplicateQuestionHints($originalQuestionId, $duplicateQuestionId);
2688
2689 // duplicate skill assignments
2690 $this->duplicateSkillAssignments($originalParentId, $originalQuestionId, $duplicateParentId, $duplicateQuestionId);
2691 }
2692
2693 protected function beforeSyncWithOriginal($origQuestionId, $dupQuestionId, $origParentObjId, $dupParentObjId)
2694 {
2695 }
2696
2697 protected function afterSyncWithOriginal($origQuestionId, $dupQuestionId, $origParentObjId, $dupParentObjId)
2698 {
2699 // sync question feeback
2700 $this->feedbackOBJ->syncFeedback($origQuestionId, $dupQuestionId);
2701 }
2702
2706 protected function onCopy($sourceParentId, $sourceQuestionId, $targetParentId, $targetQuestionId)
2707 {
2708 $this->copySuggestedSolutionFiles($sourceParentId, $sourceQuestionId);
2709
2710 // duplicate question feeback
2711 $this->feedbackOBJ->duplicateFeedback($sourceQuestionId, $targetQuestionId);
2712
2713 // duplicate question hints
2714 $this->duplicateQuestionHints($sourceQuestionId, $targetQuestionId);
2715
2716 // duplicate skill assignments
2717 $this->duplicateSkillAssignments($sourceParentId, $sourceQuestionId, $targetParentId, $targetQuestionId);
2718 }
2719
2724 {
2725 global $DIC;
2726 $ilDB = $DIC['ilDB'];
2727 // delete the links in the qpl_sol_sug table
2728 $affectedRows = $ilDB->manipulateF(
2729 "DELETE FROM qpl_sol_sug WHERE question_fi = %s",
2730 array('integer'),
2731 array($this->getId())
2732 );
2733 // delete the links in the int_link table
2734 include_once "./Services/Link/classes/class.ilInternalLink.php";
2736 $this->suggested_solutions = array();
2738 }
2739
2747 public function getSuggestedSolution($subquestion_index = 0)
2748 {
2749 if (array_key_exists($subquestion_index, $this->suggested_solutions)) {
2750 return $this->suggested_solutions[$subquestion_index];
2751 } else {
2752 return array();
2753 }
2754 }
2755
2764 public function getSuggestedSolutionTitle($subquestion_index = 0)
2765 {
2766 if (array_key_exists($subquestion_index, $this->suggested_solutions)) {
2767 $title = $this->suggested_solutions[$subquestion_index]["internal_link"];
2768 // TO DO: resolve internal link an get link type and title
2769 } else {
2770 $title = "";
2771 }
2772 return $title;
2773 }
2774
2784 public function setSuggestedSolution($solution_id = "", $subquestion_index = 0, $is_import = false)
2785 {
2786 if (strcmp($solution_id, "") != 0) {
2787 $import_id = "";
2788 if ($is_import) {
2789 $import_id = $solution_id;
2790 $solution_id = $this->_resolveInternalLink($import_id);
2791 }
2792 $this->suggested_solutions[$subquestion_index] = array(
2793 "internal_link" => $solution_id,
2794 "import_id" => $import_id
2795 );
2796 }
2797 }
2798
2802 protected function duplicateSuggestedSolutionFiles($parent_id, $question_id)
2803 {
2804 global $DIC;
2805 $ilLog = $DIC['ilLog'];
2806
2807 foreach ($this->suggested_solutions as $index => $solution) {
2808 if (strcmp($solution["type"], "file") == 0) {
2809 $filepath = $this->getSuggestedSolutionPath();
2810 $filepath_original = str_replace(
2811 "/{$this->obj_id}/{$this->id}/solution",
2812 "/$parent_id/$question_id/solution",
2813 $filepath
2814 );
2815 if (!file_exists($filepath)) {
2816 ilUtil::makeDirParents($filepath);
2817 }
2818 $filename = $solution["value"]["name"];
2819 if (strlen($filename)) {
2820 if (!copy($filepath_original . $filename, $filepath . $filename)) {
2821 $ilLog->write("File could not be duplicated!!!!", $ilLog->ERROR);
2822 $ilLog->write("object: " . print_r($this, true), $ilLog->ERROR);
2823 }
2824 }
2825 }
2826 }
2827 }
2828
2833 {
2834 global $DIC;
2835 $ilLog = $DIC['ilLog'];
2836
2837 $filepath = $this->getSuggestedSolutionPath();
2838 $filepath_original = str_replace("/$this->id/solution", "/$original_id/solution", $filepath);
2839 ilUtil::delDir($filepath_original);
2840 foreach ($this->suggested_solutions as $index => $solution) {
2841 if (strcmp($solution["type"], "file") == 0) {
2842 if (!file_exists($filepath_original)) {
2843 ilUtil::makeDirParents($filepath_original);
2844 }
2845 $filename = $solution["value"]["name"];
2846 if (strlen($filename)) {
2847 if (!@copy($filepath . $filename, $filepath_original . $filename)) {
2848 $ilLog->write("File could not be duplicated!!!!", $ilLog->ERROR);
2849 $ilLog->write("object: " . print_r($this, true), $ilLog->ERROR);
2850 }
2851 }
2852 }
2853 }
2854 }
2855
2856 protected function copySuggestedSolutionFiles($source_questionpool_id, $source_question_id)
2857 {
2858 global $DIC;
2859 $ilLog = $DIC['ilLog'];
2860
2861 foreach ($this->suggested_solutions as $index => $solution) {
2862 if (strcmp($solution["type"], "file") == 0) {
2863 $filepath = $this->getSuggestedSolutionPath();
2864 $filepath_original = str_replace("/$this->obj_id/$this->id/solution", "/$source_questionpool_id/$source_question_id/solution", $filepath);
2865 if (!file_exists($filepath)) {
2866 ilUtil::makeDirParents($filepath);
2867 }
2868 $filename = $solution["value"]["name"];
2869 if (strlen($filename)) {
2870 if (!copy($filepath_original . $filename, $filepath . $filename)) {
2871 $ilLog->write("File could not be copied!!!!", $ilLog->ERROR);
2872 $ilLog->write("object: " . print_r($this, true), $ilLog->ERROR);
2873 }
2874 }
2875 }
2876 }
2877 }
2878
2882 public function updateSuggestedSolutions($original_id = "")
2883 {
2884 global $DIC;
2885 $ilDB = $DIC['ilDB'];
2886
2887 $id = (strlen($original_id) && is_numeric($original_id)) ? $original_id : $this->getId();
2888 include_once "./Services/Link/classes/class.ilInternalLink.php";
2889 $affectedRows = $ilDB->manipulateF(
2890 "DELETE FROM qpl_sol_sug WHERE question_fi = %s",
2891 array('integer'),
2892 array($id)
2893 );
2895 include_once("./Services/RTE/classes/class.ilRTE.php");
2896 foreach ($this->suggested_solutions as $index => $solution) {
2897 $next_id = $ilDB->nextId('qpl_sol_sug');
2899 $ilDB->insert(
2900 'qpl_sol_sug',
2901 array(
2902 'suggested_solution_id' => array( 'integer', $next_id ),
2903 'question_fi' => array( 'integer', $id ),
2904 'type' => array( 'text', $solution['type'] ),
2905 'value' => array( 'clob', ilRTE::_replaceMediaObjectImageSrc((is_array($solution['value'])) ? serialize($solution[ 'value' ]) : $solution['value'], 0) ),
2906 'internal_link' => array( 'text', $solution['internal_link'] ),
2907 'import_id' => array( 'text', null ),
2908 'subquestion_index' => array( 'integer', $index ),
2909 'tstamp' => array( 'integer', time() ),
2910 )
2911 );
2912 if (preg_match("/il_(\d*?)_(\w+)_(\d+)/", $solution["internal_link"], $matches)) {
2913 ilInternalLink::_saveLink("qst", $id, $matches[2], $matches[3], $matches[1]);
2914 }
2915 }
2916 if (strlen($original_id) && is_numeric($original_id)) {
2918 }
2919 $this->cleanupMediaObjectUsage();
2920 }
2921
2931 public function saveSuggestedSolution($type, $solution_id = "", $subquestion_index = 0, $value = "")
2932 {
2933 global $DIC;
2934 $ilDB = $DIC['ilDB'];
2935
2936 $affectedRows = $ilDB->manipulateF(
2937 "DELETE FROM qpl_sol_sug WHERE question_fi = %s AND subquestion_index = %s",
2938 array("integer", "integer"),
2939 array(
2940 $this->getId(),
2941 $subquestion_index
2942 )
2943 );
2944
2945 $next_id = $ilDB->nextId('qpl_sol_sug');
2946 include_once("./Services/RTE/classes/class.ilRTE.php");
2948 $affectedRows = $ilDB->insert(
2949 'qpl_sol_sug',
2950 array(
2951 'suggested_solution_id' => array( 'integer', $next_id ),
2952 'question_fi' => array( 'integer', $this->getId() ),
2953 'type' => array( 'text', $type ),
2954 'value' => array( 'clob', ilRTE::_replaceMediaObjectImageSrc((is_array($value)) ? serialize($value) : $value, 0) ),
2955 'internal_link' => array( 'text', $solution_id ),
2956 'import_id' => array( 'text', null ),
2957 'subquestion_index' => array( 'integer', $subquestion_index ),
2958 'tstamp' => array( 'integer', time() ),
2959 )
2960 );
2961 if ($affectedRows == 1) {
2962 $this->suggested_solutions[$subquestion_index] = array(
2963 "type" => $type,
2964 "value" => $value,
2965 "internal_link" => $solution_id,
2966 "import_id" => ""
2967 );
2968 }
2969 $this->cleanupMediaObjectUsage();
2970 }
2971
2972 public function _resolveInternalLink($internal_link)
2973 {
2974 if (preg_match("/il_(\d+)_(\w+)_(\d+)/", $internal_link, $matches)) {
2975 include_once "./Services/Link/classes/class.ilInternalLink.php";
2976 include_once "./Modules/LearningModule/classes/class.ilLMObject.php";
2977 include_once "./Modules/Glossary/classes/class.ilGlossaryTerm.php";
2978 switch ($matches[2]) {
2979 case "lm":
2980 $resolved_link = ilLMObject::_getIdForImportId($internal_link);
2981 break;
2982 case "pg":
2983 $resolved_link = ilInternalLink::_getIdForImportId("PageObject", $internal_link);
2984 break;
2985 case "st":
2986 $resolved_link = ilInternalLink::_getIdForImportId("StructureObject", $internal_link);
2987 break;
2988 case "git":
2989 $resolved_link = ilInternalLink::_getIdForImportId("GlossaryItem", $internal_link);
2990 break;
2991 case "mob":
2992 $resolved_link = ilInternalLink::_getIdForImportId("MediaObject", $internal_link);
2993 break;
2994 }
2995 if (strcmp($resolved_link, "") == 0) {
2996 $resolved_link = $internal_link;
2997 }
2998 } else {
2999 $resolved_link = $internal_link;
3000 }
3001 return $resolved_link;
3002 }
3003
3004 public function _resolveIntLinks($question_id)
3005 {
3006 global $DIC;
3007 $ilDB = $DIC['ilDB'];
3008 $resolvedlinks = 0;
3009 $result = $ilDB->queryF(
3010 "SELECT * FROM qpl_sol_sug WHERE question_fi = %s",
3011 array('integer'),
3012 array($question_id)
3013 );
3014 if ($result->numRows()) {
3015 while ($row = $ilDB->fetchAssoc($result)) {
3016 $internal_link = $row["internal_link"];
3017 include_once "./Modules/TestQuestionPool/classes/class.assQuestion.php";
3018 $resolved_link = assQuestion::_resolveInternalLink($internal_link);
3019 if (strcmp($internal_link, $resolved_link) != 0) {
3020 // internal link was resolved successfully
3021 $affectedRows = $ilDB->manipulateF(
3022 "UPDATE qpl_sol_sug SET internal_link = %s WHERE suggested_solution_id = %s",
3023 array('text','integer'),
3024 array($resolved_link, $row["suggested_solution_id"])
3025 );
3026 $resolvedlinks++;
3027 }
3028 }
3029 }
3030 if ($resolvedlinks) {
3031 // there are resolved links -> reenter theses links to the database
3032
3033 // delete all internal links from the database
3034 include_once "./Services/Link/classes/class.ilInternalLink.php";
3035 ilInternalLink::_deleteAllLinksOfSource("qst", $question_id);
3036
3037 $result = $ilDB->queryF(
3038 "SELECT * FROM qpl_sol_sug WHERE question_fi = %s",
3039 array('integer'),
3040 array($question_id)
3041 );
3042 if ($result->numRows()) {
3043 while ($row = $ilDB->fetchAssoc($result)) {
3044 if (preg_match("/il_(\d*?)_(\w+)_(\d+)/", $row["internal_link"], $matches)) {
3045 ilInternalLink::_saveLink("qst", $question_id, $matches[2], $matches[3], $matches[1]);
3046 }
3047 }
3048 }
3049 }
3050 }
3051
3052 public static function _getInternalLinkHref($target = "")
3053 {
3054 global $DIC;
3055 $ilDB = $DIC['ilDB'];
3056 $linktypes = array(
3057 "lm" => "LearningModule",
3058 "pg" => "PageObject",
3059 "st" => "StructureObject",
3060 "git" => "GlossaryItem",
3061 "mob" => "MediaObject"
3062 );
3063 $href = "";
3064 if (preg_match("/il__(\w+)_(\d+)/", $target, $matches)) {
3065 $type = $matches[1];
3066 $target_id = $matches[2];
3067 include_once "./Services/Utilities/classes/class.ilUtil.php";
3068 switch ($linktypes[$matches[1]]) {
3069 case "LearningModule":
3070 $href = "./goto.php?target=" . $type . "_" . $target_id;
3071 break;
3072 case "PageObject":
3073 case "StructureObject":
3074 $href = "./goto.php?target=" . $type . "_" . $target_id;
3075 break;
3076 case "GlossaryItem":
3077 $href = "./goto.php?target=" . $type . "_" . $target_id;
3078 break;
3079 case "MediaObject":
3080 $href = "./ilias.php?baseClass=ilLMPresentationGUI&obj_type=" . $linktypes[$type] . "&cmd=media&ref_id=" . $_GET["ref_id"] . "&mob_id=" . $target_id;
3081 break;
3082 }
3083 }
3084 return $href;
3085 }
3086
3094 public static function _getOriginalId($question_id)
3095 {
3096 global $DIC;
3097 $ilDB = $DIC['ilDB'];
3098 $result = $ilDB->queryF(
3099 "SELECT * FROM qpl_questions WHERE question_id = %s",
3100 array('integer'),
3101 array($question_id)
3102 );
3103 if ($result->numRows() > 0) {
3104 $row = $ilDB->fetchAssoc($result);
3105 if ($row["original_id"] > 0) {
3106 return $row["original_id"];
3107 } else {
3108 return $row["question_id"];
3109 }
3110 } else {
3111 return "";
3112 }
3113 }
3114
3115 public static function originalQuestionExists($questionId)
3116 {
3117 global $DIC;
3118 $ilDB = $DIC['ilDB'];
3119
3120 $query = "
3121 SELECT COUNT(dupl.question_id) cnt
3122 FROM qpl_questions dupl
3123 INNER JOIN qpl_questions orig
3124 ON orig.question_id = dupl.original_id
3125 WHERE dupl.question_id = %s
3126 ";
3127
3128 $res = $ilDB->queryF($query, array('integer'), array($questionId));
3129 $row = $ilDB->fetchAssoc($res);
3130
3131 return $row['cnt'] > 0;
3132 }
3133
3134 public function syncWithOriginal()
3135 {
3136 global $DIC;
3137 $ilDB = $DIC['ilDB'];
3138
3139 if (!$this->getOriginalId()) {
3140 return;
3141 }
3142
3143 $originalObjId = self::lookupOriginalParentObjId($this->getOriginalId());
3144
3145 if (!$originalObjId) {
3146 return;
3147 }
3148
3149 $id = $this->getId();
3150 $objId = $this->getObjId();
3151 $original = $this->getOriginalId();
3152
3153 $this->beforeSyncWithOriginal($original, $id, $originalObjId, $objId);
3154
3155 $this->setId($original);
3156 $this->setOriginalId(null);
3157 $this->setObjId($originalObjId);
3158
3159 $this->saveToDb();
3160
3161 $this->deletePageOfQuestion($original);
3162 $this->createPageObject();
3163 $this->copyPageOfQuestion($id);
3164
3165 $this->setId($id);
3166 $this->setOriginalId($original);
3167 $this->setObjId($objId);
3168
3169 $this->updateSuggestedSolutions($original);
3171
3172 $this->afterSyncWithOriginal($original, $id, $originalObjId, $objId);
3173 $this->syncHints();
3174 }
3175
3176 public function createRandomSolution($test_id, $user_id)
3177 {
3178 }
3179
3187 public function _questionExists($question_id)
3188 {
3189 global $DIC;
3190 $ilDB = $DIC['ilDB'];
3191
3192 if ($question_id < 1) {
3193 return false;
3194 }
3195
3196 $result = $ilDB->queryF(
3197 "SELECT question_id FROM qpl_questions WHERE question_id = %s",
3198 array('integer'),
3199 array($question_id)
3200 );
3201 if ($result->numRows() == 1) {
3202 return true;
3203 } else {
3204 return false;
3205 }
3206 }
3207
3215 public function _questionExistsInPool($question_id)
3216 {
3217 global $DIC;
3218 $ilDB = $DIC['ilDB'];
3219
3220 if ($question_id < 1) {
3221 return false;
3222 }
3223
3224 $result = $ilDB->queryF(
3225 "SELECT question_id FROM qpl_questions INNER JOIN object_data ON obj_fi = obj_id WHERE question_id = %s AND type = 'qpl'",
3226 array('integer'),
3227 array($question_id)
3228 );
3229 if ($result->numRows() == 1) {
3230 return true;
3231 } else {
3232 return false;
3233 }
3234 }
3235
3243 public static function _instanciateQuestion($question_id)
3244 {
3245 return self::_instantiateQuestion($question_id);
3246 }
3247
3252 public static function _instantiateQuestion($question_id)
3253 {
3254 global $DIC;
3255 $ilCtrl = $DIC['ilCtrl'];
3256 $ilDB = $DIC['ilDB'];
3257 $lng = $DIC['lng'];
3258
3259 if (strcmp($question_id, "") != 0) {
3260 $question_type = assQuestion::_getQuestionType($question_id);
3261 if (!strlen($question_type)) {
3262 return null;
3263 }
3264 assQuestion::_includeClass($question_type);
3265 $objectClassname = self::getObjectClassNameByQuestionType($question_type);
3266 $question = new $objectClassname();
3267 $question->loadFromDb($question_id);
3268
3269 $feedbackObjectClassname = self::getFeedbackClassNameByQuestionType($question_type);
3270 $question->feedbackOBJ = new $feedbackObjectClassname($question, $ilCtrl, $ilDB, $lng);
3271
3272 return $question;
3273 }
3274 }
3275
3282 public function getPoints()
3283 {
3284 if (strcmp($this->points, "") == 0) {
3285 return 0;
3286 } else {
3287 return $this->points;
3288 }
3289 }
3290
3291
3298 public function setPoints($a_points)
3299 {
3300 $this->points = $a_points;
3301 }
3302
3309 public function getSolutionMaxPass($active_id)
3310 {
3311 return self::_getSolutionMaxPass($this->getId(), $active_id);
3312 }
3313
3320 public static function _getSolutionMaxPass($question_id, $active_id)
3321 {
3322 /* include_once "./Modules/Test/classes/class.ilObjTest.php";
3323 $pass = ilObjTest::_getPass($active_id);
3324 return $pass;*/
3325
3326 // the following code was the old solution which added the non answered
3327 // questions of a pass from the answered questions of the previous pass
3328 // with the above solution, only the answered questions of the last pass are counted
3329 global $DIC;
3330 $ilDB = $DIC['ilDB'];
3331
3332 $result = $ilDB->queryF(
3333 "SELECT MAX(pass) maxpass FROM tst_test_result WHERE active_fi = %s AND question_fi = %s",
3334 array('integer','integer'),
3335 array($active_id, $question_id)
3336 );
3337 if ($result->numRows() == 1) {
3338 $row = $ilDB->fetchAssoc($result);
3339 return $row["maxpass"];
3340 } else {
3341 return 0;
3342 }
3343 }
3344
3353 public static function _isWriteable($question_id, $user_id)
3354 {
3355 global $DIC;
3356 $ilDB = $DIC['ilDB'];
3357
3358 if (($question_id < 1) || ($user_id < 1)) {
3359 return false;
3360 }
3361
3362 $result = $ilDB->queryF(
3363 "SELECT obj_fi FROM qpl_questions WHERE question_id = %s",
3364 array('integer'),
3365 array($question_id)
3366 );
3367 if ($result->numRows() == 1) {
3368 $row = $ilDB->fetchAssoc($result);
3369 $qpl_object_id = $row["obj_fi"];
3370 include_once "./Modules/TestQuestionPool/classes/class.ilObjQuestionPool.php";
3371 return ilObjQuestionPool::_isWriteable($qpl_object_id, $user_id);
3372 } else {
3373 return false;
3374 }
3375 }
3376
3383 public static function _isUsedInRandomTest($question_id = "")
3384 {
3385 global $DIC;
3386 $ilDB = $DIC['ilDB'];
3387
3388 if ($question_id < 1) {
3389 return 0;
3390 }
3391 $result = $ilDB->queryF(
3392 "SELECT test_random_question_id FROM tst_test_rnd_qst WHERE question_fi = %s",
3393 array('integer'),
3394 array($question_id)
3395 );
3396 return $result->numRows();
3397 }
3398
3410 abstract public function calculateReachedPoints($active_id, $pass = null, $authorizedSolution = true, $returndetails = false);
3411
3412 public function deductHintPointsFromReachedPoints(ilAssQuestionPreviewSession $previewSession, $reachedPoints)
3413 {
3414 global $DIC;
3415
3416 $hintTracking = new ilAssQuestionPreviewHintTracking($DIC->database(), $previewSession);
3417 $requestsStatisticData = $hintTracking->getRequestStatisticData();
3418 $reachedPoints = $reachedPoints - $requestsStatisticData->getRequestsPoints();
3419
3420 return $reachedPoints;
3421 }
3422
3424 {
3425 $reachedPoints = $this->calculateReachedPointsForSolution($previewSession->getParticipantsSolution());
3426 $reachedPoints = $this->deductHintPointsFromReachedPoints($previewSession, $reachedPoints);
3427
3428 return $this->ensureNonNegativePoints($reachedPoints);
3429 }
3430
3432 {
3433 return $points > 0 ? $points : 0;
3434 }
3435
3437 {
3438 $reachedPoints = $this->calculateReachedPointsFromPreviewSession($previewSession);
3439
3440 if ($reachedPoints < $this->getMaximumPoints()) {
3441 return false;
3442 }
3443
3444 return true;
3445 }
3446
3447
3458 final public function adjustReachedPointsByScoringOptions($points, $active_id, $pass = null)
3459 {
3460 include_once "./Modules/Test/classes/class.ilObjTest.php";
3461 $count_system = ilObjTest::_getCountSystem($active_id);
3462 if ($count_system == 1) {
3463 if (abs($this->getMaximumPoints() - $points) > 0.0000000001) {
3464 $points = 0;
3465 }
3466 }
3467 $score_cutting = ilObjTest::_getScoreCutting($active_id);
3468 if ($score_cutting == 0) {
3469 if ($points < 0) {
3470 $points = 0;
3471 }
3472 }
3473 return $points;
3474 }
3475
3484 public static function _isWorkedThrough($active_id, $question_id, $pass = null)
3485 {
3486 return self::lookupResultRecordExist($active_id, $question_id, $pass);
3487
3488 // oldschool "workedthru"
3489
3490 global $DIC;
3491 $ilDB = $DIC['ilDB'];
3492
3493 $points = 0;
3494 if (is_null($pass)) {
3495 include_once "./Modules/TestQuestionPool/classes/class.assQuestion.php";
3496 $pass = assQuestion::_getSolutionMaxPass($question_id, $active_id);
3497 }
3498 $result = $ilDB->queryF(
3499 "SELECT solution_id FROM tst_solutions WHERE active_fi = %s AND question_fi = %s AND pass = %s",
3500 array('integer','integer','integer'),
3501 array($active_id, $question_id, $pass)
3502 );
3503 if ($result->numRows()) {
3504 return true;
3505 } else {
3506 return false;
3507 }
3508 }
3509
3517 public static function _areAnswered($a_user_id, $a_question_ids)
3518 {
3519 global $DIC;
3520 $ilDB = $DIC['ilDB'];
3521
3522 $res = $ilDB->queryF(
3523 "SELECT DISTINCT(question_fi) FROM tst_test_result JOIN tst_active " .
3524 "ON (active_id = active_fi) " .
3525 "WHERE " . $ilDB->in('question_fi', $a_question_ids, false, 'integer') .
3526 " AND user_fi = %s",
3527 array('integer'),
3528 array($a_user_id)
3529 );
3530 return ($res->numRows() == count($a_question_ids)) ? true : false;
3531 }
3532
3541 public function isHTML($a_text)
3542 {
3543 return ilUtil::isHTML($a_text);
3544 }
3545
3552 public function prepareTextareaOutput($txt_output, $prepare_for_latex_output = false, $omitNl2BrWhenTextArea = false)
3553 {
3554 include_once "./Services/Utilities/classes/class.ilUtil.php";
3555 return ilUtil::prepareTextareaOutput($txt_output, $prepare_for_latex_output, $omitNl2BrWhenTextArea);
3556 }
3557
3565 public function QTIMaterialToString($a_material)
3566 {
3567 $result = "";
3568 for ($i = 0; $i < $a_material->getMaterialCount(); $i++) {
3569 $material = $a_material->getMaterial($i);
3570 if (strcmp($material["type"], "mattext") == 0) {
3571 $result .= $material["material"]->getContent();
3572 }
3573 if (strcmp($material["type"], "matimage") == 0) {
3574 $matimage = $material["material"];
3575 if (preg_match("/(il_([0-9]+)_mob_([0-9]+))/", $matimage->getLabel(), $matches)) {
3576 // import an mediaobject which was inserted using tiny mce
3577 if (!is_array($_SESSION["import_mob_xhtml"])) {
3578 $_SESSION["import_mob_xhtml"] = array();
3579 }
3580 array_push($_SESSION["import_mob_xhtml"], array("mob" => $matimage->getLabel(), "uri" => $matimage->getUri()));
3581 }
3582 }
3583 }
3584 return $result;
3585 }
3586
3595 public function addQTIMaterial(&$a_xml_writer, $a_material, $close_material_tag = true, $add_mobs = true)
3596 {
3597 include_once "./Services/RTE/classes/class.ilRTE.php";
3598 include_once("./Services/MediaObjects/classes/class.ilObjMediaObject.php");
3599
3600 $a_xml_writer->xmlStartTag("material");
3601 $attrs = array(
3602 "texttype" => "text/plain"
3603 );
3604 if ($this->isHTML($a_material)) {
3605 $attrs["texttype"] = "text/xhtml";
3606 }
3607 $a_xml_writer->xmlElement("mattext", $attrs, ilRTE::_replaceMediaObjectImageSrc($a_material, 0));
3608 if ($add_mobs) {
3609 $mobs = ilObjMediaObject::_getMobsOfObject("qpl:html", $this->getId());
3610 foreach ($mobs as $mob) {
3611 $moblabel = "il_" . IL_INST_ID . "_mob_" . $mob;
3612 if (strpos($a_material, "mm_$mob") !== false) {
3613 if (ilObjMediaObject::_exists($mob)) {
3614 $mob_obj = new ilObjMediaObject($mob);
3615 $imgattrs = array(
3616 "label" => $moblabel,
3617 "uri" => "objects/" . "il_" . IL_INST_ID . "_mob_" . $mob . "/" . $mob_obj->getTitle()
3618 );
3619 }
3620 $a_xml_writer->xmlElement("matimage", $imgattrs, null);
3621 }
3622 }
3623 }
3624 if ($close_material_tag) {
3625 $a_xml_writer->xmlEndTag("material");
3626 }
3627 }
3628
3629 public function buildHashedImageFilename($plain_image_filename, $unique = false)
3630 {
3631 $extension = "";
3632
3633 if (preg_match("/.*\.(png|jpg|gif|jpeg)$/i", $plain_image_filename, $matches)) {
3634 $extension = "." . $matches[1];
3635 }
3636
3637 if ($unique) {
3638 $plain_image_filename = uniqid($plain_image_filename . microtime(true));
3639 }
3640
3641 $hashed_filename = md5($plain_image_filename) . $extension;
3642
3643 return $hashed_filename;
3644 }
3645
3656 public static function _setReachedPoints($active_id, $question_id, $points, $maxpoints, $pass, $manualscoring, $obligationsEnabled)
3657 {
3658 global $DIC;
3659 $ilDB = $DIC['ilDB'];
3660
3661 if ($points <= $maxpoints) {
3662 if (is_null($pass)) {
3663 $pass = assQuestion::_getSolutionMaxPass($question_id, $active_id);
3664 }
3665
3666 // retrieve the already given points
3667 $old_points = 0;
3668 $result = $ilDB->queryF(
3669 "SELECT points FROM tst_test_result WHERE active_fi = %s AND question_fi = %s AND pass = %s",
3670 array('integer','integer','integer'),
3671 array($active_id, $question_id, $pass)
3672 );
3673 $manual = ($manualscoring) ? 1 : 0;
3674 $rowsnum = $result->numRows();
3675 if ($rowsnum) {
3676 $row = $ilDB->fetchAssoc($result);
3677 $old_points = $row["points"];
3678 if ($old_points != $points) {
3679 $affectedRows = $ilDB->manipulateF(
3680 "UPDATE tst_test_result SET points = %s, manual = %s, tstamp = %s WHERE active_fi = %s AND question_fi = %s AND pass = %s",
3681 array('float', 'integer', 'integer', 'integer', 'integer', 'integer'),
3682 array($points, $manual, time(), $active_id, $question_id, $pass)
3683 );
3684 }
3685 } else {
3686 $next_id = $ilDB->nextId('tst_test_result');
3687 $affectedRows = $ilDB->manipulateF(
3688 "INSERT INTO tst_test_result (test_result_id, active_fi, question_fi, points, pass, manual, tstamp) VALUES (%s, %s, %s, %s, %s, %s, %s)",
3689 array('integer', 'integer','integer', 'float', 'integer', 'integer','integer'),
3690 array($next_id, $active_id, $question_id, $points, $pass, $manual, time())
3691 );
3692 }
3693
3694 if (self::isForcePassResultUpdateEnabled() || $old_points != $points || !$rowsnum) {
3695 assQuestion::_updateTestPassResults($active_id, $pass, $obligationsEnabled);
3696 // finally update objective result
3697 include_once "./Modules/Test/classes/class.ilObjTest.php";
3698 include_once './Modules/Course/classes/class.ilCourseObjectiveResult.php';
3700
3701 include_once("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
3703 global $DIC;
3704 $lng = $DIC['lng'];
3705 $ilUser = $DIC['ilUser'];
3706 include_once "./Modules/Test/classes/class.ilObjTestAccess.php";
3707 $username = ilObjTestAccess::_getParticipantData($active_id);
3708 assQuestion::logAction(sprintf($lng->txtlng("assessment", "log_answer_changed_points", ilObjAssessmentFolder::_getLogLanguage()), $username, $old_points, $points, $ilUser->getFullname() . " (" . $ilUser->getLogin() . ")"), $active_id, $question_id);
3709 }
3710 }
3711
3712 return true;
3713 } else {
3714 return false;
3715 }
3716 }
3717
3725 public function getQuestion()
3726 {
3727 return $this->question;
3728 }
3729
3737 public function setQuestion($question = "")
3738 {
3739 $this->question = $question;
3740 }
3741
3747 abstract public function getQuestionType();
3748
3757 public function getQuestionTypeID()
3758 {
3759 global $DIC;
3760 $ilDB = $DIC['ilDB'];
3761
3762 $result = $ilDB->queryF(
3763 "SELECT question_type_id FROM qpl_qst_type WHERE type_tag = %s",
3764 array('text'),
3765 array($this->getQuestionType())
3766 );
3767 if ($result->numRows() == 1) {
3768 $row = $ilDB->fetchAssoc($result);
3769 return $row["question_type_id"];
3770 }
3771 return 0;
3772 }
3773
3774 public function syncHints()
3775 {
3776 global $DIC;
3777 $ilDB = $DIC['ilDB'];
3778
3779 // delete hints of the original
3780 $ilDB->manipulateF(
3781 "DELETE FROM qpl_hints WHERE qht_question_fi = %s",
3782 array('integer'),
3783 array($this->original_id)
3784 );
3785
3786 // get hints of the actual question
3787 $result = $ilDB->queryF(
3788 "SELECT * FROM qpl_hints WHERE qht_question_fi = %s",
3789 array('integer'),
3790 array($this->getId())
3791 );
3792
3793 // save hints to the original
3794 if ($result->numRows()) {
3795 while ($row = $ilDB->fetchAssoc($result)) {
3796 $next_id = $ilDB->nextId('qpl_hints');
3798 $ilDB->insert(
3799 'qpl_hints',
3800 array(
3801 'qht_hint_id' => array('integer', $next_id),
3802 'qht_question_fi' => array('integer', $this->original_id),
3803 'qht_hint_index' => array('integer', $row["qht_hint_index"]),
3804 'qht_hint_points' => array('integer', $row["qht_hint_points"]),
3805 'qht_hint_text' => array('text', $row["qht_hint_text"]),
3806 )
3807 );
3808 }
3809 }
3810 }
3811
3816 protected function getRTETextWithMediaObjects()
3817 {
3818 // must be called in parent classes. add additional RTE text in the parent
3819 // classes and call this method to add the standard RTE text
3820 $collected = $this->getQuestion();
3821 $collected .= $this->feedbackOBJ->getGenericFeedbackContent($this->getId(), false);
3822 $collected .= $this->feedbackOBJ->getGenericFeedbackContent($this->getId(), true);
3823 $collected .= $this->feedbackOBJ->getAllSpecificAnswerFeedbackContents($this->getId());
3824
3825 foreach ($this->suggested_solutions as $solution_array) {
3826 $collected .= $solution_array["value"];
3827 }
3828
3829 require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionHintList.php';
3830 $questionHintList = ilAssQuestionHintList::getListByQuestionId($this->getId());
3831 foreach ($questionHintList as $questionHint) {
3832 /* @var $questionHint ilAssQuestionHint */
3833 $collected .= $questionHint->getText();
3834 }
3835
3836 return $collected;
3837 }
3838
3843 public function cleanupMediaObjectUsage()
3844 {
3845 $combinedtext = $this->getRTETextWithMediaObjects();
3846 include_once("./Services/RTE/classes/class.ilRTE.php");
3847 ilRTE::_cleanupMediaObjectUsage($combinedtext, "qpl:html", $this->getId());
3848 }
3849
3855 public function &getInstances()
3856 {
3857 global $DIC;
3858 $ilDB = $DIC['ilDB'];
3859
3860 $result = $ilDB->queryF(
3861 "SELECT question_id FROM qpl_questions WHERE original_id = %s",
3862 array("integer"),
3863 array($this->getId())
3864 );
3865 $instances = array();
3866 $ids = array();
3867 while ($row = $ilDB->fetchAssoc($result)) {
3868 array_push($ids, $row["question_id"]);
3869 }
3870 foreach ($ids as $question_id) {
3871 // check non random tests
3872 $result = $ilDB->queryF(
3873 "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",
3874 array("integer"),
3875 array($question_id)
3876 );
3877 while ($row = $ilDB->fetchAssoc($result)) {
3878 $instances[$row['obj_fi']] = ilObject::_lookupTitle($row['obj_fi']);
3879 }
3880 // check random tests
3881 $result = $ilDB->queryF(
3882 "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",
3883 array("integer"),
3884 array($question_id)
3885 );
3886 while ($row = $ilDB->fetchAssoc($result)) {
3887 $instances[$row['obj_fi']] = ilObject::_lookupTitle($row['obj_fi']);
3888 }
3889 }
3890 include_once "./Modules/Test/classes/class.ilObjTest.php";
3891 foreach ($instances as $key => $value) {
3892 $instances[$key] = array("obj_id" => $key, "title" => $value, "author" => ilObjTest::_lookupAuthor($key), "refs" => ilObject::_getAllReferences($key));
3893 }
3894 return $instances;
3895 }
3896
3897 public static function _needsManualScoring($question_id)
3898 {
3899 include_once "./Modules/Test/classes/class.ilObjAssessmentFolder.php";
3901 $questiontype = assQuestion::_getQuestionType($question_id);
3902 if (in_array($questiontype, $scoring)) {
3903 return true;
3904 } else {
3905 return false;
3906 }
3907 }
3908
3916 public function getActiveUserData($active_id)
3917 {
3918 global $DIC;
3919 $ilDB = $DIC['ilDB'];
3920 $result = $ilDB->queryF(
3921 "SELECT * FROM tst_active WHERE active_id = %s",
3922 array('integer'),
3923 array($active_id)
3924 );
3925 if ($result->numRows()) {
3926 $row = $ilDB->fetchAssoc($result);
3927 return array("user_id" => $row["user_fi"], "test_id" => $row["test_fi"]);
3928 } else {
3929 return array();
3930 }
3931 }
3932
3940 public static function _includeClass($question_type, $gui = 0)
3941 {
3942 if (self::isCoreQuestionType($question_type)) {
3943 self::includeCoreClass($question_type, $gui);
3944 } else {
3945 self::includePluginClass($question_type, $gui);
3946 }
3947 }
3948
3949 public static function getGuiClassNameByQuestionType($questionType)
3950 {
3951 return $questionType . 'GUI';
3952 }
3953
3954 public static function getObjectClassNameByQuestionType($questionType)
3955 {
3956 return $questionType;
3957 }
3958
3959 public static function getFeedbackClassNameByQuestionType($questionType)
3960 {
3961 return str_replace('ass', 'ilAss', $questionType) . 'Feedback';
3962 }
3963
3964 public static function isCoreQuestionType($questionType)
3965 {
3966 $guiClassName = self::getGuiClassNameByQuestionType($questionType);
3967 return file_exists("Modules/TestQuestionPool/classes/class.{$guiClassName}.php");
3968 }
3969
3970 public static function includeCoreClass($questionType, $withGuiClass)
3971 {
3972 if ($withGuiClass) {
3973 $guiClassName = self::getGuiClassNameByQuestionType($questionType);
3974 require_once "Modules/TestQuestionPool/classes/class.{$guiClassName}.php";
3975
3976 // object class is included by gui classes constructor
3977 } else {
3978 $objectClassName = self::getObjectClassNameByQuestionType($questionType);
3979 require_once "Modules/TestQuestionPool/classes/class.{$objectClassName}.php";
3980 }
3981
3982 $feedbackClassName = self::getFeedbackClassNameByQuestionType($questionType);
3983 require_once "Modules/TestQuestionPool/classes/feedback/class.{$feedbackClassName}.php";
3984 }
3985
3986 public static function includePluginClass($questionType, $withGuiClass)
3987 {
3988 global $DIC;
3989 $ilPluginAdmin = $DIC['ilPluginAdmin'];
3990
3991 $classes = array(
3992 self::getObjectClassNameByQuestionType($questionType),
3993 self::getFeedbackClassNameByQuestionType($questionType)
3994 );
3995
3996 if ($withGuiClass) {
3997 $classes[] = self::getGuiClassNameByQuestionType($questionType);
3998 }
3999
4000 $pl_names = $ilPluginAdmin->getActivePluginsForSlot(IL_COMP_MODULE, "TestQuestionPool", "qst");
4001 foreach ($pl_names as $pl_name) {
4002 $pl = ilPlugin::getPluginObject(IL_COMP_MODULE, "TestQuestionPool", "qst", $pl_name);
4003 if (strcmp($pl->getQuestionType(), $questionType) == 0) {
4004 foreach ($classes as $class) {
4005 $pl->includeClass("class.{$class}.php");
4006 }
4007
4008 break;
4009 }
4010 }
4011 }
4012
4019 public static function _getQuestionTypeName($type_tag)
4020 {
4021 if (file_exists("./Modules/TestQuestionPool/classes/class." . $type_tag . ".php")) {
4022 global $DIC;
4023 $lng = $DIC['lng'];
4024 return $lng->txt($type_tag);
4025 } else {
4026 global $DIC;
4027 $ilPluginAdmin = $DIC['ilPluginAdmin'];
4028 $pl_names = $ilPluginAdmin->getActivePluginsForSlot(IL_COMP_MODULE, "TestQuestionPool", "qst");
4029 foreach ($pl_names as $pl_name) {
4030 $pl = ilPlugin::getPluginObject(IL_COMP_MODULE, "TestQuestionPool", "qst", $pl_name);
4031 if (strcmp($pl->getQuestionType(), $type_tag) == 0) {
4032 return $pl->getQuestionTypeTranslation();
4033 }
4034 }
4035 }
4036 return "";
4037 }
4038
4048 public static function &_instanciateQuestionGUI($question_id)
4049 {
4050 return self::instantiateQuestionGUI($question_id);
4051 }
4052
4060 public static function instantiateQuestionGUI($a_question_id)
4061 {
4062 global $DIC;
4063 $ilCtrl = $DIC['ilCtrl'];
4064 $ilDB = $DIC['ilDB'];
4065 $lng = $DIC['lng'];
4066 $ilUser = $DIC['ilUser'];
4067
4068 if (strcmp($a_question_id, "") != 0) {
4069 $question_type = assQuestion::_getQuestionType($a_question_id);
4070
4071 assQuestion::_includeClass($question_type, 1);
4072
4073 $question_type_gui = self::getGuiClassNameByQuestionType($question_type);
4074 $question_gui = new $question_type_gui();
4075 $question_gui->object->loadFromDb($a_question_id);
4076
4077 $feedbackObjectClassname = self::getFeedbackClassNameByQuestionType($question_type);
4078 $question_gui->object->feedbackOBJ = new $feedbackObjectClassname($question_gui->object, $ilCtrl, $ilDB, $lng);
4079
4080 $assSettings = new ilSetting('assessment');
4081 require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionProcessLockerFactory.php';
4082 $processLockerFactory = new ilAssQuestionProcessLockerFactory($assSettings, $ilDB);
4083 $processLockerFactory->setQuestionId($question_gui->object->getId());
4084 $processLockerFactory->setUserId($ilUser->getId());
4085 include_once("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
4086 $processLockerFactory->setAssessmentLogEnabled(ilObjAssessmentFolder::_enabledAssessmentLogging());
4087 $question_gui->object->setProcessLocker($processLockerFactory->getLocker());
4088 } else {
4089 global $DIC;
4090 $ilLog = $DIC['ilLog'];
4091 $ilLog->write('Instantiate question called without question id. (instantiateQuestionGUI@assQuestion)', $ilLog->WARNING);
4092 return null;
4093 }
4094 return $question_gui;
4095 }
4096
4107 public function setExportDetailsXLS($worksheet, $startrow, $active_id, $pass)
4108 {
4109 $worksheet->setFormattedExcelTitle($worksheet->getColumnCoord(0) . $startrow, $this->lng->txt($this->getQuestionType()));
4110 $worksheet->setFormattedExcelTitle($worksheet->getColumnCoord(1) . $startrow, $this->getTitle());
4111
4112 return $startrow;
4113 }
4114
4118 public function __get($value)
4119 {
4120 switch ($value) {
4121 case "id":
4122 return $this->getId();
4123 break;
4124 case "title":
4125 return $this->getTitle();
4126 break;
4127 case "comment":
4128 return $this->getComment();
4129 break;
4130 case "owner":
4131 return $this->getOwner();
4132 break;
4133 case "author":
4134 return $this->getAuthor();
4135 break;
4136 case "question":
4137 return $this->getQuestion();
4138 break;
4139 case "points":
4140 return $this->getPoints();
4141 break;
4142 case "est_working_time":
4143 return $this->getEstimatedWorkingTime();
4144 break;
4145 case "shuffle":
4146 return $this->getShuffle();
4147 break;
4148 case "test_id":
4149 return $this->getTestId();
4150 break;
4151 case "obj_id":
4152 return $this->getObjId();
4153 break;
4154 case "ilias":
4155 return $this->ilias;
4156 break;
4157 case "tpl":
4158 return $this->tpl;
4159 break;
4160 case "page":
4161 return $this->page;
4162 break;
4163 case "outputType":
4164 return $this->getOutputType();
4165 break;
4166 case "suggested_solutions":
4167 return $this->getSuggestedSolutions();
4168 break;
4169 case "original_id":
4170 return $this->getOriginalId();
4171 break;
4172 default:
4173 if (array_key_exists($value, $this->arrData)) {
4174 return $this->arrData[$value];
4175 } else {
4176 return null;
4177 }
4178 break;
4179 }
4180 }
4181
4185 public function __set($key, $value)
4186 {
4187 switch ($key) {
4188 case "id":
4189 $this->setId($value);
4190 break;
4191 case "title":
4192 $this->setTitle($value);
4193 break;
4194 case "comment":
4195 $this->setComment($value);
4196 break;
4197 case "owner":
4198 $this->setOwner($value);
4199 break;
4200 case "author":
4201 $this->setAuthor($value);
4202 break;
4203 case "question":
4204 $this->setQuestion($value);
4205 break;
4206 case "points":
4207 $this->setPoints($value);
4208 break;
4209 case "est_working_time":
4210 if (is_array($value)) {
4211 $this->setEstimatedWorkingTime($value["h"], $value["m"], $value["s"]);
4212 }
4213 break;
4214 case "shuffle":
4215 $this->setShuffle($value);
4216 break;
4217 case "test_id":
4218 $this->setTestId($value);
4219 break;
4220 case "obj_id":
4221 $this->setObjId($value);
4222 break;
4223 case "outputType":
4224 $this->setOutputType($value);
4225 break;
4226 case "original_id":
4227 $this->setOriginalId($value);
4228 break;
4229 case "page":
4230 $this->page = &$value;
4231 break;
4232 default:
4233 $this->arrData[$key] = $value;
4234 break;
4235 }
4236 }
4237
4238 public function getNrOfTries()
4239 {
4240 return (int) $this->nr_of_tries;
4241 }
4242
4243 public function setNrOfTries($a_nr_of_tries)
4244 {
4245 $this->nr_of_tries = $a_nr_of_tries;
4246 }
4247
4248 public function setExportImagePath($a_path)
4249 {
4250 $this->export_image_path = (string) $a_path;
4251 }
4252
4253 public static function _questionExistsInTest($question_id, $test_id)
4254 {
4255 global $DIC;
4256 $ilDB = $DIC['ilDB'];
4257
4258 if ($question_id < 1) {
4259 return false;
4260 }
4261
4262 $result = $ilDB->queryF(
4263 "SELECT question_fi FROM tst_test_question WHERE question_fi = %s AND test_fi = %s",
4264 array('integer', 'integer'),
4265 array($question_id, $test_id)
4266 );
4267 if ($result->numRows() == 1) {
4268 return true;
4269 } else {
4270 return false;
4271 }
4272 }
4273
4280 public function formatSAQuestion($a_q)
4281 {
4282 return $this->getSelfAssessmentFormatter()->format($a_q);
4283 }
4284
4288 protected function getSelfAssessmentFormatter()
4289 {
4290 require_once 'Modules/TestQuestionPool/classes/questions/class.ilAssSelfAssessmentQuestionFormatter.php';
4291 return new \ilAssSelfAssessmentQuestionFormatter();
4292 }
4293
4294 // scorm2004-start ???
4295
4301 public function setPreventRteUsage($a_val)
4302 {
4303 $this->prevent_rte_usage = $a_val;
4304 }
4305
4311 public function getPreventRteUsage()
4312 {
4314 }
4315
4320 {
4321 $this->lmMigrateQuestionTypeGenericContent($migrator);
4322 $this->lmMigrateQuestionTypeSpecificContent($migrator);
4323 $this->saveToDb();
4324
4325 $this->feedbackOBJ->migrateContentForLearningModule($migrator, $this->getId());
4326 }
4327
4332 {
4333 $this->setQuestion($migrator->migrateToLmContent($this->getQuestion()));
4334 }
4335
4340 {
4341 // overwrite if any question type specific content except feedback needs to be migrated
4342 }
4343
4349 public function setSelfAssessmentEditingMode($a_selfassessmenteditingmode)
4350 {
4351 $this->selfassessmenteditingmode = $a_selfassessmenteditingmode;
4352 }
4353
4360 {
4362 }
4363
4369 public function setDefaultNrOfTries($a_defaultnroftries)
4370 {
4371 $this->defaultnroftries = $a_defaultnroftries;
4372 }
4373
4379 public function getDefaultNrOfTries()
4380 {
4381 return (int) $this->defaultnroftries;
4382 }
4383
4384 // scorm2004-end ???
4385
4391 public static function lookupParentObjId($questionId)
4392 {
4393 global $DIC;
4394 $ilDB = $DIC['ilDB'];
4395
4396 $query = "SELECT obj_fi FROM qpl_questions WHERE question_id = %s";
4397
4398 $res = $ilDB->queryF($query, array('integer'), array((int) $questionId));
4399 $row = $ilDB->fetchAssoc($res);
4400
4401 return $row['obj_fi'];
4402 }
4403
4414 public static function lookupOriginalParentObjId($originalQuestionId)
4415 {
4416 return self::lookupParentObjId($originalQuestionId);
4417 }
4418
4419 protected function duplicateQuestionHints($originalQuestionId, $duplicateQuestionId)
4420 {
4421 require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionHintList.php';
4422 $hintIds = ilAssQuestionHintList::duplicateListForQuestion($originalQuestionId, $duplicateQuestionId);
4423
4425 require_once 'Modules/TestQuestionPool/classes/class.ilAssHintPage.php';
4426
4427 foreach ($hintIds as $originalHintId => $duplicateHintId) {
4428 $originalPageObject = new ilAssHintPage($originalHintId);
4429 $originalXML = $originalPageObject->getXMLContent();
4430
4431 $duplicatePageObject = new ilAssHintPage();
4432 $duplicatePageObject->setId($duplicateHintId);
4433 $duplicatePageObject->setParentId($this->getId());
4434 $duplicatePageObject->setXMLContent($originalXML);
4435 $duplicatePageObject->createFromXML();
4436 }
4437 }
4438 }
4439
4440 protected function duplicateSkillAssignments($srcParentId, $srcQuestionId, $trgParentId, $trgQuestionId)
4441 {
4442 global $DIC;
4443 $ilDB = $DIC['ilDB'];
4444
4445 require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionSkillAssignmentList.php';
4446 $assignmentList = new ilAssQuestionSkillAssignmentList($ilDB);
4447 $assignmentList->setParentObjId($srcParentId);
4448 $assignmentList->setQuestionIdFilter($srcQuestionId);
4449 $assignmentList->loadFromDb();
4450
4451 foreach ($assignmentList->getAssignmentsByQuestionId($srcQuestionId) as $assignment) {
4452 /* @var ilAssQuestionSkillAssignment $assignment */
4453
4454 $assignment->setParentObjId($trgParentId);
4455 $assignment->setQuestionId($trgQuestionId);
4456 $assignment->saveToDb();
4457 }
4458 }
4459
4460 public function syncSkillAssignments($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($trgParentId);
4468 $assignmentList->setQuestionIdFilter($trgQuestionId);
4469 $assignmentList->loadFromDb();
4470
4471 foreach ($assignmentList->getAssignmentsByQuestionId($trgQuestionId) as $assignment) {
4472 /* @var ilAssQuestionSkillAssignment $assignment */
4473
4474 $assignment->deleteFromDb();
4475 }
4476
4477 $this->duplicateSkillAssignments($srcParentId, $srcQuestionId, $trgParentId, $trgQuestionId);
4478 }
4479
4492 public function isAnswered($active_id, $pass = null)
4493 {
4494 return true;
4495 }
4496
4509 public static function isObligationPossible($questionId)
4510 {
4511 return false;
4512 }
4513
4514 public function isAutosaveable()
4515 {
4516 return true;
4517 }
4518
4531 protected static function getNumExistingSolutionRecords($activeId, $pass, $questionId)
4532 {
4533 global $DIC;
4534 $ilDB = $DIC['ilDB'];
4535
4536 $query = "
4537 SELECT count(active_fi) cnt
4538
4539 FROM tst_solutions
4540
4541 WHERE active_fi = %s
4542 AND question_fi = %s
4543 AND pass = %s
4544 ";
4545
4546 $res = $ilDB->queryF(
4547 $query,
4548 array('integer','integer','integer'),
4549 array($activeId, $questionId, $pass)
4550 );
4551
4552 $row = $ilDB->fetchAssoc($res);
4553
4554 return (int) $row['cnt'];
4555 }
4556
4564 {
4566 }
4567
4575 {
4577 require_once 'Modules/TestQuestionPool/exceptions/class.ilTestQuestionPoolException.php';
4578 throw new ilTestQuestionPoolException('invalid additional content editing mode given: ' . $additinalContentEditingMode);
4579 }
4580
4581 $this->additinalContentEditingMode = $additinalContentEditingMode;
4582 }
4583
4591 {
4593 }
4594
4602 public function isValidAdditionalContentEditingMode($additionalContentEditingMode)
4603 {
4604 if (in_array($additionalContentEditingMode, $this->getValidAdditionalContentEditingModes())) {
4605 return true;
4606 }
4607
4608 return false;
4609 }
4610
4618 {
4619 return array(
4620 self::ADDITIONAL_CONTENT_EDITING_MODE_DEFAULT,
4621 self::ADDITIONAL_CONTENT_EDITING_MODE_PAGE_OBJECT
4622 );
4623 }
4624
4629 {
4630 $this->questionChangeListeners[] = $listener;
4631 }
4632
4637 {
4639 }
4640
4641 private function notifyQuestionCreated()
4642 {
4643 foreach ($this->getQuestionChangeListeners() as $listener) {
4644 $listener->notifyQuestionCreated($this);
4645 }
4646 }
4647
4648 private function notifyQuestionEdited()
4649 {
4650 foreach ($this->getQuestionChangeListeners() as $listener) {
4651 $listener->notifyQuestionEdited($this);
4652 }
4653 }
4654
4655 private function notifyQuestionDeleted()
4656 {
4657 foreach ($this->getQuestionChangeListeners() as $listener) {
4658 $listener->notifyQuestionDeleted($this);
4659 }
4660 }
4661
4666 {
4667 require_once 'Services/Html/classes/class.ilHtmlPurifierFactory.php';
4668 return ilHtmlPurifierFactory::_getInstanceByType('qpl_usersolution');
4669 }
4670
4675 {
4676 require_once 'Services/Html/classes/class.ilHtmlPurifierFactory.php';
4677 return ilHtmlPurifierFactory::_getInstanceByType('qpl_usersolution');
4678 }
4679
4680 protected function buildQuestionDataQuery()
4681 {
4682 return "
4683 SELECT qpl_questions.*,
4684 {$this->getAdditionalTableName()}.*
4685 FROM qpl_questions
4686 LEFT JOIN {$this->getAdditionalTableName()}
4687 ON {$this->getAdditionalTableName()}.question_fi = qpl_questions.question_id
4688 WHERE qpl_questions.question_id = %s
4689 ";
4690 }
4691
4693 {
4694 $this->lastChange = $lastChange;
4695 }
4696
4697 public function getLastChange()
4698 {
4699 return $this->lastChange;
4700 }
4701
4712 protected function getCurrentSolutionResultSet($active_id, $pass, $authorized = true)
4713 {
4714 global $DIC;
4715 $ilDB = $DIC['ilDB'];
4716
4717 if ($this->getStep() !== null) {
4718 $query = "
4719 SELECT *
4720 FROM tst_solutions
4721 WHERE active_fi = %s
4722 AND question_fi = %s
4723 AND pass = %s
4724 AND step = %s
4725 AND authorized = %s
4726 ";
4727
4728 return $ilDB->queryF(
4729 $query,
4730 array('integer', 'integer', 'integer', 'integer', 'integer'),
4731 array($active_id, $this->getId(), $pass, $this->getStep(), (int) $authorized)
4732 );
4733 } else {
4734 $query = "
4735 SELECT *
4736 FROM tst_solutions
4737 WHERE active_fi = %s
4738 AND question_fi = %s
4739 AND pass = %s
4740 AND authorized = %s
4741 ";
4742
4743 return $ilDB->queryF(
4744 $query,
4745 array('integer', 'integer', 'integer', 'integer'),
4746 array($active_id, $this->getId(), $pass, (int) $authorized)
4747 );
4748 }
4749 }
4750
4757 protected function removeSolutionRecordById($solutionId)
4758 {
4759 global $DIC;
4760 $ilDB = $DIC['ilDB'];
4761
4762 return $ilDB->manipulateF(
4763 "DELETE FROM tst_solutions WHERE solution_id = %s",
4764 array('integer'),
4765 array($solutionId)
4766 );
4767 }
4768
4769 // hey: prevPassSolutions - selected file reuse, copy solution records
4776 protected function getSolutionRecordById($solutionId)
4777 {
4778 global $DIC; /* @var ILIAS\DI\Container $DIC */
4779 $ilDB = $DIC['ilDB'];
4780
4781 $res = $ilDB->queryF(
4782 "SELECT * FROM tst_solutions WHERE solution_id = %s",
4783 array('integer'),
4784 array($solutionId)
4785 );
4786
4787 while ($row = $ilDB->fetchAssoc($res)) {
4788 return $row;
4789 }
4790 }
4791 // hey.
4792
4801 public function removeIntermediateSolution($active_id, $pass)
4802 {
4803 $this->getProcessLocker()->executeUserSolutionUpdateLockOperation(function () use ($active_id, $pass) {
4804 $this->removeCurrentSolution($active_id, $pass, false);
4805 });
4806 }
4807
4816 public function removeCurrentSolution($active_id, $pass, $authorized = true)
4817 {
4818 global $DIC;
4819 $ilDB = $DIC['ilDB'];
4820
4821 if ($this->getStep() !== null) {
4822 $query = "
4823 DELETE FROM tst_solutions
4824 WHERE active_fi = %s
4825 AND question_fi = %s
4826 AND pass = %s
4827 AND step = %s
4828 AND authorized = %s
4829 ";
4830
4831 return $ilDB->manipulateF(
4832 $query,
4833 array('integer', 'integer', 'integer', 'integer', 'integer'),
4834 array($active_id, $this->getId(), $pass, $this->getStep(), (int) $authorized)
4835 );
4836 } else {
4837 $query = "
4838 DELETE FROM tst_solutions
4839 WHERE active_fi = %s
4840 AND question_fi = %s
4841 AND pass = %s
4842 AND authorized = %s
4843 ";
4844
4845 return $ilDB->manipulateF(
4846 $query,
4847 array('integer', 'integer', 'integer', 'integer'),
4848 array($active_id, $this->getId(), $pass, (int) $authorized)
4849 );
4850 }
4851 }
4852
4853 // fau: testNav - add timestamp as parameter to saveCurrentSolution
4865 public function saveCurrentSolution($active_id, $pass, $value1, $value2, $authorized = true, $tstamp = null)
4866 {
4867 global $DIC;
4868 $ilDB = $DIC['ilDB'];
4869
4870 $next_id = $ilDB->nextId("tst_solutions");
4871
4872 $fieldData = array(
4873 "solution_id" => array("integer", $next_id),
4874 "active_fi" => array("integer", $active_id),
4875 "question_fi" => array("integer", $this->getId()),
4876 "value1" => array("clob", $value1),
4877 "value2" => array("clob", $value2),
4878 "pass" => array("integer", $pass),
4879 "tstamp" => array("integer", isset($tstamp) ? $tstamp : time()),
4880 'authorized' => array('integer', (int) $authorized)
4881 );
4882
4883 if ($this->getStep() !== null) {
4884 $fieldData['step'] = array("integer", $this->getStep());
4885 }
4886
4887 return $ilDB->insert("tst_solutions", $fieldData);
4888 }
4889 // fau.
4890
4901 public function updateCurrentSolution($solutionId, $value1, $value2, $authorized = true)
4902 {
4903 global $DIC;
4904 $ilDB = $DIC['ilDB'];
4905
4906 $fieldData = array(
4907 "value1" => array("clob", $value1),
4908 "value2" => array("clob", $value2),
4909 "tstamp" => array("integer", time()),
4910 'authorized' => array('integer', (int) $authorized)
4911 );
4912
4913 if ($this->getStep() !== null) {
4914 $fieldData['step'] = array("integer", $this->getStep());
4915 }
4916
4917 return $ilDB->update("tst_solutions", $fieldData, array(
4918 'solution_id' => array('integer', $solutionId)
4919 ));
4920 }
4921
4922 // fau: testNav - added parameter to keep the timestamp (default: false)
4923 public function updateCurrentSolutionsAuthorization($activeId, $pass, $authorized, $keepTime = false)
4924 {
4925 global $DIC;
4926 $ilDB = $DIC['ilDB'];
4927
4928 $fieldData = array(
4929 'authorized' => array('integer', (int) $authorized)
4930 );
4931
4932 if (!$keepTime) {
4933 $fieldData['tstamp'] = array('integer', time());
4934 }
4935
4936 $whereData = array(
4937 'question_fi' => array('integer', $this->getId()),
4938 'active_fi' => array('integer', $activeId),
4939 'pass' => array('integer', $pass)
4940 );
4941
4942 if ($this->getStep() !== null) {
4943 $whereData['step'] = array("integer", $this->getStep());
4944 }
4945
4946 return $ilDB->update('tst_solutions', $fieldData, $whereData);
4947 }
4948 // fau.
4949
4950 // hey: prevPassSolutions - motivation slowly decreases on imagemap
4952 protected static function getKeyValuesImplosionSeparator()
4953 {
4955 }
4956 public static function implodeKeyValues($keyValues)
4957 {
4958 return implode(self::getKeyValuesImplosionSeparator(), $keyValues);
4959 }
4960 public static function explodeKeyValues($keyValues)
4961 {
4962 return explode(self::getKeyValuesImplosionSeparator(), $keyValues);
4963 }
4964
4965 protected function deleteDummySolutionRecord($activeId, $passIndex)
4966 {
4967 foreach ($this->getSolutionValues($activeId, $passIndex, false) as $solutionRec) {
4968 if (0 == strlen($solutionRec['value1']) && 0 == strlen($solutionRec['value2'])) {
4969 $this->removeSolutionRecordById($solutionRec['solution_id']);
4970 }
4971 }
4972 }
4973
4974 protected function isDummySolutionRecord($solutionRecord)
4975 {
4976 return !strlen($solutionRecord['value1']) && !strlen($solutionRecord['value2']);
4977 }
4978
4979 protected function deleteSolutionRecordByValues($activeId, $passIndex, $authorized, $matchValues)
4980 {
4981 global $DIC; /* @var ILIAS\DI\Container $DIC */
4982 $ilDB = $DIC['ilDB'];
4983
4984 $types = array("integer", "integer", "integer", "integer");
4985 $values = array($activeId, $this->getId(), $passIndex, (int) $authorized);
4986 $valuesCondition = array();
4987
4988 foreach ($matchValues as $valueField => $value) {
4989 switch ($valueField) {
4990 case 'value1':
4991 case 'value2':
4992 $valuesCondition[] = "{$valueField} = %s";
4993 $types[] = 'text';
4994 $values[] = $value;
4995 break;
4996
4997 default:
4998 require_once 'Modules/TestQuestionPool/exceptions/class.ilTestQuestionPoolException.php';
4999 throw new ilTestQuestionPoolException('invalid value field given: ' . $valueField);
5000 }
5001 }
5002
5003 $valuesCondition = implode(' AND ', $valuesCondition);
5004
5005 $query = "
5006 DELETE FROM tst_solutions
5007 WHERE active_fi = %s
5008 AND question_fi = %s
5009 AND pass = %s
5010 AND authorized = %s
5011 AND $valuesCondition
5012 ";
5013
5014 if ($this->getStep() !== null) {
5015 $query .= " AND step = %s ";
5016 $types[] = 'integer';
5017 $values[] = $this->getStep();
5018 }
5019
5020 $ilDB->manipulateF($query, $types, $values);
5021 }
5022
5023 protected function duplicateIntermediateSolutionAuthorized($activeId, $passIndex)
5024 {
5025 foreach ($this->getSolutionValues($activeId, $passIndex, false) as $rec) {
5026 $this->saveCurrentSolution($activeId, $passIndex, $rec['value1'], $rec['value2'], true, $rec['tstamp']);
5027 }
5028 }
5029
5030 protected function forceExistingIntermediateSolution($activeId, $passIndex, $considerDummyRecordCreation)
5031 {
5032 $intermediateSolution = $this->getSolutionValues($activeId, $passIndex, false);
5033
5034 if (!count($intermediateSolution)) {
5035 // make the authorized solution intermediate (keeping timestamps)
5036 // this keeps the solution_ids in synch with eventually selected in $_POST['deletefiles']
5037 $this->updateCurrentSolutionsAuthorization($activeId, $passIndex, false, true);
5038
5039 // create a backup as authorized solution again (keeping timestamps)
5040 $this->duplicateIntermediateSolutionAuthorized($activeId, $passIndex);
5041
5042 if ($considerDummyRecordCreation) {
5043 // create an additional dummy record to indicate the existence of an intermediate solution
5044 // even if all entries are deleted from the intermediate solution later
5045 $this->saveCurrentSolution($activeId, $passIndex, null, null, false, null);
5046 }
5047 }
5048 }
5049 // hey.
5050
5054 public static function setResultGateway($resultGateway)
5055 {
5056 self::$resultGateway = $resultGateway;
5057 }
5058
5062 public static function getResultGateway()
5063 {
5064 return self::$resultGateway;
5065 }
5066
5070 public function setStep($step)
5071 {
5072 $this->step = $step;
5073 }
5074
5078 public function getStep()
5079 {
5080 return $this->step;
5081 }
5082
5088 public static function sumTimesInISO8601FormatH_i_s_Extended($time1, $time2)
5089 {
5092 return gmdate('H:i:s', $time);
5093 }
5094
5100 {
5101 $sec = 0;
5102 $time_array = explode(':', $time);
5103 if (sizeof($time_array) == 3) {
5104 $sec += $time_array[0] * 3600;
5105 $sec += $time_array[1] * 60;
5106 $sec += $time_array[2];
5107 }
5108 return $sec;
5109 }
5110
5111 public function toJSON()
5112 {
5113 return json_encode(array());
5114 }
5115
5116 abstract public function duplicate($for_test = true, $title = "", $author = "", $owner = "", $testObjId = null);
5117
5118 // hey: prevPassSolutions - check for authorized solution
5119 public function intermediateSolutionExists($active_id, $pass)
5120 {
5121 $solutionAvailability = $this->lookupForExistingSolutions($active_id, $pass);
5122 return (bool) $solutionAvailability['intermediate'];
5123 }
5124 public function authorizedSolutionExists($active_id, $pass)
5125 {
5126 $solutionAvailability = $this->lookupForExistingSolutions($active_id, $pass);
5127 return (bool) $solutionAvailability['authorized'];
5128 }
5130 {
5131 $solutionAvailability = $this->lookupForExistingSolutions($active_id, $pass);
5132 return (bool) $solutionAvailability['authorized'] || (bool) $solutionAvailability['intermediate'];
5133 }
5134 // hey.
5135
5141 protected function lookupMaxStep($active_id, $pass)
5142 {
5144 global $DIC;
5145 $ilDB = $DIC['ilDB'];
5146
5147 $res = $ilDB->queryF(
5148 "SELECT MAX(step) max_step FROM tst_solutions WHERE active_fi = %s AND pass = %s AND question_fi = %s",
5149 array("integer", "integer", "integer"),
5150 array($active_id, $pass, $this->getId())
5151 );
5152
5153 $row = $ilDB->fetchAssoc($res);
5154
5155 $maxStep = $row['max_step'];
5156
5157 return $maxStep;
5158 }
5159
5160 // fau: testNav - new function lookupForExistingSolutions
5167 public function lookupForExistingSolutions($activeId, $pass)
5168 {
5170 global $DIC;
5171 $ilDB = $DIC['ilDB'];
5172
5173 $return = array(
5174 'authorized' => false,
5175 'intermediate' => false
5176 );
5177
5178 $query = "
5179 SELECT authorized, COUNT(*) cnt
5180 FROM tst_solutions
5181 WHERE active_fi = %s
5182 AND question_fi = %s
5183 AND pass = %s
5184 ";
5185
5186 if ($this->getStep() !== null) {
5187 $query .= " AND step = " . $ilDB->quote((int) $this->getStep(), 'integer') . " ";
5188 }
5189
5190 $query .= "
5191 GROUP BY authorized
5192 ";
5193
5194 $result = $ilDB->queryF($query, array('integer', 'integer', 'integer'), array($activeId, $this->getId(), $pass));
5195
5196 while ($row = $ilDB->fetchAssoc($result)) {
5197 if ($row['authorized']) {
5198 $return['authorized'] = $row['cnt'] > 0;
5199 } else {
5200 $return['intermediate'] = $row['cnt'] > 0;
5201 }
5202 }
5203 return $return;
5204 }
5205 // fau.
5206
5207 public function isAddableAnswerOptionValue($qIndex, $answerOptionValue)
5208 {
5209 return false;
5210 }
5211
5212 public function addAnswerOptionValue($qIndex, $answerOptionValue, $points)
5213 {
5214 }
5215
5217 {
5218 global $DIC; /* @var ILIAS\DI\Container $DIC */
5219
5220 $query = "DELETE FROM tst_solutions WHERE question_fi = %s";
5221
5222 $DIC->database()->manipulateF($query, array('integer'), array($this->getId()));
5223 }
5224
5225 public function removeExistingSolutions($activeId, $pass)
5226 {
5227 global $DIC;
5228 $ilDB = $DIC['ilDB'];
5229
5230 $query = "
5231 DELETE FROM tst_solutions
5232 WHERE active_fi = %s
5233 AND question_fi = %s
5234 AND pass = %s
5235 ";
5236
5237 if ($this->getStep() !== null) {
5238 $query .= " AND step = " . $ilDB->quote((int) $this->getStep(), 'integer') . " ";
5239 }
5240
5241 return $ilDB->manipulateF(
5242 $query,
5243 array('integer', 'integer', 'integer'),
5244 array($activeId, $this->getId(), $pass)
5245 );
5246 }
5247
5248 public function resetUsersAnswer($activeId, $pass)
5249 {
5250 $this->removeExistingSolutions($activeId, $pass);
5251 $this->removeResultRecord($activeId, $pass);
5252
5253 $this->log($activeId, "log_user_solution_willingly_deleted");
5254
5255 self::_updateTestPassResults(
5256 $activeId,
5257 $pass,
5259 $this->getProcessLocker(),
5260 $this->getTestId()
5261 );
5262 }
5263
5264 public function removeResultRecord($activeId, $pass)
5265 {
5266 global $DIC;
5267 $ilDB = $DIC['ilDB'];
5268
5269 $query = "
5270 DELETE FROM tst_test_result
5271 WHERE active_fi = %s
5272 AND question_fi = %s
5273 AND pass = %s
5274 ";
5275
5276 if ($this->getStep() !== null) {
5277 $query .= " AND step = " . $ilDB->quote((int) $this->getStep(), 'integer') . " ";
5278 }
5279
5280 return $ilDB->manipulateF(
5281 $query,
5282 array('integer', 'integer', 'integer'),
5283 array($activeId, $this->getId(), $pass)
5284 );
5285 }
5286
5287 public static function missingResultRecordExists($activeId, $pass, $questionIds)
5288 {
5289 global $DIC;
5290 $ilDB = $DIC['ilDB'];
5291
5292 $IN_questionIds = $ilDB->in('question_fi', $questionIds, false, 'integer');
5293
5294 $query = "
5295 SELECT COUNT(*) cnt
5296 FROM tst_test_result
5297 WHERE active_fi = %s
5298 AND pass = %s
5299 AND $IN_questionIds
5300 ";
5301
5302 $row = $ilDB->fetchAssoc($ilDB->queryF(
5303 $query,
5304 array('integer', 'integer'),
5305 array($activeId, $pass)
5306 ));
5307
5308 return $row['cnt'] < count($questionIds);
5309 }
5310
5311 public static function getQuestionsMissingResultRecord($activeId, $pass, $questionIds)
5312 {
5313 global $DIC;
5314 $ilDB = $DIC['ilDB'];
5315
5316 $IN_questionIds = $ilDB->in('question_fi', $questionIds, false, 'integer');
5317
5318 $query = "
5319 SELECT question_fi
5320 FROM tst_test_result
5321 WHERE active_fi = %s
5322 AND pass = %s
5323 AND $IN_questionIds
5324 ";
5325
5326 $res = $ilDB->queryF(
5327 $query,
5328 array('integer', 'integer'),
5329 array($activeId, $pass)
5330 );
5331
5332 $questionsHavingResultRecord = array();
5333
5334 while ($row = $ilDB->fetchAssoc($res)) {
5335 $questionsHavingResultRecord[] = $row['question_fi'];
5336 }
5337
5338 $questionsMissingResultRecordt = array_diff(
5339 $questionIds,
5340 $questionsHavingResultRecord
5341 );
5342
5343 return $questionsMissingResultRecordt;
5344 }
5345
5346 public static function lookupResultRecordExist($activeId, $questionId, $pass)
5347 {
5348 global $DIC;
5349 $ilDB = $DIC['ilDB'];
5350
5351 $query = "
5352 SELECT COUNT(*) cnt
5353 FROM tst_test_result
5354 WHERE active_fi = %s
5355 AND question_fi = %s
5356 AND pass = %s
5357 ";
5358
5359 $row = $ilDB->fetchAssoc($ilDB->queryF($query, array('integer', 'integer', 'integer'), array($activeId, $questionId, $pass)));
5360
5361 return $row['cnt'] > 0;
5362 }
5363
5368 public function fetchValuePairsFromIndexedValues(array $indexedValues)
5369 {
5370 $valuePairs = array();
5371
5372 foreach ($indexedValues as $value1 => $value2) {
5373 $valuePairs[] = array('value1' => $value1, 'value2' => $value2);
5374 }
5375
5376 return $valuePairs;
5377 }
5378
5383 public function fetchIndexedValuesFromValuePairs(array $valuePairs)
5384 {
5385 $indexedValues = array();
5386
5387 foreach ($valuePairs as $valuePair) {
5388 $indexedValues[ $valuePair['value1'] ] = $valuePair['value2'];
5389 }
5390
5391 return $indexedValues;
5392 }
5393
5398 {
5400 }
5401
5406 {
5407 $this->obligationsToBeConsidered = $obligationsToBeConsidered;
5408 }
5409
5410 public function updateTimestamp()
5411 {
5412 global $DIC;
5413 $ilDB = $DIC['ilDB'];
5414
5415 $ilDB->manipulateF(
5416 "UPDATE qpl_questions SET tstamp = %s WHERE question_id = %s",
5417 array('integer', 'integer'),
5418 array(time(), $this->getId())
5419 );
5420 }
5421
5422 // fau: testNav - new function getTestQuestionConfig()
5423 // hey: prevPassSolutions - get caching independent from configuration (config once)
5424 // renamed: getTestPresentationConfig() -> does the caching
5425 // completed: extracted instance building
5426 // avoids configuring cached instances on every access
5427 // allows a stable reconfigure of the instance from outside
5432
5438 {
5439 if ($this->testQuestionConfigInstance === null) {
5440 $this->testQuestionConfigInstance = $this->buildTestPresentationConfig();
5441 }
5442
5444 }
5445
5454 protected function buildTestPresentationConfig()
5455 {
5456 include_once('Modules/TestQuestionPool/classes/class.ilTestQuestionConfig.php');
5457 return new ilTestQuestionConfig();
5458 }
5459 // hey.
5460// fau.
5461
5462 public function savePartial()
5463 {
5464 return false;
5465 }
5466}
$result
$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)
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)
createRandomSolution($test_id, $user_id)
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($a_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 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
$key
Definition: croninfo.php:18
$i
Definition: disco.tpl.php:19
$html
Definition: example_001.php:87
$target_id
Definition: goto.php:49
global $ilCtrl
Definition: ilias.php:18
const OUTPUT_HTML
const OUTPUT_JAVASCRIPT
$time
Definition: cron.php:21
$index
Definition: metadata.php:60
catch(Exception $e) $message
$target
Definition: test.php:19
$keys
$row
$GLOBALS['JPEG_Segment_Names']
Global Variable: XMP_tag_captions.
redirection script todo: (a better solution should control the processing via a xml file)
$query
$type
if(empty($password)) $table
Definition: pwgen.php:24
global $DIC
Definition: saml.php:7
foreach($_POST as $key=> $value) $res
global $ilDB
$values
$mobs
$ilUser
Definition: imgupload.php:18
$data
Definition: bench.php:6