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->isImageOrderingType()) { 304 $geometry = new ilNumberInputGUI($this->lng->txt("thumb_geometry"), "thumb_geometry"); 305 $geometry->setValue($this->object->getThumbGeometry()); 306 $geometry->setRequired(true); 307 $geometry->setMaxLength(6); 308 $geometry->setMinValue(20); 309 $geometry->setSize(6); 310 $geometry->setInfo($this->lng->txt("thumb_geometry_info")); 311 $form->addItem($geometry); 312 } 313 314 // points 315 $points = new ilNumberInputGUI($this->lng->txt("points"), "points"); 316 $points->allowDecimals(true); 317 $points->setValue($this->object->getPoints()); 318 $points->setRequired(true); 319 $points->setSize(3); 320 $points->setMinValue(0); 321 $points->setMinvalueShouldBeGreater(true); 322 $form->addItem($points); 323 324 return $form; 325 } 326 327 /** 328 * {@inheritdoc} 329 */ 330 protected function writePostData($forceSaving = false) 331 { 332 $savingAllowed = true; // assume saving allowed first 333 334 if (!$forceSaving) { 335 // this case seems to be a regular save call, so we consider 336 // the validation result for the decision of saving as well 337 338 // inits {this->editForm} and performs validation 339 $form = $this->buildEditForm(); 340 $form->setValuesByPost(); // manipulation and distribution of values 341 342 if (!$form->checkInput()) { // manipulations regular style input propeties 343 $form->prepareValuesReprintable($this->object); 344 $this->renderEditForm($form); 345 346 // consequence of vaidation 347 $savingAllowed = false; 348 } 349 } elseif (!$this->isSaveCommand()) { 350 // this case handles form workflow actions like the mode/view switching requests, 351 // so saving must not be skipped, even for inputs invalid by business rules 352 353 $form = $this->buildEditForm(); 354 $form->setValuesByPost(); // manipulation and distribution of values 355 $form->checkInput(); // manipulations regular style input propeties 356 } 357 358 if ($savingAllowed) { 359 $this->persistAuthoringForm($form); 360 361 return 0; // return 0 = all fine, was saved either forced or validated 362 } 363 364 return 1; // return 1 = something went wrong, no saving happened 365 } 366 367 /** 368 * Creates an output of the edit form for the question 369 */ 370 public function editQuestion($checkonly = false) 371 { 372 $this->renderEditForm($this->buildEditForm()); 373 } 374 375 /** 376 * @return ilAssOrderingQuestionAuthoringFormGUI 377 */ 378 protected function buildEditForm() 379 { 380 require_once 'Modules/TestQuestionPool/classes/forms/class.ilAssOrderingQuestionAuthoringFormGUI.php'; 381 $form = new ilAssOrderingQuestionAuthoringFormGUI(); 382 $this->editForm = $form; 383 384 $form->setFormAction($this->ctrl->getFormAction($this)); 385 $form->setTitle($this->outQuestionType()); 386 $form->setMultipart(($this->object->getOrderingType() == OQ_PICTURES) ? true : false); 387 $form->setTableWidth("100%"); 388 $form->setId("ordering"); 389 // title, author, description, question, working time (assessment mode) 390 $this->addBasicQuestionFormProperties($form); 391 $this->populateQuestionSpecificFormPart($form); 392 $this->populateAnswerSpecificFormPart($form); 393 394 $this->populateTaxonomyFormSection($form); 395 396 $form->addSpecificOrderingQuestionCommandButtons($this->object); 397 $form->addGenericAssessmentQuestionCommandButtons($this->object); 398 399 return $form; 400 } 401 402 /** 403 * Question type specific support of intermediate solution output 404 * The function getSolutionOutput respects getUseIntermediateSolution() 405 * @return bool 406 */ 407 public function supportsIntermediateSolutionOutput() 408 { 409 return true; 410 } 411 412 /** 413 * Get the question solution output 414 * 415 * @param integer $active_id The active user id 416 * @param integer $pass The test pass 417 * @param boolean $graphicalOutput Show visual feedback for right/wrong answers 418 * @param boolean $result_output Show the reached points for parts of the question 419 * @param boolean $show_question_only Show the question without the ILIAS content around 420 * @param boolean $show_feedback Show the question feedback 421 * @param boolean $show_correct_solution Show the correct solution instead of the user solution 422 * @param boolean $show_manual_scoring Show specific information for the manual scoring output 423 * @param bool $show_question_text 424 * 425 * @return string The solution output of the question as HTML code 426 */ 427 public function getSolutionOutput( 428 $active_id, 429 $pass = null, 430 $graphicalOutput = false, 431 $result_output = false, 432 $show_question_only = true, 433 $show_feedback = false, 434 $forceCorrectSolution = false, 435 $show_manual_scoring = false, 436 $show_question_text = true 437 ) { 438 $solutionOrderingList = $this->object->getOrderingElementListForSolutionOutput( 439 $forceCorrectSolution, 440 $active_id, 441 $pass, 442 $this->getUseIntermediateSolution() 443 ); 444 445 $answers_gui = $this->object->buildNestedOrderingElementInputGui(); 446 447 if ($forceCorrectSolution) { 448 $answers_gui->setContext(ilAssNestedOrderingElementsInputGUI::CONTEXT_CORRECT_SOLUTION_PRESENTATION); 449 } else { 450 $answers_gui->setContext(ilAssNestedOrderingElementsInputGUI::CONTEXT_USER_SOLUTION_PRESENTATION); 451 } 452 453 $answers_gui->setInteractionEnabled(false); 454 455 $answers_gui->setElementList($solutionOrderingList); 456 457 $answers_gui->setCorrectnessTrueElementList( 458 $solutionOrderingList->getParityTrueElementList($this->object->getOrderingElementList()) 459 ); 460 461 $solution_html = $answers_gui->getHTML(); 462 463 $template = new ilTemplate("tpl.il_as_qpl_nested_ordering_output_solution.html", true, true, "Modules/TestQuestionPool"); 464 $template->setVariable('SOLUTION_OUTPUT', $solution_html); 465 if ($show_question_text == true) { 466 $template->setVariable("QUESTIONTEXT", $this->object->prepareTextareaOutput($this->object->getQuestion(), true)); 467 } 468 $questionoutput = $template->get(); 469 470 $solutiontemplate = new ilTemplate("tpl.il_as_tst_solution_output.html", true, true, "Modules/TestQuestionPool"); 471 $solutiontemplate->setVariable("SOLUTION_OUTPUT", $questionoutput); 472 473 if ($show_feedback) { 474 $feedback = ''; 475 476 if (!$this->isTestPresentationContext()) { 477 $fb = $this->getGenericFeedbackOutput($active_id, $pass); 478 $feedback .= strlen($fb) ? $fb : ''; 479 } 480 481 $fb = $this->getSpecificFeedbackOutput(array()); 482 $feedback .= strlen($fb) ? $fb : ''; 483 484 if (strlen($feedback)) { 485 $cssClass = ( 486 $this->hasCorrectSolution($active_id, $pass) ? 487 ilAssQuestionFeedback::CSS_CLASS_FEEDBACK_CORRECT : ilAssQuestionFeedback::CSS_CLASS_FEEDBACK_WRONG 488 ); 489 490 $solutiontemplate->setVariable("ILC_FB_CSS_CLASS", $cssClass); 491 $solutiontemplate->setVariable("FEEDBACK", $this->object->prepareTextareaOutput($feedback, true)); 492 } 493 } 494 495 if ($show_question_only) { 496 return $solutiontemplate->get(); 497 } 498 499 return $this->getILIASPage($solutiontemplate->get()); 500 501 // is this template still in use? it is not used at this point any longer! 502 // $template = new ilTemplate("tpl.il_as_qpl_ordering_output_solution.html", TRUE, TRUE, "Modules/TestQuestionPool"); 503 } 504 505 public function getPreview($show_question_only = false, $showInlineFeedback = false) 506 { 507 if ($this->getPreviewSession() && $this->getPreviewSession()->hasParticipantSolution()) { 508 $solutionOrderingElementList = unserialize( 509 $this->getPreviewSession()->getParticipantsSolution() 510 ); 511 } else { 512 $solutionOrderingElementList = $this->object->getShuffledOrderingElementList(); 513 } 514 515 $answers = $this->object->buildNestedOrderingElementInputGui(); 516 $answers->setNestingEnabled($this->object->isOrderingTypeNested()); 517 $answers->setContext(ilAssNestedOrderingElementsInputGUI::CONTEXT_QUESTION_PREVIEW); 518 $answers->setInteractionEnabled($this->isInteractivePresentation()); 519 $answers->setElementList($solutionOrderingElementList); 520 521 $template = new ilTemplate("tpl.il_as_qpl_ordering_output.html", true, true, "Modules/TestQuestionPool"); 522 523 $template->setCurrentBlock('nested_ordering_output'); 524 $template->setVariable('NESTED_ORDERING', $answers->getHTML()); 525 $template->parseCurrentBlock(); 526 527 $template->setVariable("QUESTIONTEXT", $this->object->prepareTextareaOutput($this->object->getQuestion(), true)); 528 529 if ($show_question_only) { 530 return $template->get(); 531 } 532 533 return $this->getILIASPage($template->get()); 534 535 //$this->tpl->addJavascript("./Modules/TestQuestionPool/templates/default/ordering.js"); 536 } 537 538 public function getPresentationJavascripts() 539 { 540 global $DIC; /* @var ILIAS\DI\Container $DIC */ 541 542 $files = array(); 543 544 if ($DIC['ilBrowser']->isMobile() || $DIC['ilBrowser']->isIpad()) { 545 $files[] = './libs/bower/bower_components/jqueryui-touch-punch/jquery.ui.touch-punch.min.js'; 546 } 547 548 return $files; 549 } 550 551 // hey: prevPassSolutions - pass will be always available from now on 552 public function getTestOutput($activeId, $pass, $isPostponed = false, $userSolutionPost = false, $inlineFeedback = false) 553 // hey. 554 { 555 // hey: prevPassSolutions - fixed variable type, makes phpstorm stop crying 556 $userSolutionPost = is_array($userSolutionPost) ? $userSolutionPost : array(); 557 // hey. 558 559 $orderingGUI = $this->object->buildNestedOrderingElementInputGui(); 560 $orderingGUI->setNestingEnabled($this->object->isOrderingTypeNested()); 561 562 $solutionOrderingElementList = $this->object->getSolutionOrderingElementListForTestOutput( 563 $orderingGUI, 564 $userSolutionPost, 565 $activeId, 566 $pass 567 ); 568 569 $template = new ilTemplate('tpl.il_as_qpl_ordering_output.html', true, true, 'Modules/TestQuestionPool'); 570 571 $orderingGUI->setContext(ilAssNestedOrderingElementsInputGUI::CONTEXT_USER_SOLUTION_SUBMISSION); 572 $orderingGUI->setElementList($solutionOrderingElementList); 573 574 $template->setCurrentBlock('nested_ordering_output'); 575 $template->setVariable('NESTED_ORDERING', $orderingGUI->getHTML()); 576 $template->parseCurrentBlock(); 577 578 $template->setVariable('QUESTIONTEXT', $this->object->prepareTextareaOutput($this->object->getQuestion(), true)); 579 580 $pageoutput = $this->outQuestionPage('', $isPostponed, $activeId, $template->get()); 581 582 return $pageoutput; 583 } 584 585 protected function isInteractivePresentation() 586 { 587 if ($this->isRenderPurposePlayback()) { 588 return true; 589 } 590 591 if ($this->isRenderPurposeDemoplay()) { 592 return true; 593 } 594 595 return false; 596 } 597 598 /** 599 * Sets the ILIAS tabs for this question type 600 * 601 * @access public 602 * 603 * @todo: MOVE THIS STEPS TO COMMON QUESTION CLASS assQuestionGUI 604 */ 605 public function setQuestionTabs() 606 { 607 global $DIC; 608 $rbacsystem = $DIC['rbacsystem']; 609 $ilTabs = $DIC['ilTabs']; 610 611 $ilTabs->clearTargets(); 612 613 $this->ctrl->setParameterByClass("ilAssQuestionPageGUI", "q_id", $_GET["q_id"]); 614 include_once "./Modules/TestQuestionPool/classes/class.assQuestion.php"; 615 $q_type = $this->object->getQuestionType(); 616 617 if (strlen($q_type)) { 618 $classname = $q_type . "GUI"; 619 $this->ctrl->setParameterByClass(strtolower($classname), "sel_question_types", $q_type); 620 $this->ctrl->setParameterByClass(strtolower($classname), "q_id", $_GET["q_id"]); 621 } 622 623 if ($_GET["q_id"]) { 624 if ($rbacsystem->checkAccess('write', $_GET["ref_id"])) { 625 // edit page 626 $ilTabs->addTarget( 627 "edit_page", 628 $this->ctrl->getLinkTargetByClass("ilAssQuestionPageGUI", "edit"), 629 array("edit", "insert", "exec_pg"), 630 "", 631 "", 632 false 633 ); 634 } 635 636 $this->addTab_QuestionPreview($ilTabs); 637 } 638 639 $force_active = false; 640 if ($rbacsystem->checkAccess('write', $_GET["ref_id"])) { 641 $url = ""; 642 if ($classname) { 643 $url = $this->ctrl->getLinkTargetByClass($classname, "editQuestion"); 644 } 645 $commands = $_POST["cmd"]; 646 if (is_array($commands)) { 647 foreach ($commands as $key => $value) { 648 if (preg_match("/^delete_.*/", $key, $matches)) { 649 $force_active = true; 650 } 651 } 652 } 653 // edit question properties 654 $ilTabs->addTarget( 655 "edit_question", 656 $url, 657 array("orderNestedTerms","orderNestedPictures","editQuestion", "save", "saveEdit", "addanswers", "removeanswers", "changeToPictures", "uploadElementImage", "changeToText", "upanswers", "downanswers", "originalSyncForm"), 658 $classname, 659 "", 660 $force_active 661 ); 662 } 663 664 // add tab for question feedback within common class assQuestionGUI 665 $this->addTab_QuestionFeedback($ilTabs); 666 667 // add tab for question hint within common class assQuestionGUI 668 $this->addTab_QuestionHints($ilTabs); 669 670 // add tab for question's suggested solution within common class assQuestionGUI 671 $this->addTab_SuggestedSolution($ilTabs, $classname); 672 673 // Assessment of questions sub menu entry 674 if ($_GET["q_id"]) { 675 $ilTabs->addTarget( 676 "statistics", 677 $this->ctrl->getLinkTargetByClass($classname, "assessment"), 678 array("assessment"), 679 $classname, 680 "" 681 ); 682 } 683 684 $this->addBackTab($ilTabs); 685 } 686 687 public function getSpecificFeedbackOutput($userSolution) 688 { 689 if (!$this->object->feedbackOBJ->specificAnswerFeedbackExists()) { 690 return ''; 691 } 692 693 $tpl = new ilTemplate('tpl.il_as_qpl_ordering_elem_fb.html', true, true, 'Modules/TestQuestionPool'); 694 695 foreach ($this->object->getOrderingElementList() as $element) { 696 $feedback = $this->object->feedbackOBJ->getSpecificAnswerFeedbackTestPresentation( 697 $this->object->getId(), 698 0, 699 $element->getPosition() 700 ); 701 702 if ($this->object->isImageOrderingType()) { 703 $imgSrc = $this->object->getImagePathWeb() . $element->getContent(); 704 $tpl->setCurrentBlock('image'); 705 $tpl->setVariable('IMG_SRC', $imgSrc); 706 } else { 707 $tpl->setCurrentBlock('text'); 708 } 709 $tpl->setVariable('CONTENT', $element->getContent()); 710 $tpl->parseCurrentBlock(); 711 712 $tpl->setCurrentBlock('element'); 713 $tpl->setVariable('FEEDBACK', $feedback); 714 $tpl->parseCurrentBlock(); 715 } 716 717 return $this->object->prepareTextareaOutput($tpl->get(), true); 718 } 719 720 /** 721 * @param $form 722 * @throws ilTestQuestionPoolException 723 */ 724 protected function persistAuthoringForm($form) 725 { 726 $this->writeQuestionGenericPostData(); 727 $this->writeQuestionSpecificPostData($form); 728 $this->writeAnswerSpecificPostData($form); 729 $this->saveTaxonomyAssignments(); 730 } 731 732 private function getOldLeveledOrdering() 733 { 734 global $DIC; 735 $ilDB = $DIC['ilDB']; 736 737 $res = $ilDB->queryF( 738 'SELECT depth FROM qpl_a_ordering WHERE question_fi = %s ORDER BY solution_key ASC', 739 array('integer'), 740 array($this->object->getId()) 741 ); 742 while ($row = $ilDB->fetchAssoc($res)) { 743 $this->old_ordering_depth[] = $row['depth']; 744 } 745 return $this->old_ordering_depth; 746 } 747 748 /** 749 * Returns a list of postvars which will be suppressed in the form output when used in scoring adjustment. 750 * The form elements will be shown disabled, so the users see the usual form but can only edit the settings, which 751 * make sense in the given context. 752 * 753 * E.g. array('cloze_type', 'image_filename') 754 * 755 * @return string[] 756 */ 757 public function getAfterParticipationSuppressionAnswerPostVars() 758 { 759 return array(); 760 } 761 762 /** 763 * Returns a list of postvars which will be suppressed in the form output when used in scoring adjustment. 764 * The form elements will be shown disabled, so the users see the usual form but can only edit the settings, which 765 * make sense in the given context. 766 * 767 * E.g. array('cloze_type', 'image_filename') 768 * 769 * @return string[] 770 */ 771 public function getAfterParticipationSuppressionQuestionPostVars() 772 { 773 return array(); 774 } 775 776 /** 777 * Returns an html string containing a question specific representation of the answers so far 778 * given in the test for use in the right column in the scoring adjustment user interface. 779 * 780 * @param array $relevant_answers 781 * 782 * @return string 783 */ 784 public function getAggregatedAnswersView($relevant_answers) 785 { 786 $aggView = $this->aggregateAnswers( 787 $relevant_answers, 788 $this->object->getOrderingElementList() 789 ); 790 791 return $this->renderAggregateView($aggView)->get(); 792 } 793 794 public function aggregateAnswers($relevant_answers_chosen, $answers_defined_on_question) 795 { 796 $passdata = array(); // Regroup answers into units of passes. 797 foreach ($relevant_answers_chosen as $answer_chosen) { 798 $passdata[$answer_chosen['active_fi'] . '-' . $answer_chosen['pass']][$answer_chosen['value2']] = $answer_chosen['value1']; 799 } 800 801 $variants = array(); // Determine unique variants. 802 foreach ($passdata as $key => $data) { 803 $hash = md5(implode('-', $data)); 804 $value_set = false; 805 foreach ($variants as $vkey => $variant) { 806 if ($variant['hash'] == $hash) { 807 $variant['count']++; 808 $value_set = true; 809 } 810 } 811 if (!$value_set) { 812 $variants[$key]['hash'] = $hash; 813 $variants[$key]['count'] = 1; 814 } 815 } 816 817 $aggregate = array(); // Render aggregate from variant. 818 foreach ($variants as $key => $variant_entry) { 819 $variant = $passdata[$key]; 820 821 foreach ($variant as $variant_key => $variant_line) { 822 $i = 0; 823 $aggregated_info_for_answer['count'] = $variant_entry['count']; 824 foreach ($answers_defined_on_question as $element) { 825 $i++; 826 827 if ($this->object->isImageOrderingType()) { 828 $element->setImageThumbnailPrefix($this->object->getThumbPrefix()); 829 $element->setImagePathWeb($this->object->getImagePathWeb()); 830 $element->setImagePathFs($this->object->getImagePath()); 831 832 $src = $element->getPresentationImageUrl(); 833 $alt = $element->getContent(); 834 $content = "<img src='{$src}' alt='{$alt}' title='{$alt}'/>"; 835 } else { 836 $content = $element->getContent(); 837 } 838 839 $aggregated_info_for_answer[$i . ' - ' . $content] 840 = $passdata[$key][$i]; 841 } 842 } 843 $aggregate[] = $aggregated_info_for_answer; 844 } 845 return $aggregate; 846 } 847 848 /** 849 * @param $aggregate 850 * 851 * @return ilTemplate 852 */ 853 public function renderAggregateView($aggregate) 854 { 855 $tpl = new ilTemplate('tpl.il_as_aggregated_answers_table.html', true, true, "Modules/TestQuestionPool"); 856 857 foreach ($aggregate as $line_data) { 858 $tpl->setCurrentBlock('aggregaterow'); 859 $count = array_shift($line_data); 860 $html = '<ul>'; 861 foreach ($line_data as $key => $line) { 862 $html .= '<li>' . ++$line . ' - ' . $key . '</li>'; 863 } 864 $html .= '</ul>'; 865 $tpl->setVariable('COUNT', $count); 866 $tpl->setVariable('OPTION', $html); 867 868 $tpl->parseCurrentBlock(); 869 } 870 return $tpl; 871 } 872 873 protected function getAnswerStatisticOrderingElementHtml(ilAssOrderingElement $element) 874 { 875 if ($this->object->isImageOrderingType()) { 876 $element->setImageThumbnailPrefix($this->object->getThumbPrefix()); 877 $element->setImagePathWeb($this->object->getImagePathWeb()); 878 $element->setImagePathFs($this->object->getImagePath()); 879 880 $src = $element->getPresentationImageUrl(); 881 $alt = $element->getContent(); 882 $content = "<img src='{$src}' alt='{$alt}' title='{$alt}'/>"; 883 } else { 884 $content = $element->getContent(); 885 } 886 887 return $content; 888 } 889 890 protected function getAnswerStatisticOrderingVariantHtml(ilAssOrderingElementList $list) 891 { 892 $html = '<ul>'; 893 894 $lastIndent = 0; 895 $firstElem = true; 896 897 foreach ($list as $elem) { 898 if ($elem->getIndentation() > $lastIndent) { 899 $html .= '<ul><li>'; 900 } elseif ($elem->getIndentation() < $lastIndent) { 901 $html .= '</li></ul><li>'; 902 } elseif (!$firstElem) { 903 $html .= '</li><li>'; 904 } else { 905 $html .= '<li>'; 906 } 907 908 $html .= $this->getAnswerStatisticOrderingElementHtml($elem); 909 910 $firstElem = false; 911 $lastIndent = $elem->getIndentation(); 912 } 913 914 $html .= '</li>'; 915 916 for ($i = $lastIndent; $i > 0; $i--) { 917 $html .= '</ul></li>'; 918 } 919 920 $html .= '</ul>'; 921 922 return $html; 923 } 924 925 public function getAnswersFrequency($relevantAnswers, $questionIndex) 926 { 927 $answersByActiveAndPass = array(); 928 929 foreach ($relevantAnswers as $row) { 930 $key = $row['active_fi'] . ':' . $row['pass']; 931 932 if (!isset($answersByActiveAndPass[$key])) { 933 $answersByActiveAndPass[$key] = array(); 934 } 935 936 $answersByActiveAndPass[$key][$row['value1']] = $row['value2']; 937 } 938 939 $solutionLists = array(); 940 941 foreach ($answersByActiveAndPass as $indexedSolutions) { 942 $solutionLists[] = $this->object->getSolutionOrderingElementList($indexedSolutions); 943 } 944 945 /* @var ilAssOrderingElementList[] $answers */ 946 $answers = array(); 947 948 foreach ($solutionLists as $orderingElementList) { 949 $hash = $orderingElementList->getHash(); 950 951 if (!isset($answers[$hash])) { 952 $variantHtml = $this->getAnswerStatisticOrderingVariantHtml( 953 $orderingElementList 954 ); 955 956 $answers[$hash] = array( 957 'answer' => $variantHtml, 'frequency' => 0 958 ); 959 } 960 961 $answers[$hash]['frequency']++; 962 } 963 964 return array_values($answers); 965 } 966 967 /** 968 * @param ilPropertyFormGUI $form 969 */ 970 public function prepareReprintableCorrectionsForm(ilPropertyFormGUI $form) 971 { 972 $orderingInput = $form->getItemByPostVar(assOrderingQuestion::ORDERING_ELEMENT_FORM_FIELD_POSTVAR); 973 $orderingInput->prepareReprintable($this->object); 974 } 975 976 /** 977 * @param ilPropertyFormGUI $form 978 */ 979 public function populateCorrectionsFormProperties(ilPropertyFormGUI $form) 980 { 981 $points = new ilNumberInputGUI($this->lng->txt("points"), "points"); 982 $points->allowDecimals(true); 983 $points->setValue($this->object->getPoints()); 984 $points->setRequired(true); 985 $points->setSize(3); 986 $points->setMinValue(0); 987 $points->setMinvalueShouldBeGreater(true); 988 $form->addItem($points); 989 990 $header = new ilFormSectionHeaderGUI(); 991 $header->setTitle($this->lng->txt('oq_header_ordering_elements')); 992 $form->addItem($header); 993 994 $orderingElementInput = $this->object->buildNestedOrderingElementInputGui(); 995 996 $this->object->initOrderingElementAuthoringProperties($orderingElementInput); 997 998 $orderingElementInput->setElementList($this->object->getOrderingElementList()); 999 1000 $form->addItem($orderingElementInput); 1001 } 1002 1003 /** 1004 * @param ilPropertyFormGUI $form 1005 */ 1006 public function saveCorrectionsFormProperties(ilPropertyFormGUI $form) 1007 { 1008 $this->object->setPoints((float) $form->getInput('points')); 1009 1010 $submittedElementList = $this->object->fetchSolutionListFromSubmittedForm($form); 1011 1012 $curElementList = $this->object->getOrderingElementList(); 1013 1014 $newElementList = new ilAssOrderingElementList(); 1015 $newElementList->setQuestionId($this->object->getId()); 1016 1017 foreach ($submittedElementList as $submittedElement) { 1018 if (!$curElementList->elementExistByRandomIdentifier($submittedElement->getRandomIdentifier())) { 1019 continue; 1020 } 1021 1022 $curElement = $curElementList->getElementByRandomIdentifier($submittedElement->getRandomIdentifier()); 1023 1024 $curElement->setPosition($submittedElement->getPosition()); 1025 1026 if ($this->object->isOrderingTypeNested()) { 1027 $curElement->setIndentation($submittedElement->getIndentation()); 1028 } 1029 1030 $newElementList->addElement($curElement); 1031 } 1032 1033 $this->object->setOrderingElementList($newElementList); 1034 } 1035} 1036