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