• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

apsolu / local_apsolu / 18495050592

14 Oct 2025 10:31AM UTC coverage: 9.435% (+0.2%) from 9.224%
18495050592

push

github

jboulen
fix(federation): améliore le champ numéro de section sur le formulaire d'exportation des licences

559 of 5925 relevant lines covered (9.43%)

0.12 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/classes/core/gradebook.php
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
namespace local_apsolu\core;
18

19
use context_course;
20
use context_system;
21
use csv_export_writer;
22
use Exception;
23
use grade_grade;
24
use grade_item;
25
use MoodleExcelFormat;
26
use MoodleExcelWorkbook;
27
use PhpOffice\PhpSpreadsheet\Style\Border;
28
use stdClass;
29

30
/**
31
 * Classe gérant les carnets de notes.
32
 *
33
 * @package    local_apsolu
34
 * @copyright  2020 Université Rennes 2 <dsi-contact@univ-rennes2.fr>
35
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
36
 */
37
class gradebook {
38
    /**
39
     * Valeur utilisée pour cacher un élément de notation.
40
     */
41
    const GRADE_ITEM_HIDDEN = '1';
42

43
    /**
44
     * Valeur utilisée pour rendre visible un élément de notation.
45
     */
46
    const GRADE_ITEM_VISIBLE = '0';
47

48
    /**
49
     * Nom de la catégorie contenant les éléments d'évaluation d'APSOLU dans chaque cours.
50
     */
51
    const NAME = 'APSOLU';
52

53
    /**
54
     * Nom utilisé dans le champ source pour l'historique des modifications.
55
     */
56
    const SOURCE = 'apsolu-gradebook';
57

58
    /** @var array $caneditgrades Contient un tableau indexé par identifiant de cours, indiquant si l'utilisateur pour
59
                                  éditer les notes du cours. */
60
    public static $caneditgrades = [];
61

62
    /**
63
     * Indique si l'utilisateur est autorisé à éditer les notes pour un cours donné.
64
     *
65
     * @param int|string $courseid       Identifiant du cours.
66
     * @param bool       $calendarlocked Indique si l'édition des notes n'est pas hors-délai par rapport au calendrier.
67
     *
68
     * @return bool True si l'utilisateur peut éditer la note de ce cours, false si il n'a pas les droits.
69
     */
70
    public static function can_edit_grades($courseid, bool $calendarlocked) {
71
        if (isset(self::$caneditgrades[$courseid]) === true) {
×
72
            return self::$caneditgrades[$courseid];
×
73
        }
74

75
        self::$caneditgrades[$courseid] = has_capability('local/apsolu:editgradesafterdeadline', context_system::instance());
×
76

77
        if (self::$caneditgrades[$courseid] === true) {
×
78
            // L'utilisateur peut éditer les notes n'importe quand.
79
            return self::$caneditgrades[$courseid];
×
80
        }
81

82
        self::$caneditgrades[$courseid] = has_capability('local/apsolu:editgrades', context_course::instance($courseid));
×
83
        if (self::$caneditgrades[$courseid] === false) {
×
84
            // L'utilisateur n'a pas le droit de modifier la note dans ce contexte.
85
            return self::$caneditgrades[$courseid];
×
86
        }
87

88
        // On vérifie que l'utilisateur est dans les temps pour noter l'étudiant.
89
        self::$caneditgrades[$courseid] = ($calendarlocked === false);
×
90

91
        return self::$caneditgrades[$courseid];
×
92
    }
93

94
    /**
95
     * Retourne les cours où des évaluations sont en cours.
96
     *
97
     * @param int $contextlevel Si le contextlevel correspond à la constante CONTEXT_SYSTEM, tous les cours sont renvoyés
98
     *                          aux gestionnaires. Sinon, seuls les cours de l'enseignant sont renvoyés.
99
     *
100
     * @return array
101
     */
102
    public static function get_courses(int $contextlevel = CONTEXT_COURSE) {
103
        global $DB, $USER;
×
104

105
        $syscontext = context_system::instance();
×
106

107
        if ($contextlevel !== CONTEXT_SYSTEM) {
×
108
            // Bascule par défaut sur le contexte de cours.
109
            $contextlevel = CONTEXT_COURSE;
×
110
        }
111

112
        if ($contextlevel === CONTEXT_SYSTEM && has_capability('local/apsolu:viewallgrades', $syscontext) === false) {
×
113
            // Bascule par défaut sur le contexte de cours quand l'utilisateur n'a pas le droit de voir toutes les notes.
114
            $contextlevel = CONTEXT_COURSE;
×
115
        }
116

117
        // Récupère la liste des rôles pouvant être évaluer.
118
        $gradableroles = array_keys(self::get_gradable_roles());
×
119
        if (count($gradableroles) === 0) {
×
120
            return [];
×
121
        }
122

123
        $params = [];
×
124
        $params['context_course'] = CONTEXT_COURSE;
×
125

126
        if ($contextlevel === CONTEXT_SYSTEM) {
×
127
            // Requête pour les gestionnaires.
128
            $sql = "SELECT DISTINCT c.*" .
×
129
                " FROM {course} c" .
×
130
                " JOIN {course_categories} cc ON cc.id = c.category" .
×
131
                " JOIN {enrol} e ON c.id = e.courseid" .
×
132
                " JOIN {apsolu_courses} ac ON ac.id = c.id" .
×
133
                " JOIN {context} ctx ON c.id = ctx.instanceid AND ctx.contextlevel = :context_course" .
×
134
                " JOIN {role_assignments} ra ON ctx.id = ra.contextid AND ra.roleid IN (" . implode(',', $gradableroles) . ")" .
×
135
                " WHERE e.enrol = 'select'" .
×
136
                " AND e.status = 0" .
×
137
                " ORDER BY cc.name, ac.numweekday, ac.starttime";
×
138
        } else {
139
            // Récupère la liste des rôles pouvant évaluer.
140
            $graderroles = array_keys(get_roles_with_capability('local/apsolu:viewgrades', $permission = CAP_ALLOW, $syscontext));
×
141
            if (count($gradableroles) === 0) {
×
142
                return [];
×
143
            }
144

145
            // Requête pour les enseignants.
146
            $sql = "SELECT DISTINCT c.*" .
×
147
                " FROM {course} c" .
×
148
                " JOIN {course_categories} cc ON cc.id = c.category" .
×
149
                " JOIN {enrol} e ON c.id = e.courseid" .
×
150
                " JOIN {apsolu_courses} ac ON ac.id = c.id" .
×
151
                " JOIN {context} ctx ON c.id = ctx.instanceid AND ctx.contextlevel = :context_course" .
×
152
                " JOIN {role_assignments} ra ON ctx.id = ra.contextid AND ra.roleid IN (" . implode(',', $gradableroles) . ")" .
×
153
                " WHERE e.enrol = 'select'" .
×
154
                " AND e.status = 0" .
×
155
                " AND ctx.id IN (SELECT contextid
×
156
                                   FROM {role_assignments}
157
                                  WHERE userid = :userid
158
                                    AND roleid IN (" . implode(',', $graderroles) . "))" .
×
159
                " ORDER BY cc.name, ac.numweekday, ac.starttime";
×
160
            $params['userid'] = $USER->id;
×
161
        }
162

163
        return $DB->get_records_sql($sql, $params);
×
164
    }
