Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 0 additions & 43 deletions public/main/admin/statistics/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -785,49 +785,6 @@
}
// courses for each course category
$content .= Statistics::printStats(get_lang('Courses'), $courses);

$content .= '
<button class="btn btn--info mb-3" onclick="toggleNonRegisteredUsers()">
'.get_lang('Show/Hide users active in open courses (not enrolled)').'
</button>

<div id="non-registered-users-block" style="display: none; margin-top: 10px;">
';

$sessionId = api_get_session_id();
$userList = Statistics::getUsersWithActivityButNotRegistered($sessionId);

if (!empty($userList)) {
$content .= Display::page_subheader2(get_lang('Users active in open courses (not enrolled)'));
$content .= Display::tag('p', get_lang('The following users have accessed one or more courses without being officially registered. They generated activity in open courses but are not listed in the course subscription tables.'));
$table = new HTML_Table(['class' => 'table table-hover table-striped data_table']);
$table->setHeaderContents(0, 0, get_lang('Name'));
$table->setHeaderContents(0, 1, get_lang('Course'));
$table->setHeaderContents(0, 2, get_lang('Latest access'));
$row = 1;
foreach ($userList as $user) {
$name = Display::tag('strong', $user['firstname'].' '.$user['lastname']);
$course = Display::tag('em', $user['courseTitle'].' ('.$user['courseCode'].')');
$access = Security::remove_XSS($user['lastAccess']);

$table->setCellContents($row, 0, $name);
$table->setCellContents($row, 1, $course);
$table->setCellContents($row, 2, $access);
$row++;
}
$content .= $table->toHtml();
} else {
$content .= Display::tag('p', get_lang('No user found with activity in open courses without enrollment.'));
}
$content .= '</div>';
$content .= '
<script>
function toggleNonRegisteredUsers() {
const block = document.getElementById("non-registered-users-block");
block.style.display = block.style.display === "none" ? "block" : "none";
}
</script>';

break;
case 'tools':
$content .= '<canvas class="col-md-12" id="canvas" height="300px" style="margin-bottom: 20px"></canvas>';
Expand Down
75 changes: 21 additions & 54 deletions public/main/inc/lib/statistics.lib.php
Original file line number Diff line number Diff line change
Expand Up @@ -1950,71 +1950,38 @@ public static function getUnsubscriptionsByDay(string $startDate, string $endDat
}

/**
* Returns users who have activity in open courses without being officially enrolled.
* Users with activity in this course but not officially enrolled (optionally in a session).
*/
public static function getUsersWithActivityButNotRegistered(int $sessionId = 0): array
public static function getNonRegisteredActiveUsersInCourse(int $courseId, int $sessionId = 0): array
{
$em = Database::getManager();

$qb = $em->createQueryBuilder();
$qb->select('t.accessUserId AS userId, t.cId AS courseId, MAX(t.accessDate) AS lastAccess')
$qb->select('u.id AS id, u.firstname AS firstname, u.lastname AS lastname, u.email AS email, MAX(t.accessDate) AS lastAccess')
->from(TrackEAccess::class, 't')
->where('t.accessUserId IS NOT NULL')
->andWhere('t.cId IS NOT NULL')
->groupBy('t.accessUserId, t.cId');
->join(User::class, 'u', 'WITH', 'u.id = t.accessUserId')
->leftJoin(
CourseRelUser::class,
'cu',
'WITH',
'cu.user = u AND cu.course = :courseId'.($sessionId > 0 ? ' AND cu.session = :sessionId' : '')
)
->where('t.cId = :courseId')
->andWhere('t.accessUserId IS NOT NULL')
->andWhere('cu.id IS NULL')
->groupBy('u.id, u.firstname, u.lastname, u.email')
->orderBy('lastAccess', 'DESC')
->setParameter('courseId', $courseId);

if ($sessionId > 0) {
$qb->andWhere('t.sessionId = :sessionId')
->setParameter('sessionId', $sessionId);
$qb->andWhere('t.sessionId = :sessionId')->setParameter('sessionId', $sessionId);
}

$results = $qb->getQuery()->getArrayResult();

$nonRegistered = [];

foreach ($results as $row) {
$userId = $row['userId'];
$courseId = $row['courseId'];

$course = $em->getRepository(Course::class)->find($courseId);
if (!$course) {
continue;
}

if (!\in_array($course->getVisibility(), [Course::OPEN_PLATFORM, Course::OPEN_WORLD], true)) {
continue;
}

$isRegistered = $em->createQueryBuilder()
->select('1')
->from(CourseRelUser::class, 'cu')
->where('cu.user = :userId AND cu.course = :courseId')
->setParameter('userId', $userId)
->setParameter('courseId', $courseId);

if ($sessionId > 0) {
$isRegistered->andWhere('cu.session = :sessionId')
->setParameter('sessionId', $sessionId);
}

if (empty($isRegistered->getQuery()->getResult())) {
$user = $em->getRepository(User::class)->find($userId);
if (!$user) {
continue;
}

$nonRegistered[] = [
'id' => $user->getId(),
'firstname' => $user->getFirstname(),
'lastname' => $user->getLastname(),
'email' => $user->getEmail(),
'courseTitle' => $course->getTitle(),
'courseCode' => $course->getCode(),
'lastAccess' => $row['lastAccess'] ? (new \DateTime($row['lastAccess']))->format('Y-m-d H:i:s') : '',
];
}
$rows = $qb->getQuery()->getArrayResult();
foreach ($rows as &$r) {
$r['lastAccess'] = !empty($r['lastAccess']) ? (new \DateTime($r['lastAccess']))->format('Y-m-d H:i:s') : '';
}

return $nonRegistered;
return $rows;
}
}
93 changes: 93 additions & 0 deletions public/main/tracking/courseLog.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,45 @@
}
}

