ILIAS  release_8 Revision v8.24
class.assMatchingQuestionGUI.php
Go to the documentation of this file.
1<?php
18require_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';
67 return 0;
68 }
69 return 1;
70 }
71
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);
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
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}
$id
plugin.php for ilComponentBuildPluginInfoObjectiveTest::testAddPlugins
Definition: plugin.php:23
$filename
Definition: buildRTE.php:78
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...
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
isValidTermAndDefinitionAmount(ilPropertyFormGUI $form)
for mode 1:1 terms count must not be less than definitions count for mode n:n this limitation is canc...
populateAnswerSpecificFormPart(\ilPropertyFormGUI $form)
saveCorrectionsFormProperties(ilPropertyFormGUI $form)
getAnswersFrequency($relevantAnswers, $questionIndex)
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.
isCorrectMatching($pair, $definition, $term)
getAfterParticipationSuppressionAnswerPostVars()
Returns a list of postvars which will be suppressed in the form output when used in scoring adjustmen...
getPreview($show_question_only=false, $showInlineFeedback=false)
getAggregatedAnswersView(array $relevant_answers)
Returns an html string containing a question specific representation of the answers so far given in t...
getAfterParticipationSuppressionQuestionPostVars()
Returns a list of postvars which will be suppressed in the form output when used in scoring adjustmen...
writeQuestionSpecificPostData(ilPropertyFormGUI $form)
Extracts the question specific values from $_POST and applies them to the data object.
getSpecificFeedbackOutput(array $userSolution)
Returns the answer specific feedback for the question.
writeAnswerSpecificPostData(ilPropertyFormGUI $form)
Extracts the answer specific values from $_POST and applies them to the data object.
populateCorrectionsFormProperties(ilPropertyFormGUI $form)
writePostData(bool $always=false)
{Evaluates a posted edit form and writes the form data in the question object.integer A positive valu...
getTestOutput($active_id, $pass, $is_postponed=false, $user_post_solution=false, $inlineFeedback=false)
getAnswerFrequencyTableGUI($parentGui, $parentCmd, $relevantAnswers, $questionIndex)
populateQuestionSpecificFormPart(\ilPropertyFormGUI $form)
__construct($id=-1)
assMatchingQuestionGUI constructor
Class for matching questions.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
populateTaxonomyFormSection(ilPropertyFormGUI $form)
getILIASPage(string $html="")
Returns the ILIAS Page around a question.
addBasicQuestionFormProperties(ilPropertyFormGUI $form)
getGenericFeedbackOutput(int $active_id, ?int $pass)
addQuestionFormCommandButtons(ilPropertyFormGUI $form)
setErrorMessage(string $errormessage)
hasCorrectSolution($activeId, $passIndex)
generateCorrectnessIconsForCorrectness(int $correctness)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
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...
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
This class represents a number property in a property form.
This class represents a property form user interface.
getItemByPostVar(string $a_post_var)
This class represents a property in a property form.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
This class represents a selection list property in a property form.
special template class to simplify handling of ITX/PEAR
static getStyleSheetLocation(string $mode="output", string $a_css_name="", string $a_css_location="")
get full style sheet file name (path inclusive) of current user
static getImagePath(string $img, string $module_path="", string $mode="output", bool $offline=false)
get image path (for images located in a template directory)
static stripSlashes(string $a_str, bool $a_strip_html=true, string $a_allow="")
static signFile(string $path_to_file)
static initjQueryUI(ilGlobalTemplateInterface $a_tpl=null)
inits and adds the jQuery-UI JS-File to the global template (see included_components....
static initjQuery(ilGlobalTemplateInterface $a_tpl=null)
inits and adds the jQuery JS-File to the global or a passed template
global $DIC
Definition: feed.php:28
$errors
Definition: imgupload.php:65
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...
if($format !==null) $name
Definition: metadata.php:247
$index
Definition: metadata.php:145
$i
Definition: metadata.php:41
__construct(Container $dic, ilPlugin $plugin)
@inheritDoc
string $key
Consumer key/client ID value.
Definition: System.php:193