165

166
    /**
167
     * Retourne un tableau contenant la liste des rôles qui peuvent être notés dans APSOLU.
168
     *
169
     * @return array
170
     */
171
    public static function get_gradable_roles() {
172
        $capability = 'local/apsolu:gradable';
×
173
        $permission = CAP_ALLOW;
×
174
        $context = context_system::instance();
×
175

176
        $roles = get_roles_with_capability($capability, $permission, $context);
×
177

178
        return role_fix_names($roles);
×
179
    }
180

181
    /**
182
     * Retourne un tableau contenant l'intégralité du carnet de notes.
183
     *
184
     * @param array $options Liste des options d'affichage du carnet de notes (seulement les évalués en option, seulement
185
     *                       certaines activités, etc).
186
     * @param array $fields  Liste des champs à retourner.
187
     *
188
     * @return array
189
     */
190
    public static function get_gradebook(array $options, array $fields = []) {
191
        global $DB, $OUTPUT;
×
192

193
        // Indexe les champs dans un tableau associatif.
194
        foreach ($fields as $key => $value) {
×
195
            $fields[$value] = true;
×
196
            unset($fields[$key]);
×
197
        }
198

199
        // Calcule les permissions de l'utilisateur.
200
        $capability = new stdClass();
×
201
        $capability->editgrades = has_capability('local/apsolu:editgrades', context_system::instance());
×
202
        $capability->editgradesafterdeadline = has_capability('local/apsolu:editgradesafterdeadline', context_system::instance());
×
203
        $capability->viewallgrades = has_capability('local/apsolu:viewallgrades', context_system::instance());
×
204

205
        // Contrôle que les options obligatoires sont présentes.
206
        if (isset($options['courses']) === false) {
×
207
            $options['courses'] = [];
×
208
        }
209

210
        if (isset($options['calendarstypes']) === false) {
×
211
            throw new Exception(get_string('fieldrequired', 'error', get_string('calendars_types', 'local_apsolu')));
×
212
        }
213

214
        if (isset($options['roles']) === false) {
×
215
            throw new Exception(get_string('fieldrequired', 'error', get_string('roles')));
×
216
        }
217

218
        // Récupère les calendriers APSOLU.
219
        $now = time();
×
220
        $options['calendars'] = [];
×
221
        $calendars = $DB->get_records('apsolu_calendars');
×
222
        foreach ($calendars as $calendarid => $calendar) {
×
223
            if (in_array($calendar->typeid, $options['calendarstypes'], $strict = true) === false) {
×
224
                // Ignore les calendriers n'ayant pas le bon type.
225
                continue;
×
226
            }
227

228
            // Vérifie que l'édition des notes n'est pas hors-délai.
229
            $canedit = ((empty($calendar->gradestartdate) || $now > $calendar->gradestartdate) &&
×
230
                (empty($calendar->gradeenddate) || $now < $calendar->gradeenddate));
×
231
            $calendar->locked = ($canedit === false);
×
232

233
            $options['calendars'][] = $calendar->id;
×
234
        }
235

236
        // On récupère la liste des notes attendues en fonction des options passées en paramètre.
237
        $gradeitems = [];
×
238
        foreach (gradeitem::get_records($conditions = null, $sort = 'name') as $item) {
×
239
            if (isset($options['gradeitems']) === true && in_array($item->id, $options['gradeitems'], $strict = true) === false) {
×
240
                // Ignore les éléments de notation non sélectionnés par le filtre.
241
                continue;
×
242
            }
243

244
            if (is_array($options['roles']) === false) {
×
245
                $options['roles'] = [$options['roles']];
×
246
            }
247

248
            if (in_array($item->roleid, $options['roles'], $strict = true) === false) {
×
249
                continue;
×
250
            }
251

252
            if (is_array($options['calendars']) === false) {
×
253
                $options['calendars'] = [$options['calendars']];
×
254
            }
255

256
            if (in_array($item->calendarid, $options['calendars'], $strict = true) === false) {
×
257
                continue;
×
258
            }
259

260
            if (isset($calendars[$item->calendarid]) === false) {
×
261
                continue;
×
262
            }
263

264
            $item->calendar_locked = $calendars[$item->calendarid]->locked;
×
265
            $gradeitems[$item->id] = $item;
×
266
        }
267

268
        // Récupération des enseignants.
269
        $teachers = [];
×
270
        if (isset($options['teachers']) === true || isset($fields['teachers']) === true) {
×
271
            $sql = "SELECT c.id AS courseid, u.*" .
×
272
                " FROM {user} u" .
×
273
                " JOIN {role_assignments} ra ON u.id = ra.userid" .
×
274
                " JOIN {context} ctx ON ctx.id = ra.contextid" .
×
275
                " JOIN {apsolu_courses} c ON ctx.instanceid = c.id" .
×
276
                " WHERE ra.roleid = 3" . // Enseignant.
×
277
                " ORDER BY u.lastname, u.firstname";
×
278
            $recordset = $DB->get_recordset_sql($sql);
×
279
            foreach ($recordset as $record) {
×
280
                $courseid = $record->courseid;
×
281
                unset($record->courseid);
×
282

283
                if (isset($teachers[$courseid]) === false) {
×
284
                    $teachers[$courseid] = [];
×
285
                }
286

287
                $teachers[$courseid][$record->id] = fullname($record);
×
288
            }
289
            $recordset->close();
×
290
        }
291

292
        // Récupération des notes.
293
        $grades = [];
×
294
        $sql = "SELECT gi.itemname, gi.grademax, gg.userid, gg.finalgrade, gg.feedback, gi.courseid, gi.iteminfo," .
×
295
            " u.firstname, u.lastname, u.lastnamephonetic, u.firstnamephonetic, u.middlename, u.alternatename" .
×
296
            " FROM {grade_items} gi" .
×
297
            " JOIN {grade_grades} gg ON gi.id = gg.itemid" .
×
298
            " LEFT JOIN {user} u ON u.id = gg.usermodified";
×
299
        $recordset = $DB->get_recordset_sql($sql);
×
300
        foreach ($recordset as $grade) {
×
301
            if ($grade->iteminfo !== self::NAME) {
×
302
                // On garde uniquement les éléments de notation dont l'iteminfo est APSOLU.
303
                // Note: il n'y a pas d'index sur le champ grade_items.iteminfo. On traite cette info côté PHP.
304
                continue;
×
305
            }
306

307
            // On stocke toutes les notes attendues.
308
            [$apsolugradeitemid, $name] = explode('-', $grade->itemname, 2);
×
309
            if (isset($gradeitems[$apsolugradeitemid]) === false) {
×
310
                continue;
×
311
            }
312

313
            // On récupère toutes les notes par étudiant et cours.
314
            if (isset($grades[$grade->userid]) === false) {
×
315
                $grades[$grade->userid] = [];
×
316
            }
317

318
            if (isset($grades[$grade->userid][$grade->courseid]) === false) {
×
319
                $grades[$grade->userid][$grade->courseid] = [];
×
320
            }
321

322
            if (empty($grade->feedback) === false) {
×
323
                $grade->finalgrade = $grade->feedback;
×
324
            }
325

326
            $gradeitems[$apsolugradeitemid]->grademax = (float) $grade->grademax;
×
327

328
            $value = new stdClass();
×
329
            $value->grade = $grade->finalgrade;
×
330
            $value->grader = fullname($grade);
×
331
            $grades[$grade->userid][$grade->courseid][$apsolugradeitemid] = $value;
×
332
        }
333
        $recordset->close();
×
334

335
        // Récupération des utilisateurs.
336
        $customfields = customfields::getCustomFields();
×
337
        $gradableroles = self::get_gradable_roles();
×
338

339
        $conditions = [];
×
340

341
        $params = [];
×
342
        $params[] = $customfields['apsoluufr']->id;
×
343
        $params[] = $customfields['apsolucycle']->id;
×
344
        $params[] = $customfields['apsolusex']->id;
×
345
        $params[] = CONTEXT_COURSE;
×
346

347
        // Filtres.
348
        $filters = [];
×
349

350
        if (defined('APSOLU_GRADES_COURSE_SCOPE') === false) {
×
351
            define('APSOLU_GRADES_COURSE_SCOPE', CONTEXT_COURSE);
×
352
        }
353

354
        // Extraction des catégories de cours.
355
        $options['categories'] = [];
×
356
        foreach ($options['courses'] as $key => $value) {
×
357
            [$categoryid, $courseid] = explode('-', $value);
×
358
            if ($courseid === '0') {
×
359
                // Traite une activité.
360
                $options['categories'][$categoryid] = $categoryid;
×
361
                unset($options['courses'][$key]);
×
362
            } else {
363
                // Traite un créneau.
364
                $options['courses'][$key] = $courseid;
×
365
            }
366
        }
367

368
        if (APSOLU_GRADES_COURSE_SCOPE === CONTEXT_COURSE) {
×
369
            // L'utilisateur est seulement enseignant. On force à ne récupérer que ses cours.
370
            $courses = self::get_courses();
×
371

372
            // Contrôle les droits d'accès sur les activités.
373
            if ($options['categories'] !== []) {
×
374
                // Pour chaque catégorie sélectionnée, ajoute tous les cours auxquels l'utilisateur peut accéder.
375
                foreach ($courses as $course) {
×
376
                    if (isset($options['categories'][$course->category]) === false) {
×
377
                        continue;
×
378
                    }
379

380
                    $options['courses'][] = $course->id;
×
381
                }
382
            }
383

384
            foreach ($options['courses'] as $key => $courseid) {
×
385
                if (isset($courses[$courseid]) === true) {
×
386
                    // L'utilisateur est autorisé à voir ce cours.
387
                    continue;
×
388
                }
389

390
                // L'utilisateur n'a pas le droit d'accéder à ce cours.
391
                unset($options['courses'][$key]);
×
392
            }
393

394
            if ($options['courses'] === []) {
×
395
                // L'utilisateur a sélectionné tous les cours.
396
                foreach ($courses as $course) {
×
397
                    $options['courses'][] = $course->id;
×
398
                }
399
            }
400
        } else if (APSOLU_GRADES_COURSE_SCOPE === CONTEXT_SYSTEM) {
×
401
            if ($options['categories'] !== []) {
×
402
                // Gère la sélection des catégories.
403
                foreach ($DB->get_records('course') as $course) {
×
404
                    if (isset($options['categories'][$course->category]) === false) {
×
405
                        continue;
×
406
                    }
407

408
                    $options['courses'][] = $course->id;
×
409
                }
410
            }
411

412
            if ($options['courses'] === []) {
×
413
                // Gère le cas où le gestionnaire demande tous les cours.
414
                unset($options['courses']);
×
415
            }
416
        }
417

418
        $filters['courses'] = " AND c.id IN (%s)";
×
419
        $filters['roles'] = " AND ra.roleid IN (%s)";
×
420
        $filters['calendars'] = " AND aca.id IN (%s)";
×
421
        $filters['cities'] = " AND act.id IN (%s)";
×
422

423
        foreach ($filters as $optionname => $sql) {
×
424
            if (isset($options[$optionname]) === false) {
×
425
                continue;
×
426
            }
427

428
            if (is_array($options[$optionname]) === false) {
×
429
                $options[$optionname] = [$options[$optionname]];
×
430
            }
431

432
            $count = 0;
×
433
            foreach ($options[$optionname] as $option) {
×
434
                if (ctype_digit($option) === false) {
×
435
                    continue;
×
436
                }
437

438
                $count++;
×
439
                $params[] = $option;
×
440
            }
441

442
            if ($count === 0) {
×
443
                continue;
×
444
            }
445

446
            $conditions[] = sprintf($sql, substr(str_repeat('?,', $count), 0, -1));
×
447
        }
448

449
        if (isset($options['fullnameuser']) === true && empty($options['fullnameuser']) === false) {
×
450
            $params[] = '%' . $options['fullnameuser'] . '%';
×
451
            $conditions[] = sprintf(" AND %s LIKE ? ", $DB->sql_fullname('u.firstname', 'u.lastname'));
×
452
        }
453

454
        $sql = "SELECT u.id, u.lastname, u.firstname, u.email, u.idnumber, uid3.data AS sex, act.name AS city,
×
455
                       u.institution, uid1.data AS ufr, u.department,
456
                       u.lastnamephonetic, u.firstnamephonetic, u.middlename, u.alternatename, u.picture, u.imagealt,
457
                       uid2.data AS cycle, aca.id AS calendarid, aca.name AS calendar, c.id AS courseid, c.fullname AS course,
458
                       cc.name AS category, cc2.name AS grouping, ra.roleid" .
×
459
            " FROM {user} u" .
×
460
            " LEFT JOIN {user_info_data} uid1 ON u.id = uid1.userid AND uid1.fieldid = ?" .
×
461
            " LEFT JOIN {user_info_data} uid2 ON u.id = uid2.userid AND uid2.fieldid = ?" .
×
462
            " LEFT JOIN {user_info_data} uid3 ON u.id = uid3.userid AND uid3.fieldid = ?" .
×
463
            " JOIN {user_enrolments} ue ON u.id = ue.userid AND ue.status = 0" .
×
464
            " JOIN {enrol} e ON e.id = ue.enrolid AND e.enrol = 'select' AND e.status = 0" .
×
465
            " JOIN {apsolu_calendars} aca ON aca.id = e.customchar1" .
×
466
            " JOIN {course} c ON c.id = e.courseid" .
×
467
            " JOIN {course_categories} cc ON cc.id = c.category" .
×
468
            " JOIN {course_categories} cc2 ON cc2.id = cc.parent" .
×
469
            " JOIN {apsolu_courses} ac ON ac.id = c.id" .
×
470
            " JOIN {apsolu_locations} al ON al.id = ac.locationid" .
×
471
            " JOIN {apsolu_areas} aa ON aa.id = al.areaid" .
×
472
            " JOIN {apsolu_cities} act ON act.id = aa.cityid" .
×
473
            " JOIN {context} ctx ON c.id = ctx.instanceid AND ctx.contextlevel = ?" .
×
474
            " JOIN {role_assignments} ra ON ctx.id = ra.contextid AND ra.itemid = e.id AND ra.userid = u.id" .
×
475
            " WHERE u.deleted = 0" . implode(' ', $conditions) .
×
476
            " ORDER BY u.lastname, u.firstname, u.institution, u.department";
×
477
        $recordset = $DB->get_recordset_sql($sql, $params);
×
478

479
        // Construction du carnet de notes.
480
        $gradebooks = new stdClass();
×
481
        $gradebooks->headers = [];
×
482
        if (isset($fields['pictures']) === true) {
×
483
            $gradebooks->headers[] = get_string('pictureofuser');
×
484
        }
485
        $gradebooks->headers[] = get_string('idnumber');
×
486
        $gradebooks->headers[] = get_string('lastname');
×
487
        $gradebooks->headers[] = get_string('firstname');
×
488
        if (isset($fields['sexes']) === true) {
×
489
            $gradebooks->headers[] = get_string('sex', 'local_apsolu');
×
490
        }
491
        if (isset($fields['emails']) === true) {
×
492
            $gradebooks->headers[] = get_string('email');
×
493
        }
494
        if (isset($fields['cities']) === true) {
×
495
            $gradebooks->headers[] = get_string('city', 'local_apsolu');
×
496
        }
497
        if (isset($fields['institutions']) === true) {
×
498
            $gradebooks->headers[] = get_string('institution');
×
499
        }
500
        if (isset($fields['ufrs']) === true) {
×
501
            $gradebooks->headers[] = get_string('ufr', 'local_apsolu');
×
502
        }
503
        if (isset($fields['departments']) === true) {
×
504
            $gradebooks->headers[] = get_string('department');
×
505
        }
506
        if (isset($fields['cycles']) === true) {
×
507
            $gradebooks->headers[] = get_string('cycle', 'local_apsolu');
×
508
        }
509
        if (isset($fields['calendars']) === true) {
×
510
            $gradebooks->headers[] = get_string('calendar', 'local_apsolu');
×
511
        }
512
        if (isset($fields['groupings']) === true) {
×
513
            $gradebooks->headers[] = get_string('grouping', 'local_apsolu');
×
514
        }
515
        if (isset($fields['categories']) === true) {
×
516
            $gradebooks->headers[] = get_string('activity', 'local_apsolu');
×
517
        }
518
        if (isset($fields['courses']) === true) {
×
519
            $gradebooks->headers[] = get_string('course');
×
520
        }
521
        if (isset($fields['roles']) === true) {
×
522
            $gradebooks->headers[] = get_string('role');
×
523
        }
524
        if (isset($fields['teachers']) === true) {
×
525
            $gradebooks->headers[] = get_string('teacher', 'local_apsolu');
×
526
        }
527
        foreach ($gradeitems as $item) {
×
528
            $gradebooks->headers[] = $item->name;
×
529
        }
530

531
        $gradebooks->users = [];
×
532
        foreach ($recordset as $user) {
×
533
            if (
534
                isset($options['institutions']) === true &&
×
535
                in_array($user->institution, $options['institutions'], $strict = true) === false
×
536
            ) {
537
                continue;
×
538
            }
539

540
            if (isset($options['ufrs']) === true && in_array($user->ufr, $options['ufrs'], $strict = true) === false) {
×
541
                continue;
×
542
            }
543

544
            if (
545
                isset($options['departments']) === true &&
×
546
                in_array($user->department, $options['departments'], $strict = true) === false
×
547
            ) {
548
                continue;
×
549
            }
550

551
            if (isset($options['cycles']) === true && in_array($user->cycle, $options['cycles'], $strict = true) === false) {
×
552
                continue;
×
553
            }
554

555
            if (isset($options['idnumber']) === true && $user->idnumber !== $options['idnumber']) {
×
556
                continue;
×
557
            }
558

559
            if (isset($options['teachers']) === true) {
×
560
                if (isset($teachers[$user->courseid]) === false) {
×
561
                    // Il n'y a pas d'enseignants dans ce cours.
562
                    continue;
×
563
                }
564

565
                $found = false;
×
566
                foreach ($teachers[$user->courseid] as $teacherid => $teacher) {
×
567
                    if (in_array((string) $teacherid, $options['teachers'], $strict = true) === true) {
×
568
                        $found = true;
×
569
                        break;
×
570
                    }
571
                }
572

573
                if ($found === false) {
×
574
                    // Aucun enseignant de ce cours ne correspond au filtre.
575
                    continue;
×
576
                }
577
            }
578

579
            if (isset($gradableroles[$user->roleid]) === false) {
×
580
                // Ce utilisateur n'est pas évaluable.
581
                continue;
×
582
            }
583

584
            $gradebook = new stdClass();
×
585
            $gradebook->profile = [];
×
586
            if (isset($fields['pictures']) === true) {
×
587
                $gradebook->picture = $OUTPUT->user_picture($user);
×
588
            }
589
            $gradebook->profile[] = $user->idnumber;
×
590
            $gradebook->profile[] = $user->lastname;
×
591
            $gradebook->profile[] = $user->firstname;
×
592

593
            if (isset($fields['sexes']) === true) {
×
594
                $gradebook->profile[] = $user->sex;
×
595
            }
596
            if (isset($fields['emails']) === true) {
×
597
                $gradebook->profile[] = $user->email;
×
598
            }
599
            if (isset($fields['cities']) === true) {
×
600
                $gradebook->profile[] = $user->city;
×
601
            }
602
            if (isset($fields['institutions']) === true) {
×
603
                $gradebook->profile[] = $user->institution;
×
604
            }
605
            if (isset($fields['ufrs']) === true) {
×
606
                $gradebook->profile[] = $user->ufr;
×
607
            }
608
            if (isset($fields['departments']) === true) {
×
609
                $gradebook->profile[] = $user->department;
×
610
            }
611
            if (isset($fields['cycles']) === true) {
×
612
                $gradebook->profile[] = $user->cycle;
×
613
            }
614
            if (isset($fields['calendars']) === true) {
×
615
                $gradebook->profile[] = $user->calendar;
×
616
            }
617
            if (isset($fields['groupings']) === true) {
×
618
                $gradebook->profile[] = $user->grouping;
×
619
            }
620
            if (isset($fields['categories']) === true) {
×
621
                $gradebook->profile[] = $user->category;
×
622
            }
623
            if (isset($fields['courses']) === true) {
×
624
                $gradebook->profile[] = $user->course;
×
625
            }
626
            if (isset($fields['roles']) === true) {
×
627
                $gradebook->profile[] = $gradableroles[$user->roleid]->localname;
×
628
            }
629
            if (isset($fields['teachers']) === true) {
×
630
                if (isset($teachers[$user->courseid]) === true) {
×
631
                    $gradebook->profile[] = implode(', ', $teachers[$user->courseid]);
×
632
                } else {
633
                    $gradebook->profile[] = '';
×
634
                }
635
            }
636
            $gradebook->grades = [];
×
637
            $needagrade = false;
×
638
            foreach ($gradeitems as $apsolugradeitemid => $item) {
×
639
                $grade = null;
×
640
                if ($user->roleid === $item->roleid && $user->calendarid === $item->calendarid) {
×
641
                    $needagrade = true;
×
642

643
                    $grade = new stdClass();
×
644
                    $grade->locked = (self::can_edit_grades($user->courseid, $item->calendar_locked) === false);
×
645
                    $grade->abi = false;
×
646
                    $grade->abj = false;
×
647
                    $grade->value = null;
×
648
                    $grade->max = $item->grademax;
×
649
                    $grade->inputname = $user->id . '-' . $user->courseid . '-' . $apsolugradeitemid;
×
650

651
                    if (isset($grades[$user->id][$user->courseid][$apsolugradeitemid]) === true) {
×
652
                        $value = $grades[$user->id][$user->courseid][$apsolugradeitemid]->grade;
×
653
                        if ($value === 'ABI') {
×
654
                            $grade->abi = true;
×
655
                            $grade->value = $value;
×
656
                        } else if ($value === 'ABJ') {
×
657
                            $grade->abj = true;
×
658
                            $grade->value = $value;
×
659
                        } else if (empty($value) === false) {
×
660
                            $grade->value = number_format($value, 2);
×
661
                        }
662

663
                        if (isset($fields['graders']) === true) {
×
664
                            $grade->grader = $grades[$user->id][$user->courseid][$apsolugradeitemid]->grader;
×
665
                        }
666
                    }
667
                }
668

669
                $gradebook->grades[] = $grade;
×
670
            }
671

672
            if ($needagrade === true) {
×
673
                // Ajoute seulement les utilisateurs ayant une note attendue.
674
                $gradebooks->users[] = $gradebook;
×
675
            }
676
        }
677
        $recordset->close();
×
678

679
        return $gradebooks;
×
680
    }
