AutorÃa | Ultima modificación | Ver Log |
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* plugin overviewstats
*
* @package report_overviewstats
* @author DualCube <admin@dualcube.com>
* @copyright 2023 DualCube <admin@dualcube.com>
* @copyright based on work by 2013 David Mudrak <david@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Base class for all charts to be reported
*
* @package report_overviewstats
* @author DualCube <admin@dualcube.com>
* @copyright 2023 DualCube <admin@dualcube.com>
* @copyright based on work by 2013 David Mudrak <david@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class report_overviewstats_chart {
/**
* create login for login chart
*
* @return array
*/
public static function report_overviewstats_chart_logins() {
$maindata = self::prepare_data_login_parday_chart();
$title = get_string('chart-logins', 'report_overviewstats');
$titleperday = get_string('chart-logins-perday', 'report_overviewstats');
return [
$title => [
$titleperday => html_writer::tag('div',
self::get_chart(new \core\chart_line(),
get_string('user-numbers', 'report_overviewstats'),
$maindata['loggedins'],
$maindata['dates'],
false
),
[
'id' => 'chart_logins_perday',
'class' => 'chartplaceholder',
'style' => 'min-height: 300px;',
'dir' => 'ltr',
]
),
],
];
}
/**
* prepare data for login perday chart
*
* @return array
*/
protected static function prepare_data_login_parday_chart() {
global $DB, $CFG;
$now = strtotime('today midnight');
$lastmonth = [];
for ($i = 30; $i >= 0; $i--) {
$lastmonth[$now - $i * DAYSECS] = [];
}
$logmanger = get_log_manager();
$readers = $logmanger->get_readers('\core\log\sql_reader');
$reader = reset($readers);
$params = ['component' => 'core',
'eventname' => '\core\event\user_loggedin',
'guestid' => $CFG->siteguest,
'timestart' => $now - 30 * DAYSECS, ];
$select = "component = :component AND eventname = :eventname AND userid <> :guestid AND timecreated >= :timestart";
$recordset = $reader->get_events_select($select, $params, 'timecreated DESC', 0, 0);
foreach ($recordset as $record) {
foreach (array_reverse($lastmonth, true) as $timestamp => $loggedin) {
$date = usergetdate($timestamp);
if ($record->timecreated >= $timestamp) {
$lastmonth[$timestamp][$record->userid] = true;
break;
}
}
}
$maindata = [
'dates' => [],
'loggedins' => [],
];
$format = get_string('strftimedateshort', 'core_langconfig');
foreach ($lastmonth as $timestamp => $loggedin) {
$date = userdate($timestamp, $format);
$maindata['dates'][] = $date;
$maindata['loggedins'][] = count($loggedin);
}
return $maindata;
}
/**
* create chart for countries
*
* @return array
*/
public static function report_overviewstats_chart_countries() {
$maindata = self::prepare_data_chart_countries();
$title = get_string('chart-countries', 'report_overviewstats');
$info = html_writer::div(
get_string('chart-countries-info',
'report_overviewstats', count($maindata['counts'])),
'chartinfo');
$chart = html_writer::tag('div',
self::get_chart(
new \core\chart_bar(),
get_string('user-numbers', 'report_overviewstats'),
$maindata['counts'],
$maindata['countrys'],
true
),
[
'id' => 'chart_countries',
'class' => 'chartplaceholder',
'style' => 'min-height: ' . max(66, (count($maindata['counts']) * 20)) . 'px;',
'dir' => 'ltr',
]
);
return [$title => $info . $chart];
}
/**
* prepaire data for country chart
*
* @return array
*/
protected static function prepare_data_chart_countries() {
global $DB;
$sql = "SELECT country, COUNT(*)
FROM {user}
WHERE country IS NOT NULL AND country <> '' AND deleted = 0 AND confirmed = 1
GROUP BY country
ORDER BY COUNT(*) DESC, country ASC";
$maindata = [
'countrys' => [],
'counts' => [],
];
foreach ($DB->get_records_sql_menu($sql) as $country => $count) {
if (get_string_manager()->string_exists($country, 'core_countries')) {
$countryname = get_string($country, 'core_countries');
} else {
$countryname = $country;
}
$maindata['countrys'][] = $countryname;
$maindata['counts'][] = $count;
}
return $maindata;
}
/**
* create the language chart
*
* @return array
*/
public static function report_overviewstats_chart_langs() {
$maindata = self::prepare_data_chart_langs();
$title = get_string('chart-langs', 'report_overviewstats');
$info = html_writer::div(get_string('chart-langs-info', 'report_overviewstats', count($maindata['counts'])), 'chartinfo');
$chart = html_writer::tag('div',
self::get_chart(
new \core\chart_bar(),
get_string('user-numbers', 'report_overviewstats'),
$maindata['counts'],
$maindata['languages'],
true
),
[
'id' => 'chart_langs',
'class' => 'chartplaceholder',
'style' => 'min-height: ' . max(66, (count($maindata['counts']) * 20)) . 'px;',
'dir' => 'ltr',
]
);
return [$title => $info . $chart];
}
/**
* prepare data for language chart
*
* @return array
*/
protected static function prepare_data_chart_langs() {
global $DB;
$sql = "SELECT lang, COUNT(*)
FROM {user}
WHERE deleted = 0 AND confirmed = 1
GROUP BY lang
ORDER BY COUNT(*) DESC";
$maindata = [
'languages' => [],
'counts' => [],
];
foreach ($DB->get_records_sql_menu($sql) as $lang => $count) {
if (get_string_manager()->translation_exists($lang)) {
$langname = get_string_manager()->get_string('thislanguageint', 'core_langconfig', null, $lang);
} else {
$langname = $lang;
}
$maindata['languages'][] = $langname;
$maindata['counts'][] = $count;
}
return $maindata;
}
/**
* create the chart for courses
*
* @return array
*/
public static function report_overviewstats_chart_courses() {
global $OUTPUT;
$maindata = self::prepare_data_chart_courses();
$title = get_string('chart-courses', 'report_overviewstats');
$titlepercategory = get_string('chart-courses-percategory', 'report_overviewstats');
$percategorydata = new html_table();
$percategorydata->head = [
get_string('chart-courses-percategory-categoryname', 'report_overviewstats'),
get_string('chart-courses-percategory-coursesrecursive', 'report_overviewstats'),
get_string('chart-courses-percategory-coursesown', 'report_overviewstats'),
];
foreach ($maindata['percategory'] as $catdata) {
$percategorydata->data[] = new html_table_row([
$catdata['categoryname'],
$catdata['coursesrecursive'],
$catdata['coursesown'],
]);
}
$titlesizes = sprintf('%s %s', get_string('chart-courses-sizes', 'report_overviewstats'),
$OUTPUT->help_icon('chart-courses-sizes', 'report_overviewstats'));
return [
$title => [
$titlepercategory => html_writer::tag('div',
html_writer::table($percategorydata),
[
'id' => 'chart_courses_percategory',
'class' => 'simple_data_table',
],
),
$titlesizes => html_writer::tag('div',
self::get_chart(
new \core\chart_bar(),
get_string('course-numbers', 'report_overviewstats'),
$maindata['sizes']['courses'],
$maindata['sizes']['course_size'],
false
),
[
'id' => 'chart_courses_sizes',
'class' => 'chartplaceholder',
'style' => 'min-height: 300px;',
'dir' => 'ltr',
],
),
],
];
}
/**
* prepaire data for course chart
*
* @return array
*/
protected static function prepare_data_chart_courses() {
global $DB;
$maindata = [];
// Number of courses per category.
$categorieslist = core_course_category::make_categories_list();
$maindata['percategory'] = [];
$total = 0;
foreach ($categorieslist as $catid => $catname) {
$cat = core_course_category::get($catid);
$coursesown = $cat->get_courses_count();
$total += $coursesown;
$maindata['percategory'][] = [
'categoryname' => $catname,
'coursesrecursive' => $cat->get_courses_count(['recursive' => true]),
'coursesown' => $coursesown,
];
}
$maindata['percategory'][] = [
'categoryname' => html_writer::tag('strong', get_string('total')),
'coursesrecursive' => '',
'coursesown' => html_writer::tag('strong', $total),
];
// Distribution graph of number of activities per course.
$sql = "SELECT course, COUNT(id) AS modules
FROM {course_modules}
GROUP BY course";
$recordset = $DB->get_recordset_sql($sql);
$max = 0;
$data = [];
$maindata['sizes'] = [
'course_size' => [],
'courses' => [],
];
foreach ($recordset as $record) {
$distributiongroup = floor($record->modules / 5); // 0 for 0-4, 1 for 5-9, 2 for 10-14 etc.
if (!isset($data[$distributiongroup])) {
$data[$distributiongroup] = 1;
} else {
$data[$distributiongroup]++;
}
if ($distributiongroup > $max) {
$max = $distributiongroup;
}
}
$recordset->close();
for ($i = 0; $i <= $max; $i++) {
if (!isset($data[$i])) {
$data[$i] = 0;
}
}
ksort($data);
foreach ($data as $distributiongroup => $courses) {
$distributiongroupname = sprintf("%d-%d", $distributiongroup * 5, $distributiongroup * 5 + 4);
$maindata['sizes']['course_size'][] = $distributiongroupname;
$maindata['sizes']['courses'][] = $courses;
}
return $maindata;
}
/**
* create enrolment chart
*
* @return array
*/
public static function report_overviewstats_chart_enrolments($course) {
$maindata = self::prepare_data_chart_enrollments($course);
$title = get_string('chart-enrolments', 'report_overviewstats');
$titlemonth = get_string('chart-enrolments-month', 'report_overviewstats');
$titleyear = get_string('chart-enrolments-year', 'report_overviewstats');
return [
$title => [
$titlemonth => html_writer::tag('div',
self::get_chart(
new \core\chart_line(),
get_string('enrolled', 'report_overviewstats'),
$maindata['lastmonth']['enrolled'],
$maindata['lastmonth']['date'],
false
),
[
'id' => 'chart_enrolments_lastmonth',
'class' => 'chartplaceholder',
'style' => 'min-height: 300px;',
]
),
$titleyear => html_writer::tag('div',
self::get_chart(
new \core\chart_line(),
get_string('enrolled', 'report_overviewstats'),
$maindata['lastyear']['enrolled'],
$maindata['lastyear']['date'],
false
),
[
'id' => 'chart_enrolments_lastyear',
'class' => 'chartplaceholder',
'style' => 'min-height: 300px;',
]
),
],
];
}
/**
* prepare chart enrolments data
*
* @return array
*/
protected static function prepare_data_chart_enrollments($course) {
global $DB, $CFG;
if (is_null($course)) {
throw new coding_exception(get_string('null-course-exception', 'report_overviewstats'));
}
// Get the number of currently enrolled users.
$context = context_course::instance($course->id);
list($esql, $params) = get_enrolled_sql($context);
$sql = "SELECT COUNT(u.id)
FROM {user} u
JOIN ($esql) je ON je.id = u.id
WHERE u.deleted = 0";
$current = $DB->count_records_sql($sql, $params);
// Construct the estimated number of enrolled users in the last month
// and the last year using the current number and the log records.
$now = time();
$lastmonth = [];
for ($i = 30; $i >= 0; $i--) {
$lastmonth[$now - $i * DAYSECS] = $current;
}
$lastyear = [];
for ($i = 12; $i >= 0; $i--) {
$lastyear[$now - $i * 30 * DAYSECS] = $current;
}
// Fetch all the enrol/unrol log entries from the last year.
$logmanger = get_log_manager();
$readers = $logmanger->get_readers('\core\log\sql_reader');
$reader = reset($readers);
$select = "component = :component AND (eventname = :eventname1 OR eventname = :eventname2) ".
"AND timecreated >= :timestart AND courseid = :courseid";
$params = [
'component' => 'core',
'eventname1' => '\core\event\user_enrolment_created',
'eventname2' => '\core\event\user_enrolment_deleted',
'timestart' => $now - 30 * DAYSECS,
'courseid' => $course->id,
];
$events = $reader->get_events_select($select, $params, 'timecreated DESC', 0, 0);
foreach ($events as $event) {
foreach (array_reverse($lastmonth, true) as $key => $value) {
if ($event->timecreated >= $key) {
// We need to amend all days up to the key.
foreach ($lastmonth as $mkey => $mvalue) {
if ($mkey <= $key) {
if ($event->eventname === '\core\event\user_enrolment_created' && $lastmonth[$mkey] > 0) {
$lastmonth[$mkey]--;
} else if ($event->eventname === '\core\event\user_enrolment_deleted') {
$lastmonth[$mkey]++;
}
}
}
break;
}
}
foreach (array_reverse($lastyear, true) as $key => $value) {
if ($event->timecreated >= $key) {
// We need to amend all months up to the key.
foreach ($lastyear as $ykey => $yvalue) {
if ($ykey <= $key) {
if ($event->eventname === '\core\event\user_enrolment_created' && $lastyear[$ykey] > 0) {
$lastyear[$ykey]--;
} else if ($event->eventname === '\core\event\user_enrolment_deleted') {
$lastyear[$ykey]++;
}
}
}
break;
}
}
}
$maindata = [
'lastmonth' => [
'date' => [],
'enrolled' => [],
],
'lastyear' => [
'date' => [],
'enrolled' => [],
],
];
$format = get_string('strftimedateshort', 'core_langconfig');
foreach ($lastmonth as $timestamp => $enrolled) {
$date = userdate($timestamp, $format);
$maindata['lastmonth']['date'][] = $date;
$maindata['lastmonth']['enrolled'][] = $enrolled;
}
foreach ($lastyear as $timestamp => $enrolled) {
$date = userdate($timestamp, $format);
$maindata['lastyear']['date'][] = $date;
$maindata['lastyear']['enrolled'][] = $enrolled;
}
return $maindata;
}
/**
* create chart function based on inputes
*
* @param \core\chart_line $chart
* @param string $seriesname
* @param array $seriesdata
* @param array $labelsdata
* @param bool $ishorizontal
* @return chart
*/
protected static function get_chart($chart, $seriesname, $seriesdata, $labelsdata, $ishorizontal) {
global $OUTPUT;
$series = new \core\chart_series($seriesname, $seriesdata);
$labels = $labelsdata;
if ($ishorizontal) {
$chart->set_horizontal(true);
}
$chart->add_series($series);
$chart->set_labels($labels);
return $OUTPUT->render($chart);
}
}