Skip to content

Commit 3a3b1a2

Browse files
authored
Merge pull request #5508 from christianbeeznest/ofaj-21694
Internal: Add course access tracking- BT#21694
2 parents 5cf7264 + e550fc5 commit 3a3b1a2

File tree

6 files changed

+141
-34
lines changed

6 files changed

+141
-34
lines changed

public/main/inc/lib/tracking.lib.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5712,7 +5712,7 @@ public static function show_course_detail($userId, $courseId, $sessionId = 0, $s
57125712
);
57135713
$last_connection_in_lp = self::get_last_connection_time_in_lp(
57145714
$userId,
5715-
$course,
5715+
$course->getCode(),
57165716
$lp_id,
57175717
$sessionId
57185718
);

public/main/my_space/myStudents.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1705,7 +1705,7 @@
17051705
// Get last connection time in lp
17061706
$start_time = Tracking::get_last_connection_time_in_lp(
17071707
$studentId,
1708-
$course,
1708+
$course->getCode(),
17091709
$lp_id,
17101710
$sessionId
17111711
);

src/CoreBundle/EventListener/CidReqListener.php

Lines changed: 17 additions & 1 deletion
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;
@@ -39,7 +41,7 @@ public function __construct(
3941
private readonly AuthorizationCheckerInterface $authorizationChecker,
4042
private readonly TranslatorInterface $translator,
4143
private readonly EntityManagerInterface $entityManager,
42-
private readonly TokenStorageInterface $tokenStorage,
44+
private readonly TokenStorageInterface $tokenStorage
4345
) {}
4446

4547
/**
@@ -268,6 +270,20 @@ public function cleanSessionHandler(Request $request): void
268270
ChamiloSession::erase('course_already_visited');
269271
}
270272

273+
$courseId = $sessionHandler->get('cid', 0);
274+
$sessionId = $sessionHandler->get('sid', 0);
275+
$ip = $request->getClientIp();
276+
if ($courseId !== 0) {
277+
$token = $this->tokenStorage->getToken();
278+
if (null !== $token) {
279+
/** @var User $user */
280+
$user = $token->getUser();
281+
if ($user instanceof UserInterface) {
282+
$this->entityManager->getRepository(TrackECourseAccess::class)
283+
->logoutAccess($user, $courseId, $sessionId, $ip);
284+
}
285+
}
286+
}
271287
$sessionHandler->remove('toolgroup');
272288
$sessionHandler->remove('_cid');
273289
$sessionHandler->remove('cid');

src/CoreBundle/EventListener/CourseAccessListener.php

Lines changed: 37 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,42 +7,53 @@
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 Chamilo\CoreBundle\ServiceHelper\UserHelper;
13+
use DateTime;
14+
use Symfony\Component\HttpKernel\Event\RequestEvent;
15+
use Doctrine\ORM\EntityManagerInterface;
1316
use Symfony\Component\HttpFoundation\RequestStack;
17+
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
1418

1519
/**
1620
* In and outs of a course
1721
* This listener is always called when user enters the course home.
1822
*/
1923
class CourseAccessListener
2024
{
21-
protected ?Request $request = null;
22-
2325
public function __construct(
24-
private readonly EntityManager $em,
25-
RequestStack $requestStack
26-
) {
27-
$this->request = $requestStack->getCurrentRequest();
28-
}
26+
private readonly EntityManagerInterface $em,
27+
private readonly CidReqHelper $cidReqHelper,
28+
private readonly UserHelper $userHelper
29+
) {}
2930

