ILIAS  release_9 Revision v9.13-25-g2c18ec4c24f
class.assErrorTextGUI.php
Go to the documentation of this file.
1 <?php
2 
19 require_once './Modules/Test/classes/inc.AssessmentConstants.php';
20 
36 {
37  private const DEFAULT_POINTS_WRONG = -1;
38 
39  private ilTabsGUI $tabs;
40 
41  public function __construct($id = -1)
42  {
43  global $DIC;
44  $this->tabs = $DIC->tabs();
45 
47  $this->object = new assErrorText();
48  $this->setErrorMessage($this->lng->txt("msg_form_save_error"));
49  if ($id >= 0) {
50  $this->object->loadFromDb($id);
51  }
52  }
53 
57  protected function writePostData(bool $always = false): int
58  {
59  $hasErrors = (!$always) ? $this->editQuestion(true) : false;
60  if (!$hasErrors) {
64  $this->saveTaxonomyAssignments();
65  return 0;
66  }
67  return 1;
68  }
69 
70  public function writeAnswerSpecificPostData(ilPropertyFormGUI $form): void
71  {
72  $errordata = $this->restructurePostDataForSaving($this->request->raw('errordata') ?? []);
73  $this->object->setErrorData($errordata);
74  $this->object->removeErrorDataWithoutPosition();
75  }
76 
77  private function restructurePostDataForSaving(array $post): array
78  {
79  $keys = $post['key'] ?? [];
80  $restructured_array = [];
81  foreach ($keys as $key => $text_wrong) {
82  $restructured_array[] = new assAnswerErrorText(
83  $text_wrong,
84  $post['value'][$key],
85  (float) str_replace(',', '.', $post['points'][$key])
86  );
87  }
88  return $restructured_array;
89  }
90 
91  public function writeQuestionSpecificPostData(ilPropertyFormGUI $form): void
92  {
93  $this->object->setQuestion(
94  $this->request->raw('question')
95  );
96 
97  $this->object->setErrorText(
98  $this->request->raw('errortext')
99  );
100 
101  $this->object->parseErrorText();
102 
103  $points_wrong = str_replace(",", ".", $this->request->raw('points_wrong') ?? '');
104  if (mb_strlen($points_wrong) == 0) {
105  $points_wrong = self::DEFAULT_POINTS_WRONG;
106  }
107  $this->object->setPointsWrong((float) $points_wrong);
108 
109  if (!$this->object->getSelfAssessmentEditingMode()) {
110  $this->object->setTextSize(
111  (float) str_replace(',', '.', $this->request->raw('textsize'))
112  );
113  }
114  }
115 
123  public function editQuestion($checkonly = false): bool
124  {
125  $this->tabs->setTabActive('edit_question');
126  $save = $this->isSaveCommand();
127  $this->getQuestionTemplate();
128 
129  $form = new ilPropertyFormGUI();
130  $this->editForm = $form;
131 
132  $form->setFormAction($this->ctrl->getFormAction($this));
133  $form->setTitle($this->outQuestionType());
134  $form->setMultipart(false);
135  $form->setTableWidth("100%");
136  $form->setId("orderinghorizontal");
137 
138  $this->addBasicQuestionFormProperties($form);
139 
140  $this->populateQuestionSpecificFormPart($form);
141 
142  if (count($this->object->getErrorData()) || $checkonly) {
143  $this->populateAnswerSpecificFormPart($form);
144  }
145 
146  $this->populateTaxonomyFormSection($form);
147 
148  $form->addCommandButton("analyze", $this->lng->txt('analyze_errortext'));
149  $this->addQuestionFormCommandButtons($form);
150 
151  $errors = false;
152 
153  if ($save) {
154  $form->setValuesByPost();
155  $errors = !$form->checkInput();
156  $form->setValuesByPost(); // again, because checkInput now performs the whole stripSlashes handling and we need this if we don't want to have duplication of backslashes
157  if ($errors) {
158  $checkonly = false;
159  }
160  }
161 
162  if (!$checkonly) {
163  $this->tpl->setVariable("QUESTION_DATA", $form->getHTML());
164  }
165  return $errors;
166  }
167 
173  {
174  $header = new ilFormSectionHeaderGUI();
175  $header->setTitle($this->lng->txt("errors_section"));
176  $form->addItem($header);
177 
178  $errordata = new ilErrorTextWizardInputGUI($this->lng->txt("errors"), "errordata");
179  $errordata->setKeyName($this->lng->txt('text_wrong'));
180  $errordata->setValueName($this->lng->txt('text_correct'));
181  $errordata->setValues($this->object->getErrorData());
182  $form->addItem($errordata);
183 
184  // points for wrong selection
185  $points_wrong = new ilNumberInputGUI($this->lng->txt("points_wrong"), "points_wrong");
186  $points_wrong->allowDecimals(true);
187  $points_wrong->setMaxValue(0);
188  $points_wrong->setMaxvalueShouldBeLess(true);
189  $points_wrong->setValue($this->object->getPointsWrong());
190  $points_wrong->setInfo($this->lng->txt("points_wrong_info"));
191  $points_wrong->setSize(6);
192  $points_wrong->setRequired(true);
193  $form->addItem($points_wrong);
194  return $form;
195  }
196 
202  {
203  // errortext
204  $errortext = new ilTextAreaInputGUI($this->lng->txt("errortext"), "errortext");
205  $errortext->setValue($this->object->getErrorText());
206  $errortext->setRequired(true);
207  $errortext->setInfo($this->lng->txt("errortext_info"));
208  $errortext->setRows(10);
209  $errortext->setCols(80);
210  $form->addItem($errortext);
211 
212  if (!$this->object->getSelfAssessmentEditingMode()) {
213  // textsize
214  $textsize = new ilNumberInputGUI($this->lng->txt("textsize"), "textsize");
215  $textsize->setValue($this->object->getTextSize() ?? 100.0);
216  $textsize->setInfo($this->lng->txt("textsize_errortext_info"));
217  $textsize->setSize(6);
218  $textsize->setSuffix("%");
219  $textsize->setMinValue(10);
220  $textsize->setRequired(true);
221  $form->addItem($textsize);
222  }
223  return $form;
224  }
225 
229  public function analyze(): void
230  {
231  $this->writePostData(true);
232  $this->saveTaxonomyAssignments();
233  $this->object->setErrorsFromParsedErrorText();
234  $this->editQuestion();
235  }
236 
252  public function getSolutionOutput(
253  $active_id,
254  $pass = null,
255  $graphicalOutput = false,
256  $result_output = false,
257  $show_question_only = true,
258  $show_feedback = false,
259  $show_correct_solution = false,
260  $show_manual_scoring = false,
261  $show_question_text = true
262  ): string {
263  $user_solutions = $this->getUsersSolutionFromPreviewOrDatabase((int) $active_id, $pass);
264  $show_inline_feedback = false;
265  return $this->renderSolutionOutput(
266  $user_solutions,
267  $active_id,
268  $pass,
269  $graphicalOutput,
270  $result_output,
271  $show_question_only,
272  $show_feedback,
273  $show_correct_solution,
274  $show_manual_scoring,
275  $show_question_text,
276  false,
277  $show_inline_feedback,
278  );
279  }
280 
281  public function renderSolutionOutput(
282  mixed $user_solutions,
283  int $active_id,
284  ?int $pass,
285  bool $graphical_output = false,
286  bool $result_output = false,
287  bool $show_question_only = true,
288  bool $show_feedback = false,
289  bool $show_correct_solution = false,
290  bool $show_manual_scoring = false,
291  bool $show_question_text = true,
292  bool $show_autosave_title = false,
293  bool $show_inline_feedback = false,
294  ): ?string {
295  // get the solution of the user for the active pass or from the last pass if allowed
296  $template = new ilTemplate("tpl.il_as_qpl_errortext_output_solution.html", true, true, "Modules/TestQuestionPool");
297 
298  $selections = [
299  'user' => $user_solutions ?
300  $user_solutions :
301  $this->getUsersSolutionFromPreviewOrDatabase((int) $active_id, $pass)
302  ];
303 
304 
305  $selections['best'] = $this->object->getBestSelection();
306 
307  $reached_points = $this->object->getPoints();
308  if ($active_id > 0 && !$show_correct_solution) {
309  $reached_points = $this->object->getReachedPoints($active_id, $pass);
310  }
311 
312  if ($result_output === true) {
313  $resulttext = ($reached_points == 1) ? "(%s " . $this->lng->txt("point") . ")" : "(%s " . $this->lng->txt("points") . ")";
314  $template->setVariable("RESULT_OUTPUT", sprintf($resulttext, $reached_points));
315  }
316 
317  if ($this->object->getTextSize() >= 10) {
318  $template->setVariable("STYLE", " style=\"font-size: " . $this->object->getTextSize() . "%;\"");
319  }
320 
321  if ($show_question_text === true) {
322  $template->setVariable("QUESTIONTEXT", $this->object->getQuestionForHTMLOutput());
323  }
324 
325  $correctness_icons = [
326  'correct' => $this->generateCorrectnessIconsForCorrectness(self::CORRECTNESS_OK),
327  'not_correct' => $this->generateCorrectnessIconsForCorrectness(self::CORRECTNESS_NOT_OK)
328  ];
329  $errortext = $this->object->assembleErrorTextOutput($selections, $graphical_output, $show_correct_solution, false, $correctness_icons);
330 
331  $template->setVariable("ERRORTEXT", $errortext);
332  $questionoutput = $template->get();
333 
334  $solutiontemplate = new ilTemplate("tpl.il_as_tst_solution_output.html", true, true, "Modules/TestQuestionPool");
335 
336  $feedback = '';
337  if ($show_feedback) {
338  if (!$this->isTestPresentationContext()) {
339  $fb = $this->getGenericFeedbackOutput((int) $active_id, $pass);
340  $feedback .= mb_strlen($fb) ? $fb : '';
341  }
342 
343  $fb = $this->getSpecificFeedbackOutput(array());
344  $feedback .= mb_strlen($fb) ? $fb : '';
345  }
346  if (mb_strlen($feedback)) {
347  $cssClass = (
348  $this->hasCorrectSolution($active_id, $pass) ?
350  );
351 
352  $solutiontemplate->setVariable("ILC_FB_CSS_CLASS", $cssClass);
353  $solutiontemplate->setVariable("FEEDBACK", ilLegacyFormElementsUtil::prepareTextareaOutput($feedback, true));
354  }
355 
356  $solutiontemplate->setVariable("SOLUTION_OUTPUT", $questionoutput);
357 
358  $solutionoutput = $solutiontemplate->get();
359  if (!$show_question_only) {
360  // get page object output
361  $solutionoutput = $this->getILIASPage($solutionoutput);
362  }
363  return $solutionoutput;
364  }
365 
366  public function getPreview($show_question_only = false, $showInlineFeedback = false): string
367  {
368  $selections = [
369  'user' => $this->getUsersSolutionFromPreviewOrDatabase()
370  ];
371 
372  return $this->generateQuestionOutput($selections, $show_question_only);
373  }
374 
375  public function getTestOutput(
376  $active_id,
377  $pass,
378  $is_postponed = false,
379  $use_post_solutions = false,
380  $show_feedback = false
381  ): string {
382  $selections = [
383  'user' => $this->getUsersSolutionFromPreviewOrDatabase($active_id, $pass)
384  ];
385 
386  return $this->outQuestionPage(
387  '',
388  $is_postponed,
389  $active_id,
390  $this->generateQuestionOutput($selections, false)
391  );
392  }
393 
394  private function generateQuestionOutput($selections, $show_question_only): string
395  {
396  $template = new ilTemplate("tpl.il_as_qpl_errortext_output.html", true, true, "Modules/TestQuestionPool");
397 
398  if ($this->object->getTextSize() >= 10) {
399  $template->setVariable("STYLE", " style=\"font-size: " . $this->object->getTextSize() . "%;\"");
400  }
401  $template->setVariable("QUESTIONTEXT", $this->object->getQuestionForHTMLOutput());
402  $errortext = $this->object->assembleErrorTextOutput($selections);
403  if ($this->getTargetGuiClass() !== null) {
404  $this->ctrl->setParameterByClass($this->getTargetGuiClass(), 'errorvalue', '');
405  }
406  $template->setVariable("ERRORTEXT", $errortext);
407  $template->setVariable("ERRORTEXT_ID", "qst_" . $this->object->getId());
408  $template->setVariable("ERRORTEXT_VALUE", join(',', $selections['user']));
409 
410  $this->tpl->addOnLoadCode('il.test.player.errortext.init()');
411  $this->tpl->addJavascript('./Modules/TestQuestionPool/templates/default/errortext.js');
412  $questionoutput = $template->get();
413 
414  if ($show_question_only) {
415  return $questionoutput;
416  }
417 
418  return $this->getILIASPage($questionoutput);
419  }
420 
421  private function getUsersSolutionFromPreviewOrDatabase(int $active_id = 0, ?int $pass = null): array
422  {
423  if (is_object($this->getPreviewSession())) {
424  return (array) $this->getPreviewSession()->getParticipantsSolution();
425  }
426 
427  if ($active_id > 0) {
428  $selections = [];
429  $solutions = $this->object->getSolutionValues($active_id, $pass ?? 0, true);
430  foreach ($solutions as $solution) {
431  $selections[] = $solution['value1'];
432  }
433  return $selections;
434  }
435 
436  return [];
437  }
438 
439  public function getSpecificFeedbackOutput(array $user_solution): string
440  {
441  if (!$this->object->feedbackOBJ->specificAnswerFeedbackExists()) {
442  return '';
443  }
444 
445  $feedback = '<table class="test_specific_feedback"><tbody>';
446  $elements = $this->object->getErrorData();
447  foreach ($elements as $index => $element) {
448  $feedback .= '<tr>';
449  $feedback .= '<td class="text-nowrap">' . $index . '. ' . $element->getTextWrong() . ':</td>';
450  $feedback .= '<td>' . $this->object->feedbackOBJ->getSpecificAnswerFeedbackTestPresentation(
451  $this->object->getId(),
452  0,
453  $index
454  ) . '</td>';
455 
456  $feedback .= '</tr>';
457  }
458  $feedback .= '</tbody></table>';
459 
460  return ilLegacyFormElementsUtil::prepareTextareaOutput($feedback, true);
461  }
462 
473  {
474  return [];
475  }
476 
487  {
488  return [];
489  }
490 
497  public function getAggregatedAnswersView(array $relevant_answers): string
498  {
499  $errortext = $this->object->getErrorText();
500 
501  $passdata = []; // Regroup answers into units of passes.
502  foreach ($relevant_answers as $answer_chosen) {
503  $passdata[$answer_chosen['active_fi'] . '-' . $answer_chosen['pass']][$answer_chosen['value2']][] = $answer_chosen['value1'];
504  }
505 
506  $html = '';
507  foreach ($passdata as $key => $pass) {
508  $passdata[$key] = $this->object->createErrorTextOutput($pass);
509  $html .= $passdata[$key] . '<hr /><br />';
510  }
511 
512  return $html;
513  }
514 
515  public function getAnswersFrequency($relevant_answers, $question_index): array
516  {
517  $answers_by_active_and_pass = [];
518 
519  foreach ($relevant_answers as $row) {
520  $key = $row['active_fi'] . ':' . $row['pass'];
521 
522  if (!isset($answers_by_active_and_pass[$key])) {
523  $answers_by_active_and_pass[$key] = ['user' => []];
524  }
525 
526  $answers_by_active_and_pass[$key]['user'][] = $row['value1'];
527  }
528 
529  $answers = [];
530 
531  foreach ($answers_by_active_and_pass as $answer) {
532  $error_text = '<div class="errortext">' . $this->object->assembleErrorTextOutput($answer) . '</div>';
533  $error_text_hashed = md5($error_text);
534 
535  if (!isset($answers[$error_text_hashed])) {
536  $answers[$error_text_hashed] = [
537  'answer' => $error_text, 'frequency' => 0
538  ];
539  }
540 
541  $answers[$error_text_hashed]['frequency']++;
542  }
543 
544  return array_values($answers);
545  }
546 
548  {
549  $errordata = new ilAssErrorTextCorrectionsInputGUI($this->lng->txt('errors'), 'errordata');
550  $errordata->setKeyName($this->lng->txt('text_wrong'));
551  $errordata->setValueName($this->lng->txt('text_correct'));
552  $errordata->setValues($this->object->getErrorData());
553  $form->addItem($errordata);
554 
555  // points for wrong selection
556  $points_wrong = new ilNumberInputGUI($this->lng->txt('points_wrong'), 'points_wrong');
557  $points_wrong->allowDecimals(true);
558  $points_wrong->setMaxValue(0);
559  $points_wrong->setMaxvalueShouldBeLess(true);
560  $points_wrong->setValue($this->object->getPointsWrong());
561  $points_wrong->setInfo($this->lng->txt('points_wrong_info'));
562  $points_wrong->setSize(6);
563  $points_wrong->setRequired(true);
564  $form->addItem($points_wrong);
565  }
566 
571  {
572  $existing_errordata = $this->object->getErrorData();
573  $this->object->flushErrorData();
574  $new_errordata = $this->request->raw('errordata');
575  $errordata = [];
576  foreach ($new_errordata['points'] as $index => $points) {
577  $errordata[$index] = $existing_errordata[$index]->withPoints(
578  (float) str_replace(',', '.', $points)
579  );
580  }
581  $this->object->setErrorData($errordata);
582  $this->object->setPointsWrong((float) str_replace(',', '.', $form->getInput('points_wrong')));
583  }
584 }
generateQuestionOutput($selections, $show_question_only)
hasCorrectSolution($activeId, $passIndex)
generateCorrectnessIconsForCorrectness(int $correctness)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
populateQuestionSpecificFormPart(ilPropertyFormGUI $form)
populateCorrectionsFormProperties(ilPropertyFormGUI $form)
addBasicQuestionFormProperties(ilPropertyFormGUI $form)
populateAnswerSpecificFormPart(ilPropertyFormGUI $form)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
getAfterParticipationSuppressionAnswerPostVars()
Returns a list of postvars which will be suppressed in the form output when used in scoring adjustmen...
populateTaxonomyFormSection(ilPropertyFormGUI $form)
getInput(string $a_post_var, bool $ensureValidation=true)
Returns the input of an item, if item provides getInput method and as fallback the value of the HTTP-...
writeQuestionSpecificPostData(ilPropertyFormGUI $form)
Extracts the question specific values from $_POST and applies them to the data object.
getTestOutput( $active_id, $pass, $is_postponed=false, $use_post_solutions=false, $show_feedback=false)
addQuestionFormCommandButtons(ilPropertyFormGUI $form)
global $DIC
Definition: feed.php:28
getAfterParticipationSuppressionQuestionPostVars()
Returns a list of postvars which will be suppressed in the form output when used in scoring adjustmen...
__construct(VocabulariesInterface $vocabularies)
setErrorMessage(string $errormessage)
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,)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
string $key
Consumer key/client ID value.
Definition: System.php:193
saveCorrectionsFormProperties(ilPropertyFormGUI $form)
writePostData(bool $always=false)
{}
getSpecificFeedbackOutput(array $user_solution)
getPreview($show_question_only=false, $showInlineFeedback=false)
writeAnswerSpecificPostData(ilPropertyFormGUI $form)
Extracts the answer specific values from $_POST and applies them to the data object.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
Basic GUI class for assessment questions.
analyze()
Parse the error text.
restructurePostDataForSaving(array $post)
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)
Get the question solution output The getSolutionOutput() method is used to print either the user&#39;s pa...
editQuestion($checkonly=false)
Creates an output of the edit form for the question.
getILIASPage(string $html="")
Returns the ILIAS Page around a question.
outQuestionPage($a_temp_var, $a_postponed=false, $active_id="", $html="", $inlineFeedbackEnabled=false)
This class represents a text area property in a property form.
$id
plugin.php for ilComponentBuildPluginInfoObjectiveTest::testAddPlugins
Definition: plugin.php:23
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
getUsersSolutionFromPreviewOrDatabase(int $active_id=0, ?int $pass=null)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
getAnswersFrequency($relevant_answers, $question_index)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
$post
Definition: ltitoken.php:49
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...
getGenericFeedbackOutput(int $active_id, ?int $pass)
getAggregatedAnswersView(array $relevant_answers)
Returns an html string containing a question specific representation of the answers so far given in t...