Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
// This file is part of Moodle - http://moodle.org/
3
//
4
// Moodle is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8
//
9
// Moodle is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13
//
14
// You should have received a copy of the GNU General Public License
15
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16
 
17
/**
18
 * Condition on grades of current user.
19
 *
20
 * @package availability_grade
21
 * @copyright 2014 The Open University
22
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23
 */
24
 
25
namespace availability_grade;
26
 
27
defined('MOODLE_INTERNAL') || die();
28
 
29
/**
30
 * Condition on grades of current user.
31
 *
32
 * @package availability_grade
33
 * @copyright 2014 The Open University
34
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
35
 */
36
class condition extends \core_availability\condition {
37
    /** @var int Grade item id */
38
    private $gradeitemid;
39
 
40
    /** @var float|null Min grade (must be >= this) or null if none */
41
    private $min;
42
 
43
    /** @var float|null Max grade (must be < this) or null if none */
44
    private $max;
45
 
46
    /**
47
     * Constructor.
48
     *
49
     * @param \stdClass $structure Data structure from JSON decode
50
     * @throws \coding_exception If invalid data structure.
51
     */
52
    public function __construct($structure) {
53
        // Get grade item id.
54
        if (isset($structure->id) && is_int($structure->id)) {
55
            $this->gradeitemid = $structure->id;
56
        } else {
57
            throw new \coding_exception('Missing or invalid ->id for grade condition');
58
        }
59
 
60
        // Get min and max.
61
        if (!property_exists($structure, 'min')) {
62
            $this->min = null;
63
        } else if (is_float($structure->min) || is_int($structure->min)) {
64
            $this->min = $structure->min;
65
        } else {
66
            throw new \coding_exception('Missing or invalid ->min for grade condition');
67
        }
68
        if (!property_exists($structure, 'max')) {
69
            $this->max = null;
70
        } else if (is_float($structure->max) || is_int($structure->max)) {
71
            $this->max = $structure->max;
72
        } else {
73
            throw new \coding_exception('Missing or invalid ->max for grade condition');
74
        }
75
    }
76
 
77
    public function save() {
78
        $result = (object)array('type' => 'grade', 'id' => $this->gradeitemid);
79
        if (!is_null($this->min)) {
80
            $result->min = $this->min;
81
        }
82
        if (!is_null($this->max)) {
83
            $result->max = $this->max;
84
        }
85
        return $result;
86
    }
87
 
88
    /**
89
     * Returns a JSON object which corresponds to a condition of this type.
90
     *
91
     * Intended for unit testing, as normally the JSON values are constructed
92
     * by JavaScript code.
93
     *
94
     * @param int $gradeitemid Grade item id
95
     * @param number|null $min Min grade (or null if no min)
96
     * @param number|null $max Max grade (or null if no max)
97
     * @return stdClass Object representing condition
98
     */
99
    public static function get_json($gradeitemid, $min = null, $max = null) {
100
        $result = (object)array('type' => 'grade', 'id' => (int)$gradeitemid);
101
        if (!is_null($min)) {
102
            $result->min = $min;
103
        }
104
        if (!is_null($max)) {
105
            $result->max = $max;
106
        }
107
        return $result;
108
    }
109
 
110
    public function is_available($not, \core_availability\info $info, $grabthelot, $userid) {
111
        $course = $info->get_course();
112
        $score = $this->get_cached_grade_score($this->gradeitemid, $course->id, $grabthelot, $userid);
113
        $allow = $score !== false &&
114
                (is_null($this->min) || $score >= $this->min) &&
115
                (is_null($this->max) || $score < $this->max);
116
        if ($not) {
117
            $allow = !$allow;
118
        }
119
 
120
        return $allow;
121
    }
122
 
123
    public function get_description($full, $not, \core_availability\info $info) {
124
        $course = $info->get_course();
125
        // String depends on type of requirement. We are coy about
126
        // the actual numbers, in case grades aren't released to
127
        // students.
128
        if (is_null($this->min) && is_null($this->max)) {
129
            $string = 'any';
130
        } else if (is_null($this->max)) {
131
            $string = 'min';
132
        } else if (is_null($this->min)) {
133
            $string = 'max';
134
        } else {
135
            $string = 'range';
136
        }
137
        if ($not) {
138
            // The specific strings don't make as much sense with 'not'.
139
            if ($string === 'any') {
140
                $string = 'notany';
141
            } else {
142
                $string = 'notgeneral';
143
            }
144
        }
145
        // We cannot get the name at this point because it requires format_string which is not
146
        // allowed here. Instead, get it later with the callback function below.
147
        $name = $this->description_callback([$this->gradeitemid]);
148
        return get_string('requires_' . $string, 'availability_grade', $name);
149
    }
150
 
151
    /**
152
     * Gets the grade name at display time.
153
     *
154
     * @param \course_modinfo $modinfo Modinfo
155
     * @param \context $context Context
156
     * @param string[] $params Parameters (just grade item id)
157
     * @return string Text value
158
     */
159
    public static function get_description_callback_value(
160
            \course_modinfo $modinfo, \context $context, array $params): string {
161
        if (count($params) !== 1 || !is_number($params[0])) {
162
            return '<!-- Invalid grade description callback -->';
163
        }
164
        $gradeitemid = (int)$params[0];
165
        return self::get_cached_grade_name($modinfo->get_course_id(), $gradeitemid);
166
    }
167
 
168
    protected function get_debug_string() {
169
        $out = '#' . $this->gradeitemid;
170
        if (!is_null($this->min)) {
171
            $out .= ' >= ' . sprintf('%.5f', $this->min);
172
        }
173
        if (!is_null($this->max)) {
174
            if (!is_null($this->min)) {
175
                $out .= ',';
176
            }
177
            $out .= ' < ' . sprintf('%.5f', $this->max);
178
        }
179
        return $out;
180
    }
181
 
182
    /**
183
     * Obtains the name of a grade item, also checking that it exists. Uses a
184
     * cache. The name returned is suitable for display.
185
     *
186
     * @param int $courseid Course id
187
     * @param int $gradeitemid Grade item id
188
     * @return string Grade name or empty string if no grade with that id
189
     */
190
    private static function get_cached_grade_name($courseid, $gradeitemid) {
191
        global $DB, $CFG;
192
        require_once($CFG->libdir . '/gradelib.php');
193
 
194
        // Get all grade item names from cache, or using db query.
195
        $cache = \cache::make('availability_grade', 'items');
196
        if (($cacheditems = $cache->get($courseid)) === false) {
197
            // We cache the whole items table not the name; the format_string
198
            // call for the name might depend on current user (e.g. multilang)
199
            // and this is a shared cache.
200
            $cacheditems = $DB->get_records('grade_items', array('courseid' => $courseid));
201
            $cache->set($courseid, $cacheditems);
202
        }
203
 
204
        // Return name from cached item or a lang string.
205
        if (!array_key_exists($gradeitemid, $cacheditems)) {
206
            return get_string('missing', 'availability_grade');
207
        }
208
        $gradeitemobj = $cacheditems[$gradeitemid];
209
        $item = new \grade_item;
210
        \grade_object::set_properties($item, $gradeitemobj);
211
        return $item->get_name();
212
    }
213
 
214
    /**
215
     * Obtains a grade score. Note that this score should not be displayed to
216
     * the user, because gradebook rules might prohibit that. It may be a
217
     * non-final score subject to adjustment later.
218
     *
219
     * @param int $gradeitemid Grade item ID we're interested in
220
     * @param int $courseid Course id
221
     * @param bool $grabthelot If true, grabs all scores for current user on
222
     *   this course, so that later ones come from cache
223
     * @param int $userid Set if requesting grade for a different user (does
224
     *   not use cache)
225
     * @return float Grade score as a percentage in range 0-100 (e.g. 100.0
226
     *   or 37.21), or false if user does not have a grade yet
227
     */
228
    protected static function get_cached_grade_score($gradeitemid, $courseid,
229
            $grabthelot=false, $userid=0) {
230
        global $USER, $DB;
231
        if (!$userid) {
232
            $userid = $USER->id;
233
        }
234
        $cache = \cache::make('availability_grade', 'scores');
235
        if (($cachedgrades = $cache->get($userid)) === false) {
236
            $cachedgrades = array();
237
        }
238
        if (!array_key_exists($gradeitemid, $cachedgrades)) {
239
            if ($grabthelot) {
240
                // Get all grades for the current course.
241
                $rs = $DB->get_recordset_sql('
242
                        SELECT
243
                            gi.id,gg.finalgrade,gg.rawgrademin,gg.rawgrademax
244
                        FROM
245
                            {grade_items} gi
246
                            LEFT JOIN {grade_grades} gg ON gi.id=gg.itemid AND gg.userid=?
247
                        WHERE
248
                            gi.courseid = ?', array($userid, $courseid));
249
                foreach ($rs as $record) {
250
                    // This function produces division by zero error warnings when rawgrademax and rawgrademin
251
                    // are equal. Below change does not affect function behavior, just avoids the warning.
252
                    if (is_null($record->finalgrade) || $record->rawgrademax == $record->rawgrademin) {
253
                        // No grade = false.
254
                        $cachedgrades[$record->id] = false;
255
                    } else {
256
                        // Otherwise convert grade to percentage.
257
                        $cachedgrades[$record->id] =
258
                                (($record->finalgrade - $record->rawgrademin) * 100) /
259
                                ($record->rawgrademax - $record->rawgrademin);
260
                    }
261
                }
262
                $rs->close();
263
                // And if it's still not set, well it doesn't exist (eg
264
                // maybe the user set it as a condition, then deleted the
265
                // grade item) so we call it false.
266
                if (!array_key_exists($gradeitemid, $cachedgrades)) {
267
                    $cachedgrades[$gradeitemid] = false;
268
                }
269
            } else {
270
                // Just get current grade.
271
                $record = $DB->get_record('grade_grades', array(
272
                    'userid' => $userid, 'itemid' => $gradeitemid));
273
                // This function produces division by zero error warnings when rawgrademax and rawgrademin
274
                // are equal. Below change does not affect function behavior, just avoids the warning.
275
                if ($record && !is_null($record->finalgrade) && $record->rawgrademax != $record->rawgrademin) {
276
                    $score = (($record->finalgrade - $record->rawgrademin) * 100) /
277
                        ($record->rawgrademax - $record->rawgrademin);
278
                } else {
279
                    // Treat the case where row exists but is null, same as
280
                    // case where row doesn't exist.
281
                    $score = false;
282
                }
283
                $cachedgrades[$gradeitemid] = $score;
284
            }
285
            $cache->set($userid, $cachedgrades);
286
        }
287
        return $cachedgrades[$gradeitemid];
288
    }
289
 
290
    public function update_after_restore($restoreid, $courseid, \base_logger $logger, $name) {
291
        global $DB;
292
        $rec = \restore_dbops::get_backup_ids_record($restoreid, 'grade_item', $this->gradeitemid);
293
        if (!$rec || !$rec->newitemid) {
294
            // If we are on the same course (e.g. duplicate) then we can just
295
            // use the existing one.
296
            if ($DB->record_exists('grade_items',
297
                    array('id' => $this->gradeitemid, 'courseid' => $courseid))) {
298
                return false;
299
            }
300
            // Otherwise it's a warning.
301
            $this->gradeitemid = 0;
302
            $logger->process('Restored item (' . $name .
303
                    ') has availability condition on grade that was not restored',
304
                    \backup::LOG_WARNING);
305
        } else {
306
            $this->gradeitemid = (int)$rec->newitemid;
307
        }
308
        return true;
309
    }
310
 
311
    public function update_dependency_id($table, $oldid, $newid) {
312
        if ($table === 'grade_items' && (int)$this->gradeitemid === (int)$oldid) {
313
            $this->gradeitemid = $newid;
314
            return true;
315
        } else {
316
            return false;
317
        }
318
    }
319
}