19declare(strict_types=1);
88 protected \ilAssQuestionPage
$page;
132 $ilLog =
$DIC->logger();
133 $local_dic = QuestionPoolDIC::dic();
134 $this->questionrepository = $local_dic[
'question.general_properties.repository'];
135 $this->questionpool_request = $local_dic[
'request_data_collector'];
136 $this->question_files = $local_dic[
'question_files'];
137 $this->suggestedsolution_repo = $local_dic[
'question.repo.suggestedsolutions'];
138 $this->current_user =
$DIC[
'ilUser'];
143 $this->
http = $DIC->http();
157 $this->suggested_solutions = [];
158 $this->shuffle =
true;
159 $this->nr_of_tries = 0;
162 $this->questionActionCmd =
'handleQuestionAction';
163 $this->export_image_path =
'';
164 $this->shuffler =
$DIC->refinery()->random()->dontShuffle();
166 $this->skillUsageService =
$DIC->skills()->usage();
168 $this->test_result_repository = TestDIC::dic()[
'results.data.repository'];
175 abstract public function saveWorkingData(
int $active_id, ?
int $pass =
null,
bool $authorized =
true): bool;
180 bool $authorized_solution =
true
206 array $solution_values
214 array $solution_values
229 return $this->questionpool_request->getCmdIndex($this->questionActionCmd) ??
'';
234 return !empty($this->questionpool_request->strArray($post_submission_field_name));
263 string $importdirectory,
266 int $questionpool_id,
269 int &$question_counter,
270 array $import_mapping
273 $import =
new $classname($this);
274 $new_import_mapping = $import->fromXML(
284 return $new_import_mapping;
293 bool $a_include_header =
true,
294 bool $a_include_binary =
true,
295 bool $a_shuffle =
false,
296 bool $test_output =
false,
297 bool $force_image_references =
false
299 $classname = $this->getQuestionType() .
"Export";
300 $export =
new $classname($this);
301 return $export->toXML($a_include_header, $a_include_binary, $a_shuffle, $test_output, $force_image_references);
306 $this->title = $title;
316 $this->test_id =
$id;
326 $this->shuffle = $shuffle ??
false;
331 if ($author ===
'') {
332 $author = $this->current_user->getFullname();
334 $this->author = $author;
339 $this->owner = $owner;
349 return $this->
refinery->encode()->htmlSpecialCharsAsEntities()->transform($this->title);
364 return $this->shuffle;
369 return $this->test_id;
379 return $this->
refinery->encode()->htmlSpecialCharsAsEntities()->transform($this->
comment);
384 return $this->thumb_size;
389 if ($a_size >= self::MINIMUM_THUMB_SIZE) {
390 $this->thumb_size = $a_size;
392 throw new ilException(
"Thumb size must be at least " . self::MINIMUM_THUMB_SIZE .
"px");
398 return self::MINIMUM_THUMB_SIZE;
403 return self::MAXIMUM_THUMB_SIZE;
408 return $this->author;
413 return $this->
refinery->string()->stripTags()->transform($this->author);
423 return $this->obj_id;
428 $this->obj_id = $obj_id;
443 $this->external_id = $external_id;
448 if ($this->external_id ===
null || $this->external_id ===
'') {
449 if ($this->
getId() > 0) {
452 return uniqid(
'',
true);
454 return $this->external_id;
459 $question = self::instantiateQuestion($question_id);
460 if (!is_object($question)) {
463 return $question->getSuggestedSolutionOutput();
469 foreach ($this->suggested_solutions as $solution) {
470 switch ($solution->getType()) {
471 case SuggestedSolution::TYPE_LM:
472 case SuggestedSolution::TYPE_LM_CHAPTER:
473 case SuggestedSolution::TYPE_LM_PAGE:
474 case SuggestedSolution::TYPE_GLOSARY_TERM:
475 $output[] =
'<a href="'
476 . $this->getInternalLinkHref($solution->getInternalLink())
478 . $this->
lng->txt(
"solution_hint")
482 case SuggestedSolution::TYPE_FILE:
483 $possible_texts = array_values(
488 $this->lng->txt(
'tst_show_solution_suggested')
494 $path_to_solution = $this->getSuggestedSolutionPathWeb() . $solution->getFilename();
495 if (!file_exists($path_to_solution)) {
498 $output[] =
'<a href="'
506 return implode(
"<br />", $output);
511 return $this->suggested_solutions;
521 $result =
$ilDB->queryF(
522 "SELECT * FROM tst_test_result WHERE active_fi = %s AND question_fi = %s AND pass = %s",
523 [
'integer',
'integer',
'integer'],
524 [$active_id, $question_id, $pass]
526 if ($result->numRows() == 1) {
527 $row =
$ilDB->fetchAssoc($result);
528 $points = (float) $row[
"points"];
535 return round(self::_getReachedPoints($active_id, $this->
getId(), $pass), 2);
540 return $this->points;
546 $reached_points = $this->calculateReachedPoints($active_id, $pass, $authorized_solution);
548 $reached_points = $this->adjustReachedPointsByScoringOptions($reached_points, $active_id);
550 return $reached_points;
559 $reached_points = $this->calculateReachedPoints($active_id, $pass);
562 $reached_points = $this->adjustReachedPointsByScoringOptions($reached_points, $active_id);
564 if (is_null($reached_points)) {
565 $reached_points = 0.0;
569 $existing_solutions = $this->lookupForExistingSolutions($active_id, $pass);
571 $this->getProcessLocker()->executeUserQuestionResultUpdateOperation(
572 function () use ($active_id, $pass, $reached_points, $existing_solutions) {
574 DELETE FROM tst_test_result
581 $types = [
'integer',
'integer',
'integer'];
582 $values = [$active_id, $this->
getId(), $pass];
584 if ($this->getStep() !==
null) {
589 $types[] =
'integer';
590 $values[] = $this->getStep();
592 $this->db->manipulateF($query, $types, $values);
594 if ($existing_solutions[
'authorized']) {
595 $next_id = $this->db->nextId(
"tst_test_result");
597 'test_result_id' => [
'integer', $next_id],
598 'active_fi' => [
'integer', $active_id],
599 'question_fi' => [
'integer', $this->
getId()],
600 'pass' => [
'integer', $pass],
601 'points' => [
'float', $reached_points],
602 'tstamp' => [
'integer', time()],
603 'answered' => [
'integer',
true]
606 if ($this->getStep() !==
null) {
607 $fieldData[
'step'] = [
'integer', $this->getStep()];
610 $this->db->insert(
'tst_test_result', $fieldData);
615 $this->test_result_repository->updateTestAttemptResult($active_id, $pass, $this->getProcessLocker());
631 $this->getProcessLocker()->executePersistWorkingStateLockOperation(
function () use ($active_id, $pass, $authorized, &$saveStatus) {
632 if ($pass ===
null) {
636 $saveStatus = $this->saveWorkingData($active_id, $pass, $authorized);
641 $this->removeIntermediateSolution($active_id, $pass);
643 $this->calculateResultsFromSolution($active_id, $pass);
655 $this->savePreviewData($preview_session);
656 return $this->validateSolutionSubmit();
671 return CLIENT_WEB_DIR .
"/assessment/$this->obj_id/$this->id/solution/";
678 public function getImagePath($question_id =
null, $object_id =
null): string
680 if ($question_id ===
null) {
684 if ($object_id ===
null) {
685 $object_id = $this->obj_id;
688 return $this->question_files->buildImagePath($question_id, $object_id);
694 .
"/assessment/{$this->obj_id}/{$this->id}/solution/";
709 if (!$this->export_image_path) {
711 .
"/assessment/{$this->obj_id}/{$this->id}/images/";
718 return $this->export_image_path;
723 if ($this->getTestPresentationConfig()->isSolutionInitiallyPrefilled()) {
724 return $this->getSolutionValues($activeId, $pass,
true);
726 return $this->getUserSolutionPreferingIntermediate($activeId, $pass);
734 $solution = $this->getSolutionValues($active_id, $pass, false);
736 if (!count($solution)) {
737 $solution = $this->getSolutionValues($active_id, $pass,
true);
749 bool $authorized =
true
751 if ($pass === null && is_numeric($active_id)) {
752 $pass = $this->getSolutionMaxPass((
int) $active_id);
755 if ($this->getStep() !==
null) {
764 ORDER BY solution_id";
766 $result = $this->db->queryF(
768 [
'integer',
'integer',
'integer',
'integer',
'integer'],
769 [(
int) $active_id, $this->
getId(), $pass, $this->getStep(), (
int) $authorized]
782 $result = $this->db->queryF(
784 [
'integer',
'integer',
'integer',
'integer'],
785 [(
int) $active_id, $this->
getId(), $pass, (
int) $authorized]
791 while ($row = $this->db->fetchAssoc($result)) {
800 $answer_table_name = $this->getAnswerTableName();
802 if (!is_array($answer_table_name)) {
803 $answer_table_name = [$answer_table_name];
806 foreach ($answer_table_name as $table) {
807 if (strlen($table)) {
808 $this->db->manipulateF(
809 "DELETE FROM $table WHERE question_fi = %s",
819 $additional_table_name = $this->getAdditionalTableName();
821 if (!is_array($additional_table_name)) {
822 $additional_table_name = [$additional_table_name];
825 foreach ($additional_table_name as $table) {
826 if (strlen($table)) {
827 $this->db->manipulateF(
828 "DELETE FROM $table WHERE question_fi = %s",
844 public function delete(
int $question_id):
void
846 if ($question_id < 1) {
850 $result = $this->db->queryF(
851 "SELECT obj_fi FROM qpl_questions WHERE question_id = %s",
855 if ($this->db->numRows($result) !== 1) {
859 $row = $this->db->fetchAssoc($result);
860 $obj_id = $row[
"obj_fi"];
863 $this->deletePageOfQuestion($question_id);
864 }
catch (Exception
$e) {
865 $this->log->root()->error(
"EXCEPTION: Could not delete page of question $question_id: $e");
869 $affectedRows = $this->db->manipulateF(
870 "DELETE FROM qpl_questions WHERE question_id = %s",
874 if ($affectedRows == 0) {
879 $this->deleteAdditionalTableData($question_id);
880 $this->deleteAnswers($question_id);
881 $this->feedbackOBJ->deleteGenericFeedbacks($question_id, $this->isAdditionalContentEditingModePageObject());
882 $this->feedbackOBJ->deleteSpecificAnswerFeedbacks($question_id, $this->isAdditionalContentEditingModePageObject());
883 }
catch (Exception
$e) {
884 $this->log->root()->error(
"EXCEPTION: Could not delete additional table data of question {$question_id}: {$e}");
889 $affectedRows = $this->db->manipulateF(
890 "DELETE FROM tst_test_question WHERE question_fi = %s",
894 }
catch (Exception
$e) {
895 $this->log->root()->error(
"EXCEPTION: Could not delete delete question {$question_id} from a test: {$e}");
899 $this->getSuggestedSolutionsRepo()->deleteForQuestion($question_id);
900 }
catch (Exception
$e) {
901 $this->log->root()->error(
"EXCEPTION: Could not delete suggested solutions of question {$question_id}: {$e}");
904 $directory =
CLIENT_WEB_DIR .
"/assessment/" . $obj_id .
"/$question_id";
906 if (is_dir($directory)) {
909 }
catch (Exception
$e) {
910 $this->log->root()->error(
"EXCEPTION: Could not delete question file directory {$directory} of question {$question_id}: {$e}");
919 foreach ($mobs as $mob) {
926 }
catch (Exception
$e) {
927 $this->log->root()->error(
"EXCEPTION: Error deleting the media objects of question {$question_id}: {$e}");
930 $assignmentList->setParentObjId($obj_id);
931 $assignmentList->setQuestionIdFilter($question_id);
932 $assignmentList->loadFromDb();
933 foreach ($assignmentList->getAssignmentsByQuestionId($question_id) as $assignment) {
935 $assignment->deleteFromDb();
938 if (!$assignment->isSkillUsed()) {
939 $this->skillUsageService->removeUsage(
940 $assignment->getParentObjId(),
941 $assignment->getSkillBaseId(),
942 $assignment->getSkillTrefId()
947 $this->deleteTaxonomyAssignments();
948 $this->deleteComments();
952 }
catch (Exception
$e) {
953 $this->log->root()->error(
954 "EXCEPTION: Error updating the question pool question count of"
955 .
" question pool {$this->getObjId()} when deleting question {$question_id}: {$e}"
964 foreach ($taxIds as $taxId) {
966 $taxNodeAssignment->deleteAssignmentsOfItem($this->
getId());
973 $result = $this->db->queryF(
974 "SELECT question_id FROM qpl_questions WHERE original_id = %s OR question_id = %s",
975 [
'integer',
'integer'],
976 [$this->
id, $this->
id]
978 if ($this->db->numRows($result) == 0) {
982 while ($row = $this->db->fetchAssoc($result)) {
983 $found_id[] = $row[
"question_id"];
986 $result = $this->db->query(
"SELECT * FROM tst_test_result WHERE " . $this->db->in(
'question_fi', $found_id,
false,
'integer'));
988 return $this->db->numRows($result);
993 if (!file_exists($file)) {
997 if (!is_file($file)) {
1001 if (!is_readable($file)) {
1011 foreach ($mobs as $mob) {
1018 $qpl_id = $this->getObjId();
1020 $this->page->setId($this->
getId());
1021 $this->page->setParentId($qpl_id);
1022 $this->page->setXMLContent(
"<PageObject><PageContent>" .
1023 "<Question QRef=\"il__qst_" . $this->
getId() .
"\"/>" .
1024 "</PageContent></PageObject>");
1025 $this->page->create(
false);
1033 ilPCPlugged::handleCopiedPluggedContent($page, $page->getDomDoc());
1034 $xml = str_replace(
"il__qst_" . $a_q_id,
"il__qst_" . $this->
id, $page->getXMLFromDom());
1035 $this->page->setXMLContent($xml);
1036 $this->page->updateFromXML();
1043 return $page->getXMLContent();
1048 $this->original_id = $original_id;
1053 return $this->original_id;
1056 protected static $imageSourceFixReplaceMap = [
1057 'ok.svg' =>
'ok.png',
1058 'not_ok.svg' =>
'not_ok.png',
1059 'object/checkbox_checked.svg' =>
'checkbox_checked.png',
1060 'object/checkbox_unchecked.svg' =>
'checkbox_unchecked.png',
1061 'object/radiobutton_checked.svg' =>
'radiobutton_checked.png',
1062 'object/radiobutton_unchecked.svg' =>
'radiobutton_unchecked.png'
1065 public function fixSvgToPng(
string $imageFilenameContainingString): string
1067 $needles = array_keys(self::$imageSourceFixReplaceMap);
1068 $replacements = array_values(self::$imageSourceFixReplaceMap);
1069 return str_replace($needles, $replacements, $imageFilenameContainingString);
1075 if (preg_match_all(
'/src="(.*?)"/m', $html, $matches)) {
1076 $sources = $matches[1];
1078 $needleReplacementMap = [];
1080 foreach ($sources as $src) {
1083 if (file_exists($file)) {
1087 $levels = explode(DIRECTORY_SEPARATOR, $src);
1088 if (count($levels) < 5 || $levels[0] !==
'Customizing' || $levels[2] !==
'skin') {
1094 if ($levels[4] ===
'components/ILIAS' || $levels[4] ===
'components/ILIAS') {
1095 $component = $levels[4] . DIRECTORY_SEPARATOR . $levels[5];
1101 if (count($needleReplacementMap)) {
1102 $html = str_replace(array_keys($needleReplacementMap), array_values($needleReplacementMap), $html);
1111 $result = $this->db->queryF(
1112 'SELECT external_id FROM qpl_questions WHERE question_id = %s',
1116 if ($this->db->numRows($result) === 1) {
1117 $data = $this->db->fetchAssoc($result);
1118 $this->external_id =
$data[
'external_id'];
1121 $suggested_solutions = $this->loadSuggestedSolutions();
1122 $this->suggested_solutions = [];
1123 if ($suggested_solutions) {
1124 foreach ($suggested_solutions as $solution) {
1125 $this->suggested_solutions[$solution->getSubquestionIndex()] = $solution;
1139 $obj_id = $this->getObjId();
1141 && $this->questionpool_request->hasRefId()) {
1142 $obj_id = $this->questionpool_request->getRefId();
1146 $obj_id = $this->questionpool_request->int(
'sel_qpl');
1150 return $this->
getId();
1154 if ($a_create_page) {
1158 $next_id = $this->db->nextId(
'qpl_questions');
1159 $this->db->insert(
"qpl_questions", [
1160 "question_id" => [
"integer", $next_id],
1161 "question_type_fi" => [
"integer", $this->getQuestionTypeID()],
1162 "obj_fi" => [
"integer", $obj_id],
1163 "title" => [
"text",
''],
1164 "description" => [
"text",
''],
1165 "author" => [
"text", $this->getAuthor()],
1166 "owner" => [
"integer", $this->current_user->getId()],
1167 "question_text" => [
"clob",
''],
1168 "points" => [
"float",
"0.0"],
1169 "nr_of_tries" => [
"integer", $this->getDefaultNrOfTries()],
1170 "complete" => [
"text", $complete],
1171 "created" => [
"integer", time()],
1172 "original_id" => [
"integer",
null],
1173 "tstamp" => [
"integer", $tstamp],
1174 "external_id" => [
"text", $this->getExternalId()],
1175 'add_cont_edit_mode' => [
'text', $this->getAdditionalContentEditingMode()]
1177 $this->setId($next_id);
1179 if ($a_create_page) {
1181 $this->createPageObject();
1184 return $this->
getId();
1189 if ($this->
getId() === -1) {
1190 $next_id = $this->db->nextId(
'qpl_questions');
1191 $this->db->insert(
"qpl_questions", [
1192 "question_id" => [
"integer", $next_id],
1193 "question_type_fi" => [
"integer", $this->getQuestionTypeID()],
1194 "obj_fi" => [
"integer", $this->getObjId()],
1195 "title" => [
"text", mb_substr($this->
getTitle(), 0, 124)],
1196 "description" => [
"text", mb_substr($this->getComment(), 0, 1000)],
1197 "author" => [
"text", mb_substr($this->getAuthor(), 0, 512)],
1198 "owner" => [
"integer", $this->getOwner()],
1200 "points" => [
"float", $this->getMaximumPoints()],
1201 "nr_of_tries" => [
"integer", $this->getNrOfTries()],
1202 "created" => [
"integer", time()],
1203 "original_id" => [
"integer", $original_id],
1204 "tstamp" => [
"integer", time()],
1205 "external_id" => [
"text", $this->getExternalId()],
1206 'add_cont_edit_mode' => [
'text', $this->getAdditionalContentEditingMode()]
1208 $this->setId($next_id);
1210 $this->createPageObject();
1215 $this->db->update(
"qpl_questions", [
1216 "obj_fi" => [
"integer", $this->getObjId()],
1217 "title" => [
"text", mb_substr($this->
getTitle(), 0, 124)],
1218 "description" => [
"text", mb_substr($this->getComment(), 0, 1000)],
1219 "author" => [
"text", mb_substr($this->getAuthor(), 0, 512)],
1221 "points" => [
"float", $this->getMaximumPoints()],
1222 "nr_of_tries" => [
"integer", $this->getNrOfTries()],
1223 "tstamp" => [
"integer", time()],
1224 'complete' => [
'integer', $this->isComplete()],
1225 "external_id" => [
"text", $this->getExternalId()]
1227 "question_id" => [
"integer", $this->
getId()]
1232 bool $for_test =
true,
1234 string $author =
'',
1238 if ($this->
id <= 0) {
1243 $clone = clone $this;
1246 if ((
int) $test_obj_id > 0) {
1247 $clone->setObjId($test_obj_id);
1251 $clone->setTitle($title);
1254 $clone->setAuthor($author);
1257 $clone->setOwner($owner);
1260 $clone->saveToDb($this->
id);
1265 $clone->clonePageOfQuestion($this->
getId());
1266 $clone->cloneXHTMLMediaObjectsOfQuestion($this->
getId());
1268 $clone = $this->cloneQuestionTypeSpecificProperties($clone);
1270 $clone->onDuplicate($this->getObjId(), $this->
getId(), $clone->getObjId(), $clone->getId());
1276 int $target_parent_id,
1280 throw new RuntimeException(
'The question has not been saved. It cannot be duplicated');
1283 $clone = clone $this;
1285 $source_parent_id = $this->getObjId();
1286 $clone->setObjId($target_parent_id);
1288 $clone->setTitle($title);
1291 $clone->clonePageOfQuestion($this->
id);
1292 $clone->cloneXHTMLMediaObjectsOfQuestion($this->
id);
1293 $clone = $this->cloneQuestionTypeSpecificProperties($clone);
1295 $clone->onCopy($source_parent_id, $this->
id, $clone->getObjId(), $clone->getId());
1301 int $target_parent_id,
1302 string $target_question_title =
''
1305 throw new RuntimeException(
'The question has not been saved. It cannot be duplicated');
1309 $source_parent_id = $this->getObjId();
1312 $clone = clone $this;
1315 $clone->setObjId($target_parent_id);
1317 if ($target_question_title) {
1318 $clone->setTitle($target_question_title);
1322 $clone->clonePageOfQuestion($source_question_id);
1323 $clone->cloneXHTMLMediaObjectsOfQuestion($source_question_id);
1325 $clone = $this->cloneQuestionTypeSpecificProperties($clone);
1327 $clone->onCopy($source_parent_id, $source_question_id, $clone->getObjId(), $clone->getId());
1338 public function saveToDb(?
int $original_id =
null): void
1341 $this->cleanupMediaObjectUsage();
1344 if ($this->isComplete()) {
1351 'tstamp' => [
'integer', time()],
1352 'owner' => [
'integer', $this->getOwner()],
1353 'complete' => [
'integer', $complete],
1354 'lifecycle' => [
'text', $this->getLifecycle()->getIdentifier()],
1357 'question_id' => [
'integer', $this->
getId()]
1366 $target = opendir($image_target_path);
1367 while ($target_file = readdir($target)) {
1368 if ($target_file ===
'.' || $target_file ===
'..') {
1372 $image_target_path . DIRECTORY_SEPARATOR . $target_file,
1373 $image_target_path . DIRECTORY_SEPARATOR . $target_file
1382 $query =
"UPDATE qpl_questions SET tstamp = %s, original_id = %s WHERE question_id = %s";
1386 [
'integer',
'integer',
'text'],
1387 [time(), $originalId, $questionId]
1396 $query =
"UPDATE qpl_questions SET tstamp = %s, original_id = NULL WHERE question_id = %s";
1400 [
'integer',
'text'],
1401 [time(), $questionId]
1406 int $original_parent_id,
1407 int $original_question_id,
1408 int $duplicate_parent_id,
1409 int $duplicate_question_id
1411 $this->copySuggestedSolutions($duplicate_question_id);
1412 $this->cloneSuggestedSolutionFiles($original_parent_id, $original_question_id);
1413 $this->feedbackOBJ->duplicateFeedback($original_question_id, $duplicate_question_id);
1414 $this->duplicateSkillAssignments($original_parent_id, $original_question_id, $duplicate_parent_id, $duplicate_question_id);
1415 $this->duplicateComments($original_parent_id, $original_question_id, $duplicate_parent_id, $duplicate_question_id);
1419 int $original_question_id,
1420 int $clone_question_id,
1421 int $original_parent_id,
1422 int $clone_parent_id
1424 $this->feedbackOBJ->cloneFeedback($original_question_id, $clone_question_id);
1427 protected function onCopy(
int $sourceParentId,
int $sourceQuestionId,
int $targetParentId,
int $targetQuestionId): void
1429 $this->copySuggestedSolutions($targetQuestionId);
1430 $this->duplicateSuggestedSolutionFiles($sourceParentId, $sourceQuestionId);
1431 $this->feedbackOBJ->duplicateFeedback($sourceQuestionId, $targetQuestionId);
1432 $this->duplicateSkillAssignments($sourceParentId, $sourceQuestionId, $targetParentId, $targetQuestionId);
1433 $this->duplicateComments($sourceParentId, $sourceQuestionId, $targetParentId, $targetQuestionId);
1437 int $parent_source_id,
1439 int $parent_target_id,
1442 $manager = $this->getNotesManager();
1443 $data_service = $this->getNotesDataService();
1444 $notes = $manager->getNotesForRepositoryObjIds([$parent_source_id], Note::PUBLIC);
1445 $notes = array_filter(
1447 fn($n) => $n->getContext()->getSubObjId() === $source_id
1450 foreach ($notes as $note) {
1451 $new_context = $data_service->context(
1454 $note->getContext()->getType()
1456 $new_note = $data_service->note(
1462 $note->getCreationDate(),
1463 $note->getUpdateDate(),
1464 $note->getRecipient()
1466 $manager->createNote($new_note, [],
true);
1472 $repo = $this->getNotesRepo();
1473 $manager = $this->getNotesManager();
1474 $source_id = $this->
getId();
1475 $notes = $manager->getNotesForRepositoryObjIds([$this->getObjId()], Note::PUBLIC);
1476 $notes = array_filter(
1478 fn($n) => $n->getContext()->getSubObjId() === $source_id
1480 foreach ($notes as $note) {
1481 $repo->deleteNote($note->getId());
1487 $service =
new NotesService($this->dic);
1488 return $service->internal()->domain()->notes();
1493 $service =
new NotesService($this->dic);
1494 return $service->internal()->data();
1499 $service =
new NotesService($this->dic);
1500 return $service->internal()->repo()->note();
1505 $this->getSuggestedSolutionsRepo()->deleteForQuestion($this->
getId());
1508 $this->suggested_solutions = [];
1514 if (array_key_exists($subquestion_index, $this->suggested_solutions)) {
1515 return $this->suggested_solutions[$subquestion_index];
1521 int $source_question_id,
1522 int $target_question_id
1524 $this->getSuggestedSolutionsRepo()->clone($source_question_id, $target_question_id);
1525 $this->cloneSuggestedSolutionFiles($source_question_id, $target_question_id);
1533 foreach ($this->suggested_solutions as $solution) {
1534 if (!$solution->isOfTypeFile()
1535 || $solution->getFilename() ===
'') {
1539 $filepath = $this->getSuggestedSolutionPath();
1540 $filepath_original = str_replace(
1541 "/{$this->obj_id}/{$this->id}/solution",
1542 "/{$parent_id}/{$question_id}/solution",
1545 if (!file_exists($filepath)) {
1548 if (!is_file($filepath_original . $solution->getFilename())
1549 || !copy($filepath_original . $solution->getFilename(), $filepath . $solution->getFilename())) {
1550 $this->log->root()->error(
'File for suggested solutions could not be duplicated:');
1551 $this->log->root()->error(
"Question-Id: {$this->id}; Question-Title: {$this->title}; File: {$filepath_original}{$solution->getFilename()}");
1557 int $source_question_id,
1558 int $target_question_id
1560 $filepath_target = $this->getSuggestedSolutionPath();
1561 $filepath_original = str_replace(
"/$target_question_id/solution",
"/$source_question_id/solution", $filepath_target);
1563 foreach ($this->suggested_solutions as $solution) {
1564 if (!$solution->isOfTypeFile()
1565 || $solution->getFilename() ===
'') {
1569 if (!file_exists($filepath_original)) {
1573 if (!is_file($filepath_original . $solution->getFilename())
1574 || copy($filepath_target . $solution->getFilename(), $filepath_target . $solution->getFilename())) {
1575 $this->log->root()->error(
'File for suggested solutions could not be cloned:');
1576 $this->log->root()->error(
"Question-Id: {$this->id}; Question-Title: {$this->title}; File: {$filepath_original}{$solution->getFilename()}");
1584 foreach ($this->getSuggestedSolutions() as $index => $solution) {
1585 $solution = $solution->withQuestionId($target_question_id);
1586 $update[] = $solution;
1588 $this->getSuggestedSolutionsRepo()->update($update);
1593 if (preg_match(
"/il_(\d+)_(\w+)_(\d+)/", $internal_link, $matches) ===
false) {
1594 return $internal_link;
1596 switch ($matches[2]) {
1613 if ($resolved_link ===
null) {
1614 return "il__{$matches[2]}_{$matches[3]}";
1616 return $internal_link;
1625 $result_pre = $this->db->queryF(
1626 "SELECT internal_link, suggested_solution_id FROM qpl_sol_sug WHERE question_fi = %s",
1630 if ($this->db->numRows($result_pre) < 1) {
1634 while ($row = $this->db->fetchAssoc($result_pre)) {
1635 $internal_link = $row[
"internal_link"];
1636 $resolved_link = $this->resolveInternalLink($internal_link);
1637 if ($internal_link === $resolved_link) {
1641 $this->db->manipulateF(
1642 "UPDATE qpl_sol_sug SET internal_link = %s WHERE suggested_solution_id = %s",
1644 [$resolved_link, $row[
"suggested_solution_id"]]
1648 if ($resolvedlinks === 0) {
1654 $result_post = $this->db->queryF(
1655 "SELECT internal_link FROM qpl_sol_sug WHERE question_fi = %s",
1659 if ($this->db->numRows($result_post) < 1) {
1663 while ($row = $this->db->fetchAssoc($result_post)) {
1664 if (preg_match(
"/il_(\d*?)_(\w+)_(\d+)/", $row[
"internal_link"], $matches)) {
1673 "lm" =>
"LearningModule",
1674 "pg" =>
"PageObject",
1675 "st" =>
"StructureObject",
1676 "git" =>
"GlossaryItem",
1677 "mob" =>
"MediaObject"
1680 if (preg_match(
"/il__(\w+)_(\d+)/", $target, $matches)) {
1681 $type = $matches[1];
1682 $target_id = $matches[2];
1683 switch ($linktypes[$matches[1]]) {
1685 $href =
"./ilias.php?baseClass=ilLMPresentationGUI&obj_type=" . $linktypes[$type]
1686 .
"&cmd=media&ref_id=" . $this->questionpool_request->getRefId()
1687 .
"&mob_id=" . $target_id;
1689 case "StructureObject":
1690 case "GlossaryItem":
1692 case "LearningModule":
1694 $href =
"./goto.php?target=" . $type .
"_" . $target_id;
1703 if ($this->getOriginalId() ===
null) {
1707 $original_parent_id = self::lookupParentObjId($this->getOriginalId());
1709 if ($original_parent_id ===
null) {
1713 $this->cloneSuggestedSolutions($this->
getId(), $this->getOriginalId());
1714 $original = clone $this;
1716 $original->setId($this->getOriginalId());
1717 $original->setOriginalId(
null);
1718 $original->setObjId($original_parent_id);
1720 $original->saveToDb();
1722 $original->deletePageOfQuestion($this->getOriginalId());
1723 $original->createPageObject();
1724 $original->clonePageOfQuestion($this->
getId());
1725 $original = $this->cloneQuestionTypeSpecificProperties($original);
1726 $this->cloneXHTMLMediaObjectsOfQuestion($original->getId());
1727 $this->afterSyncWithOriginal($this->getOriginalId(), $this->
getId(), $this->getObjId(), $original_parent_id);
1728 $this->afterSyncWithOriginal($this->getOriginalId(), $this->
getId(), $original_parent_id, $this->getObjId());
1739 $ilCtrl =
$DIC[
'ilCtrl'];
1742 $questionrepository = QuestionPoolDIC::dic()[
'question.general_properties.repository'];
1743 $question_type = $questionrepository->
getForQuestionId($question_id)?->getClassName() ??
'';
1744 if ($question_type ===
'') {
1745 throw new InvalidArgumentException(
'No question with ID ' . $question_id .
' exists');
1748 $question =
new $question_type();
1749 $question->loadFromDb($question_id);
1751 $feedbackObjectClassname = self::getFeedbackClassNameByQuestionType($question_type);
1752 $question->feedbackOBJ =
new $feedbackObjectClassname($question, $ilCtrl,
$ilDB,
$lng);
1759 return $this->points;
1764 $this->points = $points;
1769 return self::_getSolutionMaxPass($this->
getId(), $active_id);
1783 $result =
$ilDB->queryF(
1784 "SELECT MAX(pass) maxpass FROM tst_test_result WHERE active_fi = %s AND question_fi = %s",
1785 [
'integer',
'integer'],
1786 [$active_id, $question_id]
1788 if ($result->numRows() === 1) {
1789 $row =
$ilDB->fetchAssoc($result);
1790 return $row[
"maxpass"];
1804 return $this->ensureNonNegativePoints($reached_points);
1809 return $points > 0.0 ? $points : 0.0;
1814 $reached_points = $this->calculateReachedPointsFromPreviewSession($preview_session);
1816 return !($reached_points < $this->getMaximumPoints());
1833 if ($count_system == 1) {
1834 if (abs($this->getMaximumPoints() - $points) > 0.0000000001) {
1839 if ($score_cutting == 0) {
1850 if (preg_match(
"/.*\.(png|jpg|gif|jpeg)$/i", $plain_image_filename, $matches)) {
1851 $extension =
"." . $matches[1];
1855 $plain_image_filename = uniqid($plain_image_filename . microtime(
true),
true);
1858 return md5($plain_image_filename) . $extension;
1871 public static function _setReachedPoints(
1882 $question_properties_repository = QuestionPoolDIC::dic()[
'question.general_properties.repository'];
1884 if ($points > $maxpoints) {
1888 if ($pass ===
null) {
1893 $result =
$ilDB->queryF(
1894 "SELECT points FROM tst_test_result WHERE active_fi = %s AND question_fi = %s AND pass = %s",
1895 [
'integer',
'integer',
'integer'],
1896 [$active_id, $question_id, $pass]
1898 $manual = ($manualscoring) ? 1 : 0;
1899 $rowsnum = $result->numRows();
1901 $row =
$ilDB->fetchAssoc($result);
1902 $old_points = $row[
'points'];
1903 if ($old_points !== $points) {
1904 $affectedRows =
$ilDB->manipulateF(
1905 "UPDATE tst_test_result SET points = %s, manual = %s, tstamp = %s WHERE active_fi = %s AND question_fi = %s AND pass = %s",
1906 [
'float',
'integer',
'integer',
'integer',
'integer',
'integer'],
1907 [$points, $manual, time(), $active_id, $question_id, $pass]
1911 $next_id =
$ilDB->nextId(
'tst_test_result');
1912 $affectedRows =
$ilDB->manipulateF(
1913 "INSERT INTO tst_test_result (test_result_id, active_fi, question_fi, points, pass, manual, tstamp) VALUES (%s, %s, %s, %s, %s, %s, %s)",
1914 [
'integer',
'integer',
'integer',
'float',
'integer',
'integer',
'integer'],
1915 [$next_id, $active_id, $question_id, $points, $pass, $manual, time()]
1919 if (!self::isForcePassResultUpdateEnabled() && $old_points === $points && $rowsnum !== 0) {
1924 $test_result_repository = TestDIC::dic()[
'results.data.repository'];
1925 $test_result_repository->updateTestAttemptResult($active_id, $pass);
1931 return $this->question;
1936 return $this->purifyAndPrepareTextAreaOutput($this->question);
1941 $purified_content = $this->getHtmlQuestionContentPurifier()->purify($content);
1942 if ($this->isAdditionalContentEditingModePageObject()
1943 || !(
new ilSetting(
'advanced_editing'))->
get(
'advanced_editing_javascript_editor') ===
'tinymce') {
1944 $purified_content = nl2br($purified_content);
1955 $this->question = $question;
1960 $result = $this->db->queryF(
1961 "SELECT question_type_id FROM qpl_qst_type WHERE type_tag = %s",
1963 [$this->getQuestionType()]
1965 if ($this->db->numRows($result) == 1) {
1966 $row = $this->db->fetchAssoc($result);
1967 return (
int) $row[
"question_type_id"];
1976 $collected = $this->getQuestion();
1977 $collected .= $this->feedbackOBJ->getGenericFeedbackContent($this->
getId(),
false);
1978 $collected .= $this->feedbackOBJ->getGenericFeedbackContent($this->
getId(),
true);
1979 $collected .= $this->feedbackOBJ->getAllSpecificAnswerFeedbackContents($this->
getId());
1985 $combinedtext = $this->getRTETextWithMediaObjects();
1991 $result = $this->db->queryF(
1992 "SELECT question_id FROM qpl_questions WHERE original_id = %s",
1998 while ($row = $this->db->fetchAssoc($result)) {
1999 $ids[] = $row[
"question_id"];
2001 foreach ($ids as $question_id) {
2003 $result = $this->db->queryF(
2004 "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",
2008 while ($row = $this->db->fetchAssoc($result)) {
2012 $result = $this->db->queryF(
2013 "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",
2017 while ($row = $this->db->fetchAssoc($result)) {
2021 foreach ($instances as $key => $value) {
2035 $result = $this->db->queryF(
2036 "SELECT * FROM tst_active WHERE active_id = %s",
2040 if ($this->db->numRows($result)) {
2041 $row = $this->db->fetchAssoc($result);
2042 return [
"user_id" => $row[
"user_fi"],
"test_id" => $row[
"test_fi"]];
2050 return static::HAS_SPECIFIC_FEEDBACK;
2055 return str_replace(
'ass',
'ilAss', $questionType) .
'Feedback';
2060 public static function instantiateQuestionGUI(
int $question_id): ?
assQuestionGUI
2064 $ilCtrl =
$DIC[
'ilCtrl'];
2067 $ilUser =
$DIC[
'ilUser'];
2068 $ilLog =
$DIC[
'ilLog'];
2070 if ($question_id <= 0) {
2071 $ilLog->warning(
'Instantiate question called without question id. (instantiateQuestionGUI@assQuestion)');
2072 throw new InvalidArgumentException(
'Instantiate question called without question id. (instantiateQuestionGUI@assQuestion)');
2075 $questionrepository = QuestionPoolDIC::dic()[
'question.general_properties.repository'];
2076 $question_type = $questionrepository->
getForQuestionId($question_id)?->getClassName();
2078 if ($question_type ===
null) {
2082 $question_type_gui = $question_type .
'GUI';
2083 $question_gui =
new $question_type_gui($question_id);
2085 $feedback_object_classname = self::getFeedbackClassNameByQuestionType($question_type);
2086 $question = $question_gui->getObject();
2087 $question->feedbackOBJ =
new $feedback_object_classname($question, $ilCtrl,
$ilDB,
$lng);
2089 $assSettings =
new ilSetting(
'assessment');
2091 $processLockerFactory->setQuestionId($question_gui->getObject()->getId());
2092 $processLockerFactory->setUserId($ilUser->getId());
2093 $question->setProcessLocker($processLockerFactory->getLocker());
2094 $question_gui->setObject($question);
2096 return $question_gui;
2101 return $this->nr_of_tries;
2106 $this->nr_of_tries = $a_nr_of_tries;
2111 $this->export_image_path =
$path;
2119 if ($question_id < 1) {
2123 $result =
$ilDB->queryF(
2124 "SELECT question_fi FROM tst_test_question WHERE question_fi = %s AND test_fi = %s",
2125 [
'integer',
'integer'],
2126 [$question_id, $test_id]
2128 return $ilDB->numRows($result) == 1;
2133 return $this->getSelfAssessmentFormatter()->format($a_q);
2138 return new \ilAssSelfAssessmentQuestionFormatter();
2145 $this->prevent_rte_usage = $prevent_rte_usage;
2150 return $this->prevent_rte_usage;
2155 $this->lmMigrateQuestionTypeGenericContent($migrator);
2156 $this->lmMigrateQuestionTypeSpecificContent($migrator);
2159 $this->feedbackOBJ->migrateContentForLearningModule($migrator, $this->
getId());
2174 $this->selfassessmenteditingmode = $selfassessmenteditingmode;
2179 return $this->selfassessmenteditingmode;
2184 $this->defaultnroftries = $defaultnroftries;
2189 return $this->defaultnroftries;
2199 $query =
"SELECT obj_fi FROM qpl_questions WHERE question_id = %s";
2201 $res =
$ilDB->queryF($query, [
'integer'], [$question_id]);
2204 return $row[
'obj_fi'] ??
null;
2210 $assignmentList->setParentObjId($srcParentId);
2211 $assignmentList->setQuestionIdFilter($srcQuestionId);
2212 $assignmentList->loadFromDb();
2214 foreach ($assignmentList->getAssignmentsByQuestionId($srcQuestionId) as $assignment) {
2215 $assignment->setParentObjId($trgParentId);
2216 $assignment->setQuestionId($trgQuestionId);
2217 $assignment->saveToDb();
2220 $this->skillUsageService->addUsage(
2222 $assignment->getSkillBaseId(),
2223 $assignment->getSkillTrefId()
2228 public function syncSkillAssignments(
int $srcParentId,
int $srcQuestionId,
int $trgParentId,
int $trgQuestionId): void
2231 $assignmentList->setParentObjId($trgParentId);
2232 $assignmentList->setQuestionIdFilter($trgQuestionId);
2233 $assignmentList->loadFromDb();
2235 foreach ($assignmentList->getAssignmentsByQuestionId($trgQuestionId) as $assignment) {
2236 $assignment->deleteFromDb();
2239 if (!$assignment->isSkillUsed()) {
2240 $this->skillUsageService->removeUsage(
2241 $assignment->getParentObjId(),
2242 $assignment->getSkillBaseId(),
2243 $assignment->getSkillTrefId()
2248 $this->duplicateSkillAssignments($srcParentId, $srcQuestionId, $trgParentId, $trgQuestionId);
2254 return $numExistingSolutionRecords > 0;
2263 SELECT count(active_fi) cnt
2267 WHERE active_fi = %s
2268 AND question_fi = %s
2274 [
'integer',
'integer',
'integer'],
2275 [$activeId, $questionId, $pass]
2280 return (
int) $row[
'cnt'];
2285 return $this->additionalContentEditingMode;
2290 if (!in_array($additionalContentEditingMode, $this->getValidAdditionalContentEditingModes())) {
2294 $this->additionalContentEditingMode = $additionalContentEditingMode;
2304 if (in_array($additionalContentEditingMode, $this->getValidAdditionalContentEditingModes())) {
2314 self::ADDITIONAL_CONTENT_EDITING_MODE_RTE,
2315 self::ADDITIONAL_CONTENT_EDITING_MODE_IPE
2338 SELECT qpl_questions.*,
2339 {$this->getAdditionalTableName()}.*
2341 LEFT JOIN {$this->getAdditionalTableName()}
2342 ON {$this->getAdditionalTableName()}.question_fi = qpl_questions.question_id
2343 WHERE qpl_questions.question_id = %s
2349 $this->lastChange = $lastChange;
2354 return $this->lastChange;
2359 if ($this->getStep() !==
null) {
2363 WHERE active_fi = %s
2364 AND question_fi = %s
2370 return $this->db->queryF(
2372 [
'integer',
'integer',
'integer',
'integer',
'integer'],
2373 [$active_id, $this->
getId(), $pass, $this->getStep(), (
int) $authorized]
2380 WHERE active_fi = %s
2381 AND question_fi = %s
2386 return $this->db->queryF(
2388 [
'integer',
'integer',
'integer',
'integer'],
2389 [$active_id, $this->
getId(), $pass, (
int) $authorized]
2395 return $this->db->manipulateF(
2396 "DELETE FROM tst_solutions WHERE solution_id = %s",
2408 $result = $this->db->queryF(
2409 "SELECT * FROM tst_solutions WHERE solution_id = %s",
2414 if ($this->db->numRows($result) > 0) {
2415 return $this->db->fetchAssoc($result);
2423 $this->getProcessLocker()->executeUserSolutionUpdateLockOperation(
function () use ($active_id, $pass) {
2424 $this->removeCurrentSolution($active_id, $pass,
false);
2433 if ($this->getStep() !==
null) {
2435 DELETE FROM tst_solutions
2436 WHERE active_fi = %s
2437 AND question_fi = %s
2443 return $this->db->manipulateF(
2445 [
'integer',
'integer',
'integer',
'integer',
'integer'],
2446 [$active_id, $this->
getId(), $pass, $this->getStep(), (
int) $authorized]
2451 DELETE FROM tst_solutions
2452 WHERE active_fi = %s
2453 AND question_fi = %s
2458 return $this->db->manipulateF(
2460 [
'integer',
'integer',
'integer',
'integer'],
2461 [$active_id, $this->
getId(), $pass, (
int) $authorized]
2468 $next_id = $this->db->nextId(
"tst_solutions");
2471 "solution_id" => [
"integer", $next_id],
2472 "active_fi" => [
"integer", $active_id],
2473 "question_fi" => [
"integer", $this->
getId()],
2474 "value1" => [
"clob", $value1],
2475 "value2" => [
"clob", $value2],
2476 "pass" => [
"integer", $pass],
2477 "tstamp" => [
"integer", ((
int) $tstamp > 0) ? (
int) $tstamp : time()],
2478 'authorized' => [
'integer', (
int) $authorized]
2481 if ($this->getStep() !==
null) {
2482 $fieldData[
'step'] = [
"integer", $this->getStep()];
2485 return $this->db->insert(
"tst_solutions", $fieldData);
2492 "value1" => [
"clob", $value1],
2493 "value2" => [
"clob", $value2],
2494 "tstamp" => [
"integer", time()],
2495 'authorized' => [
'integer', (
int) $authorized]
2498 if ($this->getStep() !==
null) {
2499 $fieldData[
'step'] = [
"integer", $this->getStep()];
2502 return $this->db->update(
"tst_solutions", $fieldData, [
2503 'solution_id' => [
'integer', $solutionId]
2511 'authorized' => [
'integer', (
int) $authorized]
2515 $fieldData[
'tstamp'] = [
'integer', time()];
2519 'question_fi' => [
'integer', $this->
getId()],
2520 'active_fi' => [
'integer', $activeId],
2521 'pass' => [
'integer', $pass]
2524 if ($this->getStep() !==
null) {
2525 $whereData[
'step'] = [
"integer", $this->getStep()];
2528 return $this->db->update(
'tst_solutions', $fieldData, $whereData);
2533 public const KEY_VALUES_IMPLOSION_SEPARATOR =
':';
2547 foreach ($this->getSolutionValues($activeId, $passIndex,
false) as $solutionRec) {
2548 if ($solutionRec[
'value1'] ==
'' && $solutionRec[
'value2'] ==
'') {
2549 $this->removeSolutionRecordById($solutionRec[
'solution_id']);
2556 return !strlen($solutionRecord[
'value1']) && !strlen($solutionRecord[
'value2']);
2561 $types = [
"integer",
"integer",
"integer",
"integer"];
2562 $values = [$activeId, $this->
getId(), $passIndex, (
int) $authorized];
2563 $valuesCondition = [];
2565 foreach ($matchValues as $valueField => $value) {
2566 switch ($valueField) {
2569 $valuesCondition[] =
"{$valueField} = %s";
2579 $valuesCondition = implode(
' AND ', $valuesCondition);
2582 DELETE FROM tst_solutions
2583 WHERE active_fi = %s
2584 AND question_fi = %s
2587 AND $valuesCondition
2590 if ($this->getStep() !==
null) {
2591 $query .=
" AND step = %s ";
2592 $types[] =
'integer';
2593 $values[] = $this->getStep();
2596 $this->db->manipulateF($query, $types, $values);
2601 foreach ($this->getSolutionValues($activeId, $passIndex,
false) as $rec) {
2602 $this->saveCurrentSolution($activeId, $passIndex, $rec[
'value1'], $rec[
'value2'],
true, $rec[
'tstamp']);
2608 $intermediateSolution = $this->getSolutionValues($activeId, $passIndex,
false);
2610 if (!count($intermediateSolution)) {
2611 $this->updateCurrentSolutionsAuthorization($activeId, $passIndex,
false,
true);
2614 $this->duplicateIntermediateSolutionAuthorized($activeId, $passIndex);
2616 if ($considerDummyRecordCreation) {
2619 $this->saveCurrentSolution($activeId, $passIndex,
null,
null,
false);
2630 $this->step = $step;
2644 $time_array = explode(
':', $time);
2645 if (count($time_array) == 3) {
2646 $sec += (
int) $time_array[0] * 3600;
2647 $sec += (
int) $time_array[1] * 60;
2648 $sec += (
int) $time_array[2];
2655 return json_encode([]);
2661 $solutionAvailability = $this->lookupForExistingSolutions($active_id, $pass);
2662 return (
bool) $solutionAvailability[
'intermediate'];
2667 if ($pass ===
null) {
2670 $solutionAvailability = $this->lookupForExistingSolutions($active_id, $pass);
2671 return (
bool) $solutionAvailability[
'authorized'];
2676 $solutionAvailability = $this->lookupForExistingSolutions($active_id, $pass);
2677 return $solutionAvailability[
'authorized'] || $solutionAvailability[
'intermediate'];
2683 $result = $this->db->queryF(
2684 "SELECT MAX(step) max_step FROM tst_solutions WHERE active_fi = %s AND pass = %s AND question_fi = %s",
2685 [
"integer",
"integer",
"integer"],
2686 [$active_id, $pass, $this->
getId()]
2689 $row = $this->db->fetchAssoc($result);
2691 return (
int) $row[
'max_step'];
2702 'authorized' =>
false,
2703 'intermediate' => false
2707 SELECT authorized, COUNT(*) cnt
2709 WHERE active_fi = %s
2710 AND question_fi = %s
2714 if ($this->getStep() !==
null) {
2715 $query .=
" AND step = " . $this->db->quote((
int) $this->getStep(),
'integer') .
" ";
2722 $result = $this->db->queryF($query, [
'integer',
'integer',
'integer'], [$activeId, $this->
getId(), $pass]);
2724 while ($row = $this->db->fetchAssoc($result)) {
2725 if ($row[
'authorized']) {
2726 $return[
'authorized'] = $row[
'cnt'] > 0;
2728 $return[
'intermediate'] = $row[
'cnt'] > 0;
2746 $query =
"DELETE FROM tst_solutions WHERE question_fi = %s";
2747 $this->db->manipulateF($query, [
'integer'], [$this->
getId()]);
2753 DELETE FROM tst_solutions
2754 WHERE active_fi = %s
2755 AND question_fi = %s
2759 if ($this->getStep() !==
null) {
2760 $query .=
" AND step = " . $this->db->quote((
int) $this->getStep(),
'integer') .
" ";
2763 return $this->db->manipulateF(
2765 [
'integer',
'integer',
'integer'],
2766 [$activeId, $this->
getId(), $pass]
2772 $this->removeExistingSolutions($activeId, $pass);
2773 $this->removeResultRecord($activeId, $pass);
2774 $this->test_result_repository->updateTestAttemptResult($activeId, $pass, $this->getProcessLocker(), $this->getTestId());
2780 DELETE FROM tst_test_result
2781 WHERE active_fi = %s
2782 AND question_fi = %s
2786 if ($this->getStep() !==
null) {
2787 $query .=
" AND step = " . $this->db->quote((
int) $this->getStep(),
'integer') .
" ";
2790 return $this->db->manipulateF(
2792 [
'integer',
'integer',
'integer'],
2793 [$activeId, $this->
getId(), $pass]
2801 foreach ($indexedValues as $value1 => $value2) {
2802 $valuePairs[] = [
'value1' => $value1,
'value2' => $value2];
2810 $indexed_values = [];
2812 foreach ($value_pairs as $valuePair) {
2813 $indexed_values[$valuePair[
'value1']] = $valuePair[
'value2'];
2816 return $indexed_values;
2821 $this->db->manipulateF(
2822 "UPDATE qpl_questions SET tstamp = %s WHERE question_id = %s",
2823 [
'integer',
'integer'],
2824 [time(), $this->
getId()]
2830 if ($this->test_question_config ===
null) {
2831 $this->test_question_config = $this->buildTestPresentationConfig();
2834 return $this->test_question_config;
2844 return $this->suggestedsolution_repo;
2849 $question_id = $this->
getId();
2850 return $this->getSuggestedSolutionsRepo()->selectFor($question_id);
2869 return preg_replace(self::TRIM_PATTERN,
'', $value);
2874 return !is_null($this->original_id)
2875 && $this->questionrepository->questionExistsInPool($this->original_id)
2890 $this->current_user->
getId(),
2894 $this->answerToLog($additional_info, $active_id, $pass)
2906 $this->current_user->
getId(),
2909 $this->toLog($additional_info)
2923 $this->getSolutionValues($active_id, $pass)
2932 return $this->solutionValuesToText(
2933 $this->getSolutionValues($active_id, $pass)
2941 return $this->solutionValuesToText(
2942 $this->getSolutionValues($active_id, $pass)
$id
plugin.php for ilComponentBuildPluginInfoObjectiveTest::testAddPlugins
Customizing of pimple-DIC for ILIAS.
Provides fluid interface to LoggingServices.
getForQuestionId(int $question_id)
Repository for suggested solutions.
array $suggested_solutions
calculateReachedPoints(int $active_id, ?int $pass=null, bool $authorized_solution=true)
fixSvgToPng(string $imageFilenameContainingString)
isAddableAnswerOptionValue(int $qIndex, string $answerOptionValue)
const KEY_VALUES_IMPLOSION_SEPARATOR
loadFromDb(int $question_id)
string $export_image_path
getSolutionValues(int $active_id, ?int $pass=null, bool $authorized=true)
Loads solutions of a given user from the database an returns it.
const HAS_SPECIFIC_FEEDBACK
const ADDITIONAL_CONTENT_EDITING_MODE_RTE
deletePageOfQuestion(int $question_id)
getDescriptionForHTMLOutput()
persistPreviewState(ilAssQuestionPreviewSession $preview_session)
persists the preview state for current user and question
ilAssQuestionLifecycle $lifecycle
setOriginalId(?int $original_id)
static _getReachedPoints(int $active_id, int $question_id, int $pass)
ensureNonNegativePoints(float $points)
const ADDITIONAL_CONTENT_EDITING_MODE_IPE
copySuggestedSolutions(int $target_question_id)
setProcessLocker(ilAssQuestionProcessLocker $processLocker)
static lookupParentObjId(int $question_id)
calculateReachedPointsFromPreviewSession(ilAssQuestionPreviewSession $preview_session)
updateCurrentSolutionsAuthorization(int $activeId, int $pass, bool $authorized, bool $keepTime=false)
RequestDataCollector $questionpool_request
duplicateComments(int $parent_source_id, int $source_id, int $parent_target_id, int $target_id)
lmMigrateQuestionTypeGenericContent(ilAssSelfAssessmentMigrator $migrator)
getQuestionForHTMLOutput()
duplicateSuggestedSolutionFiles(int $parent_id, int $question_id)
Duplicates the files of a suggested solution if the question is duplicated.
answerToParticipantInteraction(AdditionalInformationGenerator $additional_info, int $test_ref_id, int $active_id, int $pass, string $source_ip, TestParticipantInteractionTypes $interaction_type)
onDuplicate(int $original_parent_id, int $original_question_id, int $duplicate_parent_id, int $duplicate_question_id)
ilGlobalPageTemplate $tpl
getHtmlQuestionContentPurifier()
fromXML(string $importdirectory, int $user_id, ilQTIItem $item, int $questionpool_id, ?int $tst_id, ?ilObject &$tst_object, int &$question_counter, array $import_mapping)
authorizedOrIntermediateSolutionExists(int $active_id, int $pass)
setAdditionalContentEditingMode(?string $additionalContentEditingMode)
deleteSolutionRecordByValues(int $activeId, int $passIndex, bool $authorized, array $matchValues)
solutionValuesToLog(AdditionalInformationGenerator $additional_info, array $solution_values)
MUST convert the given solution values into an array or a string that can be stored in the log.
TestResultRepository $test_result_repository
buildHashedImageFilename(string $plain_image_filename, bool $unique=false)
cleanupMediaObjectUsage()
authorizedSolutionExists(int $active_id, ?int $pass)
resolveSuggestedSolutionLinks()
fetchValuePairsFromIndexedValues(array $indexedValues)
lmMigrateQuestionTypeSpecificContent(ilAssSelfAssessmentMigrator $migrator)
isPreviewSolutionCorrect(ilAssQuestionPreviewSession $preview_session)
static isFileAvailable(string $file)
removeAllImageFiles(string $image_target_path)
saveToDb(?int $original_id=null)
getSuggestedSolutionPath()
ilTestQuestionConfig $test_question_config
onCopy(int $sourceParentId, int $sourceQuestionId, int $targetParentId, int $targetQuestionId)
saveCurrentSolution(int $active_id, int $pass, $value1, $value2, bool $authorized=true, $tstamp=0)
getSolutionForTextOutput(int $active_id, int $pass)
getAdditionalContentEditingMode()
removeResultRecord(int $activeId, int $pass)
deleteAdditionalTableData(int $question_id)
syncSkillAssignments(int $srcParentId, int $srcQuestionId, int $trgParentId, int $trgQuestionId)
buildTestPresentationConfig()
getInternalLinkHref(string $target)
clonePageOfQuestion(int $a_q_id)
setShuffle(?bool $shuffle=true)
GeneralQuestionPropertiesRepository $questionrepository
static $force_pass_results_update_enabled
ilTestQuestionConfig $testQuestionConfig
cloneXHTMLMediaObjectsOfQuestion(int $source_question_id)
solutionValuesToText(array $solution_values)
MUST convert the given solution values into text.
static _questionExistsInTest(int $question_id, int $test_id)
resetUsersAnswer(int $activeId, int $pass)
static extendedTrim(string $value)
Trim non-printable characters from the beginning and end of a string.
afterSyncWithOriginal(int $original_question_id, int $clone_question_id, int $original_parent_id, int $clone_parent_id)
migrateContentForLearningModule(ilAssSelfAssessmentMigrator $migrator)
savePreviewData(ilAssQuestionPreviewSession $preview_session)
updateCurrentSolution(int $solutionId, $value1, $value2, bool $authorized=true)
getHtmlUserSolutionPurifier()
deleteAnswers(int $question_id)
getSolutionRecordById(int $solutionId)
setQuestion(string $question="")
ilAssQuestionProcessLocker $processLocker
getSuggestedSolutionPathWeb()
isValidAdditionalContentEditingMode(string $additionalContentEditingMode)
isAnswered(int $active_id, int $pass)
SkillUsageService $skillUsageService
static _getSolutionMaxPass(int $question_id, int $active_id)
Returns the maximum pass a users question solution.
isNonEmptyItemListPostSubmission(string $post_submission_field_name)
removeAllExistingSolutions()
getImagePath($question_id=null, $object_id=null)
Returns the image path for web accessable images of a question.
getVariablesAsTextArray(int $active_id, int $pass)
getCurrentSolutionResultSet(int $active_id, int $pass, bool $authorized=true)
setPreventRteUsage(bool $prevent_rte_usage)
setSelfAssessmentEditingMode(bool $selfassessmenteditingmode)
calculateResultsFromSolution(int $active_id, int $pass)
Calculates the question results from a previously saved question solution.
setAuthor(string $author="")
getSuggestedSolution(int $subquestion_index=0)
getCorrectSolutionForTextOutput(int $active_id, int $pass)
getTestOutputSolutions(int $activeId, int $pass)
deleteSuggestedSolutions()
setThumbSize(int $a_size)
copyObject(int $target_parent_id, string $title='')
getRTETextWithMediaObjects()
removeExistingSolutions(int $activeId, int $pass)
getValidAdditionalContentEditingModes()
static implodeKeyValues(array $keyValues)
deleteTaxonomyAssignments()
static explodeKeyValues(string $keyValues)
toQuestionAdministrationInteraction(AdditionalInformationGenerator $additional_info, int $test_ref_id, TestQuestionAdministrationInteractionTypes $interaction_type)
setExportImagePath(string $path)
deleteDummySolutionRecord(int $activeId, int $passIndex)
static convertISO8601FormatH_i_s_ExtendedToSeconds(string $time)
getSelfAssessmentEditingMode()
getActiveUserData(int $active_id)
Returns the user id and the test id for a given active id.
static saveOriginalId(int $questionId, int $originalId)
getTitleFilenameCompliant()
static setForcePassResultUpdateEnabled(bool $force_pass_results_update_enabled)
static isForcePassResultUpdateEnabled()
QuestionFiles $question_files
static _getSuggestedSolutionOutput(int $question_id)
getTestPresentationConfig()
toLog(AdditionalInformationGenerator $additional_info)
MUST return an array of the question settings that can be stored in the log.
bool $selfassessmenteditingmode
persistWorkingState(int $active_id, $pass, bool $authorized=true)
persists the working state for current testactive and testpass
getSelfAssessmentFormatter()
getUserSolutionPreferingIntermediate(int $active_id, ?int $pass=null)
addAnswerOptionValue(int $qIndex, string $answerOptionValue, float $points)
cloneQuestionTypeSpecificProperties(self $target)
purifyAndPrepareTextAreaOutput(string $content)
duplicate(bool $for_test=true, string $title='', string $author='', int $owner=-1, $test_obj_id=null)
removeIntermediateSolution(int $active_id, int $pass)
setComment(string $comment="")
duplicateSkillAssignments(int $srcParentId, int $srcQuestionId, int $trgParentId, int $trgQuestionId)
getSuggestedSolutionsRepo()
SuggestedSolutionsDatabaseRepository $suggestedsolution_repo
removeSolutionRecordById(int $solutionId)
createNewQuestion(bool $a_create_page=true)
Creates a new question without an owner when a new question is created This assures that an ID is giv...
forceExistingIntermediateSolution(int $activeId, int $passIndex, bool $considerDummyRecordCreation)
ilAssQuestionFeedback $feedbackOBJ
static getFeedbackClassNameByQuestionType(string $questionType)
setLastChange(int $lastChange)
toXML(bool $a_include_header=true, bool $a_include_binary=true, bool $a_shuffle=false, bool $test_output=false, bool $force_image_references=false)
Returns a QTI xml representation of the question.
getSolutionMaxPass(int $active_id)
setNrOfTries(int $a_nr_of_tries)
setLifecycle(ilAssQuestionLifecycle $lifecycle)
getAdjustedReachedPoints(int $active_id, int $pass, bool $authorized_solution=true)
static getNumExistingSolutionRecords(int $activeId, int $pass, int $questionId)
fixUnavailableSkinImageSources(string $html)
resolveInternalLink(string $internal_link)
static instantiateQuestion(int $question_id)
isAdditionalContentEditingModePageObject()
setExternalId(?string $external_id)
hasWritableOriginalInQuestionPool()
getSuggestedSolutionOutput()
lookupForExistingSolutions(int $activeId, int $pass)
Lookup if an authorized or intermediate solution exists.
adjustReachedPointsByScoringOptions(float $points, int $active_id)
Adjust the given reached points by checks for all special scoring options in the test container.
static resetOriginalId(int $questionId)
setTitle(string $title="")
removeCurrentSolution(int $active_id, int $pass, bool $authorized=true)
isDummySolutionRecord(array $solutionRecord)
fetchIndexedValuesFromValuePairs(array $value_pairs)
saveQuestionDataToDb(?int $original_id=null)
setDefaultNrOfTries(int $defaultnroftries)
cloneSuggestedSolutions(int $source_question_id, int $target_question_id)
intermediateSolutionExists(int $active_id, int $pass)
answerToLog(AdditionalInformationGenerator $additional_info, int $active_id, int $pass)
duplicateIntermediateSolutionAuthorized(int $activeId, int $passIndex)
getImagePathWeb()
Returns the web image path for web accessable images of a question.
string $additionalContentEditingMode
getReachedPoints(int $active_id, int $pass)
setShuffler(Transformation $shuffler)
createNewOriginalFromThisDuplicate(int $target_parent_id, string $target_question_title='')
lookupMaxStep(int $active_id, int $pass)
cloneSuggestedSolutionFiles(int $source_question_id, int $target_question_id)
saveWorkingData(int $active_id, ?int $pass=null, bool $authorized=true)
string $questionActionCmd
static getDraftInstance()
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
getParticipantsSolution()
setParticipantsSolution($participantSolution)
static _updateObjectiveResult(int $a_user_id, int $a_active_id, int $a_question_id)
Base class for ILIAS Exception handling.
static makeDirParents(string $a_dir)
Create a new directory and all parent directories.
static getASCIIFilename(string $a_filename)
static delDir(string $a_dir, bool $a_clean_only=false)
removes a dir and all its content (subdirs and files) recursively
static removeTrailingPathSeparators(string $path)
static getInstanceByType(string $type)
static _deleteAllLinksOfSource(string $a_source_type, int $a_source_id, string $a_lang="-")
Delete all links of a given source.
static _getIdForImportId(string $a_type, string $a_target)
Get current id for an import id.
static _saveLink(string $a_source_type, int $a_source_id, string $a_target_type, int $a_target_id, int $a_target_inst=0, string $a_source_lang="-")
save internal link information
static _getIdForImportId(string $a_import_id)
get current object id for import id (static)
static _isWriteable($object_id, $user_id)
Returns true, if the question pool is writeable by a given user.
static _updateQuestionCount(int $object_id)
static getUsageOfObject(int $a_obj_id, bool $a_include_titles=false)
static _getCountSystem($active_id)
static _getScoreCutting(int $active_id)
Determines if the score of a question should be cut at 0 points or the score of the whole test.
static _lookupAuthor($obj_id)
Gets the authors name of the ilObjTest object.
static _getPass($active_id)
Retrieves the actual pass of a given user for a given test.
static _getUserIdFromActiveId(int $active_id)
Class ilObject Basic functions for all objects.
static _getAllReferences(int $id)
get all reference ids for object ID
static _lookupTitle(int $obj_id)
static _exists(string $a_parent_type, int $a_id, string $a_lang="", bool $a_no_cache=false)
Checks whether page exists.
static _cleanupMediaObjectUsage(string $a_text, string $a_usage_type, int $a_usage_id)
Synchronises appearances of media objects in $a_text with media object usage table.
static _replaceMediaObjectImageSrc(string $a_text, int $a_direction=0, string $nic='')
Replaces image source from mob image urls with the mob id or replaces mob id with the correct image s...
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
static getImagePath(string $image_name, string $module_path="", string $mode="output", bool $offline=false)
get image path (for images located in a template directory)
static setTokenMaxLifetimeInSeconds(int $token_max_lifetime_in_seconds)
static signFile(string $path_to_file)
return['delivery_method'=> 'php',]
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
migrateToLmContent($content)
Interface for html sanitizing functionality.
static http()
Fetches the global http state from ILIAS.
__construct(Container $dic, ilPlugin $plugin)
@inheritDoc
TestQuestionAdministrationInteractionTypes
TestParticipantInteractionTypes
Interface Observer \BackgroundTasks Contains several chained tasks and infos about them.
if(!file_exists('../ilias.ini.php'))