ILIAS  release_5-3 Revision v5.3.23-19-g915713cf615
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 $ilias, $lng, $tpl, $ilDB;
294
295 $this->ilias = $ilias;
296 $this->lng = $lng;
297 $this->tpl = $tpl;
298 $this->db = $ilDB;
299
300 $this->original_id = null;
301 $this->title = $title;
302 $this->comment = $comment;
303 $this->page = null;
304 $this->author = $author;
305 $this->setQuestion($question);
306 if (!$this->author) {
307 $this->author = $this->ilias->account->fullname;
308 }
309 $this->owner = $owner;
310 if ($this->owner <= 0) {
311 $this->owner = $this->ilias->account->id;
312 }
313 $this->id = -1;
314 $this->test_id = -1;
315 $this->suggested_solutions = array();
316 $this->shuffle = 1;
317 $this->nr_of_tries = 0;
318 $this->setEstimatedWorkingTime(0, 1, 0);
319 $this->arrData = array();
320 $this->setExternalId('');
321
322 $this->questionActionCmd = 'handleQuestionAction';
323
324 $this->lastChange = null;
325
326 require_once 'Services/Randomization/classes/class.ilArrayElementOrderKeeper.php';
327 $this->shuffler = new ilArrayElementOrderKeeper();
328 }
329
330 protected static $forcePassResultsUpdateEnabled = false;
331
333 {
334 self::$forcePassResultsUpdateEnabled = $forcePassResultsUpdateEnabled;
335 }
336
337 public static function isForcePassResultUpdateEnabled()
338 {
340 }
341
342 public static function isAllowedImageMimeType($mimeType)
343 {
344 return (bool) count(self::getAllowedFileExtensionsForMimeType($mimeType));
345 }
346
347 public static function fetchMimeTypeIdentifier($contentTypeString)
348 {
349 return current(explode(';', $contentTypeString));
350 }
351
352 public static function getAllowedFileExtensionsForMimeType($mimeType)
353 {
354 foreach (self::$allowedFileExtensionsByMimeType as $allowedMimeType => $extensions) {
355 $rexCharsets = implode('|', self::$allowedCharsetsByMimeType[$allowedMimeType]);
356 $rexMimeType = preg_quote($allowedMimeType, '/');
357
358 $rex = '/^' . $rexMimeType . '(;(\s)*charset=(' . $rexCharsets . '))*$/';
359
360 if (!preg_match($rex, $mimeType)) {
361 continue;
362 }
363
364 return $extensions;
365 }
366
367 return array();
368 }
369
370 public static function isAllowedImageFileExtension($mimeType, $fileExtension)
371 {
372 return in_array(
373 strtolower($fileExtension),
374 self::getAllowedFileExtensionsForMimeType($mimeType)
375 );
376 }
377
378 // hey: prevPassSolutions - question action actracted (heavy use in fileupload refactoring)
379
383 protected function getQuestionAction()
384 {
385 if (!isset($_POST['cmd']) || !isset($_POST['cmd'][$this->questionActionCmd])) {
386 return '';
387 }
388
389 if (!is_array($_POST['cmd'][$this->questionActionCmd]) || !count($_POST['cmd'][$this->questionActionCmd])) {
390 return '';
391 }
392
393 return key($_POST['cmd'][$this->questionActionCmd]);
394 }
395
400 protected function isNonEmptyItemListPostSubmission($postSubmissionFieldname)
401 {
402 if (!isset($_POST[$postSubmissionFieldname])) {
403 return false;
404 }
405
406 if (!is_array($_POST[$postSubmissionFieldname])) {
407 return false;
408 }
409
410 if (!count($_POST[$postSubmissionFieldname])) {
411 return false;
412 }
413
414 return true;
415 }
416
422 protected function ensureCurrentTestPass($active_id, $pass)
423 {
424 if (is_integer($pass) && $pass >= 0) {
425 return $pass;
426 }
427
428 return $this->lookupCurrentTestPass($active_id, $pass);
429 }
430
436 protected function lookupCurrentTestPass($active_id, $pass)
437 {
438 require_once 'Modules/Test/classes/class.ilObjTest.php';
439 return ilObjTest::_getPass($active_id);
440 }
441
446 protected function lookupTestId($active_id)
447 {
448 $ilDB = isset($GLOBALS['DIC']) ? $GLOBALS['DIC']['ilDB'] : $GLOBALS['ilDB'];
449
450 $result = $ilDB->queryF(
451 "SELECT test_fi FROM tst_active WHERE active_id = %s",
452 array('integer'),
453 array($active_id)
454 );
455
456 while ($row = $ilDB->fetchAssoc($result)) {
457 return $row["test_fi"];
458 }
459
460 return null;
461 }
462 // hey.
463
468 protected function log($active_id, $langVar)
469 {
471 $message = $this->lng->txtlng('assessment', $langVar, ilObjAssessmentFolder::_getLogLanguage());
472 assQuestion::logAction($message, $active_id, $this->getId());
473 }
474 }
475
480 {
481 $extensions = array();
482
483 foreach (self::$allowedImageMaterialFileExtensionsByMimeType as $mimeType => $mimeExtensions) {
484 $extensions = array_merge($extensions, $mimeExtensions);
485 }
486 return array_unique($extensions);
487 }
488
492 public function getShuffler()
493 {
494 return $this->shuffler;
495 }
496
501 {
502 $this->shuffler = $shuffler;
503 }
504
509 {
510 $this->processLocker = $processLocker;
511 }
512
516 public function getProcessLocker()
517 {
519 }
520
532 public function fromXML(&$item, &$questionpool_id, &$tst_id, &$tst_object, &$question_counter, &$import_mapping)
533 {
534 include_once "./Modules/TestQuestionPool/classes/import/qti12/class." . $this->getQuestionType() . "Import.php";
535 $classname = $this->getQuestionType() . "Import";
536 $import = new $classname($this);
537 $import->fromXML($item, $questionpool_id, $tst_id, $tst_object, $question_counter, $import_mapping);
538 }
539
546 public function toXML($a_include_header = true, $a_include_binary = true, $a_shuffle = false, $test_output = false, $force_image_references = false)
547 {
548 include_once "./Modules/TestQuestionPool/classes/export/qti12/class." . $this->getQuestionType() . "Export.php";
549 $classname = $this->getQuestionType() . "Export";
550 $export = new $classname($this);
551 return $export->toXML($a_include_header, $a_include_binary, $a_shuffle, $test_output, $force_image_references);
552 }
553
560 public function isComplete()
561 {
562 return false;
563 }
564
572 public function questionTitleExists($questionpool_id, $title)
573 {
574 global $ilDB;
575
576 $result = $ilDB->queryF(
577 "SELECT * FROM qpl_questions WHERE obj_fi = %s AND title = %s",
578 array('integer','text'),
579 array($questionpool_id, $title)
580 );
581 return ($result->numRows() > 0) ? true : false;
582 }
583
591 public function setTitle($title = "")
592 {
593 $this->title = $title;
594 }
595
603 public function setId($id = -1)
604 {
605 $this->id = $id;
606 }
607
615 public function setTestId($id = -1)
616 {
617 $this->test_id = $id;
618 }
619
627 public function setComment($comment = "")
628 {
629 $this->comment = $comment;
630 }
631
640 {
641 $this->outputType = $outputType;
642 }
643
644
652 public function setShuffle($shuffle = true)
653 {
654 if ($shuffle) {
655 $this->shuffle = 1;
656 } else {
657 $this->shuffle = 0;
658 }
659 }
660
671 public function setEstimatedWorkingTime($hour=0, $min=0, $sec=0)
672 {
673 $this->est_working_time = array("h" => (int) $hour, "m" => (int) $min, "s" => (int) $sec);
674 }
675
682 public function setEstimatedWorkingTimeFromDurationString($durationString)
683 {
684 $this->est_working_time = array(
685 'h' => (int) substr($durationString, 0, 2),
686 'm' => (int) substr($durationString, 3, 2),
687 's' => (int) substr($durationString, 6, 2)
688 );
689 }
690
698 public function keyInArray($searchkey, $array)
699 {
700 if ($searchkey) {
701 foreach ($array as $key => $value) {
702 if (strcmp($key, $searchkey)==0) {
703 return true;
704 }
705 }
706 }
707 return false;
708 }
709
717 public function setAuthor($author = "")
718 {
719 if (!$author) {
720 $author = $this->ilias->account->fullname;
721 }
722 $this->author = $author;
723 }
724
732 public function setOwner($owner = "")
733 {
734 $this->owner = $owner;
735 }
736
744 public function getTitle()
745 {
746 return $this->title;
747 }
748
755 {
756 require_once 'Services/Utilities/classes/class.ilUtil.php';
757 return ilUtil::getASCIIFilename($this->getTitle());
758 }
759
767 public function getId()
768 {
769 return $this->id;
770 }
771
779 public function getShuffle()
780 {
781 return $this->shuffle;
782 }
783
791 public function getTestId()
792 {
793 return $this->test_id;
794 }
795
803 public function getComment()
804 {
805 return $this->comment;
806 }
807
815 public function getOutputType()
816 {
817 return $this->outputType;
818 }
819
826 public function supportsJavascriptOutput()
827 {
828 return false;
829 }
830
831 public function supportsNonJsOutput()
832 {
833 return true;
834 }
835
836 public function requiresJsSwitch()
837 {
838 return $this->supportsJavascriptOutput() && $this->supportsNonJsOutput();
839 }
840
848 public function getEstimatedWorkingTime()
849 {
850 if (!$this->est_working_time) {
851 $this->est_working_time = array("h" => 0, "m" => 0, "s" => 0);
852 }
854 }
855
863 public function getAuthor()
864 {
865 return $this->author;
866 }
867
875 public function getOwner()
876 {
877 return $this->owner;
878 }
879
887 public function getObjId()
888 {
889 return $this->obj_id;
890 }
891
899 public function setObjId($obj_id = 0)
900 {
901 $this->obj_id = $obj_id;
902 }
903
908 {
909 $this->external_id = $external_id;
910 }
911
915 public function getExternalId()
916 {
917 if (!strlen($this->external_id)) {
918 if ($this->getId() > 0) {
919 return 'il_' . IL_INST_ID . '_qst_' . $this->getId();
920 } else {
921 return uniqid('', true);
922 }
923 } else {
924 return $this->external_id;
925 }
926 }
927
934 public static function _getMaximumPoints($question_id)
935 {
936 global $ilDB;
937
938 $points = 0;
939 $result = $ilDB->queryF(
940 "SELECT points FROM qpl_questions WHERE question_id = %s",
941 array('integer'),
942 array($question_id)
943 );
944 if ($result->numRows() == 1) {
945 $row = $ilDB->fetchAssoc($result);
946 $points = $row["points"];
947 }
948 return $points;
949 }
950
957 public static function _getQuestionInfo($question_id)
958 {
959 global $ilDB;
960
961 $result = $ilDB->queryF(
962 "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",
963 array('integer'),
964 array($question_id)
965 );
966 if ($result->numRows()) {
967 return $ilDB->fetchAssoc($result);
968 } else {
969 return array();
970 }
971 }
972
979 public static function _getSuggestedSolutionCount($question_id)
980 {
981 global $ilDB;
982
983 $result = $ilDB->queryF(
984 "SELECT suggested_solution_id FROM qpl_sol_sug WHERE question_fi = %s",
985 array('integer'),
986 array($question_id)
987 );
988 return $result->numRows();
989 }
990
997 public static function _getSuggestedSolutionOutput($question_id)
998 {
1000 if (!is_object($question)) {
1001 return "";
1002 }
1003 return $question->getSuggestedSolutionOutput();
1004 }
1005
1007 {
1008 $output = array();
1009 foreach ($this->suggested_solutions as $solution) {
1010 switch ($solution["type"]) {
1011 case "lm":
1012 case "st":
1013 case "pg":
1014 case "git":
1015 array_push($output, '<a href="' . assQuestion::_getInternalLinkHref($solution["internal_link"]) . '">' . $this->lng->txt("solution_hint") . '</a>');
1016 break;
1017 case "file":
1018 $possible_texts = array_values(array_filter(array(
1019 ilUtil::prepareFormOutput($solution['value']['filename']),
1020 ilUtil::prepareFormOutput($solution['value']['name']),
1021 $this->lng->txt('tst_show_solution_suggested')
1022 )));
1023
1024 require_once 'Services/WebAccessChecker/classes/class.ilWACSignedPath.php';
1026 array_push($output, '<a href="' . ilWACSignedPath::signFile($this->getSuggestedSolutionPathWeb() . $solution["value"]["name"]) . '">' . $possible_texts[0] . '</a>');
1027 break;
1028 case "text":
1029 $solutionValue = $solution["value"];
1030 $solutionValue = $this->fixSvgToPng($solutionValue);
1031 $solutionValue = $this->fixUnavailableSkinImageSources($solutionValue);
1032 $output[] = $this->prepareTextareaOutput($solutionValue, true);
1033 break;
1034 }
1035 }
1036 return join($output, "<br />");
1037 }
1038
1047 public function &_getSuggestedSolution($question_id, $subquestion_index = 0)
1048 {
1049 global $ilDB;
1050
1051 $result = $ilDB->queryF(
1052 "SELECT * FROM qpl_sol_sug WHERE question_fi = %s AND subquestion_index = %s",
1053 array('integer','integer'),
1054 array($question_id, $subquestion_index)
1055 );
1056 if ($result->numRows() == 1) {
1057 $row = $ilDB->fetchAssoc($result);
1058 return array(
1059 "internal_link" => $row["internal_link"],
1060 "import_id" => $row["import_id"]
1061 );
1062 } else {
1063 return array();
1064 }
1065 }
1066
1072 public function getSuggestedSolutions()
1073 {
1075 }
1076
1084 public static function _getReachedPoints($active_id, $question_id, $pass = null)
1085 {
1086 global $ilDB;
1087
1088 $points = 0;
1089 if (is_null($pass)) {
1090 include_once "./Modules/TestQuestionPool/classes/class.assQuestion.php";
1091 $pass = assQuestion::_getSolutionMaxPass($question_id, $active_id);
1092 }
1093 $result = $ilDB->queryF(
1094 "SELECT * FROM tst_test_result WHERE active_fi = %s AND question_fi = %s AND pass = %s",
1095 array('integer','integer','integer'),
1096 array($active_id, $question_id, $pass)
1097 );
1098 if ($result->numRows() == 1) {
1099 $row = $ilDB->fetchAssoc($result);
1100 $points = $row["points"];
1101 }
1102 return $points;
1103 }
1104
1113 public function getReachedPoints($active_id, $pass = null)
1114 {
1115 return round(self::_getReachedPoints($active_id, $this->getId(), $pass), 2);
1116 }
1117
1124 public function getMaximumPoints()
1125 {
1126 return $this->points;
1127 }
1128
1140 final public function getAdjustedReachedPoints($active_id, $pass = null, $authorizedSolution = true)
1141 {
1142 if (is_null($pass)) {
1143 include_once "./Modules/Test/classes/class.ilObjTest.php";
1144 $pass = ilObjTest::_getPass($active_id);
1145 }
1146
1147 // determine reached points for submitted solution
1148 $reached_points = $this->calculateReachedPoints($active_id, $pass, $authorizedSolution);
1149
1150
1151
1152 // deduct points for requested hints from reached points
1153 require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionHintTracking.php';
1154 $hintTracking = new ilAssQuestionHintTracking($this->getId(), $active_id, $pass);
1155 $requestsStatisticData = $hintTracking->getRequestStatisticDataByQuestionAndTestpass();
1156 $reached_points = $reached_points - $requestsStatisticData->getRequestsPoints();
1157
1158 // adjust reached points regarding to tests scoring options
1159 $reached_points = $this->adjustReachedPointsByScoringOptions($reached_points, $active_id, $pass);
1160
1161 return $reached_points;
1162 }
1163
1173 final public function calculateResultsFromSolution($active_id, $pass = null, $obligationsEnabled = false)
1174 {
1175 global $ilDB, $ilUser;
1176
1177 if (is_null($pass)) {
1178 include_once "./Modules/Test/classes/class.ilObjTest.php";
1179 $pass = ilObjTest::_getPass($active_id);
1180 }
1181
1182 // determine reached points for submitted solution
1183 $reached_points = $this->calculateReachedPoints($active_id, $pass);
1184
1185 // deduct points for requested hints from reached points
1186 require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionHintTracking.php';
1187 $questionHintTracking = new ilAssQuestionHintTracking($this->getId(), $active_id, $pass);
1188 $requestsStatisticData = $questionHintTracking->getRequestStatisticDataByQuestionAndTestpass();
1189 $reached_points = $reached_points - $requestsStatisticData->getRequestsPoints();
1190
1191 // adjust reached points regarding to tests scoring options
1192 $reached_points = $this->adjustReachedPointsByScoringOptions($reached_points, $active_id, $pass);
1193
1194 if ($obligationsEnabled && ilObjTest::isQuestionObligatory($this->getId())) {
1195 $isAnswered = $this->isAnswered($active_id, $pass);
1196 } else {
1197 $isAnswered = true;
1198 }
1199
1200 if (is_null($reached_points)) {
1201 $reached_points = 0;
1202 }
1203
1204 // fau: testNav - check for existing authorized solution to know if a result record should be written
1205 $existingSolutions = $this->lookupForExistingSolutions($active_id, $pass);
1206
1207 $this->getProcessLocker()->executeUserQuestionResultUpdateOperation(function () use ($ilDB, $active_id, $pass, $reached_points, $requestsStatisticData, $isAnswered, $existingSolutions) {
1208 $query = "
1209 DELETE FROM tst_test_result
1210
1211 WHERE active_fi = %s
1212 AND question_fi = %s
1213 AND pass = %s
1214 ";
1215
1216 $types = array('integer', 'integer', 'integer');
1217 $values = array($active_id, $this->getId(), $pass);
1218
1219 if ($this->getStep() !== null) {
1220 $query .= "
1221 AND step = %s
1222 ";
1223
1224 $types[] = 'integer';
1225 $values[] = $this->getStep();
1226 }
1227 $ilDB->manipulateF($query, $types, $values);
1228
1229 if ($existingSolutions['authorized']) {
1230 $next_id = $ilDB->nextId("tst_test_result");
1231 $fieldData = array(
1232 'test_result_id' => array('integer', $next_id),
1233 'active_fi' => array('integer', $active_id),
1234 'question_fi' => array('integer', $this->getId()),
1235 'pass' => array('integer', $pass),
1236 'points' => array('float', $reached_points),
1237 'tstamp' => array('integer', time()),
1238 'hint_count' => array('integer', $requestsStatisticData->getRequestsCount()),
1239 'hint_points' => array('float', $requestsStatisticData->getRequestsPoints()),
1240 'answered' => array('integer', $isAnswered)
1241 );
1242
1243 if ($this->getStep() !== null) {
1244 $fieldData['step'] = array('integer', $this->getStep());
1245 }
1246
1247 $ilDB->insert('tst_test_result', $fieldData);
1248 }
1249 });
1250 // fau.
1251
1252 include_once("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
1253
1256 sprintf(
1257 $this->lng->txtlng(
1258 "assessment",
1259 "log_user_answered_question",
1261 ),
1262 $reached_points
1263 ),
1264 $active_id,
1265 $this->getId()
1266 );
1267 }
1268
1269 // update test pass results
1270 self::_updateTestPassResults($active_id, $pass, $obligationsEnabled, $this->getProcessLocker());
1271
1272 // Update objective status
1273 include_once 'Modules/Course/classes/class.ilCourseObjectiveResult.php';
1274 ilCourseObjectiveResult::_updateObjectiveResult($ilUser->getId(), $active_id, $this->getId());
1275 }
1276
1285 final public function persistWorkingState($active_id, $pass = null, $obligationsEnabled = false, $authorized = true)
1286 {
1287 if ($pass === null) {
1288 require_once 'Modules/Test/classes/class.ilObjTest.php';
1289 $pass = ilObjTest::_getPass($active_id);
1290 }
1291
1292 if (!$this->validateSolutionSubmit()) {
1293 return false;
1294 }
1295
1296 $saveStatus = false;
1297
1298 $this->getProcessLocker()->executePersistWorkingStateLockOperation(function () use ($active_id, $pass, $authorized, $obligationsEnabled, &$saveStatus) {
1299 $saveStatus = $this->saveWorkingData($active_id, $pass, $authorized);
1300
1301 if ($authorized) {
1302 // fau: testNav - remove an intermediate solution if the authorized solution is saved
1303 // the intermediate solution would set the displayed question status as "editing ..."
1304 $this->removeIntermediateSolution($active_id, $pass);
1305 // fau.
1306 $this->calculateResultsFromSolution($active_id, $pass, $obligationsEnabled);
1307 }
1308
1309 $this->reworkWorkingData($active_id, $pass, $obligationsEnabled, $authorized);
1310 });
1311
1312 return $saveStatus;
1313 }
1314
1318 final public function persistPreviewState(ilAssQuestionPreviewSession $previewSession)
1319 {
1320 $this->savePreviewData($previewSession);
1321 return $this->validateSolutionSubmit();
1322 }
1323
1324 public function validateSolutionSubmit()
1325 {
1326 return true;
1327 }
1328
1338 abstract public function saveWorkingData($active_id, $pass = null, $authorized = true);
1339
1347 abstract protected function reworkWorkingData($active_id, $pass, $obligationsAnswered, $authorized);
1348
1349 protected function savePreviewData(ilAssQuestionPreviewSession $previewSession)
1350 {
1351 $previewSession->setParticipantsSolution($this->getSolutionSubmit());
1352 }
1353
1355 public static function _updateTestResultCache($active_id, ilAssQuestionProcessLocker $processLocker = null)
1356 {
1357 global $ilDB;
1358
1359 include_once "./Modules/Test/classes/class.ilObjTest.php";
1360 include_once "./Modules/Test/classes/class.assMarkSchema.php";
1361
1362 $pass = ilObjTest::_getResultPass($active_id);
1363
1364 $query = "
1365 SELECT tst_pass_result.*
1366 FROM tst_pass_result
1367 WHERE active_fi = %s
1368 AND pass = %s
1369 ";
1370
1371 $result = $ilDB->queryF(
1372 $query,
1373 array('integer','integer'),
1374 array($active_id, $pass)
1375 );
1376
1377 $row = $ilDB->fetchAssoc($result);
1378
1379 $max = $row['maxpoints'];
1380 $reached = $row['points'];
1381
1382 $obligationsAnswered = (int) $row['obligations_answered'];
1383
1384 include_once "./Modules/Test/classes/class.assMarkSchema.php";
1385
1386 $percentage = (!$max) ? 0 : ($reached / $max) * 100.0;
1387
1388 $mark = ASS_MarkSchema::_getMatchingMarkFromActiveId($active_id, $percentage);
1389
1390 $isPassed = ($mark["passed"] ? 1 : 0);
1391 $isFailed = (!$mark["passed"] ? 1 : 0);
1392
1393 $userTestResultUpdateCallback = function () use ($ilDB, $active_id, $pass, $max, $reached, $isFailed, $isPassed, $obligationsAnswered, $row, $mark) {
1394 $query = "
1395 DELETE FROM tst_result_cache
1396 WHERE active_fi = %s
1397 ";
1398 $ilDB->manipulateF(
1399 $query,
1400 array('integer'),
1401 array($active_id)
1402 );
1403
1404 $ilDB->insert('tst_result_cache', array(
1405 'active_fi'=> array('integer', $active_id),
1406 'pass'=> array('integer', strlen($pass) ? $pass : 0),
1407 'max_points'=> array('float', strlen($max) ? $max : 0),
1408 'reached_points'=> array('float', strlen($reached) ? $reached : 0),
1409 'mark_short'=> array('text', strlen($mark["short_name"]) ? $mark["short_name"] : " "),
1410 'mark_official'=> array('text', strlen($mark["official_name"]) ? $mark["official_name"] : " "),
1411 'passed'=> array('integer', $isPassed),
1412 'failed'=> array('integer', $isFailed),
1413 'tstamp'=> array('integer', time()),
1414 'hint_count'=> array('integer', $row['hint_count']),
1415 'hint_points'=> array('float', $row['hint_points']),
1416 'obligations_answered' => array('integer', $obligationsAnswered)
1417 ));
1418 };
1419
1420 if (is_object($processLocker)) {
1421 $processLocker->executeUserTestResultUpdateLockOperation($userTestResultUpdateCallback);
1422 } else {
1423 $userTestResultUpdateCallback();
1424 }
1425 }
1426
1428 public static function _updateTestPassResults($active_id, $pass, $obligationsEnabled = false, ilAssQuestionProcessLocker $processLocker = null, $test_obj_id = null)
1429 {
1430 global $ilDB;
1431
1432 include_once "./Modules/Test/classes/class.ilObjTest.php";
1433
1434 if (self::getResultGateway() !== null) {
1435 $data = self::getResultGateway()->getQuestionCountAndPointsForPassOfParticipant($active_id, $pass);
1436 $time = self::getResultGateway()->getWorkingTimeOfParticipantForPass($active_id, $pass);
1437 } else {
1440 }
1441
1442
1443 // update test pass results
1444
1445 $result = $ilDB->queryF(
1446 "
1447 SELECT SUM(points) reachedpoints,
1448 SUM(hint_count) hint_count,
1449 SUM(hint_points) hint_points,
1450 COUNT(DISTINCT(question_fi)) answeredquestions
1451 FROM tst_test_result
1452 WHERE active_fi = %s
1453 AND pass = %s
1454 ",
1455 array('integer','integer'),
1456 array($active_id, $pass)
1457 );
1458
1459 if ($result->numRows() > 0) {
1460 if ($obligationsEnabled) {
1461 $query = '
1462 SELECT answered answ
1463 FROM tst_test_question
1464 INNER JOIN tst_active
1465 ON active_id = %s
1466 AND tst_test_question.test_fi = tst_active.test_fi
1467 LEFT JOIN tst_test_result
1468 ON tst_test_result.active_fi = %s
1469 AND tst_test_result.pass = %s
1470 AND tst_test_question.question_fi = tst_test_result.question_fi
1471 WHERE obligatory = 1';
1472
1473 $result_obligatory = $ilDB->queryF(
1474 $query,
1475 array('integer','integer','integer'),
1476 array($active_id, $active_id, $pass)
1477 );
1478
1479 $obligations_answered = 1;
1480
1481 while ($row_obligatory = $ilDB->fetchAssoc($result_obligatory)) {
1482 if (!(int) $row_obligatory['answ']) {
1483 $obligations_answered = 0;
1484 break;
1485 }
1486 }
1487 } else {
1488 $obligations_answered = 1;
1489 }
1490
1491 $row = $ilDB->fetchAssoc($result);
1492
1493 if ($row['hint_count'] === null) {
1494 $row['hint_count'] = 0;
1495 }
1496 if ($row['hint_points'] === null) {
1497 $row['hint_points'] = 0;
1498 }
1499
1500 $exam_identifier = ilObjTest::buildExamId($active_id, $pass, $test_obj_id);
1501
1502 $updatePassResultCallback = function () use ($ilDB, $data, $active_id, $pass, $row, $time, $obligations_answered, $exam_identifier) {
1503
1505 $ilDB->replace(
1506 'tst_pass_result',
1507 array(
1508 'active_fi' => array('integer', $active_id),
1509 'pass' => array('integer', strlen($pass) ? $pass : 0)),
1510 array(
1511 'points' => array('float', $row['reachedpoints'] ? $row['reachedpoints'] : 0),
1512 'maxpoints' => array('float', $data['points']),
1513 'questioncount' => array('integer', $data['count']),
1514 'answeredquestions' => array('integer', $row['answeredquestions']),
1515 'workingtime' => array('integer', $time),
1516 'tstamp' => array('integer', time()),
1517 'hint_count' => array('integer', $row['hint_count']),
1518 'hint_points' => array('float', $row['hint_points']),
1519 'obligations_answered' => array('integer', $obligations_answered),
1520 'exam_id' => array('text', $exam_identifier)
1521 )
1522 );
1523 };
1524
1525 if (is_object($processLocker)) {
1526 $processLocker->executeUserPassResultUpdateLockOperation($updatePassResultCallback);
1527 } else {
1528 $updatePassResultCallback();
1529 }
1530 }
1531
1533
1534 return array(
1535 'active_fi' => $active_id,
1536 'pass' => $pass,
1537 'points' => ($row["reachedpoints"]) ? $row["reachedpoints"] : 0,
1538 'maxpoints' => $data["points"],
1539 'questioncount' => $data["count"],
1540 'answeredquestions' => $row["answeredquestions"],
1541 'workingtime' => $time,
1542 'tstamp' => time(),
1543 'hint_count' => $row['hint_count'],
1544 'hint_points' => $row['hint_points'],
1545 'obligations_answered' => $obligations_answered,
1546 'exam_id' => $exam_identifier
1547 );
1548 }
1549
1557 public static function logAction($logtext = "", $active_id = "", $question_id = "")
1558 {
1559 $original_id = "";
1560 if (strlen($question_id)) {
1561 $original_id = self::_getOriginalId($question_id);
1562 }
1563
1564 require_once 'Modules/Test/classes/class.ilObjAssessmentFolder.php';
1565 require_once 'Modules/Test/classes/class.ilObjTest.php';
1566
1568 $GLOBALS['DIC']['ilUser']->getId(),
1570 $logtext,
1571 $question_id,
1573 );
1574 }
1575
1584 {
1585 $mediatempdir = CLIENT_WEB_DIR . "/assessment/temp";
1586 if (!@is_dir($mediatempdir)) {
1587 ilUtil::createDirectory($mediatempdir);
1588 }
1589 $temp_name = tempnam($mediatempdir, $name . "_____");
1590 $temp_name = str_replace("\\", "/", $temp_name);
1591 @unlink($temp_name);
1592 if (!ilUtil::moveUploadedFile($file, $name, $temp_name)) {
1593 return false;
1594 } else {
1595 return $temp_name;
1596 }
1597 }
1598
1605 {
1606 return CLIENT_WEB_DIR . "/assessment/$this->obj_id/$this->id/solution/";
1607 }
1608
1615 public function getJavaPath()
1616 {
1617 return CLIENT_WEB_DIR . "/assessment/$this->obj_id/$this->id/java/";
1618 }
1619
1626 public function getImagePath($question_id = null, $object_id = null)
1627 {
1628 if ($question_id === null) {
1629 $question_id = $this->id;
1630 }
1631
1632 if ($object_id === null) {
1633 $object_id = $this->obj_id;
1634 }
1635
1636 return $this->buildImagePath($question_id, $object_id);
1637 }
1638
1639 public function buildImagePath($questionId, $parentObjectId)
1640 {
1641 return CLIENT_WEB_DIR . "/assessment/{$parentObjectId}/{$questionId}/images/";
1642 }
1643
1650 public function getFlashPath()
1651 {
1652 return CLIENT_WEB_DIR . "/assessment/$this->obj_id/$this->id/flash/";
1653 }
1654
1661 public function getJavaPathWeb()
1662 {
1663 include_once "./Services/Utilities/classes/class.ilUtil.php";
1664 $webdir = ilUtil::removeTrailingPathSeparators(CLIENT_WEB_DIR) . "/assessment/$this->obj_id/$this->id/java/";
1665 return str_replace(ilUtil::removeTrailingPathSeparators(ILIAS_ABSOLUTE_PATH), ilUtil::removeTrailingPathSeparators(ILIAS_HTTP_PATH), $webdir);
1666 }
1667
1674 {
1675 include_once "./Services/Utilities/classes/class.ilUtil.php";
1676 $webdir = ilUtil::removeTrailingPathSeparators(CLIENT_WEB_DIR) . "/assessment/$this->obj_id/$this->id/solution/";
1677 return str_replace(ilUtil::removeTrailingPathSeparators(ILIAS_ABSOLUTE_PATH), ilUtil::removeTrailingPathSeparators(ILIAS_HTTP_PATH), $webdir);
1678 }
1679
1688 public function getImagePathWeb()
1689 {
1690 if (!$this->export_image_path) {
1691 include_once "./Services/Utilities/classes/class.ilUtil.php";
1692 $webdir = ilUtil::removeTrailingPathSeparators(CLIENT_WEB_DIR) . "/assessment/$this->obj_id/$this->id/images/";
1693 return str_replace(ilUtil::removeTrailingPathSeparators(ILIAS_ABSOLUTE_PATH), ilUtil::removeTrailingPathSeparators(ILIAS_HTTP_PATH), $webdir);
1694 } else {
1696 }
1697 }
1698
1705 public function getFlashPathWeb()
1706 {
1707 include_once "./Services/Utilities/classes/class.ilUtil.php";
1708 $webdir = ilUtil::removeTrailingPathSeparators(CLIENT_WEB_DIR) . "/assessment/$this->obj_id/$this->id/flash/";
1709 return str_replace(ilUtil::removeTrailingPathSeparators(ILIAS_ABSOLUTE_PATH), ilUtil::removeTrailingPathSeparators(ILIAS_HTTP_PATH), $webdir);
1710 }
1711
1712 // hey: prevPassSolutions - accept and prefer intermediate only from current pass
1713 public function getTestOutputSolutions($activeId, $pass)
1714 {
1715 // hey: refactored identifiers
1716 if ($this->getTestPresentationConfig()->isSolutionInitiallyPrefilled()) {
1717 // hey.
1718 return $this->getSolutionValues($activeId, $pass, true);
1719 }
1720
1721 return $this->getUserSolutionPreferingIntermediate($activeId, $pass);
1722 }
1723 // hey.
1724
1725 public function getUserSolutionPreferingIntermediate($active_id, $pass = null)
1726 {
1727 $solution = $this->getSolutionValues($active_id, $pass, false);
1728
1729 if (!count($solution)) {
1730 $solution = $this->getSolutionValues($active_id, $pass, true);
1731 }
1732
1733 return $solution;
1734 }
1735
1739 public function getSolutionValues($active_id, $pass = null, $authorized = true)
1740 {
1741 global $ilDB;
1742
1743 if (is_null($pass)) {
1744 $pass = $this->getSolutionMaxPass($active_id);
1745 }
1746
1747 if ($this->getStep() !== null) {
1748 $query = "
1749 SELECT *
1750 FROM tst_solutions
1751 WHERE active_fi = %s
1752 AND question_fi = %s
1753 AND pass = %s
1754 AND step = %s
1755 AND authorized = %s
1756 ORDER BY solution_id";
1757
1758 $result = $ilDB->queryF(
1759 $query,
1760 array('integer', 'integer', 'integer', 'integer', 'integer'),
1761 array($active_id, $this->getId(), $pass, $this->getStep(), (int) $authorized)
1762 );
1763 } else {
1764 $query = "
1765 SELECT *
1766 FROM tst_solutions
1767 WHERE active_fi = %s
1768 AND question_fi = %s
1769 AND pass = %s
1770 AND authorized = %s
1771 ORDER BY solution_id
1772 ";
1773
1774 $result = $ilDB->queryF(
1775 $query,
1776 array('integer', 'integer', 'integer', 'integer'),
1777 array($active_id, $this->getId(), $pass, (int) $authorized)
1778 );
1779 }
1780
1781 $values = array();
1782
1783 while ($row = $ilDB->fetchAssoc($result)) {
1784 $values[] = $row;
1785 }
1786
1787 return $values;
1788 }
1789
1796 public function isInUse($question_id = "")
1797 {
1798 global $ilDB;
1799
1800 if ($question_id < 1) {
1801 $question_id = $this->getId();
1802 }
1803 $result = $ilDB->queryF(
1804 "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",
1805 array('integer'),
1806 array($question_id)
1807 );
1808 $row = $ilDB->fetchAssoc($result);
1809 $count = $row["question_count"];
1810
1811 $result = $ilDB->queryF(
1812 "
1813 SELECT tst_active.test_fi
1814 FROM qpl_questions
1815 INNER JOIN tst_test_rnd_qst ON tst_test_rnd_qst.question_fi = qpl_questions.question_id
1816 INNER JOIN tst_active ON tst_active.active_id = tst_test_rnd_qst.active_fi
1817 WHERE qpl_questions.original_id = %s
1818 GROUP BY tst_active.test_fi",
1819 array('integer'),
1820 array($question_id)
1821 );
1822 $count += $result->numRows();
1823
1824 return $count;
1825 }
1826
1833 public function isClone($question_id = "")
1834 {
1835 global $ilDB;
1836
1837 if ($question_id < 1) {
1838 $question_id = $this->id;
1839 }
1840 $result = $ilDB->queryF(
1841 "SELECT original_id FROM qpl_questions WHERE question_id = %s",
1842 array('integer'),
1843 array($question_id)
1844 );
1845 $row = $ilDB->fetchAssoc($result);
1846 return ($row["original_id"] > 0) ? true : false;
1847 }
1848
1855 public function pcArrayShuffle($array)
1856 {
1857 $keys = array_keys($array);
1858 shuffle($keys);
1859 $result = array();
1860 foreach ($keys as $key) {
1861 $result[$key] = $array[$key];
1862 }
1863 return $result;
1864 }
1865
1871 public static function getQuestionTypeFromDb($question_id)
1872 {
1873 global $ilDB;
1874
1875 $result = $ilDB->queryF(
1876 "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",
1877 array('integer'),
1878 array($question_id)
1879 );
1880 $data = $ilDB->fetchAssoc($result);
1881 return $data["type_tag"];
1882 }
1883
1890 public function getAdditionalTableName()
1891 {
1892 return "";
1893 }
1894
1901 public function getAnswerTableName()
1902 {
1903 return "";
1904 }
1905
1912 public function deleteAnswers($question_id)
1913 {
1914 global $ilDB;
1915 $answer_table_name = $this->getAnswerTableName();
1916
1917 if (!is_array($answer_table_name)) {
1918 $answer_table_name = array($answer_table_name);
1919 }
1920
1921 foreach ($answer_table_name as $table) {
1922 if (strlen($table)) {
1923 $affectedRows = $ilDB->manipulateF(
1924 "DELETE FROM $table WHERE question_fi = %s",
1925 array('integer'),
1926 array($question_id)
1927 );
1928 }
1929 }
1930 }
1931
1938 public function deleteAdditionalTableData($question_id)
1939 {
1940 global $ilDB;
1941
1942 $additional_table_name = $this->getAdditionalTableName();
1943
1944 if (!is_array($additional_table_name)) {
1945 $additional_table_name = array($additional_table_name);
1946 }
1947
1948 foreach ($additional_table_name as $table) {
1949 if (strlen($table)) {
1950 $affectedRows = $ilDB->manipulateF(
1951 "DELETE FROM $table WHERE question_fi = %s",
1952 array('integer'),
1953 array($question_id)
1954 );
1955 }
1956 }
1957 }
1958
1965 protected function deletePageOfQuestion($question_id)
1966 {
1967 include_once "./Modules/TestQuestionPool/classes/class.ilAssQuestionPage.php";
1968 $page = new ilAssQuestionPage($question_id);
1969 $page->delete();
1970 return true;
1971 }
1972
1979 public function delete($question_id)
1980 {
1981 global $ilDB, $ilLog;
1982
1983 if ($question_id < 1) {
1984 return true;
1985 } // nothing to do
1986
1987 $result = $ilDB->queryF(
1988 "SELECT obj_fi FROM qpl_questions WHERE question_id = %s",
1989 array('integer'),
1990 array($question_id)
1991 );
1992 if ($result->numRows() == 1) {
1993 $row = $ilDB->fetchAssoc($result);
1994 $obj_id = $row["obj_fi"];
1995 } else {
1996 return true; // nothing to do
1997 }
1998 try {
1999 $this->deletePageOfQuestion($question_id);
2000 } catch (Exception $e) {
2001 $ilLog->write("EXCEPTION: Could not delete page of question $question_id: $e");
2002 return false;
2003 }
2004
2005 $affectedRows = $ilDB->manipulateF(
2006 "DELETE FROM qpl_questions WHERE question_id = %s",
2007 array('integer'),
2008 array($question_id)
2009 );
2010 if ($affectedRows == 0) {
2011 return false;
2012 }
2013
2014 try {
2015 $this->deleteAdditionalTableData($question_id);
2016 $this->deleteAnswers($question_id);
2017 $this->feedbackOBJ->deleteGenericFeedbacks($question_id, $this->isAdditionalContentEditingModePageObject());
2018 $this->feedbackOBJ->deleteSpecificAnswerFeedbacks($question_id, $this->isAdditionalContentEditingModePageObject());
2019 } catch (Exception $e) {
2020 $ilLog->write("EXCEPTION: Could not delete additional table data of question $question_id: $e");
2021 return false;
2022 }
2023
2024 try {
2025 // delete the question in the tst_test_question table (list of test questions)
2026 $affectedRows = $ilDB->manipulateF(
2027 "DELETE FROM tst_test_question WHERE question_fi = %s",
2028 array('integer'),
2029 array($question_id)
2030 );
2031 } catch (Exception $e) {
2032 $ilLog->write("EXCEPTION: Could not delete delete question $question_id from a test: $e");
2033 return false;
2034 }
2035
2036 try {
2037 // delete suggested solutions contained in the question
2038 $affectedRows = $ilDB->manipulateF(
2039 "DELETE FROM qpl_sol_sug WHERE question_fi = %s",
2040 array('integer'),
2041 array($question_id)
2042 );
2043 } catch (Exception $e) {
2044 $ilLog->write("EXCEPTION: Could not delete suggested solutions of question $question_id: $e");
2045 return false;
2046 }
2047
2048 try {
2049 $directory = CLIENT_WEB_DIR . "/assessment/" . $obj_id . "/$question_id";
2050 if (preg_match("/\d+/", $obj_id) and preg_match("/\d+/", $question_id) and is_dir($directory)) {
2051 include_once "./Services/Utilities/classes/class.ilUtil.php";
2052 ilUtil::delDir($directory);
2053 }
2054 } catch (Exception $e) {
2055 $ilLog->write("EXCEPTION: Could not delete question file directory $directory of question $question_id: $e");
2056 return false;
2057 }
2058
2059 try {
2060 include_once("./Services/MediaObjects/classes/class.ilObjMediaObject.php");
2061 $mobs = ilObjMediaObject::_getMobsOfObject("qpl:html", $question_id);
2062 // remaining usages are not in text anymore -> delete them
2063 // and media objects (note: delete method of ilObjMediaObject
2064 // checks whether object is used in another context; if yes,
2065 // the object is not deleted!)
2066 foreach ($mobs as $mob) {
2067 ilObjMediaObject::_removeUsage($mob, "qpl:html", $question_id);
2068 if (ilObjMediaObject::_exists($mob)) {
2069 $mob_obj = new ilObjMediaObject($mob);
2070 $mob_obj->delete();
2071 }
2072 }
2073 } catch (Exception $e) {
2074 $ilLog->write("EXCEPTION: Error deleting the media objects of question $question_id: $e");
2075 return false;
2076 }
2077
2078 require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionHintTracking.php';
2079 ilAssQuestionHintTracking::deleteRequestsByQuestionIds(array($question_id));
2080
2081 require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionHintList.php';
2083
2084 require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionSkillAssignmentList.php';
2085 $assignmentList = new ilAssQuestionSkillAssignmentList($ilDB);
2086 $assignmentList->setParentObjId($obj_id);
2087 $assignmentList->setQuestionIdFilter($question_id);
2088 $assignmentList->loadFromDb();
2089 foreach ($assignmentList->getAssignmentsByQuestionId($question_id) as $assignment) {
2090 /* @var ilAssQuestionSkillAssignment $assignment */
2091 $assignment->deleteFromDb();
2092 }
2093
2095
2096 try {
2097 // update question count of question pool
2098 include_once "./Modules/TestQuestionPool/classes/class.ilObjQuestionPool.php";
2100 } catch (Exception $e) {
2101 $ilLog->write("EXCEPTION: Error updating the question pool question count of question pool " . $this->getObjId() . " when deleting question $question_id: $e");
2102 return false;
2103 }
2104
2105 $this->notifyQuestionDeleted($this);
2106
2107 return true;
2108 }
2109
2110 private function deleteTaxonomyAssignments()
2111 {
2112 require_once 'Services/Taxonomy/classes/class.ilObjTaxonomy.php';
2113 require_once 'Services/Taxonomy/classes/class.ilTaxNodeAssignment.php';
2114 $taxIds = ilObjTaxonomy::getUsageOfObject($this->getObjId());
2115
2116 foreach ($taxIds as $taxId) {
2117 $taxNodeAssignment = new ilTaxNodeAssignment('qpl', $this->getObjId(), 'quest', $taxId);
2118 $taxNodeAssignment->deleteAssignmentsOfItem($this->getId());
2119 }
2120 }
2121
2125 public function getTotalAnswers()
2126 {
2127 return $this->_getTotalAnswers($this->id);
2128 }
2129
2136 public function _getTotalAnswers($a_q_id)
2137 {
2138 global $ilDB;
2139
2140 // get all question references to the question id
2141 $result = $ilDB->queryF(
2142 "SELECT question_id FROM qpl_questions WHERE original_id = %s OR question_id = %s",
2143 array('integer','integer'),
2144 array($a_q_id, $a_q_id)
2145 );
2146 if ($result->numRows() == 0) {
2147 return 0;
2148 }
2149 $found_id = array();
2150 while ($row = $ilDB->fetchAssoc($result)) {
2151 array_push($found_id, $row["question_id"]);
2152 }
2153
2154 $result = $ilDB->query("SELECT * FROM tst_test_result WHERE " . $ilDB->in('question_fi', $found_id, false, 'integer'));
2155
2156 return $result->numRows();
2157 }
2158
2159
2166 public static function _getTotalRightAnswers($a_q_id)
2167 {
2168 global $ilDB;
2169 $result = $ilDB->queryF(
2170 "SELECT question_id FROM qpl_questions WHERE original_id = %s OR question_id = %s",
2171 array('integer','integer'),
2172 array($a_q_id, $a_q_id)
2173 );
2174 if ($result->numRows() == 0) {
2175 return 0;
2176 }
2177 $found_id = array();
2178 while ($row = $ilDB->fetchAssoc($result)) {
2179 array_push($found_id, $row["question_id"]);
2180 }
2181 $result = $ilDB->query("SELECT * FROM tst_test_result WHERE " . $ilDB->in('question_fi', $found_id, false, 'integer'));
2182 $answers = array();
2183 while ($row = $ilDB->fetchAssoc($result)) {
2184 $reached = $row["points"];
2185 include_once "./Modules/TestQuestionPool/classes/class.assQuestion.php";
2186 $max = assQuestion::_getMaximumPoints($row["question_fi"]);
2187 array_push($answers, array("reached" => $reached, "max" => $max));
2188 }
2189 $max = 0.0;
2190 $reached = 0.0;
2191 foreach ($answers as $key => $value) {
2192 $max += $value["max"];
2193 $reached += $value["reached"];
2194 }
2195 if ($max > 0) {
2196 return $reached / $max;
2197 } else {
2198 return 0;
2199 }
2200 }
2201
2207 public static function _getTitle($a_q_id)
2208 {
2209 global $ilDB;
2210 $result = $ilDB->queryF(
2211 "SELECT title FROM qpl_questions WHERE question_id = %s",
2212 array('integer'),
2213 array($a_q_id)
2214 );
2215 if ($result->numRows() == 1) {
2216 $row = $ilDB->fetchAssoc($result);
2217 return $row["title"];
2218 } else {
2219 return "";
2220 }
2221 }
2222
2228 public static function _getQuestionText($a_q_id)
2229 {
2230 global $ilDB;
2231 $result = $ilDB->queryF(
2232 "SELECT question_text FROM qpl_questions WHERE question_id = %s",
2233 array('integer'),
2234 array($a_q_id)
2235 );
2236 if ($result->numRows() == 1) {
2237 $row = $ilDB->fetchAssoc($result);
2238 return $row["question_text"];
2239 } else {
2240 return "";
2241 }
2242 }
2243
2244 public static function isFileAvailable($file)
2245 {
2246 if (!file_exists($file)) {
2247 return false;
2248 }
2249
2250 if (!is_file($file)) {
2251 return false;
2252 }
2253
2254 if (!is_readable($file)) {
2255 return false;
2256 }
2257
2258 return true;
2259 }
2260
2261 public function copyXHTMLMediaObjectsOfQuestion($a_q_id)
2262 {
2263 include_once("./Services/MediaObjects/classes/class.ilObjMediaObject.php");
2264 $mobs = ilObjMediaObject::_getMobsOfObject("qpl:html", $a_q_id);
2265 foreach ($mobs as $mob) {
2266 ilObjMediaObject::_saveUsage($mob, "qpl:html", $this->getId());
2267 }
2268 }
2269
2271 {
2272 include_once("./Services/MediaObjects/classes/class.ilObjMediaObject.php");
2273 $mobs = ilObjMediaObject::_getMobsOfObject("qpl:html", $this->getId());
2274 foreach ($mobs as $mob) {
2275 ilObjMediaObject::_saveUsage($mob, "qpl:html", $this->original_id);
2276 }
2277 }
2278
2282 public function createPageObject()
2283 {
2284 $qpl_id = $this->getObjId();
2285
2286 include_once "./Modules/TestQuestionPool/classes/class.ilAssQuestionPage.php";
2287 $this->page = new ilAssQuestionPage(0);
2288 $this->page->setId($this->getId());
2289 $this->page->setParentId($qpl_id);
2290 $this->page->setXMLContent("<PageObject><PageContent>" .
2291 "<Question QRef=\"il__qst_" . $this->getId() . "\"/>" .
2292 "</PageContent></PageObject>");
2293 $this->page->create();
2294 }
2295
2296 public function copyPageOfQuestion($a_q_id)
2297 {
2298 if ($a_q_id > 0) {
2299 include_once "./Modules/TestQuestionPool/classes/class.ilAssQuestionPage.php";
2300 $page = new ilAssQuestionPage($a_q_id);
2301
2302 $xml = str_replace("il__qst_" . $a_q_id, "il__qst_" . $this->id, $page->getXMLContent());
2303 $this->page->setXMLContent($xml);
2304 $this->page->updateFromXML();
2305 }
2306 }
2307
2308 public function getPageOfQuestion()
2309 {
2310 include_once "./Modules/TestQuestionPool/classes/class.ilAssQuestionPage.php";
2311 $page = new ilAssQuestionPage($this->id);
2312 return $page->getXMLContent();
2313 }
2314
2320 public static function _getQuestionType($question_id)
2321 {
2322 global $ilDB;
2323
2324 if ($question_id < 1) {
2325 return "";
2326 }
2327 $result = $ilDB->queryF(
2328 "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",
2329 array('integer'),
2330 array($question_id)
2331 );
2332 if ($result->numRows() == 1) {
2333 $data = $ilDB->fetchAssoc($result);
2334 return $data["type_tag"];
2335 } else {
2336 return "";
2337 }
2338 }
2339
2347 public static function _getQuestionTitle($question_id)
2348 {
2349 global $ilDB;
2350
2351 if ($question_id < 1) {
2352 return "";
2353 }
2354
2355 $result = $ilDB->queryF(
2356 "SELECT title FROM qpl_questions WHERE qpl_questions.question_id = %s",
2357 array('integer'),
2358 array($question_id)
2359 );
2360 if ($result->numRows() == 1) {
2361 $data = $ilDB->fetchAssoc($result);
2362 return $data["title"];
2363 } else {
2364 return "";
2365 }
2366 }
2367
2369 {
2370 $this->original_id = $original_id;
2371 }
2372
2373 public function getOriginalId()
2374 {
2375 return $this->original_id;
2376 }
2377
2378 protected static $imageSourceFixReplaceMap = array(
2379 'ok.svg' => 'ok.png', 'not_ok.svg' => 'not_ok.png',
2380 'checkbox_checked.svg' => 'checkbox_checked.png',
2381 'checkbox_unchecked.svg' => 'checkbox_unchecked.png',
2382 'radiobutton_checked.svg' => 'radiobutton_checked.png',
2383 'radiobutton_unchecked.svg' => 'radiobutton_unchecked.png'
2384 );
2385
2386 public function fixSvgToPng($imageFilenameContainingString)
2387 {
2388 $needles = array_keys(self::$imageSourceFixReplaceMap);
2389 $replacements = array_values(self::$imageSourceFixReplaceMap);
2390 return str_replace($needles, $replacements, $imageFilenameContainingString);
2391 }
2392
2393
2395 {
2396 $matches = null;
2397 if (preg_match_all('/src="(.*?)"/m', $html, $matches)) {
2398 $sources = $matches[1];
2399
2400 $needleReplacementMap = array();
2401
2402 foreach ($sources as $src) {
2403 $file = ilUtil::removeTrailingPathSeparators(ILIAS_ABSOLUTE_PATH) . DIRECTORY_SEPARATOR . $src;
2404
2405 if (file_exists($file)) {
2406 continue;
2407 }
2408
2409 $levels = explode(DIRECTORY_SEPARATOR, $src);
2410 if (count($levels) < 5 || $levels[0] != 'Customizing' || $levels[2] != 'skin') {
2411 continue;
2412 }
2413
2414 $component = '';
2415
2416 if ($levels[4] == 'Modules' || $levels[4] == 'Services') {
2417 $component = $levels[4] . DIRECTORY_SEPARATOR . $levels[5];
2418 }
2419
2420 $needleReplacementMap[$src] = ilUtil::getImagePath(basename($src), $component);
2421 }
2422
2423 if (count($needleReplacementMap)) {
2424 $html = str_replace(array_keys($needleReplacementMap), array_values($needleReplacementMap), $html);
2425 }
2426 }
2427
2428 return $html;
2429 }
2430
2437 public function loadFromDb($question_id)
2438 {
2439 global $ilDB;
2440
2441 $result = $ilDB->queryF(
2442 "SELECT external_id FROM qpl_questions WHERE question_id = %s",
2443 array("integer"),
2444 array($question_id)
2445 );
2446 if ($result->numRows() == 1) {
2447 $data = $ilDB->fetchAssoc($result);
2448 $this->external_id = $data['external_id'];
2449 }
2450
2451 $result = $ilDB->queryF(
2452 "SELECT * FROM qpl_sol_sug WHERE question_fi = %s",
2453 array('integer'),
2454 array($this->getId())
2455 );
2456 $this->suggested_solutions = array();
2457 if ($result->numRows()) {
2458 include_once("./Services/RTE/classes/class.ilRTE.php");
2459 while ($row = $ilDB->fetchAssoc($result)) {
2460 $value = (is_array(unserialize($row["value"]))) ? unserialize($row["value"]) : ilRTE::_replaceMediaObjectImageSrc($row["value"], 1);
2461 $this->suggested_solutions[$row["subquestion_index"]] = array(
2462 "type" => $row["type"],
2463 "value" => $value,
2464 "internal_link" => $row["internal_link"],
2465 "import_id" => $row["import_id"]
2466 );
2467 }
2468 }
2469 }
2470
2477 public function createNewQuestion($a_create_page = true)
2478 {
2479 global $ilDB, $ilUser;
2480
2481 $complete = "0";
2482 $estw_time = $this->getEstimatedWorkingTime();
2483 $estw_time = sprintf("%02d:%02d:%02d", $estw_time['h'], $estw_time['m'], $estw_time['s']);
2484 $obj_id = ($this->getObjId() <= 0) ? (ilObject::_lookupObjId((strlen($_GET["ref_id"])) ? $_GET["ref_id"] : $_POST["sel_qpl"])) : $this->getObjId();
2485 if ($obj_id > 0) {
2486 if ($a_create_page) {
2487 $tstamp = 0;
2488 } else {
2489 // question pool must not try to purge
2490 $tstamp = time();
2491 }
2492
2493 $next_id = $ilDB->nextId('qpl_questions');
2494 $affectedRows = $ilDB->insert("qpl_questions", array(
2495 "question_id" => array("integer", $next_id),
2496 "question_type_fi" => array("integer", $this->getQuestionTypeID()),
2497 "obj_fi" => array("integer", $obj_id),
2498 "title" => array("text", null),
2499 "description" => array("text", null),
2500 "author" => array("text", $this->getAuthor()),
2501 "owner" => array("integer", $ilUser->getId()),
2502 "question_text" => array("clob", null),
2503 "points" => array("float", 0),
2504 "nr_of_tries" => array("integer", $this->getDefaultNrOfTries()), // #10771
2505 "working_time" => array("text", $estw_time),
2506 "complete" => array("text", $complete),
2507 "created" => array("integer", time()),
2508 "original_id" => array("integer", null),
2509 "tstamp" => array("integer", $tstamp),
2510 "external_id" => array("text", $this->getExternalId()),
2511 'add_cont_edit_mode' => array('text', $this->getAdditionalContentEditingMode())
2512 ));
2513 $this->setId($next_id);
2514
2515 if ($a_create_page) {
2516 // create page object of question
2517 $this->createPageObject();
2518 }
2519 }
2520
2521 $this->notifyQuestionCreated();
2522
2523 return $this->getId();
2524 }
2525
2527 {
2528 global $ilDB;
2529
2530 $estw_time = $this->getEstimatedWorkingTime();
2531 $estw_time = sprintf("%02d:%02d:%02d", $estw_time['h'], $estw_time['m'], $estw_time['s']);
2532
2533 // cleanup RTE images which are not inserted into the question text
2534 include_once("./Services/RTE/classes/class.ilRTE.php");
2535 if ($this->getId() == -1) {
2536 // Neuen Datensatz schreiben
2537 $next_id = $ilDB->nextId('qpl_questions');
2538 $affectedRows = $ilDB->insert("qpl_questions", array(
2539 "question_id" => array("integer", $next_id),
2540 "question_type_fi" => array("integer", $this->getQuestionTypeID()),
2541 "obj_fi" => array("integer", $this->getObjId()),
2542 "title" => array("text", $this->getTitle()),
2543 "description" => array("text", $this->getComment()),
2544 "author" => array("text", $this->getAuthor()),
2545 "owner" => array("integer", $this->getOwner()),
2546 "question_text" => array("clob", ilRTE::_replaceMediaObjectImageSrc($this->getQuestion(), 0)),
2547 "points" => array("float", $this->getMaximumPoints()),
2548 "working_time" => array("text", $estw_time),
2549 "nr_of_tries" => array("integer", $this->getNrOfTries()),
2550 "created" => array("integer", time()),
2551 "original_id" => array("integer", ($original_id) ? $original_id : null),
2552 "tstamp" => array("integer", time()),
2553 "external_id" => array("text", $this->getExternalId()),
2554 'add_cont_edit_mode' => array('text', $this->getAdditionalContentEditingMode())
2555 ));
2556 $this->setId($next_id);
2557 // create page object of question
2558 $this->createPageObject();
2559 } else {
2560 // Vorhandenen Datensatz aktualisieren
2561 $affectedRows = $ilDB->update("qpl_questions", array(
2562 "obj_fi" => array("integer", $this->getObjId()),
2563 "title" => array("text", $this->getTitle()),
2564 "description" => array("text", $this->getComment()),
2565 "author" => array("text", $this->getAuthor()),
2566 "question_text" => array("clob", ilRTE::_replaceMediaObjectImageSrc($this->getQuestion(), 0)),
2567 "points" => array("float", $this->getMaximumPoints()),
2568 "nr_of_tries" => array("integer", $this->getNrOfTries()),
2569 "working_time" => array("text", $estw_time),
2570 "tstamp" => array("integer", time()),
2571 'complete' => array('integer', $this->isComplete()),
2572 "external_id" => array("text", $this->getExternalId())
2573 ), array(
2574 "question_id" => array("integer", $this->getId())
2575 ));
2576 }
2577 }
2578
2585 public function saveToDb($original_id = "")
2586 {
2587 global $ilDB;
2588
2589 $this->updateSuggestedSolutions();
2590
2591 // remove unused media objects from ILIAS
2592 $this->cleanupMediaObjectUsage();
2593
2594 $complete = "0";
2595 if ($this->isComplete()) {
2596 $complete = "1";
2597 }
2598
2599 // update the question time stamp and completion status
2600 $affectedRows = $ilDB->manipulateF(
2601 "UPDATE qpl_questions SET tstamp = %s, owner = %s, complete = %s WHERE question_id = %s",
2602 array('integer','integer', 'integer','text'),
2603 array(time(), ($this->getOwner() <= 0) ? $this->ilias->account->id : $this->getOwner(), $complete, $this->getId())
2604 );
2605
2606 // update question count of question pool
2607 include_once "./Modules/TestQuestionPool/classes/class.ilObjQuestionPool.php";
2609
2610 $this->notifyQuestionEdited($this);
2611 }
2612
2616 public function setNewOriginalId($newId)
2617 {
2618 self::saveOriginalId($this->getId(), $newId);
2619 }
2620
2621 public static function saveOriginalId($questionId, $originalId)
2622 {
2623 $query = "UPDATE qpl_questions SET tstamp = %s, original_id = %s WHERE question_id = %s";
2624
2625 $GLOBALS['DIC']['ilDB']->manipulateF(
2626 $query,
2627 array('integer','integer', 'text'),
2628 array(time(), $originalId, $questionId)
2629 );
2630 }
2631
2632 public static function resetOriginalId($questionId)
2633 {
2634 $query = "UPDATE qpl_questions SET tstamp = %s, original_id = NULL WHERE question_id = %s";
2635
2636 $GLOBALS['DIC']['ilDB']->manipulateF(
2637 $query,
2638 array('integer', 'text'),
2639 array(time(), $questionId)
2640 );
2641 }
2642
2646 protected function onDuplicate($originalParentId, $originalQuestionId, $duplicateParentId, $duplicateQuestionId)
2647 {
2648 $this->duplicateSuggestedSolutionFiles($originalParentId, $originalQuestionId);
2649
2650 // duplicate question feeback
2651 $this->feedbackOBJ->duplicateFeedback($originalQuestionId, $duplicateQuestionId);
2652
2653 // duplicate question hints
2654 $this->duplicateQuestionHints($originalQuestionId, $duplicateQuestionId);
2655
2656 // duplicate skill assignments
2657 $this->duplicateSkillAssignments($originalParentId, $originalQuestionId, $duplicateParentId, $duplicateQuestionId);
2658 }
2659
2660 protected function beforeSyncWithOriginal($origQuestionId, $dupQuestionId, $origParentObjId, $dupParentObjId)
2661 {
2662 }
2663
2664 protected function afterSyncWithOriginal($origQuestionId, $dupQuestionId, $origParentObjId, $dupParentObjId)
2665 {
2666 // sync question feeback
2667 $this->feedbackOBJ->syncFeedback($origQuestionId, $dupQuestionId);
2668 }
2669
2673 protected function onCopy($sourceParentId, $sourceQuestionId, $targetParentId, $targetQuestionId)
2674 {
2675 $this->copySuggestedSolutionFiles($sourceParentId, $sourceQuestionId);
2676
2677 // duplicate question feeback
2678 $this->feedbackOBJ->duplicateFeedback($sourceQuestionId, $targetQuestionId);
2679
2680 // duplicate question hints
2681 $this->duplicateQuestionHints($sourceQuestionId, $targetQuestionId);
2682
2683 // duplicate skill assignments
2684 $this->duplicateSkillAssignments($sourceParentId, $sourceQuestionId, $targetParentId, $targetQuestionId);
2685 }
2686
2691 {
2692 global $ilDB;
2693 // delete the links in the qpl_sol_sug table
2694 $affectedRows = $ilDB->manipulateF(
2695 "DELETE FROM qpl_sol_sug WHERE question_fi = %s",
2696 array('integer'),
2697 array($this->getId())
2698 );
2699 // delete the links in the int_link table
2700 include_once "./Services/Link/classes/class.ilInternalLink.php";
2702 $this->suggested_solutions = array();
2704 }
2705
2713 public function getSuggestedSolution($subquestion_index = 0)
2714 {
2715 if (array_key_exists($subquestion_index, $this->suggested_solutions)) {
2716 return $this->suggested_solutions[$subquestion_index];
2717 } else {
2718 return array();
2719 }
2720 }
2721
2730 public function getSuggestedSolutionTitle($subquestion_index = 0)
2731 {
2732 if (array_key_exists($subquestion_index, $this->suggested_solutions)) {
2733 $title = $this->suggested_solutions[$subquestion_index]["internal_link"];
2734 // TO DO: resolve internal link an get link type and title
2735 } else {
2736 $title = "";
2737 }
2738 return $title;
2739 }
2740
2750 public function setSuggestedSolution($solution_id = "", $subquestion_index = 0, $is_import = false)
2751 {
2752 if (strcmp($solution_id, "") != 0) {
2753 $import_id = "";
2754 if ($is_import) {
2755 $import_id = $solution_id;
2756 $solution_id = $this->_resolveInternalLink($import_id);
2757 }
2758 $this->suggested_solutions[$subquestion_index] = array(
2759 "internal_link" => $solution_id,
2760 "import_id" => $import_id
2761 );
2762 }
2763 }
2764
2768 protected function duplicateSuggestedSolutionFiles($parent_id, $question_id)
2769 {
2770 global $ilLog;
2771
2772 foreach ($this->suggested_solutions as $index => $solution) {
2773 if (strcmp($solution["type"], "file") == 0) {
2774 $filepath = $this->getSuggestedSolutionPath();
2775 $filepath_original = str_replace(
2776 "/{$this->obj_id}/{$this->id}/solution",
2777 "/$parent_id/$question_id/solution",
2778 $filepath
2779 );
2780 if (!file_exists($filepath)) {
2781 ilUtil::makeDirParents($filepath);
2782 }
2783 $filename = $solution["value"]["name"];
2784 if (strlen($filename)) {
2785 if (!copy($filepath_original . $filename, $filepath . $filename)) {
2786 $ilLog->write("File could not be duplicated!!!!", $ilLog->ERROR);
2787 $ilLog->write("object: " . print_r($this, true), $ilLog->ERROR);
2788 }
2789 }
2790 }
2791 }
2792 }
2793
2798 {
2799 global $ilLog;
2800
2801 $filepath = $this->getSuggestedSolutionPath();
2802 $filepath_original = str_replace("/$this->id/solution", "/$original_id/solution", $filepath);
2803 ilUtil::delDir($filepath_original);
2804 foreach ($this->suggested_solutions as $index => $solution) {
2805 if (strcmp($solution["type"], "file") == 0) {
2806 if (!file_exists($filepath_original)) {
2807 ilUtil::makeDirParents($filepath_original);
2808 }
2809 $filename = $solution["value"]["name"];
2810 if (strlen($filename)) {
2811 if (!@copy($filepath . $filename, $filepath_original . $filename)) {
2812 $ilLog->write("File could not be duplicated!!!!", $ilLog->ERROR);
2813 $ilLog->write("object: " . print_r($this, true), $ilLog->ERROR);
2814 }
2815 }
2816 }
2817 }
2818 }
2819
2820 protected function copySuggestedSolutionFiles($source_questionpool_id, $source_question_id)
2821 {
2822 global $ilLog;
2823
2824 foreach ($this->suggested_solutions as $index => $solution) {
2825 if (strcmp($solution["type"], "file") == 0) {
2826 $filepath = $this->getSuggestedSolutionPath();
2827 $filepath_original = str_replace("/$this->obj_id/$this->id/solution", "/$source_questionpool_id/$source_question_id/solution", $filepath);
2828 if (!file_exists($filepath)) {
2829 ilUtil::makeDirParents($filepath);
2830 }
2831 $filename = $solution["value"]["name"];
2832 if (strlen($filename)) {
2833 if (!copy($filepath_original . $filename, $filepath . $filename)) {
2834 $ilLog->write("File could not be copied!!!!", $ilLog->ERROR);
2835 $ilLog->write("object: " . print_r($this, true), $ilLog->ERROR);
2836 }
2837 }
2838 }
2839 }
2840 }
2841
2845 public function updateSuggestedSolutions($original_id = "")
2846 {
2847 global $ilDB;
2848
2849 $id = (strlen($original_id) && is_numeric($original_id)) ? $original_id : $this->getId();
2850 include_once "./Services/Link/classes/class.ilInternalLink.php";
2851 $affectedRows = $ilDB->manipulateF(
2852 "DELETE FROM qpl_sol_sug WHERE question_fi = %s",
2853 array('integer'),
2854 array($id)
2855 );
2857 include_once("./Services/RTE/classes/class.ilRTE.php");
2858 foreach ($this->suggested_solutions as $index => $solution) {
2859 $next_id = $ilDB->nextId('qpl_sol_sug');
2861 $ilDB->insert(
2862 'qpl_sol_sug',
2863 array(
2864 'suggested_solution_id' => array( 'integer', $next_id ),
2865 'question_fi' => array( 'integer', $id ),
2866 'type' => array( 'text', $solution['type'] ),
2867 'value' => array( 'clob', ilRTE::_replaceMediaObjectImageSrc((is_array($solution['value'])) ? serialize($solution[ 'value' ]) : $solution['value'], 0) ),
2868 'internal_link' => array( 'text', $solution['internal_link'] ),
2869 'import_id' => array( 'text', null ),
2870 'subquestion_index' => array( 'integer', $index ),
2871 'tstamp' => array( 'integer', time() ),
2872 )
2873 );
2874 if (preg_match("/il_(\d*?)_(\w+)_(\d+)/", $solution["internal_link"], $matches)) {
2875 ilInternalLink::_saveLink("qst", $id, $matches[2], $matches[3], $matches[1]);
2876 }
2877 }
2878 if (strlen($original_id) && is_numeric($original_id)) {
2880 }
2881 $this->cleanupMediaObjectUsage();
2882 }
2883
2893 public function saveSuggestedSolution($type, $solution_id = "", $subquestion_index = 0, $value = "")
2894 {
2895 global $ilDB;
2896
2897 $affectedRows = $ilDB->manipulateF(
2898 "DELETE FROM qpl_sol_sug WHERE question_fi = %s AND subquestion_index = %s",
2899 array("integer", "integer"),
2900 array(
2901 $this->getId(),
2902 $subquestion_index
2903 )
2904 );
2905
2906 $next_id = $ilDB->nextId('qpl_sol_sug');
2907 include_once("./Services/RTE/classes/class.ilRTE.php");
2909 $affectedRows = $ilDB->insert(
2910 'qpl_sol_sug',
2911 array(
2912 'suggested_solution_id' => array( 'integer', $next_id ),
2913 'question_fi' => array( 'integer', $this->getId() ),
2914 'type' => array( 'text', $type ),
2915 'value' => array( 'clob', ilRTE::_replaceMediaObjectImageSrc((is_array($value)) ? serialize($value) : $value, 0) ),
2916 'internal_link' => array( 'text', $solution_id ),
2917 'import_id' => array( 'text', null ),
2918 'subquestion_index' => array( 'integer', $subquestion_index ),
2919 'tstamp' => array( 'integer', time() ),
2920 )
2921 );
2922 if ($affectedRows == 1) {
2923 $this->suggested_solutions[$subquestion_index] = array(
2924 "type" => $type,
2925 "value" => $value,
2926 "internal_link" => $solution_id,
2927 "import_id" => ""
2928 );
2929 }
2930 $this->cleanupMediaObjectUsage();
2931 }
2932
2933 public function _resolveInternalLink($internal_link)
2934 {
2935 if (preg_match("/il_(\d+)_(\w+)_(\d+)/", $internal_link, $matches)) {
2936 include_once "./Services/Link/classes/class.ilInternalLink.php";
2937 include_once "./Modules/LearningModule/classes/class.ilLMObject.php";
2938 include_once "./Modules/Glossary/classes/class.ilGlossaryTerm.php";
2939 switch ($matches[2]) {
2940 case "lm":
2941 $resolved_link = ilLMObject::_getIdForImportId($internal_link);
2942 break;
2943 case "pg":
2944 $resolved_link = ilInternalLink::_getIdForImportId("PageObject", $internal_link);
2945 break;
2946 case "st":
2947 $resolved_link = ilInternalLink::_getIdForImportId("StructureObject", $internal_link);
2948 break;
2949 case "git":
2950 $resolved_link = ilInternalLink::_getIdForImportId("GlossaryItem", $internal_link);
2951 break;
2952 case "mob":
2953 $resolved_link = ilInternalLink::_getIdForImportId("MediaObject", $internal_link);
2954 break;
2955 }
2956 if (strcmp($resolved_link, "") == 0) {
2957 $resolved_link = $internal_link;
2958 }
2959 } else {
2960 $resolved_link = $internal_link;
2961 }
2962 return $resolved_link;
2963 }
2964
2965 public function _resolveIntLinks($question_id)
2966 {
2967 global $ilDB;
2968 $resolvedlinks = 0;
2969 $result = $ilDB->queryF(
2970 "SELECT * FROM qpl_sol_sug WHERE question_fi = %s",
2971 array('integer'),
2972 array($question_id)
2973 );
2974 if ($result->numRows()) {
2975 while ($row = $ilDB->fetchAssoc($result)) {
2976 $internal_link = $row["internal_link"];
2977 include_once "./Modules/TestQuestionPool/classes/class.assQuestion.php";
2978 $resolved_link = assQuestion::_resolveInternalLink($internal_link);
2979 if (strcmp($internal_link, $resolved_link) != 0) {
2980 // internal link was resolved successfully
2981 $affectedRows = $ilDB->manipulateF(
2982 "UPDATE qpl_sol_sug SET internal_link = %s WHERE suggested_solution_id = %s",
2983 array('text','integer'),
2984 array($resolved_link, $row["suggested_solution_id"])
2985 );
2986 $resolvedlinks++;
2987 }
2988 }
2989 }
2990 if ($resolvedlinks) {
2991 // there are resolved links -> reenter theses links to the database
2992
2993 // delete all internal links from the database
2994 include_once "./Services/Link/classes/class.ilInternalLink.php";
2995 ilInternalLink::_deleteAllLinksOfSource("qst", $question_id);
2996
2997 $result = $ilDB->queryF(
2998 "SELECT * FROM qpl_sol_sug WHERE question_fi = %s",
2999 array('integer'),
3000 array($question_id)
3001 );
3002 if ($result->numRows()) {
3003 while ($row = $ilDB->fetchAssoc($result)) {
3004 if (preg_match("/il_(\d*?)_(\w+)_(\d+)/", $row["internal_link"], $matches)) {
3005 ilInternalLink::_saveLink("qst", $question_id, $matches[2], $matches[3], $matches[1]);
3006 }
3007 }
3008 }
3009 }
3010 }
3011
3012 public static function _getInternalLinkHref($target = "")
3013 {
3014 global $ilDB;
3015 $linktypes = array(
3016 "lm" => "LearningModule",
3017 "pg" => "PageObject",
3018 "st" => "StructureObject",
3019 "git" => "GlossaryItem",
3020 "mob" => "MediaObject"
3021 );
3022 $href = "";
3023 if (preg_match("/il__(\w+)_(\d+)/", $target, $matches)) {
3024 $type = $matches[1];
3025 $target_id = $matches[2];
3026 include_once "./Services/Utilities/classes/class.ilUtil.php";
3027 switch ($linktypes[$matches[1]]) {
3028 case "LearningModule":
3029 $href = "./goto.php?target=" . $type . "_" . $target_id;
3030 break;
3031 case "PageObject":
3032 case "StructureObject":
3033 $href = "./goto.php?target=" . $type . "_" . $target_id;
3034 break;
3035 case "GlossaryItem":
3036 $href = "./goto.php?target=" . $type . "_" . $target_id;
3037 break;
3038 case "MediaObject":
3039 $href = "./ilias.php?baseClass=ilLMPresentationGUI&obj_type=" . $linktypes[$type] . "&cmd=media&ref_id=" . $_GET["ref_id"] . "&mob_id=" . $target_id;
3040 break;
3041 }
3042 }
3043 return $href;
3044 }
3045
3053 public static function _getOriginalId($question_id)
3054 {
3055 global $ilDB;
3056 $result = $ilDB->queryF(
3057 "SELECT * FROM qpl_questions WHERE question_id = %s",
3058 array('integer'),
3059 array($question_id)
3060 );
3061 if ($result->numRows() > 0) {
3062 $row = $ilDB->fetchAssoc($result);
3063 if ($row["original_id"] > 0) {
3064 return $row["original_id"];
3065 } else {
3066 return $row["question_id"];
3067 }
3068 } else {
3069 return "";
3070 }
3071 }
3072
3073 public static function originalQuestionExists($questionId)
3074 {
3075 global $ilDB;
3076
3077 $query = "
3078 SELECT COUNT(dupl.question_id) cnt
3079 FROM qpl_questions dupl
3080 INNER JOIN qpl_questions orig
3081 ON orig.question_id = dupl.original_id
3082 WHERE dupl.question_id = %s
3083 ";
3084
3085 $res = $ilDB->queryF($query, array('integer'), array($questionId));
3086 $row = $ilDB->fetchAssoc($res);
3087
3088 return $row['cnt'] > 0;
3089 }
3090
3091 public function syncWithOriginal()
3092 {
3093 global $ilDB;
3094
3095 if (!$this->getOriginalId()) {
3096 return;
3097 }
3098
3099 $originalObjId = self::lookupOriginalParentObjId($this->getOriginalId());
3100
3101 if (!$originalObjId) {
3102 return;
3103 }
3104
3105 $id = $this->getId();
3106 $objId = $this->getObjId();
3107 $original = $this->getOriginalId();
3108
3109 $this->beforeSyncWithOriginal($original, $id, $originalObjId, $objId);
3110
3111 $this->setId($original);
3112 $this->setOriginalId(null);
3113 $this->setObjId($originalObjId);
3114
3115 $this->saveToDb();
3116
3117 $this->deletePageOfQuestion($original);
3118 $this->createPageObject();
3119 $this->copyPageOfQuestion($id);
3120
3121 $this->setId($id);
3122 $this->setOriginalId($original);
3123 $this->setObjId($objId);
3124
3125 $this->updateSuggestedSolutions($original);
3127
3128 $this->afterSyncWithOriginal($original, $id, $originalObjId, $objId);
3129 $this->syncHints();
3130 }
3131
3132 public function createRandomSolution($test_id, $user_id)
3133 {
3134 }
3135
3143 public function _questionExists($question_id)
3144 {
3145 global $ilDB;
3146
3147 if ($question_id < 1) {
3148 return false;
3149 }
3150
3151 $result = $ilDB->queryF(
3152 "SELECT question_id FROM qpl_questions WHERE question_id = %s",
3153 array('integer'),
3154 array($question_id)
3155 );
3156 if ($result->numRows() == 1) {
3157 return true;
3158 } else {
3159 return false;
3160 }
3161 }
3162
3170 public function _questionExistsInPool($question_id)
3171 {
3172 global $ilDB;
3173
3174 if ($question_id < 1) {
3175 return false;
3176 }
3177
3178 $result = $ilDB->queryF(
3179 "SELECT question_id FROM qpl_questions INNER JOIN object_data ON obj_fi = obj_id WHERE question_id = %s AND type = 'qpl'",
3180 array('integer'),
3181 array($question_id)
3182 );
3183 if ($result->numRows() == 1) {
3184 return true;
3185 } else {
3186 return false;
3187 }
3188 }
3189
3197 public static function _instanciateQuestion($question_id)
3198 {
3199 return self::_instantiateQuestion($question_id);
3200 }
3201
3206 public static function _instantiateQuestion($question_id)
3207 {
3208 global $ilCtrl, $ilDB, $lng;
3209
3210 if (strcmp($question_id, "") != 0) {
3211 $question_type = assQuestion::_getQuestionType($question_id);
3212 if (!strlen($question_type)) {
3213 return null;
3214 }
3215 assQuestion::_includeClass($question_type);
3216 $objectClassname = self::getObjectClassNameByQuestionType($question_type);
3217 $question = new $objectClassname();
3218 $question->loadFromDb($question_id);
3219
3220 $feedbackObjectClassname = self::getFeedbackClassNameByQuestionType($question_type);
3221 $question->feedbackOBJ = new $feedbackObjectClassname($question, $ilCtrl, $ilDB, $lng);
3222
3223 return $question;
3224 }
3225 }
3226
3233 public function getPoints()
3234 {
3235 if (strcmp($this->points, "") == 0) {
3236 return 0;
3237 } else {
3238 return $this->points;
3239 }
3240 }
3241
3242
3249 public function setPoints($a_points)
3250 {
3251 $this->points = $a_points;
3252 }
3253
3260 public function getSolutionMaxPass($active_id)
3261 {
3262 return self::_getSolutionMaxPass($this->getId(), $active_id);
3263 }
3264
3271 public static function _getSolutionMaxPass($question_id, $active_id)
3272 {
3273 /* include_once "./Modules/Test/classes/class.ilObjTest.php";
3274 $pass = ilObjTest::_getPass($active_id);
3275 return $pass;*/
3276
3277 // the following code was the old solution which added the non answered
3278 // questions of a pass from the answered questions of the previous pass
3279 // with the above solution, only the answered questions of the last pass are counted
3280 global $ilDB;
3281
3282 $result = $ilDB->queryF(
3283 "SELECT MAX(pass) maxpass FROM tst_test_result WHERE active_fi = %s AND question_fi = %s",
3284 array('integer','integer'),
3285 array($active_id, $question_id)
3286 );
3287 if ($result->numRows() == 1) {
3288 $row = $ilDB->fetchAssoc($result);
3289 return $row["maxpass"];
3290 } else {
3291 return 0;
3292 }
3293 }
3294
3303 public static function _isWriteable($question_id, $user_id)
3304 {
3305 global $ilDB;
3306
3307 if (($question_id < 1) || ($user_id < 1)) {
3308 return false;
3309 }
3310
3311 $result = $ilDB->queryF(
3312 "SELECT obj_fi FROM qpl_questions WHERE question_id = %s",
3313 array('integer'),
3314 array($question_id)
3315 );
3316 if ($result->numRows() == 1) {
3317 $row = $ilDB->fetchAssoc($result);
3318 $qpl_object_id = $row["obj_fi"];
3319 include_once "./Modules/TestQuestionPool/classes/class.ilObjQuestionPool.php";
3320 return ilObjQuestionPool::_isWriteable($qpl_object_id, $user_id);
3321 } else {
3322 return false;
3323 }
3324 }
3325
3332 public static function _isUsedInRandomTest($question_id = "")
3333 {
3334 global $ilDB;
3335
3336 if ($question_id < 1) {
3337 return 0;
3338 }
3339 $result = $ilDB->queryF(
3340 "SELECT test_random_question_id FROM tst_test_rnd_qst WHERE question_fi = %s",
3341 array('integer'),
3342 array($question_id)
3343 );
3344 return $result->numRows();
3345 }
3346
3358 abstract public function calculateReachedPoints($active_id, $pass = null, $authorizedSolution = true, $returndetails = false);
3359
3360 public function deductHintPointsFromReachedPoints(ilAssQuestionPreviewSession $previewSession, $reachedPoints)
3361 {
3362 global $DIC;
3363
3364 $hintTracking = new ilAssQuestionPreviewHintTracking($DIC->database(), $previewSession);
3365 $requestsStatisticData = $hintTracking->getRequestStatisticData();
3366 $reachedPoints = $reachedPoints - $requestsStatisticData->getRequestsPoints();
3367
3368 return $reachedPoints;
3369 }
3370
3372 {
3373 $reachedPoints = $this->calculateReachedPointsForSolution($previewSession->getParticipantsSolution());
3374 $reachedPoints = $this->deductHintPointsFromReachedPoints($previewSession, $reachedPoints);
3375
3376 return $this->ensureNonNegativePoints($reachedPoints);
3377 }
3378
3380 {
3381 return $points > 0 ? $points : 0;
3382 }
3383
3385 {
3386 $reachedPoints = $this->calculateReachedPointsFromPreviewSession($previewSession);
3387
3388 if ($reachedPoints < $this->getMaximumPoints()) {
3389 return false;
3390 }
3391
3392 return true;
3393 }
3394
3395
3406 final public function adjustReachedPointsByScoringOptions($points, $active_id, $pass = null)
3407 {
3408 include_once "./Modules/Test/classes/class.ilObjTest.php";
3409 $count_system = ilObjTest::_getCountSystem($active_id);
3410 if ($count_system == 1) {
3411 if (abs($this->getMaximumPoints() - $points) > 0.0000000001) {
3412 $points = 0;
3413 }
3414 }
3415 $score_cutting = ilObjTest::_getScoreCutting($active_id);
3416 if ($score_cutting == 0) {
3417 if ($points < 0) {
3418 $points = 0;
3419 }
3420 }
3421 return $points;
3422 }
3423
3432 public static function _isWorkedThrough($active_id, $question_id, $pass = null)
3433 {
3434 return self::lookupResultRecordExist($active_id, $question_id, $pass);
3435
3436 // oldschool "workedthru"
3437
3438 global $ilDB;
3439
3440 $points = 0;
3441 if (is_null($pass)) {
3442 include_once "./Modules/TestQuestionPool/classes/class.assQuestion.php";
3443 $pass = assQuestion::_getSolutionMaxPass($question_id, $active_id);
3444 }
3445 $result = $ilDB->queryF(
3446 "SELECT solution_id FROM tst_solutions WHERE active_fi = %s AND question_fi = %s AND pass = %s",
3447 array('integer','integer','integer'),
3448 array($active_id, $question_id, $pass)
3449 );
3450 if ($result->numRows()) {
3451 return true;
3452 } else {
3453 return false;
3454 }
3455 }
3456
3464 public static function _areAnswered($a_user_id, $a_question_ids)
3465 {
3466 global $ilDB;
3467
3468 $res = $ilDB->queryF(
3469 "SELECT DISTINCT(question_fi) FROM tst_test_result JOIN tst_active " .
3470 "ON (active_id = active_fi) " .
3471 "WHERE " . $ilDB->in('question_fi', $a_question_ids, false, 'integer') .
3472 " AND user_fi = %s",
3473 array('integer'),
3474 array($a_user_id)
3475 );
3476 return ($res->numRows() == count($a_question_ids)) ? true : false;
3477 }
3478
3487 public function isHTML($a_text)
3488 {
3489 return ilUtil::isHTML($a_text);
3490 }
3491
3498 public function prepareTextareaOutput($txt_output, $prepare_for_latex_output = false, $omitNl2BrWhenTextArea = false)
3499 {
3500 include_once "./Services/Utilities/classes/class.ilUtil.php";
3501 return ilUtil::prepareTextareaOutput($txt_output, $prepare_for_latex_output, $omitNl2BrWhenTextArea);
3502 }
3503
3511 public function QTIMaterialToString($a_material)
3512 {
3513 $result = "";
3514 for ($i = 0; $i < $a_material->getMaterialCount(); $i++) {
3515 $material = $a_material->getMaterial($i);
3516 if (strcmp($material["type"], "mattext") == 0) {
3517 $result .= $material["material"]->getContent();
3518 }
3519 if (strcmp($material["type"], "matimage") == 0) {
3520 $matimage = $material["material"];
3521 if (preg_match("/(il_([0-9]+)_mob_([0-9]+))/", $matimage->getLabel(), $matches)) {
3522 // import an mediaobject which was inserted using tiny mce
3523 if (!is_array($_SESSION["import_mob_xhtml"])) {
3524 $_SESSION["import_mob_xhtml"] = array();
3525 }
3526 array_push($_SESSION["import_mob_xhtml"], array("mob" => $matimage->getLabel(), "uri" => $matimage->getUri()));
3527 }
3528 }
3529 }
3530 return $result;
3531 }
3532
3541 public function addQTIMaterial(&$a_xml_writer, $a_material, $close_material_tag = true, $add_mobs = true)
3542 {
3543 include_once "./Services/RTE/classes/class.ilRTE.php";
3544 include_once("./Services/MediaObjects/classes/class.ilObjMediaObject.php");
3545
3546 $a_xml_writer->xmlStartTag("material");
3547 $attrs = array(
3548 "texttype" => "text/plain"
3549 );
3550 if ($this->isHTML($a_material)) {
3551 $attrs["texttype"] = "text/xhtml";
3552 }
3553 $a_xml_writer->xmlElement("mattext", $attrs, ilRTE::_replaceMediaObjectImageSrc($a_material, 0));
3554 if ($add_mobs) {
3555 $mobs = ilObjMediaObject::_getMobsOfObject("qpl:html", $this->getId());
3556 foreach ($mobs as $mob) {
3557 $moblabel = "il_" . IL_INST_ID . "_mob_" . $mob;
3558 if (strpos($a_material, "mm_$mob") !== false) {
3559 if (ilObjMediaObject::_exists($mob)) {
3560 $mob_obj = new ilObjMediaObject($mob);
3561 $imgattrs = array(
3562 "label" => $moblabel,
3563 "uri" => "objects/" . "il_" . IL_INST_ID . "_mob_" . $mob . "/" . $mob_obj->getTitle()
3564 );
3565 }
3566 $a_xml_writer->xmlElement("matimage", $imgattrs, null);
3567 }
3568 }
3569 }
3570 if ($close_material_tag) {
3571 $a_xml_writer->xmlEndTag("material");
3572 }
3573 }
3574
3575 public function buildHashedImageFilename($plain_image_filename, $unique = false)
3576 {
3577 $extension = "";
3578
3579 if (preg_match("/.*\.(png|jpg|gif|jpeg)$/i", $plain_image_filename, $matches)) {
3580 $extension = "." . $matches[1];
3581 }
3582
3583 if ($unique) {
3584 $plain_image_filename = uniqid($plain_image_filename . microtime(true));
3585 }
3586
3587 $hashed_filename = md5($plain_image_filename) . $extension;
3588
3589 return $hashed_filename;
3590 }
3591
3602 public static function _setReachedPoints($active_id, $question_id, $points, $maxpoints, $pass, $manualscoring, $obligationsEnabled)
3603 {
3604 global $ilDB;
3605
3606 if ($points <= $maxpoints) {
3607 if (is_null($pass)) {
3608 $pass = assQuestion::_getSolutionMaxPass($question_id, $active_id);
3609 }
3610
3611 // retrieve the already given points
3612 $old_points = 0;
3613 $result = $ilDB->queryF(
3614 "SELECT points FROM tst_test_result WHERE active_fi = %s AND question_fi = %s AND pass = %s",
3615 array('integer','integer','integer'),
3616 array($active_id, $question_id, $pass)
3617 );
3618 $manual = ($manualscoring) ? 1 : 0;
3619 $rowsnum = $result->numRows();
3620 if ($rowsnum) {
3621 $row = $ilDB->fetchAssoc($result);
3622 $old_points = $row["points"];
3623 if ($old_points != $points) {
3624 $affectedRows = $ilDB->manipulateF(
3625 "UPDATE tst_test_result SET points = %s, manual = %s, tstamp = %s WHERE active_fi = %s AND question_fi = %s AND pass = %s",
3626 array('float', 'integer', 'integer', 'integer', 'integer', 'integer'),
3627 array($points, $manual, time(), $active_id, $question_id, $pass)
3628 );
3629 }
3630 } else {
3631 $next_id = $ilDB->nextId('tst_test_result');
3632 $affectedRows = $ilDB->manipulateF(
3633 "INSERT INTO tst_test_result (test_result_id, active_fi, question_fi, points, pass, manual, tstamp) VALUES (%s, %s, %s, %s, %s, %s, %s)",
3634 array('integer', 'integer','integer', 'float', 'integer', 'integer','integer'),
3635 array($next_id, $active_id, $question_id, $points, $pass, $manual, time())
3636 );
3637 }
3638
3639 if (self::isForcePassResultUpdateEnabled() || $old_points != $points || !$rowsnum) {
3640 assQuestion::_updateTestPassResults($active_id, $pass, $obligationsEnabled);
3641 // finally update objective result
3642 include_once "./Modules/Test/classes/class.ilObjTest.php";
3643 include_once './Modules/Course/classes/class.ilCourseObjectiveResult.php';
3645
3646 include_once("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
3648 global $lng, $ilUser;
3649 include_once "./Modules/Test/classes/class.ilObjTestAccess.php";
3650 $username = ilObjTestAccess::_getParticipantData($active_id);
3651 assQuestion::logAction(sprintf($lng->txtlng("assessment", "log_answer_changed_points", ilObjAssessmentFolder::_getLogLanguage()), $username, $old_points, $points, $ilUser->getFullname() . " (" . $ilUser->getLogin() . ")"), $active_id, $question_id);
3652 }
3653 }
3654
3655 return true;
3656 } else {
3657 return false;
3658 }
3659 }
3660
3668 public function getQuestion()
3669 {
3670 return $this->question;
3671 }
3672
3680 public function setQuestion($question = "")
3681 {
3682 $this->question = $question;
3683 }
3684
3690 abstract public function getQuestionType();
3691
3700 public function getQuestionTypeID()
3701 {
3702 global $ilDB;
3703
3704 $result = $ilDB->queryF(
3705 "SELECT question_type_id FROM qpl_qst_type WHERE type_tag = %s",
3706 array('text'),
3707 array($this->getQuestionType())
3708 );
3709 if ($result->numRows() == 1) {
3710 $row = $ilDB->fetchAssoc($result);
3711 return $row["question_type_id"];
3712 }
3713 return 0;
3714 }
3715
3716 public function syncHints()
3717 {
3718 global $ilDB;
3719
3720 // delete hints of the original
3721 $ilDB->manipulateF(
3722 "DELETE FROM qpl_hints WHERE qht_question_fi = %s",
3723 array('integer'),
3724 array($this->original_id)
3725 );
3726
3727 // get hints of the actual question
3728 $result = $ilDB->queryF(
3729 "SELECT * FROM qpl_hints WHERE qht_question_fi = %s",
3730 array('integer'),
3731 array($this->getId())
3732 );
3733
3734 // save hints to the original
3735 if ($result->numRows()) {
3736 while ($row = $ilDB->fetchAssoc($result)) {
3737 $next_id = $ilDB->nextId('qpl_hints');
3739 $ilDB->insert(
3740 'qpl_hints',
3741 array(
3742 'qht_hint_id' => array('integer', $next_id),
3743 'qht_question_fi' => array('integer', $this->original_id),
3744 'qht_hint_index' => array('integer', $row["qht_hint_index"]),
3745 'qht_hint_points' => array('integer', $row["qht_hint_points"]),
3746 'qht_hint_text' => array('text', $row["qht_hint_text"]),
3747 )
3748 );
3749 }
3750 }
3751 }
3752
3757 protected function getRTETextWithMediaObjects()
3758 {
3759 // must be called in parent classes. add additional RTE text in the parent
3760 // classes and call this method to add the standard RTE text
3761 $collected = $this->getQuestion();
3762 $collected .= $this->feedbackOBJ->getGenericFeedbackContent($this->getId(), false);
3763 $collected .= $this->feedbackOBJ->getGenericFeedbackContent($this->getId(), true);
3764 $collected .= $this->feedbackOBJ->getAllSpecificAnswerFeedbackContents($this->getId());
3765
3766 foreach ($this->suggested_solutions as $solution_array) {
3767 $collected .= $solution_array["value"];
3768 }
3769
3770 require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionHintList.php';
3771 $questionHintList = ilAssQuestionHintList::getListByQuestionId($this->getId());
3772 foreach ($questionHintList as $questionHint) {
3773 /* @var $questionHint ilAssQuestionHint */
3774 $collected .= $questionHint->getText();
3775 }
3776
3777 return $collected;
3778 }
3779
3784 public function cleanupMediaObjectUsage()
3785 {
3786 $combinedtext = $this->getRTETextWithMediaObjects();
3787 include_once("./Services/RTE/classes/class.ilRTE.php");
3788 ilRTE::_cleanupMediaObjectUsage($combinedtext, "qpl:html", $this->getId());
3789 }
3790
3796 public function &getInstances()
3797 {
3798 global $ilDB;
3799
3800 $result = $ilDB->queryF(
3801 "SELECT question_id FROM qpl_questions WHERE original_id = %s",
3802 array("integer"),
3803 array($this->getId())
3804 );
3805 $instances = array();
3806 $ids = array();
3807 while ($row = $ilDB->fetchAssoc($result)) {
3808 array_push($ids, $row["question_id"]);
3809 }
3810 foreach ($ids as $question_id) {
3811 // check non random tests
3812 $result = $ilDB->queryF(
3813 "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",
3814 array("integer"),
3815 array($question_id)
3816 );
3817 while ($row = $ilDB->fetchAssoc($result)) {
3818 $instances[$row['obj_fi']] = ilObject::_lookupTitle($row['obj_fi']);
3819 }
3820 // check random tests
3821 $result = $ilDB->queryF(
3822 "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",
3823 array("integer"),
3824 array($question_id)
3825 );
3826 while ($row = $ilDB->fetchAssoc($result)) {
3827 $instances[$row['obj_fi']] = ilObject::_lookupTitle($row['obj_fi']);
3828 }
3829 }
3830 include_once "./Modules/Test/classes/class.ilObjTest.php";
3831 foreach ($instances as $key => $value) {
3832 $instances[$key] = array("obj_id" => $key, "title" => $value, "author" => ilObjTest::_lookupAuthor($key), "refs" => ilObject::_getAllReferences($key));
3833 }
3834 return $instances;
3835 }
3836
3837 public static function _needsManualScoring($question_id)
3838 {
3839 include_once "./Modules/Test/classes/class.ilObjAssessmentFolder.php";
3841 $questiontype = assQuestion::_getQuestionType($question_id);
3842 if (in_array($questiontype, $scoring)) {
3843 return true;
3844 } else {
3845 return false;
3846 }
3847 }
3848
3856 public function getActiveUserData($active_id)
3857 {
3858 global $ilDB;
3859 $result = $ilDB->queryF(
3860 "SELECT * FROM tst_active WHERE active_id = %s",
3861 array('integer'),
3862 array($active_id)
3863 );
3864 if ($result->numRows()) {
3865 $row = $ilDB->fetchAssoc($result);
3866 return array("user_id" => $row["user_fi"], "test_id" => $row["test_fi"]);
3867 } else {
3868 return array();
3869 }
3870 }
3871
3879 public static function _includeClass($question_type, $gui = 0)
3880 {
3881 if (self::isCoreQuestionType($question_type)) {
3882 self::includeCoreClass($question_type, $gui);
3883 } else {
3884 self::includePluginClass($question_type, $gui);
3885 }
3886 }
3887
3888 public static function getGuiClassNameByQuestionType($questionType)
3889 {
3890 return $questionType . 'GUI';
3891 }
3892
3893 public static function getObjectClassNameByQuestionType($questionType)
3894 {
3895 return $questionType;
3896 }
3897
3898 public static function getFeedbackClassNameByQuestionType($questionType)
3899 {
3900 return str_replace('ass', 'ilAss', $questionType) . 'Feedback';
3901 }
3902
3903 public static function isCoreQuestionType($questionType)
3904 {
3905 $guiClassName = self::getGuiClassNameByQuestionType($questionType);
3906 return file_exists("Modules/TestQuestionPool/classes/class.{$guiClassName}.php");
3907 }
3908
3909 public static function includeCoreClass($questionType, $withGuiClass)
3910 {
3911 if ($withGuiClass) {
3912 $guiClassName = self::getGuiClassNameByQuestionType($questionType);
3913 require_once "Modules/TestQuestionPool/classes/class.{$guiClassName}.php";
3914
3915 // object class is included by gui classes constructor
3916 } else {
3917 $objectClassName = self::getObjectClassNameByQuestionType($questionType);
3918 require_once "Modules/TestQuestionPool/classes/class.{$objectClassName}.php";
3919 }
3920
3921 $feedbackClassName = self::getFeedbackClassNameByQuestionType($questionType);
3922 require_once "Modules/TestQuestionPool/classes/feedback/class.{$feedbackClassName}.php";
3923 }
3924
3925 public static function includePluginClass($questionType, $withGuiClass)
3926 {
3927 global $ilPluginAdmin;
3928
3929 $classes = array(
3930 self::getObjectClassNameByQuestionType($questionType),
3931 self::getFeedbackClassNameByQuestionType($questionType)
3932 );
3933
3934 if ($withGuiClass) {
3935 $classes[] = self::getGuiClassNameByQuestionType($questionType);
3936 }
3937
3938 $pl_names = $ilPluginAdmin->getActivePluginsForSlot(IL_COMP_MODULE, "TestQuestionPool", "qst");
3939 foreach ($pl_names as $pl_name) {
3940 $pl = ilPlugin::getPluginObject(IL_COMP_MODULE, "TestQuestionPool", "qst", $pl_name);
3941 if (strcmp($pl->getQuestionType(), $questionType) == 0) {
3942 foreach ($classes as $class) {
3943 $pl->includeClass("class.{$class}.php");
3944 }
3945
3946 break;
3947 }
3948 }
3949 }
3950
3957 public static function _getQuestionTypeName($type_tag)
3958 {
3959 if (file_exists("./Modules/TestQuestionPool/classes/class." . $type_tag . ".php")) {
3960 global $lng;
3961 return $lng->txt($type_tag);
3962 } else {
3963 global $ilPluginAdmin;
3964 $pl_names = $ilPluginAdmin->getActivePluginsForSlot(IL_COMP_MODULE, "TestQuestionPool", "qst");
3965 foreach ($pl_names as $pl_name) {
3966 $pl = ilPlugin::getPluginObject(IL_COMP_MODULE, "TestQuestionPool", "qst", $pl_name);
3967 if (strcmp($pl->getQuestionType(), $type_tag) == 0) {
3968 return $pl->getQuestionTypeTranslation();
3969 }
3970 }
3971 }
3972 return "";
3973 }
3974
3984 public static function &_instanciateQuestionGUI($question_id)
3985 {
3986 return self::instantiateQuestionGUI($question_id);
3987 }
3988
3996 public static function instantiateQuestionGUI($a_question_id)
3997 {
3998 global $ilCtrl, $ilDB, $lng, $ilUser;
3999
4000 if (strcmp($a_question_id, "") != 0) {
4001 $question_type = assQuestion::_getQuestionType($a_question_id);
4002
4003 assQuestion::_includeClass($question_type, 1);
4004
4005 $question_type_gui = self::getGuiClassNameByQuestionType($question_type);
4006 $question_gui = new $question_type_gui();
4007 $question_gui->object->loadFromDb($a_question_id);
4008
4009 $feedbackObjectClassname = self::getFeedbackClassNameByQuestionType($question_type);
4010 $question_gui->object->feedbackOBJ = new $feedbackObjectClassname($question_gui->object, $ilCtrl, $ilDB, $lng);
4011
4012 $assSettings = new ilSetting('assessment');
4013 require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionProcessLockerFactory.php';
4014 $processLockerFactory = new ilAssQuestionProcessLockerFactory($assSettings, $ilDB);
4015 $processLockerFactory->setQuestionId($question_gui->object->getId());
4016 $processLockerFactory->setUserId($ilUser->getId());
4017 include_once("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
4018 $processLockerFactory->setAssessmentLogEnabled(ilObjAssessmentFolder::_enabledAssessmentLogging());
4019 $question_gui->object->setProcessLocker($processLockerFactory->getLocker());
4020 } else {
4021 global $ilLog;
4022 $ilLog->write('Instantiate question called without question id. (instantiateQuestionGUI@assQuestion)', $ilLog->WARNING);
4023 return null;
4024 }
4025 return $question_gui;
4026 }
4027
4038 public function setExportDetailsXLS($worksheet, $startrow, $active_id, $pass)
4039 {
4040 $worksheet->setFormattedExcelTitle($worksheet->getColumnCoord(0) . $startrow, $this->lng->txt($this->getQuestionType()));
4041 $worksheet->setFormattedExcelTitle($worksheet->getColumnCoord(1) . $startrow, $this->getTitle());
4042
4043 return $startrow;
4044 }
4045
4049 public function __get($value)
4050 {
4051 switch ($value) {
4052 case "id":
4053 return $this->getId();
4054 break;
4055 case "title":
4056 return $this->getTitle();
4057 break;
4058 case "comment":
4059 return $this->getComment();
4060 break;
4061 case "owner":
4062 return $this->getOwner();
4063 break;
4064 case "author":
4065 return $this->getAuthor();
4066 break;
4067 case "question":
4068 return $this->getQuestion();
4069 break;
4070 case "points":
4071 return $this->getPoints();
4072 break;
4073 case "est_working_time":
4074 return $this->getEstimatedWorkingTime();
4075 break;
4076 case "shuffle":
4077 return $this->getShuffle();
4078 break;
4079 case "test_id":
4080 return $this->getTestId();
4081 break;
4082 case "obj_id":
4083 return $this->getObjId();
4084 break;
4085 case "ilias":
4086 return $this->ilias;
4087 break;
4088 case "tpl":
4089 return $this->tpl;
4090 break;
4091 case "page":
4092 return $this->page;
4093 break;
4094 case "outputType":
4095 return $this->getOutputType();
4096 break;
4097 case "suggested_solutions":
4098 return $this->getSuggestedSolutions();
4099 break;
4100 case "original_id":
4101 return $this->getOriginalId();
4102 break;
4103 default:
4104 if (array_key_exists($value, $this->arrData)) {
4105 return $this->arrData[$value];
4106 } else {
4107 return null;
4108 }
4109 break;
4110 }
4111 }
4112
4116 public function __set($key, $value)
4117 {
4118 switch ($key) {
4119 case "id":
4120 $this->setId($value);
4121 break;
4122 case "title":
4123 $this->setTitle($value);
4124 break;
4125 case "comment":
4126 $this->setComment($value);
4127 break;
4128 case "owner":
4129 $this->setOwner($value);
4130 break;
4131 case "author":
4132 $this->setAuthor($value);
4133 break;
4134 case "question":
4135 $this->setQuestion($value);
4136 break;
4137 case "points":
4138 $this->setPoints($value);
4139 break;
4140 case "est_working_time":
4141 if (is_array($value)) {
4142 $this->setEstimatedWorkingTime($value["h"], $value["m"], $value["s"]);
4143 }
4144 break;
4145 case "shuffle":
4146 $this->setShuffle($value);
4147 break;
4148 case "test_id":
4149 $this->setTestId($value);
4150 break;
4151 case "obj_id":
4152 $this->setObjId($value);
4153 break;
4154 case "outputType":
4155 $this->setOutputType($value);
4156 break;
4157 case "original_id":
4158 $this->setOriginalId($value);
4159 break;
4160 case "page":
4161 $this->page =&$value;
4162 break;
4163 default:
4164 $this->arrData[$key] = $value;
4165 break;
4166 }
4167 }
4168
4169 public function getNrOfTries()
4170 {
4171 return (int) $this->nr_of_tries;
4172 }
4173
4174 public function setNrOfTries($a_nr_of_tries)
4175 {
4176 $this->nr_of_tries = $a_nr_of_tries;
4177 }
4178
4179 public function setExportImagePath($a_path)
4180 {
4181 $this->export_image_path = (string) $a_path;
4182 }
4183
4184 public static function _questionExistsInTest($question_id, $test_id)
4185 {
4186 global $ilDB;
4187
4188 if ($question_id < 1) {
4189 return false;
4190 }
4191
4192 $result = $ilDB->queryF(
4193 "SELECT question_fi FROM tst_test_question WHERE question_fi = %s AND test_fi = %s",
4194 array('integer', 'integer'),
4195 array($question_id, $test_id)
4196 );
4197 if ($result->numRows() == 1) {
4198 return true;
4199 } else {
4200 return false;
4201 }
4202 }
4203
4210 public function formatSAQuestion($a_q)
4211 {
4212 return $this->getSelfAssessmentFormatter()->format($a_q);
4213 }
4214
4218 protected function getSelfAssessmentFormatter()
4219 {
4220 require_once 'Modules/TestQuestionPool/classes/questions/class.ilAssSelfAssessmentQuestionFormatter.php';
4221 return new \ilAssSelfAssessmentQuestionFormatter();
4222 }
4223
4224 // scorm2004-start ???
4225
4231 public function setPreventRteUsage($a_val)
4232 {
4233 $this->prevent_rte_usage = $a_val;
4234 }
4235
4241 public function getPreventRteUsage()
4242 {
4244 }
4245
4250 {
4251 $this->lmMigrateQuestionTypeGenericContent($migrator);
4252 $this->lmMigrateQuestionTypeSpecificContent($migrator);
4253 $this->saveToDb();
4254
4255 $this->feedbackOBJ->migrateContentForLearningModule($migrator, $this->getId());
4256 }
4257
4262 {
4263 $this->setQuestion($migrator->migrateToLmContent($this->getQuestion()));
4264 }
4265
4270 {
4271 // overwrite if any question type specific content except feedback needs to be migrated
4272 }
4273
4279 public function setSelfAssessmentEditingMode($a_selfassessmenteditingmode)
4280 {
4281 $this->selfassessmenteditingmode = $a_selfassessmenteditingmode;
4282 }
4283
4290 {
4292 }
4293
4299 public function setDefaultNrOfTries($a_defaultnroftries)
4300 {
4301 $this->defaultnroftries = $a_defaultnroftries;
4302 }
4303
4309 public function getDefaultNrOfTries()
4310 {
4311 return (int) $this->defaultnroftries;
4312 }
4313
4314 // scorm2004-end ???
4315
4321 public static function lookupParentObjId($questionId)
4322 {
4323 global $ilDB;
4324
4325 $query = "SELECT obj_fi FROM qpl_questions WHERE question_id = %s";
4326
4327 $res = $ilDB->queryF($query, array('integer'), array((int) $questionId));
4328 $row = $ilDB->fetchAssoc($res);
4329
4330 return $row['obj_fi'];
4331 }
4332
4343 public static function lookupOriginalParentObjId($originalQuestionId)
4344 {
4345 return self::lookupParentObjId($originalQuestionId);
4346 }
4347
4348 protected function duplicateQuestionHints($originalQuestionId, $duplicateQuestionId)
4349 {
4350 require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionHintList.php';
4351 $hintIds = ilAssQuestionHintList::duplicateListForQuestion($originalQuestionId, $duplicateQuestionId);
4352
4354 require_once 'Modules/TestQuestionPool/classes/class.ilAssHintPage.php';
4355
4356 foreach ($hintIds as $originalHintId => $duplicateHintId) {
4357 $originalPageObject = new ilAssHintPage($originalHintId);
4358 $originalXML = $originalPageObject->getXMLContent();
4359
4360 $duplicatePageObject = new ilAssHintPage();
4361 $duplicatePageObject->setId($duplicateHintId);
4362 $duplicatePageObject->setParentId($this->getId());
4363 $duplicatePageObject->setXMLContent($originalXML);
4364 $duplicatePageObject->createFromXML();
4365 }
4366 }
4367 }
4368
4369 protected function duplicateSkillAssignments($srcParentId, $srcQuestionId, $trgParentId, $trgQuestionId)
4370 {
4371 global $ilDB;
4372
4373 require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionSkillAssignmentList.php';
4374 $assignmentList = new ilAssQuestionSkillAssignmentList($ilDB);
4375 $assignmentList->setParentObjId($srcParentId);
4376 $assignmentList->setQuestionIdFilter($srcQuestionId);
4377 $assignmentList->loadFromDb();
4378
4379 foreach ($assignmentList->getAssignmentsByQuestionId($srcQuestionId) as $assignment) {
4380 /* @var ilAssQuestionSkillAssignment $assignment */
4381
4382 $assignment->setParentObjId($trgParentId);
4383 $assignment->setQuestionId($trgQuestionId);
4384 $assignment->saveToDb();
4385 }
4386 }
4387
4388 public function syncSkillAssignments($srcParentId, $srcQuestionId, $trgParentId, $trgQuestionId)
4389 {
4390 global $ilDB;
4391
4392 require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionSkillAssignmentList.php';
4393 $assignmentList = new ilAssQuestionSkillAssignmentList($ilDB);
4394 $assignmentList->setParentObjId($trgParentId);
4395 $assignmentList->setQuestionIdFilter($trgQuestionId);
4396 $assignmentList->loadFromDb();
4397
4398 foreach ($assignmentList->getAssignmentsByQuestionId($trgQuestionId) as $assignment) {
4399 /* @var ilAssQuestionSkillAssignment $assignment */
4400
4401 $assignment->deleteFromDb();
4402 }
4403
4404 $this->duplicateSkillAssignments($srcParentId, $srcQuestionId, $trgParentId, $trgQuestionId);
4405 }
4406
4419 public function isAnswered($active_id, $pass = null)
4420 {
4421 return true;
4422 }
4423
4436 public static function isObligationPossible($questionId)
4437 {
4438 return false;
4439 }
4440
4441 public function isAutosaveable()
4442 {
4443 return true;
4444 }
4445
4458 protected static function getNumExistingSolutionRecords($activeId, $pass, $questionId)
4459 {
4460 global $ilDB;
4461
4462 $query = "
4463 SELECT count(active_fi) cnt
4464
4465 FROM tst_solutions
4466
4467 WHERE active_fi = %s
4468 AND question_fi = %s
4469 AND pass = %s
4470 ";
4471
4472 $res = $ilDB->queryF(
4473 $query,
4474 array('integer','integer','integer'),
4475 array($activeId, $questionId, $pass)
4476 );
4477
4478 $row = $ilDB->fetchAssoc($res);
4479
4480 return (int) $row['cnt'];
4481 }
4482
4490 {
4492 }
4493
4501 {
4503 require_once 'Modules/TestQuestionPool/exceptions/class.ilTestQuestionPoolException.php';
4504 throw new ilTestQuestionPoolException('invalid additional content editing mode given: ' . $additinalContentEditingMode);
4505 }
4506
4507 $this->additinalContentEditingMode = $additinalContentEditingMode;
4508 }
4509
4517 {
4519 }
4520
4528 public function isValidAdditionalContentEditingMode($additionalContentEditingMode)
4529 {
4530 if (in_array($additionalContentEditingMode, $this->getValidAdditionalContentEditingModes())) {
4531 return true;
4532 }
4533
4534 return false;
4535 }
4536
4544 {
4545 return array(
4546 self::ADDITIONAL_CONTENT_EDITING_MODE_DEFAULT,
4547 self::ADDITIONAL_CONTENT_EDITING_MODE_PAGE_OBJECT
4548 );
4549 }
4550
4555 {
4556 $this->questionChangeListeners[] = $listener;
4557 }
4558
4563 {
4565 }
4566
4567 private function notifyQuestionCreated()
4568 {
4569 foreach ($this->getQuestionChangeListeners() as $listener) {
4570 $listener->notifyQuestionCreated($this);
4571 }
4572 }
4573
4574 private function notifyQuestionEdited()
4575 {
4576 foreach ($this->getQuestionChangeListeners() as $listener) {
4577 $listener->notifyQuestionEdited($this);
4578 }
4579 }
4580
4581 private function notifyQuestionDeleted()
4582 {
4583 foreach ($this->getQuestionChangeListeners() as $listener) {
4584 $listener->notifyQuestionDeleted($this);
4585 }
4586 }
4587
4592 {
4593 require_once 'Services/Html/classes/class.ilHtmlPurifierFactory.php';
4594 return ilHtmlPurifierFactory::_getInstanceByType('qpl_usersolution');
4595 }
4596
4601 {
4602 require_once 'Services/Html/classes/class.ilHtmlPurifierFactory.php';
4603 return ilHtmlPurifierFactory::_getInstanceByType('qpl_usersolution');
4604 }
4605
4606 protected function buildQuestionDataQuery()
4607 {
4608 return "
4609 SELECT qpl_questions.*,
4610 {$this->getAdditionalTableName()}.*
4611 FROM qpl_questions
4612 LEFT JOIN {$this->getAdditionalTableName()}
4613 ON {$this->getAdditionalTableName()}.question_fi = qpl_questions.question_id
4614 WHERE qpl_questions.question_id = %s
4615 ";
4616 }
4617
4619 {
4620 $this->lastChange = $lastChange;
4621 }
4622
4623 public function getLastChange()
4624 {
4625 return $this->lastChange;
4626 }
4627
4638 protected function getCurrentSolutionResultSet($active_id, $pass, $authorized = true)
4639 {
4640 global $ilDB;
4641
4642 if ($this->getStep() !== null) {
4643 $query = "
4644 SELECT *
4645 FROM tst_solutions
4646 WHERE active_fi = %s
4647 AND question_fi = %s
4648 AND pass = %s
4649 AND step = %s
4650 AND authorized = %s
4651 ";
4652
4653 return $ilDB->queryF(
4654 $query,
4655 array('integer', 'integer', 'integer', 'integer', 'integer'),
4656 array($active_id, $this->getId(), $pass, $this->getStep(), (int) $authorized)
4657 );
4658 } else {
4659 $query = "
4660 SELECT *
4661 FROM tst_solutions
4662 WHERE active_fi = %s
4663 AND question_fi = %s
4664 AND pass = %s
4665 AND authorized = %s
4666 ";
4667
4668 return $ilDB->queryF(
4669 $query,
4670 array('integer', 'integer', 'integer', 'integer'),
4671 array($active_id, $this->getId(), $pass, (int) $authorized)
4672 );
4673 }
4674 }
4675
4682 protected function removeSolutionRecordById($solutionId)
4683 {
4684 global $ilDB;
4685
4686 return $ilDB->manipulateF(
4687 "DELETE FROM tst_solutions WHERE solution_id = %s",
4688 array('integer'),
4689 array($solutionId)
4690 );
4691 }
4692
4693 // hey: prevPassSolutions - selected file reuse, copy solution records
4700 protected function getSolutionRecordById($solutionId)
4701 {
4702 $ilDB = isset($GLOBALS['DIC']) ? $GLOBALS['DIC']['ilDB'] : $GLOBALS['ilDB'];
4703
4704 $res = $ilDB->queryF(
4705 "SELECT * FROM tst_solutions WHERE solution_id = %s",
4706 array('integer'),
4707 array($solutionId)
4708 );
4709
4710 while ($row = $ilDB->fetchAssoc($res)) {
4711 return $row;
4712 }
4713 }
4714 // hey.
4715
4724 public function removeIntermediateSolution($active_id, $pass)
4725 {
4726 $this->getProcessLocker()->executeUserSolutionUpdateLockOperation(function () use ($active_id, $pass) {
4727 $this->removeCurrentSolution($active_id, $pass, false);
4728 });
4729 }
4730
4739 public function removeCurrentSolution($active_id, $pass, $authorized = true)
4740 {
4741 global $ilDB;
4742
4743 if ($this->getStep() !== null) {
4744 $query = "
4745 DELETE FROM tst_solutions
4746 WHERE active_fi = %s
4747 AND question_fi = %s
4748 AND pass = %s
4749 AND step = %s
4750 AND authorized = %s
4751 ";
4752
4753 return $ilDB->manipulateF(
4754 $query,
4755 array('integer', 'integer', 'integer', 'integer', 'integer'),
4756 array($active_id, $this->getId(), $pass, $this->getStep(), (int) $authorized)
4757 );
4758 } else {
4759 $query = "
4760 DELETE FROM tst_solutions
4761 WHERE active_fi = %s
4762 AND question_fi = %s
4763 AND pass = %s
4764 AND authorized = %s
4765 ";
4766
4767 return $ilDB->manipulateF(
4768 $query,
4769 array('integer', 'integer', 'integer', 'integer'),
4770 array($active_id, $this->getId(), $pass, (int) $authorized)
4771 );
4772 }
4773 }
4774
4775 // fau: testNav - add timestamp as parameter to saveCurrentSolution
4787 public function saveCurrentSolution($active_id, $pass, $value1, $value2, $authorized = true, $tstamp = null)
4788 {
4789 global $ilDB;
4790
4791 $next_id = $ilDB->nextId("tst_solutions");
4792
4793 $fieldData = array(
4794 "solution_id" => array("integer", $next_id),
4795 "active_fi" => array("integer", $active_id),
4796 "question_fi" => array("integer", $this->getId()),
4797 "value1" => array("clob", $value1),
4798 "value2" => array("clob", $value2),
4799 "pass" => array("integer", $pass),
4800 "tstamp" => array("integer", isset($tstamp) ? $tstamp : time()),
4801 'authorized' => array('integer', (int) $authorized)
4802 );
4803
4804 if ($this->getStep() !== null) {
4805 $fieldData['step'] = array("integer", $this->getStep());
4806 }
4807
4808 return $ilDB->insert("tst_solutions", $fieldData);
4809 }
4810 // fau.
4811
4822 public function updateCurrentSolution($solutionId, $value1, $value2, $authorized = true)
4823 {
4824 global $ilDB;
4825
4826 $fieldData = array(
4827 "value1" => array("clob", $value1),
4828 "value2" => array("clob", $value2),
4829 "tstamp" => array("integer", time()),
4830 'authorized' => array('integer', (int) $authorized)
4831 );
4832
4833 if ($this->getStep() !== null) {
4834 $fieldData['step'] = array("integer", $this->getStep());
4835 }
4836
4837 return $ilDB->update("tst_solutions", $fieldData, array(
4838 'solution_id' => array('integer', $solutionId)
4839 ));
4840 }
4841
4842 // fau: testNav - added parameter to keep the timestamp (default: false)
4843 public function updateCurrentSolutionsAuthorization($activeId, $pass, $authorized, $keepTime = false)
4844 {
4845 global $ilDB;
4846
4847 $fieldData = array(
4848 'authorized' => array('integer', (int) $authorized)
4849 );
4850
4851 if (!$keepTime) {
4852 $fieldData['tstamp'] = array('integer', time());
4853 }
4854
4855 $whereData = array(
4856 'question_fi' => array('integer', $this->getId()),
4857 'active_fi' => array('integer', $activeId),
4858 'pass' => array('integer', $pass)
4859 );
4860
4861 if ($this->getStep() !== null) {
4862 $whereData['step'] = array("integer", $this->getStep());
4863 }
4864
4865 return $ilDB->update('tst_solutions', $fieldData, $whereData);
4866 }
4867 // fau.
4868
4869 // hey: prevPassSolutions - motivation slowly decreases on imagemap
4871 protected static function getKeyValuesImplosionSeparator()
4872 {
4874 }
4875 public static function implodeKeyValues($keyValues)
4876 {
4877 return implode(self::getKeyValuesImplosionSeparator(), $keyValues);
4878 }
4879 public static function explodeKeyValues($keyValues)
4880 {
4881 return explode(self::getKeyValuesImplosionSeparator(), $keyValues);
4882 }
4883
4884 protected function deleteDummySolutionRecord($activeId, $passIndex)
4885 {
4886 foreach ($this->getSolutionValues($activeId, $passIndex, false) as $solutionRec) {
4887 if (0 == strlen($solutionRec['value1']) && 0 == strlen($solutionRec['value2'])) {
4888 $this->removeSolutionRecordById($solutionRec['solution_id']);
4889 }
4890 }
4891 }
4892
4893 protected function deleteSolutionRecordByValues($activeId, $passIndex, $authorized, $matchValues)
4894 {
4895 $ilDB = isset($GLOBALS['DIC']) ? $GLOBALS['DIC']['ilDB'] : $GLOBALS['ilDB'];
4896
4897 $types = array("integer", "integer", "integer", "integer");
4898 $values = array($activeId, $this->getId(), $passIndex, (int) $authorized);
4899 $valuesCondition = array();
4900
4901 foreach ($matchValues as $valueField => $value) {
4902 switch ($valueField) {
4903 case 'value1':
4904 case 'value2':
4905 $valuesCondition[] = "{$valueField} = %s";
4906 $types[] = 'text';
4907 $values[] = $value;
4908 break;
4909
4910 default:
4911 require_once 'Modules/TestQuestionPool/exceptions/class.ilTestQuestionPoolException.php';
4912 throw new ilTestQuestionPoolException('invalid value field given: ' . $valueField);
4913 }
4914 }
4915
4916 $valuesCondition = implode(' AND ', $valuesCondition);
4917
4918 $query = "
4919 DELETE FROM tst_solutions
4920 WHERE active_fi = %s
4921 AND question_fi = %s
4922 AND pass = %s
4923 AND authorized = %s
4924 AND $valuesCondition
4925 ";
4926
4927 if ($this->getStep() !== null) {
4928 $query .= " AND step = %s ";
4929 $types[] = 'integer';
4930 $values[] = $this->getStep();
4931 }
4932
4933 $ilDB->manipulateF($query, $types, $values);
4934 }
4935
4936 protected function duplicateIntermediateSolutionAuthorized($activeId, $passIndex)
4937 {
4938 foreach ($this->getSolutionValues($activeId, $passIndex, false) as $rec) {
4939 $this->saveCurrentSolution($activeId, $passIndex, $rec['value1'], $rec['value2'], true, $rec['tstamp']);
4940 }
4941 }
4942
4943 protected function forceExistingIntermediateSolution($activeId, $passIndex, $considerDummyRecordCreation)
4944 {
4945 $intermediateSolution = $this->getSolutionValues($activeId, $passIndex, false);
4946
4947 if (!count($intermediateSolution)) {
4948 // make the authorized solution intermediate (keeping timestamps)
4949 // this keeps the solution_ids in synch with eventually selected in $_POST['deletefiles']
4950 $this->updateCurrentSolutionsAuthorization($activeId, $passIndex, false, true);
4951
4952 // create a backup as authorized solution again (keeping timestamps)
4953 $this->duplicateIntermediateSolutionAuthorized($activeId, $passIndex);
4954
4955 if ($considerDummyRecordCreation) {
4956 // create an additional dummy record to indicate the existence of an intermediate solution
4957 // even if all entries are deleted from the intermediate solution later
4958 $this->saveCurrentSolution($activeId, $passIndex, null, null, false, null);
4959 }
4960 }
4961 }
4962 // hey.
4963
4967 public static function setResultGateway($resultGateway)
4968 {
4969 self::$resultGateway = $resultGateway;
4970 }
4971
4975 public static function getResultGateway()
4976 {
4977 return self::$resultGateway;
4978 }
4979
4983 public function setStep($step)
4984 {
4985 $this->step = $step;
4986 }
4987
4991 public function getStep()
4992 {
4993 return $this->step;
4994 }
4995
5001 public static function sumTimesInISO8601FormatH_i_s_Extended($time1, $time2)
5002 {
5005 return gmdate('H:i:s', $time);
5006 }
5007
5013 {
5014 $sec = 0;
5015 $time_array = explode(':', $time);
5016 if (sizeof($time_array) == 3) {
5017 $sec += $time_array[0] * 3600;
5018 $sec += $time_array[1] * 60;
5019 $sec += $time_array[2];
5020 }
5021 return $sec;
5022 }
5023
5024 public function toJSON()
5025 {
5026 return json_encode(array());
5027 }
5028
5029 abstract public function duplicate($for_test = true, $title = "", $author = "", $owner = "", $testObjId = null);
5030
5031 // hey: prevPassSolutions - check for authorized solution
5032 public function intermediateSolutionExists($active_id, $pass)
5033 {
5034 $solutionAvailability = $this->lookupForExistingSolutions($active_id, $pass);
5035 return (bool) $solutionAvailability['intermediate'];
5036 }
5037 public function authorizedSolutionExists($active_id, $pass)
5038 {
5039 $solutionAvailability = $this->lookupForExistingSolutions($active_id, $pass);
5040 return (bool) $solutionAvailability['authorized'];
5041 }
5043 {
5044 $solutionAvailability = $this->lookupForExistingSolutions($active_id, $pass);
5045 return (bool) $solutionAvailability['authorized'] || (bool) $solutionAvailability['intermediate'];
5046 }
5047 // hey.
5048
5054 protected function lookupMaxStep($active_id, $pass)
5055 {
5057 global $ilDB;
5058
5059 $res = $ilDB->queryF(
5060 "SELECT MAX(step) max_step FROM tst_solutions WHERE active_fi = %s AND pass = %s AND question_fi = %s",
5061 array("integer", "integer", "integer"),
5062 array($active_id, $pass, $this->getId())
5063 );
5064
5065 $row = $ilDB->fetchAssoc($res);
5066
5067 $maxStep = $row['max_step'];
5068
5069 return $maxStep;
5070 }
5071
5072 // fau: testNav - new function lookupForExistingSolutions
5079 public function lookupForExistingSolutions($activeId, $pass)
5080 {
5082 global $ilDB;
5083
5084 $return = array(
5085 'authorized' => false,
5086 'intermediate' => false
5087 );
5088
5089 $query = "
5090 SELECT authorized, COUNT(*) cnt
5091 FROM tst_solutions
5092 WHERE active_fi = %s
5093 AND question_fi = %s
5094 AND pass = %s
5095 ";
5096
5097 if ($this->getStep() !== null) {
5098 $query .= " AND step = " . $ilDB->quote((int) $this->getStep(), 'integer') . " ";
5099 }
5100
5101 $query .= "
5102 GROUP BY authorized
5103 ";
5104
5105 $result = $ilDB->queryF($query, array('integer', 'integer', 'integer'), array($activeId, $this->getId(), $pass));
5106
5107 while ($row = $ilDB->fetchAssoc($result)) {
5108 if ($row['authorized']) {
5109 $return['authorized'] = $row['cnt'] > 0;
5110 } else {
5111 $return['intermediate'] = $row['cnt'] > 0;
5112 }
5113 }
5114 return $return;
5115 }
5116 // fau.
5117
5118 public function removeExistingSolutions($activeId, $pass)
5119 {
5120 global $ilDB;
5121
5122 $query = "
5123 DELETE FROM tst_solutions
5124 WHERE active_fi = %s
5125 AND question_fi = %s
5126 AND pass = %s
5127 ";
5128
5129 if ($this->getStep() !== null) {
5130 $query .= " AND step = " . $ilDB->quote((int) $this->getStep(), 'integer') . " ";
5131 }
5132
5133 return $ilDB->manipulateF(
5134 $query,
5135 array('integer', 'integer', 'integer'),
5136 array($activeId, $this->getId(), $pass)
5137 );
5138 }
5139
5140 public function resetUsersAnswer($activeId, $pass)
5141 {
5142 $this->removeExistingSolutions($activeId, $pass);
5143 $this->removeResultRecord($activeId, $pass);
5144
5145 $this->log($activeId, "log_user_solution_willingly_deleted");
5146
5147 self::_updateTestPassResults(
5148 $activeId,
5149 $pass,
5151 $this->getProcessLocker(),
5152 $this->getTestId()
5153 );
5154 }
5155
5156 public function removeResultRecord($activeId, $pass)
5157 {
5158 global $ilDB;
5159
5160 $query = "
5161 DELETE FROM tst_test_result
5162 WHERE active_fi = %s
5163 AND question_fi = %s
5164 AND pass = %s
5165 ";
5166
5167 if ($this->getStep() !== null) {
5168 $query .= " AND step = " . $ilDB->quote((int) $this->getStep(), 'integer') . " ";
5169 }
5170
5171 return $ilDB->manipulateF(
5172 $query,
5173 array('integer', 'integer', 'integer'),
5174 array($activeId, $this->getId(), $pass)
5175 );
5176 }
5177
5178 public static function missingResultRecordExists($activeId, $pass, $questionIds)
5179 {
5180 global $ilDB;
5181
5182 $IN_questionIds = $ilDB->in('question_fi', $questionIds, false, 'integer');
5183
5184 $query = "
5185 SELECT COUNT(*) cnt
5186 FROM tst_test_result
5187 WHERE active_fi = %s
5188 AND pass = %s
5189 AND $IN_questionIds
5190 ";
5191
5192 $row = $ilDB->fetchAssoc($ilDB->queryF(
5193 $query,
5194 array('integer', 'integer'),
5195 array($activeId, $pass)
5196 ));
5197
5198 return $row['cnt'] < count($questionIds);
5199 }
5200
5201 public static function getQuestionsMissingResultRecord($activeId, $pass, $questionIds)
5202 {
5203 global $ilDB;
5204
5205 $IN_questionIds = $ilDB->in('question_fi', $questionIds, false, 'integer');
5206
5207 $query = "
5208 SELECT question_fi
5209 FROM tst_test_result
5210 WHERE active_fi = %s
5211 AND pass = %s
5212 AND $IN_questionIds
5213 ";
5214
5215 $res = $ilDB->queryF(
5216 $query,
5217 array('integer', 'integer'),
5218 array($activeId, $pass)
5219 );
5220
5221 $questionsHavingResultRecord = array();
5222
5223 while ($row = $ilDB->fetchAssoc($res)) {
5224 $questionsHavingResultRecord[] = $row['question_fi'];
5225 }
5226
5227 $questionsMissingResultRecordt = array_diff(
5228 $questionIds,
5229 $questionsHavingResultRecord
5230 );
5231
5232 return $questionsMissingResultRecordt;
5233 }
5234
5235 public static function lookupResultRecordExist($activeId, $questionId, $pass)
5236 {
5237 global $ilDB;
5238
5239 $query = "
5240 SELECT COUNT(*) cnt
5241 FROM tst_test_result
5242 WHERE active_fi = %s
5243 AND question_fi = %s
5244 AND pass = %s
5245 ";
5246
5247 $row = $ilDB->fetchAssoc($ilDB->queryF($query, array('integer', 'integer', 'integer'), array($activeId, $questionId, $pass)));
5248
5249 return $row['cnt'] > 0;
5250 }
5251
5256 public function fetchValuePairsFromIndexedValues(array $indexedValues)
5257 {
5258 $valuePairs = array();
5259
5260 foreach ($indexedValues as $value1 => $value2) {
5261 $valuePairs[] = array('value1' => $value1, 'value2' => $value2);
5262 }
5263
5264 return $valuePairs;
5265 }
5266
5271 public function fetchIndexedValuesFromValuePairs(array $valuePairs)
5272 {
5273 $indexedValues = array();
5274
5275 foreach ($valuePairs as $valuePair) {
5276 $indexedValues[ $valuePair['value1'] ] = $valuePair['value2'];
5277 }
5278
5279 return $indexedValues;
5280 }
5281
5286 {
5288 }
5289
5294 {
5295 $this->obligationsToBeConsidered = $obligationsToBeConsidered;
5296 }
5297
5298 public function updateTimestamp()
5299 {
5300 global $ilDB;
5301
5302 $ilDB->manipulateF(
5303 "UPDATE qpl_questions SET tstamp = %s WHERE question_id = %s",
5304 array('integer', 'integer'),
5305 array(time(), $this->getId())
5306 );
5307 }
5308
5309 // fau: testNav - new function getTestQuestionConfig()
5310 // hey: prevPassSolutions - get caching independent from configuration (config once)
5311 // renamed: getTestPresentationConfig() -> does the caching
5312 // completed: extracted instance building
5313 // avoids configuring cached instances on every access
5314 // allows a stable reconfigure of the instance from outside
5319
5325 {
5326 if ($this->testQuestionConfigInstance === null) {
5327 $this->testQuestionConfigInstance = $this->buildTestPresentationConfig();
5328 }
5329
5331 }
5332
5341 protected function buildTestPresentationConfig()
5342 {
5343 include_once('Modules/TestQuestionPool/classes/class.ilTestQuestionConfig.php');
5344 return new ilTestQuestionConfig();
5345 }
5346 // hey.
5347// fau.
5348}
sprintf('%.4f', $callTime)
$worksheet
$result
$_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"
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.
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.
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.
reworkWorkingData($active_id, $pass, $obligationsAnswered, $authorized)
Reworks the allready saved working data if neccessary.
_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($a_ctype, $a_cname, $a_slot_id, $a_pname)
Get plugin object.
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
if(!is_dir( $entity_dir)) exit("Fatal Error ([A-Za-z0-9]+)\s+" &#(? foreach( $entity_files as $file) $output
$GLOBALS['loaded']
Global hash that tracks already loaded includes.
$target_id
Definition: goto.php:49
global $ilCtrl
Definition: ilias.php:18
const OUTPUT_HTML
const OUTPUT_JAVASCRIPT
$time
Definition: cron.php:21
if($format !==null) $name
Definition: metadata.php:146
$index
Definition: metadata.php:60
$xml
Definition: metadata.php:240
catch(Exception $e) $message
$keys
redirection script todo: (a better solution should control the processing via a xml file)
$query
$type
if(empty($password)) $table
Definition: pwgen.php:24
if(!file_exists("$old.txt")) if( $old===$new) if(file_exists("$new.txt")) $file
global $DIC
Definition: saml.php:7
foreach($_POST as $key=> $value) $res
global $ilDB
$mobs
$ilUser
Definition: imgupload.php:18