bp/app/Controllers/Profile.php

246 lines
8.5 KiB
PHP

<?php
namespace App\Controllers;
use App\Models\UserModel;
use App\Models\OrganizationModel;
use App\Models\OrganizationUserModel;
use App\Services\AccessService;
/**
* ProfileController - Управление профилем пользователя
*/
class Profile extends BaseController
{
protected UserModel $userModel;
protected OrganizationModel $orgModel;
public function __construct()
{
$this->userModel = new UserModel();
$this->orgModel = new OrganizationModel();
}
/**
* Главная страница профиля
*/
public function index()
{
$userId = $this->getCurrentUserId();
$user = $this->userModel->find($userId);
return $this->renderTwig('profile/index', [
'title' => 'Профиль',
'user' => $user,
'active_tab' => 'general',
]);
}
/**
* Вкладка "Организации"
*/
public function organizations()
{
$userId = $this->getCurrentUserId();
$user = $this->userModel->find($userId);
$currentOrgId = $this->session->get('active_org_id');
// Получаем все организации пользователя
$orgUserModel = $this->getOrgUserModel();
$memberships = $orgUserModel->where('user_id', $userId)->findAll();
$orgIds = array_column($memberships, 'organization_id');
$organizations = [];
if (!empty($orgIds)) {
$organizations = $this->orgModel->whereIn('id', $orgIds)->findAll();
}
// Объединяем данные
$orgList = [];
foreach ($organizations as $org) {
// Находим соответствующий membership
$membership = null;
foreach ($memberships as $m) {
if ($m['organization_id'] == $org['id']) {
$membership = $m;
break;
}
}
$orgList[] = [
'id' => $org['id'],
'name' => $org['name'],
'type' => $org['type'],
'role' => $membership['role'] ?? 'guest',
'status' => $membership['status'] ?? 'active',
'joined_at' => $membership['joined_at'] ?? null,
'is_owner' => ($membership['role'] ?? '') === 'owner',
'is_current_org' => ((int) $org['id'] === (int) $currentOrgId),
];
}
return $this->renderTwig('profile/organizations', [
'title' => 'Мои организации',
'user' => $user,
'organizations' => $orgList,
'active_tab' => 'organizations',
]);
}
/**
* Вкладка "Безопасность"
*/
public function security()
{
$userId = $this->getCurrentUserId();
$user = $this->userModel->find($userId);
return $this->renderTwig('profile/security', [
'title' => 'Безопасность',
'user' => $user,
'active_tab' => 'security',
]);
}
/**
* Обновление имени пользователя
*/
public function updateName()
{
$userId = $this->getCurrentUserId();
$name = trim($this->request->getPost('name'));
if (empty($name)) {
$this->session->setFlashdata('error', 'Имя обязательно для заполнения');
return redirect()->to('/profile');
}
if (strlen($name) < 3) {
$this->session->setFlashdata('error', 'Имя должно содержать минимум 3 символа');
return redirect()->to('/profile');
}
$this->userModel->update($userId, ['name' => $name]);
$this->session->set('name', $name);
$this->session->setFlashdata('success', 'Имя успешно обновлено');
return redirect()->to('/profile');
}
/**
* Загрузка аватара
*/
public function uploadAvatar()
{
$userId = $this->getCurrentUserId();
$file = $this->request->getFile('avatar');
if (!$file || !$file->isValid()) {
$this->session->setFlashdata('error', 'Ошибка загрузки файла');
return redirect()->to('/profile');
}
// Валидация
$allowedTypes = ['image/jpeg', 'image/png', 'image/gif'];
$maxSize = 2 * 1024 * 1024; // 2MB
if (!in_array($file->getMimeType(), $allowedTypes)) {
$this->session->setFlashdata('error', 'Разрешены только файлы JPG, PNG и GIF');
return redirect()->to('/profile');
}
if ($file->getSize() > $maxSize) {
$this->session->setFlashdata('error', 'Максимальный размер файла - 2 МБ');
return redirect()->to('/profile');
}
// Создаём директорию для аватаров если нет
$uploadPath = ROOTPATH . 'public/uploads/avatars';
if (!is_dir($uploadPath)) {
mkdir($uploadPath, 0755, true);
}
// Генерируем уникальное имя файла
$extension = $file->getClientExtension();
$newFileName = 'avatar_' . $userId . '_' . time() . '.' . $extension;
// Перемещаем файл
$file->move($uploadPath, $newFileName);
// Удаляем старый аватар если был
$user = $this->userModel->find($userId);
if (!empty($user['avatar']) && file_exists($uploadPath . '/' . $user['avatar'])) {
@unlink($uploadPath . '/' . $user['avatar']);
}
// Обновляем путь к аватару в базе
$this->userModel->update($userId, ['avatar' => $newFileName]);
$this->session->setFlashdata('success', 'Аватар успешно загружен');
return redirect()->to('/profile');
}
/**
* Смена пароля
*/
public function changePassword()
{
$userId = $this->getCurrentUserId();
$user = $this->userModel->find($userId);
$currentPassword = $this->request->getPost('current_password');
$newPassword = $this->request->getPost('new_password');
$confirmPassword = $this->request->getPost('confirm_password');
// Валидация
if (empty($currentPassword)) {
$this->session->setFlashdata('error', 'Введите текущий пароль');
return redirect()->to('/profile/security');
}
if (empty($newPassword)) {
$this->session->setFlashdata('error', 'Введите новый пароль');
return redirect()->to('/profile/security');
}
if (strlen($newPassword) < 6) {
$this->session->setFlashdata('error', 'Новый пароль должен содержать минимум 6 символов');
return redirect()->to('/profile/security');
}
if ($newPassword !== $confirmPassword) {
$this->session->setFlashdata('error', 'Пароли не совпадают');
return redirect()->to('/profile/security');
}
// Проверяем текущий пароль
if (!password_verify($currentPassword, $user['password'])) {
$this->session->setFlashdata('error', 'Неверный текущий пароль');
return redirect()->to('/profile/security');
}
// Обновляем пароль
$this->userModel->update($userId, ['password' => $newPassword]);
// Завершаем все сессии пользователя (кроме текущей)
$this->endAllUserSessions($userId);
$this->session->setFlashdata('success', 'Пароль успешно изменён. Для безопасности вы будете разлогинены на всех устройствах.');
return redirect()->to('/logout');
}
/**
* Завершение всех сессий пользователя
*/
private function endAllUserSessions(int $userId): void
{
// Удаляем все remember-токены пользователя
$db = \Config\Database::connect();
$db->table('remember_tokens')->where('user_id', $userId)->delete();
// Регенерируем ID текущей сессии
$this->session->regenerate(true);
}
}