if (!empty($_GET['ajax']) && (int) $_GET['ajax'] === 1 && (($_GET['fragment'] ?? '') === 'non_registered')) {
$nrUsers = Statistics::getNonRegisteredActiveUsersInCourse($courseId, (int) $sessionId);

$out = '';
$out .= Display::page_subheader2(get_lang('Users active in this course (not enrolled)'));
$out .= Display::tag('p', get_lang('These users accessed the course without an official subscription.'));

if (!empty($nrUsers)) {
$table = new HTML_Table(['class' => 'table table-hover table-striped data_table']);
$col = 0;
$table->setHeaderContents(0, $col++, get_lang('Name'));
if ('true' === api_get_setting('show_email_addresses')) {
$table->setHeaderContents(0, $col++, get_lang('E-mail'));
}
$table->setHeaderContents(0, $col++, get_lang('Last access'));

$row = 1;
foreach ($nrUsers as $u) {
$fullname = Security::remove_XSS(trim(($u['firstname'] ?? '').' '.($u['lastname'] ?? '')));
$email = Security::remove_XSS($u['email'] ?? '');
$last = Security::remove_XSS($u['lastAccess'] ?? '');

$col = 0;
$table->setCellContents($row, $col++, Display::tag('strong', $fullname));
if ('true' === api_get_setting('show_email_addresses')) {
$table->setCellContents($row, $col++, $email);
}
$table->setCellContents($row, $col++, $last);
$row++;
}
$out .= $table->toHtml();
} else {
$out .= Display::tag('p', get_lang('No users found.'));
}

echo $out;
exit;
}

// Starting the output buffering when we are exporting the information.
$export_csv = isset($_GET['export']) && 'csv' === $_GET['export'];

Expand Down Expand Up @@ -167,6 +206,50 @@ function(index) {
</script>";
$htmlHeadXtra[] = $js;

$labelShow = addslashes(get_lang('Show users active (not enrolled)'));
$labelHide = addslashes(get_lang('Hide users active (not enrolled)'));
$ajaxUrl = api_get_self().'?'.http_build_query([
'ajax' => 1,
'fragment' => 'non_registered',
'cid' => (int) $courseId,
'sid' => (int) $sessionId,
]);

$js_nonreg = "<script>
$(function() {
var btn = $('#toggle-non-registered-users');
var block = $('#non-registered-users-block');
var lblShow = '{$labelShow}';
var lblHide = '{$labelHide}';
var url = '{$ajaxUrl}';

btn.on('click', function(e){
e.preventDefault();
var open = btn.data('open') === 1;

if (open) {
block.slideUp(150);
btn.data('open', 0).text(lblShow);
} else {
if (!block.data('loaded')) {
$.get(url, function(html){
block.html(html).data('loaded', 1).hide().slideDown(150);
btn.data('open', 1).text(lblHide);
document.getElementById('non-registered-users-block')
.scrollIntoView({behavior: 'smooth', block: 'start'});
});
} else {
block.slideDown(150);
btn.data('open', 1).text(lblHide);
document.getElementById('non-registered-users-block')
.scrollIntoView({behavior: 'smooth', block: 'start'});
}
}
});
});
</script>";
$htmlHeadXtra[] = $js_nonreg;

// Database table definitions.
//@todo remove this calls
$TABLETRACK_EXERCISES = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
Expand Down Expand Up @@ -267,6 +350,8 @@ function(index) {
$users_tracking_per_page = '&users_tracking_per_page='.intval($_GET['users_tracking_per_page']);
}

$showNonRegistered = isset($_GET['show_non_registered']) ? (int) $_GET['show_non_registered'] : 0;

$actionsRight .= '<a
href="'.api_get_self().'?'.api_get_cidreq().'&export=csv&'.$additionalParams.$users_tracking_per_page.'">
'.Display::getMdiIcon(ActionIcon::EXPORT_CSV, 'ch-tool-icon', null, ICON_SIZE_MEDIUM, get_lang('CSV export')).'</a>';
Expand Down Expand Up @@ -847,6 +932,14 @@ function(index) {
$html .= Display::return_message(get_lang('No users in course'), 'warning', true);
}

$labelShowBtn = get_lang('Show users active (not enrolled)');
$html .= '<div class="mt-3">';
$html .= '<button type="button" class="btn btn--info" id="toggle-non-registered-users" data-open="0">'
. Security::remove_XSS($labelShowBtn) . '</button>';
$html .= '</div>';

$html .= '<div id="non-registered-users-block" style="display:none; margin-top:10px;"></div>';

$groupContent = '';
echo Display::panel($html, $titleSession);

Expand Down
Loading