681

682
    /**
683
     * Retourne un tableau contenant l'intégralité du carnet de notes.
684
     *
685
     * @param array $options Liste des options d'affichage du carnet de notes (seulement les évalués en option, seulement certaines
686
     *                       activités, etc).
687
     * @param array $fields  Liste des champs à retourner.
688
     * @param string $format Format de fichier de l'exportation. Valeur gérée : csv ou xls.
689
     *
690
     * @return void
691
     */
692
    public static function export(array $options, array $fields = [], string $format = 'csv') {
693
        global $CFG;
×
694

695
        $filename = 'extraction_des_notes';
×
696
        $gradebook = self::get_gradebook($options, $fields);
×
697

698
        switch ($format) {
699
            case 'xls':
×
700
                require_once($CFG->libdir . '/excellib.class.php');
×
701

702
                // Définit les entêtes.
703
                $workbook = new MoodleExcelWorkbook("-");
×
704
                $workbook->send($filename);
×
705
                $myxls = $workbook->add_worksheet();
×
706
                $properties = ['border' => Border::BORDER_THIN];
×
707
                $excelformat = new MoodleExcelFormat($properties);
×
708
                foreach ($gradebook->headers as $position => $value) {
×
709
                    $myxls->write_string(0, $position, $value, $excelformat);
×
710
                }
711

712
                // Définit les données.
713
                $line = 1;
×
714
                foreach ($gradebook->users as $user) {
×
715
                    $column = 0;
×
716
                    foreach ($user->profile as $value) {
×
717
                        $myxls->write_string($line, $column++, $value, $excelformat);
×
718
                    }
719

720
                    foreach ($user->grades as $grade) {
×
721
                        $value = $grade->value;
×
722
                        if ($value === null) {
×
723
                            $value = get_string('not_applicable', 'local_apsolu');
×
724
                        }
725

726
                        if (is_numeric($value) === true) {
×
727
                            $myxls->write_number($line, $column++, $value, ['num_format' => '0.00']);
×
728
                        } else {
729
                            $myxls->write_string($line, $column++, $value, $excelformat);
×
730
                        }
731
                    }
732
                    $line++;
×
733
                }
734

735
                // MDL-83543: positionne un cookie pour qu'un script js déverrouille le bouton submit après le téléchargement.
736
                setcookie('moodledownload_' . sesskey(), time());
×
737

738
                // Transmet le fichier au navigateur.
739
                $workbook->close();
×
740
                exit();
×
741
            case 'csv':
×
742
                require_once($CFG->libdir . '/csvlib.class.php');
×
743

744
                $csvexport = new csv_export_writer();
×
745
                $csvexport->set_filename($filename);
×
746

747
                $csvexport->add_data($gradebook->headers);
×
748

749
                foreach ($gradebook->users as $user) {
×
750
                    $data = $user->profile;
×
751
                    foreach ($user->grades as $grade) {
×
752
                        if ($grade === null) {
×
753
                            $data[] = get_string('not_applicable', 'local_apsolu');
×
754
                        } else {
755
                            $data[] = $grade->value;
×
756
                        }
757
                    }
758
                    $csvexport->add_data($data);
×
759
                }
760

761
                $csvexport->download_file();
×
762
                exit();
×
763
            default:
764
                throw new coding_exception(get_string('unknowformat', 'error', $format));
×
765
        }
766
    }
