1<?php 2/* Copyright (c) 1998-2013 ILIAS open source, Extended GPL, see docs/LICENSE */ 3 4include_once "./Modules/TestQuestionPool/classes/class.assQuestion.php"; 5include_once "./Modules/TestQuestionPool/classes/class.assFormulaQuestionResult.php"; 6include_once "./Modules/TestQuestionPool/classes/class.assFormulaQuestionVariable.php"; 7include_once "./Modules/TestQuestionPool/classes/class.ilUnitConfigurationRepository.php"; 8include_once "./Modules/Test/classes/inc.AssessmentConstants.php"; 9include_once "./Modules/TestQuestionPool/interfaces/interface.iQuestionCondition.php"; 10require_once './Modules/TestQuestionPool/classes/class.ilUserQuestionResult.php'; 11 12/** 13 * Class for single choice questions 14 * assFormulaQuestion is a class for single choice questions. 15 * @author Helmut Schottmüller <helmut.schottmueller@mac.com> 16 * @version $Id: class.assFormulaQuestion.php 1236 2010-02-15 15:44:16Z hschottm $ 17 * @ingroup ModulesTestQuestionPool 18 */ 19class assFormulaQuestion extends assQuestion implements iQuestionCondition 20{ 21 private $variables; 22 private $results; 23 private $resultunits; 24 25 /** 26 * @var ilUnitConfigurationRepository 27 */ 28 private $unitrepository; 29 30 /** 31 * assFormulaQuestion constructor 32 * The constructor takes possible arguments an creates an instance of the assFormulaQuestion object. 33 * @param string $title A title string to describe the question 34 * @param string $comment A comment string to describe the question 35 * @param string $author A string containing the name of the questions author 36 * @param integer $owner A numerical ID to identify the owner/creator 37 * @param string $question The question string of the single choice question 38 * @access public 39 * @see assQuestion:assQuestion() 40 */ 41 public function __construct( 42 $title = "", 43 $comment = "", 44 $author = "", 45 $owner = -1, 46 $question = "" 47 ) { 48 parent::__construct($title, $comment, $author, $owner, $question); 49 $this->variables = array(); 50 $this->results = array(); 51 $this->resultunits = array(); 52 $this->unitrepository = new ilUnitConfigurationRepository(0); 53 } 54 55 public function clearVariables() 56 { 57 $this->variables = array(); 58 } 59 60 public function getVariables() 61 { 62 return $this->variables; 63 } 64 65 public function getVariable($variable) 66 { 67 if (array_key_exists($variable, $this->variables)) { 68 return $this->variables[$variable]; 69 } 70 return null; 71 } 72 73 public function addVariable($variable) 74 { 75 $this->variables[$variable->getVariable()] = $variable; 76 } 77 78 public function clearResults() 79 { 80 $this->results = array(); 81 } 82 83 public function getResults() 84 { 85 return $this->results; 86 } 87 88 public function getResult($result) 89 { 90 if (array_key_exists($result, $this->results)) { 91 return $this->results[$result]; 92 } 93 return null; 94 } 95 96 public function addResult($result) 97 { 98 $this->results[$result->getResult()] = $result; 99 } 100 101 public function addResultUnits($result, $unit_ids) 102 { 103 $this->resultunits[$result->getResult()] = array(); 104 if ((!is_object($result)) || (!is_array($unit_ids))) { 105 return; 106 } 107 foreach ($unit_ids as $id) { 108 if (is_numeric($id) && ($id > 0)) { 109 $this->resultunits[$result->getResult()][$id] = $this->getUnitrepository()->getUnit($id); 110 } 111 } 112 } 113 114 public function addResultUnit($result, $unit) 115 { 116 if (is_object($result) && is_object($unit)) { 117 if (!is_array($this->resultunits[$result->getResult()])) { 118 $this->resultunits[$result->getResult()] = array(); 119 } 120 $this->resultunits[$result->getResult()][$unit->getId()] = $unit; 121 } 122 } 123 124 public function getResultUnits($result) 125 { 126 if (array_key_exists($result->getResult(), $this->resultunits)) { 127 return $this->resultunits[$result->getResult()]; 128 } else { 129 return array(); 130 } 131 } 132 133 public function hasResultUnit($result, $unit_id) 134 { 135 if (array_key_exists($result->getResult(), $this->resultunits)) { 136 if (array_key_exists($unit_id, $this->resultunits[$result->getResult()])) { 137 return true; 138 } 139 } 140 141 return false; 142 } 143 144 public function parseQuestionText() 145 { 146 $this->clearResults(); 147 $this->clearVariables(); 148 if (preg_match_all("/(\\\$v\\d+)/ims", $this->getQuestion(), $matches)) { 149 foreach ($matches[1] as $variable) { 150 $varObj = new assFormulaQuestionVariable($variable, 0, 0, null, 0); 151 $this->addVariable($varObj); 152 } 153 } 154 155 if (preg_match_all("/(\\\$r\\d+)/ims", $this->getQuestion(), $rmatches)) { 156 foreach ($rmatches[1] as $result) { 157 $resObj = new assFormulaQuestionResult($result, null, null, 0, -1, null, 1, 1, true); 158 $this->addResult($resObj); 159 } 160 } 161 } 162 163 public function checkForDuplicateVariables() 164 { 165 if (preg_match_all("/(\\\$v\\d+)/ims", $this->getQuestion(), $matches)) { 166 if ((count(array_unique($matches[1]))) != count($matches[1])) { 167 return false; 168 } 169 } 170 return true; 171 } 172 173 public function checkForDuplicateResults() 174 { 175 if (preg_match_all("/(\\\$r\\d+)/ims", $this->getQuestion(), $rmatches)) { 176 if ((count(array_unique($rmatches[1]))) != count($rmatches[1])) { 177 return false; 178 } 179 } 180 return true; 181 } 182 183 /** 184 * @param string $questionText 185 * @return assFormulaQuestionResult[] $resObjects 186 */ 187 public function fetchAllResults($questionText) 188 { 189 $resObjects = array(); 190 $matches = null; 191 192 if (preg_match_all("/(\\\$r\\d+)/ims", $questionText, $matches)) { 193 foreach ($matches[1] as $resultKey) { 194 $resObjects[] = $this->getResult($resultKey); 195 } 196 } 197 198 return $resObjects; 199 } 200 201 /** 202 * @param string $questionText 203 * @return assFormulaQuestionVariable[] $varObjects 204 */ 205 public function fetchAllVariables($questionText) 206 { 207 $varObjects = array(); 208 $matches = null; 209 210 if (preg_match_all("/(\\\$v\\d+)/ims", $questionText, $matches)) { 211 foreach ($matches[1] as $variableKey) { 212 $varObjects[] = $this->getVariable($variableKey); 213 } 214 } 215 216 return $varObjects; 217 } 218 219 /** 220 * @param array $userSolution 221 * @return bool 222 */ 223 public function hasRequiredVariableSolutionValues(array $userSolution) 224 { 225 foreach ($this->fetchAllVariables($this->getQuestion()) as $varObj) { 226 if (!isset($userSolution[$varObj->getVariable()])) { 227 return false; 228 } 229 230 if (!strlen($userSolution[$varObj->getVariable()])) { 231 return false; 232 } 233 } 234 235 return true; 236 } 237 238 /** 239 * @return array $initialVariableSolutionValues 240 */ 241 public function getInitialVariableSolutionValues() 242 { 243 foreach ($this->fetchAllResults($this->getQuestion()) as $resObj) { 244 $resObj->findValidRandomVariables($this->getVariables(), $this->getResults()); 245 } 246 247 $variableSolutionValues = array(); 248 249 foreach ($this->fetchAllVariables($this->getQuestion()) as $varObj) { 250 $variableSolutionValues[$varObj->getVariable()] = $varObj->getValue(); 251 } 252 253 return $variableSolutionValues; 254 } 255 256 /** 257 * @param array $userdata 258 * @param bool $graphicalOutput 259 * @param bool $forsolution 260 * @param bool $result_output 261 * @param ilAssQuestionPreviewSession|null $previewSession 262 * @return bool|mixed|string 263 */ 264 public function substituteVariables(array $userdata, $graphicalOutput = false, $forsolution = false, $result_output = false) 265 { 266 if ((count($this->results) == 0) && (count($this->variables) == 0)) { 267 return false; 268 } 269 270 $text = $this->getQuestion(); 271 272 foreach ($this->fetchAllVariables($this->getQuestion()) as $varObj) { 273 if (isset($userdata[$varObj->getVariable()]) && strlen($userdata[$varObj->getVariable()])) { 274 $varObj->setValue($userdata[$varObj->getVariable()]); 275 } 276 277 $unit = (is_object($varObj->getUnit())) ? $varObj->getUnit()->getUnit() : ""; 278 $val = (strlen($varObj->getValue()) > 8) ? strtoupper(sprintf("%e", $varObj->getValue())) : $varObj->getValue(); 279 280 $text = preg_replace("/\\$" . substr($varObj->getVariable(), 1) . "(?![0-9]+)/", $val . " " . $unit . "\\1", $text); 281 } 282 283 if (preg_match_all("/(\\\$r\\d+)/ims", $this->getQuestion(), $rmatches)) { 284 foreach ($rmatches[1] as $result) { 285 $resObj = $this->getResult($result); 286 $value = ""; 287 $frac_helper = ''; 288 $user_data[$result]['result_type'] = $resObj->getResultType(); 289 290 if ( 291 $resObj->getResultType() == assFormulaQuestionResult::RESULT_FRAC || 292 $resObj->getResultType() == assFormulaQuestionResult::RESULT_CO_FRAC 293 ) { 294 $is_frac = true; 295 } 296 if (is_array($userdata)) { 297 if (is_array($userdata[$result])) { 298 if (false && $forsolution && $result_output) { // fix for mantis #25956 299 $value_org = $resObj->calculateFormula($this->getVariables(), $this->getResults(), parent::getId()); 300 $value = sprintf("%." . $resObj->getPrecision() . "f", $value_org); 301 if ($is_frac) { 302 $value = assFormulaQuestionResult::convertDecimalToCoprimeFraction($value_org); 303 if (is_array($value)) { 304 $frac_helper = $value[1]; 305 $value = $value[0]; 306 } 307 } 308 } else { 309 if ($forsolution) { 310 $value = $userdata[$result]["value"]; 311 } else { 312 $value = ' value="' . $userdata[$result]["value"] . '"'; 313 } 314 } 315 } 316 } else { 317 if ($forsolution) { 318 $value = $resObj->calculateFormula($this->getVariables(), $this->getResults(), parent::getId()); 319 $value = sprintf("%." . $resObj->getPrecision() . "f", $value); 320 321 if ($is_frac) { 322 $value = assFormulaQuestionResult::convertDecimalToCoprimeFraction($value); 323 if (is_array($value)) { 324 $frac_helper = $value[1]; 325 $value = $value[0]; 326 } 327 $value = ' value="' . $value . '"'; 328 } 329 } else { 330 // Precision fix for Preview by tjoussen 331 // If all default values are set, this function is called in getPreview 332 $use_precision = !($userdata == null && $graphicalOutput == false && $forsolution == false && $result_output == false); 333 334 $val = $resObj->calculateFormula($this->getVariables(), $this->getResults(), parent::getId(), $use_precision); 335 336 if ($resObj->getResultType() == assFormulaQuestionResult::RESULT_FRAC 337 || $resObj->getResultType() == assFormulaQuestionResult::RESULT_CO_FRAC) { 338 $val = $resObj->convertDecimalToCoprimeFraction($val); 339 if (is_array($val)) { 340 $frac_helper = $val[1]; 341 $val = $val[0]; 342 } 343 } else { 344 $val = sprintf("%." . $resObj->getPrecision() . "f", $val); 345 $val = (strlen($val) > 8) ? strtoupper(sprintf("%e", $val)) : $val; 346 } 347 $value = ' value="' . $val . '"'; 348 } 349 } 350 351 if ($forsolution) { 352 $input = '<span class="ilc_qinput_TextInput solutionbox">' . ilUtil::prepareFormOutput($value) . '</span>'; 353 } else { 354 $input = '<input class="ilc_qinput_TextInput" type="text" spellcheck="false" autocomplete="off" autocorrect="off" autocapitalize="off" name="result_' . $result . '"' . $value . ' />'; 355 } 356 357 $units = ""; 358 if (count($this->getResultUnits($resObj)) > 0) { 359 if ($forsolution) { 360 if (is_array($userdata)) { 361 foreach ($this->getResultUnits($resObj) as $unit) { 362 if ($userdata[$result]["unit"] == $unit->getId()) { 363 $units = $unit->getUnit(); 364 } 365 } 366 } else { 367 if ($resObj->getUnit()) { 368 $units = $resObj->getUnit()->getUnit(); 369 } 370 } 371 } else { 372 $units = '<select name="result_' . $result . '_unit">'; 373 $units .= '<option value="-1">' . $this->lng->txt("select_unit") . '</option>'; 374 foreach ($this->getResultUnits($resObj) as $unit) { 375 $units .= '<option value="' . $unit->getId() . '"'; 376 if ((is_array($userdata[$result])) && (strlen($userdata[$result]["unit"]))) { 377 if ($userdata[$result]["unit"] == $unit->getId()) { 378 $units .= ' selected="selected"'; 379 } 380 } 381 $units .= '>' . $unit->getUnit() . '</option>'; 382 } 383 $units .= '</select>'; 384 } 385 } else { 386 $units = ""; 387 } 388 switch ($resObj->getResultType()) { 389 case assFormulaQuestionResult::RESULT_DEC: 390 $units .= ' ' . $this->lng->txt('expected_result_type') . ': ' . $this->lng->txt('result_dec'); 391 break; 392 case assFormulaQuestionResult::RESULT_FRAC: 393 if (strlen($frac_helper)) { 394 $units .= ' ≈ ' . $frac_helper . ', '; 395 } elseif (is_array($userdata) && isset($userdata[$result]) && strlen($userdata[$result]["frac_helper"])) { 396 if (!preg_match('-/-', $value)) { 397 $units .= ' ≈ ' . $userdata[$result]["frac_helper"] . ', '; 398 } 399 } 400 $units .= ' ' . $this->lng->txt('expected_result_type') . ': ' . $this->lng->txt('result_frac'); 401 break; 402 case assFormulaQuestionResult::RESULT_CO_FRAC: 403 if (strlen($frac_helper)) { 404 $units .= ' ≈ ' . $frac_helper . ', '; 405 } elseif (is_array($userdata) && isset($userdata[$result]) && strlen($userdata[$result]["frac_helper"])) { 406 if (!preg_match('-/-', $value)) { 407 $units .= ' ≈ ' . $userdata[$result]["frac_helper"] . ', '; 408 } 409 } 410 $units .= ' ' . $this->lng->txt('expected_result_type') . ': ' . $this->lng->txt('result_co_frac'); 411 break; 412 case assFormulaQuestionResult::RESULT_NO_SELECTION: 413 break; 414 } 415 $checkSign = ""; 416 if ($graphicalOutput) { 417 $resunit = null; 418 $user_value = ''; 419 if (is_array($userdata) && is_array($userdata[$result])) { 420 if ($userdata[$result]["unit"] > 0) { 421 $resunit = $this->getUnitrepository()->getUnit($userdata[$result]["unit"]); 422 } 423 424 if (isset($userdata[$result]["value"])) { 425 $user_value = $userdata[$result]["value"]; 426 } 427 } 428 429 $template = new ilTemplate("tpl.il_as_qpl_formulaquestion_output_solution_image.html", true, true, 'Modules/TestQuestionPool'); 430 431 if ($resObj->isCorrect($this->getVariables(), $this->getResults(), $user_value, $resunit)) { 432 $template->setCurrentBlock("icon_ok"); 433 $template->setVariable("ICON_OK", ilUtil::getImagePath("icon_ok.svg")); 434 $template->setVariable("TEXT_OK", $this->lng->txt("answer_is_right")); 435 $template->parseCurrentBlock(); 436 } else { 437 $template->setCurrentBlock("icon_not_ok"); 438 $template->setVariable("ICON_NOT_OK", ilUtil::getImagePath("icon_not_ok.svg")); 439 $template->setVariable("TEXT_NOT_OK", $this->lng->txt("answer_is_wrong")); 440 $template->parseCurrentBlock(); 441 } 442 $checkSign = $template->get(); 443 } 444 $resultOutput = ""; 445 if ($result_output) { 446 $template = new ilTemplate("tpl.il_as_qpl_formulaquestion_output_solution_result.html", true, true, 'Modules/TestQuestionPool'); 447 448 if (is_array($userdata)) { 449 $found = $resObj->getResultInfo($this->getVariables(), $this->getResults(), $userdata[$resObj->getResult()]["value"], $userdata[$resObj->getResult()]["unit"], $this->getUnitrepository()->getUnits()); 450 } else { 451 $found = $resObj->getResultInfo($this->getVariables(), $this->getResults(), $resObj->calculateFormula($this->getVariables(), $this->getResults(), parent::getId()), is_object($resObj->getUnit()) ? $resObj->getUnit()->getId() : null, $this->getUnitrepository()->getUnits()); 452 } 453 $resulttext = "("; 454 if ($resObj->getRatingSimple()) { 455 if ($frac_helper) { 456 $resulttext .= "n/a"; 457 } else { 458 $resulttext .= $found['points'] . " " . (($found['points'] == 1) ? $this->lng->txt('point') : $this->lng->txt('points')); 459 } 460 } else { 461 $resulttext .= $this->lng->txt("rated_sign") . " " . (($found['sign']) ? $found['sign'] : 0) . " " . (($found['sign'] == 1) ? $this->lng->txt('point') : $this->lng->txt('points')) . ", "; 462 $resulttext .= $this->lng->txt("rated_value") . " " . (($found['value']) ? $found['value'] : 0) . " " . (($found['value'] == 1) ? $this->lng->txt('point') : $this->lng->txt('points')) . ", "; 463 $resulttext .= $this->lng->txt("rated_unit") . " " . (($found['unit']) ? $found['unit'] : 0) . " " . (($found['unit'] == 1) ? $this->lng->txt('point') : $this->lng->txt('points')); 464 } 465 466 $resulttext .= ")"; 467 $template->setVariable("RESULT_OUTPUT", $resulttext); 468 469 $resultOutput = $template->get(); 470 } 471 $text = preg_replace("/\\\$" . substr($result, 1) . "(?![0-9]+)/", $input . " " . $units . " " . $checkSign . " " . $resultOutput . " " . "\\1", $text); 472 } 473 } 474 return $text; 475 } 476 477 /** 478 * Check if advanced rating can be used for a result. This is only possible if there is exactly 479 * one possible correct unit for the result, otherwise it is impossible to determine wheather the 480 * unit is correct or the value. 481 * @return boolean True if advanced rating could be used, false otherwise 482 */ 483 public function canUseAdvancedRating($result) 484 { 485 $result_units = $this->getResultUnits($result); 486 $resultunit = $result->getUnit(); 487 $similar_units = 0; 488 foreach ($result_units as $unit) { 489 if (is_object($resultunit)) { 490 if ($resultunit->getId() != $unit->getId()) { 491 if ($resultunit->getBaseUnit() && $unit->getBaseUnit()) { 492 if ($resultunit->getBaseUnit() == $unit->getBaseUnit()) { 493 return false; 494 } 495 } 496 if ($resultunit->getBaseUnit()) { 497 if ($resultunit->getBaseUnit() == $unit->getId()) { 498 return false; 499 } 500 } 501 if ($unit->getBaseUnit()) { 502 if ($unit->getBaseUnit() == $resultunit->getId()) { 503 return false; 504 } 505 } 506 } 507 } 508 } 509 return true; 510 } 511 512 /** 513 * Returns true, if the question is complete for use 514 * @return boolean True, if the single choice question is complete for use, otherwise false 515 */ 516 public function isComplete() 517 { 518 if (($this->title) and ($this->author) and ($this->question) and ($this->getMaximumPoints() > 0)) { 519 return true; 520 } else { 521 return false; 522 } 523 } 524 525 /** 526 * Saves a assFormulaQuestion object to a database 527 * @access public 528 */ 529 public function saveToDb($original_id = "") 530 { 531 global $DIC; 532 $ilDB = $DIC['ilDB']; 533 534 $this->saveQuestionDataToDb($original_id); 535 // save variables 536 $affectedRows = $ilDB->manipulateF( 537 " 538 DELETE FROM il_qpl_qst_fq_var 539 WHERE question_fi = %s", 540 array("integer"), 541 array($this->getId()) 542 ); 543 544 foreach ($this->variables as $variable) { 545 $next_id = $ilDB->nextId('il_qpl_qst_fq_var'); 546 $ilDB->insert( 547 'il_qpl_qst_fq_var', 548 array( 549 'variable_id' => array('integer', $next_id), 550 'question_fi' => array('integer', $this->getId()), 551 'variable' => array('text', $variable->getVariable()), 552 'range_min' => array('float', ((strlen($variable->getRangeMin())) ? $variable->getRangeMin() : 0.0)), 553 'range_max' => array('float', ((strlen($variable->getRangeMax())) ? $variable->getRangeMax() : 0.0)), 554 'unit_fi' => array('integer', (is_object($variable->getUnit()) ? (int) $variable->getUnit()->getId() : 0)), 555 'varprecision' => array('integer', (int) $variable->getPrecision()), 556 'intprecision' => array('integer', (int) $variable->getIntprecision()), 557 'range_min_txt' => array('text', $variable->getRangeMinTxt()), 558 'range_max_txt' => array('text', $variable->getRangeMaxTxt()) 559 ) 560 ); 561 } 562 // save results 563 $affectedRows = $ilDB->manipulateF( 564 "DELETE FROM il_qpl_qst_fq_res WHERE question_fi = %s", 565 array("integer"), 566 array($this->getId()) 567 ); 568 569 foreach ($this->results as $result) { 570 $next_id = $ilDB->nextId('il_qpl_qst_fq_res'); 571 if (is_object($result->getUnit())) { 572 $tmp_result_unit = $result->getUnit()->getId(); 573 } else { 574 $tmp_result_unit = null; 575 } 576 577 $formula = str_replace(",", ".", $result->getFormula()); 578 579 $ilDB->insert("il_qpl_qst_fq_res", array( 580 "result_id" => array("integer", $next_id), 581 "question_fi" => array("integer", $this->getId()), 582 "result" => array("text", $result->getResult()), 583 "range_min" => array("float", ((strlen($result->getRangeMin())) ? $result->getRangeMin() : 0)), 584 "range_max" => array("float", ((strlen($result->getRangeMax())) ? $result->getRangeMax() : 0)), 585 "tolerance" => array("float", ((strlen($result->getTolerance())) ? $result->getTolerance() : 0)), 586 "unit_fi" => array("integer", (int) $tmp_result_unit), 587 "formula" => array("clob", $formula), 588 "resprecision" => array("integer", $result->getPrecision()), 589 "rating_simple" => array("integer", ($result->getRatingSimple()) ? 1 : 0), 590 "rating_sign" => array("float", ($result->getRatingSimple()) ? 0 : $result->getRatingSign()), 591 "rating_value" => array("float", ($result->getRatingSimple()) ? 0 : $result->getRatingValue()), 592 "rating_unit" => array("float", ($result->getRatingSimple()) ? 0 : $result->getRatingUnit()), 593 "points" => array("float", $result->getPoints()), 594 "result_type" => array('integer', (int) $result->getResultType()), 595 "range_min_txt" => array("text", $result->getRangeMinTxt()), 596 "range_max_txt" => array("text", $result->getRangeMaxTxt()) 597 598 )); 599 } 600 // save result units 601 $affectedRows = $ilDB->manipulateF( 602 "DELETE FROM il_qpl_qst_fq_res_unit WHERE question_fi = %s", 603 array("integer"), 604 array($this->getId()) 605 ); 606 foreach ($this->results as $result) { 607 foreach ($this->getResultUnits($result) as $unit) { 608 $next_id = $ilDB->nextId('il_qpl_qst_fq_res_unit'); 609 $affectedRows = $ilDB->manipulateF( 610 "INSERT INTO il_qpl_qst_fq_res_unit (result_unit_id, question_fi, result, unit_fi) VALUES (%s, %s, %s, %s)", 611 array('integer', 'integer', 'text', 'integer'), 612 array( 613 $next_id, 614 $this->getId(), 615 $result->getResult(), 616 $unit->getId() 617 ) 618 ); 619 } 620 } 621 622 parent::saveToDb(); 623 } 624 625 /** 626 * Loads a assFormulaQuestion object from a database 627 * @param integer $question_id A unique key which defines the question in the database 628 */ 629 public function loadFromDb($question_id) 630 { 631 global $DIC; 632 $ilDB = $DIC['ilDB']; 633 634 $result = $ilDB->queryF( 635 "SELECT qpl_questions.* FROM qpl_questions WHERE question_id = %s", 636 array('integer'), 637 array($question_id) 638 ); 639 if ($result->numRows() == 1) { 640 $data = $ilDB->fetchAssoc($result); 641 $this->setId($question_id); 642 $this->setTitle($data["title"]); 643 $this->setComment($data["description"]); 644 $this->setSuggestedSolution($data["solution_hint"]); 645 $this->setPoints($data['points']); 646 $this->setOriginalId($data["original_id"]); 647 $this->setObjId($data["obj_fi"]); 648 $this->setAuthor($data["author"]); 649 $this->setOwner($data["owner"]); 650 651 try { 652 $this->setLifecycle(ilAssQuestionLifecycle::getInstance($data['lifecycle'])); 653 } catch (ilTestQuestionPoolInvalidArgumentException $e) { 654 $this->setLifecycle(ilAssQuestionLifecycle::getDraftInstance()); 655 } 656 657 try { 658 $this->setAdditionalContentEditingMode($data['add_cont_edit_mode']); 659 } catch (ilTestQuestionPoolException $e) { 660 } 661 662 $this->unitrepository = new ilUnitConfigurationRepository($question_id); 663 664 include_once("./Services/RTE/classes/class.ilRTE.php"); 665 $this->setQuestion(ilRTE::_replaceMediaObjectImageSrc($data["question_text"], 1)); 666 $this->setEstimatedWorkingTime(substr($data["working_time"], 0, 2), substr($data["working_time"], 3, 2), substr($data["working_time"], 6, 2)); 667 668 // load variables 669 $result = $ilDB->queryF( 670 "SELECT * FROM il_qpl_qst_fq_var WHERE question_fi = %s", 671 array('integer'), 672 array($question_id) 673 ); 674 if ($result->numRows() > 0) { 675 while ($data = $ilDB->fetchAssoc($result)) { 676 $varObj = new assFormulaQuestionVariable($data["variable"], $data["range_min"], $data["range_max"], $this->getUnitrepository()->getUnit($data["unit_fi"]), $data["varprecision"], $data["intprecision"]); 677 $varObj->setRangeMinTxt($data['range_min_txt']); 678 $varObj->setRangeMaxTxt($data['range_max_txt']); 679 $this->addVariable($varObj); 680 } 681 } 682 // load results 683 $result = $ilDB->queryF( 684 "SELECT * FROM il_qpl_qst_fq_res WHERE question_fi = %s", 685 array('integer'), 686 array($question_id) 687 ); 688 if ($result->numRows() > 0) { 689 while ($data = $ilDB->fetchAssoc($result)) { 690 $resObj = new assFormulaQuestionResult($data["result"], $data["range_min"], $data["range_max"], $data["tolerance"], $this->getUnitrepository()->getUnit($data["unit_fi"]), $data["formula"], $data["points"], $data["resprecision"], $data["rating_simple"], $data["rating_sign"], $data["rating_value"], $data["rating_unit"]); 691 $resObj->setResultType($data['result_type']); 692 $resObj->setRangeMinTxt($data['range_min_txt']); 693 $resObj->setRangeMaxTxt($data['range_max_txt']); 694 $this->addResult($resObj); 695 } 696 } 697 698 // load result units 699 $result = $ilDB->queryF( 700 "SELECT * FROM il_qpl_qst_fq_res_unit WHERE question_fi = %s", 701 array('integer'), 702 array($question_id) 703 ); 704 if ($result->numRows() > 0) { 705 while ($data = $ilDB->fetchAssoc($result)) { 706 $unit = $this->getUnitrepository()->getUnit($data["unit_fi"]); 707 $resObj = $this->getResult($data["result"]); 708 $this->addResultUnit($resObj, $unit); 709 } 710 } 711 } 712 parent::loadFromDb($question_id); 713 } 714 715 /** 716 * Duplicates an assFormulaQuestion 717 * @access public 718 */ 719 public function duplicate($for_test = true, $title = "", $author = "", $owner = "", $testObjId = null) 720 { 721 if ($this->id <= 0) { 722 // The question has not been saved. It cannot be duplicated 723 return; 724 } 725 // duplicate the question in database 726 $this_id = $this->getId(); 727 $thisObjId = $this->getObjId(); 728 729 $clone = $this; 730 include_once("./Modules/TestQuestionPool/classes/class.assQuestion.php"); 731 $original_id = assQuestion::_getOriginalId($this->id); 732 $clone->id = -1; 733 734 if ((int) $testObjId > 0) { 735 $clone->setObjId($testObjId); 736 } 737 738 if ($title) { 739 $clone->setTitle($title); 740 } 741 742 if ($author) { 743 $clone->setAuthor($author); 744 } 745 if ($owner) { 746 $clone->setOwner($owner); 747 } 748 749 if ($for_test) { 750 $clone->saveToDb($original_id); 751 } else { 752 $clone->saveToDb(); 753 } 754 755 $clone->unitrepository->cloneUnits($this_id, $clone->getId()); 756 757 // copy question page content 758 $clone->copyPageOfQuestion($this_id); 759 // copy XHTML media objects 760 $clone->copyXHTMLMediaObjectsOfQuestion($this_id); 761 $clone->onDuplicate($thisObjId, $this_id, $clone->getObjId(), $clone->getId()); 762 763 return $clone->id; 764 } 765 766 /** 767 * Copies an assFormulaQuestion object 768 * @access public 769 */ 770 public function copyObject($target_questionpool_id, $title = "") 771 { 772 if ($this->id <= 0) { 773 // The question has not been saved. It cannot be duplicated 774 return; 775 } 776 // duplicate the question in database 777 $clone = $this; 778 include_once("./Modules/TestQuestionPool/classes/class.assQuestion.php"); 779 $original_id = assQuestion::_getOriginalId($this->id); 780 $clone->id = -1; 781 $source_questionpool_id = $this->getObjId(); 782 $clone->setObjId($target_questionpool_id); 783 if ($title) { 784 $clone->setTitle($title); 785 } 786 $clone->saveToDb(); 787 788 $clone->unitrepository->cloneUnits($original_id, $clone->getId()); 789 790 // copy question page content 791 $clone->copyPageOfQuestion($original_id); 792 // copy XHTML media objects 793 $clone->copyXHTMLMediaObjectsOfQuestion($original_id); 794 795 $clone->onCopy($source_questionpool_id, $original_id, $clone->getObjId(), $clone->getId()); 796 797 return $clone->id; 798 } 799 800 public function createNewOriginalFromThisDuplicate($targetParentId, $targetQuestionTitle = "") 801 { 802 if ($this->id <= 0) { 803 // The question has not been saved. It cannot be duplicated 804 return; 805 } 806 807 include_once("./Modules/TestQuestionPool/classes/class.assQuestion.php"); 808 809 $sourceQuestionId = $this->id; 810 $sourceParentId = $this->getObjId(); 811 812 // duplicate the question in database 813 $clone = $this; 814 $clone->id = -1; 815 816 $clone->setObjId($targetParentId); 817 818 if ($targetQuestionTitle) { 819 $clone->setTitle($targetQuestionTitle); 820 } 821 822 $clone->saveToDb(); 823 // copy question page content 824 $clone->copyPageOfQuestion($sourceQuestionId); 825 // copy XHTML media objects 826 $clone->copyXHTMLMediaObjectsOfQuestion($sourceQuestionId); 827 828 $clone->onCopy($sourceParentId, $sourceQuestionId, $clone->getObjId(), $clone->getId()); 829 830 return $clone->id; 831 } 832 833 /** 834 * Returns the maximum points, a learner can reach answering the question 835 * @see $points 836 */ 837 public function getMaximumPoints() 838 { 839 $points = 0; 840 foreach ($this->results as $result) { 841 $points += $result->getPoints(); 842 } 843 return $points; 844 } 845 846 /** 847 * Returns the points, a learner has reached answering the question 848 * The points are calculated from the given answers. 849 * 850 * @param integer $user_id The database ID of the learner 851 * @param integer $test_id The database Id of the test containing the question 852 * @access public 853 */ 854 public function calculateReachedPoints($active_id, $pass = null, $authorizedSolution = true, $returndetails = false) 855 { 856 if (is_null($pass)) { 857 $pass = $this->getSolutionMaxPass($active_id); 858 } 859 $solutions = &$this->getSolutionValues($active_id, $pass, $authorizedSolution); 860 $user_solution = array(); 861 foreach ($solutions as $idx => $solution_value) { 862 if (preg_match("/^(\\\$v\\d+)$/", $solution_value["value1"], $matches)) { 863 $user_solution[$matches[1]] = $solution_value["value2"]; 864 $varObj = $this->getVariable($solution_value["value1"]); 865 $varObj->setValue($solution_value["value2"]); 866 } elseif (preg_match("/^(\\\$r\\d+)$/", $solution_value["value1"], $matches)) { 867 if (!array_key_exists($matches[1], $user_solution)) { 868 $user_solution[$matches[1]] = array(); 869 } 870 $user_solution[$matches[1]]["value"] = $solution_value["value2"]; 871 } elseif (preg_match("/^(\\\$r\\d+)_unit$/", $solution_value["value1"], $matches)) { 872 if (!array_key_exists($matches[1], $user_solution)) { 873 $user_solution[$matches[1]] = array(); 874 } 875 $user_solution[$matches[1]]["unit"] = $solution_value["value2"]; 876 } 877 } 878 //vd($this->getResults()); 879 $points = 0; 880 foreach ($this->getResults() as $result) { 881 //vd($user_solution[$result->getResult()]["value"]); 882 $points += $result->getReachedPoints($this->getVariables(), $this->getResults(), $user_solution[$result->getResult()]["value"], $user_solution[$result->getResult()]["unit"], $this->unitrepository->getUnits()); 883 } 884 885 return $points; 886 } 887 888 public function calculateReachedPointsFromPreviewSession(ilAssQuestionPreviewSession $previewSession) 889 { 890 $user_solution = $previewSession->getParticipantsSolution(); 891 892 $points = 0; 893 foreach ($this->getResults() as $result) { 894 $v = isset($user_solution[$result->getResult()]) ? $user_solution[$result->getResult()] : null; 895 $u = isset($user_solution[$result->getResult() . '_unit']) ? $user_solution[$result->getResult() . '_unit'] : null; 896 897 $points += $result->getReachedPoints( 898 $this->getVariables(), 899 $this->getResults(), 900 $v, 901 $u, 902 $this->unitrepository->getUnits() 903 ); 904 } 905 906 $reachedPoints = $this->deductHintPointsFromReachedPoints($previewSession, $points); 907 908 return $this->ensureNonNegativePoints($reachedPoints); 909 } 910 911 protected function isValidSolutionResultValue($submittedValue) 912 { 913 $submittedValue = str_replace(',', '.', $submittedValue); 914 915 if (is_numeric($submittedValue)) { 916 return true; 917 } 918 919 if (preg_match('/^[-+]{0,1}\d+\/\d+$/', $submittedValue)) { 920 return true; 921 } 922 923 return false; 924 } 925 926 /** 927 * Saves the learners input of the question to the database 928 * @param integer $test_id The database id of the test containing this question 929 * @return boolean Indicates the save status (true if saved successful, false otherwise) 930 * @access public 931 * @see $answers 932 */ 933 public function saveWorkingData($active_id, $pass = null, $authorized = true) 934 { 935 global $DIC; 936 $ilDB = $DIC['ilDB']; 937 938 if (is_null($pass)) { 939 include_once "./Modules/Test/classes/class.ilObjTest.php"; 940 $pass = ilObjTest::_getPass($active_id); 941 } 942 943 $entered_values = false; 944 945 $this->getProcessLocker()->executeUserSolutionUpdateLockOperation(function () use (&$entered_values, $ilDB, $active_id, $pass, $authorized) { 946 $solutionSubmit = $this->getSolutionSubmit(); 947 foreach ($solutionSubmit as $key => $value) { 948 $matches = null; 949 if (preg_match("/^result_(\\\$r\\d+)$/", $key, $matches)) { 950 if (strlen($value)) { 951 $entered_values = true; 952 } 953 954 $queryResult = "SELECT solution_id FROM tst_solutions WHERE active_fi = %s AND pass = %s AND question_fi = %s AND authorized = %s AND " . $ilDB->like('value1', 'clob', $matches[1]); 955 956 if ($this->getStep() !== null) { 957 $queryResult .= " AND step = " . $ilDB->quote((int) $this->getStep(), 'integer') . " "; 958 } 959 960 $result = $ilDB->queryF( 961 $queryResult, 962 array('integer', 'integer', 'integer', 'integer'), 963 array($active_id, $pass, $this->getId(), (int) $authorized) 964 ); 965 if ($result->numRows()) { 966 while ($row = $ilDB->fetchAssoc($result)) { 967 $ilDB->manipulateF( 968 "DELETE FROM tst_solutions WHERE solution_id = %s AND authorized = %s", 969 array('integer', 'integer'), 970 array($row['solution_id'], (int) $authorized) 971 ); 972 } 973 } 974 975 $this->saveCurrentSolution($active_id, $pass, $matches[1], str_replace(",", ".", $value), $authorized); 976 } elseif (preg_match("/^result_(\\\$r\\d+)_unit$/", $key, $matches)) { 977 $queryResultUnit = "SELECT solution_id FROM tst_solutions WHERE active_fi = %s AND pass = %s AND question_fi = %s AND authorized = %s AND " . $ilDB->like('value1', 'clob', $matches[1] . "_unit"); 978 979 if ($this->getStep() !== null) { 980 $queryResultUnit .= " AND step = " . $ilDB->quote((int) $this->getStep(), 'integer') . " "; 981 } 982 983 $result = $ilDB->queryF( 984 $queryResultUnit, 985 array('integer', 'integer', 'integer', 'integer'), 986 array($active_id, $pass, $this->getId(), (int) $authorized) 987 ); 988 if ($result->numRows()) { 989 while ($row = $ilDB->fetchAssoc($result)) { 990 $ilDB->manipulateF( 991 "DELETE FROM tst_solutions WHERE solution_id = %s AND authorized = %s", 992 array('integer', 'integer'), 993 array($row['solution_id'], (int) $authorized) 994 ); 995 } 996 } 997 998 $this->saveCurrentSolution($active_id, $pass, $matches[1] . "_unit", $value, $authorized); 999 } 1000 } 1001 }); 1002 1003 if ($entered_values) { 1004 include_once("./Modules/Test/classes/class.ilObjAssessmentFolder.php"); 1005 if (ilObjAssessmentFolder::_enabledAssessmentLogging()) { 1006 assQuestion::logAction($this->lng->txtlng("assessment", "log_user_entered_values", ilObjAssessmentFolder::_getLogLanguage()), $active_id, $this->getId()); 1007 } 1008 } else { 1009 include_once("./Modules/Test/classes/class.ilObjAssessmentFolder.php"); 1010 if (ilObjAssessmentFolder::_enabledAssessmentLogging()) { 1011 assQuestion::logAction($this->lng->txtlng("assessment", "log_user_not_entered_values", ilObjAssessmentFolder::_getLogLanguage()), $active_id, $this->getId()); 1012 } 1013 } 1014 1015 return true; 1016 } 1017 1018 // fau: testNav - overridden function lookupForExistingSolutions (specific for formula question: don't lookup variables) 1019 /** 1020 * Lookup if an authorized or intermediate solution exists 1021 * @param int $activeId 1022 * @param int $pass 1023 * @return array ['authorized' => bool, 'intermediate' => bool] 1024 */ 1025 public function lookupForExistingSolutions($activeId, $pass) 1026 { 1027 global $DIC; 1028 $ilDB = $DIC['ilDB']; 1029 1030 $return = array( 1031 'authorized' => false, 1032 'intermediate' => false 1033 ); 1034 1035 $query = " 1036 SELECT authorized, COUNT(*) cnt 1037 FROM tst_solutions 1038 WHERE active_fi = " . $ilDB->quote($activeId, 'integer') . " 1039 AND question_fi = " . $ilDB->quote($this->getId(), 'integer') . " 1040 AND pass = " . $ilDB->quote($pass, 'integer') . " 1041 AND value1 like '\$r%' 1042 AND value2 is not null 1043 AND value2 <> '' 1044 "; 1045 1046 if ($this->getStep() !== null) { 1047 $query .= " AND step = " . $ilDB->quote((int) $this->getStep(), 'integer') . " "; 1048 } 1049 1050 $query .= " 1051 GROUP BY authorized 1052 "; 1053 1054 $result = $ilDB->query($query); 1055 1056 while ($row = $ilDB->fetchAssoc($result)) { 1057 if ($row['authorized']) { 1058 $return['authorized'] = $row['cnt'] > 0; 1059 } else { 1060 $return['intermediate'] = $row['cnt'] > 0; 1061 } 1062 } 1063 return $return; 1064 } 1065 // fau. 1066 1067 // fau: testNav - Remove an existing solution (specific for formula question: don't delete variables) 1068 /** 1069 * Remove an existing solution without removing the variables 1070 * @param int $activeId 1071 * @param int $pass 1072 * @return int 1073 */ 1074 public function removeExistingSolutions($activeId, $pass) 1075 { 1076 global $DIC; 1077 $ilDB = $DIC['ilDB']; 1078 1079 $query = " 1080 DELETE FROM tst_solutions 1081 WHERE active_fi = " . $ilDB->quote($activeId, 'integer') . " 1082 AND question_fi = " . $ilDB->quote($this->getId(), 'integer') . " 1083 AND pass = " . $ilDB->quote($pass, 'integer') . " 1084 AND value1 like '\$r%' 1085 "; 1086 1087 if ($this->getStep() !== null) { 1088 $query .= " AND step = " . $ilDB->quote((int) $this->getStep(), 'integer') . " "; 1089 } 1090 1091 return $ilDB->manipulate($query); 1092 } 1093 // fau. 1094 1095 protected function savePreviewData(ilAssQuestionPreviewSession $previewSession) 1096 { 1097 $userSolution = $previewSession->getParticipantsSolution(); 1098 1099 foreach ($this->getSolutionSubmit() as $key => $val) { 1100 $matches = null; 1101 1102 if (preg_match("/^result_(\\\$r\\d+)$/", $key, $matches)) { 1103 $userSolution[$matches[1]] = $val; 1104 } elseif (preg_match("/^result_(\\\$r\\d+)_unit$/", $key, $matches)) { 1105 $userSolution[$matches[1] . "_unit"] = $val; 1106 } 1107 } 1108 1109 $previewSession->setParticipantsSolution($userSolution); 1110 } 1111 1112 /** 1113 * Returns the question type of the question 1114 * @return string The question type of the question 1115 */ 1116 public function getQuestionType() 1117 { 1118 return "assFormulaQuestion"; 1119 } 1120 1121 /** 1122 * Returns the name of the additional question data table in the database 1123 * @return string The additional table name 1124 */ 1125 public function getAdditionalTableName() 1126 { 1127 return ""; 1128 } 1129 1130 /** 1131 * Returns the name of the answer table in the database 1132 * @return string The answer table name 1133 */ 1134 public function getAnswerTableName() 1135 { 1136 return ""; 1137 } 1138 1139 /** 1140 * Deletes datasets from answers tables 1141 * @param integer $question_id The question id which should be deleted in the answers table 1142 * @access public 1143 */ 1144 public function deleteAnswers($question_id) 1145 { 1146 global $DIC; 1147 $ilDB = $DIC['ilDB']; 1148 1149 $affectedRows = $ilDB->manipulateF( 1150 "DELETE FROM il_qpl_qst_fq_var WHERE question_fi = %s", 1151 array('integer'), 1152 array($question_id) 1153 ); 1154 1155 $affectedRows = $ilDB->manipulateF( 1156 "DELETE FROM il_qpl_qst_fq_res WHERE question_fi = %s", 1157 array('integer'), 1158 array($question_id) 1159 ); 1160 1161 $affectedRows = $ilDB->manipulateF( 1162 "DELETE FROM il_qpl_qst_fq_res_unit WHERE question_fi = %s", 1163 array('integer'), 1164 array($question_id) 1165 ); 1166 1167 $affectedRows = $ilDB->manipulateF( 1168 "DELETE FROM il_qpl_qst_fq_ucat WHERE question_fi = %s", 1169 array('integer'), 1170 array($question_id) 1171 ); 1172 1173 $affectedRows = $ilDB->manipulateF( 1174 "DELETE FROM il_qpl_qst_fq_unit WHERE question_fi = %s", 1175 array('integer'), 1176 array($question_id) 1177 ); 1178 } 1179 1180 /** 1181 * Collects all text in the question which could contain media objects 1182 * which were created with the Rich Text Editor 1183 */ 1184 public function getRTETextWithMediaObjects() 1185 { 1186 $text = parent::getRTETextWithMediaObjects(); 1187 return $text; 1188 } 1189 1190 /** 1191 * {@inheritdoc} 1192 */ 1193 public function setExportDetailsXLS($worksheet, $startrow, $active_id, $pass) 1194 { 1195 parent::setExportDetailsXLS($worksheet, $startrow, $active_id, $pass); 1196 1197 $solution = $this->getSolutionValues($active_id, $pass); 1198 1199 $i = 1; 1200 foreach ($solution as $solutionvalue) { 1201 $worksheet->setCell($startrow + $i, 0, $solutionvalue["value1"]); 1202 $worksheet->setBold($worksheet->getColumnCoord(0) . ($startrow + $i)); 1203 if (strpos($solutionvalue["value1"], "_unit")) { 1204 $unit = $this->getUnitrepository()->getUnit($solutionvalue["value2"]); 1205 if (is_object($unit)) { 1206 $worksheet->setCell($startrow + $i, 1, $unit->getUnit()); 1207 } 1208 } else { 1209 $worksheet->setCell($startrow + $i, 1, $solutionvalue["value2"]); 1210 } 1211 if (preg_match("/(\\\$v\\d+)/", $solutionvalue["value1"], $matches)) { 1212 $var = $this->getVariable($solutionvalue["value1"]); 1213 if (is_object($var) && (is_object($var->getUnit()))) { 1214 $worksheet->setCell($startrow + $i, 2, $var->getUnit()->getUnit()); 1215 } 1216 } 1217 $i++; 1218 } 1219 1220 return $startrow + $i + 1; 1221 } 1222 1223 /** 1224 * Returns the best solution for a given pass of a participant 1225 * @return array An associated array containing the best solution 1226 * @access public 1227 */ 1228 public function getBestSolution($solutions) 1229 { 1230 $user_solution = array(); 1231 1232 foreach ($solutions as $idx => $solution_value) { 1233 if (preg_match("/^(\\\$v\\d+)$/", $solution_value["value1"], $matches)) { 1234 $user_solution[$matches[1]] = $solution_value["value2"]; 1235 $varObj = $this->getVariable($matches[1]); 1236 $varObj->setValue($solution_value["value2"]); 1237 } elseif (preg_match("/^(\\\$r\\d+)$/", $solution_value["value1"], $matches)) { 1238 if (!array_key_exists($matches[1], $user_solution)) { 1239 $user_solution[$matches[1]] = array(); 1240 } 1241 $user_solution[$matches[1]]["value"] = $solution_value["value2"]; 1242 } elseif (preg_match("/^(\\\$r\\d+)_unit$/", $solution_value["value1"], $matches)) { 1243 if (!array_key_exists($matches[1], $user_solution)) { 1244 $user_solution[$matches[1]] = array(); 1245 } 1246 $user_solution[$matches[1]]["unit"] = $solution_value["value2"]; 1247 } 1248 } 1249 foreach ($this->getResults() as $result) { 1250 $resVal = $result->calculateFormula($this->getVariables(), $this->getResults(), parent::getId(), false); 1251 1252 if (is_object($result->getUnit())) { 1253 $user_solution[$result->getResult()]["unit"] = $result->getUnit()->getId(); 1254 $user_solution[$result->getResult()]["value"] = $resVal; 1255 } elseif ($result->getUnit() == null) { 1256 $unit_factor = 1; 1257 // there is no fix result_unit, any "available unit" is accepted 1258 1259 $available_units = $result->getAvailableResultUnits(parent::getId()); 1260 $result_name = $result->getResult(); 1261 1262 if ($available_units[$result_name] != null) { 1263 $check_unit = in_array($user_solution[$result_name]['unit'], $available_units[$result_name]); 1264 } 1265 1266 if ($check_unit == true) { 1267 //get unit-factor 1268 $unit_factor = assFormulaQuestionUnit::lookupUnitFactor($user_solution[$result_name]['unit']); 1269 } 1270 1271 try { 1272 $user_solution[$result->getResult()]["value"] = ilMath::_div($resVal, $unit_factor, 55); 1273 } catch (ilMathDivisionByZeroException $ex) { 1274 $user_solution[$result->getResult()]["value"] = 0; 1275 } 1276 } 1277 if ($result->getResultType() == assFormulaQuestionResult::RESULT_CO_FRAC 1278 || $result->getResultType() == assFormulaQuestionResult::RESULT_FRAC) { 1279 $value = assFormulaQuestionResult::convertDecimalToCoprimeFraction($resVal); 1280 if (is_array($value)) { 1281 $user_solution[$result->getResult()]["value"] = $value[0]; 1282 $user_solution[$result->getResult()]["frac_helper"] = $value[1]; 1283 } else { 1284 $user_solution[$result->getResult()]["value"] = $value; 1285 $user_solution[$result->getResult()]["frac_helper"] = null; 1286 } 1287 } else { 1288 $user_solution[$result->getResult()]["value"] = ilMath::_div( 1289 $user_solution[$result->getResult()]["value"], 1290 1, 1291 $result->getPrecision() 1292 ); 1293 } 1294 } 1295 return $user_solution; 1296 } 1297 1298 public function setId($id = -1) 1299 { 1300 parent::setId($id); 1301 $this->unitrepository->setConsumerId($this->getId()); 1302 } 1303 1304 /** 1305 * Object getter 1306 */ 1307 public function __get($value) 1308 { 1309 switch ($value) { 1310 case "resultunits": 1311 return $this->resultunits; 1312 break; 1313 default: 1314 return parent::__get($value); 1315 break; 1316 } 1317 } 1318 1319 /** 1320 * @param \ilUnitConfigurationRepository $unitrepository 1321 */ 1322 public function setUnitrepository($unitrepository) 1323 { 1324 $this->unitrepository = $unitrepository; 1325 } 1326 1327 /** 1328 * @return \ilUnitConfigurationRepository 1329 */ 1330 public function getUnitrepository() 1331 { 1332 return $this->unitrepository; 1333 } 1334 1335 /** 1336 * @return array 1337 */ 1338 protected function getSolutionSubmit() 1339 { 1340 $solutionSubmit = array(); 1341 foreach ($_POST as $k => $v) { 1342 if (preg_match("/^result_(\\\$r\\d+)$/", $k)) { 1343 $solutionSubmit[$k] = $v; 1344 } elseif (preg_match("/^result_(\\\$r\\d+)_unit$/", $k)) { 1345 $solutionSubmit[$k] = $v; 1346 } 1347 } 1348 return $solutionSubmit; 1349 } 1350 1351 public function validateSolutionSubmit() 1352 { 1353 foreach ($this->getSolutionSubmit() as $key => $value) { 1354 if (preg_match("/^result_(\\\$r\\d+)$/", $key)) { 1355 if (strlen($value) && !$this->isValidSolutionResultValue($value)) { 1356 ilUtil::sendFailure($this->lng->txt("err_no_numeric_value"), true); 1357 return false; 1358 } 1359 } elseif (preg_match("/^result_(\\\$r\\d+)_unit$/", $key)) { 1360 continue; 1361 } 1362 } 1363 1364 return true; 1365 } 1366 1367 /** 1368 * Get all available operations for a specific question 1369 * 1370 * @param $expression 1371 * 1372 * @internal param string $expression_type 1373 * @return array 1374 */ 1375 public function getOperators($expression) 1376 { 1377 require_once "./Modules/TestQuestionPool/classes/class.ilOperatorsExpressionMapping.php"; 1378 return ilOperatorsExpressionMapping::getOperatorsByExpression($expression); 1379 } 1380 1381 /** 1382 * Get all available expression types for a specific question 1383 * @return array 1384 */ 1385 public function getExpressionTypes() 1386 { 1387 return array( 1388 iQuestionCondition::PercentageResultExpression, 1389 iQuestionCondition::NumericResultExpression, 1390 iQuestionCondition::EmptyAnswerExpression, 1391 ); 1392 } 1393 1394 /** 1395 * Get the user solution for a question by active_id and the test pass 1396 * 1397 * @param int $active_id 1398 * @param int $pass 1399 * 1400 * @return ilUserQuestionResult 1401 */ 1402 public function getUserQuestionResult($active_id, $pass) 1403 { 1404 /** @var ilDBInterface $ilDB */ 1405 global $DIC; 1406 $ilDB = $DIC['ilDB']; 1407 $result = new ilUserQuestionResult($this, $active_id, $pass); 1408 1409 $maxStep = $this->lookupMaxStep($active_id, $pass); 1410 1411 if ($maxStep !== null) { 1412 $data = $ilDB->queryF( 1413 "SELECT value1, value2 FROM tst_solutions WHERE active_fi = %s AND pass = %s AND question_fi = %s AND step = %s", 1414 array("integer", "integer", "integer",'integer'), 1415 array($active_id, $pass, $this->getId(), $maxStep) 1416 ); 1417 } else { 1418 $data = $ilDB->queryF( 1419 "SELECT value1, value2 FROM tst_solutions WHERE active_fi = %s AND pass = %s AND question_fi = %s", 1420 array("integer", "integer", "integer"), 1421 array($active_id, $pass, $this->getId()) 1422 ); 1423 } 1424 1425 while ($row = $ilDB->fetchAssoc($data)) { 1426 if (strstr($row["value1"], '$r') && $row["value2"] != null) { 1427 $result->addKeyValue(str_replace('$r', "", $row["value1"]), $row["value2"]); 1428 } 1429 } 1430 1431 $points = $this->calculateReachedPoints($active_id, $pass); 1432 $max_points = $this->getMaximumPoints(); 1433 1434 $result->setReachedPercentage(($points / $max_points) * 100); 1435 1436 return $result; 1437 } 1438 1439 /** 1440 * If index is null, the function returns an array with all anwser options 1441 * Else it returns the specific answer option 1442 * 1443 * @param null|int $index 1444 * 1445 * @return array|ASS_AnswerSimple 1446 */ 1447 public function getAvailableAnswerOptions($index = null) 1448 { 1449 if ($index !== null) { 1450 return $this->getResult('$r' . ($index + 1)); 1451 } else { 1452 return $this->getResults(); 1453 } 1454 } 1455} 1456