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
  * plugin overviewstats
19
  *
20
  * @package report_overviewstats
21
  * @author DualCube <admin@dualcube.com>
22
  * @copyright 2023 DualCube <admin@dualcube.com>
23
  * @copyright based on work by 2013 David Mudrak <david@moodle.com>
24
  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
25
  */
26
 
27
  /**
28
   * Base class for all charts to be reported
29
   *
30
   * @package report_overviewstats
31
   * @author DualCube <admin@dualcube.com>
32
   * @copyright 2023 DualCube <admin@dualcube.com>
33
   * @copyright based on work by 2013 David Mudrak <david@moodle.com>
34
   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
35
   */
36
class report_overviewstats_chart {
37
    /**
38
     * create login for login chart
39
     *
40
     * @return array
41
     */
42
    public static function report_overviewstats_chart_logins() {
43
        $maindata = self::prepare_data_login_parday_chart();
44
        $title = get_string('chart-logins', 'report_overviewstats');
45
        $titleperday = get_string('chart-logins-perday', 'report_overviewstats');
46
 
47
        return [
48
            $title => [
49
                $titleperday => html_writer::tag('div',
50
                    self::get_chart(new \core\chart_line(),
51
                        get_string('user-numbers', 'report_overviewstats'),
52
                        $maindata['loggedins'],
53
                        $maindata['dates'],
54
                        false
55
                    ),
56
                    [
57
                        'id' => 'chart_logins_perday',
58
                        'class' => 'chartplaceholder',
59
                        'style' => 'min-height: 300px;',
60
                        'dir' => 'ltr',
61
                    ]
62
                ),
63
            ],
64
        ];
65
    }
66
 
67
    /**
68
     * prepare data for login perday chart
69
     *
70
     * @return array
71
     */
72
    protected static function prepare_data_login_parday_chart() {
73
        global $DB, $CFG;
74
 
75
        $now = strtotime('today midnight');
76
        $lastmonth = [];
77
        for ($i = 30; $i >= 0; $i--) {
78
            $lastmonth[$now - $i * DAYSECS] = [];
79
        }
80
        $logmanger = get_log_manager();
81
        $readers = $logmanger->get_readers('\core\log\sql_reader');
82
        $reader = reset($readers);
83
        $params = ['component' => 'core',
84
            'eventname' => '\core\event\user_loggedin',
85
            'guestid' => $CFG->siteguest,
86
            'timestart' => $now - 30 * DAYSECS, ];
87
        $select = "component = :component AND eventname = :eventname AND userid <> :guestid AND timecreated >= :timestart";
88
        $recordset = $reader->get_events_select($select, $params, 'timecreated DESC', 0, 0);
89
 
90
        foreach ($recordset as $record) {
91
            foreach (array_reverse($lastmonth, true) as $timestamp => $loggedin) {
92
                $date = usergetdate($timestamp);
93
                if ($record->timecreated >= $timestamp) {
94
                    $lastmonth[$timestamp][$record->userid] = true;
95
                    break;
96
                }
97
            }
98
        }
99
        $maindata = [
100
            'dates' => [],
101
            'loggedins' => [],
102
        ];
103
        $format = get_string('strftimedateshort', 'core_langconfig');
104
        foreach ($lastmonth as $timestamp => $loggedin) {
105
            $date = userdate($timestamp, $format);
106
            $maindata['dates'][] = $date;
107
            $maindata['loggedins'][] = count($loggedin);
108
        }
109
 
110
        return $maindata;
111
    }
112
 
113
    /**
114
     * create chart for countries
115
     *
116
     * @return array
117
     */
118
    public static function report_overviewstats_chart_countries() {
119
        $maindata = self::prepare_data_chart_countries();
120
        $title = get_string('chart-countries', 'report_overviewstats');
121
        $info = html_writer::div(
122
            get_string('chart-countries-info',
123
            'report_overviewstats', count($maindata['counts'])),
124
            'chartinfo');
125
        $chart = html_writer::tag('div',
126
            self::get_chart(
127
                new \core\chart_bar(),
128
                get_string('user-numbers', 'report_overviewstats'),
129
                $maindata['counts'],
130
                $maindata['countrys'],
131
                true
132
            ),
133
            [
134
                'id' => 'chart_countries',
135
                'class' => 'chartplaceholder',
136
                'style' => 'min-height: ' . max(66, (count($maindata['counts']) * 20)) . 'px;',
137
                'dir' => 'ltr',
138
            ]
139
        );
140
 
141
        return [$title => $info . $chart];
142
    }
143
 
144
    /**
145
     * prepaire data for country chart
146
     *
147
     * @return array
148
     */
149
    protected static function prepare_data_chart_countries() {
150
        global $DB;
151
 
152
        $sql = "SELECT country, COUNT(*)
153
                  FROM {user}
154
                 WHERE country IS NOT NULL AND country <> '' AND deleted = 0 AND confirmed = 1
155
              GROUP BY country
156
              ORDER BY COUNT(*) DESC, country ASC";
157
 
158
        $maindata = [
159
            'countrys' => [],
160
            'counts' => [],
161
        ];
162
        foreach ($DB->get_records_sql_menu($sql) as $country => $count) {
163
            if (get_string_manager()->string_exists($country, 'core_countries')) {
164
                $countryname = get_string($country, 'core_countries');
165
            } else {
166
                $countryname = $country;
167
            }
168
            $maindata['countrys'][] = $countryname;
169
            $maindata['counts'][] = $count;
170
        }
171
        return $maindata;
172
    }
173
 
174
    /**
175
     * create the language chart
176
     *
177
     * @return array
178
     */
179
    public static function report_overviewstats_chart_langs() {
180
        $maindata = self::prepare_data_chart_langs();
181
 
182
        $title = get_string('chart-langs', 'report_overviewstats');
183
        $info = html_writer::div(get_string('chart-langs-info', 'report_overviewstats', count($maindata['counts'])), 'chartinfo');
184
        $chart = html_writer::tag('div',
185
            self::get_chart(
186
                new \core\chart_bar(),
187
                get_string('user-numbers', 'report_overviewstats'),
188
                $maindata['counts'],
189
                $maindata['languages'],
190
                true
191
            ),
192
            [
193
                'id' => 'chart_langs',
194
                'class' => 'chartplaceholder',
195
                'style' => 'min-height: ' . max(66, (count($maindata['counts']) * 20)) . 'px;',
196
                'dir' => 'ltr',
197
            ]
198
        );
199
 
200
        return [$title => $info . $chart];
201
    }
202
 
203
    /**
204
     * prepare data for language chart
205
     *
206
     * @return array
207
     */
208
    protected static function prepare_data_chart_langs() {
209
        global $DB;
210
        $sql = "SELECT lang, COUNT(*)
211
                  FROM {user}
212
                 WHERE deleted = 0 AND confirmed = 1
213
              GROUP BY lang
214
              ORDER BY COUNT(*) DESC";
215
 
216
        $maindata = [
217
            'languages' => [],
218
            'counts' => [],
219
        ];
220
        foreach ($DB->get_records_sql_menu($sql) as $lang => $count) {
221
            if (get_string_manager()->translation_exists($lang)) {
222
                $langname = get_string_manager()->get_string('thislanguageint', 'core_langconfig', null, $lang);
223
            } else {
224
                $langname = $lang;
225
            }
226
            $maindata['languages'][] = $langname;
227
            $maindata['counts'][] = $count;
228
        }
229
 
230
        return $maindata;
231
    }
232
 
233
    /**
234
     * create the chart for courses
235
     *
236
     * @return array
237
     */
238
    public static function report_overviewstats_chart_courses() {
239
        global $OUTPUT;
240
 
241
        $maindata = self::prepare_data_chart_courses();
242
 
243
        $title = get_string('chart-courses', 'report_overviewstats');
244
        $titlepercategory = get_string('chart-courses-percategory', 'report_overviewstats');
245
 
246
        $percategorydata = new html_table();
247
        $percategorydata->head = [
248
            get_string('chart-courses-percategory-categoryname', 'report_overviewstats'),
249
            get_string('chart-courses-percategory-coursesrecursive', 'report_overviewstats'),
250
            get_string('chart-courses-percategory-coursesown', 'report_overviewstats'),
251
        ];
252
        foreach ($maindata['percategory'] as $catdata) {
253
            $percategorydata->data[] = new html_table_row([
254
                $catdata['categoryname'],
255
                $catdata['coursesrecursive'],
256
                $catdata['coursesown'],
257
            ]);
258
        }
259
 
260
        $titlesizes = sprintf('%s %s', get_string('chart-courses-sizes', 'report_overviewstats'),
261
            $OUTPUT->help_icon('chart-courses-sizes', 'report_overviewstats'));
262
 
263
        return [
264
            $title => [
265
                $titlepercategory => html_writer::tag('div',
266
                    html_writer::table($percategorydata),
267
                    [
268
                        'id' => 'chart_courses_percategory',
269
                        'class' => 'simple_data_table',
270
                    ],
271
                ),
272
                $titlesizes => html_writer::tag('div',
273
                    self::get_chart(
274
                        new \core\chart_bar(),
275
                        get_string('course-numbers', 'report_overviewstats'),
276
                        $maindata['sizes']['courses'],
277
                        $maindata['sizes']['course_size'],
278
                        false
279
                    ),
280
                    [
281
                        'id' => 'chart_courses_sizes',
282
                        'class' => 'chartplaceholder',
283
                        'style' => 'min-height: 300px;',
284
                        'dir' => 'ltr',
285
                    ],
286
                ),
287
            ],
288
        ];
289
    }
290
 
291
    /**
292
     * prepaire data for course chart
293
     *
294
     * @return array
295
     */
296
    protected static function prepare_data_chart_courses() {
297
        global $DB;
298
        $maindata = [];
299
        // Number of courses per category.
300
        $categorieslist = core_course_category::make_categories_list();
301
        $maindata['percategory'] = [];
302
        $total = 0;
303
 
304
        foreach ($categorieslist as $catid => $catname) {
305
            $cat = core_course_category::get($catid);
306
            $coursesown = $cat->get_courses_count();
307
            $total += $coursesown;
308
            $maindata['percategory'][] = [
309
                'categoryname' => $catname,
310
                'coursesrecursive' => $cat->get_courses_count(['recursive' => true]),
311
                'coursesown' => $coursesown,
312
            ];
313
        }
314
 
315
        $maindata['percategory'][] = [
316
            'categoryname' => html_writer::tag('strong', get_string('total')),
317
            'coursesrecursive' => '',
318
            'coursesown' => html_writer::tag('strong', $total),
319
        ];
320
 
321
        // Distribution graph of number of activities per course.
322
        $sql = "SELECT course, COUNT(id) AS modules
323
                  FROM {course_modules}
324
              GROUP BY course";
325
 
326
        $recordset = $DB->get_recordset_sql($sql);
327
 
328
        $max = 0;
329
        $data = [];
330
        $maindata['sizes'] = [
331
            'course_size' => [],
332
            'courses' => [],
333
        ];
334
 
335
        foreach ($recordset as $record) {
336
            $distributiongroup = floor($record->modules / 5); // 0 for 0-4, 1 for 5-9, 2 for 10-14 etc.
337
            if (!isset($data[$distributiongroup])) {
338
                $data[$distributiongroup] = 1;
339
            } else {
340
                $data[$distributiongroup]++;
341
            }
342
            if ($distributiongroup > $max) {
343
                $max = $distributiongroup;
344
            }
345
        }
346
 
347
        $recordset->close();
348
 
349
        for ($i = 0; $i <= $max; $i++) {
350
            if (!isset($data[$i])) {
351
                $data[$i] = 0;
352
            }
353
        }
354
        ksort($data);
355
 
356
        foreach ($data as $distributiongroup => $courses) {
357
            $distributiongroupname = sprintf("%d-%d", $distributiongroup * 5, $distributiongroup * 5 + 4);
358
            $maindata['sizes']['course_size'][] = $distributiongroupname;
359
            $maindata['sizes']['courses'][] = $courses;
360
        }
361
 
362
        return $maindata;
363
    }
364
 
365
    /**
366
     * create enrolment chart
367
     *
368
     * @return array
369
     */
370
    public static function report_overviewstats_chart_enrolments($course) {
371
        $maindata = self::prepare_data_chart_enrollments($course);
372
 
373
        $title = get_string('chart-enrolments', 'report_overviewstats');
374
        $titlemonth = get_string('chart-enrolments-month', 'report_overviewstats');
375
        $titleyear = get_string('chart-enrolments-year', 'report_overviewstats');
376
 
377
        return [
378
            $title => [
379
                $titlemonth => html_writer::tag('div',
380
                    self::get_chart(
381
                        new \core\chart_line(),
382
                        get_string('enrolled', 'report_overviewstats'),
383
                        $maindata['lastmonth']['enrolled'],
384
                        $maindata['lastmonth']['date'],
385
                        false
386
                    ),
387
                    [
388
                        'id' => 'chart_enrolments_lastmonth',
389
                        'class' => 'chartplaceholder',
390
                        'style' => 'min-height: 300px;',
391
                    ]
392
                ),
393
                $titleyear => html_writer::tag('div',
394
                    self::get_chart(
395
                        new \core\chart_line(),
396
                        get_string('enrolled', 'report_overviewstats'),
397
                        $maindata['lastyear']['enrolled'],
398
                        $maindata['lastyear']['date'],
399
                        false
400
                    ),
401
                    [
402
                        'id' => 'chart_enrolments_lastyear',
403
                        'class' => 'chartplaceholder',
404
                        'style' => 'min-height: 300px;',
405
                    ]
406
                ),
407
            ],
408
        ];
409
    }
410
 
411
    /**
412
     * prepare chart enrolments data
413
     *
414
     * @return array
415
     */
416
    protected static function prepare_data_chart_enrollments($course) {
417
        global $DB, $CFG;
418
 
419
        if (is_null($course)) {
420
            throw new coding_exception(get_string('null-course-exception', 'report_overviewstats'));
421
        }
422
 
423
        // Get the number of currently enrolled users.
424
 
425
        $context = context_course::instance($course->id);
426
        list($esql, $params) = get_enrolled_sql($context);
427
        $sql = "SELECT COUNT(u.id)
428
                  FROM {user} u
429
                  JOIN ($esql) je ON je.id = u.id
430
                 WHERE u.deleted = 0";
431
 
432
        $current = $DB->count_records_sql($sql, $params);
433
 
434
        // Construct the estimated number of enrolled users in the last month
435
        // and the last year using the current number and the log records.
436
 
437
        $now = time();
438
 
439
        $lastmonth = [];
440
        for ($i = 30; $i >= 0; $i--) {
441
            $lastmonth[$now - $i * DAYSECS] = $current;
442
        }
443
 
444
        $lastyear = [];
445
        for ($i = 12; $i >= 0; $i--) {
446
            $lastyear[$now - $i * 30 * DAYSECS] = $current;
447
        }
448
 
449
        // Fetch all the enrol/unrol log entries from the last year.
450
        $logmanger = get_log_manager();
451
        $readers = $logmanger->get_readers('\core\log\sql_reader');
452
        $reader = reset($readers);
453
        $select = "component = :component AND (eventname = :eventname1 OR eventname = :eventname2) ".
454
        "AND timecreated >= :timestart AND courseid = :courseid";
455
        $params = [
456
            'component' => 'core',
457
            'eventname1' => '\core\event\user_enrolment_created',
458
            'eventname2' => '\core\event\user_enrolment_deleted',
459
            'timestart' => $now - 30 * DAYSECS,
460
            'courseid' => $course->id,
461
        ];
462
        $events = $reader->get_events_select($select, $params, 'timecreated DESC', 0, 0);
463
 
464
        foreach ($events as $event) {
465
            foreach (array_reverse($lastmonth, true) as $key => $value) {
466
                if ($event->timecreated >= $key) {
467
                    // We need to amend all days up to the key.
468
                    foreach ($lastmonth as $mkey => $mvalue) {
469
                        if ($mkey <= $key) {
470
                            if ($event->eventname === '\core\event\user_enrolment_created' && $lastmonth[$mkey] > 0) {
471
                                $lastmonth[$mkey]--;
472
                            } else if ($event->eventname === '\core\event\user_enrolment_deleted') {
473
                                $lastmonth[$mkey]++;
474
                            }
475
                        }
476
                    }
477
                    break;
478
                }
479
            }
480
            foreach (array_reverse($lastyear, true) as $key => $value) {
481
                if ($event->timecreated >= $key) {
482
                    // We need to amend all months up to the key.
483
                    foreach ($lastyear as $ykey => $yvalue) {
484
                        if ($ykey <= $key) {
485
                            if ($event->eventname === '\core\event\user_enrolment_created' && $lastyear[$ykey] > 0) {
486
                                $lastyear[$ykey]--;
487
                            } else if ($event->eventname === '\core\event\user_enrolment_deleted') {
488
                                $lastyear[$ykey]++;
489
                            }
490
                        }
491
                    }
492
                    break;
493
                }
494
            }
495
        }
496
 
497
        $maindata = [
498
            'lastmonth' => [
499
                'date' => [],
500
                'enrolled' => [],
501
            ],
502
            'lastyear' => [
503
                'date' => [],
504
                'enrolled' => [],
505
            ],
506
        ];
507
 
508
        $format = get_string('strftimedateshort', 'core_langconfig');
509
        foreach ($lastmonth as $timestamp => $enrolled) {
510
            $date = userdate($timestamp, $format);
511
            $maindata['lastmonth']['date'][] = $date;
512
            $maindata['lastmonth']['enrolled'][] = $enrolled;
513
        }
514
        foreach ($lastyear as $timestamp => $enrolled) {
515
            $date = userdate($timestamp, $format);
516
            $maindata['lastyear']['date'][] = $date;
517
            $maindata['lastyear']['enrolled'][] = $enrolled;
518
        }
519
        return $maindata;
520
    }
521
 
522
    /**
523
     * create chart function based on inputes
524
     *
525
     * @param \core\chart_line $chart
526
     * @param string $seriesname
527
     * @param array $seriesdata
528
     * @param array $labelsdata
529
     * @param bool $ishorizontal
530
     * @return chart
531
     */
532
    protected static function get_chart($chart, $seriesname, $seriesdata, $labelsdata, $ishorizontal) {
533
        global $OUTPUT;
534
        $series = new \core\chart_series($seriesname, $seriesdata);
535
        $labels = $labelsdata;
536
        if ($ishorizontal) {
537
            $chart->set_horizontal(true);
538
        }
539
        $chart->add_series($series);
540
        $chart->set_labels($labels);
541
        return $OUTPUT->render($chart);
542
    }
543
}