1<?php 2/* Copyright (c) 1998-2013 ILIAS open source, Extended GPL, see docs/LICENSE */ 3 4require_once './Modules/TestQuestionPool/classes/class.assQuestion.php'; 5require_once './Modules/Test/classes/inc.AssessmentConstants.php'; 6require_once './Modules/TestQuestionPool/interfaces/interface.ilObjQuestionScoringAdjustable.php'; 7require_once './Modules/TestQuestionPool/interfaces/interface.iQuestionCondition.php'; 8require_once './Modules/TestQuestionPool/classes/class.ilUserQuestionResult.php'; 9 10 11class assLongMenu extends assQuestion implements ilObjQuestionScoringAdjustable 12{ 13 private $answerType; 14 private $long_menu_text; 15 private $json_structure; 16 private $ilDB; 17 private $specificFeedbackSetting; 18 private $minAutoComplete; 19 private $identical_scoring; 20 21 const ANSWER_TYPE_SELECT_VAL = 0; 22 const ANSWER_TYPE_TEXT_VAL = 1; 23 const GAP_PLACEHOLDER = 'Longmenu'; 24 const MIN_LENGTH_AUTOCOMPLETE = 3; 25 const MAX_INPUT_FIELDS = 500; 26 27 /** @var array */ 28 private $correct_answers = []; 29 30 /** @var array */ 31 private $answers = []; 32 33 public function __construct( 34 $title = "", 35 $comment = "", 36 $author = "", 37 $owner = -1, 38 $question = "" 39 ) { 40 global $DIC; 41 require_once 'Modules/TestQuestionPool/classes/feedback/class.ilAssConfigurableMultiOptionQuestionFeedback.php'; 42 $this->specificFeedbackSetting = ilAssConfigurableMultiOptionQuestionFeedback::FEEDBACK_SETTING_ALL; 43 $this->minAutoComplete = self::MIN_LENGTH_AUTOCOMPLETE; 44 parent::__construct($title, $comment, $author, $owner, $question); 45 $this->ilDB = $DIC->database(); 46 $this->identical_scoring = 1; 47 } 48 49 /** 50 * @return mixed 51 */ 52 public function getAnswerType() 53 { 54 return $this->answerType; 55 } 56 57 /** 58 * @param mixed $answerType 59 */ 60 public function setAnswerType($answerType) 61 { 62 $this->answerType = $answerType; 63 } 64 65 /** 66 * @return mixed 67 */ 68 public function getCorrectAnswers() 69 { 70 return $this->correct_answers; 71 } 72 73 74 public function setCorrectAnswers($correct_answers) 75 { 76 $this->correct_answers = $correct_answers; 77 } 78 79 private function buildFolderName() 80 { 81 return ilUtil::getDataDir() . '/assessment/longMenuQuestion/' . $this->getId() . '/' ; 82 } 83 84 public function getAnswerTableName() 85 { 86 return "qpl_a_lome"; 87 } 88 89 private function buildFileName($gap_id) 90 { 91 try { 92 $this->assertDirExists(); 93 return $this->buildFolderName() . $gap_id . '.txt'; 94 } catch (ilException $e) { 95 } 96 } 97 98 public function setLongMenuTextValue($long_menu_text = "") 99 { 100 $this->long_menu_text = $long_menu_text; 101 } 102 103 public function getLongMenuTextValue() 104 { 105 return $this->long_menu_text; 106 } 107 108 public function setAnswers($answers) 109 { 110 $this->answers = $answers; 111 } 112 113 public function getAnswers() 114 { 115 return $this->answers; 116 } 117 118 /** 119 * @return mixed 120 */ 121 public function getJsonStructure() 122 { 123 return $this->json_structure; 124 } 125 126 /** 127 * @param mixed $json_structure 128 */ 129 public function setJsonStructure($json_structure) 130 { 131 $this->json_structure = $json_structure; 132 } 133 134 public function setSpecificFeedbackSetting($specificFeedbackSetting) 135 { 136 $this->specificFeedbackSetting = $specificFeedbackSetting; 137 } 138 139 public function getSpecificFeedbackSetting() 140 { 141 return $this->specificFeedbackSetting; 142 } 143 144 public function setMinAutoComplete($minAutoComplete) 145 { 146 $this->minAutoComplete = $minAutoComplete; 147 } 148 149 public function getMinAutoComplete() 150 { 151 return $this->minAutoComplete ? $this->minAutoComplete : self::MIN_LENGTH_AUTOCOMPLETE; 152 } 153 154 public function isComplete() 155 { 156 if (strlen($this->title) 157 && $this->author 158 && $this->long_menu_text 159 && sizeof($this->answers) > 0 160 && sizeof($this->correct_answers) > 0 161 && $this->getPoints() > 0 162 ) { 163 return true; 164 } 165 return false; 166 } 167 168 public function saveToDb($original_id = "") 169 { 170 $this->saveQuestionDataToDb($original_id); 171 $this->saveAdditionalQuestionDataToDb(); 172 $this->saveAnswerSpecificDataToDb(); 173 parent::saveToDb($original_id); 174 } 175 176 /** 177 * @param ilPropertyFormGUI|null $form 178 * @return bool 179 */ 180 public function checkQuestionCustomPart($form = null) 181 { 182 $hidden_text_files = $this->getAnswers(); 183 $correct_answers = $this->getCorrectAnswers(); 184 $points = array(); 185 if (sizeof($correct_answers) == 0 || sizeof($hidden_text_files) == 0) { 186 return false; 187 } 188 if (sizeof($correct_answers) != sizeof($hidden_text_files)) { 189 return false; 190 } 191 foreach ($correct_answers as $key => $correct_answers_row) { 192 if ($this->correctAnswerDoesNotExistInAnswerOptions($correct_answers_row, $hidden_text_files[$key])) { 193 return false; 194 } 195 if (!is_array($correct_answers_row[0]) || sizeof($correct_answers_row[0]) == 0) { 196 return false; 197 } 198 if ($correct_answers_row[1] > 0) { 199 array_push($points, $correct_answers_row[1]); 200 } 201 } 202 if (sizeof($correct_answers) != sizeof($points)) { 203 return false; 204 } 205 206 foreach ($points as $row) { 207 if ($row <= 0) { 208 return false; 209 } 210 } 211 return true; 212 } 213 214 /** 215 * @param $answers 216 * @param $answer_options 217 * @return bool 218 */ 219 private function correctAnswerDoesNotExistInAnswerOptions($answers, $answer_options) 220 { 221 foreach ($answers[0] as $key => $answer) { 222 if (!in_array($answer, $answer_options)) { 223 return true; 224 } 225 } 226 return false; 227 } 228 229 230 /** 231 * Returns the maximum points, a learner can reach answering the question 232 * 233 * @access public 234 * @see $points 235 */ 236 public function getMaximumPoints() 237 { 238 $sum = 0; 239 $points = $this->getCorrectAnswers(); 240 if ($points) { 241 foreach ($points as $add) { 242 $sum += $add[1]; 243 } 244 } 245 return $sum; 246 } 247 248 public function saveAdditionalQuestionDataToDb() 249 { 250 // save additional data 251 $this->ilDB->manipulateF( 252 "DELETE FROM " . $this->getAdditionalTableName() . " WHERE question_fi = %s", 253 array( "integer" ), 254 array( $this->getId() ) 255 ); 256 $this->ilDB->manipulateF( 257 "INSERT INTO " . $this->getAdditionalTableName( 258 ) . " (question_fi, long_menu_text, feedback_setting, min_auto_complete, identical_scoring) VALUES (%s, %s, %s, %s, %s)", 259 array( "integer", "text", "integer", "integer", "integer"), 260 array( 261 $this->getId(), 262 $this->getLongMenuTextValue(), 263 (int) $this->getSpecificFeedbackSetting(), 264 (int) $this->getMinAutoComplete(), 265 (int) $this->getIdenticalScoring() 266 ) 267 ); 268 269 $this->createFileFromArray(); 270 } 271 272 public function saveAnswerSpecificDataToDb() 273 { 274 $this->clearAnswerSpecificDataFromDb($this->getId()); 275 $type_array = $this->getAnswerType(); 276 $points = 0; 277 foreach ($this->getCorrectAnswers() as $gap_number => $gap) { 278 foreach ($gap[0] as $position => $answer) { 279 if ($type_array == null) { 280 $type = $gap[2]; 281 } else { 282 $type = $type_array[$gap_number]; 283 } 284 $this->db->replace( 285 $this->getAnswerTableName(), 286 array( 287 'question_fi' => array('integer', (int) $this->getId()), 288 'gap_number' => array('integer', (int) $gap_number), 289 'position' => array('integer', (int) $position) 290 ), 291 array( 292 'answer_text' => array('text', $answer), 293 'points' => array('float', $gap[1]), 294 'type' => array('integer', (int) $type) 295 ) 296 ); 297 } 298 $points += $gap[1]; 299 } 300 $this->setPoints($points); 301 } 302 303 private function createFileFromArray() 304 { 305 $array = $this->getAnswers(); 306 $this->clearFolder(); 307 foreach ($array as $gap => $values) { 308 $file_content = ''; 309 if (is_array($values)) { 310 foreach ($values as $key => $value) { 311 $file_content .= $value . "\n"; 312 } 313 $file_content = rtrim($file_content, "\n"); 314 $file = fopen($this->buildFileName($gap), "w"); 315 fwrite($file, $file_content); 316 fclose($file); 317 } 318 } 319 } 320 321 private function createArrayFromFile() 322 { 323 $files = glob($this->buildFolderName() . '*.txt'); 324 325 if ($files === false) { 326 $files = array(); 327 } 328 329 $answers = array(); 330 331 foreach ($files as $file) { 332 $gap = str_replace('.txt', '', basename($file)); 333 $answers[(int) $gap] = explode("\n", file_get_contents($file)); 334 } 335 $this->setAnswers($answers); 336 return $answers; 337 } 338 339 private function clearFolder($let_folder_exists = true) 340 { 341 ilUtil::delDir($this->buildFolderName(), $let_folder_exists); 342 } 343 344 private function assertDirExists() 345 { 346 $folder_name = $this->buildFolderName(); 347 if (!ilUtil::makeDirParents($folder_name)) { 348 throw new ilException('Cannot create export directory'); 349 } 350 351 if ( 352 !is_dir($folder_name) || 353 !is_readable($folder_name) || 354 !is_writable($folder_name) 355 ) { 356 throw new ilException('Cannot create export directory'); 357 } 358 } 359 360 public function loadFromDb($question_id) 361 { 362 $result = $this->ilDB->queryF( 363 "SELECT qpl_questions.*, " . $this->getAdditionalTableName() . ".* FROM qpl_questions LEFT JOIN " . $this->getAdditionalTableName() . " ON " . $this->getAdditionalTableName() . ".question_fi = qpl_questions.question_id WHERE qpl_questions.question_id = %s", 364 array("integer"), 365 array($question_id) 366 ); 367 if ($result->numRows() == 1) { 368 $data = $this->ilDB->fetchAssoc($result); 369 $this->setId($question_id); 370 $this->setObjId($data["obj_fi"]); 371 $this->setNrOfTries($data['nr_of_tries']); 372 $this->setTitle($data["title"]); 373 $this->setComment($data["description"]); 374 $this->setOriginalId($data["original_id"]); 375 $this->setAuthor($data["author"]); 376 $this->setPoints($data["points"]); 377 $this->setIdenticalScoring($data["identical_scoring"]); 378 $this->setOwner($data["owner"]); 379 include_once("./Services/RTE/classes/class.ilRTE.php"); 380 $this->setQuestion(ilRTE::_replaceMediaObjectImageSrc($data['question_text'], 1)); 381 $this->setEstimatedWorkingTime(substr($data["working_time"], 0, 2), substr($data["working_time"], 3, 2), substr($data["working_time"], 6, 2)); 382 $this->setLongMenuTextValue(ilRTE::_replaceMediaObjectImageSrc($data['long_menu_text'], 1)); 383 $this->loadCorrectAnswerData($question_id); 384 $this->setMinAutoComplete($data["min_auto_complete"]); 385 if (isset($data['feedback_setting'])) { 386 $this->setSpecificFeedbackSetting((int) $data['feedback_setting']); 387 } 388 389 try { 390 $this->setLifecycle(ilAssQuestionLifecycle::getInstance($data['lifecycle'])); 391 } catch (ilTestQuestionPoolInvalidArgumentException $e) { 392 $this->setLifecycle(ilAssQuestionLifecycle::getDraftInstance()); 393 } 394 395 try { 396 $this->setAdditionalContentEditingMode($data['add_cont_edit_mode']); 397 } catch (ilTestQuestionPoolException $e) { 398 } 399 } 400 401 $this->loadCorrectAnswerData($question_id); 402 $this->createArrayFromFile(); 403 parent::loadFromDb($question_id); 404 } 405 406 private function loadCorrectAnswerData($question_id) 407 { 408 $res = $this->db->queryF( 409 "SELECT * FROM {$this->getAnswerTableName()} WHERE question_fi = %s ORDER BY gap_number, position ASC", 410 array('integer'), 411 array($question_id) 412 ); 413 414 $correct_answers = array(); 415 while ($data = $this->ilDB->fetchAssoc($res)) { 416 $correct_answers[$data['gap_number']][0][$data['position']] = rtrim($data['answer_text']); 417 $correct_answers[$data['gap_number']][1] = $data['points']; 418 $correct_answers[$data['gap_number']][2] = $data['type']; 419 } 420 $this->setJsonStructure(json_encode($correct_answers)); 421 $this->setCorrectAnswers($correct_answers); 422 } 423 424 public function getCorrectAnswersForQuestionSolution($question_id) 425 { 426 $correct_answers = array(); 427 $res = $this->db->queryF( 428 'SELECT gap_number, answer_text FROM ' . $this->getAnswerTableName() . ' WHERE question_fi = %s', 429 array('integer'), 430 array($question_id) 431 ); 432 while ($data = $this->ilDB->fetchAssoc($res)) { 433 if (array_key_exists($data['gap_number'], $correct_answers)) { 434 $correct_answers[$data['gap_number']] .= ' ' . $this->lng->txt("or") . ' '; 435 $correct_answers[$data['gap_number']] .= rtrim($data['answer_text']); 436 } else { 437 $correct_answers[$data['gap_number']] .= rtrim($data['answer_text']); 438 } 439 } 440 return $correct_answers; 441 } 442 443 private function getCorrectAnswersForGap($question_id, $gap_id) 444 { 445 $correct_answers = array(); 446 $res = $this->db->queryF( 447 'SELECT answer_text FROM ' . $this->getAnswerTableName() . ' WHERE question_fi = %s AND gap_number = %s', 448 array('integer', 'integer'), 449 array($question_id, $gap_id) 450 ); 451 while ($data = $this->ilDB->fetchAssoc($res)) { 452 $correct_answers[] = rtrim($data['answer_text']); 453 } 454 return $correct_answers; 455 } 456 457 private function getPointsForGap($question_id, $gap_id) 458 { 459 $points = 0; 460 $res = $this->db->queryF( 461 'SELECT points FROM ' . $this->getAnswerTableName() . ' WHERE question_fi = %s AND gap_number = %s GROUP BY gap_number, points', 462 array('integer', 'integer'), 463 array($question_id, $gap_id) 464 ); 465 while ($data = $this->ilDB->fetchAssoc($res)) { 466 $points = $data['points']; 467 } 468 return $points; 469 } 470 471 472 public function getAnswersObject() 473 { 474 return json_encode($this->createArrayFromFile()); 475 } 476 477 public function getCorrectAnswersAsJson() 478 { 479 $this->loadCorrectAnswerData($this->getId()); 480 return $this->getJsonStructure(); 481 } 482 483 public function duplicate($for_test = true, $title = "", $author = "", $owner = "", $testObjId = null) 484 { 485 if ($this->id <= 0) { 486 // The question has not been saved. It cannot be duplicated 487 return; 488 } 489 490 // duplicate the question in database 491 $this_id = $this->getId(); 492 $thisObjId = $this->getObjId(); 493 494 $clone = $this; 495 include_once("./Modules/TestQuestionPool/classes/class.assQuestion.php"); 496 $original_id = assQuestion::_getOriginalId($this->id); 497 $clone->id = -1; 498 499 if ((int) $testObjId > 0) { 500 $clone->setObjId($testObjId); 501 } 502 503 if ($title) { 504 $clone->setTitle($title); 505 } 506 507 if ($author) { 508 $clone->setAuthor($author); 509 } 510 if ($owner) { 511 $clone->setOwner($owner); 512 } 513 514 if ($for_test) { 515 $clone->saveToDb($original_id); 516 } else { 517 $clone->saveToDb(); 518 } 519 520 $clone->copyPageOfQuestion($this_id); 521 $clone->copyXHTMLMediaObjectsOfQuestion($this_id); 522 $clone->onDuplicate($thisObjId, $this_id, $clone->getObjId(), $clone->getId()); 523 524 return $clone->id; 525 } 526 527 public function copyObject($target_questionpool_id, $title = "") 528 { 529 if ($this->id <= 0) { 530 // The question has not been saved. It cannot be duplicated 531 return; 532 } 533 // duplicate the question in database 534 $clone = $this; 535 include_once("./Modules/TestQuestionPool/classes/class.assQuestion.php"); 536 $original_id = assQuestion::_getOriginalId($this->id); 537 $clone->id = -1; 538 $source_questionpool_id = $this->getObjId(); 539 $clone->setObjId($target_questionpool_id); 540 if ($title) { 541 $clone->setTitle($title); 542 } 543 $clone->saveToDb(); 544 545 $clone->copyPageOfQuestion($original_id); 546 $clone->copyXHTMLMediaObjectsOfQuestion($original_id); 547 548 $clone->onCopy($source_questionpool_id, $original_id, $clone->getObjId(), $clone->getId()); 549 550 return $clone->id; 551 } 552 553 public function createNewOriginalFromThisDuplicate($targetParentId, $targetQuestionTitle = "") 554 { 555 if ($this->id <= 0) { 556 // The question has not been saved. It cannot be duplicated 557 return; 558 } 559 560 include_once("./Modules/TestQuestionPool/classes/class.assQuestion.php"); 561 562 $sourceQuestionId = $this->id; 563 $sourceParentId = $this->getObjId(); 564 565 // duplicate the question in database 566 $clone = $this; 567 $clone->id = -1; 568 569 $clone->setObjId($targetParentId); 570 571 if ($targetQuestionTitle) { 572 $clone->setTitle($targetQuestionTitle); 573 } 574 575 $clone->saveToDb(); 576 $clone->copyPageOfQuestion($sourceQuestionId); 577 $clone->copyXHTMLMediaObjectsOfQuestion($sourceQuestionId); 578 579 $clone->onCopy($sourceParentId, $sourceQuestionId, $clone->getObjId(), $clone->getId()); 580 581 return $clone->id; 582 } 583 584 585 /** 586 * Returns the points, a learner has reached answering the question. 587 * The points are calculated from the given answers. 588 * 589 * @param integer $active_id 590 * @param integer $pass 591 * @param boolean $returndetails (deprecated !!) 592 * 593 * @throws ilTestException 594 * @return integer/array $points/$details (array $details is deprecated !!) 595 */ 596 public function calculateReachedPoints($active_id, $pass = null, $authorizedSolution = true, $returndetails = false) 597 { 598 if ($returndetails) { 599 throw new ilTestException('return details not implemented for ' . __METHOD__); 600 } 601 602 $found_values = array(); 603 if (is_null($pass)) { 604 $pass = $this->getSolutionMaxPass($active_id); 605 } 606 $result = $this->getCurrentSolutionResultSet($active_id, $pass, $authorizedSolution); 607 while ($data = $this->ilDB->fetchAssoc($result)) { 608 $found_values[(int) $data['value1']] = $data['value2']; 609 } 610 611 $points = $this->calculateReachedPointsForSolution($found_values, $active_id); 612 613 return $points; 614 } 615 616 protected function calculateReachedPointsForSolution($found_values, $active_id = 0) 617 { 618 $points = 0; 619 $solution_values_text = array(); 620 foreach ($found_values as $key => $answer) { 621 if ($answer != '') { 622 $correct_answers = $this->getCorrectAnswersForGap($this->id, $key); 623 if (in_array($answer, $correct_answers)) { 624 $points_gap = $this->getPointsForGap($this->id, $key); 625 if (!$this->getIdenticalScoring()) { 626 // check if the same solution text was already entered 627 if ((in_array($answer, $solution_values_text)) && ($points > 0)) { 628 $points_gap = 0; 629 } 630 } 631 $points += $points_gap; 632 array_push($solution_values_text, $answer); 633 } 634 } 635 } 636 return $points; 637 } 638 639 /** 640 * Saves the learners input of the question to the database. 641 * 642 * @access public 643 * @param integer $active_id Active id of the user 644 * @param integer $pass Test pass 645 * @return boolean $status 646 */ 647 public function saveWorkingData($active_id, $pass = null, $authorized = true) 648 { 649 if (is_null($pass)) { 650 include_once "./Modules/Test/classes/class.ilObjTest.php"; 651 $pass = ilObjTest::_getPass($active_id); 652 } 653 654 $entered_values = 0; 655 656 $this->getProcessLocker()->executeUserSolutionUpdateLockOperation(function () use (&$entered_values, $active_id, $pass, $authorized) { 657 $this->removeCurrentSolution($active_id, $pass, $authorized); 658 659 foreach ($this->getSolutionSubmit() as $val1 => $val2) { 660 $value = ilUtil::stripSlashes($val2, false); 661 if (strlen($value)) { 662 $this->saveCurrentSolution($active_id, $pass, $val1, $value, $authorized); 663 $entered_values++; 664 } 665 } 666 }); 667 668 if ($entered_values) { 669 include_once("./Modules/Test/classes/class.ilObjAssessmentFolder.php"); 670 if (ilObjAssessmentFolder::_enabledAssessmentLogging()) { 671 assQuestion::logAction($this->lng->txtlng("assessment", "log_user_entered_values", ilObjAssessmentFolder::_getLogLanguage()), $active_id, $this->getId()); 672 } 673 } else { 674 include_once("./Modules/Test/classes/class.ilObjAssessmentFolder.php"); 675 if (ilObjAssessmentFolder::_enabledAssessmentLogging()) { 676 assQuestion::logAction($this->lng->txtlng("assessment", "log_user_not_entered_values", ilObjAssessmentFolder::_getLogLanguage()), $active_id, $this->getId()); 677 } 678 } 679 return true; 680 } 681 682 // fau: testNav - overridden function lookupForExistingSolutions (specific for long menu question: ignore unselected values) 683 /** 684 * Lookup if an authorized or intermediate solution exists 685 * @param int $activeId 686 * @param int $pass 687 * @return array ['authorized' => bool, 'intermediate' => bool] 688 */ 689 public function lookupForExistingSolutions($activeId, $pass) 690 { 691 global $DIC; 692 $ilDB = $DIC['ilDB']; 693 694 $return = array( 695 'authorized' => false, 696 'intermediate' => false 697 ); 698 699 $query = " 700 SELECT authorized, COUNT(*) cnt 701 FROM tst_solutions 702 WHERE active_fi = " . $ilDB->quote($activeId, 'integer') . " 703 AND question_fi = " . $ilDB->quote($this->getId(), 'integer') . " 704 AND pass = " . $ilDB->quote($pass, 'integer') . " 705 AND value2 <> '-1' 706 "; 707 708 if ($this->getStep() !== null) { 709 $query .= " AND step = " . $ilDB->quote((int) $this->getStep(), 'integer') . " "; 710 } 711 712 $query .= " 713 GROUP BY authorized 714 "; 715 716 $result = $ilDB->query($query); 717 718 while ($row = $ilDB->fetchAssoc($result)) { 719 if ($row['authorized']) { 720 $return['authorized'] = $row['cnt'] > 0; 721 } else { 722 $return['intermediate'] = $row['cnt'] > 0; 723 } 724 } 725 return $return; 726 } 727 // fau. 728 729 730 public function getSolutionSubmit() 731 { 732 $solutionSubmit = array(); 733 $answer = ilUtil::stripSlashesRecursive($_POST['answer']); 734 735 foreach ($answer as $key => $value) { 736 $solutionSubmit[$key] = $value; 737 } 738 739 return $solutionSubmit; 740 } 741 742 protected function savePreviewData(ilAssQuestionPreviewSession $previewSession) 743 { 744 if (array_key_exists('answer', $_POST)) { 745 $previewSession->setParticipantsSolution($_POST['answer']); 746 } else { 747 $previewSession->setParticipantsSolution(null); 748 } 749 } 750 751 /** 752 * Returns the question type of the question 753 * 754 * @return integer The question type of the question 755 */ 756 public function getQuestionType() 757 { 758 return "assLongMenu"; 759 } 760 761 /** 762 * Returns the name of the additional question data table in the database 763 * 764 * @return string The additional table name 765 */ 766 public function getAdditionalTableName() 767 { 768 return 'qpl_qst_lome'; 769 } 770 771 /** 772 * Collects all text in the question which could contain media objects 773 * which were created with the Rich Text Editor 774 */ 775 public function getRTETextWithMediaObjects() 776 { 777 return parent::getRTETextWithMediaObjects() . $this->getLongMenuTextValue(); 778 } 779 780 /** 781 * {@inheritdoc} 782 */ 783 public function setExportDetailsXLS($worksheet, $startrow, $active_id, $pass) 784 { 785 parent::setExportDetailsXLS($worksheet, $startrow, $active_id, $pass); 786 787 $solution = $this->getSolutionValues($active_id, $pass); 788 789 $i = 1; 790 foreach ($this->getCorrectAnswers() as $gap_index => $gap) { 791 $worksheet->setCell($startrow + $i, 0, $this->lng->txt('assLongMenu') . " $i"); 792 $worksheet->setBold($worksheet->getColumnCoord(0) . ($startrow + $i)); 793 foreach ($solution as $solutionvalue) { 794 if ($gap_index == $solutionvalue["value1"]) { 795 switch ($gap[2]) { 796 case self::ANSWER_TYPE_SELECT_VAL: 797 $value = $solutionvalue["value2"]; 798 if ($value == -1) { 799 $value = ''; 800 } 801 $worksheet->setCell($startrow + $i, 1, $value); 802 break; 803 case self::ANSWER_TYPE_TEXT_VAL: 804 $worksheet->setCell($startrow + $i, 1, $solutionvalue["value2"]); 805 break; 806 } 807 } 808 } 809 $i++; 810 } 811 812 return $startrow + $i + 1; 813 } 814 815 /** 816 * Get the user solution for a question by active_id and the test pass 817 * 818 * @param int $active_id 819 * @param int $pass 820 * 821 * @return ilUserQuestionResult 822 */ 823 public function getUserQuestionResult($active_id, $pass) 824 { 825 $result = new ilUserQuestionResult($this, $active_id, $pass); 826 827 $points = $this->calculateReachedPoints($active_id, $pass); 828 $max_points = $this->getMaximumPoints(); 829 830 $result->setReachedPercentage(($points / $max_points) * 100); 831 832 return $result; 833 } 834 835 /** 836 * If index is null, the function returns an array with all anwser options 837 * Else it returns the specific answer option 838 * 839 * @param null|int $index 840 * 841 * @return array|ASS_AnswerSimple 842 */ 843 public function getAvailableAnswerOptions($index = null) 844 { 845 return $this->createArrayFromFile(); 846 } 847 848 public function isShuffleAnswersEnabled() 849 { 850 return false; 851 } 852 853 public function clearAnswerSpecificDataFromDb($question_id) 854 { 855 $this->ilDB->manipulateF( 856 'DELETE FROM ' . $this->getAnswerTableName() . ' WHERE question_fi = %s', 857 array( 'integer' ), 858 array( $question_id ) 859 ); 860 } 861 862 public function delete($original_id) 863 { 864 parent::delete($original_id); 865 $this->clearFolder(false); 866 } 867 868 /** 869 * @param ilAssSelfAssessmentMigrator $migrator 870 */ 871 protected function lmMigrateQuestionTypeSpecificContent(ilAssSelfAssessmentMigrator $migrator) 872 { 873 $this->setLongMenuTextValue($migrator->migrateToLmContent($this->getLongMenuTextValue())); 874 } 875 876 /** 877 * Returns a JSON representation of the question 878 */ 879 public function toJSON() 880 { 881 include_once("./Services/RTE/classes/class.ilRTE.php"); 882 $result = array(); 883 $result['id'] = (int) $this->getId(); 884 $result['type'] = (string) $this->getQuestionType(); 885 $result['title'] = (string) $this->getTitle(); 886 $replaced_quesiton_text = $this->getLongMenuTextValue(); 887 $result['question'] = $this->formatSAQuestion($this->getQuestion()); 888 $result['lmtext'] = $this->formatSAQuestion($replaced_quesiton_text); 889 $result['nr_of_tries'] = (int) $this->getNrOfTries(); 890 $result['shuffle'] = (bool) $this->getShuffle(); 891 $result['feedback'] = array( 892 'onenotcorrect' => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(), false)), 893 'allcorrect' => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(), true)) 894 ); 895 896 $mobs = ilObjMediaObject::_getMobsOfObject("qpl:html", $this->getId()); 897 $result['answers'] = $this->getAnswers(); 898 $result['correct_answers'] = $this->getCorrectAnswers(); 899 $result['mobs'] = $mobs; 900 return json_encode($result); 901 } 902 903 public function getIdenticalScoring() 904 { 905 return ($this->identical_scoring) ? 1 : 0; 906 } 907 908 /** 909 * @param $a_identical_scoring 910 */ 911 public function setIdenticalScoring($a_identical_scoring) 912 { 913 $this->identical_scoring = ($a_identical_scoring) ? 1 : 0; 914 } 915} 916