Skip to content

Commit

Permalink
Merge pull request #5508 from christianbeeznest/ofaj-21694
Browse files Browse the repository at this point in the history
Internal: Add course access tracking- BT#21694
  • Loading branch information
NicoDucou committed May 16, 2024
2 parents 5cf7264 + e550fc5 commit 3a3b1a2
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 34 deletions.
2 changes: 1 addition & 1 deletion public/main/inc/lib/tracking.lib.php
Original file line number Diff line number Diff line change
Expand Up @@ -5712,7 +5712,7 @@ public static function show_course_detail($userId, $courseId, $sessionId = 0, $s
);
$last_connection_in_lp = self::get_last_connection_time_in_lp(
$userId,
$course,
$course->getCode(),
$lp_id,
$sessionId
);
Expand Down
2 changes: 1 addition & 1 deletion public/main/my_space/myStudents.php
Original file line number Diff line number Diff line change
Expand Up @@ -1705,7 +1705,7 @@
// Get last connection time in lp
$start_time = Tracking::get_last_connection_time_in_lp(
$studentId,
$course,
$course->getCode(),
$lp_id,
$sessionId
);
Expand Down
18 changes: 17 additions & 1 deletion src/CoreBundle/EventListener/CidReqListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use Chamilo\CoreBundle\Controller\EditorController;
use Chamilo\CoreBundle\Entity\Course;
use Chamilo\CoreBundle\Entity\Session;
use Chamilo\CoreBundle\Entity\TrackECourseAccess;
use Chamilo\CoreBundle\Entity\User;
use Chamilo\CoreBundle\Exception\NotAllowedException;
use Chamilo\CoreBundle\Security\Authorization\Voter\CourseVoter;
Expand All @@ -17,6 +18,7 @@
use Chamilo\CourseBundle\Controller\CourseControllerInterface;
use Chamilo\CourseBundle\Entity\CGroup;
use ChamiloSession;
use DateTime;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\ControllerEvent;
Expand All @@ -39,7 +41,7 @@ public function __construct(
private readonly AuthorizationCheckerInterface $authorizationChecker,
private readonly TranslatorInterface $translator,
private readonly EntityManagerInterface $entityManager,
private readonly TokenStorageInterface $tokenStorage,
private readonly TokenStorageInterface $tokenStorage
) {}

/**
Expand Down Expand Up @@ -268,6 +270,20 @@ public function cleanSessionHandler(Request $request): void
ChamiloSession::erase('course_already_visited');
}

$courseId = $sessionHandler->get('cid', 0);
$sessionId = $sessionHandler->get('sid', 0);
$ip = $request->getClientIp();
if ($courseId !== 0) {
$token = $this->tokenStorage->getToken();
if (null !== $token) {
/** @var User $user */
$user = $token->getUser();
if ($user instanceof UserInterface) {
$this->entityManager->getRepository(TrackECourseAccess::class)
->logoutAccess($user, $courseId, $sessionId, $ip);
}
}
}
$sessionHandler->remove('toolgroup');
$sessionHandler->remove('_cid');
$sessionHandler->remove('cid');
Expand Down
63 changes: 37 additions & 26 deletions src/CoreBundle/EventListener/CourseAccessListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,42 +7,53 @@
namespace Chamilo\CoreBundle\EventListener;

use Chamilo\CoreBundle\Entity\TrackECourseAccess;
use Chamilo\CourseBundle\Event\CourseAccess;
use Doctrine\ORM\EntityManager;
use Symfony\Component\HttpFoundation\Request;
use Chamilo\CoreBundle\Entity\User;
use Chamilo\CoreBundle\ServiceHelper\CidReqHelper;
use Chamilo\CoreBundle\ServiceHelper\UserHelper;
use DateTime;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;

/**
* In and outs of a course
* This listener is always called when user enters the course home.
*/
class CourseAccessListener
{
protected ?Request $request = null;

public function __construct(
private readonly EntityManager $em,
RequestStack $requestStack
) {
$this->request = $requestStack->getCurrentRequest();
}
private readonly EntityManagerInterface $em,
private readonly CidReqHelper $cidReqHelper,
private readonly UserHelper $userHelper
) {}

