refactoring
This commit is contained in:
@@ -2,12 +2,12 @@
|
||||
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Models\OrganizationUserModel;
|
||||
use App\Services\AccessService;
|
||||
use CodeIgniter\Controller;
|
||||
use CodeIgniter\HTTP\RequestInterface;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use App\Models\OrganizationModel;
|
||||
use App\Services\AccessService;
|
||||
|
||||
/**
|
||||
* BaseController provides a convenient place for loading components
|
||||
@@ -29,6 +29,7 @@ abstract class BaseController extends Controller
|
||||
|
||||
protected $session;
|
||||
protected AccessService $access;
|
||||
protected ?OrganizationUserModel $orgUserModel = null;
|
||||
|
||||
/**
|
||||
* @return void
|
||||
@@ -51,11 +52,54 @@ abstract class BaseController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверка права на действие ( shortcut для $this->access->can() )
|
||||
*
|
||||
* @param string $action
|
||||
* @param string $resource
|
||||
* @return bool
|
||||
* Получение лениво инициализированной модели OrganizationUserModel
|
||||
*/
|
||||
protected function getOrgUserModel(): OrganizationUserModel
|
||||
{
|
||||
if ($this->orgUserModel === null) {
|
||||
$this->orgUserModel = new OrganizationUserModel();
|
||||
}
|
||||
return $this->orgUserModel;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// Методы для работы с пользователем и организацией
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Получение ID текущего пользователя
|
||||
*/
|
||||
protected function getCurrentUserId(): ?int
|
||||
{
|
||||
$userId = $this->session->get('user_id');
|
||||
return $userId ? (int) $userId : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получение данных текущего пользователя
|
||||
*/
|
||||
protected function getCurrentUser(): ?array
|
||||
{
|
||||
$userId = $this->getCurrentUserId();
|
||||
if (!$userId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$userModel = new \App\Models\UserModel();
|
||||
return $userModel->find($userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Получение ID активной организации
|
||||
*/
|
||||
protected function getActiveOrgId(): ?int
|
||||
{
|
||||
$orgId = $this->session->get('active_org_id');
|
||||
return $orgId ? (int) $orgId : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверка права на действие (shortcut для $this->access->can())
|
||||
*/
|
||||
protected function can(string $action, string $resource): bool
|
||||
{
|
||||
@@ -63,16 +107,165 @@ abstract class BaseController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверка роли (shortcut для $this->access->isRole() )
|
||||
*
|
||||
* @param string|array $roles
|
||||
* @return bool
|
||||
* Проверка роли (shortcut для $this->access->isRole())
|
||||
*/
|
||||
protected function isRole($roles): bool
|
||||
{
|
||||
return $this->access->isRole($roles);
|
||||
}
|
||||
|
||||
/**
|
||||
* Получение membership пользователя для организации
|
||||
*
|
||||
* @param int $orgId
|
||||
* @return array|null
|
||||
*/
|
||||
protected function getMembership(int $orgId): ?array
|
||||
{
|
||||
$userId = $this->getCurrentUserId();
|
||||
if (!$userId || !$orgId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->getOrgUserModel()
|
||||
->where('organization_id', $orgId)
|
||||
->where('user_id', $userId)
|
||||
->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* Получение membership с требованием наличия доступа
|
||||
* Бросает исключение если доступ запрещён
|
||||
*
|
||||
* @param int $orgId
|
||||
* @return array
|
||||
*/
|
||||
protected function requireMembership(int $orgId): array
|
||||
{
|
||||
$membership = $this->getMembership($orgId);
|
||||
if (!$membership) {
|
||||
throw new \RuntimeException('Доступ запрещён');
|
||||
}
|
||||
return $membership;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получение ID активной организации с требованием
|
||||
* Бросает исключение если организация не выбрана
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function requireActiveOrg(): int
|
||||
{
|
||||
$orgId = $this->getActiveOrgId();
|
||||
if (!$orgId) {
|
||||
throw new \RuntimeException('Организация не выбрана');
|
||||
}
|
||||
return $orgId;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// Методы для редиректов и ответов
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Редирект с сообщением об ошибке
|
||||
*/
|
||||
protected function redirectWithError(string $message, string $redirectUrl): ResponseInterface
|
||||
{
|
||||
if ($this->request->isAJAX()) {
|
||||
return service('response')
|
||||
->setStatusCode(403)
|
||||
->setJSON(['error' => $message]);
|
||||
}
|
||||
|
||||
$this->session->setFlashdata('error', $message);
|
||||
return redirect()->to($redirectUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Редирект с сообщением об успехе
|
||||
*/
|
||||
protected function redirectWithSuccess(string $message, string $redirectUrl): ResponseInterface
|
||||
{
|
||||
if ($this->request->isAJAX()) {
|
||||
return service('response')
|
||||
->setStatusCode(200)
|
||||
->setJSON(['success' => true, 'message' => $message]);
|
||||
}
|
||||
|
||||
$this->session->setFlashdata('success', $message);
|
||||
return redirect()->to($redirectUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ответ для AJAX запросов с ошибкой доступа
|
||||
*/
|
||||
protected function forbiddenResponse(string $message = 'Доступ запрещён'): ResponseInterface
|
||||
{
|
||||
return service('response')
|
||||
->setStatusCode(403)
|
||||
->setJSON(['error' => $message]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ответ для AJAX запросов с ошибкой валидации
|
||||
*/
|
||||
protected function validationErrorResponse(string $message = 'Ошибка валидации', array $errors = []): ResponseInterface
|
||||
{
|
||||
return service('response')
|
||||
->setStatusCode(422)
|
||||
->setJSON([
|
||||
'success' => false,
|
||||
'message' => $message,
|
||||
'errors' => $errors,
|
||||
]);
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// Утилиты для вывода
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Форматирование времени блокировки для отображения
|
||||
*/
|
||||
protected function formatBlockTime(int $seconds): string
|
||||
{
|
||||
if ($seconds >= 60) {
|
||||
$minutes = ceil($seconds / 60);
|
||||
return $minutes . ' ' . $this->pluralize($minutes, ['минуту', 'минуты', 'минут']);
|
||||
}
|
||||
return $seconds . ' ' . $this->pluralize($seconds, ['секунду', 'секунды', 'секунд']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Склонение окончаний для чисел
|
||||
*
|
||||
* @param int $number
|
||||
* @param array $forms [одна, две, пять]
|
||||
* @return string
|
||||
*/
|
||||
protected function pluralize(int $number, array $forms): string
|
||||
{
|
||||
$abs = abs($number);
|
||||
$mod = $abs % 10;
|
||||
|
||||
if ($abs % 100 >= 11 && $abs % 100 <= 19) {
|
||||
return $forms[2];
|
||||
}
|
||||
if ($mod === 1) {
|
||||
return $forms[0];
|
||||
}
|
||||
if ($mod >= 2 && $mod <= 4) {
|
||||
return $forms[1];
|
||||
}
|
||||
return $forms[2];
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// Рендеринг Twig
|
||||
// ========================================
|
||||
|
||||
public function renderTwig($template, $data = [])
|
||||
{
|
||||
helper('csrf');
|
||||
@@ -154,7 +347,7 @@ abstract class BaseController extends Controller
|
||||
// Старый способ извлечения фильтров для совместимости
|
||||
foreach ($this->request->getGet() as $key => $value) {
|
||||
if (str_starts_with($key, 'filters[') && str_ends_with($key, ']')) {
|
||||
$field = substr($key, 8, -1); // Исправлено: было 9, должно быть 8
|
||||
$field = substr($key, 8, -1);
|
||||
$filters[$field] = $value;
|
||||
}
|
||||
}
|
||||
@@ -185,7 +378,7 @@ abstract class BaseController extends Controller
|
||||
if ($value === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
// Сначала проверяем fieldMap (алиасы) — они имеют приоритет
|
||||
if (isset($config['fieldMap']) && isset($config['fieldMap'][$filterKey])) {
|
||||
$realField = $config['fieldMap'][$filterKey];
|
||||
@@ -222,7 +415,7 @@ abstract class BaseController extends Controller
|
||||
];
|
||||
|
||||
$data = [
|
||||
'items' => $items, // Алиас для универсального шаблона
|
||||
'items' => $items,
|
||||
'pagerDetails' => $pagerData,
|
||||
'perPage' => $perPage,
|
||||
'sort' => $sort,
|
||||
@@ -259,7 +452,6 @@ abstract class BaseController extends Controller
|
||||
$tableData['actions'] = $config['actions'] ?? false;
|
||||
$tableData['actionsConfig'] = $config['actionsConfig'] ?? [];
|
||||
$tableData['columns'] = $config['columns'] ?? [];
|
||||
$tableData['onRowClick'] = $config['onRowClick'] ?? null;
|
||||
|
||||
// Параметры для пустого состояния
|
||||
$tableData['emptyMessage'] = $config['emptyMessage'] ?? 'Нет данных';
|
||||
@@ -287,17 +479,17 @@ abstract class BaseController extends Controller
|
||||
public function table(?array $config = null, ?string $pageUrl = null)
|
||||
{
|
||||
$isPartial = $this->request->getGet('format') === 'partial' || $this->isAjax();
|
||||
|
||||
|
||||
// Если это частичный запрос (AJAX) — возвращаем только таблицу
|
||||
if ($isPartial) {
|
||||
return $this->renderTable($config, true);
|
||||
}
|
||||
|
||||
|
||||
// Прямой запрос к /table — редиректим на основную страницу
|
||||
// Сохраняем все параметры: page, perPage, sort, order, filters
|
||||
$params = $this->request->getGet();
|
||||
unset($params['format']); // Убираем format=partial если был
|
||||
|
||||
|
||||
if ($pageUrl) {
|
||||
$redirectUrl = $pageUrl;
|
||||
} else {
|
||||
@@ -305,11 +497,11 @@ abstract class BaseController extends Controller
|
||||
$tableUrl = $config['url'] ?? '/table';
|
||||
$redirectUrl = $tableUrl;
|
||||
}
|
||||
|
||||
|
||||
if (!empty($params)) {
|
||||
$redirectUrl .= '?' . http_build_query($params);
|
||||
}
|
||||
|
||||
|
||||
return redirect()->to($redirectUrl);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user