Proyectos de Subversion Moodle

Rev

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