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