ILIAS  release_4-3 Revision
 All Data Structures Namespaces Files Functions Variables Groups Pages
class.assQuestion.php
Go to the documentation of this file.
1 <?php
2 
3 /* Copyright (c) 1998-2010 ILIAS open source, Extended GPL, see docs/LICENSE */
4 
5 include_once "./Modules/Test/classes/inc.AssessmentConstants.php";
6 
21 abstract class assQuestion
22 {
28  protected $id;
29 
35  protected $title;
36 
42  protected $comment;
43 
49  protected $owner;
50 
56  protected $author;
57 
63  protected $question;
64 
70  protected $points;
71 
77  protected $est_working_time;
78 
84  protected $shuffle;
85 
91  protected $test_id;
92 
98  protected $obj_id;
99 
105  protected $ilias;
106 
112  protected $tpl;
113 
119  protected $lng;
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 
182  function __construct(
183  $title = "",
184  $comment = "",
185  $author = "",
186  $owner = -1,
187  $question = ""
188  )
189  {
190  global $ilias;
191  global $lng;
192  global $tpl;
193 
194  $this->ilias =& $ilias;
195  $this->lng =& $lng;
196  $this->tpl =& $tpl;
197 
198  $this->original_id = null;
199  $this->title = $title;
200  $this->comment = $comment;
201  $this->page = null;
202  $this->author = $author;
203  $this->setQuestion($question);
204  if (!$this->author)
205  {
206  $this->author = $this->ilias->account->fullname;
207  }
208  $this->owner = $owner;
209  if ($this->owner <= 0)
210  {
211  $this->owner = $this->ilias->account->id;
212  }
213  $this->id = -1;
214  $this->test_id = -1;
215  $this->suggested_solutions = array();
216  $this->shuffle = 1;
217  $this->nr_of_tries = "";
218  $this->setEstimatedWorkingTime(0,1,0);
219  $this->outputType = OUTPUT_HTML;
220  $this->arrData = array();
221  }
222 
234  function fromXML(&$item, &$questionpool_id, &$tst_id, &$tst_object, &$question_counter, &$import_mapping)
235  {
236  include_once "./Modules/TestQuestionPool/classes/import/qti12/class." . $this->getQuestionType() . "Import.php";
237  $classname = $this->getQuestionType() . "Import";
238  $import = new $classname($this);
239  $import->fromXML($item, $questionpool_id, $tst_id, $tst_object, $question_counter, $import_mapping);
240  }
241 
248  function toXML($a_include_header = true, $a_include_binary = true, $a_shuffle = false, $test_output = false, $force_image_references = false)
249  {
250  include_once "./Modules/TestQuestionPool/classes/export/qti12/class." . $this->getQuestionType() . "Export.php";
251  $classname = $this->getQuestionType() . "Export";
252  $export = new $classname($this);
253  return $export->toXML($a_include_header, $a_include_binary, $a_shuffle, $test_output, $force_image_references);
254  }
255 
262  function isComplete()
263  {
264  return false;
265  }
266 
274  function questionTitleExists($questionpool_id, $title)
275  {
276  global $ilDB;
277 
278  $result = $ilDB->queryF("SELECT * FROM qpl_questions WHERE obj_fi = %s AND title = %s",
279  array('integer','text'),
280  array($questionpool_id, $title)
281  );
282  return ($result->numRows() == 1) ? TRUE : FALSE;
283  }
284 
292  function setTitle($title = "")
293  {
294  $this->title = $title;
295  }
296 
304  function setId($id = -1)
305  {
306  $this->id = $id;
307  }
308 
316  function setTestId($id = -1)
317  {
318  $this->test_id = $id;
319  }
320 
328  function setComment($comment = "")
329  {
330  $this->comment = $comment;
331  }
332 
341  {
342  $this->outputType = $outputType;
343  }
344 
345 
353  function setShuffle($shuffle = true)
354  {
355  if ($shuffle)
356  {
357  $this->shuffle = 1;
358  }
359  else
360  {
361  $this->shuffle = 0;
362  }
363  }
364 
374  function setEstimatedWorkingTime($hour=0, $min=0, $sec=0)
375  {
376  $this->est_working_time = array("h" => (int)$hour, "m" => (int)$min, "s" => (int)$sec);
377  }
378 
386  function keyInArray($searchkey, $array)
387  {
388  if ($searchkey)
389  {
390  foreach ($array as $key => $value)
391  {
392  if (strcmp($key, $searchkey)==0)
393  {
394  return true;
395  }
396  }
397  }
398  return false;
399  }
400 
408  function setAuthor($author = "")
409  {
410  if (!$author)
411  {
412  $author = $this->ilias->account->fullname;
413  }
414  $this->author = $author;
415  }
416 
424  function setOwner($owner = "")
425  {
426  $this->owner = $owner;
427  }
428 
436  function getTitle()
437  {
438  return $this->title;
439  }
440 
448  function getId()
449  {
450  return $this->id;
451  }
452 
460  function getShuffle()
461  {
462  return $this->shuffle;
463  }
464 
472  function getTestId()
473  {
474  return $this->test_id;
475  }
476 
484  function getComment()
485  {
486  return $this->comment;
487  }
488 
496  function getOutputType()
497  {
498  return $this->outputType;
499  }
500 
508  {
509  return FALSE;
510  }
511 
520  {
521  if (!$this->est_working_time)
522  {
523  $this->est_working_time = array("h" => 0, "m" => 0, "s" => 0);
524  }
526  }
527 
535  function getAuthor()
536  {
537  return $this->author;
538  }
539 
547  function getOwner()
548  {
549  return $this->owner;
550  }
551 
559  function getObjId()
560  {
561  return $this->obj_id;
562  }
563 
571  function setObjId($obj_id = 0)
572  {
573  $this->obj_id = $obj_id;
574  }
575 
582  function _getMaximumPoints($question_id)
583  {
584  global $ilDB;
585 
586  $points = 0;
587  $result = $ilDB->queryF("SELECT points FROM qpl_questions WHERE question_id = %s",
588  array('integer'),
589  array($question_id)
590  );
591  if ($result->numRows() == 1)
592  {
593  $row = $ilDB->fetchAssoc($result);
594  $points = $row["points"];
595  }
596  return $points;
597  }
598 
605  function &_getQuestionInfo($question_id)
606  {
607  global $ilDB;
608 
609  $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",
610  array('integer'),
611  array($question_id)
612  );
613  if ($result->numRows())
614  {
615  return $ilDB->fetchAssoc($result);
616  }
617  else return array();
618  }
619 
626  public static function _getSuggestedSolutionCount($question_id)
627  {
628  global $ilDB;
629 
630  $result = $ilDB->queryF("SELECT suggested_solution_id FROM qpl_sol_sug WHERE question_fi = %s",
631  array('integer'),
632  array($question_id)
633  );
634  return $result->numRows();
635  }
636 
643  public static function _getSuggestedSolutionOutput($question_id)
644  {
646  if (!is_object($question)) return "";
647  return $question->getSuggestedSolutionOutput();
648  }
649 
650  public function getSuggestedSolutionOutput()
651  {
652  $output = array();
653  foreach ($this->suggested_solutions as $solution)
654  {
655  switch ($solution["type"])
656  {
657  case "lm":
658  case "st":
659  case "pg":
660  case "git":
661  array_push($output, '<a href="' . assQuestion::_getInternalLinkHref($solution["internal_link"]) . '">' . $this->lng->txt("solution_hint") . '</a>');
662  break;
663  case "file":
664  $possible_texts = array_values(array_filter(array(
665  ilUtil::prepareFormOutput($solution['value']['filename']),
666  ilUtil::prepareFormOutput($solution['value']['name']),
667  $this->lng->txt('tst_show_solution_suggested')
668  )));
669  array_push($output, '<a href="' . $this->getSuggestedSolutionPathWeb() . $solution["value"]["name"] . '">' . $possible_texts[0] . '</a>');
670  break;
671  case "text":
672  array_push($output, $this->prepareTextareaOutput($solution["value"]));
673  break;
674  }
675  }
676  return join($output, "<br />");
677  }
678 
687  function &_getSuggestedSolution($question_id, $subquestion_index = 0)
688  {
689  global $ilDB;
690 
691  $result = $ilDB->queryF("SELECT * FROM qpl_sol_sug WHERE question_fi = %s AND subquestion_index = %s",
692  array('integer','integer'),
693  array($question_id, $subquestion_index)
694  );
695  if ($result->numRows() == 1)
696  {
697  $row = $ilDB->fetchAssoc($result);
698  return array(
699  "internal_link" => $row["internal_link"],
700  "import_id" => $row["import_id"]
701  );
702  }
703  else
704  {
705  return array();
706  }
707  }
708 
714  public function getSuggestedSolutions()
715  {
717  }
718 
726  function _getReachedPoints($active_id, $question_id, $pass = NULL)
727  {
728  global $ilDB;
729 
730  $points = 0;
731  if (is_null($pass))
732  {
733  include_once "./Modules/TestQuestionPool/classes/class.assQuestion.php";
734  $pass = assQuestion::_getSolutionMaxPass($question_id, $active_id);
735  }
736  $result = $ilDB->queryF("SELECT * FROM tst_test_result WHERE active_fi = %s AND question_fi = %s AND pass = %s",
737  array('integer','integer','integer'),
738  array($active_id, $question_id, $pass)
739  );
740  if ($result->numRows() == 1)
741  {
742  $row = $ilDB->fetchAssoc($result);
743  $points = $row["points"];
744  }
745  return $points;
746  }
747 
756  function getReachedPoints($active_id, $pass = NULL)
757  {
758  return round($this->_getReachedPoints($active_id, $this->getId(), $pass), 2);
759  }
760 
767  function getMaximumPoints()
768  {
769  return $this->points;
770  }
771 
783  final public function getAdjustedReachedPoints($active_id, $pass = NULL)
784  {
785  if (is_null($pass))
786  {
787  include_once "./Modules/Test/classes/class.ilObjTest.php";
788  $pass = ilObjTest::_getPass($active_id);
789  }
790 
791  // determine reached points for submitted solution
792  $reached_points = $this->calculateReachedPoints($active_id, $pass);
793 
794  // deduct points for requested hints from reached points
795  require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionHintTracking.php';
796  $requestsStatisticData = ilAssQuestionHintTracking::getRequestStatisticDataByQuestionAndTestpass($this->getId(), $active_id, $pass);
797  $reached_points = $reached_points - $requestsStatisticData->getRequestsPoints();
798 
799  // adjust reached points regarding to tests scoring options
800  $reached_points = $this->adjustReachedPointsByScoringOptions($reached_points, $active_id, $pass);
801 
802  return $reached_points;
803  }
804 
814  final public function calculateResultsFromSolution($active_id, $pass = NULL, $obligationsEnabled = false)
815  {
816  global $ilDB, $ilUser;
817 
818  if( is_null($pass) )
819  {
820  include_once "./Modules/Test/classes/class.ilObjTest.php";
821  $pass = ilObjTest::_getPass($active_id);
822  }
823 
824  // determine reached points for submitted solution
825  $reached_points = $this->calculateReachedPoints($active_id, $pass);
826 
827  // deduct points for requested hints from reached points
828  require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionHintTracking.php';
829  $requestsStatisticData = ilAssQuestionHintTracking::getRequestStatisticDataByQuestionAndTestpass($this->getId(), $active_id, $pass);
830  $reached_points = $reached_points - $requestsStatisticData->getRequestsPoints();
831 
832  // adjust reached points regarding to tests scoring options
833  $reached_points = $this->adjustReachedPointsByScoringOptions($reached_points, $active_id, $pass);
834 
835  if( $obligationsEnabled && ilObjTest::isQuestionObligatory($this->getId()) )
836  {
837  $isAnswered = $this->isAnswered($active_id, $pass);
838  }
839  else
840  {
841  $isAnswered = true;
842  }
843 
844  if( is_null($reached_points) ) $reached_points = 0;
845 
846  $query = "
847  DELETE FROM tst_test_result
848 
849  WHERE active_fi = %s
850  AND question_fi = %s
851  AND pass = %s
852  ";
853 
854  $affectedRows = $ilDB->manipulateF(
855  $query, array("integer", "integer", "integer"), array($active_id, $this->getId(), $pass)
856  );
857 
858  $next_id = $ilDB->nextId("tst_test_result");
859 
860  $ilDB->insert('tst_test_result', array(
861  'test_result_id' => array('integer', $next_id),
862  'active_fi' => array('integer', $active_id),
863  'question_fi' => array('integer', $this->getId()),
864  'pass' => array('integer', $pass),
865  'points' => array('float', $reached_points),
866  'tstamp' => array('integer', time()),
867  'hint_count' => array('integer', $requestsStatisticData->getRequestsCount()),
868  'hint_points' => array('float', $requestsStatisticData->getRequestsPoints()),
869  'answered' => array('integer', $isAnswered)
870  ));
871 
872  include_once ("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
873 
875  {
876  $this->logAction(
877  sprintf(
878  $this->lng->txtlng(
879  "assessment", "log_user_answered_question", ilObjAssessmentFolder::_getLogLanguage()
880  ),
881  $reached_points
882  ),
883  $active_id,
884  $this->getId()
885  );
886  }
887 
888  // update test pass results
889  $this->_updateTestPassResults($active_id, $pass, $obligationsEnabled);
890 
891  // Update objective status
892  include_once 'Modules/Course/classes/class.ilCourseObjectiveResult.php';
893  ilCourseObjectiveResult::_updateObjectiveResult($ilUser->getId(),$active_id,$this->getId());
894  }
895 
904  final public function persistWorkingState($active_id, $pass = NULL, $obligationsEnabled = false)
905  {
906  if( $pass === null )
907  {
908  require_once 'Modules/Test/classes/class.ilObjTest.php';
909  $pass = ilObjTest::_getPass($active_id);
910  }
911 
912  $saveStatus = $this->saveWorkingData($active_id, $pass);
913 
914  $this->calculateResultsFromSolution($active_id, $pass, $obligationsEnabled);
915 
916  $this->reworkWorkingData($active_id, $pass, $obligationsEnabled);
917 
918  return $saveStatus;
919  }
920 
930  abstract public function saveWorkingData($active_id, $pass = NULL);
931 
941  abstract protected function reworkWorkingData($active_id, $pass, $obligationsAnswered);
942 
943  function _updateTestResultCache($active_id)
944  {
945  global $ilDB;
946 
947  include_once "./Modules/Test/classes/class.ilObjTest.php";
948  include_once "./Modules/Test/classes/class.assMarkSchema.php";
949 
950  $pass = ilObjTest::_getResultPass($active_id);
951 
952  $query = "
953  SELECT tst_pass_result.*
954  FROM tst_pass_result
955  WHERE active_fi = %s
956  AND pass = %s
957  ";
958 
959  $result = $ilDB->queryF(
960  $query, array('integer','integer'), array($active_id, $pass)
961  );
962 
963  $row = $ilDB->fetchAssoc($result);
964 
965  $max = $row['maxpoints'];
966  $reached = $row['points'];
967 
968  $obligationsAnswered = (int)$row['obligations_answered'];
969 
970  include_once "./Modules/Test/classes/class.assMarkSchema.php";
971 
972  $percentage = (!$max) ? 0 : ($reached / $max) * 100.0;
973 
974  $mark = ASS_MarkSchema::_getMatchingMarkFromActiveId($active_id, $percentage);
975 
976  $isPassed = ( $mark["passed"] ? 1 : 0 );
977  $isFailed = ( !$mark["passed"] ? 1 : 0 );
978 
979  $query = "
980  DELETE FROM tst_result_cache
981  WHERE active_fi = %s
982  ";
983 
984  $affectedRows = $ilDB->manipulateF(
985  $query, array('integer'), array($active_id)
986  );
987 
988  $ilDB->insert('tst_result_cache', array(
989  'active_fi'=> array('integer', $active_id),
990  'pass'=> array('integer', strlen($pass) ? $pass : 0),
991  'max_points'=> array('float', strlen($max) ? $max : 0),
992  'reached_points'=> array('float', strlen($reached) ? $reached : 0),
993  'mark_short'=> array('text', strlen($mark["short_name"]) ? $mark["short_name"] : " "),
994  'mark_official'=> array('text', strlen($mark["official_name"]) ? $mark["official_name"] : " "),
995  'passed'=> array('integer', $isPassed),
996  'failed'=> array('integer', $isFailed),
997  'tstamp'=> array('integer', time()),
998  'hint_count'=> array('integer', $row['hint_count']),
999  'hint_points'=> array('float', $row['hint_points']),
1000  'obligations_answered' => array('integer', $obligationsAnswered)
1001  ));
1002  }
1003 
1004  function _updateTestPassResults($active_id, $pass, $obligationsEnabled = false)
1005  {
1006  global $ilDB;
1007 
1008  include_once "./Modules/Test/classes/class.ilObjTest.php";
1009 
1012 
1013  // update test pass results
1014 
1015  $result = $ilDB->queryF("
1016  SELECT SUM(points) reachedpoints,
1017  SUM(hint_count) hint_count,
1018  SUM(hint_points) hint_points,
1019  COUNT(question_fi) answeredquestions
1020  FROM tst_test_result
1021  WHERE active_fi = %s
1022  AND pass = %s
1023  ",
1024  array('integer','integer'),
1025  array($active_id, $pass)
1026  );
1027 
1028  if ($result->numRows() > 0)
1029  {
1030  if( $obligationsEnabled )
1031  {
1032  $query = '
1033  SELECT count(*) cnt,
1034  min( answered ) answ
1035  FROM tst_test_question
1036  INNER JOIN tst_active
1037  ON active_id = %s
1038  AND tst_test_question.test_fi = tst_active.test_fi
1039  LEFT JOIN tst_test_result
1040  ON tst_test_result.active_fi = %s
1041  AND tst_test_result.pass = %s
1042  AND tst_test_question.question_fi = tst_test_result.question_fi
1043  WHERE obligatory = 1';
1044 
1045  $result_obligatory = $ilDB->queryF(
1046  $query, array('integer','integer','integer'), array($active_id, $active_id, $pass)
1047  );
1048 
1049  $row_obligatory = $ilDB->fetchAssoc($result_obligatory);
1050 
1051  if ($row_obligatory['cnt'] == 0)
1052  {
1053  $obligations_answered = 1;
1054  }
1055  else
1056  {
1057  $obligations_answered = (int) $row_obligatory['answ'];
1058  }
1059  }
1060  else
1061  {
1062  $obligations_answered = 1;
1063  }
1064 
1065  $row = $ilDB->fetchAssoc($result);
1066 
1067  if( $row['hint_count'] === null ) $row['hint_count'] = 0;
1068  if( $row['hint_points'] === null ) $row['hint_points'] = 0;
1069 
1070  $query = "
1071  DELETE FROM tst_pass_result
1072 
1073  WHERE active_fi = %s
1074  AND pass = %s
1075  ";
1076 
1077  $affectedRows = $ilDB->manipulateF(
1078  $query, array('integer','integer'), array($active_id, $pass)
1079  );
1080 
1081  $ilDB->insert('tst_pass_result', array(
1082  'active_fi' => array('integer', $active_id),
1083  'pass' => array('integer', strlen($pass) ? $pass : 0),
1084  'points' => array('float', $row['reachedpoints'] ? $row['reachedpoints'] : 0),
1085  'maxpoints' => array('float', $data['points']),
1086  'questioncount' => array('integer', $data['count']),
1087  'answeredquestions' => array('integer', $row['answeredquestions']),
1088  'workingtime' => array('integer', $time),
1089  'tstamp' => array('integer', time()),
1090  'hint_count' => array('integer', $row['hint_count']),
1091  'hint_points' => array('float', $row['hint_points']),
1092  'obligations_answered' => array('integer', $obligations_answered)
1093  ));
1094  }
1095 
1097 
1098  return array(
1099  'active_fi' => $active_id,
1100  'pass' => $pass,
1101  'points' => ($row["reachedpoints"]) ? $row["reachedpoints"] : 0,
1102  'maxpoints' => $data["points"],
1103  'questioncount' => $data["count"],
1104  'answeredquestions' => $row["answeredquestions"],
1105  'workingtime' => $time,
1106  'tstamp' => time(),
1107  'hint_count' => $row['hint_count'],
1108  'hint_points' => $row['hint_points'],
1109  'obligations_answered' => $obligations_answered
1110  );
1111  }
1112 
1120  function logAction($logtext = "", $active_id = "", $question_id = "")
1121  {
1122  global $ilUser;
1123 
1124  $original_id = "";
1125  if (strcmp($question_id, "") != 0)
1126  {
1127  include_once "./Modules/TestQuestionPool/classes/class.assQuestion.php";
1128  $original_id = assQuestion::_getOriginalId($question_id);
1129  }
1130  include_once "./Modules/Test/classes/class.ilObjAssessmentFolder.php";
1131  include_once "./Modules/Test/classes/class.ilObjTest.php";
1132  ilObjAssessmentFolder::_addLog($ilUser->id, ilObjTest::_getObjectIDFromActiveID($active_id), $logtext, $question_id, $original_id);
1133  }
1134 
1142  function _logAction($logtext = "", $active_id = "", $question_id = "")
1143  {
1144  global $ilUser;
1145 
1146  $original_id = "";
1147  if (strcmp($question_id, "") != 0)
1148  {
1149  include_once "./Modules/TestQuestionPool/classes/class.assQuestion.php";
1150  $original_id = assQuestion::_getOriginalId($question_id);
1151  }
1152  include_once "./Modules/Test/classes/class.ilObjAssessmentFolder.php";
1153  include_once "./Modules/Test/classes/class.ilObjTest.php";
1154  ilObjAssessmentFolder::_addLog($ilUser->id, ilObjTest::_getObjectIDFromActiveID($active_id), $logtext, $question_id, $original_id);
1155  }
1156 
1164  function moveUploadedMediaFile($file, $name)
1165  {
1166  $mediatempdir = CLIENT_WEB_DIR . "/assessment/temp";
1167  if (!@is_dir($mediatempdir)) ilUtil::createDirectory($mediatempdir);
1168  $temp_name = tempnam($mediatempdir, $name . "_____");
1169  $temp_name = str_replace("\\", "/", $temp_name);
1170  @unlink($temp_name);
1171  if (!ilUtil::moveUploadedFile($file, $name, $temp_name))
1172  {
1173  return FALSE;
1174  }
1175  else
1176  {
1177  return $temp_name;
1178  }
1179  }
1180 
1187  return CLIENT_WEB_DIR . "/assessment/$this->obj_id/$this->id/solution/";
1188  }
1189 
1196  function getJavaPath() {
1197  return CLIENT_WEB_DIR . "/assessment/$this->obj_id/$this->id/java/";
1198  }
1199 
1206  function getImagePath($question_id = null, $object_id = null)
1207  {
1208  if( $question_id === null)
1209  {
1210  $question_id = $this->id;
1211  }
1212 
1213  if( $object_id === null)
1214  {
1215  $object_id = $this->obj_id;
1216  }
1217 
1218  return CLIENT_WEB_DIR . "/assessment/$object_id/$question_id/images/";
1219  }
1220 
1227  function getFlashPath()
1228  {
1229  return CLIENT_WEB_DIR . "/assessment/$this->obj_id/$this->id/flash/";
1230  }
1231 
1238  function getJavaPathWeb()
1239  {
1240  include_once "./Services/Utilities/classes/class.ilUtil.php";
1241  $webdir = ilUtil::removeTrailingPathSeparators(CLIENT_WEB_DIR) . "/assessment/$this->obj_id/$this->id/java/";
1243  }
1244 
1251  {
1252  include_once "./Services/Utilities/classes/class.ilUtil.php";
1253  $webdir = ilUtil::removeTrailingPathSeparators(CLIENT_WEB_DIR) . "/assessment/$this->obj_id/$this->id/solution/";
1255  }
1256 
1263  function getImagePathWeb()
1264  {
1265  if(!$this->export_image_path)
1266  {
1267  include_once "./Services/Utilities/classes/class.ilUtil.php";
1268  $webdir = ilUtil::removeTrailingPathSeparators(CLIENT_WEB_DIR) . "/assessment/$this->obj_id/$this->id/images/";
1270  }
1271  else
1272  {
1273  return $this->export_image_path;
1274  }
1275  }
1276 
1283  function getFlashPathWeb()
1284  {
1285  include_once "./Services/Utilities/classes/class.ilUtil.php";
1286  $webdir = ilUtil::removeTrailingPathSeparators(CLIENT_WEB_DIR) . "/assessment/$this->obj_id/$this->id/flash/";
1288  }
1289 
1297  function &getSolutionValues($active_id, $pass = NULL)
1298  {
1299  global $ilDB;
1300 
1301  $values = array();
1302 
1303  if (is_null($pass))
1304  {
1305  $pass = $this->getSolutionMaxPass($active_id);
1306  }
1307 
1308  $result = $ilDB->queryF("SELECT * FROM tst_solutions WHERE active_fi = %s AND question_fi = %s AND pass = %s ORDER BY solution_id",
1309  array('integer','integer','integer'),
1310  array($active_id, $this->getId(), $pass)
1311  );
1312  while ($row = $ilDB->fetchAssoc($result))
1313  {
1314  array_push($values, $row);
1315  }
1316 
1317  return $values;
1318  }
1319 
1326  function isInUse($question_id = "")
1327  {
1328  global $ilDB;
1329 
1330  if ($question_id < 1) $question_id = $this->getId();
1331  $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",
1332  array('integer'),
1333  array($question_id)
1334  );
1335  $row = $ilDB->fetchAssoc($result);
1336  $count = $row["question_count"];
1337 
1338  $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",
1339  array('integer'),
1340  array($question_id)
1341  );
1342  $count += $result->numRows();
1343 
1344  return $count;
1345  }
1346 
1353  function isClone($question_id = "")
1354  {
1355  global $ilDB;
1356 
1357  if ($question_id < 1) $question_id = $this->id;
1358  $result = $ilDB->queryF("SELECT original_id FROM qpl_questions WHERE question_id = %s",
1359  array('integer'),
1360  array($question_id)
1361  );
1362  $row = $ilDB->fetchAssoc($result);
1363  return ($row["original_id"] > 0) ? TRUE : FALSE;
1364  }
1365 
1372  function pcArrayShuffle($array)
1373  {
1374  $keys = array_keys($array);
1375  shuffle($keys);
1376  $result = array();
1377  foreach ($keys as $key)
1378  {
1379  $result[$key] = $array[$key];
1380  }
1381  return $result;
1382  }
1383 
1389  function getQuestionTypeFromDb($question_id)
1390  {
1391  global $ilDB;
1392 
1393  $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",
1394  array('integer'),
1395  array($question_id)
1396  );
1397  $data = $ilDB->fetchAssoc($result);
1398  return $data["type_tag"];
1399  }
1400 
1408  {
1409  return "";
1410  }
1411 
1419  {
1420  return "";
1421  }
1422 
1429  function deleteAnswers($question_id)
1430  {
1431  global $ilDB;
1432  $answer_table_name = $this->getAnswerTableName();
1433  if (is_array($answer_table_name))
1434  {
1435  foreach ($answer_table_name as $table)
1436  {
1437  if (strlen($table))
1438  {
1439  $affectedRows = $ilDB->manipulateF("DELETE FROM $table WHERE question_fi = %s",
1440  array('integer'),
1441  array($question_id)
1442  );
1443  }
1444  }
1445  }
1446  else
1447  {
1448  if (strlen($answer_table_name))
1449  {
1450  $affectedRows = $ilDB->manipulateF("DELETE FROM $answer_table_name WHERE question_fi = %s",
1451  array('integer'),
1452  array($question_id)
1453  );
1454  }
1455  }
1456  }
1457 
1464  function deleteAdditionalTableData($question_id)
1465  {
1466  global $ilDB;
1467  $additional_table_name = $this->getAdditionalTableName();
1468  if (is_array($additional_table_name))
1469  {
1470  foreach ($additional_table_name as $table)
1471  {
1472  if (strlen($table))
1473  {
1474  $affectedRows = $ilDB->manipulateF("DELETE FROM $table WHERE question_fi = %s",
1475  array('integer'),
1476  array($question_id)
1477  );
1478  }
1479  }
1480  }
1481  else
1482  {
1483  if (strlen($additional_table_name))
1484  {
1485  $affectedRows = $ilDB->manipulateF("DELETE FROM $additional_table_name WHERE question_fi = %s",
1486  array('integer'),
1487  array($question_id)
1488  );
1489  }
1490  }
1491  }
1492 
1499  protected function deletePageOfQuestion($question_id)
1500  {
1501  include_once "./Services/COPage/classes/class.ilPageObject.php";
1502  $page = new ilPageObject("qpl", $question_id);
1503  $page->delete();
1504  return true;
1505  }
1506 
1513  public function delete($question_id)
1514  {
1515  global $ilDB, $ilLog;
1516 
1517  if ($question_id < 1) return true; // nothing to do
1518 
1519  $result = $ilDB->queryF("SELECT obj_fi FROM qpl_questions WHERE question_id = %s",
1520  array('integer'),
1521  array($question_id)
1522  );
1523  if ($result->numRows() == 1)
1524  {
1525  $row = $ilDB->fetchAssoc($result);
1526  $obj_id = $row["obj_fi"];
1527  }
1528  else
1529  {
1530  return true; // nothing to do
1531  }
1532  try
1533  {
1534  $this->deletePageOfQuestion($question_id);
1535  }
1536  catch (Exception $e)
1537  {
1538  $ilLog->write("EXCEPTION: Could not delete page of question $question_id: $e");
1539  return false;
1540  }
1541 
1542  $affectedRows = $ilDB->manipulateF("DELETE FROM qpl_questions WHERE question_id = %s",
1543  array('integer'),
1544  array($question_id)
1545  );
1546  if ($affectedRows == 0) return false;
1547 
1548  try
1549  {
1550  $this->deleteAdditionalTableData($question_id);
1551  $this->deleteAnswers($question_id);
1552  $this->deleteFeedbackGeneric($question_id);
1553  $this->deleteFeedbackSpecific($question_id);
1554  }
1555  catch (Exception $e)
1556  {
1557  $ilLog->write("EXCEPTION: Could not delete additional table data of question $question_id: $e");
1558  return false;
1559  }
1560 
1561  try
1562  {
1563  // delete the question in the tst_test_question table (list of test questions)
1564  $affectedRows = $ilDB->manipulateF("DELETE FROM tst_test_question WHERE question_fi = %s",
1565  array('integer'),
1566  array($question_id)
1567  );
1568  }
1569  catch (Exception $e)
1570  {
1571  $ilLog->write("EXCEPTION: Could not delete delete question $question_id from a test: $e");
1572  return false;
1573  }
1574 
1575  try
1576  {
1577  // delete suggested solutions contained in the question
1578  $affectedRows = $ilDB->manipulateF("DELETE FROM qpl_sol_sug WHERE question_fi = %s",
1579  array('integer'),
1580  array($question_id)
1581  );
1582  }
1583  catch (Exception $e)
1584  {
1585  $ilLog->write("EXCEPTION: Could not delete suggested solutions of question $question_id: $e");
1586  return false;
1587  }
1588 
1589  try
1590  {
1591  $directory = CLIENT_WEB_DIR . "/assessment/" . $obj_id . "/$question_id";
1592  if (preg_match("/\d+/", $obj_id) and preg_match("/\d+/", $question_id) and is_dir($directory))
1593  {
1594  include_once "./Services/Utilities/classes/class.ilUtil.php";
1595  ilUtil::delDir($directory);
1596  }
1597  }
1598  catch (Exception $e)
1599  {
1600  $ilLog->write("EXCEPTION: Could not delete question file directory $directory of question $question_id: $e");
1601  return false;
1602  }
1603 
1604  try
1605  {
1606  include_once("./Services/MediaObjects/classes/class.ilObjMediaObject.php");
1607  $mobs = ilObjMediaObject::_getMobsOfObject("qpl:html", $question_id);
1608  // remaining usages are not in text anymore -> delete them
1609  // and media objects (note: delete method of ilObjMediaObject
1610  // checks whether object is used in another context; if yes,
1611  // the object is not deleted!)
1612  foreach($mobs as $mob)
1613  {
1614  ilObjMediaObject::_removeUsage($mob, "qpl:html", $question_id);
1615  if (ilObjMediaObject::_exists($mob))
1616  {
1617  $mob_obj =& new ilObjMediaObject($mob);
1618  $mob_obj->delete();
1619  }
1620  }
1621  }
1622  catch (Exception $e)
1623  {
1624  $ilLog->write("EXCEPTION: Error deleting the media objects of question $question_id: $e");
1625  return false;
1626  }
1627 
1628  require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionHintTracking.php';
1630 
1631  require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionHintList.php';
1633 
1634  try
1635  {
1636  // update question count of question pool
1637  include_once "./Modules/TestQuestionPool/classes/class.ilObjQuestionPool.php";
1639  }
1640  catch (Exception $e)
1641  {
1642  $ilLog->write("EXCEPTION: Error updating the question pool question count of question pool " . $this->getObjId() . " when deleting question $question_id: $e");
1643  return false;
1644  }
1645  return true;
1646  }
1647 
1651  function getTotalAnswers()
1652  {
1653  return $this->_getTotalAnswers($this->id);
1654  }
1655 
1662  function _getTotalAnswers($a_q_id)
1663  {
1664  global $ilDB;
1665 
1666  // get all question references to the question id
1667  $result = $ilDB->queryF("SELECT question_id FROM qpl_questions WHERE original_id = %s OR question_id = %s",
1668  array('integer','integer'),
1669  array($a_q_id, $a_q_id)
1670  );
1671  if ($result->numRows() == 0)
1672  {
1673  return 0;
1674  }
1675  $found_id = array();
1676  while ($row = $ilDB->fetchAssoc($result))
1677  {
1678  array_push($found_id, $row["question_id"]);
1679  }
1680 
1681  $result = $ilDB->query("SELECT * FROM tst_test_result WHERE " . $ilDB->in('question_fi', $found_id, false, 'integer'));
1682 
1683  return $result->numRows();
1684  }
1685 
1686 
1693  function _getTotalRightAnswers($a_q_id)
1694  {
1695  global $ilDB;
1696  $result = $ilDB->queryF("SELECT question_id FROM qpl_questions WHERE original_id = %s OR question_id = %s",
1697  array('integer','integer'),
1698  array($a_q_id, $a_q_id)
1699  );
1700  if ($result->numRows() == 0)
1701  {
1702  return 0;
1703  }
1704  $found_id = array();
1705  while ($row = $ilDB->fetchAssoc($result))
1706  {
1707  array_push($found_id, $row["question_id"]);
1708  }
1709  $result = $ilDB->query("SELECT * FROM tst_test_result WHERE " . $ilDB->in('question_fi', $found_id, false, 'integer'));
1710  $answers = array();
1711  while ($row = $ilDB->fetchAssoc($result))
1712  {
1713  $reached = $row["points"];
1714  include_once "./Modules/TestQuestionPool/classes/class.assQuestion.php";
1715  $max = assQuestion::_getMaximumPoints($row["question_fi"]);
1716  array_push($answers, array("reached" => $reached, "max" => $max));
1717  }
1718  $max = 0.0;
1719  $reached = 0.0;
1720  foreach ($answers as $key => $value)
1721  {
1722  $max += $value["max"];
1723  $reached += $value["reached"];
1724  }
1725  if ($max > 0)
1726  {
1727  return $reached / $max;
1728  }
1729  else
1730  {
1731  return 0;
1732  }
1733  }
1734 
1740  function _getTitle($a_q_id)
1741  {
1742  global $ilDB;
1743  $result = $ilDB->queryF("SELECT title FROM qpl_questions WHERE question_id = %s",
1744  array('integer'),
1745  array($a_q_id)
1746  );
1747  if ($result->numRows() == 1)
1748  {
1749  $row = $ilDB->fetchAssoc($result);
1750  return $row["title"];
1751  }
1752  else
1753  {
1754  return "";
1755  }
1756  }
1757 
1763  function _getQuestionText($a_q_id)
1764  {
1765  global $ilDB;
1766  $result = $ilDB->queryF("SELECT question_text FROM qpl_questions WHERE question_id = %s",
1767  array('integer'),
1768  array($a_q_id)
1769  );
1770  if ($result->numRows() == 1)
1771  {
1772  $row = $ilDB->fetchAssoc($result);
1773  return $row["question_text"];
1774  }
1775  else
1776  {
1777  return "";
1778  }
1779  }
1780 
1781 
1783  {
1784  include_once("./Services/MediaObjects/classes/class.ilObjMediaObject.php");
1785  $mobs = ilObjMediaObject::_getMobsOfObject("qpl:html", $a_q_id);
1786  foreach ($mobs as $mob)
1787  {
1788  ilObjMediaObject::_saveUsage($mob, "qpl:html", $this->getId());
1789  }
1790  }
1791 
1793  {
1794  include_once("./Services/MediaObjects/classes/class.ilObjMediaObject.php");
1795  $mobs = ilObjMediaObject::_getMobsOfObject("qpl:html", $this->getId());
1796  foreach ($mobs as $mob)
1797  {
1798  ilObjMediaObject::_saveUsage($mob, "qpl:html", $this->original_id);
1799  }
1800  }
1801 
1805  function createPageObject()
1806  {
1807  $qpl_id = $this->getObjId();
1808 
1809  include_once "./Services/COPage/classes/class.ilPageObject.php";
1810  $this->page = new ilPageObject("qpl", 0);
1811  $this->page->setId($this->getId());
1812  $this->page->setParentId($qpl_id);
1813  $this->page->setXMLContent("<PageObject><PageContent>".
1814  "<Question QRef=\"il__qst_".$this->getId()."\"/>".
1815  "</PageContent></PageObject>");
1816  $this->page->create();
1817  }
1818 
1819  function copyPageOfQuestion($a_q_id)
1820  {
1821  if ($a_q_id > 0)
1822  {
1823  include_once "./Services/COPage/classes/class.ilPageObject.php";
1824  $page = new ilPageObject("qpl", $a_q_id);
1825 
1826  $xml = str_replace("il__qst_".$a_q_id, "il__qst_".$this->id, $page->getXMLContent());
1827  $this->page->setXMLContent($xml);
1828  $this->page->saveMobUsage($xml);
1829  $this->page->updateFromXML();
1830  }
1831  }
1832 
1834  {
1835  include_once "./Services/COPage/classes/class.ilPageObject.php";
1836  $page = new ilPageObject("qpl", $this->id);
1837  return $page->getXMLContent();
1838  }
1839 
1847  function _getQuestionType($question_id)
1848  {
1849  global $ilDB;
1850 
1851  if ($question_id < 1) return "";
1852  $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",
1853  array('integer'),
1854  array($question_id)
1855  );
1856  if ($result->numRows() == 1)
1857  {
1858  $data = $ilDB->fetchAssoc($result);
1859  return $data["type_tag"];
1860  }
1861  else
1862  {
1863  return "";
1864  }
1865  }
1866 
1874  function _getQuestionTitle($question_id)
1875  {
1876  global $ilDB;
1877 
1878  if ($question_id < 1) return "";
1879 
1880  $result = $ilDB->queryF("SELECT title FROM qpl_questions WHERE qpl_questions.question_id = %s",
1881  array('integer'),
1882  array($question_id)
1883  );
1884  if ($result->numRows() == 1)
1885  {
1886  $data = $ilDB->fetchAssoc($result);
1887  return $data["title"];
1888  }
1889  else
1890  {
1891  return "";
1892  }
1893  }
1894 
1896  {
1897  $this->original_id = $original_id;
1898  }
1899 
1900  function getOriginalId()
1901  {
1902  return $this->original_id;
1903  }
1904 
1911  function loadFromDb($question_id)
1912  {
1913  global $ilDB;
1914 
1915  $result = $ilDB->queryF("SELECT * FROM qpl_sol_sug WHERE question_fi = %s",
1916  array('integer'),
1917  array($this->getId())
1918  );
1919  $this->suggested_solutions = array();
1920  if ($result->numRows())
1921  {
1922  include_once("./Services/RTE/classes/class.ilRTE.php");
1923  while ($row = $ilDB->fetchAssoc($result))
1924  {
1925  $value = (is_array(unserialize($row["value"]))) ? unserialize($row["value"]) : ilRTE::_replaceMediaObjectImageSrc($row["value"], 1);
1926  $this->suggested_solutions[$row["subquestion_index"]] = array(
1927  "type" => $row["type"],
1928  "value" => $value,
1929  "internal_link" => $row["internal_link"],
1930  "import_id" => $row["import_id"]
1931  );
1932  }
1933  }
1934  }
1935 
1942  public function createNewQuestion($a_create_page = true)
1943  {
1944  global $ilDB, $ilUser;
1945 
1946  $complete = "0";
1947  $estw_time = $this->getEstimatedWorkingTime();
1948  $estw_time = sprintf("%02d:%02d:%02d", $estw_time['h'], $estw_time['m'], $estw_time['s']);
1949  $obj_id = ($this->getObjId() <= 0) ? (ilObject::_lookupObjId((strlen($_GET["ref_id"])) ? $_GET["ref_id"] : $_POST["sel_qpl"])) : $this->getObjId();
1950  if ($obj_id > 0)
1951  {
1952  if($a_create_page)
1953  {
1954  $tstamp = 0;
1955  }
1956  else
1957  {
1958  // question pool must not try to purge
1959  $tstamp = time();
1960  }
1961 
1962  $next_id = $ilDB->nextId('qpl_questions');
1963  $affectedRows = $ilDB->insert("qpl_questions", array(
1964  "question_id" => array("integer", $next_id),
1965  "question_type_fi" => array("integer", $this->getQuestionTypeID()),
1966  "obj_fi" => array("integer", $obj_id),
1967  "title" => array("text", NULL),
1968  "description" => array("text", NULL),
1969  "author" => array("text", $this->getAuthor()),
1970  "owner" => array("integer", $ilUser->getId()),
1971  "question_text" => array("clob", NULL),
1972  "points" => array("float", 0),
1973  "nr_of_tries" => array("integer", 1),
1974  "working_time" => array("text", $estw_time),
1975  "complete" => array("text", $complete),
1976  "created" => array("integer", time()),
1977  "original_id" => array("integer", NULL),
1978  "tstamp" => array("integer", $tstamp)
1979  ));
1980  $this->setId($next_id);
1981 
1982  if($a_create_page)
1983  {
1984  // create page object of question
1985  $this->createPageObject();
1986  }
1987  }
1988  return $this->getId();
1989  }
1990 
1991  public function saveQuestionDataToDb($original_id = "")
1992  {
1993  global $ilDB;
1994 
1995  $estw_time = $this->getEstimatedWorkingTime();
1996  $estw_time = sprintf("%02d:%02d:%02d", $estw_time['h'], $estw_time['m'], $estw_time['s']);
1997 
1998  // cleanup RTE images which are not inserted into the question text
1999  include_once("./Services/RTE/classes/class.ilRTE.php");
2000  if ($this->getId() == -1)
2001  {
2002  // Neuen Datensatz schreiben
2003  $next_id = $ilDB->nextId('qpl_questions');
2004  $affectedRows = $ilDB->insert("qpl_questions", array(
2005  "question_id" => array("integer", $next_id),
2006  "question_type_fi" => array("integer", $this->getQuestionTypeID()),
2007  "obj_fi" => array("integer", $this->getObjId()),
2008  "title" => array("text", $this->getTitle()),
2009  "description" => array("text", $this->getComment()),
2010  "author" => array("text", $this->getAuthor()),
2011  "owner" => array("integer", $this->getOwner()),
2012  "question_text" => array("clob", ilRTE::_replaceMediaObjectImageSrc($this->getQuestion(), 0)),
2013  "points" => array("float", $this->getMaximumPoints()),
2014  "working_time" => array("text", $estw_time),
2015  "nr_of_tries" => array("integer", (strlen($this->getNrOfTries())) ? $this->getNrOfTries() : 1),
2016  "created" => array("integer", time()),
2017  "original_id" => array("integer", ($original_id) ? $original_id : NULL),
2018  "tstamp" => array("integer", time())
2019  ));
2020  $this->setId($next_id);
2021  // create page object of question
2022  $this->createPageObject();
2023  }
2024  else
2025  {
2026  // Vorhandenen Datensatz aktualisieren
2027  $affectedRows = $ilDB->update("qpl_questions", array(
2028  "obj_fi" => array("integer", $this->getObjId()),
2029  "title" => array("text", $this->getTitle()),
2030  "description" => array("text", $this->getComment()),
2031  "author" => array("text", $this->getAuthor()),
2032  "question_text" => array("clob", ilRTE::_replaceMediaObjectImageSrc($this->getQuestion(), 0)),
2033  "points" => array("float", $this->getMaximumPoints()),
2034  "nr_of_tries" => array("integer", (strlen($this->getNrOfTries())) ? $this->getNrOfTries() : 1),
2035  "working_time" => array("text", $estw_time),
2036  "tstamp" => array("integer", time())
2037  ), array(
2038  "question_id" => array("integer", $this->getId())
2039  ));
2040  }
2041  }
2042 
2049  function saveToDb($original_id = "")
2050  {
2051  global $ilDB;
2052 
2053  $this->updateSuggestedSolutions();
2054 
2055  // remove unused media objects from ILIAS
2056  $this->cleanupMediaObjectUsage();
2057 
2058  $complete = "0";
2059  if ($this->isComplete())
2060  {
2061  $complete = "1";
2062  }
2063 
2064  // update the question time stamp and completion status
2065  $affectedRows = $ilDB->manipulateF("UPDATE qpl_questions SET tstamp = %s, owner = %s, complete = %s WHERE question_id = %s",
2066  array('integer','integer', 'integer','text'),
2067  array(time(), ($this->getOwner() <= 0) ? $this->ilias->account->id : $this->getOwner(), $complete, $this->getId())
2068  );
2069 
2070  // update question count of question pool
2071  include_once "./Modules/TestQuestionPool/classes/class.ilObjQuestionPool.php";
2073  }
2074 
2075  public function setNewOriginalId($newId) {
2076  global $ilDB;
2077  $ilDB->manipulateF("UPDATE qpl_questions SET tstamp = %s, original_id = %s WHERE question_id = %s",
2078  array('integer','integer', 'text'),
2079  array(time(), $newId, $this->getId())
2080  );
2081  }
2082 
2086  protected function onDuplicate($originalParentId, $originalQuestionId, $duplicateParentId, $duplicateQuestionId)
2087  {
2088  $this->duplicateSuggestedSolutionFiles($originalParentId, $originalQuestionId);
2089 
2090  // duplicate question hints
2091  $this->duplicateQuestionHints($originalQuestionId, $this->getId());
2092  }
2093 
2097  protected function onCopy($source_questionpool_id, $source_question_id)
2098  {
2099  $this->copySuggestedSolutionFiles($source_questionpool_id, $source_question_id);
2100  }
2101 
2105  public function deleteSuggestedSolutions()
2106  {
2107  global $ilDB;
2108  // delete the links in the qpl_sol_sug table
2109  $affectedRows = $ilDB->manipulateF("DELETE FROM qpl_sol_sug WHERE question_fi = %s",
2110  array('integer'),
2111  array($this->getId())
2112  );
2113  // delete the links in the int_link table
2114  include_once "./Services/COPage/classes/class.ilInternalLink.php";
2116  $this->suggested_solutions = array();
2118  }
2119 
2127  function getSuggestedSolution($subquestion_index = 0)
2128  {
2129  if (array_key_exists($subquestion_index, $this->suggested_solutions))
2130  {
2131  return $this->suggested_solutions[$subquestion_index];
2132  }
2133  else
2134  {
2135  return array();
2136  }
2137  }
2138 
2147  function getSuggestedSolutionTitle($subquestion_index = 0)
2148  {
2149  if (array_key_exists($subquestion_index, $this->suggested_solutions))
2150  {
2151  $title = $this->suggested_solutions[$subquestion_index]["internal_link"];
2152  // TO DO: resolve internal link an get link type and title
2153  }
2154  else
2155  {
2156  $title = "";
2157  }
2158  return $title;
2159  }
2160 
2170  function setSuggestedSolution($solution_id = "", $subquestion_index = 0, $is_import = false)
2171  {
2172  if (strcmp($solution_id, "") != 0)
2173  {
2174  $import_id = "";
2175  if ($is_import)
2176  {
2177  $import_id = $solution_id;
2178  $solution_id = $this->_resolveInternalLink($import_id);
2179  }
2180  $this->suggested_solutions[$subquestion_index] = array(
2181  "internal_link" => $solution_id,
2182  "import_id" => $import_id
2183  );
2184  }
2185  }
2186 
2190  protected function duplicateSuggestedSolutionFiles($parent_id, $question_id)
2191  {
2192  global $ilLog;
2193 
2194  foreach ($this->suggested_solutions as $index => $solution)
2195  {
2196  if (strcmp($solution["type"], "file") == 0)
2197  {
2198  $filepath = $this->getSuggestedSolutionPath();
2199  $filepath_original = str_replace(
2200  "/{$this->obj_id}/{$this->id}/solution",
2201  "/$parent_id/$question_id/solution",
2202  $filepath
2203  );
2204  if (!file_exists($filepath))
2205  {
2206  ilUtil::makeDirParents($filepath);
2207  }
2208  $filename = $solution["value"]["name"];
2209  if (strlen($filename))
2210  {
2211  if (!copy($filepath_original . $filename, $filepath . $filename))
2212  {
2213  $ilLog->write("File could not be duplicated!!!!", $ilLog->ERROR);
2214  $ilLog->write("object: " . print_r($this, TRUE), $ilLog->ERROR);
2215  }
2216  }
2217  }
2218  }
2219  }
2220 
2225  {
2226  global $ilLog;
2227 
2228  $filepath = $this->getSuggestedSolutionPath();
2229  $filepath_original = str_replace("/$this->id/solution", "/$original_id/solution", $filepath);
2230  ilUtil::delDir($filepath_original);
2231  foreach ($this->suggested_solutions as $index => $solution)
2232  {
2233  if (strcmp($solution["type"], "file") == 0)
2234  {
2235  if (!file_exists($filepath_original))
2236  {
2237  ilUtil::makeDirParents($filepath_original);
2238  }
2239  $filename = $solution["value"]["name"];
2240  if (strlen($filename))
2241  {
2242  if (!@copy($filepath . $filename, $filepath_original . $filename))
2243  {
2244  $ilLog->write("File could not be duplicated!!!!", $ilLog->ERROR);
2245  $ilLog->write("object: " . print_r($this, TRUE), $ilLog->ERROR);
2246  }
2247  }
2248  }
2249  }
2250  }
2251 
2252  protected function copySuggestedSolutionFiles($source_questionpool_id, $source_question_id)
2253  {
2254  global $ilLog;
2255 
2256  foreach ($this->suggested_solutions as $index => $solution)
2257  {
2258  if (strcmp($solution["type"], "file") == 0)
2259  {
2260  $filepath = $this->getSuggestedSolutionPath();
2261  $filepath_original = str_replace("/$this->obj_id/$this->id/solution", "/$source_questionpool_id/$source_question_id/solution", $filepath);
2262  if (!file_exists($filepath))
2263  {
2264  ilUtil::makeDirParents($filepath);
2265  }
2266  $filename = $solution["value"]["name"];
2267  if (strlen($filename))
2268  {
2269  if (!copy($filepath_original . $filename, $filepath . $filename))
2270  {
2271  $ilLog->write("File could not be copied!!!!", $ilLog->ERROR);
2272  $ilLog->write("object: " . print_r($this, TRUE), $ilLog->ERROR);
2273  }
2274  }
2275  }
2276  }
2277  }
2278 
2282  public function updateSuggestedSolutions($original_id = "")
2283  {
2284  global $ilDB;
2285 
2286  $id = (strlen($original_id) && is_numeric($original_id)) ? $original_id : $this->getId();
2287  include_once "./Services/COPage/classes/class.ilInternalLink.php";
2288  $affectedRows = $ilDB->manipulateF("DELETE FROM qpl_sol_sug WHERE question_fi = %s",
2289  array('integer'),
2290  array($id)
2291  );
2293  include_once("./Services/RTE/classes/class.ilRTE.php");
2294  foreach ($this->suggested_solutions as $index => $solution)
2295  {
2296  $next_id = $ilDB->nextId('qpl_sol_sug');
2297 
2299  $ilDB->insert('qpl_sol_sug', array(
2300  'suggested_solution_id' => array( 'integer', $next_id ),
2301  'question_fi' => array( 'integer', $id ),
2302  'type' => array( 'text', $solution['type'] ),
2303  'value' => array( 'clob', ilRTE::_replaceMediaObjectImageSrc( (is_array( $solution['value'] ) ) ? serialize( $solution[ 'value' ] ) : $solution['value'], 0 ) ),
2304  'internal_link' => array( 'text', $solution['internal_link'] ),
2305  'import_id' => array( 'text', null ),
2306  'subquestion_index' => array( 'integer', $index ),
2307  'tstamp' => array( 'integer', time() ),
2308  )
2309  );
2310 
2311  if (preg_match("/il_(\d*?)_(\w+)_(\d+)/", $solution["internal_link"], $matches))
2312  {
2313  ilInternalLink::_saveLink("qst", $id, $matches[2], $matches[3], $matches[1]);
2314  }
2315  }
2316  if (strlen($original_id) && is_numeric($original_id)) $this->syncSuggestedSolutionFiles($id);
2317  $this->cleanupMediaObjectUsage();
2318  }
2319 
2329  function saveSuggestedSolution($type, $solution_id = "", $subquestion_index = 0, $value = "")
2330  {
2331  global $ilDB;
2332 
2333  $affectedRows = $ilDB->manipulateF("DELETE FROM qpl_sol_sug WHERE question_fi = %s AND subquestion_index = %s",
2334  array("integer", "integer"),
2335  array(
2336  $this->getId(),
2337  $subquestion_index
2338  )
2339  );
2340 
2341  $next_id = $ilDB->nextId('qpl_sol_sug');
2342  include_once("./Services/RTE/classes/class.ilRTE.php");
2344  $affectedRows = $ilDB->insert('qpl_sol_sug', array(
2345  'suggested_solution_id' => array( 'integer', $next_id ),
2346  'question_fi' => array( 'integer', $this->getId() ),
2347  'type' => array( 'text', $type ),
2348  'value' => array( 'clob', ilRTE::_replaceMediaObjectImageSrc( (is_array( $value ) ) ? serialize( $value ) : $value, 0 ) ),
2349  'internal_link' => array( 'text', $solution_id ),
2350  'import_id' => array( 'text', null ),
2351  'subquestion_index' => array( 'integer', $subquestion_index ),
2352  'tstamp' => array( 'integer', time() ),
2353  )
2354  );
2355 
2356  if ($affectedRows == 1)
2357  {
2358  $this->suggested_solutions["subquestion_index"] = array(
2359  "type" => $type,
2360  "value" => $value,
2361  "internal_link" => $solution_id,
2362  "import_id" => ""
2363  );
2364  }
2365  $this->cleanupMediaObjectUsage();
2366  }
2367 
2368  function _resolveInternalLink($internal_link)
2369  {
2370  if (preg_match("/il_(\d+)_(\w+)_(\d+)/", $internal_link, $matches))
2371  {
2372  include_once "./Services/COPage/classes/class.ilInternalLink.php";
2373  include_once "./Modules/LearningModule/classes/class.ilLMObject.php";
2374  include_once "./Modules/Glossary/classes/class.ilGlossaryTerm.php";
2375  switch ($matches[2])
2376  {
2377  case "lm":
2378  $resolved_link = ilLMObject::_getIdForImportId($internal_link);
2379  break;
2380  case "pg":
2381  $resolved_link = ilInternalLink::_getIdForImportId("PageObject", $internal_link);
2382  break;
2383  case "st":
2384  $resolved_link = ilInternalLink::_getIdForImportId("StructureObject", $internal_link);
2385  break;
2386  case "git":
2387  $resolved_link = ilInternalLink::_getIdForImportId("GlossaryItem", $internal_link);
2388  break;
2389  case "mob":
2390  $resolved_link = ilInternalLink::_getIdForImportId("MediaObject", $internal_link);
2391  break;
2392  }
2393  if (strcmp($resolved_link, "") == 0)
2394  {
2395  $resolved_link = $internal_link;
2396  }
2397  }
2398  else
2399  {
2400  $resolved_link = $internal_link;
2401  }
2402  return $resolved_link;
2403  }
2404 
2405  function _resolveIntLinks($question_id)
2406  {
2407  global $ilDB;
2408  $resolvedlinks = 0;
2409  $result = $ilDB->queryF("SELECT * FROM qpl_sol_sug WHERE question_fi = %s",
2410  array('integer'),
2411  array($question_id)
2412  );
2413  if ($result->numRows())
2414  {
2415  while ($row = $ilDB->fetchAssoc($result))
2416  {
2417  $internal_link = $row["internal_link"];
2418  include_once "./Modules/TestQuestionPool/classes/class.assQuestion.php";
2419  $resolved_link = assQuestion::_resolveInternalLink($internal_link);
2420  if (strcmp($internal_link, $resolved_link) != 0)
2421  {
2422  // internal link was resolved successfully
2423  $affectedRows = $ilDB->manipulateF("UPDATE qpl_sol_sug SET internal_link = %s WHERE suggested_solution_id = %s",
2424  array('text','integer'),
2425  array($resolved_link, $row["suggested_solution_id"])
2426  );
2427  $resolvedlinks++;
2428  }
2429  }
2430  }
2431  if ($resolvedlinks)
2432  {
2433  // there are resolved links -> reenter theses links to the database
2434 
2435  // delete all internal links from the database
2436  include_once "./Services/COPage/classes/class.ilInternalLink.php";
2437  ilInternalLink::_deleteAllLinksOfSource("qst", $question_id);
2438 
2439  $result = $ilDB->queryF("SELECT * FROM qpl_sol_sug WHERE question_fi = %s",
2440  array('integer'),
2441  array($question_id)
2442  );
2443  if ($result->numRows())
2444  {
2445  while ($row = $ilDB->fetchAssoc($result))
2446  {
2447  if (preg_match("/il_(\d*?)_(\w+)_(\d+)/", $row["internal_link"], $matches))
2448  {
2449  ilInternalLink::_saveLink("qst", $question_id, $matches[2], $matches[3], $matches[1]);
2450  }
2451  }
2452  }
2453  }
2454  }
2455 
2456  function _getInternalLinkHref($target = "")
2457  {
2458  global $ilDB;
2459  $linktypes = array(
2460  "lm" => "LearningModule",
2461  "pg" => "PageObject",
2462  "st" => "StructureObject",
2463  "git" => "GlossaryItem",
2464  "mob" => "MediaObject"
2465  );
2466  $href = "";
2467  if (preg_match("/il__(\w+)_(\d+)/", $target, $matches))
2468  {
2469  $type = $matches[1];
2470  $target_id = $matches[2];
2471  include_once "./Services/Utilities/classes/class.ilUtil.php";
2472  switch($linktypes[$matches[1]])
2473  {
2474  case "LearningModule":
2475  $href = "./goto.php?target=" . $type . "_" . $target_id;
2476  break;
2477  case "PageObject":
2478  case "StructureObject":
2479  $href = "./goto.php?target=" . $type . "_" . $target_id;
2480  break;
2481  case "GlossaryItem":
2482  $href = "./goto.php?target=" . $type . "_" . $target_id;
2483  break;
2484  case "MediaObject":
2485  $href = "./ilias.php?baseClass=ilLMPresentationGUI&obj_type=" . $linktypes[$type] . "&cmd=media&ref_id=".$_GET["ref_id"]."&mob_id=".$target_id;
2486  break;
2487  }
2488  }
2489  return $href;
2490  }
2491 
2499  function _getOriginalId($question_id)
2500  {
2501  global $ilDB;
2502  $result = $ilDB->queryF("SELECT * FROM qpl_questions WHERE question_id = %s",
2503  array('integer'),
2504  array($question_id)
2505  );
2506  if ($result->numRows() > 0)
2507  {
2508  $row = $ilDB->fetchAssoc($result);
2509  if ($row["original_id"] > 0)
2510  {
2511  return $row["original_id"];
2512  }
2513  else
2514  {
2515  return $row["question_id"];
2516  }
2517  }
2518  else
2519  {
2520  return "";
2521  }
2522  }
2523 
2524  function syncWithOriginal()
2525  {
2526  global $ilDB;
2527 
2528  if( !$this->getOriginalId() )
2529  {
2530  return;
2531  }
2532 
2533  $originalObjId = self::lookupOriginalParentObjId($this->getOriginalId());
2534 
2535  if ( !$originalObjId )
2536  {
2537  return;
2538  }
2539 
2540  $id = $this->getId();
2541  $original = $this->getOriginalId();
2542 
2543  $this->setId($this->getOriginalId());
2544  $this->setOriginalId(NULL);
2545  $this->setObjId($originalObjId);
2546  $this->saveToDb();
2547  $this->deletePageOfQuestion($original);
2548  $this->createPageObject();
2549  $this->copyPageOfQuestion($id);
2550 
2551  $this->setId($id);
2552  $this->setOriginalId($original);
2553  $this->updateSuggestedSolutions($original);
2554  $this->syncFeedbackGeneric();
2556  $this->syncHints();
2557  }
2566  public static function lookupOriginalParentObjId($originalQuestionId)
2567  {
2568  global $ilDB;
2569 
2570  $query = "SELECT obj_fi FROM qpl_questions WHERE question_id = %s";
2571 
2572  $res = $ilDB->queryF($query, array('integer'), array((int)$originalQuestionId));
2573  $row = $ilDB->fetchAssoc($res);
2574 
2575  return $row['obj_fi'];
2576  }
2577 
2578  function createRandomSolution($test_id, $user_id)
2579  {
2580  }
2581 
2589  function _questionExists($question_id)
2590  {
2591  global $ilDB;
2592 
2593  if ($question_id < 1)
2594  {
2595  return false;
2596  }
2597 
2598  $result = $ilDB->queryF("SELECT question_id FROM qpl_questions WHERE question_id = %s",
2599  array('integer'),
2600  array($question_id)
2601  );
2602  if ($result->numRows() == 1)
2603  {
2604  return true;
2605  }
2606  else
2607  {
2608  return false;
2609  }
2610  }
2611 
2619  function _questionExistsInPool($question_id)
2620  {
2621  global $ilDB;
2622 
2623  if ($question_id < 1)
2624  {
2625  return false;
2626  }
2627 
2628  $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'",
2629  array('integer'),
2630  array($question_id)
2631  );
2632  if ($result->numRows() == 1)
2633  {
2634  return true;
2635  }
2636  else
2637  {
2638  return false;
2639  }
2640  }
2641 
2649  public static function _instanciateQuestion($question_id)
2650  {
2651  return self::_instantiateQuestion($question_id);
2652  }
2653 
2654  public static function _instantiateQuestion($question_id)
2655  {
2656  if (strcmp($question_id, "") != 0)
2657  {
2658  $question_type = assQuestion::_getQuestionType($question_id);
2659  if (!strlen($question_type)) return null;
2660  assQuestion::_includeClass($question_type);
2661  $question = new $question_type();
2662  $question->loadFromDb($question_id);
2663  return $question;
2664  }
2665  }
2666 
2673  function getPoints()
2674  {
2675  if (strcmp($this->points, "") == 0)
2676  {
2677  return 0;
2678  }
2679  else
2680  {
2681  return $this->points;
2682  }
2683  }
2684 
2685 
2692  function setPoints($a_points)
2693  {
2694  $this->points = $a_points;
2695  }
2696 
2703  function getSolutionMaxPass($active_id)
2704  {
2705  return $this->_getSolutionMaxPass($this->getId(), $active_id);
2706  }
2707 
2714  function _getSolutionMaxPass($question_id, $active_id)
2715  {
2716 /* include_once "./Modules/Test/classes/class.ilObjTest.php";
2717  $pass = ilObjTest::_getPass($active_id);
2718  return $pass;*/
2719 
2720  // the following code was the old solution which added the non answered
2721  // questions of a pass from the answered questions of the previous pass
2722  // with the above solution, only the answered questions of the last pass are counted
2723  global $ilDB;
2724 
2725  $result = $ilDB->queryF("SELECT MAX(pass) maxpass FROM tst_test_result WHERE active_fi = %s AND question_fi = %s",
2726  array('integer','integer'),
2727  array($active_id, $question_id)
2728  );
2729  if ($result->numRows() == 1)
2730  {
2731  $row = $ilDB->fetchAssoc($result);
2732  return $row["maxpass"];
2733  }
2734  else
2735  {
2736  return 0;
2737  }
2738  }
2739 
2748  function _isWriteable($question_id, $user_id)
2749  {
2750  global $ilDB;
2751 
2752  if (($question_id < 1) || ($user_id < 1))
2753  {
2754  return false;
2755  }
2756 
2757  $result = $ilDB->queryF("SELECT obj_fi FROM qpl_questions WHERE question_id = %s",
2758  array('integer'),
2759  array($question_id)
2760  );
2761  if ($result->numRows() == 1)
2762  {
2763  $row = $ilDB->fetchAssoc($result);
2764  $qpl_object_id = $row["obj_fi"];
2765  include_once "./Modules/TestQuestionPool/classes/class.ilObjQuestionPool.php";
2766  return ilObjQuestionPool::_isWriteable($qpl_object_id, $user_id);
2767  }
2768  else
2769  {
2770  return false;
2771  }
2772  }
2773 
2780  function _isUsedInRandomTest($question_id = "")
2781  {
2782  global $ilDB;
2783 
2784  if ($question_id < 1) return 0;
2785  $result = $ilDB->queryF("SELECT test_random_question_id FROM tst_test_rnd_qst WHERE question_fi = %s",
2786  array('integer'),
2787  array($question_id)
2788  );
2789  return $result->numRows();
2790  }
2791 
2803  abstract public function calculateReachedPoints($active_id, $pass = NULL, $returndetails = FALSE);
2804 
2815  final public function adjustReachedPointsByScoringOptions($points, $active_id, $pass = NULL)
2816  {
2817  include_once "./Modules/Test/classes/class.ilObjTest.php";
2818  $count_system = ilObjTest::_getCountSystem($active_id);
2819  if ($count_system == 1)
2820  {
2821  if ($points != $this->getMaximumPoints())
2822  {
2823  $points = 0;
2824  }
2825  }
2826  $score_cutting = ilObjTest::_getScoreCutting($active_id);
2827  if ($score_cutting == 0)
2828  {
2829  if ($points < 0)
2830  {
2831  $points = 0;
2832  }
2833  }
2834  return $points;
2835  }
2836 
2845  public static function _isWorkedThrough($active_id, $question_id, $pass = NULL)
2846  {
2847  global $ilDB;
2848 
2849  $points = 0;
2850  if (is_null($pass))
2851  {
2852  include_once "./Modules/TestQuestionPool/classes/class.assQuestion.php";
2853  $pass = assQuestion::_getSolutionMaxPass($question_id, $active_id);
2854  }
2855  $result = $ilDB->queryF("SELECT solution_id FROM tst_solutions WHERE active_fi = %s AND question_fi = %s AND pass = %s",
2856  array('integer','integer','integer'),
2857  array($active_id, $question_id, $pass)
2858  );
2859  if ($result->numRows())
2860  {
2861  return TRUE;
2862  }
2863  else
2864  {
2865  return FALSE;
2866  }
2867  }
2868 
2876  public static function _areAnswered($a_user_id,$a_question_ids)
2877  {
2878  global $ilDB;
2879 
2880  $res = $ilDB->queryF("SELECT DISTINCT(question_fi) FROM tst_test_result JOIN tst_active ".
2881  "ON (active_id = active_fi) ".
2882  "WHERE " . $ilDB->in('question_fi', $a_question_ids, false, 'integer') .
2883  " AND user_fi = %s",
2884  array('integer'),
2885  array($a_user_id)
2886  );
2887  return ($res->numRows() == count($a_question_ids)) ? true : false;
2888  }
2889 
2898  function isHTML($a_text)
2899  {
2900  if (preg_match("/<[^>]*?>/", $a_text))
2901  {
2902  return TRUE;
2903  }
2904  else
2905  {
2906  return FALSE;
2907  }
2908  }
2909 
2916  function prepareTextareaOutput($txt_output, $prepare_for_latex_output = FALSE)
2917  {
2918  include_once "./Services/Utilities/classes/class.ilUtil.php";
2919  return ilUtil::prepareTextareaOutput($txt_output, $prepare_for_latex_output);
2920  }
2921 
2929  function QTIMaterialToString($a_material)
2930  {
2931  $result = "";
2932  for ($i = 0; $i < $a_material->getMaterialCount(); $i++)
2933  {
2934  $material = $a_material->getMaterial($i);
2935  if (strcmp($material["type"], "mattext") == 0)
2936  {
2937  $result .= $material["material"]->getContent();
2938  }
2939  if (strcmp($material["type"], "matimage") == 0)
2940  {
2941  $matimage = $material["material"];
2942  if (preg_match("/(il_([0-9]+)_mob_([0-9]+))/", $matimage->getLabel(), $matches))
2943  {
2944  // import an mediaobject which was inserted using tiny mce
2945  if (!is_array($_SESSION["import_mob_xhtml"])) $_SESSION["import_mob_xhtml"] = array();
2946  array_push($_SESSION["import_mob_xhtml"], array("mob" => $matimage->getLabel(), "uri" => $matimage->getUri()));
2947  }
2948  }
2949  }
2950  return $result;
2951  }
2952 
2961  function addQTIMaterial(&$a_xml_writer, $a_material, $close_material_tag = TRUE, $add_mobs = TRUE)
2962  {
2963  include_once "./Services/RTE/classes/class.ilRTE.php";
2964  include_once("./Services/MediaObjects/classes/class.ilObjMediaObject.php");
2965 
2966  $a_xml_writer->xmlStartTag("material");
2967  $attrs = array(
2968  "texttype" => "text/plain"
2969  );
2970  if ($this->isHTML($a_material))
2971  {
2972  $attrs["texttype"] = "text/xhtml";
2973  }
2974  $a_xml_writer->xmlElement("mattext", $attrs, ilRTE::_replaceMediaObjectImageSrc($a_material, 0));
2975  if ($add_mobs)
2976  {
2977  $mobs = ilObjMediaObject::_getMobsOfObject("qpl:html", $this->getId());
2978  foreach ($mobs as $mob)
2979  {
2980  $moblabel = "il_" . IL_INST_ID . "_mob_" . $mob;
2981  if (strpos($a_material, "mm_$mob") !== FALSE)
2982  {
2983  if (ilObjMediaObject::_exists($mob))
2984  {
2985  $mob_obj =& new ilObjMediaObject($mob);
2986  $imgattrs = array(
2987  "label" => $moblabel,
2988  "uri" => "objects/" . "il_" . IL_INST_ID . "_mob_" . $mob . "/" . $mob_obj->getTitle()
2989  );
2990  }
2991  $a_xml_writer->xmlElement("matimage", $imgattrs, NULL);
2992  }
2993  }
2994  }
2995  if ($close_material_tag) $a_xml_writer->xmlEndTag("material");
2996  }
2997 
2998  function createNewImageFileName($image_filename)
2999  {
3000  $extension = "";
3001  if (preg_match("/.*\.(png|jpg|gif|jpeg)$/i", $image_filename, $matches))
3002  {
3003  $extension = "." . $matches[1];
3004  }
3005  $image_filename = md5($image_filename) . $extension;
3006  return $image_filename;
3007  }
3008 
3019  function _setReachedPoints($active_id, $question_id, $points, $maxpoints, $pass, $manualscoring, $obligationsEnabled)
3020  {
3021  global $ilDB;
3022 
3023  if ($points <= $maxpoints)
3024  {
3025  if (is_null($pass))
3026  {
3027  $pass = assQuestion::_getSolutionMaxPass($question_id, $active_id);
3028  }
3029 
3030  // retrieve the already given points
3031  $old_points = 0;
3032  $result = $ilDB->queryF("SELECT points FROM tst_test_result WHERE active_fi = %s AND question_fi = %s AND pass = %s",
3033  array('integer','integer','integer'),
3034  array($active_id, $question_id, $pass)
3035  );
3036  $manual = ($manualscoring) ? 1 : 0;
3037  if ($result->numRows())
3038  {
3039  $row = $ilDB->fetchAssoc($result);
3040  $old_points = $row["points"];
3041  $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",
3042  array('float', 'integer', 'integer', 'integer', 'integer', 'integer'),
3043  array($points, $manual, time(), $active_id, $question_id, $pass)
3044  );
3045  }
3046  else
3047  {
3048  $next_id = $ilDB->nextId('tst_test_result');
3049  $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)",
3050  array('integer', 'integer','integer', 'float', 'integer', 'integer','integer'),
3051  array($next_id, $active_id, $question_id, $points, $pass, $manual, time())
3052  );
3053  }
3054  assQuestion::_updateTestPassResults($active_id, $pass, $obligationsEnabled);
3055  // finally update objective result
3056  include_once "./Modules/Test/classes/class.ilObjTest.php";
3057  include_once './Modules/Course/classes/class.ilCourseObjectiveResult.php';
3059 
3060  include_once ("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
3062  {
3063  global $lng, $ilUser;
3064  include_once "./Modules/Test/classes/class.ilObjTestAccess.php";
3065  $username = ilObjTestAccess::_getParticipantData($active_id);
3066  assQuestion::_logAction(sprintf($lng->txtlng("assessment", "log_answer_changed_points", ilObjAssessmentFolder::_getLogLanguage()), $username, $old_points, $points, $ilUser->getFullname() . " (" . $ilUser->getLogin() . ")"), $active_id, $question_id);
3067  }
3068 
3069  return TRUE;
3070  }
3071  else
3072  {
3073  return FALSE;
3074  }
3075  }
3076 
3084  function getQuestion()
3085  {
3086  return $this->question;
3087  }
3088 
3096  function setQuestion($question = "")
3097  {
3098  $this->question = $question;
3099  }
3100 
3107  function getQuestionType()
3108  {
3109  // must be overwritten in every parent class
3110  return "";
3111  }
3112 
3122  {
3123  global $ilDB;
3124 
3125  $result = $ilDB->queryF("SELECT question_type_id FROM qpl_qst_type WHERE type_tag = %s",
3126  array('text'),
3127  array($this->getQuestionType())
3128  );
3129  if ($result->numRows() == 1)
3130  {
3131  $row = $ilDB->fetchAssoc($result);
3132  return $row["question_type_id"];
3133  }
3134  return 0;
3135  }
3136 
3146  function saveFeedbackGeneric($correctness, $feedback)
3147  {
3148  global $ilDB;
3149 
3150  switch ($correctness)
3151  {
3152  case 0:
3153  $correctness = 0;
3154  break;
3155  case 1:
3156  default:
3157  $correctness = 1;
3158  break;
3159  }
3160  $affectedRows = $ilDB->manipulateF("DELETE FROM qpl_fb_generic WHERE question_fi = %s AND correctness = %s",
3161  array('integer', 'text'),
3162  array($this->getId(), $correctness)
3163  );
3164  if (strlen($feedback))
3165  {
3166  include_once("./Services/RTE/classes/class.ilRTE.php");
3167  $next_id = $ilDB->nextId('qpl_fb_generic');
3168 
3170  $ilDB->insert('qpl_fb_generic', array(
3171  'feedback_id' => array( 'integer', $next_id ),
3172  'question_fi' => array( 'integer', $this->getId() ),
3173  'correctness' => array( 'text', $correctness ),
3174  'feedback' => array( 'clob', ilRTE::_replaceMediaObjectImageSrc( $feedback, 0 ) ),
3175  'tstamp' => array( 'integer', time() ),
3176  )
3177  );
3178 
3179  }
3180  }
3181 
3191  function getFeedbackGeneric($correctness)
3192  {
3193  global $ilDB;
3194 
3195  $feedback = "";
3196  $result = $ilDB->queryF("SELECT * FROM qpl_fb_generic WHERE question_fi = %s AND correctness = %s",
3197  array('integer', 'text'),
3198  array($this->getId(), $correctness)
3199  );
3200  if ($result->numRows())
3201  {
3202  $row = $ilDB->fetchAssoc($result);
3203  include_once("./Services/RTE/classes/class.ilRTE.php");
3204  $feedback = ilRTE::_replaceMediaObjectImageSrc($row["feedback"], 1);
3205  }
3206  return $feedback;
3207  }
3208 
3215  function duplicateGenericFeedback($original_id)
3216  {
3217  global $ilDB;
3218 
3219  $feedback = "";
3220  $result = $ilDB->queryF("SELECT * FROM qpl_fb_generic WHERE question_fi = %s",
3221  array('integer'),
3222  array($original_id)
3223  );
3224  if ($result->numRows())
3225  {
3226  while ($row = $ilDB->fetchAssoc($result))
3227  {
3228  $next_id = $ilDB->nextId('qpl_fb_generic');
3229 
3231  $ilDB->insert('qpl_fb_generic', array(
3232  'feedback_id' => array( 'integer', $next_id ),
3233  'question_fi' => array( 'integer', $this->getId() ),
3234  'correctness' => array( 'text', $row["correctness"] ),
3235  'feedback' => array( 'clob', $row["feedback"] ),
3236  'tstamp' => array( 'integer', time() ),
3237  )
3238  );
3239  }
3240  }
3241  }
3242 
3243  function syncFeedbackGeneric()
3244  {
3245  global $ilDB;
3246 
3247  $feedback = "";
3248 
3249  // delete generic feedback of the original
3250  $affectedRows = $ilDB->manipulateF("DELETE FROM qpl_fb_generic WHERE question_fi = %s",
3251  array('integer'),
3252  array($this->original_id)
3253  );
3254 
3255  // get generic feedback of the actual question
3256  $result = $ilDB->queryF("SELECT * FROM qpl_fb_generic WHERE question_fi = %s",
3257  array('integer'),
3258  array($this->getId())
3259  );
3260 
3261  // save generic feedback to the original
3262  if ($result->numRows())
3263  {
3264  while ($row = $ilDB->fetchAssoc($result))
3265  {
3266  $next_id = $ilDB->nextId('qpl_fb_generic');
3268  $ilDB->insert('qpl_fb_generic', array(
3269  'feedback_id' => array( 'integer', $next_id ),
3270  'question_fi' => array( 'integer', $this->original_id ),
3271  'correctness' => array( 'text', $row["correctness"] ),
3272  'feedback' => array( 'clob', $row["feedback"] ),
3273  'tstamp' => array( 'integer', time() ),
3274  )
3275  );
3276  }
3277  }
3278  }
3279 
3280  public function syncHints()
3281  {
3282  global $ilDB;
3283 
3284  // delete hints of the original
3285  $ilDB->manipulateF("DELETE FROM qpl_hints WHERE qht_question_fi = %s",
3286  array('integer'),
3287  array($this->original_id)
3288  );
3289 
3290  // get hints of the actual question
3291  $result = $ilDB->queryF("SELECT * FROM qpl_hints WHERE qht_question_fi = %s",
3292  array('integer'),
3293  array($this->getId())
3294  );
3295 
3296  // save hints to the original
3297  if ($result->numRows())
3298  {
3299  while ($row = $ilDB->fetchAssoc($result))
3300  {
3301  $next_id = $ilDB->nextId('qpl_hints');
3303  $ilDB->insert('qpl_hints', array(
3304  'qht_hint_id' => array('integer', $next_id),
3305  'qht_question_fi' => array('integer', $this->original_id),
3306  'qht_hint_index' => array('integer', $row["qht_hint_index"]),
3307  'qht_hint_points' => array('integer', $row["qht_hint_points"]),
3308  'qht_hint_text' => array('text', $row["qht_hint_text"]),
3309  )
3310  );
3311  }
3312  }
3313  }
3314 
3320  {
3321  // must be called in parent classes. add additional RTE text in the parent
3322  // classes and call this method to add the standard RTE text
3323  $collected = $this->getQuestion();
3324  $collected .= $this->getFeedbackGeneric(0);
3325  $collected .= $this->getFeedbackGeneric(1);
3326  for( $i = 0; $i <= $this->getTotalAnswers(); $i++ )
3327  {
3328  $collected .= $this->getFeedbackSingleAnswer($i);
3329  }
3330  foreach ($this->suggested_solutions as $solution_array)
3331  {
3332  $collected .= $solution_array["value"];
3333  }
3334  return $collected;
3335  }
3336 
3337  function getFeedbackSingleAnswer($answer_index)
3338  {
3339  return '';
3340  }
3341 
3347  {
3348  $combinedtext = $this->getRTETextWithMediaObjects();
3349  include_once("./Services/RTE/classes/class.ilRTE.php");
3350  ilRTE::_cleanupMediaObjectUsage($combinedtext, "qpl:html", $this->getId());
3351  }
3352 
3358  function &getInstances()
3359  {
3360  global $ilDB;
3361 
3362  $result = $ilDB->queryF("SELECT question_id FROM qpl_questions WHERE original_id = %s",
3363  array("integer"),
3364  array($this->getId())
3365  );
3366  $instances = array();
3367  $ids = array();
3368  while ($row = $ilDB->fetchAssoc($result))
3369  {
3370  array_push($ids, $row["question_id"]);
3371  }
3372  foreach ($ids as $question_id)
3373  {
3374  // check non random tests
3375  $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",
3376  array("integer"),
3377  array($question_id)
3378  );
3379  while ($row = $ilDB->fetchAssoc($result))
3380  {
3381  $instances[$row['obj_fi']] = ilObject::_lookupTitle($row['obj_fi']);
3382  }
3383  // check random tests
3384  $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",
3385  array("integer"),
3386  array($question_id)
3387  );
3388  while ($row = $ilDB->fetchAssoc($result))
3389  {
3390  $instances[$row['obj_fi']] = ilObject::_lookupTitle($row['obj_fi']);
3391  }
3392  }
3393  include_once "./Modules/Test/classes/class.ilObjTest.php";
3394  foreach ($instances as $key => $value)
3395  {
3396  $instances[$key] = array("obj_id" => $key, "title" => $value, "author" => ilObjTest::_lookupAuthor($key), "refs" => ilObject::_getAllReferences($key));
3397  }
3398  return $instances;
3399  }
3400 
3401  function _needsManualScoring($question_id)
3402  {
3403  include_once "./Modules/Test/classes/class.ilObjAssessmentFolder.php";
3405  $questiontype = assQuestion::_getQuestionType($question_id);
3406  if (in_array($questiontype, $scoring))
3407  {
3408  return TRUE;
3409  }
3410  else
3411  {
3412  return FALSE;
3413  }
3414  }
3415 
3423  function getActiveUserData($active_id)
3424  {
3425  global $ilDB;
3426  $result = $ilDB->queryF("SELECT * FROM tst_active WHERE active_id = %s",
3427  array('integer'),
3428  array($active_id)
3429  );
3430  if ($result->numRows())
3431  {
3432  $row = $ilDB->fetchAssoc($result);
3433  return array("user_id" => $row["user_fi"], "test_id" => $row["test_fi"]);
3434  }
3435  else
3436  {
3437  return array();
3438  }
3439  }
3440 
3448  static function _includeClass($question_type, $gui = 0)
3449  {
3450  $type = $question_type;
3451  if ($gui) $type .= "GUI";
3452  if (file_exists("./Modules/TestQuestionPool/classes/class.".$type.".php"))
3453  {
3454  include_once "./Modules/TestQuestionPool/classes/class.".$type.".php";
3455  }
3456  else
3457  {
3458  global $ilPluginAdmin;
3459  $pl_names = $ilPluginAdmin->getActivePluginsForSlot(IL_COMP_MODULE, "TestQuestionPool", "qst");
3460  foreach ($pl_names as $pl_name)
3461  {
3462  $pl = ilPlugin::getPluginObject(IL_COMP_MODULE, "TestQuestionPool", "qst", $pl_name);
3463  if (strcmp($pl->getQuestionType(), $question_type) == 0)
3464  {
3465  $pl->includeClass("class.".$type.".php");
3466  }
3467  }
3468  }
3469  }
3470 
3477  static function _getQuestionTypeName($type_tag)
3478  {
3479  if (file_exists("./Modules/TestQuestionPool/classes/class.".$type_tag.".php"))
3480  {
3481  global $lng;
3482  return $lng->txt($type_tag);
3483  }
3484  else
3485  {
3486  global $ilPluginAdmin;
3487  $pl_names = $ilPluginAdmin->getActivePluginsForSlot(IL_COMP_MODULE, "TestQuestionPool", "qst");
3488  foreach ($pl_names as $pl_name)
3489  {
3490  $pl = ilPlugin::getPluginObject(IL_COMP_MODULE, "TestQuestionPool", "qst", $pl_name);
3491  if (strcmp($pl->getQuestionType(), $type_tag) == 0)
3492  {
3493  return $pl->getQuestionTypeTranslation();
3494  }
3495  }
3496  }
3497  return "";
3498  }
3499 
3507  function &_instanciateQuestionGUI($question_id)
3508  {
3509  if (strcmp($question_id, "") != 0)
3510  {
3511  $question_type = assQuestion::_getQuestionType($question_id);
3512  $question_type_gui = $question_type . "GUI";
3513  assQuestion::_includeClass($question_type, 1);
3514  $question_gui = new $question_type_gui();
3515  $question_gui->object->loadFromDb($question_id);
3516  return $question_gui;
3517  }
3518  }
3519 
3532  public function setExportDetailsXLS(&$worksheet, $startrow, $active_id, $pass, &$format_title, &$format_bold)
3533  {
3534  return $startrow;
3535  }
3536 
3540  public function __get($value)
3541  {
3542  switch ($value)
3543  {
3544  case "id":
3545  return $this->getId();
3546  break;
3547  case "title":
3548  return $this->getTitle();
3549  break;
3550  case "comment":
3551  return $this->getComment();
3552  break;
3553  case "owner":
3554  return $this->getOwner();
3555  break;
3556  case "author":
3557  return $this->getAuthor();
3558  break;
3559  case "question":
3560  return $this->getQuestion();
3561  break;
3562  case "points":
3563  return $this->getPoints();
3564  break;
3565  case "est_working_time":
3566  return $this->getEstimatedWorkingTime();
3567  break;
3568  case "shuffle":
3569  return $this->getShuffle();
3570  break;
3571  case "test_id":
3572  return $this->getTestId();
3573  break;
3574  case "obj_id":
3575  return $this->getObjId();
3576  break;
3577  case "ilias":
3578  return $this->ilias;
3579  break;
3580  case "tpl":
3581  return $this->tpl;
3582  break;
3583  case "page":
3584  return $this->page;
3585  break;
3586  case "outputType":
3587  return $this->getOutputType();
3588  break;
3589  case "suggested_solutions":
3590  return $this->getSuggestedSolutions();
3591  break;
3592  case "original_id":
3593  return $this->getOriginalId();
3594  break;
3595  default:
3596  if (array_key_exists($value, $this->arrData))
3597  {
3598  return $this->arrData[$value];
3599  }
3600  else
3601  {
3602  return null;
3603  }
3604  break;
3605  }
3606  }
3607 
3611  public function __set($key, $value)
3612  {
3613  switch ($key)
3614  {
3615  case "id":
3616  $this->setId($value);
3617  break;
3618  case "title":
3619  $this->setTitle($value);
3620  break;
3621  case "comment":
3622  $this->setComment($value);
3623  break;
3624  case "owner":
3625  $this->setOwner($value);
3626  break;
3627  case "author":
3628  $this->setAuthor($value);
3629  break;
3630  case "question":
3631  $this->setQuestion($value);
3632  break;
3633  case "points":
3634  $this->setPoints($value);
3635  break;
3636  case "est_working_time":
3637  if (is_array($value))
3638  {
3639  $this->setEstimatedWorkingTime($value["h"], $value["m"], $value["s"]);
3640  }
3641  break;
3642  case "shuffle":
3643  $this->setShuffle($value);
3644  break;
3645  case "test_id":
3646  $this->setTestId($value);
3647  break;
3648  case "obj_id":
3649  $this->setObjId($value);
3650  break;
3651  case "outputType":
3652  $this->setOutputType($value);
3653  break;
3654  case "original_id":
3655  $this->setOriginalId($value);
3656  break;
3657  case "page":
3658  $this->page =& $value;
3659  break;
3660  default:
3661  $this->arrData[$key] = $value;
3662  break;
3663  }
3664  }
3665 
3666  public function getNrOfTries()
3667  {
3668  return $this->nr_of_tries;
3669  }
3670 
3671  public function setNrOfTries($a_nr_of_tries)
3672  {
3673  $this->nr_of_tries = $a_nr_of_tries;
3674  }
3675 
3676  public function setExportImagePath($a_path)
3677  {
3678  $this->export_image_path = (string)$a_path;
3679  }
3680 
3681  function _questionExistsInTest($question_id, $test_id)
3682  {
3683  global $ilDB;
3684 
3685  if ($question_id < 1)
3686  {
3687  return false;
3688  }
3689 
3690  $result = $ilDB->queryF("SELECT question_fi FROM tst_test_question WHERE question_fi = %s AND test_fi = %s",
3691  array('integer', 'integer'),
3692  array($question_id, $test_id)
3693  );
3694  if ($result->numRows() == 1)
3695  {
3696  return true;
3697  }
3698  else
3699  {
3700  return false;
3701  }
3702  }
3703 
3710  function formatSAQuestion($a_q)
3711  {
3712  include_once("./Services/RTE/classes/class.ilRTE.php");
3713  $a_q = nl2br((string) ilRTE::_replaceMediaObjectImageSrc($a_q, 0));
3714  $a_q = str_replace("</li><br />", "</li>", $a_q);
3715  $a_q = str_replace("</li><br>", "</li>", $a_q);
3716 
3717  $a_q = ilUtil::insertLatexImages($a_q, "\[tex\]", "\[\/tex\]");
3718  $a_q = ilUtil::insertLatexImages($a_q, "<span class\=\"latex\">", "<\/span>");
3719 
3720  $a_q = str_replace('{', '&#123;', $a_q);
3721  $a_q = str_replace('}', '&#125;', $a_q);
3722 
3723  return $a_q;
3724  }
3725 
3726  protected function duplicateQuestionHints($originalQuestionId, $duplicateQuestionId)
3727  {
3728  require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionHintList.php';
3729  ilAssQuestionHintList::duplicateListForQuestion($originalQuestionId, $duplicateQuestionId);
3730  }
3731 
3732  protected function deleteFeedbackSpecific($question_id)
3733  {
3734  return;
3735  }
3736 
3737  private function deleteFeedbackGeneric($question_id)
3738  {
3739  global $ilDB;
3740  $ilDB->manipulateF(
3741  'DELETE FROM qpl_fb_generic WHERE question_fi = %s',
3742  array('integer'),
3743  array($question_id)
3744  );
3745  }
3746 
3759  public function isAnswered($active_id, $pass = null)
3760  {
3761  return true;
3762  }
3763 
3776  public static function isObligationPossible($questionId)
3777  {
3778  return false;
3779  }
3780 
3787  {
3788  $this->obligationsToBeConsidered = (bool)$obligationsToBeConsidered;
3789  }
3790 
3797  {
3798  return (bool)$this->obligationsToBeConsidered;
3799  }
3800 
3801  public function isAutosaveable()
3802  {
3803  return TRUE;
3804  }
3805 
3818  protected static function doesSolutionRecordsExist($activeId, $pass, $questionId)
3819  {
3820  // check if a solution was stored in tst_solution
3821 
3822  global $ilDB;
3823 
3824  $query = "
3825  SELECT count(active_fi) cnt
3826 
3827  FROM tst_solutions
3828 
3829  WHERE active_fi = %s
3830  AND question_fi = %s
3831  AND pass = %s
3832  ";
3833 
3834  $res = $ilDB->queryF(
3835  $query, array('integer','integer','integer'),
3836  array($activeId, $questionId, $pass)
3837  );
3838 
3839  $row = $ilDB->fetchAssoc($res);
3840 
3841  $solutionRecordsExist = (
3842  0 < (int)$row['cnt'] ? true : false
3843  );
3844 
3845  return $solutionRecordsExist;
3846  }
3847 
3852  {
3853  require_once 'Services/Html/classes/class.ilHtmlPurifierFactory.php';
3854  return ilHtmlPurifierFactory::_getInstanceByType('qpl_usersolution');
3855  }
3856 }