Proyectos de Subversion Moodle

Rev

Rev 11 | | Comparar con el anterior | 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
declare(strict_types=1);
18
 
19
namespace core_course\reportbuilder\local\entities;
20
 
21
use core_reportbuilder\local\entities\base;
22
use core_course\reportbuilder\local\formatters\completion as completion_formatter;
23
use core_reportbuilder\local\filters\boolean_select;
24
use core_reportbuilder\local\filters\date;
25
use core_reportbuilder\local\helpers\database;
26
use core_reportbuilder\local\helpers\format;
27
use core_reportbuilder\local\report\column;
28
use core_reportbuilder\local\report\filter;
29
use completion_criteria_completion;
30
use completion_info;
31
use html_writer;
32
use lang_string;
33
use stdClass;
34
 
35
/**
36
 * Course completion entity implementation
37
 *
38
 * @package     core_course
39
 * @copyright   2022 David Matamoros <davidmc@moodle.com>
40
 * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
41
 */
42
class completion extends base {
43
 
44
    /**
45
     * Database tables that this entity uses
46
     *
47
     * @return string[]
48
     */
49
    protected function get_default_tables(): array {
50
        return [
51
            'course',
1441 ariadna 52
            'course_completions',
1 efrain 53
            'grade_grades' ,
54
            'grade_items',
55
            'user',
56
        ];
57
    }
58
 
59
    /**
1441 ariadna 60
     * Database tables that this entity no longer uses
61
     *
62
     * @return string[]
63
     */
64
    protected function get_deprecated_tables(): array {
65
        return [
66
            'course_completions' => 'course_completion',
67
        ];
68
    }
69
 
70
    /**
1 efrain 71
     * The default title for this entity in the list of columns/conditions/filters in the report builder
72
     *
73
     * @return lang_string
74
     */
75
    protected function get_default_entity_title(): lang_string {
76
        return new lang_string('coursecompletion', 'completion');
77
    }
78
 
79
    /**
80
     * Initialise the entity
81
     *
82
     * @return base
83
     */
84
    public function initialise(): base {
85
        foreach ($this->get_all_columns() as $column) {
86
            $this->add_column($column);
87
        }
88
 
89
        // All the filters defined by the entity can also be used as conditions.
90
        foreach ($this->get_all_filters() as $filter) {
91
            $this
92
                ->add_filter($filter)
93
                ->add_condition($filter);
94
        }
95
 
96
        return $this;
97
    }
98
 
99
    /**
100
     * Returns list of all available columns
101
     *
102
     * @return column[]
103
     */
104
    protected function get_all_columns(): array {
105
        [
106
            'course' => $course,
1441 ariadna 107
            'course_completions' => $coursecompletion,
1 efrain 108
            'grade_grades' => $grade,
109
            'grade_items' => $gradeitem,
110
            'user' => $user,
111
        ] = $this->get_table_aliases();
112
 
113
        // Completed column.
114
        $columns[] = (new column(
115
            'completed',
116
            new lang_string('completed', 'completion'),
117
            $this->get_entity_name()
118
        ))
119
            ->add_joins($this->get_joins())
120
            ->set_type(column::TYPE_BOOLEAN)
11 efrain 121
            ->add_field("
122
                CASE
123
                    WHEN {$coursecompletion}.id IS NULL THEN NULL
124
                    WHEN {$coursecompletion}.timecompleted > 0 THEN 1
125
                    ELSE 0
126
                END", 'completed')
1 efrain 127
            ->set_is_sortable(true)
11 efrain 128
            ->add_callback([format::class, 'boolean_as_text']);
1 efrain 129
 
130
        // Completion criteria column.
131
        $criterias = database::generate_alias();
132
        $columns[] = (new column(
133
            'criteria',
134
            new lang_string('criteria', 'core_completion'),
135
            $this->get_entity_name()
136
        ))
137
            ->add_joins($this->get_joins())
138
            // Determine whether any criteria exist for the course. We also group per course, rather than report each separately.
139
            ->add_join("LEFT JOIN (
140
                            SELECT DISTINCT course FROM {course_completion_criteria}
141
                       ) {$criterias} ON {$criterias}.course = {$course}.id")
142
            ->set_type(column::TYPE_TEXT)
143
            // Select enough fields to determine user criteria for the course.
144
            ->add_field("{$criterias}.course", 'courseid')
145
            ->add_field("{$course}.enablecompletion")
146
            ->add_field("{$user}.id", 'userid')
147
            ->set_disabled_aggregation_all()
148
            ->add_callback(static function($id, stdClass $record): string {
149
                if (!$record->courseid) {
150
                    return '';
151
                }
152
 
153
                $info = new completion_info((object) ['id' => $record->courseid, 'enablecompletion' => $record->enablecompletion]);
154
                if ($info->get_aggregation_method() == COMPLETION_AGGREGATION_ALL) {
155
                    $title = get_string('criteriarequiredall', 'core_completion');
156
                } else {
157
                    $title = get_string('criteriarequiredany', 'core_completion');
158
                }
159
 
160
                // Map all completion data to their criteria summaries.
161
                $items = array_map(static function(completion_criteria_completion $completion): string {
162
                    $criteria = $completion->get_criteria();
163
 
164
                    return get_string('criteriasummary', 'core_completion', [
165
                        'type' => $criteria->get_details($completion)['type'],
166
                        'summary' => $criteria->get_title_detailed(),
167
                    ]);
168
                }, $info->get_completions((int) $record->userid));
169
 
170
                return $title . html_writer::alist($items);
171
            });
172
 
173
        // Progress percentage column.
174
        $columns[] = (new column(
175
            'progresspercent',
176
            new lang_string('progress', 'completion'),
177
            $this->get_entity_name()
178
        ))
179
            ->add_joins($this->get_joins())
180
            ->set_type(column::TYPE_TEXT)
181
            ->add_field("{$course}.id", 'courseid')
182
            ->add_field("{$user}.id", 'userid')
183
            ->set_is_sortable(false)
184
            ->add_callback([completion_formatter::class, 'completion_progress']);
185
 
186
        // Time enrolled.
187
        $columns[] = (new column(
188
            'timeenrolled',
189
            new lang_string('timeenrolled', 'enrol'),
190
            $this->get_entity_name()
191
        ))
192
            ->add_joins($this->get_joins())
193
            ->set_type(column::TYPE_TIMESTAMP)
194
            ->add_field("{$coursecompletion}.timeenrolled")
195
            ->set_is_sortable(true)
196
            ->add_callback([format::class, 'userdate']);
197
 
198
        // Time started.
199
        $columns[] = (new column(
200
            'timestarted',
201
            new lang_string('timestarted', 'enrol'),
202
            $this->get_entity_name()
203
        ))
204
            ->add_joins($this->get_joins())
205
            ->set_type(column::TYPE_TIMESTAMP)
206
            ->add_field("{$coursecompletion}.timestarted")
207
            ->set_is_sortable(true)
208
            ->add_callback([format::class, 'userdate']);
209
 
210
        // Time completed.
211
        $columns[] = (new column(
212
            'timecompleted',
213
            new lang_string('timecompleted', 'completion'),
214
            $this->get_entity_name()
215
        ))
216
            ->add_joins($this->get_joins())
217
            ->set_type(column::TYPE_TIMESTAMP)
218
            ->add_field("{$coursecompletion}.timecompleted")
219
            ->set_is_sortable(true)
220
            ->add_callback([format::class, 'userdate']);
221
 
222
        // Time reaggregated.
223
        $columns[] = (new column(
224
            'reaggregate',
225
            new lang_string('timereaggregated', 'enrol'),
226
            $this->get_entity_name()
227
        ))
228
            ->add_joins($this->get_joins())
229
            ->set_type(column::TYPE_TIMESTAMP)
230
            ->add_field("{$coursecompletion}.reaggregate")
231
            ->set_is_sortable(true)
232
            ->add_callback([format::class, 'userdate']);
233
 
234
        // Days taking course (days since course start date until completion or until current date if not completed).
235
        $currenttime = time();
236
        $columns[] = (new column(
237
            'dayscourse',
1441 ariadna 238
            new lang_string('daystakingcourse', 'completion'),
1 efrain 239
            $this->get_entity_name()
240
        ))
241
            ->add_joins($this->get_joins())
242
            ->set_type(column::TYPE_INTEGER)
243
            ->add_field("(
244
                CASE
11 efrain 245
                    WHEN {$coursecompletion}.id IS NULL THEN NULL
246
                    ELSE (CASE WHEN {$coursecompletion}.timecompleted > 0 THEN
1 efrain 247
                        {$coursecompletion}.timecompleted
11 efrain 248
                        ELSE
1 efrain 249
                        {$currenttime}
1441 ariadna 250
                    END - {$course}.startdate)
11 efrain 251
                END)", 'dayscourse')
1441 ariadna 252
            ->set_is_sortable(true)
253
            ->set_callback([format::class, 'format_time']);
1 efrain 254
 
255
        // Days since last completion (days since last enrolment date until completion or until current date if not completed).
256
        $columns[] = (new column(
257
            'daysuntilcompletion',
258
            new lang_string('daysuntilcompletion', 'completion'),
259
            $this->get_entity_name()
260
        ))
261
            ->add_joins($this->get_joins())
262
            ->set_type(column::TYPE_INTEGER)
263
            ->add_field("(
264
                CASE
11 efrain 265
                    WHEN {$coursecompletion}.id IS NULL THEN NULL
266
                    ELSE (CASE WHEN {$coursecompletion}.timecompleted > 0 THEN
1 efrain 267
                        {$coursecompletion}.timecompleted
11 efrain 268
                        ELSE
1 efrain 269
                        {$currenttime}
1441 ariadna 270
                    END - {$coursecompletion}.timeenrolled)
11 efrain 271
                END)", 'daysuntilcompletion')
1441 ariadna 272
            ->set_is_sortable(true)
273
            ->set_callback([format::class, 'format_time']);
1 efrain 274
 
275
        // Student course grade.
276
        $columns[] = (new column(
277
            'grade',
278
            new lang_string('gradenoun'),
279
            $this->get_entity_name()
280
        ))
281
            ->add_joins($this->get_joins())
282
            ->add_join("
283
                LEFT JOIN {grade_items} {$gradeitem}
284
                       ON ({$gradeitem}.itemtype = 'course' AND {$course}.id = {$gradeitem}.courseid)
285
            ")
286
            ->add_join("
287
                LEFT JOIN {grade_grades} {$grade}
288
                       ON ({$user}.id = {$grade}.userid AND {$gradeitem}.id = {$grade}.itemid)
289
            ")
290
            ->set_type(column::TYPE_FLOAT)
291
            ->add_fields("{$grade}.finalgrade")
292
            ->set_is_sortable(true)
293
            ->add_callback(function(?float $value): string {
294
                if ($value === null) {
295
                    return '';
296
                }
297
                return format_float($value, 2);
298
            });
299
 
300
        return $columns;
301
    }
302
 
303
    /**
304
     * Return list of all available filters
305
     *
306
     * @return filter[]
307
     */
308
    protected function get_all_filters(): array {
1441 ariadna 309
        $coursecompletion = $this->get_table_alias('course_completions');
1 efrain 310
 
311
        // Completed status filter.
312
        $filters[] = (new filter(
313
            boolean_select::class,
314
            'completed',
315
            new lang_string('completed', 'completion'),
316
            $this->get_entity_name(),
317
            "CASE WHEN {$coursecompletion}.timecompleted > 0 THEN 1 ELSE 0 END"
318
        ))
319
            ->add_joins($this->get_joins());
320
 
321
        // Time completed filter.
322
        $filters[] = (new filter(
323
            date::class,
324
            'timecompleted',
325
            new lang_string('timecompleted', 'completion'),
326
            $this->get_entity_name(),
327
            "{$coursecompletion}.timecompleted"
328
        ))
329
            ->add_joins($this->get_joins())
330
            ->set_limited_operators([
331
                date::DATE_ANY,
332
                date::DATE_NOT_EMPTY,
333
                date::DATE_EMPTY,
334
                date::DATE_RANGE,
335
                date::DATE_LAST,
336
                date::DATE_CURRENT,
337
            ]);
338
 
339
        // Time enrolled/started filter and condition.
340
        $fields = ['timeenrolled', 'timestarted'];
341
        foreach ($fields as $field) {
342
            $filters[] = (new filter(
343
                date::class,
344
                $field,
345
                new lang_string($field, 'enrol'),
346
                $this->get_entity_name(),
347
                "{$coursecompletion}.{$field}"
348
            ))
349
                ->add_joins($this->get_joins())
350
                ->set_limited_operators([
351
                    date::DATE_ANY,
352
                    date::DATE_NOT_EMPTY,
353
                    date::DATE_EMPTY,
354
                    date::DATE_RANGE,
355
                    date::DATE_LAST,
356
                    date::DATE_CURRENT,
357
                ]);
358
        }
359
 
360
        // Time reaggregated filter and condition.
361
        $filters[] = (new filter(
362
            date::class,
363
            'reaggregate',
364
            new lang_string('timereaggregated', 'enrol'),
365
            $this->get_entity_name(),
366
            "{$coursecompletion}.reaggregate"
367
        ))
368
            ->add_joins($this->get_joins())
369
            ->set_limited_operators([
370
                date::DATE_ANY,
371
                date::DATE_NOT_EMPTY,
372
                date::DATE_EMPTY,
373
                date::DATE_RANGE,
374
                date::DATE_LAST,
375
                date::DATE_CURRENT,
376
            ]);
377
 
378
        return $filters;
379
    }
380
}