public function __invoke(CourseAccess $event): void
public function onKernelRequest(RequestEvent $event): void
{
// CourseAccess
$user = $event->getUser();
$course = $event->getCourse();
$ip = $this->request->getClientIp();

$access = new TrackECourseAccess();
$access
->setCId($course->getId())
->setUser($user)
->setSessionId(0)
->setUserIp($ip)
;

$this->em->persist($access);
$this->em->flush();
if (!$event->isMainRequest() || $event->getRequest()->attributes->get('access_checked')) {
// If it's not the main request or we've already handled access in this request, do nothing
return;
}

$courseId = (int) $this->cidReqHelper->getCourseId();
$sessionId = (int) $this->cidReqHelper->getSessionId();

if ($courseId > 0) {
$user = $this->userHelper->getCurrent();
if ($user) {
$ip = $event->getRequest()->getClientIp();
$accessRepository = $this->em->getRepository(TrackECourseAccess::class);
$access = $accessRepository->findExistingAccess($user, $courseId, $sessionId);

if ($access) {
$accessRepository->updateAccess($access);
} else {
$accessRepository->recordAccess($user, $courseId, $sessionId, $ip);
}

// Set a flag on the request to indicate that access has been checked
$event->getRequest()->attributes->set('access_checked', true);
}
}
}
}
81 changes: 81 additions & 0 deletions src/CoreBundle/Repository/TrackECourseAccessRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

use Chamilo\CoreBundle\Entity\TrackECourseAccess;
use Chamilo\CoreBundle\Entity\User;
use DateTime;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;

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

return null;
}

/**
* Find existing access for a user.
*/
public function findExistingAccess(User $user, int $courseId, int $sessionId)
{
return $this->findOneBy(['user' => $user, 'cId' => $courseId, 'sessionId' => $sessionId]);
}

/**
* Update access record.
*/
public function updateAccess(TrackECourseAccess $access): void
{
$now = new DateTime();
if (!$access->getLogoutCourseDate() || $now->getTimestamp() - $access->getLogoutCourseDate()->getTimestamp() > 300) {
$access->setLogoutCourseDate($now);
$access->setCounter($access->getCounter() + 1);
$this->_em->flush();
}
}

/**
* Record a new access entry.
*/
public function recordAccess(User $user, int $courseId, int $sessionId, string $ip): void
{
$access = new TrackECourseAccess();
$access->setUser($user);
$access->setCId($courseId);
$access->setSessionId($sessionId);
$access->setUserIp($ip);
$access->setLoginCourseDate(new \DateTime());
$access->setCounter(1);
$this->_em->persist($access);
$this->_em->flush();
}

/**
* Log out user access to a course.
*/
public function logoutAccess(User $user, int $courseId, int $sessionId, string $ip): void
{
$now = new DateTime("now", new \DateTimeZone("UTC"));
$sessionLifetime = 3600;
$limitTime = (new DateTime())->setTimestamp(time() - $sessionLifetime);

$access = $this->createQueryBuilder('a')
->where('a.user = :user AND a.cId = :courseId AND a.sessionId = :sessionId')
->andWhere('a.loginCourseDate > :limitTime')
->setParameters([
'user' => $user,
'courseId' => $courseId,
'sessionId' => $sessionId,
'limitTime' => $limitTime,
])
->orderBy('a.loginCourseDate', 'DESC')
->setMaxResults(1)
->getQuery()
->getOneOrNullResult();

if ($access) {
$access->setLogoutCourseDate($now);
$access->setCounter($access->getCounter() + 1);
$this->_em->flush();
} else {
// No access found or existing access is outside the session lifetime
// Insert new access record
$newAccess = new TrackECourseAccess();
$newAccess->setUser($user);
$newAccess->setCId($courseId);
$newAccess->setSessionId($sessionId);
$newAccess->setUserIp($ip);
$newAccess->setLoginCourseDate($now);
$newAccess->setLogoutCourseDate($now);
$newAccess->setCounter(1);
$this->_em->persist($newAccess);
$this->_em->flush();
}
}
}
9 changes: 4 additions & 5 deletions src/CoreBundle/Resources/config/listeners.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,11 @@ services:

# Sets the user access in a course listener
Chamilo\CoreBundle\EventListener\CourseAccessListener:
arguments:
- '@doctrine.orm.entity_manager'
tags:
- {name: kernel.event_listener, event: chamilo_course.course.access}
tags:
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }


# Sets the user access in a course session listener
# Sets the user access in a course session listener
Chamilo\CoreBundle\EventListener\SessionAccessListener:
tags:
- {name: kernel.event_listener, event: chamilo_course.course.session}
Expand Down

0 comments on commit 3a3b1a2

Please sign in to comment.