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