chamilo/chamilo-lms

View on GitHub
src/CoreBundle/EventSubscriber/AnonymousUserSubscriber.php

Summary

Maintainability
A
0 mins
Test Coverage
<?php

/* For licensing terms, see /license.txt */

declare(strict_types=1);

namespace Chamilo\CoreBundle\EventSubscriber;

use Chamilo\CoreBundle\Entity\TrackELogin;
use Chamilo\CoreBundle\Entity\User;
use Chamilo\CoreBundle\Settings\SettingsManager;
use DateTime;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\KernelEvents;

class AnonymousUserSubscriber implements EventSubscriberInterface
{
    private const MAX_ANONYMOUS_USERS = 5;

    public function __construct(
        private readonly Security $security,
        private readonly EntityManagerInterface $entityManager,
        private readonly SettingsManager $settingsManager
    ) {}

    public function onKernelRequest(RequestEvent $event): void
    {
        if (null !== $this->security->getUser()) {
            return;
        }

        $request = $event->getRequest();
        $userIp = $request->getClientIp() ?: '127.0.0.1';

        $anonymousUserId = $this->getOrCreateAnonymousUserId($userIp);
        if (null !== $anonymousUserId) {
            $trackLoginRepository = $this->entityManager->getRepository(TrackELogin::class);

            // Check if a login record already exists for this user and IP
            $existingLogin = $trackLoginRepository->findOneBy(['userIp' => $userIp, 'user' => $anonymousUserId]);
            if (!$existingLogin) {
                // Record the access if it does not exist
                $trackLogin = new TrackELogin();
                $trackLogin->setUserIp($userIp)
                    ->setLoginDate(new DateTime())
                    ->setUser($this->entityManager->getReference(User::class, $anonymousUserId))
                ;

                $this->entityManager->persist($trackLogin);
                $this->entityManager->flush();
            }

            $userRepository = $this->entityManager->getRepository(User::class);
            $user = $userRepository->find($anonymousUserId);

            if ($user) {
                // Store user information in the session
                $userInfo = [
                    'user_id' => $user->getId(),
                    'username' => $user->getUsername(),
                    'firstname' => $user->getFirstname(),
                    'lastname' => $user->getLastname(),
                    'firstName' => $user->getFirstname(),
                    'lastName' => $user->getLastname(),
                    'email' => $user->getEmail(),
                    'official_code' => $user->getOfficialCode(),
                    'picture_uri' => $user->getPictureUri(),
                    'status' => $user->getStatus(),
                    'active' => $user->isActive(),
                    'auth_source' => $user->getAuthSource(),
                    'theme' => $user->getTheme(),
                    'language' => $user->getLocale(),
                    'registration_date' => $user->getRegistrationDate()->format('Y-m-d H:i:s'),
                    'expiration_date' => $user->getExpirationDate() ? $user->getExpirationDate()->format('Y-m-d H:i:s') : null,
                    'last_login' => $user->getLastLogin() ? $user->getLastLogin()->format('Y-m-d H:i:s') : null,
                    'is_anonymous' => true,
                ];

                $request->getSession()->set('_user', $userInfo);
            }
        }
    }

    public static function getSubscribedEvents(): array
    {
        return [
            KernelEvents::REQUEST => 'onKernelRequest',
        ];
    }

    private function getOrCreateAnonymousUserId(string $userIp): ?int
    {
        $userRepository = $this->entityManager->getRepository(User::class);
        $trackLoginRepository = $this->entityManager->getRepository(TrackELogin::class);
        $anonymousAutoProvisioning = 'true' === $this->settingsManager->getSetting('security.anonymous_autoprovisioning');

        if (!$anonymousAutoProvisioning) {
            $anonymousUser = $userRepository->findOneBy(['status' => User::ANONYMOUS], ['registrationDate' => 'ASC']);
            if ($anonymousUser) {
                return $anonymousUser->getId();
            }

            return $this->createAnonymousUser()->getId();
        }

        $maxAnonymousUsers = (int) $this->settingsManager->getSetting('admin.max_anonymous_users');
        if (0 === $maxAnonymousUsers) {
            $maxAnonymousUsers = self::MAX_ANONYMOUS_USERS;
        }
        $anonymousUsers = $userRepository->findBy(['status' => User::ANONYMOUS], ['registrationDate' => 'ASC']);

        // Check in TrackELogin if there is an anonymous user with the same IP
        foreach ($anonymousUsers as $user) {
            $loginRecord = $trackLoginRepository->findOneBy(['userIp' => $userIp, 'user' => $user]);
            if ($loginRecord) {
                return $user->getId();
            }
        }

        // Delete excess anonymous users
        while (\count($anonymousUsers) >= $maxAnonymousUsers) {
            $oldestAnonymousUser = array_shift($anonymousUsers);
            if ($oldestAnonymousUser) {
                error_log('Deleting oldest anonymous user: '.$oldestAnonymousUser->getId());

                $this->entityManager->remove($oldestAnonymousUser);
                $this->entityManager->flush();
            }
        }

        return $this->createAnonymousUser()->getId();
    }

    private function createAnonymousUser(): User
    {
        $uniqueId = uniqid('anon_');
        $email = $uniqueId.'@localhost.local';

        if ('true' === $this->settingsManager->getSetting('profile.login_is_email')) {
            $uniqueId = $email;
        }

        $anonymousUser = (new User())
            ->setSkipResourceNode(true)
            ->setLastname('Joe')
            ->setFirstname('Anonymous')
            ->setUsername('anon_'.$uniqueId)
            ->setStatus(User::ANONYMOUS)
            ->setPlainPassword('anon')
            ->setEmail($email)
            ->setOfficialCode('anonymous')
            ->setCreatorId(1)
            ->addRole('ROLE_ANONYMOUS')
        ;

        $this->entityManager->persist($anonymousUser);
        $this->entityManager->flush();

        return $anonymousUser;
    }
}