Merge pull request #5508 from christianbeeznest/ofaj-21694

Internal: Add course access tracking- BT#21694
pull/5514/head
Nicolas Ducoulombier 6 months ago committed by GitHub
commit 3a3b1a26db
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      public/main/inc/lib/tracking.lib.php
  2. 2
      public/main/my_space/myStudents.php
  3. 18
      src/CoreBundle/EventListener/CidReqListener.php
  4. 63
      src/CoreBundle/EventListener/CourseAccessListener.php
  5. 81
      src/CoreBundle/Repository/TrackECourseAccessRepository.php
  6. 5
      src/CoreBundle/Resources/config/listeners.yml

@ -5712,7 +5712,7 @@ class Tracking
);
$last_connection_in_lp = self::get_last_connection_time_in_lp(
$userId,
$course,
$course->getCode(),
$lp_id,
$sessionId
);

@ -1705,7 +1705,7 @@ if (empty($details)) {
// Get last connection time in lp
$start_time = Tracking::get_last_connection_time_in_lp(
$studentId,
$course,
$course->getCode(),
$lp_id,
$sessionId
);

@ -9,6 +9,7 @@ namespace Chamilo\CoreBundle\EventListener;
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;
@ -17,6 +18,7 @@ use Chamilo\CoreBundle\Security\Authorization\Voter\SessionVoter;
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;
@ -39,7 +41,7 @@ class CidReqListener
private readonly AuthorizationCheckerInterface $authorizationChecker,
private readonly TranslatorInterface $translator,
private readonly EntityManagerInterface $entityManager,
private readonly TokenStorageInterface $tokenStorage,
private readonly TokenStorageInterface $tokenStorage
) {}
/**
@ -268,6 +270,20 @@ class CidReqListener
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');

@ -7,10 +7,14 @@ declare(strict_types=1);
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
@ -18,31 +22,38 @@ use Symfony\Component\HttpFoundation\RequestStack;
*/
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);
}
}
}
}

@ -8,6 +8,7 @@ namespace Chamilo\CoreBundle\Repository;
use Chamilo\CoreBundle\Entity\TrackECourseAccess;
use Chamilo\CoreBundle\Entity\User;
use DateTime;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
@ -46,4 +47,84 @@ class TrackECourseAccessRepository extends ServiceEntityRepository
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();
}
}
}

@ -17,10 +17,9 @@ 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}
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }
# Sets the user access in a course session listener
Chamilo\CoreBundle\EventListener\SessionAccessListener:

Loading…
Cancel
Save