Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions assets/css/scss/_social.scss
Original file line number Diff line number Diff line change
Expand Up @@ -976,3 +976,11 @@
}
}
}

.circle-green {
color: green;
}

.circle-gray {
color: gray;
}
10 changes: 10 additions & 0 deletions assets/vue/components/social/MyFriendsCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
<a :href="`/social?id=${friend.friend.id}`" class="d-flex align-items-center text-decoration-none">
<BaseUserAvatar :image-url="friend.friend.illustrationUrl" class="mr-2" />
<span>{{ friend.friend.firstname }} {{ friend.friend.lastname }} <small class="text-muted">({{ friend.friend.username }})</small></span>
<span v-if="friend.friend.isOnline" class="mdi mdi-circle circle-green mx-2" title="Online"></span>
<span v-else class="mdi mdi-circle circle-gray mx-2" title="Offline"></span>
</a>
</li>
</ul>
Expand Down Expand Up @@ -84,6 +86,14 @@ async function fetchFriends(userId) {
},
})
friends.value = response.data['hydra:member']

const friendIds = friends.value.map(friend => friend.friend.id)
const onlineStatusResponse = await axios.post(`/social-network/online-status`, { userIds: friendIds })
const onlineStatuses = onlineStatusResponse.data

friends.value.forEach(friend => {
friend.friend.isOnline = onlineStatuses[friend.friend.id] || false
})
} catch (error) {
console.error('Error fetching friends:', error)
}
Expand Down
4 changes: 2 additions & 2 deletions assets/vue/views/social/SocialSearch.vue
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@
<div class="flex items-center">
<img :src="user.avatar" class="w-16 h-16 rounded-full mr-4">
<span>{{ user.name }}</span>
<span v-if="user.status === 'online'" class="mdi mdi-circle green mx-2" title="Online"></span>
<span v-else class="mdi mdi-circle gray mx-2" title="Offline"></span>
<span v-if="user.status === 'online'" class="mdi mdi-circle circle-green mx-2" title="Online"></span>
<span v-else class="mdi mdi-circle circle-gray mx-2" title="Offline"></span>
<span :class="getRoleIcon(user.role)" class="mx-2"></span>
</div>
<div>
Expand Down
14 changes: 14 additions & 0 deletions src/CoreBundle/Controller/SocialController.php
Original file line number Diff line number Diff line change
Expand Up @@ -736,6 +736,20 @@ public function getUserRelation(int $currentUserId, int $profileUserId, EntityMa
]);
}

#[Route('/online-status', name: 'chamilo_core_social_get_online_status', methods: ['POST'])]
public function getOnlineStatus(Request $request, TrackEOnlineRepository $trackOnlineRepository): JsonResponse
{
$data = json_decode($request->getContent(), true);
$userIds = $data['userIds'] ?? [];

$onlineStatuses = [];
foreach ($userIds as $userId) {
$onlineStatuses[$userId] = $trackOnlineRepository->isUserOnline($userId);
}

return $this->json($onlineStatuses);
}