30-
public function __invoke(CourseAccess $event): void
31+
public function onKernelRequest(RequestEvent $event): void
3132
{
32-
// CourseAccess
33-
$user = $event->getUser();
34-
$course = $event->getCourse();
35-
$ip = $this->request->getClientIp();
36-
37-
$access = new TrackECourseAccess();
38-
$access
39-
->setCId($course->getId())
40-
->setUser($user)
41-
->setSessionId(0)
42-
->setUserIp($ip)
43-
;
44-
45-
$this->em->persist($access);
46-
$this->em->flush();
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->userHelper->getCurrent();
43+
if ($user) {
44+
$ip = $event->getRequest()->getClientIp();
45+
$accessRepository = $this->em->getRepository(TrackECourseAccess::class);
46+
$access = $accessRepository->findExistingAccess($user, $courseId, $sessionId);
47+
48+
if ($access) {
49+
$accessRepository->updateAccess($access);
50+
} else {
51+
$accessRepository->recordAccess($user, $courseId, $sessionId, $ip);
52+
}
53+
54+
// Set a flag on the request to indicate that access has been checked
55+
$event->getRequest()->attributes->set('access_checked', true);
56+
}
57+
}
4758
}
4859
}

src/CoreBundle/Repository/TrackECourseAccessRepository.php

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
use Chamilo\CoreBundle\Entity\TrackECourseAccess;
1010
use Chamilo\CoreBundle\Entity\User;
11+
use DateTime;
1112
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
1213
use Doctrine\Persistence\ManagerRegistry;
1314

@@ -46,4 +47,84 @@ public function getLastAccessByUser(?User $user = null): ?TrackECourseAccess
4647

4748
return null;
4849
}
50+
51+
/**
52+
* Find existing access for a user.
53+
*/
54+
public function findExistingAccess(User $user, int $courseId, int $sessionId)
55+
{
56+
return $this->findOneBy(['user' => $user, 'cId' => $courseId, 'sessionId' => $sessionId]);
57+
}
58+
59+
/**
60+
* Update access record.
61+
*/
62+
public function updateAccess(TrackECourseAccess $access): void
63+
{
64+
$now = new DateTime();
65+
if (!$access->getLogoutCourseDate() || $now->getTimestamp() - $access->getLogoutCourseDate()->getTimestamp() > 300) {
66+
$access->setLogoutCourseDate($now);
67+
$access->setCounter($access->getCounter() + 1);
68+
$this->_em->flush();
69+
}
70+
}
71+
72+
/**
73+
* Record a new access entry.
74+
*/
75+
public 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);
84+
$this->_em->persist($access);
85+
$this->_em->flush();
86+
}
87+
88+
/**
89+
* Log out user access to a course.
90+
*/
91+
public function logoutAccess(User $user, int $courseId, int $sessionId, string $ip): void
92+
{
93+
$now = new DateTime("now", new \DateTimeZone("UTC"));
94+
$sessionLifetime = 3600;
95+
$limitTime = (new DateTime())->setTimestamp(time() - $sessionLifetime);
96+
97+
$access = $this->createQueryBuilder('a')
98+
->where('a.user = :user AND a.cId = :courseId AND a.sessionId = :sessionId')
99+
->andWhere('a.loginCourseDate > :limitTime')
100+
->setParameters([
101+
'user' => $user,
102+
'courseId' => $courseId,
103+
'sessionId' => $sessionId,
104+
'limitTime' => $limitTime,
105+
])
106+
->orderBy('a.loginCourseDate', 'DESC')
107+
->setMaxResults(1)
108+
->getQuery()
109+
->getOneOrNullResult();
110+
111+
if ($access) {
112+
$access->setLogoutCourseDate($now);
113+
$access->setCounter($access->getCounter() + 1);
114+
$this->_em->flush();
115+
} else {
116+
// No access found or existing access is outside the session lifetime
117+
// Insert new access record
118+
$newAccess = new TrackECourseAccess();
119+
$newAccess->setUser($user);
120+
$newAccess->setCId($courseId);
121+
$newAccess->setSessionId($sessionId);
122+
$newAccess->setUserIp($ip);
123+
$newAccess->setLoginCourseDate($now);
124+
$newAccess->setLogoutCourseDate($now);
125+
$newAccess->setCounter(1);
126+
$this->_em->persist($newAccess);
127+
$this->_em->flush();
128+
}
129+
}
49130
}

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 }
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)