419 lines
17 KiB
PHP
Executable File
419 lines
17 KiB
PHP
Executable File
<?php
|
|
|
|
namespace Modules\Users\Controllers;
|
|
|
|
use App\Controllers\BaseAdminController;
|
|
use Symfony\Component\HttpFoundation\Request;
|
|
use Symfony\Component\HttpFoundation\Response;
|
|
use Symfony\Component\HttpFoundation\RedirectResponse;
|
|
use Modules\Auth\Models\User;
|
|
use App\Models\Module;
|
|
use App\Models\UserPermission;
|
|
use Intervention\Image\ImageManagerStatic as Image;
|
|
|
|
class UsersController extends BaseAdminController
|
|
{
|
|
public function index(Request $request): Response
|
|
{
|
|
$currentUser = $this->getCurrentUser();
|
|
|
|
if ($currentUser->isSuperAdmin()) {
|
|
$users = User::all();
|
|
} else {
|
|
// Admins only see users from their company
|
|
$users = User::where('users_bus_id', $currentUser->users_bus_id)->get();
|
|
}
|
|
|
|
return $this->renderAdminView('users/views/admin/index.twig', [
|
|
'users' => $users,
|
|
'title' => __('users.titles.management')
|
|
]);
|
|
}
|
|
|
|
public function create(Request $request): Response
|
|
{
|
|
$error = $request->query->get('error');
|
|
$errorMessage = $error ? $this->getErrorMessage($error) : null;
|
|
|
|
return $this->renderAdminView('users/views/admin/form.twig', [
|
|
'title' => __('users.titles.create'),
|
|
'error' => $errorMessage,
|
|
'target_user' => null
|
|
]);
|
|
}
|
|
|
|
public function store(Request $request): Response
|
|
{
|
|
$currentUserId = $_SESSION['user_id'] ?? null;
|
|
$currentUserRole = $_SESSION['user_role'] ?? null;
|
|
$currentUserBus = $_SESSION['user_idbus'] ?? null;
|
|
|
|
$data = $request->request->all();
|
|
|
|
// Basic validation
|
|
if (empty($data['name']) || empty($data['email']) || empty($data['password'])) {
|
|
return new RedirectResponse(url('admin/users/create?error=missing_fields'));
|
|
}
|
|
|
|
// Email format validation
|
|
if (!filter_var($data['email'], FILTER_VALIDATE_EMAIL)) {
|
|
return new RedirectResponse(url('admin/users/create?error=invalid_email'));
|
|
}
|
|
|
|
// Check if email exists
|
|
if (User::where('users_email', $data['email'])->exists()) {
|
|
return new RedirectResponse(url('admin/users/create?error=email_exists'));
|
|
}
|
|
|
|
// Password strength validation
|
|
if (strlen($data['password']) < 8 || !preg_match('/[A-Za-z]/', $data['password']) || !preg_match('/[0-9]/', $data['password'])) {
|
|
return new RedirectResponse(url('admin/users/create?error=weak_password'));
|
|
}
|
|
|
|
$requestedRole = $data['role'] ?? 'user';
|
|
|
|
// Prevent non-super_admin from creating super_admin
|
|
if ($currentUserRole !== 'super_admin' && $requestedRole === 'super_admin') {
|
|
$requestedRole = 'user';
|
|
}
|
|
|
|
$user = new User();
|
|
$user->users_name = $data['name'];
|
|
$user->users_email = $data['email'];
|
|
$user->users_password = $data['password'];
|
|
$user->users_role = $requestedRole;
|
|
$user->users_status = $data['status'] ?? 'active';
|
|
$user->users_bus_id = $currentUserRole === 'super_admin' ? ($data['bus_id'] ?? $currentUserBus) : $currentUserBus;
|
|
$user->save();
|
|
|
|
if ($request->files->has('avatar')) {
|
|
$file = $request->files->get('avatar');
|
|
if ($file && $file->isValid()) {
|
|
$allowedMimes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
|
|
if (in_array($file->getMimeType(), $allowedMimes)) {
|
|
$uploadDir = dirname(__DIR__, 4) . '/storage/uploads/avatars';
|
|
if (!file_exists($uploadDir)) {
|
|
mkdir($uploadDir, 0755, true);
|
|
}
|
|
$formatSetting = strtolower((string) get_env('IMG_FORMAT', 'webp'));
|
|
$format = $formatSetting === 'auto' ? 'webp' : $formatSetting;
|
|
$quality = env_int('IMG_QUALITY', 80);
|
|
$resolutionPercent = env_int('IMG_RESOLUTION', 100);
|
|
$image = Image::make($file->getPathname());
|
|
$width = $image->width();
|
|
$height = $image->height();
|
|
if ($resolutionPercent > 0 && $resolutionPercent < 100) {
|
|
$newW = (int) round($width * $resolutionPercent / 100);
|
|
$newH = (int) round($height * $resolutionPercent / 100);
|
|
$image->resize($newW, $newH, function ($constraint) {
|
|
$constraint->aspectRatio();
|
|
$constraint->upsize();
|
|
});
|
|
}
|
|
$ext = $format === 'jpeg' || $format === 'jpg' ? 'jpg' : ($format === 'png' ? 'png' : ($format === 'gif' ? 'gif' : 'webp'));
|
|
$filename = 'avatar_' . $user->users_id . '_' . time() . '.' . $ext;
|
|
if ($format === 'jpeg' || $format === 'jpg') {
|
|
$image->interlace(true);
|
|
$image->encode('jpeg', max(1, min(100, $quality)));
|
|
} elseif ($format === 'png') {
|
|
$pngCompression = max(0, min(9, (int) round((100 - max(1, min(100, $quality))) / 10)));
|
|
$image->encode('png', $pngCompression);
|
|
} elseif ($format === 'gif') {
|
|
$image->encode('gif');
|
|
} else {
|
|
$image->encode('webp', max(1, min(100, $quality)));
|
|
}
|
|
$image->save($uploadDir . '/' . $filename);
|
|
$user->users_avatar = $filename;
|
|
$user->save();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Restore session to ensure super_admin remains logged
|
|
$_SESSION['user_id'] = $currentUserId;
|
|
$_SESSION['user_role'] = $currentUserRole;
|
|
$_SESSION['user_idbus'] = $currentUserBus;
|
|
|
|
return new RedirectResponse(url('admin/users'));
|
|
}
|
|
|
|
public function show(Request $request, string $id): Response
|
|
{
|
|
$currentUser = $this->getCurrentUser();
|
|
$query = User::where('users_id', $id);
|
|
|
|
if (!$currentUser->isSuperAdmin()) {
|
|
$query->where('users_bus_id', $currentUser->users_bus_id);
|
|
}
|
|
|
|
$user = $query->first();
|
|
|
|
if (!$user) {
|
|
return new RedirectResponse(url('admin/users'));
|
|
}
|
|
|
|
return $this->renderAdminView('users/views/admin/show.twig', [
|
|
'user' => $user,
|
|
'title' => 'User Details'
|
|
]);
|
|
}
|
|
|
|
public function edit(Request $request, string $id): Response
|
|
{
|
|
$currentUser = $this->getCurrentUser();
|
|
$query = User::where('users_id', $id);
|
|
|
|
if (!$currentUser->isSuperAdmin()) {
|
|
$query->where('users_bus_id', $currentUser->users_bus_id);
|
|
}
|
|
|
|
$targetUser = $query->first();
|
|
|
|
if (!$targetUser) {
|
|
return new RedirectResponse(url('admin/users'));
|
|
}
|
|
|
|
$error = $request->query->get('error');
|
|
$errorMessage = $error ? $this->getErrorMessage($error) : null;
|
|
|
|
return $this->renderAdminView('users/views/admin/form.twig', [
|
|
'target_user' => $targetUser,
|
|
'title' => 'Edit User',
|
|
'error' => $errorMessage
|
|
]);
|
|
}
|
|
|
|
public function update(Request $request, string $id): Response
|
|
{
|
|
$currentUser = $this->getCurrentUser();
|
|
$currentUserId = $_SESSION['user_id'] ?? null;
|
|
$currentUserRole = $_SESSION['user_role'] ?? null;
|
|
$currentUserBus = $_SESSION['user_idbus'] ?? null;
|
|
|
|
$query = User::where('users_id', $id);
|
|
|
|
if (!$currentUser->isSuperAdmin()) {
|
|
$query->where('users_bus_id', $currentUser->users_bus_id);
|
|
}
|
|
|
|
$user = $query->first();
|
|
|
|
if (!$user) {
|
|
return new RedirectResponse(url('admin/users'));
|
|
}
|
|
|
|
$data = $request->request->all();
|
|
|
|
// Basic validation
|
|
if (empty($data['name']) || empty($data['email'])) {
|
|
return new RedirectResponse(url('admin/users/' . $id . '/edit?error=missing_fields'));
|
|
}
|
|
|
|
// Email format validation
|
|
if (!filter_var($data['email'], FILTER_VALIDATE_EMAIL)) {
|
|
return new RedirectResponse(url('admin/users/' . $id . '/edit?error=invalid_email'));
|
|
}
|
|
|
|
// Check if email exists (exclude current user)
|
|
if ($data['email'] !== $user->users_email && User::where('users_email', $data['email'])->exists()) {
|
|
return new RedirectResponse(url('admin/users/' . $id . '/edit?error=email_exists'));
|
|
}
|
|
|
|
$requestedRole = $data['role'] ?? $user->users_role;
|
|
|
|
// Prevent non-super_admin from promoting to super_admin or changing a super_admin
|
|
if ($currentUserRole !== 'super_admin') {
|
|
if ($requestedRole === 'super_admin' || $user->users_role === 'super_admin') {
|
|
$requestedRole = $user->users_role;
|
|
}
|
|
}
|
|
|
|
$user->users_name = $data['name'];
|
|
$user->users_email = $data['email'];
|
|
$user->users_role = $requestedRole;
|
|
$user->users_status = $data['status'] ?? 'active';
|
|
|
|
// Only super_admin can change company of a user
|
|
if ($currentUserRole === 'super_admin' && isset($data['bus_id'])) {
|
|
$user->users_bus_id = $data['bus_id'];
|
|
}
|
|
|
|
if (!empty($data['password'])) {
|
|
// Password strength validation
|
|
if (strlen($data['password']) < 8 || !preg_match('/[A-Za-z]/', $data['password']) || !preg_match('/[0-9]/', $data['password'])) {
|
|
return new RedirectResponse(url('admin/users/' . $id . '/edit?error=weak_password'));
|
|
}
|
|
$user->users_password = $data['password']; // Mutator handles hashing
|
|
}
|
|
|
|
if ($request->files->has('avatar')) {
|
|
$file = $request->files->get('avatar');
|
|
if ($file && $file->isValid()) {
|
|
$allowedMimes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
|
|
if (in_array($file->getMimeType(), $allowedMimes)) {
|
|
$uploadDir = dirname(__DIR__, 4) . '/storage/uploads/avatars';
|
|
if (!file_exists($uploadDir)) {
|
|
mkdir($uploadDir, 0755, true);
|
|
}
|
|
$formatSetting = strtolower((string) get_env('IMG_FORMAT', 'webp'));
|
|
$format = $formatSetting === 'auto' ? 'webp' : $formatSetting;
|
|
$quality = env_int('IMG_QUALITY', 80);
|
|
$resolutionPercent = env_int('IMG_RESOLUTION', 100);
|
|
$image = Image::make($file->getPathname());
|
|
$width = $image->width();
|
|
$height = $image->height();
|
|
if ($resolutionPercent > 0 && $resolutionPercent < 100) {
|
|
$newW = (int) round($width * $resolutionPercent / 100);
|
|
$newH = (int) round($height * $resolutionPercent / 100);
|
|
$image->resize($newW, $newH, function ($constraint) {
|
|
$constraint->aspectRatio();
|
|
$constraint->upsize();
|
|
});
|
|
}
|
|
$ext = $format === 'jpeg' || $format === 'jpg' ? 'jpg' : ($format === 'png' ? 'png' : ($format === 'gif' ? 'gif' : 'webp'));
|
|
$filename = 'avatar_' . $user->users_id . '_' . time() . '.' . $ext;
|
|
if ($format === 'jpeg' || $format === 'jpg') {
|
|
$image->interlace(true);
|
|
$image->encode('jpeg', max(1, min(100, $quality)));
|
|
} elseif ($format === 'png') {
|
|
$pngCompression = max(0, min(9, (int) round((100 - max(1, min(100, $quality))) / 10)));
|
|
$image->encode('png', $pngCompression);
|
|
} elseif ($format === 'gif') {
|
|
$image->encode('gif');
|
|
} else {
|
|
$image->encode('webp', max(1, min(100, $quality)));
|
|
}
|
|
$image->save($uploadDir . '/' . $filename);
|
|
if ($user->users_avatar && file_exists($uploadDir . '/' . $user->users_avatar)) {
|
|
unlink($uploadDir . '/' . $user->users_avatar);
|
|
}
|
|
$user->users_avatar = $filename;
|
|
}
|
|
}
|
|
}
|
|
|
|
$user->save();
|
|
|
|
// Restore session to ensure super_admin remains logged
|
|
$_SESSION['user_id'] = $currentUserId;
|
|
$_SESSION['user_role'] = $currentUserRole;
|
|
$_SESSION['user_idbus'] = $currentUserBus;
|
|
|
|
return new RedirectResponse(url('admin/users'));
|
|
}
|
|
|
|
public function editPermissions(Request $request, string $id): Response
|
|
{
|
|
$currentUser = $this->getCurrentUser();
|
|
if (!$currentUser || (!$currentUser->isAdmin() && !$currentUser->isSuperAdmin())) {
|
|
return new RedirectResponse(url('admin/dashboard?error=unauthorized'));
|
|
}
|
|
|
|
$user = User::find($id);
|
|
if (!$user) {
|
|
return new RedirectResponse(url('admin/users'));
|
|
}
|
|
|
|
// Check if Admin is from the same company
|
|
if (!$currentUser->isSuperAdmin() && $user->users_bus_id !== $currentUser->users_bus_id) {
|
|
return new RedirectResponse(url('admin/users?error=unauthorized'));
|
|
}
|
|
|
|
// Get enabled modules for the company
|
|
$modules = $this->getEnabledModules($user);
|
|
|
|
// Get current user permissions
|
|
$userPermissions = UserPermission::where('user_permissions_user_id', $user->users_id)
|
|
->where('user_permissions_bus_id', $user->users_bus_id)
|
|
->pluck('user_permissions_permission')
|
|
->toArray();
|
|
|
|
return $this->renderAdminView('users/views/admin/permissions.twig', [
|
|
'target_user' => $user,
|
|
'modules' => $modules,
|
|
'user_permissions' => $userPermissions,
|
|
'title' => __('users.headers.user_permissions')
|
|
]);
|
|
}
|
|
|
|
public function updatePermissions(Request $request, string $id): Response
|
|
{
|
|
$currentUser = $this->getCurrentUser();
|
|
if (!$currentUser || (!$currentUser->isAdmin() && !$currentUser->isSuperAdmin())) {
|
|
return new RedirectResponse(url('admin/dashboard?error=unauthorized'));
|
|
}
|
|
|
|
$user = User::find($id);
|
|
if (!$user) {
|
|
return new RedirectResponse(url('admin/users'));
|
|
}
|
|
|
|
// Check if Admin is from the same company
|
|
if (!$currentUser->isSuperAdmin() && $user->users_bus_id !== $currentUser->users_bus_id) {
|
|
return new RedirectResponse(url('admin/users?error=unauthorized'));
|
|
}
|
|
|
|
$permissions = $request->request->all()['permissions'] ?? [];
|
|
|
|
// Remove existing permissions for this user in this company
|
|
UserPermission::where('user_permissions_user_id', $user->users_id)
|
|
->where('user_permissions_bus_id', $user->users_bus_id)
|
|
->delete();
|
|
|
|
// Add new permissions
|
|
foreach ($permissions as $permission) {
|
|
$userPermission = new UserPermission();
|
|
$userPermission->user_permissions_user_id = $user->users_id;
|
|
$userPermission->user_permissions_bus_id = $user->users_bus_id;
|
|
$userPermission->user_permissions_permission = $permission;
|
|
$userPermission->user_permissions_granted_by = $currentUser->users_id;
|
|
$userPermission->save();
|
|
}
|
|
|
|
return new RedirectResponse(url('admin/users/' . $id . '?success=permissions_updated'));
|
|
}
|
|
|
|
private function getErrorMessage(string $error): string
|
|
{
|
|
$messages = [
|
|
'missing_fields' => 'Please fill in all required fields.',
|
|
'email_exists' => 'This email address is already registered.',
|
|
'invalid_email' => 'Please enter a valid email address.',
|
|
'weak_password' => 'Password must be at least 8 characters long and contain at least one letter and one number.',
|
|
];
|
|
return $messages[$error] ?? 'An error occurred.';
|
|
}
|
|
|
|
public function destroy(Request $request, string $id): Response
|
|
{
|
|
$currentUser = $this->getCurrentUser();
|
|
$query = User::where('users_id', $id);
|
|
|
|
if (!$currentUser->isSuperAdmin()) {
|
|
$query->where('users_bus_id', $currentUser->users_bus_id);
|
|
}
|
|
|
|
$user = $query->first();
|
|
|
|
// Prevent deleting self and check if user exists/accessible
|
|
if ($user && $user->users_id != $_SESSION['user_id']) {
|
|
if ($user->users_avatar) {
|
|
$uploadDir = dirname(__DIR__, 4) . '/storage/uploads/avatars';
|
|
if (file_exists($uploadDir . '/' . $user->users_avatar)) {
|
|
unlink($uploadDir . '/' . $user->users_avatar);
|
|
}
|
|
}
|
|
$user->delete();
|
|
}
|
|
return new RedirectResponse(url('admin/users'));
|
|
}
|
|
|
|
public function manual(Request $request): Response
|
|
{
|
|
return $this->renderAdminView('users/views/admin/manual.twig', [
|
|
'title' => 'Manual do Usuário - Usuários e Permissões'
|
|
]);
|
|
}
|
|
}
|