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
 * Compontent definition of a gradeitem.
19
 *
20
 * @package   core_grades
21
 * @copyright Andrew Nicols <andrew@nicols.co.uk>
22
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23
 */
24
 
25
declare(strict_types = 1);
26
 
27
namespace core_grades;
28
 
29
use context;
30
use gradingform_controller;
31
use gradingform_instance;
32
use moodle_exception;
33
use stdClass;
34
use grade_item as core_gradeitem;
35
use grading_manager;
36
 
37
/**
38
 * Compontent definition of a gradeitem.
39
 *
40
 * @package   core_grades
41
 * @copyright Andrew Nicols <andrew@nicols.co.uk>
42
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
43
 */
44
abstract class component_gradeitem {
45
 
46
    /** @var array The scale data for the current grade item */
47
    protected $scale;
48
 
49
    /** @var string The component */
50
    protected $component;
51
 
52
    /** @var context The context for this activity */
53
    protected $context;
54
 
55
    /** @var string The item name */
56
    protected $itemname;
57
 
58
    /** @var int The grade itemnumber */
59
    protected $itemnumber;
60
 
61
    /**
62
     * component_gradeitem constructor.
63
     *
64
     * @param string $component
65
     * @param context $context
66
     * @param string $itemname
67
     * @throws \coding_exception
68
     */
69
    final protected function __construct(string $component, context $context, string $itemname) {
70
        $this->component = $component;
71
        $this->context = $context;
72
        $this->itemname = $itemname;
73
        $this->itemnumber = component_gradeitems::get_itemnumber_from_itemname($component, $itemname);
74
    }
75
 
76
    /**
77
     * Fetch an instance of a specific component_gradeitem.
78
     *
79
     * @param string $component
80
     * @param context $context
81
     * @param string $itemname
82
     * @return self
83
     */
84
    public static function instance(string $component, context $context, string $itemname): self {
85
        $itemnumber = component_gradeitems::get_itemnumber_from_itemname($component, $itemname);
86
 
87
        $classname = "{$component}\\grades\\{$itemname}_gradeitem";
88
        if (!class_exists($classname)) {
89
            throw new \coding_exception("Unknown gradeitem {$itemname} for component {$classname}");
90
        }
91
 
92
        return $classname::load_from_context($context);
93
    }
94
 
95
    /**
96
     * Load an instance of the current component_gradeitem based on context.
97
     *
98
     * @param context $context
99
     * @return self
100
     */
101
    abstract public static function load_from_context(context $context): self;
102
 
103
    /**
104
     * The table name used for grading.
105
     *
106
     * @return string
107
     */
108
    abstract protected function get_table_name(): string;
109
 
110
    /**
111
     * Get the itemid for the current gradeitem.
112
     *
113
     * @return int
114
     */
115
    public function get_grade_itemid(): int {
116
        return component_gradeitems::get_itemnumber_from_itemname($this->component, $this->itemname);
117
    }
118
 
119
    /**
120
     * Whether grading is enabled for this item.
121
     *
122
     * @return bool
123
     */
124
    abstract public function is_grading_enabled(): bool;
125
 
126
    /**
127
     * Get the grade value for this instance.
128
     * The itemname is translated to the relevant grade field for the activity.
129
     *
130
     * @return int
131
     */
132
    abstract protected function get_gradeitem_value(): ?int;
133
 
134
    /**
135
     * Whether the grader can grade the gradee.
136
     *
137
     * @param stdClass $gradeduser The user being graded
138
     * @param stdClass $grader The user who is grading
139
     * @return bool
140
     */
141
    abstract public function user_can_grade(stdClass $gradeduser, stdClass $grader): bool;
142
 
143
    /**
144
     * Require that the user can grade, throwing an exception if not.
145
     *
146
     * @param stdClass $gradeduser The user being graded
147
     * @param stdClass $grader The user who is grading
148
     * @throws \required_capability_exception
149
     */
150
    abstract public function require_user_can_grade(stdClass $gradeduser, stdClass $grader): void;
151
 
152
    /**
153
     * Get the scale if a scale is being used.
154
     *
155
     * @return stdClass
156
     */
157
    protected function get_scale(): ?stdClass {
158
        global $DB;
159
 
160
        $gradetype = $this->get_gradeitem_value();
161
        if ($gradetype > 0) {
162
            return null;
163
        }
164
 
165
        // This is a scale.
166
        if (null === $this->scale) {
167
            $this->scale = $DB->get_record('scale', ['id' => -1 * $gradetype]);
168
        }
169
 
170
        return $this->scale;
171
    }
172
 
173
    /**
174
     * Check whether a scale is being used for this grade item.
175
     *
176
     * @return bool
177
     */
178
    public function is_using_scale(): bool {
179
        $gradetype = $this->get_gradeitem_value();
180
 
181
        return $gradetype < 0;
182
    }
183
 
184
    /**
185
     * Whether this grade item is configured to use direct grading.
186
     *
187
     * @return bool
188
     */
189
    public function is_using_direct_grading(): bool {
190
        if ($this->is_using_scale()) {
191
            return false;
192
        }
193
 
194
        if ($this->get_advanced_grading_controller()) {
195
            return false;
196
        }
197
 
198
        return true;
199
    }
200
 
201
    /**
202
     * Whether this grade item is configured to use advanced grading.
203
     *
204
     * @return bool
205
     */
206
    public function is_using_advanced_grading(): bool {
207
        if ($this->is_using_scale()) {
208
            return false;
209
        }
210
 
211
        if ($this->get_advanced_grading_controller()) {
212
            return true;
213
        }
214
 
215
        return false;
216
    }
217
 
218
    /**
219
     * Get the name of the advanced grading method.
220
     *
221
     * @return string
222
     */
223
    public function get_advanced_grading_method(): ?string {
224
        $gradingmanager = $this->get_grading_manager();
225
 
226
        if (empty($gradingmanager)) {
227
            return null;
228
        }
229
 
230
        return $gradingmanager->get_active_method();
231
    }
232
 
233
    /**
234
     * Get the name of the component responsible for grading this gradeitem.
235
     *
236
     * @return string
237
     */
238
    public function get_grading_component_name(): ?string {
239
        if (!$this->is_grading_enabled()) {
240
            return null;
241
        }
242
 
243
        if ($method = $this->get_advanced_grading_method()) {
244
            return "gradingform_{$method}";
245
        }
246
 
247
        return 'core_grades';
248
    }
249
 
250
    /**
251
     * Get the name of the component subtype responsible for grading this gradeitem.
252
     *
253
     * @return string
254
     */
255
    public function get_grading_component_subtype(): ?string {
256
        if (!$this->is_grading_enabled()) {
257
            return null;
258
        }
259
 
260
        if ($method = $this->get_advanced_grading_method()) {
261
            return null;
262
        }
263
 
264
        if ($this->is_using_scale()) {
265
            return 'scale';
266
        }
267
 
268
        return 'point';
269
    }
270
 
271
    /**
272
     * Whether decimals are allowed.
273
     *
274
     * @return bool
275
     */
276
    protected function allow_decimals(): bool {
277
        return $this->get_gradeitem_value() > 0;
278
    }
279
 
280
    /**
281
     * Get the grading manager for this advanced grading definition.
282
     *
283
     * @return grading_manager
284
     */
285
    protected function get_grading_manager(): ?grading_manager {
286
        require_once(__DIR__ . '/../grading/lib.php');
287
        return get_grading_manager($this->context, $this->component, $this->itemname);
288
 
289
    }
290
 
291
    /**
292
     * Get the advanced grading controller if advanced grading is enabled.
293
     *
294
     * @return gradingform_controller
295
     */
296
    protected function get_advanced_grading_controller(): ?gradingform_controller {
297
        $gradingmanager = $this->get_grading_manager();
298
 
299
        if (empty($gradingmanager)) {
300
            return null;
301
        }
302
 
303
        if ($gradingmethod = $gradingmanager->get_active_method()) {
304
            return $gradingmanager->get_controller($gradingmethod);
305
        }
306
 
307
        return null;
308
    }
309
 
310
    /**
311
     * Get the list of available grade items.
312
     *
313
     * @return array
314
     */
315
    public function get_grade_menu(): array {
316
        return make_grades_menu($this->get_gradeitem_value());
317
    }
318
 
319
    /**
320
     * Check whether the supplied grade is valid and throw an exception if not.
321
     *
322
     * @param float $grade The value being checked
323
     * @throws moodle_exception
324
     * @return bool
325
     */
326
    public function check_grade_validity(?float $grade): bool {
327
        $grade = grade_floatval(unformat_float($grade));
328
        if ($grade) {
329
            if ($this->is_using_scale()) {
330
                // Fetch all options for this scale.
331
                $scaleoptions = make_menu_from_list($this->get_scale()->scale);
332
 
333
                if ($grade != -1 && !array_key_exists((int) $grade, $scaleoptions)) {
334
                    // The selected option did not exist.
335
                    throw new moodle_exception('error:notinrange', 'core_grading', '', (object) [
336
                        'maxgrade' => count($scaleoptions),
337
                        'grade' => $grade,
338
                    ]);
339
                }
340
            } else if ($grade) {
341
                $maxgrade = $this->get_gradeitem_value();
342
                if ($grade > $maxgrade) {
343
                    // The grade is greater than the maximum possible value.
344
                    throw new moodle_exception('error:notinrange', 'core_grading', '', (object) [
345
                        'maxgrade' => $maxgrade,
346
                        'grade' => $grade,
347
                    ]);
348
                } else if ($grade < 0) {
349
                    // Negative grades are not supported.
350
                    throw new moodle_exception('error:notinrange', 'core_grading', '', (object) [
351
                        'maxgrade' => $maxgrade,
352
                        'grade' => $grade,
353
                    ]);
354
                }
355
            }
356
        }
357
 
358
        return true;
359
    }
360
 
361
    /**
362
     * Create an empty row in the grade for the specified user and grader.
363
     *
364
     * @param stdClass $gradeduser The user being graded
365
     * @param stdClass $grader The user who is grading
366
     * @return stdClass The newly created grade record
367
     */
368
    abstract public function create_empty_grade(stdClass $gradeduser, stdClass $grader): stdClass;
369
 
370
    /**
371
     * Get the grade record for the specified grade id.
372
     *
373
     * @param int $gradeid
374
     * @return stdClass
375
     * @throws \dml_exception
376
     */
377
    public function get_grade(int $gradeid): stdClass {
378
        global $DB;
379
 
380
        return $DB->get_record($this->get_table_name(), ['id' => $gradeid]);
381
    }
382
 
383
    /**
384
     * Get the grade for the specified user.
385
     *
386
     * @param stdClass $gradeduser The user being graded
387
     * @param stdClass $grader The user who is grading
388
     * @return stdClass The grade value
389
     */
390
    abstract public function get_grade_for_user(stdClass $gradeduser, stdClass $grader): ?stdClass;
391
 
392
    /**
393
     * Returns the grade that should be displayed to the user.
394
     *
395
     * The grade does not necessarily return a float value, this method takes grade settings into considering so
396
     * the correct value be shown, eg. a float vs a letter.
397
     *
398
     * @param stdClass $gradeduser
399
     * @param stdClass $grader
400
     * @return stdClass|null
401
     */
402
    public function get_formatted_grade_for_user(stdClass $gradeduser, stdClass $grader): ?stdClass {
403
        global $DB;
404
 
405
        if ($grade = $this->get_grade_for_user($gradeduser, $grader)) {
406
            $gradeitem = $this->get_grade_item();
407
            if (!$this->is_using_scale()) {
408
                $grade->grade = !is_null($grade->grade) ? (float)$grade->grade : null; // Cast non-null values, keeping nulls.
409
                $grade->usergrade = grade_format_gradevalue($grade->grade, $gradeitem);
410
                $grade->maxgrade = format_float($gradeitem->grademax, $gradeitem->get_decimals());
411
                // If displaying the raw grade, also display the total value.
412
                if ($gradeitem->get_displaytype() == GRADE_DISPLAY_TYPE_REAL) {
413
                    $grade->usergrade .= ' / ' . $grade->maxgrade;
414
                }
415
            } else {
416
                $grade->usergrade = '-';
417
                if ($scale = $DB->get_record('scale', ['id' => $gradeitem->scaleid])) {
418
                    $options = make_menu_from_list($scale->scale);
419
 
420
                    $gradeint = (int) $grade->grade;
421
                    if (isset($options[$gradeint])) {
422
                        $grade->usergrade = $options[$gradeint];
423
                    }
424
                }
425
 
426
                $grade->maxgrade = format_float($gradeitem->grademax, $gradeitem->get_decimals());
427
            }
428
 
429
            return $grade;
430
        }
431
 
432
        return null;
433
    }
434
 
435
    /**
436
     * Get the grade status for the specified user.
437
     * If the user has a grade as defined by the implementor return true else return false.
438
     *
439
     * @param stdClass $gradeduser The user being graded
440
     * @return bool The grade status
441
     */
442
    abstract public function user_has_grade(stdClass $gradeduser): bool;
443
 
444
    /**
445
     * Get grades for all users for the specified gradeitem.
446
     *
447
     * @return stdClass[] The grades
448
     */
449
    abstract public function get_all_grades(): array;
450
 
451
    /**
452
     * Get the grade item instance id.
453
     *
454
     * This is typically the cmid in the case of an activity, and relates to the iteminstance field in the grade_items
455
     * table.
456
     *
457
     * @return int
458
     */
459
    abstract public function get_grade_instance_id(): int;
460
 
461
    /**
462
     * Get the core grade item from the current component grade item.
463
     * This is mainly used to access the max grade for a gradeitem
464
     *
465
     * @return \grade_item The grade item
466
     */
467
    public function get_grade_item(): \grade_item {
468
        global $CFG;
469
        require_once("{$CFG->libdir}/gradelib.php");
470
 
471
        [$itemtype, $itemmodule] = \core_component::normalize_component($this->component);
472
        $gradeitem = \grade_item::fetch([
473
            'itemtype' => $itemtype,
474
            'itemmodule' => $itemmodule,
475
            'itemnumber' => $this->itemnumber,
476
            'iteminstance' => $this->get_grade_instance_id(),
477
        ]);
478
 
479
        return $gradeitem;
480
    }
481
 
482
    /**
483
     * Create or update the grade.
484
     *
485
     * @param stdClass $grade
486
     * @return bool Success
487
     */
488
    abstract protected function store_grade(stdClass $grade): bool;
489
 
490
    /**
491
     * Create or update the grade.
492
     *
493
     * @param stdClass $gradeduser The user being graded
494
     * @param stdClass $grader The user who is grading
495
     * @param stdClass $formdata The data submitted
496
     * @return bool Success
497
     */
498
    public function store_grade_from_formdata(stdClass $gradeduser, stdClass $grader, stdClass $formdata): bool {
499
        // Require gradelib for grade_floatval.
500
        require_once(__DIR__ . '/../../lib/gradelib.php');
501
        $grade = $this->get_grade_for_user($gradeduser, $grader);
502
 
503
        if ($this->is_using_advanced_grading()) {
504
            $instanceid = $formdata->instanceid;
505
            $gradinginstance = $this->get_advanced_grading_instance($grader, $grade, (int) $instanceid);
506
            $grade->grade = $gradinginstance->submit_and_get_grade($formdata->advancedgrading, $grade->id);
507
 
508
            if ($grade->grade == -1) {
509
                // In advanced grading, a value of -1 means no data.
510
                return false;
511
            }
512
        } else {
513
            // Handle the case when grade is set to No Grade.
514
            if (isset($formdata->grade)) {
515
                $grade->grade = grade_floatval(unformat_float($formdata->grade));
516
            }
517
        }
518
 
519
        return $this->store_grade($grade);
520
    }
521
 
522
    /**
523
     * Get the advanced grading instance for the specified grade entry.
524
     *
525
     * @param stdClass $grader The user who is grading
526
     * @param stdClass $grade The row from the grade table.
527
     * @param int $instanceid The instanceid of the advanced grading form
528
     * @return gradingform_instance
529
     */
530
    public function get_advanced_grading_instance(stdClass $grader, stdClass $grade, int $instanceid = null): ?gradingform_instance {
531
        $controller = $this->get_advanced_grading_controller($this->itemname);
532
 
533
        if (empty($controller)) {
534
            // Advanced grading not enabeld for this item.
535
            return null;
536
        }
537
 
538
        if (!$controller->is_form_available()) {
539
            // The form is not available for this item.
540
            return null;
541
        }
542
 
543
        // Fetch the instance for the specified graderid/itemid.
544
        $gradinginstance = $controller->fetch_instance(
545
            (int) $grader->id,
546
            (int) $grade->id,
547
            $instanceid
548
        );
549
 
550
        // Set the allowed grade range.
551
        $gradinginstance->get_controller()->set_grade_range(
552
            $this->get_grade_menu(),
553
            $this->allow_decimals()
554
        );
555
 
556
        return $gradinginstance;
557
    }
558
 
559
    /**
560
     * Sends a notification about the item being graded for the student.
561
     *
562
     * @param stdClass $gradeduser The user being graded
563
     * @param stdClass $grader The user who is grading
564
     */
565
    public function send_student_notification(stdClass $gradeduser, stdClass $grader): void {
566
        $contextname = $this->context->get_context_name();
567
        $eventdata = new \core\message\message();
568
        $eventdata->courseid          = $this->context->get_course_context()->instanceid;
569
        $eventdata->component         = 'moodle';
570
        $eventdata->name              = 'gradenotifications';
571
        $eventdata->userfrom          = $grader;
572
        $eventdata->userto            = $gradeduser;
573
        $eventdata->subject           = get_string('gradenotificationsubject', 'grades');
574
        $eventdata->fullmessage       = get_string('gradenotificationmessage', 'grades', $contextname);
575
        $eventdata->contexturl        = $this->context->get_url();
576
        $eventdata->contexturlname    = $contextname;
577
        $eventdata->fullmessageformat = FORMAT_HTML;
578
        $eventdata->fullmessagehtml   = '';
579
        $eventdata->smallmessage      = '';
580
        $eventdata->notification      = 1;
581
        message_send($eventdata);
582
    }
583
}