304 lines
12 KiB
PHP
304 lines
12 KiB
PHP
<?php
|
||
|
||
namespace App\Controllers;
|
||
|
||
use App\Models\OrganizationUserModel;
|
||
use App\Models\UserModel;
|
||
use App\Models\OrganizationModel;
|
||
use App\Services\InvitationService;
|
||
use App\Services\AccessService;
|
||
|
||
class InvitationController extends BaseController
|
||
{
|
||
protected InvitationService $invitationService;
|
||
|
||
public function __construct()
|
||
{
|
||
$this->invitationService = new InvitationService();
|
||
}
|
||
|
||
/**
|
||
* Страница принятия/отклонения приглашения
|
||
*/
|
||
public function accept(string $token)
|
||
{
|
||
$invitation = $this->invitationService->orgUserModel->findByInviteToken($token);
|
||
|
||
if (!$invitation) {
|
||
// Проверяем, есть ли приглашение с таким токеном (может быть истекшим)
|
||
$db = \Config\Database::connect();
|
||
$expiredInvitation = $db->table('organization_users')
|
||
->where('invite_token', $token)
|
||
->get()
|
||
->getRowArray();
|
||
|
||
if ($expiredInvitation && !empty($expiredInvitation['invite_expires_at'])) {
|
||
$expiredAt = strtotime($expiredInvitation['invite_expires_at']);
|
||
$isExpired = $expiredAt < time();
|
||
|
||
return $this->renderTwig('organizations/invitation_expired', [
|
||
'title' => $isExpired ? 'Приглашение истекло' : 'Приглашение недействительно',
|
||
'expired' => $isExpired,
|
||
'expired_at' => $expiredInvitation['invite_expires_at'] ?? null,
|
||
]);
|
||
}
|
||
|
||
return $this->renderTwig('organizations/invitation_expired', [
|
||
'title' => 'Приглашение недействительно',
|
||
]);
|
||
}
|
||
|
||
// Получаем данные организации
|
||
$orgModel = new OrganizationModel();
|
||
$organization = $orgModel->find($invitation['organization_id']);
|
||
|
||
// Получаем данные приглашающего
|
||
$invitedByUser = null;
|
||
if ($invitation['invited_by']) {
|
||
$userModel = new UserModel();
|
||
$invitedByUser = $userModel->find($invitation['invited_by']);
|
||
}
|
||
|
||
// Определяем, авторизован ли пользователь
|
||
$currentUserId = session()->get('user_id');
|
||
$isLoggedIn = !empty($currentUserId);
|
||
|
||
// Если пользователь авторизован - проверяем, тот ли это email
|
||
$emailMatches = true;
|
||
if ($isLoggedIn && $invitation['user_id']) {
|
||
$currentUser = (new UserModel())->find($currentUserId);
|
||
// Проверяем, тот ли пользователь (по ID или email)
|
||
$emailMatches = ($currentUserId == $invitation['user_id']);
|
||
}
|
||
|
||
// Метка роли
|
||
$roleLabels = [
|
||
'owner' => 'Владелец',
|
||
'admin' => 'Администратор',
|
||
'manager' => 'Менеджер',
|
||
'guest' => 'Гость',
|
||
];
|
||
|
||
return $this->renderTwig('organizations/invitation_accept', [
|
||
'title' => 'Приглашение в ' . ($organization['name'] ?? 'организацию'),
|
||
'token' => $token,
|
||
'organization' => $organization,
|
||
'role' => $invitation['role'],
|
||
'role_label' => $roleLabels[$invitation['role']] ?? $invitation['role'],
|
||
'invited_by' => $invitedByUser,
|
||
'invited_at' => $invitation['invited_at'],
|
||
'is_logged_in' => $isLoggedIn,
|
||
'email_matches' => $emailMatches,
|
||
'current_user_id'=> $currentUserId,
|
||
]);
|
||
}
|
||
|
||
/**
|
||
* Обработка принятия приглашения
|
||
*/
|
||
public function processAccept()
|
||
{
|
||
$token = $this->request->getPost('token');
|
||
$action = $this->request->getPost('action');
|
||
|
||
if ($action === 'decline') {
|
||
return $this->decline($token);
|
||
}
|
||
|
||
$userId = session()->get('user_id');
|
||
|
||
// Если пользователь не авторизован - редирект на страницу создания пароля
|
||
if (!$userId) {
|
||
return redirect()->to('/invitation/complete/' . $token);
|
||
}
|
||
|
||
// Принимаем приглашение
|
||
$result = $this->invitationService->acceptInvitation($token, $userId);
|
||
|
||
if (!$result['success']) {
|
||
session()->setFlashdata('error', $result['message']);
|
||
return redirect()->to('/invitation/accept/' . $token);
|
||
}
|
||
|
||
session()->setFlashdata('success', 'Вы приняли приглашение в организацию "' . $result['organization_name'] . '"');
|
||
|
||
// Переключаем организацию и редиректим на главную
|
||
session()->set('active_org_id', $result['organization_id']);
|
||
(new AccessService())->resetCache();
|
||
|
||
return redirect()->to('/');
|
||
}
|
||
|
||
/**
|
||
* Отклонение приглашения
|
||
*/
|
||
public function decline(string $token)
|
||
{
|
||
$result = $this->invitationService->declineInvitation($token);
|
||
|
||
if (!$result['success']) {
|
||
session()->setFlashdata('error', $result['message']);
|
||
} else {
|
||
session()->setFlashdata('info', 'Приглашение отклонено');
|
||
}
|
||
|
||
return redirect()->to('/');
|
||
}
|
||
|
||
/**
|
||
* Страница завершения регистрации (для новых пользователей)
|
||
*/
|
||
public function complete(string $token)
|
||
{
|
||
$invitation = $this->invitationService->orgUserModel->findByInviteToken($token);
|
||
|
||
if (!$invitation) {
|
||
// Проверяем, есть ли приглашение с таким токеном (может быть истекшим)
|
||
$db = \Config\Database::connect();
|
||
$expiredInvitation = $db->table('organization_users')
|
||
->where('invite_token', $token)
|
||
->get()
|
||
->getRowArray();
|
||
|
||
if ($expiredInvitation && !empty($expiredInvitation['invite_expires_at'])) {
|
||
$expiredAt = strtotime($expiredInvitation['invite_expires_at']);
|
||
$isExpired = $expiredAt < time();
|
||
|
||
return $this->renderTwig('organizations/invitation_expired', [
|
||
'title' => $isExpired ? 'Приглашение истекло' : 'Приглашение недействительно',
|
||
'expired' => $isExpired,
|
||
'expired_at' => $expiredInvitation['invite_expires_at'] ?? null,
|
||
]);
|
||
}
|
||
|
||
return $this->renderTwig('organizations/invitation_expired', [
|
||
'title' => 'Приглашение недействительно',
|
||
]);
|
||
}
|
||
|
||
// Если пользователь уже авторизован и это его приглашение
|
||
$userId = session()->get('user_id');
|
||
if ($userId && $invitation['user_id'] == $userId) {
|
||
return redirect()->to('/invitation/accept/' . $token);
|
||
}
|
||
|
||
// Получаем данные организации
|
||
$orgModel = new OrganizationModel();
|
||
$organization = $orgModel->find($invitation['organization_id']);
|
||
|
||
// Метка роли
|
||
$roleLabels = [
|
||
'owner' => 'Владелец',
|
||
'admin' => 'Администратор',
|
||
'manager' => 'Менеджер',
|
||
'guest' => 'Гость',
|
||
];
|
||
|
||
return $this->renderTwig('organizations/invitation_complete', [
|
||
'title' => 'Завершение регистрации',
|
||
'token' => $token,
|
||
'email' => $invitation['user_id'] ? '' : '', // Email возьмем из теневой записи
|
||
'organization' => $organization,
|
||
'role' => $invitation['role'],
|
||
'role_label' => $roleLabels[$invitation['role']] ?? $invitation['role'],
|
||
]);
|
||
}
|
||
|
||
/**
|
||
* Обработка завершения регистрации
|
||
*/
|
||
public function processComplete()
|
||
{
|
||
$token = $this->request->getPost('token');
|
||
$name = $this->request->getPost('name');
|
||
$password = $this->request->getPost('password');
|
||
$passwordConfirm = $this->request->getPost('password_confirm');
|
||
|
||
// Валидация
|
||
$errors = [];
|
||
|
||
if (empty($name) || strlen($name) < 2) {
|
||
$errors[] = 'Имя должно содержать минимум 2 символа';
|
||
}
|
||
|
||
if (empty($password) || strlen($password) < 8) {
|
||
$errors[] = 'Пароль должен содержать минимум 8 символов';
|
||
}
|
||
|
||
if ($password !== $passwordConfirm) {
|
||
$errors[] = 'Пароли не совпадают';
|
||
}
|
||
|
||
if (!empty($errors)) {
|
||
return redirect()->back()->withInput()->with('errors', $errors);
|
||
}
|
||
|
||
$invitation = $this->invitationService->orgUserModel->findByInviteToken($token);
|
||
|
||
if (!$invitation) {
|
||
return redirect()->to('/');
|
||
}
|
||
|
||
// Находим теневую запись пользователя
|
||
$userModel = new UserModel();
|
||
|
||
// Если user_id указан - обновляем существующего пользователя
|
||
if ($invitation['user_id']) {
|
||
$user = $userModel->find($invitation['user_id']);
|
||
if (!$user) {
|
||
session()->setFlashdata('error', 'Пользователь не найден');
|
||
return redirect()->to('/invitation/complete/' . $token);
|
||
}
|
||
|
||
// Обновляем профиль
|
||
$userModel->update($user['id'], [
|
||
'name' => $name,
|
||
'password' => $password,
|
||
]);
|
||
|
||
$userId = $user['id'];
|
||
} else {
|
||
// Создаем нового пользователя (теневой + реальный)
|
||
// На самом деле у нас уже есть запись в users - нужно её "активировать"
|
||
$shadowUsers = $userModel->where('email', $userModel->getFindByEmail($invitation['organization_id']))->findAll();
|
||
// Логика для теневых пользователей требует доработки
|
||
|
||
session()->setFlashdata('error', 'Ошибка регистрации');
|
||
return redirect()->to('/invitation/complete/' . $token);
|
||
}
|
||
|
||
// Авторизуем пользователя
|
||
$user = $userModel->find($userId);
|
||
$this->loginUser($user);
|
||
|
||
// Принимаем приглашение
|
||
$result = $this->invitationService->acceptInvitation($token, $userId);
|
||
|
||
if (!$result['success']) {
|
||
session()->setFlashdata('error', $result['message']);
|
||
return redirect()->to('/');
|
||
}
|
||
|
||
session()->setFlashdata('success', 'Добро пожаловать! Вы успешно зарегистрировались и приняли приглашение.');
|
||
|
||
// Переключаем организацию
|
||
session()->set('active_org_id', $result['organization_id']);
|
||
(new AccessService())->resetCache();
|
||
|
||
return redirect()->to('/');
|
||
}
|
||
|
||
/**
|
||
* Авторизация пользователя после регистрации
|
||
*/
|
||
protected function loginUser(array $user): void
|
||
{
|
||
session()->set([
|
||
'user_id' => $user['id'],
|
||
'email' => $user['email'],
|
||
'name' => $user['name'],
|
||
'logged_in' => true,
|
||
]);
|
||
}
|
||
}
|