269 lines
8.7 KiB
PHP
269 lines
8.7 KiB
PHP
<?php
|
|
|
|
namespace App\Service;
|
|
|
|
|
|
use App\Entity\Roles;
|
|
use App\Entity\User;
|
|
use App\Entity\UserOrganizatonApp;
|
|
use App\Entity\UsersOrganizations;
|
|
use DateTimeImmutable;
|
|
use Doctrine\ORM\EntityManagerInterface;
|
|
use Doctrine\ORM\EntityNotFoundException;
|
|
use Exception;
|
|
use League\Bundle\OAuth2ServerBundle\Model\AccessToken;
|
|
use Random\RandomException;
|
|
use SebastianBergmann\CodeCoverage\Util\DirectoryCouldNotBeCreatedException;
|
|
use Symfony\Bundle\SecurityBundle\Security;
|
|
use Symfony\Component\HttpFoundation\File\Exception\FileException;
|
|
|
|
class UserService
|
|
{
|
|
|
|
public const NOT_FOUND = 'Entity not found';
|
|
private string $profileDirectory;
|
|
|
|
public function __construct(private readonly EntityManagerInterface $entityManager,
|
|
private readonly Security $security,
|
|
string $profileDirectory
|
|
)
|
|
{
|
|
$this->profileDirectory = $profileDirectory;
|
|
}
|
|
|
|
/**
|
|
* Generate a random password for a new user until they set their own.
|
|
* @throws RandomException
|
|
*/
|
|
public function generateRandomPassword(): string
|
|
{
|
|
$length = 50; // Length of the password
|
|
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()_+';
|
|
$charactersLength = strlen($characters);
|
|
$randomPassword = '';
|
|
|
|
for ($i = 0; $i < $length; $i++) {
|
|
$randomPassword .= $characters[random_int(0, $charactersLength - 1)];
|
|
}
|
|
|
|
return $randomPassword;
|
|
}
|
|
|
|
|
|
/**
|
|
* Check if the user is currently connected.
|
|
* This method check if the user is currently connected to one of the applications.
|
|
*
|
|
* @param String $userIdentifier
|
|
* @return bool
|
|
*/
|
|
public function isUserConnected(string $userIdentifier): bool
|
|
{
|
|
$now = new DateTimeImmutable();
|
|
$tokens = $this->entityManager->getRepository(AccessToken::class)->findBy([
|
|
'userIdentifier' => $userIdentifier,
|
|
'revoked' => false
|
|
]);
|
|
|
|
foreach ($tokens as $token) {
|
|
// Assuming $token->getExpiry() returns a DateTimeInterface
|
|
if ($token->getExpiry() > $now) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Check if the user have the rights to access the page
|
|
*
|
|
* @param User $user
|
|
* @param bool $skipSelfCheck
|
|
* @return bool
|
|
* @throws Exception
|
|
*/
|
|
public function hasAccessTo(User $user, bool $skipSelfCheck = false): bool
|
|
{
|
|
if (!$skipSelfCheck && $user->getUserIdentifier() === $this->security->getUser()->getUserIdentifier()) {
|
|
return true;
|
|
}
|
|
$userOrganization = $this->entityManager->getRepository(UsersOrganizations::class)->findBy(['users' => $user]);
|
|
if ($userOrganization) {
|
|
foreach ($userOrganization as $uo) {
|
|
if ($this->isAdminOfOrganization($uo)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
if ($this->security->isGranted('ROLE_SUPER_ADMIN')) {
|
|
return true;
|
|
}
|
|
return false;
|
|
|
|
}
|
|
|
|
/**
|
|
* Check if the user is an admin of the organization
|
|
* A user is considered an admin of an organization if they have the 'ROLE_ADMIN' AND have the link to the
|
|
* entity role 'ROLE_ADMIN' in the UsersOrganizationsApp entity
|
|
* (if he is admin for any application of the organization).
|
|
*
|
|
* @param UsersOrganizations $usersOrganizations
|
|
* @return bool
|
|
* @throws Exception
|
|
*/
|
|
public function isAdminOfOrganization(UsersOrganizations $usersOrganizations): bool
|
|
{
|
|
$actingUser = $this->getUserByIdentifier($this->security->getUser()->getUserIdentifier());
|
|
$uo = $this->entityManager->getRepository(UsersOrganizations::class)->findOneBy(['users' => $actingUser]);
|
|
$roleAdmin = $this->entityManager->getRepository(Roles::class)->findOneBy(['name' => 'ADMIN']);
|
|
if ($uo) {
|
|
$uoa = $this->entityManager->getRepository(UserOrganizatonApp::class)->findOneBy(['userOrganization' => $uo,
|
|
'role' => $roleAdmin,
|
|
'isActive' => true]);
|
|
if ($uoa && $this->security->isGranted('ROLE_ADMIN')) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Get the Organizations id where the user is admin
|
|
*
|
|
* @param User $user
|
|
* @return array
|
|
* @throws Exception
|
|
*/
|
|
public function getAdminOrganizationsIds(User $user): array
|
|
{
|
|
$orgIds = [];
|
|
try {
|
|
$uo = $this->entityManager->getRepository(UsersOrganizations::class)->findBy(['users' => $user]);
|
|
$roleAdmin = $this->entityManager->getRepository(Roles::class)->findOneBy(['name' => 'ADMIN']);
|
|
if ($uo) {
|
|
foreach ($uo as $u) {
|
|
$uoa = $this->entityManager->getRepository(UserOrganizatonApp::class)->findOneBy(['userOrganization' => $u,
|
|
'role' => $roleAdmin,
|
|
'isActive' => true]);
|
|
if ($uoa && $this->security->isGranted('ROLE_ADMIN')) {
|
|
$orgIds[] = $u->getOrganization()->getId();
|
|
}
|
|
}
|
|
}
|
|
} catch (EntityNotFoundException $e) {
|
|
throw new EntityNotFoundException("Error while fetching organizations ids where the user is admin");
|
|
}
|
|
|
|
return array_unique($orgIds);
|
|
}
|
|
|
|
/**
|
|
* Get the user by their identifier.
|
|
*
|
|
* @param string $userIdentifier
|
|
* @return User|null
|
|
* @throws Exception
|
|
*/
|
|
public function getUserByIdentifier(string $userIdentifier): ?User
|
|
{
|
|
$user = $this->entityManager->getRepository(User::class)->findOneBy(['email' => $userIdentifier]);
|
|
if (!$user) {
|
|
throw new EntityNotFoundException(self::NOT_FOUND);
|
|
}
|
|
return $user;
|
|
}
|
|
|
|
/**
|
|
* Get users grouped by their organizations for the index page.
|
|
* This method should return an array of users grouped by their organizations.
|
|
*
|
|
* @return array
|
|
*/
|
|
public function groupByOrganization(array $usersOrganizations): array
|
|
{
|
|
$grouped = [];
|
|
|
|
foreach ($usersOrganizations as $userOrg) {
|
|
$org = $userOrg->getOrganization();
|
|
if (!$org) {
|
|
continue;
|
|
}
|
|
|
|
$orgId = $org->getId();
|
|
$orgName = $org->getName();
|
|
|
|
if (!isset($grouped[$orgId])) {
|
|
$grouped[$orgId] = [
|
|
'id' => $orgId,
|
|
'name' => $orgName,
|
|
'users' => [],
|
|
];
|
|
}
|
|
|
|
$user = $userOrg->getUsers();
|
|
$grouped[$orgId]['users'][] = [
|
|
'entity' => $user,
|
|
'connected' => $this->isUserConnected($user->getUserIdentifier()),
|
|
'isActive' => (bool)$userOrg->isActive()
|
|
];
|
|
}
|
|
|
|
return $grouped;
|
|
}
|
|
|
|
/**
|
|
* Format users without organization for admin view.
|
|
*
|
|
* @param array $users
|
|
* @return array
|
|
*/
|
|
public function formatNoOrgUsersAsAssoc(array $noOrgUsers): array
|
|
{
|
|
$group = [
|
|
'id' => null,
|
|
'name' => 'Utilisateurs',
|
|
'users' => [],
|
|
];
|
|
|
|
foreach ($noOrgUsers as $user) {
|
|
$group['users'][] = [
|
|
'entity' => $user,
|
|
'connected' => $this->isUserConnected($user->getUserIdentifier()),
|
|
];
|
|
}
|
|
|
|
// Use a fixed key (e.g., 0 or 'none') to avoid collisions with real org IDs
|
|
return ['none' => $group];
|
|
}
|
|
|
|
public function handleProfilePicture(User $user, $logoFile): void
|
|
{
|
|
// Get file extension
|
|
$extension = $logoFile->guessExtension();
|
|
|
|
// Create custom filename: userNameUserSurname_ddmmyyhhmmss
|
|
$customFilename = $user->getName() . $user->getSurname() . '_' . date('dmyHis') . '.' . $extension;
|
|
|
|
// Define upload directory
|
|
$uploadDirectory = $this->profileDirectory;
|
|
// Create directory if it doesn't exist
|
|
if (!is_dir($uploadDirectory) && !mkdir($uploadDirectory, 0755, true) && !is_dir($uploadDirectory)) {
|
|
throw new DirectoryCouldNotBeCreatedException(sprintf('Directory "%s" was not created', $uploadDirectory));
|
|
}
|
|
try {
|
|
|
|
// Move the file to the upload directory
|
|
$logoFile->move($uploadDirectory, $customFilename);
|
|
|
|
// Update user entity with the file path (relative to public directory)
|
|
$user->setPictureUrl('uploads/profile/' . $customFilename);
|
|
|
|
} catch (FileException $e) {
|
|
// Handle upload error
|
|
throw new FileException('File upload failed: ' . $e->getMessage());
|
|
}
|
|
}
|
|
}
|