ILIAS  release_5-2 Revision v5.2.25-18-g3f80b828510
class.assQuestion.php
Go to the documentation of this file.
1 <?php
2 /* Copyright (c) 1998-2013 ILIAS open source, Extended GPL, see docs/LICENSE */
3 
4 include_once "./Modules/Test/classes/inc.AssessmentConstants.php";
5 
20 abstract class assQuestion
21 {
22  const IMG_MIME_TYPE_JPG = 'image/jpeg';
23  const IMG_MIME_TYPE_PNG = 'image/png';
24  const IMG_MIME_TYPE_GIF = 'image/gif';
25 
27  self::IMG_MIME_TYPE_JPG => array('jpg', 'jpeg'),
28  self::IMG_MIME_TYPE_PNG => array('png'),
29  self::IMG_MIME_TYPE_GIF => array('gif')
30  );
31 
32  protected static $allowedCharsetsByMimeType = array(
33  self::IMG_MIME_TYPE_JPG => array('binary'),
34  self::IMG_MIME_TYPE_PNG => array('binary'),
35  self::IMG_MIME_TYPE_GIF => array('binary')
36  );
37 
43  protected $id;
44 
50  protected $title;
51 
57  protected $comment;
58 
64  protected $owner;
65 
71  protected $author;
72 
78  protected $question;
79 
85  protected $points;
86 
92  protected $est_working_time;
93 
99  protected $shuffle;
100 
106  protected $test_id;
107 
113  protected $obj_id;
114 
120  protected $ilias;
121 
125  protected $tpl;
126 
130  protected $lng;
131 
135  protected $db;
136 
143 
150 
156  protected $original_id;
157 
163  protected $page;
164 
168  private $nr_of_tries;
169 
173  private $arrData;
174 
179 
184  protected $external_id = '';
185 
190 
195 
202 
208  public $feedbackOBJ = null;
209 
215  var $prevent_rte_usage = false;
216 
223 
230 
235 
239  protected $processLocker;
240 
241  public $questionActionCmd = 'handleQuestionAction';
242 
246  private static $resultGateway = null;
247 
251  protected $step = null;
252 
253  protected $lastChange;
254 
258  protected $shuffler;
259 
263  private $obligationsToBeConsidered = false;
264 
265 // fau: testNav - new variable $testQuestionConfig
270 // fau.
271 
273  'image/jpeg' => array('jpg', 'jpeg'), 'image/png' => array('png'), 'image/gif' => array('gif')
274  );
275 
286  function __construct(
287  $title = "",
288  $comment = "",
289  $author = "",
290  $owner = -1,
291  $question = ""
292  )
293  {
294  global $ilias, $lng, $tpl, $ilDB;
295 
296  $this->ilias = $ilias;
297  $this->lng = $lng;
298  $this->tpl = $tpl;
299  $this->db = $ilDB;
300 
301  $this->original_id = null;
302  $this->title = $title;
303  $this->comment = $comment;
304  $this->page = null;
305  $this->author = $author;
306  $this->setQuestion($question);
307  if (!$this->author)
308  {
309  $this->author = $this->ilias->account->fullname;
310  }
311  $this->owner = $owner;
312  if ($this->owner <= 0)
313  {
314  $this->owner = $this->ilias->account->id;
315  }
316  $this->id = -1;
317  $this->test_id = -1;
318  $this->suggested_solutions = array();
319  $this->shuffle = 1;
320  $this->nr_of_tries = 0;
321  $this->setEstimatedWorkingTime(0,1,0);
322  $this->arrData = array();
323  $this->setExternalId('');
324 
325  $this->questionActionCmd = 'handleQuestionAction';
326 
327  $this->lastChange = null;
328 
329  require_once 'Services/Randomization/classes/class.ilArrayElementOrderKeeper.php';
330  $this->shuffler = new ilArrayElementOrderKeeper();
331  }
332 
333  protected static $forcePassResultsUpdateEnabled = false;
334 
336  {
337  self::$forcePassResultsUpdateEnabled = $forcePassResultsUpdateEnabled;
338  }
339 
340  public static function isForcePassResultUpdateEnabled()
341  {
342  return self::$forcePassResultsUpdateEnabled;
343  }
344 
345  public static function isAllowedImageMimeType($mimeType)
346  {
347  return (bool)count(self::getAllowedFileExtensionsForMimeType($mimeType));
348  }
349 
350  public static function fetchMimeTypeIdentifier($contentTypeString)
351  {
352  return current(explode(';', $contentTypeString));
353  }
354 
355  public static function getAllowedFileExtensionsForMimeType($mimeType)
356  {
357  foreach(self::$allowedFileExtensionsByMimeType as $allowedMimeType => $extensions)
358  {
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  {
366  continue;
367  }
368 
369  return $extensions;
370  }
371 
372  return array();
373  }
374 
375  public static function isAllowedImageFileExtension($mimeType, $fileExtension)
376  {
377  return in_array(
378  strtolower($fileExtension), 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  {
391  return '';
392  }
393 
394  if( !is_array($_POST['cmd'][$this->questionActionCmd]) || !count($_POST['cmd'][$this->questionActionCmd]) )
395  {
396  return '';
397  }
398 
399  return key($_POST['cmd'][$this->questionActionCmd]);
400  }
401 
406  protected function isNonEmptyItemListPostSubmission($postSubmissionFieldname)
407  {
408  if( !isset($_POST[$postSubmissionFieldname]) )
409  {
410  return false;
411  }
412 
413  if( !is_array($_POST[$postSubmissionFieldname]) )
414  {
415  return false;
416  }
417 
418  if( !count($_POST[$postSubmissionFieldname]) )
419  {
420  return false;
421  }
422 
423  return true;
424  }
425 
431  protected function ensureCurrentTestPass($active_id, $pass)
432  {
433  if( is_integer($pass) && $pass >= 0 )
434  {
435  return $pass;
436  }
437 
438  return $this->lookupCurrentTestPass($active_id, $pass);
439  }
440 
446  protected function lookupCurrentTestPass($active_id, $pass)
447  {
448  require_once 'Modules/Test/classes/class.ilObjTest.php';
449  return ilObjTest::_getPass($active_id);
450  }
451 
456  protected function lookupTestId($active_id)
457  {
458  $ilDB = isset($GLOBALS['DIC']) ? $GLOBALS['DIC']['ilDB'] : $GLOBALS['ilDB'];
459 
460  $result = $ilDB->queryF("SELECT test_fi FROM tst_active WHERE active_id = %s",
461  array('integer'), array($active_id)
462  );
463 
464  while($row = $ilDB->fetchAssoc($result))
465  {
466  return $row["test_fi"];
467  }
468 
469  return null;
470  }
471  // hey.
472 
477  protected function log($active_id, $langVar)
478  {
480  {
481  $message = $this->lng->txtlng('assessment', $langVar, ilObjAssessmentFolder::_getLogLanguage());
482  assQuestion::logAction($message, $active_id, $this->getId());
483  }
484  }
485 
489  public static function getAllowedImageMaterialFileExtensions()
490  {
491  $extensions = array();
492 
493  foreach (self::$allowedImageMaterialFileExtensionsByMimeType as $mimeType => $mimeExtensions)
494  {
495  $extensions = array_merge($extensions, $mimeExtensions);
496  }
497  return array_unique($extensions);
498  }
499 
503  public function getShuffler()
504  {
505  return $this->shuffler;
506  }
507 
512  {
513  $this->shuffler = $shuffler;
514  }
515 
520  {
521  $this->processLocker = $processLocker;
522  }
523 
527  public function getProcessLocker()
528  {
529  return $this->processLocker;
530  }
531 
543  function fromXML(&$item, &$questionpool_id, &$tst_id, &$tst_object, &$question_counter, &$import_mapping)
544  {
545  include_once "./Modules/TestQuestionPool/classes/import/qti12/class." . $this->getQuestionType() . "Import.php";
546  $classname = $this->getQuestionType() . "Import";
547  $import = new $classname($this);
548  $import->fromXML($item, $questionpool_id, $tst_id, $tst_object, $question_counter, $import_mapping);
549  }
550 
557  function toXML($a_include_header = true, $a_include_binary = true, $a_shuffle = false, $test_output = false, $force_image_references = false)
558  {
559  include_once "./Modules/TestQuestionPool/classes/export/qti12/class." . $this->getQuestionType() . "Export.php";
560  $classname = $this->getQuestionType() . "Export";
561  $export = new $classname($this);
562  return $export->toXML($a_include_header, $a_include_binary, $a_shuffle, $test_output, $force_image_references);
563  }
564 
571  function isComplete()
572  {
573  return false;
574  }
575 
583  function questionTitleExists($questionpool_id, $title)
584  {
585  global $ilDB;
586 
587  $result = $ilDB->queryF("SELECT * FROM qpl_questions WHERE obj_fi = %s AND title = %s",
588  array('integer','text'),
589  array($questionpool_id, $title)
590  );
591  return ($result->numRows() > 0) ? TRUE : FALSE;
592  }
593 
601  function setTitle($title = "")
602  {
603  $this->title = $title;
604  }
605 
613  function setId($id = -1)
614  {
615  $this->id = $id;
616  }
617 
625  function setTestId($id = -1)
626  {
627  $this->test_id = $id;
628  }
629 
637  function setComment($comment = "")
638  {
639  $this->comment = $comment;
640  }
641 
650  {
651  $this->outputType = $outputType;
652  }
653 
654 
662  function setShuffle($shuffle = true)
663  {
664  if ($shuffle)
665  {
666  $this->shuffle = 1;
667  }
668  else
669  {
670  $this->shuffle = 0;
671  }
672  }
673 
684  function setEstimatedWorkingTime($hour=0, $min=0, $sec=0)
685  {
686  $this->est_working_time = array("h" => (int)$hour, "m" => (int)$min, "s" => (int)$sec);
687  }
688 
696  {
697  $this->est_working_time = array(
698  'h' => (int)substr($durationString, 0, 2),
699  'm' => (int)substr($durationString, 3, 2),
700  's' => (int)substr($durationString, 6, 2)
701  );
702  }
703 
711  function keyInArray($searchkey, $array)
712  {
713  if ($searchkey)
714  {
715  foreach ($array as $key => $value)
716  {
717  if (strcmp($key, $searchkey)==0)
718  {
719  return true;
720  }
721  }
722  }
723  return false;
724  }
725 
733  function setAuthor($author = "")
734  {
735  if (!$author)
736  {
737  $author = $this->ilias->account->fullname;
738  }
739  $this->author = $author;
740  }
741 
749  function setOwner($owner = "")
750  {
751  $this->owner = $owner;
752  }
753 
761  function getTitle()
762  {
763  return $this->title;
764  }
765 
771  public function getTitleFilenameCompliant()
772  {
773  require_once 'Services/Utilities/classes/class.ilUtil.php';
774  return ilUtil::getASCIIFilename($this->getTitle());
775  }
776 
784  function getId()
785  {
786  return $this->id;
787  }
788 
796  function getShuffle()
797  {
798  return $this->shuffle;
799  }
800 
808  function getTestId()
809  {
810  return $this->test_id;
811  }
812 
820  function getComment()
821  {
822  return $this->comment;
823  }
824 
832  function getOutputType()
833  {
834  return $this->outputType;
835  }
836 
843  public function supportsJavascriptOutput()
844  {
845  return FALSE;
846  }
847 
848  public function supportsNonJsOutput()
849  {
850  return true;
851  }
852 
853  public function requiresJsSwitch()
854  {
855  return $this->supportsJavascriptOutput() && $this->supportsNonJsOutput();
856  }
857 
866  {
867  if (!$this->est_working_time)
868  {
869  $this->est_working_time = array("h" => 0, "m" => 0, "s" => 0);
870  }
872  }
873 
881  function getAuthor()
882  {
883  return $this->author;
884  }
885 
893  function getOwner()
894  {
895  return $this->owner;
896  }
897 
905  function getObjId()
906  {
907  return $this->obj_id;
908  }
909 
917  function setObjId($obj_id = 0)
918  {
919  $this->obj_id = $obj_id;
920  }
921 
925  public function setExternalId($external_id)
926  {
927  $this->external_id = $external_id;
928  }
929 
933  public function getExternalId()
934  {
935  if(!strlen($this->external_id))
936  {
937  if($this->getId() > 0)
938  {
939  return 'il_' . IL_INST_ID . '_qst_' . $this->getId();
940  }
941  else
942  {
943  return uniqid('', true);
944  }
945  }
946  else
947  {
948  return $this->external_id;
949  }
950  }
951 
958  public static function _getMaximumPoints($question_id)
959  {
960  global $ilDB;
961 
962  $points = 0;
963  $result = $ilDB->queryF("SELECT points FROM qpl_questions WHERE question_id = %s",
964  array('integer'),
965  array($question_id)
966  );
967  if ($result->numRows() == 1)
968  {
969  $row = $ilDB->fetchAssoc($result);
970  $points = $row["points"];
971  }
972  return $points;
973  }
974 
981  public static function _getQuestionInfo($question_id)
982  {
983  global $ilDB;
984 
985  $result = $ilDB->queryF("SELECT qpl_questions.*, qpl_qst_type.type_tag FROM qpl_qst_type, qpl_questions WHERE qpl_questions.question_id = %s AND qpl_questions.question_type_fi = qpl_qst_type.question_type_id",
986  array('integer'),
987  array($question_id)
988  );
989  if ($result->numRows())
990  {
991  return $ilDB->fetchAssoc($result);
992  }
993  else return array();
994  }
995 
1002  public static function _getSuggestedSolutionCount($question_id)
1003  {
1004  global $ilDB;
1005 
1006  $result = $ilDB->queryF("SELECT suggested_solution_id FROM qpl_sol_sug WHERE question_fi = %s",
1007  array('integer'),
1008  array($question_id)
1009  );
1010  return $result->numRows();
1011  }
1012 
1019  public static function _getSuggestedSolutionOutput($question_id)
1020  {
1022  if (!is_object($question)) return "";
1023  return $question->getSuggestedSolutionOutput();
1024  }
1025 
1026  public function getSuggestedSolutionOutput()
1027  {
1028  $output = array();
1029  foreach ($this->suggested_solutions as $solution)
1030  {
1031  switch ($solution["type"])
1032  {
1033  case "lm":
1034  case "st":
1035  case "pg":
1036  case "git":
1037  array_push($output, '<a href="' . assQuestion::_getInternalLinkHref($solution["internal_link"]) . '">' . $this->lng->txt("solution_hint") . '</a>');
1038  break;
1039  case "file":
1040  $possible_texts = array_values(array_filter(array(
1041  ilUtil::prepareFormOutput($solution['value']['filename']),
1042  ilUtil::prepareFormOutput($solution['value']['name']),
1043  $this->lng->txt('tst_show_solution_suggested')
1044  )));
1045 
1046  require_once 'Services/WebAccessChecker/classes/class.ilWACSignedPath.php';
1048  array_push($output, '<a href="' . ilWACSignedPath::signFile($this->getSuggestedSolutionPathWeb() . $solution["value"]["name"]) . '">' . $possible_texts[0] . '</a>');
1049  break;
1050  case "text":
1051  $solutionValue = $solution["value"];
1052  $solutionValue = $this->fixSvgToPng($solutionValue);
1053  $solutionValue = $this->fixUnavailableSkinImageSources($solutionValue);
1054  $output[] = $this->prepareTextareaOutput($solutionValue, true);
1055  break;
1056  }
1057  }
1058  return join($output, "<br />");
1059  }
1060 
1069  function &_getSuggestedSolution($question_id, $subquestion_index = 0)
1070  {
1071  global $ilDB;
1072 
1073  $result = $ilDB->queryF("SELECT * FROM qpl_sol_sug WHERE question_fi = %s AND subquestion_index = %s",
1074  array('integer','integer'),
1075  array($question_id, $subquestion_index)
1076  );
1077  if ($result->numRows() == 1)
1078  {
1079  $row = $ilDB->fetchAssoc($result);
1080  return array(
1081  "internal_link" => $row["internal_link"],
1082  "import_id" => $row["import_id"]
1083  );
1084  }
1085  else
1086  {
1087  return array();
1088  }
1089  }
1090 
1096  public function getSuggestedSolutions()
1097  {
1099  }
1100 
1108  public static function _getReachedPoints($active_id, $question_id, $pass = NULL)
1109  {
1110  global $ilDB;
1111 
1112  $points = 0;
1113  if (is_null($pass))
1114  {
1115  include_once "./Modules/TestQuestionPool/classes/class.assQuestion.php";
1116  $pass = assQuestion::_getSolutionMaxPass($question_id, $active_id);
1117  }
1118  $result = $ilDB->queryF("SELECT * FROM tst_test_result WHERE active_fi = %s AND question_fi = %s AND pass = %s",
1119  array('integer','integer','integer'),
1120  array($active_id, $question_id, $pass)
1121  );
1122  if ($result->numRows() == 1)
1123  {
1124  $row = $ilDB->fetchAssoc($result);
1125  $points = $row["points"];
1126  }
1127  return $points;
1128  }
1129 
1138  function getReachedPoints($active_id, $pass = NULL)
1139  {
1140  return round(self::_getReachedPoints($active_id, $this->getId(), $pass), 2);
1141  }
1142 
1149  function getMaximumPoints()
1150  {
1151  return $this->points;
1152  }
1153 
1165  final public function getAdjustedReachedPoints($active_id, $pass = NULL, $authorizedSolution = true)
1166  {
1167  if (is_null($pass))
1168  {
1169  include_once "./Modules/Test/classes/class.ilObjTest.php";
1170  $pass = ilObjTest::_getPass($active_id);
1171  }
1172 
1173  // determine reached points for submitted solution
1174  $reached_points = $this->calculateReachedPoints($active_id, $pass, $authorizedSolution);
1175 
1176 
1177 
1178  // deduct points for requested hints from reached points
1179  require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionHintTracking.php';
1180  $hintTracking = new ilAssQuestionHintTracking($this->getId(), $active_id, $pass);
1181  $requestsStatisticData = $hintTracking->getRequestStatisticDataByQuestionAndTestpass();
1182  $reached_points = $reached_points - $requestsStatisticData->getRequestsPoints();
1183 
1184  // adjust reached points regarding to tests scoring options
1185  $reached_points = $this->adjustReachedPointsByScoringOptions($reached_points, $active_id, $pass);
1186 
1187  return $reached_points;
1188  }
1189 
1199  final public function calculateResultsFromSolution($active_id, $pass = NULL, $obligationsEnabled = false)
1200  {
1201  global $ilDB, $ilUser;
1202 
1203  if( is_null($pass) )
1204  {
1205  include_once "./Modules/Test/classes/class.ilObjTest.php";
1206  $pass = ilObjTest::_getPass($active_id);
1207  }
1208 
1209  // determine reached points for submitted solution
1210  $reached_points = $this->calculateReachedPoints($active_id, $pass);
1211 
1212  // deduct points for requested hints from reached points
1213  require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionHintTracking.php';
1214  $questionHintTracking = new ilAssQuestionHintTracking($this->getId(), $active_id, $pass);
1215  $requestsStatisticData = $questionHintTracking->getRequestStatisticDataByQuestionAndTestpass();
1216  $reached_points = $reached_points - $requestsStatisticData->getRequestsPoints();
1217 
1218  // adjust reached points regarding to tests scoring options
1219  $reached_points = $this->adjustReachedPointsByScoringOptions($reached_points, $active_id, $pass);
1220 
1221  if( $obligationsEnabled && ilObjTest::isQuestionObligatory($this->getId()) )
1222  {
1223  $isAnswered = $this->isAnswered($active_id, $pass);
1224  }
1225  else
1226  {
1227  $isAnswered = true;
1228  }
1229 
1230  if( is_null($reached_points) ) $reached_points = 0;
1231 
1232 // fau: testNav - check for existing authorized solution to know if a result record should be written
1233  $existingSolutions = $this->lookupForExistingSolutions($active_id, $pass);
1234 
1235  $this->getProcessLocker()->executeUserQuestionResultUpdateOperation(function() use($ilDB, $active_id, $pass, $reached_points, $requestsStatisticData, $isAnswered, $existingSolutions) {
1236 
1237  $query = "
1238  DELETE FROM tst_test_result
1239 
1240  WHERE active_fi = %s
1241  AND question_fi = %s
1242  AND pass = %s
1243  ";
1244 
1245  $types = array('integer', 'integer', 'integer');
1246  $values = array($active_id, $this->getId(), $pass);
1247 
1248  if( $this->getStep() !== NULL )
1249  {
1250  $query .= "
1251  AND step = %s
1252  ";
1253 
1254  $types[] = 'integer';
1255  $values[] = $this->getStep();
1256  }
1257  $ilDB->manipulateF($query, $types, $values);
1258 
1259  if ($existingSolutions['authorized'])
1260  {
1261  $next_id = $ilDB->nextId("tst_test_result");
1262  $fieldData = array(
1263  'test_result_id' => array('integer', $next_id),
1264  'active_fi' => array('integer', $active_id),
1265  'question_fi' => array('integer', $this->getId()),
1266  'pass' => array('integer', $pass),
1267  'points' => array('float', $reached_points),
1268  'tstamp' => array('integer', time()),
1269  'hint_count' => array('integer', $requestsStatisticData->getRequestsCount()),
1270  'hint_points' => array('float', $requestsStatisticData->getRequestsPoints()),
1271  'answered' => array('integer', $isAnswered)
1272  );
1273 
1274  if( $this->getStep() !== NULL )
1275  {
1276  $fieldData['step'] = array('integer', $this->getStep());
1277  }
1278 
1279  $ilDB->insert('tst_test_result', $fieldData);
1280  }
1281 
1282  });
1283 // fau.
1284 
1285  include_once ("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
1286 
1288  {
1290  sprintf(
1291  $this->lng->txtlng(
1292  "assessment", "log_user_answered_question", ilObjAssessmentFolder::_getLogLanguage()
1293  ),
1294  $reached_points
1295  ),
1296  $active_id,
1297  $this->getId()
1298  );
1299  }
1300 
1301  // update test pass results
1302  self::_updateTestPassResults($active_id, $pass, $obligationsEnabled, $this->getProcessLocker());
1303 
1304  // Update objective status
1305  include_once 'Modules/Course/classes/class.ilCourseObjectiveResult.php';
1306  ilCourseObjectiveResult::_updateObjectiveResult($ilUser->getId(),$active_id,$this->getId());
1307  }
1308 
1317  final public function persistWorkingState($active_id, $pass = NULL, $obligationsEnabled = false, $authorized = true)
1318  {
1319  if( $pass === null )
1320  {
1321  require_once 'Modules/Test/classes/class.ilObjTest.php';
1322  $pass = ilObjTest::_getPass($active_id);
1323  }
1324 
1325  if( !$this->validateSolutionSubmit() )
1326  {
1327  return false;
1328  }
1329 
1330  $saveStatus = false;
1331 
1332  $this->getProcessLocker()->executePersistWorkingStateLockOperation(function() use ($active_id, $pass, $authorized, $obligationsEnabled, &$saveStatus) {
1333 
1334  $saveStatus = $this->saveWorkingData($active_id, $pass, $authorized);
1335 
1336  if($authorized)
1337  {
1338 // fau: testNav - remove an intermediate solution if the authorized solution is saved
1339 // the intermediate solution would set the displayed question status as "editing ..."
1340  $this->removeIntermediateSolution($active_id, $pass);
1341 // fau.
1342  $this->calculateResultsFromSolution($active_id, $pass, $obligationsEnabled);
1343  }
1344 
1345  $this->reworkWorkingData($active_id, $pass, $obligationsEnabled, $authorized);
1346 
1347  });
1348 
1349  return $saveStatus;
1350  }
1351 
1355  final public function persistPreviewState(ilAssQuestionPreviewSession $previewSession)
1356  {
1357  $this->savePreviewData($previewSession);
1358  return $this->validateSolutionSubmit();
1359  }
1360 
1361  public function validateSolutionSubmit()
1362  {
1363  return true;
1364  }
1365 
1375  abstract public function saveWorkingData($active_id, $pass = NULL, $authorized = true);
1376 
1384  abstract protected function reworkWorkingData($active_id, $pass, $obligationsAnswered, $authorized);
1385 
1386  protected function savePreviewData(ilAssQuestionPreviewSession $previewSession)
1387  {
1388  $previewSession->setParticipantsSolution($this->getSolutionSubmit());
1389  }
1390 
1392  public static function _updateTestResultCache($active_id, ilAssQuestionProcessLocker $processLocker = null)
1393  {
1394  global $ilDB;
1395 
1396  include_once "./Modules/Test/classes/class.ilObjTest.php";
1397  include_once "./Modules/Test/classes/class.assMarkSchema.php";
1398 
1399  $pass = ilObjTest::_getResultPass($active_id);
1400 
1401  $query = "
1402  SELECT tst_pass_result.*
1403  FROM tst_pass_result
1404  WHERE active_fi = %s
1405  AND pass = %s
1406  ";
1407 
1408  $result = $ilDB->queryF(
1409  $query, array('integer','integer'), array($active_id, $pass)
1410  );
1411 
1412  $row = $ilDB->fetchAssoc($result);
1413 
1414  $max = $row['maxpoints'];
1415  $reached = $row['points'];
1416 
1417  $obligationsAnswered = (int)$row['obligations_answered'];
1418 
1419  include_once "./Modules/Test/classes/class.assMarkSchema.php";
1420 
1421  $percentage = (!$max) ? 0 : ($reached / $max) * 100.0;
1422 
1423  $mark = ASS_MarkSchema::_getMatchingMarkFromActiveId($active_id, $percentage);
1424 
1425  $isPassed = ( $mark["passed"] ? 1 : 0 );
1426  $isFailed = ( !$mark["passed"] ? 1 : 0 );
1427 
1428  $userTestResultUpdateCallback = function() use ($ilDB, $active_id, $pass, $max, $reached, $isFailed, $isPassed, $obligationsAnswered, $row, $mark) {
1429 
1430  $query = "
1431  DELETE FROM tst_result_cache
1432  WHERE active_fi = %s
1433  ";
1434  $ilDB->manipulateF(
1435  $query, array('integer'), array($active_id)
1436  );
1437 
1438  $ilDB->insert('tst_result_cache', array(
1439  'active_fi'=> array('integer', $active_id),
1440  'pass'=> array('integer', strlen($pass) ? $pass : 0),
1441  'max_points'=> array('float', strlen($max) ? $max : 0),
1442  'reached_points'=> array('float', strlen($reached) ? $reached : 0),
1443  'mark_short'=> array('text', strlen($mark["short_name"]) ? $mark["short_name"] : " "),
1444  'mark_official'=> array('text', strlen($mark["official_name"]) ? $mark["official_name"] : " "),
1445  'passed'=> array('integer', $isPassed),
1446  'failed'=> array('integer', $isFailed),
1447  'tstamp'=> array('integer', time()),
1448  'hint_count'=> array('integer', $row['hint_count']),
1449  'hint_points'=> array('float', $row['hint_points']),
1450  'obligations_answered' => array('integer', $obligationsAnswered)
1451  ));
1452 
1453  };
1454 
1455  if(is_object($processLocker))
1456  {
1457  $processLocker->executeUserTestResultUpdateLockOperation($userTestResultUpdateCallback);
1458  }
1459  else
1460  {
1461  $userTestResultUpdateCallback();
1462  }
1463  }
1464 
1466  public static function _updateTestPassResults($active_id, $pass, $obligationsEnabled = false, ilAssQuestionProcessLocker $processLocker = null, $test_obj_id = null)
1467  {
1468  global $ilDB;
1469 
1470  include_once "./Modules/Test/classes/class.ilObjTest.php";
1471 
1472  if( self::getResultGateway() !== null )
1473  {
1474  $data = self::getResultGateway()->getQuestionCountAndPointsForPassOfParticipant($active_id, $pass);
1475  $time = self::getResultGateway()->getWorkingTimeOfParticipantForPass($active_id, $pass);
1476  }
1477  else
1478  {
1481  }
1482 
1483 
1484  // update test pass results
1485 
1486  $result = $ilDB->queryF("
1487  SELECT SUM(points) reachedpoints,
1488  SUM(hint_count) hint_count,
1489  SUM(hint_points) hint_points,
1490  COUNT(DISTINCT(question_fi)) answeredquestions
1491  FROM tst_test_result
1492  WHERE active_fi = %s
1493  AND pass = %s
1494  ",
1495  array('integer','integer'),
1496  array($active_id, $pass)
1497  );
1498 
1499  if ($result->numRows() > 0)
1500  {
1501  if( $obligationsEnabled )
1502  {
1503  $query = '
1504  SELECT answered answ
1505  FROM tst_test_question
1506  INNER JOIN tst_active
1507  ON active_id = %s
1508  AND tst_test_question.test_fi = tst_active.test_fi
1509  LEFT JOIN tst_test_result
1510  ON tst_test_result.active_fi = %s
1511  AND tst_test_result.pass = %s
1512  AND tst_test_question.question_fi = tst_test_result.question_fi
1513  WHERE obligatory = 1';
1514 
1515  $result_obligatory = $ilDB->queryF(
1516  $query, array('integer','integer','integer'), array($active_id, $active_id, $pass)
1517  );
1518 
1519  $obligations_answered = 1;
1520 
1521  while($row_obligatory = $ilDB->fetchAssoc($result_obligatory))
1522  {
1523  if(!(int)$row_obligatory['answ'])
1524  {
1525  $obligations_answered = 0;
1526  break;
1527  }
1528  }
1529  }
1530  else
1531  {
1532  $obligations_answered = 1;
1533  }
1534 
1535  $row = $ilDB->fetchAssoc($result);
1536 
1537  if( $row['hint_count'] === null ) $row['hint_count'] = 0;
1538  if( $row['hint_points'] === null ) $row['hint_points'] = 0;
1539 
1540  $exam_identifier = ilObjTest::buildExamId( $active_id, $pass, $test_obj_id);
1541 
1542  $updatePassResultCallback = function() use ($ilDB, $data, $active_id, $pass, $row, $time, $obligations_answered, $exam_identifier) {
1543 
1545  $ilDB->replace('tst_pass_result',
1546  array(
1547  'active_fi' => array('integer', $active_id),
1548  'pass' => array('integer', strlen($pass) ? $pass : 0)),
1549  array(
1550  'points' => array('float', $row['reachedpoints'] ? $row['reachedpoints'] : 0),
1551  'maxpoints' => array('float', $data['points']),
1552  'questioncount' => array('integer', $data['count']),
1553  'answeredquestions' => array('integer', $row['answeredquestions']),
1554  'workingtime' => array('integer', $time),
1555  'tstamp' => array('integer', time()),
1556  'hint_count' => array('integer', $row['hint_count']),
1557  'hint_points' => array('float', $row['hint_points']),
1558  'obligations_answered' => array('integer', $obligations_answered),
1559  'exam_id' => array('text', $exam_identifier)
1560  )
1561  );
1562 
1563  };
1564 
1565  if(is_object($processLocker))
1566  {
1567  $processLocker->executeUserPassResultUpdateLockOperation($updatePassResultCallback);
1568  }
1569  else
1570  {
1571  $updatePassResultCallback();
1572  }
1573  }
1574 
1576 
1577  return array(
1578  'active_fi' => $active_id,
1579  'pass' => $pass,
1580  'points' => ($row["reachedpoints"]) ? $row["reachedpoints"] : 0,
1581  'maxpoints' => $data["points"],
1582  'questioncount' => $data["count"],
1583  'answeredquestions' => $row["answeredquestions"],
1584  'workingtime' => $time,
1585  'tstamp' => time(),
1586  'hint_count' => $row['hint_count'],
1587  'hint_points' => $row['hint_points'],
1588  'obligations_answered' => $obligations_answered,
1589  'exam_id' => $exam_identifier
1590  );
1591  }
1592 
1600  public static function logAction($logtext = "", $active_id = "", $question_id = "")
1601  {
1602  $original_id = "";
1603  if( strlen($question_id) )
1604  {
1605  $original_id = self::_getOriginalId($question_id);
1606  }
1607 
1608  require_once 'Modules/Test/classes/class.ilObjAssessmentFolder.php';
1609  require_once 'Modules/Test/classes/class.ilObjTest.php';
1610 
1612  $GLOBALS['DIC']['ilUser']->getId(),
1614  $logtext,
1615  $question_id,
1616  $original_id
1617  );
1618  }
1619 
1627  function moveUploadedMediaFile($file, $name)
1628  {
1629  $mediatempdir = CLIENT_WEB_DIR . "/assessment/temp";
1630  if (!@is_dir($mediatempdir)) ilUtil::createDirectory($mediatempdir);
1631  $temp_name = tempnam($mediatempdir, $name . "_____");
1632  $temp_name = str_replace("\\", "/", $temp_name);
1633  @unlink($temp_name);
1634  if (!ilUtil::moveUploadedFile($file, $name, $temp_name))
1635  {
1636  return FALSE;
1637  }
1638  else
1639  {
1640  return $temp_name;
1641  }
1642  }
1643 
1650  return CLIENT_WEB_DIR . "/assessment/$this->obj_id/$this->id/solution/";
1651  }
1652 
1659  function getJavaPath() {
1660  return CLIENT_WEB_DIR . "/assessment/$this->obj_id/$this->id/java/";
1661  }
1662 
1669  function getImagePath($question_id = null, $object_id = null)
1670  {
1671  if( $question_id === null)
1672  {
1673  $question_id = $this->id;
1674  }
1675 
1676  if( $object_id === null)
1677  {
1678  $object_id = $this->obj_id;
1679  }
1680 
1681  return $this->buildImagePath($question_id, $object_id);
1682  }
1683 
1684  public function buildImagePath($questionId, $parentObjectId)
1685  {
1686  return CLIENT_WEB_DIR . "/assessment/{$parentObjectId}/{$questionId}/images/";
1687  }
1688 
1695  function getFlashPath()
1696  {
1697  return CLIENT_WEB_DIR . "/assessment/$this->obj_id/$this->id/flash/";
1698  }
1699 
1706  function getJavaPathWeb()
1707  {
1708  include_once "./Services/Utilities/classes/class.ilUtil.php";
1709  $webdir = ilUtil::removeTrailingPathSeparators(CLIENT_WEB_DIR) . "/assessment/$this->obj_id/$this->id/java/";
1710  return str_replace(ilUtil::removeTrailingPathSeparators(ILIAS_ABSOLUTE_PATH), ilUtil::removeTrailingPathSeparators(ILIAS_HTTP_PATH), $webdir);
1711  }
1712 
1719  {
1720  include_once "./Services/Utilities/classes/class.ilUtil.php";
1721  $webdir = ilUtil::removeTrailingPathSeparators(CLIENT_WEB_DIR) . "/assessment/$this->obj_id/$this->id/solution/";
1722  return str_replace(ilUtil::removeTrailingPathSeparators(ILIAS_ABSOLUTE_PATH), ilUtil::removeTrailingPathSeparators(ILIAS_HTTP_PATH), $webdir);
1723  }
1724 
1733  function getImagePathWeb()
1734  {
1735  if(!$this->export_image_path)
1736  {
1737  include_once "./Services/Utilities/classes/class.ilUtil.php";
1738  $webdir = ilUtil::removeTrailingPathSeparators(CLIENT_WEB_DIR) . "/assessment/$this->obj_id/$this->id/images/";
1739  return str_replace(ilUtil::removeTrailingPathSeparators(ILIAS_ABSOLUTE_PATH), ilUtil::removeTrailingPathSeparators(ILIAS_HTTP_PATH), $webdir);
1740  }
1741  else
1742  {
1743  return $this->export_image_path;
1744  }
1745  }
1746 
1753  function getFlashPathWeb()
1754  {
1755  include_once "./Services/Utilities/classes/class.ilUtil.php";
1756  $webdir = ilUtil::removeTrailingPathSeparators(CLIENT_WEB_DIR) . "/assessment/$this->obj_id/$this->id/flash/";
1757  return str_replace(ilUtil::removeTrailingPathSeparators(ILIAS_ABSOLUTE_PATH), ilUtil::removeTrailingPathSeparators(ILIAS_HTTP_PATH), $webdir);
1758  }
1759 
1760  // hey: prevPassSolutions - accept and prefer intermediate only from current pass
1761  public function getTestOutputSolutions($activeId, $pass)
1762  {
1763  // hey: refactored identifiers
1764  if( $this->getTestPresentationConfig()->isSolutionInitiallyPrefilled() )
1765  // hey.
1766  {
1767  return $this->getSolutionValues($activeId, $pass, true);
1768  }
1769 
1770  return $this->getUserSolutionPreferingIntermediate($activeId, $pass);
1771  }
1772  // hey.
1773 
1774  public function getUserSolutionPreferingIntermediate($active_id, $pass = NULL)
1775  {
1776  $solution = $this->getSolutionValues($active_id, $pass, false);
1777 
1778  if( !count($solution) )
1779  {
1780  $solution = $this->getSolutionValues($active_id, $pass, true);
1781  }
1782 
1783  return $solution;
1784  }
1785 
1789  public function getSolutionValues($active_id, $pass = NULL, $authorized = true)
1790  {
1791  global $ilDB;
1792 
1793  if (is_null($pass))
1794  {
1795  $pass = $this->getSolutionMaxPass($active_id);
1796  }
1797 
1798  if( $this->getStep() !== NULL )
1799  {
1800  $query = "
1801  SELECT *
1802  FROM tst_solutions
1803  WHERE active_fi = %s
1804  AND question_fi = %s
1805  AND pass = %s
1806  AND step = %s
1807  AND authorized = %s
1808  ORDER BY solution_id";
1809 
1810  $result = $ilDB->queryF($query, array('integer', 'integer', 'integer', 'integer', 'integer'),
1811  array($active_id, $this->getId(), $pass, $this->getStep(), (int)$authorized)
1812  );
1813  }
1814  else
1815  {
1816  $query = "
1817  SELECT *
1818  FROM tst_solutions
1819  WHERE active_fi = %s
1820  AND question_fi = %s
1821  AND pass = %s
1822  AND authorized = %s
1823  ORDER BY solution_id
1824  ";
1825 
1826  $result = $ilDB->queryF($query, array('integer', 'integer', 'integer', 'integer'),
1827  array($active_id, $this->getId(), $pass, (int)$authorized)
1828  );
1829  }
1830 
1831  $values = array();
1832 
1833  while( $row = $ilDB->fetchAssoc($result) )
1834  {
1835  $values[] = $row;
1836  }
1837 
1838  return $values;
1839  }
1840 
1847  public function isInUse($question_id = "")
1848  {
1849  global $ilDB;
1850 
1851  if ($question_id < 1) $question_id = $this->getId();
1852  $result = $ilDB->queryF("SELECT COUNT(qpl_questions.question_id) question_count FROM qpl_questions, tst_test_question WHERE qpl_questions.original_id = %s AND qpl_questions.question_id = tst_test_question.question_fi",
1853  array('integer'),
1854  array($question_id)
1855  );
1856  $row = $ilDB->fetchAssoc($result);
1857  $count = $row["question_count"];
1858 
1859  $result = $ilDB->queryF("
1860  SELECT tst_active.test_fi
1861  FROM qpl_questions
1862  INNER JOIN tst_test_rnd_qst ON tst_test_rnd_qst.question_fi = qpl_questions.question_id
1863  INNER JOIN tst_active ON tst_active.active_id = tst_test_rnd_qst.active_fi
1864  WHERE qpl_questions.original_id = %s
1865  GROUP BY tst_active.test_fi",
1866  array('integer'),
1867  array($question_id)
1868  );
1869  $count += $result->numRows();
1870 
1871  return $count;
1872  }
1873 
1880  function isClone($question_id = "")
1881  {
1882  global $ilDB;
1883 
1884  if ($question_id < 1) $question_id = $this->id;
1885  $result = $ilDB->queryF("SELECT original_id FROM qpl_questions WHERE question_id = %s",
1886  array('integer'),
1887  array($question_id)
1888  );
1889  $row = $ilDB->fetchAssoc($result);
1890  return ($row["original_id"] > 0) ? TRUE : FALSE;
1891  }
1892 
1899  function pcArrayShuffle($array)
1900  {
1901  $keys = array_keys($array);
1902  shuffle($keys);
1903  $result = array();
1904  foreach ($keys as $key)
1905  {
1906  $result[$key] = $array[$key];
1907  }
1908  return $result;
1909  }
1910 
1916  public static function getQuestionTypeFromDb($question_id)
1917  {
1918  global $ilDB;
1919 
1920  $result = $ilDB->queryF("SELECT qpl_qst_type.type_tag FROM qpl_qst_type, qpl_questions WHERE qpl_questions.question_id = %s AND qpl_questions.question_type_fi = qpl_qst_type.question_type_id",
1921  array('integer'),
1922  array($question_id)
1923  );
1924  $data = $ilDB->fetchAssoc($result);
1925  return $data["type_tag"];
1926  }
1927 
1935  {
1936  return "";
1937  }
1938 
1946  {
1947  return "";
1948  }
1949 
1956  function deleteAnswers($question_id)
1957  {
1958  global $ilDB;
1959  $answer_table_name = $this->getAnswerTableName();
1960 
1961  if( !is_array($answer_table_name) )
1962  {
1963  $answer_table_name = array($answer_table_name);
1964  }
1965 
1966  foreach ($answer_table_name as $table)
1967  {
1968  if (strlen($table))
1969  {
1970  $affectedRows = $ilDB->manipulateF("DELETE FROM $table WHERE question_fi = %s",
1971  array('integer'),
1972  array($question_id)
1973  );
1974  }
1975  }
1976  }
1977 
1984  function deleteAdditionalTableData($question_id)
1985  {
1986  global $ilDB;
1987 
1988  $additional_table_name = $this->getAdditionalTableName();
1989 
1990  if( !is_array($additional_table_name) )
1991  {
1992  $additional_table_name = array($additional_table_name);
1993  }
1994 
1995  foreach ($additional_table_name as $table)
1996  {
1997  if (strlen($table))
1998  {
1999  $affectedRows = $ilDB->manipulateF("DELETE FROM $table WHERE question_fi = %s",
2000  array('integer'),
2001  array($question_id)
2002  );
2003  }
2004  }
2005  }
2006 
2013  protected function deletePageOfQuestion($question_id)
2014  {
2015  include_once "./Modules/TestQuestionPool/classes/class.ilAssQuestionPage.php";
2016  $page = new ilAssQuestionPage($question_id);
2017  $page->delete();
2018  return true;
2019  }
2020 
2027  public function delete($question_id)
2028  {
2029  global $ilDB, $ilLog;
2030 
2031  if ($question_id < 1) return true; // nothing to do
2032 
2033  $result = $ilDB->queryF("SELECT obj_fi FROM qpl_questions WHERE question_id = %s",
2034  array('integer'),
2035  array($question_id)
2036  );
2037  if ($result->numRows() == 1)
2038  {
2039  $row = $ilDB->fetchAssoc($result);
2040  $obj_id = $row["obj_fi"];
2041  }
2042  else
2043  {
2044  return true; // nothing to do
2045  }
2046  try
2047  {
2048  $this->deletePageOfQuestion($question_id);
2049  }
2050  catch (Exception $e)
2051  {
2052  $ilLog->write("EXCEPTION: Could not delete page of question $question_id: $e");
2053  return false;
2054  }
2055 
2056  $affectedRows = $ilDB->manipulateF("DELETE FROM qpl_questions WHERE question_id = %s",
2057  array('integer'),
2058  array($question_id)
2059  );
2060  if ($affectedRows == 0) return false;
2061 
2062  try
2063  {
2064  $this->deleteAdditionalTableData($question_id);
2065  $this->deleteAnswers($question_id);
2066  $this->feedbackOBJ->deleteGenericFeedbacks($question_id, $this->isAdditionalContentEditingModePageObject());
2067  $this->feedbackOBJ->deleteSpecificAnswerFeedbacks($question_id, $this->isAdditionalContentEditingModePageObject());
2068  }
2069  catch (Exception $e)
2070  {
2071  $ilLog->write("EXCEPTION: Could not delete additional table data of question $question_id: $e");
2072  return false;
2073  }
2074 
2075  try
2076  {
2077  // delete the question in the tst_test_question table (list of test questions)
2078  $affectedRows = $ilDB->manipulateF("DELETE FROM tst_test_question WHERE question_fi = %s",
2079  array('integer'),
2080  array($question_id)
2081  );
2082  }
2083  catch (Exception $e)
2084  {
2085  $ilLog->write("EXCEPTION: Could not delete delete question $question_id from a test: $e");
2086  return false;
2087  }
2088 
2089  try
2090  {
2091  // delete suggested solutions contained in the question
2092  $affectedRows = $ilDB->manipulateF("DELETE FROM qpl_sol_sug WHERE question_fi = %s",
2093  array('integer'),
2094  array($question_id)
2095  );
2096  }
2097  catch (Exception $e)
2098  {
2099  $ilLog->write("EXCEPTION: Could not delete suggested solutions of question $question_id: $e");
2100  return false;
2101  }
2102 
2103  try
2104  {
2105  $directory = CLIENT_WEB_DIR . "/assessment/" . $obj_id . "/$question_id";
2106  if (preg_match("/\d+/", $obj_id) and preg_match("/\d+/", $question_id) and is_dir($directory))
2107  {
2108  include_once "./Services/Utilities/classes/class.ilUtil.php";
2109  ilUtil::delDir($directory);
2110  }
2111  }
2112  catch (Exception $e)
2113  {
2114  $ilLog->write("EXCEPTION: Could not delete question file directory $directory of question $question_id: $e");
2115  return false;
2116  }
2117 
2118  try
2119  {
2120  include_once("./Services/MediaObjects/classes/class.ilObjMediaObject.php");
2121  $mobs = ilObjMediaObject::_getMobsOfObject("qpl:html", $question_id);
2122  // remaining usages are not in text anymore -> delete them
2123  // and media objects (note: delete method of ilObjMediaObject
2124  // checks whether object is used in another context; if yes,
2125  // the object is not deleted!)
2126  foreach($mobs as $mob)
2127  {
2128  ilObjMediaObject::_removeUsage($mob, "qpl:html", $question_id);
2129  if (ilObjMediaObject::_exists($mob))
2130  {
2131  $mob_obj = new ilObjMediaObject($mob);
2132  $mob_obj->delete();
2133  }
2134  }
2135  }
2136  catch (Exception $e)
2137  {
2138  $ilLog->write("EXCEPTION: Error deleting the media objects of question $question_id: $e");
2139  return false;
2140  }
2141 
2142  require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionHintTracking.php';
2143  ilAssQuestionHintTracking::deleteRequestsByQuestionIds(array($question_id));
2144 
2145  require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionHintList.php';
2147 
2148  require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionSkillAssignmentList.php';
2149  $assignmentList = new ilAssQuestionSkillAssignmentList($ilDB);
2150  $assignmentList->setParentObjId($obj_id);
2151  $assignmentList->setQuestionIdFilter($question_id);
2152  $assignmentList->loadFromDb();
2153  foreach($assignmentList->getAssignmentsByQuestionId($question_id) as $assignment)
2154  {
2155  /* @var ilAssQuestionSkillAssignment $assignment */
2156  $assignment->deleteFromDb();
2157  }
2158 
2159  $this->deleteTaxonomyAssignments();
2160 
2161  try
2162  {
2163  // update question count of question pool
2164  include_once "./Modules/TestQuestionPool/classes/class.ilObjQuestionPool.php";
2166  }
2167  catch (Exception $e)
2168  {
2169  $ilLog->write("EXCEPTION: Error updating the question pool question count of question pool " . $this->getObjId() . " when deleting question $question_id: $e");
2170  return false;
2171  }
2172 
2173  $this->notifyQuestionDeleted($this);
2174 
2175  return true;
2176  }
2177 
2178  private function deleteTaxonomyAssignments()
2179  {
2180  require_once 'Services/Taxonomy/classes/class.ilObjTaxonomy.php';
2181  require_once 'Services/Taxonomy/classes/class.ilTaxNodeAssignment.php';
2182  $taxIds = ilObjTaxonomy::getUsageOfObject($this->getObjId());
2183 
2184  foreach($taxIds as $taxId)
2185  {
2186  $taxNodeAssignment = new ilTaxNodeAssignment('qpl', $this->getObjId(), 'quest', $taxId);
2187  $taxNodeAssignment->deleteAssignmentsOfItem($this->getId());
2188  }
2189  }
2190 
2194  function getTotalAnswers()
2195  {
2196  return $this->_getTotalAnswers($this->id);
2197  }
2198 
2205  function _getTotalAnswers($a_q_id)
2206  {
2207  global $ilDB;
2208 
2209  // get all question references to the question id
2210  $result = $ilDB->queryF("SELECT question_id FROM qpl_questions WHERE original_id = %s OR question_id = %s",
2211  array('integer','integer'),
2212  array($a_q_id, $a_q_id)
2213  );
2214  if ($result->numRows() == 0)
2215  {
2216  return 0;
2217  }
2218  $found_id = array();
2219  while ($row = $ilDB->fetchAssoc($result))
2220  {
2221  array_push($found_id, $row["question_id"]);
2222  }
2223 
2224  $result = $ilDB->query("SELECT * FROM tst_test_result WHERE " . $ilDB->in('question_fi', $found_id, false, 'integer'));
2225 
2226  return $result->numRows();
2227  }
2228 
2229 
2236  public static function _getTotalRightAnswers($a_q_id)
2237  {
2238  global $ilDB;
2239  $result = $ilDB->queryF("SELECT question_id FROM qpl_questions WHERE original_id = %s OR question_id = %s",
2240  array('integer','integer'),
2241  array($a_q_id, $a_q_id)
2242  );
2243  if ($result->numRows() == 0)
2244  {
2245  return 0;
2246  }
2247  $found_id = array();
2248  while ($row = $ilDB->fetchAssoc($result))
2249  {
2250  array_push($found_id, $row["question_id"]);
2251  }
2252  $result = $ilDB->query("SELECT * FROM tst_test_result WHERE " . $ilDB->in('question_fi', $found_id, false, 'integer'));
2253  $answers = array();
2254  while ($row = $ilDB->fetchAssoc($result))
2255  {
2256  $reached = $row["points"];
2257  include_once "./Modules/TestQuestionPool/classes/class.assQuestion.php";
2258  $max = assQuestion::_getMaximumPoints($row["question_fi"]);
2259  array_push($answers, array("reached" => $reached, "max" => $max));
2260  }
2261  $max = 0.0;
2262  $reached = 0.0;
2263  foreach ($answers as $key => $value)
2264  {
2265  $max += $value["max"];
2266  $reached += $value["reached"];
2267  }
2268  if ($max > 0)
2269  {
2270  return $reached / $max;
2271  }
2272  else
2273  {
2274  return 0;
2275  }
2276  }
2277 
2283  static function _getTitle($a_q_id)
2284  {
2285  global $ilDB;
2286  $result = $ilDB->queryF("SELECT title FROM qpl_questions WHERE question_id = %s",
2287  array('integer'),
2288  array($a_q_id)
2289  );
2290  if ($result->numRows() == 1)
2291  {
2292  $row = $ilDB->fetchAssoc($result);
2293  return $row["title"];
2294  }
2295  else
2296  {
2297  return "";
2298  }
2299  }
2300 
2306  static function _getQuestionText($a_q_id)
2307  {
2308  global $ilDB;
2309  $result = $ilDB->queryF("SELECT question_text FROM qpl_questions WHERE question_id = %s",
2310  array('integer'),
2311  array($a_q_id)
2312  );
2313  if ($result->numRows() == 1)
2314  {
2315  $row = $ilDB->fetchAssoc($result);
2316  return $row["question_text"];
2317  }
2318  else
2319  {
2320  return "";
2321  }
2322  }
2323 
2324  public static function isFileAvailable($file)
2325  {
2326  if( !file_exists($file) )
2327  {
2328  return false;
2329  }
2330 
2331  if( !is_file($file) )
2332  {
2333  return false;
2334  }
2335 
2336  if( !is_readable($file) )
2337  {
2338  return false;
2339  }
2340 
2341  return true;
2342  }
2343 
2345  {
2346  include_once("./Services/MediaObjects/classes/class.ilObjMediaObject.php");
2347  $mobs = ilObjMediaObject::_getMobsOfObject("qpl:html", $a_q_id);
2348  foreach ($mobs as $mob)
2349  {
2350  ilObjMediaObject::_saveUsage($mob, "qpl:html", $this->getId());
2351  }
2352  }
2353 
2355  {
2356  include_once("./Services/MediaObjects/classes/class.ilObjMediaObject.php");
2357  $mobs = ilObjMediaObject::_getMobsOfObject("qpl:html", $this->getId());
2358  foreach ($mobs as $mob)
2359  {
2360  ilObjMediaObject::_saveUsage($mob, "qpl:html", $this->original_id);
2361  }
2362  }
2363 
2367  function createPageObject()
2368  {
2369  $qpl_id = $this->getObjId();
2370 
2371  include_once "./Modules/TestQuestionPool/classes/class.ilAssQuestionPage.php";
2372  $this->page = new ilAssQuestionPage(0);
2373  $this->page->setId($this->getId());
2374  $this->page->setParentId($qpl_id);
2375  $this->page->setXMLContent("<PageObject><PageContent>".
2376  "<Question QRef=\"il__qst_".$this->getId()."\"/>".
2377  "</PageContent></PageObject>");
2378  $this->page->create();
2379  }
2380 
2381  function copyPageOfQuestion($a_q_id)
2382  {
2383  if ($a_q_id > 0)
2384  {
2385  include_once "./Modules/TestQuestionPool/classes/class.ilAssQuestionPage.php";
2386  $page = new ilAssQuestionPage($a_q_id);
2387 
2388  $xml = str_replace("il__qst_".$a_q_id, "il__qst_".$this->id, $page->getXMLContent());
2389  $this->page->setXMLContent($xml);
2390  $this->page->updateFromXML();
2391  }
2392  }
2393 
2395  {
2396  include_once "./Modules/TestQuestionPool/classes/class.ilAssQuestionPage.php";
2397  $page = new ilAssQuestionPage($this->id);
2398  return $page->getXMLContent();
2399  }
2400 
2406  public static function _getQuestionType($question_id)
2407  {
2408  global $ilDB;
2409 
2410  if ($question_id < 1) return "";
2411  $result = $ilDB->queryF("SELECT type_tag FROM qpl_questions, qpl_qst_type WHERE qpl_questions.question_id = %s AND qpl_questions.question_type_fi = qpl_qst_type.question_type_id",
2412  array('integer'),
2413  array($question_id)
2414  );
2415  if ($result->numRows() == 1)
2416  {
2417  $data = $ilDB->fetchAssoc($result);
2418  return $data["type_tag"];
2419  }
2420  else
2421  {
2422  return "";
2423  }
2424  }
2425 
2433  public static function _getQuestionTitle($question_id)
2434  {
2435  global $ilDB;
2436 
2437  if ($question_id < 1) return "";
2438 
2439  $result = $ilDB->queryF("SELECT title FROM qpl_questions WHERE qpl_questions.question_id = %s",
2440  array('integer'),
2441  array($question_id)
2442  );
2443  if ($result->numRows() == 1)
2444  {
2445  $data = $ilDB->fetchAssoc($result);
2446  return $data["title"];
2447  }
2448  else
2449  {
2450  return "";
2451  }
2452  }
2453 
2455  {
2456  $this->original_id = $original_id;
2457  }
2458 
2459  function getOriginalId()
2460  {
2461  return $this->original_id;
2462  }
2463 
2464  protected static $imageSourceFixReplaceMap = array(
2465  'ok.svg' => 'ok.png', 'not_ok.svg' => 'not_ok.png',
2466  'checkbox_checked.svg' => 'checkbox_checked.png',
2467  'checkbox_unchecked.svg' => 'checkbox_unchecked.png',
2468  'radiobutton_checked.svg' => 'radiobutton_checked.png',
2469  'radiobutton_unchecked.svg' => 'radiobutton_unchecked.png'
2470  );
2471 
2472  public function fixSvgToPng($imageFilenameContainingString)
2473  {
2474  $needles = array_keys(self::$imageSourceFixReplaceMap);
2475  $replacements = array_values(self::$imageSourceFixReplaceMap);
2476  return str_replace($needles, $replacements, $imageFilenameContainingString);
2477  }
2478 
2479 
2481  {
2482  $matches = null;
2483  if( preg_match_all('/src="(.*?)"/m', $html, $matches) )
2484  {
2485  $sources = $matches[1];
2486 
2487  $needleReplacementMap = array();
2488 
2489  foreach($sources as $src)
2490  {
2491  $file = ilUtil::removeTrailingPathSeparators( ILIAS_ABSOLUTE_PATH ) . DIRECTORY_SEPARATOR . $src;
2492 
2493  if( file_exists($file) )
2494  {
2495  continue;
2496  }
2497 
2498  $levels = explode(DIRECTORY_SEPARATOR, $src);
2499  if( count($levels) < 5 || $levels[0] != 'Customizing' || $levels[2] != 'skin' )
2500  {
2501  continue;
2502  }
2503 
2504  $component = '';
2505 
2506  if( $levels[4] == 'Modules' || $levels[4] == 'Services' )
2507  {
2508  $component = $levels[4] . DIRECTORY_SEPARATOR . $levels[5];
2509  }
2510 
2511  $needleReplacementMap[$src] = ilUtil::getImagePath(basename($src), $component);
2512  }
2513 
2514  if( count($needleReplacementMap) )
2515  {
2516  $html = str_replace(array_keys($needleReplacementMap), array_values($needleReplacementMap), $html);
2517  }
2518  }
2519 
2520  return $html;
2521  }
2522 
2529  function loadFromDb($question_id)
2530  {
2531  global $ilDB;
2532 
2533  $result = $ilDB->queryF(
2534  "SELECT external_id FROM qpl_questions WHERE question_id = %s",
2535  array("integer"),
2536  array($question_id)
2537  );
2538  if($result->numRows() == 1)
2539  {
2540  $data = $ilDB->fetchAssoc($result);
2541  $this->external_id = $data['external_id'];
2542  }
2543 
2544  $result = $ilDB->queryF("SELECT * FROM qpl_sol_sug WHERE question_fi = %s",
2545  array('integer'),
2546  array($this->getId())
2547  );
2548  $this->suggested_solutions = array();
2549  if ($result->numRows())
2550  {
2551  include_once("./Services/RTE/classes/class.ilRTE.php");
2552  while ($row = $ilDB->fetchAssoc($result))
2553  {
2554  $value = (is_array(unserialize($row["value"]))) ? unserialize($row["value"]) : ilRTE::_replaceMediaObjectImageSrc($row["value"], 1);
2555  $this->suggested_solutions[$row["subquestion_index"]] = array(
2556  "type" => $row["type"],
2557  "value" => $value,
2558  "internal_link" => $row["internal_link"],
2559  "import_id" => $row["import_id"]
2560  );
2561  }
2562  }
2563  }
2564 
2571  public function createNewQuestion($a_create_page = true)
2572  {
2573  global $ilDB, $ilUser;
2574 
2575  $complete = "0";
2576  $estw_time = $this->getEstimatedWorkingTime();
2577  $estw_time = sprintf("%02d:%02d:%02d", $estw_time['h'], $estw_time['m'], $estw_time['s']);
2578  $obj_id = ($this->getObjId() <= 0) ? (ilObject::_lookupObjId((strlen($_GET["ref_id"])) ? $_GET["ref_id"] : $_POST["sel_qpl"])) : $this->getObjId();
2579  if ($obj_id > 0)
2580  {
2581  if($a_create_page)
2582  {
2583  $tstamp = 0;
2584  }
2585  else
2586  {
2587  // question pool must not try to purge
2588  $tstamp = time();
2589  }
2590 
2591  $next_id = $ilDB->nextId('qpl_questions');
2592  $affectedRows = $ilDB->insert("qpl_questions", array(
2593  "question_id" => array("integer", $next_id),
2594  "question_type_fi" => array("integer", $this->getQuestionTypeID()),
2595  "obj_fi" => array("integer", $obj_id),
2596  "title" => array("text", NULL),
2597  "description" => array("text", NULL),
2598  "author" => array("text", $this->getAuthor()),
2599  "owner" => array("integer", $ilUser->getId()),
2600  "question_text" => array("clob", NULL),
2601  "points" => array("float", 0),
2602  "nr_of_tries" => array("integer", $this->getDefaultNrOfTries()), // #10771
2603  "working_time" => array("text", $estw_time),
2604  "complete" => array("text", $complete),
2605  "created" => array("integer", time()),
2606  "original_id" => array("integer", NULL),
2607  "tstamp" => array("integer", $tstamp),
2608  "external_id" => array("text", $this->getExternalId()),
2609  'add_cont_edit_mode' => array('text', $this->getAdditionalContentEditingMode())
2610  ));
2611  $this->setId($next_id);
2612 
2613  if($a_create_page)
2614  {
2615  // create page object of question
2616  $this->createPageObject();
2617  }
2618  }
2619 
2620  $this->notifyQuestionCreated();
2621 
2622  return $this->getId();
2623  }
2624 
2625  public function saveQuestionDataToDb($original_id = "")
2626  {
2627  global $ilDB;
2628 
2629  $estw_time = $this->getEstimatedWorkingTime();
2630  $estw_time = sprintf("%02d:%02d:%02d", $estw_time['h'], $estw_time['m'], $estw_time['s']);
2631 
2632  // cleanup RTE images which are not inserted into the question text
2633  include_once("./Services/RTE/classes/class.ilRTE.php");
2634  if ($this->getId() == -1)
2635  {
2636  // Neuen Datensatz schreiben
2637  $next_id = $ilDB->nextId('qpl_questions');
2638  $affectedRows = $ilDB->insert("qpl_questions", array(
2639  "question_id" => array("integer", $next_id),
2640  "question_type_fi" => array("integer", $this->getQuestionTypeID()),
2641  "obj_fi" => array("integer", $this->getObjId()),
2642  "title" => array("text", $this->getTitle()),
2643  "description" => array("text", $this->getComment()),
2644  "author" => array("text", $this->getAuthor()),
2645  "owner" => array("integer", $this->getOwner()),
2646  "question_text" => array("clob", ilRTE::_replaceMediaObjectImageSrc($this->getQuestion(), 0)),
2647  "points" => array("float", $this->getMaximumPoints()),
2648  "working_time" => array("text", $estw_time),
2649  "nr_of_tries" => array("integer", $this->getNrOfTries()),
2650  "created" => array("integer", time()),
2651  "original_id" => array("integer", ($original_id) ? $original_id : NULL),
2652  "tstamp" => array("integer", time()),
2653  "external_id" => array("text", $this->getExternalId()),
2654  'add_cont_edit_mode' => array('text', $this->getAdditionalContentEditingMode())
2655  ));
2656  $this->setId($next_id);
2657  // create page object of question
2658  $this->createPageObject();
2659  }
2660  else
2661  {
2662  // Vorhandenen Datensatz aktualisieren
2663  $affectedRows = $ilDB->update("qpl_questions", array(
2664  "obj_fi" => array("integer", $this->getObjId()),
2665  "title" => array("text", $this->getTitle()),
2666  "description" => array("text", $this->getComment()),
2667  "author" => array("text", $this->getAuthor()),
2668  "question_text" => array("clob", ilRTE::_replaceMediaObjectImageSrc($this->getQuestion(), 0)),
2669  "points" => array("float", $this->getMaximumPoints()),
2670  "nr_of_tries" => array("integer", $this->getNrOfTries()),
2671  "working_time" => array("text", $estw_time),
2672  "tstamp" => array("integer", time()),
2673  'complete' => array('integer', $this->isComplete()),
2674  "external_id" => array("text", $this->getExternalId())
2675  ), array(
2676  "question_id" => array("integer", $this->getId())
2677  ));
2678  }
2679  }
2680 
2687  function saveToDb($original_id = "")
2688  {
2689  global $ilDB;
2690 
2691  $this->updateSuggestedSolutions();
2692 
2693  // remove unused media objects from ILIAS
2694  $this->cleanupMediaObjectUsage();
2695 
2696  $complete = "0";
2697  if ($this->isComplete())
2698  {
2699  $complete = "1";
2700  }
2701 
2702  // update the question time stamp and completion status
2703  $affectedRows = $ilDB->manipulateF("UPDATE qpl_questions SET tstamp = %s, owner = %s, complete = %s WHERE question_id = %s",
2704  array('integer','integer', 'integer','text'),
2705  array(time(), ($this->getOwner() <= 0) ? $this->ilias->account->id : $this->getOwner(), $complete, $this->getId())
2706  );
2707 
2708  // update question count of question pool
2709  include_once "./Modules/TestQuestionPool/classes/class.ilObjQuestionPool.php";
2711 
2712  $this->notifyQuestionEdited($this);
2713  }
2714 
2718  public function setNewOriginalId($newId) {
2719  self::saveOriginalId($this->getId(), $newId);
2720  }
2721 
2722  public static function saveOriginalId($questionId, $originalId)
2723  {
2724  $query = "UPDATE qpl_questions SET tstamp = %s, original_id = %s WHERE question_id = %s";
2725 
2726  $GLOBALS['DIC']['ilDB']->manipulateF(
2727  $query, array('integer','integer', 'text'), array(time(), $originalId, $questionId)
2728  );
2729  }
2730 
2731  public static function resetOriginalId($questionId)
2732  {
2733  $query = "UPDATE qpl_questions SET tstamp = %s, original_id = NULL WHERE question_id = %s";
2734 
2735  $GLOBALS['DIC']['ilDB']->manipulateF(
2736  $query, array('integer', 'text'), array(time(), $questionId)
2737  );
2738  }
2739 
2743  protected function onDuplicate($originalParentId, $originalQuestionId, $duplicateParentId, $duplicateQuestionId)
2744  {
2745  $this->duplicateSuggestedSolutionFiles($originalParentId, $originalQuestionId);
2746 
2747  // duplicate question feeback
2748  $this->feedbackOBJ->duplicateFeedback($originalQuestionId, $duplicateQuestionId);
2749 
2750  // duplicate question hints
2751  $this->duplicateQuestionHints($originalQuestionId, $duplicateQuestionId);
2752 
2753  // duplicate skill assignments
2754  $this->duplicateSkillAssignments($originalParentId, $originalQuestionId, $duplicateParentId, $duplicateQuestionId);
2755  }
2756 
2757  protected function beforeSyncWithOriginal($origQuestionId, $dupQuestionId, $origParentObjId, $dupParentObjId)
2758  {
2759 
2760  }
2761 
2762  protected function afterSyncWithOriginal($origQuestionId, $dupQuestionId, $origParentObjId, $dupParentObjId)
2763  {
2764  // sync question feeback
2765  $this->feedbackOBJ->syncFeedback($origQuestionId, $dupQuestionId);
2766  }
2767 
2771  protected function onCopy($sourceParentId, $sourceQuestionId, $targetParentId, $targetQuestionId)
2772  {
2773  $this->copySuggestedSolutionFiles($sourceParentId, $sourceQuestionId);
2774 
2775  // duplicate question feeback
2776  $this->feedbackOBJ->duplicateFeedback($sourceQuestionId, $targetQuestionId);
2777 
2778  // duplicate question hints
2779  $this->duplicateQuestionHints($sourceQuestionId, $targetQuestionId);
2780 
2781  // duplicate skill assignments
2782  $this->duplicateSkillAssignments($sourceParentId, $sourceQuestionId, $targetParentId, $targetQuestionId);
2783  }
2784 
2788  public function deleteSuggestedSolutions()
2789  {
2790  global $ilDB;
2791  // delete the links in the qpl_sol_sug table
2792  $affectedRows = $ilDB->manipulateF("DELETE FROM qpl_sol_sug WHERE question_fi = %s",
2793  array('integer'),
2794  array($this->getId())
2795  );
2796  // delete the links in the int_link table
2797  include_once "./Services/Link/classes/class.ilInternalLink.php";
2799  $this->suggested_solutions = array();
2801  }
2802 
2810  function getSuggestedSolution($subquestion_index = 0)
2811  {
2812  if (array_key_exists($subquestion_index, $this->suggested_solutions))
2813  {
2814  return $this->suggested_solutions[$subquestion_index];
2815  }
2816  else
2817  {
2818  return array();
2819  }
2820  }
2821 
2830  function getSuggestedSolutionTitle($subquestion_index = 0)
2831  {
2832  if (array_key_exists($subquestion_index, $this->suggested_solutions))
2833  {
2834  $title = $this->suggested_solutions[$subquestion_index]["internal_link"];
2835  // TO DO: resolve internal link an get link type and title
2836  }
2837  else
2838  {
2839  $title = "";
2840  }
2841  return $title;
2842  }
2843 
2853  function setSuggestedSolution($solution_id = "", $subquestion_index = 0, $is_import = false)
2854  {
2855  if (strcmp($solution_id, "") != 0)
2856  {
2857  $import_id = "";
2858  if ($is_import)
2859  {
2860  $import_id = $solution_id;
2861  $solution_id = $this->_resolveInternalLink($import_id);
2862  }
2863  $this->suggested_solutions[$subquestion_index] = array(
2864  "internal_link" => $solution_id,
2865  "import_id" => $import_id
2866  );
2867  }
2868  }
2869 
2873  protected function duplicateSuggestedSolutionFiles($parent_id, $question_id)
2874  {
2875  global $ilLog;
2876 
2877  foreach ($this->suggested_solutions as $index => $solution)
2878  {
2879  if (strcmp($solution["type"], "file") == 0)
2880  {
2881  $filepath = $this->getSuggestedSolutionPath();
2882  $filepath_original = str_replace(
2883  "/{$this->obj_id}/{$this->id}/solution",
2884  "/$parent_id/$question_id/solution",
2885  $filepath
2886  );
2887  if (!file_exists($filepath))
2888  {
2889  ilUtil::makeDirParents($filepath);
2890  }
2891  $filename = $solution["value"]["name"];
2892  if (strlen($filename))
2893  {
2894  if (!copy($filepath_original . $filename, $filepath . $filename))
2895  {
2896  $ilLog->write("File could not be duplicated!!!!", $ilLog->ERROR);
2897  $ilLog->write("object: " . print_r($this, TRUE), $ilLog->ERROR);
2898  }
2899  }
2900  }
2901  }
2902  }
2903 
2908  {
2909  global $ilLog;
2910 
2911  $filepath = $this->getSuggestedSolutionPath();
2912  $filepath_original = str_replace("/$this->id/solution", "/$original_id/solution", $filepath);
2913  ilUtil::delDir($filepath_original);
2914  foreach ($this->suggested_solutions as $index => $solution)
2915  {
2916  if (strcmp($solution["type"], "file") == 0)
2917  {
2918  if (!file_exists($filepath_original))
2919  {
2920  ilUtil::makeDirParents($filepath_original);
2921  }
2922  $filename = $solution["value"]["name"];
2923  if (strlen($filename))
2924  {
2925  if (!@copy($filepath . $filename, $filepath_original . $filename))
2926  {
2927  $ilLog->write("File could not be duplicated!!!!", $ilLog->ERROR);
2928  $ilLog->write("object: " . print_r($this, TRUE), $ilLog->ERROR);
2929  }
2930  }
2931  }
2932  }
2933  }
2934 
2935  protected function copySuggestedSolutionFiles($source_questionpool_id, $source_question_id)
2936  {
2937  global $ilLog;
2938 
2939  foreach ($this->suggested_solutions as $index => $solution)
2940  {
2941  if (strcmp($solution["type"], "file") == 0)
2942  {
2943  $filepath = $this->getSuggestedSolutionPath();
2944  $filepath_original = str_replace("/$this->obj_id/$this->id/solution", "/$source_questionpool_id/$source_question_id/solution", $filepath);
2945  if (!file_exists($filepath))
2946  {
2947  ilUtil::makeDirParents($filepath);
2948  }
2949  $filename = $solution["value"]["name"];
2950  if (strlen($filename))
2951  {
2952  if (!copy($filepath_original . $filename, $filepath . $filename))
2953  {
2954  $ilLog->write("File could not be copied!!!!", $ilLog->ERROR);
2955  $ilLog->write("object: " . print_r($this, TRUE), $ilLog->ERROR);
2956  }
2957  }
2958  }
2959  }
2960  }
2961 
2965  public function updateSuggestedSolutions($original_id = "")
2966  {
2967  global $ilDB;
2968 
2969  $id = (strlen($original_id) && is_numeric($original_id)) ? $original_id : $this->getId();
2970  include_once "./Services/Link/classes/class.ilInternalLink.php";
2971  $affectedRows = $ilDB->manipulateF("DELETE FROM qpl_sol_sug WHERE question_fi = %s",
2972  array('integer'),
2973  array($id)
2974  );
2976  include_once("./Services/RTE/classes/class.ilRTE.php");
2977  foreach ($this->suggested_solutions as $index => $solution)
2978  {
2979  $next_id = $ilDB->nextId('qpl_sol_sug');
2981  $ilDB->insert('qpl_sol_sug', array(
2982  'suggested_solution_id' => array( 'integer', $next_id ),
2983  'question_fi' => array( 'integer', $id ),
2984  'type' => array( 'text', $solution['type'] ),
2985  'value' => array( 'clob', ilRTE::_replaceMediaObjectImageSrc( (is_array( $solution['value'] ) ) ? serialize( $solution[ 'value' ] ) : $solution['value'], 0 ) ),
2986  'internal_link' => array( 'text', $solution['internal_link'] ),
2987  'import_id' => array( 'text', null ),
2988  'subquestion_index' => array( 'integer', $index ),
2989  'tstamp' => array( 'integer', time() ),
2990  )
2991  );
2992  if (preg_match("/il_(\d*?)_(\w+)_(\d+)/", $solution["internal_link"], $matches))
2993  {
2994  ilInternalLink::_saveLink("qst", $id, $matches[2], $matches[3], $matches[1]);
2995  }
2996  }
2997  if (strlen($original_id) && is_numeric($original_id)) $this->syncSuggestedSolutionFiles($id);
2998  $this->cleanupMediaObjectUsage();
2999  }
3000 
3010  function saveSuggestedSolution($type, $solution_id = "", $subquestion_index = 0, $value = "")
3011  {
3012  global $ilDB;
3013 
3014  $affectedRows = $ilDB->manipulateF("DELETE FROM qpl_sol_sug WHERE question_fi = %s AND subquestion_index = %s",
3015  array("integer", "integer"),
3016  array(
3017  $this->getId(),
3018  $subquestion_index
3019  )
3020  );
3021 
3022  $next_id = $ilDB->nextId('qpl_sol_sug');
3023  include_once("./Services/RTE/classes/class.ilRTE.php");
3025  $affectedRows = $ilDB->insert('qpl_sol_sug', array(
3026  'suggested_solution_id' => array( 'integer', $next_id ),
3027  'question_fi' => array( 'integer', $this->getId() ),
3028  'type' => array( 'text', $type ),
3029  'value' => array( 'clob', ilRTE::_replaceMediaObjectImageSrc( (is_array( $value ) ) ? serialize( $value ) : $value, 0 ) ),
3030  'internal_link' => array( 'text', $solution_id ),
3031  'import_id' => array( 'text', null ),
3032  'subquestion_index' => array( 'integer', $subquestion_index ),
3033  'tstamp' => array( 'integer', time() ),
3034  )
3035  );
3036  if ($affectedRows == 1)
3037  {
3038  $this->suggested_solutions[$subquestion_index] = array(
3039  "type" => $type,
3040  "value" => $value,
3041  "internal_link" => $solution_id,
3042  "import_id" => ""
3043  );
3044  }
3045  $this->cleanupMediaObjectUsage();
3046  }
3047 
3048  function _resolveInternalLink($internal_link)
3049  {
3050  if (preg_match("/il_(\d+)_(\w+)_(\d+)/", $internal_link, $matches))
3051  {
3052  include_once "./Services/Link/classes/class.ilInternalLink.php";
3053  include_once "./Modules/LearningModule/classes/class.ilLMObject.php";
3054  include_once "./Modules/Glossary/classes/class.ilGlossaryTerm.php";
3055  switch ($matches[2])
3056  {
3057  case "lm":
3058  $resolved_link = ilLMObject::_getIdForImportId($internal_link);
3059  break;
3060  case "pg":
3061  $resolved_link = ilInternalLink::_getIdForImportId("PageObject", $internal_link);
3062  break;
3063  case "st":
3064  $resolved_link = ilInternalLink::_getIdForImportId("StructureObject", $internal_link);
3065  break;
3066  case "git":
3067  $resolved_link = ilInternalLink::_getIdForImportId("GlossaryItem", $internal_link);
3068  break;
3069  case "mob":
3070  $resolved_link = ilInternalLink::_getIdForImportId("MediaObject", $internal_link);
3071  break;
3072  }
3073  if (strcmp($resolved_link, "") == 0)
3074  {
3075  $resolved_link = $internal_link;
3076  }
3077  }
3078  else
3079  {
3080  $resolved_link = $internal_link;
3081  }
3082  return $resolved_link;
3083  }
3084 
3085  function _resolveIntLinks($question_id)
3086  {
3087  global $ilDB;
3088  $resolvedlinks = 0;
3089  $result = $ilDB->queryF("SELECT * FROM qpl_sol_sug WHERE question_fi = %s",
3090  array('integer'),
3091  array($question_id)
3092  );
3093  if ($result->numRows())
3094  {
3095  while ($row = $ilDB->fetchAssoc($result))
3096  {
3097  $internal_link = $row["internal_link"];
3098  include_once "./Modules/TestQuestionPool/classes/class.assQuestion.php";
3099  $resolved_link = assQuestion::_resolveInternalLink($internal_link);
3100  if (strcmp($internal_link, $resolved_link) != 0)
3101  {
3102  // internal link was resolved successfully
3103  $affectedRows = $ilDB->manipulateF("UPDATE qpl_sol_sug SET internal_link = %s WHERE suggested_solution_id = %s",
3104  array('text','integer'),
3105  array($resolved_link, $row["suggested_solution_id"])
3106  );
3107  $resolvedlinks++;
3108  }
3109  }
3110  }
3111  if ($resolvedlinks)
3112  {
3113  // there are resolved links -> reenter theses links to the database
3114 
3115  // delete all internal links from the database
3116  include_once "./Services/Link/classes/class.ilInternalLink.php";
3117  ilInternalLink::_deleteAllLinksOfSource("qst", $question_id);
3118 
3119  $result = $ilDB->queryF("SELECT * FROM qpl_sol_sug WHERE question_fi = %s",
3120  array('integer'),
3121  array($question_id)
3122  );
3123  if ($result->numRows())
3124  {
3125  while ($row = $ilDB->fetchAssoc($result))
3126  {
3127  if (preg_match("/il_(\d*?)_(\w+)_(\d+)/", $row["internal_link"], $matches))
3128  {
3129  ilInternalLink::_saveLink("qst", $question_id, $matches[2], $matches[3], $matches[1]);
3130  }
3131  }
3132  }
3133  }
3134  }
3135 
3136  public static function _getInternalLinkHref($target = "")
3137  {
3138  global $ilDB;
3139  $linktypes = array(
3140  "lm" => "LearningModule",
3141  "pg" => "PageObject",
3142  "st" => "StructureObject",
3143  "git" => "GlossaryItem",
3144  "mob" => "MediaObject"
3145  );
3146  $href = "";
3147  if (preg_match("/il__(\w+)_(\d+)/", $target, $matches))
3148  {
3149  $type = $matches[1];
3150  $target_id = $matches[2];
3151  include_once "./Services/Utilities/classes/class.ilUtil.php";
3152  switch($linktypes[$matches[1]])
3153  {
3154  case "LearningModule":
3155  $href = "./goto.php?target=" . $type . "_" . $target_id;
3156  break;
3157  case "PageObject":
3158  case "StructureObject":
3159  $href = "./goto.php?target=" . $type . "_" . $target_id;
3160  break;
3161  case "GlossaryItem":
3162  $href = "./goto.php?target=" . $type . "_" . $target_id;
3163  break;
3164  case "MediaObject":
3165  $href = "./ilias.php?baseClass=ilLMPresentationGUI&obj_type=" . $linktypes[$type] . "&cmd=media&ref_id=".$_GET["ref_id"]."&mob_id=".$target_id;
3166  break;
3167  }
3168  }
3169  return $href;
3170  }
3171 
3179  public static function _getOriginalId($question_id)
3180  {
3181  global $ilDB;
3182  $result = $ilDB->queryF("SELECT * FROM qpl_questions WHERE question_id = %s",
3183  array('integer'),
3184  array($question_id)
3185  );
3186  if ($result->numRows() > 0)
3187  {
3188  $row = $ilDB->fetchAssoc($result);
3189  if ($row["original_id"] > 0)
3190  {
3191  return $row["original_id"];
3192  }
3193  else
3194  {
3195  return $row["question_id"];
3196  }
3197  }
3198  else
3199  {
3200  return "";
3201  }
3202  }
3203 
3204  public static function originalQuestionExists($questionId)
3205  {
3206  global $ilDB;
3207 
3208  $query = "
3209  SELECT COUNT(dupl.question_id) cnt
3210  FROM qpl_questions dupl
3211  INNER JOIN qpl_questions orig
3212  ON orig.question_id = dupl.original_id
3213  WHERE dupl.question_id = %s
3214  ";
3215 
3216  $res = $ilDB->queryF($query, array('integer'), array($questionId));
3217  $row = $ilDB->fetchAssoc($res);
3218 
3219  return $row['cnt'] > 0;
3220  }
3221 
3222  function syncWithOriginal()
3223  {
3224  global $ilDB;
3225 
3226  if( !$this->getOriginalId() )
3227  {
3228  return;
3229  }
3230 
3231  $originalObjId = self::lookupOriginalParentObjId($this->getOriginalId());
3232 
3233  if ( !$originalObjId )
3234  {
3235  return;
3236  }
3237 
3238  $id = $this->getId();
3239  $objId = $this->getObjId();
3240  $original = $this->getOriginalId();
3241 
3242  $this->beforeSyncWithOriginal($original, $id, $originalObjId, $objId);
3243 
3244  $this->setId($original);
3245  $this->setOriginalId(NULL);
3246  $this->setObjId($originalObjId);
3247 
3248  $this->saveToDb();
3249 
3250  $this->deletePageOfQuestion($original);
3251  $this->createPageObject();
3252  $this->copyPageOfQuestion($id);
3253 
3254  $this->setId($id);
3255  $this->setOriginalId($original);
3256  $this->setObjId($objId);
3257 
3258  $this->updateSuggestedSolutions($original);
3260 
3261  $this->afterSyncWithOriginal($original, $id, $originalObjId, $objId);
3262  $this->syncHints();
3263  }
3264 
3265  function createRandomSolution($test_id, $user_id)
3266  {
3267  }
3268 
3276  function _questionExists($question_id)
3277  {
3278  global $ilDB;
3279 
3280  if ($question_id < 1)
3281  {
3282  return false;
3283  }
3284 
3285  $result = $ilDB->queryF("SELECT question_id FROM qpl_questions WHERE question_id = %s",
3286  array('integer'),
3287  array($question_id)
3288  );
3289  if ($result->numRows() == 1)
3290  {
3291  return true;
3292  }
3293  else
3294  {
3295  return false;
3296  }
3297  }
3298 
3306  function _questionExistsInPool($question_id)
3307  {
3308  global $ilDB;
3309 
3310  if ($question_id < 1)
3311  {
3312  return false;
3313  }
3314 
3315  $result = $ilDB->queryF("SELECT question_id FROM qpl_questions INNER JOIN object_data ON obj_fi = obj_id WHERE question_id = %s AND type = 'qpl'",
3316  array('integer'),
3317  array($question_id)
3318  );
3319  if ($result->numRows() == 1)
3320  {
3321  return true;
3322  }
3323  else
3324  {
3325  return false;
3326  }
3327  }
3328 
3336  public static function _instanciateQuestion($question_id)
3337  {
3338  return self::_instantiateQuestion($question_id);
3339  }
3340 
3345  public static function _instantiateQuestion($question_id)
3346  {
3347  global $ilCtrl, $ilDB, $lng;
3348 
3349  if (strcmp($question_id, "") != 0)
3350  {
3351  $question_type = assQuestion::_getQuestionType($question_id);
3352  if (!strlen($question_type)) return null;
3353  assQuestion::_includeClass($question_type);
3354  $objectClassname = self::getObjectClassNameByQuestionType($question_type);
3355  $question = new $objectClassname();
3356  $question->loadFromDb($question_id);
3357 
3358  $feedbackObjectClassname = self::getFeedbackClassNameByQuestionType($question_type);
3359  $question->feedbackOBJ = new $feedbackObjectClassname($question, $ilCtrl, $ilDB, $lng);
3360 
3361  return $question;
3362  }
3363  }
3364 
3371  function getPoints()
3372  {
3373  if (strcmp($this->points, "") == 0)
3374  {
3375  return 0;
3376  }
3377  else
3378  {
3379  return $this->points;
3380  }
3381  }
3382 
3383 
3390  function setPoints($a_points)
3391  {
3392  $this->points = $a_points;
3393  }
3394 
3401  function getSolutionMaxPass($active_id)
3402  {
3403  return self::_getSolutionMaxPass($this->getId(), $active_id);
3404  }
3405 
3412  public static function _getSolutionMaxPass($question_id, $active_id)
3413  {
3414 /* include_once "./Modules/Test/classes/class.ilObjTest.php";
3415  $pass = ilObjTest::_getPass($active_id);
3416  return $pass;*/
3417 
3418  // the following code was the old solution which added the non answered
3419  // questions of a pass from the answered questions of the previous pass
3420  // with the above solution, only the answered questions of the last pass are counted
3421  global $ilDB;
3422 
3423  $result = $ilDB->queryF("SELECT MAX(pass) maxpass FROM tst_test_result WHERE active_fi = %s AND question_fi = %s",
3424  array('integer','integer'),
3425  array($active_id, $question_id)
3426  );
3427  if ($result->numRows() == 1)
3428  {
3429  $row = $ilDB->fetchAssoc($result);
3430  return $row["maxpass"];
3431  }
3432  else
3433  {
3434  return 0;
3435  }
3436  }
3437 
3446  public static function _isWriteable($question_id, $user_id)
3447  {
3448  global $ilDB;
3449 
3450  if (($question_id < 1) || ($user_id < 1))
3451  {
3452  return false;
3453  }
3454 
3455  $result = $ilDB->queryF("SELECT obj_fi FROM qpl_questions WHERE question_id = %s",
3456  array('integer'),
3457  array($question_id)
3458  );
3459  if ($result->numRows() == 1)
3460  {
3461  $row = $ilDB->fetchAssoc($result);
3462  $qpl_object_id = $row["obj_fi"];
3463  include_once "./Modules/TestQuestionPool/classes/class.ilObjQuestionPool.php";
3464  return ilObjQuestionPool::_isWriteable($qpl_object_id, $user_id);
3465  }
3466  else
3467  {
3468  return false;
3469  }
3470  }
3471 
3478  public static function _isUsedInRandomTest($question_id = "")
3479  {
3480  global $ilDB;
3481 
3482  if ($question_id < 1) return 0;
3483  $result = $ilDB->queryF("SELECT test_random_question_id FROM tst_test_rnd_qst WHERE question_fi = %s",
3484  array('integer'),
3485  array($question_id)
3486  );
3487  return $result->numRows();
3488  }
3489 
3501  abstract public function calculateReachedPoints($active_id, $pass = NULL, $authorizedSolution = true, $returndetails = FALSE);
3502 
3503  public function deductHintPointsFromReachedPoints(ilAssQuestionPreviewSession $previewSession, $reachedPoints)
3504  {
3505  global $DIC;
3506 
3507  $hintTracking = new ilAssQuestionPreviewHintTracking($DIC->database(), $previewSession);
3508  $requestsStatisticData = $hintTracking->getRequestStatisticData();
3509  $reachedPoints = $reachedPoints - $requestsStatisticData->getRequestsPoints();
3510 
3511  return $reachedPoints;
3512  }
3513 
3515  {
3516  $reachedPoints = $this->calculateReachedPointsForSolution($previewSession->getParticipantsSolution());
3517  $reachedPoints = $this->deductHintPointsFromReachedPoints($previewSession, $reachedPoints);
3518 
3519  return $this->ensureNonNegativePoints($reachedPoints);
3520  }
3521 
3522  protected function ensureNonNegativePoints($points)
3523  {
3524  return $points > 0 ? $points : 0;
3525  }
3526 
3528  {
3529  $reachedPoints = $this->calculateReachedPointsFromPreviewSession($previewSession);
3530 
3531  if( $reachedPoints < $this->getMaximumPoints() )
3532  {
3533  return false;
3534  }
3535 
3536  return true;
3537  }
3538 
3539 
3550  final public function adjustReachedPointsByScoringOptions($points, $active_id, $pass = NULL)
3551  {
3552  include_once "./Modules/Test/classes/class.ilObjTest.php";
3553  $count_system = ilObjTest::_getCountSystem($active_id);
3554  if ($count_system == 1)
3555  {
3556  if (abs($this->getMaximumPoints() - $points) > 0.0000000001)
3557  {
3558  $points = 0;
3559  }
3560  }
3561  $score_cutting = ilObjTest::_getScoreCutting($active_id);
3562  if ($score_cutting == 0)
3563  {
3564  if ($points < 0)
3565  {
3566  $points = 0;
3567  }
3568  }
3569  return $points;
3570  }
3571 
3580  public static function _isWorkedThrough($active_id, $question_id, $pass = NULL)
3581  {
3582  return self::lookupResultRecordExist($active_id, $question_id, $pass);
3583 
3584  // oldschool "workedthru"
3585 
3586  global $ilDB;
3587 
3588  $points = 0;
3589  if (is_null($pass))
3590  {
3591  include_once "./Modules/TestQuestionPool/classes/class.assQuestion.php";
3592  $pass = assQuestion::_getSolutionMaxPass($question_id, $active_id);
3593  }
3594  $result = $ilDB->queryF("SELECT solution_id FROM tst_solutions WHERE active_fi = %s AND question_fi = %s AND pass = %s",
3595  array('integer','integer','integer'),
3596  array($active_id, $question_id, $pass)
3597  );
3598  if ($result->numRows())
3599  {
3600  return TRUE;
3601  }
3602  else
3603  {
3604  return FALSE;
3605  }
3606  }
3607 
3615  public static function _areAnswered($a_user_id,$a_question_ids)
3616  {
3617  global $ilDB;
3618 
3619  $res = $ilDB->queryF("SELECT DISTINCT(question_fi) FROM tst_test_result JOIN tst_active ".
3620  "ON (active_id = active_fi) ".
3621  "WHERE " . $ilDB->in('question_fi', $a_question_ids, false, 'integer') .
3622  " AND user_fi = %s",
3623  array('integer'),
3624  array($a_user_id)
3625  );
3626  return ($res->numRows() == count($a_question_ids)) ? true : false;
3627  }
3628 
3637  function isHTML($a_text)
3638  {
3639  return ilUtil::isHTML($a_text);
3640  }
3641 
3648  function prepareTextareaOutput($txt_output, $prepare_for_latex_output = FALSE, $omitNl2BrWhenTextArea = false)
3649  {
3650  include_once "./Services/Utilities/classes/class.ilUtil.php";
3651  return ilUtil::prepareTextareaOutput($txt_output, $prepare_for_latex_output, $omitNl2BrWhenTextArea);
3652  }
3653 
3661  function QTIMaterialToString($a_material)
3662  {
3663  $result = "";
3664  for ($i = 0; $i < $a_material->getMaterialCount(); $i++)
3665  {
3666  $material = $a_material->getMaterial($i);
3667  if (strcmp($material["type"], "mattext") == 0)
3668  {
3669  $result .= $material["material"]->getContent();
3670  }
3671  if (strcmp($material["type"], "matimage") == 0)
3672  {
3673  $matimage = $material["material"];
3674  if (preg_match("/(il_([0-9]+)_mob_([0-9]+))/", $matimage->getLabel(), $matches))
3675  {
3676  // import an mediaobject which was inserted using tiny mce
3677  if (!is_array($_SESSION["import_mob_xhtml"])) $_SESSION["import_mob_xhtml"] = array();
3678  array_push($_SESSION["import_mob_xhtml"], array("mob" => $matimage->getLabel(), "uri" => $matimage->getUri()));
3679  }
3680  }
3681  }
3682  return $result;
3683  }
3684 
3693  function addQTIMaterial(&$a_xml_writer, $a_material, $close_material_tag = TRUE, $add_mobs = TRUE)
3694  {
3695  include_once "./Services/RTE/classes/class.ilRTE.php";
3696  include_once("./Services/MediaObjects/classes/class.ilObjMediaObject.php");
3697 
3698  $a_xml_writer->xmlStartTag("material");
3699  $attrs = array(
3700  "texttype" => "text/plain"
3701  );
3702  if ($this->isHTML($a_material))
3703  {
3704  $attrs["texttype"] = "text/xhtml";
3705  }
3706  $a_xml_writer->xmlElement("mattext", $attrs, ilRTE::_replaceMediaObjectImageSrc($a_material, 0));
3707  if ($add_mobs)
3708  {
3709  $mobs = ilObjMediaObject::_getMobsOfObject("qpl:html", $this->getId());
3710  foreach ($mobs as $mob)
3711  {
3712  $moblabel = "il_" . IL_INST_ID . "_mob_" . $mob;
3713  if (strpos($a_material, "mm_$mob") !== FALSE)
3714  {
3715  if (ilObjMediaObject::_exists($mob))
3716  {
3717  $mob_obj = new ilObjMediaObject($mob);
3718  $imgattrs = array(
3719  "label" => $moblabel,
3720  "uri" => "objects/" . "il_" . IL_INST_ID . "_mob_" . $mob . "/" . $mob_obj->getTitle()
3721  );
3722  }
3723  $a_xml_writer->xmlElement("matimage", $imgattrs, NULL);
3724  }
3725  }
3726  }
3727  if ($close_material_tag) $a_xml_writer->xmlEndTag("material");
3728  }
3729 
3730  function buildHashedImageFilename($plain_image_filename, $unique = false)
3731  {
3732  $extension = "";
3733 
3734  if (preg_match("/.*\.(png|jpg|gif|jpeg)$/i", $plain_image_filename, $matches))
3735  {
3736  $extension = "." . $matches[1];
3737  }
3738 
3739  if($unique)
3740  {
3741  $plain_image_filename = uniqid($plain_image_filename.microtime(true));
3742  }
3743 
3744  $hashed_filename = md5($plain_image_filename) . $extension;
3745 
3746  return $hashed_filename;
3747  }
3748 
3759  public static function _setReachedPoints($active_id, $question_id, $points, $maxpoints, $pass, $manualscoring, $obligationsEnabled)
3760  {
3761  global $ilDB;
3762 
3763  if ($points <= $maxpoints)
3764  {
3765  if (is_null($pass))
3766  {
3767  $pass = assQuestion::_getSolutionMaxPass($question_id, $active_id);
3768  }
3769 
3770  // retrieve the already given points
3771  $old_points = 0;
3772  $result = $ilDB->queryF("SELECT points FROM tst_test_result WHERE active_fi = %s AND question_fi = %s AND pass = %s",
3773  array('integer','integer','integer'),
3774  array($active_id, $question_id, $pass)
3775  );
3776  $manual = ($manualscoring) ? 1 : 0;
3777  $rowsnum = $result->numRows();
3778  if($rowsnum)
3779  {
3780  $row = $ilDB->fetchAssoc($result);
3781  $old_points = $row["points"];
3782  if($old_points != $points)
3783  {
3784  $affectedRows = $ilDB->manipulateF("UPDATE tst_test_result SET points = %s, manual = %s, tstamp = %s WHERE active_fi = %s AND question_fi = %s AND pass = %s",
3785  array('float', 'integer', 'integer', 'integer', 'integer', 'integer'),
3786  array($points, $manual, time(), $active_id, $question_id, $pass)
3787  );
3788  }
3789  }
3790  else
3791  {
3792  $next_id = $ilDB->nextId('tst_test_result');
3793  $affectedRows = $ilDB->manipulateF("INSERT INTO tst_test_result (test_result_id, active_fi, question_fi, points, pass, manual, tstamp) VALUES (%s, %s, %s, %s, %s, %s, %s)",
3794  array('integer', 'integer','integer', 'float', 'integer', 'integer','integer'),
3795  array($next_id, $active_id, $question_id, $points, $pass, $manual, time())
3796  );
3797  }
3798 
3799  if(self::isForcePassResultUpdateEnabled() || $old_points != $points || !$rowsnum)
3800  {
3801  assQuestion::_updateTestPassResults($active_id, $pass, $obligationsEnabled);
3802  // finally update objective result
3803  include_once "./Modules/Test/classes/class.ilObjTest.php";
3804  include_once './Modules/Course/classes/class.ilCourseObjectiveResult.php';
3806 
3807  include_once ("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
3809  {
3810  global $lng, $ilUser;
3811  include_once "./Modules/Test/classes/class.ilObjTestAccess.php";
3812  $username = ilObjTestAccess::_getParticipantData($active_id);
3813  assQuestion::logAction(sprintf($lng->txtlng("assessment", "log_answer_changed_points", ilObjAssessmentFolder::_getLogLanguage()), $username, $old_points, $points, $ilUser->getFullname() . " (" . $ilUser->getLogin() . ")"), $active_id, $question_id);
3814  }
3815  }
3816 
3817  return TRUE;
3818  }
3819  else
3820  {
3821  return FALSE;
3822  }
3823  }
3824 
3832  function getQuestion()
3833  {
3834  return $this->question;
3835  }
3836 
3844  function setQuestion($question = "")
3845  {
3846  $this->question = $question;
3847  }
3848 
3854  abstract public function getQuestionType();
3855 
3865  {
3866  global $ilDB;
3867 
3868  $result = $ilDB->queryF("SELECT question_type_id FROM qpl_qst_type WHERE type_tag = %s",
3869  array('text'),
3870  array($this->getQuestionType())
3871  );
3872  if ($result->numRows() == 1)
3873  {
3874  $row = $ilDB->fetchAssoc($result);
3875  return $row["question_type_id"];
3876  }
3877  return 0;
3878  }
3879 
3880  public function syncHints()
3881  {
3882  global $ilDB;
3883 
3884  // delete hints of the original
3885  $ilDB->manipulateF("DELETE FROM qpl_hints WHERE qht_question_fi = %s",
3886  array('integer'),
3887  array($this->original_id)
3888  );
3889 
3890  // get hints of the actual question
3891  $result = $ilDB->queryF("SELECT * FROM qpl_hints WHERE qht_question_fi = %s",
3892  array('integer'),
3893  array($this->getId())
3894  );
3895 
3896  // save hints to the original
3897  if ($result->numRows())
3898  {
3899  while ($row = $ilDB->fetchAssoc($result))
3900  {
3901  $next_id = $ilDB->nextId('qpl_hints');
3903  $ilDB->insert('qpl_hints', array(
3904  'qht_hint_id' => array('integer', $next_id),
3905  'qht_question_fi' => array('integer', $this->original_id),
3906  'qht_hint_index' => array('integer', $row["qht_hint_index"]),
3907  'qht_hint_points' => array('integer', $row["qht_hint_points"]),
3908  'qht_hint_text' => array('text', $row["qht_hint_text"]),
3909  )
3910  );
3911  }
3912  }
3913  }
3914 
3919  protected function getRTETextWithMediaObjects()
3920  {
3921  // must be called in parent classes. add additional RTE text in the parent
3922  // classes and call this method to add the standard RTE text
3923  $collected = $this->getQuestion();
3924  $collected .= $this->feedbackOBJ->getGenericFeedbackContent($this->getId(), false);
3925  $collected .= $this->feedbackOBJ->getGenericFeedbackContent($this->getId(), true);
3926  $collected .= $this->feedbackOBJ->getAllSpecificAnswerFeedbackContents($this->getId());
3927 
3928  foreach ($this->suggested_solutions as $solution_array)
3929  {
3930  $collected .= $solution_array["value"];
3931  }
3932 
3933  require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionHintList.php';
3934  $questionHintList = ilAssQuestionHintList::getListByQuestionId($this->getId());
3935  foreach($questionHintList as $questionHint)
3936  {
3937  /* @var $questionHint ilAssQuestionHint */
3938  $collected .= $questionHint->getText();
3939  }
3940 
3941  return $collected;
3942  }
3943 
3949  {
3950  $combinedtext = $this->getRTETextWithMediaObjects();
3951  include_once("./Services/RTE/classes/class.ilRTE.php");
3952  ilRTE::_cleanupMediaObjectUsage($combinedtext, "qpl:html", $this->getId());
3953  }
3954 
3960  function &getInstances()
3961  {
3962  global $ilDB;
3963 
3964  $result = $ilDB->queryF("SELECT question_id FROM qpl_questions WHERE original_id = %s",
3965  array("integer"),
3966  array($this->getId())
3967  );
3968  $instances = array();
3969  $ids = array();
3970  while ($row = $ilDB->fetchAssoc($result))
3971  {
3972  array_push($ids, $row["question_id"]);
3973  }
3974  foreach ($ids as $question_id)
3975  {
3976  // check non random tests
3977  $result = $ilDB->queryF("SELECT tst_tests.obj_fi FROM tst_tests, tst_test_question WHERE tst_test_question.question_fi = %s AND tst_test_question.test_fi = tst_tests.test_id",
3978  array("integer"),
3979  array($question_id)
3980  );
3981  while ($row = $ilDB->fetchAssoc($result))
3982  {
3983  $instances[$row['obj_fi']] = ilObject::_lookupTitle($row['obj_fi']);
3984  }
3985  // check random tests
3986  $result = $ilDB->queryF("SELECT tst_tests.obj_fi FROM tst_tests, tst_test_rnd_qst, tst_active WHERE tst_test_rnd_qst.active_fi = tst_active.active_id AND tst_test_rnd_qst.question_fi = %s AND tst_tests.test_id = tst_active.test_fi",
3987  array("integer"),
3988  array($question_id)
3989  );
3990  while ($row = $ilDB->fetchAssoc($result))
3991  {
3992  $instances[$row['obj_fi']] = ilObject::_lookupTitle($row['obj_fi']);
3993  }
3994  }
3995  include_once "./Modules/Test/classes/class.ilObjTest.php";
3996  foreach ($instances as $key => $value)
3997  {
3998  $instances[$key] = array("obj_id" => $key, "title" => $value, "author" => ilObjTest::_lookupAuthor($key), "refs" => ilObject::_getAllReferences($key));
3999  }
4000  return $instances;
4001  }
4002 
4003  public static function _needsManualScoring($question_id)
4004  {
4005  include_once "./Modules/Test/classes/class.ilObjAssessmentFolder.php";
4007  $questiontype = assQuestion::_getQuestionType($question_id);
4008  if (in_array($questiontype, $scoring))
4009  {
4010  return TRUE;
4011  }
4012  else
4013  {
4014  return FALSE;
4015  }
4016  }
4017 
4025  function getActiveUserData($active_id)
4026  {
4027  global $ilDB;
4028  $result = $ilDB->queryF("SELECT * FROM tst_active WHERE active_id = %s",
4029  array('integer'),
4030  array($active_id)
4031  );
4032  if ($result->numRows())
4033  {
4034  $row = $ilDB->fetchAssoc($result);
4035  return array("user_id" => $row["user_fi"], "test_id" => $row["test_fi"]);
4036  }
4037  else
4038  {
4039  return array();
4040  }
4041  }
4042 
4050  static function _includeClass($question_type, $gui = 0)
4051  {
4052  if( self::isCoreQuestionType($question_type) )
4053  {
4054  self::includeCoreClass($question_type, $gui);
4055  }
4056  else
4057  {
4058  self::includePluginClass($question_type, $gui);
4059  }
4060  }
4061 
4062  public static function getGuiClassNameByQuestionType($questionType)
4063  {
4064  return $questionType.'GUI';
4065  }
4066 
4067  public static function getObjectClassNameByQuestionType($questionType)
4068  {
4069  return $questionType;
4070  }
4071 
4072  public static function getFeedbackClassNameByQuestionType($questionType)
4073  {
4074  return str_replace('ass', 'ilAss', $questionType).'Feedback';
4075  }
4076 
4077  public static function isCoreQuestionType($questionType)
4078  {
4079  $guiClassName = self::getGuiClassNameByQuestionType($questionType);
4080  return file_exists("Modules/TestQuestionPool/classes/class.{$guiClassName}.php");
4081  }
4082 
4083  public static function includeCoreClass($questionType, $withGuiClass)
4084  {
4085  if( $withGuiClass )
4086  {
4087  $guiClassName = self::getGuiClassNameByQuestionType($questionType);
4088  require_once "Modules/TestQuestionPool/classes/class.{$guiClassName}.php";
4089 
4090  // object class is included by gui classes constructor
4091  }
4092  else
4093  {
4094  $objectClassName = self::getObjectClassNameByQuestionType($questionType);
4095  require_once "Modules/TestQuestionPool/classes/class.{$objectClassName}.php";
4096  }
4097 
4098  $feedbackClassName = self::getFeedbackClassNameByQuestionType($questionType);
4099  require_once "Modules/TestQuestionPool/classes/feedback/class.{$feedbackClassName}.php";
4100  }
4101 
4102  public static function includePluginClass($questionType, $withGuiClass)
4103  {
4104  global $ilPluginAdmin;
4105 
4106  $classes = array(
4107  self::getObjectClassNameByQuestionType($questionType),
4108  self::getFeedbackClassNameByQuestionType($questionType)
4109  );
4110 
4111  if( $withGuiClass )
4112  {
4113  $classes[] = self::getGuiClassNameByQuestionType($questionType);
4114  }
4115 
4116  $pl_names = $ilPluginAdmin->getActivePluginsForSlot(IL_COMP_MODULE, "TestQuestionPool", "qst");
4117  foreach ($pl_names as $pl_name)
4118  {
4119  $pl = ilPlugin::getPluginObject(IL_COMP_MODULE, "TestQuestionPool", "qst", $pl_name);
4120  if (strcmp($pl->getQuestionType(), $questionType) == 0)
4121  {
4122  foreach($classes as $class)
4123  {
4124  $pl->includeClass("class.{$class}.php");
4125  }
4126 
4127  break;
4128  }
4129  }
4130  }
4131 
4138  static function _getQuestionTypeName($type_tag)
4139  {
4140  if (file_exists("./Modules/TestQuestionPool/classes/class.".$type_tag.".php"))
4141  {
4142  global $lng;
4143  return $lng->txt($type_tag);
4144  }
4145  else
4146  {
4147  global $ilPluginAdmin;
4148  $pl_names = $ilPluginAdmin->getActivePluginsForSlot(IL_COMP_MODULE, "TestQuestionPool", "qst");
4149  foreach ($pl_names as $pl_name)
4150  {
4151  $pl = ilPlugin::getPluginObject(IL_COMP_MODULE, "TestQuestionPool", "qst", $pl_name);
4152  if (strcmp($pl->getQuestionType(), $type_tag) == 0)
4153  {
4154  return $pl->getQuestionTypeTranslation();
4155  }
4156  }
4157  }
4158  return "";
4159  }
4160 
4170  public static function &_instanciateQuestionGUI($question_id)
4171  {
4172  return self::instantiateQuestionGUI($question_id);
4173  }
4174 
4182  public static function instantiateQuestionGUI($a_question_id)
4183  {
4184  global $ilCtrl, $ilDB, $lng, $ilUser;
4185 
4186  if (strcmp($a_question_id, "") != 0)
4187  {
4188  $question_type = assQuestion::_getQuestionType($a_question_id);
4189 
4190  assQuestion::_includeClass($question_type, 1);
4191 
4192  $question_type_gui = self::getGuiClassNameByQuestionType($question_type);
4193  $question_gui = new $question_type_gui();
4194  $question_gui->object->loadFromDb($a_question_id);
4195 
4196  $feedbackObjectClassname = self::getFeedbackClassNameByQuestionType($question_type);
4197  $question_gui->object->feedbackOBJ = new $feedbackObjectClassname($question_gui->object, $ilCtrl, $ilDB, $lng);
4198 
4199  $assSettings = new ilSetting('assessment');
4200  require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionProcessLockerFactory.php';
4201  $processLockerFactory = new ilAssQuestionProcessLockerFactory($assSettings, $ilDB);
4202  $processLockerFactory->setQuestionId($question_gui->object->getId());
4203  $processLockerFactory->setUserId($ilUser->getId());
4204  include_once ("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
4205  $processLockerFactory->setAssessmentLogEnabled(ilObjAssessmentFolder::_enabledAssessmentLogging());
4206  $question_gui->object->setProcessLocker($processLockerFactory->getLocker());
4207  }
4208  else
4209  {
4210  global $ilLog;
4211  $ilLog->write('Instantiate question called without question id. (instantiateQuestionGUI@assQuestion)', $ilLog->WARNING);
4212  return null;
4213  }
4214  return $question_gui;
4215  }
4216 
4227  public function setExportDetailsXLS($worksheet, $startrow, $active_id, $pass)
4228  {
4229  $worksheet->setFormattedExcelTitle($worksheet->getColumnCoord(0) . $startrow, $this->lng->txt($this->getQuestionType()));
4230  $worksheet->setFormattedExcelTitle($worksheet->getColumnCoord(1) . $startrow, $this->getTitle());
4231 
4232  return $startrow;
4233  }
4234 
4238  public function __get($value)
4239  {
4240  switch ($value)
4241  {
4242  case "id":
4243  return $this->getId();
4244  break;
4245  case "title":
4246  return $this->getTitle();
4247  break;
4248  case "comment":
4249  return $this->getComment();
4250  break;
4251  case "owner":
4252  return $this->getOwner();
4253  break;
4254  case "author":
4255  return $this->getAuthor();
4256  break;
4257  case "question":
4258  return $this->getQuestion();
4259  break;
4260  case "points":
4261  return $this->getPoints();
4262  break;
4263  case "est_working_time":
4264  return $this->getEstimatedWorkingTime();
4265  break;
4266  case "shuffle":
4267  return $this->getShuffle();
4268  break;
4269  case "test_id":
4270  return $this->getTestId();
4271  break;
4272  case "obj_id":
4273  return $this->getObjId();
4274  break;
4275  case "ilias":
4276  return $this->ilias;
4277  break;
4278  case "tpl":
4279  return $this->tpl;
4280  break;
4281  case "page":
4282  return $this->page;
4283  break;
4284  case "outputType":
4285  return $this->getOutputType();
4286  break;
4287  case "suggested_solutions":
4288  return $this->getSuggestedSolutions();
4289  break;
4290  case "original_id":
4291  return $this->getOriginalId();
4292  break;
4293  default:
4294  if (array_key_exists($value, $this->arrData))
4295  {
4296  return $this->arrData[$value];
4297  }
4298  else
4299  {
4300  return null;
4301  }
4302  break;
4303  }
4304  }
4305 
4309  public function __set($key, $value)
4310  {
4311  switch ($key)
4312  {
4313  case "id":
4314  $this->setId($value);
4315  break;
4316  case "title":
4317  $this->setTitle($value);
4318  break;
4319  case "comment":
4320  $this->setComment($value);
4321  break;
4322  case "owner":
4323  $this->setOwner($value);
4324  break;
4325  case "author":
4326  $this->setAuthor($value);
4327  break;
4328  case "question":
4329  $this->setQuestion($value);
4330  break;
4331  case "points":
4332  $this->setPoints($value);
4333  break;
4334  case "est_working_time":
4335  if (is_array($value))
4336  {
4337  $this->setEstimatedWorkingTime($value["h"], $value["m"], $value["s"]);
4338  }
4339  break;
4340  case "shuffle":
4341  $this->setShuffle($value);
4342  break;
4343  case "test_id":
4344  $this->setTestId($value);
4345  break;
4346  case "obj_id":
4347  $this->setObjId($value);
4348  break;
4349  case "outputType":
4350  $this->setOutputType($value);
4351  break;
4352  case "original_id":
4353  $this->setOriginalId($value);
4354  break;
4355  case "page":
4356  $this->page =& $value;
4357  break;
4358  default:
4359  $this->arrData[$key] = $value;
4360  break;
4361  }
4362  }
4363 
4364  public function getNrOfTries()
4365  {
4366  return (int)$this->nr_of_tries;
4367  }
4368 
4369  public function setNrOfTries($a_nr_of_tries)
4370  {
4371  $this->nr_of_tries = $a_nr_of_tries;
4372  }
4373 
4374  public function setExportImagePath($a_path)
4375  {
4376  $this->export_image_path = (string)$a_path;
4377  }
4378 
4379  public static function _questionExistsInTest($question_id, $test_id)
4380  {
4381  global $ilDB;
4382 
4383  if ($question_id < 1)
4384  {
4385  return false;
4386  }
4387 
4388  $result = $ilDB->queryF("SELECT question_fi FROM tst_test_question WHERE question_fi = %s AND test_fi = %s",
4389  array('integer', 'integer'),
4390  array($question_id, $test_id)
4391  );
4392  if ($result->numRows() == 1)
4393  {
4394  return true;
4395  }
4396  else
4397  {
4398  return false;
4399  }
4400  }
4401 
4408  public function formatSAQuestion($a_q)
4409  {
4410  return $this->getSelfAssessmentFormatter()->format($a_q);
4411  }
4412 
4416  protected function getSelfAssessmentFormatter()
4417  {
4418  require_once 'Modules/TestQuestionPool/classes/questions/class.ilAssSelfAssessmentQuestionFormatter.php';
4419  return new \ilAssSelfAssessmentQuestionFormatter();
4420  }
4421 
4422  // scorm2004-start ???
4423 
4429  function setPreventRteUsage($a_val)
4430  {
4431  $this->prevent_rte_usage = $a_val;
4432  }
4433 
4440  {
4441  return $this->prevent_rte_usage;
4442  }
4443 
4448  {
4449  $this->lmMigrateQuestionTypeGenericContent($migrator);
4450  $this->lmMigrateQuestionTypeSpecificContent($migrator);
4451  $this->saveToDb();
4452 
4453  $this->feedbackOBJ->migrateContentForLearningModule($migrator, $this->getId());
4454  }
4455 
4460  {
4461  $this->setQuestion( $migrator->migrateToLmContent( $this->getQuestion() ) );
4462  }
4463 
4468  {
4469  // overwrite if any question type specific content except feedback needs to be migrated
4470  }
4471 
4477  function setSelfAssessmentEditingMode($a_selfassessmenteditingmode)
4478  {
4479  $this->selfassessmenteditingmode = $a_selfassessmenteditingmode;
4480  }
4481 
4488  {
4490  }
4491 
4497  function setDefaultNrOfTries($a_defaultnroftries)
4498  {
4499  $this->defaultnroftries = $a_defaultnroftries;
4500  }
4501 
4508  {
4509  return (int)$this->defaultnroftries;
4510  }
4511 
4512  // scorm2004-end ???
4513 
4519  public static function lookupParentObjId($questionId)
4520  {
4521  global $ilDB;
4522 
4523  $query = "SELECT obj_fi FROM qpl_questions WHERE question_id = %s";
4524 
4525  $res = $ilDB->queryF($query, array('integer'), array((int)$questionId));
4526  $row = $ilDB->fetchAssoc($res);
4527 
4528  return $row['obj_fi'];
4529  }
4530 
4541  public static function lookupOriginalParentObjId($originalQuestionId)
4542  {
4543  return self::lookupParentObjId($originalQuestionId);
4544  }
4545 
4546  protected function duplicateQuestionHints($originalQuestionId, $duplicateQuestionId)
4547  {
4548  require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionHintList.php';
4549  $hintIds = ilAssQuestionHintList::duplicateListForQuestion($originalQuestionId, $duplicateQuestionId);
4550 
4552  {
4553  require_once 'Modules/TestQuestionPool/classes/class.ilAssHintPage.php';
4554 
4555  foreach($hintIds as $originalHintId => $duplicateHintId)
4556  {
4557  $originalPageObject = new ilAssHintPage($originalHintId);
4558  $originalXML = $originalPageObject->getXMLContent();
4559 
4560  $duplicatePageObject = new ilAssHintPage();
4561  $duplicatePageObject->setId($duplicateHintId);
4562  $duplicatePageObject->setParentId($this->getId());
4563  $duplicatePageObject->setXMLContent($originalXML);
4564  $duplicatePageObject->createFromXML();
4565  }
4566  }
4567  }
4568 
4569  protected function duplicateSkillAssignments($srcParentId, $srcQuestionId, $trgParentId, $trgQuestionId)
4570  {
4571  global $ilDB;
4572 
4573  require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionSkillAssignmentList.php';
4574  $assignmentList = new ilAssQuestionSkillAssignmentList($ilDB);
4575  $assignmentList->setParentObjId($srcParentId);
4576  $assignmentList->setQuestionIdFilter($srcQuestionId);
4577  $assignmentList->loadFromDb();
4578 
4579  foreach($assignmentList->getAssignmentsByQuestionId($srcQuestionId) as $assignment)
4580  {
4581  /* @var ilAssQuestionSkillAssignment $assignment */
4582 
4583  $assignment->setParentObjId($trgParentId);
4584  $assignment->setQuestionId($trgQuestionId);
4585  $assignment->saveToDb();
4586  }
4587  }
4588 
4589  public function syncSkillAssignments($srcParentId, $srcQuestionId, $trgParentId, $trgQuestionId)
4590  {
4591  global $ilDB;
4592 
4593  require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionSkillAssignmentList.php';
4594  $assignmentList = new ilAssQuestionSkillAssignmentList($ilDB);
4595  $assignmentList->setParentObjId($trgParentId);
4596  $assignmentList->setQuestionIdFilter($trgQuestionId);
4597  $assignmentList->loadFromDb();
4598 
4599  foreach($assignmentList->getAssignmentsByQuestionId($trgQuestionId) as $assignment)
4600  {
4601  /* @var ilAssQuestionSkillAssignment $assignment */
4602 
4603  $assignment->deleteFromDb();
4604  }
4605 
4606  $this->duplicateSkillAssignments($srcParentId, $srcQuestionId, $trgParentId, $trgQuestionId);
4607  }
4608 
4621  public function isAnswered($active_id, $pass = null)
4622  {
4623  return true;
4624  }
4625 
4638  public static function isObligationPossible($questionId)
4639  {
4640  return false;
4641  }
4642 
4643  public function isAutosaveable()
4644  {
4645  return TRUE;
4646  }
4647 
4660  protected static function getNumExistingSolutionRecords($activeId, $pass, $questionId)
4661  {
4662  global $ilDB;
4663 
4664  $query = "
4665  SELECT count(active_fi) cnt
4666 
4667  FROM tst_solutions
4668 
4669  WHERE active_fi = %s
4670  AND question_fi = %s
4671  AND pass = %s
4672  ";
4673 
4674  $res = $ilDB->queryF(
4675  $query, array('integer','integer','integer'),
4676  array($activeId, $questionId, $pass)
4677  );
4678 
4679  $row = $ilDB->fetchAssoc($res);
4680 
4681  return (int)$row['cnt'];
4682  }
4683 
4691  {
4693  }
4694 
4702  {
4704  {
4705  require_once 'Modules/TestQuestionPool/exceptions/class.ilTestQuestionPoolException.php';
4706  throw new ilTestQuestionPoolException('invalid additional content editing mode given: '.$additinalContentEditingMode);
4707  }
4708 
4709  $this->additinalContentEditingMode = $additinalContentEditingMode;
4710  }
4711 
4719  {
4721  }
4722 
4730  public function isValidAdditionalContentEditingMode($additionalContentEditingMode)
4731  {
4732  if( in_array($additionalContentEditingMode, $this->getValidAdditionalContentEditingModes()) )
4733  {
4734  return true;
4735  }
4736 
4737  return false;
4738  }
4739 
4747  {
4748  return array(
4749  self::ADDITIONAL_CONTENT_EDITING_MODE_DEFAULT,
4750  self::ADDITIONAL_CONTENT_EDITING_MODE_PAGE_OBJECT
4751  );
4752  }
4753 
4758  {
4759  $this->questionChangeListeners[] = $listener;
4760  }
4761 
4765  public function getQuestionChangeListeners()
4766  {
4768  }
4769 
4770  private function notifyQuestionCreated()
4771  {
4772  foreach($this->getQuestionChangeListeners() as $listener)
4773  {
4774  $listener->notifyQuestionCreated($this);
4775  }
4776  }
4777 
4778  private function notifyQuestionEdited()
4779  {
4780  foreach($this->getQuestionChangeListeners() as $listener)
4781  {
4782  $listener->notifyQuestionEdited($this);
4783  }
4784  }
4785 
4786  private function notifyQuestionDeleted()
4787  {
4788  foreach($this->getQuestionChangeListeners() as $listener)
4789  {
4790  $listener->notifyQuestionDeleted($this);
4791  }
4792  }
4793 
4798  {
4799  require_once 'Services/Html/classes/class.ilHtmlPurifierFactory.php';
4800  return ilHtmlPurifierFactory::_getInstanceByType('qpl_usersolution');
4801  }
4802 
4807  {
4808  require_once 'Services/Html/classes/class.ilHtmlPurifierFactory.php';
4809  return ilHtmlPurifierFactory::_getInstanceByType('qpl_usersolution');
4810  }
4811 
4812  protected function buildQuestionDataQuery()
4813  {
4814  return "
4815  SELECT qpl_questions.*,
4816  {$this->getAdditionalTableName()}.*
4817  FROM qpl_questions
4818  LEFT JOIN {$this->getAdditionalTableName()}
4819  ON {$this->getAdditionalTableName()}.question_fi = qpl_questions.question_id
4820  WHERE qpl_questions.question_id = %s
4821  ";
4822  }
4823 
4824  public function setLastChange($lastChange)
4825  {
4826  $this->lastChange = $lastChange;
4827  }
4828 
4829  public function getLastChange()
4830  {
4831  return $this->lastChange;
4832  }
4833 
4844  protected function getCurrentSolutionResultSet($active_id, $pass, $authorized = true)
4845  {
4846  global $ilDB;
4847 
4848  if($this->getStep() !== NULL)
4849  {
4850  $query = "
4851  SELECT *
4852  FROM tst_solutions
4853  WHERE active_fi = %s
4854  AND question_fi = %s
4855  AND pass = %s
4856  AND step = %s
4857  AND authorized = %s
4858  ";
4859 
4860  return $ilDB->queryF($query, array('integer', 'integer', 'integer', 'integer', 'integer'),
4861  array($active_id, $this->getId(), $pass, $this->getStep(), (int)$authorized)
4862  );
4863  }
4864  else
4865  {
4866  $query = "
4867  SELECT *
4868  FROM tst_solutions
4869  WHERE active_fi = %s
4870  AND question_fi = %s
4871  AND pass = %s
4872  AND authorized = %s
4873  ";
4874 
4875  return $ilDB->queryF($query, array('integer', 'integer', 'integer', 'integer'),
4876  array($active_id, $this->getId(), $pass, (int)$authorized)
4877  );
4878  }
4879 
4880  }
4881 
4888  protected function removeSolutionRecordById($solutionId)
4889  {
4890  global $ilDB;
4891 
4892  return $ilDB->manipulateF("DELETE FROM tst_solutions WHERE solution_id = %s",
4893  array('integer'), array($solutionId)
4894  );
4895  }
4896 
4897  // hey: prevPassSolutions - selected file reuse, copy solution records
4904  protected function getSolutionRecordById($solutionId)
4905  {
4906  $ilDB = isset($GLOBALS['DIC']) ? $GLOBALS['DIC']['ilDB'] : $GLOBALS['ilDB'];
4907 
4908  $res = $ilDB->queryF("SELECT * FROM tst_solutions WHERE solution_id = %s",
4909  array('integer'), array($solutionId)
4910  );
4911 
4912  while($row = $ilDB->fetchAssoc($res))
4913  {
4914  return $row;
4915  }
4916  }
4917  // hey.
4918 
4927  public function removeIntermediateSolution($active_id, $pass)
4928  {
4929  $this->getProcessLocker()->executeUserSolutionUpdateLockOperation(function() use ($active_id, $pass) {
4930  $this->removeCurrentSolution($active_id, $pass, false);
4931  });
4932  }
4933 
4942  public function removeCurrentSolution($active_id, $pass, $authorized = true)
4943  {
4944  global $ilDB;
4945 
4946  if($this->getStep() !== NULL)
4947  {
4948  $query = "
4949  DELETE FROM tst_solutions
4950  WHERE active_fi = %s
4951  AND question_fi = %s
4952  AND pass = %s
4953  AND step = %s
4954  AND authorized = %s
4955  ";
4956 
4957  return $ilDB->manipulateF($query, array('integer', 'integer', 'integer', 'integer', 'integer'),
4958  array($active_id, $this->getId(), $pass, $this->getStep(), (int)$authorized)
4959  );
4960  }
4961  else
4962  {
4963  $query = "
4964  DELETE FROM tst_solutions
4965  WHERE active_fi = %s
4966  AND question_fi = %s
4967  AND pass = %s
4968  AND authorized = %s
4969  ";
4970 
4971  return $ilDB->manipulateF($query, array('integer', 'integer', 'integer', 'integer'),
4972  array($active_id, $this->getId(), $pass, (int)$authorized)
4973  );
4974  }
4975  }
4976 
4977 // fau: testNav - add timestamp as parameter to saveCurrentSolution
4989  public function saveCurrentSolution($active_id, $pass, $value1, $value2, $authorized = true, $tstamp = null)
4990  {
4991  global $ilDB;
4992 
4993  $next_id = $ilDB->nextId("tst_solutions");
4994 
4995  $fieldData = array(
4996  "solution_id" => array("integer", $next_id),
4997  "active_fi" => array("integer", $active_id),
4998  "question_fi" => array("integer", $this->getId()),
4999  "value1" => array("clob", $value1),
5000  "value2" => array("clob", $value2),
5001  "pass" => array("integer", $pass),
5002  "tstamp" => array("integer", isset($tstamp) ? $tstamp : time()),
5003  'authorized' => array('integer', (int)$authorized)
5004  );
5005 
5006  if( $this->getStep() !== null )
5007  {
5008  $fieldData['step'] = array("integer", $this->getStep());
5009  }
5010 
5011  return $ilDB->insert("tst_solutions", $fieldData);
5012  }
5013 // fau.
5014 
5025  public function updateCurrentSolution($solutionId, $value1, $value2, $authorized = true)
5026  {
5027  global $ilDB;
5028 
5029  $fieldData = array(
5030  "value1" => array("clob", $value1),
5031  "value2" => array("clob", $value2),
5032  "tstamp" => array("integer", time()),
5033  'authorized' => array('integer', (int)$authorized)
5034  );
5035 
5036  if( $this->getStep() !== null )
5037  {
5038  $fieldData['step'] = array("integer", $this->getStep());
5039  }
5040 
5041  return $ilDB->update("tst_solutions", $fieldData, array(
5042  'solution_id' => array('integer', $solutionId)
5043  ));
5044  }
5045 
5046 // fau: testNav - added parameter to keep the timestamp (default: false)
5047  public function updateCurrentSolutionsAuthorization($activeId, $pass, $authorized, $keepTime = false)
5048  {
5049  global $ilDB;
5050 
5051  $fieldData = array(
5052  'authorized' => array('integer', (int)$authorized)
5053  );
5054 
5055  if (!$keepTime)
5056  {
5057  $fieldData['tstamp'] = array('integer', time());
5058  }
5059 
5060  $whereData = array(
5061  'question_fi' => array('integer', $this->getId()),
5062  'active_fi' => array('integer', $activeId),
5063  'pass' => array('integer', $pass)
5064  );
5065 
5066  if( $this->getStep() !== NULL )
5067  {
5068  $whereData['step'] = array("integer", $this->getStep());
5069  }
5070 
5071  return $ilDB->update('tst_solutions', $fieldData, $whereData);
5072  }
5073  // fau.
5074 
5075  // hey: prevPassSolutions - motivation slowly decreases on imagemap
5077  protected static function getKeyValuesImplosionSeparator()
5078  {
5079  return self::KEY_VALUES_IMPLOSION_SEPARATOR;
5080  }
5081  public static function implodeKeyValues($keyValues)
5082  {
5083  return implode(self::getKeyValuesImplosionSeparator(), $keyValues);
5084  }
5085  public static function explodeKeyValues($keyValues)
5086  {
5087  return explode(self::getKeyValuesImplosionSeparator(), $keyValues);
5088  }
5089 
5090  protected function deleteDummySolutionRecord($activeId, $passIndex)
5091  {
5092  foreach( $this->getSolutionValues($activeId, $passIndex, false) as $solutionRec )
5093  {
5094  if( 0 == strlen($solutionRec['value1']) && 0 == strlen($solutionRec['value2']) )
5095  {
5096  $this->removeSolutionRecordById($solutionRec['solution_id']);
5097  }
5098  }
5099  }
5100 
5101  protected function deleteSolutionRecordByValues($activeId, $passIndex, $authorized, $matchValues)
5102  {
5103  $ilDB = isset($GLOBALS['DIC']) ? $GLOBALS['DIC']['ilDB'] : $GLOBALS['ilDB'];
5104 
5105  $types = array("integer", "integer", "integer", "integer");
5106  $values = array($activeId, $this->getId(), $passIndex, (int)$authorized);
5107  $valuesCondition = array();
5108 
5109  foreach($matchValues as $valueField => $value)
5110  {
5111  switch($valueField)
5112  {
5113  case 'value1':
5114  case 'value2':
5115  $valuesCondition[] = "{$valueField} = %s";
5116  $types[] = 'text';
5117  $values[] = $value;
5118  break;
5119 
5120  default:
5121  require_once 'Modules/TestQuestionPool/exceptions/class.ilTestQuestionPoolException.php';
5122  throw new ilTestQuestionPoolException('invalid value field given: '.$valueField);
5123  }
5124  }
5125 
5126  $valuesCondition = implode(' AND ', $valuesCondition);
5127 
5128  $query = "
5129  DELETE FROM tst_solutions
5130  WHERE active_fi = %s
5131  AND question_fi = %s
5132  AND pass = %s
5133  AND authorized = %s
5134  AND $valuesCondition
5135  ";
5136 
5137  if( $this->getStep() !== NULL )
5138  {
5139  $query .= " AND step = %s ";
5140  $types[] = 'integer';
5141  $values[] = $this->getStep();
5142  }
5143 
5144  $ilDB->manipulateF($query, $types, $values);
5145  }
5146 
5147  protected function duplicateIntermediateSolutionAuthorized($activeId, $passIndex)
5148  {
5149  foreach($this->getSolutionValues($activeId, $passIndex, false) as $rec)
5150  {
5151  $this->saveCurrentSolution($activeId, $passIndex, $rec['value1'], $rec['value2'], true, $rec['tstamp']);
5152  }
5153  }
5154 
5155  protected function forceExistingIntermediateSolution($activeId, $passIndex, $considerDummyRecordCreation)
5156  {
5157  $intermediateSolution = $this->getSolutionValues($activeId, $passIndex, false);
5158 
5159  if( !count($intermediateSolution) )
5160  {
5161  // make the authorized solution intermediate (keeping timestamps)
5162  // this keeps the solution_ids in synch with eventually selected in $_POST['deletefiles']
5163  $this->updateCurrentSolutionsAuthorization($activeId, $passIndex, false, true);
5164 
5165  // create a backup as authorized solution again (keeping timestamps)
5166  $this->duplicateIntermediateSolutionAuthorized($activeId, $passIndex);
5167 
5168  if( $considerDummyRecordCreation )
5169  {
5170  // create an additional dummy record to indicate the existence of an intermediate solution
5171  // even if all entries are deleted from the intermediate solution later
5172  $this->saveCurrentSolution($activeId, $passIndex, null, null, false, null);
5173  }
5174  }
5175  }
5176  // hey.
5177 
5181  public static function setResultGateway($resultGateway)
5182  {
5183  self::$resultGateway = $resultGateway;
5184  }
5185 
5189  public static function getResultGateway()
5190  {
5191  return self::$resultGateway;
5192  }
5193 
5197  public function setStep($step)
5198  {
5199  $this->step = $step;
5200  }
5201 
5205  public function getStep()
5206  {
5207  return $this->step;
5208  }
5209 
5215  public static function sumTimesInISO8601FormatH_i_s_Extended($time1, $time2)
5216  {
5219  return gmdate('H:i:s', $time);
5220  }
5221 
5226  public static function convertISO8601FormatH_i_s_ExtendedToSeconds($time)
5227  {
5228  $sec = 0;
5229  $time_array = explode(':',$time);
5230  if( sizeof($time_array) == 3)
5231  {
5232  $sec += $time_array[0] * 3600;
5233  $sec += $time_array[1] * 60;
5234  $sec += $time_array[2];
5235  }
5236  return $sec;
5237  }
5238 
5239  public function toJSON()
5240  {
5241  return json_encode(array());
5242  }
5243 
5244  abstract public function duplicate($for_test = true, $title = "", $author = "", $owner = "", $testObjId = null);
5245 
5246  // hey: prevPassSolutions - check for authorized solution
5247  public function intermediateSolutionExists($active_id, $pass)
5248  {
5249  $solutionAvailability = $this->lookupForExistingSolutions($active_id, $pass);
5250  return (bool)$solutionAvailability['intermediate'];
5251  }
5252  public function authorizedSolutionExists($active_id, $pass)
5253  {
5254  $solutionAvailability = $this->lookupForExistingSolutions($active_id, $pass);
5255  return (bool)$solutionAvailability['authorized'];
5256  }
5257  public function authorizedOrIntermediateSolutionExists($active_id, $pass)
5258  {
5259  $solutionAvailability = $this->lookupForExistingSolutions($active_id, $pass);
5260  return (bool)$solutionAvailability['authorized'] || (bool)$solutionAvailability['intermediate'];
5261  }
5262  // hey.
5263 
5269  protected function lookupMaxStep($active_id, $pass)
5270  {
5272  global $ilDB;
5273 
5274  $res = $ilDB->queryF(
5275  "SELECT MAX(step) max_step FROM tst_solutions WHERE active_fi = %s AND pass = %s AND question_fi = %s",
5276  array("integer", "integer", "integer"), array($active_id, $pass, $this->getId())
5277  );
5278 
5279  $row = $ilDB->fetchAssoc($res);
5280 
5281  $maxStep = $row['max_step'];
5282 
5283  return $maxStep;
5284  }
5285 
5286 // fau: testNav - new function lookupForExistingSolutions
5293  public function lookupForExistingSolutions($activeId, $pass)
5294  {
5296  global $ilDB;
5297 
5298  $return = array(
5299  'authorized' => false,
5300  'intermediate' => false
5301  );
5302 
5303  $query = "
5304  SELECT authorized, COUNT(*) cnt
5305  FROM tst_solutions
5306  WHERE active_fi = %s
5307  AND question_fi = %s
5308  AND pass = %s
5309  ";
5310 
5311  if( $this->getStep() !== NULL )
5312  {
5313  $query .= " AND step = " . $ilDB->quote((int)$this->getStep(), 'integer') . " ";
5314  }
5315 
5316  $query .= "
5317  GROUP BY authorized
5318  ";
5319 
5320  $result = $ilDB->queryF($query, array('integer', 'integer', 'integer'), array($activeId, $this->getId(), $pass));
5321 
5322  while ($row = $ilDB->fetchAssoc($result))
5323  {
5324  if ($row['authorized']) {
5325  $return['authorized'] = $row['cnt'] > 0;
5326  }
5327  else
5328  {
5329  $return['intermediate'] = $row['cnt'] > 0;
5330  }
5331  }
5332  return $return;
5333  }
5334 // fau.
5335 
5336  public function removeExistingSolutions($activeId, $pass)
5337  {
5338  global $ilDB;
5339 
5340  $query = "
5341  DELETE FROM tst_solutions
5342  WHERE active_fi = %s
5343  AND question_fi = %s
5344  AND pass = %s
5345  ";
5346 
5347  if( $this->getStep() !== NULL )
5348  {
5349  $query .= " AND step = " . $ilDB->quote((int)$this->getStep(), 'integer') . " ";
5350  }
5351 
5352  return $ilDB->manipulateF($query, array('integer', 'integer', 'integer'),
5353  array($activeId, $this->getId(), $pass)
5354  );
5355  }
5356 
5357  public function resetUsersAnswer($activeId, $pass)
5358  {
5359  $this->removeExistingSolutions($activeId, $pass);
5360  $this->removeResultRecord($activeId, $pass);
5361 
5362  $this->log($activeId, "log_user_solution_willingly_deleted");
5363 
5364  self::_updateTestPassResults(
5365  $activeId, $pass, $this->areObligationsToBeConsidered(), $this->getProcessLocker(), $this->getTestId()
5366  );
5367  }
5368 
5369  public function removeResultRecord($activeId, $pass)
5370  {
5371  global $ilDB;
5372 
5373  $query = "
5374  DELETE FROM tst_test_result
5375  WHERE active_fi = %s
5376  AND question_fi = %s
5377  AND pass = %s
5378  ";
5379 
5380  if( $this->getStep() !== NULL )
5381  {
5382  $query .= " AND step = " . $ilDB->quote((int)$this->getStep(), 'integer') . " ";
5383  }
5384 
5385  return $ilDB->manipulateF($query, array('integer', 'integer', 'integer'),
5386  array($activeId, $this->getId(), $pass)
5387  );
5388  }
5389 
5390  public static function missingResultRecordExists($activeId, $pass, $questionIds)
5391  {
5392  global $ilDB;
5393 
5394  $IN_questionIds = $ilDB->in('question_fi', $questionIds, false, 'integer');
5395 
5396  $query = "
5397  SELECT COUNT(*) cnt
5398  FROM tst_test_result
5399  WHERE active_fi = %s
5400  AND pass = %s
5401  AND $IN_questionIds
5402  ";
5403 
5404  $row = $ilDB->fetchAssoc($ilDB->queryF(
5405  $query, array('integer', 'integer'), array($activeId, $pass)
5406  ));
5407 
5408  return $row['cnt'] < count($questionIds);
5409  }
5410 
5411  public static function getQuestionsMissingResultRecord($activeId, $pass, $questionIds)
5412  {
5413  global $ilDB;
5414 
5415  $IN_questionIds = $ilDB->in('question_fi', $questionIds, false, 'integer');
5416 
5417  $query = "
5418  SELECT question_fi
5419  FROM tst_test_result
5420  WHERE active_fi = %s
5421  AND pass = %s
5422  AND $IN_questionIds
5423  ";
5424 
5425  $res = $ilDB->queryF(
5426  $query, array('integer', 'integer'), array($activeId, $pass)
5427  );
5428 
5429  $questionsHavingResultRecord = array();
5430 
5431  while($row = $ilDB->fetchAssoc($res))
5432  {
5433  $questionsHavingResultRecord[] = $row['question_fi'];
5434  }
5435 
5436  $questionsMissingResultRecordt = array_diff(
5437  $questionIds, $questionsHavingResultRecord
5438  );
5439 
5440  return $questionsMissingResultRecordt;
5441  }
5442 
5443  public static function lookupResultRecordExist($activeId, $questionId, $pass)
5444  {
5445  global $ilDB;
5446 
5447  $query = "
5448  SELECT COUNT(*) cnt
5449  FROM tst_test_result
5450  WHERE active_fi = %s
5451  AND question_fi = %s
5452  AND pass = %s
5453  ";
5454 
5455  $row = $ilDB->fetchAssoc($ilDB->queryF($query, array('integer', 'integer', 'integer'), array($activeId, $questionId, $pass)));
5456 
5457  return $row['cnt'] > 0;
5458  }
5459 
5464  public function fetchValuePairsFromIndexedValues(array $indexedValues)
5465  {
5466  $valuePairs = array();
5467 
5468  foreach($indexedValues as $value1 => $value2)
5469  {
5470  $valuePairs[] = array('value1' => $value1, 'value2' => $value2);
5471  }
5472 
5473  return $valuePairs;
5474  }
5475 
5480  public function fetchIndexedValuesFromValuePairs(array $valuePairs)
5481  {
5482  $indexedValues = array();
5483 
5484  foreach($valuePairs as $valuePair)
5485  {
5486  $indexedValues[ $valuePair['value1'] ] = $valuePair['value2'];
5487  }
5488 
5489  return $indexedValues;
5490  }
5491 
5496  {
5498  }
5499 
5504  {
5505  $this->obligationsToBeConsidered = $obligationsToBeConsidered;
5506  }
5507 
5508  public function updateTimestamp()
5509  {
5510  global $ilDB;
5511 
5512  $ilDB->manipulateF("UPDATE qpl_questions SET tstamp = %s WHERE question_id = %s",
5513  array('integer', 'integer'),
5514  array(time(), $this->getId())
5515  );
5516  }
5517 
5518 // fau: testNav - new function getTestQuestionConfig()
5519  // hey: prevPassSolutions - get caching independent from configuration (config once)
5520  // renamed: getTestPresentationConfig() -> does the caching
5521  // completed: extracted instance building
5522  // avoids configuring cached instances on every access
5523  // allows a stable reconfigure of the instance from outside
5528 
5533  public function getTestPresentationConfig()
5534  {
5535  if( $this->testQuestionConfigInstance === null )
5536  {
5537  $this->testQuestionConfigInstance = $this->buildTestPresentationConfig();
5538  }
5539 
5541  }
5542 
5551  protected function buildTestPresentationConfig()
5552  {
5553  include_once('Modules/TestQuestionPool/classes/class.ilTestQuestionConfig.php');
5554  return new ilTestQuestionConfig();
5555  }
5556  // hey.
5557 // fau.
5558 }
static _getUserIdFromActiveId($active_id)
isInUse($question_id="")
Checks whether the question is in use or not.
static isObligationPossible($questionId)
returns boolean wether it is possible to set this question type as obligatory or not considering the ...
static isCoreQuestionType($questionType)
static resetOriginalId($questionId)
static getPluginObject($a_ctype, $a_cname, $a_slot_id, $a_pname)
Get plugin object.
static makeDirParents($a_dir)
Create a new directory and all parent directories.
static logAction($logtext="", $active_id="", $question_id="")
Logs an action into the Test&Assessment log.
deletePageOfQuestion($question_id)
Deletes the page object of a question with a given ID.
afterSyncWithOriginal($origQuestionId, $dupQuestionId, $origParentObjId, $dupParentObjId)
getId()
Gets the id of the assQuestion object.
static _getManualScoringTypes()
Retrieve the manual scoring settings as type strings.
getUserSolutionPreferingIntermediate($active_id, $pass=NULL)
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.
ILIAS Setting Class.
static isFileAvailable($file)
$export_image_path
(Web) Path to images
Test Question configuration.
static _getMobsOfObject($a_type, $a_id, $a_usage_hist_nr=0, $a_lang="-")
get mobs of object
static _getQuestionText($a_q_id)
Returns question text.
getFlashPathWeb()
Returns the web image path for web accessable flash applications of a question.
static _instanciateQuestion($question_id)
Creates an instance of a question with a given question id.
static getAllowedImageMaterialFileExtensions()
static _getOriginalId($question_id)
Returns the original id of a question.
formatSAQuestion($a_q)
Format self assessment question.
$worksheet
setSuggestedSolution($solution_id="", $subquestion_index=0, $is_import=false)
Sets a suggested solution for the question.
fromXML(&$item, &$questionpool_id, &$tst_id, &$tst_object, &$question_counter, &$import_mapping)
Receives parameters from a QTI parser and creates a valid ILIAS question object.
static getObjectClassNameByQuestionType($questionType)
Taxonomy node <-> item assignment.
migrateContentForLearningModule(ilAssSelfAssessmentMigrator $migrator)
static _includeClass($question_type, $gui=0)
Include the php class file for a given question type.
static _updateTestResultCache($active_id, ilAssQuestionProcessLocker $processLocker=null)
Move this to a proper place.
static getQuestionTypeFromDb($question_id)
get question type for question id
static _isWriteable($object_id, $user_id)
Returns true, if the question pool is writeable by a given user.
forceExistingIntermediateSolution($activeId, $passIndex, $considerDummyRecordCreation)
_getTotalAnswers($a_q_id)
get number of answers for question id (static) note: do not use $this inside this method ...
static _getPass($active_id)
Retrieves the actual pass of a given user for a given test.
static sumTimesInISO8601FormatH_i_s_Extended($time1, $time2)
static _getParticipantData($active_id)
Retrieves a participant name from active id.
_questionExistsInPool($question_id)
Returns true if the question already exists in the database and is assigned to a question pool...
copySuggestedSolutionFiles($source_questionpool_id, $source_question_id)
static getNumExistingSolutionRecords($activeId, $pass, $questionId)
returns the number of existing solution records for the given test active / pass and given question i...
duplicateIntermediateSolutionAuthorized($activeId, $passIndex)
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...
buildHashedImageFilename($plain_image_filename, $unique=false)
static _addLog($user_id, $object_id, $logtext, $question_id="", $original_id="", $test_only=FALSE, $test_ref_id=NULL)
Add an assessment log entry.
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 _exists($a_id, $a_reference=false, $a_type=NULL)
checks wether a lm content object with specified id exists or not
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)
static _isUsedInRandomTest($question_id="")
Checks whether the question is used in a random test or not.
copyPageOfQuestion($a_q_id)
questionTitleExists($questionpool_id, $title)
Returns TRUE if the question title exists in the database.
toXML($a_include_header=true, $a_include_binary=true, $a_shuffle=false, $test_output=false, $force_image_references=false)
Returns a QTI xml representation of the question.
const ADDITIONAL_CONTENT_EDITING_MODE_PAGE_OBJECT
constant for additional content editing mode "pageobject"
buildTestPresentationConfig()
build basic test question configuration instance
$_GET["client_id"]
static getQuestionsMissingResultRecord($activeId, $pass, $questionIds)
__set($key, $value)
Object setter.
__get($value)
Object getter.
Abstract basic class which is to be extended by the concrete assessment question type classes...
static _needsManualScoring($question_id)
& _getSuggestedSolution($question_id, $subquestion_index=0)
Returns a suggested solution for a given subquestion index.
setDefaultNrOfTries($a_defaultnroftries)
Set Default Nr of Tries.
adjustReachedPointsByScoringOptions($points, $active_id, $pass=NULL)
Adjust the given reached points by checks for all special scoring options in the test container...
static _getIdForImportId($a_import_id)
get current object id for import id (static)
createPageObject()
create page object of question
intermediateSolutionExists($active_id, $pass)
ensureNonNegativePoints($points)
deleteAnswers($question_id)
Deletes datasets from answers tables.
deleteDummySolutionRecord($activeId, $passIndex)
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.
prepareTextareaOutput($txt_output, $prepare_for_latex_output=FALSE, $omitNl2BrWhenTextArea=false)
Prepares a string for a text area output in tests.
getSuggestedSolutionTitle($subquestion_index=0)
Returns the title of a suggested solution at a given subquestion_index.
getReachedPoints($active_id, $pass=NULL)
Returns the points, a learner has reached answering the question This is the fast way to get the poin...
$GLOBALS['loaded']
Global hash that tracks already loaded includes.
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.
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:51
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
Add rich text string
The name of the decorator.
syncSuggestedSolutionFiles($original_id)
Syncs the files of a suggested solution if the question is synced.
__construct( $title="", $comment="", $author="", $owner=-1, $question="")
assQuestion constructor
setNewOriginalId($newId)
beforeSyncWithOriginal($origQuestionId, $dupQuestionId, $origParentObjId, $dupParentObjId)
getAdditionalContentEditingMode()
getter for additional content editing mode for this question
getJavaPath()
Returns the image path for web accessable images of a question.
getMaximumPoints()
Returns the maximum points, a learner can reach answering the question.
deleteAdditionalTableData($question_id)
Deletes datasets from the additional question table in the database.
isAnswered($active_id, $pass=null)
returns boolean wether the question is answered during test pass or not
setEstimatedWorkingTimeFromDurationString($durationString)
Sets the estimated working time of a question from a given datetime string.
getSelfAssessmentEditingMode()
Get Self-Assessment Editing Mode.
calculateResultsFromSolution($active_id, $pass=NULL, $obligationsEnabled=false)
Calculates the question results from a previously saved question solution.
setNrOfTries($a_nr_of_tries)
static _removeUsage($a_mob_id, $a_type, $a_id, $a_usage_hist_nr=0, $a_lang="-")
Remove usage of mob in another container.
setAdditionalContentEditingMode($additinalContentEditingMode)
setter for additional content editing mode for this question
static lookupParentObjId($questionId)
ilDBInterface $ilDB
const OUTPUT_JAVASCRIPT
isHTML($a_text)
Checks if a given string contains HTML or not.
static _getObjectIDFromActiveID($active_id)
Returns the ILIAS test object id for a given active id.
loadFromDb($question_id)
Loads the question from the database.
persistPreviewState(ilAssQuestionPreviewSession $previewSession)
persists the preview state for current user and question
authorizedSolutionExists($active_id, $pass)
static getASCIIFilename($a_filename)
convert utf8 to ascii filename
setShuffle($shuffle=true)
Sets the shuffle flag.
static _replaceMediaObjectImageSrc($a_text, $a_direction=0, $nic=IL_INST_ID)
Replaces image source from mob image urls with the mob id or replaces mob id with the correct image s...
getObjId()
Get the object id of the container object.
getValidAdditionalContentEditingModes()
getter for valid additional content editing modes
static _getMaximumPoints($question_id)
Returns the maximum points, a learner can reach answering the question.
getShuffle()
Gets the shuffle flag.
$arrData
Associative array to store properties.
static _getAllReferences($a_id)
get all reference ids of object
isValidAdditionalContentEditingMode($additionalContentEditingMode)
returns the fact wether the passed additional content mode is valid or not
& getInstances()
Gets all instances of the question.
fetchIndexedValuesFromValuePairs(array $valuePairs)
global $ilCtrl
Definition: ilias.php:18
getAdjustedReachedPoints($active_id, $pass=NULL, $authorizedSolution=true)
returns the reached points ...
setProcessLocker($processLocker)
static _getInternalLinkHref($target="")
isPreviewSolutionCorrect(ilAssQuestionPreviewSession $previewSession)
static _getQuestionInfo($question_id)
Returns question information from the database.
static convertISO8601FormatH_i_s_ExtendedToSeconds($time)
static isQuestionObligatory($question_id)
checks wether the question with given id is marked as obligatory or not
static deleteHintsByQuestionIds($questionIds)
Deletes all question hints relating to questions included in given question ids.
getSuggestedSolution($subquestion_index=0)
Returns a suggested solution for a given subquestion index.
authorizedOrIntermediateSolutionExists($active_id, $pass)
removeSolutionRecordById($solutionId)
calculateReachedPointsFromPreviewSession(ilAssQuestionPreviewSession $previewSession)
duplicateQuestionHints($originalQuestionId, $duplicateQuestionId)
static _getLogLanguage()
retrieve the log language for assessment logging
supportsJavascriptOutput()
Returns true if the question type supports JavaScript output.
if(!is_dir( $entity_dir)) exit("Fatal Error ([A-Za-z0-9]+)\+" &#(? foreach( $entity_files as $file) $output
getJavaPathWeb()
Returns the web image path for web accessable java applets of a question.
getFlashPath()
Returns the image path for web accessable flash files of a question.
static isAllowedImageMimeType($mimeType)
getTestId()
Gets the test id of the assQuestion object.
setAuthor($author="")
Sets the authors name of the assQuestion object.
setObligationsToBeConsidered($obligationsToBeConsidered)
Assessment hint page object.
static _enabledAssessmentLogging()
check wether assessment logging is enabled or not
getAuthor()
Gets the authors name of the assQuestion object.
getTotalAnswers()
get total number of answers
reworkWorkingData($active_id, $pass, $obligationsAnswered, $authorized)
Reworks the allready saved working data if neccessary.
getQuestionTypeID()
Returns the question type of the question.
getImagePath($question_id=null, $object_id=null)
Returns the image path for web accessable images of a question.
static _updateQuestionCount($object_id)
Updates the number of available questions for a question pool in the database.
$mobs
static _getQuestionCountAndPointsForPassOfParticipant($active_id, $pass)
static setForcePassResultUpdateEnabled($forcePassResultsUpdateEnabled)
updateCurrentSolutionsAuthorization($activeId, $pass, $authorized, $keepTime=false)
duplicate($for_test=true, $title="", $author="", $owner="", $testObjId=null)
comment()
Definition: comment.php:2
log($active_id, $langVar)
static _getScoreCutting($active_id)
Determines if the score of a question should be cut at 0 points or the score of the whole test...
saveCurrentSolution($active_id, $pass, $value1, $value2, $authorized=true, $tstamp=null)
static getImagePath($img, $module_path="", $mode="output", $offline=false)
get image path (for images located in a template directory)
static _getResultPass($active_id)
Retrieves the pass number that should be counted for a given user.
static _getReachedPoints($active_id, $question_id, $pass=NULL)
Returns the points, a learner has reached answering the question.
static moveUploadedFile($a_file, $a_name, $a_target, $a_raise_errors=true, $a_mode="move_uploaded")
move uploaded file
static explodeKeyValues($keyValues)
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)
saveWorkingData($active_id, $pass=NULL, $authorized=true)
Saves the learners input of the question to the database.
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.
persistWorkingState($active_id, $pass=NULL, $obligationsEnabled=false, $authorized=true)
persists the working state for current testactive and testpass
static _lookupAuthor($obj_id)
Gets the authors name of the ilObjTest object.
getSuggestedSolutionPathWeb()
Returns the web path for a suggested solution.
static _updateObjectiveResult($a_user_id, $a_active_id, $a_question_id)
static signFile($path_to_file)
moveUploadedMediaFile($file, $name)
Move an uploaded media file to an public accessible temp dir to present it.
getDefaultNrOfTries()
Get Default Nr of Tries.
isClone($question_id="")
Checks whether the question is a clone of another question or not.
updateCurrentSolution($solutionId, $value1, $value2, $authorized=true)
static removeTrailingPathSeparators($path)
cleanupMediaObjectUsage()
synchronises appearances of media objects in the question with media object usage table ...
deleteSuggestedSolutions()
Deletes all suggestes solutions in the database.
Create styles array
The data for the language used.
setPreventRteUsage($a_val)
Set prevent rte usage.
fixSvgToPng($imageFilenameContainingString)
static _instantiateQuestion($question_id)
setExternalId($external_id)
fetchValuePairsFromIndexedValues(array $indexedValues)
getRTETextWithMediaObjects()
Collects all text in the question which could contain media objects which were created with the Rich ...
static duplicateListForQuestion($originalQuestionId, $duplicateQuestionId)
duplicates a hint list from given original question id to given duplicate question id and returns an ...
isAdditionalContentEditingModePageObject()
isser for additional "pageobject" content editing mode
getTestOutputSolutions($activeId, $pass)
deductHintPointsFromReachedPoints(ilAssQuestionPreviewSession $previewSession, $reachedPoints)
static $allowedCharsetsByMimeType
const ADDITIONAL_CONTENT_EDITING_MODE_DEFAULT
constant for additional content editing mode "default"
static _questionExistsInTest($question_id, $test_id)
setPoints($a_points)
Sets the maximum available points for the question.
saveQuestionDataToDb($original_id="")
_resolveIntLinks($question_id)
static _getSuggestedSolutionOutput($question_id)
Returns the output of the suggested solution.
const KEY_VALUES_IMPLOSION_SEPARATOR
onDuplicate($originalParentId, $originalQuestionId, $duplicateParentId, $duplicateQuestionId)
Will be called when a question is duplicated (inside a question pool or for insertion in a test) ...
_questionExists($question_id)
Returns true if the question already exists in the database.
static _getInstanceByType($a_type)
Factory method for creating purifier instances.
static $imageSourceFixReplaceMap
getOwner()
Gets the creator/owner ID of the assQuestion object.
static isAllowedImageFileExtension($mimeType, $fileExtension)
lmMigrateQuestionTypeGenericContent(ilAssSelfAssessmentMigrator $migrator)
lookupTestId($active_id)
lookupCurrentTestPass($active_id, $pass)
resetUsersAnswer($activeId, $pass)
getEstimatedWorkingTime()
Gets the estimated working time of a question.
setQuestion($question="")
Sets the question string of the question object.
removeCurrentSolution($active_id, $pass, $authorized=true)
setTestId($id=-1)
Sets the test id of the assQuestion object.
ensureCurrentTestPass($active_id, $pass)
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.
static buildExamId($active_id, $pass, $test_obj_id=null)
calculateReachedPoints($active_id, $pass=NULL, $authorizedSolution=true, $returndetails=FALSE)
Returns the points, a learner has reached answering the question.
removeIntermediateSolution($active_id, $pass)
getSolutionValues($active_id, $pass=NULL, $authorized=true)
Loads solutions of a given user from the database an returns it.
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.
global $DIC
static setTokenMaxLifetimeInSeconds($token_max_lifetime_in_seconds)
Add data(end) time
Method that wraps PHPs time in order to allow simulations with the workflow.
if(!file_exists("$old.txt")) if($old===$new) if(file_exists("$new.txt")) $file
const OUTPUT_HTML
addQuestionChangeListener(ilQuestionChangeListener $listener)
static isHTML($a_text)
Checks if a given string contains HTML or not.
static fetchMimeTypeIdentifier($contentTypeString)
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)
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 ...
setObjId($obj_id=0)
Set the object id of the container object.
getActiveUserData($active_id)
Returns the user id and the test id for a given active id.
static delDir($a_dir, $a_clean_only=false)
removes a dir and all its content (subdirs and files) recursively
static _saveUsage($a_mob_id, $a_type, $a_id, $a_usage_hist_nr=0, $a_lang="-")
Save usage of mob within another container (e.g.
setExportImagePath($a_path)
setComment($comment="")
Sets the comment string of the assQuestion object.
setShuffler(ilArrayElementShuffler $shuffler)
static $allowedFileExtensionsByMimeType
static getFeedbackClassNameByQuestionType($questionType)
static _getQuestionTitle($question_id)
Returns the question title of a question with a given id.
static getKeyValuesImplosionSeparator()
$_POST["username"]
$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
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.
QTIMaterialToString($a_material)
Reads an QTI material tag an creates a text string.
isNonEmptyItemListPostSubmission($postSubmissionFieldname)
setOwner($owner="")
Sets the creator/owner ID of the assQuestion object.
setSelfAssessmentEditingMode($a_selfassessmenteditingmode)
Set Self-Assessment Editing Mode.
static missingResultRecordExists($activeId, $pass, $questionIds)
getSolutionRecordById($solutionId)
getPreventRteUsage()
Get prevent rte usage.
static _isWriteable($question_id, $user_id)
Returns true if the question is writeable by a certain user.
static getAllowedFileExtensionsForMimeType($mimeType)