1<?php 2/* Copyright (c) 1998-2013 ILIAS open source, Extended GPL, see docs/LICENSE */ 3 4require_once './Modules/Test/classes/inc.AssessmentConstants.php'; 5 6/** 7 * A class defining mark schemas for assessment test objects 8 * 9 * @author Helmut Schottmüller <helmut.schottmueller@mac.com> 10 * @author Maximilian Becker <mbecker@databay.de> 11 * 12 * @version $Id$ 13 * 14 * @ingroup ModulesTest 15 */ 16class ASS_MarkSchema 17{ 18 /** @var $mark_steps array An array containing all mark steps defined for the test. */ 19 public $mark_steps; 20 21 /** 22 * ASS_MarkSchema constructor 23 * 24 * The constructor takes possible arguments an creates an instance of the ASS_MarkSchema object. 25 * 26 * @return ASS_MarkSchema 27 */ 28 public function __construct() 29 { 30 $this->mark_steps = array(); 31 } 32 33 /** 34 * Creates a simple mark schema for two mark steps: 35 * failed and passed. 36 * 37 * @see $mark_steps 38 * 39 * @param string $txt_failed_short The short text of the failed mark. 40 * @param string $txt_failed_official The official text of the failed mark. 41 * @param float|int $percentage_failed The minimum percentage level reaching the failed mark. 42 * @param integer $failed_passed Indicates the passed status of the failed mark (0 = failed, 1 = passed). 43 * @param string $txt_passed_short The short text of the passed mark. 44 * @param string $txt_passed_official The official text of the passed mark. 45 * @param float|int $percentage_passed The minimum percentage level reaching the passed mark. 46 * @param integer $passed_passed Indicates the passed status of the passed mark (0 = failed, 1 = passed). 47 */ 48 public function createSimpleSchema( 49 $txt_failed_short = "failed", 50 $txt_failed_official = "failed", 51 $percentage_failed = 0, 52 $failed_passed = 0, 53 $txt_passed_short = "passed", 54 $txt_passed_official = "passed", 55 $percentage_passed = 50, 56 $passed_passed = 1 57 ) { 58 $this->flush(); 59 $this->addMarkStep($txt_failed_short, $txt_failed_official, $percentage_failed, $failed_passed); 60 $this->addMarkStep($txt_passed_short, $txt_passed_official, $percentage_passed, $passed_passed); 61 } 62 63 /** 64 * Adds a mark step to the mark schema. A new ASS_Mark object will be created and stored 65 * in the $mark_steps array. 66 * 67 * @see $mark_steps 68 * 69 * @param string $txt_short The short text of the mark. 70 * @param string $txt_official The official text of the mark. 71 * @param float|integer $percentage The minimum percentage level reaching the mark. 72 * @param integer $passed The passed status of the mark (0 = failed, 1 = passed). 73 */ 74 public function addMarkStep($txt_short = "", $txt_official = "", $percentage = 0, $passed = 0) 75 { 76 require_once './Modules/Test/classes/class.assMark.php'; 77 $mark = new ASS_Mark($txt_short, $txt_official, $percentage, $passed); 78 array_push($this->mark_steps, $mark); 79 } 80 81 /** 82 * Saves an ASS_MarkSchema object to a database. 83 * 84 * @param integer $test_id The database id of the related test. 85 */ 86 public function saveToDb($test_id) 87 { 88 global $DIC; 89 $lng = $DIC['lng']; 90 $ilDB = $DIC['ilDB']; 91 92 $oldmarks = array(); 93 include_once "./Modules/Test/classes/class.ilObjAssessmentFolder.php"; 94 if (ilObjAssessmentFolder::_enabledAssessmentLogging()) { 95 $result = $ilDB->queryF( 96 "SELECT * FROM tst_mark WHERE test_fi = %s ORDER BY minimum_level", 97 array('integer'), 98 array($test_id) 99 ); 100 if ($result->numRows()) { 101 /** @noinspection PhpAssignmentInConditionInspection */ 102 while ($row = $ilDB->fetchAssoc($result)) { 103 $oldmarks[$row["minimum_level"]] = $row; 104 } 105 } 106 } 107 108 if (!$test_id) { 109 return; 110 } 111 // Delete all entries 112 $ilDB->manipulateF( 113 "DELETE FROM tst_mark WHERE test_fi = %s", 114 array('integer'), 115 array($test_id) 116 ); 117 if (count($this->mark_steps) == 0) { 118 return; 119 } 120 121 // Write new datasets 122 foreach ($this->mark_steps as $key => $value) { 123 $next_id = $ilDB->nextId('tst_mark'); 124 $ilDB->manipulateF( 125 "INSERT INTO tst_mark (mark_id, test_fi, short_name, official_name, minimum_level, passed, tstamp) VALUES (%s, %s, %s, %s, %s, %s, %s)", 126 array('integer','integer','text','text','float','text','integer'), 127 array( 128 $next_id, 129 $test_id, 130 $value->getShortName(), 131 $value->getOfficialName(), 132 $value->getMinimumLevel(), 133 $value->getPassed(), 134 time() 135 ) 136 ); 137 } 138 if (ilObjAssessmentFolder::_enabledAssessmentLogging()) { 139 $result = $ilDB->queryF( 140 "SELECT * FROM tst_mark WHERE test_fi = %s ORDER BY minimum_level", 141 array('integer'), 142 array($test_id) 143 ); 144 $newmarks = array(); 145 if ($result->numRows()) { 146 /** @noinspection PhpAssignmentInConditionInspection */ 147 while ($row = $ilDB->fetchAssoc($result)) { 148 $newmarks[$row["minimum_level"]] = $row; 149 } 150 } 151 foreach ($oldmarks as $level => $row) { 152 if (array_key_exists($level, $newmarks)) { 153 $difffields = array(); 154 foreach ($row as $key => $value) { 155 if (strcmp($value, $newmarks[$level][$key]) != 0) { 156 switch ($key) { 157 case "mark_id": 158 case "tstamp": 159 break; 160 default: 161 array_push($difffields, "$key: $value => " . $newmarks[$level][$key]); 162 break; 163 } 164 } 165 } 166 if (count($difffields)) { 167 $this->logAction($test_id, $lng->txtlng("assessment", "log_mark_changed", ilObjAssessmentFolder::_getLogLanguage()) . ": " . join(", ", $difffields)); 168 } 169 } else { 170 $this->logAction($test_id, $lng->txtlng("assessment", "log_mark_removed", ilObjAssessmentFolder::_getLogLanguage()) . ": " . 171 $lng->txtlng("assessment", "tst_mark_minimum_level", ilObjAssessmentFolder::_getLogLanguage()) . " = " . $row["minimum_level"] . ", " . 172 $lng->txtlng("assessment", "tst_mark_short_form", ilObjAssessmentFolder::_getLogLanguage()) . " = " . $row["short_name"] . ", " . 173 $lng->txtlng("assessment", "tst_mark_official_form", ilObjAssessmentFolder::_getLogLanguage()) . " = " . $row["official_name"] . ", " . 174 $lng->txtlng("assessment", "tst_mark_passed", ilObjAssessmentFolder::_getLogLanguage()) . " = " . $row["passed"]); 175 } 176 } 177 foreach ($newmarks as $level => $row) { 178 if (!array_key_exists($level, $oldmarks)) { 179 $this->logAction($test_id, $lng->txtlng("assessment", "log_mark_added", ilObjAssessmentFolder::_getLogLanguage()) . ": " . 180 $lng->txtlng("assessment", "tst_mark_minimum_level", ilObjAssessmentFolder::_getLogLanguage()) . " = " . $row["minimum_level"] . ", " . 181 $lng->txtlng("assessment", "tst_mark_short_form", ilObjAssessmentFolder::_getLogLanguage()) . " = " . $row["short_name"] . ", " . 182 $lng->txtlng("assessment", "tst_mark_official_form", ilObjAssessmentFolder::_getLogLanguage()) . " = " . $row["official_name"] . ", " . 183 $lng->txtlng("assessment", "tst_mark_passed", ilObjAssessmentFolder::_getLogLanguage()) . " = " . $row["passed"]); 184 } 185 } 186 } 187 } 188 189 /** 190 * Loads an ASS_MarkSchema object from a database. 191 * 192 * @param integer $test_id A unique key which defines the test in the database. 193 */ 194 public function loadFromDb($test_id) 195 { 196 global $DIC; 197 $ilDB = $DIC['ilDB']; 198 199 if (!$test_id) { 200 return; 201 } 202 $result = $ilDB->queryF( 203 "SELECT * FROM tst_mark WHERE test_fi = %s ORDER BY minimum_level", 204 array('integer'), 205 array($test_id) 206 ); 207 if ($result->numRows() > 0) { 208 /** @noinspection PhpAssignmentInConditionInspection */ 209 while ($data = $ilDB->fetchAssoc($result)) { 210 $this->addMarkStep($data["short_name"], $data["official_name"], $data["minimum_level"], $data["passed"]); 211 } 212 } 213 } 214 215 /** 216 * Empties the mark schema and removes all mark steps. 217 * 218 * @see $mark_steps 219 */ 220 public function flush() 221 { 222 $this->mark_steps = array(); 223 } 224 225 /** 226 * Sorts the mark schema using the minimum level values. 227 * 228 * @see $mark_steps 229 */ 230 public function sort() 231 { 232 function level_sort($a, $b) 233 { 234 if ($a->getMinimumLevel() == $b->getMinimumLevel()) { 235 $res = strcmp($a->getShortName(), $b->getShortName()); 236 if ($res == 0) { 237 return strcmp($a->getOfficialName(), $b->getOfficialName()); 238 } else { 239 return $res; 240 } 241 } 242 return ($a->getMinimumLevel() < $b->getMinimumLevel()) ? -1 : 1; 243 } 244 usort($this->mark_steps, 'level_sort'); 245 } 246 247 /** 248 * Deletes the mark step with a given index. 249 * 250 * @see $mark_steps 251 * 252 * @param integer $index The index of the mark step to delete. 253 */ 254 public function deleteMarkStep($index = 0) 255 { 256 if ($index < 0) { 257 return; 258 } 259 if (count($this->mark_steps) < 1) { 260 return; 261 } 262 if ($index >= count($this->mark_steps)) { 263 return; 264 } 265 unset($this->mark_steps[$index]); 266 $this->mark_steps = array_values($this->mark_steps); 267 } 268 269 /** 270 * Deletes multiple mark steps using their index positions. 271 * 272 * @see $mark_steps 273 * 274 * @param array $indexes An array with all the index positions to delete. 275 */ 276 public function deleteMarkSteps($indexes) 277 { 278 foreach ($indexes as $key => $index) { 279 if (!(($index < 0) or (count($this->mark_steps) < 1))) { 280 unset($this->mark_steps[$index]); 281 } 282 } 283 $this->mark_steps = array_values($this->mark_steps); 284 } 285 286 /** 287 * Returns the matching mark for a given percentage. 288 * 289 * @see $mark_steps 290 * 291 * @param double $percentage A percentage value between 0 and 100. 292 * 293 * @return ASS_Mark|bool The mark object, if a matching mark was found, false otherwise. 294 */ 295 public function getMatchingMark($percentage) 296 { 297 for ($i = count($this->mark_steps) - 1; $i >= 0; $i--) { 298 $curMinLevel = $this->mark_steps[$i]->getMinimumLevel(); 299 300 if ($percentage > $curMinLevel || (string) $percentage == (string) $curMinLevel) { // >= does NOT work since PHP is a fucking female float pig !!!! 301 return $this->mark_steps[$i]; 302 } 303 } 304 return false; 305 } 306 307 /** 308 * Returns the matching mark for a given percentage. 309 * 310 * @see $mark_steps 311 * 312 * @param integer $test_id The database id of the test. 313 * @param double $percentage A percentage value between 0 and 100. 314 * 315 * @return mixed The mark object, if a matching mark was found, false otherwise. 316 */ 317 public static function _getMatchingMark($test_id, $percentage) 318 { 319 global $DIC; 320 $ilDB = $DIC['ilDB']; 321 $result = $ilDB->queryF( 322 "SELECT * FROM tst_mark WHERE test_fi = %s ORDER BY minimum_level DESC", 323 array('integer'), 324 array($test_id) 325 ); 326 327 /** @noinspection PhpAssignmentInConditionInspection */ 328 while ($row = $ilDB->fetchAssoc($result)) { 329 if ($percentage >= $row["minimum_level"]) { 330 return $row; 331 } 332 } 333 return false; 334 } 335 336 /** 337 * Returns the matching mark for a given percentage. 338 * 339 * @see $mark_steps 340 * 341 * @param integer $a_obj_id The database id of the test. 342 * @param double $percentage A percentage value between 0 and 100. 343 * 344 * @return mixed The mark object, if a matching mark was found, false otherwise. 345 */ 346 public static function _getMatchingMarkFromObjId($a_obj_id, $percentage) 347 { 348 global $DIC; 349 $ilDB = $DIC['ilDB']; 350 $result = $ilDB->queryF( 351 "SELECT tst_mark.* FROM tst_mark, tst_tests WHERE tst_mark.test_fi = tst_tests.test_id AND tst_tests.obj_fi = %s ORDER BY minimum_level DESC", 352 array('integer'), 353 array($a_obj_id) 354 ); 355 while ($row = $ilDB->fetchAssoc($result)) { 356 if ($percentage >= $row["minimum_level"]) { 357 return $row; 358 } 359 } 360 return false; 361 } 362 363 /** 364 * Returns the matching mark for a given percentage 365 * 366 * @see $mark_steps 367 * 368 * @param int $active_id The database id of the test 369 * @param double $percentage A percentage value between 0 and 100 370 * 371 * @return ASS_Mark|bool The mark object, if a matching mark was found, false otherwise 372 */ 373 public static function _getMatchingMarkFromActiveId($active_id, $percentage) 374 { 375 /** @var $ilDB ilDBInterface */ 376 global $DIC; 377 $ilDB = $DIC['ilDB']; 378 $result = $ilDB->queryF( 379 "SELECT tst_mark.* FROM tst_active, tst_mark, tst_tests WHERE tst_mark.test_fi = tst_tests.test_id AND tst_tests.test_id = tst_active.test_fi AND tst_active.active_id = %s ORDER BY minimum_level DESC", 380 array('integer'), 381 array($active_id) 382 ); 383 384 /** @noinspection PhpAssignmentInConditionInspection */ 385 while ($row = $ilDB->fetchAssoc($result)) { 386 if ($percentage >= $row["minimum_level"]) { 387 return $row; 388 } 389 } 390 return false; 391 } 392 393 /** 394 * Check the marks for consistency. 395 * 396 * @see $mark_steps 397 * 398 * @return bool|string true if the check succeeds, als a text string containing a language string for an error message 399 */ 400 public function checkMarks() 401 { 402 $minimum_percentage = 100; 403 $passed = 0; 404 for ($i = 0; $i < count($this->mark_steps); $i++) { 405 if ($this->mark_steps[$i]->getMinimumLevel() < $minimum_percentage) { 406 $minimum_percentage = $this->mark_steps[$i]->getMinimumLevel(); 407 } 408 if ($this->mark_steps[$i]->getPassed()) { 409 $passed++; 410 } 411 } 412 413 if ($minimum_percentage != 0) { 414 return "min_percentage_ne_0"; 415 } 416 417 if ($passed == 0) { 418 return "no_passed_mark"; 419 } 420 return true; 421 } 422 423 /** 424 * @return ASS_Mark[] 425 */ 426 public function getMarkSteps() 427 { 428 return $this->mark_steps; 429 } 430 431 /** 432 * @param ASS_Mark[] $mark_steps 433 */ 434 public function setMarkSteps($mark_steps) 435 { 436 $this->mark_steps = $mark_steps; 437 } 438 439 /** 440 * Logs an action into the Test&Assessment log. 441 * 442 * @param integer $test_id The database id of the test. 443 * @param string $logtext The log text. 444 * 445 * @return void 446 */ 447 public function logAction($test_id, $logtext = "") 448 { 449 /** @var $ilUser ilObjUser */ 450 global $DIC; 451 $ilUser = $DIC['ilUser']; 452 include_once "./Modules/Test/classes/class.ilObjAssessmentFolder.php"; 453 ilObjAssessmentFolder::_addLog($ilUser->id, ilObjTest::_getObjectIDFromTestID($test_id), $logtext, "", "", true, $_GET["ref_id"]); 454 } 455} 456