/**
* Checks the relationship between the current user and another user.
*
Expand Down
3 changes: 2 additions & 1 deletion src/CoreBundle/Entity/TrackELogin.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

namespace Chamilo\CoreBundle\Entity;

use Chamilo\CoreBundle\Repository\TrackELoginRepository;
use DateTime;
use Doctrine\ORM\Mapping as ORM;

Expand All @@ -15,7 +16,7 @@
#[ORM\Table(name: 'track_e_login')]
#[ORM\Index(name: 'login_user_id', columns: ['login_user_id'])]
#[ORM\Index(name: 'idx_track_e_login_date', columns: ['login_date'])]
#[ORM\Entity]
#[ORM\Entity(repositoryClass: TrackELoginRepository::class)]
class TrackELogin
{
#[ORM\Column(name: 'login_id', type: 'integer')]
Expand Down
26 changes: 25 additions & 1 deletion src/CoreBundle/EventListener/LoginSuccessHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,13 @@

namespace Chamilo\CoreBundle\EventListener;

use Chamilo\CoreBundle\Entity\TrackELogin;
use Chamilo\CoreBundle\Entity\TrackEOnline;
use Chamilo\CoreBundle\Entity\User;
use Chamilo\CoreBundle\Repository\TrackELoginRepository;
use Chamilo\CoreBundle\Repository\TrackEOnlineRepository;
use Chamilo\CoreBundle\Settings\SettingsManager;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
Expand All @@ -20,15 +25,18 @@ class LoginSuccessHandler
protected UrlGeneratorInterface $router;
protected AuthorizationCheckerInterface $checker;
protected SettingsManager $settingsManager;
protected EntityManagerInterface $entityManager;

public function __construct(
UrlGeneratorInterface $urlGenerator,
AuthorizationCheckerInterface $checker,
SettingsManager $settingsManager
SettingsManager $settingsManager,
EntityManagerInterface $entityManager
) {
$this->router = $urlGenerator;
$this->checker = $checker;
$this->settingsManager = $settingsManager;
$this->entityManager = $entityManager;
}

/**
Expand All @@ -37,6 +45,7 @@ public function __construct(
public function onSecurityInteractiveLogin(InteractiveLoginEvent $event)
{
$request = $event->getRequest();
$session = $request->getSession();

/** @var User $user */
$user = $event->getAuthenticationToken()->getUser();
Expand Down Expand Up @@ -128,6 +137,21 @@ public function onSecurityInteractiveLogin(InteractiveLoginEvent $event)
}
}

if (!$session->get('login_records_created')) {
$userIp = $request->getClientIp();

/** @var TrackEOnlineRepository $trackEOnlineRepository */
$trackEOnlineRepository = $this->entityManager->getRepository(TrackEOnline::class);

/** @var TrackELoginRepository $trackELoginRepository */
$trackELoginRepository = $this->entityManager->getRepository(TrackELogin::class);

$trackELoginRepository->createLoginRecord($user, new \DateTime(), $userIp);
$trackEOnlineRepository->createOnlineSession($user, $userIp);

$session->set('login_records_created', true);
}

if (!empty($url)) {
$response = new RedirectResponse($url);
}
Expand Down
40 changes: 8 additions & 32 deletions src/CoreBundle/EventListener/LogoutListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@

namespace Chamilo\CoreBundle\EventListener;

use Chamilo\CoreBundle\Entity\TrackELogin;
use Chamilo\CoreBundle\Entity\TrackEOnline;
use Chamilo\CoreBundle\Entity\User;
use Database;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
Expand Down Expand Up @@ -42,15 +42,11 @@ public function onSymfonyComponentSecurityHttpEventLogoutEvent(LogoutEvent $even
{
$request = $event->getRequest();

// Chamilo logout
// Chamilo logout operations
$request->getSession()->remove('_selected_locale');
$request->getSession()->remove('_locale');
$request->getSession()->remove('_locale_user');

/*if (api_is_global_chat_enabled()) {
$chat = new \Chat();
$chat->setUserStatus(0);
}*/
$token = $this->storage->getToken();
if (null === $token) {
$login = $this->router->generate('index');
Expand All @@ -62,40 +58,20 @@ public function onSymfonyComponentSecurityHttpEventLogoutEvent(LogoutEvent $even
$user = $token->getUser();
if ($user instanceof User) {
$userId = $user->getId();
$table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LOGIN);

$sql = "SELECT login_id, login_date
FROM {$table}
WHERE login_user_id = {$userId}
ORDER BY login_date DESC
LIMIT 0,1";
$loginId = null;
$connection = $this->em->getConnection();
$result = $connection->executeQuery($sql);
if ($result->rowCount() > 0) {
$row = $result->fetchAssociative();
if ($row) {
$loginId = $row['login_id'];
}
}

$trackELoginRepository = $this->em->getRepository(TrackELogin::class);
$loginAs = $this->checker->isGranted('ROLE_PREVIOUS_ADMIN');
if (!$loginAs) {
$current_date = api_get_utc_datetime();
$sql = "UPDATE {$table}
SET logout_date='".$current_date."'
WHERE login_id='{$loginId}'";
$connection->executeQuery($sql);
$currentDate = new \DateTime("now", new \DateTimeZone('UTC'));
$trackELoginRepository->updateLastLoginLogoutDate($userId, $currentDate);
}

$table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ONLINE);
$sql = "DELETE FROM $table WHERE login_user_id = $userId";
$connection->executeQuery($sql);
$trackEOnlineRepository = $this->em->getRepository(TrackEOnline::class);
$trackEOnlineRepository->removeOnlineSessionsByUser($userId);
}

$login = $this->router->generate('index');

return new RedirectResponse($login);
// return new JsonResponse('logout out', 200);
}
}
58 changes: 58 additions & 0 deletions src/CoreBundle/Repository/TrackELoginRepository.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php

