Skip to content

Commit 112488d

Browse files
Internal: Add course access tracking- BT#21694
1 parent 7b51cc5 commit 112488d

File tree

3 files changed

+114
-26
lines changed

3 files changed

+114
-26
lines changed

src/CoreBundle/EventListener/CidReqListener.php

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use Chamilo\CoreBundle\Controller\EditorController;
1010
use Chamilo\CoreBundle\Entity\Course;
1111
use Chamilo\CoreBundle\Entity\Session;
12+
use Chamilo\CoreBundle\Entity\TrackECourseAccess;
1213
use Chamilo\CoreBundle\Entity\User;
1314
use Chamilo\CoreBundle\Exception\NotAllowedException;
1415
use Chamilo\CoreBundle\Security\Authorization\Voter\CourseVoter;
@@ -17,6 +18,7 @@
1718
use Chamilo\CourseBundle\Controller\CourseControllerInterface;
1819
use Chamilo\CourseBundle\Entity\CGroup;
1920
use ChamiloSession;
21+
use DateTime;
2022
use Doctrine\ORM\EntityManagerInterface;
2123
use Symfony\Component\HttpFoundation\Request;
2224
use Symfony\Component\HttpKernel\Event\ControllerEvent;
@@ -268,6 +270,13 @@ public function cleanSessionHandler(Request $request): void
268270
ChamiloSession::erase('course_already_visited');
269271
}
270272

273+
$user = $this->tokenStorage->getToken()->getUser();
274+
$courseId = $sessionHandler->get('cid', 0);
275+
$sessionId = $sessionHandler->get('sid', 0);
276+
$ip = $request->getClientIp();
277+
if ($courseId !== 0) {
278+
$this->logoutAccess($user, $courseId, $sessionId, $ip);
279+
}
271280
$sessionHandler->remove('toolgroup');
272281
$sessionHandler->remove('_cid');
273282
$sessionHandler->remove('cid');
@@ -326,4 +335,45 @@ private function generateCourseUrl(?Course $course, int $sessionId, int $groupId
326335

327336
return '';
328337
}
338+
339+
private function logoutAccess(User $user, int $courseId, int $sessionId, string $ip): void
340+
{
341+
$now = new DateTime("now", new \DateTimeZone("UTC"));
342+
$sessionLifetime = 3600;
343+
$limitTime = (new DateTime())->setTimestamp(time() - $sessionLifetime);
344+
345+
$access = $this->entityManager->getRepository(TrackECourseAccess::class)
346+
->createQueryBuilder('a')
347+
->where('a.user = :user AND a.cId = :courseId AND a.sessionId = :sessionId')
348+
->andWhere('a.loginCourseDate > :limitTime')
349+
->setParameters([
350+
'user' => $user,
351+
'courseId' => $courseId,
352+
'sessionId' => $sessionId,
353+
'limitTime' => $limitTime,
354+
])
355+
->orderBy('a.loginCourseDate', 'DESC')
356+
->setMaxResults(1)
357+
->getQuery()
358+
->getOneOrNullResult();
359+
360+
if ($access) {
361+
$access->setLogoutCourseDate($now);
362+
$access->setCounter($access->getCounter() + 1);
363+
$this->entityManager->flush();
364+
} else {
365+
// No access found or existing access is outside the session lifetime
366+
// Insert new access record
367+
$newAccess = new TrackECourseAccess();
368+
$newAccess->setUser($user);
369+
$newAccess->setCId($courseId);
370+
$newAccess->setSessionId($sessionId);
371+
$newAccess->setUserIp($ip);
372+
$newAccess->setLoginCourseDate($now);
373+
$newAccess->setLogoutCourseDate($now);
374+
$newAccess->setCounter(1);
375+
$this->entityManager->persist($newAccess);
376+
$this->entityManager->flush();
377+
}
378+
}
329379
}

src/CoreBundle/EventListener/CourseAccessListener.php

Lines changed: 60 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,41 +7,80 @@
77
namespace Chamilo\CoreBundle\EventListener;
88

99
use Chamilo\CoreBundle\Entity\TrackECourseAccess;
10-
use Chamilo\CourseBundle\Event\CourseAccess;
11-
use Doctrine\ORM\EntityManager;
12-
use Symfony\Component\HttpFoundation\Request;
10+
use Chamilo\CoreBundle\Entity\User;
11+
use Chamilo\CoreBundle\ServiceHelper\CidReqHelper;
12+
use DateTime;
13+
use Symfony\Component\HttpKernel\Event\RequestEvent;
14+
use Doctrine\ORM\EntityManagerInterface;
1315
use Symfony\Component\HttpFoundation\RequestStack;
16+
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
1417

