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