declare(strict_types=1);

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

namespace Chamilo\CoreBundle\Repository;

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

class TrackELoginRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, TrackELogin::class);
}

public function createLoginRecord(User $user, DateTime $loginDate, string $userIp): TrackELogin
{
$loginRecord = new TrackELogin();
$loginRecord->setUser($user);
$loginRecord->setLoginDate($loginDate);
$loginRecord->setUserIp($userIp);

$this->_em->persist($loginRecord);
$this->_em->flush();

return $loginRecord;
}

public function updateLastLoginLogoutDate(int $userId, DateTime $logoutDate): void
{
$lastLoginId = $this->createQueryBuilder('t')
->select('t.loginId')
->where('t.user = :userId')
->andWhere('t.logoutDate IS NULL')
->setParameter('userId', $userId)
->orderBy('t.loginDate', 'DESC')
->setMaxResults(1)
->getQuery()
->getSingleScalarResult();

if ($lastLoginId) {
$qb = $this->createQueryBuilder('t')
->update()
->set('t.logoutDate', ':logoutDate')
->where('t.loginId = :loginId')
->setParameter('loginId', $lastLoginId)
->setParameter('logoutDate', $logoutDate);

$qb->getQuery()->execute();
}
}
}
26 changes: 26 additions & 0 deletions src/CoreBundle/Repository/TrackEOnlineRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
namespace Chamilo\CoreBundle\Repository;

use Chamilo\CoreBundle\Entity\TrackEOnline;
use Chamilo\CoreBundle\Entity\User;
use Chamilo\CoreBundle\Settings\SettingsManager;
use DateTime;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
Expand Down Expand Up @@ -50,4 +51,29 @@ public function isUserOnline(int $userId): bool
return false;
}
}

public function createOnlineSession(User $user, string $userIp, int $cId = 0, int $sessionId = 0, int $accessUrlId = 1): void
{
$trackEOnline = new TrackEOnline();
$trackEOnline->setLoginUserId($user->getId());
$trackEOnline->setLoginDate(new \DateTime());
$trackEOnline->setUserIp($userIp);
$trackEOnline->setCId($cId);
$trackEOnline->setSessionId($sessionId);
$trackEOnline->setAccessUrlId($accessUrlId);

$this->_em->persist($trackEOnline);
$this->_em->flush();
}

public function removeOnlineSessionsByUser(int $userId): void
{
$sessions = $this->findBy(['loginUserId' => $userId]);

foreach ($sessions as $session) {
$this->_em->remove($session);
}

$this->_em->flush();
}
}
3 changes: 2 additions & 1 deletion src/CoreBundle/Resources/config/listeners.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,12 @@ services:

# Auth listeners
Chamilo\CoreBundle\EventListener\LoginSuccessHandler:
arguments: ['@router', '@security.authorization_checker', '@Chamilo\CoreBundle\Settings\SettingsManager']
arguments: ['@router', '@security.authorization_checker', '@Chamilo\CoreBundle\Settings\SettingsManager', '@doctrine.orm.entity_manager']
tags:
- {name: kernel.event_listener, event: security.interactive_login, method: onSecurityInteractiveLogin}

Chamilo\CoreBundle\EventListener\LogoutListener:
arguments: ['@router', '@security.authorization_checker', '@security.token_storage', '@doctrine.orm.entity_manager']
tags:
- name: kernel.event_listener
event: Symfony\Component\Security\Http\Event\LogoutEvent
Expand Down