Files
bp/app/Controllers/BaseController.php
T
2026-01-10 19:43:04 +03:00

233 lines
8.7 KiB
PHP

<?php
namespace App\Controllers;
use CodeIgniter\Controller;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
use Psr\Log\LoggerInterface;
use App\Models\OrganizationModel;
/**
* BaseController provides a convenient place for loading components
* and performing functions that are needed by all your controllers.
*
* Extend this class in any new controllers:
* ```
* class Home extends BaseController
* ```
*
* For security, be sure to declare any new methods as protected or private.
*/
abstract class BaseController extends Controller
{
/**
* Be sure to declare properties for any property fetch you initialized.
* The creation of dynamic property is deprecated in PHP 8.2.
*/
protected $session;
/**
* @return void
*/
public function initController(RequestInterface $request, ResponseInterface $response, LoggerInterface $logger)
{
// Load here all helpers you want to be available in your controllers that extend BaseController.
// Caution: Do not put the this below the parent::initController() call below.
// $this->helpers = ['form', 'url'];
// Caution: Do not edit this line.
parent::initController($request, $response, $logger);
// Preload any models, libraries, etc, here.
$this->session = service('session');
}
public function renderTwig($template, $data = [])
{
helper('csrf');
$twig = \Config\Services::twig();
// oldInput из сессии добавляется в данные шаблона
// Расширение TwigGlobalsExtension автоматически добавляет session, alerts, old, currentOrg
$oldInput = $this->session->get('_ci_old_input') ?? [];
$data['old'] = $data['old'] ?? $oldInput;
ob_start();
$twig->display($template, $data);
$content = ob_get_clean();
return $content;
}
// ========================================
// Методы для универсальных таблиц
// ========================================
/**
* Конфигурация таблицы - переопределяется в каждом контроллере
*/
protected function getTableConfig(): array
{
return [
'model' => null,
'columns' => [],
'searchable' => [],
'sortable' => [],
'defaultSort' => 'id',
'order' => 'asc',
'itemsKey' => 'items',
'scope' => null, // callable($builder) для дополнительных модификаций
];
}
/**
* Проверка AJAX запроса
*/
protected function isAjax(): bool
{
$header = $this->request->header('X-Requested-With');
$value = $header ? $header->getValue() : '';
return strtolower($value) === 'xmlhttprequest';
}
/**
* Подготовка данных таблицы (общая логика для всех таблиц)
*/
protected function prepareTableData(?array $config = null): array
{
$config = array_merge($this->getTableConfig(), $config ?? []);
$page = (int) ($this->request->getGet('page') ?? 1);
$perPage = (int) ($this->request->getGet('perPage') ?? 10);
$sort = $this->request->getGet('sort') ?? $config['defaultSort'];
$order = $this->request->getGet('order') ?? $config['order'];
// Исправление: получаем фильтры из параметра filters[]
$filters = [];
$rawFilters = $this->request->getGet('filters');
if ($rawFilters) {
if (is_array($rawFilters)) {
$filters = $rawFilters;
} else {
// Для обратной совместимости, если фильтры пришли в строке
parse_str($rawFilters, $filters);
if (isset($filters['filters'])) {
$filters = $filters['filters'];
}
}
} else {
// Старый способ извлечения фильтров для совместимости
foreach ($this->request->getGet() as $key => $value) {
if (str_starts_with($key, 'filters[') && str_ends_with($key, ']')) {
$field = substr($key, 8, -1); // Исправлено: было 9, должно быть 8
$filters[$field] = $value;
}
}
}
$model = $config['model'];
$builder = $model->builder();
// Сбрасываем все предыдущие условия
$builder->resetQuery();
if (isset($config['scope']) && is_callable($config['scope'])) {
$config['scope']($builder);
}
// Применяем фильтры
foreach ($filters as $field => $value) {
if ($value !== '' && in_array($field, $config['searchable'])) {
$builder->like($field, $value);
}
}
// Сортировка
if ($sort && in_array($sort, $config['sortable'])) {
$builder->orderBy($sort, $order);
}
// Исправлено: countAllResults(false) вместо countAll()
// Сохраняем текущее состояние builder для подсчета
$countBuilder = clone $builder;
$total = $countBuilder->countAllResults(false);
// Получаем данные с пагинацией
$builder->select('*');
$items = $builder->limit($perPage, ($page - 1) * $perPage)->get()->getResultArray();
$from = ($page - 1) * $perPage + 1;
$to = min($page * $perPage, $total);
$pagerData = [
'currentPage' => $page,
'pageCount' => $total > 0 ? (int) ceil($total / $perPage) : 1,
'total' => $total,
'perPage' => $perPage,
'from' => $from,
'to' => $to,
];
$data = [
'items' => $items, // Алиас для универсального шаблона
'pagerDetails' => $pagerData,
'perPage' => $perPage,
'sort' => $sort,
'order' => $order,
'filters' => $filters,
'columns' => $config['columns'],
'actionsConfig' => $config['actionsConfig'] ?? [],
];
return $data;
}
/**
* Рендерит HTML таблицы из конфигурации
*
* @param array|null $config Конфигурация таблицы (если null, используется getTableConfig())
* @param bool $isPartial Если true, возвращает только tbody + tfoot (для AJAX)
* @return string HTML таблицы
*/
protected function renderTable(?array $config = null, bool $isPartial = false): string
{
$config = $config ?? $this->getTableConfig();
$tableData = $this->prepareTableData($config);
// Дополнительные параметры для компонента таблицы
$tableData['id'] = $config['id'] ?? 'data-table';
$tableData['url'] = $config['url'] ?? '/table';
$tableData['perPage'] = $tableData['perPage'] ?? 10;
$tableData['sort'] = $tableData['sort'] ?? '';
$tableData['order'] = $tableData['order'] ?? 'asc';
$tableData['filters'] = $tableData['filters'] ?? [];
$tableData['actions'] = $config['actions'] ?? false;
$tableData['actionsConfig'] = $config['actionsConfig'] ?? [];
$tableData['columns'] = $config['columns'] ?? [];
// Параметры для пустого состояния
$tableData['emptyMessage'] = $config['emptyMessage'] ?? 'Нет данных';
$tableData['emptyIcon'] = $config['emptyIcon'] ?? '';
$tableData['emptyActionUrl'] = $config['emptyActionUrl'] ?? '';
$tableData['emptyActionLabel'] = $config['emptyActionLabel'] ?? 'Добавить';
$tableData['emptyActionIcon'] = $config['emptyActionIcon'] ?? '';
$template = $isPartial ? '@components/table/ajax_table' : '@components/table/table';
return $this->renderTwig($template, $tableData);
}
/**
* AJAX endpoint для таблицы - возвращает partial (tbody + tfoot)
* Если запрос не AJAX - возвращает полную таблицу
*/
public function table()
{
$isPartial = $this->request->getGet('format') === 'partial' || $this->isAjax();
return $this->renderTable(null, $isPartial);
}
}