ILIAS  release_9 Revision v9.13-25-g2c18ec4c24f
class.assQuestionGUI.php
Go to the documentation of this file.
1 <?php
2 
22 
33 abstract class assQuestionGUI
34 {
35  public const FORM_MODE_EDIT = 'edit';
36  public const FORM_MODE_ADJUST = 'adjust';
37 
38  public const FORM_ENCODING_URLENCODE = 'application/x-www-form-urlencoded';
39  public const FORM_ENCODING_MULTIPART = 'multipart/form-data';
40 
41  protected const SUGGESTED_SOLUTION_COMMANDS_CANCEL = 'cancelSuggestedSolution';
42  protected const SUGGESTED_SOLUTION_COMMANDS_SAVE = 'saveSuggestedSolution';
43  protected const SUGGESTED_SOLUTION_COMMANDS_DEFAULT = 'suggestedsolution';
44 
45  public const CORRECTNESS_NOT_OK = 0;
46  public const CORRECTNESS_MOSTLY_OK = 1;
47  public const CORRECTNESS_OK = 2;
48 
49  protected const HAS_SPECIAL_QUESTION_COMMANDS = false;
50 
56  public const ALLOWED_PLAIN_TEXT_TAGS = "<em>, <strong>";
57 
59  private const RETURN_AFTER_EXISTING_SAVE = 0;
60 
61  public const SESSION_PREVIEW_DATA_BASE_INDEX = 'ilAssQuestionPreviewAnswers';
62  private $ui;
64  private ilHelpGUI $ilHelp;
66  private ilObjUser $ilUser;
67  private ilTabsGUI $ilTabs;
69 
70  private ilTree $tree;
71  private ilDBInterface $db;
72  protected ilLogger $logger;
74  protected \ILIAS\TestQuestionPool\QuestionInfoService $questioninfo;
75 
76  protected \ILIAS\Notes\GUIService $notes_gui;
77 
78  protected ilCtrl $ctrl;
79  private array $new_id_listeners = array();
80  private int $new_id_listener_cnt = 0;
81 
83  private $previewSession;
84 
87  public ilLanguage $lng;
88  protected Refinery $refinery;
89 
90  public $error;
91  public string $errormessage;
92 
94  public int $sequence_no;
95 
97  public int $question_count;
98 
99  private $taxonomyIds = [];
100 
101  private $targetGuiClass = null;
102 
103  private string $questionActionCmd = 'handleQuestionAction';
104 
106 
108 
109  public const PRESENTATION_CONTEXT_TEST = 'pContextTest';
110  public const PRESENTATION_CONTEXT_RESULTS = 'pContextResults';
111 
112  private ?string $presentationContext = null;
113 
114  public const RENDER_PURPOSE_PLAYBACK = 'renderPurposePlayback';
115  public const RENDER_PURPOSE_DEMOPLAY = 'renderPurposeDemoplay';
116  public const RENDER_PURPOSE_PREVIEW = 'renderPurposePreview';
117  public const RENDER_PURPOSE_PRINT_PDF = 'renderPurposePrintPdf';
118  public const RENDER_PURPOSE_INPUT_VALUE = 'renderPurposeInputValue';
119 
120  private string $renderPurpose = self::RENDER_PURPOSE_PLAYBACK;
121 
122  public const EDIT_CONTEXT_AUTHORING = 'authoring';
123  public const EDIT_CONTEXT_ADJUSTMENT = 'adjustment';
124 
125  private string $editContext = self::EDIT_CONTEXT_AUTHORING;
126 
127  private bool $previousSolutionPrefilled = false;
128 
130  protected \ILIAS\TestQuestionPool\InternalRequestService $request;
131  protected bool $parent_type_is_lm = false;
132 
133  public function __construct()
134  {
136  global $DIC;
137  $this->lng = $DIC['lng'];
138  $this->tpl = $DIC['tpl'];
139  $this->ctrl = $DIC['ilCtrl'];
140  $this->ui = $DIC->ui();
141  $this->ilObjDataCache = $DIC['ilObjDataCache'];
142  $this->access = $DIC->access();
143  $this->ilHelp = $DIC['ilHelp'];
144  $this->ilUser = $DIC['ilUser'];
145  $this->ilTabs = $DIC['ilTabs'];
146  $this->rbacsystem = $DIC['rbacsystem'];
147  $this->request = $DIC->testQuestionPool()->internal()->request();
148  $this->tree = $DIC['tree'];
149  $this->db = $DIC->database();
150  $this->logger = $DIC['ilLog'];
151  $this->questioninfo = $DIC->testQuestionPool()->questionInfo();
152  $this->component_repository = $DIC['component.repository'];
153  $this->refinery = $DIC['refinery'];
154  $this->ctrl->saveParameter($this, "q_id");
155  $this->ctrl->saveParameter($this, "prev_qid");
156  $this->ctrl->saveParameter($this, "calling_test");
157  $this->ctrl->saveParameter($this, "consumer_context");
158  $this->ctrl->saveParameterByClass('ilAssQuestionPageGUI', 'test_express_mode');
159  $this->ctrl->saveParameterByClass('ilAssQuestionPageGUI', 'consumer_context');
160  $this->ctrl->saveParameterByClass('ilobjquestionpoolgui', 'test_express_mode');
161  $this->ctrl->saveParameterByClass('ilobjquestionpoolgui', 'consumer_context');
162 
163  $this->errormessage = $this->lng->txt("fill_out_all_required_fields");
164  $this->notes_gui = $DIC->notes()->gui();
165  }
166 
167  public function hasInlineFeedback(): bool
168  {
169  return false;
170  }
171 
172  public function addHeaderAction(): void
173  {
174  }
175 
176  public function redrawHeaderAction(): void
177  {
178  echo $this->getHeaderAction() . $this->ui->mainTemplate()->getOnLoadCodeForAsynch();
179  exit;
180  }
181 
182  public function getHeaderAction(): string
183  {
184  $parentObjType = $this->ilObjDataCache->lookupType($this->object->getObjId());
185 
186  $dispatcher = new ilCommonActionDispatcherGUI(
188  $this->access,
189  $parentObjType,
190  $this->request->getRefId(),
191  $this->object->getObjId()
192  );
193 
194  $dispatcher->setSubObject("quest", $this->object->getId());
195 
196  $ha = $dispatcher->initHeaderAction();
197  $ha->enableComments(true, false);
198 
199  return $ha->getHeaderAction($this->ui->mainTemplate());
200  }
201 
202  public function geCommentsPanelHTML(): string
203  {
204  $comment_gui = new ilCommentGUI($this->object->getObjId(), $this->object->getId(), 'quest');
205  return $comment_gui->getListHTML();
206  }
207 
208  public function executeCommand()
209  {
210  $this->ilHelp->setScreenIdComponent('qpl');
211 
212  $next_class = $this->ctrl->getNextClass($this);
213 
214  switch ($next_class) {
215  case 'ilformpropertydispatchgui':
216  $form = $this->buildEditForm();
217  $form_prop_dispatch = new ilFormPropertyDispatchGUI();
218  $form_prop_dispatch->setItem($form->getItemByPostVar(ilUtil::stripSlashes($this->request->raw('postvar'))));
219  $this->ctrl->forwardCommand($form_prop_dispatch);
220  break;
221  default:
222  $cmd = $this->ctrl->getCmd('editQuestion');
223  switch ($cmd) {
224  case self::SUGGESTED_SOLUTION_COMMANDS_CANCEL:
225  case self::SUGGESTED_SOLUTION_COMMANDS_SAVE:
226  case self::SUGGESTED_SOLUTION_COMMANDS_DEFAULT:
227  $this->suggestedsolution();
228  break;
229  case 'saveSuggestedSolutionType':
230  case 'saveContentsSuggestedSolution':
231  case 'deleteSuggestedSolution':
232  case 'linkChilds':
233  case 'cancelExplorer':
234  case 'outSolutionExplorer':
235  case 'addST':
236  case 'addPG':
237  case 'addGIT':
238  $this->$cmd();
239  break;
240  case 'save':
241  case 'saveReturn':
242  case 'editQuestion':
243  $this->addSaveOnEnterOnLoadCode();
244  $this->$cmd();
245  break;
246  default:
247  if (method_exists($this, $cmd)) {
248  $this->$cmd();
249  return;
250  }
251  if ($this->hasSpecialQuestionCommands() === true) {
252  $this->callSpecialQuestionCommands($cmd);
253  }
254  }
255  }
256  }
257 
258  protected function hasSpecialQuestionCommands(): bool
259  {
260  return static::HAS_SPECIAL_QUESTION_COMMANDS;
261  }
262 
264  public function getType(): string
265  {
266  return $this->getQuestionType();
267  }
268 
269  public function getPresentationContext(): ?string
270  {
272  }
273 
274  public function setPresentationContext(string $presentationContext): void
275  {
276  $this->presentationContext = $presentationContext;
277  }
278 
279  public function isTestPresentationContext(): bool
280  {
281  return $this->getPresentationContext() == self::PRESENTATION_CONTEXT_TEST;
282  }
283 
284  // hey: previousPassSolutions - setter/getter for Previous Solution Prefilled flag
285  public function isPreviousSolutionPrefilled(): bool
286  {
288  }
289 
290  public function setPreviousSolutionPrefilled(bool $previousSolutionPrefilled): void
291  {
292  $this->previousSolutionPrefilled = $previousSolutionPrefilled;
293  }
294  // hey.
295 
296  public function getRenderPurpose(): string
297  {
298  return $this->renderPurpose;
299  }
300 
301  public function setRenderPurpose(string $renderPurpose): void
302  {
303  $this->renderPurpose = $renderPurpose;
304  }
305 
306  public function isRenderPurposePrintPdf(): bool
307  {
308  return $this->getRenderPurpose() == self::RENDER_PURPOSE_PRINT_PDF;
309  }
310 
311  public function isRenderPurposePreview(): bool
312  {
313  return $this->getRenderPurpose() == self::RENDER_PURPOSE_PREVIEW;
314  }
315 
316  public function isRenderPurposeInputValue(): bool
317  {
318  return $this->getRenderPurpose() == self::RENDER_PURPOSE_INPUT_VALUE;
319  }
320 
321  public function isRenderPurposePlayback(): bool
322  {
323  return $this->getRenderPurpose() == self::RENDER_PURPOSE_PLAYBACK;
324  }
325 
326  public function isRenderPurposeDemoplay(): bool
327  {
328  return $this->getRenderPurpose() == self::RENDER_PURPOSE_DEMOPLAY;
329  }
330 
331  public function renderPurposeSupportsFormHtml(): bool
332  {
333  if ($this->isRenderPurposePrintPdf()) {
334  return false;
335  }
336 
337  if ($this->isRenderPurposeInputValue()) {
338  return false;
339  }
340 
341  return true;
342  }
343 
344  public function getEditContext(): string
345  {
346  return $this->editContext;
347  }
348 
349  public function setEditContext(string $editContext): void
350  {
351  $this->editContext = $editContext;
352  }
353 
354  public function isAuthoringEditContext(): bool
355  {
356  return $this->getEditContext() == self::EDIT_CONTEXT_AUTHORING;
357  }
358 
359  public function isAdjustmentEditContext(): bool
360  {
361  return $this->getEditContext() == self::EDIT_CONTEXT_ADJUSTMENT;
362  }
363 
364  public function setAdjustmentEditContext(): void
365  {
366  $this->setEditContext(self::EDIT_CONTEXT_ADJUSTMENT);
367  }
368 
370  {
371  return $this->navigationGUI;
372  }
373 
374  public function setNavigationGUI(?ilTestQuestionNavigationGUI $navigationGUI): void
375  {
376  $this->navigationGUI = $navigationGUI;
377  }
378 
379  public function setTaxonomyIds(array $taxonomyIds): void
380  {
381  $this->taxonomyIds = $taxonomyIds;
382  }
383 
384  public function getTaxonomyIds(): array
385  {
386  return $this->taxonomyIds;
387  }
388 
389  public function setTargetGui($linkTargetGui): void
390  {
391  $this->setTargetGuiClass(get_class($linkTargetGui));
392  }
393 
394  public function setTargetGuiClass($targetGuiClass): void
395  {
396  $this->targetGuiClass = $targetGuiClass;
397  }
398 
399  public function getTargetGuiClass(): ?string
400  {
401  return $this->targetGuiClass;
402  }
403 
404  public function setQuestionHeaderBlockBuilder(\ilQuestionHeaderBlockBuilder $questionHeaderBlockBuilder): void
405  {
406  $this->questionHeaderBlockBuilder = $questionHeaderBlockBuilder;
407  }
408 
409  // fau: testNav - get the question header block bulder (for tweaking)
411  {
413  }
414  // fau.
415 
416  public function setQuestionActionCmd(string $questionActionCmd): void
417  {
418  $this->questionActionCmd = $questionActionCmd;
419 
420  if (is_object($this->object)) {
421  $this->object->questionActionCmd = $questionActionCmd;
422  }
423  }
424 
425  public function getQuestionActionCmd(): string
426  {
428  }
429 
434  protected function writePostData(bool $always = false): int
435  {
436  return 0;
437  }
438 
439  public function assessment(): void
440  {
441  $stats_table = new ilQuestionCumulatedStatisticsTableGUI($this, 'assessment', '', $this->object, $this->questioninfo);
442  $usage_table = new ilQuestionUsagesTableGUI($this, 'assessment', '', $this->object);
443 
444  $this->tpl->setContent(implode('<br />', array(
445  $stats_table->getHTML(),
446  $usage_table->getHTML()
447  )));
448  }
449 
453  public static function _getQuestionGUI(string $question_type = '', int $question_id = -1): ?assQuestionGUI
454  {
455  global $DIC;
456  $ilCtrl = $DIC['ilCtrl'];
457  $ilDB = $DIC['ilDB'];
458  $lng = $DIC['lng'];
459 
460  if (($question_type === '') && ($question_id > 0)) {
461  $question_type = $DIC->testQuestionPool()->questionInfo()->getQuestionType($question_id);
462  }
463 
464  if ($question_type === '') {
465  return null;
466  }
467 
468  $question_type_gui = $question_type . 'GUI';
469  $question = new $question_type_gui();
470 
471  $feedbackObjectClassname = assQuestion::getFeedbackClassNameByQuestionType($question_type);
472  $question->object->feedbackOBJ = new $feedbackObjectClassname($question->object, $ilCtrl, $ilDB, $lng);
473 
474  if ($question_id > 0) {
475  $question->object->loadFromDb($question_id);
476  }
477 
478  return $question;
479  }
480 
484  public static function _getGUIClassNameForId($a_q_id): string
485  {
486  global $DIC;
487  $q_type = $DIC->testQuestionPool()->questionInfo()->getQuestionType($a_q_id);
488  $class_name = assQuestionGUI::_getClassNameForQType($q_type);
489  return $class_name;
490  }
491 
495  public static function _getClassNameForQType($q_type): string
496  {
497  return $q_type . "GUI";
498  }
499 
501  {
502  foreach ($this->getPresentationJavascripts() as $jsFile) {
503  $tpl->addJavaScript($jsFile);
504  }
505  }
506 
507  public function getPresentationJavascripts(): array
508  {
509  return array();
510  }
511 
512  public function getQuestionTemplate(): void
513  {
514  // @todo Björn: Maybe this has to be changed for PHP 7/ILIAS 5.2.x (ilObjTestGUI::executeCommand, switch -> default case -> $this->prepareOutput(); already added a template to the CONTENT variable wrapped in a block named content)
515  if (!$this->tpl->blockExists('content')) {
516  $this->tpl->addBlockFile("CONTENT", "content", "tpl.il_as_qpl_content.html", "Modules/TestQuestionPool");
517  }
518  // @todo Björn: Maybe this has to be changed for PHP 7/ILIAS 5.2.x (ilObjTestGUI::executeCommand, switch -> default case -> $this->prepareOutput(); already added a template to the STATUSLINE variable wrapped in a block named statusline)
519  if (!$this->tpl->blockExists('statusline')) {
520  $this->tpl->addBlockFile("STATUSLINE", "statusline", "tpl.statusline.html");
521  }
522  // @todo Björn: Maybe this has to be changed for PHP 7/ILIAS 5.2.x because ass[XYZ]QuestionGUI::editQuestion is called multiple times
523  if (!$this->tpl->blockExists('adm_content')) {
524  $this->tpl->addBlockFile("ADM_CONTENT", "adm_content", "tpl.il_as_question.html", "Modules/TestQuestionPool");
525  }
526  }
527 
528  protected function renderEditForm(ilPropertyFormGUI $form): void
529  {
530  $this->getQuestionTemplate();
531  $this->tpl->setVariable("QUESTION_DATA", $form->getHTML());
532  }
533 
537  public function getILIASPage(string $html = ""): string
538  {
539  $page_gui = new ilAssQuestionPageGUI($this->object->getId());
540  $page_gui->setQuestionHTML(
541  [$this->object->getId() => $html]
542  );
543  $presentation = $page_gui->presentation();
544  $presentation = preg_replace("/src=\"\\.\\//ims", "src=\"" . ILIAS_HTTP_PATH . "/", $presentation);
545  return $presentation;
546  }
547 
548  public function outQuestionPage($a_temp_var, $a_postponed = false, $active_id = "", $html = "", $inlineFeedbackEnabled = false): string
549  {
550  if ($this->object->getTestPresentationConfig()->isSolutionInitiallyPrefilled()) {
551  // hey
552  $this->tpl->setOnScreenMessage('info', $this->getPreviousSolutionProvidedMessage());
554  } elseif ($this->object->getTestPresentationConfig()->isUnchangedAnswerPossible()) {
555  $html .= $this->getUseUnchangedAnswerCheckboxHtml();
556  }
557 
558  $this->lng->loadLanguageModule("content");
559 
560  $page_gui = new ilAssQuestionPageGUI($this->object->getId());
561  $page_gui->setOutputMode("presentation");
562  $page_gui->setTemplateTargetVar($a_temp_var);
563 
564  if ($this->getNavigationGUI()) {
565  $html .= $this->getNavigationGUI()->getHTML();
566  $page_gui->setQuestionActionsHTML($this->getNavigationGUI()->getActionsHTML());
567  }
568 
569  if (strlen($html)) {
570  $page_gui->setQuestionHTML(array($this->object->getId() => $html));
571  }
572 
573  $page_gui->setPresentationTitle($this->questionHeaderBlockBuilder->getPresentationTitle());
574  $page_gui->setQuestionInfoHTML($this->questionHeaderBlockBuilder->getQuestionInfoHTML());
575 
576  return $page_gui->presentation();
577  }
578 
579  protected function getUseUnchangedAnswerCheckboxHtml(): string
580  {
581  $tpl = new ilTemplate('tpl.tst_question_additional_behaviour_checkbox.html', true, true, 'Modules/TestQuestionPool');
582  $tpl->setVariable('TXT_FORCE_FORM_DIFF_LABEL', $this->object->getTestPresentationConfig()->getUseUnchangedAnswerLabel());
583  return $tpl->get();
584  }
585 
586  protected function getPreviousSolutionProvidedMessage(): string
587  {
588  return $this->lng->txt('use_previous_solution_advice');
589  }
590 
591  protected function getPreviousSolutionConfirmationCheckboxHtml(): string
592  {
593  $tpl = new ilTemplate('tpl.tst_question_additional_behaviour_checkbox.html', true, true, 'Modules/TestQuestionPool');
594  $tpl->setVariable('TXT_FORCE_FORM_DIFF_LABEL', $this->lng->txt('use_previous_solution'));
595  return $tpl->get();
596  }
597 
598  public function cancel(): void
599  {
600  if ($this->request->raw("calling_test")) {
601  $_GET["ref_id"] = $this->request->raw("calling_test");
602  ilUtil::redirect("ilias.php?baseClass=ilObjTestGUI&cmd=questions&ref_id=" . $this->request->raw("calling_test"));
603  } elseif ($this->request->raw("test_ref_id")) {
604  $_GET["ref_id"] = $this->request->raw("test_ref_id");
605  ilUtil::redirect("ilias.php?baseClass=ilObjTestGUI&cmd=questions&ref_id=" . $this->request->raw("test_ref_id"));
606  } else {
607  if ($this->request->raw("q_id") > 0) {
608  $this->ctrl->setParameterByClass("ilAssQuestionPageGUI", "q_id", $this->request->getQuestionId());
609  $this->ctrl->redirectByClass("ilAssQuestionPageGUI", "edit");
610  } else {
611  $this->ctrl->redirectByClass("ilobjquestionpoolgui", "questions");
612  }
613  }
614  }
615 
616  public function originalSyncForm(string $return_to = "", string $return_to_feedback = ''): void
617  {
618  if (strlen($return_to)) {
619  $this->ctrl->setParameter($this, "return_to", $return_to);
620  } elseif ($this->request->raw('return_to')) {
621  $this->ctrl->setParameter($this, "return_to", $this->request->raw('return_to'));
622  }
623  if (strlen($return_to_feedback)) {
624  $this->ctrl->setParameter($this, 'return_to_fb', 'true');
625  }
626 
627  $this->ctrl->saveParameter($this, 'test_express_mode');
628 
629  $template = new ilTemplate("tpl.il_as_qpl_sync_original.html", true, true, "Modules/TestQuestionPool");
630  $template->setVariable("BUTTON_YES", $this->lng->txt("yes"));
631  $template->setVariable("BUTTON_NO", $this->lng->txt("no"));
632  $template->setVariable("FORM_ACTION", $this->ctrl->getFormAction($this));
633  $template->setVariable("TEXT_SYNC", $this->lng->txt("confirm_sync_questions"));
634  $this->tpl->setVariable("ADM_CONTENT", $template->get());
635  }
636 
637  public function sync(): void
638  {
639  $original_id = $this->object->getOriginalId();
640  if ($original_id !== null) {
641  $this->object->syncWithOriginal();
642  $this->tpl->setOnScreenMessage('success', $this->lng->txt("msg_obj_modified"), true);
643  }
644  if ($this->request->raw("return_to") !== null) {
645  $this->ctrl->redirect($this, $this->request->raw("return_to"));
646  }
647  if ($this->request->raw("return_to_fb") !== null) {
648  $this->ctrl->redirectByClass(ilAssQuestionFeedbackEditingGUI::class, 'showFeedbackForm');
649  }
650 
651  if ($this->request->raw('test_express_mode')) {
652  $this->ctrl->redirectToURL(ilTestExpressPage::getReturnToPageLink($this->object->getId()));
653  }
654 
655  $this->ctrl->redirectByClass(ilAssQuestionPreviewGUI::class, ilAssQuestionPreviewGUI::CMD_SHOW);
656  }
657 
658  public function cancelSync(): void
659  {
660  $this->tpl->setOnScreenMessage('success', $this->lng->txt("msg_obj_modified"), true);
661 
662  if ($this->request->raw("return_to") !== '' && $this->request->raw("return_to") !== null) {
663  $this->ctrl->redirect($this, $this->request->raw("return_to"));
664  }
665  if ($this->request->raw('return_to_fb') !== '' && $this->request->raw('return_to_fb') !== null) {
666  $this->ctrl->redirectByClass(ilAssQuestionFeedbackEditingGUI::class, 'showFeedbackForm');
667  }
668  if ($this->request->raw('test_express_mode')) {
669  $this->ctrl->redirectToURL(ilTestExpressPage::getReturnToPageLink($this->object->getId()));
670  }
671  $this->ctrl->redirectByClass(ilAssQuestionPreviewGUI::class, ilAssQuestionPreviewGUI::CMD_SHOW);
672  }
673 
674  public function saveEdit(): void
675  {
676  $ilUser = $this->ilUser;
677  $result = $this->writePostData();
678  if ($result == 0) {
679  $ilUser->setPref("tst_lastquestiontype", $this->object->getQuestionType());
680  $ilUser->writePref("tst_lastquestiontype", $this->object->getQuestionType());
681  $this->object->saveToDb();
682  $originalexists = $this->questioninfo->questionExists($this->object->getOriginalId());
683 
684  if ($this->request->raw("calling_test") && $originalexists && assQuestion::_isWriteable($this->object->getOriginalId(), $ilUser->getId())) {
685  $this->ctrl->redirect($this, "originalSyncForm");
686  } elseif ($this->request->raw("calling_test")) {
687  $_GET["ref_id"] = $this->request->raw("calling_test");
688  ilUtil::redirect("ilias.php?baseClass=ilObjTestGUI&cmd=questions&ref_id=" . $this->request->raw("calling_test"));
689  return;
690  } elseif ($this->request->raw("test_ref_id")) {
691  // TODO: Courier Antipattern!
692  $_GET["ref_id"] = $this->request->raw("test_ref_id");
693  $test = new ilObjTest($this->request->raw("test_ref_id"), true);
694 
695  $testQuestionSetConfigFactory = new ilTestQuestionSetConfigFactory(
696  $this->tree,
697  $this->db,
698  $this->lng,
699  $this->logger,
700  $this->component_repository,
701  $test,
702  $this->questioninfo
703  );
704 
705  $test->insertQuestion($testQuestionSetConfigFactory->getQuestionSetConfig(), $this->object->getId());
706 
707  ilUtil::redirect("ilias.php?baseClass=ilObjTestGUI&cmd=questions&ref_id=" . $this->request->raw("test_ref_id"));
708  } else {
709  $this->ctrl->setParameter($this, "q_id", $this->object->getId());
710  $this->editQuestion();
711  $this->tpl->setOnScreenMessage('success', $this->lng->txt("msg_obj_modified"), false);
712  $this->ctrl->setParameterByClass(ilAssQuestionPageGUI::class, "q_id", $this->object->getId());
713  $this->ctrl->redirectByClass(ilAssQuestionPageGUI::class, "edit");
714  }
715  }
716  }
717 
718  public function save(): void
719  {
720  $this->ilTabs->setTabActive('edit_question');
721  $result = $this->writePostData();
722 
723  if ($result !== 0) {
724  return;
725  }
726 
727  $old_id = $this->request->int('q_id');
728 
729  $this->ilUser->setPref("tst_lastquestiontype", $this->object->getQuestionType());
730  $this->ilUser->writePref("tst_lastquestiontype", $this->object->getQuestionType());
731  $this->object->saveToDb();
732 
733  if ($this->object->getId() !== $old_id) {
734  $this->callNewIdListeners($this->object->getId());
735  }
736 
737  if ($this->request->int('calling_test') !== 0) {
738  if (($q_id = $this->saveQuestionToTest()) === self::RETURN_AFTER_EXISTING_WITH_ORIGINAL_SAVE) {
739  $this->tpl->setOnScreenMessage('success', $this->lng->txt("msg_obj_modified"), true);
740  $this->ctrl->setParameter($this, 'return_to', 'editQuestion');
741  $this->ctrl->redirect($this, "originalSyncForm");
742  }
743 
744  $this->ctrl->setParameter(
745  $this,
746  'q_id',
747  $q_id === self::RETURN_AFTER_EXISTING_SAVE ? $this->object->getId() : $q_id
748  );
749  $this->ctrl->setParameter($this, 'ref_id', $this->request->raw('calling_test'));
750  $this->ctrl->setParameter($this, 'calling_test', $this->request->raw('calling_test'));
751  }
752 
753  $this->tpl->setOnScreenMessage('success', $this->lng->txt("msg_obj_modified"), true);
754  $this->ctrl->redirect($this, 'editQuestion');
755  }
756 
757  public function saveReturn(): void
758  {
759  $this->ilTabs->setTabActive('edit_question');
760  $result = $this->writePostData();
761  if ($result !== 0) {
762  return;
763  }
764 
765  $old_id = $this->request->getQuestionId();
766 
767  $this->ilUser->setPref("tst_lastquestiontype", $this->object->getQuestionType());
768  $this->ilUser->writePref("tst_lastquestiontype", $this->object->getQuestionType());
769  $this->object->saveToDb();
770 
771  if ($this->object->getId() !== $old_id) {
772  $this->callNewIdListeners($this->object->getId());
773  }
774 
775  if ($this->request->int('calling_test') !== 0
776  && $this->saveQuestionToTest() === self::RETURN_AFTER_EXISTING_WITH_ORIGINAL_SAVE) {
777  $this->tpl->setOnScreenMessage('success', $this->lng->txt("msg_obj_modified"), true);
778  $this->ctrl->setParameter($this, 'test_express_mode', $this->request->raw('test_express_mode'));
779  $this->ctrl->redirect($this, "originalSyncForm");
780  }
781 
782  $this->tpl->setOnScreenMessage('success', $this->lng->txt("msg_obj_modified"), true);
783  $this->ctrl->redirectByClass('ilAssQuestionPreviewGUI', ilAssQuestionPreviewGUI::CMD_SHOW);
784  }
785 
786  private function saveQuestionToTest(): int
787  {
788  $originalexists = !is_null($this->object->getOriginalId())
789  && $this->questioninfo->questionExistsInPool($this->object->getOriginalId());
790 
791  if ($originalexists
792  && assQuestion::_isWriteable($this->object->getOriginalId(), $this->ilUser->getId())) {
793  return self::RETURN_AFTER_EXISTING_WITH_ORIGINAL_SAVE;
794  }
795 
796  $test = new ilObjTest($this->request->raw("calling_test"), true);
797  if (assQuestion::_questionExistsInTest($this->object->getId(), $test->getTestId())) {
798  return self::RETURN_AFTER_EXISTING_SAVE;
799  }
800 
801  $testQuestionSetConfigFactory = new ilTestQuestionSetConfigFactory(
802  $this->tree,
803  $this->db,
804  $this->lng,
805  $this->logger,
806  $this->component_repository,
807  $test,
808  $this->questioninfo
809  );
810 
811  $new_q_id = $this->object->getId();
812  if ($test->getRefId() !== $this->request->int('ref_id')) {
813  $new_q_id = $this->object->duplicate(true, $this->object->getTitle(), $this->object->getAuthor(), $this->object->getOwner(), $test->getId());
814  }
815 
816  $test->insertQuestion(
817  $testQuestionSetConfigFactory->getQuestionSetConfig(),
818  $new_q_id,
819  true
820  );
821 
822  if ($this->request->isset('prev_qid')) {
823  $test->moveQuestionAfter($new_q_id, $this->request->raw('prev_qid'));
824  }
825 
826  $this->ctrl->setParameter($this, 'calling_test', $this->request->raw("calling_test"));
827  return $new_q_id;
828  }
829 
830  public function apply(): void
831  {
832  $this->writePostData();
833  $this->object->saveToDb();
834  $this->ctrl->setParameter($this, "q_id", $this->object->getId());
835  $this->editQuestion();
836  }
837 
841  public function getContextPath($cont_obj, int $a_endnode_id, int $a_startnode_id = 1): string
842  {
843  $path = "";
844 
845  $tmpPath = $cont_obj->getLMTree()->getPathFull($a_endnode_id, $a_startnode_id);
846 
847  // count -1, to exclude the learning module itself
848  for ($i = 1; $i < (count($tmpPath) - 1); $i++) {
849  if ($path != "") {
850  $path .= " > ";
851  }
852 
853  $path .= $tmpPath[$i]["title"];
854  }
855 
856  return $path;
857  }
858 
859  public function setSequenceNumber(int $nr): void
860  {
861  $this->sequence_no = $nr;
862  }
863 
864  public function getSequenceNumber(): int
865  {
866  return $this->sequence_no;
867  }
868 
869  public function setQuestionCount(int $a_question_count): void
870  {
871  $this->question_count = $a_question_count;
872  }
873 
874  public function getQuestionCount(): int
875  {
876  return $this->question_count;
877  }
878 
879  public function getErrorMessage(): string
880  {
881  return $this->errormessage;
882  }
883 
884  public function setErrorMessage(string $errormessage): void
885  {
886  $this->errormessage = $errormessage;
887  }
888 
889  public function addErrorMessage(string $errormessage): void
890  {
891  $this->errormessage .= ((strlen($this->errormessage)) ? "<br />" : "") . $errormessage;
892  }
893 
895  public function outAdditionalOutput(): void
896  {
897  }
898 
899  public function getQuestionType(): string
900  {
901  return $this->object->getQuestionType();
902  }
903 
904  public function getAsValueAttribute(string $a_value): string
905  {
906  $result = "";
907  if (strlen($a_value)) {
908  $result = " value=\"$a_value\" ";
909  }
910  return $result;
911  }
912 
913  // scorm2004-start
918  public function addNewIdListener($a_object, string $a_method, string $a_parameters = ""): void
919  {
921  $this->new_id_listeners[$cnt]["object"] = &$a_object;
922  $this->new_id_listeners[$cnt]["method"] = $a_method;
923  $this->new_id_listeners[$cnt]["parameters"] = $a_parameters;
924  $this->new_id_listener_cnt++;
925  }
926 
927  public function callNewIdListeners(int $a_new_id): void
928  {
929  for ($i = 0; $i < $this->new_id_listener_cnt; $i++) {
930  $this->new_id_listeners[$i]["parameters"]["new_id"] = $a_new_id;
931  $object = &$this->new_id_listeners[$i]["object"];
932  $method = $this->new_id_listeners[$i]["method"];
933  $parameters = $this->new_id_listeners[$i]["parameters"];
934  $object->$method($parameters);
935  }
936  }
937 
939  {
940  if (!$this->object->getSelfAssessmentEditingMode()) {
941  $form->addCommandButton("saveReturn", $this->lng->txt("save_return"));
942  }
943  $form->addCommandButton("save", $this->lng->txt("save"));
944  }
945 
947  {
948  // title
949  $title = new ilTextInputGUI($this->lng->txt("title"), "title");
950  $title->setMaxLength(100);
951  $title->setValue($this->object->getTitle());
952  $title->setRequired(true);
953  $form->addItem($title);
954 
955  if (!$this->object->getSelfAssessmentEditingMode()) {
956  // author
957  $author = new ilTextInputGUI($this->lng->txt("author"), "author");
958  $author->setValue($this->object->getAuthor());
959  $author->setMaxLength(512);
960  $author->setRequired(true);
961  $form->addItem($author);
962 
963  // description
964  $description = new ilTextInputGUI($this->lng->txt("description"), "comment");
965  $description->setValue($this->object->getComment());
966  $description->setRequired(false);
967  $description->setMaxLength(1000);
968  $form->addItem($description);
969  } else {
970  // author as hidden field
971  $hi = new ilHiddenInputGUI("author");
972  $author = ilLegacyFormElementsUtil::prepareFormOutput($this->object->getAuthor());
973  if (trim($author) == "") {
974  $author = "-";
975  }
976  $hi->setValue($author);
977  $form->addItem($hi);
978  }
979 
980  // lifecycle
981  $lifecycle = new ilSelectInputGUI($this->lng->txt('qst_lifecycle'), 'lifecycle');
982  $lifecycle->setOptions($this->object->getLifecycle()->getSelectOptions($this->lng));
983  $lifecycle->setValue($this->object->getLifecycle()->getIdentifier());
984  $form->addItem($lifecycle);
985 
986  // questiontext
987  $question = new ilTextAreaInputGUI($this->lng->txt("question"), "question");
988  $question->setValue($this->object->getQuestion());
989  $question->setRequired(true);
990  $question->setRows(10);
991  $question->setCols(80);
992 
993  if (!$this->object->getSelfAssessmentEditingMode()) {
994  if ($this->object->getAdditionalContentEditingMode() != assQuestion::ADDITIONAL_CONTENT_EDITING_MODE_IPE) {
995  $question->setUseRte(true);
996  $question->setRteTags(ilObjAdvancedEditing::_getUsedHTMLTags("assessment"));
997  $question->addPlugin("latex");
998  $question->addButton("latex");
999  $question->addButton("pastelatex");
1000  $question->setRTESupport($this->object->getId(), "qpl", "assessment");
1001  }
1002  } else {
1004  $question->setUseTagsForRteOnly(false);
1005  }
1006  $form->addItem($question);
1007  $this->addNumberOfTriesToFormIfNecessary($form);
1008  }
1009 
1011  {
1012  if (!$this->object->getSelfAssessmentEditingMode()) {
1013  return;
1014  }
1015 
1016  $nr_tries = $this->object->getNrOfTries() ?? $this->object->getDefaultNrOfTries();
1017 
1018  if ($nr_tries < 1) {
1019  $nr_tries = "";
1020  }
1021 
1022  $ni = new ilNumberInputGUI($this->lng->txt("qst_nr_of_tries"), "nr_of_tries");
1023  $ni->setValue($nr_tries);
1024  $ni->setMinValue(0);
1025  $ni->setSize(5);
1026  $ni->setMaxLength(5);
1027  $form->addItem($ni);
1028  }
1029 
1030  protected function saveTaxonomyAssignments(): void
1031  {
1032  if (count($this->getTaxonomyIds())) {
1033  foreach ($this->getTaxonomyIds() as $taxonomyId) {
1034  $postvar = "tax_node_assign_$taxonomyId";
1035 
1036  $tax_node_assign = new ilTaxAssignInputGUI($taxonomyId, true, '', $postvar);
1037  // TODO: determine tst/qpl when tax assigns become maintainable within tests
1038  $tax_node_assign->saveInput("qpl", $this->object->getObjId(), "quest", $this->object->getId());
1039  }
1040  }
1041  }
1042 
1043  protected function populateTaxonomyFormSection(ilPropertyFormGUI $form): void
1044  {
1045  if ($this->getTaxonomyIds() !== []) {
1046  // this is needed by ilTaxSelectInputGUI in some cases
1047  ilOverlayGUI::initJavaScript();
1048 
1049  $sectHeader = new ilFormSectionHeaderGUI();
1050  $sectHeader->setTitle($this->lng->txt('qpl_qst_edit_form_taxonomy_section'));
1051  $form->addItem($sectHeader);
1052 
1053  foreach ($this->getTaxonomyIds() as $taxonomyId) {
1054  $taxonomy = new ilObjTaxonomy($taxonomyId);
1055  $label = sprintf($this->lng->txt('qpl_qst_edit_form_taxonomy'), $taxonomy->getTitle());
1056  $postvar = "tax_node_assign_$taxonomyId";
1057 
1058  $taxSelect = new ilTaxSelectInputGUI($taxonomy->getId(), $postvar, true);
1059  $taxSelect->setTitle($label);
1060 
1061 
1062  $taxNodeAssignments = new ilTaxNodeAssignment(ilObject::_lookupType($this->object->getObjId()), $this->object->getObjId(), 'quest', $taxonomyId);
1063  $assignedNodes = $taxNodeAssignments->getAssignmentsOfItem($this->object->getId());
1064 
1065  $taxSelect->setValue(array_map(function ($assignedNode) {
1066  return $assignedNode['node_id'];
1067  }, $assignedNodes));
1068  $form->addItem($taxSelect);
1069  }
1070  }
1071  }
1072 
1076  public function getGenericFeedbackOutput(int $active_id, ?int $pass): string
1077  {
1078  $output = '';
1079  $manual_feedback = ilObjTest::getManualFeedback($active_id, $this->object->getId(), $pass);
1080  if ($manual_feedback !== '') {
1081  return $manual_feedback;
1082  }
1083 
1084  $correct_feedback = $this->object->feedbackOBJ->getGenericFeedbackTestPresentation($this->object->getId(), true);
1085  $incorrect_feedback = $this->object->feedbackOBJ->getGenericFeedbackTestPresentation($this->object->getId(), false);
1086  if ($correct_feedback . $incorrect_feedback !== '') {
1087  $output = $this->genericFeedbackOutputBuilder($correct_feedback, $incorrect_feedback, $active_id, $pass);
1088  }
1089 
1090  if ($this->object->isAdditionalContentEditingModePageObject()) {
1091  return $output;
1092  }
1094  }
1095 
1096  protected function genericFeedbackOutputBuilder(
1097  string $feedback_correct,
1098  string $feedback_incorrect,
1099  int $active_id,
1100  ?int $pass
1101  ): string {
1102  if ($pass === null) {
1103  return '';
1104  }
1105  $reached_points = $this->object->calculateReachedPoints($active_id, $pass);
1106  $max_points = $this->object->getMaximumPoints();
1107  if ($reached_points == $max_points) {
1108  return $feedback_correct;
1109  }
1110 
1111  return $feedback_incorrect;
1112  }
1113 
1115  {
1117  $this->object->feedbackOBJ->getGenericFeedbackTestPresentation($this->object->getId(), true),
1118  true
1119  );
1120  }
1121 
1123  {
1125  $this->object->feedbackOBJ->getGenericFeedbackTestPresentation($this->object->getId(), false),
1126  true
1127  );
1128  }
1129 
1134  abstract public function getSpecificFeedbackOutput(array $userSolution): string;
1135 
1136  public function outQuestionType(): string
1137  {
1138  $count = $this->questioninfo->usageNumber($this->object->getId());
1139 
1140  if ($this->questioninfo->questionExistsInPool($this->object->getId()) && $count) {
1141  if ($this->rbacsystem->checkAccess("write", $this->request->getRefId())) {
1142  $this->tpl->setOnScreenMessage('info', sprintf($this->lng->txt("qpl_question_is_in_use"), $count));
1143  }
1144  }
1145 
1146  return $this->questioninfo->getQuestionTypeName($this->object->getId());
1147  }
1148 
1149  protected function getTypeOptions(): array
1150  {
1151  foreach (assQuestionSuggestedSolution::TYPES as $k => $v) {
1152  $options[$k] = $this->lng->txt($v);
1153  }
1154  return $options;
1155  }
1156 
1157  public function suggestedsolution(): void
1158  {
1159  $ilUser = $this->ilUser;
1160  $ilAccess = $this->access;
1161 
1162  $cmd = $this->request->raw('cmd');
1163  $save = is_array($cmd) && array_key_exists('saveSuggestedSolution', $cmd);
1164  if ($save && $this->request->int('deleteSuggestedSolution') === 1) {
1165  $this->object->deleteSuggestedSolutions();
1166  $this->tpl->setOnScreenMessage('success', $this->lng->txt("msg_obj_modified"), true);
1167  $this->ctrl->redirect($this, "suggestedsolution");
1168  }
1169 
1170  $output = "";
1171 
1172  $solution = $this->object->getSuggestedSolution();
1173  $options = $this->getTypeOptions();
1174 
1175  $solution_type = $this->ctrl->getCmd() === 'cancelSuggestedSolution'
1176  ? $solution->getType()
1177  : $this->request->string('solutiontype');
1178  if (is_string($solution_type) && strcmp($solution_type, "file") == 0
1179  && (!$solution || $solution->getType() !== assQuestionSuggestedSolution::TYPE_FILE)
1180  ) {
1181  $solution = $this->getSuggestedSolutionsRepo()->create(
1182  $this->object->getId(),
1183  assQuestionSuggestedSolution::TYPE_FILE
1184  );
1185  }
1186 
1187  $solution_filename = $this->request->raw('filename');
1188  if ($save &&
1189  is_string($solution_filename) &&
1190  strlen($solution_filename)) {
1191  $solution = $solution->withTitle($solution_filename);
1192  }
1193 
1194  if ($solution) {
1195  $form = new ilPropertyFormGUI();
1196  $form->setFormAction($this->ctrl->getFormAction($this));
1197  $form->setTitle($this->lng->txt("solution_hint"));
1198  $form->setMultipart(true);
1199  $form->setTableWidth("100%");
1200  $form->setId("suggestedsolutiondisplay");
1201 
1202  $title = new ilSolutionTitleInputGUI($this->lng->txt("showSuggestedSolution"), "solutiontype");
1203  $template = new ilTemplate("tpl.il_as_qpl_suggested_solution_input_presentation.html", true, true, "Modules/TestQuestionPool");
1204 
1205  if ($solution->isOfTypeLink()) {
1206  $href = assQuestion::_getInternalLinkHref($solution->getInternalLink());
1207  $template->setCurrentBlock("preview");
1208  $template->setVariable("TEXT_SOLUTION", $this->lng->txt("suggested_solution"));
1209  $template->setVariable("VALUE_SOLUTION", " <a href=\"$href\" target=\"content\">" . $this->lng->txt("view") . "</a> ");
1210  $template->parseCurrentBlock();
1211  } elseif (
1212  $solution->isOfTypeFile()
1213  && $solution->getFilename()
1214  ) {
1215  $href = $this->object->getSuggestedSolutionPathWeb() . $solution->getFilename();
1216  $link = " <a href=\"$href\" target=\"content\">"
1217  . ilLegacyFormElementsUtil::prepareFormOutput($solution->getTitle())
1218  . "</a> ";
1219  $template->setCurrentBlock("preview");
1220  $template->setVariable("TEXT_SOLUTION", $this->lng->txt("suggested_solution"));
1221  $template->setVariable("VALUE_SOLUTION", $link);
1222  $template->parseCurrentBlock();
1223  }
1224 
1225  $template->setVariable("TEXT_TYPE", $this->lng->txt("type"));
1226  $template->setVariable("VALUE_TYPE", $options[$solution->getType()]);
1227 
1228  $title->setHtml($template->get());
1229  $deletesolution = new ilCheckboxInputGUI("", "deleteSuggestedSolution");
1230  $deletesolution->setOptionTitle($this->lng->txt("deleteSuggestedSolution"));
1231  $title->addSubItem($deletesolution);
1232  $form->addItem($title);
1233 
1234  if ($solution->isOfTypeFile()) {
1235  $file = new ilFileInputGUI($this->lng->txt("fileDownload"), "file");
1236  $file->setRequired(true);
1237  $file->enableFileNameSelection("filename");
1238 
1239  //$file->setSuffixes(array("doc","xls","png","jpg","gif","pdf"));
1240  if ($save && $_FILES && $_FILES["file"]["tmp_name"] && $file->checkInput()) {
1241  if (!file_exists($this->object->getSuggestedSolutionPath())) {
1242  ilFileUtils::makeDirParents($this->object->getSuggestedSolutionPath());
1243  }
1244 
1246  $_FILES["file"]["tmp_name"],
1247  $_FILES["file"]["name"],
1248  $this->object->getSuggestedSolutionPath() . $_FILES["file"]["name"]
1249  );
1250  if ($res) {
1251  ilFileUtils::renameExecutables($this->object->getSuggestedSolutionPath());
1252 
1253  // remove an old file download
1254  if ($solution->getFilename()) {
1255  @unlink($this->object->getSuggestedSolutionPath() . $solution->getFilename());
1256  }
1257 
1258  $file->setValue($_FILES["file"]["name"]);
1259  $solution = $solution
1260  ->withFilename($_FILES["file"]["name"])
1261  ->withMime($_FILES["file"]["type"])
1262  ->withSize($_FILES["file"]["size"])
1263  ->withTitle($_POST["filename"]);
1264 
1265  $this->getSuggestedSolutionsRepo()->update([$solution]);
1266 
1267  $originalexists = $this->object->getOriginalId() &&
1268  $this->questioninfo->questionExistsInPool($this->object->getOriginalId());
1269  if ($this->request->raw("calling_test") && $originalexists
1270  && assQuestion::_isWriteable($this->object->getOriginalId(), $ilUser->getId())) {
1271  $this->originalSyncForm("suggestedsolution");
1272  return;
1273  } else {
1274  $this->tpl->setOnScreenMessage('success', $this->lng->txt("suggested_solution_added_successfully"), true);
1275  $this->ctrl->redirect($this, "suggestedsolution");
1276  }
1277  } else {
1278  // BH: $res as info string? wtf? it holds a bool or something else!!?
1279  $this->tpl->setOnScreenMessage('info', $res);
1280  }
1281  } else {
1282  if ($solution->getFilename()) {
1283  $file->setValue($solution->getFilename());
1284  $file->setFilename($solution->getTitle());
1285  }
1286  }
1287  $form->addItem($file);
1288  $hidden = new ilHiddenInputGUI("solutiontype");
1289  $hidden->setValue("file");
1290  $form->addItem($hidden);
1291  }
1292  if ($ilAccess->checkAccess("write", "", $this->request->getRefId())) {
1293  $form->addCommandButton('cancelSuggestedSolution', $this->lng->txt('cancel'));
1294  $form->addCommandButton('saveSuggestedSolution', $this->lng->txt('save'));
1295  }
1296 
1297  if ($save) {
1298  if ($form->checkInput()) {
1299  if ($solution->isOfTypeFile()) {
1300  $solution = $solution->withTitle($_POST["filename"]);
1301  }
1302 
1303  if (!$solution->isOfTypeLink()) {
1304  $this->getSuggestedSolutionsRepo()->update([$solution]);
1305  }
1306 
1307  $originalexists = !is_null($this->object->getOriginalId()) &&
1308  $this->questioninfo->questionExistsInPool($this->object->getOriginalId());
1309  if ($this->request->raw("calling_test") && $originalexists
1310  && assQuestion::_isWriteable($this->object->getOriginalId(), $ilUser->getId())) {
1311  $this->originalSyncForm("suggestedsolution");
1312  return;
1313  } else {
1314  $this->tpl->setOnScreenMessage('success', $this->lng->txt("msg_obj_modified"), true);
1315  $this->ctrl->redirect($this, "suggestedsolution");
1316  }
1317  }
1318  }
1319 
1320  $output = $form->getHTML();
1321  }
1322 
1323  $savechange = $this->ctrl->getCmd() === "saveSuggestedSolutionType";
1324 
1325  $changeoutput = "";
1326  if ($ilAccess->checkAccess("write", "", $this->request->getRefId())) {
1327  $formchange = new ilPropertyFormGUI();
1328  $formchange->setFormAction($this->ctrl->getFormAction($this));
1329 
1330  $title = $solution ? $this->lng->txt("changeSuggestedSolution") : $this->lng->txt("addSuggestedSolution");
1331  $formchange->setTitle($title);
1332  $formchange->setMultipart(false);
1333  $formchange->setTableWidth("100%");
1334  $formchange->setId("suggestedsolution");
1335 
1336  $solutiontype = new ilRadioGroupInputGUI($this->lng->txt("suggestedSolutionType"), "solutiontype");
1337  foreach ($options as $opt_value => $opt_caption) {
1338  $solutiontype->addOption(new ilRadioOption($opt_caption, $opt_value));
1339  }
1340  if ($solution) {
1341  $solutiontype->setValue($solution->getType());
1342  }
1343  $solutiontype->setRequired(true);
1344  $formchange->addItem($solutiontype);
1345 
1346  $formchange->addCommandButton("saveSuggestedSolutionType", $this->lng->txt("select"));
1347 
1348  if ($savechange) {
1349  $formchange->checkInput();
1350  }
1351  $changeoutput = $formchange->getHTML();
1352  }
1353 
1354  $this->tpl->setVariable("ADM_CONTENT", $changeoutput . $output);
1355  }
1356 
1357  public function outSolutionExplorer(): void
1358  {
1359  $type = $this->request->raw("link_new_type");
1360  $search = $this->request->raw("search_link_type");
1361  $this->ctrl->setParameter($this, "link_new_type", $type);
1362  $this->ctrl->setParameter($this, "search_link_type", $search);
1363  $this->ctrl->saveParameter($this, array("subquestion_index", "link_new_type", "search_link_type"));
1364 
1365  $this->tpl->setOnScreenMessage('info', $this->lng->txt("select_object_to_link"));
1366 
1367  $parent_ref_id = $this->tree->getParentId($this->request->getRefId());
1368  $exp = new ilSolutionExplorer($this->ctrl->getLinkTarget($this, 'suggestedsolution'), get_class($this));
1369  $exp->setExpand($this->request->raw('expand_sol') ? $this->request->raw('expand_sol') : $parent_ref_id);
1370  $exp->setExpandTarget($this->ctrl->getLinkTarget($this, 'outSolutionExplorer'));
1371  $exp->setTargetGet("ref_id");
1372  $exp->setRefId($this->request->getRefId());
1373  $exp->addFilter($type);
1374  $exp->setSelectableType($type);
1375  if ($this->request->isset('expandCurrentPath') && $this->request->raw('expandCurrentPath')) {
1376  $exp->expandPathByRefId($parent_ref_id);
1377  }
1378 
1379  // build html-output
1380  $exp->setOutput(0);
1381 
1382  $template = new ilTemplate("tpl.il_as_qpl_explorer.html", true, true, "Modules/TestQuestionPool");
1383  $template->setVariable("EXPLORER_TREE", $exp->getOutput());
1384  $template->setVariable("BUTTON_CANCEL", $this->lng->txt("cancel"));
1385  $template->setVariable("FORMACTION", $this->ctrl->getFormAction($this, "suggestedsolution"));
1386  $this->tpl->setVariable("ADM_CONTENT", $template->get());
1387  }
1388 
1389  public function saveSuggestedSolutionType(): void
1390  {
1391  switch ($_POST["solutiontype"]) {
1392  case "lm":
1393  $type = "lm";
1394  $search = "lm";
1395  break;
1396  case "git":
1397  $type = "glo";
1398  $search = "glo";
1399  break;
1400  case "st":
1401  $type = "lm";
1402  $search = "st";
1403  break;
1404  case "pg":
1405  $type = "lm";
1406  $search = "pg";
1407  break;
1408  case "file":
1409  case "text":
1410  default:
1411  $this->suggestedsolution();
1412  return;
1413  }
1414  if (isset($_POST['solutiontype'])) {
1415  $this->ctrl->setParameter($this, 'expandCurrentPath', 1);
1416  }
1417  $this->ctrl->setParameter($this, "link_new_type", $type);
1418  $this->ctrl->setParameter($this, "search_link_type", $search);
1419  $this->ctrl->redirect($this, "outSolutionExplorer");
1420  }
1421 
1422  public function cancelExplorer(): void
1423  {
1424  $this->ctrl->redirect($this, "suggestedsolution");
1425  }
1426 
1427  public function outPageSelector(): void
1428  {
1429  $this->ctrl->setParameter($this, 'q_id', $this->object->getId());
1430 
1431  $cont_obj_gui = new ilObjContentObjectGUI('', $this->request->raw('source_id'), true);
1432  $cont_obj = $cont_obj_gui->getObject();
1433  $pages = ilLMPageObject::getPageList($cont_obj->getId());
1434  $shownpages = array();
1435  $tree = $cont_obj->getLMTree();
1436  $chapters = $tree->getSubtree($tree->getNodeData($tree->getRootId()));
1437 
1438  $rows = array();
1439 
1440  foreach ($chapters as $chapter) {
1441  $chapterpages = $tree->getChildsByType($chapter['obj_id'], 'pg');
1442  foreach ($chapterpages as $page) {
1443  if ($page['type'] == $this->request->raw('search_link_type')) {
1444  array_push($shownpages, $page['obj_id']);
1445 
1446  if ($tree->isInTree($page['obj_id'])) {
1447  $path_str = $this->getContextPath($cont_obj, $page['obj_id']);
1448  } else {
1449  $path_str = '---';
1450  }
1451 
1452  $this->ctrl->setParameter($this, $page['type'], $page['obj_id']);
1453  $rows[] = array(
1454  'title' => $page['title'],
1455  'description' => ilLegacyFormElementsUtil::prepareFormOutput($path_str),
1456  'text_add' => $this->lng->txt('add'),
1457  'href_add' => $this->ctrl->getLinkTarget($this, 'add' . strtoupper($page['type']))
1458  );
1459  }
1460  }
1461  }
1462  foreach ($pages as $page) {
1463  if (!in_array($page['obj_id'], $shownpages)) {
1464  $this->ctrl->setParameter($this, $page['type'], $page['obj_id']);
1465  $rows[] = array(
1466  'title' => $page['title'],
1467  'description' => '---',
1468  'text_add' => $this->lng->txt('add'),
1469  'href_add' => $this->ctrl->getLinkTarget($this, 'add' . strtoupper($page['type']))
1470  );
1471  }
1472  }
1473 
1474  $table = new ilQuestionInternalLinkSelectionTableGUI($this, 'cancelExplorer', __METHOD__);
1475  $table->setTitle($this->lng->txt('obj_' . ilUtil::stripSlashes($this->request->raw('search_link_type'))));
1476  $table->setData($rows);
1477 
1478  $this->tpl->setContent($table->getHTML());
1479  }
1480 
1481  public function outChapterSelector(): void
1482  {
1483  $this->ctrl->setParameter($this, 'q_id', $this->object->getId());
1484 
1485  $cont_obj_gui = new ilObjContentObjectGUI('', $this->request->raw('source_id'), true);
1486  $cont_obj = $cont_obj_gui->getObject();
1487  $ctree = $cont_obj->getLMTree();
1488  $nodes = $ctree->getSubtree($ctree->getNodeData($ctree->getRootId()));
1489 
1490  $rows = array();
1491 
1492  foreach ($nodes as $node) {
1493  if ($node['type'] == $this->request->raw('search_link_type')) {
1494  $this->ctrl->setParameter($this, $node['type'], $node['obj_id']);
1495  $rows[] = array(
1496  'title' => $node['title'],
1497  'description' => '',
1498  'text_add' => $this->lng->txt('add'),
1499  'href_add' => $this->ctrl->getLinkTarget($this, 'add' . strtoupper($node['type']))
1500  );
1501  }
1502  }
1503 
1504  $table = new ilQuestionInternalLinkSelectionTableGUI($this, 'cancelExplorer', __METHOD__);
1505  $table->setTitle($this->lng->txt('obj_' . ilUtil::stripSlashes($this->request->raw('search_link_type'))));
1506  $table->setData($rows);
1507 
1508  $this->tpl->setContent($table->getHTML());
1509  }
1510 
1511  public function outGlossarySelector(): void
1512  {
1513  $this->ctrl->setParameter($this, 'q_id', $this->object->getId());
1514 
1515  $glossary = new ilObjGlossary($this->request->raw('source_id'), true);
1516  $terms = $glossary->getTermList();
1517 
1518  $rows = array();
1519 
1520  foreach ($terms as $term) {
1521  $this->ctrl->setParameter($this, 'git', $term['id']);
1522  $rows[] = array(
1523  'title' => $term['term'],
1524  'description' => '',
1525  'text_add' => $this->lng->txt('add'),
1526  'href_add' => $this->ctrl->getLinkTarget($this, 'addGIT')
1527  );
1528  }
1529 
1530  $table = new ilQuestionInternalLinkSelectionTableGUI($this, 'cancelExplorer', __METHOD__);
1531  $table->setTitle($this->lng->txt('glossary_term'));
1532  $table->setData($rows);
1533 
1534  $this->tpl->setContent($table->getHTML());
1535  }
1536 
1537  protected function createSuggestedSolutionLinkingTo(string $type, string $target)
1538  {
1539  $repo = $this->getSuggestedSolutionsRepo();
1540  $question_id = $this->object->getId();
1541  $subquestion_index = ($this->request->raw("subquestion_index") > 0) ? $this->request->raw("subquestion_index") : 0;
1542 
1543  $solution = $repo->create($question_id, $type)
1544  ->withSubquestionIndex($subquestion_index)
1545  ->withInternalLink($target);
1546 
1547  $repo->update([$solution]);
1548  }
1549 
1550  public function linkChilds(): void
1551  {
1552  $this->ctrl->saveParameter($this, array("subquestion_index", "link_new_type", "search_link_type"));
1553  switch ($this->request->raw("search_link_type")) {
1554  case "pg":
1555  $this->outPageSelector();
1556  break;
1557  case "st":
1558  $this->outChapterSelector();
1559  break;
1560  case "glo":
1561  $this->outGlossarySelector();
1562  break;
1563  case "lm":
1564  $target = "il__lm_" . $this->request->raw("source_id");
1565  $this->createSuggestedSolutionLinkingTo('lm', $target);
1566  $this->tpl->setOnScreenMessage('success', $this->lng->txt("suggested_solution_added_successfully"), true);
1567  $this->ctrl->redirect($this, "suggestedsolution");
1568  break;
1569  }
1570  }
1571 
1572  public function addPG(): void
1573  {
1574  $target = "il__pg_" . $this->request->raw("pg");
1575  $this->createSuggestedSolutionLinkingTo('pg', $target);
1576  $this->tpl->setOnScreenMessage('success', $this->lng->txt("suggested_solution_added_successfully"), true);
1577  $this->ctrl->redirect($this, "suggestedsolution");
1578  }
1579 
1580  public function addST(): void
1581  {
1582  $target = "il__st_" . $this->request->raw("st");
1583  $this->createSuggestedSolutionLinkingTo('st', $target);
1584  $this->tpl->setOnScreenMessage('success', $this->lng->txt("suggested_solution_added_successfully"), true);
1585  $this->ctrl->redirect($this, "suggestedsolution");
1586  }
1587 
1588  public function addGIT(): void
1589  {
1590  $target = "il__git_" . $this->request->raw("git");
1591  $this->createSuggestedSolutionLinkingTo('git', $target);
1592  $this->tpl->setOnScreenMessage('success', $this->lng->txt("suggested_solution_added_successfully"), true);
1593  $this->ctrl->redirect($this, "suggestedsolution");
1594  }
1595 
1596  public function isSaveCommand(): bool
1597  {
1598  return in_array($this->ctrl->getCmd(), array('save', 'saveEdit', 'saveReturn'));
1599  }
1600 
1601  public static function getCommandsFromClassConstants(
1602  string $guiClassName,
1603  string $cmdConstantNameBegin = 'CMD_'
1604  ): array {
1605  $reflectionClass = new ReflectionClass($guiClassName);
1606 
1607  $commands = null;
1608 
1609  if ($reflectionClass instanceof ReflectionClass) {
1610  $commands = array();
1611 
1612  foreach ($reflectionClass->getConstants() as $constName => $constValue) {
1613  if (substr($constName, 0, strlen($cmdConstantNameBegin)) == $cmdConstantNameBegin) {
1614  $commands[] = $constValue;
1615  }
1616  }
1617  }
1618 
1619  return $commands;
1620  }
1621 
1622  public function setQuestionTabs(): void
1623  {
1624  $this->ilTabs->clearTargets();
1625 
1626  $this->setDefaultTabs($this->ilTabs);
1627  $this->setQuestionSpecificTabs($this->ilTabs);
1628  $this->addBackTab($this->ilTabs);
1629  }
1630 
1631  protected function setDefaultTabs(ilTabsGUI $ilTabs): void
1632  {
1633  $this->ctrl->setParameterByClass("ilAssQuestionPageGUI", "q_id", $this->request->getQuestionId());
1634  $q_type = $this->object->getQuestionType();
1635 
1636  if (strlen($q_type)) {
1637  $classname = $q_type . "GUI";
1638  $this->ctrl->setParameterByClass(strtolower($classname), "sel_question_types", $q_type);
1639  $this->ctrl->setParameterByClass(strtolower($classname), "q_id", $this->request->getQuestionId());
1640  }
1641 
1642  if ($this->request->isset("q_id")) {
1643  $this->addTab_Question($ilTabs);
1644  }
1645 
1646  // add tab for question feedback within common class assQuestionGUI
1647  $this->addTab_QuestionFeedback($ilTabs);
1648 
1649  // add tab for question hint within common class assQuestionGUI
1650  $this->addTab_QuestionHints($ilTabs);
1651 
1652  // add tab for question's suggested solution within common class assQuestionGUI
1653  $this->addTab_SuggestedSolution($ilTabs, $classname);
1654 
1655  $this->addBackTab($ilTabs);
1656  }
1657 
1658  protected function setQuestionSpecificTabs(ilTabsGUI $ilTabs): void
1659  {
1660  }
1661 
1662  public function addTab_SuggestedSolution(ilTabsGUI $tabs, string $classname): void
1663  {
1664  if ($this->request->getQuestionId()) {
1665  $tabs->addTarget(
1666  "suggested_solution",
1667  $this->ctrl->getLinkTargetByClass($classname, "suggestedsolution"),
1668  array("suggestedsolution", "saveSuggestedSolution", "outSolutionExplorer", "cancel",
1669  "addSuggestedSolution","cancelExplorer", "linkChilds", "removeSuggestedSolution"
1670  ),
1671  $classname,
1672  ""
1673  );
1674  }
1675  }
1676 
1677  final public function getEditQuestionTabCommands(): array
1678  {
1679  return array_merge($this->getBasicEditQuestionTabCommands(), $this->getAdditionalEditQuestionCommands());
1680  }
1681 
1682  protected function getBasicEditQuestionTabCommands(): array
1683  {
1684  return array('editQuestion', 'save', 'saveEdit', 'originalSyncForm');
1685  }
1686 
1687  protected function getAdditionalEditQuestionCommands(): array
1688  {
1689  return array();
1690  }
1691 
1692  protected function addTab_QuestionFeedback(ilTabsGUI $tabs): void
1693  {
1694  $tabCommands = self::getCommandsFromClassConstants('ilAssQuestionFeedbackEditingGUI');
1695 
1696  $tabLink = $this->ctrl->getLinkTargetByClass('ilAssQuestionFeedbackEditingGUI', ilAssQuestionFeedbackEditingGUI::CMD_SHOW);
1697 
1698  $tabs->addTarget('feedback', $tabLink, $tabCommands, $this->ctrl->getCmdClass(), '');
1699  }
1700 
1701  protected function addTab_QuestionHints(ilTabsGUI $tabs): void
1702  {
1703  switch ($this->ctrl->getCmdClass()) {
1704  case 'ilassquestionhintsgui':
1705  $tabCommands = self::getCommandsFromClassConstants('ilAssQuestionHintsGUI');
1706  break;
1707 
1708  case 'ilassquestionhintgui':
1709  $tabCommands = self::getCommandsFromClassConstants('ilAssQuestionHintGUI');
1710  break;
1711 
1712  default:
1713 
1714  $tabCommands = array();
1715  }
1716 
1717  $tabLink = $this->ctrl->getLinkTargetByClass('ilAssQuestionHintsGUI', ilAssQuestionHintsGUI::CMD_SHOW_LIST);
1718 
1719  $tabs->addTarget('tst_question_hints_tab', $tabLink, $tabCommands, $this->ctrl->getCmdClass(), '');
1720  }
1721 
1722  protected function addTab_Question(ilTabsGUI $tabsGUI): void
1723  {
1724  $tabsGUI->addTarget(
1725  'edit_question',
1726  $this->ctrl->getLinkTargetByClass(
1727  array('ilrepositorygui','ilobjquestionpoolgui', get_class($this)),
1728  'editQuestion'
1729  ),
1730  'editQuestion',
1731  '',
1732  '',
1733  false
1734  );
1735  }
1736 
1737  // TODO: OWN "PASS" IN THE REFACTORING getSolutionOutput
1738  abstract public function getSolutionOutput(
1739  $active_id,
1740  $pass = null,
1741  $graphicalOutput = false,
1742  $result_output = false,
1743  $show_question_only = true,
1744  $show_feedback = false,
1745  $show_correct_solution = false,
1746  $show_manual_scoring = false,
1747  $show_question_text = true
1748  ): string;
1749 
1750  public function renderSolutionOutput(
1751  mixed $user_solutions,
1752  int $active_id,
1753  int $pass,
1754  bool $graphical_output = false,
1755  bool $result_output = false,
1756  bool $show_question_only = true,
1757  bool $show_feedback = false,
1758  bool $show_correct_solution = false,
1759  bool $show_manual_scoring = false,
1760  bool $show_question_text = true,
1761  bool $show_autosave_title = false,
1762  bool $show_inline_feedback = false,
1763  ): ?string {
1764  return null;
1765  }
1766 
1767 
1768  protected function hasCorrectSolution($activeId, $passIndex): bool
1769  {
1770  $reachedPoints = $this->object->getAdjustedReachedPoints((int) $activeId, (int) $passIndex, true);
1771  $maximumPoints = $this->object->getMaximumPoints();
1772 
1773  return $reachedPoints == $maximumPoints;
1774  }
1775 
1776  public function isAutosaveable(): bool
1777  {
1778  return $this->object instanceof ilAssQuestionAutosaveable;
1779  }
1780 
1781  protected function writeQuestionGenericPostData(): void
1782  {
1783  $this->object->setTitle($_POST["title"]);
1784  $this->object->setAuthor($_POST["author"]);
1785  $this->object->setComment($_POST["comment"] ?? '');
1786  if ($this->object->getSelfAssessmentEditingMode()) {
1787  $this->object->setNrOfTries((int) ($_POST['nr_of_tries'] ?? 0));
1788  }
1789 
1790  try {
1791  $lifecycle = ilAssQuestionLifecycle::getInstance($_POST['lifecycle']);
1792  $this->object->setLifecycle($lifecycle);
1794  }
1795 
1796  $this->object->setQuestion(ilUtil::stripOnlySlashes($_POST['question']));
1797  }
1798 
1799  // TODO: OWN "PASS" IN THE REFACTORING getPreview
1800  abstract public function getPreview($show_question_only = false, $showInlineFeedback = false);
1801 
1802  final public function outQuestionForTest(
1803  string $formaction,
1804  int $active_id,
1805  ?int $pass,
1806  bool $is_question_postponed = false,
1807  $user_post_solutions = false,
1808  bool $show_specific_inline_feedback = false
1809  ): void {
1810  $formaction = $this->completeTestOutputFormAction($formaction, $active_id, $pass);
1811 
1812  $test_output = $this->getTestOutput(
1813  $active_id,
1814  $pass,
1815  $is_question_postponed,
1816  $user_post_solutions,
1817  $show_specific_inline_feedback
1818  );
1819 
1820  $this->magicAfterTestOutput();
1821 
1822  $this->tpl->setVariable("QUESTION_OUTPUT", $test_output);
1823  $this->tpl->setVariable("FORMACTION", $formaction);
1824  $this->tpl->setVariable("ENCTYPE", 'enctype="' . $this->getFormEncodingType() . '"');
1825  $this->tpl->setVariable("FORM_TIMESTAMP", (string) time());
1826  }
1827 
1828  // hey: prevPassSolutions - $pass will be passed always from now on
1829  protected function completeTestOutputFormAction($formAction, $active_id, $pass)
1830  // hey.
1831  {
1832  return $formAction;
1833  }
1834 
1835  public function magicAfterTestOutput(): void
1836  {
1837  return;
1838  }
1839 
1840  // TODO: OWN "PASS" IN THE REFACTORING getPreview
1841  abstract public function getTestOutput(
1842  $active_id,
1843  $pass,
1844  $is_question_postponed,
1845  $user_post_solutions,
1846  $show_specific_inline_feedback
1847  );
1848 
1849  public function getFormEncodingType(): string
1850  {
1851  return self::FORM_ENCODING_URLENCODE;
1852  }
1853 
1854  protected function addBackTab(ilTabsGUI $ilTabs): void
1855  {
1856  $this->ctrl->saveParameterByClass(ilAssQuestionPreviewGUI::class, 'prev_qid');
1857  $ilTabs->setBackTarget(
1858  $this->lng->txt('backtocallingpage'),
1859  $this->ctrl->getLinkTargetByClass(ilAssQuestionPreviewGUI::class, ilAssQuestionPreviewGUI::CMD_SHOW)
1860  );
1861  }
1862 
1864  {
1865  $this->previewSession = $previewSession;
1866  }
1867 
1872  {
1873  return $this->previewSession;
1874  }
1875 
1877  {
1878  $form = new ilPropertyFormGUI();
1879  $form->setFormAction($this->ctrl->getFormAction($this));
1880  $form->setId($this->getType());
1881  $form->setTitle($this->outQuestionType());
1882  $form->setTableWidth('100%');
1883  $form->setMultipart(true);
1884  return $form;
1885  }
1886 
1887  public function showHints(): void
1888  {
1889  $this->ctrl->redirectByClass('ilAssQuestionHintsGUI', ilAssQuestionHintsGUI::CMD_SHOW_LIST);
1890  }
1891 
1892  protected function escapeTemplatePlaceholders(string $text): string
1893  {
1894  return str_replace(['{','}'], ['&#123;','&#125;'], $text);
1895  }
1896 
1897  protected function buildEditForm(): ilPropertyFormGUI
1898  {
1899  $this->editQuestion(true); // TODO bheyser: editQuestion should be added to the abstract base class with a unified signature
1900  return $this->editForm;
1901  }
1902 
1903  public function buildFocusAnchorHtml(): string
1904  {
1905  return '<div id="focus"></div>';
1906  }
1907 
1908  public function isAnswerFrequencyStatisticSupported(): bool
1909  {
1910  return true;
1911  }
1912 
1913  public function getSubQuestionsIndex(): array
1914  {
1915  return array(0);
1916  }
1917 
1918  public function getAnswersFrequency($relevantAnswers, $questionIndex): array
1919  {
1920  return array();
1921  }
1922 
1923  public function getAnswerFrequencyTableGUI($parentGui, $parentCmd, $relevantAnswers, $questionIndex): ilAnswerFrequencyStatisticTableGUI
1924  {
1925  $table = new ilAnswerFrequencyStatisticTableGUI($parentGui, $parentCmd, $this->object);
1926  $table->setQuestionIndex($questionIndex);
1927  $table->setData($this->getAnswersFrequency($relevantAnswers, $questionIndex));
1928  $table->initColumns();
1929  return $table;
1930  }
1931 
1933  {
1934  }
1935 
1937  {
1938  }
1939 
1941  {
1942  }
1943 
1944 
1945  protected function generateCorrectnessIconsForCorrectness(int $correctness): string
1946  {
1947  switch ($correctness) {
1948  case self::CORRECTNESS_NOT_OK:
1949  $icon_name = 'standard/icon_not_ok.svg';
1950  $label = $this->lng->txt("answer_is_wrong");
1951  break;
1952  case self::CORRECTNESS_MOSTLY_OK:
1953  $icon_name = 'standard/icon_mostly_ok.svg';
1954  $label = $this->lng->txt("answer_is_not_correct_but_positive");
1955  break;
1956  case self::CORRECTNESS_OK:
1957  $icon_name = 'standard/icon_ok.svg';
1958  $label = $this->lng->txt("answer_is_right");
1959  break;
1960  default:
1961  return '';
1962  }
1963  $path = ilUtil::getImagePath($icon_name);
1964  $icon = $this->ui->factory()->symbol()->icon()->custom(
1965  $path,
1966  $label
1967  );
1968  return $this->ui->renderer()->render($icon);
1969  }
1970 
1979  public static function prepareTextareaOutput(
1980  ?string $txt_output,
1981  bool $prepare_for_latex_output = false,
1982  bool $omitNl2BrWhenTextArea = false
1983  ): string {
1984  if ($txt_output === null || $txt_output === '') {
1985  return '';
1986  }
1987 
1988  $result = $txt_output;
1989  $is_html = false;
1990 
1991  if (strlen(strip_tags($result)) < strlen($result)) {
1992  $is_html = true;
1993  }
1994 
1995  // removed: did not work with magic_quotes_gpc = On
1996  if (!$is_html) {
1997  if (!$omitNl2BrWhenTextArea) {
1998  // if the string does not contain HTML code, replace the newlines with HTML line breaks
1999  $result = preg_replace("/[\n]/", "<br />", $result);
2000  }
2001  } else {
2002  // patch for problems with the <pre> tags in tinyMCE
2003  if (preg_match_all("/(<pre>.*?<\/pre>)/ims", $result, $matches)) {
2004  foreach ($matches[0] as $found) {
2005  $replacement = "";
2006  if (strpos("\n", $found) === false) {
2007  $replacement = "\n";
2008  }
2009  $removed = preg_replace("/<br\s*?\/>/ims", $replacement, $found);
2010  $result = str_replace($found, $removed, $result);
2011  }
2012  }
2013  }
2014 
2015  // since server side mathjax rendering does include svg-xml structures that indeed have linebreaks,
2016  // do latex conversion AFTER replacing linebreaks with <br>. <svg> tag MUST NOT contain any <br> tags.
2017  if ($prepare_for_latex_output) {
2018  $result = ilMathJax::getInstance()->insertLatexImages($result, "<span class\=\"latex\">", "<\/span>");
2019  $result = ilMathJax::getInstance()->insertLatexImages($result, "\[tex\]", "\[\/tex\]");
2020  }
2021 
2022  if ($prepare_for_latex_output) {
2023  // replace special characters to prevent problems with the ILIAS template system
2024  // eg. if someone uses {1} as an answer, nothing will be shown without the replacement
2025  $result = str_replace("{", "&#123;", $result);
2026  $result = str_replace("}", "&#125;", $result);
2027  $result = str_replace("\\", "&#92;", $result);
2028  }
2029 
2030  return $result;
2031  }
2032 
2035  {
2036  if (is_null($this->suggestedsolution_repo)) {
2038  $this->suggestedsolution_repo = $dic['question.repo.suggestedsolutions'];
2039  }
2041  }
2042 
2047  protected function cleanupAnswerText(array $answer_text, bool $is_rte): array
2048  {
2049  if (!is_array($answer_text)) {
2050  return [];
2051  }
2052 
2053  if ($is_rte) {
2055  $answer_text,
2056  false,
2058  );
2059  }
2060 
2062  $answer_text,
2063  true,
2064  self::ALLOWED_PLAIN_TEXT_TAGS
2065  );
2066  }
2067 
2068  public function isInLearningModuleContext(): bool
2069  {
2070  return $this->parent_type_is_lm;
2071  }
2072  public function setInLearningModuleContext(bool $flag): void
2073  {
2074  $this->parent_type_is_lm = $flag;
2075  }
2076 
2077  protected function addSaveOnEnterOnLoadCode(): void
2078  {
2079  $this->tpl->addOnloadCode("
2080  let form = document.querySelector('#ilContentContainer form');
2081  let button = form.querySelector('input[name=\"cmd[save]\"]');
2082  if (form && button) {
2083  form.addEventListener('keydown', function (e) {
2084  if (e.key === 'Enter'
2085  && e.target.type !== 'textarea'
2086  && e.target.type !== 'submit'
2087  && e.target.type !== 'file'
2088  ) {
2089  e.preventDefault();
2090  form.requestSubmit(button);
2091  }
2092  })
2093  }
2094  ");
2095  }
2096 
2098  int $active_id,
2099  int $pass,
2100  bool $graphical_output = false,
2101  bool $result_output = false,
2102  bool $show_question_only = true,
2103  bool $show_feedback = false,
2104  bool $show_correct_solution = false,
2105  bool $show_manual_scoring = false,
2106  bool $show_question_text = true,
2107  bool $show_autosave_title = false,
2108  bool $show_inline_feedback = false
2109  ): ?string {
2110  $autosave_solutions = $this->object->getSolutionValues($active_id, $pass, false);
2111  if ($autosave_solutions === []) {
2112  return null;
2113  }
2114  return $this->renderSolutionOutput(
2115  $autosave_solutions,
2116  $active_id,
2117  $pass,
2118  $graphical_output,
2119  $result_output,
2120  $show_question_only,
2121  $show_feedback,
2122  $show_correct_solution,
2123  $show_manual_scoring,
2124  $show_question_text,
2125  $show_autosave_title,
2126  $show_inline_feedback
2127  );
2128  }
2129 
2130 }
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
const ADDITIONAL_CONTENT_EDITING_MODE_IPE
setDefaultTabs(ilTabsGUI $ilTabs)
static getSelfAssessmentTags()
Get tags allowed in question tags in self assessment mode.
hasCorrectSolution($activeId, $passIndex)
saveCorrectionsFormProperties(ilPropertyFormGUI $form)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
$res
Definition: ltiservices.php:69
Readable part of repository interface to ilComponentDataDB.
genericFeedbackOutputBuilder(string $feedback_correct, string $feedback_incorrect, int $active_id, ?int $pass)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
$_GET["client_id"]
Definition: webdav.php:30
getTestOutput( $active_id, $pass, $is_question_postponed, $user_post_solutions, $show_specific_inline_feedback)
exit
Definition: login.php:29
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
generateCorrectnessIconsForCorrectness(int $correctness)
getAnswersFrequency($relevantAnswers, $questionIndex)
getNodeData(int $a_node_id, ?int $a_tree_pk=null)
get all information of a node.
static stripSlashesRecursive($a_data, bool $a_strip_html=true, string $a_allow="")
getPreview($show_question_only=false, $showInlineFeedback=false)
int $sequence_no
sequence number in test
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...
addTab_SuggestedSolution(ilTabsGUI $tabs, string $classname)
getAutoSavedSolutionOutput(int $active_id, int $pass, bool $graphical_output=false, bool $result_output=false, bool $show_question_only=true, bool $show_feedback=false, bool $show_correct_solution=false, bool $show_manual_scoring=false, bool $show_question_text=true, bool $show_autosave_title=false, bool $show_inline_feedback=false)
writePref(string $a_keyword, string $a_value)
This class represents a selection list property in a property form.
addTab_QuestionHints(ilTabsGUI $tabs)
ilTestQuestionNavigationGUI $navigationGUI
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
static _getGUIClassNameForId($a_q_id)
setTaxonomyIds(array $taxonomyIds)
addTarget(string $a_text, string $a_link, $a_cmd="", $a_cmdClass="", string $a_frame="", bool $a_activate=false, bool $a_dir_text=false)
setOutputMode(string $a_mode=self::PRESENTATION)
int $question_count
question count in test
Abstract basic class which is to be extended by the concrete assessment question type classes...
This class represents a file property in a property form.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
ilPropertyFormGUI $editForm
const SESSION_PREVIEW_DATA_BASE_INDEX
static stripSlashes(string $a_str, bool $a_strip_html=true, string $a_allow="")
createSuggestedSolutionLinkingTo(string $type, string $target)
getListHTML(bool $a_init_form=true)
static getImagePath(string $img, string $module_path="", string $mode="output", bool $offline=false)
get image path (for images located in a template directory)
Help GUI class.
addBasicQuestionFormProperties(ilPropertyFormGUI $form)
static _getInternalLinkHref(string $target="")
isInTree(?int $a_node_id)
get all information of a node.
static prepareTextareaOutput(?string $txt_output, bool $prepare_for_latex_output=false, bool $omitNl2BrWhenTextArea=false)
Prepares a string for a text area output where latex code may be in it If the text is HTML-free...
setQuestionHeaderBlockBuilder(\ilQuestionHeaderBlockBuilder $questionHeaderBlockBuilder)
renderSolutionOutput(mixed $user_solutions, int $active_id, int $pass, bool $graphical_output=false, bool $result_output=false, bool $show_question_only=true, bool $show_feedback=false, bool $show_correct_solution=false, bool $show_manual_scoring=false, bool $show_question_text=true, bool $show_autosave_title=false, bool $show_inline_feedback=false,)
getAsValueAttribute(string $a_value)
escapeTemplatePlaceholders(string $text)
addOption(ilRadioOption $a_option)
const SUGGESTED_SOLUTION_COMMANDS_CANCEL
writePostData(bool $always=false)
Evaluates a posted edit form and writes the form data in the question object.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
ilGlobalPageTemplate $tpl
static prepareFormOutput($a_str, bool $a_strip=false)
originalSyncForm(string $return_to="", string $return_to_feedback='')
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
static makeDirParents(string $a_dir)
Create a new directory and all parent directories.
setQuestionCount(int $a_question_count)
static _questionExistsInTest(int $question_id, int $test_id)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
populateTaxonomyFormSection(ilPropertyFormGUI $form)
static _getQuestionGUI(string $question_type='', int $question_id=-1)
Creates a question gui representation and returns the alias to the question gui.
const CMD_SHOW_LIST
command constants
$path
Definition: ltiservices.php:32
addNewIdListener($a_object, string $a_method, string $a_parameters="")
Add a listener that is notified with the new question ID, when a new question is saved.
addQuestionFormCommandButtons(ilPropertyFormGUI $form)
setPreviewSession(ilAssQuestionPreviewSession $previewSession)
getContextPath($cont_obj, int $a_endnode_id, int $a_startnode_id=1)
get context path in content object tree
prepareReprintableCorrectionsForm(ilPropertyFormGUI $form)
ILIAS Notes GUIService $notes_gui
populateJavascriptFilesRequiredForWorkForm(ilGlobalTemplateInterface $tpl)
global $DIC
Definition: feed.php:28
static renameExecutables(string $a_dir)
const SUGGESTED_SOLUTION_COMMANDS_SAVE
getType()
needed for page editor compliance
getChildsByType(int $a_node_id, string $a_type)
get child nodes of given node by object type
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
setPresentationContext(string $presentationContext)
populateCorrectionsFormProperties(ilPropertyFormGUI $form)
This class represents a property in a property form.
__construct(VocabulariesInterface $vocabularies)
ilRbacSystem $rbacsystem
setErrorMessage(string $errormessage)
get(string $part=self::DEFAULT_BLOCK)
Renders the given block and returns the html string.
setQuestionActionCmd(string $questionActionCmd)
static getReturnToPageLink($q_id=null)
getTermList(string $searchterm="", string $a_letter="", string $a_def="", int $a_tax_node=0, bool $a_include_offline_childs=false, bool $a_add_amet_fields=false, array $a_amet_filter=null, bool $a_omit_virtual=false, bool $a_include_references=false)
setInLearningModuleContext(bool $flag)
setBackTarget(string $a_title, string $a_target, string $a_frame="")
addTab_Question(ilTabsGUI $tabsGUI)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
static _getUsedHTMLTagsAsString(string $a_module="")
Returns a string of all allowed HTML tags for text editing.
addTab_QuestionFeedback(ilTabsGUI $tabs)
addJavaScript(string $a_js_file, bool $a_add_version_parameter=true, int $a_batch=2)
Add a javascript file that should be included in the header.
setTargetGui($linkTargetGui)
static moveUploadedFile(string $a_file, string $a_name, string $a_target, bool $a_raise_errors=true, string $a_mode="move_uploaded")
move uploaded file
setTargetGuiClass($targetGuiClass)
$lifecycle
Basic GUI class for assessment questions.
setRequired(bool $a_required)
callNewIdListeners(int $a_new_id)
setPref(string $a_keyword, ?string $a_value)
addCommandButton(string $a_cmd, string $a_text, string $a_id="")
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
getAssignmentsOfItem(int $a_item_id)
Get assignments for item.
static _getClassNameForQType($q_type)
ILIAS TestQuestionPool InternalRequestService $request
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
setRenderPurpose(string $renderPurpose)
completeTestOutputFormAction($formAction, $active_id, $pass)
static redirect(string $a_script)
const RETURN_AFTER_EXISTING_WITH_ORIGINAL_SAVE
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
setEditContext(string $editContext)
setQuestionHTML(array $question_html)
getSpecificFeedbackOutput(array $userSolution)
Returns the answer specific feedback for the question.
const ALLOWED_PLAIN_TEXT_TAGS
sk - 12.05.2023: This const is also used in ilKprimChoiceWizardInputGUI.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
getILIASPage(string $html="")
Returns the ILIAS Page around a question.
outQuestionPage($a_temp_var, $a_postponed=false, $active_id="", $html="", $inlineFeedbackEnabled=false)
static getInstance()
Singleton: get instance for use in ILIAS requests with a config loaded from the settings.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
getSolutionOutput( $active_id, $pass=null, $graphicalOutput=false, $result_output=false, $show_question_only=true, $show_feedback=false, $show_correct_solution=false, $show_manual_scoring=false, $show_question_text=true)
static getPageList(int $lm_id)
Comment GUI.
This class represents a text area property in a property form.
addBackTab(ilTabsGUI $ilTabs)
ilComponentRepository $component_repository
Class ilObjContentObjectGUI.
setQuestionSpecificTabs(ilTabsGUI $ilTabs)
cleanupAnswerText(array $answer_text, bool $is_rte)
sk - 12.05.2023: This is one more of those that we need, but don&#39;t want.
addErrorMessage(string $errormessage)
$dic
Definition: result.php:32
static _isWriteable(int $question_id, int $user_id)
setPreviousSolutionPrefilled(bool $previousSolutionPrefilled)
const SUGGESTED_SOLUTION_COMMANDS_DEFAULT
addNumberOfTriesToFormIfNecessary(ilPropertyFormGUI $form)
setSubObject(?string $sub_obj_type, ?int $sub_obj_id)
Set sub object attributes.
static _lookupType(int $id, bool $reference=false)
static getManualFeedback(int $active_id, int $question_id, ?int $pass)
Retrieves the feedback comment for a question in a test if it is finalized.
ilObjectDataCache $ilObjDataCache
static prepareTextareaOutput(string $txt_output, bool $prepare_for_latex_output=false, bool $omitNl2BrWhenTextArea=false)
Prepares a string for a text area output where latex code may be in it If the text is HTML-free...
outAdditionalOutput()
Why are you here? Some magic for plugins?
setVariable(string $variable, $value='')
Sets the given variable to the given value.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
getAnswerFrequencyTableGUI($parentGui, $parentCmd, $relevantAnswers, $questionIndex)
ilQuestionHeaderBlockBuilder $questionHeaderBlockBuilder
outQuestionForTest(string $formaction, int $active_id, ?int $pass, bool $is_question_postponed=false, $user_post_solutions=false, bool $show_specific_inline_feedback=false)
setExpand($a_node_id)
set the expand option this value is stored in a SESSION variable to save it different view (lo view...
static getCommandsFromClassConstants(string $guiClassName, string $cmdConstantNameBegin='CMD_')
static _getUsedHTMLTags(string $a_module="")
Returns an array of all allowed HTML tags for text editing.
ILIAS TestQuestionPool QuestionInfoService $questioninfo
setNavigationGUI(?ilTestQuestionNavigationGUI $navigationGUI)
renderEditForm(ilPropertyFormGUI $form)
assQuestionSuggestedSolutionsDatabaseRepository $suggestedsolution_repo
ilAccessHandler $access
static getFeedbackClassNameByQuestionType(string $questionType)
static stripOnlySlashes(string $a_str)
getGenericFeedbackOutput(int $active_id, ?int $pass)