1518
/**
1619
* In and outs of a course
1720
* This listener is always called when user enters the course home.
1821
*/
1922
class CourseAccessListener
2023
{
21-
protected ?Request $request = null;
22-
2324
public function __construct(
24-
private readonly EntityManager $em,
25-
RequestStack $requestStack
26-
) {
27-
$this->request = $requestStack->getCurrentRequest();
25+
private readonly EntityManagerInterface $em,
26+
private readonly RequestStack $requestStack,
27+
private readonly CidReqHelper $cidReqHelper,
28+
private readonly TokenStorageInterface $tokenStorage,
29+
) {}
30+
31+
public function onKernelRequest(RequestEvent $event): void
32+
{
33+
if (!$event->isMainRequest() || $event->getRequest()->attributes->get('access_checked')) {
34+
// If it's not the main request or we've already handled access in this request, do nothing
35+
return;
36+
}
37+
38+
$courseId = (int) $this->cidReqHelper->getCourseId();
39+
$sessionId = (int) $this->cidReqHelper->getSessionId();
40+
41+
if ($courseId > 0) {
42+
$user = $this->tokenStorage->getToken()?->getUser();
43+
if ($user instanceof User) {
44+
$ip = $this->requestStack->getCurrentRequest()->getClientIp();
45+
$access = $this->findExistingAccess($user, $courseId, $sessionId);
46+
47+
if ($access) {
48+
$this->updateAccess($access);
49+
} else {
50+
$this->recordAccess($user, $courseId, $sessionId, $ip);
51+
}
52+
53+
// Set a flag on the request to indicate that access has been checked
54+
$event->getRequest()->attributes->set('access_checked', true);
55+
}
56+
}
2857
}
2958

30-
public function __invoke(CourseAccess $event): void
59+
private function findExistingAccess(User $user, int $courseId, int $sessionId)
3160
{
32-
// CourseAccess
33-
$user = $event->getUser();
34-
$course = $event->getCourse();
35-
$ip = $this->request->getClientIp();
61+
return $this->em->getRepository(TrackECourseAccess::class)
62+
->findOneBy(['user' => $user, 'cId' => $courseId, 'sessionId' => $sessionId]);
63+
}
3664

37-
$access = new TrackECourseAccess();
38-
$access
39-
->setCId($course->getId())
40-
->setUser($user)
41-
->setSessionId(0)
42-
->setUserIp($ip)
43-
;
65+
private function updateAccess(TrackECourseAccess $access): void
66+
{
67+
$now = new DateTime();
68+
if (!$access->getLogoutCourseDate() || $now->getTimestamp() - $access->getLogoutCourseDate()->getTimestamp() > 300) {
69+
$access->setLogoutCourseDate($now);
70+
$access->setCounter($access->getCounter() + 1);
71+
$this->em->flush();
72+
}
73+
}
4474

75+
private function recordAccess(User $user, int $courseId, int $sessionId, string $ip): void
76+
{
77+
$access = new TrackECourseAccess();
78+
$access->setUser($user);
79+
$access->setCId($courseId);
80+
$access->setSessionId($sessionId);
81+
$access->setUserIp($ip);
82+
$access->setLoginCourseDate(new \DateTime());
83+
$access->setCounter(1);
4584
$this->em->persist($access);
4685
$this->em->flush();
4786
}

src/CoreBundle/Resources/config/listeners.yml

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,11 @@ services:
1717

1818
# Sets the user access in a course listener
1919
Chamilo\CoreBundle\EventListener\CourseAccessListener:
20-
arguments:
21-
- '@doctrine.orm.entity_manager'
22-
tags:
23-
- {name: kernel.event_listener, event: chamilo_course.course.access}
20+
tags:
21+
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest, priority: 6 }
22+
2423

25-
# Sets the user access in a course session listener
24+
# Sets the user access in a course session listener
2625
Chamilo\CoreBundle\EventListener\SessionAccessListener:
2726
tags:
2827
- {name: kernel.event_listener, event: chamilo_course.course.session}

0 commit comments

Comments
 (0)