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