['html']]),
new TwigFunction('get_current_org', [$this, 'getCurrentOrg'], ['is_safe' => ['html']]),
new TwigFunction('get_alerts', [$this, 'getAlerts'], ['is_safe' => ['html']]),
new TwigFunction('render_pager', [$this, 'renderPager'], ['is_safe' => ['html']]),
new TwigFunction('is_active_route', [$this, 'isActiveRoute'], ['is_safe' => ['html']]),
new TwigFunction('get_current_route', [$this, 'getCurrentRoute'], ['is_safe' => ['html']]),
new TwigFunction('render_actions', [$this, 'renderActions'], ['is_safe' => ['html']]),
new TwigFunction('render_cell', [$this, 'renderCell'], ['is_safe' => ['html']]),
];
}
public function getSession()
{
return session();
}
public function getCurrentOrg()
{
$session = session();
$activeOrgId = $session->get('active_org_id');
if ($activeOrgId) {
$orgModel = new OrganizationModel();
return $orgModel->find($activeOrgId);
}
return null;
}
public function getAlerts(): array
{
$session = session();
$alerts = [];
$types = ['success', 'error', 'warning', 'info'];
foreach ($types as $type) {
if ($msg = $session->getFlashdata($type)) {
$alerts[] = ['type' => $type, 'message' => $msg];
}
}
if ($validationErrors = $session->getFlashdata('errors')) {
foreach ($validationErrors as $error) {
$alerts[] = ['type' => 'error', 'message' => $error];
}
}
return $alerts;
}
public function renderPager($pager)
{
if (!$pager) {
return '';
}
return $pager->links();
}
/**
* Проверяет, является ли текущий маршрут активным
*/
public function isActiveRoute($routes, $exact = false): bool
{
$currentRoute = $this->getCurrentRoute();
if (is_string($routes)) {
$routes = [$routes];
}
foreach ($routes as $route) {
if ($exact) {
// Точное совпадение
if ($currentRoute === $route) {
return true;
}
} else {
// Частичное совпадение (начинается с)
// Исключаем пустую строку, так как она совпадает с любым маршрутом
if ($route === '') {
// Для пустого маршрута проверяем только корень
if ($currentRoute === '') {
return true;
}
} elseif (strpos($currentRoute, $route) === 0) {
return true;
}
}
}
return false;
}
/**
* Получает текущий маршрут без базового URL
*/
public function getCurrentRoute(): string
{
$uri = service('uri');
$route = $uri->getRoutePath();
// Убираем начальный слеш если есть
return ltrim($route, '/');
}
/**
* Генерирует HTML для кнопок действий в строке таблицы
*
* @param object|array $item Данные строки (объект или массив)
* @param array $actions Массив конфигураций действий
* @return string HTML код кнопок
*/
public function renderActions($item, array $actions = []): string
{
if (empty($actions)) {
return '';
}
// Конвертируем объект в массив для доступа к свойствам
$itemArray = $this->objectToArray($item);
// DEBUG: логируем для отладки
log_message('debug', 'renderActions: item type = ' . gettype($item));
log_message('debug', 'renderActions: item keys = ' . (is_array($itemArray) ? implode(', ', array_keys($itemArray)) : 'N/A'));
log_message('debug', 'renderActions: item = ' . print_r($itemArray, true));
$html = '
';
foreach ($actions as $action) {
$label = $action['label'] ?? 'Action';
$urlPattern = $action['url'] ?? '#';
$icon = $action['icon'] ?? '';
$class = $action['class'] ?? 'btn-outline-secondary';
$title = $action['title'] ?? $label;
$target = $action['target'] ?? '';
// Подставляем значения из item в URL
$url = $this->interpolate($urlPattern, $itemArray);
log_message('debug', 'renderActions: urlPattern = ' . $urlPattern . ', url = ' . $url);
// Формируем HTML кнопки/ссылки
$iconHtml = $icon ? '
' : '';
$targetAttr = $target ? ' target="' . esc($target) . '"' : '';
$html .= '
'
. $iconHtml . esc($label)
. '';
}
$html .= '
';
return $html;
}
/**
* Конвертирует объект в массив (включая защищённые свойства)
*/
private function objectToArray($data): array
{
if (is_array($data)) {
return $data;
}
if (is_object($data)) {
// Используем json_decode/encode для надёжного извлечения всех свойств
$json = json_encode($data);
return json_decode($json, true);
}
return [];
}
/**
* Рендерит значение ячейки таблицы
*
* @param object|array $item Данные строки
* @param string $key Ключ поля для отображения
* @param array $config Конфигурация колонки (опционально)
* @return string HTML код ячейки
*/
public function renderCell($item, string $key, array $config = []): string
{
$itemArray = $this->objectToArray($item);
$value = $itemArray[$key] ?? null;
// Если значение пустое и есть значение по умолчанию
if (($value === null || $value === '' || $value === false) && isset($config['default'])) {
return $config['default'];
}
// Если указан шаблон, используем его для рендеринга
if (isset($config['template'])) {
$template = $config['template'];
// Подставляем все значения из item в шаблон
foreach ($itemArray as $k => $v) {
$template = str_replace('{' . $k . '}', esc($v ?? ''), $template);
}
return $template;
}
// Применяем тип преобразования если указан
if (isset($config['type'])) {
switch ($config['type']) {
case 'email':
if ($value) {
return '' . esc($value) . '';
}
return $config['default'] ?? '—';
case 'phone':
if ($value) {
return '' . esc($value) . '';
}
return $config['default'] ?? '—';
case 'date':
if ($value) {
return esc(date('d.m.Y', strtotime($value)));
}
return $config['default'] ?? '—';
case 'datetime':
if ($value) {
return esc(date('d.m.Y H:i', strtotime($value)));
}
return $config['default'] ?? '—';
case 'boolean':
case 'bool':
if ($value) {
return 'Да';
}
return 'Нет';
case 'uppercase':
return $value ? esc(strtoupper($value)) : ($config['default'] ?? '');
case 'lowercase':
return $value ? esc(strtolower($value)) : ($config['default'] ?? '');
case 'truncate':
$length = $config['length'] ?? 50;
if ($value && strlen($value) > $length) {
return esc(substr($value, 0, $length)) . '...';
}
return esc($value ?? '');
case 'currency':
if ($value !== null && $value !== '') {
return number_format((float) $value, 0, '.', ' ') . ' ₽';
}
return $config['default'] ?? '—';
case 'percent':
if ($value !== null && $value !== '') {
return esc((float) $value) . '%';
}
return $config['default'] ?? '—';
}
}
// По умолчанию просто возвращаем значение
return $value !== null && $value !== '' ? esc((string) $value) : '—';
}
/**
* Подставляет значения из данных в шаблон строки
*
* @param string $pattern Шаблон с плейсхолдерами вида {field_name}
* @param object|array $data Данные для подстановки
* @return string Результирующая строка
*/
private function interpolate(string $pattern, $data): string
{
// Конвертируем объект в массив
$data = is_object($data) ? $this->objectToArray($data) : $data;
// Заменяем все {key} на значения
return preg_replace_callback('/\{(\w+)\}/', function ($matches) use ($data) {
$key = $matches[1];
return isset($data[$key]) ? esc($data[$key]) : $matches[0];
}, $pattern);
}
}