ILIAS  release_5-1 Revision 5.0.0-5477-g43f3e3fab5f
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
142 protected $outputType;
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
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
275 function __construct(
276 $title = "",
277 $comment = "",
278 $author = "",
279 $owner = -1,
280 $question = ""
281 )
282 {
283 global $ilias, $lng, $tpl, $ilDB;
284
285 $this->ilias = $ilias;
286 $this->lng = $lng;
287 $this->tpl = $tpl;
288 $this->db = $ilDB;
289
290 $this->original_id = null;
291 $this->title = $title;
292 $this->comment = $comment;
293 $this->page = null;
294 $this->author = $author;
295 $this->setQuestion($question);
296 if (!$this->author)
297 {
298 $this->author = $this->ilias->account->fullname;
299 }
300 $this->owner = $owner;
301 if ($this->owner <= 0)
302 {
303 $this->owner = $this->ilias->account->id;
304 }
305 $this->id = -1;
306 $this->test_id = -1;
307 $this->suggested_solutions = array();
308 $this->shuffle = 1;
309 $this->nr_of_tries = 0;
310 $this->setEstimatedWorkingTime(0,1,0);
311 $this->outputType = OUTPUT_JAVASCRIPT;
312 $this->arrData = array();
313 $this->setExternalId('');
314
315 $this->questionActionCmd = 'handleQuestionAction';
316
317 $this->lastChange = null;
318
319 require_once 'Services/Randomization/classes/class.ilArrayElementOrderKeeper.php';
320 $this->shuffler = new ilArrayElementOrderKeeper();
321 }
322
323 protected static $forcePassResultsUpdateEnabled = false;
324
326 {
327 self::$forcePassResultsUpdateEnabled = $forcePassResultsUpdateEnabled;
328 }
329
330 public static function isForcePassResultUpdateEnabled()
331 {
333 }
334
335 public static function isAllowedImageMimeType($mimeType)
336 {
337 return (bool)count(self::getAllowedFileExtensionsForMimeType($mimeType));
338 }
339
340 public static function fetchMimeTypeIdentifier($contentTypeString)
341 {
342 return current(explode(';', $contentTypeString));
343 }
344
345 public static function getAllowedFileExtensionsForMimeType($mimeType)
346 {
347 foreach(self::$allowedFileExtensionsByMimeType as $allowedMimeType => $extensions)
348 {
349 $rexCharsets = implode('|', self::$allowedCharsetsByMimeType[$allowedMimeType]);
350 $rexMimeType = preg_quote($allowedMimeType, '/');
351
352 $rex = '/^'.$rexMimeType.'(;(\s)*charset=('.$rexCharsets.'))*$/';
353
354 if( !preg_match($rex, $mimeType) )
355 {
356 continue;
357 }
358
359 return $extensions;
360 }
361
362 return array();
363 }
364
365 public static function isAllowedImageFileExtension($mimeType, $fileExtension)
366 {
367 return in_array(
368 strtolower($fileExtension), self::getAllowedFileExtensionsForMimeType($mimeType)
369 );
370 }
371
372 public static function getAllowedImageFileExtensions()
373 {
374 $allowedExtensions = array();
375
376 foreach(self::$allowedFileExtensionsByMimeType as $mimeType => $fileExtensions)
377 {
378 $allowedExtensions = array_merge($allowedExtensions, $fileExtensions);
379 }
380
381 return $allowedExtensions;
382 }
383
387 public function getShuffler()
388 {
389 return $this->shuffler;
390 }
391
396 {
397 $this->shuffler = $shuffler;
398 }
399
404 {
405 $this->processLocker = $processLocker;
406 }
407
411 public function getProcessLocker()
412 {
414 }
415
427 function fromXML(&$item, &$questionpool_id, &$tst_id, &$tst_object, &$question_counter, &$import_mapping)
428 {
429 include_once "./Modules/TestQuestionPool/classes/import/qti12/class." . $this->getQuestionType() . "Import.php";
430 $classname = $this->getQuestionType() . "Import";
431 $import = new $classname($this);
432 $import->fromXML($item, $questionpool_id, $tst_id, $tst_object, $question_counter, $import_mapping);
433 }
434
441 function toXML($a_include_header = true, $a_include_binary = true, $a_shuffle = false, $test_output = false, $force_image_references = false)
442 {
443 include_once "./Modules/TestQuestionPool/classes/export/qti12/class." . $this->getQuestionType() . "Export.php";
444 $classname = $this->getQuestionType() . "Export";
445 $export = new $classname($this);
446 return $export->toXML($a_include_header, $a_include_binary, $a_shuffle, $test_output, $force_image_references);
447 }
448
455 function isComplete()
456 {
457 return false;
458 }
459
467 function questionTitleExists($questionpool_id, $title)
468 {
469 global $ilDB;
470
471 $result = $ilDB->queryF("SELECT * FROM qpl_questions WHERE obj_fi = %s AND title = %s",
472 array('integer','text'),
473 array($questionpool_id, $title)
474 );
475 return ($result->numRows() > 0) ? TRUE : FALSE;
476 }
477
485 function setTitle($title = "")
486 {
487 $this->title = $title;
488 }
489
497 function setId($id = -1)
498 {
499 $this->id = $id;
500 }
501
509 function setTestId($id = -1)
510 {
511 $this->test_id = $id;
512 }
513
521 function setComment($comment = "")
522 {
523 $this->comment = $comment;
524 }
525
534 {
535 $this->outputType = $outputType;
536 }
537
538
546 function setShuffle($shuffle = true)
547 {
548 if ($shuffle)
549 {
550 $this->shuffle = 1;
551 }
552 else
553 {
554 $this->shuffle = 0;
555 }
556 }
557
568 function setEstimatedWorkingTime($hour=0, $min=0, $sec=0)
569 {
570 $this->est_working_time = array("h" => (int)$hour, "m" => (int)$min, "s" => (int)$sec);
571 }
572
580 {
581 $this->est_working_time = array(
582 'h' => (int)substr($durationString, 0, 2),
583 'm' => (int)substr($durationString, 3, 2),
584 's' => (int)substr($durationString, 6, 2)
585 );
586 }
587
595 function keyInArray($searchkey, $array)
596 {
597 if ($searchkey)
598 {
599 foreach ($array as $key => $value)
600 {
601 if (strcmp($key, $searchkey)==0)
602 {
603 return true;
604 }
605 }
606 }
607 return false;
608 }
609
617 function setAuthor($author = "")
618 {
619 if (!$author)
620 {
621 $author = $this->ilias->account->fullname;
622 }
623 $this->author = $author;
624 }
625
633 function setOwner($owner = "")
634 {
635 $this->owner = $owner;
636 }
637
645 function getTitle()
646 {
647 return $this->title;
648 }
649
657 function getId()
658 {
659 return $this->id;
660 }
661
669 function getShuffle()
670 {
671 return $this->shuffle;
672 }
673
681 function getTestId()
682 {
683 return $this->test_id;
684 }
685
693 function getComment()
694 {
695 return $this->comment;
696 }
697
705 function getOutputType()
706 {
707 return $this->outputType;
708 }
709
716 public function supportsJavascriptOutput()
717 {
718 return FALSE;
719 }
720
721 public function supportsNonJsOutput()
722 {
723 return true;
724 }
725
726 public function requiresJsSwitch()
727 {
728 return $this->supportsJavascriptOutput() && $this->supportsNonJsOutput();
729 }
730
739 {
740 if (!$this->est_working_time)
741 {
742 $this->est_working_time = array("h" => 0, "m" => 0, "s" => 0);
743 }
745 }
746
754 function getAuthor()
755 {
756 return $this->author;
757 }
758
766 function getOwner()
767 {
768 return $this->owner;
769 }
770
778 function getObjId()
779 {
780 return $this->obj_id;
781 }
782
790 function setObjId($obj_id = 0)
791 {
792 $this->obj_id = $obj_id;
793 }
794
799 {
800 $this->external_id = $external_id;
801 }
802
806 public function getExternalId()
807 {
808 if(!strlen($this->external_id))
809 {
810 if($this->getId() > 0)
811 {
812 return 'il_' . IL_INST_ID . '_qst_' . $this->getId();
813 }
814 else
815 {
816 return uniqid('', true);
817 }
818 }
819 else
820 {
821 return $this->external_id;
822 }
823 }
824
831 function _getMaximumPoints($question_id)
832 {
833 global $ilDB;
834
835 $points = 0;
836 $result = $ilDB->queryF("SELECT points FROM qpl_questions WHERE question_id = %s",
837 array('integer'),
838 array($question_id)
839 );
840 if ($result->numRows() == 1)
841 {
842 $row = $ilDB->fetchAssoc($result);
843 $points = $row["points"];
844 }
845 return $points;
846 }
847
854 function &_getQuestionInfo($question_id)
855 {
856 global $ilDB;
857
858 $result = $ilDB->queryF("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",
859 array('integer'),
860 array($question_id)
861 );
862 if ($result->numRows())
863 {
864 return $ilDB->fetchAssoc($result);
865 }
866 else return array();
867 }
868
875 public static function _getSuggestedSolutionCount($question_id)
876 {
877 global $ilDB;
878
879 $result = $ilDB->queryF("SELECT suggested_solution_id FROM qpl_sol_sug WHERE question_fi = %s",
880 array('integer'),
881 array($question_id)
882 );
883 return $result->numRows();
884 }
885
892 public static function _getSuggestedSolutionOutput($question_id)
893 {
895 if (!is_object($question)) return "";
896 return $question->getSuggestedSolutionOutput();
897 }
898
900 {
901 $output = array();
902 foreach ($this->suggested_solutions as $solution)
903 {
904 switch ($solution["type"])
905 {
906 case "lm":
907 case "st":
908 case "pg":
909 case "git":
910 array_push($output, '<a href="' . assQuestion::_getInternalLinkHref($solution["internal_link"]) . '">' . $this->lng->txt("solution_hint") . '</a>');
911 break;
912 case "file":
913 $possible_texts = array_values(array_filter(array(
914 ilUtil::prepareFormOutput($solution['value']['filename']),
915 ilUtil::prepareFormOutput($solution['value']['name']),
916 $this->lng->txt('tst_show_solution_suggested')
917 )));
918 array_push($output, '<a href="' . $this->getSuggestedSolutionPathWeb() . $solution["value"]["name"] . '">' . $possible_texts[0] . '</a>');
919 break;
920 case "text":
921 $solutionValue = $solution["value"];
922 $solutionValue = $this->fixSvgToPng($solutionValue);
923 $solutionValue = $this->fixUnavailableSkinImageSources($solutionValue);
924 $output[] = $this->prepareTextareaOutput($solutionValue, true);
925 break;
926 }
927 }
928 return join($output, "<br />");
929 }
930
939 function &_getSuggestedSolution($question_id, $subquestion_index = 0)
940 {
941 global $ilDB;
942
943 $result = $ilDB->queryF("SELECT * FROM qpl_sol_sug WHERE question_fi = %s AND subquestion_index = %s",
944 array('integer','integer'),
945 array($question_id, $subquestion_index)
946 );
947 if ($result->numRows() == 1)
948 {
949 $row = $ilDB->fetchAssoc($result);
950 return array(
951 "internal_link" => $row["internal_link"],
952 "import_id" => $row["import_id"]
953 );
954 }
955 else
956 {
957 return array();
958 }
959 }
960
966 public function getSuggestedSolutions()
967 {
969 }
970
978 function _getReachedPoints($active_id, $question_id, $pass = NULL)
979 {
980 global $ilDB;
981
982 $points = 0;
983 if (is_null($pass))
984 {
985 include_once "./Modules/TestQuestionPool/classes/class.assQuestion.php";
986 $pass = assQuestion::_getSolutionMaxPass($question_id, $active_id);
987 }
988 $result = $ilDB->queryF("SELECT * FROM tst_test_result WHERE active_fi = %s AND question_fi = %s AND pass = %s",
989 array('integer','integer','integer'),
990 array($active_id, $question_id, $pass)
991 );
992 if ($result->numRows() == 1)
993 {
994 $row = $ilDB->fetchAssoc($result);
995 $points = $row["points"];
996 }
997 return $points;
998 }
999
1008 function getReachedPoints($active_id, $pass = NULL)
1009 {
1010 return round($this->_getReachedPoints($active_id, $this->getId(), $pass), 2);
1011 }
1012
1020 {
1021 return $this->points;
1022 }
1023
1035 final public function getAdjustedReachedPoints($active_id, $pass = NULL, $authorizedSolution = true)
1036 {
1037 if (is_null($pass))
1038 {
1039 include_once "./Modules/Test/classes/class.ilObjTest.php";
1040 $pass = ilObjTest::_getPass($active_id);
1041 }
1042
1043 // determine reached points for submitted solution
1044 $reached_points = $this->calculateReachedPoints($active_id, $pass, $authorizedSolution);
1045
1046
1047
1048 // deduct points for requested hints from reached points
1049 require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionHintTracking.php';
1050 $hintTracking = new ilAssQuestionHintTracking($this->getId(), $active_id, $pass);
1051 $requestsStatisticData = $hintTracking->getRequestStatisticDataByQuestionAndTestpass();
1052 $reached_points = $reached_points - $requestsStatisticData->getRequestsPoints();
1053
1054 // adjust reached points regarding to tests scoring options
1055 $reached_points = $this->adjustReachedPointsByScoringOptions($reached_points, $active_id, $pass);
1056
1057 return $reached_points;
1058 }
1059
1069 final public function calculateResultsFromSolution($active_id, $pass = NULL, $obligationsEnabled = false)
1070 {
1071 global $ilDB, $ilUser;
1072
1073 if( is_null($pass) )
1074 {
1075 include_once "./Modules/Test/classes/class.ilObjTest.php";
1076 $pass = ilObjTest::_getPass($active_id);
1077 }
1078
1079 // determine reached points for submitted solution
1080 $reached_points = $this->calculateReachedPoints($active_id, $pass);
1081
1082 // deduct points for requested hints from reached points
1083 require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionHintTracking.php';
1084 $questionHintTracking = new ilAssQuestionHintTracking($this->getId(), $active_id, $pass);
1085 $requestsStatisticData = $questionHintTracking->getRequestStatisticDataByQuestionAndTestpass();
1086 $reached_points = $reached_points - $requestsStatisticData->getRequestsPoints();
1087
1088 // adjust reached points regarding to tests scoring options
1089 $reached_points = $this->adjustReachedPointsByScoringOptions($reached_points, $active_id, $pass);
1090
1091 if( $obligationsEnabled && ilObjTest::isQuestionObligatory($this->getId()) )
1092 {
1093 $isAnswered = $this->isAnswered($active_id, $pass);
1094 }
1095 else
1096 {
1097 $isAnswered = true;
1098 }
1099
1100 if( is_null($reached_points) ) $reached_points = 0;
1101
1102 $this->getProcessLocker()->requestUserQuestionResultUpdateLock();
1103
1104 $query = "
1105 DELETE FROM tst_test_result
1106
1107 WHERE active_fi = %s
1108 AND question_fi = %s
1109 AND pass = %s
1110 ";
1111
1112 $types = array('integer', 'integer', 'integer');
1113 $values = array($active_id, $this->getId(), $pass);
1114
1115 if( $this->getStep() !== NULL )
1116 {
1117 $query .= "
1118 AND step = %s
1119 ";
1120
1121 $types[] = 'integer';
1122 $values[] = $this->getStep();
1123 }
1124
1125 $affectedRows = $ilDB->manipulateF($query, $types, $values);
1126
1127 $next_id = $ilDB->nextId("tst_test_result");
1128
1129 $fieldData = array(
1130 'test_result_id' => array('integer', $next_id),
1131 'active_fi' => array('integer', $active_id),
1132 'question_fi' => array('integer', $this->getId()),
1133 'pass' => array('integer', $pass),
1134 'points' => array('float', $reached_points),
1135 'tstamp' => array('integer', time()),
1136 'hint_count' => array('integer', $requestsStatisticData->getRequestsCount()),
1137 'hint_points' => array('float', $requestsStatisticData->getRequestsPoints()),
1138 'answered' => array('integer', $isAnswered)
1139 );
1140
1141 if( $this->getStep() !== NULL )
1142 {
1143 $fieldData['step'] = array('integer', $this->getStep());
1144 }
1145
1146 $ilDB->insert('tst_test_result', $fieldData);
1147
1148 $this->getProcessLocker()->releaseUserQuestionResultUpdateLock();
1149
1150 include_once ("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
1151
1153 {
1154 $this->logAction(
1155 sprintf(
1156 $this->lng->txtlng(
1157 "assessment", "log_user_answered_question", ilObjAssessmentFolder::_getLogLanguage()
1158 ),
1159 $reached_points
1160 ),
1161 $active_id,
1162 $this->getId()
1163 );
1164 }
1165
1166 // update test pass results
1167 $this->_updateTestPassResults($active_id, $pass, $obligationsEnabled, $this->getProcessLocker());
1168
1169 // Update objective status
1170 include_once 'Modules/Course/classes/class.ilCourseObjectiveResult.php';
1171 ilCourseObjectiveResult::_updateObjectiveResult($ilUser->getId(),$active_id,$this->getId());
1172 }
1173
1182 final public function persistWorkingState($active_id, $pass = NULL, $obligationsEnabled = false, $authorized = true)
1183 {
1184 if( $pass === null )
1185 {
1186 require_once 'Modules/Test/classes/class.ilObjTest.php';
1187 $pass = ilObjTest::_getPass($active_id);
1188 }
1189
1190 $this->getProcessLocker()->requestPersistWorkingStateLock();
1191
1192 $saveStatus = $this->saveWorkingData($active_id, $pass, $authorized);
1193
1194 if( $authorized )
1195 {
1196 $this->calculateResultsFromSolution($active_id, $pass, $obligationsEnabled);
1197 }
1198
1199 $this->reworkWorkingData($active_id, $pass, $obligationsEnabled, $authorized);
1200
1201 $this->getProcessLocker()->releasePersistWorkingStateLock();
1202
1203 return $saveStatus;
1204 }
1205
1209 final public function persistPreviewState(ilAssQuestionPreviewSession $previewSession)
1210 {
1211 if( !$this->validateSolutionSubmit() )
1212 {
1213 return false;
1214 }
1215
1216 $this->savePreviewData($previewSession);
1217 }
1218
1228 abstract public function saveWorkingData($active_id, $pass = NULL, $authorized = true);
1229
1239 //abstract protected function reworkWorkingData($active_id, $pass, $obligationsAnswered, $intermediate);
1240
1241 protected function savePreviewData(ilAssQuestionPreviewSession $previewSession)
1242 {
1243 $previewSession->setParticipantsSolution($this->getSolutionSubmit());
1244 }
1245
1248 {
1249 global $ilDB;
1250
1251 include_once "./Modules/Test/classes/class.ilObjTest.php";
1252 include_once "./Modules/Test/classes/class.assMarkSchema.php";
1253
1254 $pass = ilObjTest::_getResultPass($active_id);
1255
1256 $query = "
1257 SELECT tst_pass_result.*
1258 FROM tst_pass_result
1259 WHERE active_fi = %s
1260 AND pass = %s
1261 ";
1262
1263 $result = $ilDB->queryF(
1264 $query, array('integer','integer'), array($active_id, $pass)
1265 );
1266
1267 $row = $ilDB->fetchAssoc($result);
1268
1269 $max = $row['maxpoints'];
1270 $reached = $row['points'];
1271
1272 $obligationsAnswered = (int)$row['obligations_answered'];
1273
1274 include_once "./Modules/Test/classes/class.assMarkSchema.php";
1275
1276 $percentage = (!$max) ? 0 : ($reached / $max) * 100.0;
1277
1278 $mark = ASS_MarkSchema::_getMatchingMarkFromActiveId($active_id, $percentage);
1279
1280 $isPassed = ( $mark["passed"] ? 1 : 0 );
1281 $isFailed = ( !$mark["passed"] ? 1 : 0 );
1282
1283 if( is_object($processLocker) )
1284 {
1285 $processLocker->requestUserTestResultUpdateLock();
1286 }
1287
1288 $query = "
1289 DELETE FROM tst_result_cache
1290 WHERE active_fi = %s
1291 ";
1292
1293 $affectedRows = $ilDB->manipulateF(
1294 $query, array('integer'), array($active_id)
1295 );
1296
1297 $ilDB->insert('tst_result_cache', array(
1298 'active_fi'=> array('integer', $active_id),
1299 'pass'=> array('integer', strlen($pass) ? $pass : 0),
1300 'max_points'=> array('float', strlen($max) ? $max : 0),
1301 'reached_points'=> array('float', strlen($reached) ? $reached : 0),
1302 'mark_short'=> array('text', strlen($mark["short_name"]) ? $mark["short_name"] : " "),
1303 'mark_official'=> array('text', strlen($mark["official_name"]) ? $mark["official_name"] : " "),
1304 'passed'=> array('integer', $isPassed),
1305 'failed'=> array('integer', $isFailed),
1306 'tstamp'=> array('integer', time()),
1307 'hint_count'=> array('integer', $row['hint_count']),
1308 'hint_points'=> array('float', $row['hint_points']),
1309 'obligations_answered' => array('integer', $obligationsAnswered)
1310 ));
1311
1312 if( is_object($processLocker) )
1313 {
1314 $processLocker->releaseUserTestResultUpdateLock();
1315 }
1316 }
1317
1319 function _updateTestPassResults($active_id, $pass, $obligationsEnabled = false, ilAssQuestionProcessLocker $processLocker = null, $test_obj_id = null)
1320 {
1321 global $ilDB;
1322
1323 include_once "./Modules/Test/classes/class.ilObjTest.php";
1324
1325 if( self::getResultGateway() !== null )
1326 {
1327 $data = self::getResultGateway()->getQuestionCountAndPointsForPassOfParticipant($active_id, $pass);
1328 $time = self::getResultGateway()->getWorkingTimeOfParticipantForPass($active_id, $pass);
1329 }
1330 else
1331 {
1334 }
1335
1336
1337 // update test pass results
1338
1339 $result = $ilDB->queryF("
1340 SELECT SUM(points) reachedpoints,
1341 SUM(hint_count) hint_count,
1342 SUM(hint_points) hint_points,
1343 COUNT(DISTINCT(question_fi)) answeredquestions
1344 FROM tst_test_result
1345 WHERE active_fi = %s
1346 AND pass = %s
1347 ",
1348 array('integer','integer'),
1349 array($active_id, $pass)
1350 );
1351
1352 if ($result->numRows() > 0)
1353 {
1354 if( $obligationsEnabled )
1355 {
1356 $query = '
1357 SELECT count(*) cnt,
1358 min( answered ) answ
1359 FROM tst_test_question
1360 INNER JOIN tst_active
1361 ON active_id = %s
1362 AND tst_test_question.test_fi = tst_active.test_fi
1363 LEFT JOIN tst_test_result
1364 ON tst_test_result.active_fi = %s
1365 AND tst_test_result.pass = %s
1366 AND tst_test_question.question_fi = tst_test_result.question_fi
1367 WHERE obligatory = 1';
1368
1369 $result_obligatory = $ilDB->queryF(
1370 $query, array('integer','integer','integer'), array($active_id, $active_id, $pass)
1371 );
1372
1373 $row_obligatory = $ilDB->fetchAssoc($result_obligatory);
1374
1375 if ($row_obligatory['cnt'] == 0)
1376 {
1377 $obligations_answered = 1;
1378 }
1379 else
1380 {
1381 $obligations_answered = (int) $row_obligatory['answ'];
1382 }
1383 }
1384 else
1385 {
1386 $obligations_answered = 1;
1387 }
1388
1389 $row = $ilDB->fetchAssoc($result);
1390
1391 if( $row['hint_count'] === null ) $row['hint_count'] = 0;
1392 if( $row['hint_points'] === null ) $row['hint_points'] = 0;
1393
1394 $exam_identifier = ilObjTest::buildExamId( $active_id, $pass, $test_obj_id);
1395
1396 if( is_object($processLocker) )
1397 {
1398 $processLocker->requestUserPassResultUpdateLock();
1399 }
1400
1401 /*
1402 $query = "
1403 DELETE FROM tst_pass_result
1404
1405 WHERE active_fi = %s
1406 AND pass = %s
1407 ";
1408
1409 $affectedRows = $ilDB->manipulateF(
1410 $query, array('integer','integer'), array($active_id, $pass)
1411 );
1412 */
1414 $ilDB->replace('tst_pass_result',
1415 array(
1416 'active_fi' => array('integer', $active_id),
1417 'pass' => array('integer', strlen($pass) ? $pass : 0)),
1418 array(
1419 'points' => array('float', $row['reachedpoints'] ? $row['reachedpoints'] : 0),
1420 'maxpoints' => array('float', $data['points']),
1421 'questioncount' => array('integer', $data['count']),
1422 'answeredquestions' => array('integer', $row['answeredquestions']),
1423 'workingtime' => array('integer', $time),
1424 'tstamp' => array('integer', time()),
1425 'hint_count' => array('integer', $row['hint_count']),
1426 'hint_points' => array('float', $row['hint_points']),
1427 'obligations_answered' => array('integer', $obligations_answered),
1428 'exam_id' => array('text', $exam_identifier)
1429 )
1430 );
1431
1432 /*
1433 $ilDB->insert('tst_pass_result', array(
1434 'active_fi' => array('integer', $active_id),
1435 'pass' => array('integer', strlen($pass) ? $pass : 0),
1436 'points' => array('float', $row['reachedpoints'] ? $row['reachedpoints'] : 0),
1437 'maxpoints' => array('float', $data['points']),
1438 'questioncount' => array('integer', $data['count']),
1439 'answeredquestions' => array('integer', $row['answeredquestions']),
1440 'workingtime' => array('integer', $time),
1441 'tstamp' => array('integer', time()),
1442 'hint_count' => array('integer', $row['hint_count']),
1443 'hint_points' => array('float', $row['hint_points']),
1444 'obligations_answered' => array('integer', $obligations_answered),
1445 'exam_id' => array('text', $exam_identifier)
1446 ));
1447 */
1448
1449 if( is_object($processLocker) )
1450 {
1451 $this->getProcessLocker()->releaseUserPassResultUpdateLock();
1452 }
1453 }
1454
1456
1457 return array(
1458 'active_fi' => $active_id,
1459 'pass' => $pass,
1460 'points' => ($row["reachedpoints"]) ? $row["reachedpoints"] : 0,
1461 'maxpoints' => $data["points"],
1462 'questioncount' => $data["count"],
1463 'answeredquestions' => $row["answeredquestions"],
1464 'workingtime' => $time,
1465 'tstamp' => time(),
1466 'hint_count' => $row['hint_count'],
1467 'hint_points' => $row['hint_points'],
1468 'obligations_answered' => $obligations_answered,
1469 'exam_id' => $exam_identifier
1470 );
1471 }
1472
1480 function logAction($logtext = "", $active_id = "", $question_id = "")
1481 {
1482 global $ilUser;
1483
1484 $original_id = "";
1485 if (strcmp($question_id, "") != 0)
1486 {
1487 include_once "./Modules/TestQuestionPool/classes/class.assQuestion.php";
1489 }
1490 include_once "./Modules/Test/classes/class.ilObjAssessmentFolder.php";
1491 include_once "./Modules/Test/classes/class.ilObjTest.php";
1493 }
1494
1502 function _logAction($logtext = "", $active_id = "", $question_id = "")
1503 {
1504 global $ilUser;
1505
1506 $original_id = "";
1507 if (strcmp($question_id, "") != 0)
1508 {
1509 include_once "./Modules/TestQuestionPool/classes/class.assQuestion.php";
1511 }
1512 include_once "./Modules/Test/classes/class.ilObjAssessmentFolder.php";
1513 include_once "./Modules/Test/classes/class.ilObjTest.php";
1515 }
1516
1525 {
1526 $mediatempdir = CLIENT_WEB_DIR . "/assessment/temp";
1527 if (!@is_dir($mediatempdir)) ilUtil::createDirectory($mediatempdir);
1528 $temp_name = tempnam($mediatempdir, $name . "_____");
1529 $temp_name = str_replace("\\", "/", $temp_name);
1530 @unlink($temp_name);
1531 if (!ilUtil::moveUploadedFile($file, $name, $temp_name))
1532 {
1533 return FALSE;
1534 }
1535 else
1536 {
1537 return $temp_name;
1538 }
1539 }
1540
1547 return CLIENT_WEB_DIR . "/assessment/$this->obj_id/$this->id/solution/";
1548 }
1549
1556 function getJavaPath() {
1557 return CLIENT_WEB_DIR . "/assessment/$this->obj_id/$this->id/java/";
1558 }
1559
1566 function getImagePath($question_id = null, $object_id = null)
1567 {
1568 if( $question_id === null)
1569 {
1570 $question_id = $this->id;
1571 }
1572
1573 if( $object_id === null)
1574 {
1575 $object_id = $this->obj_id;
1576 }
1577
1578 return $this->buildImagePath($question_id, $object_id);
1579 }
1580
1581 public function buildImagePath($questionId, $parentObjectId)
1582 {
1583 return CLIENT_WEB_DIR . "/assessment/{$parentObjectId}/{$questionId}/images/";
1584 }
1585
1592 function getFlashPath()
1593 {
1594 return CLIENT_WEB_DIR . "/assessment/$this->obj_id/$this->id/flash/";
1595 }
1596
1604 {
1605 include_once "./Services/Utilities/classes/class.ilUtil.php";
1606 $webdir = ilUtil::removeTrailingPathSeparators(CLIENT_WEB_DIR) . "/assessment/$this->obj_id/$this->id/java/";
1608 }
1609
1616 {
1617 include_once "./Services/Utilities/classes/class.ilUtil.php";
1618 $webdir = ilUtil::removeTrailingPathSeparators(CLIENT_WEB_DIR) . "/assessment/$this->obj_id/$this->id/solution/";
1620 }
1621
1629 {
1630 if(!$this->export_image_path)
1631 {
1632 include_once "./Services/Utilities/classes/class.ilUtil.php";
1633 $webdir = ilUtil::removeTrailingPathSeparators(CLIENT_WEB_DIR) . "/assessment/$this->obj_id/$this->id/images/";
1635 }
1636 else
1637 {
1639 }
1640 }
1641
1649 {
1650 include_once "./Services/Utilities/classes/class.ilUtil.php";
1651 $webdir = ilUtil::removeTrailingPathSeparators(CLIENT_WEB_DIR) . "/assessment/$this->obj_id/$this->id/flash/";
1653 }
1654
1655 public function getUserSolutionPreferingIntermediate($active_id, $pass = NULL)
1656 {
1657 $solution = $this->getSolutionValues($active_id, $pass, false);
1658
1659 if( !count($solution) )
1660 {
1661 $solution = $this->getSolutionValues($active_id, $pass, true);
1662 }
1663
1664 return $solution;
1665 }
1666
1670 public function getSolutionValues($active_id, $pass = NULL, $authorized = true)
1671 {
1672 global $ilDB;
1673
1674 if (is_null($pass))
1675 {
1676 $pass = $this->getSolutionMaxPass($active_id);
1677 }
1678
1679 if( $this->getStep() !== NULL )
1680 {
1681 $query = "
1682 SELECT *
1683 FROM tst_solutions
1684 WHERE active_fi = %s
1685 AND question_fi = %s
1686 AND pass = %s
1687 AND step = %s
1688 AND authorized = %s
1689 ORDER BY solution_id";
1690
1691 $result = $ilDB->queryF($query, array('integer', 'integer', 'integer', 'integer', 'integer'),
1692 array($active_id, $this->getId(), $pass, $this->getStep(), (int)$authorized)
1693 );
1694 }
1695 else
1696 {
1697 $query = "
1698 SELECT *
1699 FROM tst_solutions
1700 WHERE active_fi = %s
1701 AND question_fi = %s
1702 AND pass = %s
1703 AND authorized = %s
1704 ORDER BY solution_id
1705 ";
1706
1707 $result = $ilDB->queryF($query, array('integer', 'integer', 'integer', 'integer'),
1708 array($active_id, $this->getId(), $pass, (int)$authorized)
1709 );
1710 }
1711
1712 $values = array();
1713
1714 while( $row = $ilDB->fetchAssoc($result) )
1715 {
1716 $values[] = $row;
1717 }
1718
1719 return $values;
1720 }
1721
1728 function isInUse($question_id = "")
1729 {
1730 global $ilDB;
1731
1732 if ($question_id < 1) $question_id = $this->getId();
1733 $result = $ilDB->queryF("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",
1734 array('integer'),
1735 array($question_id)
1736 );
1737 $row = $ilDB->fetchAssoc($result);
1738 $count = $row["question_count"];
1739
1740 $result = $ilDB->queryF("SELECT DISTINCT tst_active.test_fi, qpl_questions.question_id FROM qpl_questions, tst_test_rnd_qst, tst_active WHERE qpl_questions.original_id = %s AND qpl_questions.question_id = tst_test_rnd_qst.question_fi AND tst_test_rnd_qst.active_fi = tst_active.active_id",
1741 array('integer'),
1742 array($question_id)
1743 );
1744 $count += $result->numRows();
1745
1746 return $count;
1747 }
1748
1755 function isClone($question_id = "")
1756 {
1757 global $ilDB;
1758
1759 if ($question_id < 1) $question_id = $this->id;
1760 $result = $ilDB->queryF("SELECT original_id FROM qpl_questions WHERE question_id = %s",
1761 array('integer'),
1762 array($question_id)
1763 );
1764 $row = $ilDB->fetchAssoc($result);
1765 return ($row["original_id"] > 0) ? TRUE : FALSE;
1766 }
1767
1774 function pcArrayShuffle($array)
1775 {
1776 $keys = array_keys($array);
1777 shuffle($keys);
1778 $result = array();
1779 foreach ($keys as $key)
1780 {
1781 $result[$key] = $array[$key];
1782 }
1783 return $result;
1784 }
1785
1791 function getQuestionTypeFromDb($question_id)
1792 {
1793 global $ilDB;
1794
1795 $result = $ilDB->queryF("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",
1796 array('integer'),
1797 array($question_id)
1798 );
1799 $data = $ilDB->fetchAssoc($result);
1800 return $data["type_tag"];
1801 }
1802
1810 {
1811 return "";
1812 }
1813
1821 {
1822 return "";
1823 }
1824
1831 function deleteAnswers($question_id)
1832 {
1833 global $ilDB;
1834 $answer_table_name = $this->getAnswerTableName();
1835
1836 if( !is_array($answer_table_name) )
1837 {
1838 $answer_table_name = array($answer_table_name);
1839 }
1840
1841 foreach ($answer_table_name as $table)
1842 {
1843 if (strlen($table))
1844 {
1845 $affectedRows = $ilDB->manipulateF("DELETE FROM $table WHERE question_fi = %s",
1846 array('integer'),
1847 array($question_id)
1848 );
1849 }
1850 }
1851 }
1852
1859 function deleteAdditionalTableData($question_id)
1860 {
1861 global $ilDB;
1862
1863 $additional_table_name = $this->getAdditionalTableName();
1864
1865 if( !is_array($additional_table_name) )
1866 {
1867 $additional_table_name = array($additional_table_name);
1868 }
1869
1870 foreach ($additional_table_name as $table)
1871 {
1872 if (strlen($table))
1873 {
1874 $affectedRows = $ilDB->manipulateF("DELETE FROM $table WHERE question_fi = %s",
1875 array('integer'),
1876 array($question_id)
1877 );
1878 }
1879 }
1880 }
1881
1888 protected function deletePageOfQuestion($question_id)
1889 {
1890 include_once "./Modules/TestQuestionPool/classes/class.ilAssQuestionPage.php";
1891 $page = new ilAssQuestionPage($question_id);
1892 $page->delete();
1893 return true;
1894 }
1895
1902 public function delete($question_id)
1903 {
1904 global $ilDB, $ilLog;
1905
1906 if ($question_id < 1) return true; // nothing to do
1907
1908 $result = $ilDB->queryF("SELECT obj_fi FROM qpl_questions WHERE question_id = %s",
1909 array('integer'),
1910 array($question_id)
1911 );
1912 if ($result->numRows() == 1)
1913 {
1914 $row = $ilDB->fetchAssoc($result);
1915 $obj_id = $row["obj_fi"];
1916 }
1917 else
1918 {
1919 return true; // nothing to do
1920 }
1921 try
1922 {
1923 $this->deletePageOfQuestion($question_id);
1924 }
1925 catch (Exception $e)
1926 {
1927 $ilLog->write("EXCEPTION: Could not delete page of question $question_id: $e");
1928 return false;
1929 }
1930
1931 $affectedRows = $ilDB->manipulateF("DELETE FROM qpl_questions WHERE question_id = %s",
1932 array('integer'),
1933 array($question_id)
1934 );
1935 if ($affectedRows == 0) return false;
1936
1937 try
1938 {
1939 $this->deleteAdditionalTableData($question_id);
1940 $this->deleteAnswers($question_id);
1941 $this->feedbackOBJ->deleteGenericFeedbacks($question_id, $this->isAdditionalContentEditingModePageObject());
1942 $this->feedbackOBJ->deleteSpecificAnswerFeedbacks($question_id, $this->isAdditionalContentEditingModePageObject());
1943 }
1944 catch (Exception $e)
1945 {
1946 $ilLog->write("EXCEPTION: Could not delete additional table data of question $question_id: $e");
1947 return false;
1948 }
1949
1950 try
1951 {
1952 // delete the question in the tst_test_question table (list of test questions)
1953 $affectedRows = $ilDB->manipulateF("DELETE FROM tst_test_question WHERE question_fi = %s",
1954 array('integer'),
1955 array($question_id)
1956 );
1957 }
1958 catch (Exception $e)
1959 {
1960 $ilLog->write("EXCEPTION: Could not delete delete question $question_id from a test: $e");
1961 return false;
1962 }
1963
1964 try
1965 {
1966 // delete suggested solutions contained in the question
1967 $affectedRows = $ilDB->manipulateF("DELETE FROM qpl_sol_sug WHERE question_fi = %s",
1968 array('integer'),
1969 array($question_id)
1970 );
1971 }
1972 catch (Exception $e)
1973 {
1974 $ilLog->write("EXCEPTION: Could not delete suggested solutions of question $question_id: $e");
1975 return false;
1976 }
1977
1978 try
1979 {
1980 $directory = CLIENT_WEB_DIR . "/assessment/" . $obj_id . "/$question_id";
1981 if (preg_match("/\d+/", $obj_id) and preg_match("/\d+/", $question_id) and is_dir($directory))
1982 {
1983 include_once "./Services/Utilities/classes/class.ilUtil.php";
1984 ilUtil::delDir($directory);
1985 }
1986 }
1987 catch (Exception $e)
1988 {
1989 $ilLog->write("EXCEPTION: Could not delete question file directory $directory of question $question_id: $e");
1990 return false;
1991 }
1992
1993 try
1994 {
1995 include_once("./Services/MediaObjects/classes/class.ilObjMediaObject.php");
1996 $mobs = ilObjMediaObject::_getMobsOfObject("qpl:html", $question_id);
1997 // remaining usages are not in text anymore -> delete them
1998 // and media objects (note: delete method of ilObjMediaObject
1999 // checks whether object is used in another context; if yes,
2000 // the object is not deleted!)
2001 foreach($mobs as $mob)
2002 {
2003 ilObjMediaObject::_removeUsage($mob, "qpl:html", $question_id);
2004 if (ilObjMediaObject::_exists($mob))
2005 {
2006 $mob_obj =& new ilObjMediaObject($mob);
2007 $mob_obj->delete();
2008 }
2009 }
2010 }
2011 catch (Exception $e)
2012 {
2013 $ilLog->write("EXCEPTION: Error deleting the media objects of question $question_id: $e");
2014 return false;
2015 }
2016
2017 require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionHintTracking.php';
2019
2020 require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionHintList.php';
2022
2023 require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionSkillAssignmentList.php';
2024 $assignmentList = new ilAssQuestionSkillAssignmentList($ilDB);
2025 $assignmentList->setParentObjId($obj_id);
2026 $assignmentList->setQuestionIdFilter($question_id);
2027 $assignmentList->loadFromDb();
2028 foreach($assignmentList->getAssignmentsByQuestionId($question_id) as $assignment)
2029 {
2030 /* @var ilAssQuestionSkillAssignment $assignment */
2031 $assignment->deleteFromDb();
2032 }
2033
2035
2036 try
2037 {
2038 // update question count of question pool
2039 include_once "./Modules/TestQuestionPool/classes/class.ilObjQuestionPool.php";
2041 }
2042 catch (Exception $e)
2043 {
2044 $ilLog->write("EXCEPTION: Error updating the question pool question count of question pool " . $this->getObjId() . " when deleting question $question_id: $e");
2045 return false;
2046 }
2047
2048 $this->notifyQuestionDeleted($this);
2049
2050 return true;
2051 }
2052
2053 private function deleteTaxonomyAssignments()
2054 {
2055 require_once 'Services/Taxonomy/classes/class.ilObjTaxonomy.php';
2056 require_once 'Services/Taxonomy/classes/class.ilTaxNodeAssignment.php';
2057 $taxIds = ilObjTaxonomy::getUsageOfObject($this->getObjId());
2058
2059 foreach($taxIds as $taxId)
2060 {
2061 $taxNodeAssignment = new ilTaxNodeAssignment('qpl', $this->getObjId(), 'quest', $taxId);
2062 $taxNodeAssignment->deleteAssignmentsOfItem($this->getId());
2063 }
2064 }
2065
2070 {
2071 return $this->_getTotalAnswers($this->id);
2072 }
2073
2080 function _getTotalAnswers($a_q_id)
2081 {
2082 global $ilDB;
2083
2084 // get all question references to the question id
2085 $result = $ilDB->queryF("SELECT question_id FROM qpl_questions WHERE original_id = %s OR question_id = %s",
2086 array('integer','integer'),
2087 array($a_q_id, $a_q_id)
2088 );
2089 if ($result->numRows() == 0)
2090 {
2091 return 0;
2092 }
2093 $found_id = array();
2094 while ($row = $ilDB->fetchAssoc($result))
2095 {
2096 array_push($found_id, $row["question_id"]);
2097 }
2098
2099 $result = $ilDB->query("SELECT * FROM tst_test_result WHERE " . $ilDB->in('question_fi', $found_id, false, 'integer'));
2100
2101 return $result->numRows();
2102 }
2103
2104
2111 public static function _getTotalRightAnswers($a_q_id)
2112 {
2113 global $ilDB;
2114 $result = $ilDB->queryF("SELECT question_id FROM qpl_questions WHERE original_id = %s OR question_id = %s",
2115 array('integer','integer'),
2116 array($a_q_id, $a_q_id)
2117 );
2118 if ($result->numRows() == 0)
2119 {
2120 return 0;
2121 }
2122 $found_id = array();
2123 while ($row = $ilDB->fetchAssoc($result))
2124 {
2125 array_push($found_id, $row["question_id"]);
2126 }
2127 $result = $ilDB->query("SELECT * FROM tst_test_result WHERE " . $ilDB->in('question_fi', $found_id, false, 'integer'));
2128 $answers = array();
2129 while ($row = $ilDB->fetchAssoc($result))
2130 {
2131 $reached = $row["points"];
2132 include_once "./Modules/TestQuestionPool/classes/class.assQuestion.php";
2133 $max = assQuestion::_getMaximumPoints($row["question_fi"]);
2134 array_push($answers, array("reached" => $reached, "max" => $max));
2135 }
2136 $max = 0.0;
2137 $reached = 0.0;
2138 foreach ($answers as $key => $value)
2139 {
2140 $max += $value["max"];
2141 $reached += $value["reached"];
2142 }
2143 if ($max > 0)
2144 {
2145 return $reached / $max;
2146 }
2147 else
2148 {
2149 return 0;
2150 }
2151 }
2152
2158 function _getTitle($a_q_id)
2159 {
2160 global $ilDB;
2161 $result = $ilDB->queryF("SELECT title FROM qpl_questions WHERE question_id = %s",
2162 array('integer'),
2163 array($a_q_id)
2164 );
2165 if ($result->numRows() == 1)
2166 {
2167 $row = $ilDB->fetchAssoc($result);
2168 return $row["title"];
2169 }
2170 else
2171 {
2172 return "";
2173 }
2174 }
2175
2181 function _getQuestionText($a_q_id)
2182 {
2183 global $ilDB;
2184 $result = $ilDB->queryF("SELECT question_text FROM qpl_questions WHERE question_id = %s",
2185 array('integer'),
2186 array($a_q_id)
2187 );
2188 if ($result->numRows() == 1)
2189 {
2190 $row = $ilDB->fetchAssoc($result);
2191 return $row["question_text"];
2192 }
2193 else
2194 {
2195 return "";
2196 }
2197 }
2198
2199 public static function isFileAvailable($file)
2200 {
2201 if( !file_exists($file) )
2202 {
2203 return false;
2204 }
2205
2206 if( !is_file($file) )
2207 {
2208 return false;
2209 }
2210
2211 if( !is_readable($file) )
2212 {
2213 return false;
2214 }
2215
2216 return true;
2217 }
2218
2220 {
2221 include_once("./Services/MediaObjects/classes/class.ilObjMediaObject.php");
2222 $mobs = ilObjMediaObject::_getMobsOfObject("qpl:html", $a_q_id);
2223 foreach ($mobs as $mob)
2224 {
2225 ilObjMediaObject::_saveUsage($mob, "qpl:html", $this->getId());
2226 }
2227 }
2228
2230 {
2231 include_once("./Services/MediaObjects/classes/class.ilObjMediaObject.php");
2232 $mobs = ilObjMediaObject::_getMobsOfObject("qpl:html", $this->getId());
2233 foreach ($mobs as $mob)
2234 {
2235 ilObjMediaObject::_saveUsage($mob, "qpl:html", $this->original_id);
2236 }
2237 }
2238
2243 {
2244 $qpl_id = $this->getObjId();
2245
2246 include_once "./Modules/TestQuestionPool/classes/class.ilAssQuestionPage.php";
2247 $this->page = new ilAssQuestionPage(0);
2248 $this->page->setId($this->getId());
2249 $this->page->setParentId($qpl_id);
2250 $this->page->setXMLContent("<PageObject><PageContent>".
2251 "<Question QRef=\"il__qst_".$this->getId()."\"/>".
2252 "</PageContent></PageObject>");
2253 $this->page->create();
2254 }
2255
2256 function copyPageOfQuestion($a_q_id)
2257 {
2258 if ($a_q_id > 0)
2259 {
2260 include_once "./Modules/TestQuestionPool/classes/class.ilAssQuestionPage.php";
2261 $page = new ilAssQuestionPage($a_q_id);
2262
2263 $xml = str_replace("il__qst_".$a_q_id, "il__qst_".$this->id, $page->getXMLContent());
2264 $this->page->setXMLContent($xml);
2265 $this->page->updateFromXML();
2266 }
2267 }
2268
2270 {
2271 include_once "./Modules/TestQuestionPool/classes/class.ilAssQuestionPage.php";
2272 $page = new ilAssQuestionPage($this->id);
2273 return $page->getXMLContent();
2274 }
2275
2283 function _getQuestionType($question_id)
2284 {
2285 global $ilDB;
2286
2287 if ($question_id < 1) return "";
2288 $result = $ilDB->queryF("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",
2289 array('integer'),
2290 array($question_id)
2291 );
2292 if ($result->numRows() == 1)
2293 {
2294 $data = $ilDB->fetchAssoc($result);
2295 return $data["type_tag"];
2296 }
2297 else
2298 {
2299 return "";
2300 }
2301 }
2302
2310 function _getQuestionTitle($question_id)
2311 {
2312 global $ilDB;
2313
2314 if ($question_id < 1) return "";
2315
2316 $result = $ilDB->queryF("SELECT title FROM qpl_questions WHERE qpl_questions.question_id = %s",
2317 array('integer'),
2318 array($question_id)
2319 );
2320 if ($result->numRows() == 1)
2321 {
2322 $data = $ilDB->fetchAssoc($result);
2323 return $data["title"];
2324 }
2325 else
2326 {
2327 return "";
2328 }
2329 }
2330
2332 {
2333 $this->original_id = $original_id;
2334 }
2335
2336 function getOriginalId()
2337 {
2338 return $this->original_id;
2339 }
2340
2341 protected static $imageSourceFixReplaceMap = array(
2342 'ok.svg' => 'ok.png', 'not_ok.svg' => 'not_ok.png',
2343 'checkbox_checked.svg' => 'checkbox_checked.png',
2344 'checkbox_unchecked.svg' => 'checkbox_unchecked.png',
2345 'radiobutton_checked.svg' => 'radiobutton_checked.png',
2346 'radiobutton_unchecked.svg' => 'radiobutton_unchecked.png'
2347 );
2348
2349 public function fixSvgToPng($imageFilenameContainingString)
2350 {
2351 $needles = array_keys(self::$imageSourceFixReplaceMap);
2352 $replacements = array_values(self::$imageSourceFixReplaceMap);
2353 return str_replace($needles, $replacements, $imageFilenameContainingString);
2354 }
2355
2356
2358 {
2359 $matches = null;
2360 if( preg_match_all('/src="(.*?)"/m', $html, $matches) )
2361 {
2362 $sources = $matches[1];
2363
2364 $needleReplacementMap = array();
2365
2366 foreach($sources as $src)
2367 {
2368 $file = ilUtil::removeTrailingPathSeparators( ILIAS_ABSOLUTE_PATH ) . DIRECTORY_SEPARATOR . $src;
2369
2370 if( file_exists($file) )
2371 {
2372 continue;
2373 }
2374
2375 $levels = explode(DIRECTORY_SEPARATOR, $src);
2376 if( count($levels) < 5 || $levels[0] != 'Customizing' || $levels[2] != 'skin' )
2377 {
2378 continue;
2379 }
2380
2381 $component = '';
2382
2383 if( $levels[4] == 'Modules' || $levels[4] == 'Services' )
2384 {
2385 $component = $levels[4] . DIRECTORY_SEPARATOR . $levels[5];
2386 }
2387
2388 $needleReplacementMap[$src] = ilUtil::getImagePath(basename($src), $component);
2389 }
2390
2391 if( count($needleReplacementMap) )
2392 {
2393 $html = str_replace(array_keys($needleReplacementMap), array_values($needleReplacementMap), $html);
2394 }
2395 }
2396
2397 return $html;
2398 }
2399
2406 function loadFromDb($question_id)
2407 {
2408 global $ilDB;
2409
2410 $result = $ilDB->queryF(
2411 "SELECT external_id FROM qpl_questions WHERE question_id = %s",
2412 array("integer"),
2413 array($question_id)
2414 );
2415 if($result->numRows() == 1)
2416 {
2417 $data = $ilDB->fetchAssoc($result);
2418 $this->external_id = $data['external_id'];
2419 }
2420
2421 $result = $ilDB->queryF("SELECT * FROM qpl_sol_sug WHERE question_fi = %s",
2422 array('integer'),
2423 array($this->getId())
2424 );
2425 $this->suggested_solutions = array();
2426 if ($result->numRows())
2427 {
2428 include_once("./Services/RTE/classes/class.ilRTE.php");
2429 while ($row = $ilDB->fetchAssoc($result))
2430 {
2431 $value = (is_array(unserialize($row["value"]))) ? unserialize($row["value"]) : ilRTE::_replaceMediaObjectImageSrc($row["value"], 1);
2432 $this->suggested_solutions[$row["subquestion_index"]] = array(
2433 "type" => $row["type"],
2434 "value" => $value,
2435 "internal_link" => $row["internal_link"],
2436 "import_id" => $row["import_id"]
2437 );
2438 }
2439 }
2440 }
2441
2448 public function createNewQuestion($a_create_page = true)
2449 {
2450 global $ilDB, $ilUser;
2451
2452 $complete = "0";
2453 $estw_time = $this->getEstimatedWorkingTime();
2454 $estw_time = sprintf("%02d:%02d:%02d", $estw_time['h'], $estw_time['m'], $estw_time['s']);
2455 $obj_id = ($this->getObjId() <= 0) ? (ilObject::_lookupObjId((strlen($_GET["ref_id"])) ? $_GET["ref_id"] : $_POST["sel_qpl"])) : $this->getObjId();
2456 if ($obj_id > 0)
2457 {
2458 if($a_create_page)
2459 {
2460 $tstamp = 0;
2461 }
2462 else
2463 {
2464 // question pool must not try to purge
2465 $tstamp = time();
2466 }
2467
2468 $next_id = $ilDB->nextId('qpl_questions');
2469 $affectedRows = $ilDB->insert("qpl_questions", array(
2470 "question_id" => array("integer", $next_id),
2471 "question_type_fi" => array("integer", $this->getQuestionTypeID()),
2472 "obj_fi" => array("integer", $obj_id),
2473 "title" => array("text", NULL),
2474 "description" => array("text", NULL),
2475 "author" => array("text", $this->getAuthor()),
2476 "owner" => array("integer", $ilUser->getId()),
2477 "question_text" => array("clob", NULL),
2478 "points" => array("float", 0),
2479 "nr_of_tries" => array("integer", $this->getDefaultNrOfTries()), // #10771
2480 "working_time" => array("text", $estw_time),
2481 "complete" => array("text", $complete),
2482 "created" => array("integer", time()),
2483 "original_id" => array("integer", NULL),
2484 "tstamp" => array("integer", $tstamp),
2485 "external_id" => array("text", $this->getExternalId()),
2486 'add_cont_edit_mode' => array('text', $this->getAdditionalContentEditingMode())
2487 ));
2488 $this->setId($next_id);
2489
2490 if($a_create_page)
2491 {
2492 // create page object of question
2493 $this->createPageObject();
2494 }
2495 }
2496
2497 $this->notifyQuestionCreated();
2498
2499 return $this->getId();
2500 }
2501
2503 {
2504 global $ilDB;
2505
2506 $estw_time = $this->getEstimatedWorkingTime();
2507 $estw_time = sprintf("%02d:%02d:%02d", $estw_time['h'], $estw_time['m'], $estw_time['s']);
2508
2509 // cleanup RTE images which are not inserted into the question text
2510 include_once("./Services/RTE/classes/class.ilRTE.php");
2511 if ($this->getId() == -1)
2512 {
2513 // Neuen Datensatz schreiben
2514 $next_id = $ilDB->nextId('qpl_questions');
2515 $affectedRows = $ilDB->insert("qpl_questions", array(
2516 "question_id" => array("integer", $next_id),
2517 "question_type_fi" => array("integer", $this->getQuestionTypeID()),
2518 "obj_fi" => array("integer", $this->getObjId()),
2519 "title" => array("text", $this->getTitle()),
2520 "description" => array("text", $this->getComment()),
2521 "author" => array("text", $this->getAuthor()),
2522 "owner" => array("integer", $this->getOwner()),
2523 "question_text" => array("clob", ilRTE::_replaceMediaObjectImageSrc($this->getQuestion(), 0)),
2524 "points" => array("float", $this->getMaximumPoints()),
2525 "working_time" => array("text", $estw_time),
2526 "nr_of_tries" => array("integer", $this->getNrOfTries()),
2527 "created" => array("integer", time()),
2528 "original_id" => array("integer", ($original_id) ? $original_id : NULL),
2529 "tstamp" => array("integer", time()),
2530 "external_id" => array("text", $this->getExternalId()),
2531 'add_cont_edit_mode' => array('text', $this->getAdditionalContentEditingMode())
2532 ));
2533 $this->setId($next_id);
2534 // create page object of question
2535 $this->createPageObject();
2536 }
2537 else
2538 {
2539 // Vorhandenen Datensatz aktualisieren
2540 $affectedRows = $ilDB->update("qpl_questions", array(
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 "question_text" => array("clob", ilRTE::_replaceMediaObjectImageSrc($this->getQuestion(), 0)),
2546 "points" => array("float", $this->getMaximumPoints()),
2547 "nr_of_tries" => array("integer", $this->getNrOfTries()),
2548 "working_time" => array("text", $estw_time),
2549 "tstamp" => array("integer", time()),
2550 'complete' => array('integer', $this->isComplete()),
2551 "external_id" => array("text", $this->getExternalId())
2552 ), array(
2553 "question_id" => array("integer", $this->getId())
2554 ));
2555 }
2556 }
2557
2564 function saveToDb($original_id = "")
2565 {
2566 global $ilDB;
2567
2568 $this->updateSuggestedSolutions();
2569
2570 // remove unused media objects from ILIAS
2571 $this->cleanupMediaObjectUsage();
2572
2573 $complete = "0";
2574 if ($this->isComplete())
2575 {
2576 $complete = "1";
2577 }
2578
2579 // update the question time stamp and completion status
2580 $affectedRows = $ilDB->manipulateF("UPDATE qpl_questions SET tstamp = %s, owner = %s, complete = %s WHERE question_id = %s",
2581 array('integer','integer', 'integer','text'),
2582 array(time(), ($this->getOwner() <= 0) ? $this->ilias->account->id : $this->getOwner(), $complete, $this->getId())
2583 );
2584
2585 // update question count of question pool
2586 include_once "./Modules/TestQuestionPool/classes/class.ilObjQuestionPool.php";
2588
2589 $this->notifyQuestionEdited($this);
2590 }
2591
2592 public function setNewOriginalId($newId) {
2593 global $ilDB;
2594 $ilDB->manipulateF("UPDATE qpl_questions SET tstamp = %s, original_id = %s WHERE question_id = %s",
2595 array('integer','integer', 'text'),
2596 array(time(), $newId, $this->getId())
2597 );
2598 }
2599
2603 protected function onDuplicate($originalParentId, $originalQuestionId, $duplicateParentId, $duplicateQuestionId)
2604 {
2605 $this->duplicateSuggestedSolutionFiles($originalParentId, $originalQuestionId);
2606
2607 // duplicate question feeback
2608 $this->feedbackOBJ->duplicateFeedback($originalQuestionId, $duplicateQuestionId);
2609
2610 // duplicate question hints
2611 $this->duplicateQuestionHints($originalQuestionId, $duplicateQuestionId);
2612
2613 // duplicate skill assignments
2614 $this->duplicateSkillAssignments($originalParentId, $originalQuestionId, $duplicateParentId, $duplicateQuestionId);
2615 }
2616
2617 protected function beforeSyncWithOriginal($origQuestionId, $dupQuestionId, $origParentObjId, $dupParentObjId)
2618 {
2619
2620 }
2621
2622 protected function afterSyncWithOriginal($origQuestionId, $dupQuestionId, $origParentObjId, $dupParentObjId)
2623 {
2624 // sync question feeback
2625 $this->feedbackOBJ->syncFeedback($origQuestionId, $dupQuestionId);
2626 }
2627
2631 protected function onCopy($sourceParentId, $sourceQuestionId, $targetParentId, $targetQuestionId)
2632 {
2633 $this->copySuggestedSolutionFiles($sourceParentId, $sourceQuestionId);
2634
2635 // duplicate question feeback
2636 $this->feedbackOBJ->duplicateFeedback($sourceQuestionId, $targetQuestionId);
2637
2638 // duplicate question hints
2639 $this->duplicateQuestionHints($sourceQuestionId, $targetQuestionId);
2640
2641 // duplicate skill assignments
2642 $this->duplicateSkillAssignments($sourceParentId, $sourceQuestionId, $targetParentId, $targetQuestionId);
2643 }
2644
2649 {
2650 global $ilDB;
2651 // delete the links in the qpl_sol_sug table
2652 $affectedRows = $ilDB->manipulateF("DELETE FROM qpl_sol_sug WHERE question_fi = %s",
2653 array('integer'),
2654 array($this->getId())
2655 );
2656 // delete the links in the int_link table
2657 include_once "./Services/Link/classes/class.ilInternalLink.php";
2659 $this->suggested_solutions = array();
2661 }
2662
2670 function getSuggestedSolution($subquestion_index = 0)
2671 {
2672 if (array_key_exists($subquestion_index, $this->suggested_solutions))
2673 {
2674 return $this->suggested_solutions[$subquestion_index];
2675 }
2676 else
2677 {
2678 return array();
2679 }
2680 }
2681
2690 function getSuggestedSolutionTitle($subquestion_index = 0)
2691 {
2692 if (array_key_exists($subquestion_index, $this->suggested_solutions))
2693 {
2694 $title = $this->suggested_solutions[$subquestion_index]["internal_link"];
2695 // TO DO: resolve internal link an get link type and title
2696 }
2697 else
2698 {
2699 $title = "";
2700 }
2701 return $title;
2702 }
2703
2713 function setSuggestedSolution($solution_id = "", $subquestion_index = 0, $is_import = false)
2714 {
2715 if (strcmp($solution_id, "") != 0)
2716 {
2717 $import_id = "";
2718 if ($is_import)
2719 {
2720 $import_id = $solution_id;
2721 $solution_id = $this->_resolveInternalLink($import_id);
2722 }
2723 $this->suggested_solutions[$subquestion_index] = array(
2724 "internal_link" => $solution_id,
2725 "import_id" => $import_id
2726 );
2727 }
2728 }
2729
2733 protected function duplicateSuggestedSolutionFiles($parent_id, $question_id)
2734 {
2735 global $ilLog;
2736
2737 foreach ($this->suggested_solutions as $index => $solution)
2738 {
2739 if (strcmp($solution["type"], "file") == 0)
2740 {
2741 $filepath = $this->getSuggestedSolutionPath();
2742 $filepath_original = str_replace(
2743 "/{$this->obj_id}/{$this->id}/solution",
2744 "/$parent_id/$question_id/solution",
2745 $filepath
2746 );
2747 if (!file_exists($filepath))
2748 {
2749 ilUtil::makeDirParents($filepath);
2750 }
2751 $filename = $solution["value"]["name"];
2752 if (strlen($filename))
2753 {
2754 if (!copy($filepath_original . $filename, $filepath . $filename))
2755 {
2756 $ilLog->write("File could not be duplicated!!!!", $ilLog->ERROR);
2757 $ilLog->write("object: " . print_r($this, TRUE), $ilLog->ERROR);
2758 }
2759 }
2760 }
2761 }
2762 }
2763
2768 {
2769 global $ilLog;
2770
2771 $filepath = $this->getSuggestedSolutionPath();
2772 $filepath_original = str_replace("/$this->id/solution", "/$original_id/solution", $filepath);
2773 ilUtil::delDir($filepath_original);
2774 foreach ($this->suggested_solutions as $index => $solution)
2775 {
2776 if (strcmp($solution["type"], "file") == 0)
2777 {
2778 if (!file_exists($filepath_original))
2779 {
2780 ilUtil::makeDirParents($filepath_original);
2781 }
2782 $filename = $solution["value"]["name"];
2783 if (strlen($filename))
2784 {
2785 if (!@copy($filepath . $filename, $filepath_original . $filename))
2786 {
2787 $ilLog->write("File could not be duplicated!!!!", $ilLog->ERROR);
2788 $ilLog->write("object: " . print_r($this, TRUE), $ilLog->ERROR);
2789 }
2790 }
2791 }
2792 }
2793 }
2794
2795 protected function copySuggestedSolutionFiles($source_questionpool_id, $source_question_id)
2796 {
2797 global $ilLog;
2798
2799 foreach ($this->suggested_solutions as $index => $solution)
2800 {
2801 if (strcmp($solution["type"], "file") == 0)
2802 {
2803 $filepath = $this->getSuggestedSolutionPath();
2804 $filepath_original = str_replace("/$this->obj_id/$this->id/solution", "/$source_questionpool_id/$source_question_id/solution", $filepath);
2805 if (!file_exists($filepath))
2806 {
2807 ilUtil::makeDirParents($filepath);
2808 }
2809 $filename = $solution["value"]["name"];
2810 if (strlen($filename))
2811 {
2812 if (!copy($filepath_original . $filename, $filepath . $filename))
2813 {
2814 $ilLog->write("File could not be copied!!!!", $ilLog->ERROR);
2815 $ilLog->write("object: " . print_r($this, TRUE), $ilLog->ERROR);
2816 }
2817 }
2818 }
2819 }
2820 }
2821
2825 public function updateSuggestedSolutions($original_id = "")
2826 {
2827 global $ilDB;
2828
2829 $id = (strlen($original_id) && is_numeric($original_id)) ? $original_id : $this->getId();
2830 include_once "./Services/Link/classes/class.ilInternalLink.php";
2831 $affectedRows = $ilDB->manipulateF("DELETE FROM qpl_sol_sug WHERE question_fi = %s",
2832 array('integer'),
2833 array($id)
2834 );
2836 include_once("./Services/RTE/classes/class.ilRTE.php");
2837 foreach ($this->suggested_solutions as $index => $solution)
2838 {
2839 $next_id = $ilDB->nextId('qpl_sol_sug');
2841 $ilDB->insert('qpl_sol_sug', array(
2842 'suggested_solution_id' => array( 'integer', $next_id ),
2843 'question_fi' => array( 'integer', $id ),
2844 'type' => array( 'text', $solution['type'] ),
2845 'value' => array( 'clob', ilRTE::_replaceMediaObjectImageSrc( (is_array( $solution['value'] ) ) ? serialize( $solution[ 'value' ] ) : $solution['value'], 0 ) ),
2846 'internal_link' => array( 'text', $solution['internal_link'] ),
2847 'import_id' => array( 'text', null ),
2848 'subquestion_index' => array( 'integer', $index ),
2849 'tstamp' => array( 'integer', time() ),
2850 )
2851 );
2852 if (preg_match("/il_(\d*?)_(\w+)_(\d+)/", $solution["internal_link"], $matches))
2853 {
2854 ilInternalLink::_saveLink("qst", $id, $matches[2], $matches[3], $matches[1]);
2855 }
2856 }
2857 if (strlen($original_id) && is_numeric($original_id)) $this->syncSuggestedSolutionFiles($id);
2858 $this->cleanupMediaObjectUsage();
2859 }
2860
2870 function saveSuggestedSolution($type, $solution_id = "", $subquestion_index = 0, $value = "")
2871 {
2872 global $ilDB;
2873
2874 $affectedRows = $ilDB->manipulateF("DELETE FROM qpl_sol_sug WHERE question_fi = %s AND subquestion_index = %s",
2875 array("integer", "integer"),
2876 array(
2877 $this->getId(),
2878 $subquestion_index
2879 )
2880 );
2881
2882 $next_id = $ilDB->nextId('qpl_sol_sug');
2883 include_once("./Services/RTE/classes/class.ilRTE.php");
2885 $affectedRows = $ilDB->insert('qpl_sol_sug', array(
2886 'suggested_solution_id' => array( 'integer', $next_id ),
2887 'question_fi' => array( 'integer', $this->getId() ),
2888 'type' => array( 'text', $type ),
2889 'value' => array( 'clob', ilRTE::_replaceMediaObjectImageSrc( (is_array( $value ) ) ? serialize( $value ) : $value, 0 ) ),
2890 'internal_link' => array( 'text', $solution_id ),
2891 'import_id' => array( 'text', null ),
2892 'subquestion_index' => array( 'integer', $subquestion_index ),
2893 'tstamp' => array( 'integer', time() ),
2894 )
2895 );
2896 if ($affectedRows == 1)
2897 {
2898 $this->suggested_solutions[$subquestion_index] = array(
2899 "type" => $type,
2900 "value" => $value,
2901 "internal_link" => $solution_id,
2902 "import_id" => ""
2903 );
2904 }
2905 $this->cleanupMediaObjectUsage();
2906 }
2907
2908 function _resolveInternalLink($internal_link)
2909 {
2910 if (preg_match("/il_(\d+)_(\w+)_(\d+)/", $internal_link, $matches))
2911 {
2912 include_once "./Services/Link/classes/class.ilInternalLink.php";
2913 include_once "./Modules/LearningModule/classes/class.ilLMObject.php";
2914 include_once "./Modules/Glossary/classes/class.ilGlossaryTerm.php";
2915 switch ($matches[2])
2916 {
2917 case "lm":
2918 $resolved_link = ilLMObject::_getIdForImportId($internal_link);
2919 break;
2920 case "pg":
2921 $resolved_link = ilInternalLink::_getIdForImportId("PageObject", $internal_link);
2922 break;
2923 case "st":
2924 $resolved_link = ilInternalLink::_getIdForImportId("StructureObject", $internal_link);
2925 break;
2926 case "git":
2927 $resolved_link = ilInternalLink::_getIdForImportId("GlossaryItem", $internal_link);
2928 break;
2929 case "mob":
2930 $resolved_link = ilInternalLink::_getIdForImportId("MediaObject", $internal_link);
2931 break;
2932 }
2933 if (strcmp($resolved_link, "") == 0)
2934 {
2935 $resolved_link = $internal_link;
2936 }
2937 }
2938 else
2939 {
2940 $resolved_link = $internal_link;
2941 }
2942 return $resolved_link;
2943 }
2944
2945 function _resolveIntLinks($question_id)
2946 {
2947 global $ilDB;
2948 $resolvedlinks = 0;
2949 $result = $ilDB->queryF("SELECT * FROM qpl_sol_sug WHERE question_fi = %s",
2950 array('integer'),
2951 array($question_id)
2952 );
2953 if ($result->numRows())
2954 {
2955 while ($row = $ilDB->fetchAssoc($result))
2956 {
2957 $internal_link = $row["internal_link"];
2958 include_once "./Modules/TestQuestionPool/classes/class.assQuestion.php";
2959 $resolved_link = assQuestion::_resolveInternalLink($internal_link);
2960 if (strcmp($internal_link, $resolved_link) != 0)
2961 {
2962 // internal link was resolved successfully
2963 $affectedRows = $ilDB->manipulateF("UPDATE qpl_sol_sug SET internal_link = %s WHERE suggested_solution_id = %s",
2964 array('text','integer'),
2965 array($resolved_link, $row["suggested_solution_id"])
2966 );
2967 $resolvedlinks++;
2968 }
2969 }
2970 }
2971 if ($resolvedlinks)
2972 {
2973 // there are resolved links -> reenter theses links to the database
2974
2975 // delete all internal links from the database
2976 include_once "./Services/Link/classes/class.ilInternalLink.php";
2977 ilInternalLink::_deleteAllLinksOfSource("qst", $question_id);
2978
2979 $result = $ilDB->queryF("SELECT * FROM qpl_sol_sug WHERE question_fi = %s",
2980 array('integer'),
2981 array($question_id)
2982 );
2983 if ($result->numRows())
2984 {
2985 while ($row = $ilDB->fetchAssoc($result))
2986 {
2987 if (preg_match("/il_(\d*?)_(\w+)_(\d+)/", $row["internal_link"], $matches))
2988 {
2989 ilInternalLink::_saveLink("qst", $question_id, $matches[2], $matches[3], $matches[1]);
2990 }
2991 }
2992 }
2993 }
2994 }
2995
2996 function _getInternalLinkHref($target = "")
2997 {
2998 global $ilDB;
2999 $linktypes = array(
3000 "lm" => "LearningModule",
3001 "pg" => "PageObject",
3002 "st" => "StructureObject",
3003 "git" => "GlossaryItem",
3004 "mob" => "MediaObject"
3005 );
3006 $href = "";
3007 if (preg_match("/il__(\w+)_(\d+)/", $target, $matches))
3008 {
3009 $type = $matches[1];
3010 $target_id = $matches[2];
3011 include_once "./Services/Utilities/classes/class.ilUtil.php";
3012 switch($linktypes[$matches[1]])
3013 {
3014 case "LearningModule":
3015 $href = "./goto.php?target=" . $type . "_" . $target_id;
3016 break;
3017 case "PageObject":
3018 case "StructureObject":
3019 $href = "./goto.php?target=" . $type . "_" . $target_id;
3020 break;
3021 case "GlossaryItem":
3022 $href = "./goto.php?target=" . $type . "_" . $target_id;
3023 break;
3024 case "MediaObject":
3025 $href = "./ilias.php?baseClass=ilLMPresentationGUI&obj_type=" . $linktypes[$type] . "&cmd=media&ref_id=".$_GET["ref_id"]."&mob_id=".$target_id;
3026 break;
3027 }
3028 }
3029 return $href;
3030 }
3031
3039 public static function _getOriginalId($question_id)
3040 {
3041 global $ilDB;
3042 $result = $ilDB->queryF("SELECT * FROM qpl_questions WHERE question_id = %s",
3043 array('integer'),
3044 array($question_id)
3045 );
3046 if ($result->numRows() > 0)
3047 {
3048 $row = $ilDB->fetchAssoc($result);
3049 if ($row["original_id"] > 0)
3050 {
3051 return $row["original_id"];
3052 }
3053 else
3054 {
3055 return $row["question_id"];
3056 }
3057 }
3058 else
3059 {
3060 return "";
3061 }
3062 }
3063
3064 public static function originalQuestionExists($questionId)
3065 {
3066 global $ilDB;
3067
3068 $query = "
3069 SELECT COUNT(dupl.question_id) cnt
3070 FROM qpl_questions dupl
3071 INNER JOIN qpl_questions orig
3072 ON orig.question_id = dupl.original_id
3073 WHERE dupl.question_id = %s
3074 ";
3075
3076 $res = $ilDB->queryF($query, array('integer'), array($questionId));
3077 $row = $ilDB->fetchAssoc($res);
3078
3079 return $row['cnt'] > 0;
3080 }
3081
3083 {
3084 global $ilDB;
3085
3086 if( !$this->getOriginalId() )
3087 {
3088 return;
3089 }
3090
3091 $originalObjId = self::lookupOriginalParentObjId($this->getOriginalId());
3092
3093 if ( !$originalObjId )
3094 {
3095 return;
3096 }
3097
3098 $id = $this->getId();
3099 $objId = $this->getObjId();
3100 $original = $this->getOriginalId();
3101
3102 $this->beforeSyncWithOriginal($original, $id, $originalObjId, $objId);
3103
3104 $this->setId($original);
3105 $this->setOriginalId(NULL);
3106 $this->setObjId($originalObjId);
3107
3108 $this->saveToDb();
3109
3110 $this->deletePageOfQuestion($original);
3111 $this->createPageObject();
3112 $this->copyPageOfQuestion($id);
3113
3114 $this->setId($id);
3115 $this->setOriginalId($original);
3116 $this->setObjId($objId);
3117
3118 $this->updateSuggestedSolutions($original);
3120
3121 $this->afterSyncWithOriginal($original, $id, $originalObjId, $objId);
3122 $this->syncHints();
3123 }
3124
3125 function createRandomSolution($test_id, $user_id)
3126 {
3127 }
3128
3136 function _questionExists($question_id)
3137 {
3138 global $ilDB;
3139
3140 if ($question_id < 1)
3141 {
3142 return false;
3143 }
3144
3145 $result = $ilDB->queryF("SELECT question_id FROM qpl_questions WHERE question_id = %s",
3146 array('integer'),
3147 array($question_id)
3148 );
3149 if ($result->numRows() == 1)
3150 {
3151 return true;
3152 }
3153 else
3154 {
3155 return false;
3156 }
3157 }
3158
3166 function _questionExistsInPool($question_id)
3167 {
3168 global $ilDB;
3169
3170 if ($question_id < 1)
3171 {
3172 return false;
3173 }
3174
3175 $result = $ilDB->queryF("SELECT question_id FROM qpl_questions INNER JOIN object_data ON obj_fi = obj_id WHERE question_id = %s AND type = 'qpl'",
3176 array('integer'),
3177 array($question_id)
3178 );
3179 if ($result->numRows() == 1)
3180 {
3181 return true;
3182 }
3183 else
3184 {
3185 return false;
3186 }
3187 }
3188
3196 public static function _instanciateQuestion($question_id)
3197 {
3198 return self::_instantiateQuestion($question_id);
3199 }
3200
3205 public static function _instantiateQuestion($question_id)
3206 {
3207 global $ilCtrl, $ilDB, $lng;
3208
3209 if (strcmp($question_id, "") != 0)
3210 {
3211 $question_type = assQuestion::_getQuestionType($question_id);
3212 if (!strlen($question_type)) return null;
3213 assQuestion::_includeClass($question_type);
3214 $objectClassname = self::getObjectClassNameByQuestionType($question_type);
3215 $question = new $objectClassname();
3216 $question->loadFromDb($question_id);
3217
3218 $feedbackObjectClassname = self::getFeedbackClassNameByQuestionType($question_type);
3219 $question->feedbackOBJ = new $feedbackObjectClassname($question, $ilCtrl, $ilDB, $lng);
3220
3221 return $question;
3222 }
3223 }
3224
3231 function getPoints()
3232 {
3233 if (strcmp($this->points, "") == 0)
3234 {
3235 return 0;
3236 }
3237 else
3238 {
3239 return $this->points;
3240 }
3241 }
3242
3243
3250 function setPoints($a_points)
3251 {
3252 $this->points = $a_points;
3253 }
3254
3261 function getSolutionMaxPass($active_id)
3262 {
3263 return $this->_getSolutionMaxPass($this->getId(), $active_id);
3264 }
3265
3272 function _getSolutionMaxPass($question_id, $active_id)
3273 {
3274/* include_once "./Modules/Test/classes/class.ilObjTest.php";
3275 $pass = ilObjTest::_getPass($active_id);
3276 return $pass;*/
3277
3278 // the following code was the old solution which added the non answered
3279 // questions of a pass from the answered questions of the previous pass
3280 // with the above solution, only the answered questions of the last pass are counted
3281 global $ilDB;
3282
3283 $result = $ilDB->queryF("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 {
3289 $row = $ilDB->fetchAssoc($result);
3290 return $row["maxpass"];
3291 }
3292 else
3293 {
3294 return 0;
3295 }
3296 }
3297
3306 function _isWriteable($question_id, $user_id)
3307 {
3308 global $ilDB;
3309
3310 if (($question_id < 1) || ($user_id < 1))
3311 {
3312 return false;
3313 }
3314
3315 $result = $ilDB->queryF("SELECT obj_fi FROM qpl_questions WHERE question_id = %s",
3316 array('integer'),
3317 array($question_id)
3318 );
3319 if ($result->numRows() == 1)
3320 {
3321 $row = $ilDB->fetchAssoc($result);
3322 $qpl_object_id = $row["obj_fi"];
3323 include_once "./Modules/TestQuestionPool/classes/class.ilObjQuestionPool.php";
3324 return ilObjQuestionPool::_isWriteable($qpl_object_id, $user_id);
3325 }
3326 else
3327 {
3328 return false;
3329 }
3330 }
3331
3338 function _isUsedInRandomTest($question_id = "")
3339 {
3340 global $ilDB;
3341
3342 if ($question_id < 1) return 0;
3343 $result = $ilDB->queryF("SELECT test_random_question_id FROM tst_test_rnd_qst WHERE question_fi = %s",
3344 array('integer'),
3345 array($question_id)
3346 );
3347 return $result->numRows();
3348 }
3349
3361 abstract public function calculateReachedPoints($active_id, $pass = NULL, $authorizedSolution = true, $returndetails = FALSE);
3362
3364 {
3365 return $this->calculateReachedPointsForSolution($previewSession->getParticipantsSolution());
3366 }
3367
3369 {
3370 $reachedPoints = $this->calculateReachedPointsFromPreviewSession($previewSession);
3371
3372 if( $reachedPoints < $this->getMaximumPoints() )
3373 {
3374 return false;
3375 }
3376
3377 return true;
3378 }
3379
3380
3391 final public function adjustReachedPointsByScoringOptions($points, $active_id, $pass = NULL)
3392 {
3393 include_once "./Modules/Test/classes/class.ilObjTest.php";
3394 $count_system = ilObjTest::_getCountSystem($active_id);
3395 if ($count_system == 1)
3396 {
3397 if (abs($this->getMaximumPoints() - $points) > 0.0000000001)
3398 {
3399 $points = 0;
3400 }
3401 }
3402 $score_cutting = ilObjTest::_getScoreCutting($active_id);
3403 if ($score_cutting == 0)
3404 {
3405 if ($points < 0)
3406 {
3407 $points = 0;
3408 }
3409 }
3410 return $points;
3411 }
3412
3421 public static function _isWorkedThrough($active_id, $question_id, $pass = NULL)
3422 {
3423 return self::lookupResultRecordExist($active_id, $question_id, $pass);
3424
3425 // oldschool "workedthru"
3426
3427 global $ilDB;
3428
3429 $points = 0;
3430 if (is_null($pass))
3431 {
3432 include_once "./Modules/TestQuestionPool/classes/class.assQuestion.php";
3433 $pass = assQuestion::_getSolutionMaxPass($question_id, $active_id);
3434 }
3435 $result = $ilDB->queryF("SELECT solution_id FROM tst_solutions WHERE active_fi = %s AND question_fi = %s AND pass = %s",
3436 array('integer','integer','integer'),
3437 array($active_id, $question_id, $pass)
3438 );
3439 if ($result->numRows())
3440 {
3441 return TRUE;
3442 }
3443 else
3444 {
3445 return FALSE;
3446 }
3447 }
3448
3456 public static function _areAnswered($a_user_id,$a_question_ids)
3457 {
3458 global $ilDB;
3459
3460 $res = $ilDB->queryF("SELECT DISTINCT(question_fi) FROM tst_test_result JOIN tst_active ".
3461 "ON (active_id = active_fi) ".
3462 "WHERE " . $ilDB->in('question_fi', $a_question_ids, false, 'integer') .
3463 " AND user_fi = %s",
3464 array('integer'),
3465 array($a_user_id)
3466 );
3467 return ($res->numRows() == count($a_question_ids)) ? true : false;
3468 }
3469
3478 function isHTML($a_text)
3479 {
3480 return ilUtil::isHTML($a_text);
3481 }
3482
3489 function prepareTextareaOutput($txt_output, $prepare_for_latex_output = FALSE, $omitNl2BrWhenTextArea = false)
3490 {
3491 include_once "./Services/Utilities/classes/class.ilUtil.php";
3492 return ilUtil::prepareTextareaOutput($txt_output, $prepare_for_latex_output, $omitNl2BrWhenTextArea);
3493 }
3494
3502 function QTIMaterialToString($a_material)
3503 {
3504 $result = "";
3505 for ($i = 0; $i < $a_material->getMaterialCount(); $i++)
3506 {
3507 $material = $a_material->getMaterial($i);
3508 if (strcmp($material["type"], "mattext") == 0)
3509 {
3510 $result .= $material["material"]->getContent();
3511 }
3512 if (strcmp($material["type"], "matimage") == 0)
3513 {
3514 $matimage = $material["material"];
3515 if (preg_match("/(il_([0-9]+)_mob_([0-9]+))/", $matimage->getLabel(), $matches))
3516 {
3517 // import an mediaobject which was inserted using tiny mce
3518 if (!is_array($_SESSION["import_mob_xhtml"])) $_SESSION["import_mob_xhtml"] = array();
3519 array_push($_SESSION["import_mob_xhtml"], array("mob" => $matimage->getLabel(), "uri" => $matimage->getUri()));
3520 }
3521 }
3522 }
3523 return $result;
3524 }
3525
3534 function addQTIMaterial(&$a_xml_writer, $a_material, $close_material_tag = TRUE, $add_mobs = TRUE)
3535 {
3536 include_once "./Services/RTE/classes/class.ilRTE.php";
3537 include_once("./Services/MediaObjects/classes/class.ilObjMediaObject.php");
3538
3539 $a_xml_writer->xmlStartTag("material");
3540 $attrs = array(
3541 "texttype" => "text/plain"
3542 );
3543 if ($this->isHTML($a_material))
3544 {
3545 $attrs["texttype"] = "text/xhtml";
3546 }
3547 $a_xml_writer->xmlElement("mattext", $attrs, ilRTE::_replaceMediaObjectImageSrc($a_material, 0));
3548 if ($add_mobs)
3549 {
3550 $mobs = ilObjMediaObject::_getMobsOfObject("qpl:html", $this->getId());
3551 foreach ($mobs as $mob)
3552 {
3553 $moblabel = "il_" . IL_INST_ID . "_mob_" . $mob;
3554 if (strpos($a_material, "mm_$mob") !== FALSE)
3555 {
3556 if (ilObjMediaObject::_exists($mob))
3557 {
3558 $mob_obj =& new ilObjMediaObject($mob);
3559 $imgattrs = array(
3560 "label" => $moblabel,
3561 "uri" => "objects/" . "il_" . IL_INST_ID . "_mob_" . $mob . "/" . $mob_obj->getTitle()
3562 );
3563 }
3564 $a_xml_writer->xmlElement("matimage", $imgattrs, NULL);
3565 }
3566 }
3567 }
3568 if ($close_material_tag) $a_xml_writer->xmlEndTag("material");
3569 }
3570
3571 function createNewImageFileName($image_filename, $unique = false)
3572 {
3573 $extension = "";
3574
3575 if (preg_match("/.*\.(png|jpg|gif|jpeg)$/i", $image_filename, $matches))
3576 {
3577 $extension = "." . $matches[1];
3578 }
3579
3580 if($unique)
3581 {
3582 $image_filename = uniqid($image_filename.microtime(true));
3583 }
3584
3585 $image_filename = md5($image_filename) . $extension;
3586
3587 return $image_filename;
3588 }
3589
3600 function _setReachedPoints($active_id, $question_id, $points, $maxpoints, $pass, $manualscoring, $obligationsEnabled)
3601 {
3602 global $ilDB;
3603
3604 if ($points <= $maxpoints)
3605 {
3606 if (is_null($pass))
3607 {
3608 $pass = assQuestion::_getSolutionMaxPass($question_id, $active_id);
3609 }
3610
3611 // retrieve the already given points
3612 $old_points = 0;
3613 $result = $ilDB->queryF("SELECT points FROM tst_test_result WHERE active_fi = %s AND question_fi = %s AND pass = %s",
3614 array('integer','integer','integer'),
3615 array($active_id, $question_id, $pass)
3616 );
3617 $manual = ($manualscoring) ? 1 : 0;
3618 $rowsnum = $result->numRows();
3619 if($rowsnum)
3620 {
3621 $row = $ilDB->fetchAssoc($result);
3622 $old_points = $row["points"];
3623 if($old_points != $points)
3624 {
3625 $affectedRows = $ilDB->manipulateF("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 }
3631 else
3632 {
3633 $next_id = $ilDB->nextId('tst_test_result');
3634 $affectedRows = $ilDB->manipulateF("INSERT INTO tst_test_result (test_result_id, active_fi, question_fi, points, pass, manual, tstamp) VALUES (%s, %s, %s, %s, %s, %s, %s)",
3635 array('integer', 'integer','integer', 'float', 'integer', 'integer','integer'),
3636 array($next_id, $active_id, $question_id, $points, $pass, $manual, time())
3637 );
3638 }
3639
3640 if(self::isForcePassResultUpdateEnabled() || $old_points != $points || !$rowsnum)
3641 {
3642 assQuestion::_updateTestPassResults($active_id, $pass, $obligationsEnabled);
3643 // finally update objective result
3644 include_once "./Modules/Test/classes/class.ilObjTest.php";
3645 include_once './Modules/Course/classes/class.ilCourseObjectiveResult.php';
3647
3648 include_once ("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
3650 {
3651 global $lng, $ilUser;
3652 include_once "./Modules/Test/classes/class.ilObjTestAccess.php";
3653 $username = ilObjTestAccess::_getParticipantData($active_id);
3654 assQuestion::_logAction(sprintf($lng->txtlng("assessment", "log_answer_changed_points", ilObjAssessmentFolder::_getLogLanguage()), $username, $old_points, $points, $ilUser->getFullname() . " (" . $ilUser->getLogin() . ")"), $active_id, $question_id);
3655 }
3656 }
3657
3658 return TRUE;
3659 }
3660 else
3661 {
3662 return FALSE;
3663 }
3664 }
3665
3673 function getQuestion()
3674 {
3675 return $this->question;
3676 }
3677
3685 function setQuestion($question = "")
3686 {
3687 $this->question = $question;
3688 }
3689
3695 abstract public function getQuestionType();
3696
3706 {
3707 global $ilDB;
3708
3709 $result = $ilDB->queryF("SELECT question_type_id FROM qpl_qst_type WHERE type_tag = %s",
3710 array('text'),
3711 array($this->getQuestionType())
3712 );
3713 if ($result->numRows() == 1)
3714 {
3715 $row = $ilDB->fetchAssoc($result);
3716 return $row["question_type_id"];
3717 }
3718 return 0;
3719 }
3720
3721 public function syncHints()
3722 {
3723 global $ilDB;
3724
3725 // delete hints of the original
3726 $ilDB->manipulateF("DELETE FROM qpl_hints WHERE qht_question_fi = %s",
3727 array('integer'),
3728 array($this->original_id)
3729 );
3730
3731 // get hints of the actual question
3732 $result = $ilDB->queryF("SELECT * FROM qpl_hints WHERE qht_question_fi = %s",
3733 array('integer'),
3734 array($this->getId())
3735 );
3736
3737 // save hints to the original
3738 if ($result->numRows())
3739 {
3740 while ($row = $ilDB->fetchAssoc($result))
3741 {
3742 $next_id = $ilDB->nextId('qpl_hints');
3744 $ilDB->insert('qpl_hints', array(
3745 'qht_hint_id' => array('integer', $next_id),
3746 'qht_question_fi' => array('integer', $this->original_id),
3747 'qht_hint_index' => array('integer', $row["qht_hint_index"]),
3748 'qht_hint_points' => array('integer', $row["qht_hint_points"]),
3749 'qht_hint_text' => array('text', $row["qht_hint_text"]),
3750 )
3751 );
3752 }
3753 }
3754 }
3755
3760 protected function getRTETextWithMediaObjects()
3761 {
3762 // must be called in parent classes. add additional RTE text in the parent
3763 // classes and call this method to add the standard RTE text
3764 $collected = $this->getQuestion();
3765 $collected .= $this->feedbackOBJ->getGenericFeedbackContent($this->getId(), false);
3766 $collected .= $this->feedbackOBJ->getGenericFeedbackContent($this->getId(), true);
3767 $collected .= $this->feedbackOBJ->getAllSpecificAnswerFeedbackContents($this->getId());
3768
3769 foreach ($this->suggested_solutions as $solution_array)
3770 {
3771 $collected .= $solution_array["value"];
3772 }
3773
3774 require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionHintList.php';
3775 $questionHintList = ilAssQuestionHintList::getListByQuestionId($this->getId());
3776 foreach($questionHintList as $questionHint)
3777 {
3778 /* @var $questionHint ilAssQuestionHint */
3779 $collected .= $questionHint->getText();
3780 }
3781
3782 return $collected;
3783 }
3784
3790 {
3791 $combinedtext = $this->getRTETextWithMediaObjects();
3792 include_once("./Services/RTE/classes/class.ilRTE.php");
3793 ilRTE::_cleanupMediaObjectUsage($combinedtext, "qpl:html", $this->getId());
3794 }
3795
3801 function &getInstances()
3802 {
3803 global $ilDB;
3804
3805 $result = $ilDB->queryF("SELECT question_id FROM qpl_questions WHERE original_id = %s",
3806 array("integer"),
3807 array($this->getId())
3808 );
3809 $instances = array();
3810 $ids = array();
3811 while ($row = $ilDB->fetchAssoc($result))
3812 {
3813 array_push($ids, $row["question_id"]);
3814 }
3815 foreach ($ids as $question_id)
3816 {
3817 // check non random tests
3818 $result = $ilDB->queryF("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",
3819 array("integer"),
3820 array($question_id)
3821 );
3822 while ($row = $ilDB->fetchAssoc($result))
3823 {
3824 $instances[$row['obj_fi']] = ilObject::_lookupTitle($row['obj_fi']);
3825 }
3826 // check random tests
3827 $result = $ilDB->queryF("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",
3828 array("integer"),
3829 array($question_id)
3830 );
3831 while ($row = $ilDB->fetchAssoc($result))
3832 {
3833 $instances[$row['obj_fi']] = ilObject::_lookupTitle($row['obj_fi']);
3834 }
3835 }
3836 include_once "./Modules/Test/classes/class.ilObjTest.php";
3837 foreach ($instances as $key => $value)
3838 {
3839 $instances[$key] = array("obj_id" => $key, "title" => $value, "author" => ilObjTest::_lookupAuthor($key), "refs" => ilObject::_getAllReferences($key));
3840 }
3841 return $instances;
3842 }
3843
3844 function _needsManualScoring($question_id)
3845 {
3846 include_once "./Modules/Test/classes/class.ilObjAssessmentFolder.php";
3848 $questiontype = assQuestion::_getQuestionType($question_id);
3849 if (in_array($questiontype, $scoring))
3850 {
3851 return TRUE;
3852 }
3853 else
3854 {
3855 return FALSE;
3856 }
3857 }
3858
3866 function getActiveUserData($active_id)
3867 {
3868 global $ilDB;
3869 $result = $ilDB->queryF("SELECT * FROM tst_active WHERE active_id = %s",
3870 array('integer'),
3871 array($active_id)
3872 );
3873 if ($result->numRows())
3874 {
3875 $row = $ilDB->fetchAssoc($result);
3876 return array("user_id" => $row["user_fi"], "test_id" => $row["test_fi"]);
3877 }
3878 else
3879 {
3880 return array();
3881 }
3882 }
3883
3891 static function _includeClass($question_type, $gui = 0)
3892 {
3893 if( self::isCoreQuestionType($question_type) )
3894 {
3895 self::includeCoreClass($question_type, $gui);
3896 }
3897 else
3898 {
3899 self::includePluginClass($question_type, $gui);
3900 }
3901 }
3902
3903 public static function getGuiClassNameByQuestionType($questionType)
3904 {
3905 return $questionType.'GUI';
3906 }
3907
3908 public static function getObjectClassNameByQuestionType($questionType)
3909 {
3910 return $questionType;
3911 }
3912
3913 public static function getFeedbackClassNameByQuestionType($questionType)
3914 {
3915 return str_replace('ass', 'ilAss', $questionType).'Feedback';
3916 }
3917
3918 public static function isCoreQuestionType($questionType)
3919 {
3920 $guiClassName = self::getGuiClassNameByQuestionType($questionType);
3921 return file_exists("Modules/TestQuestionPool/classes/class.{$guiClassName}.php");
3922 }
3923
3924 public static function includeCoreClass($questionType, $withGuiClass)
3925 {
3926 if( $withGuiClass )
3927 {
3928 $guiClassName = self::getGuiClassNameByQuestionType($questionType);
3929 require_once "Modules/TestQuestionPool/classes/class.{$guiClassName}.php";
3930
3931 // object class is included by gui classes constructor
3932 }
3933 else
3934 {
3935 $objectClassName = self::getObjectClassNameByQuestionType($questionType);
3936 require_once "Modules/TestQuestionPool/classes/class.{$objectClassName}.php";
3937 }
3938
3939 $feedbackClassName = self::getFeedbackClassNameByQuestionType($questionType);
3940 require_once "Modules/TestQuestionPool/classes/feedback/class.{$feedbackClassName}.php";
3941 }
3942
3943 public static function includePluginClass($questionType, $withGuiClass)
3944 {
3945 global $ilPluginAdmin;
3946
3947 $classes = array(
3948 self::getObjectClassNameByQuestionType($questionType),
3949 self::getFeedbackClassNameByQuestionType($questionType)
3950 );
3951
3952 if( $withGuiClass )
3953 {
3954 $classes[] = self::getGuiClassNameByQuestionType($questionType);
3955 }
3956
3957 $pl_names = $ilPluginAdmin->getActivePluginsForSlot(IL_COMP_MODULE, "TestQuestionPool", "qst");
3958 foreach ($pl_names as $pl_name)
3959 {
3960 $pl = ilPlugin::getPluginObject(IL_COMP_MODULE, "TestQuestionPool", "qst", $pl_name);
3961 if (strcmp($pl->getQuestionType(), $questionType) == 0)
3962 {
3963 foreach($classes as $class)
3964 {
3965 $pl->includeClass("class.{$class}.php");
3966 }
3967
3968 break;
3969 }
3970 }
3971 }
3972
3979 static function _getQuestionTypeName($type_tag)
3980 {
3981 if (file_exists("./Modules/TestQuestionPool/classes/class.".$type_tag.".php"))
3982 {
3983 global $lng;
3984 return $lng->txt($type_tag);
3985 }
3986 else
3987 {
3988 global $ilPluginAdmin;
3989 $pl_names = $ilPluginAdmin->getActivePluginsForSlot(IL_COMP_MODULE, "TestQuestionPool", "qst");
3990 foreach ($pl_names as $pl_name)
3991 {
3992 $pl = ilPlugin::getPluginObject(IL_COMP_MODULE, "TestQuestionPool", "qst", $pl_name);
3993 if (strcmp($pl->getQuestionType(), $type_tag) == 0)
3994 {
3995 return $pl->getQuestionTypeTranslation();
3996 }
3997 }
3998 }
3999 return "";
4000 }
4001
4011 public static function &_instanciateQuestionGUI($question_id)
4012 {
4013 return self::instantiateQuestionGUI($question_id);
4014 }
4015
4023 public static function instantiateQuestionGUI($a_question_id)
4024 {
4025 global $ilCtrl, $ilDB, $lng, $ilUser;
4026
4027 if (strcmp($a_question_id, "") != 0)
4028 {
4029 $question_type = assQuestion::_getQuestionType($a_question_id);
4030
4031 assQuestion::_includeClass($question_type, 1);
4032
4033 $question_type_gui = self::getGuiClassNameByQuestionType($question_type);
4034 $question_gui = new $question_type_gui();
4035 $question_gui->object->loadFromDb($a_question_id);
4036
4037 $feedbackObjectClassname = self::getFeedbackClassNameByQuestionType($question_type);
4038 $question_gui->object->feedbackOBJ = new $feedbackObjectClassname($question_gui->object, $ilCtrl, $ilDB, $lng);
4039
4040 $assSettings = new ilSetting('assessment');
4041 require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionProcessLockerFactory.php';
4042 $processLockerFactory = new ilAssQuestionProcessLockerFactory($assSettings, $ilDB);
4043 $processLockerFactory->setQuestionId($question_gui->object->getId());
4044 $processLockerFactory->setUserId($ilUser->getId());
4045 include_once ("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
4046 $processLockerFactory->setAssessmentLogEnabled(ilObjAssessmentFolder::_enabledAssessmentLogging());
4047 $question_gui->object->setProcessLocker($processLockerFactory->getLocker());
4048 }
4049 else
4050 {
4051 global $ilLog;
4052 $ilLog->write('Instantiate question called without question id. (instantiateQuestionGUI@assQuestion)', $ilLog->WARNING);
4053 return null;
4054 }
4055 return $question_gui;
4056 }
4057
4070 public function setExportDetailsXLS(&$worksheet, $startrow, $active_id, $pass, &$format_title, &$format_bold)
4071 {
4072 return $startrow;
4073 }
4074
4078 public function __get($value)
4079 {
4080 switch ($value)
4081 {
4082 case "id":
4083 return $this->getId();
4084 break;
4085 case "title":
4086 return $this->getTitle();
4087 break;
4088 case "comment":
4089 return $this->getComment();
4090 break;
4091 case "owner":
4092 return $this->getOwner();
4093 break;
4094 case "author":
4095 return $this->getAuthor();
4096 break;
4097 case "question":
4098 return $this->getQuestion();
4099 break;
4100 case "points":
4101 return $this->getPoints();
4102 break;
4103 case "est_working_time":
4104 return $this->getEstimatedWorkingTime();
4105 break;
4106 case "shuffle":
4107 return $this->getShuffle();
4108 break;
4109 case "test_id":
4110 return $this->getTestId();
4111 break;
4112 case "obj_id":
4113 return $this->getObjId();
4114 break;
4115 case "ilias":
4116 return $this->ilias;
4117 break;
4118 case "tpl":
4119 return $this->tpl;
4120 break;
4121 case "page":
4122 return $this->page;
4123 break;
4124 case "outputType":
4125 return $this->getOutputType();
4126 break;
4127 case "suggested_solutions":
4128 return $this->getSuggestedSolutions();
4129 break;
4130 case "original_id":
4131 return $this->getOriginalId();
4132 break;
4133 default:
4134 if (array_key_exists($value, $this->arrData))
4135 {
4136 return $this->arrData[$value];
4137 }
4138 else
4139 {
4140 return null;
4141 }
4142 break;
4143 }
4144 }
4145
4149 public function __set($key, $value)
4150 {
4151 switch ($key)
4152 {
4153 case "id":
4154 $this->setId($value);
4155 break;
4156 case "title":
4157 $this->setTitle($value);
4158 break;
4159 case "comment":
4160 $this->setComment($value);
4161 break;
4162 case "owner":
4163 $this->setOwner($value);
4164 break;
4165 case "author":
4166 $this->setAuthor($value);
4167 break;
4168 case "question":
4169 $this->setQuestion($value);
4170 break;
4171 case "points":
4172 $this->setPoints($value);
4173 break;
4174 case "est_working_time":
4175 if (is_array($value))
4176 {
4177 $this->setEstimatedWorkingTime($value["h"], $value["m"], $value["s"]);
4178 }
4179 break;
4180 case "shuffle":
4181 $this->setShuffle($value);
4182 break;
4183 case "test_id":
4184 $this->setTestId($value);
4185 break;
4186 case "obj_id":
4187 $this->setObjId($value);
4188 break;
4189 case "outputType":
4190 $this->setOutputType($value);
4191 break;
4192 case "original_id":
4193 $this->setOriginalId($value);
4194 break;
4195 case "page":
4196 $this->page =& $value;
4197 break;
4198 default:
4199 $this->arrData[$key] = $value;
4200 break;
4201 }
4202 }
4203
4204 public function getNrOfTries()
4205 {
4206 return (int)$this->nr_of_tries;
4207 }
4208
4209 public function setNrOfTries($a_nr_of_tries)
4210 {
4211 $this->nr_of_tries = $a_nr_of_tries;
4212 }
4213
4214 public function setExportImagePath($a_path)
4215 {
4216 $this->export_image_path = (string)$a_path;
4217 }
4218
4219 function _questionExistsInTest($question_id, $test_id)
4220 {
4221 global $ilDB;
4222
4223 if ($question_id < 1)
4224 {
4225 return false;
4226 }
4227
4228 $result = $ilDB->queryF("SELECT question_fi FROM tst_test_question WHERE question_fi = %s AND test_fi = %s",
4229 array('integer', 'integer'),
4230 array($question_id, $test_id)
4231 );
4232 if ($result->numRows() == 1)
4233 {
4234 return true;
4235 }
4236 else
4237 {
4238 return false;
4239 }
4240 }
4241
4248 function formatSAQuestion($a_q)
4249 {
4250 return $this->getSelfAssessmentFormatter()->format($a_q);
4251 }
4252
4253 // scorm2004-start ???
4254
4260 function setPreventRteUsage($a_val)
4261 {
4262 $this->prevent_rte_usage = $a_val;
4263 }
4264
4271 {
4273 }
4274
4279 {
4280 $this->lmMigrateQuestionTypeGenericContent($migrator);
4281 $this->lmMigrateQuestionTypeSpecificContent($migrator);
4282 $this->saveToDb();
4283
4284 $this->feedbackOBJ->migrateContentForLearningModule($migrator, $this->getId());
4285 }
4286
4291 {
4292 $this->setQuestion( $migrator->migrateToLmContent( $this->getQuestion() ) );
4293 }
4294
4299 {
4300 // overwrite if any question type specific content except feedback needs to be migrated
4301 }
4302
4308 function setSelfAssessmentEditingMode($a_selfassessmenteditingmode)
4309 {
4310 $this->selfassessmenteditingmode = $a_selfassessmenteditingmode;
4311 }
4312
4319 {
4321 }
4322
4328 function setDefaultNrOfTries($a_defaultnroftries)
4329 {
4330 $this->defaultnroftries = $a_defaultnroftries;
4331 }
4332
4339 {
4340 return (int)$this->defaultnroftries;
4341 }
4342
4343 // scorm2004-end ???
4344
4350 public static function lookupParentObjId($questionId)
4351 {
4352 global $ilDB;
4353
4354 $query = "SELECT obj_fi FROM qpl_questions WHERE question_id = %s";
4355
4356 $res = $ilDB->queryF($query, array('integer'), array((int)$questionId));
4357 $row = $ilDB->fetchAssoc($res);
4358
4359 return $row['obj_fi'];
4360 }
4361
4372 public static function lookupOriginalParentObjId($originalQuestionId)
4373 {
4374 return self::lookupParentObjId($originalQuestionId);
4375 }
4376
4377 protected function duplicateQuestionHints($originalQuestionId, $duplicateQuestionId)
4378 {
4379 require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionHintList.php';
4380 $hintIds = ilAssQuestionHintList::duplicateListForQuestion($originalQuestionId, $duplicateQuestionId);
4381
4383 {
4384 require_once 'Modules/TestQuestionPool/classes/class.ilAssHintPage.php';
4385
4386 foreach($hintIds as $originalHintId => $duplicateHintId)
4387 {
4388 $originalPageObject = new ilAssHintPage($originalHintId);
4389 $originalXML = $originalPageObject->getXMLContent();
4390
4391 $duplicatePageObject = new ilAssHintPage();
4392 $duplicatePageObject->setId($duplicateHintId);
4393 $duplicatePageObject->setParentId($this->getId());
4394 $duplicatePageObject->setXMLContent($originalXML);
4395 $duplicatePageObject->createFromXML();
4396 }
4397 }
4398 }
4399
4400 protected function duplicateSkillAssignments($srcParentId, $srcQuestionId, $trgParentId, $trgQuestionId)
4401 {
4402 global $ilDB;
4403
4404 require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionSkillAssignmentList.php';
4405 $assignmentList = new ilAssQuestionSkillAssignmentList($ilDB);
4406 $assignmentList->setParentObjId($srcParentId);
4407 $assignmentList->setQuestionIdFilter($srcQuestionId);
4408 $assignmentList->loadFromDb();
4409
4410 foreach($assignmentList->getAssignmentsByQuestionId($srcQuestionId) as $assignment)
4411 {
4412 /* @var ilAssQuestionSkillAssignment $assignment */
4413
4414 $assignment->setParentObjId($trgParentId);
4415 $assignment->setQuestionId($trgQuestionId);
4416 $assignment->saveToDb();
4417 }
4418 }
4419
4420 public function syncSkillAssignments($srcParentId, $srcQuestionId, $trgParentId, $trgQuestionId)
4421 {
4422 global $ilDB;
4423
4424 require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionSkillAssignmentList.php';
4425 $assignmentList = new ilAssQuestionSkillAssignmentList($ilDB);
4426 $assignmentList->setParentObjId($trgParentId);
4427 $assignmentList->setQuestionIdFilter($trgQuestionId);
4428 $assignmentList->loadFromDb();
4429
4430 foreach($assignmentList->getAssignmentsByQuestionId($trgQuestionId) as $assignment)
4431 {
4432 /* @var ilAssQuestionSkillAssignment $assignment */
4433
4434 $assignment->deleteFromDb();
4435 }
4436
4437 $this->duplicateSkillAssignments($srcParentId, $srcQuestionId, $trgParentId, $trgQuestionId);
4438 }
4439
4452 public function isAnswered($active_id, $pass = null)
4453 {
4454 return true;
4455 }
4456
4469 public static function isObligationPossible($questionId)
4470 {
4471 return false;
4472 }
4473
4474 public function isAutosaveable()
4475 {
4476 return TRUE;
4477 }
4478
4491 protected static function getNumExistingSolutionRecords($activeId, $pass, $questionId)
4492 {
4493 global $ilDB;
4494
4495 $query = "
4496 SELECT count(active_fi) cnt
4497
4498 FROM tst_solutions
4499
4500 WHERE active_fi = %s
4501 AND question_fi = %s
4502 AND pass = %s
4503 ";
4504
4505 $res = $ilDB->queryF(
4506 $query, array('integer','integer','integer'),
4507 array($activeId, $questionId, $pass)
4508 );
4509
4510 $row = $ilDB->fetchAssoc($res);
4511
4512 return (int)$row['cnt'];
4513 }
4514
4522 {
4524 }
4525
4533 {
4535 {
4536 require_once 'Modules/TestQuestionPool/exceptions/class.ilTestQuestionPoolException.php';
4537 throw new ilTestQuestionPoolException('invalid additional content editing mode given: '.$additinalContentEditingMode);
4538 }
4539
4540 $this->additinalContentEditingMode = $additinalContentEditingMode;
4541 }
4542
4550 {
4552 }
4553
4561 public function isValidAdditionalContentEditingMode($additionalContentEditingMode)
4562 {
4563 if( in_array($additionalContentEditingMode, $this->getValidAdditionalContentEditingModes()) )
4564 {
4565 return true;
4566 }
4567
4568 return false;
4569 }
4570
4578 {
4579 return array(
4580 self::ADDITIONAL_CONTENT_EDITING_MODE_DEFAULT,
4581 self::ADDITIONAL_CONTENT_EDITING_MODE_PAGE_OBJECT
4582 );
4583 }
4584
4589 {
4590 $this->questionChangeListeners[] = $listener;
4591 }
4592
4597 {
4599 }
4600
4601 private function notifyQuestionCreated()
4602 {
4603 foreach($this->getQuestionChangeListeners() as $listener)
4604 {
4605 $listener->notifyQuestionCreated($this);
4606 }
4607 }
4608
4609 private function notifyQuestionEdited()
4610 {
4611 foreach($this->getQuestionChangeListeners() as $listener)
4612 {
4613 $listener->notifyQuestionEdited($this);
4614 }
4615 }
4616
4617 private function notifyQuestionDeleted()
4618 {
4619 foreach($this->getQuestionChangeListeners() as $listener)
4620 {
4621 $listener->notifyQuestionDeleted($this);
4622 }
4623 }
4624
4629 {
4630 require_once 'Services/Html/classes/class.ilHtmlPurifierFactory.php';
4631 return ilHtmlPurifierFactory::_getInstanceByType('qpl_usersolution');
4632 }
4633
4638 {
4639 require_once 'Services/Html/classes/class.ilHtmlPurifierFactory.php';
4640 return ilHtmlPurifierFactory::_getInstanceByType('qpl_usersolution');
4641 }
4642
4643 protected function buildQuestionDataQuery()
4644 {
4645 return "
4646 SELECT qpl_questions.*,
4647 {$this->getAdditionalTableName()}.*
4648 FROM qpl_questions
4649 LEFT JOIN {$this->getAdditionalTableName()}
4650 ON {$this->getAdditionalTableName()}.question_fi = qpl_questions.question_id
4651 WHERE qpl_questions.question_id = %s
4652 ";
4653 }
4654
4656 {
4657 $this->lastChange = $lastChange;
4658 }
4659
4660 public function getLastChange()
4661 {
4662 return $this->lastChange;
4663 }
4664
4675 protected function getCurrentSolutionResultSet($active_id, $pass, $authorized = true)
4676 {
4677 global $ilDB;
4678
4679 if($this->getStep() !== NULL)
4680 {
4681 $query = "
4682 SELECT *
4683 FROM tst_solutions
4684 WHERE active_fi = %s
4685 AND question_fi = %s
4686 AND pass = %s
4687 AND step = %s
4688 AND authorized = %s
4689 ";
4690
4691 return $ilDB->queryF($query, array('integer', 'integer', 'integer', 'integer', 'integer'),
4692 array($active_id, $this->getId(), $pass, $this->getStep(), (int)$authorized)
4693 );
4694 }
4695 else
4696 {
4697 $query = "
4698 SELECT *
4699 FROM tst_solutions
4700 WHERE active_fi = %s
4701 AND question_fi = %s
4702 AND pass = %s
4703 AND authorized = %s
4704 ";
4705
4706 return $ilDB->queryF($query, array('integer', 'integer', 'integer', 'integer'),
4707 array($active_id, $this->getId(), $pass, (int)$authorized)
4708 );
4709 }
4710
4711 }
4712
4719 protected function removeSolutionRecordById($solutionId)
4720 {
4721 global $ilDB;
4722
4723 return $ilDB->manipulateF("DELETE FROM tst_solutions WHERE solution_id = %s",
4724 array('integer'), array($solutionId)
4725 );
4726 }
4727
4736 public function removeIntermediateSolution($active_id, $pass)
4737 {
4738 return $this->removeCurrentSolution($active_id, $pass, false);
4739 }
4740
4750 public function removeCurrentSolution($active_id, $pass, $authorized = true, $ignoredSolutionIds = array())
4751 {
4752 global $ilDB;
4753
4754 $not_in = '';
4755 if(count($ignoredSolutionIds) > 0)
4756 {
4757 $not_in = ' AND ' . $ilDB->in('solution_id', $ignoredSolutionIds, true, 'integer') . ' ';
4758 }
4759
4760 if($this->getStep() !== NULL)
4761 {
4762 $query = "
4763 DELETE FROM tst_solutions
4764 WHERE active_fi = %s
4765 AND question_fi = %s
4766 AND pass = %s
4767 AND step = %s
4768 AND authorized = %s
4769 $not_in
4770 ";
4771
4772 return $ilDB->manipulateF($query, array('integer', 'integer', 'integer', 'integer', 'integer'),
4773 array($active_id, $this->getId(), $pass, $this->getStep(), (int)$authorized)
4774 );
4775 }
4776 else
4777 {
4778 $query = "
4779 DELETE FROM tst_solutions
4780 WHERE active_fi = %s
4781 AND question_fi = %s
4782 AND pass = %s
4783 AND authorized = %s
4784 $not_in
4785 ";
4786
4787 return $ilDB->manipulateF($query, array('integer', 'integer', 'integer', 'integer'),
4788 array($active_id, $this->getId(), $pass, (int)$authorized)
4789 );
4790 }
4791 }
4792
4803 public function saveCurrentSolution($active_id, $pass, $value1, $value2, $authorized = true)
4804 {
4805 global $ilDB;
4806
4807 $next_id = $ilDB->nextId("tst_solutions");
4808
4809 $fieldData = array(
4810 "solution_id" => array("integer", $next_id),
4811 "active_fi" => array("integer", $active_id),
4812 "question_fi" => array("integer", $this->getId()),
4813 "value1" => array("clob", $value1),
4814 "value2" => array("clob", $value2),
4815 "pass" => array("integer", $pass),
4816 "tstamp" => array("integer", time()),
4817 'authorized' => array('integer', (int)$authorized)
4818 );
4819
4820 if( $this->getStep() !== null )
4821 {
4822 $fieldData['step'] = array("integer", $this->getStep());
4823 }
4824
4825 return $ilDB->insert("tst_solutions", $fieldData);
4826 }
4827
4838 public function updateCurrentSolution($solutionId, $value1, $value2, $authorized = true)
4839 {
4840 global $ilDB;
4841
4842 $fieldData = array(
4843 "value1" => array("clob", $value1),
4844 "value2" => array("clob", $value2),
4845 "tstamp" => array("integer", time()),
4846 'authorized' => array('integer', (int)$authorized)
4847 );
4848
4849 if( $this->getStep() !== null )
4850 {
4851 $fieldData['step'] = array("integer", $this->getStep());
4852 }
4853
4854 return $ilDB->update("tst_solutions", $fieldData, array(
4855 'solution_id' => array('integer', $solutionId)
4856 ));
4857 }
4858
4859 public function updateCurrentSolutionsAuthorization($activeId, $pass, $authorized)
4860 {
4861 global $ilDB;
4862
4863 $fieldData = array(
4864 'tstamp' => array('integer', time()),
4865 'authorized' => array('integer', (int)$authorized)
4866 );
4867
4868 $whereData = array(
4869 'question_fi' => array('integer', $this->getId()),
4870 'active_fi' => array('integer', $activeId),
4871 'pass' => array('integer', $pass)
4872 );
4873
4874 return $ilDB->update('tst_solutions', $fieldData, $whereData);
4875 }
4876
4877
4881 public static function setResultGateway($resultGateway)
4882 {
4883 self::$resultGateway = $resultGateway;
4884 }
4885
4889 public static function getResultGateway()
4890 {
4891 return self::$resultGateway;
4892 }
4893
4897 public function setStep($step)
4898 {
4899 $this->step = $step;
4900 }
4901
4905 public function getStep()
4906 {
4907 return $this->step;
4908 }
4909
4915 public static function sumTimesInISO8601FormatH_i_s_Extended($time1, $time2)
4916 {
4919 return gmdate('H:i:s', $time);
4920 }
4921
4927 {
4928 $sec = 0;
4929 $time_array = explode(':',$time);
4930 if( sizeof($time_array) == 3)
4931 {
4932 $sec += $time_array[0] * 3600;
4933 $sec += $time_array[1] * 60;
4934 $sec += $time_array[2];
4935 }
4936 return $sec;
4937 }
4938
4942 protected function getSelfAssessmentFormatter()
4943 {
4944 require_once 'Modules/TestQuestionPool/classes/questions/class.ilAssSelfAssessmentQuestionFormatter.php';
4945 return new \ilAssSelfAssessmentQuestionFormatter();
4946 }
4947
4948 public function toJSON()
4949 {
4950 return json_encode(array());
4951 }
4952
4953 abstract public function duplicate($for_test = true, $title = "", $author = "", $owner = "", $testObjId = null);
4954
4955 // hey: prevPassSolutions - check for authorized solution
4956 public function authorizedSolutionExists($active_id, $pass)
4957 {
4958 $solutionAvailability = $this->lookupForExistingSolutions($active_id, $pass);
4959 return (bool)$solutionAvailability['authorized'];
4960 }
4962 {
4963 $solutionAvailability = $this->lookupForExistingSolutions($active_id, $pass);
4964 return (bool)$solutionAvailability['authorized'] || (bool)$solutionAvailability['intermediate'];
4965 }
4966 // hey.
4967
4973 protected function lookupMaxStep($active_id, $pass)
4974 {
4976 global $ilDB;
4977
4978 $res = $ilDB->queryF(
4979 "SELECT MAX(step) max_step FROM tst_solutions WHERE active_fi = %s AND pass = %s AND question_fi = %s",
4980 array("integer", "integer", "integer"), array($active_id, $pass, $this->getId())
4981 );
4982
4983 $row = $ilDB->fetchAssoc($res);
4984
4985 $maxStep = $row['max_step'];
4986
4987 return $maxStep;
4988 }
4989
4990 // fau: testNav - new function lookupForExistingSolutions
4997 public function lookupForExistingSolutions($activeId, $pass)
4998 {
4999 global $ilDB;
5000
5001 $return = array(
5002 'authorized' => false,
5003 'intermediate' => false
5004 );
5005
5006 $query = "
5007 SELECT authorized, COUNT(*) cnt
5008 FROM tst_solutions
5009 WHERE active_fi = %s
5010 AND question_fi = %s
5011 AND pass = %s
5012 GROUP BY authorized
5013 ";
5014 $result = $ilDB->queryF($query, array('integer', 'integer', 'integer'), array($activeId, $this->getId(), $pass));
5015
5016 while ($row = $ilDB->fetchAssoc($result))
5017 {
5018 if ($row['authorized']) {
5019 $return['authorized'] = $row['cnt'] > 0;
5020 }
5021 else
5022 {
5023 $return['intermediate'] = $row['cnt'] > 0;
5024 }
5025 }
5026 return $return;
5027 }
5028 // fau.
5029
5030 public function removeExistingSolutions($activeId, $pass)
5031 {
5032 global $ilDB;
5033
5034 $query = "
5035 DELETE FROM tst_solutions
5036 WHERE active_fi = %s
5037 AND question_fi = %s
5038 AND pass = %s
5039 ";
5040
5041 return $ilDB->manipulateF($query, array('integer', 'integer', 'integer'),
5042 array($activeId, $this->getId(), $pass)
5043 );
5044 }
5045
5046 public function resetUsersAnswer($activeId, $pass)
5047 {
5048 $this->removeExistingSolutions($activeId, $pass);
5049 $this->removeResultRecord($activeId, $pass);
5050
5051 $this->_updateTestPassResults(
5052 $activeId, $pass, $this->areObligationsToBeConsidered(), $this->getProcessLocker(), $this->getTestId()
5053 );
5054 }
5055
5056 public function removeResultRecord($activeId, $pass)
5057 {
5058 global $ilDB;
5059
5060 $query = "
5061 DELETE FROM tst_test_result
5062 WHERE active_fi = %s
5063 AND question_fi = %s
5064 AND pass = %s
5065 ";
5066
5067 return $ilDB->manipulateF($query, array('integer', 'integer', 'integer'),
5068 array($activeId, $this->getId(), $pass)
5069 );
5070 }
5071
5072 public static function missingResultRecordExists($activeId, $pass, $questionIds)
5073 {
5074 global $ilDB;
5075
5076 $IN_questionIds = $ilDB->in('question_fi', $questionIds, false, 'integer');
5077
5078 $query = "
5079 SELECT COUNT(*) cnt
5080 FROM tst_test_result
5081 WHERE active_fi = %s
5082 AND pass = %s
5083 AND $IN_questionIds
5084 ";
5085
5086 $row = $ilDB->fetchAssoc($ilDB->queryF(
5087 $query, array('integer', 'integer'), array($activeId, $pass)
5088 ));
5089
5090 return $row['cnt'] < count($questionIds);
5091 }
5092
5093 public static function lookupResultRecordExist($activeId, $questionId, $pass)
5094 {
5095 global $ilDB;
5096
5097 $query = "
5098 SELECT COUNT(*) cnt
5099 FROM tst_test_result
5100 WHERE active_fi = %s
5101 AND question_fi = %s
5102 AND pass = %s
5103 ";
5104
5105 $row = $ilDB->fetchAssoc($ilDB->queryF($query, array('integer', 'integer', 'integer'), array($activeId, $questionId, $pass)));
5106
5107 return $row['cnt'] > 0;
5108 }
5109
5114 {
5116 }
5117
5122 {
5123 $this->obligationsToBeConsidered = $obligationsToBeConsidered;
5124 }
5125
5126 public function validateSolutionSubmit()
5127 {
5128 return true;
5129 }
5130
5131 public function updateTimestamp()
5132 {
5133 global $ilDB;
5134
5135 $ilDB->manipulateF("UPDATE qpl_questions SET tstamp = %s WHERE question_id = %s",
5136 array('integer', 'integer'),
5137 array(time(), $this->getId())
5138 );
5139 }
5140}
$result
print $file
$filename
Definition: buildRTE.php:89
$_GET["client_id"]
$_SESSION["AccountId"]
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
_getQuestionTitle($question_id)
Returns the question title of a question with a given id.
$export_image_path
(Web) Path to images
moveUploadedMediaFile($file, $name)
Move an uploaded media file to an public accessible temp dir to present it.
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)
static getFeedbackClassNameByQuestionType($questionType)
setPreventRteUsage($a_val)
Set prevent rte usage.
removeResultRecord($activeId, $pass)
static _getOriginalId($question_id)
Returns the original id of a question.
setExportDetailsXLS(&$worksheet, $startrow, $active_id, $pass, &$format_title, &$format_bold)
Creates an Excel worksheet for the detailed cumulated results of this question.
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.
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)
copySuggestedSolutionFiles($source_questionpool_id, $source_question_id)
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.
_getSolutionMaxPass($question_id, $active_id)
Returns the maximum pass a users question solution.
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.
isClone($question_id="")
Checks whether the question is a clone of another question or not.
static fetchMimeTypeIdentifier($contentTypeString)
fixUnavailableSkinImageSources($html)
adjustReachedPointsByScoringOptions($points, $active_id, $pass=NULL)
Adjust the given reached points by checks for all special scoring options in the test container.
deletePageOfQuestion($question_id)
Deletes the page object of a question with a given ID.
lmMigrateQuestionTypeGenericContent(ilAssSelfAssessmentMigrator $migrator)
_getMaximumPoints($question_id)
Returns the maximum points, a learner can reach answering the question.
duplicateSkillAssignments($srcParentId, $srcQuestionId, $trgParentId, $trgQuestionId)
savePreviewData(ilAssQuestionPreviewSession $previewSession)
Reworks the allready saved working data if neccessary.
isComplete()
Returns true, if a question is complete for use.
const ADDITIONAL_CONTENT_EDITING_MODE_DEFAULT
constant for additional content editing mode "default"
updateCurrentSolutionsAuthorization($activeId, $pass, $authorized)
static _getSuggestedSolutionCount($question_id)
Returns the number of suggested solutions associated with a question.
isInUse($question_id="")
Checks whether the question is in use or not.
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
createNewImageFileName($image_filename, $unique=false)
_questionExistsInTest($question_id, $test_id)
getUserSolutionPreferingIntermediate($active_id, $pass=NULL)
_getReachedPoints($active_id, $question_id, $pass=NULL)
Returns the points, a learner has reached answering the question.
getId()
Gets the id of the assQuestion object.
_getQuestionText($a_q_id)
Returns question text.
_updateTestResultCache($active_id, ilAssQuestionProcessLocker $processLocker=null)
@TODO Move this to a proper place.
lookupForExistingSolutions($activeId, $pass)
Lookup if an authorized or intermediate solution exists.
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.
persistWorkingState($active_id, $pass=NULL, $obligationsEnabled=false, $authorized=true)
persists the working state for current testactive and testpass
static _getQuestionTypeName($type_tag)
Return the translation for a given question type tag.
_getInternalLinkHref($target="")
getSuggestedSolutionPath()
Returns the path for a suggested solution.
removeExistingSolutions($activeId, $pass)
setTitle($title="")
Sets the title string of the assQuestion object.
getOwner()
Gets the creator/owner ID of the assQuestion object.
getAdditionalContentEditingMode()
getter for additional content editing mode for this question
isHTML($a_text)
Checks if a given string contains HTML or not.
setObligationsToBeConsidered($obligationsToBeConsidered)
persistPreviewState(ilAssQuestionPreviewSession $previewSession)
persists the preview state for current user and question
static $forcePassResultsUpdateEnabled
addQuestionChangeListener(ilQuestionChangeListener $listener)
calculateReachedPoints($active_id, $pass=NULL, $authorizedSolution=true, $returndetails=FALSE)
Returns the points, a learner has reached answering the question.
onCopy($sourceParentId, $sourceQuestionId, $targetParentId, $targetQuestionId)
Will be called when a question is copied (into another question pool)
authorizedOrIntermediateSolutionExists($active_id, $pass)
prepareTextareaOutput($txt_output, $prepare_for_latex_output=FALSE, $omitNl2BrWhenTextArea=false)
Prepares a string for a text area output in tests.
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.
getSuggestedSolutionTitle($subquestion_index=0)
Returns the title of a suggested solution at a given subquestion_index.
static getObjectClassNameByQuestionType($questionType)
setExportImagePath($a_path)
isValidAdditionalContentEditingMode($additionalContentEditingMode)
returns the fact wether the passed additional content mode is valid or not
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)
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.
getFlashPath()
Returns the image path for web accessable flash files of a question.
__get($value)
Object getter.
getImagePath($question_id=null, $object_id=null)
Returns the image path for web accessable images of a question.
getSuggestedSolution($subquestion_index=0)
Returns a suggested solution for a given subquestion index.
_isUsedInRandomTest($question_id="")
Checks whether the question is used in a random test or not.
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 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.
static isCoreQuestionType($questionType)
_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...
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.
setExternalId($external_id)
getValidAdditionalContentEditingModes()
getter for valid additional content editing modes
static _getSuggestedSolutionOutput($question_id)
Returns the output of the suggested solution.
_getTotalAnswers($a_q_id)
get number of answers for question id (static) note: do not use $this inside this method
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.
calculateResultsFromSolution($active_id, $pass=NULL, $obligationsEnabled=false)
Calculates the question results from a previously saved question solution.
removeIntermediateSolution($active_id, $pass)
_resolveIntLinks($question_id)
saveWorkingData($active_id, $pass=NULL, $authorized=true)
Saves the learners input of the question to the database.
getSelfAssessmentEditingMode()
Get Self-Assessment Editing Mode.
static $allowedCharsetsByMimeType
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)
static & _instanciateQuestionGUI($question_id)
Creates an instance of a question gui with a given question id.
getSolutionValues($active_id, $pass=NULL, $authorized=true)
Loads solutions of a given user from the database an returns it.
_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 ilDB $ilDB
static _getTotalRightAnswers($a_q_id)
get number of answers for question id (static) note: do not use $this inside this method
static $imageSourceFixReplaceMap
static getGuiClassNameByQuestionType($questionType)
& _getQuestionInfo($question_id)
Returns question information from the database.
_isWriteable($question_id, $user_id)
Returns true if the question is writeable by a certain user.
static getAllowedImageFileExtensions()
getDefaultNrOfTries()
Get Default Nr of Tries.
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.
logAction($logtext="", $active_id="", $question_id="")
Logs an action into the Test&Assessment log.
static includeCoreClass($questionType, $withGuiClass)
_needsManualScoring($question_id)
createPageObject()
create page object of question
static missingResultRecordExists($activeId, $pass, $questionIds)
copyXHTMLMediaObjectsOfQuestion($a_q_id)
const ADDITIONAL_CONTENT_EDITING_MODE_PAGE_OBJECT
constant for additional content editing mode "pageobject"
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 ...
getSuggestedSolutions()
Return the suggested solutions.
_logAction($logtext="", $active_id="", $question_id="")
Logs an action into the Test&Assessment log.
& _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.
removeCurrentSolution($active_id, $pass, $authorized=true, $ignoredSolutionIds=array())
fixSvgToPng($imageFilenameContainingString)
syncSkillAssignments($srcParentId, $srcQuestionId, $trgParentId, $trgQuestionId)
_getTitle($a_q_id)
Returns the title of a question.
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.
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.
getComment()
Gets the comment string of the assQuestion object.
setTestId($id=-1)
Sets the test id of the assQuestion object.
getAdjustedReachedPoints($active_id, $pass=NULL, $authorizedSolution=true)
returns the reached points ...
getAuthor()
Gets the authors name of the assQuestion object.
_getQuestionType($question_id)
Returns the question type of a question with a given id.
setNrOfTries($a_nr_of_tries)
__construct( $title="", $comment="", $author="", $owner=-1, $question="")
assQuestion constructor
getReachedPoints($active_id, $pass=NULL)
Returns the points, a learner has reached answering the question This is the fast way to get the poin...
$nr_of_tries
Number of tries.
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.
saveCurrentSolution($active_id, $pass, $value1, $value2, $authorized=true)
_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.
createRandomSolution($test_id, $user_id)
getEstimatedWorkingTime()
Gets the estimated working time of a question.
getQuestionTypeFromDb($question_id)
get question type for question id
getPreventRteUsage()
Get prevent rte usage.
deleteAnswers($question_id)
Deletes datasets from answers tables.
setDefaultNrOfTries($a_defaultnroftries)
Set Default Nr of Tries.
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
deleteRequestsByQuestionIds($questionIds)
Deletes all hint requests relating to a question included in given question ids.
Question page object.
_updateObjectiveResult($a_user_id, $a_active_id, $a_question_id)
static _getInstanceByType($a_type)
Factory method for creating purifier instances.
_getIdForImportId($a_import_id)
get current object id for import id (static)
_addLog($user_id, $object_id, $logtext, $question_id="", $original_id="", $test_only=FALSE, $test_ref_id=NULL)
Add an assessment log entry.
_getLogLanguage()
retrieve the log language for assessment logging
_getManualScoringTypes()
Retrieve the manual scoring settings as type strings.
_enabledAssessmentLogging()
check wether assessment logging is enabled or not
Class ilObjMediaObject.
_saveUsage($a_mob_id, $a_type, $a_id, $a_usage_hist_nr=0, $a_lang="-")
Save usage of mob within another container (e.g.
_getMobsOfObject($a_type, $a_id, $a_usage_hist_nr=0, $a_lang="-")
get mobs of object
static _exists($a_id)
checks wether a lm content object with specified id exists or not
_removeUsage($a_mob_id, $a_type, $a_id, $a_usage_hist_nr=0, $a_lang="-")
Remove usage of mob in another container.
_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.
_getParticipantData($active_id)
Retrieves a participant name from active id.
_getCountSystem($active_id)
Gets the count system for the calculation of points.
_getPass($active_id)
Retrieves the actual pass of a given user for a given test.
_getQuestionCountAndPointsForPassOfParticipant($active_id, $pass)
_lookupAuthor($obj_id)
Gets the authors name of the ilObjTest object.
static buildExamId($active_id, $pass, $test_obj_id=null)
_getObjectIDFromActiveID($active_id)
Returns the ILIAS test object id for a given active id.
static _getUserIdFromActiveId($active_id)
_getResultPass($active_id)
Retrieves the pass number that should be counted for a given user.
_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
_getWorkingTimeOfParticipantForPass($active_id, $pass)
Returns the complete working time in seconds for a test participant.
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...
_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.
static moveUploadedFile($a_file, $a_name, $a_target, $a_raise_errors=true, $a_mode="move_uploaded")
move uploaded file
static delDir($a_dir, $a_clean_only=false)
removes a dir and all its content (subdirs and files) recursively
static isHTML($a_text)
Checks if a given string contains HTML or not.
static prepareTextareaOutput($txt_output, $prepare_for_latex_output=FALSE, $omitNl2BrWhenTextArea=false)
Prepares a string for a text area output where latex code may be in it If the text is HTML-free,...
static 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
$_POST['username']
Definition: cron.php:12
$html
Definition: example_001.php:87
$data
$target_id
Definition: goto.php:88
global $ilCtrl
Definition: ilias.php:18
const OUTPUT_HTML
const OUTPUT_JAVASCRIPT
redirection script todo: (a better solution should control the processing via a xml file)
global $ilDB
$mobs
global $ilUser
Definition: imgupload.php:15
const ILIAS_ABSOLUTE_PATH