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