Merge pull request #5802 from christianbeeznest/GH-3002

Internal: Add new user registration report with date range and creator stats - refs #3002
pull/5766/merge
christianbeeznest 2 months ago committed by GitHub
commit fd443252bf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 140
      public/main/admin/statistics/index.php
  2. 31
      public/main/inc/ajax/statistics.ajax.php
  3. 167
      public/main/inc/lib/statistics.lib.php

@ -23,7 +23,7 @@ $validated = false;
if (
in_array(
$report,
['recentlogins', 'tools', 'courses', 'coursebylanguage', 'users', 'users_active', 'session_by_date']
['recentlogins', 'tools', 'courses', 'coursebylanguage', 'users', 'users_active', 'session_by_date', 'new_user_registrations']
)
) {
$htmlHeadXtra[] = api_get_build_js('libs/chartjs/chart.js');
@ -349,6 +349,7 @@ $tools = [
'report=zombies' => get_lang('Zombies'),
'report=users_active' => get_lang('Users statistics'),
'report=users_online' => get_lang('Users online'),
'report=new_user_registrations' => get_lang('New users registrations'),
],
get_lang('System') => [
'report=activities' => get_lang('ImportantActivities'),
@ -1445,6 +1446,143 @@ switch ($report) {
</div>';
break;
case 'new_user_registrations':
$form = new FormValidator('new_user_registrations', 'get', api_get_self());
$form->addDateRangePicker('daterange', get_lang('Date range'), true, [
'format' => 'YYYY-MM-DD',
'timePicker' => 'false',
'validate_format' => 'Y-m-d'
]);
$form->addHidden('report', 'new_user_registrations');
$form->addButtonSearch(get_lang('Search'));
$validated = $form->validate() || isset($_REQUEST['daterange']);
$chartContent = '';
$chartCreatorContent = '';
$textChart = '';
if ($validated) {
$values = $form->getSubmitValues();
$dateStart = Security::remove_XSS($values['daterange_start']);
$dateEnd = Security::remove_XSS($values['daterange_end']);
$all = Statistics::initializeDateRangeArray($dateStart, $dateEnd);
$registrations = Statistics::getNewUserRegistrations($dateStart, $dateEnd);
if (empty($registrations)) {
$content .= '<div class="alert alert-info">' . get_lang('No data available for the selected date range') . '</div>';
} else {
if (Statistics::isMoreThanAMonth($dateStart, $dateEnd)) {
$textChart = get_lang('User registrations by month');
$all = Statistics::groupByMonth($registrations);
$chartData = Statistics::buildJsChartData($all, get_lang('User Registrations by Month'));
// Allow clicks only when showing by month
$onClickHandler = '
var activePoints = chart.getElementsAtEventForMode(evt, "nearest", { intersect: true }, false);
if (activePoints.length > 0) {
var firstPoint = activePoints[0];
var label = chart.data.labels[firstPoint.index];
var yearMonth = label.split("-");
var year = yearMonth[0];
var month = yearMonth[1];
$.ajax({
url: "/main/inc/ajax/statistics.ajax.php?a=get_user_registration_by_day",
type: "POST",
data: { year: year, month: month },
success: function(response) {
var dailyData = JSON.parse(response);
chart.data.labels = dailyData.labels;
chart.data.datasets[0].data = dailyData.data;
chart.data.datasets[0].label = "User Registrations for " + year + "-" + month;
chart.update();
$("#backButton").show();
}
});
}';
} else {
$textChart = get_lang('User registrations by days');
foreach ($registrations as $registration) {
$date = $registration['date'];
if (isset($all[$date])) {
$all[$date] += $registration['count'];
}
}
$chartData = Statistics::buildJsChartData($all, $textChart);
$onClickHandler = '';
}
$htmlHeadXtra[] = Statistics::getJSChartTemplateWithData(
$chartData['chart'],
'bar',
'title: { text: "'.$textChart.'", display: true },
scales: {
x: { beginAtZero: true },
y: { barPercentage: 0.4, categoryPercentage: 0.5, barThickness: 10, maxBarThickness: 15 }
},
layout: {
padding: { left: 10, right: 10, top: 10, bottom: 10 }
}',
'user_registration_chart',
true,
$onClickHandler,
'
$("#backButton").click(function() {
$.ajax({
url: "/main/inc/ajax/statistics.ajax.php?a=get_user_registration_by_month",
type: "POST",
data: { date_start: "'.$dateStart.'", date_end: "'.$dateEnd.'" },
success: function(response) {
var monthlyData = JSON.parse(response);
chart.data.labels = monthlyData.labels;
chart.data.datasets[0].data = monthlyData.data;
chart.data.datasets[0].label = "'.get_lang('User Registrations by month').'";
chart.update();
$("#backButton").hide();
}
});
});
'
);
$chartContent .= '<canvas id="user_registration_chart"></canvas>';
$chartContent .= '<button id="backButton" style="display:none;" class="btn btn--info">'.get_lang('Back to Months').'</button>';
$creators = Statistics::getUserRegistrationsByCreator($dateStart, $dateEnd);
if (!empty($creators)) {
$chartCreatorContent = '<hr />';
$creatorLabels = [];
$creatorData = [];
foreach ($creators as $creator) {
$creatorLabels[] = $creator['name'];
$creatorData[] = $creator['count'];
}
$htmlHeadXtra[] = Statistics::getJSChartTemplateWithData(
['labels' => $creatorLabels, 'datasets' => [['label' => get_lang('Registrations by Creator'), 'data' => $creatorData]]],
'pie',
'title: { text: "'.get_lang('User Registrations by Creator').'", display: true },
legend: { position: "top" },
layout: {
padding: { left: 10, right: 10, top: 10, bottom: 10 }
}',
'user_registration_by_creator_chart',
false,
'',
'',
['width' => 700, 'height' => 700]
);
$chartCreatorContent .= '<canvas id="user_registration_by_creator_chart"></canvas>';
}
}
}
$content .= $form->returnForm();
$content .= $chartContent;
$content .= $chartCreatorContent;
break;
case 'users':
$content .= '<div class="grid grid-cols-3 gap-4">';
$content .= '<div><canvas id="canvas1" class="mb-5"></canvas></div>';

@ -20,6 +20,37 @@ $order = isset($_REQUEST['sord']) && in_array($_REQUEST['sord'], ['asc', 'desc']
$table = '';
switch ($action) {
case 'get_user_registration_by_month':
$dateStart = Security::remove_XSS($_POST['date_start']);
$dateEnd = Security::remove_XSS($_POST['date_end']);
$registrations = Statistics::getNewUserRegistrations($dateStart, $dateEnd);
$all = Statistics::groupByMonth($registrations);
$labels = [];
$data = [];
foreach ($all as $month => $count) {
$labels[] = $month;
$data[] = $count;
}
echo json_encode(['labels' => $labels, 'data' => $data]);
exit;
case 'get_user_registration_by_day':
$year = intval($_POST['year']);
$month = intval($_POST['month']);
$startDate = "$year-$month-01";
$endDate = date("Y-m-t", strtotime($startDate));
$dailyData = Statistics::getNewUserRegistrations($startDate, $endDate);
$labels = [];
$data = [];
foreach ($dailyData as $registration) {
$labels[] = $registration['date'];
$data[] = $registration['count'];
}
echo json_encode(['labels' => $labels, 'data' => $data]);
exit;
case 'add_student_to_boss':
$studentId = isset($_GET['student_id']) ? (int) $_GET['student_id'] : 0;
$bossId = isset($_GET['boss_id']) ? (int) $_GET['boss_id'] : 0;

@ -1248,31 +1248,75 @@ class Statistics
return $chartCode;
}
public static function getJSChartTemplateWithData($data, $type = 'pie', $options = '', $elementId = 'canvas', $responsive = true)
{
public static function getJSChartTemplateWithData(
$data,
$type = 'pie',
$options = '',
$elementId = 'canvas',
$responsive = true,
$onClickHandler = '',
$extraButtonHandler = '',
$canvasDimensions = ['width' => 420, 'height' => 420]
): string {
$data = json_encode($data);
$responsiveValue = $responsive ? 'true' : 'false';
$indexAxisOption = '';
if ($type === 'bar') {
$indexAxisOption = 'indexAxis: "y",';
}
$onClickScript = '';
if (!empty($onClickHandler)) {
$onClickScript = '
onClick: function(evt) {
'.$onClickHandler.'
},
';
}
$canvasSize = '';
if ($responsiveValue === 'false') {
$canvasSize = '
ctx.canvas.width = '.$canvasDimensions['width'].';
ctx.canvas.height = '.$canvasDimensions['height'].';
';
}
$chartCode = '
<script>
$(function() {
Chart.defaults.responsive = '.$responsiveValue.';
var ctx = document.getElementById("'.$elementId.'").getContext("2d");
ctx.canvas.width = 420;
ctx.canvas.height = 420;
'.$canvasSize.'
var chart = new Chart(ctx, {
type: "'.$type.'",
data: '.$data.',
options: {
plugins: {
'.$options.'
'.$options.',
datalabels: {
anchor: "end",
align: "left",
formatter: function(value) {
return value;
},
color: "#000"
},
},
cutout: "25%"
'.$indexAxisOption.'
scales: {
x: { beginAtZero: true },
y: { barPercentage: 0.5 }
},
'.$onClickScript.'
}
});
var title = chart.options.plugins.title.text;
$("#'.$elementId.'_title").html(title);
$("#'.$elementId.'_table").html(chart.data.datasets[0].data);
'.$extraButtonHandler.'
});
</script>';
@ -1499,4 +1543,115 @@ class Statistics
return Database::store_result($stmt, 'ASSOC');
}
/**
* Gets the number of new users registered between two dates.
*/
public static function getNewUserRegistrations(string $startDate, string $endDate): array
{
$sql = "SELECT DATE_FORMAT(registration_date, '%Y-%m-%d') as reg_date, COUNT(*) as user_count
FROM user
WHERE registration_date BETWEEN '$startDate' AND '$endDate'
GROUP BY reg_date";
$result = Database::query($sql);
$data = [];
while ($row = Database::fetch_array($result)) {
$userCount = is_numeric($row['user_count']) ? (int) $row['user_count'] : 0;
$data[] = ['date' => $row['reg_date'], 'count' => $userCount];
}
return $data;
}
/**
* Gets the number of users registered by creator (creator_id) between two dates.
*/
public static function getUserRegistrationsByCreator(string $startDate, string $endDate): array
{
$sql = "SELECT u.creator_id, COUNT(u.id) as user_count, c.firstname, c.lastname
FROM user u
LEFT JOIN user c ON u.creator_id = c.id
WHERE u.registration_date BETWEEN '$startDate' AND '$endDate'
AND u.creator_id IS NOT NULL
GROUP BY u.creator_id";
$result = Database::query($sql);
$data = [];
while ($row = Database::fetch_array($result)) {
$userCount = is_numeric($row['user_count']) ? (int) $row['user_count'] : 0;
$name = trim($row['firstname'] . ' ' . $row['lastname']);
if (!empty($name)) {
$data[] = [
'name' => $name,
'count' => $userCount
];
}
}
return $data;
}
/**
* Initializes an array with dates between two given dates, setting each date's value to 0.
*/
public static function initializeDateRangeArray(string $startDate, string $endDate): array
{
$dateRangeArray = [];
$currentDate = new DateTime($startDate);
$endDate = new DateTime($endDate);
// Loop through the date range and initialize each date with 0
while ($currentDate <= $endDate) {
$formattedDate = $currentDate->format('Y-m-d');
$dateRangeArray[$formattedDate] = 0;
$currentDate->modify('+1 day');
}
return $dateRangeArray;
}
/**
* Checks if the difference between two dates is more than one month.
*/
public static function isMoreThanAMonth(string $dateStart, string $dateEnd): bool
{
$startDate = new DateTime($dateStart);
$endDate = new DateTime($dateEnd);
$diff = $startDate->diff($endDate);
if ($diff->y >= 1) {
return true;
}
if ($diff->m > 1) {
return true;
}
if ($diff->m == 1) {
return $diff->d > 0;
}
return false;
}
/**
* Groups registration data by month.
*/
public static function groupByMonth(array $registrations): array
{
$groupedData = [];
foreach ($registrations as $registration) {
$monthYear = (new DateTime($registration['date']))->format('Y-m');
if (isset($groupedData[$monthYear])) {
$groupedData[$monthYear] += $registration['count'];
} else {
$groupedData[$monthYear] = $registration['count'];
}
}
return $groupedData;
}
}

Loading…
Cancel
Save