diff --git a/CLAUDE.md b/CLAUDE.md index 81a0f55..cfb7c9a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -85,6 +85,8 @@ PhpStorm может показывать предупреждение «Namespac | `System\Classes\Session` | Синглтон сессии: `instance(string $name)`, `get/set/delete/destroy/regenerate` | | `System\Classes\Config` | Статический конфиг: lazy-load `App/config/config.php` + merge `config.local.php` | | `System\Classes\Log` | Файловый логгер: уровни, аудит запросов, лог ошибок; `info()/error()`, маскировка | +| `System\Classes\LogReader` | Интерфейс чтения логов (`read()`, `dates()`); абстракция над хранилищем | +| `System\Classes\FileLogReader` | Чтение/парсинг логов из файлов; реализация `LogReader` (позже возможен `DbLogReader`) | | `System\Classes\Validation` | Валидация входных данных по декларативным правилам; `factory()`, `rule()`, `check()`, `errors()` | | `System\Classes\CSRF` | Защита форм от CSRF; токен в сессии: `token()`, `validate()`, `field()` | | `System\Classes\HTTP` | `redirect()`, `requestHeaders()` | @@ -270,6 +272,12 @@ Log::debug(...); Log::warning(...); Каталог логов в `.gitignore` (`/App/logs/`). +**Просмотр логов:** страница `/admin/logs` (`App\Controller\Admin\LogsController`) с фильтрами +(дата, канал, уровень, текст). Источник — через интерфейс `LogReader` (реализация +`FileLogReader` парсит файлы; позже подменяется на `DbLogReader`). Каталог берётся из +`Log::directory()`. Вывод сообщений в шаблоне `App/view/Admin/Logs/logs.html` экранируется +(`htmlspecialchars`) — в логах есть пользовательский ввод (XSS-вектор). + ### HTTPException (System\Classes\HTTP\HTTPException) ```php @@ -309,10 +317,12 @@ $response = HTTPException::factory(403)->getResponse(); Расстановка имён — как в проекте eoffice_v3: - `System\Classes\BaseController` (abstract) — голое ядро: жизненный цикл `executeAction()` (`before()` → экшен → `after()`), хуки по умолчанию пустые. Нейтрально к вебу/API. -- `System\Classes\Controller extends BaseController` — веб: `render()` (layout + content), авто-CSRF в `before()`, `json($data, $status)` для JSON-ответов. **Контроллеры приложения наследуют его.** +- `System\Classes\Controller extends BaseController` — веб: `render()` (layout + content), авто-CSRF в `before()`, `json($data, $status)` для JSON-ответов, хук `layoutData()` (доп. данные в layout помимо `content`). **Контроллеры приложения наследуют его.** API-контроллер делается не отдельным классом, а флагом: `extends Controller` + `$_csrf_protection = false` + ответы через `json()` (так же, как в eoffice_v3). +**Админка** (`App/Controller/Admin/`): базовый `App\Controller\Admin\AdminController extends Controller` задаёт layout `admin` (боковое меню, `System/view/views/admin.html`, Bootstrap из `/vendor/`) и наполняет `layoutData()` пунктами меню (`menu()` — пока статичный список, задел под БД). `before()` — заготовка под проверку прав (логина пока нет, **админка открыта**). Конкретные страницы наследуют `AdminController`. + `Request::execute()` вызывает `executeAction({action}Action)`, поэтому `before()/after()` работают для любого контроллера прозрачно. Авторизацию добавлять в `before()` конкретных контроллеров (логина пока нет). ### Environment @@ -349,8 +359,9 @@ PHPUnit 11 в Docker-контейнере `bicycle`. Bootstrap: `tests/bootstrap | `tests/Unit/CSRFTest.php` | `token()` стабильность/формат, `validate()`, `field()`, нет токена в сессии | | `tests/Unit/ControllerTest.php` | `executeAction()`, порядок `before/action/after`, no-op хуки | | `tests/Unit/LogTest.php` | уровни/порог, каналы (action/error), формат, append, `strtr`, маскировка, `requestInfo()` | +| `tests/Unit/FileLogReaderTest.php` | парсинг, фильтры (level/q/channel), newest-first, missing file, `dates()` | -**154 теста, 253 assertion — все проходят.** +**162 теста, 270 assertion — все проходят.** ### Frontend dependencies (через Composer) diff --git a/System/Classes/Controller.php b/System/Classes/Controller.php index 9fc500e..51ae3b5 100644 --- a/System/Classes/Controller.php +++ b/System/Classes/Controller.php @@ -64,8 +64,19 @@ class Controller extends BaseController } return (new View($this->_layout, 'view/views', [ - 'content' => (new View($template, $dir, $data))->render() - ]))->render(); + 'content' => (new View($template, $dir, $data))->render(), + ] + $this->layoutData()))->render(); + } + + /** + * Дополнительные данные, передаваемые в layout помимо 'content' + * (например меню). Переопределяется наследниками. По умолчанию пусто. + * + * @return array + */ + protected function layoutData(): array + { + return []; } /** diff --git a/System/Classes/Log.php b/System/Classes/Log.php index fd078d0..7e9c606 100644 --- a/System/Classes/Log.php +++ b/System/Classes/Log.php @@ -152,10 +152,11 @@ class Log /** * Каталог логов (из свойства, конфига или дефолт APPPATH/logs). + * Публичный — переиспользуется читалками логов (FileLogReader). * * @return string */ - private static function directory(): string + public static function directory(): string { return self::$directory ?? Config::get('log', 'path') ?? APPPATH . '/logs'; }