ILIAS  Release_5_0_x_branch Revision 61816
 All Data Structures Namespaces Files Functions Variables Groups Pages
class.assQuestion.php
Go to the documentation of this file.
1 <?php
2 /* Copyright (c) 1998-2013 ILIAS open source, Extended GPL, see docs/LICENSE */
3 
4 include_once "./Modules/Test/classes/inc.AssessmentConstants.php";
5 
20 abstract class assQuestion
21 {
27  protected $id;
28 
34  protected $title;
35 
41  protected $comment;
42 
48  protected $owner;
49 
55  protected $author;
56 
62  protected $question;
63 
69  protected $points;
70 
76  protected $est_working_time;
77 
83  protected $shuffle;
84 
90  protected $test_id;
91 
97  protected $obj_id;
98 
104  protected $ilias;
105 
109  protected $tpl;
110 
114  protected $lng;
115 
119  protected $db;
120 
126  protected $outputType;
127 
134 
140  protected $original_id;
141 
147  protected $page;
148 
152  private $nr_of_tries;
153 
157  private $arrData;
158 
163 
170  private $obligationsToBeConsidered = false;
171 
176  protected $external_id = '';
177 
182 
187 
194 
200  public $feedbackOBJ = null;
201 
207  var $prevent_rte_usage = false;
208 
215 
222 
226  protected $questionChangeListeners = array();
227 
231  protected $processLocker;
232 
233  public $questionActionCmd = 'handleQuestionAction';
234 
238  private static $resultGateway = null;
239 
243  protected $step = null;
244 
245  protected $lastChange;
246 
257  function __construct(
258  $title = "",
259  $comment = "",
260  $author = "",
261  $owner = -1,
262  $question = ""
263  )
264  {
265  global $ilias, $lng, $tpl, $ilDB;
266 
267  $this->ilias = $ilias;
268  $this->lng = $lng;
269  $this->tpl = $tpl;
270  $this->db = $ilDB;
271 
272  $this->original_id = null;
273  $this->title = $title;
274  $this->comment = $comment;
275  $this->page = null;
276  $this->author = $author;
277  $this->setQuestion($question);
278  if (!$this->author)
279  {
280  $this->author = $this->ilias->account->fullname;
281  }
282  $this->owner = $owner;
283  if ($this->owner <= 0)
284  {
285  $this->owner = $this->ilias->account->id;
286  }
287  $this->id = -1;
288  $this->test_id = -1;
289  $this->suggested_solutions = array();
290  $this->shuffle = 1;
291  $this->nr_of_tries = 0;
292  $this->setEstimatedWorkingTime(0,1,0);
293  $this->outputType = OUTPUT_JAVASCRIPT;
294  $this->arrData = array();
295  $this->setExternalId('');
296 
297  $this->questionActionCmd = 'handleQuestionAction';
298 
299  $this->lastChange = null;
300  }
301 
306  {
307  $this->processLocker = $processLocker;
308  }
309 
313  public function getProcessLocker()
314  {
315  return $this->processLocker;
316  }
317 
329  function fromXML(&$item, &$questionpool_id, &$tst_id, &$tst_object, &$question_counter, &$import_mapping)
330  {
331  include_once "./Modules/TestQuestionPool/classes/import/qti12/class." . $this->getQuestionType() . "Import.php";
332  $classname = $this->getQuestionType() . "Import";
333  $import = new $classname($this);
334  $import->fromXML($item, $questionpool_id, $tst_id, $tst_object, $question_counter, $import_mapping);
335  }
336 
343  function toXML($a_include_header = true, $a_include_binary = true, $a_shuffle = false, $test_output = false, $force_image_references = false)
344  {
345  include_once "./Modules/TestQuestionPool/classes/export/qti12/class." . $this->getQuestionType() . "Export.php";
346  $classname = $this->getQuestionType() . "Export";
347  $export = new $classname($this);
348  return $export->toXML($a_include_header, $a_include_binary, $a_shuffle, $test_output, $force_image_references);
349  }
350 
357  function isComplete()
358  {
359  return false;
360  }
361 
369  function questionTitleExists($questionpool_id, $title)
370  {
371  global $ilDB;
372 
373  $result = $ilDB->queryF("SELECT * FROM qpl_questions WHERE obj_fi = %s AND title = %s",
374  array('integer','text'),
375  array($questionpool_id, $title)
376  );
377  return ($result->numRows() > 0) ? TRUE : FALSE;
378  }
379 
387  function setTitle($title = "")
388  {
389  $this->title = $title;
390  }
391 
399  function setId($id = -1)
400  {
401  $this->id = $id;
402  }
403 
411  function setTestId($id = -1)
412  {
413  $this->test_id = $id;
414  }
415 
423  function setComment($comment = "")
424  {
425  $this->comment = $comment;
426  }
427 
436  {
437  $this->outputType = $outputType;
438  }
439 
440 
448  function setShuffle($shuffle = true)
449  {
450  if ($shuffle)
451  {
452  $this->shuffle = 1;
453  }
454  else
455  {
456  $this->shuffle = 0;
457  }
458  }
459 
470  function setEstimatedWorkingTime($hour=0, $min=0, $sec=0)
471  {
472  $this->est_working_time = array("h" => (int)$hour, "m" => (int)$min, "s" => (int)$sec);
473  }
474 
482  {
483  $this->est_working_time = array(
484  'h' => (int)substr($durationString, 0, 2),
485  'm' => (int)substr($durationString, 3, 2),
486  's' => (int)substr($durationString, 6, 2)
487  );
488  }
489 
497  function keyInArray($searchkey, $array)
498  {
499  if ($searchkey)
500  {
501  foreach ($array as $key => $value)
502  {
503  if (strcmp($key, $searchkey)==0)
504  {
505  return true;
506  }
507  }
508  }
509  return false;
510  }
511 
519  function setAuthor($author = "")
520  {
521  if (!$author)
522  {
523  $author = $this->ilias->account->fullname;
524  }
525  $this->author = $author;
526  }
527 
535  function setOwner($owner = "")
536  {
537  $this->owner = $owner;
538  }
539 
547  function getTitle()
548  {
549  return $this->title;
550  }
551 
559  function getId()
560  {
561  return $this->id;
562  }
563 
571  function getShuffle()
572  {
573  return $this->shuffle;
574  }
575 
583  function getTestId()
584  {
585  return $this->test_id;
586  }
587 
595  function getComment()
596  {
597  return $this->comment;
598  }
599 
607  function getOutputType()
608  {
609  return $this->outputType;
610  }
611 
618  public function supportsJavascriptOutput()
619  {
620  return FALSE;
621  }
622 
623  public function supportsNonJsOutput()
624  {
625  return true;
626  }
627 
628  public function requiresJsSwitch()
629  {
630  return $this->supportsJavascriptOutput() && $this->supportsNonJsOutput();
631  }
632 
641  {
642  if (!$this->est_working_time)
643  {
644  $this->est_working_time = array("h" => 0, "m" => 0, "s" => 0);
645  }
647  }
648 
656  function getAuthor()
657  {
658  return $this->author;
659  }
660 
668  function getOwner()
669  {
670  return $this->owner;
671  }
672 
680  function getObjId()
681  {
682  return $this->obj_id;
683  }
684 
692  function setObjId($obj_id = 0)
693  {
694  $this->obj_id = $obj_id;
695  }
696 
700  public function setExternalId($external_id)
701  {
702  $this->external_id = $external_id;
703  }
704 
708  public function getExternalId()
709  {
710  if(!strlen($this->external_id))
711  {
712  if($this->getId() > 0)
713  {
714  return 'il_' . IL_INST_ID . '_qst_' . $this->getId();
715  }
716  else
717  {
718  return uniqid('', true);
719  }
720  }
721  else
722  {
723  return $this->external_id;
724  }
725  }
726 
733  function _getMaximumPoints($question_id)
734  {
735  global $ilDB;
736 
737  $points = 0;
738  $result = $ilDB->queryF("SELECT points FROM qpl_questions WHERE question_id = %s",
739  array('integer'),
740  array($question_id)
741  );
742  if ($result->numRows() == 1)
743  {
744  $row = $ilDB->fetchAssoc($result);
745  $points = $row["points"];
746  }
747  return $points;
748  }
749 
756  function &_getQuestionInfo($question_id)
757  {
758  global $ilDB;
759 
760  $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",
761  array('integer'),
762  array($question_id)
763  );
764  if ($result->numRows())
765  {
766  return $ilDB->fetchAssoc($result);
767  }
768  else return array();
769  }
770 
777  public static function _getSuggestedSolutionCount($question_id)
778  {
779  global $ilDB;
780 
781  $result = $ilDB->queryF("SELECT suggested_solution_id FROM qpl_sol_sug WHERE question_fi = %s",
782  array('integer'),
783  array($question_id)
784  );
785  return $result->numRows();
786  }
787 
794  public static function _getSuggestedSolutionOutput($question_id)
795  {
797  if (!is_object($question)) return "";
798  return $question->getSuggestedSolutionOutput();
799  }
800 
801  public function getSuggestedSolutionOutput()
802  {
803  $output = array();
804  foreach ($this->suggested_solutions as $solution)
805  {
806  switch ($solution["type"])
807  {
808  case "lm":
809  case "st":
810  case "pg":
811  case "git":
812  array_push($output, '<a href="' . assQuestion::_getInternalLinkHref($solution["internal_link"]) . '">' . $this->lng->txt("solution_hint") . '</a>');
813  break;
814  case "file":
815  $possible_texts = array_values(array_filter(array(
816  ilUtil::prepareFormOutput($solution['value']['filename']),
817  ilUtil::prepareFormOutput($solution['value']['name']),
818  $this->lng->txt('tst_show_solution_suggested')
819  )));
820  array_push($output, '<a href="' . $this->getSuggestedSolutionPathWeb() . $solution["value"]["name"] . '">' . $possible_texts[0] . '</a>');
821  break;
822  case "text":
823  array_push($output, $this->prepareTextareaOutput($solution["value"], true));
824  break;
825  }
826  }
827  return join($output, "<br />");
828  }
829 
838  function &_getSuggestedSolution($question_id, $subquestion_index = 0)
839  {
840  global $ilDB;
841 
842  $result = $ilDB->queryF("SELECT * FROM qpl_sol_sug WHERE question_fi = %s AND subquestion_index = %s",
843  array('integer','integer'),
844  array($question_id, $subquestion_index)
845  );
846  if ($result->numRows() == 1)
847  {
848  $row = $ilDB->fetchAssoc($result);
849  return array(
850  "internal_link" => $row["internal_link"],
851  "import_id" => $row["import_id"]
852  );
853  }
854  else
855  {
856  return array();
857  }
858  }
859 
865  public function getSuggestedSolutions()
866  {
868  }
869 
877  function _getReachedPoints($active_id, $question_id, $pass = NULL)
878  {
879  global $ilDB;
880 
881  $points = 0;
882  if (is_null($pass))
883  {
884  include_once "./Modules/TestQuestionPool/classes/class.assQuestion.php";
885  $pass = assQuestion::_getSolutionMaxPass($question_id, $active_id);
886  }
887  $result = $ilDB->queryF("SELECT * FROM tst_test_result WHERE active_fi = %s AND question_fi = %s AND pass = %s",
888  array('integer','integer','integer'),
889  array($active_id, $question_id, $pass)
890  );
891  if ($result->numRows() == 1)
892  {
893  $row = $ilDB->fetchAssoc($result);
894  $points = $row["points"];
895  }
896  return $points;
897  }
898 
907  function getReachedPoints($active_id, $pass = NULL)
908  {
909  return round($this->_getReachedPoints($active_id, $this->getId(), $pass), 2);
910  }
911 
918  function getMaximumPoints()
919  {
920  return $this->points;
921  }
922 
934  final public function getAdjustedReachedPoints($active_id, $pass = NULL)
935  {
936  if (is_null($pass))
937  {
938  include_once "./Modules/Test/classes/class.ilObjTest.php";
939  $pass = ilObjTest::_getPass($active_id);
940  }
941 
942  // determine reached points for submitted solution
943  $reached_points = $this->calculateReachedPoints($active_id, $pass);
944 
945 
946 
947  // deduct points for requested hints from reached points
948  require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionHintTracking.php';
949  $hintTracking = new ilAssQuestionHintTracking($this->getId(), $active_id, $pass);
950  $requestsStatisticData = $hintTracking->getRequestStatisticDataByQuestionAndTestpass();
951  $reached_points = $reached_points - $requestsStatisticData->getRequestsPoints();
952 
953  // adjust reached points regarding to tests scoring options
954  $reached_points = $this->adjustReachedPointsByScoringOptions($reached_points, $active_id, $pass);
955 
956  return $reached_points;
957  }
958 
968  final public function calculateResultsFromSolution($active_id, $pass = NULL, $obligationsEnabled = false)
969  {
970  global $ilDB, $ilUser;
971 
972  if( is_null($pass) )
973  {
974  include_once "./Modules/Test/classes/class.ilObjTest.php";
975  $pass = ilObjTest::_getPass($active_id);
976  }
977 
978  // determine reached points for submitted solution
979  $reached_points = $this->calculateReachedPoints($active_id, $pass);
980 
981  // deduct points for requested hints from reached points
982  require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionHintTracking.php';
983  $questionHintTracking = new ilAssQuestionHintTracking($this->getId(), $active_id, $pass);
984  $requestsStatisticData = $questionHintTracking->getRequestStatisticDataByQuestionAndTestpass();
985  $reached_points = $reached_points - $requestsStatisticData->getRequestsPoints();
986 
987  // adjust reached points regarding to tests scoring options
988  $reached_points = $this->adjustReachedPointsByScoringOptions($reached_points, $active_id, $pass);
989 
990  if( $obligationsEnabled && ilObjTest::isQuestionObligatory($this->getId()) )
991  {
992  $isAnswered = $this->isAnswered($active_id, $pass);
993  }
994  else
995  {
996  $isAnswered = true;
997  }
998 
999  if( is_null($reached_points) ) $reached_points = 0;
1000 
1001  $this->getProcessLocker()->requestUserQuestionResultUpdateLock();
1002 
1003  $query = "
1004  DELETE FROM tst_test_result
1005 
1006  WHERE active_fi = %s
1007  AND question_fi = %s
1008  AND pass = %s
1009  ";
1010 
1011  $types = array('integer', 'integer', 'integer');
1012  $values = array($active_id, $this->getId(), $pass);
1013 
1014  if( $this->getStep() !== NULL )
1015  {
1016  $query .= "
1017  AND step = %s
1018  ";
1019 
1020  $types[] = 'integer';
1021  $values[] = $this->getStep();
1022  }
1023 
1024  $affectedRows = $ilDB->manipulateF($query, $types, $values);
1025 
1026  $next_id = $ilDB->nextId("tst_test_result");
1027 
1028  $fieldData = array(
1029  'test_result_id' => array('integer', $next_id),
1030  'active_fi' => array('integer', $active_id),
1031  'question_fi' => array('integer', $this->getId()),
1032  'pass' => array('integer', $pass),
1033  'points' => array('float', $reached_points),
1034  'tstamp' => array('integer', time()),
1035  'hint_count' => array('integer', $requestsStatisticData->getRequestsCount()),
1036  'hint_points' => array('float', $requestsStatisticData->getRequestsPoints()),
1037  'answered' => array('integer', $isAnswered)
1038  );
1039 
1040  if( $this->getStep() !== NULL )
1041  {
1042  $fieldData['step'] = array('integer', $this->getStep());
1043  }
1044 
1045  $ilDB->insert('tst_test_result', $fieldData);
1046 
1047  $this->getProcessLocker()->releaseUserQuestionResultUpdateLock();
1048 
1049  include_once ("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
1050 
1052  {
1053  $this->logAction(
1054  sprintf(
1055  $this->lng->txtlng(
1056  "assessment", "log_user_answered_question", ilObjAssessmentFolder::_getLogLanguage()
1057  ),
1058  $reached_points
1059  ),
1060  $active_id,
1061  $this->getId()
1062  );
1063  }
1064 
1065  // update test pass results
1066  $this->_updateTestPassResults($active_id, $pass, $obligationsEnabled, $this->getProcessLocker());
1067 
1068  // Update objective status
1069  include_once 'Modules/Course/classes/class.ilCourseObjectiveResult.php';
1070  ilCourseObjectiveResult::_updateObjectiveResult($ilUser->getId(),$active_id,$this->getId());
1071  }
1072 
1081  final public function persistWorkingState($active_id, $pass = NULL, $obligationsEnabled = false)
1082  {
1083  if( $pass === null )
1084  {
1085  require_once 'Modules/Test/classes/class.ilObjTest.php';
1086  $pass = ilObjTest::_getPass($active_id);
1087  }
1088 
1089  $this->getProcessLocker()->requestPersistWorkingStateLock();
1090 
1091  $saveStatus = $this->saveWorkingData($active_id, $pass);
1092 
1093  $this->calculateResultsFromSolution($active_id, $pass, $obligationsEnabled);
1094 
1095  $this->reworkWorkingData($active_id, $pass, $obligationsEnabled);
1096 
1097  $this->getProcessLocker()->releasePersistWorkingStateLock();
1098 
1099  return $saveStatus;
1100  }
1101 
1105  final public function persistPreviewState(ilAssQuestionPreviewSession $previewSession)
1106  {
1107  $this->savePreviewData($previewSession);
1108  }
1109 
1119  abstract public function saveWorkingData($active_id, $pass = NULL);
1120 
1130  abstract protected function reworkWorkingData($active_id, $pass, $obligationsAnswered);
1131 
1132  protected function savePreviewData(ilAssQuestionPreviewSession $previewSession)
1133  {
1134  $previewSession->setParticipantsSolution($this->getSolutionSubmit());
1135  }
1136 
1139  {
1140  global $ilDB;
1141 
1142  include_once "./Modules/Test/classes/class.ilObjTest.php";
1143  include_once "./Modules/Test/classes/class.assMarkSchema.php";
1144 
1145  $pass = ilObjTest::_getResultPass($active_id);
1146 
1147  $query = "
1148  SELECT tst_pass_result.*
1149  FROM tst_pass_result
1150  WHERE active_fi = %s
1151  AND pass = %s
1152  ";
1153 
1154  $result = $ilDB->queryF(
1155  $query, array('integer','integer'), array($active_id, $pass)
1156  );
1157 
1158  $row = $ilDB->fetchAssoc($result);
1159 
1160  $max = $row['maxpoints'];
1161  $reached = $row['points'];
1162 
1163  $obligationsAnswered = (int)$row['obligations_answered'];
1164 
1165  include_once "./Modules/Test/classes/class.assMarkSchema.php";
1166 
1167  $percentage = (!$max) ? 0 : ($reached / $max) * 100.0;
1168 
1169  $mark = ASS_MarkSchema::_getMatchingMarkFromActiveId($active_id, $percentage);
1170 
1171  $isPassed = ( $mark["passed"] ? 1 : 0 );
1172  $isFailed = ( !$mark["passed"] ? 1 : 0 );
1173 
1174  if( is_object($processLocker) )
1175  {
1176  $processLocker->requestUserTestResultUpdateLock();
1177  }
1178 
1179  $query = "
1180  DELETE FROM tst_result_cache
1181  WHERE active_fi = %s
1182  ";
1183 
1184  $affectedRows = $ilDB->manipulateF(
1185  $query, array('integer'), array($active_id)
1186  );
1187 
1188  $ilDB->insert('tst_result_cache', array(
1189  'active_fi'=> array('integer', $active_id),
1190  'pass'=> array('integer', strlen($pass) ? $pass : 0),
1191  'max_points'=> array('float', strlen($max) ? $max : 0),
1192  'reached_points'=> array('float', strlen($reached) ? $reached : 0),
1193  'mark_short'=> array('text', strlen($mark["short_name"]) ? $mark["short_name"] : " "),
1194  'mark_official'=> array('text', strlen($mark["official_name"]) ? $mark["official_name"] : " "),
1195  'passed'=> array('integer', $isPassed),
1196  'failed'=> array('integer', $isFailed),
1197  'tstamp'=> array('integer', time()),
1198  'hint_count'=> array('integer', $row['hint_count']),
1199  'hint_points'=> array('float', $row['hint_points']),
1200  'obligations_answered' => array('integer', $obligationsAnswered)
1201  ));
1202 
1203  if( is_object($processLocker) )
1204  {
1205  $processLocker->releaseUserTestResultUpdateLock();
1206  }
1207  }
1208 
1210  function _updateTestPassResults($active_id, $pass, $obligationsEnabled = false, ilAssQuestionProcessLocker $processLocker = null, $test_obj_id = null)
1211  {
1212  global $ilDB;
1213 
1214  include_once "./Modules/Test/classes/class.ilObjTest.php";
1215 
1216  if( self::getResultGateway() !== null )
1217  {
1218  $data = self::getResultGateway()->getQuestionCountAndPointsForPassOfParticipant($active_id, $pass);
1219  $time = self::getResultGateway()->getWorkingTimeOfParticipantForPass($active_id, $pass);
1220  }
1221  else
1222  {
1225  }
1226 
1227 
1228  // update test pass results
1229 
1230  $result = $ilDB->queryF("
1231  SELECT SUM(points) reachedpoints,
1232  SUM(hint_count) hint_count,
1233  SUM(hint_points) hint_points,
1234  COUNT(DISTINCT(question_fi)) answeredquestions
1235  FROM tst_test_result
1236  WHERE active_fi = %s
1237  AND pass = %s
1238  ",
1239  array('integer','integer'),
1240  array($active_id, $pass)
1241  );
1242 
1243  if ($result->numRows() > 0)
1244  {
1245  if( $obligationsEnabled )
1246  {
1247  $query = '
1248  SELECT count(*) cnt,
1249  min( answered ) answ
1250  FROM tst_test_question
1251  INNER JOIN tst_active
1252  ON active_id = %s
1253  AND tst_test_question.test_fi = tst_active.test_fi
1254  LEFT JOIN tst_test_result
1255  ON tst_test_result.active_fi = %s
1256  AND tst_test_result.pass = %s
1257  AND tst_test_question.question_fi = tst_test_result.question_fi
1258  WHERE obligatory = 1';
1259 
1260  $result_obligatory = $ilDB->queryF(
1261  $query, array('integer','integer','integer'), array($active_id, $active_id, $pass)
1262  );
1263 
1264  $row_obligatory = $ilDB->fetchAssoc($result_obligatory);
1265 
1266  if ($row_obligatory['cnt'] == 0)
1267  {
1268  $obligations_answered = 1;
1269  }
1270  else
1271  {
1272  $obligations_answered = (int) $row_obligatory['answ'];
1273  }
1274  }
1275  else
1276  {
1277  $obligations_answered = 1;
1278  }
1279 
1280  $row = $ilDB->fetchAssoc($result);
1281 
1282  if( $row['hint_count'] === null ) $row['hint_count'] = 0;
1283  if( $row['hint_points'] === null ) $row['hint_points'] = 0;
1284 
1285  $exam_identifier = ilObjTest::buildExamId( $active_id, $pass, $test_obj_id);
1286 
1287  if( is_object($processLocker) )
1288  {
1289  $processLocker->requestUserPassResultUpdateLock();
1290  }
1291 
1292  /*
1293  $query = "
1294  DELETE FROM tst_pass_result
1295 
1296  WHERE active_fi = %s
1297  AND pass = %s
1298  ";
1299 
1300  $affectedRows = $ilDB->manipulateF(
1301  $query, array('integer','integer'), array($active_id, $pass)
1302  );
1303  */
1305  $ilDB->replace('tst_pass_result',
1306  array(
1307  'active_fi' => array('integer', $active_id),
1308  'pass' => array('integer', strlen($pass) ? $pass : 0)),
1309  array(
1310  'points' => array('float', $row['reachedpoints'] ? $row['reachedpoints'] : 0),
1311  'maxpoints' => array('float', $data['points']),
1312  'questioncount' => array('integer', $data['count']),
1313  'answeredquestions' => array('integer', $row['answeredquestions']),
1314  'workingtime' => array('integer', $time),
1315  'tstamp' => array('integer', time()),
1316  'hint_count' => array('integer', $row['hint_count']),
1317  'hint_points' => array('float', $row['hint_points']),
1318  'obligations_answered' => array('integer', $obligations_answered),
1319  'exam_id' => array('text', $exam_identifier)
1320  )
1321  );
1322 
1323  /*
1324  $ilDB->insert('tst_pass_result', array(
1325  'active_fi' => array('integer', $active_id),
1326  'pass' => array('integer', strlen($pass) ? $pass : 0),
1327  'points' => array('float', $row['reachedpoints'] ? $row['reachedpoints'] : 0),
1328  'maxpoints' => array('float', $data['points']),
1329  'questioncount' => array('integer', $data['count']),
1330  'answeredquestions' => array('integer', $row['answeredquestions']),
1331  'workingtime' => array('integer', $time),
1332  'tstamp' => array('integer', time()),
1333  'hint_count' => array('integer', $row['hint_count']),
1334  'hint_points' => array('float', $row['hint_points']),
1335  'obligations_answered' => array('integer', $obligations_answered),
1336  'exam_id' => array('text', $exam_identifier)
1337  ));
1338  */
1339 
1340  if( is_object($processLocker) )
1341  {
1342  $this->getProcessLocker()->releaseUserPassResultUpdateLock();
1343  }
1344  }
1345 
1347 
1348  return array(
1349  'active_fi' => $active_id,
1350  'pass' => $pass,
1351  'points' => ($row["reachedpoints"]) ? $row["reachedpoints"] : 0,
1352  'maxpoints' => $data["points"],
1353  'questioncount' => $data["count"],
1354  'answeredquestions' => $row["answeredquestions"],
1355  'workingtime' => $time,
1356  'tstamp' => time(),
1357  'hint_count' => $row['hint_count'],
1358  'hint_points' => $row['hint_points'],
1359  'obligations_answered' => $obligations_answered,
1360  'exam_id' => $exam_identifier
1361  );
1362  }
1363 
1371  function logAction($logtext = "", $active_id = "", $question_id = "")
1372  {
1373  global $ilUser;
1374 
1375  $original_id = "";
1376  if (strcmp($question_id, "") != 0)
1377  {
1378  include_once "./Modules/TestQuestionPool/classes/class.assQuestion.php";
1379  $original_id = assQuestion::_getOriginalId($question_id);
1380  }
1381  include_once "./Modules/Test/classes/class.ilObjAssessmentFolder.php";
1382  include_once "./Modules/Test/classes/class.ilObjTest.php";
1383  ilObjAssessmentFolder::_addLog($ilUser->id, ilObjTest::_getObjectIDFromActiveID($active_id), $logtext, $question_id, $original_id);
1384  }
1385 
1393  function _logAction($logtext = "", $active_id = "", $question_id = "")
1394  {
1395  global $ilUser;
1396 
1397  $original_id = "";
1398  if (strcmp($question_id, "") != 0)
1399  {
1400  include_once "./Modules/TestQuestionPool/classes/class.assQuestion.php";
1401  $original_id = assQuestion::_getOriginalId($question_id);
1402  }
1403  include_once "./Modules/Test/classes/class.ilObjAssessmentFolder.php";
1404  include_once "./Modules/Test/classes/class.ilObjTest.php";
1405  ilObjAssessmentFolder::_addLog($ilUser->id, ilObjTest::_getObjectIDFromActiveID($active_id), $logtext, $question_id, $original_id);
1406  }
1407 
1415  function moveUploadedMediaFile($file, $name)
1416  {
1417  $mediatempdir = CLIENT_WEB_DIR . "/assessment/temp";
1418  if (!@is_dir($mediatempdir)) ilUtil::createDirectory($mediatempdir);
1419  $temp_name = tempnam($mediatempdir, $name . "_____");
1420  $temp_name = str_replace("\\", "/", $temp_name);
1421  @unlink($temp_name);
1422  if (!ilUtil::moveUploadedFile($file, $name, $temp_name))
1423  {
1424  return FALSE;
1425  }
1426  else
1427  {
1428  return $temp_name;
1429  }
1430  }
1431 
1438  return CLIENT_WEB_DIR . "/assessment/$this->obj_id/$this->id/solution/";
1439  }
1440 
1447  function getJavaPath() {
1448  return CLIENT_WEB_DIR . "/assessment/$this->obj_id/$this->id/java/";
1449  }
1450 
1457  function getImagePath($question_id = null, $object_id = null)
1458  {
1459  if( $question_id === null)
1460  {
1461  $question_id = $this->id;
1462  }
1463 
1464  if( $object_id === null)
1465  {
1466  $object_id = $this->obj_id;
1467  }
1468 
1469  return $this->buildImagePath($question_id, $object_id);
1470  }
1471 
1472  public function buildImagePath($questionId, $parentObjectId)
1473  {
1474  return CLIENT_WEB_DIR . "/assessment/{$parentObjectId}/{$questionId}/images/";
1475  }
1476 
1483  function getFlashPath()
1484  {
1485  return CLIENT_WEB_DIR . "/assessment/$this->obj_id/$this->id/flash/";
1486  }
1487 
1494  function getJavaPathWeb()
1495  {
1496  include_once "./Services/Utilities/classes/class.ilUtil.php";
1497  $webdir = ilUtil::removeTrailingPathSeparators(CLIENT_WEB_DIR) . "/assessment/$this->obj_id/$this->id/java/";
1499  }
1500 
1507  {
1508  include_once "./Services/Utilities/classes/class.ilUtil.php";
1509  $webdir = ilUtil::removeTrailingPathSeparators(CLIENT_WEB_DIR) . "/assessment/$this->obj_id/$this->id/solution/";
1511  }
1512 
1519  function getImagePathWeb()
1520  {
1521  if(!$this->export_image_path)
1522  {
1523  include_once "./Services/Utilities/classes/class.ilUtil.php";
1524  $webdir = ilUtil::removeTrailingPathSeparators(CLIENT_WEB_DIR) . "/assessment/$this->obj_id/$this->id/images/";
1526  }
1527  else
1528  {
1529  return $this->export_image_path;
1530  }
1531  }
1532 
1539  function getFlashPathWeb()
1540  {
1541  include_once "./Services/Utilities/classes/class.ilUtil.php";
1542  $webdir = ilUtil::removeTrailingPathSeparators(CLIENT_WEB_DIR) . "/assessment/$this->obj_id/$this->id/flash/";
1544  }
1545 
1553  function &getSolutionValues($active_id, $pass = NULL)
1554  {
1555  global $ilDB;
1556 
1557  $values = array();
1558 
1559  if (is_null($pass))
1560  {
1561  $pass = $this->getSolutionMaxPass($active_id);
1562  }
1563 
1564  $result = null;
1565  if( $this->getStep() !== NULL )
1566  {
1567  $result = $ilDB->queryF("SELECT * FROM tst_solutions WHERE active_fi = %s AND question_fi = %s AND pass = %s AND step = %s ORDER BY solution_id",
1568  array('integer','integer','integer', 'integer'),
1569  array($active_id, $this->getId(), $pass, $this->getStep())
1570  );
1571  }
1572  else
1573  {
1574  $result = $ilDB->queryF("SELECT * FROM tst_solutions WHERE active_fi = %s AND question_fi = %s AND pass = %s ORDER BY solution_id",
1575  array('integer','integer','integer'),
1576  array($active_id, $this->getId(), $pass)
1577  );
1578  }
1579 
1580  while ($row = $ilDB->fetchAssoc($result))
1581  {
1582  array_push($values, $row);
1583  }
1584 
1585  return $values;
1586  }
1587 
1594  function isInUse($question_id = "")
1595  {
1596  global $ilDB;
1597 
1598  if ($question_id < 1) $question_id = $this->getId();
1599  $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",
1600  array('integer'),
1601  array($question_id)
1602  );
1603  $row = $ilDB->fetchAssoc($result);
1604  $count = $row["question_count"];
1605 
1606  $result = $ilDB->queryF("SELECT DISTINCT tst_active.test_fi, qpl_questions.question_id FROM qpl_questions, tst_test_rnd_qst, tst_active WHERE qpl_questions.original_id = %s AND qpl_questions.question_id = tst_test_rnd_qst.question_fi AND tst_test_rnd_qst.active_fi = tst_active.active_id",
1607  array('integer'),
1608  array($question_id)
1609  );
1610  $count += $result->numRows();
1611 
1612  return $count;
1613  }
1614 
1621  function isClone($question_id = "")
1622  {
1623  global $ilDB;
1624 
1625  if ($question_id < 1) $question_id = $this->id;
1626  $result = $ilDB->queryF("SELECT original_id FROM qpl_questions WHERE question_id = %s",
1627  array('integer'),
1628  array($question_id)
1629  );
1630  $row = $ilDB->fetchAssoc($result);
1631  return ($row["original_id"] > 0) ? TRUE : FALSE;
1632  }
1633 
1640  function pcArrayShuffle($array)
1641  {
1642  $keys = array_keys($array);
1643  shuffle($keys);
1644  $result = array();
1645  foreach ($keys as $key)
1646  {
1647  $result[$key] = $array[$key];
1648  }
1649  return $result;
1650  }
1651 
1657  function getQuestionTypeFromDb($question_id)
1658  {
1659  global $ilDB;
1660 
1661  $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",
1662  array('integer'),
1663  array($question_id)
1664  );
1665  $data = $ilDB->fetchAssoc($result);
1666  return $data["type_tag"];
1667  }
1668 
1676  {
1677  return "";
1678  }
1679 
1687  {
1688  return "";
1689  }
1690 
1697  function deleteAnswers($question_id)
1698  {
1699  global $ilDB;
1700  $answer_table_name = $this->getAnswerTableName();
1701 
1702  if( !is_array($answer_table_name) )
1703  {
1704  $answer_table_name = array($answer_table_name);
1705  }
1706 
1707  foreach ($answer_table_name as $table)
1708  {
1709  if (strlen($table))
1710  {
1711  $affectedRows = $ilDB->manipulateF("DELETE FROM $table WHERE question_fi = %s",
1712  array('integer'),
1713  array($question_id)
1714  );
1715  }
1716  }
1717  }
1718 
1725  function deleteAdditionalTableData($question_id)
1726  {
1727  global $ilDB;
1728 
1729  $additional_table_name = $this->getAdditionalTableName();
1730 
1731  if( !is_array($additional_table_name) )
1732  {
1733  $additional_table_name = array($additional_table_name);
1734  }
1735 
1736  foreach ($additional_table_name as $table)
1737  {
1738  if (strlen($table))
1739  {
1740  $affectedRows = $ilDB->manipulateF("DELETE FROM $table WHERE question_fi = %s",
1741  array('integer'),
1742  array($question_id)
1743  );
1744  }
1745  }
1746  }
1747 
1754  protected function deletePageOfQuestion($question_id)
1755  {
1756  include_once "./Modules/TestQuestionPool/classes/class.ilAssQuestionPage.php";
1757  $page = new ilAssQuestionPage($question_id);
1758  $page->delete();
1759  return true;
1760  }
1761 
1768  public function delete($question_id)
1769  {
1770  global $ilDB, $ilLog;
1771 
1772  if ($question_id < 1) return true; // nothing to do
1773 
1774  $result = $ilDB->queryF("SELECT obj_fi FROM qpl_questions WHERE question_id = %s",
1775  array('integer'),
1776  array($question_id)
1777  );
1778  if ($result->numRows() == 1)
1779  {
1780  $row = $ilDB->fetchAssoc($result);
1781  $obj_id = $row["obj_fi"];
1782  }
1783  else
1784  {
1785  return true; // nothing to do
1786  }
1787  try
1788  {
1789  $this->deletePageOfQuestion($question_id);
1790  }
1791  catch (Exception $e)
1792  {
1793  $ilLog->write("EXCEPTION: Could not delete page of question $question_id: $e");
1794  return false;
1795  }
1796 
1797  $affectedRows = $ilDB->manipulateF("DELETE FROM qpl_questions WHERE question_id = %s",
1798  array('integer'),
1799  array($question_id)
1800  );
1801  if ($affectedRows == 0) return false;
1802 
1803  try
1804  {
1805  $this->deleteAdditionalTableData($question_id);
1806  $this->deleteAnswers($question_id);
1807  $this->feedbackOBJ->deleteGenericFeedbacks($question_id, $this->isAdditionalContentEditingModePageObject());
1808  $this->feedbackOBJ->deleteSpecificAnswerFeedbacks($question_id, $this->isAdditionalContentEditingModePageObject());
1809  }
1810  catch (Exception $e)
1811  {
1812  $ilLog->write("EXCEPTION: Could not delete additional table data of question $question_id: $e");
1813  return false;
1814  }
1815 
1816  try
1817  {
1818  // delete the question in the tst_test_question table (list of test questions)
1819  $affectedRows = $ilDB->manipulateF("DELETE FROM tst_test_question WHERE question_fi = %s",
1820  array('integer'),
1821  array($question_id)
1822  );
1823  }
1824  catch (Exception $e)
1825  {
1826  $ilLog->write("EXCEPTION: Could not delete delete question $question_id from a test: $e");
1827  return false;
1828  }
1829 
1830  try
1831  {
1832  // delete suggested solutions contained in the question
1833  $affectedRows = $ilDB->manipulateF("DELETE FROM qpl_sol_sug WHERE question_fi = %s",
1834  array('integer'),
1835  array($question_id)
1836  );
1837  }
1838  catch (Exception $e)
1839  {
1840  $ilLog->write("EXCEPTION: Could not delete suggested solutions of question $question_id: $e");
1841  return false;
1842  }
1843 
1844  try
1845  {
1846  $directory = CLIENT_WEB_DIR . "/assessment/" . $obj_id . "/$question_id";
1847  if (preg_match("/\d+/", $obj_id) and preg_match("/\d+/", $question_id) and is_dir($directory))
1848  {
1849  include_once "./Services/Utilities/classes/class.ilUtil.php";
1850  ilUtil::delDir($directory);
1851  }
1852  }
1853  catch (Exception $e)
1854  {
1855  $ilLog->write("EXCEPTION: Could not delete question file directory $directory of question $question_id: $e");
1856  return false;
1857  }
1858 
1859  try
1860  {
1861  include_once("./Services/MediaObjects/classes/class.ilObjMediaObject.php");
1862  $mobs = ilObjMediaObject::_getMobsOfObject("qpl:html", $question_id);
1863  // remaining usages are not in text anymore -> delete them
1864  // and media objects (note: delete method of ilObjMediaObject
1865  // checks whether object is used in another context; if yes,
1866  // the object is not deleted!)
1867  foreach($mobs as $mob)
1868  {
1869  ilObjMediaObject::_removeUsage($mob, "qpl:html", $question_id);
1870  if (ilObjMediaObject::_exists($mob))
1871  {
1872  $mob_obj =& new ilObjMediaObject($mob);
1873  $mob_obj->delete();
1874  }
1875  }
1876  }
1877  catch (Exception $e)
1878  {
1879  $ilLog->write("EXCEPTION: Error deleting the media objects of question $question_id: $e");
1880  return false;
1881  }
1882 
1883  require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionHintTracking.php';
1885 
1886  require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionHintList.php';
1888 
1889  $this->deleteTaxonomyAssignments();
1890 
1891  try
1892  {
1893  // update question count of question pool
1894  include_once "./Modules/TestQuestionPool/classes/class.ilObjQuestionPool.php";
1896  }
1897  catch (Exception $e)
1898  {
1899  $ilLog->write("EXCEPTION: Error updating the question pool question count of question pool " . $this->getObjId() . " when deleting question $question_id: $e");
1900  return false;
1901  }
1902 
1903  $this->notifyQuestionDeleted($this);
1904 
1905  return true;
1906  }
1907 
1908  private function deleteTaxonomyAssignments()
1909  {
1910  require_once 'Services/Taxonomy/classes/class.ilObjTaxonomy.php';
1911  require_once 'Services/Taxonomy/classes/class.ilTaxNodeAssignment.php';
1912  $taxIds = ilObjTaxonomy::getUsageOfObject($this->getObjId());
1913 
1914  foreach($taxIds as $taxId)
1915  {
1916  $taxNodeAssignment = new ilTaxNodeAssignment('qpl', $this->getObjId(), 'quest', $taxId);
1917  $taxNodeAssignment->deleteAssignmentsOfItem($this->getId());
1918  }
1919  }
1920 
1924  function getTotalAnswers()
1925  {
1926  return $this->_getTotalAnswers($this->id);
1927  }
1928 
1935  function _getTotalAnswers($a_q_id)
1936  {
1937  global $ilDB;
1938 
1939  // get all question references to the question id
1940  $result = $ilDB->queryF("SELECT question_id FROM qpl_questions WHERE original_id = %s OR question_id = %s",
1941  array('integer','integer'),
1942  array($a_q_id, $a_q_id)
1943  );
1944  if ($result->numRows() == 0)
1945  {
1946  return 0;
1947  }
1948  $found_id = array();
1949  while ($row = $ilDB->fetchAssoc($result))
1950  {
1951  array_push($found_id, $row["question_id"]);
1952  }
1953 
1954  $result = $ilDB->query("SELECT * FROM tst_test_result WHERE " . $ilDB->in('question_fi', $found_id, false, 'integer'));
1955 
1956  return $result->numRows();
1957  }
1958 
1959 
1966  public static function _getTotalRightAnswers($a_q_id)
1967  {
1968  global $ilDB;
1969  $result = $ilDB->queryF("SELECT question_id FROM qpl_questions WHERE original_id = %s OR question_id = %s",
1970  array('integer','integer'),
1971  array($a_q_id, $a_q_id)
1972  );
1973  if ($result->numRows() == 0)
1974  {
1975  return 0;
1976  }
1977  $found_id = array();
1978  while ($row = $ilDB->fetchAssoc($result))
1979  {
1980  array_push($found_id, $row["question_id"]);
1981  }
1982  $result = $ilDB->query("SELECT * FROM tst_test_result WHERE " . $ilDB->in('question_fi', $found_id, false, 'integer'));
1983  $answers = array();
1984  while ($row = $ilDB->fetchAssoc($result))
1985  {
1986  $reached = $row["points"];
1987  include_once "./Modules/TestQuestionPool/classes/class.assQuestion.php";
1988  $max = assQuestion::_getMaximumPoints($row["question_fi"]);
1989  array_push($answers, array("reached" => $reached, "max" => $max));
1990  }
1991  $max = 0.0;
1992  $reached = 0.0;
1993  foreach ($answers as $key => $value)
1994  {
1995  $max += $value["max"];
1996  $reached += $value["reached"];
1997  }
1998  if ($max > 0)
1999  {
2000  return $reached / $max;
2001  }
2002  else
2003  {
2004  return 0;
2005  }
2006  }
2007 
2013  function _getTitle($a_q_id)
2014  {
2015  global $ilDB;
2016  $result = $ilDB->queryF("SELECT title FROM qpl_questions WHERE question_id = %s",
2017  array('integer'),
2018  array($a_q_id)
2019  );
2020  if ($result->numRows() == 1)
2021  {
2022  $row = $ilDB->fetchAssoc($result);
2023  return $row["title"];
2024  }
2025  else
2026  {
2027  return "";
2028  }
2029  }
2030 
2036  function _getQuestionText($a_q_id)
2037  {
2038  global $ilDB;
2039  $result = $ilDB->queryF("SELECT question_text FROM qpl_questions WHERE question_id = %s",
2040  array('integer'),
2041  array($a_q_id)
2042  );
2043  if ($result->numRows() == 1)
2044  {
2045  $row = $ilDB->fetchAssoc($result);
2046  return $row["question_text"];
2047  }
2048  else
2049  {
2050  return "";
2051  }
2052  }
2053 
2054 
2056  {
2057  include_once("./Services/MediaObjects/classes/class.ilObjMediaObject.php");
2058  $mobs = ilObjMediaObject::_getMobsOfObject("qpl:html", $a_q_id);
2059  foreach ($mobs as $mob)
2060  {
2061  ilObjMediaObject::_saveUsage($mob, "qpl:html", $this->getId());
2062  }
2063  }
2064 
2066  {
2067  include_once("./Services/MediaObjects/classes/class.ilObjMediaObject.php");
2068  $mobs = ilObjMediaObject::_getMobsOfObject("qpl:html", $this->getId());
2069  foreach ($mobs as $mob)
2070  {
2071  ilObjMediaObject::_saveUsage($mob, "qpl:html", $this->original_id);
2072  }
2073  }
2074 
2078  function createPageObject()
2079  {
2080  $qpl_id = $this->getObjId();
2081 
2082  include_once "./Modules/TestQuestionPool/classes/class.ilAssQuestionPage.php";
2083  $this->page = new ilAssQuestionPage(0);
2084  $this->page->setId($this->getId());
2085  $this->page->setParentId($qpl_id);
2086  $this->page->setXMLContent("<PageObject><PageContent>".
2087  "<Question QRef=\"il__qst_".$this->getId()."\"/>".
2088  "</PageContent></PageObject>");
2089  $this->page->create();
2090  }
2091 
2092  function copyPageOfQuestion($a_q_id)
2093  {
2094  if ($a_q_id > 0)
2095  {
2096  include_once "./Modules/TestQuestionPool/classes/class.ilAssQuestionPage.php";
2097  $page = new ilAssQuestionPage($a_q_id);
2098 
2099  $xml = str_replace("il__qst_".$a_q_id, "il__qst_".$this->id, $page->getXMLContent());
2100  $this->page->setXMLContent($xml);
2101  $this->page->updateFromXML();
2102  }
2103  }
2104 
2106  {
2107  include_once "./Modules/TestQuestionPool/classes/class.ilAssQuestionPage.php";
2108  $page = new ilAssQuestionPage($this->id);
2109  return $page->getXMLContent();
2110  }
2111 
2119  function _getQuestionType($question_id)
2120  {
2121  global $ilDB;
2122 
2123  if ($question_id < 1) return "";
2124  $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",
2125  array('integer'),
2126  array($question_id)
2127  );
2128  if ($result->numRows() == 1)
2129  {
2130  $data = $ilDB->fetchAssoc($result);
2131  return $data["type_tag"];
2132  }
2133  else
2134  {
2135  return "";
2136  }
2137  }
2138 
2146  function _getQuestionTitle($question_id)
2147  {
2148  global $ilDB;
2149 
2150  if ($question_id < 1) return "";
2151 
2152  $result = $ilDB->queryF("SELECT title FROM qpl_questions WHERE qpl_questions.question_id = %s",
2153  array('integer'),
2154  array($question_id)
2155  );
2156  if ($result->numRows() == 1)
2157  {
2158  $data = $ilDB->fetchAssoc($result);
2159  return $data["title"];
2160  }
2161  else
2162  {
2163  return "";
2164  }
2165  }
2166 
2168  {
2169  $this->original_id = $original_id;
2170  }
2171 
2172  function getOriginalId()
2173  {
2174  return $this->original_id;
2175  }
2176 
2183  function loadFromDb($question_id)
2184  {
2185  global $ilDB;
2186 
2187  $result = $ilDB->queryF(
2188  "SELECT external_id FROM qpl_questions WHERE question_id = %s",
2189  array("integer"),
2190  array($question_id)
2191  );
2192  if($result->numRows() == 1)
2193  {
2194  $data = $ilDB->fetchAssoc($result);
2195  $this->external_id = $data['external_id'];
2196  }
2197 
2198  $result = $ilDB->queryF("SELECT * FROM qpl_sol_sug WHERE question_fi = %s",
2199  array('integer'),
2200  array($this->getId())
2201  );
2202  $this->suggested_solutions = array();
2203  if ($result->numRows())
2204  {
2205  include_once("./Services/RTE/classes/class.ilRTE.php");
2206  while ($row = $ilDB->fetchAssoc($result))
2207  {
2208  $value = (is_array(unserialize($row["value"]))) ? unserialize($row["value"]) : ilRTE::_replaceMediaObjectImageSrc($row["value"], 1);
2209  $this->suggested_solutions[$row["subquestion_index"]] = array(
2210  "type" => $row["type"],
2211  "value" => $value,
2212  "internal_link" => $row["internal_link"],
2213  "import_id" => $row["import_id"]
2214  );
2215  }
2216  }
2217  }
2218 
2225  public function createNewQuestion($a_create_page = true)
2226  {
2227  global $ilDB, $ilUser;
2228 
2229  $complete = "0";
2230  $estw_time = $this->getEstimatedWorkingTime();
2231  $estw_time = sprintf("%02d:%02d:%02d", $estw_time['h'], $estw_time['m'], $estw_time['s']);
2232  $obj_id = ($this->getObjId() <= 0) ? (ilObject::_lookupObjId((strlen($_GET["ref_id"])) ? $_GET["ref_id"] : $_POST["sel_qpl"])) : $this->getObjId();
2233  if ($obj_id > 0)
2234  {
2235  if($a_create_page)
2236  {
2237  $tstamp = 0;
2238  }
2239  else
2240  {
2241  // question pool must not try to purge
2242  $tstamp = time();
2243  }
2244 
2245  $next_id = $ilDB->nextId('qpl_questions');
2246  $affectedRows = $ilDB->insert("qpl_questions", array(
2247  "question_id" => array("integer", $next_id),
2248  "question_type_fi" => array("integer", $this->getQuestionTypeID()),
2249  "obj_fi" => array("integer", $obj_id),
2250  "title" => array("text", NULL),
2251  "description" => array("text", NULL),
2252  "author" => array("text", $this->getAuthor()),
2253  "owner" => array("integer", $ilUser->getId()),
2254  "question_text" => array("clob", NULL),
2255  "points" => array("float", 0),
2256  "nr_of_tries" => array("integer", $this->getDefaultNrOfTries()), // #10771
2257  "working_time" => array("text", $estw_time),
2258  "complete" => array("text", $complete),
2259  "created" => array("integer", time()),
2260  "original_id" => array("integer", NULL),
2261  "tstamp" => array("integer", $tstamp),
2262  "external_id" => array("text", $this->getExternalId()),
2263  'add_cont_edit_mode' => array('text', $this->getAdditionalContentEditingMode())
2264  ));
2265  $this->setId($next_id);
2266 
2267  if($a_create_page)
2268  {
2269  // create page object of question
2270  $this->createPageObject();
2271  }
2272  }
2273 
2274  $this->notifyQuestionCreated();
2275 
2276  return $this->getId();
2277  }
2278 
2279  public function saveQuestionDataToDb($original_id = "")
2280  {
2281  global $ilDB;
2282 
2283  $estw_time = $this->getEstimatedWorkingTime();
2284  $estw_time = sprintf("%02d:%02d:%02d", $estw_time['h'], $estw_time['m'], $estw_time['s']);
2285 
2286  // cleanup RTE images which are not inserted into the question text
2287  include_once("./Services/RTE/classes/class.ilRTE.php");
2288  if ($this->getId() == -1)
2289  {
2290  // Neuen Datensatz schreiben
2291  $next_id = $ilDB->nextId('qpl_questions');
2292  $affectedRows = $ilDB->insert("qpl_questions", array(
2293  "question_id" => array("integer", $next_id),
2294  "question_type_fi" => array("integer", $this->getQuestionTypeID()),
2295  "obj_fi" => array("integer", $this->getObjId()),
2296  "title" => array("text", $this->getTitle()),
2297  "description" => array("text", $this->getComment()),
2298  "author" => array("text", $this->getAuthor()),
2299  "owner" => array("integer", $this->getOwner()),
2300  "question_text" => array("clob", ilRTE::_replaceMediaObjectImageSrc($this->getQuestion(), 0)),
2301  "points" => array("float", $this->getMaximumPoints()),
2302  "working_time" => array("text", $estw_time),
2303  "nr_of_tries" => array("integer", $this->getNrOfTries()),
2304  "created" => array("integer", time()),
2305  "original_id" => array("integer", ($original_id) ? $original_id : NULL),
2306  "tstamp" => array("integer", time()),
2307  "external_id" => array("text", $this->getExternalId()),
2308  'add_cont_edit_mode' => array('text', $this->getAdditionalContentEditingMode())
2309  ));
2310  $this->setId($next_id);
2311  // create page object of question
2312  $this->createPageObject();
2313  }
2314  else
2315  {
2316  // Vorhandenen Datensatz aktualisieren
2317  $affectedRows = $ilDB->update("qpl_questions", array(
2318  "obj_fi" => array("integer", $this->getObjId()),
2319  "title" => array("text", $this->getTitle()),
2320  "description" => array("text", $this->getComment()),
2321  "author" => array("text", $this->getAuthor()),
2322  "question_text" => array("clob", ilRTE::_replaceMediaObjectImageSrc($this->getQuestion(), 0)),
2323  "points" => array("float", $this->getMaximumPoints()),
2324  "nr_of_tries" => array("integer", $this->getNrOfTries()),
2325  "working_time" => array("text", $estw_time),
2326  "tstamp" => array("integer", time()),
2327  'complete' => array('integer', $this->isComplete()),
2328  "external_id" => array("text", $this->getExternalId())
2329  ), array(
2330  "question_id" => array("integer", $this->getId())
2331  ));
2332  }
2333  }
2334 
2341  function saveToDb($original_id = "")
2342  {
2343  global $ilDB;
2344 
2345  $this->updateSuggestedSolutions();
2346 
2347  // remove unused media objects from ILIAS
2348  $this->cleanupMediaObjectUsage();
2349 
2350  $complete = "0";
2351  if ($this->isComplete())
2352  {
2353  $complete = "1";
2354  }
2355 
2356  // update the question time stamp and completion status
2357  $affectedRows = $ilDB->manipulateF("UPDATE qpl_questions SET tstamp = %s, owner = %s, complete = %s WHERE question_id = %s",
2358  array('integer','integer', 'integer','text'),
2359  array(time(), ($this->getOwner() <= 0) ? $this->ilias->account->id : $this->getOwner(), $complete, $this->getId())
2360  );
2361 
2362  // update question count of question pool
2363  include_once "./Modules/TestQuestionPool/classes/class.ilObjQuestionPool.php";
2365 
2366  $this->notifyQuestionEdited($this);
2367  }
2368 
2369  public function setNewOriginalId($newId) {
2370  global $ilDB;
2371  $ilDB->manipulateF("UPDATE qpl_questions SET tstamp = %s, original_id = %s WHERE question_id = %s",
2372  array('integer','integer', 'text'),
2373  array(time(), $newId, $this->getId())
2374  );
2375  }
2376 
2380  protected function onDuplicate($originalParentId, $originalQuestionId, $duplicateParentId, $duplicateQuestionId)
2381  {
2382  $this->duplicateSuggestedSolutionFiles($originalParentId, $originalQuestionId);
2383 
2384  // duplicate question feeback
2385  $this->feedbackOBJ->duplicateFeedback($originalQuestionId, $duplicateQuestionId);
2386 
2387  // duplicate question hints
2388  $this->duplicateQuestionHints($originalQuestionId, $duplicateQuestionId);
2389  }
2390 
2391  protected function beforeSyncWithOriginal($origQuestionId, $dupQuestionId, $origParentObjId, $dupParentObjId)
2392  {
2393 
2394  }
2395 
2396  protected function afterSyncWithOriginal($origQuestionId, $dupQuestionId, $origParentObjId, $dupParentObjId)
2397  {
2398  // sync question feeback
2399  $this->feedbackOBJ->syncFeedback($origQuestionId, $dupQuestionId);
2400  }
2401 
2405  protected function onCopy($sourceParentId, $sourceQuestionId, $targetParentId, $targetQuestionId)
2406  {
2407  $this->copySuggestedSolutionFiles($sourceParentId, $sourceQuestionId);
2408 
2409  // duplicate question feeback
2410  $this->feedbackOBJ->duplicateFeedback($sourceQuestionId, $targetQuestionId);
2411 
2412  // duplicate question hints
2413  $this->duplicateQuestionHints($sourceQuestionId, $targetQuestionId);
2414  }
2415 
2419  public function deleteSuggestedSolutions()
2420  {
2421  global $ilDB;
2422  // delete the links in the qpl_sol_sug table
2423  $affectedRows = $ilDB->manipulateF("DELETE FROM qpl_sol_sug WHERE question_fi = %s",
2424  array('integer'),
2425  array($this->getId())
2426  );
2427  // delete the links in the int_link table
2428  include_once "./Services/Link/classes/class.ilInternalLink.php";
2430  $this->suggested_solutions = array();
2432  }
2433 
2441  function getSuggestedSolution($subquestion_index = 0)
2442  {
2443  if (array_key_exists($subquestion_index, $this->suggested_solutions))
2444  {
2445  return $this->suggested_solutions[$subquestion_index];
2446  }
2447  else
2448  {
2449  return array();
2450  }
2451  }
2452 
2461  function getSuggestedSolutionTitle($subquestion_index = 0)
2462  {
2463  if (array_key_exists($subquestion_index, $this->suggested_solutions))
2464  {
2465  $title = $this->suggested_solutions[$subquestion_index]["internal_link"];
2466  // TO DO: resolve internal link an get link type and title
2467  }
2468  else
2469  {
2470  $title = "";
2471  }
2472  return $title;
2473  }
2474 
2484  function setSuggestedSolution($solution_id = "", $subquestion_index = 0, $is_import = false)
2485  {
2486  if (strcmp($solution_id, "") != 0)
2487  {
2488  $import_id = "";
2489  if ($is_import)
2490  {
2491  $import_id = $solution_id;
2492  $solution_id = $this->_resolveInternalLink($import_id);
2493  }
2494  $this->suggested_solutions[$subquestion_index] = array(
2495  "internal_link" => $solution_id,
2496  "import_id" => $import_id
2497  );
2498  }
2499  }
2500 
2504  protected function duplicateSuggestedSolutionFiles($parent_id, $question_id)
2505  {
2506  global $ilLog;
2507 
2508  foreach ($this->suggested_solutions as $index => $solution)
2509  {
2510  if (strcmp($solution["type"], "file") == 0)
2511  {
2512  $filepath = $this->getSuggestedSolutionPath();
2513  $filepath_original = str_replace(
2514  "/{$this->obj_id}/{$this->id}/solution",
2515  "/$parent_id/$question_id/solution",
2516  $filepath
2517  );
2518  if (!file_exists($filepath))
2519  {
2520  ilUtil::makeDirParents($filepath);
2521  }
2522  $filename = $solution["value"]["name"];
2523  if (strlen($filename))
2524  {
2525  if (!copy($filepath_original . $filename, $filepath . $filename))
2526  {
2527  $ilLog->write("File could not be duplicated!!!!", $ilLog->ERROR);
2528  $ilLog->write("object: " . print_r($this, TRUE), $ilLog->ERROR);
2529  }
2530  }
2531  }
2532  }
2533  }
2534 
2539  {
2540  global $ilLog;
2541 
2542  $filepath = $this->getSuggestedSolutionPath();
2543  $filepath_original = str_replace("/$this->id/solution", "/$original_id/solution", $filepath);
2544  ilUtil::delDir($filepath_original);
2545  foreach ($this->suggested_solutions as $index => $solution)
2546  {
2547  if (strcmp($solution["type"], "file") == 0)
2548  {
2549  if (!file_exists($filepath_original))
2550  {
2551  ilUtil::makeDirParents($filepath_original);
2552  }
2553  $filename = $solution["value"]["name"];
2554  if (strlen($filename))
2555  {
2556  if (!@copy($filepath . $filename, $filepath_original . $filename))
2557  {
2558  $ilLog->write("File could not be duplicated!!!!", $ilLog->ERROR);
2559  $ilLog->write("object: " . print_r($this, TRUE), $ilLog->ERROR);
2560  }
2561  }
2562  }
2563  }
2564  }
2565 
2566  protected function copySuggestedSolutionFiles($source_questionpool_id, $source_question_id)
2567  {
2568  global $ilLog;
2569 
2570  foreach ($this->suggested_solutions as $index => $solution)
2571  {
2572  if (strcmp($solution["type"], "file") == 0)
2573  {
2574  $filepath = $this->getSuggestedSolutionPath();
2575  $filepath_original = str_replace("/$this->obj_id/$this->id/solution", "/$source_questionpool_id/$source_question_id/solution", $filepath);
2576  if (!file_exists($filepath))
2577  {
2578  ilUtil::makeDirParents($filepath);
2579  }
2580  $filename = $solution["value"]["name"];
2581  if (strlen($filename))
2582  {
2583  if (!copy($filepath_original . $filename, $filepath . $filename))
2584  {
2585  $ilLog->write("File could not be copied!!!!", $ilLog->ERROR);
2586  $ilLog->write("object: " . print_r($this, TRUE), $ilLog->ERROR);
2587  }
2588  }
2589  }
2590  }
2591  }
2592 
2596  public function updateSuggestedSolutions($original_id = "")
2597  {
2598  global $ilDB;
2599 
2600  $id = (strlen($original_id) && is_numeric($original_id)) ? $original_id : $this->getId();
2601  include_once "./Services/Link/classes/class.ilInternalLink.php";
2602  $affectedRows = $ilDB->manipulateF("DELETE FROM qpl_sol_sug WHERE question_fi = %s",
2603  array('integer'),
2604  array($id)
2605  );
2607  include_once("./Services/RTE/classes/class.ilRTE.php");
2608  foreach ($this->suggested_solutions as $index => $solution)
2609  {
2610  $next_id = $ilDB->nextId('qpl_sol_sug');
2612  $ilDB->insert('qpl_sol_sug', array(
2613  'suggested_solution_id' => array( 'integer', $next_id ),
2614  'question_fi' => array( 'integer', $id ),
2615  'type' => array( 'text', $solution['type'] ),
2616  'value' => array( 'clob', ilRTE::_replaceMediaObjectImageSrc( (is_array( $solution['value'] ) ) ? serialize( $solution[ 'value' ] ) : $solution['value'], 0 ) ),
2617  'internal_link' => array( 'text', $solution['internal_link'] ),
2618  'import_id' => array( 'text', null ),
2619  'subquestion_index' => array( 'integer', $index ),
2620  'tstamp' => array( 'integer', time() ),
2621  )
2622  );
2623  if (preg_match("/il_(\d*?)_(\w+)_(\d+)/", $solution["internal_link"], $matches))
2624  {
2625  ilInternalLink::_saveLink("qst", $id, $matches[2], $matches[3], $matches[1]);
2626  }
2627  }
2628  if (strlen($original_id) && is_numeric($original_id)) $this->syncSuggestedSolutionFiles($id);
2629  $this->cleanupMediaObjectUsage();
2630  }
2631 
2641  function saveSuggestedSolution($type, $solution_id = "", $subquestion_index = 0, $value = "")
2642  {
2643  global $ilDB;
2644 
2645  $affectedRows = $ilDB->manipulateF("DELETE FROM qpl_sol_sug WHERE question_fi = %s AND subquestion_index = %s",
2646  array("integer", "integer"),
2647  array(
2648  $this->getId(),
2649  $subquestion_index
2650  )
2651  );
2652 
2653  $next_id = $ilDB->nextId('qpl_sol_sug');
2654  include_once("./Services/RTE/classes/class.ilRTE.php");
2656  $affectedRows = $ilDB->insert('qpl_sol_sug', array(
2657  'suggested_solution_id' => array( 'integer', $next_id ),
2658  'question_fi' => array( 'integer', $this->getId() ),
2659  'type' => array( 'text', $type ),
2660  'value' => array( 'clob', ilRTE::_replaceMediaObjectImageSrc( (is_array( $value ) ) ? serialize( $value ) : $value, 0 ) ),
2661  'internal_link' => array( 'text', $solution_id ),
2662  'import_id' => array( 'text', null ),
2663  'subquestion_index' => array( 'integer', $subquestion_index ),
2664  'tstamp' => array( 'integer', time() ),
2665  )
2666  );
2667  if ($affectedRows == 1)
2668  {
2669  $this->suggested_solutions["subquestion_index"] = array(
2670  "type" => $type,
2671  "value" => $value,
2672  "internal_link" => $solution_id,
2673  "import_id" => ""
2674  );
2675  }
2676  $this->cleanupMediaObjectUsage();
2677  }
2678 
2679  function _resolveInternalLink($internal_link)
2680  {
2681  if (preg_match("/il_(\d+)_(\w+)_(\d+)/", $internal_link, $matches))
2682  {
2683  include_once "./Services/Link/classes/class.ilInternalLink.php";
2684  include_once "./Modules/LearningModule/classes/class.ilLMObject.php";
2685  include_once "./Modules/Glossary/classes/class.ilGlossaryTerm.php";
2686  switch ($matches[2])
2687  {
2688  case "lm":
2689  $resolved_link = ilLMObject::_getIdForImportId($internal_link);
2690  break;
2691  case "pg":
2692  $resolved_link = ilInternalLink::_getIdForImportId("PageObject", $internal_link);
2693  break;
2694  case "st":
2695  $resolved_link = ilInternalLink::_getIdForImportId("StructureObject", $internal_link);
2696  break;
2697  case "git":
2698  $resolved_link = ilInternalLink::_getIdForImportId("GlossaryItem", $internal_link);
2699  break;
2700  case "mob":
2701  $resolved_link = ilInternalLink::_getIdForImportId("MediaObject", $internal_link);
2702  break;
2703  }
2704  if (strcmp($resolved_link, "") == 0)
2705  {
2706  $resolved_link = $internal_link;
2707  }
2708  }
2709  else
2710  {
2711  $resolved_link = $internal_link;
2712  }
2713  return $resolved_link;
2714  }
2715 
2716  function _resolveIntLinks($question_id)
2717  {
2718  global $ilDB;
2719  $resolvedlinks = 0;
2720  $result = $ilDB->queryF("SELECT * FROM qpl_sol_sug WHERE question_fi = %s",
2721  array('integer'),
2722  array($question_id)
2723  );
2724  if ($result->numRows())
2725  {
2726  while ($row = $ilDB->fetchAssoc($result))
2727  {
2728  $internal_link = $row["internal_link"];
2729  include_once "./Modules/TestQuestionPool/classes/class.assQuestion.php";
2730  $resolved_link = assQuestion::_resolveInternalLink($internal_link);
2731  if (strcmp($internal_link, $resolved_link) != 0)
2732  {
2733  // internal link was resolved successfully
2734  $affectedRows = $ilDB->manipulateF("UPDATE qpl_sol_sug SET internal_link = %s WHERE suggested_solution_id = %s",
2735  array('text','integer'),
2736  array($resolved_link, $row["suggested_solution_id"])
2737  );
2738  $resolvedlinks++;
2739  }
2740  }
2741  }
2742  if ($resolvedlinks)
2743  {
2744  // there are resolved links -> reenter theses links to the database
2745 
2746  // delete all internal links from the database
2747  include_once "./Services/Link/classes/class.ilInternalLink.php";
2748  ilInternalLink::_deleteAllLinksOfSource("qst", $question_id);
2749 
2750  $result = $ilDB->queryF("SELECT * FROM qpl_sol_sug WHERE question_fi = %s",
2751  array('integer'),
2752  array($question_id)
2753  );
2754  if ($result->numRows())
2755  {
2756  while ($row = $ilDB->fetchAssoc($result))
2757  {
2758  if (preg_match("/il_(\d*?)_(\w+)_(\d+)/", $row["internal_link"], $matches))
2759  {
2760  ilInternalLink::_saveLink("qst", $question_id, $matches[2], $matches[3], $matches[1]);
2761  }
2762  }
2763  }
2764  }
2765  }
2766 
2767  function _getInternalLinkHref($target = "")
2768  {
2769  global $ilDB;
2770  $linktypes = array(
2771  "lm" => "LearningModule",
2772  "pg" => "PageObject",
2773  "st" => "StructureObject",
2774  "git" => "GlossaryItem",
2775  "mob" => "MediaObject"
2776  );
2777  $href = "";
2778  if (preg_match("/il__(\w+)_(\d+)/", $target, $matches))
2779  {
2780  $type = $matches[1];
2781  $target_id = $matches[2];
2782  include_once "./Services/Utilities/classes/class.ilUtil.php";
2783  switch($linktypes[$matches[1]])
2784  {
2785  case "LearningModule":
2786  $href = "./goto.php?target=" . $type . "_" . $target_id;
2787  break;
2788  case "PageObject":
2789  case "StructureObject":
2790  $href = "./goto.php?target=" . $type . "_" . $target_id;
2791  break;
2792  case "GlossaryItem":
2793  $href = "./goto.php?target=" . $type . "_" . $target_id;
2794  break;
2795  case "MediaObject":
2796  $href = "./ilias.php?baseClass=ilLMPresentationGUI&obj_type=" . $linktypes[$type] . "&cmd=media&ref_id=".$_GET["ref_id"]."&mob_id=".$target_id;
2797  break;
2798  }
2799  }
2800  return $href;
2801  }
2802 
2810  public static function _getOriginalId($question_id)
2811  {
2812  global $ilDB;
2813  $result = $ilDB->queryF("SELECT * FROM qpl_questions WHERE question_id = %s",
2814  array('integer'),
2815  array($question_id)
2816  );
2817  if ($result->numRows() > 0)
2818  {
2819  $row = $ilDB->fetchAssoc($result);
2820  if ($row["original_id"] > 0)
2821  {
2822  return $row["original_id"];
2823  }
2824  else
2825  {
2826  return $row["question_id"];
2827  }
2828  }
2829  else
2830  {
2831  return "";
2832  }
2833  }
2834 
2835  public static function originalQuestionExists($questionId)
2836  {
2837  global $ilDB;
2838 
2839  $query = "
2840  SELECT COUNT(dupl.question_id) cnt
2841  FROM qpl_questions dupl
2842  INNER JOIN qpl_questions orig
2843  ON orig.question_id = dupl.original_id
2844  WHERE dupl.question_id = %s
2845  ";
2846 
2847  $res = $ilDB->queryF($query, array('integer'), array($questionId));
2848  $row = $ilDB->fetchAssoc($res);
2849 
2850  return $row['cnt'] > 0;
2851  }
2852 
2853  function syncWithOriginal()
2854  {
2855  global $ilDB;
2856 
2857  if( !$this->getOriginalId() )
2858  {
2859  return;
2860  }
2861 
2862  $originalObjId = self::lookupOriginalParentObjId($this->getOriginalId());
2863 
2864  if ( !$originalObjId )
2865  {
2866  return;
2867  }
2868 
2869  $id = $this->getId();
2870  $objId = $this->getObjId();
2871  $original = $this->getOriginalId();
2872 
2873  $this->beforeSyncWithOriginal($original, $id, $originalObjId, $objId);
2874 
2875  $this->setId($this->getOriginalId());
2876  $this->setOriginalId(NULL);
2877  $this->setObjId($originalObjId);
2878  $this->saveToDb();
2879  $this->deletePageOfQuestion($original);
2880  $this->createPageObject();
2881  $this->copyPageOfQuestion($id);
2882 
2883  $this->setId($id);
2884  $this->setOriginalId($original);
2885  $this->updateSuggestedSolutions($original);
2887 
2888  $this->afterSyncWithOriginal($original, $id, $originalObjId, $objId);
2889  $this->syncHints();
2890  }
2891 
2892  function createRandomSolution($test_id, $user_id)
2893  {
2894  }
2895 
2903  function _questionExists($question_id)
2904  {
2905  global $ilDB;
2906 
2907  if ($question_id < 1)
2908  {
2909  return false;
2910  }
2911 
2912  $result = $ilDB->queryF("SELECT question_id FROM qpl_questions WHERE question_id = %s",
2913  array('integer'),
2914  array($question_id)
2915  );
2916  if ($result->numRows() == 1)
2917  {
2918  return true;
2919  }
2920  else
2921  {
2922  return false;
2923  }
2924  }
2925 
2933  function _questionExistsInPool($question_id)
2934  {
2935  global $ilDB;
2936 
2937  if ($question_id < 1)
2938  {
2939  return false;
2940  }
2941 
2942  $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'",
2943  array('integer'),
2944  array($question_id)
2945  );
2946  if ($result->numRows() == 1)
2947  {
2948  return true;
2949  }
2950  else
2951  {
2952  return false;
2953  }
2954  }
2955 
2963  public static function _instanciateQuestion($question_id)
2964  {
2965  return self::_instantiateQuestion($question_id);
2966  }
2967 
2968  public static function _instantiateQuestion($question_id)
2969  {
2970  global $ilCtrl, $ilDB, $lng;
2971 
2972  if (strcmp($question_id, "") != 0)
2973  {
2974  $question_type = assQuestion::_getQuestionType($question_id);
2975  if (!strlen($question_type)) return null;
2976  assQuestion::_includeClass($question_type);
2977  $objectClassname = self::getObjectClassNameByQuestionType($question_type);
2978  $question = new $objectClassname();
2979  $question->loadFromDb($question_id);
2980 
2981  $feedbackObjectClassname = self::getFeedbackClassNameByQuestionType($question_type);
2982  $question->feedbackOBJ = new $feedbackObjectClassname($question, $ilCtrl, $ilDB, $lng);
2983 
2984  return $question;
2985  }
2986  }
2987 
2994  function getPoints()
2995  {
2996  if (strcmp($this->points, "") == 0)
2997  {
2998  return 0;
2999  }
3000  else
3001  {
3002  return $this->points;
3003  }
3004  }
3005 
3006 
3013  function setPoints($a_points)
3014  {
3015  $this->points = $a_points;
3016  }
3017 
3024  function getSolutionMaxPass($active_id)
3025  {
3026  return $this->_getSolutionMaxPass($this->getId(), $active_id);
3027  }
3028 
3035  function _getSolutionMaxPass($question_id, $active_id)
3036  {
3037 /* include_once "./Modules/Test/classes/class.ilObjTest.php";
3038  $pass = ilObjTest::_getPass($active_id);
3039  return $pass;*/
3040 
3041  // the following code was the old solution which added the non answered
3042  // questions of a pass from the answered questions of the previous pass
3043  // with the above solution, only the answered questions of the last pass are counted
3044  global $ilDB;
3045 
3046  $result = $ilDB->queryF("SELECT MAX(pass) maxpass FROM tst_test_result WHERE active_fi = %s AND question_fi = %s",
3047  array('integer','integer'),
3048  array($active_id, $question_id)
3049  );
3050  if ($result->numRows() == 1)
3051  {
3052  $row = $ilDB->fetchAssoc($result);
3053  return $row["maxpass"];
3054  }
3055  else
3056  {
3057  return 0;
3058  }
3059  }
3060 
3069  function _isWriteable($question_id, $user_id)
3070  {
3071  global $ilDB;
3072 
3073  if (($question_id < 1) || ($user_id < 1))
3074  {
3075  return false;
3076  }
3077 
3078  $result = $ilDB->queryF("SELECT obj_fi FROM qpl_questions WHERE question_id = %s",
3079  array('integer'),
3080  array($question_id)
3081  );
3082  if ($result->numRows() == 1)
3083  {
3084  $row = $ilDB->fetchAssoc($result);
3085  $qpl_object_id = $row["obj_fi"];
3086  include_once "./Modules/TestQuestionPool/classes/class.ilObjQuestionPool.php";
3087  return ilObjQuestionPool::_isWriteable($qpl_object_id, $user_id);
3088  }
3089  else
3090  {
3091  return false;
3092  }
3093  }
3094 
3101  function _isUsedInRandomTest($question_id = "")
3102  {
3103  global $ilDB;
3104 
3105  if ($question_id < 1) return 0;
3106  $result = $ilDB->queryF("SELECT test_random_question_id FROM tst_test_rnd_qst WHERE question_fi = %s",
3107  array('integer'),
3108  array($question_id)
3109  );
3110  return $result->numRows();
3111  }
3112 
3124  abstract public function calculateReachedPoints($active_id, $pass = NULL, $returndetails = FALSE);
3125 
3127  {
3128  return $this->calculateReachedPointsForSolution($previewSession->getParticipantsSolution());
3129  }
3130 
3132  {
3133  $reachedPoints = $this->calculateReachedPointsFromPreviewSession($previewSession);
3134 
3135  if( $reachedPoints < $this->getMaximumPoints() )
3136  {
3137  return false;
3138  }
3139 
3140  return true;
3141  }
3142 
3143 
3154  final public function adjustReachedPointsByScoringOptions($points, $active_id, $pass = NULL)
3155  {
3156  include_once "./Modules/Test/classes/class.ilObjTest.php";
3157  $count_system = ilObjTest::_getCountSystem($active_id);
3158  if ($count_system == 1)
3159  {
3160  if (abs($this->getMaximumPoints() - $points) > 0.0000000001)
3161  {
3162  $points = 0;
3163  }
3164  }
3165  $score_cutting = ilObjTest::_getScoreCutting($active_id);
3166  if ($score_cutting == 0)
3167  {
3168  if ($points < 0)
3169  {
3170  $points = 0;
3171  }
3172  }
3173  return $points;
3174  }
3175 
3184  public static function _isWorkedThrough($active_id, $question_id, $pass = NULL)
3185  {
3186  global $ilDB;
3187 
3188  $points = 0;
3189  if (is_null($pass))
3190  {
3191  include_once "./Modules/TestQuestionPool/classes/class.assQuestion.php";
3192  $pass = assQuestion::_getSolutionMaxPass($question_id, $active_id);
3193  }
3194  $result = $ilDB->queryF("SELECT solution_id FROM tst_solutions WHERE active_fi = %s AND question_fi = %s AND pass = %s",
3195  array('integer','integer','integer'),
3196  array($active_id, $question_id, $pass)
3197  );
3198  if ($result->numRows())
3199  {
3200  return TRUE;
3201  }
3202  else
3203  {
3204  return FALSE;
3205  }
3206  }
3207 
3215  public static function _areAnswered($a_user_id,$a_question_ids)
3216  {
3217  global $ilDB;
3218 
3219  $res = $ilDB->queryF("SELECT DISTINCT(question_fi) FROM tst_test_result JOIN tst_active ".
3220  "ON (active_id = active_fi) ".
3221  "WHERE " . $ilDB->in('question_fi', $a_question_ids, false, 'integer') .
3222  " AND user_fi = %s",
3223  array('integer'),
3224  array($a_user_id)
3225  );
3226  return ($res->numRows() == count($a_question_ids)) ? true : false;
3227  }
3228 
3237  function isHTML($a_text)
3238  {
3239  if (preg_match("/<[^>]*?>/", $a_text))
3240  {
3241  return TRUE;
3242  }
3243  else
3244  {
3245  return FALSE;
3246  }
3247  }
3248 
3255  function prepareTextareaOutput($txt_output, $prepare_for_latex_output = FALSE)
3256  {
3257  include_once "./Services/Utilities/classes/class.ilUtil.php";
3258  return ilUtil::prepareTextareaOutput($txt_output, $prepare_for_latex_output);
3259  }
3260 
3268  function QTIMaterialToString($a_material)
3269  {
3270  $result = "";
3271  for ($i = 0; $i < $a_material->getMaterialCount(); $i++)
3272  {
3273  $material = $a_material->getMaterial($i);
3274  if (strcmp($material["type"], "mattext") == 0)
3275  {
3276  $result .= $material["material"]->getContent();
3277  }
3278  if (strcmp($material["type"], "matimage") == 0)
3279  {
3280  $matimage = $material["material"];
3281  if (preg_match("/(il_([0-9]+)_mob_([0-9]+))/", $matimage->getLabel(), $matches))
3282  {
3283  // import an mediaobject which was inserted using tiny mce
3284  if (!is_array($_SESSION["import_mob_xhtml"])) $_SESSION["import_mob_xhtml"] = array();
3285  array_push($_SESSION["import_mob_xhtml"], array("mob" => $matimage->getLabel(), "uri" => $matimage->getUri()));
3286  }
3287  }
3288  }
3289  return $result;
3290  }
3291 
3300  function addQTIMaterial(&$a_xml_writer, $a_material, $close_material_tag = TRUE, $add_mobs = TRUE)
3301  {
3302  include_once "./Services/RTE/classes/class.ilRTE.php";
3303  include_once("./Services/MediaObjects/classes/class.ilObjMediaObject.php");
3304 
3305  $a_xml_writer->xmlStartTag("material");
3306  $attrs = array(
3307  "texttype" => "text/plain"
3308  );
3309  if ($this->isHTML($a_material))
3310  {
3311  $attrs["texttype"] = "text/xhtml";
3312  }
3313  $a_xml_writer->xmlElement("mattext", $attrs, ilRTE::_replaceMediaObjectImageSrc($a_material, 0));
3314  if ($add_mobs)
3315  {
3316  $mobs = ilObjMediaObject::_getMobsOfObject("qpl:html", $this->getId());
3317  foreach ($mobs as $mob)
3318  {
3319  $moblabel = "il_" . IL_INST_ID . "_mob_" . $mob;
3320  if (strpos($a_material, "mm_$mob") !== FALSE)
3321  {
3322  if (ilObjMediaObject::_exists($mob))
3323  {
3324  $mob_obj =& new ilObjMediaObject($mob);
3325  $imgattrs = array(
3326  "label" => $moblabel,
3327  "uri" => "objects/" . "il_" . IL_INST_ID . "_mob_" . $mob . "/" . $mob_obj->getTitle()
3328  );
3329  }
3330  $a_xml_writer->xmlElement("matimage", $imgattrs, NULL);
3331  }
3332  }
3333  }
3334  if ($close_material_tag) $a_xml_writer->xmlEndTag("material");
3335  }
3336 
3337  function createNewImageFileName($image_filename, $unique = false)
3338  {
3339  $extension = "";
3340 
3341  if (preg_match("/.*\.(png|jpg|gif|jpeg)$/i", $image_filename, $matches))
3342  {
3343  $extension = "." . $matches[1];
3344  }
3345 
3346  if($unique)
3347  {
3348  $image_filename = uniqid($image_filename.microtime(true));
3349  }
3350 
3351  $image_filename = md5($image_filename) . $extension;
3352 
3353  return $image_filename;
3354  }
3355 
3366  function _setReachedPoints($active_id, $question_id, $points, $maxpoints, $pass, $manualscoring, $obligationsEnabled)
3367  {
3368  global $ilDB;
3369 
3370  if ($points <= $maxpoints)
3371  {
3372  if (is_null($pass))
3373  {
3374  $pass = assQuestion::_getSolutionMaxPass($question_id, $active_id);
3375  }
3376 
3377  // retrieve the already given points
3378  $old_points = 0;
3379  $result = $ilDB->queryF("SELECT points FROM tst_test_result WHERE active_fi = %s AND question_fi = %s AND pass = %s",
3380  array('integer','integer','integer'),
3381  array($active_id, $question_id, $pass)
3382  );
3383  $manual = ($manualscoring) ? 1 : 0;
3384  $rowsnum = $result->numRows();
3385  if($rowsnum)
3386  {
3387  $row = $ilDB->fetchAssoc($result);
3388  $old_points = $row["points"];
3389  if($old_points != $points)
3390  {
3391  $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",
3392  array('float', 'integer', 'integer', 'integer', 'integer', 'integer'),
3393  array($points, $manual, time(), $active_id, $question_id, $pass)
3394  );
3395  }
3396  }
3397  else
3398  {
3399  $next_id = $ilDB->nextId('tst_test_result');
3400  $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)",
3401  array('integer', 'integer','integer', 'float', 'integer', 'integer','integer'),
3402  array($next_id, $active_id, $question_id, $points, $pass, $manual, time())
3403  );
3404  }
3405 
3406  if($old_points != $points || !$rowsnum)
3407  {
3408  assQuestion::_updateTestPassResults($active_id, $pass, $obligationsEnabled);
3409  // finally update objective result
3410  include_once "./Modules/Test/classes/class.ilObjTest.php";
3411  include_once './Modules/Course/classes/class.ilCourseObjectiveResult.php';
3413 
3414  include_once ("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
3416  {
3417  global $lng, $ilUser;
3418  include_once "./Modules/Test/classes/class.ilObjTestAccess.php";
3419  $username = ilObjTestAccess::_getParticipantData($active_id);
3420  assQuestion::_logAction(sprintf($lng->txtlng("assessment", "log_answer_changed_points", ilObjAssessmentFolder::_getLogLanguage()), $username, $old_points, $points, $ilUser->getFullname() . " (" . $ilUser->getLogin() . ")"), $active_id, $question_id);
3421  }
3422  }
3423 
3424  return TRUE;
3425  }
3426  else
3427  {
3428  return FALSE;
3429  }
3430  }
3431 
3439  function getQuestion()
3440  {
3441  return $this->question;
3442  }
3443 
3451  function setQuestion($question = "")
3452  {
3453  $this->question = $question;
3454  }
3455 
3461  abstract public function getQuestionType();
3462 
3472  {
3473  global $ilDB;
3474 
3475  $result = $ilDB->queryF("SELECT question_type_id FROM qpl_qst_type WHERE type_tag = %s",
3476  array('text'),
3477  array($this->getQuestionType())
3478  );
3479  if ($result->numRows() == 1)
3480  {
3481  $row = $ilDB->fetchAssoc($result);
3482  return $row["question_type_id"];
3483  }
3484  return 0;
3485  }
3486 
3487  public function syncHints()
3488  {
3489  global $ilDB;
3490 
3491  // delete hints of the original
3492  $ilDB->manipulateF("DELETE FROM qpl_hints WHERE qht_question_fi = %s",
3493  array('integer'),
3494  array($this->original_id)
3495  );
3496 
3497  // get hints of the actual question
3498  $result = $ilDB->queryF("SELECT * FROM qpl_hints WHERE qht_question_fi = %s",
3499  array('integer'),
3500  array($this->getId())
3501  );
3502 
3503  // save hints to the original
3504  if ($result->numRows())
3505  {
3506  while ($row = $ilDB->fetchAssoc($result))
3507  {
3508  $next_id = $ilDB->nextId('qpl_hints');
3510  $ilDB->insert('qpl_hints', array(
3511  'qht_hint_id' => array('integer', $next_id),
3512  'qht_question_fi' => array('integer', $this->original_id),
3513  'qht_hint_index' => array('integer', $row["qht_hint_index"]),
3514  'qht_hint_points' => array('integer', $row["qht_hint_points"]),
3515  'qht_hint_text' => array('text', $row["qht_hint_text"]),
3516  )
3517  );
3518  }
3519  }
3520  }
3521 
3526  protected function getRTETextWithMediaObjects()
3527  {
3528  // must be called in parent classes. add additional RTE text in the parent
3529  // classes and call this method to add the standard RTE text
3530  $collected = $this->getQuestion();
3531  $collected .= $this->feedbackOBJ->getGenericFeedbackContent($this->getId(), false);
3532  $collected .= $this->feedbackOBJ->getGenericFeedbackContent($this->getId(), true);
3533  for( $i = 0; $i <= $this->getTotalAnswers(); $i++ )
3534  {
3535  $collected .= $this->feedbackOBJ->getSpecificAnswerFeedbackContent($this->getId(), $i);
3536  }
3537  foreach ($this->suggested_solutions as $solution_array)
3538  {
3539  $collected .= $solution_array["value"];
3540  }
3541  return $collected;
3542  }
3543 
3549  {
3550  $combinedtext = $this->getRTETextWithMediaObjects();
3551  include_once("./Services/RTE/classes/class.ilRTE.php");
3552  ilRTE::_cleanupMediaObjectUsage($combinedtext, "qpl:html", $this->getId());
3553  }
3554 
3560  function &getInstances()
3561  {
3562  global $ilDB;
3563 
3564  $result = $ilDB->queryF("SELECT question_id FROM qpl_questions WHERE original_id = %s",
3565  array("integer"),
3566  array($this->getId())
3567  );
3568  $instances = array();
3569  $ids = array();
3570  while ($row = $ilDB->fetchAssoc($result))
3571  {
3572  array_push($ids, $row["question_id"]);
3573  }
3574  foreach ($ids as $question_id)
3575  {
3576  // check non random tests
3577  $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",
3578  array("integer"),
3579  array($question_id)
3580  );
3581  while ($row = $ilDB->fetchAssoc($result))
3582  {
3583  $instances[$row['obj_fi']] = ilObject::_lookupTitle($row['obj_fi']);
3584  }
3585  // check random tests
3586  $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",
3587  array("integer"),
3588  array($question_id)
3589  );
3590  while ($row = $ilDB->fetchAssoc($result))
3591  {
3592  $instances[$row['obj_fi']] = ilObject::_lookupTitle($row['obj_fi']);
3593  }
3594  }
3595  include_once "./Modules/Test/classes/class.ilObjTest.php";
3596  foreach ($instances as $key => $value)
3597  {
3598  $instances[$key] = array("obj_id" => $key, "title" => $value, "author" => ilObjTest::_lookupAuthor($key), "refs" => ilObject::_getAllReferences($key));
3599  }
3600  return $instances;
3601  }
3602 
3603  function _needsManualScoring($question_id)
3604  {
3605  include_once "./Modules/Test/classes/class.ilObjAssessmentFolder.php";
3607  $questiontype = assQuestion::_getQuestionType($question_id);
3608  if (in_array($questiontype, $scoring))
3609  {
3610  return TRUE;
3611  }
3612  else
3613  {
3614  return FALSE;
3615  }
3616  }
3617 
3625  function getActiveUserData($active_id)
3626  {
3627  global $ilDB;
3628  $result = $ilDB->queryF("SELECT * FROM tst_active WHERE active_id = %s",
3629  array('integer'),
3630  array($active_id)
3631  );
3632  if ($result->numRows())
3633  {
3634  $row = $ilDB->fetchAssoc($result);
3635  return array("user_id" => $row["user_fi"], "test_id" => $row["test_fi"]);
3636  }
3637  else
3638  {
3639  return array();
3640  }
3641  }
3642 
3650  static function _includeClass($question_type, $gui = 0)
3651  {
3652  if( self::isCoreQuestionType($question_type) )
3653  {
3654  self::includeCoreClass($question_type, $gui);
3655  }
3656  else
3657  {
3658  self::includePluginClass($question_type, $gui);
3659  }
3660  }
3661 
3662  public static function getGuiClassNameByQuestionType($questionType)
3663  {
3664  return $questionType.'GUI';
3665  }
3666 
3667  public static function getObjectClassNameByQuestionType($questionType)
3668  {
3669  return $questionType;
3670  }
3671 
3672  public static function getFeedbackClassNameByQuestionType($questionType)
3673  {
3674  return str_replace('ass', 'ilAss', $questionType).'Feedback';
3675  }
3676 
3677  public static function isCoreQuestionType($questionType)
3678  {
3679  $guiClassName = self::getGuiClassNameByQuestionType($questionType);
3680  return file_exists("Modules/TestQuestionPool/classes/class.{$guiClassName}.php");
3681  }
3682 
3683  public static function includeCoreClass($questionType, $withGuiClass)
3684  {
3685  if( $withGuiClass )
3686  {
3687  $guiClassName = self::getGuiClassNameByQuestionType($questionType);
3688  require_once "Modules/TestQuestionPool/classes/class.{$guiClassName}.php";
3689 
3690  // object class is included by gui classes constructor
3691  }
3692  else
3693  {
3694  $objectClassName = self::getObjectClassNameByQuestionType($questionType);
3695  require_once "Modules/TestQuestionPool/classes/class.{$objectClassName}.php";
3696  }
3697 
3698  $feedbackClassName = self::getFeedbackClassNameByQuestionType($questionType);
3699  require_once "Modules/TestQuestionPool/classes/feedback/class.{$feedbackClassName}.php";
3700  }
3701 
3702  public static function includePluginClass($questionType, $withGuiClass)
3703  {
3704  global $ilPluginAdmin;
3705 
3706  $classes = array(
3707  self::getObjectClassNameByQuestionType($questionType),
3708  self::getFeedbackClassNameByQuestionType($questionType)
3709  );
3710 
3711  if( $withGuiClass )
3712  {
3713  $classes[] = self::getGuiClassNameByQuestionType($questionType);
3714  }
3715 
3716  $pl_names = $ilPluginAdmin->getActivePluginsForSlot(IL_COMP_MODULE, "TestQuestionPool", "qst");
3717  foreach ($pl_names as $pl_name)
3718  {
3719  $pl = ilPlugin::getPluginObject(IL_COMP_MODULE, "TestQuestionPool", "qst", $pl_name);
3720  if (strcmp($pl->getQuestionType(), $questionType) == 0)
3721  {
3722  foreach($classes as $class)
3723  {
3724  $pl->includeClass("class.{$class}.php");
3725  }
3726 
3727  break;
3728  }
3729  }
3730  }
3731 
3738  static function _getQuestionTypeName($type_tag)
3739  {
3740  if (file_exists("./Modules/TestQuestionPool/classes/class.".$type_tag.".php"))
3741  {
3742  global $lng;
3743  return $lng->txt($type_tag);
3744  }
3745  else
3746  {
3747  global $ilPluginAdmin;
3748  $pl_names = $ilPluginAdmin->getActivePluginsForSlot(IL_COMP_MODULE, "TestQuestionPool", "qst");
3749  foreach ($pl_names as $pl_name)
3750  {
3751  $pl = ilPlugin::getPluginObject(IL_COMP_MODULE, "TestQuestionPool", "qst", $pl_name);
3752  if (strcmp($pl->getQuestionType(), $type_tag) == 0)
3753  {
3754  return $pl->getQuestionTypeTranslation();
3755  }
3756  }
3757  }
3758  return "";
3759  }
3760 
3770  public static function &_instanciateQuestionGUI($question_id)
3771  {
3772  return self::instantiateQuestionGUI($question_id);
3773  }
3774 
3782  public static function instantiateQuestionGUI($a_question_id)
3783  {
3784  global $ilCtrl, $ilDB, $lng, $ilUser;
3785 
3786  if (strcmp($a_question_id, "") != 0)
3787  {
3788  $question_type = assQuestion::_getQuestionType($a_question_id);
3789 
3790  assQuestion::_includeClass($question_type, 1);
3791 
3792  $question_type_gui = self::getGuiClassNameByQuestionType($question_type);
3793  $question_gui = new $question_type_gui();
3794  $question_gui->object->loadFromDb($a_question_id);
3795 
3796  $feedbackObjectClassname = self::getFeedbackClassNameByQuestionType($question_type);
3797  $question_gui->object->feedbackOBJ = new $feedbackObjectClassname($question_gui->object, $ilCtrl, $ilDB, $lng);
3798 
3799  $assSettings = new ilSetting('assessment');
3800  require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionProcessLockerFactory.php';
3801  $processLockerFactory = new ilAssQuestionProcessLockerFactory($assSettings, $ilDB);
3802  $processLockerFactory->setQuestionId($question_gui->object->getId());
3803  $processLockerFactory->setUserId($ilUser->getId());
3804  include_once ("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
3805  $processLockerFactory->setAssessmentLogEnabled(ilObjAssessmentFolder::_enabledAssessmentLogging());
3806  $question_gui->object->setProcessLocker($processLockerFactory->getLocker());
3807  }
3808  else
3809  {
3810  global $ilLog;
3811  $ilLog->write('Instantiate question called without question id. (instantiateQuestionGUI@assQuestion)', $ilLog->WARNING);
3812  return null;
3813  }
3814  return $question_gui;
3815  }
3816 
3829  public function setExportDetailsXLS(&$worksheet, $startrow, $active_id, $pass, &$format_title, &$format_bold)
3830  {
3831  return $startrow;
3832  }
3833 
3837  public function __get($value)
3838  {
3839  switch ($value)
3840  {
3841  case "id":
3842  return $this->getId();
3843  break;
3844  case "title":
3845  return $this->getTitle();
3846  break;
3847  case "comment":
3848  return $this->getComment();
3849  break;
3850  case "owner":
3851  return $this->getOwner();
3852  break;
3853  case "author":
3854  return $this->getAuthor();
3855  break;
3856  case "question":
3857  return $this->getQuestion();
3858  break;
3859  case "points":
3860  return $this->getPoints();
3861  break;
3862  case "est_working_time":
3863  return $this->getEstimatedWorkingTime();
3864  break;
3865  case "shuffle":
3866  return $this->getShuffle();
3867  break;
3868  case "test_id":
3869  return $this->getTestId();
3870  break;
3871  case "obj_id":
3872  return $this->getObjId();
3873  break;
3874  case "ilias":
3875  return $this->ilias;
3876  break;
3877  case "tpl":
3878  return $this->tpl;
3879  break;
3880  case "page":
3881  return $this->page;
3882  break;
3883  case "outputType":
3884  return $this->getOutputType();
3885  break;
3886  case "suggested_solutions":
3887  return $this->getSuggestedSolutions();
3888  break;
3889  case "original_id":
3890  return $this->getOriginalId();
3891  break;
3892  default:
3893  if (array_key_exists($value, $this->arrData))
3894  {
3895  return $this->arrData[$value];
3896  }
3897  else
3898  {
3899  return null;
3900  }
3901  break;
3902  }
3903  }
3904 
3908  public function __set($key, $value)
3909  {
3910  switch ($key)
3911  {
3912  case "id":
3913  $this->setId($value);
3914  break;
3915  case "title":
3916  $this->setTitle($value);
3917  break;
3918  case "comment":
3919  $this->setComment($value);
3920  break;
3921  case "owner":
3922  $this->setOwner($value);
3923  break;
3924  case "author":
3925  $this->setAuthor($value);
3926  break;
3927  case "question":
3928  $this->setQuestion($value);
3929  break;
3930  case "points":
3931  $this->setPoints($value);
3932  break;
3933  case "est_working_time":
3934  if (is_array($value))
3935  {
3936  $this->setEstimatedWorkingTime($value["h"], $value["m"], $value["s"]);
3937  }
3938  break;
3939  case "shuffle":
3940  $this->setShuffle($value);
3941  break;
3942  case "test_id":
3943  $this->setTestId($value);
3944  break;
3945  case "obj_id":
3946  $this->setObjId($value);
3947  break;
3948  case "outputType":
3949  $this->setOutputType($value);
3950  break;
3951  case "original_id":
3952  $this->setOriginalId($value);
3953  break;
3954  case "page":
3955  $this->page =& $value;
3956  break;
3957  default:
3958  $this->arrData[$key] = $value;
3959  break;
3960  }
3961  }
3962 
3963  public function getNrOfTries()
3964  {
3965  return (int)$this->nr_of_tries;
3966  }
3967 
3968  public function setNrOfTries($a_nr_of_tries)
3969  {
3970  $this->nr_of_tries = $a_nr_of_tries;
3971  }
3972 
3973  public function setExportImagePath($a_path)
3974  {
3975  $this->export_image_path = (string)$a_path;
3976  }
3977 
3978  function _questionExistsInTest($question_id, $test_id)
3979  {
3980  global $ilDB;
3981 
3982  if ($question_id < 1)
3983  {
3984  return false;
3985  }
3986 
3987  $result = $ilDB->queryF("SELECT question_fi FROM tst_test_question WHERE question_fi = %s AND test_fi = %s",
3988  array('integer', 'integer'),
3989  array($question_id, $test_id)
3990  );
3991  if ($result->numRows() == 1)
3992  {
3993  return true;
3994  }
3995  else
3996  {
3997  return false;
3998  }
3999  }
4000 
4007  function formatSAQuestion($a_q)
4008  {
4009  include_once("./Services/RTE/classes/class.ilRTE.php");
4010  $a_q = nl2br((string) ilRTE::_replaceMediaObjectImageSrc($a_q, 0));
4011  $a_q = str_replace("</li><br />", "</li>", $a_q);
4012  $a_q = str_replace("</li><br>", "</li>", $a_q);
4013 
4014  $a_q = ilUtil::insertLatexImages($a_q, "\[tex\]", "\[\/tex\]");
4015  $a_q = ilUtil::insertLatexImages($a_q, "<span class\=\"latex\">", "<\/span>");
4016 
4017  $a_q = str_replace('{', '&#123;', $a_q);
4018  $a_q = str_replace('}', '&#125;', $a_q);
4019 
4020  return $a_q;
4021  }
4022 
4023  // scorm2004-start ???
4024 
4030  function setPreventRteUsage($a_val)
4031  {
4032  $this->prevent_rte_usage = $a_val;
4033  }
4034 
4041  {
4042  return $this->prevent_rte_usage;
4043  }
4044 
4050  function setSelfAssessmentEditingMode($a_selfassessmenteditingmode)
4051  {
4052  $this->selfassessmenteditingmode = $a_selfassessmenteditingmode;
4053  }
4054 
4061  {
4063  }
4064 
4070  function setDefaultNrOfTries($a_defaultnroftries)
4071  {
4072  $this->defaultnroftries = $a_defaultnroftries;
4073  }
4074 
4081  {
4082  return (int)$this->defaultnroftries;
4083  }
4084 
4085  // scorm2004-end ???
4086 
4092  public static function lookupParentObjId($questionId)
4093  {
4094  global $ilDB;
4095 
4096  $query = "SELECT obj_fi FROM qpl_questions WHERE question_id = %s";
4097 
4098  $res = $ilDB->queryF($query, array('integer'), array((int)$questionId));
4099  $row = $ilDB->fetchAssoc($res);
4100 
4101  return $row['obj_fi'];
4102  }
4103 
4114  public static function lookupOriginalParentObjId($originalQuestionId)
4115  {
4116  return self::lookupParentObjId($originalQuestionId);
4117  }
4118 
4119  protected function duplicateQuestionHints($originalQuestionId, $duplicateQuestionId)
4120  {
4121  require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionHintList.php';
4122  $hintIds = ilAssQuestionHintList::duplicateListForQuestion($originalQuestionId, $duplicateQuestionId);
4123 
4125  {
4126  require_once 'Modules/TestQuestionPool/classes/class.ilAssHintPage.php';
4127 
4128  foreach($hintIds as $originalHintId => $duplicateHintId)
4129  {
4130  $originalPageObject = new ilAssHintPage($originalHintId);
4131  $originalXML = $originalPageObject->getXMLContent();
4132 
4133  $duplicatePageObject = new ilAssHintPage();
4134  $duplicatePageObject->setId($duplicateHintId);
4135  $duplicatePageObject->setParentId($this->getId());
4136  $duplicatePageObject->setXMLContent($originalXML);
4137  $duplicatePageObject->createFromXML();
4138  }
4139  }
4140  }
4141 
4154  public function isAnswered($active_id, $pass = null)
4155  {
4156  return true;
4157  }
4158 
4171  public static function isObligationPossible($questionId)
4172  {
4173  return false;
4174  }
4175 
4182  {
4183  $this->obligationsToBeConsidered = (bool)$obligationsToBeConsidered;
4184  }
4185 
4192  {
4193  return (bool)$this->obligationsToBeConsidered;
4194  }
4195 
4196  public function isAutosaveable()
4197  {
4198  return TRUE;
4199  }
4200 
4213  protected static function getNumExistingSolutionRecords($activeId, $pass, $questionId)
4214  {
4215  global $ilDB;
4216 
4217  $query = "
4218  SELECT count(active_fi) cnt
4219 
4220  FROM tst_solutions
4221 
4222  WHERE active_fi = %s
4223  AND question_fi = %s
4224  AND pass = %s
4225  ";
4226 
4227  $res = $ilDB->queryF(
4228  $query, array('integer','integer','integer'),
4229  array($activeId, $questionId, $pass)
4230  );
4231 
4232  $row = $ilDB->fetchAssoc($res);
4233 
4234  return (int)$row['cnt'];
4235  }
4236 
4244  {
4246  }
4247 
4255  {
4257  {
4258  require_once 'Modules/TestQuestionPool/exceptions/class.ilTestQuestionPoolException.php';
4259  throw new ilTestQuestionPoolException('invalid additional content editing mode given: '.$additinalContentEditingMode);
4260  }
4261 
4262  $this->additinalContentEditingMode = $additinalContentEditingMode;
4263  }
4264 
4272  {
4274  }
4275 
4283  public function isValidAdditionalContentEditingMode($additionalContentEditingMode)
4284  {
4285  if( in_array($additionalContentEditingMode, $this->getValidAdditionalContentEditingModes()) )
4286  {
4287  return true;
4288  }
4289 
4290  return false;
4291  }
4292 
4300  {
4301  return array(
4302  self::ADDITIONAL_CONTENT_EDITING_MODE_DEFAULT,
4303  self::ADDITIONAL_CONTENT_EDITING_MODE_PAGE_OBJECT
4304  );
4305  }
4306 
4311  {
4312  $this->questionChangeListeners[] = $listener;
4313  }
4314 
4318  public function getQuestionChangeListeners()
4319  {
4321  }
4322 
4323  private function notifyQuestionCreated()
4324  {
4325  foreach($this->getQuestionChangeListeners() as $listener)
4326  {
4327  $listener->notifyQuestionCreated($this);
4328  }
4329  }
4330 
4331  private function notifyQuestionEdited()
4332  {
4333  foreach($this->getQuestionChangeListeners() as $listener)
4334  {
4335  $listener->notifyQuestionEdited($this);
4336  }
4337  }
4338 
4339  private function notifyQuestionDeleted()
4340  {
4341  foreach($this->getQuestionChangeListeners() as $listener)
4342  {
4343  $listener->notifyQuestionDeleted($this);
4344  }
4345  }
4346 
4351  {
4352  require_once 'Services/Html/classes/class.ilHtmlPurifierFactory.php';
4353  return ilHtmlPurifierFactory::_getInstanceByType('qpl_usersolution');
4354  }
4355 
4360  {
4361  require_once 'Services/Html/classes/class.ilHtmlPurifierFactory.php';
4362  return ilHtmlPurifierFactory::_getInstanceByType('qpl_usersolution');
4363  }
4364 
4365  protected function buildQuestionDataQuery()
4366  {
4367  return "
4368  SELECT qpl_questions.*,
4369  {$this->getAdditionalTableName()}.*
4370  FROM qpl_questions
4371  LEFT JOIN {$this->getAdditionalTableName()}
4372  ON {$this->getAdditionalTableName()}.question_fi = qpl_questions.question_id
4373  WHERE qpl_questions.question_id = %s
4374  ";
4375  }
4376 
4377  public function setLastChange($lastChange)
4378  {
4379  $this->lastChange = $lastChange;
4380  }
4381 
4382  public function getLastChange()
4383  {
4384  return $this->lastChange;
4385  }
4386 
4395  protected function getCurrentSolutionResultSet($active_id, $pass)
4396  {
4398  global $ilDB;
4399 
4400  if($this->getStep() !== NULL)
4401  {
4402  return $ilDB->queryF("SELECT * FROM tst_solutions WHERE active_fi = %s AND question_fi = %s AND pass = %s AND step = %s",
4403  array('integer','integer','integer', 'integer'),
4404  array($active_id, $this->getId(), $pass, $this->getStep())
4405  );
4406  }
4407  else
4408  {
4409  return $ilDB->queryF("SELECT * FROM tst_solutions WHERE active_fi = %s AND question_fi = %s AND pass = %s",
4410  array('integer','integer','integer'),
4411  array($active_id, $this->getId(), $pass)
4412  );
4413  }
4414 
4415  }
4416 
4423  protected function removeCurrentSolution($active_id, $pass)
4424  {
4428  global $ilDB;
4429 
4430  if($this->getStep() !== NULL)
4431  {
4432  return $ilDB->manipulateF("DELETE FROM tst_solutions WHERE active_fi = %s AND question_fi = %s AND pass = %s AND step = %s",
4433  array('integer','integer','integer', 'integer'),
4434  array($active_id, $this->getId(), $pass, $this->getStep())
4435  );
4436  }
4437  else
4438  {
4439  return $ilDB->manipulateF("DELETE FROM tst_solutions WHERE active_fi = %s AND question_fi = %s AND pass = %s",
4440  array('integer','integer','integer'),
4441  array($active_id, $this->getId(), $pass)
4442  );
4443  }
4444 
4445  }
4446 
4455  public function saveCurrentSolution($active_id, $pass, $value1, $value2)
4456  {
4458  global $ilDB;
4459 
4460  $next_id = $ilDB->nextId("tst_solutions");
4461 
4462  $fieldData = array(
4463  "solution_id" => array("integer", $next_id),
4464  "active_fi" => array("integer", $active_id),
4465  "question_fi" => array("integer", $this->getId()),
4466  "value1" => array("clob", $value1),
4467  "value2" => array("clob", $value2),
4468  "pass" => array("integer", $pass),
4469  "tstamp" => array("integer", time())
4470  );
4471 
4472  if( $this->getStep() !== null )
4473  {
4474  $fieldData['step'] = array("integer", $this->getStep());
4475  }
4476 
4477  $aff = $ilDB->insert("tst_solutions", $fieldData);
4478 
4479  return $aff;
4480  }
4481 
4482 
4486  public static function setResultGateway($resultGateway)
4487  {
4488  self::$resultGateway = $resultGateway;
4489  }
4490 
4494  public static function getResultGateway()
4495  {
4496  return self::$resultGateway;
4497  }
4498 
4502  public function setStep($step)
4503  {
4504  $this->step = $step;
4505  }
4506 
4510  public function getStep()
4511  {
4512  return $this->step;
4513  }
4514 
4520  public static function sumTimesInISO8601FormatH_i_s_Extended($time1, $time2)
4521  {
4524  return gmdate('H:i:s', $time);
4525  }
4526 
4531  public static function convertISO8601FormatH_i_s_ExtendedToSeconds($time)
4532  {
4533  $sec = 0;
4534  $time_array = explode(':',$time);
4535  if( sizeof($time_array) == 3)
4536  {
4537  $sec += $time_array[0] * 3600;
4538  $sec += $time_array[1] * 60;
4539  $sec += $time_array[2];
4540  }
4541  return $sec;
4542  }
4543 
4544  public function toJSON()
4545  {
4546  return json_encode(array());
4547  }
4548 
4549  abstract public function duplicate($for_test = true, $title = "", $author = "", $owner = "", $testObjId = null);
4550 }