ILIAS  release_9 Revision v9.13-25-g2c18ec4c24f
class.assMatchingQuestionGUI.php
Go to the documentation of this file.
1 <?php
2 
19 require_once './Modules/Test/classes/inc.AssessmentConstants.php';
20 
36 {
45  public function __construct($id = -1)
46  {
48  $this->object = new assMatchingQuestion();
49  $this->setErrorMessage($this->lng->txt("msg_form_save_error"));
50  if ($id >= 0) {
51  $this->object->loadFromDb($id);
52  }
53  }
54 
58  protected function writePostData(bool $always = false): int
59  {
60  $hasErrors = (!$always) ? $this->editQuestion(true) : false;
61  if (!$hasErrors) {
65  $this->saveTaxonomyAssignments();
66  return 0;
67  }
68  return 1;
69  }
70 
71  public function writeAnswerSpecificPostData(ilPropertyFormGUI $form): void
72  {
73  // Delete all existing answers and create new answers from the form data
74  $this->object->flushMatchingPairs();
75  $this->object->flushTerms();
76  $this->object->flushDefinitions();
77 
78  $uploads = $this->request->getProcessedUploads();
79  $allowed_mime_types = ['image/jpeg', 'image/png', 'image/gif'];
80 
81  if ($this->request->isset('terms')) {
82  $answers = $this->request->raw('terms')['answer'] ?? [];
83  $terms_image_names = $this->request->raw('terms')['imagename'] ?? [];
84  $terms_identifiers = $this->request->raw('terms')['identifier'] ?? [];
85 
86  foreach ($answers as $index => $answer) {
87  $filename = $terms_image_names[$index] ?? '';
88 
89  $upload_tmp_name = $this->request->getUploadFilename(['terms', 'image'], $index);
90 
91  if (isset($uploads[$upload_tmp_name]) && $uploads[$upload_tmp_name]->isOk() &&
92  in_array($uploads[$upload_tmp_name]->getMimeType(), $allowed_mime_types)) {
93  $filename = '';
94  $name = $uploads[$upload_tmp_name]->getName();
95  if ($this->object->setImageFile(
96  $uploads[$upload_tmp_name]->getPath(),
97  $this->object->getEncryptedFilename($name)
98  )) {
99  $filename = $this->object->getEncryptedFilename($name);
100  }
101  }
102  // @PHP8-CR: There seems to be a bigger issue lingering here and won't suppress / "quickfix" this but
103  // postpone further analysis, eventually involving T&A TechSquad (see also remark in assMatchingQuestionGUI
104  $this->object->addTerm(
106  ilUtil::stripSlashes(htmlentities($answer)),
107  $filename,
108  $terms_identifiers[$index] ?? 0
109  )
110  );
111  }
112  }
113 
114  if ($this->request->isset('definitions')) {
115  $answers = $this->request->raw('definitions')['answer'] ?? [];
116  $definitions_image_names = $this->request->raw('definitions')['imagename'] ?? [];
117  $definitions_identifiers = $this->request->raw('definitions')['identifier'] ?? [];
118 
119  foreach ($answers as $index => $answer) {
120  $filename = $definitions_image_names[$index] ?? '';
121 
122  $upload_tmp_name = $this->request->getUploadFilename(['definitions', 'image'], $index);
123 
124  if (isset($uploads[$upload_tmp_name]) && $uploads[$upload_tmp_name]->isOk() &&
125  in_array($uploads[$upload_tmp_name]->getMimeType(), $allowed_mime_types)) {
126  $filename = '';
127  $name = $uploads[$upload_tmp_name]->getName();
128  if ($this->object->setImageFile(
129  $uploads[$upload_tmp_name]->getPath(),
130  $this->object->getEncryptedFilename($name)
131  )) {
132  $filename = $this->object->getEncryptedFilename($name);
133  }
134  }
135 
136  $this->object->addDefinition(
138  ilUtil::stripSlashes(htmlentities($answer)),
139  $filename,
140  $definitions_identifiers[$index] ?? 0
141  )
142  );
143  }
144  }
145 
146  if ($this->request->isset('pairs')) {
147  $points_of_pairs = $this->request->raw('pairs')['points'] ?? [];
148  $pair_terms = $this->request->raw('pairs')['term'] ?? [];
149  $pair_definitions = $this->request->raw('pairs')['definition'] ?? [];
150 
151  foreach ($points_of_pairs as $index => $points) {
152  $term_id = $pair_terms[$index] ?? 0;
153  $definition_id = $pair_definitions[$index] ?? 0;
154  $this->object->addMatchingPair(
155  $this->object->getTermWithIdentifier($term_id),
156  $this->object->getDefinitionWithIdentifier($definition_id),
157  (float) str_replace(',', '.', $points)
158  );
159  }
160  }
161  }
162 
164  {
165  if (!$this->object->getSelfAssessmentEditingMode()) {
166  $this->object->setShuffle($_POST["shuffle"] ?? '0');
167  $this->object->setShuffleMode($_POST["shuffle"] ?? '0');
168  } else {
169  $this->object->setShuffle(1);
170  $this->object->setShuffleMode(1);
171  }
172  $this->object->setThumbGeometry($_POST["thumb_geometry"] ?? 0);
173  $this->object->setMatchingMode($_POST['matching_mode']);
174  }
175 
176  public function uploadterms(): void
177  {
178  $this->writePostData(true);
179  $this->editQuestion();
180  }
181 
182  public function removeimageterms(): void
183  {
184  $this->writePostData(true);
185  $position = key($_POST['cmd']['removeimageterms']);
186  $this->object->removeTermImage($position);
187  $this->editQuestion();
188  }
189 
190  public function uploaddefinitions(): void
191  {
192  $this->writePostData(true);
193  $this->editQuestion();
194  }
195 
196  public function removeimagedefinitions(): void
197  {
198  $this->writePostData(true);
199  $position = key($_POST['cmd']['removeimagedefinitions']);
200  $this->object->removeDefinitionImage($position);
201  $this->editQuestion();
202  }
203 
204  public function addterms(): void
205  {
206  $this->writePostData(true);
207  $position = key($_POST["cmd"]["addterms"]);
208  $this->object->insertTerm($position + 1);
209  $this->editQuestion();
210  }
211 
212  public function removeterms(): void
213  {
214  $this->writePostData(true);
215  $position = key($_POST["cmd"]["removeterms"]);
216  $this->object->deleteTerm($position);
217  $this->editQuestion();
218  }
219 
220  public function adddefinitions(): void
221  {
222  $this->writePostData(true);
223  $position = key($_POST["cmd"]["adddefinitions"]);
224  $this->object->insertDefinition($position + 1);
225  $this->editQuestion();
226  }
227 
228  public function removedefinitions(): void
229  {
230  $this->writePostData(true);
231  $position = key($_POST["cmd"]["removedefinitions"]);
232  $this->object->deleteDefinition($position);
233  $this->editQuestion();
234  }
235 
236  public function addpairs(): void
237  {
238  $this->writePostData(true);
239  $position = key($_POST["cmd"]["addpairs"]);
240  $this->object->insertMatchingPair($position + 1);
241  $this->editQuestion();
242  }
243 
244  public function removepairs(): void
245  {
246  $this->writePostData(true);
247  $position = key($_POST["cmd"]["removepairs"]);
248  $this->object->deleteMatchingPair($position);
249  $this->editQuestion();
250  }
251 
252  public function editQuestion($checkonly = false): bool
253  {
254  $save = $this->isSaveCommand();
255  $this->getQuestionTemplate();
256 
257  $form = new ilPropertyFormGUI();
258  $this->editForm = $form;
259 
260  $form->setFormAction($this->ctrl->getFormAction($this));
261  $form->setTitle($this->outQuestionType());
262  $form->setMultipart(true);
263  $form->setTableWidth("100%");
264  $form->setId("matching");
265 
266  $this->addBasicQuestionFormProperties($form);
267  $this->populateQuestionSpecificFormPart($form);
268  $this->populateAnswerSpecificFormPart($form);
269  $this->populateTaxonomyFormSection($form);
270  $this->addQuestionFormCommandButtons($form);
271 
272  $errors = false;
273  if ($save) {
274  $form->setValuesByPost();
275  $errors = !$form->checkInput();
276  $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
277  if (!$errors && !$this->isValidTermAndDefinitionAmount($form) && !$this->object->getSelfAssessmentEditingMode()) {
278  $errors = true;
279  $terms = $form->getItemByPostVar('terms');
280  $terms->setAlert($this->lng->txt("msg_number_of_terms_too_low"));
281  $this->tpl->setOnScreenMessage('failure', $this->lng->txt('form_input_not_valid'));
282  }
283  if ($errors) {
284  $checkonly = false;
285  }
286  }
287 
288  if (!$checkonly) {
289  $this->tpl->setVariable("QUESTION_DATA", $form->getHTML());
290  }
291  return $errors;
292  }
293 
294  private function isDefImgUploadCommand(): bool
295  {
296  return $this->ctrl->getCmd() == 'uploaddefinitions';
297  }
298 
299  private function isTermImgUploadCommand(): bool
300  {
301  return $this->ctrl->getCmd() == 'uploadterms';
302  }
303 
311  private function isValidTermAndDefinitionAmount(ilPropertyFormGUI $form): bool
312  {
313  $matchingMode = $form->getItemByPostVar('matching_mode')->getValue();
314 
315  if ($matchingMode == assMatchingQuestion::MATCHING_MODE_N_ON_N) {
316  return true;
317  }
318 
319  $numTerms = count($form->getItemByPostVar('terms')->getValues());
320  $numDefinitions = count($form->getItemByPostVar('definitions')->getValues());
321 
322  if ($numTerms >= $numDefinitions) {
323  return true;
324  }
325 
326  return false;
327  }
328 
330  {
331  $definitions = new ilMatchingWizardInputGUI($this->lng->txt("definitions"), "definitions");
332  if ($this->object->getSelfAssessmentEditingMode()) {
333  $definitions->setHideImages(true);
334  }
335 
336  $stripHtmlEntitesFromValues = function (assAnswerMatchingTerm $value) {
337  return $value->withText(html_entity_decode($value->getText()));
338  };
339 
340  $definitions->setRequired(true);
341  $definitions->setQuestionObject($this->object);
342  $definitions->setTextName($this->lng->txt('definition_text'));
343  $definitions->setImageName($this->lng->txt('definition_image'));
344  if (!count($this->object->getDefinitions())) {
345  $this->object->addDefinition(new assAnswerMatchingDefinition());
346  }
347  $definitionvalues = array_map($stripHtmlEntitesFromValues, $this->object->getDefinitions());
348  $definitions->setValues($definitionvalues);
349  if ($this->isDefImgUploadCommand()) {
350  $definitions->checkInput();
351  }
352  $form->addItem($definitions);
353 
354  $terms = new ilMatchingWizardInputGUI($this->lng->txt("terms"), "terms");
355  if ($this->object->getSelfAssessmentEditingMode()) {
356  $terms->setHideImages(true);
357  }
358  $terms->setRequired(true);
359  $terms->setQuestionObject($this->object);
360  $terms->setTextName($this->lng->txt('term_text'));
361  $terms->setImageName($this->lng->txt('term_image'));
362 
363  if (0 === count($this->object->getTerms())) {
364  // @PHP8-CR: If you look above, how $this->object->addDefinition does in fact take an object, I take this
365  // issue as an indicator for a bigger issue and won't suppress / "quickfix" this but postpone further
366  // analysis, eventually involving T&A TechSquad
367  $this->object->addTerm(new assAnswerMatchingTerm());
368  }
369  $termvalues = array_map($stripHtmlEntitesFromValues, $this->object->getTerms());
370  $terms->setValues($termvalues);
371  if ($this->isTermImgUploadCommand()) {
372  $terms->checkInput();
373  }
374  $form->addItem($terms);
375 
376  $pairs = new ilMatchingPairWizardInputGUI($this->lng->txt('matching_pairs'), 'pairs');
377  $pairs->setRequired(true);
378  $pairs->setTerms($this->object->getTerms());
379  $pairs->setDefinitions($this->object->getDefinitions());
380  if (count($this->object->getMatchingPairs()) == 0) {
381  $this->object->addMatchingPair($termvalues[0], $definitionvalues[0], 0);
382  //$this->object->addMatchingPair(new assAnswerMatchingPair($termvalues[0], $definitionvalues[0], 0));
383  }
384  $pairs->setPairs($this->object->getMatchingPairs());
385  $form->addItem($pairs);
386 
387  return $form;
388  }
389 
391  {
392  // Edit mode
393  $hidden = new ilHiddenInputGUI("matching_type");
394  $hidden->setValue('');
395  $form->addItem($hidden);
396 
397  if (!$this->object->getSelfAssessmentEditingMode()) {
398  // shuffle
399  $shuffle = new ilSelectInputGUI($this->lng->txt("shuffle_answers"), "shuffle");
400  $shuffle_options = array(
401  0 => $this->lng->txt("no"),
402  1 => $this->lng->txt("matching_shuffle_terms_definitions"),
403  2 => $this->lng->txt("matching_shuffle_terms"),
404  3 => $this->lng->txt("matching_shuffle_definitions")
405  );
406  $shuffle->setOptions($shuffle_options);
407  $shuffle->setValue($this->object->getShuffleMode());
408  $shuffle->setRequired(false);
409  $form->addItem($shuffle);
410 
411  $geometry = new ilNumberInputGUI($this->lng->txt('thumb_size'), 'thumb_geometry');
412  $geometry->setValue($this->object->getThumbGeometry());
413  $geometry->setRequired(true);
414  $geometry->setMaxLength(6);
415  $geometry->setMinValue($this->object->getMinimumThumbSize());
416  $geometry->setMaxValue($this->object->getMaximumThumbSize());
417  $geometry->setSize(6);
418  $geometry->setInfo($this->lng->txt('thumb_size_info'));
419  $form->addItem($geometry);
420  }
421 
422  // Matching Mode
423  $mode = new ilRadioGroupInputGUI($this->lng->txt('qpl_qst_inp_matching_mode'), 'matching_mode');
424  $mode->setRequired(true);
425 
426  $modeONEonONE = new ilRadioOption(
427  $this->lng->txt('qpl_qst_inp_matching_mode_one_on_one'),
429  );
430  $mode->addOption($modeONEonONE);
431 
432  $modeALLonALL = new ilRadioOption(
433  $this->lng->txt('qpl_qst_inp_matching_mode_all_on_all'),
435  );
436  $mode->addOption($modeALLonALL);
437 
438  $mode->setValue($this->object->getMatchingMode());
439 
440  $form->addItem($mode);
441  return $form;
442  }
443 
456  public function getSolutionOutput(
457  $active_id,
458  $pass = null,
459  $graphicalOutput = false,
460  $result_output = false,
461  $show_question_only = true,
462  $show_feedback = false,
463  $show_correct_solution = false,
464  $show_manual_scoring = false,
465  $show_question_text = true
466  ): string {
467 
468  $solutions = array();
469  if (($active_id > 0) && (!$show_correct_solution)) {
470  $solutions = $this->object->getSolutionValues($active_id, $pass);
471  } else {
472  foreach ($this->object->getMaximumScoringMatchingPairs() as $pair) {
473  $solutions[] = array(
474  "value1" => $pair->getTerm()->getIdentifier(),
475  "value2" => $pair->getDefinition()->getIdentifier(),
476  'points' => $pair->getPoints()
477  );
478  }
479  }
480 
481  $user_solutions = $solutions;
482  $show_inline_feedback = false;
483  return $this->renderSolutionOutput(
484  $user_solutions,
485  $active_id,
486  $pass,
487  $graphicalOutput,
488  $result_output,
489  $show_question_only,
490  $show_feedback,
491  $show_correct_solution,
492  $show_manual_scoring,
493  $show_question_text,
494  false,
495  $show_inline_feedback,
496  );
497  }
498 
499  public function renderSolutionOutput(
500  mixed $user_solutions,
501  int $active_id,
502  ?int $pass,
503  bool $graphical_output = false,
504  bool $result_output = false,
505  bool $show_question_only = true,
506  bool $show_feedback = false,
507  bool $show_correct_solution = false,
508  bool $show_manual_scoring = false,
509  bool $show_question_text = true,
510  bool $show_autosave_title = false,
511  bool $show_inline_feedback = false,
512  ): ?string {
513  $solutions = $user_solutions;
514 
515  $template = new ilTemplate("tpl.il_as_qpl_matching_output_solution.html", true, true, "Modules/TestQuestionPool");
516  $solutiontemplate = new ilTemplate("tpl.il_as_tst_solution_output.html", true, true, "Modules/TestQuestionPool");
517  $i = 0;
518 
519  foreach ($solutions as $solution) {
520  $definition = $this->object->getDefinitionWithIdentifier($solution['value2']);
521  $term = $this->object->getTermWithIdentifier($solution['value1']);
522  $points = $solution['points'];
523 
524  if (is_object($definition)) {
525  if (strlen($definition->getPicture())) {
526  if (strlen($definition->getText())) {
527  $template->setCurrentBlock('definition_image_text');
528  $template->setVariable(
529  "TEXT_DEFINITION",
530  ilLegacyFormElementsUtil::prepareFormOutput($definition->getText())
531  );
532  $template->parseCurrentBlock();
533  }
534 
535  $answerImageSrc = ilWACSignedPath::signFile(
536  $this->object->getImagePathWeb() . $this->object->getThumbPrefix() . $definition->getPicture()
537  );
538 
539  $template->setCurrentBlock('definition_image');
540  $template->setVariable('ANSWER_IMAGE_URL', $answerImageSrc);
541  $template->setVariable(
542  'ANSWER_IMAGE_ALT',
543  (strlen($definition->getText())) ? ilLegacyFormElementsUtil::prepareFormOutput(
544  $definition->getText()
545  ) : ilLegacyFormElementsUtil::prepareFormOutput($definition->getPicture())
546  );
547  $template->setVariable(
548  'ANSWER_IMAGE_TITLE',
549  (strlen($definition->getText())) ? ilLegacyFormElementsUtil::prepareFormOutput(
550  $definition->getText()
551  ) : ilLegacyFormElementsUtil::prepareFormOutput($definition->getPicture())
552  );
553  $template->setVariable('URL_PREVIEW', $this->object->getImagePathWeb() . $definition->getPicture());
554  $template->setVariable("TEXT_PREVIEW", $this->lng->txt('preview'));
555  $template->setVariable("IMG_PREVIEW", ilUtil::getImagePath('media/enlarge.svg'));
556  $template->parseCurrentBlock();
557  } else {
558  $template->setCurrentBlock('definition_text');
559  $template->setVariable("DEFINITION", ilLegacyFormElementsUtil::prepareTextareaOutput($definition->getText(), true));
560  $template->parseCurrentBlock();
561  }
562  }
563  if (is_object($term)) {
564  if (strlen($term->getPicture())) {
565  if (strlen($term->getText())) {
566  $template->setCurrentBlock('term_image_text');
567  $template->setVariable("TEXT_TERM", ilLegacyFormElementsUtil::prepareFormOutput($term->getText()));
568  $template->parseCurrentBlock();
569  }
570 
571  $answerImageSrc = ilWACSignedPath::signFile(
572  $this->object->getImagePathWeb() . $this->object->getThumbPrefix() . $term->getPicture()
573  );
574 
575  $template->setCurrentBlock('term_image');
576  $template->setVariable('ANSWER_IMAGE_URL', $answerImageSrc);
577  $template->setVariable(
578  'ANSWER_IMAGE_ALT',
579  (strlen($term->getText())) ? ilLegacyFormElementsUtil::prepareFormOutput(
580  $term->getText()
581  ) : ilLegacyFormElementsUtil::prepareFormOutput($term->getPicture())
582  );
583  $template->setVariable(
584  'ANSWER_IMAGE_TITLE',
585  (strlen($term->getText())) ? ilLegacyFormElementsUtil::prepareFormOutput(
586  $term->getText()
587  ) : ilLegacyFormElementsUtil::prepareFormOutput($term->getPicture())
588  );
589  $template->setVariable('URL_PREVIEW', $this->object->getImagePathWeb() . $term->getPicture());
590  $template->setVariable("TEXT_PREVIEW", $this->lng->txt('preview'));
591  $template->setVariable("IMG_PREVIEW", ilUtil::getImagePath('media/enlarge.svg'));
592  $template->parseCurrentBlock();
593  } else {
594  $template->setCurrentBlock('term_text');
595  $template->setVariable("TERM", ilLegacyFormElementsUtil::prepareTextareaOutput($term->getText(), true));
596  $template->parseCurrentBlock();
597  }
598  $i++;
599  }
600  if (($active_id > 0) && (!$show_correct_solution)) {
601  if ($graphical_output) {
602  // output of ok/not ok icons for user entered solutions
603  $ok = false;
604  foreach ($this->object->getMatchingPairs() as $pair) {
605  if ($this->isCorrectMatching($pair, $definition, $term)) {
606  $ok = true;
607  }
608  }
609 
610  $correctness_icon = $this->generateCorrectnessIconsForCorrectness(self::CORRECTNESS_NOT_OK);
611  if ($ok) {
612  $correctness_icon = $this->generateCorrectnessIconsForCorrectness(self::CORRECTNESS_OK);
613  }
614  $template->setCurrentBlock("icon_ok");
615  $template->setVariable("ICON_OK", $correctness_icon);
616  $template->parseCurrentBlock();
617  }
618  }
619 
620  if ($result_output) {
621  $resulttext = ($points == 1) ? "(%s " . $this->lng->txt("point") . ")" : "(%s " . $this->lng->txt("points") . ")";
622  $template->setCurrentBlock("result_output");
623  $template->setVariable("RESULT_OUTPUT", sprintf($resulttext, $points));
624  $template->parseCurrentBlock();
625  }
626 
627  $template->setCurrentBlock("row");
628  $template->setVariable("TEXT_MATCHES", $this->lng->txt("matches"));
629  $template->parseCurrentBlock();
630  }
631 
632  if ($show_question_text == true) {
633  $template->setVariable("QUESTIONTEXT", $this->object->getQuestionForHTMLOutput());
634  }
635 
636  $questionoutput = $template->get();
637 
638  $feedback = '';
639  if ($show_feedback) {
640  if (!$this->isTestPresentationContext()) {
641  $fb = $this->getGenericFeedbackOutput((int) $active_id, $pass);
642  $feedback .= strlen($fb) ? $fb : '';
643  }
644 
645  $fb = $this->getSpecificFeedbackOutput(array());
646  $feedback .= strlen($fb) ? $fb : '';
647  }
648  if (strlen($feedback)) {
649  $cssClass = (
650  $this->hasCorrectSolution($active_id, $pass) ?
652  );
653 
654  $solutiontemplate->setVariable("ILC_FB_CSS_CLASS", $cssClass);
655  $solutiontemplate->setVariable("FEEDBACK", ilLegacyFormElementsUtil::prepareTextareaOutput($feedback, true));
656  }
657 
658  $solutiontemplate->setVariable("SOLUTION_OUTPUT", $questionoutput);
659 
660  $solutionoutput = $solutiontemplate->get();
661  if (!$show_question_only) {
662  // get page object output
663  $solutionoutput = $this->getILIASPage($solutionoutput);
664  }
665  return $solutionoutput;
666  }
667 
668  public function getPreview($show_question_only = false, $showInlineFeedback = false): string
669  {
670  $solutions = is_object($this->getPreviewSession()) ? (array) $this->getPreviewSession()->getParticipantsSolution() : array();
671 
672  global $DIC; /* @var ILIAS\DI\Container $DIC */
673  if ($DIC->http()->agent()->isMobile() || $DIC->http()->agent()->isIpad()) {
676  $this->tpl->addJavaScript('./node_modules/@andxor/jquery-ui-touch-punch-fix/jquery.ui.touch-punch.js');
677  }
678  $this->tpl->addJavaScript('Modules/TestQuestionPool/js/ilMatchingQuestion.js');
679  $this->tpl->addOnLoadCode('ilMatchingQuestionInit();');
680  $this->tpl->addCss(ilUtil::getStyleSheetLocation('output', 'test_javascript.css', 'Modules/TestQuestionPool'));
681 
682  $template = new ilTemplate("tpl.il_as_qpl_matching_output.html", true, true, "Modules/TestQuestionPool");
683 
684  foreach ($solutions as $defId => $terms) {
685  foreach ($terms as $termId) {
686  $template->setCurrentBlock("matching_data");
687  $template->setVariable("DEFINITION_ID", $defId);
688  $template->setVariable("TERM_ID", $termId);
689  $template->parseCurrentBlock();
690  }
691  }
692 
693  // shuffle output
694  $terms = $this->object->getTerms();
695  $definitions = $this->object->getDefinitions();
696  switch ($this->object->getShuffleMode()) {
697  case 1:
698  $terms = $this->object->getShuffler()->transform($terms);
699  $definitions = $this->object->getShuffler()->transform(
700  $this->object->getShuffler()->transform($definitions)
701  );
702  break;
703  case 2:
704  $terms = $this->object->getShuffler()->transform($terms);
705  break;
706  case 3:
707  $definitions = $this->object->getShuffler()->transform($definitions);
708  break;
709  }
710 
711  // create definitions
712  $counter = 0;
713  foreach ($definitions as $definition) {
714  if (strlen($definition->getPicture())) {
715  $template->setCurrentBlock("definition_picture");
716  $template->setVariable("DEFINITION_ID", $definition->getIdentifier());
717  $template->setVariable("IMAGE_HREF", $this->object->getImagePathWeb() . $definition->getPicture());
718  $thumbweb = $this->object->getImagePathWeb() . $this->object->getThumbPrefix() . $definition->getPicture();
719  $thumb = $this->object->getImagePath() . $this->object->getThumbPrefix() . $definition->getPicture();
720  if (!@file_exists($thumb)) {
721  $this->object->rebuildThumbnails();
722  }
723  $template->setVariable("THUMBNAIL_HREF", $thumbweb);
724  $template->setVariable("THUMB_ALT", $this->lng->txt("image"));
725  $template->setVariable("THUMB_TITLE", $this->lng->txt("image"));
726  $template->setVariable("TEXT_DEFINITION", (strlen($definition->getText())) ? ilLegacyFormElementsUtil::prepareTextareaOutput($definition->getText(), true, true) : '');
727  $template->setVariable("TEXT_PREVIEW", $this->lng->txt('preview'));
728  $template->setVariable("IMG_PREVIEW", ilUtil::getImagePath('media/enlarge.svg'));
729  $template->parseCurrentBlock();
730  } else {
731  $template->setCurrentBlock("definition_text");
732  $template->setVariable("DEFINITION", ilLegacyFormElementsUtil::prepareTextareaOutput($definition->getText(), true, true));
733  $template->parseCurrentBlock();
734  }
735 
736  $template->setCurrentBlock("droparea");
737  $template->setVariable("ID_DROPAREA", $definition->getIdentifier());
738  $template->setVariable("QUESTION_ID", $this->object->getId());
739  $template->parseCurrentBlock();
740 
741  $template->setCurrentBlock("definition_data");
742  $template->setVariable("DEFINITION_ID", $definition->getIdentifier());
743  $template->parseCurrentBlock();
744  }
745 
746  // create terms
747  $counter = 0;
748  foreach ($terms as $term) {
749  if (strlen($term->getPicture())) {
750  $template->setCurrentBlock("term_picture");
751  $template->setVariable("TERM_ID", $term->getIdentifier());
752  $template->setVariable("IMAGE_HREF", $this->object->getImagePathWeb() . $term->getPicture());
753  $thumbweb = $this->object->getImagePathWeb() . $this->object->getThumbPrefix() . $term->getPicture();
754  $thumb = $this->object->getImagePath() . $this->object->getThumbPrefix() . $term->getPicture();
755  if (!@file_exists($thumb)) {
756  $this->object->rebuildThumbnails();
757  }
758  $template->setVariable("THUMBNAIL_HREF", $thumbweb);
759  $template->setVariable("THUMB_ALT", $this->lng->txt("image"));
760  $template->setVariable("THUMB_TITLE", $this->lng->txt("image"));
761  $template->setVariable("TEXT_PREVIEW", $this->lng->txt('preview'));
762  $template->setVariable("TEXT_TERM", (strlen($term->getText())) ? ilLegacyFormElementsUtil::prepareTextareaOutput($term->getText(), true, true) : '');
763  $template->setVariable("IMG_PREVIEW", ilUtil::getImagePath('media/enlarge.svg'));
764  $template->parseCurrentBlock();
765  } else {
766  $template->setCurrentBlock("term_text");
767  $template->setVariable("TERM_TEXT", ilLegacyFormElementsUtil::prepareTextareaOutput($term->getText(), true, true));
768  $template->parseCurrentBlock();
769  }
770  $template->setCurrentBlock("draggable");
771  $template->setVariable("ID_DRAGGABLE", $term->getIdentifier());
772  $template->parseCurrentBlock();
773 
774  $template->setCurrentBlock("term_data");
775  $template->setVariable("TERM_ID", $term->getIdentifier());
776  $template->parseCurrentBlock();
777  }
778 
779  $template->setVariable('MATCHING_MODE', $this->object->getMatchingMode());
780 
781  $template->setVariable("RESET_BUTTON", $this->lng->txt("reset_terms"));
782 
783  $template->setVariable("QUESTIONTEXT", $this->object->getQuestionForHTMLOutput());
784 
785  $questionoutput = $template->get();
786 
787  if (!$show_question_only) {
788  // get page object output
789  $questionoutput = $this->getILIASPage($questionoutput);
790  }
791 
792  return $questionoutput;
793  }
794 
800  protected function sortDefinitionsBySolution(array $solution, array $definitions): array
801  {
802  $neworder = array();
803  $handled_defintions = array();
804  foreach ($solution as $solution_values) {
805  $id = $solution_values['value2'];
806  if (!isset($handled_defintions[$id])) {
807  $neworder[] = $this->object->getDefinitionWithIdentifier($id);
808  $handled_defintions[$id] = $id;
809  }
810  }
811 
812  foreach ($definitions as $definition) {
816  if (!isset($handled_defintions[$definition->getIdentifier()])) {
817  $neworder[] = $definition;
818  }
819  }
820 
821  return $neworder;
822  }
823 
824  public function getPresentationJavascripts(): array
825  {
826  global $DIC; /* @var ILIAS\DI\Container $DIC */
827 
828  $files = array();
829 
830  if ($DIC->http()->agent()->isMobile() || $DIC->http()->agent()->isIpad()) {
831  $files[] = './node_modules/@andxor/jquery-ui-touch-punch-fix/jquery.ui.touch-punch.js';
832  }
833 
834  $files[] = 'Modules/TestQuestionPool/js/ilMatchingQuestion.js';
835 
836  return $files;
837  }
838 
839  // hey: prevPassSolutions - pass will be always available from now on
840  public function getTestOutput($active_id, $pass, $is_postponed = false, $user_post_solution = false, $inlineFeedback = false): string
841  // hey.
842  {
843  global $DIC; /* @var ILIAS\DI\Container $DIC */
844  if ($DIC->http()->agent()->isMobile() || $DIC->http()->agent()->isIpad()) {
847  $this->tpl->addJavaScript('./node_modules/@andxor/jquery-ui-touch-punch-fix/jquery.ui.touch-punch.js');
848  }
849  $this->tpl->addJavaScript('Modules/TestQuestionPool/js/ilMatchingQuestion.js');
850  $this->tpl->addOnLoadCode('ilMatchingQuestionInit();');
851  $this->tpl->addCss(ilUtil::getStyleSheetLocation('output', 'test_javascript.css', 'Modules/TestQuestionPool'));
852 
853  $template = new ilTemplate("tpl.il_as_qpl_matching_output.html", true, true, "Modules/TestQuestionPool");
854 
855  $solutions = array();
856  if ($active_id) {
857  if (is_array($user_post_solution)) {
858  foreach ($user_post_solution['matching'][$this->object->getId()] as $definition => $term) {
859  array_push($solutions, array("value1" => $term, "value2" => $definition));
860  }
861  } else {
862  // hey: prevPassSolutions - obsolete due to central check
863  $solutions = $this->object->getTestOutputSolutions($active_id, $pass);
864  // hey.
865  }
866 
867  $counter = 0;
868  foreach ($solutions as $idx => $solution_value) {
869  if (($solution_value['value2'] > -1) && ($solution_value['value1'] > -1)) {
870  $template->setCurrentBlock("matching_data");
871  $template->setVariable("TERM_ID", $solution_value['value1']);
872  $template->setVariable("DEFINITION_ID", $solution_value['value2']);
873  $template->parseCurrentBlock();
874  }
875 
876  $counter++;
877  }
878  }
879 
880  $terms = $this->object->getTerms();
881  $definitions = $this->object->getDefinitions();
882  switch ($this->object->getShuffleMode()) {
883  case 1:
884  $terms = $this->object->getShuffler()->transform($terms);
885  if (count($solutions)) {
886  $definitions = $this->sortDefinitionsBySolution($solutions, $definitions);
887  } else {
888  $definitions = $this->object->getShuffler()->transform(
889  $this->object->getShuffler()->transform($definitions)
890  );
891  }
892  break;
893  case 2:
894  $terms = $this->object->getShuffler()->transform($terms);
895  break;
896  case 3:
897  if (count($solutions)) {
898  $definitions = $this->sortDefinitionsBySolution($solutions, $definitions);
899  } else {
900  $definitions = $this->object->getShuffler()->transform($definitions);
901  }
902  break;
903  }
904 
905  // create definitions
906  $counter = 0;
907  foreach ($definitions as $definition) {
908  if (strlen($definition->getPicture())) {
909  $template->setCurrentBlock("definition_picture");
910  $template->setVariable("DEFINITION_ID", $definition->getIdentifier());
911  $template->setVariable("IMAGE_HREF", $this->object->getImagePathWeb() . $definition->getPicture());
912  $thumbweb = $this->object->getImagePathWeb() . $this->object->getThumbPrefix() . $definition->getPicture();
913  $thumb = $this->object->getImagePath() . $this->object->getThumbPrefix() . $definition->getPicture();
914  if (!@file_exists($thumb)) {
915  $this->object->rebuildThumbnails();
916  }
917  $template->setVariable("THUMBNAIL_HREF", $thumbweb);
918  $template->setVariable("THUMB_ALT", $this->lng->txt("image"));
919  $template->setVariable("THUMB_TITLE", $this->lng->txt("image"));
920  $template->setVariable("TEXT_DEFINITION", (strlen($definition->getText())) ? ilLegacyFormElementsUtil::prepareTextareaOutput($definition->getText(), true, true) : '');
921  $template->setVariable("TEXT_PREVIEW", $this->lng->txt('preview'));
922  $template->setVariable("IMG_PREVIEW", ilUtil::getImagePath('media/enlarge.svg'));
923  $template->parseCurrentBlock();
924  } else {
925  $template->setCurrentBlock("definition_text");
926  $template->setVariable("DEFINITION", ilLegacyFormElementsUtil::prepareTextareaOutput($definition->getText(), true, true));
927  $template->parseCurrentBlock();
928  }
929 
930  $template->setCurrentBlock("droparea");
931  $template->setVariable("ID_DROPAREA", $definition->getIdentifier());
932  $template->setVariable("QUESTION_ID", $this->object->getId());
933  $template->parseCurrentBlock();
934 
935  $template->setCurrentBlock("definition_data");
936  $template->setVariable("DEFINITION_ID", $definition->getIdentifier());
937  $template->parseCurrentBlock();
938  }
939 
940  // create terms
941  $counter = 0;
942  foreach ($terms as $term) {
943  if (strlen($term->getPicture())) {
944  $template->setCurrentBlock("term_picture");
945  $template->setVariable("TERM_ID", $term->getIdentifier());
946  $template->setVariable("IMAGE_HREF", $this->object->getImagePathWeb() . $term->getPicture());
947  $thumbweb = $this->object->getImagePathWeb() . $this->object->getThumbPrefix() . $term->getPicture();
948  $thumb = $this->object->getImagePath() . $this->object->getThumbPrefix() . $term->getPicture();
949  if (!@file_exists($thumb)) {
950  $this->object->rebuildThumbnails();
951  }
952  $template->setVariable("THUMBNAIL_HREF", $thumbweb);
953  $template->setVariable("THUMB_ALT", $this->lng->txt("image"));
954  $template->setVariable("THUMB_TITLE", $this->lng->txt("image"));
955  $template->setVariable("TEXT_PREVIEW", $this->lng->txt('preview'));
956  $template->setVariable("TEXT_TERM", (strlen($term->getText())) ? ilLegacyFormElementsUtil::prepareTextareaOutput($term->getText(), true, true) : '');
957  $template->setVariable("IMG_PREVIEW", ilUtil::getImagePath('media/enlarge.svg'));
958  $template->parseCurrentBlock();
959  } else {
960  $template->setCurrentBlock("term_text");
961  $template->setVariable("TERM_TEXT", ilLegacyFormElementsUtil::prepareTextareaOutput($term->getText(), true, true));
962  $template->parseCurrentBlock();
963  }
964  $template->setCurrentBlock("draggable");
965  $template->setVariable("ID_DRAGGABLE", $term->getIdentifier());
966  $template->parseCurrentBlock();
967 
968  $template->setCurrentBlock('term_data');
969  $template->setVariable('TERM_ID', $term->getIdentifier());
970  $template->parseCurrentBlock();
971  }
972 
973  $template->setVariable('MATCHING_MODE', $this->object->getMatchingMode());
974 
975  $template->setVariable("RESET_BUTTON", $this->lng->txt("reset_terms"));
976 
977  $template->setVariable("QUESTIONTEXT", $this->object->getQuestionForHTMLOutput());
978 
979  return $this->outQuestionPage("", $is_postponed, $active_id, $template->get());
980  }
981 
985  public function checkInput(): bool
986  {
987  if ((!$_POST["title"]) or (!$_POST["author"]) or (!$_POST["question"])) {
988  return false;
989  }
990  return true;
991  }
992 
993  public function getSpecificFeedbackOutput(array $userSolution): string
994  {
995  $matches = array_values($this->object->matchingpairs);
996 
997  if (!$this->object->feedbackOBJ->specificAnswerFeedbackExists()) {
998  return '';
999  }
1000 
1001  $feedback = '<table class="test_specific_feedback"><tbody>';
1002 
1003  foreach ($matches as $idx => $ans) {
1004  if (!isset($userSolution[$ans->getDefinition()->getIdentifier()])) {
1005  continue;
1006  }
1007 
1008  if (!is_array($userSolution[$ans->getDefinition()->getIdentifier()])) {
1009  continue;
1010  }
1011 
1012  if (!in_array($ans->getTerm()->getIdentifier(), $userSolution[$ans->getDefinition()->getIdentifier()])) {
1013  continue;
1014  }
1015 
1016  $fb = $this->object->feedbackOBJ->getSpecificAnswerFeedbackTestPresentation(
1017  $this->object->getId(),
1018  0,
1019  $idx
1020  );
1021  $feedback .= '<tr><td>"' . $ans->getDefinition()->getText() . '"&nbsp;' . $this->lng->txt("matches") . '&nbsp;"';
1022  $feedback .= $ans->getTerm()->getText() . '"</td><td>';
1023  $feedback .= $fb . '</td> </tr>';
1024  }
1025 
1026  $feedback .= '</tbody></table>';
1027  return ilLegacyFormElementsUtil::prepareTextareaOutput($feedback, true);
1028  }
1029 
1040  {
1041  return array();
1042  }
1043 
1054  {
1055  return array();
1056  }
1057 
1064  public function getAggregatedAnswersView(array $relevant_answers): string
1065  {
1066  return ''; //print_r($relevant_answers,true);
1067  }
1068 
1069  private function isCorrectMatching($pair, $definition, $term): bool
1070  {
1071  if (!($pair->getPoints() > 0)) {
1072  return false;
1073  }
1074 
1075  if (!is_object($term)) {
1076  return false;
1077  }
1078 
1079  if ($pair->getDefinition()->getIdentifier() != $definition->getIdentifier()) {
1080  return false;
1081  }
1082 
1083  if ($pair->getTerm()->getIdentifier() != $term->getIdentifier()) {
1084  return false;
1085  }
1086 
1087  return true;
1088  }
1089 
1090  protected function getAnswerStatisticImageHtml($picture): string
1091  {
1092  $thumbweb = $this->object->getImagePathWeb() . $this->object->getThumbPrefix() . $picture;
1093  return '<img src="' . $thumbweb . '" alt="' . $picture . '" title="' . $picture . '"/>';
1094  }
1095 
1096  protected function getAnswerStatisticMatchingElemHtml($elem): string
1097  {
1098  $html = '';
1099 
1100  if (strlen($elem->getText())) {
1101  $html .= $elem->getText();
1102  }
1103 
1104  if (strlen($elem->getPicture())) {
1105  $html .= $this->getAnswerStatisticImageHtml($elem->getPicture());
1106  }
1107 
1108  return $html;
1109  }
1110 
1111  public function getAnswersFrequency($relevantAnswers, $questionIndex): array
1112  {
1113  $answersByActiveAndPass = array();
1114 
1115  foreach ($relevantAnswers as $row) {
1116  $key = $row['active_fi'] . ':' . $row['pass'];
1117 
1118  if (!isset($answersByActiveAndPass[$key])) {
1119  $answersByActiveAndPass[$key] = array();
1120  }
1121 
1122  $answersByActiveAndPass[$key][$row['value1']] = $row['value2'];
1123  }
1124 
1125  $answers = array();
1126 
1127  foreach ($answersByActiveAndPass as $key => $matchingPairs) {
1128  foreach ($matchingPairs as $termId => $defId) {
1129  $hash = md5($termId . ':' . $defId);
1130 
1131  if (!isset($answers[$hash])) {
1132  $termHtml = $this->getAnswerStatisticMatchingElemHtml(
1133  $this->object->getTermWithIdentifier($termId)
1134  );
1135 
1136  $defHtml = $this->getAnswerStatisticMatchingElemHtml(
1137  $this->object->getDefinitionWithIdentifier($defId)
1138  );
1139 
1140  $answers[$hash] = array(
1141  'answer' => $termHtml . $defHtml,
1142  'term' => $termHtml,
1143  'definition' => $defHtml,
1144  'frequency' => 0
1145  );
1146  }
1147 
1148  $answers[$hash]['frequency']++;
1149  }
1150  }
1151 
1152  return $answers;
1153  }
1154 
1162  public function getAnswerFrequencyTableGUI($parentGui, $parentCmd, $relevantAnswers, $questionIndex): ilAnswerFrequencyStatisticTableGUI
1163  {
1164  $table = new ilMatchingQuestionAnswerFreqStatTableGUI($parentGui, $parentCmd, $this->object);
1165  $table->setQuestionIndex($questionIndex);
1166  $table->setData($this->getAnswersFrequency($relevantAnswers, $questionIndex));
1167  $table->initColumns();
1168 
1169  return $table;
1170  }
1171 
1173  {
1174  $pairs = new ilAssMatchingPairCorrectionsInputGUI($this->lng->txt('matching_pairs'), 'pairs');
1175  $pairs->setRequired(true);
1176  $pairs->setTerms($this->object->getTerms());
1177  $pairs->setDefinitions($this->object->getDefinitions());
1178  $pairs->setPairs($this->object->getMatchingPairs());
1179  $pairs->setThumbsWebPathWithPrefix($this->object->getImagePathWeb() . $this->object->getThumbPrefix());
1180  $form->addItem($pairs);
1181  }
1182 
1187  {
1188  $pairs = $this->object->getMatchingPairs();
1189  $nu_pairs = [];
1190 
1191  if ($this->request->isset('pairs')) {
1192  $points_of_pairs = $this->request->raw('pairs')['points'];
1193  $pair_terms = explode(',', $this->request->raw('pairs')['term_id']);
1194  $pair_definitions = explode(',', $this->request->raw('pairs')['definition_id']);
1195  $values = [];
1196  foreach ($points_of_pairs as $idx => $points) {
1197  $k = implode('.', [$pair_terms[$idx],$pair_definitions[$idx]]);
1198  $values[$k] = (float) str_replace(',', '.', $points);
1199  }
1200 
1201  foreach ($pairs as $idx => $pair) {
1202  $id = implode('.', [
1203  $pair->getTerm()->getIdentifier(),
1204  $pair->getDefinition()->getIdentifier()
1205  ]);
1206  $nu_pairs[$id] = $pair->withPoints($values[$id]);
1207  }
1208 
1209  $this->object = $this->object->withMatchingPairs($nu_pairs);
1210  }
1211  }
1212 }
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
hasCorrectSolution($activeId, $passIndex)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
generateCorrectnessIconsForCorrectness(int $correctness)
This class represents a single choice wizard property in a property form.
This class represents a selection list property in a property form.
setHideImages($a_hide)
Set hide images.
saveCorrectionsFormProperties(ilPropertyFormGUI $form)
getItemByPostVar(string $a_post_var)
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)
addBasicQuestionFormProperties(ilPropertyFormGUI $form)
getTestOutput($active_id, $pass, $is_postponed=false, $user_post_solution=false, $inlineFeedback=false)
isValidTermAndDefinitionAmount(ilPropertyFormGUI $form)
for mode 1:1 terms count must not be less than definitions count for mode n:n this limitation is canc...
populateCorrectionsFormProperties(ilPropertyFormGUI $form)
setOptions(array $a_options)
static prepareFormOutput($a_str, bool $a_strip=false)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
static getStyleSheetLocation(string $mode="output", string $a_css_name="", string $a_css_location="")
get full style sheet file name (path inclusive) of current user
populateTaxonomyFormSection(ilPropertyFormGUI $form)
addQuestionFormCommandButtons(ilPropertyFormGUI $form)
Class for matching questions.
writeAnswerSpecificPostData(ilPropertyFormGUI $form)
Extracts the answer specific values from $_POST and applies them to the data object.
global $DIC
Definition: feed.php:28
populateQuestionSpecificFormPart(\ilPropertyFormGUI $form)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
This class represents a property in a property form.
__construct(VocabulariesInterface $vocabularies)
setErrorMessage(string $errormessage)
__construct($id=-1)
assMatchingQuestionGUI constructor
writePostData(bool $always=false)
{}
writeQuestionSpecificPostData(ilPropertyFormGUI $form)
Extracts the question specific values from $_POST and applies them to the data object.
setValue(string $a_value)
getAggregatedAnswersView(array $relevant_answers)
Returns an html string containing a question specific representation of the answers so far given in t...
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.
string $key
Consumer key/client ID value.
Definition: System.php:193
isCorrectMatching($pair, $definition, $term)
static initjQueryUI(ilGlobalTemplateInterface $a_tpl=null)
inits and adds the jQuery-UI JS-File to the global template (see included_components.txt for included components)
getAnswerFrequencyTableGUI($parentGui, $parentCmd, $relevantAnswers, $questionIndex)
getPreview($show_question_only=false, $showInlineFeedback=false)
Basic GUI class for assessment questions.
setRequired(bool $a_required)
populateAnswerSpecificFormPart(\ilPropertyFormGUI $form)
$filename
Definition: buildRTE.php:78
getSpecificFeedbackOutput(array $userSolution)
getILIASPage(string $html="")
Returns the ILIAS Page around a question.
outQuestionPage($a_temp_var, $a_postponed=false, $active_id="", $html="", $inlineFeedbackEnabled=false)
static initjQuery(ilGlobalTemplateInterface $a_tpl=null)
inits and adds the jQuery JS-File to the global or a passed template
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,)
getAnswersFrequency($relevantAnswers, $questionIndex)
$id
plugin.php for ilComponentBuildPluginInfoObjectiveTest::testAddPlugins
Definition: plugin.php:23
static signFile(string $path_to_file)
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...
getAfterParticipationSuppressionQuestionPostVars()
Returns a list of postvars which will be suppressed in the form output when used in scoring adjustmen...
getAfterParticipationSuppressionAnswerPostVars()
Returns a list of postvars which will be suppressed in the form output when used in scoring adjustmen...
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
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...
This class represents a key value pair wizard property in a property form.
getGenericFeedbackOutput(int $active_id, ?int $pass)