1<?php 2/* Copyright (c) 1998-2013 ILIAS open source, Extended GPL, see docs/LICENSE */ 3 4require_once './Modules/TestQuestionPool/classes/class.assQuestionGUI.php'; 5require_once './Modules/TestQuestionPool/interfaces/interface.ilGuiQuestionScoringAdjustable.php'; 6require_once './Modules/TestQuestionPool/interfaces/interface.ilGuiAnswerScoringAdjustable.php'; 7require_once './Modules/Test/classes/inc.AssessmentConstants.php'; 8 9/** 10 * Ordering question GUI representation 11 * 12 * The assOrderingQuestionGUI class encapsulates the GUI representation for ordering questions. 13 * 14 * @author Helmut Schottmüller <helmut.schottmueller@mac.com> 15 * @author Björn Heyser <bheyser@databay.de> 16 * @author Maximilian Becker <mbecker@databay.de> 17 * 18 * @version $Id$ 19 * 20 * @ingroup ModulesTestQuestionPool 21 * @ilCtrl_Calls assOrderingQuestionGUI: ilFormPropertyDispatchGUI 22 */ 23class assOrderingQuestionGUI extends assQuestionGUI implements ilGuiQuestionScoringAdjustable, ilGuiAnswerScoringAdjustable 24{ 25 /** 26 * @var assOrderingQuestion 27 */ 28 public $object; 29 30 public $old_ordering_depth = array(); 31 public $leveled_ordering = array(); 32 33 /** 34 * @var bool 35 */ 36 private $clearAnswersOnWritingPostDataEnabled; 37 38 /** 39 * assOrderingQuestionGUI constructor 40 * 41 * The constructor takes possible arguments an creates an instance of the assOrderingQuestionGUI object. 42 * 43 * @param integer $id The database id of a ordering question object 44 * 45 * @return assOrderingQuestionGUI 46 */ 47 public function __construct($id = -1) 48 { 49 parent::__construct(); 50 include_once "./Modules/TestQuestionPool/classes/class.assOrderingQuestion.php"; 51 $this->object = new assOrderingQuestion(); 52 if ($id >= 0) { 53 $this->object->loadFromDb($id); 54 } 55 $this->clearAnswersOnWritingPostDataEnabled = false; 56 } 57 58 /** 59 * @param boolean $clearAnswersOnWritingPostDataEnabled 60 */ 61 public function setClearAnswersOnWritingPostDataEnabled($clearAnswersOnWritingPostDataEnabled) 62 { 63 $this->clearAnswersOnWritingPostDataEnabled = $clearAnswersOnWritingPostDataEnabled; 64 } 65 66 /** 67 * @return boolean 68 */ 69 public function isClearAnswersOnWritingPostDataEnabled() 70 { 71 return $this->clearAnswersOnWritingPostDataEnabled; 72 } 73 74 public function changeToPictures() 75 { 76 if ($this->object->getOrderingType() != OQ_NESTED_PICTURES && $this->object->getOrderingType() != OQ_PICTURES) { 77 $this->setClearAnswersOnWritingPostDataEnabled(true); 78 } 79 80 $form = $this->buildEditForm(); 81 $form->setValuesByPost(); 82 $this->persistAuthoringForm($form); 83 84 $this->object->setOrderingType(OQ_PICTURES); 85 $this->object->saveToDb(); 86 87 $form->ensureReprintableFormStructure($this->object); 88 $this->renderEditForm($form); 89 } 90 91 public function changeToText() 92 { 93 if ($this->object->getOrderingType() != OQ_NESTED_TERMS && $this->object->getOrderingType() != OQ_TERMS) { 94 $this->setClearAnswersOnWritingPostDataEnabled(true); 95 } 96 97 $form = $this->buildEditForm(); 98 $form->setValuesByPost(); 99 $this->persistAuthoringForm($form); 100 101 $this->object->setOrderingType(OQ_TERMS); 102 $this->object->saveToDb(); 103 104 $form->ensureReprintableFormStructure($this->object); 105 $this->renderEditForm($form); 106 } 107 108 public function orderNestedTerms() 109 { 110 $this->writePostData(true); 111 $this->object->setOrderingType(OQ_NESTED_TERMS); 112 $this->object->saveToDb(); 113 114 $this->renderEditForm($this->buildEditForm()); 115 } 116 117 public function orderNestedPictures() 118 { 119 $this->writePostData(true); 120 $this->object->setOrderingType(OQ_NESTED_PICTURES); 121 $this->object->saveToDb(); 122 123 $this->renderEditForm($this->buildEditForm()); 124 } 125 126 public function removeElementImage() 127 { 128 $orderingInput = $this->object->buildOrderingImagesInputGui(); 129 $this->object->initOrderingElementAuthoringProperties($orderingInput); 130 131 $form = $this->buildEditForm(); 132 $form->replaceFormItemByPostVar($orderingInput); 133 $form->setValuesByPost(); 134 135 $replacementElemList = ilAssOrderingElementList::buildInstance( 136 $this->object->getId(), 137 array() 138 ); 139 140 $storedElementList = $this->object->getOrderingElementList(); 141 142 foreach ($orderingInput->getElementList($this->object->getId()) as $submittedElement) { 143 if ($submittedElement->isImageRemovalRequest()) { 144 if ($this->object->isImageFileStored($submittedElement->getContent())) { 145 $this->object->dropImageFile($submittedElement->getContent()); 146 } 147 148 $submittedElement->setContent(null); 149 } 150 151 if ($storedElementList->elementExistByRandomIdentifier($submittedElement->getRandomIdentifier())) { 152 $storedElement = $storedElementList->getElementByRandomIdentifier( 153 $submittedElement->getRandomIdentifier() 154 ); 155 156 $submittedElement->setSolutionIdentifier($storedElement->getSolutionIdentifier()); 157 $submittedElement->setIndentation($storedElement->getIndentation()); 158 } 159 160 $replacementElemList->addElement($submittedElement); 161 } 162 163 $replacementElemList->saveToDb(); 164 165 $orderingInput->setElementList($replacementElemList); 166 $this->renderEditForm($form); 167 } 168 169 public function uploadElementImage() 170 { 171 $orderingInput = $this->object->buildOrderingImagesInputGui(); 172 $this->object->initOrderingElementAuthoringProperties($orderingInput); 173 174 $form = $this->buildEditForm(); 175 $form->replaceFormItemByPostVar($orderingInput); 176 $form->setValuesByPost(); 177 178 if (!$orderingInput->checkInput()) { 179 ilUtil::sendFailure($this->lng->txt('form_input_not_valid')); 180 } 181 182 $this->writeAnswerSpecificPostData($form); 183 $this->object->getOrderingElementList()->saveToDb(); 184 $orderingInput->setElementList($this->object->getOrderingElementList()); 185 186 $this->renderEditForm($form); 187 } 188 189 public function writeQuestionSpecificPostData(ilPropertyFormGUI $form) 190 { 191 $this->object->setThumbGeometry($_POST["thumb_geometry"]); 192 $this->object->setElementHeight($_POST["element_height"]); 193 //$this->object->setOrderingType( $_POST["ordering_type"] ); 194 $this->object->setPoints($_POST["points"]); 195 } 196 197 public function writeAnswerSpecificPostData(ilPropertyFormGUI $form) 198 { 199 if (!is_array($_POST[assOrderingQuestion::ORDERING_ELEMENT_FORM_FIELD_POSTVAR])) { 200 throw new ilTestQuestionPoolException('form submit request missing the form submit!?'); 201 } 202 203 #$submittedElementList = $this->object->fetchSolutionListFromFormSubmissionData($_POST); 204 $submittedElementList = $this->object->fetchSolutionListFromSubmittedForm($form); 205 206 $replacementElementList = new ilAssOrderingElementList(); 207 $replacementElementList->setQuestionId($this->object->getId()); 208 209 $currentElementList = $this->object->getOrderingElementList(); 210 211 foreach ($submittedElementList as $submittedElement) { 212 if ($this->object->hasOrderingTypeUploadSupport()) { 213 if ($submittedElement->isImageUploadAvailable()) { 214 $suffix = strtolower(array_pop(explode(".", $submittedElement->getUploadImageName()))); 215 if (in_array($suffix, array("jpg", "jpeg", "png", "gif"))) { 216 $submittedElement->setUploadImageName($this->object->buildHashedImageFilename( 217 $submittedElement->getUploadImageName(), 218 true 219 )); 220 221 $wasImageFileStored = $this->object->storeImageFile( 222 $submittedElement->getUploadImageFile(), 223 $submittedElement->getUploadImageName() 224 ); 225 226 if ($wasImageFileStored) { 227 if ($this->object->isImageFileStored($submittedElement->getContent())) { 228 $this->object->dropImageFile($submittedElement->getContent()); 229 } 230 231 $submittedElement->setContent($submittedElement->getUploadImageName()); 232 } 233 } 234 } 235 } 236 237 if ($currentElementList->elementExistByRandomIdentifier($submittedElement->getRandomIdentifier())) { 238 $storedElement = $currentElementList->getElementByRandomIdentifier( 239 $submittedElement->getRandomIdentifier() 240 ); 241 242 $submittedElement->setSolutionIdentifier($storedElement->getSolutionIdentifier()); 243 244 if ($this->isAdjustmentEditContext() || $this->object->isOrderingTypeNested()) { 245 $submittedElement->setContent($storedElement->getContent()); 246 } 247 248 if (!$this->object->isOrderingTypeNested()) { 249 $submittedElement->setIndentation($storedElement->getIndentation()); 250 } 251 252 if ($this->object->isImageReplaced($submittedElement, $storedElement)) { 253 $this->object->dropImageFile($storedElement->getContent()); 254 } 255 } 256 257 $replacementElementList->addElement($submittedElement); 258 } 259 260 if ($this->object->isImageOrderingType()) { 261 $this->object->handleThumbnailCreation($replacementElementList); 262 } 263 264 if ($this->isClearAnswersOnWritingPostDataEnabled()) { 265 $replacementElementList->clearElementContents(); 266 } 267 268 if ($this->object->hasOrderingTypeUploadSupport()) { 269 $obsoleteElementList = $currentElementList->getDifferenceElementList($replacementElementList); 270 271 foreach ($obsoleteElementList as $obsoleteElement) { 272 $this->object->dropImageFile($obsoleteElement->getContent()); 273 } 274 } 275 276 $this->object->setOrderingElementList($replacementElementList); 277 } 278 279 public function populateAnswerSpecificFormPart(ilPropertyFormGUI $form) 280 { 281 $header = new ilFormSectionHeaderGUI(); 282 $header->setTitle($this->lng->txt('oq_header_ordering_elements')); 283 $form->addItem($header); 284 285 if ($this->isAdjustmentEditContext()) { 286 $orderingElementInput = $this->object->buildNestedOrderingElementInputGui(); 287 } else { 288 $orderingElementInput = $this->object->buildOrderingElementInputGui(); 289 } 290 291 $orderingElementInput->setStylingDisabled($this->isRenderPurposePrintPdf()); 292 $this->object->initOrderingElementAuthoringProperties($orderingElementInput); 293 294 $orderingElementInput->setElementList($this->object->getOrderingElementList()); 295 296 $form->addItem($orderingElementInput); 297 298 return $form; 299 } 300 301 public function populateQuestionSpecificFormPart(\ilPropertyFormGUI $form) 302 { 303 if (!$this->object->getSelfAssessmentEditingMode()) { 304 $element_height = new ilNumberInputGUI($this->lng->txt("element_height"), "element_height"); 305 $element_height->setValue($this->object->getElementHeight()); 306 $element_height->setRequired(false); 307 $element_height->setMaxLength(6); 308 $element_height->setMinValue(20); 309 $element_height->setSize(6); 310 $element_height->setInfo($this->lng->txt("element_height_info")); 311 $form->addItem($element_height); 312 } 313 314 if ($this->object->isImageOrderingType()) { 315 $geometry = new ilNumberInputGUI($this->lng->txt("thumb_geometry"), "thumb_geometry"); 316 $geometry->setValue($this->object->getThumbGeometry()); 317 $geometry->setRequired(true); 318 $geometry->setMaxLength(6); 319 $geometry->setMinValue(20); 320 $geometry->setSize(6); 321 $geometry->setInfo($this->lng->txt("thumb_geometry_info")); 322 $form->addItem($geometry); 323 } 324 325 // points 326 $points = new ilNumberInputGUI($this->lng->txt("points"), "points"); 327 $points->allowDecimals(true); 328 $points->setValue($this->object->getPoints()); 329 $points->setRequired(true); 330 $points->setSize(3); 331 $points->setMinValue(0); 332 $points->setMinvalueShouldBeGreater(true); 333 $form->addItem($points); 334 335 return $form; 336 } 337 338 /** 339 * {@inheritdoc} 340 */ 341 protected function writePostData($forceSaving = false) 342 { 343 $savingAllowed = true; // assume saving allowed first 344 345 if (!$forceSaving) { 346 // this case seems to be a regular save call, so we consider 347 // the validation result for the decision of saving as well 348 349 // inits {this->editForm} and performs validation 350 $form = $this->buildEditForm(); 351 $form->setValuesByPost(); // manipulation and distribution of values 352 353 if (!$form->checkInput()) { // manipulations regular style input propeties 354 $form->prepareValuesReprintable($this->object); 355 $this->renderEditForm($form); 356 357 // consequence of vaidation 358 $savingAllowed = false; 359 } 360 } elseif (!$this->isSaveCommand()) { 361 // this case handles form workflow actions like the mode/view switching requests, 362 // so saving must not be skipped, even for inputs invalid by business rules 363 364 $form = $this->buildEditForm(); 365 $form->setValuesByPost(); // manipulation and distribution of values 366 $form->checkInput(); // manipulations regular style input propeties 367 } 368 369 if ($savingAllowed) { 370 $this->persistAuthoringForm($form); 371 372 return 0; // return 0 = all fine, was saved either forced or validated 373 } 374 375 return 1; // return 1 = something went wrong, no saving happened 376 } 377 378 /** 379 * Creates an output of the edit form for the question 380 */ 381 public function editQuestion($checkonly = false) 382 { 383 $this->renderEditForm($this->buildEditForm()); 384 } 385 386 /** 387 * @return ilAssOrderingQuestionAuthoringFormGUI 388 */ 389 protected function buildEditForm() 390 { 391 require_once 'Modules/TestQuestionPool/classes/forms/class.ilAssOrderingQuestionAuthoringFormGUI.php'; 392 $form = new ilAssOrderingQuestionAuthoringFormGUI(); 393 $this->editForm = $form; 394 395 $form->setFormAction($this->ctrl->getFormAction($this)); 396 $form->setTitle($this->outQuestionType()); 397 $form->setMultipart(($this->object->getOrderingType() == OQ_PICTURES) ? true : false); 398 $form->setTableWidth("100%"); 399 $form->setId("ordering"); 400 // title, author, description, question, working time (assessment mode) 401 $this->addBasicQuestionFormProperties($form); 402 $this->populateQuestionSpecificFormPart($form); 403 $this->populateAnswerSpecificFormPart($form); 404 405 $this->populateTaxonomyFormSection($form); 406 407 $form->addSpecificOrderingQuestionCommandButtons($this->object); 408 $form->addGenericAssessmentQuestionCommandButtons($this->object); 409 410 return $form; 411 } 412 413 /** 414 * Question type specific support of intermediate solution output 415 * The function getSolutionOutput respects getUseIntermediateSolution() 416 * @return bool 417 */ 418 public function supportsIntermediateSolutionOutput() 419 { 420 return true; 421 } 422 423 /** 424 * Get the question solution output 425 * 426 * @param integer $active_id The active user id 427 * @param integer $pass The test pass 428 * @param boolean $graphicalOutput Show visual feedback for right/wrong answers 429 * @param boolean $result_output Show the reached points for parts of the question 430 * @param boolean $show_question_only Show the question without the ILIAS content around 431 * @param boolean $show_feedback Show the question feedback 432 * @param boolean $show_correct_solution Show the correct solution instead of the user solution 433 * @param boolean $show_manual_scoring Show specific information for the manual scoring output 434 * @param bool $show_question_text 435 * 436 * @return string The solution output of the question as HTML code 437 */ 438 public function getSolutionOutput( 439 $active_id, 440 $pass = null, 441 $graphicalOutput = false, 442 $result_output = false, 443 $show_question_only = true, 444 $show_feedback = false, 445 $forceCorrectSolution = false, 446 $show_manual_scoring = false, 447 $show_question_text = true 448 ) { 449 $solutionOrderingList = $this->object->getOrderingElementListForSolutionOutput( 450 $forceCorrectSolution, 451 $active_id, 452 $pass, 453 $this->getUseIntermediateSolution() 454 ); 455 456 $answers_gui = $this->object->buildNestedOrderingElementInputGui(); 457 458 if ($forceCorrectSolution) { 459 $answers_gui->setContext(ilAssNestedOrderingElementsInputGUI::CONTEXT_CORRECT_SOLUTION_PRESENTATION); 460 } else { 461 $answers_gui->setContext(ilAssNestedOrderingElementsInputGUI::CONTEXT_USER_SOLUTION_PRESENTATION); 462 } 463 464 $answers_gui->setInteractionEnabled(false); 465 466 $answers_gui->setElementList($solutionOrderingList); 467 468 $answers_gui->setCorrectnessTrueElementList( 469 $solutionOrderingList->getParityTrueElementList($this->object->getOrderingElementList()) 470 ); 471 472 $solution_html = $answers_gui->getHTML(); 473 474 $template = new ilTemplate("tpl.il_as_qpl_nested_ordering_output_solution.html", true, true, "Modules/TestQuestionPool"); 475 $template->setVariable('SOLUTION_OUTPUT', $solution_html); 476 if ($show_question_text == true) { 477 $template->setVariable("QUESTIONTEXT", $this->object->prepareTextareaOutput($this->object->getQuestion(), true)); 478 } 479 $questionoutput = $template->get(); 480 481 $solutiontemplate = new ilTemplate("tpl.il_as_tst_solution_output.html", true, true, "Modules/TestQuestionPool"); 482 $solutiontemplate->setVariable("SOLUTION_OUTPUT", $questionoutput); 483 484 if ($show_feedback) { 485 $feedback = ''; 486 487 if (!$this->isTestPresentationContext()) { 488 $fb = $this->getGenericFeedbackOutput($active_id, $pass); 489 $feedback .= strlen($fb) ? $fb : ''; 490 } 491 492 $fb = $this->getSpecificFeedbackOutput(array()); 493 $feedback .= strlen($fb) ? $fb : ''; 494 495 if (strlen($feedback)) { 496 $cssClass = ( 497 $this->hasCorrectSolution($active_id, $pass) ? 498 ilAssQuestionFeedback::CSS_CLASS_FEEDBACK_CORRECT : ilAssQuestionFeedback::CSS_CLASS_FEEDBACK_WRONG 499 ); 500 501 $solutiontemplate->setVariable("ILC_FB_CSS_CLASS", $cssClass); 502 $solutiontemplate->setVariable("FEEDBACK", $this->object->prepareTextareaOutput($feedback, true)); 503 } 504 } 505 506 if ($show_question_only) { 507 return $solutiontemplate->get(); 508 } 509 510 return $this->getILIASPage($solutiontemplate->get()); 511 512 // is this template still in use? it is not used at this point any longer! 513 // $template = new ilTemplate("tpl.il_as_qpl_ordering_output_solution.html", TRUE, TRUE, "Modules/TestQuestionPool"); 514 } 515 516 public function getPreview($show_question_only = false, $showInlineFeedback = false) 517 { 518 if ($this->getPreviewSession() && $this->getPreviewSession()->hasParticipantSolution()) { 519 $solutionOrderingElementList = unserialize( 520 $this->getPreviewSession()->getParticipantsSolution() 521 ); 522 } else { 523 $solutionOrderingElementList = $this->object->getShuffledOrderingElementList(); 524 } 525 526 $answers = $this->object->buildNestedOrderingElementInputGui(); 527 $answers->setNestingEnabled($this->object->isOrderingTypeNested()); 528 $answers->setContext(ilAssNestedOrderingElementsInputGUI::CONTEXT_QUESTION_PREVIEW); 529 $answers->setInteractionEnabled($this->isInteractivePresentation()); 530 $answers->setElementList($solutionOrderingElementList); 531 532 $template = new ilTemplate("tpl.il_as_qpl_ordering_output.html", true, true, "Modules/TestQuestionPool"); 533 534 $template->setCurrentBlock('nested_ordering_output'); 535 $template->setVariable('NESTED_ORDERING', $answers->getHTML()); 536 $template->parseCurrentBlock(); 537 538 $template->setVariable("QUESTIONTEXT", $this->object->prepareTextareaOutput($this->object->getQuestion(), true)); 539 540 if ($show_question_only) { 541 return $template->get(); 542 } 543 544 return $this->getILIASPage($template->get()); 545 546 //$this->tpl->addJavascript("./Modules/TestQuestionPool/templates/default/ordering.js"); 547 } 548 549 // hey: prevPassSolutions - pass will be always available from now on 550 public function getTestOutput($activeId, $pass, $isPostponed = false, $userSolutionPost = false, $inlineFeedback = false) 551 // hey. 552 { 553 // hey: prevPassSolutions - fixed variable type, makes phpstorm stop crying 554 $userSolutionPost = is_array($userSolutionPost) ? $userSolutionPost : array(); 555 // hey. 556 557 $orderingGUI = $this->object->buildNestedOrderingElementInputGui(); 558 $orderingGUI->setNestingEnabled($this->object->isOrderingTypeNested()); 559 560 $solutionOrderingElementList = $this->object->getSolutionOrderingElementListForTestOutput( 561 $orderingGUI, 562 $userSolutionPost, 563 $activeId, 564 $pass 565 ); 566 567 $template = new ilTemplate('tpl.il_as_qpl_ordering_output.html', true, true, 'Modules/TestQuestionPool'); 568 569 $orderingGUI->setContext(ilAssNestedOrderingElementsInputGUI::CONTEXT_USER_SOLUTION_SUBMISSION); 570 $orderingGUI->setElementList($solutionOrderingElementList); 571 572 $template->setCurrentBlock('nested_ordering_output'); 573 $template->setVariable('NESTED_ORDERING', $orderingGUI->getHTML()); 574 $template->parseCurrentBlock(); 575 576 $template->setVariable('QUESTIONTEXT', $this->object->prepareTextareaOutput($this->object->getQuestion(), true)); 577 578 $pageoutput = $this->outQuestionPage('', $isPostponed, $activeId, $template->get()); 579 580 return $pageoutput; 581 } 582 583 protected function isInteractivePresentation() 584 { 585 if ($this->isRenderPurposePlayback()) { 586 return true; 587 } 588 589 if ($this->isRenderPurposeDemoplay()) { 590 return true; 591 } 592 593 return false; 594 } 595 596 /** 597 * Sets the ILIAS tabs for this question type 598 * 599 * @access public 600 * 601 * @todo: MOVE THIS STEPS TO COMMON QUESTION CLASS assQuestionGUI 602 */ 603 public function setQuestionTabs() 604 { 605 global $DIC; 606 $rbacsystem = $DIC['rbacsystem']; 607 $ilTabs = $DIC['ilTabs']; 608 609 $ilTabs->clearTargets(); 610 611 $this->ctrl->setParameterByClass("ilAssQuestionPageGUI", "q_id", $_GET["q_id"]); 612 include_once "./Modules/TestQuestionPool/classes/class.assQuestion.php"; 613 $q_type = $this->object->getQuestionType(); 614 615 if (strlen($q_type)) { 616 $classname = $q_type . "GUI"; 617 $this->ctrl->setParameterByClass(strtolower($classname), "sel_question_types", $q_type); 618 $this->ctrl->setParameterByClass(strtolower($classname), "q_id", $_GET["q_id"]); 619 } 620 621 if ($_GET["q_id"]) { 622 if ($rbacsystem->checkAccess('write', $_GET["ref_id"])) { 623 // edit page 624 $ilTabs->addTarget( 625 "edit_page", 626 $this->ctrl->getLinkTargetByClass("ilAssQuestionPageGUI", "edit"), 627 array("edit", "insert", "exec_pg"), 628 "", 629 "", 630 false 631 ); 632 } 633 634 $this->addTab_QuestionPreview($ilTabs); 635 } 636 637 $force_active = false; 638 if ($rbacsystem->checkAccess('write', $_GET["ref_id"])) { 639 $url = ""; 640 if ($classname) { 641 $url = $this->ctrl->getLinkTargetByClass($classname, "editQuestion"); 642 } 643 $commands = $_POST["cmd"]; 644 if (is_array($commands)) { 645 foreach ($commands as $key => $value) { 646 if (preg_match("/^delete_.*/", $key, $matches)) { 647 $force_active = true; 648 } 649 } 650 } 651 // edit question properties 652 $ilTabs->addTarget( 653 "edit_question", 654 $url, 655 array("orderNestedTerms","orderNestedPictures","editQuestion", "save", "saveEdit", "addanswers", "removeanswers", "changeToPictures", "uploadElementImage", "changeToText", "upanswers", "downanswers", "originalSyncForm"), 656 $classname, 657 "", 658 $force_active 659 ); 660 } 661 662 // add tab for question feedback within common class assQuestionGUI 663 $this->addTab_QuestionFeedback($ilTabs); 664 665 // add tab for question hint within common class assQuestionGUI 666 $this->addTab_QuestionHints($ilTabs); 667 668 // add tab for question's suggested solution within common class assQuestionGUI 669 $this->addTab_SuggestedSolution($ilTabs, $classname); 670 671 // Assessment of questions sub menu entry 672 if ($_GET["q_id"]) { 673 $ilTabs->addTarget( 674 "statistics", 675 $this->ctrl->getLinkTargetByClass($classname, "assessment"), 676 array("assessment"), 677 $classname, 678 "" 679 ); 680 } 681 682 $this->addBackTab($ilTabs); 683 } 684 685 public function getSpecificFeedbackOutput($userSolution) 686 { 687 if (!$this->object->feedbackOBJ->specificAnswerFeedbackExists()) { 688 return ''; 689 } 690 691 $tpl = new ilTemplate('tpl.il_as_qpl_ordering_elem_fb.html', true, true, 'Modules/TestQuestionPool'); 692 693 foreach ($this->object->getOrderingElementList() as $element) { 694 $feedback = $this->object->feedbackOBJ->getSpecificAnswerFeedbackTestPresentation( 695 $this->object->getId(), 696 0, 697 $element->getPosition() 698 ); 699 700 if ($this->object->isImageOrderingType()) { 701 $imgSrc = $this->object->getImagePathWeb() . $element->getContent(); 702 $tpl->setCurrentBlock('image'); 703 $tpl->setVariable('IMG_SRC', $imgSrc); 704 } else { 705 $tpl->setCurrentBlock('text'); 706 } 707 $tpl->setVariable('CONTENT', $element->getContent()); 708 $tpl->parseCurrentBlock(); 709 710 $tpl->setCurrentBlock('element'); 711 $tpl->setVariable('FEEDBACK', $feedback); 712 $tpl->parseCurrentBlock(); 713 } 714 715 return $this->object->prepareTextareaOutput($tpl->get(), true); 716 } 717 718 /** 719 * @param $form 720 * @throws ilTestQuestionPoolException 721 */ 722 protected function persistAuthoringForm($form) 723 { 724 $this->writeQuestionGenericPostData(); 725 $this->writeQuestionSpecificPostData($form); 726 $this->writeAnswerSpecificPostData($form); 727 $this->saveTaxonomyAssignments(); 728 } 729 730 private function getOldLeveledOrdering() 731 { 732 global $DIC; 733 $ilDB = $DIC['ilDB']; 734 735 $res = $ilDB->queryF( 736 'SELECT depth FROM qpl_a_ordering WHERE question_fi = %s ORDER BY solution_key ASC', 737 array('integer'), 738 array($this->object->getId()) 739 ); 740 while ($row = $ilDB->fetchAssoc($res)) { 741 $this->old_ordering_depth[] = $row['depth']; 742 } 743 return $this->old_ordering_depth; 744 } 745 746 /** 747 * Returns a list of postvars which will be suppressed in the form output when used in scoring adjustment. 748 * The form elements will be shown disabled, so the users see the usual form but can only edit the settings, which 749 * make sense in the given context. 750 * 751 * E.g. array('cloze_type', 'image_filename') 752 * 753 * @return string[] 754 */ 755 public function getAfterParticipationSuppressionAnswerPostVars() 756 { 757 return array(); 758 } 759 760 /** 761 * Returns a list of postvars which will be suppressed in the form output when used in scoring adjustment. 762 * The form elements will be shown disabled, so the users see the usual form but can only edit the settings, which 763 * make sense in the given context. 764 * 765 * E.g. array('cloze_type', 'image_filename') 766 * 767 * @return string[] 768 */ 769 public function getAfterParticipationSuppressionQuestionPostVars() 770 { 771 return array(); 772 } 773 774 /** 775 * Returns an html string containing a question specific representation of the answers so far 776 * given in the test for use in the right column in the scoring adjustment user interface. 777 * 778 * @param array $relevant_answers 779 * 780 * @return string 781 */ 782 public function getAggregatedAnswersView($relevant_answers) 783 { 784 $aggView = $this->aggregateAnswers( 785 $relevant_answers, 786 $this->object->getOrderingElementList() 787 ); 788 789 return $this->renderAggregateView($aggView)->get(); 790 } 791 792 public function aggregateAnswers($relevant_answers_chosen, $answers_defined_on_question) 793 { 794 $passdata = array(); // Regroup answers into units of passes. 795 foreach ($relevant_answers_chosen as $answer_chosen) { 796 $passdata[$answer_chosen['active_fi'] . '-' . $answer_chosen['pass']][$answer_chosen['value2']] = $answer_chosen['value1']; 797 } 798 799 $variants = array(); // Determine unique variants. 800 foreach ($passdata as $key => $data) { 801 $hash = md5(implode('-', $data)); 802 $value_set = false; 803 foreach ($variants as $vkey => $variant) { 804 if ($variant['hash'] == $hash) { 805 $variant['count']++; 806 $value_set = true; 807 } 808 } 809 if (!$value_set) { 810 $variants[$key]['hash'] = $hash; 811 $variants[$key]['count'] = 1; 812 } 813 } 814 815 $aggregate = array(); // Render aggregate from variant. 816 foreach ($variants as $key => $variant_entry) { 817 $variant = $passdata[$key]; 818 819 foreach ($variant as $variant_key => $variant_line) { 820 $i = 0; 821 $aggregated_info_for_answer['count'] = $variant_entry['count']; 822 foreach ($answers_defined_on_question as $element) { 823 $i++; 824 825 if ($this->object->isImageOrderingType()) { 826 $element->setImageThumbnailPrefix($this->object->getThumbPrefix()); 827 $element->setImagePathWeb($this->object->getImagePathWeb()); 828 $element->setImagePathFs($this->object->getImagePath()); 829 830 $src = $element->getPresentationImageUrl(); 831 $alt = $element->getContent(); 832 $content = "<img src='{$src}' alt='{$alt}' title='{$alt}'/>"; 833 } else { 834 $content = $element->getContent(); 835 } 836 837 $aggregated_info_for_answer[$i . ' - ' . $content] 838 = $passdata[$key][$i]; 839 } 840 } 841 $aggregate[] = $aggregated_info_for_answer; 842 } 843 return $aggregate; 844 } 845 846 /** 847 * @param $aggregate 848 * 849 * @return ilTemplate 850 */ 851 public function renderAggregateView($aggregate) 852 { 853 $tpl = new ilTemplate('tpl.il_as_aggregated_answers_table.html', true, true, "Modules/TestQuestionPool"); 854 855 foreach ($aggregate as $line_data) { 856 $tpl->setCurrentBlock('aggregaterow'); 857 $count = array_shift($line_data); 858 $html = '<ul>'; 859 foreach ($line_data as $key => $line) { 860 $html .= '<li>' . ++$line . ' - ' . $key . '</li>'; 861 } 862 $html .= '</ul>'; 863 $tpl->setVariable('COUNT', $count); 864 $tpl->setVariable('OPTION', $html); 865 866 $tpl->parseCurrentBlock(); 867 } 868 return $tpl; 869 } 870 871 protected function getAnswerStatisticOrderingElementHtml(ilAssOrderingElement $element) 872 { 873 if ($this->object->isImageOrderingType()) { 874 $element->setImageThumbnailPrefix($this->object->getThumbPrefix()); 875 $element->setImagePathWeb($this->object->getImagePathWeb()); 876 $element->setImagePathFs($this->object->getImagePath()); 877 878 $src = $element->getPresentationImageUrl(); 879 $alt = $element->getContent(); 880 $content = "<img src='{$src}' alt='{$alt}' title='{$alt}'/>"; 881 } else { 882 $content = $element->getContent(); 883 } 884 885 return $content; 886 } 887 888 protected function getAnswerStatisticOrderingVariantHtml(ilAssOrderingElementList $list) 889 { 890 $html = '<ul>'; 891 892 $lastIndent = 0; 893 $firstElem = true; 894 895 foreach ($list as $elem) { 896 if ($elem->getIndentation() > $lastIndent) { 897 $html .= '<ul><li>'; 898 } elseif ($elem->getIndentation() < $lastIndent) { 899 $html .= '</li></ul><li>'; 900 } elseif (!$firstElem) { 901 $html .= '</li><li>'; 902 } else { 903 $html .= '<li>'; 904 } 905 906 $html .= $this->getAnswerStatisticOrderingElementHtml($elem); 907 908 $firstElem = false; 909 $lastIndent = $elem->getIndentation(); 910 } 911 912 $html .= '</li>'; 913 914 for ($i = $lastIndent; $i > 0; $i--) { 915 $html .= '</ul></li>'; 916 } 917 918 $html .= '</ul>'; 919 920 return $html; 921 } 922 923 public function getAnswersFrequency($relevantAnswers, $questionIndex) 924 { 925 $answersByActiveAndPass = array(); 926 927 foreach ($relevantAnswers as $row) { 928 $key = $row['active_fi'] . ':' . $row['pass']; 929 930 if (!isset($answersByActiveAndPass[$key])) { 931 $answersByActiveAndPass[$key] = array(); 932 } 933 934 $answersByActiveAndPass[$key][$row['value1']] = $row['value2']; 935 } 936 937 $solutionLists = array(); 938 939 foreach ($answersByActiveAndPass as $indexedSolutions) { 940 $solutionLists[] = $this->object->getSolutionOrderingElementList($indexedSolutions); 941 } 942 943 /* @var ilAssOrderingElementList[] $answers */ 944 $answers = array(); 945 946 foreach ($solutionLists as $orderingElementList) { 947 $hash = $orderingElementList->getHash(); 948 949 if (!isset($answers[$hash])) { 950 $variantHtml = $this->getAnswerStatisticOrderingVariantHtml( 951 $orderingElementList 952 ); 953 954 $answers[$hash] = array( 955 'answer' => $variantHtml, 'frequency' => 0 956 ); 957 } 958 959 $answers[$hash]['frequency']++; 960 } 961 962 return array_values($answers); 963 } 964 965 /** 966 * @param ilPropertyFormGUI $form 967 */ 968 public function prepareReprintableCorrectionsForm(ilPropertyFormGUI $form) 969 { 970 $orderingInput = $form->getItemByPostVar(assOrderingQuestion::ORDERING_ELEMENT_FORM_FIELD_POSTVAR); 971 $orderingInput->prepareReprintable($this->object); 972 } 973 974 /** 975 * @param ilPropertyFormGUI $form 976 */ 977 public function populateCorrectionsFormProperties(ilPropertyFormGUI $form) 978 { 979 $points = new ilNumberInputGUI($this->lng->txt("points"), "points"); 980 $points->allowDecimals(true); 981 $points->setValue($this->object->getPoints()); 982 $points->setRequired(true); 983 $points->setSize(3); 984 $points->setMinValue(0); 985 $points->setMinvalueShouldBeGreater(true); 986 $form->addItem($points); 987 988 $header = new ilFormSectionHeaderGUI(); 989 $header->setTitle($this->lng->txt('oq_header_ordering_elements')); 990 $form->addItem($header); 991 992 $orderingElementInput = $this->object->buildNestedOrderingElementInputGui(); 993 994 $this->object->initOrderingElementAuthoringProperties($orderingElementInput); 995 996 $orderingElementInput->setElementList($this->object->getOrderingElementList()); 997 998 $form->addItem($orderingElementInput); 999 } 1000 1001 /** 1002 * @param ilPropertyFormGUI $form 1003 */ 1004 public function saveCorrectionsFormProperties(ilPropertyFormGUI $form) 1005 { 1006 $this->object->setPoints((float) $form->getInput('points')); 1007 1008 $submittedElementList = $this->object->fetchSolutionListFromSubmittedForm($form); 1009 1010 $curElementList = $this->object->getOrderingElementList(); 1011 1012 $newElementList = new ilAssOrderingElementList(); 1013 $newElementList->setQuestionId($this->object->getId()); 1014 1015 foreach ($submittedElementList as $submittedElement) { 1016 if (!$curElementList->elementExistByRandomIdentifier($submittedElement->getRandomIdentifier())) { 1017 continue; 1018 } 1019 1020 $curElement = $curElementList->getElementByRandomIdentifier($submittedElement->getRandomIdentifier()); 1021 1022 $curElement->setPosition($submittedElement->getPosition()); 1023 1024 if ($this->object->isOrderingTypeNested()) { 1025 $curElement->setIndentation($submittedElement->getIndentation()); 1026 } 1027 1028 $newElementList->addElement($curElement); 1029 } 1030 1031 $this->object->setOrderingElementList($newElementList); 1032 } 1033} 1034