767

768
    /**
769
     * Enregistre les notes.
770
     *
771
     * @param array $grades Listes des notes.
772
     *
773
     * @return void
774
     */
775
    public static function set_grades(array $grades) {
776
        global $DB;
×
777

778
        $transaction = $DB->start_delegated_transaction();
×
779

780
        $gradeitems = [];
×
781

782
        foreach ($grades as $gradename => $value) {
×
783
            [$userid, $courseid, $apsolugradeitemid] = explode('-', $gradename, 3);
×
784

785
            if (isset($gradeitems[$courseid]) === false) {
×
786
                $gradeitems[$courseid] = [];
×
787
                foreach (grade_item::fetch_all(['courseid' => $courseid, 'iteminfo' => self::NAME]) as $item) {
×
788
                    $id = explode('-', $item->itemname);
×
789
                    if (isset($id[0]) === false) {
×
790
                        continue;
×
791
                    }
792

793
                    if (ctype_digit($id[0]) === false) {
×
794
                        continue;
×
795
                    }
796

797
                    $gradeitems[$courseid][$id[0]] = $item;
×
798
                }
799
            }
800

801
            if (isset($gradeitems[$courseid][$apsolugradeitemid]) === false) {
×
802
                // Hmm, y'a un souci... visiblement, l'élément de notation n'existe pas pour ce cours.
803
                continue;
×
804
            }
805

806
            $item = $gradeitems[$courseid][$apsolugradeitemid];
×
807

808
            $grade = new stdClass();
×
809
            $grade->userid = $userid;
×
810
            if (in_array(strtoupper($value), ['ABI', 'ABJ'], $strict = true) === true) {
×
811
                $grade->finalgrade = null;
×
812
                $grade->feedback = strtoupper($value);
×
813
            } else {
814
                $grade->finalgrade = str_replace(',', '.', $value);
×
815
                $grade->feedback = null;
×
816

817
                if (is_numeric($grade->finalgrade) === false) {
×
818
                    continue;
×
819
                }
820
            }
821

822
            $currentgrade = grade_grade::fetch(['itemid' => $item->id, 'userid' => $userid]);
×
823
            if (
824
                $currentgrade !== false && grade_floats_different($currentgrade->finalgrade, $grade->finalgrade) === false &&
×
825
                $currentgrade->feedback === $grade->feedback
×
826
            ) {
827
                // La note n'a pas changé, on continue.
828
                continue;
×
829
            }
830

831
            // On met à jour la note.
832
            if ($item->update_final_grade($grade->userid, $grade->finalgrade, $src = 'local_apsolu', $grade->feedback) === false) {
×
833
                $transaction->rollback(new Exception(get_string('an_error_occurred_while_saving_your_grades', 'local_apsolu')));
×
834
                return false;
×
835
            }
836
        }
837

838
        $transaction->allow_commit();
×
839
        return true;
×
840
    }
841
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc