# Введение

В данной документации мы постараемся описать все ключевые моменты, с которыми вы столкнетесь при работе с системой.&#x20;

Если документация не смогла помочь Вам в решении Вашего вопроса, Вы всегда можете обратиться за помощью на [наш форум](https://johncms.com/forum/)


# Установка и системные требования

### Системные требования

Для корректной работы JohnCMS, на хостинге, который вы используете, должно быть установлено следующее программное обеспечение

* Web сервер Apache
* PHP 7.3 и выше
* MySQL 5.6.4 и выше
* Для работы с MуSQL должен использоваться встроенный драйвер [MySQL Native Driver (mysqlnd)](https://www.php.net/manual/ru/book.mysqlnd.php)

Для работы системы требуются следующие расширения php:&#x20;

* imagick или gd
* mbstring
* pdo
* simplexml

### Установка

* Скачиваем архив
* Распаковываем в корневую папку на хостинге (обычно это папка с названием вашего сайта или public\_html)
* Переходим по адресу **ваш.сайт/install**
* Следуйте инструкциям описанным на странице установки

{% hint style="info" %}
Обязательно указывайте существующий e-mail адрес при установке т.к. он будет использоваться для отправки e-mail.
{% endhint %}

{% hint style="danger" %}
**После установки обязательно удалите папку install**
{% endhint %}


# Настройка

После установки JohnCMS перейдите в панель администратора.

1. **Выберите пункт Система > Обновить смайлы.** \
   Это обновит кэш смайлов и после этой операции смайлы в сообщениях будут работать
2. **Выберите пункт Система > Настройки языка.**\
   Далее нажмите **Обновить список** после этого выберите язык по умолчанию, на котором будет работать Ваш сайт.

{% hint style="info" %}
Далее по желанию Вы можете проверить и изменить все остальные параметры системы. Для этого просто переходите в другие разделы панели администратора и меняйте настройки так, как Вам необходимо.
{% endhint %}


# Структура файлов/папок

JohnCMS имеет следующую структуру папок:

* assets
* config
* data
* install
* modules
* system
* themes
* upload

### assets

В папке хранятся аватары (**avatars**), смайлы (**emoticons**) и некоторые системные скрипты (**modules**) для генерации картинок предпросмотра.

{% hint style="info" %}
Подпапка **modules** будет удалена в следующих версиях.
{% endhint %}

### config

В папке хранятся различные конфигурационные файлы необходимые для работы системы. \
Файл **routes.php** отвечает за настройку адресов страниц.\
Файл **constants.php** содержит константы необходимые для работы системы.\
В подпапке **autoload** хранятся файлы, которые автоматически загружаются системой. Работа с конфигурационными файлами подробно описана здесь: [Конфигурационные файлы](https://johncms.com/documentation/configs/).

### data

В папке data хранятся различные системные данные, такие как кэш и логи

### install

В папке install хранятся скрипты и прочие данные необходимые для установки системы.\
Данную папку необходимо удалять после установки JohnCMS

### modules

Папка modules содержит все модули системы\
Подробно про структуру папки модуля будет описано отдельно.

### system

Папка system содержит все системные библиотеки\
В этой папке не рекомендуется ничего менять и добавлять в целях сохранения возможности простого обновления на следующие версии JohnCMS

### themes

Папка themes содержит шаблоны сайта\
В этой папке расположен шаблон **default** в папке с этим шаблоном **не рекомендуется ничего менять** для сохранения возможности простого обновления на следующие версии JohnCMS \
Для кастомизации шаблона создайте отдельную папку и скопируйте в неё содержимое папки default.\
Более подробно про работу с шаблонами читайте в соответствующем разделе документации

### upload

Папка upload содержит файлы модулей, такие как загрузки, прикрепленные файлы форума, библиотеки, альбомы, аватары и файлы личных сообщений.


# Проблемы и их решение

Иногда при переносе сайта на другой хостинг или после каких-то изменений в коде вы можете столкнуться с ошибками. Здесь мы рассмотрим распространенные проблемы и варианты их решений.

### Ошибка 500.

Причин появления этой ошибки много. Каждую причину нужно рассматривать индивидуально. Для начала чтобы понять от чего отталкиваться нужно включить вывод ошибок.

Для включения вывода ошибок откройте файл **config/constants.php**, найдите строки﻿

```php
// Включаем режим отладки
const DEBUG = false;
```

Замените false на true

```php
const DEBUG = true;
```

После этих действий на сайте должен отображаться текст ошибки.

Если этого не произошло, нужно смотреть журнал ошибок на сервере.


# Конфигурационные файлы (configs)

Наверное Вы уже задавались вопросом "Где хранятся настройки JohnCMS и как добавлять свои настройки?". Давайте рассмотрим подробнее.

Ранее когда мы рассматривали [структуру папок](https://johncms.com/documentation/structure/), мы уже упоминали в ней папку [config](https://johncms.com/documentation/structure/#config). Теперь рассмотрим, что и за что отвечает...

Когда мы открываем папку config, то видим в ней примерно такую структуру:

![Список конфигурационных файлов в JohnCMS](/files/-MV1UQ50x-Alz-XXROng)

Файлов достаточно много, давайте разберёмся за что они отвечают.

## Файлы в директории autoload:

Директория autoload содержит все конфигурационные файлы, которые автоматически загружаются системой.\
Как вы наверное заметили есть файлы содержащие в названии **global** и **local**.\
Файлы **global** это обычно файлы, которые могут обновляться при выходе новых версий JohnCMS. Не рекомендуем их редактировать, т.к. это осложнит обновление CMS.

Файлы **local** - это локальные файлы конкретно для вашего сайта. Они не содержаться в дистрибутиве JohnCMS. Некоторые из них создаются автоматически при установке системы, а некоторые вы можете создавать вручную.

### Как же быть если вы хотите изменить какие-то параметры, которые есть в global файле?

Всё очень просто. Нужно создать файл с таким же названием, но заменить global на local.

Например, вы хотите изменить настройки в файле **mail.global.php**, для этого скопируйте этот файл и сохраните под именем **mail.local.php**. Далее измените в нем нужные параметры и они переопределят те параметры, которые уже содержатся в **mail.global.php**.

{% hint style="info" %}
Обратите внимание. При необходимости Вы можете изменить только определенные параметры, а остальные останутся стандартными.
{% endhint %}

Давайте рассмотрим пример:

### Содержимое mail.global.php

```php
return [
    'mail' => [
        // Default transport (can be sendmail, smtp, file or memory)
        'transport' => 'sendmail',

        // Transport settings
        'options'   => [
            'smtp' => [
                'name'              => 'localhost.localdomain',
                'host'              => '127.0.0.1',
                'connection_class'  => 'plain',
                'connection_config' => [
                    'username' => 'user',
                    'password' => 'pass',
                ],
            ],
            'file' => [
                'path'     => DATA_PATH . 'mail/',
                'callback' => static function (FileTransport $transport) {
                    return 'Message_' . microtime(true) . '_' . mt_rand() . '.txt';
                },
            ],
        ],
    ],
];
```

Допустим нам нужно изменить имя пользователя: username. Это можно сделать так:

### Содержимое файла mail.local.php

```php
return [
    'mail' => [
        // Transport settings
        'options'   => [
            'smtp' => [
                'connection_config' => [
                    'username' => 'my_user',
                ],
            ],
        ],
    ],
];
```

Давайте теперь получим итоговый результат.

{% hint style="info" %}
Содержимое всех конфигурационных файлов можно получить следующим образом:\
**$config = di('config');**\
Это вернет содержимое всех конфигурационных файлов из папки **config/autoload**.
{% endhint %}

Чтобы получить содержимое файла mail, выполним следующий код:

```php
d($config['mail']);
```

Это вернет следующий результат:

```php
Array
(
    [transport] => sendmail
    [options] => Array
        (
            [smtp] => Array
                (
                    [name] => localhost.localdomain
                    [host] => 127.0.0.1
                    [connection_class] => plain
                    [connection_config] => Array
                        (
                            [username] => my_user
                            [password] => pass
                        )
                )
            [file] => Array
                (
                    [path] => /Users/maksim/MyProjects/johncms_public/data/mail/
                    [callback] => Closure Object
                        (
                            [parameter] => Array
                                (
                                    [$transport] => 
                                )
                        )
                )
        )
)
```

Как видите, в итоговом результате username переопределился тем, что мы указали в файле **mail.local.php**

Вы можете самостоятельно поэкспериментировать, создать свой конфигурационный файл (global/local), а так же можете переопределить настройки из других файлов.

Для удобства можете создать файл **test.php** в корне вашего сайта со следующим содержимым:

```php
<?php

require 'system/bootstrap.php';
$config = di('config');

// Выведем содержимое конфига mail
d($config['mail']);
```

После этого в браузере перейдите по адресу site.com/test.php и увидите результат. (site.com необходимо заменить на адрес вашего сайта).

{% hint style="warning" %}
Обратите внимание.\
Хоть технически вы можете создавать конфигурационные файлы любой структуры и с любыми именами содержащими **local.php** или **global.php**, мы бы рекомендовали создавать осмысленные названия и первый элемент массива называть так же как и сам конфигурационный файл чтобы избежать путаницы и пересечения параметров.\
Например файл **my.global.php**, должен возвращать следующую структуру:\
**return \[**\
&#x20;   **'my' => \[**\
&#x20;       **'name' => 'value'**\
&#x20;   **],**\
**];**
{% endhint %}

Autoload рассмотрели, теперь кратко рассмотрим остальные файлы.

## Прочие конфигурационные файлы:

constants.php - Файл содержит различные константы. В нем вам скорее всего понадобятся константы USE\_CRON (для перевода отправки email на cron) и DEBUG для включения режима отладки при возникновении ошибок или при разработке модулей.

notifications.global.php - Этот файл содержит шаблоны уведомлений. Параметры в данном файле можно переопределить или дополнить с помощью файла notifications.local.php

places.global.php - Файл содержит информацию о местоположении пользователей. Параметры в данном файле можно переопределить или дополнить с помощью файла places.local.php

routes.php - файл для настройки маршрутизации. Подробно работу с ним мы рассматривали в этой статье: [Маршрутизация (роутинг)](https://johncms.com/documentation/routing/)


# Шаблоны электронных сообщений (email)

Начиная с JohnCMS 9.3 в системе появилась поддержка шаблонов для email.

### Для чего это нужно?

Это нужно для того, чтобы отправлять хорошо оформленные письма, которые будут иметь дизайн такой же как и ваш сайт, а так же в таких письмах можно выделять нужную информацию в отличие от обычных текстовых сообщений.

### Как это работает?

Рассмотрим пример письма:

![Пример сообщения о регистрации](/files/-MV1VL1GXa9fY-lX-Ey1)

В письмах как и на всем сайте есть основной шаблон, который является общим практически для всех страниц (header/footer. На скриншоте отмечен цифрами 1 и 3). Сам текст письма - это контентная область (на скриншоте отмечена цифрой 2), которая в разных письмах может выглядеть по разному.

Базовых шаблонов может быть несколько и каждый шаблон сообщения может использовать любой базовый шаблон.

Всё это позволит вам менять базовый шаблон не меняя все шаблоны писем. Например, вы можете сделать несколько шаблонов на все времена года, зимний, летний, весенний, осенний и менять их когда это необходимо. При этом вам нужно будет изменить всего 1 файл, а шаблоны писем изменять не придется вовсе.

### Где хранятся шаблоны?

Почтовые шаблоны так же как и основные шаблоны сайта хранятся в папке themes.

![](/files/-MV1VSYmpwxtX1X45p3g)

Основной шаблон расположен в папке **themes/default/templates/system/mail/layouts/default.phtml**

В этом файле расположен основной макет письма.

Шаблоны конкретных сообщений расположены в папке **themes/default/templates/system/mail/templates**

Шаблонная система для почтовых сообщений работает так же как и шаблоны основного сайта. Поддерживается возможность переопределения и все прочие возможности. Для кастомизации системных шаблонов копируйте их в папку с собственным шаблоном. Таким образом вам не придется переносить изменения при обновлении CMS.


# Отправка электронной почты (email)

В JohnCMS для отправки электронной почты используется библиотека [laminas-mail ](https://docs.laminas.dev/laminas-mail/)\
Она позволяет обобщить отправку сообщений и легко переключать драйверы через которые будет отправляться письмо. Благодаря этому вы сможете выбрать наиболее подходящий вам метод отправки в зависимости от возможностей вашего хостинга и наличия его ip в спам фильтрах.

### Драйверы и настройка

На данный момент поддерживаются следующие драйверы: **Sendmail, SMTP, File.** Этих драйверов обычно более чем достаточно большинству проектов.

Драйвер по умолчанию и настройки драйвера указываются в конфигурационном файле **config/autoload/mail.global.php**. По умолчанию установлен sendmail, но вы можете сменить драйвер на smtp или file. Примеры настроек есть в указанном файле. Вы можете просто их переопределить. Как это сделать, а так же про работу с конфигурационными файлами рекомендуем прочитать здесь: [Конфигурационные файлы.](https://johncms.com/documentation/configs/)

### Отправка сообщений

Отправка email достаточно затратная операция. Для решения этой проблемы отправку email можно переложить на сервер. Для этого в JohnCMS реализована очередь сообщений. Чтобы отправить письмо, необходимо просто добавить его в очередь.

**Рассмотрим пример добавления письма в очередь:**

```php
(new \Johncms\Mail\EmailMessage())->create(
    [
        'locale'   => 'ru',
        'template' => 'system::mail/templates/registration',
        'fields'   => [
            'email_to'        => 'user@example.com',
            'name_to'         => 'Имя Пользователя',
            'subject'         => 'Регистрация на сайте',
            'user_name'       => 'UserName',
            'user_login'      => 'UserLogin',
            'link_to_confirm' => 'https://johncms.com',
        ],
    ]
);
```

Что делает этот код?\
Он добавляет запись в таблицу **email\_messages**. А дальше система проверяет наличие не отправленных писем в очереди и отправляет их.

#### Какие поля необходимы?

* **priority** - Приоритет отправки сообщения. Чем меньше, тем выше. (**не обязательно**)
* **locale** - Поле обязательно и содержит код языка, на котором будет отправлено сообщение.
* **template** - содержит шаблон, который будет использоваться для формирования письма.
* **fields** - содержит массив полей, которые будут доступны в шаблоне, а так же будут использоваться для отправки:
  * **email\_to** - E-mail адрес получателя сообщения (**обязательное поле**)
  * **name\_to** - Имя получателя, которое будет отображаться в почтовом клиенте. (**не обязательно**)
  * **subject** - Тема сообщения. (**не обязательно, но рекомендуется**)
  * Прочие поля доступны только в шаблоне, не требуются для работы драйвера и могут отсутствовать.

Отправка почтовых сообщений по умолчанию выполняется на хитах. Это значит, что для отправки письма какой либо пользователь должен зайти на сайт. В момент на сайт, выполняется проверка наличия неотправленных сообщений и если таковые находятся, выполняется отправка. Этот вариант не всегда подходит, особенно если сообщений отправляется много. По этому рекомендуется перевести отправку сообщений на cron.

### Перевод отправки Email на CRON

Для того, чтобы избежать подвисания страницы для пользователей в JohnCMS реализована отправка сообщений с помощью планировщика cron. Если ваш хостинг поддерживает cron, то рекомендуем перевести отправку сообщений на него. Для этого откройте файл: **config/constants.php**\
Найдите строчку:

```php
const USE_CRON = false;
```

И замените **false** на **true**.

Далее необходимо добавить задачу в cron:

**php system/cron.php**

Периодичность выполнения установить раз в 1 минуту.\
Обратите внимание, что может потребоваться указать полный путь к файлу от корня. Посмотреть его можно в **phpinfo()**, параметр **DOCUMENT\_ROOT** или вывести так: \
**echo $\_SERVER\['DOCUMENT\_ROOT'];**\
Более подробно про то как добавить задачу, вы можете уточнить у вашего хостинг провайдера.


# Работа с уведомлениями

Как вы наверное уже знаете, в JohnCMS начиная с версии 9.2 появились улучшенные уведомления. Давайте разберемся как они работают и научимся добавлять свои уведомления.

Для работы уведомлений существует таблица в базе данных, которая называется **notifications**. Она хранит все уведомления для всех пользователей сайта.

Рассмотрим поля, которые доступны в таблице уведомлений:

| Наименование | Описание                                                                                                                                                 |
| ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| id           | Идентификатор уведомления                                                                                                                                |
| module       | Наименование модуля, который добавил уведомление. (**обязательное поле**)                                                                                |
| event\_type  | Наименование типа события, из-за которого отправоено уведомление. (**обязательное поле**)                                                                |
| user\_id     | Пользователь, для которого предназначено уведомление. (**обязательное поле**)                                                                            |
| sender\_id   | Идентификатор пользователя, который инициировал отправку уведомления. (не обязательно)                                                                   |
| entity\_id   | Идентификатор сущности к которой привязано уведомление. (например сообщение на форуме из-за которого было отправлено уведомление). Не обязательное поле. |
| fields       | Массив полей, которые будут доступны в шаблоне уведомления.                                                                                              |
| read\_at     | Время прочтения уведомления.                                                                                                                             |

### Принцип работы уведомлений:

* Какой либо модуль добавляет уведомление в систему, привязывая его к модулю, типу события и пользователю, которому предназначено это уведомление.
* Когда пользователь открывает сайт, для него выполняется выборка уведомлений у которых поле read\_at = NULL. (т.е. не прочитанные).
* После того как пользователь заходит на страницу уведомлений, ему формируется список в соответствии с заданным шаблоном, далее показанные на странице уведомления помечаются прочитанными.

### Добавление уведомлений:

Уведомления обязательно должны иметь наименование модуля, тип события и пользователя, которому они предназначены.

Рассмотрим пример добавления уведомления:

```php
(new \Johncms\Notifications\Notification())->create(
    [
        'module'     => 'my_the_best_module',
        'event_type' => 'my_module_event1',
        'user_id'    => 1,
        'sender_id'  => 1,
        'entity_id'  => null,
        'fields'     => [
            'variable' => 'Привет! Это'
        ],
    ]
);
```

Этот код добавит уведомление для модуля **my\_the\_best\_module** и события с типом **my\_module\_event1.**

Для чего же нам нужно название модуля и тип события?\
Это нужно для того, чтобы отображать уведомления в соответствии с заданным шаблоном.

### Шаблоны уведомлений:

Шаблоны уведомлений настраиваются в файле **config/notifications.local.php.** Если у вас нет этого файла, переименуйте файл **notifications.local.php.example** в **notifications.local.php**

Файл с шаблонами должен иметь следующую структуру:

```php
return [
    // Пример шаблонов уведомлений для модулей
    'my_the_best_module' => [
        'name'   => 'Мой лучший модуль!',
        'events' => [
            'my_module_event1' => [
                'name'    => 'Новое сообщение',
                'message' => 'Текст сообщения! #variable# дополнительный текст',
            ],
            'my_module_event2' => [
                'name'    => 'Новый пост',
                'message' => 'Текст уведомления! #variable# дополнительный текст',
            ],
        ],
    ],
];
```

Как мы видим, тут используется название модуля и тип события.

Давайте посмотрим как выглядит наше уведомление, которое мы добавили выше.

![Пример отображения уведомления](/files/-MV1WHkg75By0Gn5RF32)

Как видно на скриншоте, вывелось уведомление с типом **my\_module\_event1**. В тексте уведомления заменилась макропеременная **#variable#** на ту, которую мы подавали при создании уведомления в массиве **fields**.


# Работа с запросом (Request)

Для работы с данными HTTP запроса в JohnCMS используется класс **\Johncms\System\Http\Request**. Он позволяет получить доступ к таким суперглобальным переменным как: **$\_POST, $\_GET, $\_COOKIE, $\_FILES, $\_SERVER**. Это позволяет упростить получение значений по умолчанию, и фильтрацию данных, пришедших от пользователя. Давайте посмотрим на примеры.

Для начала необходимо получить объект класса Request.

```php
/** @var \Johncms\System\Http\Request $request */
$request = di(\Johncms\System\Http\Request::class);
```

Строка /\*\* @var \Johncms\System\Http\Request $request \*/ не обязательна и служит лишь для работы автодополнения в IDE если вы конечно используете IDE.

## Получение данных из $\_GET

Предположим, что пользователь открыл страницу <http://domain.com/?user\\_id=123> и нам нужно получить идентификатор пользователя 123. Сделать это можно следующим образом:

```php
$user = $request->getQuery('user_id', 0, FILTER_VALIDATE_INT);
```

Разберем что же тут происходит. Метод **getQuery** пытается получить **user\_id** из суперглобального массива **$\_GET**.\
Первым параметром принимает название параметра запроса, вторым параметром можно передать стандартное значение, а третим параметром передается фильтр, с помощью которого будет обработано значение. Вы можете ознакомиться со списком фильтров в официальной документации по этой ссылке: <https://www.php.net/manual/ru/filter.filters.php>

Коротко что делает строка из примера: Пытается получить параметр GET запроса **user\_id**, если его нет, то возвращает 0, если есть, то очищает и возвращает число. Если передано не число, то вернет значение по умолчанию, то есть 0.

## Получение данных из $\_POST

Предположим, что отправлена форма, которая содержит **user\_id** и **name**. Форма отправлена методом POST.

```php
$user = $request->getPost('user_id', 0, FILTER_VALIDATE_INT);
$name = $request->getPost('name', '', FILTER_SANITIZE_STRING);
```

Эти примеры работают так же как и предыдущий. Во втором примере от пользователя ожидается строка, а фильтр **FILTER\_SANITIZE\_STRING** удаляет из нее теги, и при необходимости удаляет или кодирует специальные символы.

## Получение данных из $\_COOKIE

```php
$name = $request->getCookie('name', '', FILTER_SANITIZE_STRING);
```

В этом примере как видите всё так же просто как и в предыдущих. Просто поменялось название метода, а принцип работы такой же.

## Получение данных из $\_SERVER

```php
$user_agent = $request->getServer('HTTP_USER_AGENT', '', FILTER_SANITIZE_STRING);
```

Этот пример работает так же как и остальные. Получает **HTTP\_USER\_AGENT** из суперглобальной переменной **$\_SERVER**.

## Получение данных из $\_FILES

```php
$files = $request->getUploadedFiles();
```

Этот код вернет массив файлов, в котором каждый элемент будет представлен объектом класса GuzzleHttp\Psr7\UploadedFile. Если вы уже работали с выгрузкой файлов в php, то наверное знаете, что множественные файлы в массиве $\_FILES описываются примерно так:

```php
array(
    'files' => array(
        'name' => array(
            0 => 'file0.txt',
            1 => 'file1.html',
        ),
        'type' => array(
            0 => 'text/plain',
            1 => 'text/html',
        ),
        /* etc. */
    ),
)
```

для работы с этим стандартными средствами вам необходимо позаботиться о сборе всех данных в нормальную структуру. Если вы используете метод **getUploadedFiles**, то эта задача уже решена для вас и массив файлов будет уже в нормальной структуре:

```php
array(
    'files' => array(
        0 => array(
            'name' => 'file0.txt',
            'type' => 'text/plain',
            /* etc. */
        ),
        1 => array(
            'name' => 'file1.html',
            'type' => 'text/html',
            /* etc. */
        ),
    ),
)
```

Давайте рассмотрим пример сохранения файлов

Допустим, у нас есть такая форма, которая принимает 1 обычный файл и поле с возможностью выбирать несколько файлов.

```markup
<form action="" method="post" enctype="multipart/form-data">
    <input type="file" name="file">
    <input type="file" name="multiple_files[]" multiple>
    <button type="submit">Отправить</button>
</form>
```

Пример сохранения файлов будет выглядеть так:

```php
$files = $request->getUploadedFiles();

// Сохраняем файл из обычного поля
if (! empty($files['file'])) {
    /** @var  $attached_file \Psr\Http\Message\UploadedFileInterface */
    $attached_file = $files['file'];
    try {
        $attached_file->moveTo(UPLOAD_PATH . '/tmp/' . $attached_file->getClientFilename());
        echo 'Файл успешно сохранен';
    } catch (\Exception $exception) {
        echo 'Ошибка сохранения файла: ' . $exception->getMessage();
    }
}

// Сохраняем файлы из множественного поля
if (! empty($files['multiple_files'])) {
    /** @var  $multiple_files \Psr\Http\Message\UploadedFileInterface[] */
    $multiple_files = $files['multiple_files'];
    foreach ($multiple_files as $multiple_file) {
        try {
            $multiple_file->moveTo(UPLOAD_PATH . '/tmp/' . $multiple_file->getClientFilename());
            echo 'Файл успешно сохранен';
        } catch (\Exception $exception) {
            echo 'Ошибка сохранения файла: ' . $exception->getMessage();
        }
    }
}
```

В результате отправки формы с файлами, все файлы будут сохранены в папке upload/tmp c оригинальными названиями, с которыми отправил клиент.

{% hint style="danger" %}
Обратите внимание, что в примере рассмотрен простой вариант сохранения файлов без каких либо проверок допустимых типов файлов.
{% endhint %}


# Валидация

## Что такое валидатор и зачем он нужен?

Разработчики модулей создавая модули часто сталкиваются с задачей валидации форм, которые отправляет пользователь.\
Например, практически в любой форме есть поля,  обязательные для заполнения. Так же есть поля, значения которых нужно проверить на наличие в базе данных, в некоторых полях может находиться файл, размер которого нам нужно проверить, ссылка, правильность которой тоже нужно проверить или же email адрес в котором, например, нужно проверить не только корректность текста до и после символа @, но и наличие MX записей для указанного домена.

Для избавления от частых рутинных операций и упрощения кода в JohnCMS встроены различные, часто используемые базовые валидаторы.

## Что позволяет делать валидатор?

Валидатор проверяет входные данные на соответствие настройкам правил валидации. Если данные не соответствуют правилам, валидатор возвращает false и так же позволяет получить информацию о том, какие именно требования не выполнены.

В JohnCMS используется [laminas-validator](https://docs.laminas.dev/laminas-validator/), большинство существующих правил, которые описаны в официальной документации будут работать и в JohnCMS, но есть правила для которых требуются дополнительные зависимости и эти правила могут не работать, но таких как правило единицы и они редко используются.

При просмотре документации по валидаторам вам может показаться, что это достаточно громоздко, но не спешите, работа с валидатором в JohnCMS сильно упрощена, к тому же реализованы некоторые дополнительные валидаторы, которые вам скорее всего пригодятся.\
Давайте рассмотрим пример и разберемся в некоторых деталях.

```php
<?php

require 'system/bootstrap.php';

// Массив полей и значений
$data = [
    'test'   => '',
    'number' => 100,
    'email'  => 'email@example.ru',
    'model'  => 110,
];

// Настройки валидатора
$rules = [
    // Название поля => [ правила валидации и их параметры ]
    'test'   => [
        'NotEmpty',
        'StringLength' => [
            'min' => 6,
            'max' => 80,
        ],
    ],
    'number' => [
        'NotEmpty',
        'LessThan' => ['max' => 90],
    ],
    'email'  => [
        'EmailAddress' => [
            'useMxCheck' => true,
        ],
    ],
    'model'  => [
        'ModelExists' => [
            'model' => \Johncms\Users\User::class,
            'field' => 'id',
        ],
    ],
];

// Валидация
$validator = new \Johncms\Validator\Validator($data, $rules);
if ($validator->isValid()) {
    echo 'OK';
} else {
    d($validator->getErrors());
}
```

Здесь массив **$data** содержит набор данных, которые будут проверяться. Часто это данные из формы, полученные методом **POST** или **GET**.

Массив **$rules** содержит набор правил и их настройку. В качестве ключа указывается название поля из массива $data, а в качестве массива со значениями используется валидатор или набор валидаторов и их настройки.\
Например в первом правиле проверяется значение поля под названием **test**, к нему применяется валидатор **NotEmpty** и **StringLength**. Валидатор **NotEmpty** проверяет не пустое ли значение в поле **test**, а валидатор **StringLength** проверяет длину значения. В данном случае длина значения должна быть от 6 до 80 символов.

Как видите, валидатор может не иметь настроек, а может иметь настройки. Если валидатор не имеет настроек или же вам подходят настройки по умолчанию, то вы можете передать только название валидатора. Если вам нужно дополнительно настроить валидатор, просто передаете массив настроек.

Многие популярные валидаторы мы рассмотрим отдельно. Пока можете попробовать выполнить код выше.\
Для этого в корне вашего сайта создайте файл **test.php** и вставьте в него этот код. После этого откройте в браузере страницу **site.ru/test.php.** Вы увидите следующий результат:

```php
Array
(
    [test] => Array
        (
            [isEmpty] => Поле является обязательным и не может быть пустым
        )

    [number] => Array
        (
            [notLessThan] => The input is not less than '90'
        )

    [email] => Array
        (
            [emailAddressInvalidMxRecord] => 'example.ru' Похоже, что записи MX или A для адреса электронной почты не действительны
        )

    [model] => Array
        (
            [modelNotFound] => Нет записей, соответствующих введенным данным
        )

)
```

Как видно из результата, массив $data не прошел проверку. Валидатор вернул массив полей и правила валидации, которые не прошли проверку. Вы можете изменить в нем значения и понаблюдать за результатом, а так же поэкспериментировать с другими правилами.


# NotEmpty - Не пустое значение

Этот валидатор позволяет вам проверить, является ли данное значение не пустым. Это часто полезно при работе с элементами формы или другим пользовательским вводом. Вы можете использовать этот валидатор, чтобы убедиться, что пользователь заполнил поле и оно не пустое.

По умолчанию этот валидатор работает иначе, чем вы ожидаете, работая с PHP функцией empty(). В частности, этот валидатор будет оценивать как целое число 0, так и строку «0» как пустые.

Вам может не подойти это поведение и, например, в вашем случае 0 не должен считаться пустым. Для таких случаев в валидаторе NotEmpty вы можете задать некоторые настройки.

### Поддерживаемые параметры

* **type**: Устанавливает тип проверки, которая будет выполнена.

### Обрабатываемые типы

* **boolean**: Возвращает false, когда логическое значение равно false.
* **integer**: Возвращает false, когда задано целое число 0. По умолчанию эта проверка не активирована и возвращает true для любых целочисленных значений.
* **float**: Возвращает false, когда задано значение с плавающей запятой 0.0. По умолчанию эта проверка не активирована и возвращает true для любых значений с плавающей запятой.
* **string**: Возвращает false, когда задана пустая строка.
* **zero**: Возвращает false, когда задан один символ ноль ('0').
* **empty\_array**: Возвращает false, когда задан пустой массив.
* **null**: Возвращает false, когда задано значение null.
* **php**: Возвращает false везде, где PHP empty () возвращает true.
* **space**: Возвращает false, если задана строка, содержащая только пробел.
* **object**: Возвращает true. false будет возвращено, когда объект не разрешен, но объект задан.
* **object\_string**: Возвращает false, когда объект задан, а его метод \_\_toString () возвращает пустую строку.
* **object\_count**: Возвращает false, когда объект задан, он реализует Countable, и его количество равно 0.
* **all**: Возвращает false для всех вышеперечисленных типов.

Рассмотрим пример как передавать эти параметры в валидатор.

### Примеры использования

```php
// Массив полей и значений
$data = [
    'test' => 0,
];

// Настройки валидатора
$rules = [
    'test' => [
        'NotEmpty' => [
            'type' => [
                'integer',
                'zero',
            ],
        ],
    ],
];

// Валидация
$validator = new \Johncms\Validator\Validator($data, $rules);
if ($validator->isValid()) {
    echo 'OK';
} else {
    d($validator->getErrors());
}
```

Как видите, для валидатора **NotEmpty** задан массив настроек, в нем передается параметр **type** со значениями из списка выше (обрабатываемые типы).


# StringLength - длина строки

Валидатор StringLength позволяет проверить находится ли длина строки в диапазоне заданных значений или нет.

По умолчанию этот валидатор проверяет, находится ли значение между min и max, используя минимальное значение по умолчанию, равное 0, и максимальное значение по умолчанию, равное NULL (то есть неограниченное).\
Таким образом, без каких-либо опций, валидатор только проверяет, что ввод является строкой.

## Поддерживаемые параметры

* **encoding**: Устанавливает кодировку ICONV в которой будет проверяться строка.
* **min**: Устанавливает минимально допустимую длину строки.
* **max**: Устанавливает максимально допустимую длину для строки.

## Примеры использования

```php
// Массив полей и значений
$data = [
    'test' => 'Строка',
];

// Настройки валидатора
$rules = [
    'test' => [
        'StringLength' => [
            'min'      => 3,
            'max'      => 60,
            'encoding' => 'UTF-8',
        ],
    ],
];

// Валидация
$validator = new \Johncms\Validator\Validator($data, $rules);
if ($validator->isValid()) {
    echo 'OK';
} else {
    d($validator->getErrors());
}
```

В указанном примере валидатор проверит строку в кодировке UTF-8 на длину от 3 до 60 символов.

### Проверка только минимальной длины:

```php
// Массив полей и значений
$data = [
    'test' => 'Строка',
];

// Настройки валидатора
$rules = [
    'test' => [
        'StringLength' => [
            'min' => 3
        ],
    ],
];

// Валидация
$validator = new \Johncms\Validator\Validator($data, $rules);
if ($validator->isValid()) {
    echo 'OK';
} else {
    d($validator->getErrors());
}
```

В этом примере будет проверяться только минимальная длина строки (3 символа). Максимальная будет считаться не ограниченной.

### Проверка только максимальной длины:

```php
// Массив полей и значений
$data = [
    'test' => 'Строка',
];

// Настройки валидатора
$rules = [
    'test' => [
        'StringLength' => [
            'max' => 50
        ],
    ],
];

// Валидация
$validator = new \Johncms\Validator\Validator($data, $rules);
if ($validator->isValid()) {
    echo 'OK';
} else {
    d($validator->getErrors());
}
```

В этом же примере будет проверяться только максимальная длина строки. Если строка будет длиннее 50 символов, проверка не пройдет, если менее 50 символов, то проверка пройдет.

### Строгое ограничение длины строки:

```php
// Массив полей и значений
$data = [
    'test' => 'Строка',
];

// Настройки валидатора
$rules = [
    'test' => [
        'StringLength' => [
            'max' => 6,
            'min' => 6,
        ],
    ],
];

// Валидация
$validator = new \Johncms\Validator\Validator($data, $rules);
if ($validator->isValid()) {
    echo 'OK';
} else {
    d($validator->getErrors());
}
```

А в этом примере мы строго ограничили длину строки 6 символами. Т.е. проверка пройдет только если строка будет длиной в 6 символов. Больше или меньше не допускается.


# LessThan - Менее чем

Валидатор LessThan позволяет проверить число на предмет того, что оно меньше чем заданное в параметре.\
Обратите внимание, что данный валидатор работает только с числами. Строки или даты этот валидатор не позволяет проверять.

### Поддерживаемые параметры

* **inclusive**: Включая максимальное значение. Если задано **true**, то значение равное максимальное значение будет проходить валидацию. Если задано **false**, то значение равное максимальному значению не будет проходить валидацию.
* **max**: Устанавливает максимальное значение.

### Примеры использования

```php
// Массив полей и значений
$data = [
    'test' => 60,
];

// Настройки валидатора
$rules = [
    'test' => [
        'LessThan' => [
            'max'       => 60,
            'inclusive' => true,
        ],
    ],
];

// Валидация
$validator = new \Johncms\Validator\Validator($data, $rules);
if ($validator->isValid()) {
    echo 'OK';
} else {
    d($validator->getErrors());
}
```

Этот пример выведет "OK", т.к. включен параметр inclusive и значение равно максимальному.

```php
// Массив полей и значений
$data = [
    'test' => 60,
];

// Настройки валидатора
$rules = [
    'test' => [
        'LessThan' => [
            'max'       => 60,
            'inclusive' => false,
        ],
    ],
];

// Валидация
$validator = new \Johncms\Validator\Validator($data, $rules);
if ($validator->isValid()) {
    echo 'OK';
} else {
    d($validator->getErrors());
}
```

А этот пример выведет ошибку т.к. параметр inclusive имеет значение false т.к. этот параметр исключает максимальное значение.


# EmailAddress - Проверка email адреса

Валидатор EmailAddress позволяет выполнить различные проверки email адреса.\
Валидатор сначала разбивает адрес электронной почты на local-part\@hostname и пытается сопоставить их с известными спецификациями для адресов электронной почты и имен хостов.

### Поддерживаемые параметры

* **allow**: Определяет, какой тип доменных имен принимает валидатор. Эта опция используется вместе с опцией hostnameValidator для установки валидатора имени хоста. Возможные значения этой опции определены в константах ALLOW\_ \* валидатора Hostname:
  * **ALLOW\_DNS**: (по умолчанию) Разрешает доменные имена (например example.com)
  * **ALLOW\_IP**: Разрешает IP адреса.
  * **ALLOW\_LOCAL**: Разрешает локальные домены такие как localhost или [www.localdomain](http://www.localdomain)
  * **ALLOW\_URI**: Разрешает имена хостов в универсальном синтаксисе URI. См. [RFC 3986](https://www.ietf.org/rfc/rfc3986.txt)
  * **ALLOW\_ALL**: Разрешить все типы хостов.
* **useDeepMxCheck**: Указывает валидатору на необходимость усиленной проверки MX записей домена. Если для этого параметра установлено значение true, то в дополнение к записям MX также используются записи A, A6 и AAAA для проверки того, принимает ли сервер электронную почту. Эта опция по умолчанию имеет значение false.
* **useDomainCheck**: Определяет, должна ли быть проверена часть домена. Если для этого параметра установлено значение false, будет проверяться только локальная часть адреса электронной почты. В этом случае валидатор имени хоста не будет вызван. Эта опция по умолчанию имеет значение true.
* **hostnameValidator**: Задает экземпляр объекта валидатора имени хоста, с помощью которого будет проверяться доменная часть адреса электронной почты.
* **useMxCheck**: Определяет, должны ли быть обнаружены записи MX с сервера. Если для этого параметра задано значение true, то MX-записи используются для проверки того, принимает ли сервер электронную почту или нет. Эта опция по умолчанию имеет значение false.

### Пример использования

Рассмотрим наиболее распространенный пример, которого скорее всего вам будет достаточно. Этот пример проверяет существование домена и возможность принимать email. Т.е. выполняется максимально возможная проверка. Она пропустит только точно существующий домен с MX записями.

```php
// Массив полей и значений
$data = [
    'test' => 'info@johncms.com',
];

// Настройки валидатора
$rules = [
    'test' => [
        'EmailAddress'   => [
            'allow'          => Laminas\Validator\Hostname::ALLOW_DNS,
            'useMxCheck'     => true,
            'useDeepMxCheck' => true,
        ],
    ],
];

// Валидация
$validator = new \Johncms\Validator\Validator($data, $rules);
if ($validator->isValid()) {
    echo 'OK';
} else {
    d($validator->getErrors());
}
```


# ModelExists - Проверка существования записи в БД

Валидатор **ModelExists** позволяет проверить существование записи в базе данных. Это хорошо подходит для тех случаев, когда у вас в форме есть привязка к каким-то существующим записям в базе данных.

Для работы этого валидатора вам потребуется существующая [модель](https://johncms.com/documentation/eloquent-orm/).&#x20;

### Поддерживаемые параметры

* **model**: Класс модели, который будет использоваться для построения запроса к БД.
* **field**: Столбец в БД по которому будет осуществляться поиск записи.

### Примеры использования

```php
// Массив полей и значений
$data = [
    'test' => 45,
];

// Настройки валидатора
$rules = [
    'test' => [
        'ModelExists'   => [
            'model' => \Johncms\Users\User::class,
            'field' => 'id',
        ],
    ],
];

// Валидация
$validator = new \Johncms\Validator\Validator($data, $rules);
if ($validator->isValid()) {
    echo 'OK';
} else {
    d($validator->getErrors());
}
```

В указанном примере будет выполнена проверка наличия пользователя c идентификатором 45 в таблице users.

Запрос который будет выполнен:

```sql
SELECT * FROM `users` WHERE `id` = 45
```

В результате, если будет найдена запись с id = 45, то валидатор будет считать проверку успешной, если не найдет, то вернёт ошибку.

Рассмотрим ещё один пример:

```php
// Массив полей и значений
$data = [
    'test' => 'admin',
];

// Настройки валидатора
$rules = [
    'test' => [
        'ModelExists'   => [
            'model' => \Johncms\Users\User::class,
            'field' => 'name',
        ],
    ],
];

// Валидация
$validator = new \Johncms\Validator\Validator($data, $rules);
if ($validator->isValid()) {
    echo 'OK';
} else {
    d($validator->getErrors());
}
```

В этом примере будет выполнен поиск записи у которой поле name = admin.

Будет выполнен следующий запрос:

```sql
SELECT * FROM `users` WHERE `name` = 'admin'
```

Результат будет такой же как и в случае с id. Если будет найдена строка с полем name = admin, то валидация пройдет успешно, если нет, будет возвращена ошибка.


# ModelNotExists - Проверка отсутствия записи в БД

Валидатор **ModelNotExists** позволяет проверить отсутствие записи в базе данных. Это подойдет для тех случаев, когда вам нужно проверить отсутствие записи в таблице прежде чем её добавить. Например, с помощью этого валидатора, в форме регистрации пользователя вы можете проверить существует ли пользователь с введенным логином или нет.

Для работы этого валидатора вам потребуется существующая [модель](https://johncms.com/documentation/eloquent-orm/).&#x20;

## Поддерживаемые параметры

* **model**: Класс модели, который будет использоваться для построения запроса к БД.
* **field**: Столбец в БД по которому будет осуществляться поиск записи.
* **exclude**: Параметры для задания условий исключения из выборки. Может содержать анонимную функцию или массив с полями **field** и **value**.

## Примеры использования

```php
// Массив полей и значений
$data = [
    'test' => 'admin@admin.ru',
];

// Настройки валидатора
$rules = [
    'test' => [
        'ModelNotExists' => [
            'model'   => \Johncms\Users\User::class,
            'field'   => 'mail',
            'exclude' => static function ($query) {
                return $query->where('name', '!=', 'admin')->where('id', '!=', 1);
            },
        ],
    ],
];

// Валидация
$validator = new \Johncms\Validator\Validator($data, $rules);
if ($validator->isValid()) {
    echo 'OK';
} else {
    d($validator->getErrors());
}
```

В примере выше выполняется проверка наличия в таблице users пользователя с полем mail, содержащим <admin@admin.ru>. При этом из выборки исключаются строки с name = admin и id = 1. Для расширения запроса на выборку используется анонимная функция. Она позволяет дополнять запрос любыми условиями.\
Валидатор выполнит следующий запрос:

```sql
select * from `users` where (`name` != 'admin' and `id` != 1) and `mail` = 'admin@admin.ru' limit 1
```

Рассмотрим более простой пример, где в параметр **exclude** передается массив:

```php
// Массив полей и значений
$data = [
    'test' => 'admin@admin.ru',
];

// Настройки валидатора
$rules = [
    'test' => [
        'ModelNotExists' => [
            'model'   => \Johncms\Users\User::class,
            'field'   => 'mail',
            'exclude' => [
                'field' => 'name',
                'value' => 'admin',
            ],
        ],
    ],
];

// Валидация
$validator = new \Johncms\Validator\Validator($data, $rules);
if ($validator->isValid()) {
    echo 'OK';
} else {
    d($validator->getErrors());
}
```

Если вам не требуется сложное условие для исключения записей из выборки, то вы можете использовать такой вариант задания исключений. При таких настройках валидатор выполнит следующий запрос:

```sql
select * from `users` where `name` != 'admin' and `mail` = 'admin@admin.ru' limit 1
```

Ну и давайте рассмотрим минимальный вариант использования, вообще без исключений.

```php
// Массив полей и значений
$data = [
    'test' => 'admin@admin.ru',
];

// Настройки валидатора
$rules = [
    'test' => [
        'ModelNotExists' => [
            'model'   => \Johncms\Users\User::class,
            'field'   => 'mail',
        ],
    ],
];

// Валидация
$validator = new \Johncms\Validator\Validator($data, $rules);
if ($validator->isValid()) {
    echo 'OK';
} else {
    d($validator->getErrors());
}
```

При таких настройках валидатор просто проверит наличие записи с mail = <admin@admin.ru>. Если запись будет найдена, то валидатор вернёт ошибку. Если нет, проверка пройдет успешно.

Запрос, который выполнит валидатор при этих настройках будет таким:

```sql
select * from `users` where `mail` = 'admin@admin.ru' limit 1
```


# Csrf - Проверка токена

Валидатор **Csrf** предназначен для проверки токена csrf. Токен предназначен для защиты формы от подделки запроса. Данный валидатор работает в паре с генератором токенов **\Johncms\Security\Csrf**

### Поддерживаемые параметры

* **tokenId**: Идентификатор токена. Если не задан, используется токен по умолчанию для всего сайта.

### Примеры использования

```php
// Массив полей и значений
$data = [
    'test' => 'token',
];

// Настройки валидатора
$rules = [
    'test' => [
        'Csrf',
    ],
];

// Валидация
$validator = new \Johncms\Validator\Validator($data, $rules);
if ($validator->isValid()) {
    echo 'OK';
} else {
    d($validator->getErrors());
}
```

В указанном примере будет проверяться токен по умолчанию.

```php
// Массив полей и значений
$data = [
    'test' => 'token',
];

// Настройки валидатора
$rules = [
    'test' => [
        'Csrf' => [
            'tokenId' => 'guestbook_form'
        ],
    ],
];

// Валидация
$validator = new \Johncms\Validator\Validator($data, $rules);
if ($validator->isValid()) {
    echo 'OK';
} else {
    d($validator->getErrors());
}
```

В этом примере мы добавили идентификатор токена, который будет проверяться.

Более подробно работу с токенами мы рассмотрим в отдельной статье.


# Flood - проверка на флуд

Валидатор **Flood** предназначен для упрощения проверки формы на флуд. Валидатор не имеет параметров, не привязывается к какому либо полю и обычно используется вместе с валидатором токена из-за особенностей технической реализации валидаторов.

### Пример использования

```php
// Массив полей и значений
$data = [
    'test' => 'token',
];

// Настройки валидатора
$rules = [
    'test' => [
        'Csrf',
        'Flood',
    ],
];

// Валидация
$validator = new \Johncms\Validator\Validator($data, $rules);
if ($validator->isValid()) {
    echo 'OK';
} else {
    d($validator->getErrors());
}
```

В указанном примере валидатор Flood добавлен для того же поля, что и валидатор токена.


# Ban - Проверка банов

Валидатор **Ban** предназначен для упрощенной проверки наличия банов у пользователя. Валидатор так же обычно используется вместе с валидатором **Csrf**, т.к. не имеет привязки к данным в форме, но для работы валидатора, он должен быть добавлен для определенного поля.

### Поддерживаемые параметры

* **bans**: Массив банов, наличие которых будет проверяться. Параметр не обязателен. По умолчанию проверяется бан "Полная блокировка".

### Пример использования

```php
// Массив полей и значений
$data = [
    'test' => 'token',
];

// Настройки валидатора
$rules = [
    'test' => [
        'Csrf',
        'Flood',
        'Ban' => [11, 13],
    ],
];

// Валидация
$validator = new \Johncms\Validator\Validator($data, $rules);
if ($validator->isValid()) {
    echo 'OK';
} else {
    d($validator->getErrors());
}
```

В указанном примере будет проверяться бан для форума (11) и гостевой (13). Если у пользователя есть хотя бы 1 из этих банов, проверка не пройдет.


# Captcha - Проверка защитного кода

Валидатор Captcha предназначен для проверки защитного кода, который указал пользователь в форме.\
Перед проверкой, код должен быть сгенерирован и записан в сессию.

### Поддерживаемые параметры

* **sessionField**: Указывается ключ в сессии из которого валидатор будет использовать код. По умолчанию: **code**

### Примеры использования

```php
// Массив полей и значений
$data = [
    'test' => 'captcha_code',
];

// Настройки валидатора
$rules = [
    'test' => [
        'Captcha',
    ],
];

// Валидация
$validator = new \Johncms\Validator\Validator($data, $rules);
if ($validator->isValid()) {
    echo 'OK';
} else {
    d($validator->getErrors());
}
```

В указанном выше примере код код будет использоваться из переменной по умолчанию **$\_SESSION\['code']**

```php
// Массив полей и значений
$data = [
    'test' => 'captcha_code',
];

// Настройки валидатора
$rules = [
    'test' => [
        'Captcha' => [
            'sessionField' => 'captcha_code'
        ],
    ],
];

// Валидация
$validator = new \Johncms\Validator\Validator($data, $rules);
if ($validator->isValid()) {
    echo 'OK';
} else {
    d($validator->getErrors());
}
```

А в этом примере будет использован код из переменной **$\_SESSION\['captcha\_code']**

Более подробно работу с формами и с капчей рассмотрим в отдельной статье.


# Структура стандартного шаблона

Шаблоны располагаются в папке **themes**

Обычно шаблон для JohnCMS имеет следующую структуру:

* themes
* * template\_name
  * * assets
    * src
    * templates

В данной структуре обязательными являются только папки **assets** и **templates**, но в некоторых исключениях они вам могут не понадобиться.

#### Что такое шаблон в JohnCMS?

С точки зрения структуры шаблоном является любая папка в папке **/themes**\
В этой папке есть тема по умолчанию - **default**\
В этой теме находятся все необходимые для работы файлы по умолчанию: шаблоны, стили, картинки, скрипты и т.д.\
Также, каждый отдельный модуль может иметь свою папку с шаблонами **/module\_name/templates**, или другими файлами общего доступа **/assets/modules/module\_name**.

{% hint style="info" %}
Для частичной кастомизации стандартного шаблона, вы можете создать свою тему только с теми файлами, которые хотите изменить. При создании темы, просто сохраните такое же расположение нужного файла в папке с вашей кастомной темой. Более подробно про создание собственных тем, читайте на соответствующей странице.
{% endhint %}


# Изменение стилей шаблона

Начиная с JohnCMS 9.0.0 в системе используются современные средства для сборки файлов стилей и скриптов.\
Вы можете конечно не использовать эти средства, но они существенно облегчают разработку после того как вы разберетесь с ними.

Давайте разберемся как же нам теперь работать с нововведениями...\
Для работы сборщика нам понадобится Node.js. Вы можете скачать его с официального сайта <https://nodejs.org/ru/>\
Скачайте и установите Node.js на ваш компьютере. После установки перезагрузите компьютер.\
Установите JohnCMS на своем компьютере если ещё не установили.

Давайте разберемся где у нас подключаются стили и js и начнем делать свою тему на основе этого.

Откроем файл \
**themes/default/templates/system/layout/default.phtml**\
Как вы наверное догадались это основной шаблон нашего сайта.

Вверху найдем строчку:

```markup
<link rel="stylesheet" href="<?= $this->asset('css/app.css', true) ?>">
```

Эта строка у нас подключает css файл из папки **themes/default/assets/css/app.css**

Внизу строчку:

```markup
<script src="<?= $this->asset('js/app.js', true) ?>"></script>
```

Эта строчка подключает javascript из папки **themes/default/assets/js/app.js**

Если мы откроем эти файлы, то увидим там много кода в одну строку. Это нормально. Эти файлы собираются сборщиком и сжимаются для ускорения загрузки браузером пользователей.\
Как вы наверное уже догадались, эти файлы редактировать не нужно т.к. их собирает сборщик.

Давайте разберемся со сборщиком.\
Настройка сборщика производится в файле **/webpack.mix.js** (в корне сайта).\
Давайте откроем его и посмотрим что там есть.\
Найдем там 2 строчки которые там нужны:

```javascript
mix.js('themes/default/src/js/app.js', 'themes/default/assets/js')
    .sass('themes/default/src/scss/app.scss', 'themes/default/assets/css')
```

Что-то знакомое тут, не правда ли?\
Давайте разберемся, что у нас тут для чего.

```javascript
mix.js('themes/default/src/js/app.js', 'themes/default/assets/js')
```

Эта строчка говорит сборщику чтобы он взял файл по пути **themes/default/src/js/app.js** произвел все необходимые операции с ним и положил его в папку **themes/default/assets/js**. Т.к. во втором параметре мы явно не указали название файла, сборщик соберет файл и сохранит с таким же именем что и исходный файл т.е. **app.js**. В итоге получится так: **themes/default/assets/js/app.js**

Посмотрим на вторую строку

```javascript
.sass('themes/default/src/scss/app.scss', 'themes/default/assets/css')
```

В этой строке мы говорим сборщику чтобы он взял файл **themes/default/src/scss/app.scss**, преобразовал его в пригодный для браузера вид, сжал и положил его в папку **themes/default/assets/css**. Т.к. название файла явно не указали, сборщик назовет файл так же как и исходный, но расширение укажет css. т.е. app.css. В итоге получится так: **themes/default/assets/css/app.css**

Теперь мы разобрались как у нас попадают файлы **app.js** и **app.css** в нужные папки.

Давайте теперь создадим свою тему и настроим сборщик так, чтобы он собирал ещё и стили и скрипты в нашей теме.\
Создаем в папке **themes** подпапку с нашей темой **my\_theme**\
Из папки с темой **default** давайте скопируем 2 папки. src и assets\
На этом наша тема готова к сборке.\
Теперь давайте расскажем о ней сборщику и соберем наши стили и скрипты.

Открываем файл **/webpack.mix.js** \
Вставим после строки

```javascript
mix.sourceMaps(true, 'source-map');
```

следующие 2 строки:

```javascript
mix.js('themes/my_theme/src/js/app.js', 'themes/my_theme/assets/js')
    .sass('themes/my_theme/src/scss/app.scss', 'themes/my_theme/assets/css');
```

Мы видим тут те же самые пути, которые рассматривали выше, за исключением папки с темой. Это наша новая папка с темой, которую мы создали.

На этом сборщик настроен и уже будет работать.

Давайте откроем командную строку, перейдем в папку с установленным johncms для этого наберите cd и путь к папке в которой установлен johncms.\
После этого давайте установим зависимости и запустим сборщик.\
Выполните команду\
**`npm install`**\
Эта команда установит bootstrap и прочие библиотеки, необходимые для работы.

После этого выполните команду\
**`npm run watch`**

Эта команда соберет app.js и app.css и будет следить за изменением исходных файлов и пересобирать app.js и app.css когда вы изменяете исходные файлы.\
В результате её выполнения вы должны увидеть следующее:

```bash
        Asset                          Size                         Chunks                   Chunk Names
themes/default/assets/css/app.css      258 KiB  /themes/default/assets/js/app  [emitted]        /themes/default/assets/js/app
themes/default/assets/css/app.css.map  295 KiB  /themes/default/assets/js/app  [emitted] [dev]  /themes/default/assets/js/app
themes/my_theme/assets/css/app.css     258 KiB  /themes/default/assets/js/app  [emitted]        /themes/default/assets/js/app
themes/my_theme/assets/css/app.css.map 295 KiB  /themes/default/assets/js/app  [emitted] [dev]  /themes/default/assets/js/app
 + 4 hidden assets
```

Давайте теперь разбираться в структуре css и js.\
Откроем файл: **themes/my\_theme/src/js/app.js**\
Этот файл является основным и в нем подключаются все дополнительные файлы. \
Все дополнительные файлы лежат в той же папке что и основной файл. Вы можете открывать их, редактировать или смотреть что в них находится.

Откроем файл: **themes/my\_theme/src/scss/app.scss**\
так же как и app.js этот файл является основным файлом в котором подключаются все дочерние.

Давайте посмотрим файл и найдем наш сайдбар чтобы поменять цвет.\
Найдем строки

```css
// Левое меню
@import "sidebar";
```

Эта строка подключает файл **sidebar.scss** из той же папки что и **app.scss**\
Давайте откроем файл **sidebar.scss**\
В этом файле мы видим практически привычный CSS код. Но он поддерживает вложенность селекторов и прочие возможности. Вы можете подробнее прочитать про SCSS (SASS) на просторах интернета или спросить у нас на форуме.

И так, давайте поменяем всё таки цвет нашего меню. Цвет меню задан прямо во второй строке:

```css
background-color: #ffffff;
```

Меняем код цвета и сохраняем файл.\
После сохранения, сборщик пересоберет app.css и вы увидите изменения на сайте.

Обратите внимание, что команда **`npm run watch`** выполняет сборку, но не выполняет сжатие CSS и JS файлов для ускорения работы.\
Перед тем, как вы захотите выгрузить изменения на сайт, выполните команду **`npm run prod`** она соберет файлы и выполнить минификацию. После этого размер файлов будет меньше.

Примечание:\
После создания темы, не забудьте зайти в настройки и выбрать новую тему :)


# Создание собственного шаблона

Давайте создадим свой первый простой шаблон.\
Начнем с задачи, которая изначально возникнет практически у всех, кто установит себе JohnCMS: мы будем менять Главную страницу сайта и логотип. Перед тем, как взяться за создание своего шаблона, давайте составим примерный план предполагаемых работ.

### **Что мы сделаем?**

* Создадим свою тему с названием "lesson"
* Поменяем Главную страницу сайта. Вместо имеющегося по умолчанию текста, на ней крупными буквами выведем "Добро пожаловать!"
* Заменим логотип сайта. Вместо JohnCMS будем использовать свою .PNG картинку.
* Изменим цвет боковой панели навигации: вместо белого использовать какой-нибудь темный оттенок, подходящий по дизайну. Соответственно поменяем цвет иконок.

{% hint style="danger" %}

### Внимание!

У движка есть тема "**default**", которая является системной, поставляется вместе с дистрибутивом и находится в папке `/themes/default`. В этой теме находятся все необходимые для работы файлы по умолчанию: шаблоны, стили, картинки, скрипты и т.д. Также, каждый отдельный модуль может иметь свою папку с шаблонами `/module_name/templates`, или другими файлами общего доступа `/assets/modules/module_name`.

**Нельзя редактировать, или удалять файлы в этих папках, нельзя ничего туда добавлять**, иначе Вы потеряете совместимость с последующими обновлениями, или же в работе движка могут возникнуть ошибки, вплоть до полной потери работоспособности.
{% endhint %}

#### Пошаговая инструкция

1. В папке `/themes` создаем папку `lesson`
2. Заходим в админку и далее в системные настройки. Там в списке имеющихся тем мы увидим нашу **lesson**. Выбираем ее и нажимаем "Сохранить".\
   Теперь для нашего сайта применена тема "lesson" и все, что мы будем в ней делать, сразу же будет видно.
3. Чтобы поменять Главную страницу сайта, мы должны отредактировать ее шаблон, который находится в модуле `/modules/homepage`.\
   В папке с модулем есть папка `/templates` а в ней лежит файл `index.phtml` - это и есть Главная страница, этот файл нам и нужен.\
   Из предупреждения выше мы знаем, что менять шаблон в самом модуле нельзя, поэтому мы должны сначала скопировать файл шаблона в свою тему, и только потом его изменять. Не переместить, а именно скопировать, оригинал файла должен остаться на своем месте
4. Куда? `/themes/lesson` - это папка с нашей темой, которую мы создали выше. Мы должны скопировать сюда файл `index.phtml` из модуля homepage. Для шаблонов в папке с нашей темой должна быть подпапка `templates`.\
   Чтоб не возникало конфликтов (например файл `index.phtml` может быть у многих модулей), в папке `templates` создается подпапка с названием пространства имен для шаблонов модуля (обычно совпадает с именем папки модуля) и уже в нее копируется нужный нам файл.
5. В папке с нашей темой `/themes/lesson` создаем подпапку `templates` а в ней подпапку с именем модуля ( в нашем случае это `homepage`) откуда мы копируем шаблон. В итоге должно получиться `/themes/lesson/templates/homepage/` сюда и копируем наш `index.phtml`\
   Теперь, пока у нас в админке включена наша тема "lesson", для Главной страницы используется именно тот файл, который мы только что скопировали в нашу тему. И все изменения в этом файле сразу будут видны на Главной странице нашего сайта.

{% hint style="info" %}
Инструкция будет дополнена.
{% endhint %}


# Структура модуля

Модули располагаются в папке **modules**\
Обычно модуль для JohnCMS имеет следующую структуру:

* modules
* * module\_name
  * * includes
    * locale
    * templates
    * index.php

Данная структура носит лишь рекомендательный характер и не является обязательной.\
Система не накладывает ограничений на разработчика и разработчик вправе использовать свою структуру модуля, которая для него будет удобнее.&#x20;

Давайте подробнее посмотрим на структуру и разберемся что и для чего предназначено.

**modules** - это обычная системная папка с модулями.\
**module\_name** - это папка с названием модуля (например forum, community и т.п.)\
**includes** - папка для дополнительных страниц. Её может и не быть если модуль достаточно простой и не содержит большого количества страниц.\
**locale** - это папка в которой хранятся файлы локализации модуля. Если модуль мультиязычный, то эта папка обычно есть.\
**templates** - в этой папке хранятся шаблоны модуля.\
**index.php** - Этот файл обычно служит точкой входа в модуль и содержит программный код или часть кода всего модуля.


# Создание модуля

Давайте создадим свой первый простой модуль.\
Это будет обычная простая страница контактов для связи с администрацией сайта.

Как нам уже известно, модули располагаются в папке **modules**

Сначала давайте создадим папку с модулем и назовем её **contacts** путь к папке получится такой: **modules/contacts**

Пока создадим простой модуль без мультиязычности.

Внутри папки **modules/contacts** создадим подпапку **templates** для шаблона нашей страницы.\
Дополнительные папки нам больше не понадобятся т.к. модуль у нас будет содержать всего лишь одну страницу.\
Создадим точку входа в модуль, которая будет открываться при запросе страницы контактов и в которой будет подключен наш шаблон. Для этого создадим файл **index.php**

В этом файле поместим следующий код:

```php
<?php

// Запрещаем прямой запрос к файлу модуля без подключенного ядра
defined('_IN_JOHNCMS') || die('Error: restricted access');

// Инициализируем шаблонизатор
$view = di(Johncms\System\View\Render::class);

// Инициализируем хлебные крошки (цепочка навигации вверху всех страниц)
$nav_chain = di(Johncms\NavChain::class);

// Указываем шаблонизатору папку, из которой нужно загружать шаблоны нашего модуля
$view->addFolder('contacts', __DIR__ . '/templates/');

// Добавляем ссылку Контакты в хлебные крошки
$nav_chain->add('Контакты', '/contacts/');

// Собираем массив данных, который будет передан в шаблон
$data = [
    'title'      => 'Контакты',
    'page_title' => 'Наши контакты',
];

// Дополним массив $data нашими контактными данными, которые выведем дальше в шаблоне
$data['contacts'] = [
    [
        'name'  => 'E-mail', // Название контакта
        'value' => 'admin@example.com', // Значение, которое будет отображаться
    ],
    [
        'name'  => 'Номер телефона',
        'value' => '+7 (999) 121-12-21',
    ],
    [
        'name'  => 'Telegram',
        'value' => '@johncms_official',
    ],
];

// Подключаем шаблон index.phtml и передаем в него собранные выше данные
echo $view->render('contacts::index', ['data' => $data]);
```

В комментариях к каждой строке кода даны пояснения для чего она.

Далее давайте создадим наш шаблон. Шаблон будет располагаться в папке **templates** и т.к. это основная страница контактов, назовем шаблон **index.phtml**\
В этом файле разместим следующий код:

```php
<?php

// Подключаем основной шаблон сайта
$this->layout(
    'system::layout/default',
    [
        'title'      => $data['title'], // Передаем заголовок страницы в тег title
        'page_title' => $data['page_title'], // Передаем заголовок страницы в тег h1
    ]
);
?>

<div>
    Вы можете связаться с нами по любому из нижеперечисленных контактов:
</div>

<ul>
    <!-- Тут мы перебираем наш массив контактов и выводим название контакта и значение, разделяя их двоеточием -->
    <?php foreach ($data['contacts'] as $contact): ?>
        <li><?= $contact['name'] ?>: <b><?= $contact['value'] ?></b></li>
    <?php endforeach; ?>
</ul>
```

Наш модуль готов, но пока ещё не доступен в браузере. Давайте это исправим.\
Чтобы модуль стал доступен, нужно сообщить системе, что у нас есть такой модуль и мы хотим чтобы он был доступен по определенному адресу.\
Для этого давайте перейдем в папку **config** и в ней создадим файл **routes.local.php** если его ещё нет. Если есть, то откроем его и добавим маршрут для нашего модуля.

```php
<?php

/**
 * /contacts/ - Это адрес страницы по которому будет доступен наш модуль
 * modules/contacts/index.php - Это путь к точке входа в наш модуль
 */
$map->addRoute(['GET', 'POST'], '/contacts/', 'modules/contacts/index.php');
```

Теперь наш модуль доступен по адресу ваш.сайт/contacts/\
\
Теперь давайте сообщим модулю online, что у нас появился модуль контактов и нужно в списке пользователей онлайн отображать тех, кто смотрит контакты.\
Для этого давайте перейдем в папку **config** и в ней создадим файл **places.local.php** если его ещё нет.

```php
<?php

return [
    '/contacts' => '<a href="/contacts/">Смотрит контакты</a>',
];
```

Отлично, наш модуль теперь полностью работоспособен, вам останется только добавить на него ссылку в основном шаблоне или на любой другой странице на ваше усмотрение.


# Маршрутизация (роутинг)

## **Для чего нужен роутер в JohnCMS?**

Как и в других CMS и фреймворках роутер в JohnCMS обрабатывает запрошенный URL адрес и определяет какой модуль запустить для обработки этого запроса.\
В свою очередь модуль может получить от роутера различные параметры в зависимости от настроек маршрута и использовать для реализации своего функционала.

Перейдем к практической части.

## Где хранятся настройки маршрутизации?

Настройки для системных модулей JohnCMS хранятся в файле **/config/routes.php**

Так же система позволяет задавать маршруты для сторонних модулей.\
Для этого предназначен файл **/config/routes.local.php**

Почему для маршрутов сторонних модулей используется отдельный файл?\
Дело в том, что при обновлениях JohnCMS файл **/config/routes.php** может меняться и при очередном обновлении все Ваши изменения в нем, будут утеряны.\
Чтобы решить эту проблему, используется файл **/config/routes.local.php**

Пример файла **/config/routes.local.php**

{% code title="/config/routes.local.php" %}

```php
<?php

declare(strict_types=1);

/**
 * @var FastRoute\RouteCollector $map
 */

/*
 * /contacts/ - Это адрес страницы по которому будет доступен наш модуль
 * modules/contacts/index.php - Это путь к точке входа в наш модуль
 */

$map->addRoute(['GET', 'POST'], '/contacts/', 'modules/contacts/index.php');
```

{% endcode %}

Давайте теперь рассмотрим детально как работать с роутером и как использовать его в своих модулях?

Возьмём простой пример из примера выше.\
Маршрут у нас в нем задается такой строкой:

```php
$map->addRoute(['GET', 'POST'], '/contacts/', 'modules/contacts/index.php');
```

Эта строка говорит роутеру следующее:\
Если запрос пришел методом GET или POST и он поступил на страницу site.ru/contacts/, то необходимо выполнить файл modules/contacts/index.php\
Таким образом, когда пользователь переходит по адресу site.ru/contacts/ он видит результат выполнения файла modules/contacts/index.php

Давайте рассмотрим более сложные примеры маршрутизации.\
Для этого давайте изменим наш простой модуль контактов, который мы создавали в предыдущей статье [Создание модуля](https://johncms.com/documentation/create_module/)

Откроем файл /config/routes.local.php

Изменим нашу строку маршрута следующим образом:

```php
$map->addRoute(['GET', 'POST'], '/contacts/[{action}/]', 'modules/contacts/index.php');
```

Мы добавили в неё дополнительный параметр \[{action}/]\
Что это значит?\
Квадратные скобки говорят роутеру, что этот параметр у нас не обязателен (он может быть, а может и не быть).\
В фигурных скобках задается название параметра, чтобы модуль смог с ним работать.\
Слэш мы ставим чтобы ограничить выбор т.е. выбираться будет та часть адреса, которая расположена между /contacts/ и следующим слешем.

Чтобы было понятнее, давайте разберем на примерах.\
1\. **site.ru/contacts/** - В таком варианте у нас параметр action будет игнорироваться т.к. роутер считает его необязательным и откроет нашу страницу контактов.\
2\. **site.ru/contacts/moscow** - В таком варианте роутер откроет страницу ошибки 404 т.к. URL у нас не заканчивается обратным слешем, а в настройках маршрута мы явно указали, что если после /contacts/ есть ещё что-то, то обрабатываем этот маршрут только если он заканчивается слешем (/).\
3\. **site.ru/contacts/moscow/**  - Такой вариант откроет нашу страницу контактов и в модуле будет доступен параметр action. В этом параметре будет содержаться слово "moscow".\
4\. **site.ru/contacts/new\_york/** - Тоже самое что и в варианте 3, только в параметре action будет "new\_york"\
5\. **site.ru/contacts/new\_york/test1/** - Выдаст ошибку 404 т.к. роутер видит, что маршрут не подходит нам (содержит больше данных чем нужно для нашего маршрута).

Давайте теперь разберемся как в модуле нам получить параметры, которые мы указываем в роутере.\
Откроем файл **modules/contacts/index.php**\
В начале файла после строки **defined('\_IN\_JOHNCMS') || die('Error: restricted access');** вставим следующий код:

{% code title="modules/contacts/index.php" %}

```php
// Получаем массив параметров, которые вернул нам роутер

$route = di('route');
// Выведем их на экран
d($route);

// Прекратим выполнение скрипта
exit;
```

{% endcode %}

Перейдем по адресу: **site.ru/contacts/moscow/**

В браузере у вас отобразится следующий текст:

```php
Array
(
    [action] => moscow
)
```

Как мы видим, параметр, который мы назвали в настройках маршрута action, появился у нас в массиве и содержит слово moscow.

В модуле мы можем обратиться к этому параметру и в зависимости от его содержимого управлять логикой работы модуля.\
Получить этот параметр можно, как вы наверное уже догадались, следующим образом:\
&#x20;$route\['action']

Давайте усложним маршрут.\
Откроем файл **/config/routes.local.php**\
Изменим нашу строку маршрута следующим образом:

```php
$map->addRoute(['GET', 'POST'], '/contacts/[{action}/[{id:\d+}/]]', 'modules/contacts/index.php');
```

В этом параметре мы добавили ещё один необязательный параметр и назвали его id. Через двоеточие мы указали регулярное выражение по которому будем вызывать этот маршрут. Указанное регулярное выражение принимает только цифры.\
Теперь у нас модуль контактов открывается по адресам:\
site.ru/contacts/\
site.ru/contacts/moscow/\
site.ru/contacts/moscow/123456/

Вместо слова moscow может быть любое слово, а вместо 123456 может быть любое число.\
Перейдем по адресу site.ru/contacts/moscow/123456/ и посмотрим что у нас выведется.

Вывелось следующее:

```php
Array
(
    [action] => moscow
    [id] => 123456
)
```

Как видим, пришел параметр action и id

Давайте добавим третий параметр и ещё усложним наш маршрут.\
Откроем файл /config/routes.local.php\
Изменим нашу строку маршрута следующим образом:

```php
$map->addRoute(['GET', 'POST'], '/contacts/[{action}/[{id:\d+}/[{street}/]]]', 'modules/contacts/index.php');
```

В этом маршруте мы добавили параметр street, он не ограничен только цифрами и может принимать любую строку.\
Рассмотрим примеры адресов, которые будут доступны для такого маршрута:\
**site.ru/contacts/**\
**site.ru/contacts/moscow/**\
**site.ru/contacts/moscow/123456/**\
**site.ru/contacts/moscow/123456/sadovaya/**

Перейдем по адресу:\
**site.ru/contacts/moscow/123456/sadovaya/**

Отобразилось следующее:

```php
Array
(
    [action] => moscow
    [id] => 123456
    [street] => sadovaya
)
```

Давайте переименуем параметр action в city чтобы на различных примерах посмотреть на что влияет это название

```php
$map->addRoute(['GET', 'POST'], '/contacts/[{city}/[{id:\d+}/[{street}/]]]', 'modules/contacts/index.php');
```

Перейдем по тому же адресу:\
site.ru/contacts/moscow/123456/sadovaya/

Получим результат:

```php
Array
(
    [city] => moscow
    [id] => 123456
    [street] => sadovaya
)
```

Как видим в результате тоже поменялось название параметра.

Мы рассмотрели наиболее частые варианты использования маршрутизации и надеемся дальше вы сможете самостоятельно строить ещё более сложные маршруты.\
С другими примерами маршрутов, вы так же можете ознакомиться в документации к библиотеке <https://github.com/nikic/FastRoute> , которая используется в JohnCMS для работы с маршрутами.


# Перевод JohnCMS на другие языки

JohnCMS является мультиязычной CMS. К сожалению разработчики не знают всех языков, которые существуют на планете и для того чтобы система оставалась мультиязычной, необходимо чтобы люди, знающие другие языки, помогали с переводом.\
Мы постарались максимально упростить процесс перевода системы на другие языки. Для того, чтобы перевести систему на другой язык, нет необходимости обладать специальными навыками программирования. Перевод осуществляется непосредственно в браузере и может быть выполнен любым желающим.

Чтобы поучаствовать в переводе нужно выполнить некоторые действия. Давайте рассмотрим их по порядку.

Переходим по ссылке [translate.johncms.com](https://translate.johncms.com/)

Вам необходимо авторизоваться на сайте. Если у вас уже есть учетная запись на crowdin.com или вы зарегистрированы в Facebook, Google, Twitter, Github или Gitlab, то можете нажать на кнопку **Log in.** Перед вами появится окно авторизации в котором вы можете ввести логин и пароль от учетной записи или войти через описанные выше сервисы.\
Если учетной записи нет, то нажимаете на Sign up и регистрируетесь.

![страница авторизации](/files/-MUj1pTMpJYk5_RndktT)

После авторизации вы можете приступать к переводу. Для этого в списке языков выберите тот язык, на который вы хотите перевести систему

![список языков](/files/-MUj2-RRv8WZrZ8l3yVE)

Если нужного языка нет в списке, мы можем его добавить. Для этого свяжитесь с разработчиками любым удобным для вас способом (например: <info@johncms.com>) и напишите название языка, на который Вы хотите перевести систему.

После выбора языка перед вами появится эта страница:

![список переводов](/files/-MUj27dr87Uhu2P1Yegd)

На этой странице отображен список переводов для модулей системы и процент фраз, которые уже переведены.

Выбираете модуль, который хотите перевести. Попадаете на страницу со списком фраз:

![список фраз](/files/-MUj2EaT-vrEeC7tMtD6)

В левой части отображается список фраз для перевода.\
В центре отображается сам перевод и информация о том, в каких файлах содержится фраза (**context**). Так же вы можете посмотреть как эта фраза переведена на другие языки, для этого разверните блок **OTHER LANGUAGES.** Так же есть уже автоматически переведенные варианты (**TM and MT Suggestions**), которые вы можете выбрать если там есть подходящий вариант. Если подходящего варианта в автоматических переводах нет, то нужно вписать перевод вручную в поле ввода и нажать **Save** (сохранить). После сохранения автоматически откроется следующая фраза для перевода в текущем модуле.

После завершения перевода модуля, вы можете выйти назад в список модулей. Для этого вверху слева нажимаете на меню и выбираете Quit Editor. После этого вы попадете обратно в список модулей и можете переводить другие модули аналогичным образом.

После того как Вы выполняете перевод, Ваши переводы проходят проверку разработчиками и загружаются в CMS, а потом с выходом обновлений становятся доступны всем.

Обращаем Ваше внимание, что для того, чтобы перевод был включен в дистрибутив, он должен охватывать как минимум 50% фраз.


# Настройки подключения к базе данных

JohnCMS как и многие другие системы для работы использует базу данных.\
Когда вы устанавливаете систему, создается файл **config/autoload/database.local.php** в этом файле хранятся настройки подключения к базе данных.

На данный момент по умолчанию он выглядит так:

```php
array (
    'db_host' => 'localhost',
    'db_name' => 'johncms',
    'db_user' => 'database_user',
    'db_pass' => 'password',
  ),
);
```

Это минимально необходимый список параметров для работы системы. В некоторых случаях может понадобиться задать дополнительные параметры, такие как порт и драйвер.\
На данный момент максимально полный файл конфигурации подключения выглядит так:

```php
array (
    'db_driver' => 'mysql',
    'db_host' => 'localhost',
    'db_name' => 'johncms',
    'db_user' => 'database_user',
    'db_pass' => 'password',
    'db_port' => '3306',
  ),
);
```

В параметре db\_port указывается порт, который используется для подключения к БД.\
В параметре **db\_driver** указывается драйвер для работы с базой данных.

{% hint style="info" %}
В настоящее время полностью поддерживается работа с **MySQL 5.6.4** и выше.
{% endhint %}


# Выполнение запросов к базе данных

На данный момент в JohnCMS доступны несколько вариантов выполнения запросов к базе данных.

## **PDO**

Этот вариант многим известен и применяется ещё с JohnCMS 7.0.\
Давайте рассмотрим особенности использования этого варианта.\
Чтобы получить объект PDO нам достаточно написать следующий код:&#x20;

```php
$db = di(PDO::class);
```

Далее используя объект **$db** вы можете выполнять запросы к базе данных.\
Рассмотрим пример, который получает записи из таблицы users:&#x20;

```php
$db = di(PDO::class);
$req = $db->query('SELECT * FROM `users`');
while ($row = $req->fetch()) {
    echo $row['name'] .'
';
}
```

Этот пример выведет список имен пользователей, которые есть в таблице users.

{% hint style="danger" %}
**Обратите внимание**\
При работе с этим вариантом вы должны самостоятельно заботиться о безопасности запросов.
{% endhint %}

## **Конструктор запросов (Query Builder)**

В JohnCMS для работы с БД используется библиотека [illuminate/database](https://github.com/illuminate/database) которая и предоставляет конструктор запросов и ORM.\
Рассмотрим несколько основных примеров чтобы понять особенности работы с библиотекой в JohnCMS.

Для выполнения запросов, сначала нам необходимо получить объект текущего подключения к БД:&#x20;

```php
$connection = \Illuminate\Database\Capsule\Manager::connection();
```

&#x20;\
Далее давайте выполним тот же запрос, который выполняли в обычном PDO варианте выше.&#x20;

```php
$users = $connection->table('users')->get();
foreach ($users as $user) {
    echo $user->name . '<br>';
}
```

Этот запрос так же как и в предыдущем варианте выведет список имен пользователей, которые есть в таблице users.\
Метод **get** возвращает объект **Illuminate\Support\Collection** c результатами, в котором каждый результат — это экземпляр PHP-класса **StdClass**. Вы можете получить значение каждого столбца, обращаясь к столбцу как к свойству объекта.\
Давайте рассмотрим вариант получения одной строки из таблицы.&#x20;

```php
$user = $connection->table('users')->where('name', 'admin')->first();
echo $user->name;
```

Этот запрос вернет пользователя, у которого поле **name** равно **admin**.

Рассмотрим вариант вывода записей из таблицы с разбивкой на страницы по 5 элементов:&#x20;

```php
$user = $connection->table('users')->paginate(5);
foreach ($user as $item) {
    echo $item->name;
}
echo $user->render(); 
```

При вызове метода **paginate** будет автоматически установлены ограничения для запроса и построен запрос количества элементов в таблице по указанному вами запросу.\
Т.е. при таком вызове вам не нужно заботиться об указании **limit** для запроса и не нужно строить запрос на количество записей, конструктор запросов сделает это за вас.\
При вызове метода render из нашего объекта, будет отрисована постраничная навигация.\
URL адреса будут построены исходя из текущей страницы. Вам так же не нужно заботиться об их формировании.\
Шаблон вывода постраничной навигации расположен тут:\
**themes/default/templates/system/app/model\_paginator.phtml**

### **Выборка только необходимых столбцов**

Иногда вам может понадобиться выбрать только определенные столбцы из таблицы в базе данных.\
Сделать это можно так:

```php
$user = $connection->table('users')->select(['name', 'id'])->get();
foreach ($user as $item) {
    echo $item->id . ' - ' . $item->name;
}
```

Для выборки конкретных столбцов используется метод select(). Он принимает названия столбцов в виде массива или просто списком аргументов. Например: **select('name', 'id')**

### **Сортировка результата выборки**

Часто есть необходимость отсортировать результат выборки по какому-либо столбцу.

```php
$user = $connection->table('users')
    ->select('name', 'id')
    ->orderBy('id')
    ->orderByDesc('name')
    ->get();
foreach ($user as $item) {
    echo $item->id . ' - ' . $item->name;
}
```

В этом примере результат будет отсортирован по **id** и **name**. Метод **orderBy** вторым аргументом принимает направление сортировки **asc** или **desc**. По умолчанию **asc**. Метод **orderByDesc** это то же самое, что и **orderBy('id', 'desc')**\
Как видно из примера, сортировать можно по нескольким колонкам. Указанный выше пример выполнит следующий запрос к базе данных:

```sql
select `name`, `id` from `users` order by `id` asc, `name` desc
```

Мы рассмотрели общий принцип построения запросов.\
Если вы хотите ознакомиться подробно с конструктором запросов, вы можете это сделать [здесь](https://laravel.com/docs/7.x/queries) или на русском: [здесь](https://laravel.su/docs/5.4/queries)\
Обратите внимание, что для выполнения запросов нужно использовать объект **$connection,** а не **DB::**. В остальном все возможности, которые описаны по ссылкам, будут работать и в JohnCMS.


# Вставка записей (insert)

Конструктор запросов позволяет вставлять записи в базу данных. При этом конструктор избавляет вас от необходимости писать SQL запросы самостоятельно. Вы просто используете объектно ориентированные возможности PHP. А если вы используете IDE, то это существенно упростит вам жизнь благодаря автодополнению кода.

Для работы с базой данных нужно получить объект подключения к базе данных. Это можно сделать следующим образом:

```php
$connection = \Illuminate\Database\Capsule\Manager::connection();
```

Далее рассмотрим примеры вставки данных в таблицу в базе данных. В примере будет рассматриваться таблица со следующей структурой:

![Структура таблицы test\_table](/files/-MUj5zjuImtp0570iXba)

## Вставка строки

Рассмотрим пример обычной вставки строки в таблицу **test\_table**.

```php
$connection->table('test_table')->insert(
    [
        'name' => 'test name',
        'text' => 'text text text',
    ]
);
```

Этот пример кода вставит строку в базу данных. Как видите всё достаточно просто. В метод **table** подается название таблицы с которой работаем, а далее вызывается метод **insert** в который подается ассоциативный массив в котором ключем является название колонки в таблице **test\_table**, а значением является значение, которое будет вставлено.

## Вставка строки и получение идентификатора вставленной записи

В примере выше мы рассмотрели обычную вставку строки в базу данных. Но часто нам нужно вдобавок к этому получить идентификатор вставленной записи. Давайте сделаем это.

```php
$id = $connection->table('test_table')->insertGetId(
    [
        'name' => 'test name',
        'text' => 'text text text',
    ]
);
```

Как вы видите, вместо метода **insert** использовался метод **insertGetId**, а результат присваивается переменной **$id**. После выполнения этого кода в переменной **$id** будет содержаться идентификатор вставленной записи.

## Вставка нескольких строк в таблицу

Иногда есть необходимость вставить сразу много строк в таблицу в базе данных. Конструктор запросов позволяет сделать и это.

```php
$connection->table('test_table')->insert(
    [
        [
            'name' => 'test name 1',
            'text' => 'text text text 1',
        ],
        [
            'name' => 'test name 2',
            'text' => 'text text text 2',
        ],
        [
            'name' => 'test name 3',
            'text' => 'text text text 3',
        ],
    ]
);
```

Этот пример кода вставит в таблицу **test\_table** сразу 3 строки. В массиве, который передается в метод **insert** должны передаваться массивы с записями, которые необходимо вставить.

## Вставка с игнорированием ошибок

Иногда необходимо вставить запись в таблицу, но при этом проигнорировать записи с уже существующими уникальными значениями столбцов. Рассмотрим пример кода чтобы лучше понять.

```php
$connection->table('test_table')->insertOrIgnore(
    [
        [
            'id'   => 1,
            'name' => 'test name 1',
            'text' => 'text text text 1',
        ],
        [
            'id'   => 2,
            'name' => 'test name 2',
            'text' => 'text text text 2',
        ],
        [
            'id'   => 3,
            'name' => 'test name 3',
            'text' => 'text text text 3',
        ],
    ]
);
```

В таблице **test\_table** есть колонка **id**. Это первичный ключ и он должен быть уникальным. Если мы попытаемся вставить строку с существующим **id** обычным методом **insert**, то мы получим ошибку. Метод **insertOrIgnore** вставит 3 строки в таблицу только в том случае, если в ней нет строк с такими же идентификаторами. Если в таблице есть строки с id = 1, но нет строк с идентификаторами 2 и 3, то вставятся только строки с идентификаторами 2 и 3, а первая строка будет проигнорирована.

{% hint style="info" %}
Обратите внимание, что вам не нужно заботиться о защите от SQL инъекций. При выполнении запросов в конструкторе используются подготовленные запросы, благодаря чему выполнение запросов становится безопасным. Но учтите, что это не избавляет вас от необходимости делать данные безопасными при выводе из базы данных там где это необходимо.
{% endhint %}


# Обновление записей (update)

Помимо вставки и выборки данных конструктор запросов так же позволяет и обновлять данные в таблицах.\
Так же как и в остальных случаях работы с базой данных нам необходимо получить объект подключения к базе данных.&#x20;

```php
$connection = \Illuminate\Database\Capsule\Manager::connection();
```

В примерах ниже мы так же будем работать с таблицей test\_table, структуру которой вы можете посмотреть в предыдущей статье [Вставка записей (insert)](https://johncms.com/documentation/db-insert/)

## Обновление строки в БД

Рассмотрим пример обновления записи в БД. Так же как и метод **insert** метод **update** принимает пару **название\_колонки => значение**.

```php
$connection->table('test_table')
    ->where('id', '=', 1)
    ->update(
        [
            'name' => 'test name 1',
            'text' => 'text text text 1',
        ]
    );
```

Указанный пример обновит строку с идентификатором 1 в таблице **test\_table** и установит значения столбцов, переданные в методе **update**. Обратите внимание, что мы ещё добавили вызов метода **where**, который устанавливает условие выборки. Для уточнения выборки может вызываться так же несколько методов **where** чтобы задать точное условие выборки записей, которые нужно обновить.

## Обновление или вставка

Часто встречается ситуация, когда нам нужно обновить запись в базе данных если она уже есть или же вставить если её нет. Обычно это делается вручную. Проверяется наличие записи в БД, и в зависимости от этого вызываются методы на вставку или обновление записи. Это не всегда удобно и заставляет писать много кода.\
Конструктор позволяет упростить выполнение этой операции. По факту он делает то же самое, но для выполнения этих действий вам не нужно вручную писать выборку, проверку и вставку или обновление.

Рассмотрим на примере:

```php
$connection->table('test_table')
    ->updateOrInsert(
        [
            'name' => 'test',
        ],
        [
            'text' => 'text text text 1',
        ]
    );
```

В этом примере будет выполнен поиск строки с полем **name** в котором содержится значение **test** и если эта запись уже существует, в ней будет обновлено поле **text**. Если такой строки в БД найдено не будет, то она будет вставлена с обоими значениями.\
Подытожим. Метод **updateOrInsert** принимает 2 массива. В первом аргументе принимается массив с условиями, которые будет выполнен поиск записи, а во втором будут значения, которые будут установлены. Если записи не существует, то будет вставлена новая запись со значениями из обоих массивов.

{% hint style="info" %}
Обратите внимание, что вам не нужно заботиться о защите от SQL инъекций. При выполнении запросов в конструкторе используются подготовленные запросы, благодаря чему выполнение запросов становится безопасным. Но учтите, что это не избавляет вас от необходимости делать данные безопасными при выводе из базы данных там где это необходимо.
{% endhint %}


# Удаление записей (delete)

Конструктор запросов так же позволяет удалять данные из таблиц в базе данных. Так же как и в остальных случаях работы с базой данных нам необходимо получить объект подключения к базе данных.&#x20;

```php
$connection = \Illuminate\Database\Capsule\Manager::connection();
```

В примерах ниже мы так же будем работать с таблицей test\_table, структуру которой вы можете посмотреть в статье [Вставка записей (insert)](https://johncms.com/documentation/db-insert/)

Вы можете удалить все записи из таблицы следующим образом:

```php
$connection->table('test_table')->delete();
```

Этот пример кода удалит все записи из таблицы test\_table. Обратите внимание, значение автоинкремента не изменяется при таком подходе, по этому идентификаторы будут генерироваться не с нуля, а продолжат с того же номера на котором остановились.

Так же вы можете удалить запись с определенным идентификатором (если в таблице есть колонка id). Для этого в метод delete() передайте идентификатор строки, которую хотите удалить.

```php
$connection->table('test_table')->delete(10);
```

Конструктор поддерживает установку дополнительных условий для удаления. В следующем примере удалятся все записи у которых идентификатор будет меньше чем 15

```php
$connection->table('test_table')->where('id', '<', 15)->delete();
```

А этот пример удалит все записи с именем test

```php
$connection->table('test_table')->where('name', '=', 'test')->delete();
```

Иногда нужно очистить таблицу полностью и сбросить значение автоинкремента. Сделать это можно следующим образом:

```php
$connection->table('test_table')->truncate();
```


# Общие сведения и начало работы

## Введение

Система объектно-реляционного отображения (ORM) Eloquent — простая реализация шаблона ActiveRecord для работы с базами данных. Каждая таблица имеет соответствующий класс-модель, который используется для работы с этой таблицей. Модели позволяют запрашивать данные из таблиц, а также вставлять, обновлять и удалять в них записи.

## Определение моделей

Для начала создадим модель Eloquent. Все модели Eloquent наследуют класс **Illuminate\Database\Eloquent\Model**.\
Допустим мы делаем модуль блогов. Создадим базовую структуру модуля как [описано здесь](https://johncms.com/documentation/create_module/). Модуль назовём **blog.**\
Теперь давайте создадим в нем папку **lib** в которой будем хранить классы нашего модуля и в этой папке создадим подпапку **models** в которой уже будем размещать наши модели.\
В итоге должен получиться такой путь: **lib/models**\
Теперь настроим автозагрузку классов из папки lib. Для этого в файле index.php нашего модуля поместим следующий код:

```php
$loader = new Aura\Autoload\Loader();
$loader->register();
$loader->addPrefix('Blog', __DIR__ . '/lib');
```

С помощью метода **addPrefix** первым параметром мы указываем пространство имен (namespace) **Blog** и указываем папку в которой располагаются классы для этого пространства имен.\
Создайте таблицу posts с примерно таким набором полей:

* id
* user\_id
* name
* text
* created\_at
* updated\_at

Пример запроса на создание таблицы:

```sql
CREATE TABLE `posts`
(
    `id`         INT          NOT NULL AUTO_INCREMENT,
    `user_id`    INT          NOT NULL,
    `name`       VARCHAR(255) NOT NULL,
    `text`       LONGTEXT     NULL DEFAULT NULL,
    `created_at` TIMESTAMP    NULL DEFAULT NULL,
    `updated_at` TIMESTAMP    NULL DEFAULT NULL,
    PRIMARY KEY (`id`),
    INDEX `user_id` (`user_id`)
) ENGINE = InnoDB;
```

&#x20;Теперь создадим модель.\
&#x20;В папке **lib/models** создайте файл **Post.php** со следующим содержимым

{% code title="lib/models/Post.php" %}

```php
<?php

namespace Blog\Models;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;

/**
 * @mixin Builder
 */
class Post extends Model
{

}
```

{% endcode %}

На этом модель готова и она уже работоспособна. &#x20;

### Имена таблиц

Заметьте, что мы не указали, какую таблицу Eloquent должен привязать к нашей модели. Если это имя не указано явно, то в соответствии с принятым соглашением будет использовано имя класса в нижнем регистре (snake case) и во множественном числе. В нашем случае Eloquent предположит, что модель Post хранит свои данные в таблице posts. Вы можете указать произвольную таблицу, определив свойство table в классе модели:

```php
<?php

namespace Blog\Models;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;

/**
 * @mixin Builder
 */
class Post extends Model
{
    /**
     * Таблица, связанная с моделью.
     *
     * @var string
     */
    protected $table = 'my_table';

}
```

### Первичные ключи

Eloquent также предполагает, что каждая таблица имеет первичный ключ с именем id. Вы можете определить свойство $primaryKey для указания другого имени.\
Вдобавок, Eloquent предполагает, что первичный ключ является инкрементным числом, и автоматически приведёт его к типу int. Если вы хотите использовать неинкрементный или нечисловой первичный ключ, задайте открытому свойству $incrementing вашей модели значение false.

### Отметки времени

По умолчанию Eloquent ожидает наличия в ваших таблицах столбцов `created_at` и `updated_at`. Если вы не хотите, чтобы они автоматически обрабатывались в Eloquent, установите свойство `$timestamps` класса модели в `false`:

```php
<?php

namespace Blog\Models;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;

/**
 * @mixin Builder
 */
class Post extends Model
{
    /**
     * Определяет необходимость отметок времени для модели.
     *
     * @var bool
     */
    public $timestamps = false;

}
```

Если вы хотите изменить формат отметок времени, задайте свойство `$dateFormat` вашей модели. Это свойство определяет, как атрибуты времени будут храниться в базе данных, а также задаёт их формат при сериализации модели в массив или JSON:

```php
<?php

namespace Blog\Models;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;

/**
 * @mixin Builder
 */
class Post extends Model
{
    /**
     * Формат хранения отметок времени модели.
     *
     * @var string
     */
    protected $dateFormat = 'U';

}
```

Если вам надо изменить имена столбцов для хранения отметок времени, вы можете задать константы `CREATED_AT` и `UPDATED_AT`:

```php
<?php

namespace Blog\Models;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;

/**
 * @mixin Builder
 */
class Post extends Model
{
    const CREATED_AT = 'creation_date';
    const UPDATED_AT = 'last_update';
}
```

### Получение моделей

После создания модели и связанной с ней таблицы, вы можете начать получать данные из вашей БД. Каждая модель Eloquent представляет собой мощный конструктор запросов, позволяющий удобно выполнять запросы к связанной таблице. Например:

```php
$post = new \Blog\Models\Post();
$all_posts = $post->all();

foreach ($all_posts as $post) {
    echo $post->name . '<br>';
}
```

Этот код выведет все записи из таблицы posts.

### Добавление дополнительных ограничений

Метод all в Eloquent возвращает все результаты из таблицы модели. Поскольку модели Eloquent работают как конструктор запросов, вы можете также добавить ограничения в запрос, а затем использовать метод get для получения результатов:

```php
$post = new \Blog\Models\Post();
$all_posts = $post->where('user_id', '=', 1)
    ->orderBy('name', 'desc')
    ->get();

foreach ($all_posts as $post) {
    echo $post->name . '<br>';
}
```

{% hint style="info" %}
Все методы, доступные в конструкторе запросов, также доступны при работе с моделями Eloquent. Вы можете использовать любой из них в запросах Eloquent.
{% endhint %}


# Поля (свойства) пользователей

Для работы с пользователями в JohnCMS используется класс **`\Johncms\Users\User()`**\
У пользователя есть различные свойства (поля).

## Основные свойства пользователя

Список основных свойств пользователя, которые есть в таблице users:

| Название поля          | Описание                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| name                   | Логин пользователя                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| name\_lat              | Логин, но в нижнем регистре, латиницей                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| password               | Хэш пароля пользователя                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| rights                 | <p>Права пользователя. Может содержать одно из следующих значений:<br><strong>0</strong> - Обычный пользователь<br><strong>3</strong> - Модератор форума<br><strong>4</strong> - Модератор загрузок<br><strong>5</strong> - Модератор библиотеки<br><strong>6</strong> - Супермодератор<br><strong>7</strong> - Администратор<br><strong>9</strong> - Супервизор</p>                                                                                                                                                                                                                                                                                    |
| failed\_login          | Количество неудачных попыток авторизации                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| imname                 | Имя                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| sex                    | <p>Пол пользователя. Содержит одно из следующих значений:<br><strong>m</strong> - Мужчина<br><strong>zh</strong> - Женщина</p>                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| komm                   | Количество комментариев                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| postforum              | Количество постов на форуме                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| postguest              | Количество постов в гостевой                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| yearofbirth            | Год рождения                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| datereg                | Дата регистрации (timestamp)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| lastdate               | Дата последнего визита (timestamp)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| mail                   | E-mail адрес                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| icq                    | ICQ (устаревшее)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| skype                  | Skype                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| jabber                 | Jabber (устаревшее)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| www                    | Сайт пользователя                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| about                  | О себе                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| live                   | Город, страна проживания                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| mibile                 | Номер телефона                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| status                 | Статус пользователя                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| ip                     | <p>IP адрес<br>В таблице хранится в преобразованном формате (ip2long).<br>Если вы получаете и записываете данные с помощью класса <strong>\Johncms\Users\User()</strong>, вам не нужно заботиться о преобразовании.<br>Вы будете видеть IP в обычном формате. Все преобразования выполняются автоматически.</p>                                                                                                                                                                                                                                                                                                                                         |
| ip\_via\_proxy         | <p>IP адрес за прокси (если удалось определить)<br>В таблице хранится в преобразованном формате (ip2long).<br>Если вы получаете и записываете данные с помощью класса <strong>\Johncms\Users\User()</strong>, вам не нужно заботиться о преобразовании.<br>Вы будете видеть IP в обычном формате. Все преобразования выполняются автоматически.</p>                                                                                                                                                                                                                                                                                                     |
| browser                | User Agent. Если используете модель **\Johncms\Users\User()**, то поле будет в безопасном для вывода виде. Дополнительно экранировать не требуется.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| preg                   | Пометка подтвержденного пользователя. Если поле запрашивается из модели, то оно будет содержать **boolean** значение (**true/false**). В таблице хранится число 0 или 1                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| regadm                 | Логин администратора, который подтвердил регистрацию пользователя                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| mailvis                | Пометка включенного отображения e-mail адреса в профиле. Если поле запрашивается из модели, то оно будет содержать **boolean** значение (**true/false**). В таблице хранится число 0 или 1                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| dayb                   | День рождения                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| monthb                 | Месяц рождения                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| sestime                | Текущее время активности пользователя (время активности сессии)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| total\_on\_site        | Сколько провёл на сайте (устаревшее и не используется).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| lastpost               | Время последнего поста (timestamp)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| rest\_code             | Код восстановления пароля                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| rest\_time             | Время восстановления пароля                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| movings                | Количество переходов по страницам в рамках текущей сессии.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| place                  | Местоположение пользователя                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| set\_user              | <p>Настройки пользователя.<br>При запросе этого поля из модели содержит объект класса <strong>Johncms\System\Users\UserConfig</strong><br>При записи через модель, принимает обычный массив и автоматически преобразует в нужный формат.<br>В таблице данные хранятся в сериализованном виде.<br><strong>Поля доступные в объекте:</strong><br><strong>directUrl</strong> - Прямые ссылки<br><strong>fieldHeight</strong> - Высота полей ввода<br><strong>kmess</strong> - Количество элементов на страницу<br><strong>lng</strong> - Выбранный язык<br><strong>timeshift</strong> - Сдвиг времени<br><strong>youtube</strong> - Youtube плеер<br> </p> |
| set\_forum             | Настройки форума. Массив с настройками форума. Может быть пустым, если пользователь не сохранял настройки.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| set\_mail              | Настройки почты. Массив с настройками почты. Может быть пустым, если пользователь не сохранял настройки.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| karma\_plus            | Количество положительных голосов в карме                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| karma\_minus           | Количество отрицательных голосов в карме                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| karma\_time            | Время голосования в карме                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| karma\_off             | Запрет кармы. Если поле запрашивается из модели, то оно будет содержать **boolean** значение (**true/false**). В таблице хранится число 0 или 1                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| comm\_count            | Количество комментариев                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| comm\_old              | Устаревшее, не используется                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| smileys                | Подборка смайлов пользователя. Массив. Может быть пустым, если пользователь не добавлял смайлы в подборку.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| notification\_settings | Настройки уведомлений. Массив с настройками уведомлений.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |

\
Модель **`\Johncms\Users\User()`** в дополнение к основным полям возвращает дополнительные вычисленные поля.

## **Дополнительные свойства**

Список дополнительных свойств пользователя:

| Название поля               | Описание                                                                                                                                                                                                                                                                       |
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| is\_online                  | Метка пользователя онлайн (**true/false**)                                                                                                                                                                                                                                     |
| rights\_name                | Название прав доступа текущего пользователя (для обычных пользователей пустая строка)                                                                                                                                                                                          |
| profile\_url                | Ссылка на страницу просмотра профиля пользователя                                                                                                                                                                                                                              |
| search\_ip\_url             | Ссылка на страницу поиска по ip                                                                                                                                                                                                                                                |
| whois\_ip\_url              | Ссылка на страницу whois ip                                                                                                                                                                                                                                                    |
| search\_ip\_via\_proxy\_url | Ссылка на страницу поиска по IP за прокси                                                                                                                                                                                                                                      |
| whois\_ip\_via\_proxy\_url  | Ссылка на страницу whois IP за прокси                                                                                                                                                                                                                                          |
| ban                         | Массив активных банов пользователя                                                                                                                                                                                                                                             |
| is\_valid                   | <p>Свойство используется при работе от текущего пользователя.<br><strong>true</strong> - если пользователь авторизован и подтвержден.<br><strong>false</strong> - если пользователь не авторизован или не подтвержден.</p>                                                     |
| is\_birthday                | <p><strong>true</strong> - если у пользователя день рождения.<br><strong>false</strong> - если нет.</p>                                                                                                                                                                        |
| birthday\_date              | Т.к. дата рождения в таблице users хранится в отдельных полях, то при запросе этого свойства она собирается в одну строку.                                                                                                                                                     |
| display\_place              | Местоположение пользователя для отображения. Содержит html код ссылки на страницу.                                                                                                                                                                                             |
| formatted\_about            | Обработанное поле "О себе". bb-коды преобразованы в html код.                                                                                                                                                                                                                  |
| website                     | Обработанное поле "Сайт". bb-коды преобразованы в html код.                                                                                                                                                                                                                    |
| last\_visit                 | <p>Дата последнего визита в человекопонятном виде.<br>Обратите внимание, если пользователь сейчас онлайн, это свойство будет пустым.</p>                                                                                                                                       |
| photo                       | <p>Фотография пользователя.<br>Если фотографии нет, возвращает пустой массив.<br>Если фотография есть, возвращает массив со ссылками на фото:<br><strong>photo</strong> - Большая фотография.<br><strong>photo\_preview</strong> - Маленькая фотография для предпросмотра.</p> |


# Работа с пользователями в примерах

В предыдущей статье мы рассмотрели список [полей пользователя.](https://johncms.com/documentation/user_fields/)\
Теперь давайте рассмотрим несколько примеров получения данных.\
Во всех примерах **$user** позволяет получить доступ ко всем полям, которые описаны в [предыдущей статье](https://johncms.com/documentation/user_fields/).

Модель пользователя уже имеет некоторые предустановленные условия для выборки (заготовки запросов).\
Например для получения подтвержденных пользователей, вы можете просто вызвать метод **approved()**, а для получения пользователей, которые сейчас находятся на сайте можно вызвать метод **online()**.

**Как это работает?**\
В моделях можно создавать свои заготовки частей запросов.\
Например сейчас есть заготовка, которая вызывается методом **approved()**.\
Эта заготовка по своей сути равнозначна обычному вызову **where('preg', '=', 1)**\
Это достаточно простой вариант, но есть вариант немного сложнее.\
Например чтобы получить пользователей онлайн нам нужно ограничить выборку по времени.\
Чтобы каждый раз не писать **where('lastdate', '>', (time() - 300))** мы можем вызвать заготовку **online()**.\
Теперь предположим, что у нас есть 10 страниц, на которых выводятся различные пользователи онлайн.\
Если бы мы не использовали заготовки запросов, нам бы пришлось везде писать условие для выборки **where('lastdate', '>', (time() - 300))** и если бы мы захотели изменить время, в течение которого мы считаем пользователя онлайн, то нам бы пришлось менять его во всех 10 страницах. С заготовкой же нам достаточно изменить время в одном месте и это изменение применится для всех страниц.

**А теперь перейдем к примерам:**

Получим последних 10 зарегистрированных и подтвержденных пользователей и выведем их идентификаторы и логины:

```php
$users = (new \Johncms\Users\User())->approved()->orderBy('id', 'desc')->limit(10)->get();
foreach ($users as $user) {
    echo $user->id . ' - ' . $user->name . '<br>';
}
```

Получим 10 последних пользователей онлайн:

```php
$users = (new \Johncms\Users\User())->online()->orderBy('lastdate', 'desc')->limit(10)->get();
foreach ($users as $user) {
    echo $user->id . ' - ' . $user->name . '<br>';
}
```

Получим всех модераторов, администраторов, супервизоров и дополнительно выведем должность:

```php
$users = (new \Johncms\Users\User())->online()->where('rights', '>', 0)->orderBy('lastdate', 'desc')->get();
foreach ($users as $user) {
    echo $user->id . ' - ' . $user->name . ' -  ' . $user->rights_name . '<br>';
}
```

Получим 10 пользователей мужского пола:

```php
$users = (new \Johncms\Users\User())->where('sex', '=', 'm')->orderBy('id')->limit(10)->get();
foreach ($users as $user) {
    echo $user->id . ' - ' . $user->name . '<br>';
}
```

Получим 10 пользователей женского пола:

```php
$users = (new \Johncms\Users\User())->where('sex', '=', 'zh')->orderBy('id')->limit(10)->get();
foreach ($users as $user) {
    echo $user->id . ' - ' . $user->name . '<br>';
}
```

Получим 10 пользователей, у которых больше 100 постов на форуме:

```php
$users = (new \Johncms\Users\User())->where('postforum', '>', 100)->orderBy('id')->limit(10)->get();
foreach ($users as $user) {
    echo $user->id . ' - ' . $user->name . '<br>';
}
```

Усложним задачу и получим всех пользователей у которых больше 100 постов и разобьём выборку страницы (15 пользователей на страницу):

```php
$users = (new \Johncms\Users\User())->where('postforum', '>', 100)->orderBy('id')->paginate(15);
foreach ($users as $user) {
    echo $user->id . ' - ' . $user->name . '<br>';
}
echo $users->render();
```

Как видите, всё достаточно просто. Мы заменили **get()** на **paginate()** убрали **limit(10)** и в **paginate** передали количество пользователей, которое мы хотим видеть на одной странице.\
А дальше с помощью строки **echo $users->render();** отрисовали список страниц.

Ну и давайте рассмотрим ещё 1 пример. Получим список пользователей, у которых поле статус не пустое и так же разобьём на страницы и выведем текст статуса.

```php
$users = (new \Johncms\Users\User())->where('status', '!=', '')->orderBy('id')->paginate(15);
foreach ($users as $user) {
    echo $user->id . ' - ' . $user->name . ' - ' . $user->status . '<br>';
}
echo $users->render();
```

На этом всё, если у вас остались вопросы, задайте их на форуме.


# Работа с текущим авторизованным пользователем

Часто возникает необходимость получить данные пользователя который в данный момент находится на сайте и в зависимости от его свойств показать какую-либо информацию ему или наоборот скрыть.

Для работы с ткущим пользователем необходимо получить объект этого пользователя. Сделать это можно следующим образом:

```php
$user = di(\Johncms\Users\User::class);
```

После этого в переменной **$user** будут доступны все свойства, описанные в [этом списке](https://johncms.com/documentation/user_fields/)

### Проверка авторизации пользователя

```php
if ($user->is_valid) {
    echo 'Пользователь авторизован. Его логин: ' . $user->name;
} else {
    echo 'Пользователь не авторизован';
}
```

В этом примере если пользователь авторизован, выведется сообщение об этом и логин пользователя.

### Проверка прав доступа

```php
if ($user->rights === 9) {
    echo 'Пользователь супервизор!';
} else {
    echo 'Пользователь не супервизор';
}
```

В этом примере проверяем должность пользователя, и если пользователь супервизор, выведем ему сообщение об этом. Проверяется свойство rights и номер должности. Все номера должностей описаны в [списке свойств](https://johncms.com/documentation/user_fields/).

Мы рассмотрели несколько частых примеров работы с текущим пользователем. По аналогии с этими примерами, вы можете использовать другие свойства пользователя и выводить их значения или же в зависимости от значений выводить или скрывать какую-либо информацию.


# Введение

В данной документации мы постараемся описать все ключевые моменты, с которыми вы столкнетесь при работе с системой.

Если документация не смогла помочь Вам в решении Вашего вопроса, Вы всегда можете обратиться за помощью на [наш форум](https://johncms.com/forum/)


# Обновление с версии 9.9

{% hint style="warning" %}
Обновляться на 10.0 можно **только с версии 9.9**. Если у вас более старая версия, сначала обновитесь до 9.9 по инструкции в [документации ветки 9.9](https://github.com/johncms/documentation/tree/9.9), а затем возвращайтесь сюда.
{% endhint %}

## Главное изменение: корень сайта переехал в `public/`

Раньше корнем сайта (document root) была корневая папка JohnCMS. Это означало, что по прямому адресу были доступны и `config/`, и `system/`, и `vendor/`, и файл `.env` — от чтения их спасали только правила в `.htaccess`.

Теперь по HTTP доступна только папка **`public/`**. Код, конфигурация и шаблоны остались в корне и больше не отдаются веб-сервером.

**Адреса страниц и файлов при этом не изменились.** Структура внутри `public/` повторяет прежнюю, поэтому `/themes/default/assets/css/app.css`, `/upload/...` и `/install/` открываются по тем же адресам, что и раньше.

### Что нужно сделать при обновлении

#### 1. Переведите корень сайта на папку `public`

Это главный и обязательный шаг.

* **Обычный хостинг с панелью управления.** В настройках сайта найдите поле «Корневая директория сайта» (или «Document root») и укажите в нём папку `public` внутри каталога сайта. Например, было `/var/www/mysite`, стало `/var/www/mysite/public`.
* **Nginx.** В конфигурации сервера измените директиву `root`:

  ```nginx
  server {
      # было: root /var/www/mysite;
      root /var/www/mysite/public;
      index index.php;
  }
  ```
* **Apache.** Измените `DocumentRoot` в конфигурации виртуального хоста:

  ```apache
  DocumentRoot /var/www/mysite/public
  <Directory /var/www/mysite/public>
      AllowOverride All
      Require all granted
  </Directory>
  ```

#### 2. Перезапустите PHP

После обновления обязательно перезапустите **php-fpm** (или веб-сервер, если PHP работает как модуль Apache). PHP кэширует пути к файлам (realpath cache), и без перезапуска сайт может продолжать искать файлы по старым адресам.

```bash
sudo systemctl restart php8.2-fpm
```

#### 3. Обновите зависимости

```bash
composer install
```

{% hint style="danger" %}
Если корень сайта не перевести на `public/`, сайт перестанет открываться: в корневой папке больше нет файла `index.php`.
{% endhint %}

### Если корень сайта сменить нельзя

На некоторых дешёвых хостингах корневую папку сайта поменять невозможно. Для таких случаев в корне JohnCMS лежит файл `.htaccess`, который перенаправляет все запросы в `public/` и закрывает доступ к папкам с кодом. Он подхватится сам, и сайт продолжит работать.

{% hint style="warning" %}
Это запасной вариант, а не полноценная замена. Код при нём физически остаётся внутри веб-дерева, и защита держится на правилах `.htaccess`. Работает он **только на Apache**: если у вас nginx, единственный способ — перевести корень сайта на `public/`.
{% endhint %}

### Что это значит для авторов шаблонов

Шаблон теперь разложен по двум местам:

* исходники и шаблоны страниц остались в корне — `themes/<ваш шаблон>/src/` и `themes/<ваш шаблон>/templates/`;
* собранные стили, скрипты и картинки переехали в `public/themes/<ваш шаблон>/assets/`.

Если вы собираете стили через webpack, поправьте пути назначения в `webpack.<ваш шаблон>.mix.js` — они должны указывать в `public/themes/<ваш шаблон>/assets/`. Адреса файлов в браузере при этом не меняются, функция `asset()` в шаблонах работает как прежде.

## Удалены скрипты обновления со старых версий

Из папки `install` удалены разовые скрипты обновления с версий ниже 9.9, конвертеры данных и скрипты доустановки модулей 9.9 (`install_collections.php`, `install_consent.php`, `install_contacts.php`, `update_counters_cookie_consent.php`). Теперь папка `install` содержит только веб-инсталлятор.

Если вы обновляетесь с версии ниже 9.9, эти скрипты и инструкции к ним остались в ветке `9.x` и в [документации ветки 9.9](https://github.com/johncms/documentation/tree/9.9). Выполните их **до** перехода на 10.0.

## Зависимости переехали в `vendor/`

Composer-зависимости, которые раньше лежали в `system/vendor`, теперь устанавливаются в стандартную папку `vendor/` в корне. При обновлении удалите старую папку и переустановите зависимости:

```bash
rm -rf system/vendor
composer install
```


# Установка и системные требования

### Системные требования

Для корректной работы JohnCMS, на хостинге, который вы используете, должно быть установлено следующее программное обеспечение

* Web сервер Apache или nginx
* PHP 8.2 и выше
* MySQL 5.6.4 и выше
* Для работы с MуSQL должен использоваться встроенный драйвер [MySQL Native Driver (mysqlnd)](https://www.php.net/manual/ru/book.mysqlnd.php)

Для работы системы требуются следующие расширения php:

* imagick или gd
* mbstring
* pdo
* simplexml

### Установка

* Скачиваем архив
* Распаковываем в папку сайта на хостинге (обычно это папка с названием вашего сайта или public\_html)
* **Указываем в настройках сайта корневую директорию (document root)** — это должна быть папка `public` внутри распакованного архива. Например, если архив распакован в `/var/www/mysite`, то корень сайта — `/var/www/mysite/public`
* Убеждаемся, что папки `data`, `public/upload`, `public` и `config/autoload` доступны для записи
* Переходим по адресу **ваш.сайт/install**
* Следуйте инструкциям описанным на странице установки

{% hint style="warning" %}
**Корнем сайта должна быть папка `public`, а не папка с распакованным архивом.** По HTTP доступна только она — так код, настройки и пароли от базы данных недоступны из браузера.

В панели управления хостингом нужное поле обычно называется «Корневая директория сайта» или «Document root». В nginx это директива `root`, в Apache — `DocumentRoot`.
{% endhint %}

{% hint style="info" %}
Если сменить корневую директорию на хостинге нельзя, на **Apache** сработает файл `.htaccess` из корня архива: он перенаправит запросы в `public/` и закроет доступ к коду. На nginx такого запасного варианта нет — там смена корня обязательна.
{% endhint %}

{% hint style="info" %}
Обязательно указывайте существующий e-mail адрес при установке т.к. он будет использоваться для отправки e-mail.
{% endhint %}

{% hint style="danger" %}
**После установки обязательно удалите папку public/install**
{% endhint %}


# Настройка

После установки JohnCMS перейдите в панель администратора.

1. **Выберите пункт Система > Обновить смайлы.**\
   Это обновит кэш смайлов и после этой операции смайлы в сообщениях будут работать
2. **Выберите пункт Система > Настройки языка.**\
   Далее нажмите **Обновить список** после этого выберите язык по умолчанию, на котором будет работать Ваш сайт.
3. **Настройте cron для планировщика задач.**\
   Добавьте cron-задачу с периодичностью 1 раз в минуту:

```bash
php /path/to/project/system/bin/console schedule:run --no-interaction
```

Планировщик разбирает и очередь писем, поэтому без этой cron-задачи почта отправляться не будет.

Подробная инструкция: [Планировщик задач (schedule)](/10.0/konsol/planirovshchik-zadach-schedule)

{% hint style="info" %}
Далее по желанию Вы можете проверить и изменить все остальные параметры системы. Для этого просто переходите в другие разделы панели администратора и меняйте настройки так, как Вам необходимо.
{% endhint %}


# Структура файлов/папок

JohnCMS имеет следующую структуру папок:

* config
* data
* modules
* public
* system
* themes

{% hint style="info" %}
Корнем сайта (document root) является папка **public**. Всё остальное лежит рядом с ней и по HTTP недоступно.
{% endhint %}

### public

Единственная папка, доступная из браузера. В ней лежат точка входа `index.php`, `favicon.ico`, `robots.txt`, генерируемые карты сайта `sitemap*.xml`, а также:

* **assets** — аватары (**avatars**), смайлы (**emoticons**) и некоторые системные скрипты (**modules**) для генерации картинок предпросмотра;
* **install** — веб-инсталлятор (см. ниже);
* **themes** — собранные стили, скрипты и картинки шаблонов (`public/themes/<шаблон>/assets`);
* **upload** — файлы модулей: загрузки, прикреплённые файлы форума, библиотека, альбомы, аватары и файлы личных сообщений.

{% hint style="info" %}
Подпапка **assets/modules** будет удалена в следующих версиях.
{% endhint %}

### config

В папке хранятся различные конфигурационные файлы необходимые для работы системы.\
Файл **routes.php** отвечает за настройку адресов страниц.\
Файл **constants.php** содержит константы необходимые для работы системы.\
В подпапке **autoload** хранятся файлы, которые автоматически загружаются системой. Работа с конфигурационными файлами подробно описана здесь: [Конфигурационные файлы](https://johncms.com/documentation/configs/).

### data

В папке data хранятся различные системные данные, такие как кэш и логи

### modules

Папка modules содержит все модули системы\
Подробно про структуру папки модуля будет описано отдельно.

### system

Папка system содержит все системные библиотеки\
В этой папке не рекомендуется ничего менять и добавлять в целях сохранения возможности простого обновления на следующие версии JohnCMS

### themes

Папка themes содержит исходники шаблонов сайта: `src` (исходные стили и скрипты) и `templates` (шаблоны страниц). Собранные стили, скрипты и картинки лежат отдельно — в `public/themes/<шаблон>/assets`, потому что их отдаёт браузеру веб-сервер.

В этой папке расположен шаблон **default** в папке с этим шаблоном **не рекомендуется ничего менять** для сохранения возможности простого обновления на следующие версии JohnCMS\
Для кастомизации шаблона создайте отдельную папку и скопируйте в неё содержимое папки default.\
Более подробно про работу с шаблонами читайте в соответствующем разделе документации


# Проблемы и их решение

Иногда при переносе сайта на другой хостинг или после каких-то изменений в коде вы можете столкнуться с ошибками. Здесь мы рассмотрим распространенные проблемы и варианты их решений.

### Ошибка 500.

Причин появления этой ошибки много. Каждую причину нужно рассматривать индивидуально. Для начала чтобы понять от чего отталкиваться нужно включить вывод ошибок.

Для включения вывода ошибок откройте файл **config/constants.php**, найдите строки﻿

```php
// Включаем режим отладки
const DEBUG = false;
```

Замените false на true

```php
const DEBUG = true;
```

После этих действий на сайте должен отображаться текст ошибки.

Если этого не произошло, нужно смотреть журнал ошибок на сервере.


# Конфигурационные файлы (configs)

Наверное Вы уже задавались вопросом "Где хранятся настройки JohnCMS и как добавлять свои настройки?". Давайте рассмотрим подробнее.

Ранее когда мы рассматривали [структуру папок](https://johncms.com/documentation/structure/), мы уже упоминали в ней папку [config](https://johncms.com/documentation/structure/#config). Теперь рассмотрим, что и за что отвечает...

Когда мы открываем папку config, то видим в ней примерно такую структуру:

![Список конфигурационных файлов в JohnCMS](/files/-MUj5zjuImtp0570iXba)

Файлов достаточно много, давайте разберёмся за что они отвечают.

## Файлы в директории autoload:

Директория autoload содержит все конфигурационные файлы, которые автоматически загружаются системой.\
Как вы наверное заметили есть файлы содержащие в названии **global** и **local**.\
Файлы **global** это обычно файлы, которые могут обновляться при выходе новых версий JohnCMS. Не рекомендуем их редактировать, т.к. это осложнит обновление CMS.

Файлы **local** - это локальные файлы конкретно для вашего сайта. Они не содержаться в дистрибутиве JohnCMS. Некоторые из них создаются автоматически при установке системы, а некоторые вы можете создавать вручную.

### Как же быть если вы хотите изменить какие-то параметры, которые есть в global файле?

Всё очень просто. Нужно создать файл с таким же названием, но заменить global на local.

Например, вы хотите изменить настройки в файле **mail.global.php**, для этого скопируйте этот файл и сохраните под именем **mail.local.php**. Далее измените в нем нужные параметры и они переопределят те параметры, которые уже содержатся в **mail.global.php**.

{% hint style="info" %}
Обратите внимание. При необходимости Вы можете изменить только определенные параметры, а остальные останутся стандартными.
{% endhint %}

Давайте рассмотрим пример:

### Содержимое mail.global.php

```php
return [
    'mail' => [
        // Default transport (can be sendmail, smtp, file or memory)
        'transport' => 'sendmail',

        // Transport settings
        'options'   => [
            'smtp' => [
                'name'              => 'localhost.localdomain',
                'host'              => '127.0.0.1',
                'connection_class'  => 'plain',
                'connection_config' => [
                    'username' => 'user',
                    'password' => 'pass',
                ],
            ],
            'file' => [
                'path'     => DATA_PATH . 'mail/',
                'callback' => static function (FileTransport $transport) {
                    return 'Message_' . microtime(true) . '_' . mt_rand() . '.txt';
                },
            ],
        ],
    ],
];
```

Допустим нам нужно изменить имя пользователя: username. Это можно сделать так:

### Содержимое файла mail.local.php

```php
return [
    'mail' => [
        // Transport settings
        'options'   => [
            'smtp' => [
                'connection_config' => [
                    'username' => 'my_user',
                ],
            ],
        ],
    ],
];
```

Давайте теперь получим итоговый результат.

{% hint style="info" %}
Содержимое всех конфигурационных файлов можно получить следующим образом:\
\&#xNAN;**$config = config();**\
Это вернет содержимое всех конфигурационных файлов из папки **config/autoload**.
{% endhint %}

Чтобы получить содержимое файла mail, выполним следующий код:

```php
d($config['mail']);
```

Это вернет следующий результат:

```php
Array
(
    [transport] => sendmail
    [options] => Array
        (
            [smtp] => Array
                (
                    [name] => localhost.localdomain
                    [host] => 127.0.0.1
                    [connection_class] => plain
                    [connection_config] => Array
                        (
                            [username] => my_user
                            [password] => pass
                        )
                )
            [file] => Array
                (
                    [path] => /Users/maksim/MyProjects/johncms_public/data/mail/
                    [callback] => Closure Object
                        (
                            [parameter] => Array
                                (
                                    [$transport] => 
                                )
                        )
                )
        )
)
```

Как видите, в итоговом результате username переопределился тем, что мы указали в файле **mail.local.php**

Вы можете самостоятельно поэкспериментировать, создать свой конфигурационный файл (global/local), а так же можете переопределить настройки из других файлов.

Для удобства можете создать файл **test.php** в корне вашего сайта со следующим содержимым:

```php
<?php

require 'system/bootstrap.php';
$config = config();

// Выведем содержимое конфига mail
d($config['mail']);
```

После этого в браузере перейдите по адресу site.com/test.php и увидите результат. (site.com необходимо заменить на адрес вашего сайта).

{% hint style="warning" %}
Обратите внимание.\
Хоть технически вы можете создавать конфигурационные файлы любой структуры и с любыми именами содержащими **local.php** или **global.php**, мы бы рекомендовали создавать осмысленные названия и первый элемент массива называть так же как и сам конфигурационный файл чтобы избежать путаницы и пересечения параметров.\
Например файл **my.global.php**, должен возвращать следующую структуру:\
**return \[**\
\&#xNAN;**'my' => \[**\
\&#xNAN;**'name' => 'value'**\
\&#xNAN;**],**\
\&#xNAN;**];**
{% endhint %}

Autoload рассмотрели, теперь кратко рассмотрим остальные файлы.

## Прочие конфигурационные файлы:

constants.php - Файл содержит различные константы. В нем вам скорее всего понадобятся константы USE\_CRON (для перевода отправки email на cron) и DEBUG для включения режима отладки при возникновении ошибок или при разработке модулей.

notifications.global.php - Этот файл содержит шаблоны уведомлений. Параметры в данном файле можно переопределить или дополнить с помощью файла notifications.local.php

places.global.php - Файл содержит информацию о местоположении пользователей. Параметры в данном файле можно переопределить или дополнить с помощью файла places.local.php

routes.php - файл для настройки маршрутизации. Подробно работу с ним мы рассматривали в этой статье: [Маршрутизация (роутинг)](https://johncms.com/documentation/routing/)


# Шаблоны электронных сообщений (email)

Начиная с JohnCMS 9.3 в системе появилась поддержка шаблонов для email.

### Для чего это нужно?

Это нужно для того, чтобы отправлять хорошо оформленные письма, которые будут иметь дизайн такой же как и ваш сайт, а так же в таких письмах можно выделять нужную информацию в отличие от обычных текстовых сообщений.

### Как это работает?

Рассмотрим пример письма:

![Пример сообщения о регистрации](/files/-MaUdGZoZwJTz4MKotPR)

В письмах как и на всем сайте есть основной шаблон, который является общим практически для всех страниц (header/footer. На скриншоте отмечен цифрами 1 и 3). Сам текст письма - это контентная область (на скриншоте отмечена цифрой 2), которая в разных письмах может выглядеть по разному.

Базовых шаблонов может быть несколько и каждый шаблон сообщения может использовать любой базовый шаблон.

Всё это позволит вам менять базовый шаблон не меняя все шаблоны писем. Например, вы можете сделать несколько шаблонов на все времена года, зимний, летний, весенний, осенний и менять их когда это необходимо. При этом вам нужно будет изменить всего 1 файл, а шаблоны писем изменять не придется вовсе.

### Где хранятся шаблоны?

Почтовые шаблоны так же как и основные шаблоны сайта хранятся в папке themes.

![](/files/-MUj1pTMpJYk5_RndktT)

Основной шаблон расположен в папке **themes/default/templates/system/mail/layouts/default.phtml**

В этом файле расположен основной макет письма.

Шаблоны конкретных сообщений расположены в папке **themes/default/templates/system/mail/templates**

Шаблонная система для почтовых сообщений работает так же как и шаблоны основного сайта. Поддерживается возможность переопределения и все прочие возможности. Для кастомизации системных шаблонов копируйте их в папку с собственным шаблоном. Таким образом вам не придется переносить изменения при обновлении CMS.


# Отправка электронной почты (email)

В JohnCMS для отправки электронной почты используется библиотека [laminas-mail](https://docs.laminas.dev/laminas-mail/)\
Она позволяет обобщить отправку сообщений и легко переключать драйверы через которые будет отправляться письмо. Благодаря этому вы сможете выбрать наиболее подходящий вам метод отправки в зависимости от возможностей вашего хостинга и наличия его ip в спам фильтрах.

### Драйверы и настройка

На данный момент поддерживаются следующие драйверы: **Sendmail, SMTP, File.** Этих драйверов обычно более чем достаточно большинству проектов.

Драйвер по умолчанию и настройки драйвера указываются в конфигурационном файле **config/autoload/mail.global.php**. По умолчанию установлен sendmail, но вы можете сменить драйвер на smtp или file. Примеры настроек есть в указанном файле. Вы можете просто их переопределить. Как это сделать, а так же про работу с конфигурационными файлами рекомендуем прочитать здесь: [Конфигурационные файлы.](https://johncms.com/documentation/configs/)

### Отправка сообщений

Отправка email достаточно затратная операция. Для решения этой проблемы отправку email можно переложить на сервер. Для этого в JohnCMS реализована очередь сообщений. Чтобы отправить письмо, необходимо просто добавить его в очередь.

**Рассмотрим пример добавления письма в очередь:**

```php
(new \Johncms\Mail\EmailMessage())->create(
    [
        'locale'   => 'ru',
        'template' => 'system::mail/templates/registration',
        'fields'   => [
            'email_to'        => 'user@example.com',
            'name_to'         => 'Имя Пользователя',
            'subject'         => 'Регистрация на сайте',
            'user_name'       => 'UserName',
            'user_login'      => 'UserLogin',
            'link_to_confirm' => 'https://johncms.com',
        ],
    ]
);
```

Что делает этот код?\
Он добавляет запись в таблицу **email\_messages**. А дальше система проверяет наличие не отправленных писем в очереди и отправляет их.

#### Какие поля необходимы?

* **priority** - Приоритет отправки сообщения. Чем меньше, тем выше. (**не обязательно**)
* **locale** - Поле обязательно и содержит код языка, на котором будет отправлено сообщение.
* **template** - содержит шаблон, который будет использоваться для формирования письма.
* **fields** - содержит массив полей, которые будут доступны в шаблоне, а так же будут использоваться для отправки:
  * **email\_to** - E-mail адрес получателя сообщения (**обязательное поле**)
  * **name\_to** - Имя получателя, которое будет отображаться в почтовом клиенте. (**не обязательно**)
  * **subject** - Тема сообщения. (**не обязательно, но рекомендуется**)
  * Прочие поля доступны только в шаблоне, не требуются для работы драйвера и могут отсутствовать.

Очередь разбирает планировщик задач: команда **mail:send-pending** запускается им раз в минуту и отправляет письма пачками. Отправки «на хитах» больше нет — если cron-задача планировщика не настроена, письма так и останутся в очереди.

### Настройка отправки Email

Добавьте задачу в cron:

```bash
php system/bin/console schedule:run --no-interaction
```

Периодичность выполнения установить раз в 1 минуту.\
Обратите внимание, что может потребоваться указать полный путь к файлу от корня. Посмотреть его можно в **phpinfo()**, параметр **DOCUMENT\_ROOT** или вывести так:\
**echo $\_SERVER\['DOCUMENT\_ROOT'];**\
Более подробно про то как добавить задачу, вы можете уточнить у вашего хостинг провайдера.

Если вы обновляетесь со старых версий, обратите внимание на изменения:

* было: `php system/cron.php`
* стало: `php system/bin/console schedule:run --no-interaction`

Константа `USE_CRON` в `config/constants.php` удалена — отправка выполняется только планировщиком, переключать больше нечего.

Подробнее про работу планировщика: [Планировщик задач (schedule)](/10.0/konsol/planirovshchik-zadach-schedule)


# Работа с уведомлениями

Как вы наверное уже знаете, в JohnCMS начиная с версии 9.2 появились улучшенные уведомления. Давайте разберемся как они работают и научимся добавлять свои уведомления.

Для работы уведомлений существует таблица в базе данных, которая называется **notifications**. Она хранит все уведомления для всех пользователей сайта.

Рассмотрим поля, которые доступны в таблице уведомлений:

| Наименование | Описание                                                                                                                                                 |
| ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| id           | Идентификатор уведомления                                                                                                                                |
| module       | Наименование модуля, который добавил уведомление. (**обязательное поле**)                                                                                |
| event\_type  | Наименование типа события, из-за которого отправоено уведомление. (**обязательное поле**)                                                                |
| user\_id     | Пользователь, для которого предназначено уведомление. (**обязательное поле**)                                                                            |
| sender\_id   | Идентификатор пользователя, который инициировал отправку уведомления. (не обязательно)                                                                   |
| entity\_id   | Идентификатор сущности к которой привязано уведомление. (например сообщение на форуме из-за которого было отправлено уведомление). Не обязательное поле. |
| fields       | Массив полей, которые будут доступны в шаблоне уведомления.                                                                                              |
| read\_at     | Время прочтения уведомления.                                                                                                                             |

### Принцип работы уведомлений:

* Какой либо модуль добавляет уведомление в систему, привязывая его к модулю, типу события и пользователю, которому предназначено это уведомление.
* Когда пользователь открывает сайт, для него выполняется выборка уведомлений у которых поле read\_at = NULL. (т.е. не прочитанные).
* После того как пользователь заходит на страницу уведомлений, ему формируется список в соответствии с заданным шаблоном, далее показанные на странице уведомления помечаются прочитанными.

### Добавление уведомлений:

Уведомления обязательно должны иметь наименование модуля, тип события и пользователя, которому они предназначены.

Рассмотрим пример добавления уведомления:

```php
(new \Johncms\Notifications\Notification())->create(
    [
        'module'     => 'my_the_best_module',
        'event_type' => 'my_module_event1',
        'user_id'    => 1,
        'sender_id'  => 1,
        'entity_id'  => null,
        'fields'     => [
            'variable' => 'Привет! Это'
        ],
    ]
);
```

Этот код добавит уведомление для модуля **my\_the\_best\_module** и события с типом **my\_module\_event1.**

Для чего же нам нужно название модуля и тип события?\
Это нужно для того, чтобы отображать уведомления в соответствии с заданным шаблоном.

### Шаблоны уведомлений:

Шаблоны уведомлений настраиваются в файле **config/notifications.local.php.** Если у вас нет этого файла, переименуйте файл **notifications.local.php.example** в **notifications.local.php**

Файл с шаблонами должен иметь следующую структуру:

```php
return [
    // Пример шаблонов уведомлений для модулей
    'my_the_best_module' => [
        'name'   => 'Мой лучший модуль!',
        'events' => [
            'my_module_event1' => [
                'name'    => 'Новое сообщение',
                'message' => 'Текст сообщения! #variable# дополнительный текст',
            ],
            'my_module_event2' => [
                'name'    => 'Новый пост',
                'message' => 'Текст уведомления! #variable# дополнительный текст',
            ],
        ],
    ],
];
```

Как мы видим, тут используется название модуля и тип события.

Давайте посмотрим как выглядит наше уведомление, которое мы добавили выше.

![Пример отображения уведомления](/files/-MUj1owDEidN8AD2MGsc)

Как видно на скриншоте, вывелось уведомление с типом **my\_module\_event1**. В тексте уведомления заменилась макропеременная **#variable#** на ту, которую мы подавали при создании уведомления в массиве **fields**.


# Согласия (Consent)

Как выводить согласия (Consent) в своих формах и записывать их принятие в лог

Модуль `consent` — это общая подсистема согласий: чекбоксов вида «Я принимаю правила сайта», которые нужно показать в форме, проверить при отправке и зафиксировать факт принятия.

Тексты согласий создаёт администратор в админке (**Согласия**), а модули не хранят их у себя и не знают, сколько согласий настроено. Модуль только объявляет **контекст** — имя своей формы — и спрашивает у сервиса, что нужно показать.

Из коробки согласия подключены к форме регистрации (контекст `register`) и форме обратной связи (контекст `contacts`).

## Как это работает

1. В админке создаётся согласие: контекст, язык, заголовок (с ссылками при необходимости), текст, версия, флаги «обязательное» и «активное».
2. Контроллер формы запрашивает у `ConsentService` список согласий для своего контекста.
3. Шаблон формы выводит для каждого согласия чекбокс с именем `consent_{id}`.
4. При отправке формы обязательные согласия проверяются валидатором.
5. После успешного сохранения данных факт принятия каждого отмеченного согласия пишется в лог (`consent_log`) вместе с версией, ID пользователя и IP.

Согласия выбираются по языку текущего интерфейса. Если для него согласий нет — берутся согласия языка сайта по умолчанию.

## ConsentService

Единственная точка входа для других модулей — `Johncms\Modules\Consent\Application\Services\ConsentService`. Внедряется через конструктор.

| Метод                                                                                                 | Назначение                                                     |
| ----------------------------------------------------------------------------------------------------- | -------------------------------------------------------------- |
| `getFormConsents(string $context): list<FormConsentDTO>`                                              | Активные согласия контекста, подготовленные для вывода в форме |
| `getActiveConsents(string $context): Collection<Consent>`                                             | То же, но моделями — если нужен полный текст согласия          |
| `getConsent(int $id): ?Consent`                                                                       | Согласие по ID                                                 |
| `logAcceptance(int $consentId, ?int $userId, string $ipAddress, ?string $version = null): ConsentLog` | Запись принятия в лог                                          |

### FormConsentDTO

```php
final readonly class FormConsentDTO
{
    public int $id;
    public string $titleHtml; // очищенный HTML, выводится как есть
    public ?string $url;      // ссылка на страницу текста согласия или null
    public bool $isRequired;
    public string $version;
}
```

`titleHtml` уже пропущен через HTMLPurifier с разрешёнными инлайн-тегами (`a`, `b`, `strong`, `i`, `em`, `u`, `br`), поэтому в шаблоне печатается без `$this->e()`.

`url` заполняется только тогда, когда у согласия есть текст и в заголовке нет собственных ссылок. Если администратор указал в заголовке свои ссылки, оборачивать заголовок ещё одной ссылкой нельзя.

## Подключение к своей форме

### 1. Выберите контекст

Контекст — произвольная строка, идентификатор вашей формы, например `feedback` или `my_module_order`. В админке поле контекста — свободный ввод со списком подсказок, поэтому свой код указывается вручную и работает без изменений в модуле `consent`.

Удобно хранить контекст константой контроллера:

```php
private const CONSENT_CONTEXT = 'my_module_order';
```

### 2. Получите согласия в контроллере

```php
use Johncms\Http\Environment;
use Johncms\Http\Request;
use Johncms\Modules\Consent\Application\Services\ConsentService;
use Johncms\Users\User;
use Johncms\Validator\Validator;
use Laminas\Validator\Identical;

final class OrderController
{
    private const CONSENT_CONTEXT = 'my_module_order';

    public function __construct(
        private ConsentService $consentService,
        private Environment $env,
        private User $user,
        // ...
    ) {
    }

    public function __invoke(Request $request): string
    {
        $consents = $this->consentService->getFormConsents(self::CONSENT_CONTEXT);

        $fields = [
            'comment' => $request->body('comment'),
        ];

        // Значения чекбоксов попадают в общий массив полей формы
        foreach ($consents as $consent) {
            $fields['consent_' . $consent->id] = $request->body('consent_' . $consent->id);
        }

        $errors = [];
        // ...
    }
}
```

### 3. Проверьте обязательные согласия

Обязательное согласие считается принятым, только если чекбокс отмечен, то есть пришло значение `1`. Проверяется валидатором `Identical`:

```php
$rules = [
    'comment' => ['NotEmpty' => []],
];

foreach ($consents as $consent) {
    if ($consent->isRequired) {
        $rules['consent_' . $consent->id] = ['Identical' => ['token' => '1']];
    }
}

$consentMessage = __('You must accept the consent to continue');
$messages = [
    'Identical' => [
        Identical::NOT_SAME      => $consentMessage,
        Identical::MISSING_TOKEN => $consentMessage,
    ],
];

$validator = new Validator($fields, $rules, $messages);
```

Необязательные согласия не валидируются: пользователь может их не отмечать.

### 4. Запишите принятие в лог

Лог заполняется **после** успешного сохранения основных данных формы — записывать нужно только реально отмеченные согласия. Версия берётся из DTO, чтобы в логе остался снимок той версии, которую пользователь видел в момент отправки.

```php
if ($validator->isValid()) {
    $order = $this->createOrder->execute($dto);

    $ip = (string) $this->env->getIp(false);
    foreach ($consents as $consent) {
        if ($fields['consent_' . $consent->id] === '1') {
            $this->consentService->logAcceptance(
                $consent->id,
                $this->user->isValid() ? $this->user->id : null,
                $ip,
                $consent->version
            );
        }
    }
}
```

Для гостевых форм вместо ID пользователя передаётся `null`.

### 5. Выведите чекбоксы в шаблоне

Готовый шаблон чекбокса `system::app/consent-checkbox` уже умеет выводить заголовок, ссылку на текст согласия, звёздочку обязательности и ошибку валидации. Свою разметку писать не нужно:

```php
<?php
/**
 * @var list<Johncms\Modules\Consent\Application\DTO\FormConsentDTO> $consents
 * @var array<string, mixed> $fields
 * @var array<string, array<int, string>> $errors
 */
?>

<?php foreach ($consents as $consent): ?>
    <?php $consentField = 'consent_' . $consent->id ?>
    <?= $this->fetch('system::app/consent-checkbox', [
        'consent' => $consent,
        'field'   => $consentField,
        'checked' => ($fields[$consentField] ?? null) === '1',
        'errors'  => $errors[$consentField] ?? [],
    ]) ?>
<?php endforeach ?>
```

Не забудьте передать `consents` в шаблон из контроллера:

```php
return $this->render->render('my-module::order', [
    'consents' => $consents,
    'fields'   => $fields,
    'errors'   => $errors,
]);
```

## Страница текста согласия

Если у согласия заполнен текст, оно доступно по адресу `/consent/{id}` — именно на неё ведёт ссылка из чекбокса. Отдельный роут в своём модуле создавать не нужно.

Согласия без текста — это просто заголовок со своими ссылками. Так делают, когда правила уже опубликованы отдельной страницей сайта.

## Структура таблиц

`consents` — сами согласия:

| Поле          | Описание                                           |
| ------------- | -------------------------------------------------- |
| `context`     | Код формы, в которой выводится согласие            |
| `language`    | Код языка согласия                                 |
| `title`       | Заголовок рядом с чекбоксом, допускает инлайн-HTML |
| `text`        | Полный текст, показывается на `/consent/{id}`      |
| `version`     | Версия согласия, попадает в лог при принятии       |
| `is_required` | Обязательно ли принять для отправки формы          |
| `is_active`   | Выводится ли согласие в форме                      |

`consent_log` — журнал принятий: `user_id`, `consent_id`, `version`, `ip_address`, `accepted_at`. Просматривается в админке: **Согласия → Лог**.

## Cookie-баннер

Модуль также отвечает за баннер о cookie. Он настраивается отдельно в админке (**Cookie-баннер**): включение, тексты по языкам и версия. Со стороны кода модулей ничего подключать не нужно — баннер выводится сам.


# Работа с запросом (Request)

Данные HTTP запроса в JohnCMS представлены классом **\Johncms\Http\Request**. Это тонкая обёртка над `Symfony\Component\HttpFoundation\Request`: доступны все методы HttpFoundation (`getClientIp()`, `isSecure()`, `getPathInfo()`, бэги `query`, `request`, `cookies`, `files`, `headers`, `server`, `attributes`), а обёртка добавляет к ним несколько коротких методов для самых частых операций чтения.

## Как получить запрос

Запрос принадлежит одному циклу обработки, поэтому он **не является сервисом контейнера**. Способ получить его ровно один: запрос передают туда, где он нужен.

### В контроллере — аргумент действия

Объявите параметр с типом `Request` — запрос подставится в него автоматически:

```php
<?php

declare(strict_types=1);

namespace Johncms\Modules\MyModule\Application\Controllers;

use Johncms\Http\Request;
use Symfony\Component\HttpFoundation\Response;

final class MyController
{
    public function view(Request $request, int $id): Response
    {
        $page = $request->queryInt('page', 1);

        // ...
    }
}
```

Запрос подставляется по типу параметра, а параметры маршрута — по имени, поэтому порядок аргументов роли не играет.

Запрос объявляют только те действия, которые действительно его читают. Если действие лишь показывает форму — параметр не нужен.

{% hint style="danger" %}
Не сохраняйте запрос в конструкторе или в свойстве контроллера. Контроллеры — синглтоны контейнера, поэтому сохранённый запрос переживёт цикл, которому принадлежит, и в worker-режиме следующие посетители получат ответ по данным первого. По той же причине приватным методам контроллера запрос передают параметром.
{% endhint %}

### В middleware — аргумент `handle()`

```php
public function handle(Request $request, callable $next): Response
{
    // проверка/подготовка
    return $next($request);
}
```

### В сервисе, который живёт дольше запроса

Варианты в порядке предпочтения:

1. принять нужный факт параметром (строку адреса, хост — см. `ClientInfoDTO`);
2. принять `Request` параметром того метода, который его читает, если нужен целый набор полей;
3. прочитать текущий запрос из `Symfony\Component\HttpFoundation\RequestStack`.

Стек — крайний вариант и допустим только в `system/src/`; в Application-слое модуля это тот же скрытый захват запроса, только в другой форме. Он оправдан, когда вызывающих десятки и передать запрос неоткуда (`PaginationFactory`, `Theme`, `Environment`).

### В шаблоне — факт, а не запрос

Шаблон не обращается к запросу. Нужный ему факт отдаёт тонкий сервис поверх стека, и шаблон резолвит именно этот сервис:

```php
$currentPage = di(\Johncms\Http\CurrentPage::class);

if ($currentPage->isHomePage()) {
    // ...
}
```

{% hint style="warning" %}
`di(\Johncms\Http\Request::class)` и `$container->get(Request::class)` бросают исключение: такого сервиса нет. Если вы встретили этот вызов в старом коде или стороннем модуле — его нужно заменить на аргумент действия.
{% endhint %}

## Получение данных из строки запроса ($\_GET)

Пользователь открыл `http://domain.com/?user_id=123&search=john`:

```php
$userId = $request->queryInt('user_id');          // 123, по умолчанию 0
$page   = $request->queryInt('page', 1);          // 1, если параметра нет
$search = $request->queryParam('search');         // 'john', по умолчанию ''
$ids    = $request->queryInts('ids');             // список чисел из ?ids[]=1&ids[]=2
```

Первым параметром идёт имя параметра запроса, вторым — значение по умолчанию. Отдельного аргумента с фильтром нет: тип задаёт сам метод.

## Получение данных из тела запроса ($\_POST и JSON)

Методы `body*` читают тело запроса независимо от того, пришло оно формой или JSON:

```php
$name  = $request->body('name');                  // строка, по умолчанию ''
$type  = $request->body('type', 'default');
$id    = $request->bodyInt('user_id');            // число, по умолчанию 0
$files = $request->bodyInts('attached_files');    // список чисел
$users = $request->bodyList('users');             // список без приведения типа
```

Проверить наличие ключа (например, галочки в форме) можно так:

```php
if ($request->hasBody('subscribe')) {
    // чекбокс отмечен
}
```

Метод запроса проверяется через `isPost()` или общий `isMethod()`:

```php
if ($request->isPost()) {
    // обработка отправленной формы
}
```

## Некорректные данные

`queryInt()` и `bodyInt()` мягко относятся к мусору: `?id=abc` вернёт значение по умолчанию, а не ошибку. Но массив в скалярном параметре (`?id[]=1`) — это попытка подмены типа, она намеренно не подавляется и превращается в ответ **400 Bad Request**.

Если нужна строгая семантика, обращайтесь к бэгам HttpFoundation напрямую — там неверное значение бросает исключение:

```php
$id = $request->query->getInt('id');
```

## Строки приходят обрезанными

Все строки в теле формы и в строке запроса обрезаются по краям (`trim`) глобальным middleware `TrimStringsMiddleware` до того, как отработает контроллер. Это касается и чтения через бэги напрямую. Тело в формате JSON не обрезается.

## Параметры маршрута

Параметры маршрута — это не данные запроса, их объявляют аргументами действия по имени, и они приводятся к типу аргумента:

```php
// маршрут: /forum/{id}/page/{page}
public function topic(Request $request, int $id, int $page = 1): Response
```

При необходимости все параметры совпавшего маршрута доступны как атрибуты запроса:

```php
$params = $request->attributes->all();
```

## Cookies, заголовки и данные сервера

Для них используются штатные бэги HttpFoundation:

```php
$theme     = $request->cookies->get('theme', 'default');
$userAgent = $request->headers->get('User-Agent', '');
$ip        = $request->getClientIp();
$isSecure  = $request->isSecure();
```

## Получение файлов ($\_FILES)

Загруженные файлы доступны в бэге `files`. Для одного поля:

```php
$uploaded = $request->files->get('imagefile');
```

Для всех сразу — `$request->files->all()`. Множественное поле (`<input type="file" name="photos[]" multiple>`) возвращается уже нормальным списком объектов, собирать структуру `$_FILES` вручную не нужно.

Элемент бэга — это `Symfony\Component\HttpFoundation\File\UploadedFile`, то есть HTTP-тип. Он не должен покидать слой HTTP: контроллер преобразует его в `\Johncms\Http\UploadedFileDTO` с помощью `\Johncms\Http\UploadedFileMapper`, и дальше — в use case, сервисы, хранилище — передаётся уже DTO.

```php
use Johncms\Http\UploadedFileMapper;
use Symfony\Component\HttpFoundation\File\UploadedFile;

final class PhotoUploadController
{
    public function __construct(
        private readonly SavePhotoUseCase $savePhotoUseCase,
        private readonly UploadedFileMapper $uploadedFileMapper,
    ) {
    }

    public function upload(Request $request, int $albumId): Response
    {
        $uploaded = $request->files->get('imagefile');
        if (! $uploaded instanceof UploadedFile) {
            // файл не пришёл или загрузка не удалась
        }

        $this->savePhotoUseCase->execute(
            $albumId,
            $this->uploadedFileMapper->fromUploadedFile($uploaded),
            $request->body('description'),
        );

        // ...
    }
}
```

Сам DTO умеет проверять успешность загрузки и переместить файл, поэтому оригинальный HTTP-объект дальше не нужен:

```php
if (! $file->isValid()) {
    throw new RuntimeException('Ошибка загрузки файла');
}

$file->moveTo(UPLOAD_PATH . 'photos/' . $newName);
```

Доступные поля DTO: `clientName`, `mimeType`, `size`, `tmpPath`, `error`.

{% hint style="danger" %}
В примерах рассмотрен простой вариант сохранения файлов без проверок допустимых типов и размеров. Имя файла, полученное от клиента, использовать как имя на диске нельзя — генерируйте своё.
{% endhint %}


# Пагинация

Начиная с версии 9.9 в JohnCMS появился собственный компонент пагинации **\Johncms\Http\Pagination**. Он заменил устаревший форк `johncms/johncms-pagination` (Laravel `LengthAwarePaginator` / метод `->paginate()`) и метод `Tools::displayPagination()` — оба **удалены** в версии 9.9. Весь код должен использовать только новый компонент.

> Обновляетесь с 9.8 и в ваших модулях есть `->paginate()`, `LengthAwarePaginator` или `Tools::displayPagination()`? Переход на новый компонент описан в инструкции по обновлению с 9.8 — она осталась в [документации ветки 9.9](https://github.com/johncms/documentation/blob/9.9/nachalo-raboty/obnovlenie-s-versii-9.8.md), так как обновляться на 10.0 нужно через 9.9.

## Состав компонента

* **`PaginationFactory`** — сервис DI-контейнера, создаёт объект `Pagination` из общего количества записей. Внедряется в контроллеры через конструктор.
* **`Pagination`** — неизменяемый (immutable) объект без обращения к контейнеру. Содержит математику страниц (`getOffset()`, `getPerPage()`, `getCurrentPage()`, `getTotalPages()`, `getTotal()`, `hasPages()`), строит URL страниц (`getUrl()`), отдаёт элементы для шаблона (`getItems()`) и рендерит готовый HTML (`render()`).
* **`PaginationGuard`** — сервис DI-контейнера, возвращает URL для редиректа с неканонических страниц. Сам редирект не выполняет (это упрощает тестирование) — контроллер вызывает `redirect()` явно.

## Использование в контроллере

```php
use Johncms\Http\PageMeta;
use Johncms\Http\Pagination\PaginationFactory;
use Johncms\Http\Pagination\PaginationGuard;

// В конструкторе контроллера: PaginationFactory $paginationFactory, PaginationGuard $paginationGuard

// 1. Строим пагинацию из общего количества записей (дешёвый COUNT-запрос).
$pagination = $this->paginationFactory->create($this->listUseCase->count());

// 2. Редирект с неканонических страниц.
$redirectUrl = $this->paginationGuard->redirectUrl($pagination);
if ($redirectUrl !== null) {
    redirect($redirectUrl);
}

// 3. Получаем срез данных по limit/offset из пагинации.
$items = $this->listUseCase->getPage($pagination->getPerPage(), $pagination->getOffset());

// 4. Строим мета-данные страницы и рендерим HTML пагинации.
$meta = new PageMeta($pageTitle, $pagination->getCurrentPage());
return $this->render->render('module::index', [
    'title'       => $meta->title,
    'description' => $meta->description,
    'items'       => $items,
    'pagination'  => $pagination->render(),
]);
```

### Параметры `PaginationFactory::create()`

```php
create(int $total, ?int $perPage = null, string $pageParamName = 'page', ?int $currentPage = null): Pagination
```

* `total` — общее количество записей.
* `perPage` — размер страницы. По умолчанию берётся из настроек текущего пользователя (`config->kmess`). Передавайте явно только если у списка собственный размер страницы.
* `pageParamName` — имя GET-параметра страницы, по умолчанию `page`.
* `currentPage` — номер текущей страницы. По умолчанию берётся из GET-параметра; передавайте явно только вне HTTP-контекста (например, в консольных командах).

## Канонические URL страниц

Первая страница канонична **без** параметра `page` — метод `getUrl(1)` возвращает URL без него. `PaginationGuard` приводит остальные случаи к каноническому виду:

* `?page=1`, мусорное значение (`?page=abc`) или `page < 1` → URL без параметра `page`;
* `page > totalPages` → URL последней страницы.

Если страница уже каноничная, `redirectUrl()` возвращает `null`. Редиректы выполняются со статусом 302.

## Разделение use case / репозиторий

Use case не должен знать ни про HTML-рендер, ни про пагинатор. Доступ к данным разделяется на два метода, а срезом управляет контроллер:

* `count(): int` → в репозитории метод `count*(...)` (запрос `COUNT`);
* `getPage(int $limit, int $offset): array` → в репозитории метод `get*(..., int $limit, int $offset): Collection`, результат маппится в DTO.

Репозитории принимают явные `limit`/`offset` и возвращают `Collection`. Метод `->paginate()` использовать нельзя.

## Рендеринг

* В стандартном случае используйте `$pagination->render()` в контроллере — HTML строится из шаблона `system::app/pagination` (темы `default` и `admin`).
* Для собственной разметки или JSON-эндпоинтов используйте `$pagination->getItems()` — массив элементов с сырыми полями `type`/`page`/`url`/`active`.
* Экранирование происходит на выводе: шаблон сам экранирует URL через `$this->e(...)`. Не экранируйте данные заранее в PHP.

## Заголовок и описание страницы (PageMeta)

Для формирования `title` и meta `description` на страницах с пагинацией используйте `Johncms\Http\PageMeta` — начиная со 2-й страницы он автоматически добавляет суффикс с номером страницы (разделитель `—` и переведённое слово «Page»):

```php
use Johncms\Http\PageMeta;

$meta = new PageMeta($documentTitle, $pagination->getCurrentPage());
// или с собственным описанием:
$meta = new PageMeta($documentTitle, $pagination->getCurrentPage(), $description);
```

* Первая страница остаётся без суффикса — `title` и `description` не изменяются.
* Если `description` не передан или пуст, базой для него служит `title`.


# Валидация

## Что такое валидатор и зачем он нужен?

Разработчики модулей создавая модули часто сталкиваются с задачей валидации форм, которые отправляет пользователь.\
Например, практически в любой форме есть поля, обязательные для заполнения. Так же есть поля, значения которых нужно проверить на наличие в базе данных, в некоторых полях может находиться файл, размер которого нам нужно проверить, ссылка, правильность которой тоже нужно проверить или же email адрес в котором, например, нужно проверить не только корректность текста до и после символа @, но и наличие MX записей для указанного домена.

Для избавления от частых рутинных операций и упрощения кода в JohnCMS встроены различные, часто используемые базовые валидаторы.

## Что позволяет делать валидатор?

Валидатор проверяет входные данные на соответствие настройкам правил валидации. Если данные не соответствуют правилам, валидатор возвращает false и так же позволяет получить информацию о том, какие именно требования не выполнены.

В JohnCMS используется [laminas-validator](https://docs.laminas.dev/laminas-validator/), большинство существующих правил, которые описаны в официальной документации будут работать и в JohnCMS, но есть правила для которых требуются дополнительные зависимости и эти правила могут не работать, но таких как правило единицы и они редко используются.

При просмотре документации по валидаторам вам может показаться, что это достаточно громоздко, но не спешите, работа с валидатором в JohnCMS сильно упрощена, к тому же реализованы некоторые дополнительные валидаторы, которые вам скорее всего пригодятся.\
Давайте рассмотрим пример и разберемся в некоторых деталях.

```php
<?php

require 'system/bootstrap.php';

// Массив полей и значений
$data = [
    'test'   => '',
    'number' => 100,
    'email'  => 'email@example.ru',
    'model'  => 110,
];

// Настройки валидатора
$rules = [
    // Название поля => [ правила валидации и их параметры ]
    'test'   => [
        'NotEmpty',
        'StringLength' => [
            'min' => 6,
            'max' => 80,
        ],
    ],
    'number' => [
        'NotEmpty',
        'LessThan' => ['max' => 90],
    ],
    'email'  => [
        'EmailAddress' => [
            'useMxCheck' => true,
        ],
    ],
    'model'  => [
        'ModelExists' => [
            'model' => \Johncms\Users\User::class,
            'field' => 'id',
        ],
    ],
];

// Валидация
$validator = new \Johncms\Validator\Validator($data, $rules);
if ($validator->isValid()) {
    echo 'OK';
} else {
    d($validator->getErrors());
}
```

Здесь массив **$data** содержит набор данных, которые будут проверяться. Часто это данные из формы, полученные методом **POST** или **GET**.

Массив **$rules** содержит набор правил и их настройку. В качестве ключа указывается название поля из массива $data, а в качестве массива со значениями используется валидатор или набор валидаторов и их настройки.\
Например в первом правиле проверяется значение поля под названием **test**, к нему применяется валидатор **NotEmpty** и **StringLength**. Валидатор **NotEmpty** проверяет не пустое ли значение в поле **test**, а валидатор **StringLength** проверяет длину значения. В данном случае длина значения должна быть от 6 до 80 символов.

Как видите, валидатор может не иметь настроек, а может иметь настройки. Если валидатор не имеет настроек или же вам подходят настройки по умолчанию, то вы можете передать только название валидатора. Если вам нужно дополнительно настроить валидатор, просто передаете массив настроек.

Многие популярные валидаторы мы рассмотрим отдельно. Пока можете попробовать выполнить код выше.\
Для этого в корне вашего сайта создайте файл **test.php** и вставьте в него этот код. После этого откройте в браузере страницу **site.ru/test.php.** Вы увидите следующий результат:

```php
Array
(
    [test] => Array
        (
            [isEmpty] => Поле является обязательным и не может быть пустым
        )

    [number] => Array
        (
            [notLessThan] => The input is not less than '90'
        )

    [email] => Array
        (
            [emailAddressInvalidMxRecord] => 'example.ru' Похоже, что записи MX или A для адреса электронной почты не действительны
        )

    [model] => Array
        (
            [modelNotFound] => Нет записей, соответствующих введенным данным
        )

)
```

Как видно из результата, массив $data не прошел проверку. Валидатор вернул массив полей и правила валидации, которые не прошли проверку. Вы можете изменить в нем значения и понаблюдать за результатом, а так же поэкспериментировать с другими правилами.


# NotEmpty - Не пустое значение

Этот валидатор позволяет вам проверить, является ли данное значение не пустым. Это часто полезно при работе с элементами формы или другим пользовательским вводом. Вы можете использовать этот валидатор, чтобы убедиться, что пользователь заполнил поле и оно не пустое.

По умолчанию этот валидатор работает иначе, чем вы ожидаете, работая с PHP функцией empty(). В частности, этот валидатор будет оценивать как целое число 0, так и строку «0» как пустые.

Вам может не подойти это поведение и, например, в вашем случае 0 не должен считаться пустым. Для таких случаев в валидаторе NotEmpty вы можете задать некоторые настройки.

### Поддерживаемые параметры

* **type**: Устанавливает тип проверки, которая будет выполнена.

### Обрабатываемые типы

* **boolean**: Возвращает false, когда логическое значение равно false.
* **integer**: Возвращает false, когда задано целое число 0. По умолчанию эта проверка не активирована и возвращает true для любых целочисленных значений.
* **float**: Возвращает false, когда задано значение с плавающей запятой 0.0. По умолчанию эта проверка не активирована и возвращает true для любых значений с плавающей запятой.
* **string**: Возвращает false, когда задана пустая строка.
* **zero**: Возвращает false, когда задан один символ ноль ('0').
* **empty\_array**: Возвращает false, когда задан пустой массив.
* **null**: Возвращает false, когда задано значение null.
* **php**: Возвращает false везде, где PHP empty () возвращает true.
* **space**: Возвращает false, если задана строка, содержащая только пробел.
* **object**: Возвращает true. false будет возвращено, когда объект не разрешен, но объект задан.
* **object\_string**: Возвращает false, когда объект задан, а его метод \_\_toString () возвращает пустую строку.
* **object\_count**: Возвращает false, когда объект задан, он реализует Countable, и его количество равно 0.
* **all**: Возвращает false для всех вышеперечисленных типов.

Рассмотрим пример как передавать эти параметры в валидатор.

### Примеры использования

```php
// Массив полей и значений
$data = [
    'test' => 0,
];

// Настройки валидатора
$rules = [
    'test' => [
        'NotEmpty' => [
            'type' => [
                'integer',
                'zero',
            ],
        ],
    ],
];

// Валидация
$validator = new \Johncms\Validator\Validator($data, $rules);
if ($validator->isValid()) {
    echo 'OK';
} else {
    d($validator->getErrors());
}
```

Как видите, для валидатора **NotEmpty** задан массив настроек, в нем передается параметр **type** со значениями из списка выше (обрабатываемые типы).


# StringLength - длина строки

Валидатор StringLength позволяет проверить находится ли длина строки в диапазоне заданных значений или нет.

По умолчанию этот валидатор проверяет, находится ли значение между min и max, используя минимальное значение по умолчанию, равное 0, и максимальное значение по умолчанию, равное NULL (то есть неограниченное).\
Таким образом, без каких-либо опций, валидатор только проверяет, что ввод является строкой.

## Поддерживаемые параметры

* **encoding**: Устанавливает кодировку ICONV в которой будет проверяться строка.
* **min**: Устанавливает минимально допустимую длину строки.
* **max**: Устанавливает максимально допустимую длину для строки.

## Примеры использования

```php
// Массив полей и значений
$data = [
    'test' => 'Строка',
];

// Настройки валидатора
$rules = [
    'test' => [
        'StringLength' => [
            'min'      => 3,
            'max'      => 60,
            'encoding' => 'UTF-8',
        ],
    ],
];

// Валидация
$validator = new \Johncms\Validator\Validator($data, $rules);
if ($validator->isValid()) {
    echo 'OK';
} else {
    d($validator->getErrors());
}
```

В указанном примере валидатор проверит строку в кодировке UTF-8 на длину от 3 до 60 символов.

### Проверка только минимальной длины:

```php
// Массив полей и значений
$data = [
    'test' => 'Строка',
];

// Настройки валидатора
$rules = [
    'test' => [
        'StringLength' => [
            'min' => 3
        ],
    ],
];

// Валидация
$validator = new \Johncms\Validator\Validator($data, $rules);
if ($validator->isValid()) {
    echo 'OK';
} else {
    d($validator->getErrors());
}
```

В этом примере будет проверяться только минимальная длина строки (3 символа). Максимальная будет считаться не ограниченной.

### Проверка только максимальной длины:

```php
// Массив полей и значений
$data = [
    'test' => 'Строка',
];

// Настройки валидатора
$rules = [
    'test' => [
        'StringLength' => [
            'max' => 50
        ],
    ],
];

// Валидация
$validator = new \Johncms\Validator\Validator($data, $rules);
if ($validator->isValid()) {
    echo 'OK';
} else {
    d($validator->getErrors());
}
```

В этом же примере будет проверяться только максимальная длина строки. Если строка будет длиннее 50 символов, проверка не пройдет, если менее 50 символов, то проверка пройдет.

### Строгое ограничение длины строки:

```php
// Массив полей и значений
$data = [
    'test' => 'Строка',
];

// Настройки валидатора
$rules = [
    'test' => [
        'StringLength' => [
            'max' => 6,
            'min' => 6,
        ],
    ],
];

// Валидация
$validator = new \Johncms\Validator\Validator($data, $rules);
if ($validator->isValid()) {
    echo 'OK';
} else {
    d($validator->getErrors());
}
```

А в этом примере мы строго ограничили длину строки 6 символами. Т.е. проверка пройдет только если строка будет длиной в 6 символов. Больше или меньше не допускается.


# LessThan - Менее чем

Валидатор LessThan позволяет проверить число на предмет того, что оно меньше чем заданное в параметре.\
Обратите внимание, что данный валидатор работает только с числами. Строки или даты этот валидатор не позволяет проверять.

### Поддерживаемые параметры

* **inclusive**: Включая максимальное значение. Если задано **true**, то значение равное максимальное значение будет проходить валидацию. Если задано **false**, то значение равное максимальному значению не будет проходить валидацию.
* **max**: Устанавливает максимальное значение.

### Примеры использования

```php
// Массив полей и значений
$data = [
    'test' => 60,
];

// Настройки валидатора
$rules = [
    'test' => [
        'LessThan' => [
            'max'       => 60,
            'inclusive' => true,
        ],
    ],
];

// Валидация
$validator = new \Johncms\Validator\Validator($data, $rules);
if ($validator->isValid()) {
    echo 'OK';
} else {
    d($validator->getErrors());
}
```

Этот пример выведет "OK", т.к. включен параметр inclusive и значение равно максимальному.

```php
// Массив полей и значений
$data = [
    'test' => 60,
];

// Настройки валидатора
$rules = [
    'test' => [
        'LessThan' => [
            'max'       => 60,
            'inclusive' => false,
        ],
    ],
];

// Валидация
$validator = new \Johncms\Validator\Validator($data, $rules);
if ($validator->isValid()) {
    echo 'OK';
} else {
    d($validator->getErrors());
}
```

А этот пример выведет ошибку т.к. параметр inclusive имеет значение false т.к. этот параметр исключает максимальное значение.


# EmailAddress - Проверка email адреса

Валидатор EmailAddress позволяет выполнить различные проверки email адреса.\
Валидатор сначала разбивает адрес электронной почты на local-part\@hostname и пытается сопоставить их с известными спецификациями для адресов электронной почты и имен хостов.

### Поддерживаемые параметры

* **allow**: Определяет, какой тип доменных имен принимает валидатор. Эта опция используется вместе с опцией hostnameValidator для установки валидатора имени хоста. Возможные значения этой опции определены в константах ALLOW\_ \* валидатора Hostname:
  * **ALLOW\_DNS**: (по умолчанию) Разрешает доменные имена (например example.com)
  * **ALLOW\_IP**: Разрешает IP адреса.
  * **ALLOW\_LOCAL**: Разрешает локальные домены такие как localhost или [www.localdomain](http://www.localdomain)
  * **ALLOW\_URI**: Разрешает имена хостов в универсальном синтаксисе URI. См. [RFC 3986](https://www.ietf.org/rfc/rfc3986.txt)
  * **ALLOW\_ALL**: Разрешить все типы хостов.
* **useDeepMxCheck**: Указывает валидатору на необходимость усиленной проверки MX записей домена. Если для этого параметра установлено значение true, то в дополнение к записям MX также используются записи A, A6 и AAAA для проверки того, принимает ли сервер электронную почту. Эта опция по умолчанию имеет значение false.
* **useDomainCheck**: Определяет, должна ли быть проверена часть домена. Если для этого параметра установлено значение false, будет проверяться только локальная часть адреса электронной почты. В этом случае валидатор имени хоста не будет вызван. Эта опция по умолчанию имеет значение true.
* **hostnameValidator**: Задает экземпляр объекта валидатора имени хоста, с помощью которого будет проверяться доменная часть адреса электронной почты.
* **useMxCheck**: Определяет, должны ли быть обнаружены записи MX с сервера. Если для этого параметра задано значение true, то MX-записи используются для проверки того, принимает ли сервер электронную почту или нет. Эта опция по умолчанию имеет значение false.

### Пример использования

Рассмотрим наиболее распространенный пример, которого скорее всего вам будет достаточно. Этот пример проверяет существование домена и возможность принимать email. Т.е. выполняется максимально возможная проверка. Она пропустит только точно существующий домен с MX записями.

```php
// Массив полей и значений
$data = [
    'test' => 'info@johncms.com',
];

// Настройки валидатора
$rules = [
    'test' => [
        'EmailAddress'   => [
            'allow'          => Laminas\Validator\Hostname::ALLOW_DNS,
            'useMxCheck'     => true,
            'useDeepMxCheck' => true,
        ],
    ],
];

// Валидация
$validator = new \Johncms\Validator\Validator($data, $rules);
if ($validator->isValid()) {
    echo 'OK';
} else {
    d($validator->getErrors());
}
```


# ModelExists - Проверка существования записи в БД

Валидатор **ModelExists** позволяет проверить существование записи в базе данных. Это хорошо подходит для тех случаев, когда у вас в форме есть привязка к каким-то существующим записям в базе данных.

Для работы этого валидатора вам потребуется существующая [модель](https://johncms.com/documentation/eloquent-orm/).

### Поддерживаемые параметры

* **model**: Класс модели, который будет использоваться для построения запроса к БД.
* **field**: Столбец в БД по которому будет осуществляться поиск записи.

### Примеры использования

```php
// Массив полей и значений
$data = [
    'test' => 45,
];

// Настройки валидатора
$rules = [
    'test' => [
        'ModelExists'   => [
            'model' => \Johncms\Users\User::class,
            'field' => 'id',
        ],
    ],
];

// Валидация
$validator = new \Johncms\Validator\Validator($data, $rules);
if ($validator->isValid()) {
    echo 'OK';
} else {
    d($validator->getErrors());
}
```

В указанном примере будет выполнена проверка наличия пользователя c идентификатором 45 в таблице users.

Запрос который будет выполнен:

```sql
SELECT * FROM `users` WHERE `id` = 45
```

В результате, если будет найдена запись с id = 45, то валидатор будет считать проверку успешной, если не найдет, то вернёт ошибку.

Рассмотрим ещё один пример:

```php
// Массив полей и значений
$data = [
    'test' => 'admin',
];

// Настройки валидатора
$rules = [
    'test' => [
        'ModelExists'   => [
            'model' => \Johncms\Users\User::class,
            'field' => 'name',
        ],
    ],
];

// Валидация
$validator = new \Johncms\Validator\Validator($data, $rules);
if ($validator->isValid()) {
    echo 'OK';
} else {
    d($validator->getErrors());
}
```

В этом примере будет выполнен поиск записи у которой поле name = admin.

Будет выполнен следующий запрос:

```sql
SELECT * FROM `users` WHERE `name` = 'admin'
```

Результат будет такой же как и в случае с id. Если будет найдена строка с полем name = admin, то валидация пройдет успешно, если нет, будет возвращена ошибка.


# ModelNotExists - Проверка отсутствия записи в БД

Валидатор **ModelNotExists** позволяет проверить отсутствие записи в базе данных. Это подойдет для тех случаев, когда вам нужно проверить отсутствие записи в таблице прежде чем её добавить. Например, с помощью этого валидатора, в форме регистрации пользователя вы можете проверить существует ли пользователь с введенным логином или нет.

Для работы этого валидатора вам потребуется существующая [модель](https://johncms.com/documentation/eloquent-orm/).

## Поддерживаемые параметры

* **model**: Класс модели, который будет использоваться для построения запроса к БД.
* **field**: Столбец в БД по которому будет осуществляться поиск записи.
* **exclude**: Параметры для задания условий исключения из выборки. Может содержать анонимную функцию или массив с полями **field** и **value**.

## Примеры использования

```php
// Массив полей и значений
$data = [
    'test' => 'admin@admin.ru',
];

// Настройки валидатора
$rules = [
    'test' => [
        'ModelNotExists' => [
            'model'   => \Johncms\Users\User::class,
            'field'   => 'mail',
            'exclude' => static function ($query) {
                return $query->where('name', '!=', 'admin')->where('id', '!=', 1);
            },
        ],
    ],
];

// Валидация
$validator = new \Johncms\Validator\Validator($data, $rules);
if ($validator->isValid()) {
    echo 'OK';
} else {
    d($validator->getErrors());
}
```

В примере выше выполняется проверка наличия в таблице users пользователя с полем mail, содержащим <admin@admin.ru>. При этом из выборки исключаются строки с name = admin и id = 1. Для расширения запроса на выборку используется анонимная функция. Она позволяет дополнять запрос любыми условиями.\
Валидатор выполнит следующий запрос:

```sql
select * from `users` where (`name` != 'admin' and `id` != 1) and `mail` = 'admin@admin.ru' limit 1
```

Рассмотрим более простой пример, где в параметр **exclude** передается массив:

```php
// Массив полей и значений
$data = [
    'test' => 'admin@admin.ru',
];

// Настройки валидатора
$rules = [
    'test' => [
        'ModelNotExists' => [
            'model'   => \Johncms\Users\User::class,
            'field'   => 'mail',
            'exclude' => [
                'field' => 'name',
                'value' => 'admin',
            ],
        ],
    ],
];

// Валидация
$validator = new \Johncms\Validator\Validator($data, $rules);
if ($validator->isValid()) {
    echo 'OK';
} else {
    d($validator->getErrors());
}
```

Если вам не требуется сложное условие для исключения записей из выборки, то вы можете использовать такой вариант задания исключений. При таких настройках валидатор выполнит следующий запрос:

```sql
select * from `users` where `name` != 'admin' and `mail` = 'admin@admin.ru' limit 1
```

Ну и давайте рассмотрим минимальный вариант использования, вообще без исключений.

```php
// Массив полей и значений
$data = [
    'test' => 'admin@admin.ru',
];

// Настройки валидатора
$rules = [
    'test' => [
        'ModelNotExists' => [
            'model'   => \Johncms\Users\User::class,
            'field'   => 'mail',
        ],
    ],
];

// Валидация
$validator = new \Johncms\Validator\Validator($data, $rules);
if ($validator->isValid()) {
    echo 'OK';
} else {
    d($validator->getErrors());
}
```

При таких настройках валидатор просто проверит наличие записи с mail = <admin@admin.ru>. Если запись будет найдена, то валидатор вернёт ошибку. Если нет, проверка пройдет успешно.

Запрос, который выполнит валидатор при этих настройках будет таким:

```sql
select * from `users` where `mail` = 'admin@admin.ru' limit 1
```


# Csrf - Проверка токена

Валидатор **Csrf** предназначен для проверки токена csrf. Токен предназначен для защиты формы от подделки запроса. Данный валидатор работает в паре с генератором токенов **\Johncms\Security\Csrf**

### Поддерживаемые параметры

* **tokenId**: Идентификатор токена. Если не задан, используется токен по умолчанию для всего сайта.

### Примеры использования

```php
// Массив полей и значений
$data = [
    'test' => 'token',
];

// Настройки валидатора
$rules = [
    'test' => [
        'Csrf',
    ],
];

// Валидация
$validator = new \Johncms\Validator\Validator($data, $rules);
if ($validator->isValid()) {
    echo 'OK';
} else {
    d($validator->getErrors());
}
```

В указанном примере будет проверяться токен по умолчанию.

```php
// Массив полей и значений
$data = [
    'test' => 'token',
];

// Настройки валидатора
$rules = [
    'test' => [
        'Csrf' => [
            'tokenId' => 'guestbook_form'
        ],
    ],
];

// Валидация
$validator = new \Johncms\Validator\Validator($data, $rules);
if ($validator->isValid()) {
    echo 'OK';
} else {
    d($validator->getErrors());
}
```

В этом примере мы добавили идентификатор токена, который будет проверяться.

Более подробно работу с токенами мы рассмотрим в отдельной статье.


# Flood - проверка на флуд

Валидатор **Flood** предназначен для упрощения проверки формы на флуд. Валидатор не имеет параметров, не привязывается к какому либо полю и обычно используется вместе с валидатором токена из-за особенностей технической реализации валидаторов.

### Пример использования

```php
// Массив полей и значений
$data = [
    'test' => 'token',
];

// Настройки валидатора
$rules = [
    'test' => [
        'Csrf',
        'Flood',
    ],
];

// Валидация
$validator = new \Johncms\Validator\Validator($data, $rules);
if ($validator->isValid()) {
    echo 'OK';
} else {
    d($validator->getErrors());
}
```

В указанном примере валидатор Flood добавлен для того же поля, что и валидатор токена.


# Ban - Проверка банов

Валидатор **Ban** предназначен для упрощенной проверки наличия банов у пользователя. Валидатор так же обычно используется вместе с валидатором **Csrf**, т.к. не имеет привязки к данным в форме, но для работы валидатора, он должен быть добавлен для определенного поля.

### Поддерживаемые параметры

* **bans**: Массив банов, наличие которых будет проверяться. Параметр не обязателен. По умолчанию проверяется бан "Полная блокировка".

### Пример использования

```php
// Массив полей и значений
$data = [
    'test' => 'token',
];

// Настройки валидатора
$rules = [
    'test' => [
        'Csrf',
        'Flood',
        'Ban' => [11, 13],
    ],
];

// Валидация
$validator = new \Johncms\Validator\Validator($data, $rules);
if ($validator->isValid()) {
    echo 'OK';
} else {
    d($validator->getErrors());
}
```

В указанном примере будет проверяться бан для форума (11) и гостевой (13). Если у пользователя есть хотя бы 1 из этих банов, проверка не пройдет.


# Captcha - Проверка защитного кода

Валидатор Captcha предназначен для проверки защитного кода, который указал пользователь в форме.\
Перед проверкой, код должен быть сгенерирован и записан в сессию.

### Поддерживаемые параметры

* **sessionField**: Указывается ключ в сессии из которого валидатор будет использовать код. По умолчанию: **code**

### Примеры использования

```php
// Массив полей и значений
$data = [
    'test' => 'captcha_code',
];

// Настройки валидатора
$rules = [
    'test' => [
        'Captcha',
    ],
];

// Валидация
$validator = new \Johncms\Validator\Validator($data, $rules);
if ($validator->isValid()) {
    echo 'OK';
} else {
    d($validator->getErrors());
}
```

В указанном выше примере код код будет использоваться из переменной по умолчанию **$\_SESSION\['code']**

```php
// Массив полей и значений
$data = [
    'test' => 'captcha_code',
];

// Настройки валидатора
$rules = [
    'test' => [
        'Captcha' => [
            'sessionField' => 'captcha_code'
        ],
    ],
];

// Валидация
$validator = new \Johncms\Validator\Validator($data, $rules);
if ($validator->isValid()) {
    echo 'OK';
} else {
    d($validator->getErrors());
}
```

А в этом примере будет использован код из переменной **$\_SESSION\['captcha\_code']**

Более подробно работу с формами и с капчей рассмотрим в отдельной статье.


# Консольные команды

В JohnCMS есть единая консольная точка входа:

```bash
php system/bin/console
```

Команда выводит список всех доступных CLI-команд и их описания.

## Базовые команды

### Показать список команд

```bash
php system/bin/console list
```

### Показать справку по команде

```bash
php system/bin/console help schedule:run
```

## Текущие команды проекта

Ниже примеры команд, доступных в текущей версии:

* `i18n:scan` — сканирование исходников и генерация POT-файлов
* `i18n:translate` — генерация `*.lng.php` из `*.po`
* `mail:send-pending` — отправка накопившейся почтовой очереди
* `forum:cleanup-orphan-files` — очистка orphan-файлов форума
* `sitemap:generate` — генерация sitemap
* `schedule:list` — список задач планировщика
* `schedule:run` — запуск задач, которые должны выполниться в текущую минуту
* `router:list` — список зарегистрированных маршрутов
* `cache:clear` — очистка файлового кэша приложения
* `admin-tasks:run-queued` — запуск задач обслуживания, поставленных в очередь из админки (см. [Задачи обслуживания в админке](/10.0/konsol/zadachi-obsluzhivaniya-v-adminke))

### Показать все маршруты роутера

```bash
php system/bin/console router:list
```

### Показать маршруты с деталями

```bash
php system/bin/console router:list --details
```

### Очистить кэш приложения

```bash
php system/bin/console cache:clear
```

## Запуск в Docker

Если проект запущен через docker compose, команды обычно выполняют внутри контейнера `php-fpm`:

```bash
docker exec -it $(docker ps -q -f name=${COMPOSE_PROJECT_NAME}.php-fpm) php system/bin/console list
```

## Как добавить свою команду

1. Создайте класс, наследующий `Symfony\Component\Console\Command\Command`.
2. Добавьте атрибут `#[AsCommand(...)]` с именем и описанием команды.
3. Для вывода используйте `SymfonyStyle`.

Команды автоматически подхватываются контейнером и появляются в `system/bin/console list`.

### Примеры в коде

Если хотите быстро посмотреть рабочие реализации, ориентируйтесь на эти классы:

* `system/src/Console/Commands/I18nScanCommand.php`
* `system/src/Console/Commands/I18nTranslateCommand.php`
* `system/src/Console/Commands/ScheduleListCommand.php`
* `system/src/Console/Commands/ScheduleRunCommand.php`
* `system/src/Console/Commands/CronSendEmailCommand.php`

Для примера планировщика (расписание через атрибут) см.:

* `system/src/Scheduler/AsScheduledTask.php`
* `system/src/Scheduler/ScheduleRunner.php`
* `system/src/Scheduler/ScheduledTaskRegistry.php`

## См. также

* [Планировщик задач (schedule)](/10.0/konsol/planirovshchik-zadach-schedule)
* [Задачи обслуживания в админке](/10.0/konsol/zadachi-obsluzhivaniya-v-adminke)
* [Конфигурационные файлы (configs)](/10.0/obshie-svedeniya/konfiguracionnye-faily-configs)


# Планировщик задач (schedule)

Для периодических задач в JohnCMS используется встроенный планировщик:

* `schedule:list` — посмотреть список зарегистрированных задач
* `schedule:run` — выполнить задачи, которые должны сработать в текущую минуту

## Быстрый старт

### Показать задачи

```bash
php system/bin/console schedule:list
```

### Выполнить due-задачи

```bash
php system/bin/console schedule:run --no-interaction
```

## Как зарегистрировать задачу

Задачи описываются атрибутом `AsScheduledTask` прямо на классе консольной команды:

```php
#[AsCommand(name: 'sitemap:generate', description: 'Generate sitemap')]
#[AsScheduledTask(expression: '0 3 * * *', withoutOverlapping: true)]
final class SitemapGenerateCommand extends Command
{
    // ...
}
```

Поддерживаемые параметры:

* `expression` — cron-выражение (обязательно)
* `timezone` — таймзона для вычисления расписания
* `arguments` — аргументы/опции, передаваемые в команду при запуске планировщиком
* `withoutOverlapping` — запрет параллельного запуска одной и той же задачи
* `description` — описание задачи для вывода в `schedule:list`

## withoutOverlapping

При `withoutOverlapping: true` используется file-lock. Lock-файлы создаются в:

```
data/cache/schedule
```

Это защищает от повторного запуска одной задачи, если предыдущий запуск еще не завершился.

## Запуск по cron

Планировщик рассчитан на запуск раз в минуту любым внешним cron-механизмом (системный cron, scheduler панели хостинга, контейнерный scheduler и т.д.).

Рекомендуемая команда:

```
php /app/system/bin/console schedule:run --no-interaction
```

`/app/system/bin/console` — это пример пути для docker-окружения. В вашем cron нужно указать корректный абсолютный путь к файлу `system/bin/console` для вашего размещения проекта (с учетом document root/рабочей директории на сервере).

## Типовые cron-выражения

* Каждую минуту: `* * * * *`
* Каждый час (в начале часа): `0 * * * *`
* Каждый день в 03:00: `0 3 * * *`

## Частые проблемы

* Неверное cron-выражение — задача пропускается, ошибка уходит в лог.
* Команда не найдена — проверьте `#[AsCommand(name: ...)]`.
* Задача постоянно пропускается с overlap — проверьте, не висит ли старый процесс.

## См. также

* [Консольные команды](/10.0/konsol/konsolnye-komandy)
* [Проблемы и их решение](/10.0/obshie-svedeniya/problemy-i-ikh-reshenie)


# Задачи обслуживания в админке

Часть консольных команд можно запускать прямо из админ-панели на странице **Обслуживание** (`/admin/maintenance`, доступна пользователям с правами ≥ 9). Это удобно для рутинных операций вроде очистки кэша или генерации карты сайта, когда нет доступа к консоли.

## Как это работает

Команда попадает на страницу обслуживания, если её класс помечен атрибутом `#[AsAdminTask]`. Страница показывает список таких команд, их статус и последний вывод, а также кнопку запуска.

Предусмотрено два режима выполнения:

* **Синхронный (foreground)** — команда выполняется прямо в запросе, а её вывод показывается на странице сразу после завершения. Подходит для быстрых операций.
* **Фоновый (background)** — при нажатии кнопки задача только ставится в очередь, а выполняется отдельно планировщиком. Это защищает от таймаутов веб-сервера и повторного запуска при обновлении страницы. Подходит для долгих операций.

## Как пометить команду

Добавьте атрибут `#[AsAdminTask]` на класс консольной команды:

```php
use Johncms\AdminTasks\AsAdminTask;
use Johncms\Scheduler\AsScheduledTask;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;

#[AsCommand(name: 'cache:clear', description: 'Clear application cache files')]
#[AsAdminTask(title: 'Clear cache', description: 'Remove all application cache files')]
final class CacheClearCommand extends Command
{
    // ...
}
```

Параметры атрибута:

* `title` — заголовок задачи на странице (по умолчанию — имя команды).
* `description` — описание задачи (по умолчанию — описание из `#[AsCommand]`).
* `background` — если `true`, задача выполняется в фоне через планировщик; по умолчанию `false` (синхронно).

Пример фоновой задачи:

```php
#[AsCommand(name: 'sitemap:generate', description: 'Generate sitemap and update robots.txt')]
#[AsScheduledTask(expression: '0 3 * * *', withoutOverlapping: true)]
#[AsAdminTask(title: 'Generate sitemap', description: 'Generate sitemap and update robots.txt', background: true)]
final class CronGenerateSitemapCommand extends Command
{
    // ...
}
```

Атрибуты независимы: `#[AsScheduledTask]` отвечает за запуск по расписанию, `#[AsAdminTask]` — за появление на странице обслуживания. Их можно использовать вместе или по отдельности.

## Обработка фоновой очереди

Фоновые задачи выполняет команда `admin-tasks:run-queued`. Она помечена `#[AsScheduledTask(expression: '* * * * *')]`, поэтому запускается автоматически при каждом вызове планировщика — **отдельная настройка cron не требуется**, достаточно уже настроенного `schedule:run` (см. [Планировщик задач](/10.0/konsol/planirovshchik-zadach-schedule)).

Команда забирает задачи из очереди, помечает «зависшие» (например, после фатальной ошибки процесса) как завершившиеся с ошибкой и запускает каждую с защитой от параллельного выполнения.

При желании очередь можно обработать вручную:

```bash
php system/bin/console admin-tasks:run-queued
```

## Хранение данных

Состояние задач (статус, код выхода, вывод, метки времени) и файлы блокировок хранятся в:

```
data/admin_tasks
```

Каталог намеренно расположен вне `data/cache`, чтобы задача `cache:clear` не удаляла очередь и результаты во время работы. Он создаётся автоматически при первом запуске и не требует ручной подготовки.

## См. также

* [Консольные команды](/10.0/konsol/konsolnye-komandy)
* [Планировщик задач (schedule)](/10.0/konsol/planirovshchik-zadach-schedule)


# Структура стандартного шаблона

Шаблоны располагаются в папке **themes**, а их собранные стили, скрипты и картинки — в **public/themes**.

Обычно шаблон для JohnCMS имеет следующую структуру:

* themes
* * template\_name
  * * src
    * templates
* public
* * themes
  * * template\_name
    * * assets

В данной структуре обязательными являются только папки **assets** и **templates**, но в некоторых исключениях они вам могут не понадобиться.

{% hint style="info" %}
Папка **assets** лежит отдельно от остального шаблона потому, что корнем сайта (document root) является папка **public** — только её содержимое отдаётся браузеру. Исходники (**src**) и шаблоны страниц (**templates**) остаются вне доступа из веба.
{% endhint %}

#### Что такое шаблон в JohnCMS?

С точки зрения структуры шаблоном является любая папка в папке **/themes**\
В этой папке есть тема по умолчанию - **default**\
В этой теме находятся все необходимые для работы файлы по умолчанию: шаблоны, стили, картинки, скрипты и т.д.\
Также, каждый отдельный модуль может иметь свою папку с шаблонами **/module\_name/templates**, или другими файлами общего доступа **/public/assets/modules/module\_name**.

{% hint style="info" %}
Для частичной кастомизации стандартного шаблона, вы можете создать свою тему только с теми файлами, которые хотите изменить. При создании темы, просто сохраните такое же расположение нужного файла в папке с вашей кастомной темой. Более подробно про создание собственных тем, читайте на соответствующей странице.
{% endhint %}


# Изменение стилей шаблона

Начиная с JohnCMS 9.0.0 в системе используются современные средства для сборки файлов стилей и скриптов.\
Вы можете конечно не использовать эти средства, но они существенно облегчают разработку после того как вы разберетесь с ними.

Давайте разберемся как же нам теперь работать с нововведениями...\
Для работы сборщика нам понадобится Node.js. Вы можете скачать его с официального сайта <https://nodejs.org/ru/>\
Скачайте и установите Node.js на ваш компьютере. После установки перезагрузите компьютер.\
Установите JohnCMS на своем компьютере если ещё не установили.

Давайте разберемся где у нас подключаются стили и js и начнем делать свою тему на основе этого.

Откроем файл\
**themes/default/templates/system/layout/default.phtml**\
Как вы наверное догадались это основной шаблон нашего сайта.

Вверху найдем строчку:

```markup
<link rel="stylesheet" href="<?= $this->asset('css/app.css', true) ?>">
```

Эта строка у нас подключает css файл из папки **public/themes/default/assets/css/app.css**

Внизу строчку:

```markup
<script src="<?= $this->asset('js/app.js', true) ?>"></script>
```

Эта строчка подключает javascript из папки **public/themes/default/assets/js/app.js**

{% hint style="info" %}
Собранные стили и скрипты лежат в **public/themes/**, а их исходники — в **themes/**. Так сделано потому, что корнем сайта является папка **public**: браузер должен получать только собранные файлы, а исходники ему не нужны. Функция `asset()` подставляет адрес сама, поэтому в шаблоне путь не меняется.
{% endhint %}

Если мы откроем эти файлы, то увидим там много кода в одну строку. Это нормально. Эти файлы собираются сборщиком и сжимаются для ускорения загрузки браузером пользователей.\
Как вы наверное уже догадались, эти файлы редактировать не нужно т.к. их собирает сборщик.

Давайте разберемся со сборщиком.\
Настройка сборщика производится в файле **/webpack.mix.js** (в корне сайта).\
Давайте откроем его и посмотрим что там есть.\
Найдем там 2 строчки которые там нужны:

```javascript
mix.js('themes/default/src/js/app.js', 'public/themes/default/assets/js')
    .sass('themes/default/src/scss/app.scss', 'public/themes/default/assets/css')
```

Что-то знакомое тут, не правда ли?\
Давайте разберемся, что у нас тут для чего.

```javascript
mix.js('themes/default/src/js/app.js', 'public/themes/default/assets/js')
```

Эта строчка говорит сборщику чтобы он взял файл по пути **themes/default/src/js/app.js** произвел все необходимые операции с ним и положил его в папку **public/themes/default/assets/js**. Т.к. во втором параметре мы явно не указали название файла, сборщик соберет файл и сохранит с таким же именем что и исходный файл т.е. **app.js**. В итоге получится так: **public/themes/default/assets/js/app.js**

Посмотрим на вторую строку

```javascript
.sass('themes/default/src/scss/app.scss', 'public/themes/default/assets/css')
```

В этой строке мы говорим сборщику чтобы он взял файл **themes/default/src/scss/app.scss**, преобразовал его в пригодный для браузера вид, сжал и положил его в папку **public/themes/default/assets/css**. Т.к. название файла явно не указали, сборщик назовет файл так же как и исходный, но расширение укажет css. т.е. app.css. В итоге получится так: **public/themes/default/assets/css/app.css**

Теперь мы разобрались как у нас попадают файлы **app.js** и **app.css** в нужные папки.

Давайте теперь создадим свою тему и настроим сборщик так, чтобы он собирал ещё и стили и скрипты в нашей теме.\
Создаем в папке **themes** подпапку с нашей темой **my\_theme** и копируем в неё из темы **default** папки **src** и **templates**\
Затем создаем папку **public/themes/my\_theme** и копируем в неё папку **assets** из **public/themes/default**\
На этом наша тема готова к сборке.\
Теперь давайте расскажем о ней сборщику и соберем наши стили и скрипты.

Открываем файл **/webpack.mix.js**\
Вставим после строки

```javascript
mix.sourceMaps(true, 'source-map');
```

следующие 2 строки:

```javascript
mix.js('themes/my_theme/src/js/app.js', 'public/themes/my_theme/assets/js')
    .sass('themes/my_theme/src/scss/app.scss', 'public/themes/my_theme/assets/css');
```

Мы видим тут те же самые пути, которые рассматривали выше, за исключением папки с темой. Это наша новая папка с темой, которую мы создали.

На этом сборщик настроен и уже будет работать.

Давайте откроем командную строку, перейдем в папку с установленным johncms для этого наберите cd и путь к папке в которой установлен johncms.\
После этого давайте установим зависимости и запустим сборщик.\
Выполните команду\
\&#xNAN;**`npm install`**\
Эта команда установит bootstrap и прочие библиотеки, необходимые для работы.

После этого выполните команду\
\&#xNAN;**`npm run watch`**

Эта команда соберет app.js и app.css и будет следить за изменением исходных файлов и пересобирать app.js и app.css когда вы изменяете исходные файлы.\
В результате её выполнения вы должны увидеть следующее:

```bash
        Asset                                 Size                         Chunks                   Chunk Names
public/themes/default/assets/css/app.css      258 KiB  /themes/default/assets/js/app  [emitted]        /themes/default/assets/js/app
public/themes/default/assets/css/app.css.map  295 KiB  /themes/default/assets/js/app  [emitted] [dev]  /themes/default/assets/js/app
public/themes/my_theme/assets/css/app.css     258 KiB  /themes/default/assets/js/app  [emitted]        /themes/default/assets/js/app
public/themes/my_theme/assets/css/app.css.map 295 KiB  /themes/default/assets/js/app  [emitted] [dev]  /themes/default/assets/js/app
 + 4 hidden assets
```

Давайте теперь разбираться в структуре css и js.\
Откроем файл: **themes/my\_theme/src/js/app.js**\
Этот файл является основным и в нем подключаются все дополнительные файлы.\
Все дополнительные файлы лежат в той же папке что и основной файл. Вы можете открывать их, редактировать или смотреть что в них находится.

Откроем файл: **themes/my\_theme/src/scss/app.scss**\
так же как и app.js этот файл является основным файлом в котором подключаются все дочерние.

Давайте посмотрим файл и найдем наш сайдбар чтобы поменять цвет.\
Найдем строки

```css
// Левое меню
@import "sidebar";
```

Эта строка подключает файл **sidebar.scss** из той же папки что и **app.scss**\
Давайте откроем файл **sidebar.scss**\
В этом файле мы видим практически привычный CSS код. Но он поддерживает вложенность селекторов и прочие возможности. Вы можете подробнее прочитать про SCSS (SASS) на просторах интернета или спросить у нас на форуме.

И так, давайте поменяем всё таки цвет нашего меню. Цвет меню задан прямо во второй строке:

```css
background-color: #ffffff;
```

Меняем код цвета и сохраняем файл.\
После сохранения, сборщик пересоберет app.css и вы увидите изменения на сайте.

Обратите внимание, что команда **`npm run watch`** выполняет сборку, но не выполняет сжатие CSS и JS файлов для ускорения работы.\
Перед тем, как вы захотите выгрузить изменения на сайт, выполните команду **`npm run prod`** она соберет файлы и выполнить минификацию. После этого размер файлов будет меньше.

Примечание:\
После создания темы, не забудьте зайти в настройки и выбрать новую тему :)


# Создание собственного шаблона

Давайте создадим свой первый простой шаблон.\
Начнем с задачи, которая изначально возникнет практически у всех, кто установит себе JohnCMS: мы будем менять Главную страницу сайта и логотип. Перед тем, как взяться за создание своего шаблона, давайте составим примерный план предполагаемых работ.

### **Что мы сделаем?**

* Создадим свою тему с названием "lesson"
* Поменяем Главную страницу сайта. Вместо имеющегося по умолчанию текста, на ней крупными буквами выведем "Добро пожаловать!"
* Заменим логотип сайта. Вместо JohnCMS будем использовать свою .PNG картинку.
* Изменим цвет боковой панели навигации: вместо белого использовать какой-нибудь темный оттенок, подходящий по дизайну. Соответственно поменяем цвет иконок.

{% hint style="danger" %}

#### Внимание!

У движка есть тема "**default**", которая является системной, поставляется вместе с дистрибутивом и находится в папке `/themes/default`. В этой теме находятся все необходимые для работы файлы по умолчанию: шаблоны, стили, картинки, скрипты и т.д. Также, каждый отдельный модуль может иметь свою папку с шаблонами `/module_name/templates`, или другими файлами общего доступа `/public/assets/modules/module_name`.

**Нельзя редактировать, или удалять файлы в этих папках, нельзя ничего туда добавлять**, иначе Вы потеряете совместимость с последующими обновлениями, или же в работе движка могут возникнуть ошибки, вплоть до полной потери работоспособности.
{% endhint %}

#### Пошаговая инструкция

1. В папке `/themes` создаем папку `lesson`
2. Заходим в админку и далее в системные настройки. Там в списке имеющихся тем мы увидим нашу **lesson**. Выбираем ее и нажимаем "Сохранить".\
   Теперь для нашего сайта применена тема "lesson" и все, что мы будем в ней делать, сразу же будет видно.
3. Чтобы поменять Главную страницу сайта, мы должны отредактировать ее шаблон, который находится в модуле `/modules/homepage`.\
   В папке с модулем есть папка `/templates` а в ней лежит файл `index.phtml` - это и есть Главная страница, этот файл нам и нужен.\
   Из предупреждения выше мы знаем, что менять шаблон в самом модуле нельзя, поэтому мы должны сначала скопировать файл шаблона в свою тему, и только потом его изменять. Не переместить, а именно скопировать, оригинал файла должен остаться на своем месте
4. Куда? `/themes/lesson` - это папка с нашей темой, которую мы создали выше. Мы должны скопировать сюда файл `index.phtml` из модуля homepage. Для шаблонов в папке с нашей темой должна быть подпапка `templates`.\
   Чтоб не возникало конфликтов (например файл `index.phtml` может быть у многих модулей), в папке `templates` создается подпапка с названием пространства имен для шаблонов модуля (обычно совпадает с именем папки модуля) и уже в нее копируется нужный нам файл.
5. В папке с нашей темой `/themes/lesson` создаем подпапку `templates` а в ней подпапку с именем модуля ( в нашем случае это `homepage`) откуда мы копируем шаблон. В итоге должно получиться `/themes/lesson/templates/homepage/` сюда и копируем наш `index.phtml`\
   Теперь, пока у нас в админке включена наша тема "lesson", для Главной страницы используется именно тот файл, который мы только что скопировали в нашу тему. И все изменения в этом файле сразу будут видны на Главной странице нашего сайта.

{% hint style="info" %}
Инструкция будет дополнена.
{% endhint %}


# Структура модуля

Модули располагаются в папке **modules**.

Начиная с версии **9.9** рекомендуется использовать слоистую структуру модуля, при которой код разделён по слоям (Application, Domain, Infrastructure). Такая структура упрощает поддержку и рефакторинг больших модулей.

{% hint style="info" %}
Старая структура (папки `Controllers`, `templates`, `locale` прямо в корне модуля) по-прежнему работает и остаётся полностью совместимой. Но для новых модулей рекомендуется использовать новую слоистую структуру, описанную ниже.
{% endhint %}

Обычно модуль для JohnCMS 9.9 имеет следующую структуру:

* modules
  * module\_name
    * config
    * locale
    * src
      * Application
      * Domain
      * Infrastructure
      * Install
    * templates

Данная структура носит рекомендательный характер и не является обязательной.\
Система не накладывает ограничений на разработчика, и разработчик вправе использовать свою структуру модуля, которая для него будет удобнее.

Давайте подробнее посмотрим на структуру и разберёмся, что и для чего предназначено.

* **modules** — это обычная системная папка с модулями.
  * **module\_name** — это папка с названием модуля (например forum, community и т.п.).
    * **config** — конфигурация модуля: маршруты (`routes.php`) и регистрация сервисов в контейнере (`services.php`).
    * **locale** — папка, в которой хранятся файлы локализации модуля. Если модуль мультиязычный, то эта папка обычно есть.
    * **src** — исходный код модуля, разделённый по слоям.
      * **Application** — прикладной слой: контроллеры (`Controllers`), сценарии использования (`UseCases`), объекты передачи данных (`DTO`), сервисы (`Services`), middleware (`Middlewares`), консольные команды (`Console`), исключения (`Exceptions`).
      * **Domain** — доменный слой: модели (`Models`), контракты репозиториев (`Repository`), сущности (`Entities`), перечисления (`Enums`).
      * **Infrastructure** — инфраструктурный слой: реализации репозиториев и работа с хранилищем данных (`Persistence/Repository`, `Persistence/Models`).
      * **Install** — папка с установочными файлами (например `Installer.php`).
    * **templates** — в этой папке хранятся шаблоны модуля.

{% hint style="info" %}
Вложенные папки внутри `src` (например `Controllers`, `UseCases`, `Repository`) создаются по мере необходимости — только когда в них появляется первый класс. Заранее создавать все папки не нужно.
{% endhint %}

Директория `src` внутри модуля используется как пространство имён для автоматической загрузки классов (PSR-4). Пространство имён регистрируется в `composer.json`, например:

```json
"Johncms\\Modules\\ModuleName\\": "modules/module_name/src/"
```

После регистрации пространства имён нужно обновить карту автозагрузки, выполнив команду:

```bash
composer dump-autoload
```


# Создание модуля

Давайте создадим свой первый простой модуль.\
Это будет обычная простая страница со списком наших партнёров.

Как нам уже известно, модули располагаются в папке **modules**.

Сначала создадим папку с модулем и назовём её **partners**, путь к папке получится такой: **modules/partners**.

Пока создадим простой модуль без мультиязычности.

Начиная с версии **9.9** рекомендуется использовать слоистую [структуру модуля](/10.0/moduli/struktura-modulya). Исходный код располагается в папке **src** и делится по слоям (Application, Domain, Infrastructure). Для нашего простого модуля понадобится только прикладной слой (Application), конфигурация и шаблоны.

После выполнения всех действий из этой статьи у нас получится такая структура:

* modules
  * partners
    * config
      * routes.php
    * src
      * Application
        * Controllers
          * PartnersController.php
    * templates
      * index.phtml

{% hint style="info" %}
Старая структура (папка `Controllers` прямо в корне модуля) по-прежнему работает. Но для новых модулей рекомендуется использовать новую структуру, описанную здесь.
{% endhint %}

Контроллеры позволяют избавиться от большого количества базового кода, который необходимо написать для начала работы, а также упрощают настройку маршрутов: не нужно самостоятельно писать логику по определению страницы, которую необходимо показать пользователю.

Контроллеры являются обычными PHP-классами. В JohnCMS используется автозагрузка классов модулей по стандарту [PSR-4](https://www.php-fig.org/psr/psr-4/). Чтобы она работала, нужно придерживаться некоторых правил:

1. Классы модуля должны располагаться в папке **src** внутри модуля, а [пространство имён](https://www.php.net/manual/ru/language.namespaces.rationale.php) должно соответствовать структуре папок. Разрешено использовать любые директории внутри **src** для логического разделения классов.
2. Для работы автозагрузки пространство имён модуля должно быть зарегистрировано в `composer.json`, а сам модуль — в конфигурации. Как это сделать, рассмотрим ниже.

## Регистрация пространства имён

Чтобы классы модуля загружались автоматически, зарегистрируйте пространство имён в секции `autoload.psr-4` файла `composer.json`:

```json
"Johncms\\Modules\\Partners\\": "modules/partners/src/"
```

Таким образом, пространством имён нашего модуля будет **Johncms\Modules\Partners**, и оно указывает на папку **modules/partners/src**.

После регистрации пространства имён нужно обновить карту автозагрузки, выполнив команду:

```bash
composer dump-autoload
```

## Регистрация модуля

Помимо автозагрузки классов, модуль нужно зарегистрировать в системе, добавив его в список установленных модулей.

Создайте в папке **config/autoload** файл с именем **modules.local.php** (если его ещё нет) со следующим содержимым:

{% code title="/config/autoload/modules.local.php" %}

```php
<?php

return [
    'modules' => [
        'installed_modules' => [
            'partners', // Название папки с модулем
        ],
    ],
];
```

{% endcode %}

В данном случае **partners** — это название папки с модулем. При добавлении дополнительных модулей просто добавьте их названия по аналогии.

## Создание контроллера

Создадим наш первый контроллер, который будет отвечать за отображение страницы партнёров.

Исходя из типовой [структуры модуля](/10.0/moduli/struktura-modulya), классы контроллеров располагаются в папке **src/Application/Controllers**. С учётом зарегистрированного пространства имён полное пространство имён для нашего контроллера будет таким: `Johncms\Modules\Partners\Application\Controllers`.

Давайте создадим файл **PartnersController.php** со следующим содержимым:

{% code title="modules/partners/src/Application/Controllers/PartnersController.php" %}

```php
<?php

declare(strict_types=1);

namespace Johncms\Modules\Partners\Application\Controllers;

use Johncms\Http\Controller\ControllerContext;
use Johncms\NavChain;
use Johncms\System\View\Render;

final readonly class PartnersController
{
    public function __construct(
        private ControllerContext $controllerContext,
        private Render $render,
        private NavChain $navChain,
    ) {
        // Инициализируем модуль: регистрируем папку с шаблонами и файлы локализации
        $this->controllerContext->initModule('partners');
    }

    public function __invoke(): string
    {
    }
}
```

{% endcode %}

Разберёмся с этим кодом:

* Контроллер объявлен как `final readonly class` — это рекомендуемый стиль для новых классов.
* Зависимости передаются через конструктор (constructor injection) и разрешаются автоматически контейнером зависимостей. Нам понадобятся:
  * `ControllerContext` — вспомогательный сервис. Его метод `initModule('partners')` регистрирует папку с шаблонами модуля и файлы локализации. Вызываем его в конструкторе, передавая название папки модуля.
  * `Render` — сервис шаблонизатора.
  * `NavChain` — сервис для работы с цепочкой навигации (хлебными крошками).
* Метод `__invoke()` делает контроллер «вызываемым»: именно он выполняется при обращении к маршруту. Он должен вернуть строку с содержимым страницы.

Теперь дополним метод `__invoke()`.

Установим заголовок страницы в тегах title и h1. Для этого в шаблонизатор нужно добавить 2 переменные с именами `title` и `page_title`:

```php
// Устанавливаем заголовок страницы в теге title и h1
$this->render->addData(
    [
        'title'      => 'Партнёры',
        'page_title' => 'Наши партнёры',
    ]
);
```

Добавим нашу страницу в цепочку навигации:

```php
$this->navChain->add('Партнёры', '/partners/');
```

Подготовим данные для шаблона. Наполним массив нашими партнёрами и передадим его в шаблон:

```php
// Собираем массив данных, который будет передан в шаблон
$data = [
    'partners' => [
        [
            'name' => 'JohnCMS', // Название партнёра
            'url'  => 'https://johncms.com', // Ссылка на сайт партнёра
        ],
        [
            'name' => 'Партнёр 2',
            'url'  => 'https://example.com',
        ],
        [
            'name' => 'Партнёр 3',
            'url'  => 'https://example.org',
        ],
    ],
];

return $this->render->render('partners::index', ['data' => $data]);
```

Обратите внимание на последнюю строку. Шаблонизатор имеет своё пространство имён для шаблонов. Оно регистрируется вызовом `initModule('partners')` и совпадает с названием папки модуля. В строке `'partners::index'` слева от `::` — название модуля, справа — название файла шаблона из папки **templates** (без расширения). Вторым параметром `['data' => $data]` передаётся массив данных, доступных в шаблоне: ключи массива становятся именами переменных. В данном примере в шаблоне будет доступна переменная `$data` с массивом партнёров.

### Полный код файла контроллера

{% code title="modules/partners/src/Application/Controllers/PartnersController.php" %}

```php
<?php

declare(strict_types=1);

namespace Johncms\Modules\Partners\Application\Controllers;

use Johncms\Http\Controller\ControllerContext;
use Johncms\NavChain;
use Johncms\System\View\Render;

final readonly class PartnersController
{
    public function __construct(
        private ControllerContext $controllerContext,
        private Render $render,
        private NavChain $navChain,
    ) {
        $this->controllerContext->initModule('partners');
    }

    public function __invoke(): string
    {
        // Устанавливаем заголовок страницы в теге title и h1
        $this->render->addData(
            [
                'title'      => 'Партнёры',
                'page_title' => 'Наши партнёры',
            ]
        );

        // Добавляем страницу в цепочку навигации
        $this->navChain->add('Партнёры', '/partners/');

        // Собираем массив данных, который будет передан в шаблон
        $data = [
            'partners' => [
                [
                    'name' => 'JohnCMS', // Название партнёра
                    'url'  => 'https://johncms.com', // Ссылка на сайт партнёра
                ],
                [
                    'name' => 'Партнёр 2',
                    'url'  => 'https://example.com',
                ],
                [
                    'name' => 'Партнёр 3',
                    'url'  => 'https://example.org',
                ],
            ],
        ];

        return $this->render->render('partners::index', ['data' => $data]);
    }
}
```

{% endcode %}

## Создание шаблона

Далее создадим наш шаблон. Шаблон будет располагаться в папке **templates**, и т.к. это основная страница партнёров, назовём его **index.phtml**.

{% code title="modules/partners/templates/index.phtml" %}

```php
<?php
// Подключаем основной шаблон сайта
$this->layout('system::layout/default');
?>

<div>
    Мы сотрудничаем со следующими партнёрами:
</div>

<ul>
    <!-- Тут мы перебираем наш массив партнёров и выводим название партнёра со ссылкой на его сайт -->
    <?php foreach ($data['partners'] as $partner): ?>
        <li><a href="<?= $partner['url'] ?>"><?= $partner['name'] ?></a></li>
    <?php endforeach; ?>
</ul>
```

{% endcode %}

## Добавление маршрута

Наш модуль готов, но пока ещё не доступен в браузере. Давайте это исправим.\
Чтобы модуль стал доступен, нужно создать файл `config/routes.php` внутри папки модуля. Система подхватит его автоматически.

{% code title="modules/partners/config/routes.php" %}

```php
<?php

declare(strict_types=1);

use Johncms\Modules\Partners\Application\Controllers\PartnersController;
use Johncms\Router\RouteCollection;

return static function (RouteCollection $router): void {
    /*
     * /partners - Это адрес страницы, по которому будет доступен наш модуль.
     *
     * Вторым параметром передаётся класс контроллера. Так как контроллер
     * является вызываемым (реализует метод __invoke), название метода указывать не нужно.
     */
    $router->map(['GET', 'POST'], '/partners', PartnersController::class)->name('partners');
};
```

{% endcode %}

Теперь наш модуль доступен по адресу **ваш.сайт/partners/**

Теперь давайте сообщим модулю online, что у нас появился модуль партнёров и нужно в списке пользователей онлайн отображать тех, кто смотрит эту страницу.\
Для этого перейдём в папку **config** и создадим в ней файл **places.local.php**, если его ещё нет.

```php
<?php

return [
    '/partners' => '<a href="/partners/">Смотрит партнёров</a>',
];
```

Отлично, наш модуль теперь полностью работоспособен, вам останется только добавить на него ссылку в основном шаблоне или на любой другой странице на ваше усмотрение.


# Маршрутизация (роутинг)

В JohnCMS маршрутизация построена на `Johncms\Router\RouteCollection` и `Symfony Routing`.

Эта страница описывает текущий подход: как объявлять маршруты, как подключать middleware, какие бывают обработчики и как происходит dispatch.

## Где описываются маршруты

Маршруты загружаются в следующем порядке:

1. `config/routes.php` — глобальные/системные маршруты (зарезервирован для ядра)
2. `modules/{name}/config/routes.php` — маршруты каждого модуля (подхватываются автоматически)

Каждый модуль регистрирует свои маршруты в `config/routes.php` внутри папки модуля. Файл подхватывается автоматически — вручную подключать его не нужно.

## Базовый пример маршрута

Файл `modules/{name}/config/routes.php` должен возвращать callable:

```php
<?php

declare(strict_types=1);

use Johncms\Modules\Partners\Application\Controllers\PartnersController;
use Johncms\Router\RouteCollection;
use Johncms\System\Users\User;

return static function (RouteCollection $router, User $user): void {
    $router->get('/partners', PartnersController::class)->name('partners.index');
    $router->map(['GET', 'POST'], '/feedback', FeedbackController::class)->name('feedback');
};
```

Параметр `$user` доступен для регистрации маршрутов, зависящих от состояния пользователя:

```php
return static function (RouteCollection $router, User $user): void {
    $router->get('/posts', PostsController::class);

    if ($user->isValid()) {
        $router->post('/posts/create', CreatePostController::class);
    }
};
```

{% hint style="info" %}
**Конвенция завершающего слэша.** Маршруты принято регистрировать **без** завершающего слэша (`/partners`), а в ссылках (в шаблонах и контроллерах) — использовать слэш (`/partners/`). Перед сопоставлением `index.php` нормализует URI через `rtrim`, поэтому оба варианта работают.
{% endhint %}

## Именованные маршруты

Маршруту можно задать имя с помощью метода `name()`. Имя используется как внутренний идентификатор маршрута в системе.

```php
$router->map(['GET', 'POST'], '/guestbook', GuestbookController::class)->name('guestbook.index');
```

Рекомендуется именовать маршруты по схеме `модуль.действие` (например `guestbook.index`, `guestbook.edit`, `admin.contacts.save`). Это соглашение используется во всех штатных модулях. `name()` можно комбинировать с другими методами (`requirements()`, `addMiddleware()` и т.д.) в цепочке вызовов.

## Параметры и ограничения

Маршрут может содержать параметры и ограничения через `requirements()`:

```php
$router
    ->map(['GET', 'POST'], '/contacts/{city}/{id}/{street}', 'modules/contacts/index.php')
    ->defaults([
        'city' => null,
        'id' => null,
        'street' => null,
    ])
    ->requirements([
        'id' => '\\d+',
    ]);
```

Также поддерживаются пресеты в пути:

* `{id:number}`
* `{article_code:slug}`
* `{category:path}`

Примеры можно посмотреть в файлах `modules/*/config/routes.php`.

## Middleware на маршрутах

Middleware — это промежуточный обработчик между совпавшим маршрутом и его handler. Он получает `Request`, может выполнить проверку/подготовку и передать управление дальше через `$next($request)`.

Обычно middleware используют для:

* проверки доступа (права, авторизация, владение ресурсом)
* валидации обязательных условий перед действием
* логирования и других сквозных задач

### Middleware для конкретного маршрута

Добавление middleware к одному маршруту:

```php
use Johncms\Modules\Guestbook\Application\Controllers\ClearGuestbookController;
use Johncms\Modules\Guestbook\Application\Middlewares\GuestbookCleanAccessMiddleware;

$router
    ->map(['GET', 'POST'], '/guestbook/clean', ClearGuestbookController::class)
    ->addMiddleware(GuestbookCleanAccessMiddleware::class);
```

Можно указывать несколько middleware, они будут вызваны по порядку добавления.

### Middleware для группы/коллекции маршрутов

Можно добавить middleware сразу на коллекцию (например, в группе):

```php
$router->group('/guestbook', static function (\Johncms\Router\RouteCollection $group): void {
    $group->addMiddleware(GuestbookCommonAccessMiddleware::class);

    $group->get('/edit/{id:number}', EditEntryController::class);
    $group->post('/reply/{id:number}', ReplyController::class);
});
```

Такой middleware будет применяться ко всем маршрутам внутри этой коллекции.

### Допустимые типы middleware

Middleware может быть:

* классом (получается из контейнера), реализующим `Johncms\Router\MiddlewareInterface`
* callable

Контракт middleware для класса:

```php
public function handle(Request $request, callable $next): Response;
```

Пример класса middleware:

```php
<?php

declare(strict_types=1);

namespace Johncms\Modules\Guestbook\Application\Middlewares;

use Johncms\Http\Request;
use Johncms\Router\MiddlewareInterface;
use Symfony\Component\HttpFoundation\Response;

final class GuestbookCleanAccessMiddleware implements MiddlewareInterface
{
    public function handle(Request $request, callable $next): Response
    {
        // Параметры совпавшего маршрута доступны как атрибуты запроса
        $params = $request->attributes->all();

        // Если условие не выполнено, можно прервать цепочку (throw/return)
        // throw new \Johncms\Exceptions\PageNotFoundException();

        return $next($request);
    }
}
```

Пример callable middleware:

```php
$router
    ->get('/partners', Johncms\Modules\Partners\Application\Controllers\PartnersController::class)
    ->addMiddleware(static function (\Johncms\Http\Request $request, callable $next): \Symfony\Component\HttpFoundation\Response {
        return $next($request);
    });
```

### Порядок выполнения

При обработке совпавшего маршрута выполняется цепочка:

1. middleware коллекции/группы
2. middleware самого маршрута
3. handler маршрута

Если middleware не вызывает `$next($request)`, цепочка останавливается и handler не будет вызван.

## Какие обработчики поддерживаются

В маршруте можно указать:

1. Строку с путем legacy-файла (`'modules/contacts/index.php'`)
2. Invokable-контроллер (`SomeController::class` с `__invoke()`)
3. Массив `[ControllerClass::class, 'method']`

Это обрабатывается в `index.php` через `ActionInvoker` и `MiddlewareDispatcher`.

## Как работает dispatch

Схема обработки запроса:

1. URI нормализуется в `index.php`
2. `SymfonyRouteMatcher::dispatch()` пытается сопоставить маршрут
3. При `FOUND`:
   * route params кладутся в request
   * запускается цепочка middleware
   * вызывается handler
4. При `METHOD_NOT_ALLOWED` возвращается `405 Method Not Allowed`
5. При `NOT_FOUND` вызывается `pageNotFound()`

## Типовые ошибки

### 404 Not Found

Причины:

* путь не совпадает с шаблоном маршрута
* route params не прошли `requirements`
* маршрут не зарегистрирован в `modules/{name}/config/routes.php`

### 405 Method Not Allowed

Причина:

* URL найден, но HTTP-метод не разрешен для маршрута (например, `POST` вместо `GET`).

### Ошибка middleware

Причины:

* middleware-класс не зарегистрирован в контейнере
* middleware не callable и не реализует `MiddlewareInterface`

В этом случае `MiddlewareDispatcher` выбросит `InvalidArgumentException`.

## См. также

* [Создание модуля](/10.0/moduli/sozdanie-modulya)
* [Структура модуля](/10.0/moduli/struktura-modulya)
* [Конфигурационные файлы (configs)](/10.0/obshie-svedeniya/konfiguracionnye-faily-configs)
* [Проблемы и их решение](/10.0/obshie-svedeniya/problemy-i-ikh-reshenie)


# Sitemap-провайдер

Как добавить URL-адреса своего модуля в автоматически генерируемый Sitemap

JohnCMS автоматически генерирует XML-карту сайта (`sitemap.xml`) по расписанию. Каждый модуль может добавить свои URL в sitemap, реализовав интерфейс `SitemapUrlProviderInterface` и зарегистрировав провайдер в контейнере.

## Как это работает

При генерации sitemap система собирает все зарегистрированные провайдеры (помеченные тегом `johncms.sitemap_provider`), вызывает у каждого метод `getEntries()` и записывает результат в отдельный файл `sitemap-{groupName}-1.xml`. Итоговый `sitemap.xml` — это индексный файл со ссылками на все чанки.

## Создание провайдера

Реализуйте интерфейс `Johncms\Sitemap\SitemapUrlProviderInterface`:

```php
<?php

declare(strict_types=1);

namespace Johncms\Modules\MyModule\Application\Sitemap;

use Johncms\Modules\MyModule\Domain\Models\MyModel;
use Johncms\Sitemap\SitemapUrlEntry;
use Johncms\Sitemap\SitemapUrlProviderInterface;

final class MyModuleUrlsProvider implements SitemapUrlProviderInterface
{
    public function groupName(): string
    {
        // Уникальное имя группы — определяет имя файла: sitemap-my-module-1.xml
        return 'my-module';
    }

    /**
     * @return iterable<SitemapUrlEntry>
     */
    public function getEntries(string $homeUrl): iterable
    {
        $items = MyModel::query()
            ->select(['id', 'slug', 'updated_at'])
            ->orderBy('id')
            ->cursor(); // cursor() вместо get() экономит память на больших таблицах

        foreach ($items as $item) {
            yield new SitemapUrlEntry(
                loc: $homeUrl . '/my-module/' . $item->slug . '/',
                lastmod: $item->updated_at?->format('c'),
            );
        }
    }
}
```

### SitemapUrlEntry

Конструктор принимает два параметра:

| Параметр  | Тип            | Описание                                                     |
| --------- | -------------- | ------------------------------------------------------------ |
| `loc`     | `string`       | Полный URL страницы (включая домен)                          |
| `lastmod` | `string\|null` | Дата последнего изменения в формате ISO 8601 (необязательно) |

## Регистрация провайдера

В файле `modules/my-module/config/services.php` зарегистрируйте провайдер с тегом `johncms.sitemap_provider`:

```php
<?php

declare(strict_types=1);

use Johncms\Modules\MyModule\Application\Sitemap\MyModuleUrlsProvider;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;

return function (ContainerConfigurator $configurator): void {
    $services = $configurator->services();

    $services
        ->set(MyModuleUrlsProvider::class)
        ->autowire()
        ->tag('johncms.sitemap_provider')
        ->public();
};
```

После этого при следующем запуске планировщика провайдер будет автоматически подхвачен и его URL попадут в sitemap.

## Важные моменты

* `groupName()` должен быть уникальным среди всех провайдеров. При дублировании система выбросит исключение.
* Используйте `cursor()` вместо `get()` для выборки большого количества записей — это не загружает все записи в память сразу.
* Лимит URL на один файл — 45 000. При превышении система автоматически создаёт дополнительные чанки (`sitemap-my-module-2.xml` и т.д.).


# Перевод JohnCMS на другие языки

JohnCMS является мультиязычной CMS. К сожалению разработчики не знают всех языков, которые существуют на планете и для того чтобы система оставалась мультиязычной, необходимо чтобы люди, знающие другие языки, помогали с переводом.\
Мы постарались максимально упростить процесс перевода системы на другие языки.

Есть два способа поучаствовать в переводе:

1. **Через Crowdin** — перевод прямо в браузере, не требует навыков программирования. Подходит большинству переводчиков.
2. **Через пулреквест на GitHub** — редактирование `.po`-файлов в репозитории. Подходит тем, кто привык работать с git и программами вроде Poedit.

Переводы между Crowdin и репозиторием синхронизируют разработчики вручную, поэтому оба способа равнозначны — выбирайте удобный.

## Способ 1: перевод через Crowdin

Чтобы поучаствовать в переводе нужно выполнить некоторые действия. Давайте рассмотрим их по порядку.

Переходим по ссылке [translate.johncms.com](https://translate.johncms.com/)

Вам необходимо авторизоваться на сайте. Если у вас уже есть учетная запись на crowdin.com или вы зарегистрированы в Facebook, Google, Twitter, Github или Gitlab, то можете нажать на кнопку **Log in.** Перед вами появится окно авторизации в котором вы можете ввести логин и пароль от учетной записи или войти через описанные выше сервисы.\
Если учетной записи нет, то нажимаете на Sign up и регистрируетесь.

![страница авторизации](/files/-MUj27dr87Uhu2P1Yegd)

После авторизации вы можете приступать к переводу. Для этого в списке языков выберите тот язык, на который вы хотите перевести систему

![список языков](/files/-MV1VSYmpwxtX1X45p3g)

Если нужного языка нет в списке, мы можем его добавить — см. раздел [Добавление нового языка](#dobavlenie-novogo-yazyka) ниже.

После выбора языка перед вами появится эта страница:

![список переводов](/files/-MUj2EaT-vrEeC7tMtD6)

На этой странице отображен список переводов для модулей системы и процент фраз, которые уже переведены.

Выбираете модуль, который хотите перевести. Попадаете на страницу со списком фраз:

![список фраз](/files/-MV1UQ50x-Alz-XXROng)

В левой части отображается список фраз для перевода.\
В центре отображается сам перевод и информация о том, в каких файлах содержится фраза (**context**). Так же вы можете посмотреть как эта фраза переведена на другие языки, для этого разверните блок **OTHER LANGUAGES.** Так же есть уже автоматически переведенные варианты (**TM and MT Suggestions**), которые вы можете выбрать если там есть подходящий вариант. Если подходящего варианта в автоматических переводах нет, то нужно вписать перевод вручную в поле ввода и нажать **Save** (сохранить). После сохранения автоматически откроется следующая фраза для перевода в текущем модуле.

После завершения перевода модуля, вы можете выйти назад в список модулей. Для этого вверху слева нажимаете на меню и выбираете Quit Editor. После этого вы попадете обратно в список модулей и можете переводить другие модули аналогичным образом.

После того как Вы выполняете перевод, Ваши переводы проходят проверку разработчиками и загружаются в CMS, а потом с выходом обновлений становятся доступны всем.

## Способ 2: перевод через пулреквест на GitHub

Если вам удобнее работать с файлами напрямую, вы можете переводить в репозитории и отправлять изменения пулреквестом.

Файлы переводов лежат в формате gettext `.po`:

* `system/locale/<язык>.po` — системные фразы (ядро, общие шаблоны);
* `modules/<модуль>/locale/<язык>.po` — фразы конкретного модуля (например, `modules/forum/locale/de.po`).

Порядок действий:

1. Сделайте форк репозитория [johncms/johncms](https://github.com/johncms/johncms) и создайте ветку.
2. Откройте нужный `.po`-файл в любом редакторе переводов — например, [Poedit](https://poedit.net/), Lokalize, Gtranslator или обычном текстовом редакторе. Специализированные программы удобнее: они показывают исходную фразу, перевод и формы множественного числа.
3. Переведите фразы и сохраните файл.
4. Отправьте пулреквест с изменёнными `.po`-файлами.

Важные моменты:

* **Редактируйте только `.po`-файлы.** Файлы `.lng.php` генерируются из `.po` автоматически (командой `composer translate`), а `.pot` — шаблоны, которые создаются из исходного кода. Их править вручную не нужно: если в пулреквесте будут только `.po`, словари `.lng.php` пересоберут разработчики.
* Не меняйте исходные фразы (`msgid`) — переводится только `msgstr`.
* Сохраняйте плейсхолдеры (`%s`, `%d`) и HTML-теги из оригинала — без них система не сможет подставить значения.
* После принятия пулреквеста разработчики загружают перевод в Crowdin вручную, так что конфликтов между способами не возникает.

## Добавление нового языка

Если нужного языка ещё нет ни в Crowdin, ни в репозитории, свяжитесь с разработчиками любым удобным способом (например: <info@johncms.com>) и напишите название языка — мы его добавим.

Обращаем Ваше внимание, что для того, чтобы перевод был включен в дистрибутив, он должен охватывать как минимум 50% фраз.


# Исправление ошибок в переводе

Часто в переводах можно встретить ошибки. Перевод выполняют разные люди и уровень грамотности у всех разный. Если вы заметили ошибку в переводе, вы можете внести свой вклад и исправить её.

Исправить ошибку можно двумя способами:

* **Через Crowdin** — прямо в браузере, как описано ниже.
* **Через пулреквест на GitHub** — отредактируйте нужный `.po`-файл (`system/locale/<язык>.po` или `modules/<модуль>/locale/<язык>.po`) и отправьте пулреквест. Подробнее — на странице [Перевод JohnCMS на другие языки](/10.0/multiyazychnost/perevod-johncms-na-drugie-yazyki).

Для исправления через Crowdin откройте <https://translate.johncms.com/> и авторизуйтесь и выберите нужный язык.\
После этого откроется окно с выбором модуля.

![Список модулей для перевода](/files/-MVg9rGwjnr6Z4IVSPEF)

Выберите из списка модуль в котором вы заметили ошибку. После выбора модуля, в левой части страницы отобразится список фраз на английском языке.

![Список фраз для перевода](/files/-MV1WHkg75By0Gn5RF32)

Над списком фраз есть строка поиска. Она позволяет искать нужную фразу как на английском языке, так и на языке, на который вы переводите.\
Введите нужную вам фразу и выберите из списка результатов если их несколько.

В центральной части страницы вы увидите её подтвержденный перевод если он есть и список автоматически сгенерированных переводов. Введите перевод или выберите готовый и нажмите Сохранить (Save).

После этих действий администратор должен подтвердить ваш перевод. После подтверждения ваш перевод будет включен в дистрибутив JohnCMS и/или будет выпущено обновление языкового пакета.


# Настройки подключения к базе данных

JohnCMS как и многие другие системы для работы использует базу данных.\
Когда вы устанавливаете систему, создается файл **config/autoload/database.local.php** в этом файле хранятся настройки подключения к базе данных.

На данный момент по умолчанию он выглядит так:

```php
array (
    'db_host' => 'localhost',
    'db_name' => 'johncms',
    'db_user' => 'database_user',
    'db_pass' => 'password',
  ),
);
```

Это минимально необходимый список параметров для работы системы. В некоторых случаях может понадобиться задать дополнительные параметры, такие как порт и драйвер.\
На данный момент максимально полный файл конфигурации подключения выглядит так:

```php
array (
    'db_driver' => 'mysql',
    'db_host' => 'localhost',
    'db_name' => 'johncms',
    'db_user' => 'database_user',
    'db_pass' => 'password',
    'db_port' => '3306',
  ),
);
```

В параметре db\_port указывается порт, который используется для подключения к БД.\
В параметре **db\_driver** указывается драйвер для работы с базой данных.

{% hint style="info" %}
В настоящее время полностью поддерживается работа с **MySQL 5.6.4** и выше.
{% endhint %}


# Выполнение запросов к базе данных

На данный момент в JohnCMS доступны несколько вариантов выполнения запросов к базе данных.

## **PDO**

Этот вариант многим известен и применяется ещё с JohnCMS 7.0.\
Давайте рассмотрим особенности использования этого варианта.\
Чтобы получить объект PDO нам достаточно написать следующий код:

```php
$db = di(PDO::class);
```

Далее используя объект **$db** вы можете выполнять запросы к базе данных.\
Рассмотрим пример, который получает записи из таблицы users:

```php
$db = di(PDO::class);
$req = $db->query('SELECT * FROM `users`');
while ($row = $req->fetch()) {
    echo $row['name'] .'
';
}
```

Этот пример выведет список имен пользователей, которые есть в таблице users.

{% hint style="danger" %}
**Обратите внимание**\
При работе с этим вариантом вы должны самостоятельно заботиться о безопасности запросов.
{% endhint %}

## **Конструктор запросов (Query Builder)**

В JohnCMS для работы с БД используется библиотека [illuminate/database](https://github.com/illuminate/database) которая и предоставляет конструктор запросов и ORM.\
Рассмотрим несколько основных примеров чтобы понять особенности работы с библиотекой в JohnCMS.

Для выполнения запросов, сначала нам необходимо получить объект текущего подключения к БД:

```php
$connection = \Illuminate\Database\Capsule\Manager::connection();
```

\
Далее давайте выполним тот же запрос, который выполняли в обычном PDO варианте выше.

```php
$users = $connection->table('users')->get();
foreach ($users as $user) {
    echo $user->name . '<br>';
}
```

Этот запрос так же как и в предыдущем варианте выведет список имен пользователей, которые есть в таблице users.\
Метод **get** возвращает объект **Illuminate\Support\Collection** c результатами, в котором каждый результат — это экземпляр PHP-класса **StdClass**. Вы можете получить значение каждого столбца, обращаясь к столбцу как к свойству объекта.\
Давайте рассмотрим вариант получения одной строки из таблицы.

```php
$user = $connection->table('users')->where('name', 'admin')->first();
echo $user->name;
```

Этот запрос вернет пользователя, у которого поле **name** равно **admin**.

Рассмотрим вариант вывода записей из таблицы с разбивкой на страницы по 5 элементов:

```php
$user = $connection->table('users')->paginate(5);
foreach ($user as $item) {
    echo $item->name;
}
echo $user->render(); 
```

При вызове метода **paginate** будет автоматически установлены ограничения для запроса и построен запрос количества элементов в таблице по указанному вами запросу.\
Т.е. при таком вызове вам не нужно заботиться об указании **limit** для запроса и не нужно строить запрос на количество записей, конструктор запросов сделает это за вас.\
При вызове метода render из нашего объекта, будет отрисована постраничная навигация.\
URL адреса будут построены исходя из текущей страницы. Вам так же не нужно заботиться об их формировании.\
Шаблон вывода постраничной навигации расположен тут:\
**themes/default/templates/system/app/model\_paginator.phtml**

### **Выборка только необходимых столбцов**

Иногда вам может понадобиться выбрать только определенные столбцы из таблицы в базе данных.\
Сделать это можно так:

```php
$user = $connection->table('users')->select(['name', 'id'])->get();
foreach ($user as $item) {
    echo $item->id . ' - ' . $item->name;
}
```

Для выборки конкретных столбцов используется метод select(). Он принимает названия столбцов в виде массива или просто списком аргументов. Например: **select('name', 'id')**

### **Сортировка результата выборки**

Часто есть необходимость отсортировать результат выборки по какому-либо столбцу.

```php
$user = $connection->table('users')
    ->select('name', 'id')
    ->orderBy('id')
    ->orderByDesc('name')
    ->get();
foreach ($user as $item) {
    echo $item->id . ' - ' . $item->name;
}
```

В этом примере результат будет отсортирован по **id** и **name**. Метод **orderBy** вторым аргументом принимает направление сортировки **asc** или **desc**. По умолчанию **asc**. Метод **orderByDesc** это то же самое, что и **orderBy('id', 'desc')**\
Как видно из примера, сортировать можно по нескольким колонкам. Указанный выше пример выполнит следующий запрос к базе данных:

```sql
select `name`, `id` from `users` order by `id` asc, `name` desc
```

Мы рассмотрели общий принцип построения запросов.\
Если вы хотите ознакомиться подробно с конструктором запросов, вы можете это сделать [здесь](https://laravel.com/docs/7.x/queries) или на русском: [здесь](https://laravel.su/docs/5.4/queries)\
Обратите внимание, что для выполнения запросов нужно использовать объект **$connection,** а не **DB::**. В остальном все возможности, которые описаны по ссылкам, будут работать и в JohnCMS.


# Вставка записей (insert)

Конструктор запросов позволяет вставлять записи в базу данных. При этом конструктор избавляет вас от необходимости писать SQL запросы самостоятельно. Вы просто используете объектно ориентированные возможности PHP. А если вы используете IDE, то это существенно упростит вам жизнь благодаря автодополнению кода.

Для работы с базой данных нужно получить объект подключения к базе данных. Это можно сделать следующим образом:

```php
$connection = \Illuminate\Database\Capsule\Manager::connection();
```

Далее рассмотрим примеры вставки данных в таблицу в базе данных. В примере будет рассматриваться таблица со следующей структурой:

![Структура таблицы test\_table](/files/-MVfvjxpMy77bFECuF_8)

## Вставка строки

Рассмотрим пример обычной вставки строки в таблицу **test\_table**.

```php
$connection->table('test_table')->insert(
    [
        'name' => 'test name',
        'text' => 'text text text',
    ]
);
```

Этот пример кода вставит строку в базу данных. Как видите всё достаточно просто. В метод **table** подается название таблицы с которой работаем, а далее вызывается метод **insert** в который подается ассоциативный массив в котором ключем является название колонки в таблице **test\_table**, а значением является значение, которое будет вставлено.

## Вставка строки и получение идентификатора вставленной записи

В примере выше мы рассмотрели обычную вставку строки в базу данных. Но часто нам нужно вдобавок к этому получить идентификатор вставленной записи. Давайте сделаем это.

```php
$id = $connection->table('test_table')->insertGetId(
    [
        'name' => 'test name',
        'text' => 'text text text',
    ]
);
```

Как вы видите, вместо метода **insert** использовался метод **insertGetId**, а результат присваивается переменной **$id**. После выполнения этого кода в переменной **$id** будет содержаться идентификатор вставленной записи.

## Вставка нескольких строк в таблицу

Иногда есть необходимость вставить сразу много строк в таблицу в базе данных. Конструктор запросов позволяет сделать и это.

```php
$connection->table('test_table')->insert(
    [
        [
            'name' => 'test name 1',
            'text' => 'text text text 1',
        ],
        [
            'name' => 'test name 2',
            'text' => 'text text text 2',
        ],
        [
            'name' => 'test name 3',
            'text' => 'text text text 3',
        ],
    ]
);
```

Этот пример кода вставит в таблицу **test\_table** сразу 3 строки. В массиве, который передается в метод **insert** должны передаваться массивы с записями, которые необходимо вставить.

## Вставка с игнорированием ошибок

Иногда необходимо вставить запись в таблицу, но при этом проигнорировать записи с уже существующими уникальными значениями столбцов. Рассмотрим пример кода чтобы лучше понять.

```php
$connection->table('test_table')->insertOrIgnore(
    [
        [
            'id'   => 1,
            'name' => 'test name 1',
            'text' => 'text text text 1',
        ],
        [
            'id'   => 2,
            'name' => 'test name 2',
            'text' => 'text text text 2',
        ],
        [
            'id'   => 3,
            'name' => 'test name 3',
            'text' => 'text text text 3',
        ],
    ]
);
```

В таблице **test\_table** есть колонка **id**. Это первичный ключ и он должен быть уникальным. Если мы попытаемся вставить строку с существующим **id** обычным методом **insert**, то мы получим ошибку. Метод **insertOrIgnore** вставит 3 строки в таблицу только в том случае, если в ней нет строк с такими же идентификаторами. Если в таблице есть строки с id = 1, но нет строк с идентификаторами 2 и 3, то вставятся только строки с идентификаторами 2 и 3, а первая строка будет проигнорирована.

{% hint style="info" %}
Обратите внимание, что вам не нужно заботиться о защите от SQL инъекций. При выполнении запросов в конструкторе используются подготовленные запросы, благодаря чему выполнение запросов становится безопасным. Но учтите, что это не избавляет вас от необходимости делать данные безопасными при выводе из базы данных там где это необходимо.
{% endhint %}


# Обновление записей (update)

Помимо вставки и выборки данных конструктор запросов так же позволяет и обновлять данные в таблицах.\
Так же как и в остальных случаях работы с базой данных нам необходимо получить объект подключения к базе данных.

```php
$connection = \Illuminate\Database\Capsule\Manager::connection();
```

В примерах ниже мы так же будем работать с таблицей test\_table, структуру которой вы можете посмотреть в предыдущей статье [Вставка записей (insert)](https://johncms.com/documentation/db-insert/)

## Обновление строки в БД

Рассмотрим пример обновления записи в БД. Так же как и метод **insert** метод **update** принимает пару **название\_колонки => значение**.

```php
$connection->table('test_table')
    ->where('id', '=', 1)
    ->update(
        [
            'name' => 'test name 1',
            'text' => 'text text text 1',
        ]
    );
```

Указанный пример обновит строку с идентификатором 1 в таблице **test\_table** и установит значения столбцов, переданные в методе **update**. Обратите внимание, что мы ещё добавили вызов метода **where**, который устанавливает условие выборки. Для уточнения выборки может вызываться так же несколько методов **where** чтобы задать точное условие выборки записей, которые нужно обновить.

## Обновление или вставка

Часто встречается ситуация, когда нам нужно обновить запись в базе данных если она уже есть или же вставить если её нет. Обычно это делается вручную. Проверяется наличие записи в БД, и в зависимости от этого вызываются методы на вставку или обновление записи. Это не всегда удобно и заставляет писать много кода.\
Конструктор позволяет упростить выполнение этой операции. По факту он делает то же самое, но для выполнения этих действий вам не нужно вручную писать выборку, проверку и вставку или обновление.

Рассмотрим на примере:

```php
$connection->table('test_table')
    ->updateOrInsert(
        [
            'name' => 'test',
        ],
        [
            'text' => 'text text text 1',
        ]
    );
```

В этом примере будет выполнен поиск строки с полем **name** в котором содержится значение **test** и если эта запись уже существует, в ней будет обновлено поле **text**. Если такой строки в БД найдено не будет, то она будет вставлена с обоими значениями.\
Подытожим. Метод **updateOrInsert** принимает 2 массива. В первом аргументе принимается массив с условиями, которые будет выполнен поиск записи, а во втором будут значения, которые будут установлены. Если записи не существует, то будет вставлена новая запись со значениями из обоих массивов.

{% hint style="info" %}
Обратите внимание, что вам не нужно заботиться о защите от SQL инъекций. При выполнении запросов в конструкторе используются подготовленные запросы, благодаря чему выполнение запросов становится безопасным. Но учтите, что это не избавляет вас от необходимости делать данные безопасными при выводе из базы данных там где это необходимо.
{% endhint %}


# Удаление записей (delete)

Конструктор запросов так же позволяет удалять данные из таблиц в базе данных. Так же как и в остальных случаях работы с базой данных нам необходимо получить объект подключения к базе данных.

```php
$connection = \Illuminate\Database\Capsule\Manager::connection();
```

В примерах ниже мы так же будем работать с таблицей test\_table, структуру которой вы можете посмотреть в статье [Вставка записей (insert)](https://johncms.com/documentation/db-insert/)

Вы можете удалить все записи из таблицы следующим образом:

```php
$connection->table('test_table')->delete();
```

Этот пример кода удалит все записи из таблицы test\_table. Обратите внимание, значение автоинкремента не изменяется при таком подходе, по этому идентификаторы будут генерироваться не с нуля, а продолжат с того же номера на котором остановились.

Так же вы можете удалить запись с определенным идентификатором (если в таблице есть колонка id). Для этого в метод delete() передайте идентификатор строки, которую хотите удалить.

```php
$connection->table('test_table')->delete(10);
```

Конструктор поддерживает установку дополнительных условий для удаления. В следующем примере удалятся все записи у которых идентификатор будет меньше чем 15

```php
$connection->table('test_table')->where('id', '<', 15)->delete();
```

А этот пример удалит все записи с именем test

```php
$connection->table('test_table')->where('name', '=', 'test')->delete();
```

Иногда нужно очистить таблицу полностью и сбросить значение автоинкремента. Сделать это можно следующим образом:

```php
$connection->table('test_table')->truncate();
```


# Общие сведения и начало работы

## Введение

Система объектно-реляционного отображения (ORM) Eloquent — простая реализация шаблона ActiveRecord для работы с базами данных. Каждая таблица имеет соответствующий класс-модель, который используется для работы с этой таблицей. Модели позволяют запрашивать данные из таблиц, а также вставлять, обновлять и удалять в них записи.

## Определение моделей

Для начала создадим модель Eloquent. Все модели Eloquent наследуют класс **Illuminate\Database\Eloquent\Model**.\
Допустим мы делаем модуль блогов. Создадим базовую структуру модуля как [описано здесь](https://johncms.com/documentation/create_module/). Модуль назовём **blog.**\
Теперь давайте создадим в нем папку **lib** в которой будем хранить классы нашего модуля и в этой папке создадим подпапку **models** в которой уже будем размещать наши модели.\
В итоге должен получиться такой путь: **lib/models**\
Теперь настроим автозагрузку классов из папки lib. Для этого в файле index.php нашего модуля поместим следующий код:

```php
$loader = new Aura\Autoload\Loader();
$loader->register();
$loader->addPrefix('Blog', __DIR__ . '/lib');
```

С помощью метода **addPrefix** первым параметром мы указываем пространство имен (namespace) **Blog** и указываем папку в которой располагаются классы для этого пространства имен.\
Создайте таблицу posts с примерно таким набором полей:

* id
* user\_id
* name
* text
* created\_at
* updated\_at

Пример запроса на создание таблицы:

```sql
CREATE TABLE `posts`
(
    `id`         INT          NOT NULL AUTO_INCREMENT,
    `user_id`    INT          NOT NULL,
    `name`       VARCHAR(255) NOT NULL,
    `text`       LONGTEXT     NULL DEFAULT NULL,
    `created_at` TIMESTAMP    NULL DEFAULT NULL,
    `updated_at` TIMESTAMP    NULL DEFAULT NULL,
    PRIMARY KEY (`id`),
    INDEX `user_id` (`user_id`)
) ENGINE = InnoDB;
```

Теперь создадим модель.\
В папке **lib/models** создайте файл **Post.php** со следующим содержимым

{% code title="lib/models/Post.php" %}

```php
<?php

namespace Blog\Models;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;

/**
 * @mixin Builder
 */
class Post extends Model
{

}
```

{% endcode %}

На этом модель готова и она уже работоспособна.

### Имена таблиц

Заметьте, что мы не указали, какую таблицу Eloquent должен привязать к нашей модели. Если это имя не указано явно, то в соответствии с принятым соглашением будет использовано имя класса в нижнем регистре (snake case) и во множественном числе. В нашем случае Eloquent предположит, что модель Post хранит свои данные в таблице posts. Вы можете указать произвольную таблицу, определив свойство table в классе модели:

```php
<?php

namespace Blog\Models;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;

/**
 * @mixin Builder
 */
class Post extends Model
{
    /**
     * Таблица, связанная с моделью.
     *
     * @var string
     */
    protected $table = 'my_table';

}
```

### Первичные ключи

Eloquent также предполагает, что каждая таблица имеет первичный ключ с именем id. Вы можете определить свойство $primaryKey для указания другого имени.\
Вдобавок, Eloquent предполагает, что первичный ключ является инкрементным числом, и автоматически приведёт его к типу int. Если вы хотите использовать неинкрементный или нечисловой первичный ключ, задайте открытому свойству $incrementing вашей модели значение false.

### Отметки времени

По умолчанию Eloquent ожидает наличия в ваших таблицах столбцов `created_at` и `updated_at`. Если вы не хотите, чтобы они автоматически обрабатывались в Eloquent, установите свойство `$timestamps` класса модели в `false`:

```php
<?php

namespace Blog\Models;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;

/**
 * @mixin Builder
 */
class Post extends Model
{
    /**
     * Определяет необходимость отметок времени для модели.
     *
     * @var bool
     */
    public $timestamps = false;

}
```

Если вы хотите изменить формат отметок времени, задайте свойство `$dateFormat` вашей модели. Это свойство определяет, как атрибуты времени будут храниться в базе данных, а также задаёт их формат при сериализации модели в массив или JSON:

```php
<?php

namespace Blog\Models;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;

/**
 * @mixin Builder
 */
class Post extends Model
{
    /**
     * Формат хранения отметок времени модели.
     *
     * @var string
     */
    protected $dateFormat = 'U';

}
```

Если вам надо изменить имена столбцов для хранения отметок времени, вы можете задать константы `CREATED_AT` и `UPDATED_AT`:

```php
<?php

namespace Blog\Models;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;

/**
 * @mixin Builder
 */
class Post extends Model
{
    const CREATED_AT = 'creation_date';
    const UPDATED_AT = 'last_update';
}
```

### Получение моделей

После создания модели и связанной с ней таблицы, вы можете начать получать данные из вашей БД. Каждая модель Eloquent представляет собой мощный конструктор запросов, позволяющий удобно выполнять запросы к связанной таблице. Например:

```php
$post = new \Blog\Models\Post();
$all_posts = $post->all();

foreach ($all_posts as $post) {
    echo $post->name . '<br>';
}
```

Этот код выведет все записи из таблицы posts.

### Добавление дополнительных ограничений

Метод all в Eloquent возвращает все результаты из таблицы модели. Поскольку модели Eloquent работают как конструктор запросов, вы можете также добавить ограничения в запрос, а затем использовать метод get для получения результатов:

```php
$post = new \Blog\Models\Post();
$all_posts = $post->where('user_id', '=', 1)
    ->orderBy('name', 'desc')
    ->get();

foreach ($all_posts as $post) {
    echo $post->name . '<br>';
}
```

{% hint style="info" %}
Все методы, доступные в конструкторе запросов, также доступны при работе с моделями Eloquent. Вы можете использовать любой из них в запросах Eloquent.
{% endhint %}


# Поля (свойства) пользователей

Для работы с пользователями в JohnCMS используется класс **`\Johncms\Users\User()`**\
У пользователя есть различные свойства (поля).

## Основные свойства пользователя

Список основных свойств пользователя, которые есть в таблице users:

| Название поля          | Описание                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| name                   | Логин пользователя                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| name\_lat              | Логин, но в нижнем регистре, латиницей                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| password               | Хэш пароля пользователя                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| rights                 | <p>Права пользователя. Может содержать одно из следующих значений:<br><strong>0</strong> - Обычный пользователь<br><strong>3</strong> - Модератор форума<br><strong>4</strong> - Модератор загрузок<br><strong>5</strong> - Модератор библиотеки<br><strong>6</strong> - Супермодератор<br><strong>7</strong> - Администратор<br><strong>9</strong> - Супервизор</p>                                                                                                                                                                                                                                                                                   |
| failed\_login          | Количество неудачных попыток авторизации                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| imname                 | Имя                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| sex                    | <p>Пол пользователя. Содержит одно из следующих значений:<br><strong>m</strong> - Мужчина<br><strong>zh</strong> - Женщина</p>                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| komm                   | Количество комментариев                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| postforum              | Количество постов на форуме                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| postguest              | Количество постов в гостевой                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| yearofbirth            | Год рождения                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| datereg                | Дата регистрации (timestamp)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| lastdate               | Дата последнего визита (timestamp)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| mail                   | E-mail адрес                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| icq                    | ICQ (устаревшее)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| skype                  | Skype                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| jabber                 | Jabber (устаревшее)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| www                    | Сайт пользователя                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| about                  | О себе                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| live                   | Город, страна проживания                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| mibile                 | Номер телефона                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| status                 | Статус пользователя                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| ip                     | <p>IP адрес<br>В таблице хранится в преобразованном формате (ip2long).<br>Если вы получаете и записываете данные с помощью класса <strong>\Johncms\Users\User()</strong>, вам не нужно заботиться о преобразовании.<br>Вы будете видеть IP в обычном формате. Все преобразования выполняются автоматически.</p>                                                                                                                                                                                                                                                                                                                                        |
| ip\_via\_proxy         | <p>IP адрес за прокси (если удалось определить)<br>В таблице хранится в преобразованном формате (ip2long).<br>Если вы получаете и записываете данные с помощью класса <strong>\Johncms\Users\User()</strong>, вам не нужно заботиться о преобразовании.<br>Вы будете видеть IP в обычном формате. Все преобразования выполняются автоматически.</p>                                                                                                                                                                                                                                                                                                    |
| browser                | User Agent. Если используете модель **\Johncms\Users\User()**, то поле будет в безопасном для вывода виде. Дополнительно экранировать не требуется.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| preg                   | Пометка подтвержденного пользователя. Если поле запрашивается из модели, то оно будет содержать **boolean** значение (**true/false**). В таблице хранится число 0 или 1                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| regadm                 | Логин администратора, который подтвердил регистрацию пользователя                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| mailvis                | Пометка включенного отображения e-mail адреса в профиле. Если поле запрашивается из модели, то оно будет содержать **boolean** значение (**true/false**). В таблице хранится число 0 или 1                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| dayb                   | День рождения                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| monthb                 | Месяц рождения                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| sestime                | Текущее время активности пользователя (время активности сессии)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| total\_on\_site        | Сколько провёл на сайте (устаревшее и не используется).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| lastpost               | Время последнего поста (timestamp)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| rest\_code             | Код восстановления пароля                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| rest\_time             | Время восстановления пароля                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| movings                | Количество переходов по страницам в рамках текущей сессии.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| place                  | Местоположение пользователя                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| set\_user              | <p>Настройки пользователя.<br>При запросе этого поля из модели содержит объект класса <strong>Johncms\System\Users\UserConfig</strong><br>При записи через модель, принимает обычный массив и автоматически преобразует в нужный формат.<br>В таблице данные хранятся в сериализованном виде.<br><strong>Поля доступные в объекте:</strong><br><strong>directUrl</strong> - Прямые ссылки<br><strong>fieldHeight</strong> - Высота полей ввода<br><strong>kmess</strong> - Количество элементов на страницу<br><strong>lng</strong> - Выбранный язык<br><strong>timeshift</strong> - Сдвиг времени<br><strong>youtube</strong> - Youtube плеер<br></p> |
| set\_forum             | Настройки форума. Массив с настройками форума. Может быть пустым, если пользователь не сохранял настройки.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| set\_mail              | Настройки почты. Массив с настройками почты. Может быть пустым, если пользователь не сохранял настройки.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| karma\_plus            | Количество положительных голосов в карме                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| karma\_minus           | Количество отрицательных голосов в карме                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| karma\_time            | Время голосования в карме                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| karma\_off             | Запрет кармы. Если поле запрашивается из модели, то оно будет содержать **boolean** значение (**true/false**). В таблице хранится число 0 или 1                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| comm\_count            | Количество комментариев                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| comm\_old              | Устаревшее, не используется                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| smileys                | Подборка смайлов пользователя. Массив. Может быть пустым, если пользователь не добавлял смайлы в подборку.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| notification\_settings | Настройки уведомлений. Массив с настройками уведомлений.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |

\
Модель **`\Johncms\Users\User()`** в дополнение к основным полям возвращает дополнительные вычисленные поля.

## **Дополнительные свойства**

Список дополнительных свойств пользователя:

| Название поля               | Описание                                                                                                                                                                                                                                                                       |
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| is\_online                  | Метка пользователя онлайн (**true/false**)                                                                                                                                                                                                                                     |
| rights\_name                | Название прав доступа текущего пользователя (для обычных пользователей пустая строка)                                                                                                                                                                                          |
| profile\_url                | Ссылка на страницу просмотра профиля пользователя                                                                                                                                                                                                                              |
| search\_ip\_url             | Ссылка на страницу поиска по ip                                                                                                                                                                                                                                                |
| whois\_ip\_url              | Ссылка на страницу whois ip                                                                                                                                                                                                                                                    |
| search\_ip\_via\_proxy\_url | Ссылка на страницу поиска по IP за прокси                                                                                                                                                                                                                                      |
| whois\_ip\_via\_proxy\_url  | Ссылка на страницу whois IP за прокси                                                                                                                                                                                                                                          |
| ban                         | Массив активных банов пользователя                                                                                                                                                                                                                                             |
| is\_valid                   | <p>Свойство используется при работе от текущего пользователя.<br><strong>true</strong> - если пользователь авторизован и подтвержден.<br><strong>false</strong> - если пользователь не авторизован или не подтвержден.</p>                                                     |
| is\_birthday                | <p><strong>true</strong> - если у пользователя день рождения.<br><strong>false</strong> - если нет.</p>                                                                                                                                                                        |
| birthday\_date              | Т.к. дата рождения в таблице users хранится в отдельных полях, то при запросе этого свойства она собирается в одну строку.                                                                                                                                                     |
| display\_place              | Местоположение пользователя для отображения. Содержит html код ссылки на страницу.                                                                                                                                                                                             |
| formatted\_about            | Обработанное поле "О себе". bb-коды преобразованы в html код.                                                                                                                                                                                                                  |
| website                     | Обработанное поле "Сайт". bb-коды преобразованы в html код.                                                                                                                                                                                                                    |
| last\_visit                 | <p>Дата последнего визита в человекопонятном виде.<br>Обратите внимание, если пользователь сейчас онлайн, это свойство будет пустым.</p>                                                                                                                                       |
| photo                       | <p>Фотография пользователя.<br>Если фотографии нет, возвращает пустой массив.<br>Если фотография есть, возвращает массив со ссылками на фото:<br><strong>photo</strong> - Большая фотография.<br><strong>photo\_preview</strong> - Маленькая фотография для предпросмотра.</p> |


# Работа с пользователями в примерах

В предыдущей статье мы рассмотрели список [полей пользователя.](https://johncms.com/documentation/user_fields/)\
Теперь давайте рассмотрим несколько примеров получения данных.\
Во всех примерах **$user** позволяет получить доступ ко всем полям, которые описаны в [предыдущей статье](https://johncms.com/documentation/user_fields/).

Модель пользователя уже имеет некоторые предустановленные условия для выборки (заготовки запросов).\
Например для получения подтвержденных пользователей, вы можете просто вызвать метод **approved()**, а для получения пользователей, которые сейчас находятся на сайте можно вызвать метод **online()**.

**Как это работает?**\
В моделях можно создавать свои заготовки частей запросов.\
Например сейчас есть заготовка, которая вызывается методом **approved()**.\
Эта заготовка по своей сути равнозначна обычному вызову **where('preg', '=', 1)**\
Это достаточно простой вариант, но есть вариант немного сложнее.\
Например чтобы получить пользователей онлайн нам нужно ограничить выборку по времени.\
Чтобы каждый раз не писать **where('lastdate', '>', (time() - 300))** мы можем вызвать заготовку **online()**.\
Теперь предположим, что у нас есть 10 страниц, на которых выводятся различные пользователи онлайн.\
Если бы мы не использовали заготовки запросов, нам бы пришлось везде писать условие для выборки **where('lastdate', '>', (time() - 300))** и если бы мы захотели изменить время, в течение которого мы считаем пользователя онлайн, то нам бы пришлось менять его во всех 10 страницах. С заготовкой же нам достаточно изменить время в одном месте и это изменение применится для всех страниц.

**А теперь перейдем к примерам:**

Получим последних 10 зарегистрированных и подтвержденных пользователей и выведем их идентификаторы и логины:

```php
$users = (new \Johncms\Users\User())->approved()->orderBy('id', 'desc')->limit(10)->get();
foreach ($users as $user) {
    echo $user->id . ' - ' . $user->name . '<br>';
}
```

Получим 10 последних пользователей онлайн:

```php
$users = (new \Johncms\Users\User())->online()->orderBy('lastdate', 'desc')->limit(10)->get();
foreach ($users as $user) {
    echo $user->id . ' - ' . $user->name . '<br>';
}
```

Получим всех модераторов, администраторов, супервизоров и дополнительно выведем должность:

```php
$users = (new \Johncms\Users\User())->online()->where('rights', '>', 0)->orderBy('lastdate', 'desc')->get();
foreach ($users as $user) {
    echo $user->id . ' - ' . $user->name . ' -  ' . $user->rights_name . '<br>';
}
```

Получим 10 пользователей мужского пола:

```php
$users = (new \Johncms\Users\User())->where('sex', '=', 'm')->orderBy('id')->limit(10)->get();
foreach ($users as $user) {
    echo $user->id . ' - ' . $user->name . '<br>';
}
```

Получим 10 пользователей женского пола:

```php
$users = (new \Johncms\Users\User())->where('sex', '=', 'zh')->orderBy('id')->limit(10)->get();
foreach ($users as $user) {
    echo $user->id . ' - ' . $user->name . '<br>';
}
```

Получим 10 пользователей, у которых больше 100 постов на форуме:

```php
$users = (new \Johncms\Users\User())->where('postforum', '>', 100)->orderBy('id')->limit(10)->get();
foreach ($users as $user) {
    echo $user->id . ' - ' . $user->name . '<br>';
}
```

Усложним задачу и получим всех пользователей у которых больше 100 постов и разобьём выборку страницы (15 пользователей на страницу):

```php
$users = (new \Johncms\Users\User())->where('postforum', '>', 100)->orderBy('id')->paginate(15);
foreach ($users as $user) {
    echo $user->id . ' - ' . $user->name . '<br>';
}
echo $users->render();
```

Как видите, всё достаточно просто. Мы заменили **get()** на **paginate()** убрали **limit(10)** и в **paginate** передали количество пользователей, которое мы хотим видеть на одной странице.\
А дальше с помощью строки **echo $users->render();** отрисовали список страниц.

Ну и давайте рассмотрим ещё 1 пример. Получим список пользователей, у которых поле статус не пустое и так же разобьём на страницы и выведем текст статуса.

```php
$users = (new \Johncms\Users\User())->where('status', '!=', '')->orderBy('id')->paginate(15);
foreach ($users as $user) {
    echo $user->id . ' - ' . $user->name . ' - ' . $user->status . '<br>';
}
echo $users->render();
```

На этом всё, если у вас остались вопросы, задайте их на форуме.


# Работа с текущим авторизованным пользователем

Часто возникает необходимость получить данные пользователя который в данный момент находится на сайте и в зависимости от его свойств показать какую-либо информацию ему или наоборот скрыть.

Для работы с ткущим пользователем необходимо получить объект этого пользователя. Сделать это можно следующим образом:

```php
$user = di(\Johncms\Users\User::class);
```

После этого в переменной **$user** будут доступны все свойства, описанные в [этом списке](https://johncms.com/documentation/user_fields/)

### Проверка авторизации пользователя

```php
if ($user->is_valid) {
    echo 'Пользователь авторизован. Его логин: ' . $user->name;
} else {
    echo 'Пользователь не авторизован';
}
```

В этом примере если пользователь авторизован, выведется сообщение об этом и логин пользователя.

### Проверка прав доступа

```php
if ($user->rights === 9) {
    echo 'Пользователь супервизор!';
} else {
    echo 'Пользователь не супервизор';
}
```

В этом примере проверяем должность пользователя, и если пользователь супервизор, выведем ему сообщение об этом. Проверяется свойство rights и номер должности. Все номера должностей описаны в [списке свойств](https://johncms.com/documentation/user_fields/).

Мы рассмотрели несколько частых примеров работы с текущим пользователем. По аналогии с этими примерами, вы можете использовать другие свойства пользователя и выводить их значения или же в зависимости от значений выводить или скрывать какую-либо информацию.


# Введение

В данной документации мы постараемся описать все ключевые моменты, с которыми вы столкнетесь при работе с системой.&#x20;

Если документация не смогла помочь Вам в решении Вашего вопроса, Вы всегда можете обратиться за помощью на [наш форум](https://johncms.com/forum/)


# Установка и системные требования

### Системные требования

Для корректной работы JohnCMS, на хостинге, который вы используете, должно быть установлено следующее программное обеспечение

* Web сервер Apache
* PHP 7.3 и выше
* MySQL 5.6.4 и выше
* Для работы с MуSQL должен использоваться встроенный драйвер [MySQL Native Driver (mysqlnd)](https://www.php.net/manual/ru/book.mysqlnd.php)

Для работы системы требуются следующие расширения php:&#x20;

* imagick или gd
* mbstring
* pdo
* simplexml

### Установка

* Скачиваем архив
* Распаковываем в корневую папку на хостинге (обычно это папка с названием вашего сайта или public\_html)
* Переходим по адресу **ваш.сайт/install**
* Следуйте инструкциям описанным на странице установки

{% hint style="info" %}
Обязательно указывайте существующий e-mail адрес при установке т.к. он будет использоваться для отправки e-mail.
{% endhint %}

{% hint style="danger" %}
**После установки обязательно удалите папку install**
{% endhint %}


# Настройка

После установки JohnCMS перейдите в панель администратора.

1. **Выберите пункт Система > Обновить смайлы.** \
   Это обновит кэш смайлов и после этой операции смайлы в сообщениях будут работать
2. **Выберите пункт Система > Настройки языка.**\
   Далее нажмите **Обновить список** после этого выберите язык по умолчанию, на котором будет работать Ваш сайт.

{% hint style="info" %}
Далее по желанию Вы можете проверить и изменить все остальные параметры системы. Для этого просто переходите в другие разделы панели администратора и меняйте настройки так, как Вам необходимо.
{% endhint %}


# Структура файлов/папок

JohnCMS имеет следующую структуру папок:

* assets
* config
* data
* install
* modules
* system
* themes
* upload

### assets

В папке хранятся аватары (**avatars**), смайлы (**emoticons**) и некоторые системные скрипты (**modules**) для генерации картинок предпросмотра.

{% hint style="info" %}
Подпапка **modules** будет удалена в следующих версиях.
{% endhint %}

### config

В папке хранятся различные конфигурационные файлы необходимые для работы системы. \
Файл **routes.php** отвечает за настройку адресов страниц.\
Файл **constants.php** содержит константы необходимые для работы системы.\
В подпапке **autoload** хранятся файлы, которые автоматически загружаются системой. Работа с конфигурационными файлами подробно описана здесь: [Конфигурационные файлы](https://johncms.com/documentation/configs/).

### data

В папке data хранятся различные системные данные, такие как кэш и логи

### install

В папке install хранятся скрипты и прочие данные необходимые для установки системы.\
Данную папку необходимо удалять после установки JohnCMS

### modules

Папка modules содержит все модули системы\
Подробно про структуру папки модуля будет описано отдельно.

### system

Папка system содержит все системные библиотеки\
В этой папке не рекомендуется ничего менять и добавлять в целях сохранения возможности простого обновления на следующие версии JohnCMS

### themes

Папка themes содержит шаблоны сайта\
В этой папке расположен шаблон **default** в папке с этим шаблоном **не рекомендуется ничего менять** для сохранения возможности простого обновления на следующие версии JohnCMS \
Для кастомизации шаблона создайте отдельную папку и скопируйте в неё содержимое папки default.\
Более подробно про работу с шаблонами читайте в соответствующем разделе документации

### upload

Папка upload содержит файлы модулей, такие как загрузки, прикрепленные файлы форума, библиотеки, альбомы, аватары и файлы личных сообщений.


# Проблемы и их решение

Иногда при переносе сайта на другой хостинг или после каких-то изменений в коде вы можете столкнуться с ошибками. Здесь мы рассмотрим распространенные проблемы и варианты их решений.

### Ошибка 500.

Причин появления этой ошибки много. Каждую причину нужно рассматривать индивидуально. Для начала чтобы понять от чего отталкиваться нужно включить вывод ошибок.

Для включения вывода ошибок откройте файл **config/constants.php**, найдите строки﻿

```php
// Включаем режим отладки
const DEBUG = false;
```

Замените false на true

```php
const DEBUG = true;
```

После этих действий на сайте должен отображаться текст ошибки.

Если этого не произошло, нужно смотреть журнал ошибок на сервере.


# Конфигурационные файлы (configs)

Наверное Вы уже задавались вопросом "Где хранятся настройки JohnCMS и как добавлять свои настройки?". Давайте рассмотрим подробнее.

Ранее когда мы рассматривали [структуру папок](https://johncms.com/documentation/structure/), мы уже упоминали в ней папку [config](https://johncms.com/documentation/structure/#config). Теперь рассмотрим, что и за что отвечает...

Когда мы открываем папку config, то видим в ней примерно такую структуру:

![Список конфигурационных файлов в JohnCMS](/files/-MV1UQ50x-Alz-XXROng)

Файлов достаточно много, давайте разберёмся за что они отвечают.

## Файлы в директории autoload:

Директория autoload содержит все конфигурационные файлы, которые автоматически загружаются системой.\
Как вы наверное заметили есть файлы содержащие в названии **global** и **local**.\
Файлы **global** это обычно файлы, которые могут обновляться при выходе новых версий JohnCMS. Не рекомендуем их редактировать, т.к. это осложнит обновление CMS.

Файлы **local** - это локальные файлы конкретно для вашего сайта. Они не содержаться в дистрибутиве JohnCMS. Некоторые из них создаются автоматически при установке системы, а некоторые вы можете создавать вручную.

### Как же быть если вы хотите изменить какие-то параметры, которые есть в global файле?

Всё очень просто. Нужно создать файл с таким же названием, но заменить global на local.

Например, вы хотите изменить настройки в файле **mail.global.php**, для этого скопируйте этот файл и сохраните под именем **mail.local.php**. Далее измените в нем нужные параметры и они переопределят те параметры, которые уже содержатся в **mail.global.php**.

{% hint style="info" %}
Обратите внимание. При необходимости Вы можете изменить только определенные параметры, а остальные останутся стандартными.
{% endhint %}

Давайте рассмотрим пример:

### Содержимое mail.global.php

```php
return [
    'mail' => [
        // Default transport (can be sendmail, smtp, file or memory)
        'transport' => 'sendmail',

        // Transport settings
        'options'   => [
            'smtp' => [
                'name'              => 'localhost.localdomain',
                'host'              => '127.0.0.1',
                'connection_class'  => 'plain',
                'connection_config' => [
                    'username' => 'user',
                    'password' => 'pass',
                ],
            ],
            'file' => [
                'path'     => DATA_PATH . 'mail/',
                'callback' => static function (FileTransport $transport) {
                    return 'Message_' . microtime(true) . '_' . mt_rand() . '.txt';
                },
            ],
        ],
    ],
];
```

Допустим нам нужно изменить имя пользователя: username. Это можно сделать так:

### Содержимое файла mail.local.php

```php
return [
    'mail' => [
        // Transport settings
        'options'   => [
            'smtp' => [
                'connection_config' => [
                    'username' => 'my_user',
                ],
            ],
        ],
    ],
];
```

Давайте теперь получим итоговый результат.

{% hint style="info" %}
Содержимое всех конфигурационных файлов можно получить следующим образом:\
**$config = di('config');**\
Это вернет содержимое всех конфигурационных файлов из папки **config/autoload**.
{% endhint %}

Чтобы получить содержимое файла mail, выполним следующий код:

```php
d($config['mail']);
```

Это вернет следующий результат:

```php
Array
(
    [transport] => sendmail
    [options] => Array
        (
            [smtp] => Array
                (
                    [name] => localhost.localdomain
                    [host] => 127.0.0.1
                    [connection_class] => plain
                    [connection_config] => Array
                        (
                            [username] => my_user
                            [password] => pass
                        )
                )
            [file] => Array
                (
                    [path] => /Users/maksim/MyProjects/johncms_public/data/mail/
                    [callback] => Closure Object
                        (
                            [parameter] => Array
                                (
                                    [$transport] => 
                                )
                        )
                )
        )
)
```

Как видите, в итоговом результате username переопределился тем, что мы указали в файле **mail.local.php**

Вы можете самостоятельно поэкспериментировать, создать свой конфигурационный файл (global/local), а так же можете переопределить настройки из других файлов.

Для удобства можете создать файл **test.php** в корне вашего сайта со следующим содержимым:

```php
<?php

require 'system/bootstrap.php';
$config = di('config');

// Выведем содержимое конфига mail
d($config['mail']);
```

После этого в браузере перейдите по адресу site.com/test.php и увидите результат. (site.com необходимо заменить на адрес вашего сайта).

{% hint style="warning" %}
Обратите внимание.\
Хоть технически вы можете создавать конфигурационные файлы любой структуры и с любыми именами содержащими **local.php** или **global.php**, мы бы рекомендовали создавать осмысленные названия и первый элемент массива называть так же как и сам конфигурационный файл чтобы избежать путаницы и пересечения параметров.\
Например файл **my.global.php**, должен возвращать следующую структуру:\
**return \[**\
&#x20;   **'my' => \[**\
&#x20;       **'name' => 'value'**\
&#x20;   **],**\
**];**
{% endhint %}

Autoload рассмотрели, теперь кратко рассмотрим остальные файлы.

## Прочие конфигурационные файлы:

constants.php - Файл содержит различные константы. В нем вам скорее всего понадобятся константы USE\_CRON (для перевода отправки email на cron) и DEBUG для включения режима отладки при возникновении ошибок или при разработке модулей.

notifications.global.php - Этот файл содержит шаблоны уведомлений. Параметры в данном файле можно переопределить или дополнить с помощью файла notifications.local.php

places.global.php - Файл содержит информацию о местоположении пользователей. Параметры в данном файле можно переопределить или дополнить с помощью файла places.local.php

routes.php - файл для настройки маршрутизации. Подробно работу с ним мы рассматривали в этой статье: [Маршрутизация (роутинг)](https://johncms.com/documentation/routing/)


# Шаблоны электронных сообщений (email)

Начиная с JohnCMS 9.3 в системе появилась поддержка шаблонов для email.

### Для чего это нужно?

Это нужно для того, чтобы отправлять хорошо оформленные письма, которые будут иметь дизайн такой же как и ваш сайт, а так же в таких письмах можно выделять нужную информацию в отличие от обычных текстовых сообщений.

### Как это работает?

Рассмотрим пример письма:

![Пример сообщения о регистрации](/files/-MV1VL1GXa9fY-lX-Ey1)

В письмах как и на всем сайте есть основной шаблон, который является общим практически для всех страниц (header/footer. На скриншоте отмечен цифрами 1 и 3). Сам текст письма - это контентная область (на скриншоте отмечена цифрой 2), которая в разных письмах может выглядеть по разному.

Базовых шаблонов может быть несколько и каждый шаблон сообщения может использовать любой базовый шаблон.

Всё это позволит вам менять базовый шаблон не меняя все шаблоны писем. Например, вы можете сделать несколько шаблонов на все времена года, зимний, летний, весенний, осенний и менять их когда это необходимо. При этом вам нужно будет изменить всего 1 файл, а шаблоны писем изменять не придется вовсе.

### Где хранятся шаблоны?

Почтовые шаблоны так же как и основные шаблоны сайта хранятся в папке themes.

![](/files/-MV1VSYmpwxtX1X45p3g)

Основной шаблон расположен в папке **themes/default/templates/system/mail/layouts/default.phtml**

В этом файле расположен основной макет письма.

Шаблоны конкретных сообщений расположены в папке **themes/default/templates/system/mail/templates**

Шаблонная система для почтовых сообщений работает так же как и шаблоны основного сайта. Поддерживается возможность переопределения и все прочие возможности. Для кастомизации системных шаблонов копируйте их в папку с собственным шаблоном. Таким образом вам не придется переносить изменения при обновлении CMS.


# Отправка электронной почты (email)

В JohnCMS для отправки электронной почты используется библиотека [laminas-mail ](https://docs.laminas.dev/laminas-mail/)\
Она позволяет обобщить отправку сообщений и легко переключать драйверы через которые будет отправляться письмо. Благодаря этому вы сможете выбрать наиболее подходящий вам метод отправки в зависимости от возможностей вашего хостинга и наличия его ip в спам фильтрах.

### Драйверы и настройка

На данный момент поддерживаются следующие драйверы: **Sendmail, SMTP, File.** Этих драйверов обычно более чем достаточно большинству проектов.

Драйвер по умолчанию и настройки драйвера указываются в конфигурационном файле **config/autoload/mail.global.php**. По умолчанию установлен sendmail, но вы можете сменить драйвер на smtp или file. Примеры настроек есть в указанном файле. Вы можете просто их переопределить. Как это сделать, а так же про работу с конфигурационными файлами рекомендуем прочитать здесь: [Конфигурационные файлы.](https://johncms.com/documentation/configs/)

### Отправка сообщений

Отправка email достаточно затратная операция. Для решения этой проблемы отправку email можно переложить на сервер. Для этого в JohnCMS реализована очередь сообщений. Чтобы отправить письмо, необходимо просто добавить его в очередь.

**Рассмотрим пример добавления письма в очередь:**

```php
(new \Johncms\Mail\EmailMessage())->create(
    [
        'locale'   => 'ru',
        'template' => 'system::mail/templates/registration',
        'fields'   => [
            'email_to'        => 'user@example.com',
            'name_to'         => 'Имя Пользователя',
            'subject'         => 'Регистрация на сайте',
            'user_name'       => 'UserName',
            'user_login'      => 'UserLogin',
            'link_to_confirm' => 'https://johncms.com',
        ],
    ]
);
```

Что делает этот код?\
Он добавляет запись в таблицу **email\_messages**. А дальше система проверяет наличие не отправленных писем в очереди и отправляет их.

#### Какие поля необходимы?

* **priority** - Приоритет отправки сообщения. Чем меньше, тем выше. (**не обязательно**)
* **locale** - Поле обязательно и содержит код языка, на котором будет отправлено сообщение.
* **template** - содержит шаблон, который будет использоваться для формирования письма.
* **fields** - содержит массив полей, которые будут доступны в шаблоне, а так же будут использоваться для отправки:
  * **email\_to** - E-mail адрес получателя сообщения (**обязательное поле**)
  * **name\_to** - Имя получателя, которое будет отображаться в почтовом клиенте. (**не обязательно**)
  * **subject** - Тема сообщения. (**не обязательно, но рекомендуется**)
  * Прочие поля доступны только в шаблоне, не требуются для работы драйвера и могут отсутствовать.

Отправка почтовых сообщений по умолчанию выполняется на хитах. Это значит, что для отправки письма какой либо пользователь должен зайти на сайт. В момент на сайт, выполняется проверка наличия неотправленных сообщений и если таковые находятся, выполняется отправка. Этот вариант не всегда подходит, особенно если сообщений отправляется много. По этому рекомендуется перевести отправку сообщений на cron.

### Перевод отправки Email на CRON

Для того, чтобы избежать подвисания страницы для пользователей в JohnCMS реализована отправка сообщений с помощью планировщика cron. Если ваш хостинг поддерживает cron, то рекомендуем перевести отправку сообщений на него. Для этого откройте файл: **config/constants.php**\
Найдите строчку:

```php
const USE_CRON = false;
```

И замените **false** на **true**.

Далее необходимо добавить задачу в cron:

**php system/cron.php**

Периодичность выполнения установить раз в 1 минуту.\
Обратите внимание, что может потребоваться указать полный путь к файлу от корня. Посмотреть его можно в **phpinfo()**, параметр **DOCUMENT\_ROOT** или вывести так: \
**echo $\_SERVER\['DOCUMENT\_ROOT'];**\
Более подробно про то как добавить задачу, вы можете уточнить у вашего хостинг провайдера.


# Работа с уведомлениями

Как вы наверное уже знаете, в JohnCMS начиная с версии 9.2 появились улучшенные уведомления. Давайте разберемся как они работают и научимся добавлять свои уведомления.

Для работы уведомлений существует таблица в базе данных, которая называется **notifications**. Она хранит все уведомления для всех пользователей сайта.

Рассмотрим поля, которые доступны в таблице уведомлений:

| Наименование | Описание                                                                                                                                                 |
| ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| id           | Идентификатор уведомления                                                                                                                                |
| module       | Наименование модуля, который добавил уведомление. (**обязательное поле**)                                                                                |
| event\_type  | Наименование типа события, из-за которого отправоено уведомление. (**обязательное поле**)                                                                |
| user\_id     | Пользователь, для которого предназначено уведомление. (**обязательное поле**)                                                                            |
| sender\_id   | Идентификатор пользователя, который инициировал отправку уведомления. (не обязательно)                                                                   |
| entity\_id   | Идентификатор сущности к которой привязано уведомление. (например сообщение на форуме из-за которого было отправлено уведомление). Не обязательное поле. |
| fields       | Массив полей, которые будут доступны в шаблоне уведомления.                                                                                              |
| read\_at     | Время прочтения уведомления.                                                                                                                             |

### Принцип работы уведомлений:

* Какой либо модуль добавляет уведомление в систему, привязывая его к модулю, типу события и пользователю, которому предназначено это уведомление.
* Когда пользователь открывает сайт, для него выполняется выборка уведомлений у которых поле read\_at = NULL. (т.е. не прочитанные).
* После того как пользователь заходит на страницу уведомлений, ему формируется список в соответствии с заданным шаблоном, далее показанные на странице уведомления помечаются прочитанными.

### Добавление уведомлений:

Уведомления обязательно должны иметь наименование модуля, тип события и пользователя, которому они предназначены.

Рассмотрим пример добавления уведомления:

```php
(new \Johncms\Notifications\Notification())->create(
    [
        'module'     => 'my_the_best_module',
        'event_type' => 'my_module_event1',
        'user_id'    => 1,
        'sender_id'  => 1,
        'entity_id'  => null,
        'fields'     => [
            'variable' => 'Привет! Это'
        ],
    ]
);
```

Этот код добавит уведомление для модуля **my\_the\_best\_module** и события с типом **my\_module\_event1.**

Для чего же нам нужно название модуля и тип события?\
Это нужно для того, чтобы отображать уведомления в соответствии с заданным шаблоном.

### Шаблоны уведомлений:

Шаблоны уведомлений настраиваются в файле **config/notifications.local.php.** Если у вас нет этого файла, переименуйте файл **notifications.local.php.example** в **notifications.local.php**

Файл с шаблонами должен иметь следующую структуру:

```php
return [
    // Пример шаблонов уведомлений для модулей
    'my_the_best_module' => [
        'name'   => 'Мой лучший модуль!',
        'events' => [
            'my_module_event1' => [
                'name'    => 'Новое сообщение',
                'message' => 'Текст сообщения! #variable# дополнительный текст',
            ],
            'my_module_event2' => [
                'name'    => 'Новый пост',
                'message' => 'Текст уведомления! #variable# дополнительный текст',
            ],
        ],
    ],
];
```

Как мы видим, тут используется название модуля и тип события.

Давайте посмотрим как выглядит наше уведомление, которое мы добавили выше.

![Пример отображения уведомления](/files/-MV1WHkg75By0Gn5RF32)

Как видно на скриншоте, вывелось уведомление с типом **my\_module\_event1**. В тексте уведомления заменилась макропеременная **#variable#** на ту, которую мы подавали при создании уведомления в массиве **fields**.


# Работа с запросом (Request)

Для работы с данными HTTP запроса в JohnCMS используется класс **\Johncms\System\Http\Request**. Он позволяет получить доступ к таким суперглобальным переменным как: **$\_POST, $\_GET, $\_COOKIE, $\_FILES, $\_SERVER**. Это позволяет упростить получение значений по умолчанию, и фильтрацию данных, пришедших от пользователя. Давайте посмотрим на примеры.

Для начала необходимо получить объект класса Request.

```php
/** @var \Johncms\System\Http\Request $request */
$request = di(\Johncms\System\Http\Request::class);
```

Строка /\*\* @var \Johncms\System\Http\Request $request \*/ не обязательна и служит лишь для работы автодополнения в IDE если вы конечно используете IDE.

## Получение данных из $\_GET

Предположим, что пользователь открыл страницу <http://domain.com/?user\\_id=123> и нам нужно получить идентификатор пользователя 123. Сделать это можно следующим образом:

```php
$user = $request->getQuery('user_id', 0, FILTER_VALIDATE_INT);
```

Разберем что же тут происходит. Метод **getQuery** пытается получить **user\_id** из суперглобального массива **$\_GET**.\
Первым параметром принимает название параметра запроса, вторым параметром можно передать стандартное значение, а третим параметром передается фильтр, с помощью которого будет обработано значение. Вы можете ознакомиться со списком фильтров в официальной документации по этой ссылке: <https://www.php.net/manual/ru/filter.filters.php>

Коротко что делает строка из примера: Пытается получить параметр GET запроса **user\_id**, если его нет, то возвращает 0, если есть, то очищает и возвращает число. Если передано не число, то вернет значение по умолчанию, то есть 0.

## Получение данных из $\_POST

Предположим, что отправлена форма, которая содержит **user\_id** и **name**. Форма отправлена методом POST.

```php
$user = $request->getPost('user_id', 0, FILTER_VALIDATE_INT);
$name = $request->getPost('name', '', FILTER_SANITIZE_STRING);
```

Эти примеры работают так же как и предыдущий. Во втором примере от пользователя ожидается строка, а фильтр **FILTER\_SANITIZE\_STRING** удаляет из нее теги, и при необходимости удаляет или кодирует специальные символы.

## Получение данных из $\_COOKIE

```php
$name = $request->getCookie('name', '', FILTER_SANITIZE_STRING);
```

В этом примере как видите всё так же просто как и в предыдущих. Просто поменялось название метода, а принцип работы такой же.

## Получение данных из $\_SERVER

```php
$user_agent = $request->getServer('HTTP_USER_AGENT', '', FILTER_SANITIZE_STRING);
```

Этот пример работает так же как и остальные. Получает **HTTP\_USER\_AGENT** из суперглобальной переменной **$\_SERVER**.

## Получение данных из $\_FILES

```php
$files = $request->getUploadedFiles();
```

Этот код вернет массив файлов, в котором каждый элемент будет представлен объектом класса GuzzleHttp\Psr7\UploadedFile. Если вы уже работали с выгрузкой файлов в php, то наверное знаете, что множественные файлы в массиве $\_FILES описываются примерно так:

```php
array(
    'files' => array(
        'name' => array(
            0 => 'file0.txt',
            1 => 'file1.html',
        ),
        'type' => array(
            0 => 'text/plain',
            1 => 'text/html',
        ),
        /* etc. */
    ),
)
```

для работы с этим стандартными средствами вам необходимо позаботиться о сборе всех данных в нормальную структуру. Если вы используете метод **getUploadedFiles**, то эта задача уже решена для вас и массив файлов будет уже в нормальной структуре:

```php
array(
    'files' => array(
        0 => array(
            'name' => 'file0.txt',
            'type' => 'text/plain',
            /* etc. */
        ),
        1 => array(
            'name' => 'file1.html',
            'type' => 'text/html',
            /* etc. */
        ),
    ),
)
```

Давайте рассмотрим пример сохранения файлов

Допустим, у нас есть такая форма, которая принимает 1 обычный файл и поле с возможностью выбирать несколько файлов.

```markup
<form action="" method="post" enctype="multipart/form-data">
    <input type="file" name="file">
    <input type="file" name="multiple_files[]" multiple>
    <button type="submit">Отправить</button>
</form>
```

Пример сохранения файлов будет выглядеть так:

```php
$files = $request->getUploadedFiles();

// Сохраняем файл из обычного поля
if (! empty($files['file'])) {
    /** @var  $attached_file \Psr\Http\Message\UploadedFileInterface */
    $attached_file = $files['file'];
    try {
        $attached_file->moveTo(UPLOAD_PATH . '/tmp/' . $attached_file->getClientFilename());
        echo 'Файл успешно сохранен';
    } catch (\Exception $exception) {
        echo 'Ошибка сохранения файла: ' . $exception->getMessage();
    }
}

// Сохраняем файлы из множественного поля
if (! empty($files['multiple_files'])) {
    /** @var  $multiple_files \Psr\Http\Message\UploadedFileInterface[] */
    $multiple_files = $files['multiple_files'];
    foreach ($multiple_files as $multiple_file) {
        try {
            $multiple_file->moveTo(UPLOAD_PATH . '/tmp/' . $multiple_file->getClientFilename());
            echo 'Файл успешно сохранен';
        } catch (\Exception $exception) {
            echo 'Ошибка сохранения файла: ' . $exception->getMessage();
        }
    }
}
```

В результате отправки формы с файлами, все файлы будут сохранены в папке upload/tmp c оригинальными названиями, с которыми отправил клиент.

{% hint style="danger" %}
Обратите внимание, что в примере рассмотрен простой вариант сохранения файлов без каких либо проверок допустимых типов файлов.
{% endhint %}


# Валидация

## Что такое валидатор и зачем он нужен?

Разработчики модулей создавая модули часто сталкиваются с задачей валидации форм, которые отправляет пользователь.\
Например, практически в любой форме есть поля,  обязательные для заполнения. Так же есть поля, значения которых нужно проверить на наличие в базе данных, в некоторых полях может находиться файл, размер которого нам нужно проверить, ссылка, правильность которой тоже нужно проверить или же email адрес в котором, например, нужно проверить не только корректность текста до и после символа @, но и наличие MX записей для указанного домена.

Для избавления от частых рутинных операций и упрощения кода в JohnCMS встроены различные, часто используемые базовые валидаторы.

## Что позволяет делать валидатор?

Валидатор проверяет входные данные на соответствие настройкам правил валидации. Если данные не соответствуют правилам, валидатор возвращает false и так же позволяет получить информацию о том, какие именно требования не выполнены.

В JohnCMS используется [laminas-validator](https://docs.laminas.dev/laminas-validator/), большинство существующих правил, которые описаны в официальной документации будут работать и в JohnCMS, но есть правила для которых требуются дополнительные зависимости и эти правила могут не работать, но таких как правило единицы и они редко используются.

При просмотре документации по валидаторам вам может показаться, что это достаточно громоздко, но не спешите, работа с валидатором в JohnCMS сильно упрощена, к тому же реализованы некоторые дополнительные валидаторы, которые вам скорее всего пригодятся.\
Давайте рассмотрим пример и разберемся в некоторых деталях.

```php
<?php

require 'system/bootstrap.php';

// Массив полей и значений
$data = [
    'test'   => '',
    'number' => 100,
    'email'  => 'email@example.ru',
    'model'  => 110,
];

// Настройки валидатора
$rules = [
    // Название поля => [ правила валидации и их параметры ]
    'test'   => [
        'NotEmpty',
        'StringLength' => [
            'min' => 6,
            'max' => 80,
        ],
    ],
    'number' => [
        'NotEmpty',
        'LessThan' => ['max' => 90],
    ],
    'email'  => [
        'EmailAddress' => [
            'useMxCheck' => true,
        ],
    ],
    'model'  => [
        'ModelExists' => [
            'model' => \Johncms\Users\User::class,
            'field' => 'id',
        ],
    ],
];

// Валидация
$validator = new \Johncms\Validator\Validator($data, $rules);
if ($validator->isValid()) {
    echo 'OK';
} else {
    d($validator->getErrors());
}
```

Здесь массив **$data** содержит набор данных, которые будут проверяться. Часто это данные из формы, полученные методом **POST** или **GET**.

Массив **$rules** содержит набор правил и их настройку. В качестве ключа указывается название поля из массива $data, а в качестве массива со значениями используется валидатор или набор валидаторов и их настройки.\
Например в первом правиле проверяется значение поля под названием **test**, к нему применяется валидатор **NotEmpty** и **StringLength**. Валидатор **NotEmpty** проверяет не пустое ли значение в поле **test**, а валидатор **StringLength** проверяет длину значения. В данном случае длина значения должна быть от 6 до 80 символов.

Как видите, валидатор может не иметь настроек, а может иметь настройки. Если валидатор не имеет настроек или же вам подходят настройки по умолчанию, то вы можете передать только название валидатора. Если вам нужно дополнительно настроить валидатор, просто передаете массив настроек.

Многие популярные валидаторы мы рассмотрим отдельно. Пока можете попробовать выполнить код выше.\
Для этого в корне вашего сайта создайте файл **test.php** и вставьте в него этот код. После этого откройте в браузере страницу **site.ru/test.php.** Вы увидите следующий результат:

```php
Array
(
    [test] => Array
        (
            [isEmpty] => Поле является обязательным и не может быть пустым
        )

    [number] => Array
        (
            [notLessThan] => The input is not less than '90'
        )

    [email] => Array
        (
            [emailAddressInvalidMxRecord] => 'example.ru' Похоже, что записи MX или A для адреса электронной почты не действительны
        )

    [model] => Array
        (
            [modelNotFound] => Нет записей, соответствующих введенным данным
        )

)
```

Как видно из результата, массив $data не прошел проверку. Валидатор вернул массив полей и правила валидации, которые не прошли проверку. Вы можете изменить в нем значения и понаблюдать за результатом, а так же поэкспериментировать с другими правилами.


# NotEmpty - Не пустое значение

Этот валидатор позволяет вам проверить, является ли данное значение не пустым. Это часто полезно при работе с элементами формы или другим пользовательским вводом. Вы можете использовать этот валидатор, чтобы убедиться, что пользователь заполнил поле и оно не пустое.

По умолчанию этот валидатор работает иначе, чем вы ожидаете, работая с PHP функцией empty(). В частности, этот валидатор будет оценивать как целое число 0, так и строку «0» как пустые.

Вам может не подойти это поведение и, например, в вашем случае 0 не должен считаться пустым. Для таких случаев в валидаторе NotEmpty вы можете задать некоторые настройки.

### Поддерживаемые параметры

* **type**: Устанавливает тип проверки, которая будет выполнена.

### Обрабатываемые типы

* **boolean**: Возвращает false, когда логическое значение равно false.
* **integer**: Возвращает false, когда задано целое число 0. По умолчанию эта проверка не активирована и возвращает true для любых целочисленных значений.
* **float**: Возвращает false, когда задано значение с плавающей запятой 0.0. По умолчанию эта проверка не активирована и возвращает true для любых значений с плавающей запятой.
* **string**: Возвращает false, когда задана пустая строка.
* **zero**: Возвращает false, когда задан один символ ноль ('0').
* **empty\_array**: Возвращает false, когда задан пустой массив.
* **null**: Возвращает false, когда задано значение null.
* **php**: Возвращает false везде, где PHP empty () возвращает true.
* **space**: Возвращает false, если задана строка, содержащая только пробел.
* **object**: Возвращает true. false будет возвращено, когда объект не разрешен, но объект задан.
* **object\_string**: Возвращает false, когда объект задан, а его метод \_\_toString () возвращает пустую строку.
* **object\_count**: Возвращает false, когда объект задан, он реализует Countable, и его количество равно 0.
* **all**: Возвращает false для всех вышеперечисленных типов.

Рассмотрим пример как передавать эти параметры в валидатор.

### Примеры использования

```php
// Массив полей и значений
$data = [
    'test' => 0,
];

// Настройки валидатора
$rules = [
    'test' => [
        'NotEmpty' => [
            'type' => [
                'integer',
                'zero',
            ],
        ],
    ],
];

// Валидация
$validator = new \Johncms\Validator\Validator($data, $rules);
if ($validator->isValid()) {
    echo 'OK';
} else {
    d($validator->getErrors());
}
```

Как видите, для валидатора **NotEmpty** задан массив настроек, в нем передается параметр **type** со значениями из списка выше (обрабатываемые типы).


# StringLength - длина строки

Валидатор StringLength позволяет проверить находится ли длина строки в диапазоне заданных значений или нет.

По умолчанию этот валидатор проверяет, находится ли значение между min и max, используя минимальное значение по умолчанию, равное 0, и максимальное значение по умолчанию, равное NULL (то есть неограниченное).\
Таким образом, без каких-либо опций, валидатор только проверяет, что ввод является строкой.

## Поддерживаемые параметры

* **encoding**: Устанавливает кодировку ICONV в которой будет проверяться строка.
* **min**: Устанавливает минимально допустимую длину строки.
* **max**: Устанавливает максимально допустимую длину для строки.

## Примеры использования

```php
// Массив полей и значений
$data = [
    'test' => 'Строка',
];

// Настройки валидатора
$rules = [
    'test' => [
        'StringLength' => [
            'min'      => 3,
            'max'      => 60,
            'encoding' => 'UTF-8',
        ],
    ],
];

// Валидация
$validator = new \Johncms\Validator\Validator($data, $rules);
if ($validator->isValid()) {
    echo 'OK';
} else {
    d($validator->getErrors());
}
```

В указанном примере валидатор проверит строку в кодировке UTF-8 на длину от 3 до 60 символов.

### Проверка только минимальной длины:

```php
// Массив полей и значений
$data = [
    'test' => 'Строка',
];

// Настройки валидатора
$rules = [
    'test' => [
        'StringLength' => [
            'min' => 3
        ],
    ],
];

// Валидация
$validator = new \Johncms\Validator\Validator($data, $rules);
if ($validator->isValid()) {
    echo 'OK';
} else {
    d($validator->getErrors());
}
```

В этом примере будет проверяться только минимальная длина строки (3 символа). Максимальная будет считаться не ограниченной.

### Проверка только максимальной длины:

```php
// Массив полей и значений
$data = [
    'test' => 'Строка',
];

// Настройки валидатора
$rules = [
    'test' => [
        'StringLength' => [
            'max' => 50
        ],
    ],
];

// Валидация
$validator = new \Johncms\Validator\Validator($data, $rules);
if ($validator->isValid()) {
    echo 'OK';
} else {
    d($validator->getErrors());
}
```

В этом же примере будет проверяться только максимальная длина строки. Если строка будет длиннее 50 символов, проверка не пройдет, если менее 50 символов, то проверка пройдет.

### Строгое ограничение длины строки:

```php
// Массив полей и значений
$data = [
    'test' => 'Строка',
];

// Настройки валидатора
$rules = [
    'test' => [
        'StringLength' => [
            'max' => 6,
            'min' => 6,
        ],
    ],
];

// Валидация
$validator = new \Johncms\Validator\Validator($data, $rules);
if ($validator->isValid()) {
    echo 'OK';
} else {
    d($validator->getErrors());
}
```

А в этом примере мы строго ограничили длину строки 6 символами. Т.е. проверка пройдет только если строка будет длиной в 6 символов. Больше или меньше не допускается.


# LessThan - Менее чем

Валидатор LessThan позволяет проверить число на предмет того, что оно меньше чем заданное в параметре.\
Обратите внимание, что данный валидатор работает только с числами. Строки или даты этот валидатор не позволяет проверять.

### Поддерживаемые параметры

* **inclusive**: Включая максимальное значение. Если задано **true**, то значение равное максимальное значение будет проходить валидацию. Если задано **false**, то значение равное максимальному значению не будет проходить валидацию.
* **max**: Устанавливает максимальное значение.

### Примеры использования

```php
// Массив полей и значений
$data = [
    'test' => 60,
];

// Настройки валидатора
$rules = [
    'test' => [
        'LessThan' => [
            'max'       => 60,
            'inclusive' => true,
        ],
    ],
];

// Валидация
$validator = new \Johncms\Validator\Validator($data, $rules);
if ($validator->isValid()) {
    echo 'OK';
} else {
    d($validator->getErrors());
}
```

Этот пример выведет "OK", т.к. включен параметр inclusive и значение равно максимальному.

```php
// Массив полей и значений
$data = [
    'test' => 60,
];

// Настройки валидатора
$rules = [
    'test' => [
        'LessThan' => [
            'max'       => 60,
            'inclusive' => false,
        ],
    ],
];

// Валидация
$validator = new \Johncms\Validator\Validator($data, $rules);
if ($validator->isValid()) {
    echo 'OK';
} else {
    d($validator->getErrors());
}
```

А этот пример выведет ошибку т.к. параметр inclusive имеет значение false т.к. этот параметр исключает максимальное значение.


# EmailAddress - Проверка email адреса

Валидатор EmailAddress позволяет выполнить различные проверки email адреса.\
Валидатор сначала разбивает адрес электронной почты на local-part\@hostname и пытается сопоставить их с известными спецификациями для адресов электронной почты и имен хостов.

### Поддерживаемые параметры

* **allow**: Определяет, какой тип доменных имен принимает валидатор. Эта опция используется вместе с опцией hostnameValidator для установки валидатора имени хоста. Возможные значения этой опции определены в константах ALLOW\_ \* валидатора Hostname:
  * **ALLOW\_DNS**: (по умолчанию) Разрешает доменные имена (например example.com)
  * **ALLOW\_IP**: Разрешает IP адреса.
  * **ALLOW\_LOCAL**: Разрешает локальные домены такие как localhost или [www.localdomain](http://www.localdomain)
  * **ALLOW\_URI**: Разрешает имена хостов в универсальном синтаксисе URI. См. [RFC 3986](https://www.ietf.org/rfc/rfc3986.txt)
  * **ALLOW\_ALL**: Разрешить все типы хостов.
* **useDeepMxCheck**: Указывает валидатору на необходимость усиленной проверки MX записей домена. Если для этого параметра установлено значение true, то в дополнение к записям MX также используются записи A, A6 и AAAA для проверки того, принимает ли сервер электронную почту. Эта опция по умолчанию имеет значение false.
* **useDomainCheck**: Определяет, должна ли быть проверена часть домена. Если для этого параметра установлено значение false, будет проверяться только локальная часть адреса электронной почты. В этом случае валидатор имени хоста не будет вызван. Эта опция по умолчанию имеет значение true.
* **hostnameValidator**: Задает экземпляр объекта валидатора имени хоста, с помощью которого будет проверяться доменная часть адреса электронной почты.
* **useMxCheck**: Определяет, должны ли быть обнаружены записи MX с сервера. Если для этого параметра задано значение true, то MX-записи используются для проверки того, принимает ли сервер электронную почту или нет. Эта опция по умолчанию имеет значение false.

### Пример использования

Рассмотрим наиболее распространенный пример, которого скорее всего вам будет достаточно. Этот пример проверяет существование домена и возможность принимать email. Т.е. выполняется максимально возможная проверка. Она пропустит только точно существующий домен с MX записями.

```php
// Массив полей и значений
$data = [
    'test' => 'info@johncms.com',
];

// Настройки валидатора
$rules = [
    'test' => [
        'EmailAddress'   => [
            'allow'          => Laminas\Validator\Hostname::ALLOW_DNS,
            'useMxCheck'     => true,
            'useDeepMxCheck' => true,
        ],
    ],
];

// Валидация
$validator = new \Johncms\Validator\Validator($data, $rules);
if ($validator->isValid()) {
    echo 'OK';
} else {
    d($validator->getErrors());
}
```


# ModelExists - Проверка существования записи в БД

Валидатор **ModelExists** позволяет проверить существование записи в базе данных. Это хорошо подходит для тех случаев, когда у вас в форме есть привязка к каким-то существующим записям в базе данных.

Для работы этого валидатора вам потребуется существующая [модель](https://johncms.com/documentation/eloquent-orm/).&#x20;

### Поддерживаемые параметры

* **model**: Класс модели, который будет использоваться для построения запроса к БД.
* **field**: Столбец в БД по которому будет осуществляться поиск записи.

### Примеры использования

```php
// Массив полей и значений
$data = [
    'test' => 45,
];

// Настройки валидатора
$rules = [
    'test' => [
        'ModelExists'   => [
            'model' => \Johncms\Users\User::class,
            'field' => 'id',
        ],
    ],
];

// Валидация
$validator = new \Johncms\Validator\Validator($data, $rules);
if ($validator->isValid()) {
    echo 'OK';
} else {
    d($validator->getErrors());
}
```

В указанном примере будет выполнена проверка наличия пользователя c идентификатором 45 в таблице users.

Запрос который будет выполнен:

```sql
SELECT * FROM `users` WHERE `id` = 45
```

В результате, если будет найдена запись с id = 45, то валидатор будет считать проверку успешной, если не найдет, то вернёт ошибку.

Рассмотрим ещё один пример:

```php
// Массив полей и значений
$data = [
    'test' => 'admin',
];

// Настройки валидатора
$rules = [
    'test' => [
        'ModelExists'   => [
            'model' => \Johncms\Users\User::class,
            'field' => 'name',
        ],
    ],
];

// Валидация
$validator = new \Johncms\Validator\Validator($data, $rules);
if ($validator->isValid()) {
    echo 'OK';
} else {
    d($validator->getErrors());
}
```

В этом примере будет выполнен поиск записи у которой поле name = admin.

Будет выполнен следующий запрос:

```sql
SELECT * FROM `users` WHERE `name` = 'admin'
```

Результат будет такой же как и в случае с id. Если будет найдена строка с полем name = admin, то валидация пройдет успешно, если нет, будет возвращена ошибка.


# ModelNotExists - Проверка отсутствия записи в БД

Валидатор **ModelNotExists** позволяет проверить отсутствие записи в базе данных. Это подойдет для тех случаев, когда вам нужно проверить отсутствие записи в таблице прежде чем её добавить. Например, с помощью этого валидатора, в форме регистрации пользователя вы можете проверить существует ли пользователь с введенным логином или нет.

Для работы этого валидатора вам потребуется существующая [модель](https://johncms.com/documentation/eloquent-orm/).&#x20;

## Поддерживаемые параметры

* **model**: Класс модели, который будет использоваться для построения запроса к БД.
* **field**: Столбец в БД по которому будет осуществляться поиск записи.
* **exclude**: Параметры для задания условий исключения из выборки. Может содержать анонимную функцию или массив с полями **field** и **value**.

## Примеры использования

```php
// Массив полей и значений
$data = [
    'test' => 'admin@admin.ru',
];

// Настройки валидатора
$rules = [
    'test' => [
        'ModelNotExists' => [
            'model'   => \Johncms\Users\User::class,
            'field'   => 'mail',
            'exclude' => static function ($query) {
                return $query->where('name', '!=', 'admin')->where('id', '!=', 1);
            },
        ],
    ],
];

// Валидация
$validator = new \Johncms\Validator\Validator($data, $rules);
if ($validator->isValid()) {
    echo 'OK';
} else {
    d($validator->getErrors());
}
```

В примере выше выполняется проверка наличия в таблице users пользователя с полем mail, содержащим <admin@admin.ru>. При этом из выборки исключаются строки с name = admin и id = 1. Для расширения запроса на выборку используется анонимная функция. Она позволяет дополнять запрос любыми условиями.\
Валидатор выполнит следующий запрос:

```sql
select * from `users` where (`name` != 'admin' and `id` != 1) and `mail` = 'admin@admin.ru' limit 1
```

Рассмотрим более простой пример, где в параметр **exclude** передается массив:

```php
// Массив полей и значений
$data = [
    'test' => 'admin@admin.ru',
];

// Настройки валидатора
$rules = [
    'test' => [
        'ModelNotExists' => [
            'model'   => \Johncms\Users\User::class,
            'field'   => 'mail',
            'exclude' => [
                'field' => 'name',
                'value' => 'admin',
            ],
        ],
    ],
];

// Валидация
$validator = new \Johncms\Validator\Validator($data, $rules);
if ($validator->isValid()) {
    echo 'OK';
} else {
    d($validator->getErrors());
}
```

Если вам не требуется сложное условие для исключения записей из выборки, то вы можете использовать такой вариант задания исключений. При таких настройках валидатор выполнит следующий запрос:

```sql
select * from `users` where `name` != 'admin' and `mail` = 'admin@admin.ru' limit 1
```

Ну и давайте рассмотрим минимальный вариант использования, вообще без исключений.

```php
// Массив полей и значений
$data = [
    'test' => 'admin@admin.ru',
];

// Настройки валидатора
$rules = [
    'test' => [
        'ModelNotExists' => [
            'model'   => \Johncms\Users\User::class,
            'field'   => 'mail',
        ],
    ],
];

// Валидация
$validator = new \Johncms\Validator\Validator($data, $rules);
if ($validator->isValid()) {
    echo 'OK';
} else {
    d($validator->getErrors());
}
```

При таких настройках валидатор просто проверит наличие записи с mail = <admin@admin.ru>. Если запись будет найдена, то валидатор вернёт ошибку. Если нет, проверка пройдет успешно.

Запрос, который выполнит валидатор при этих настройках будет таким:

```sql
select * from `users` where `mail` = 'admin@admin.ru' limit 1
```


# Csrf - Проверка токена

Валидатор **Csrf** предназначен для проверки токена csrf. Токен предназначен для защиты формы от подделки запроса. Данный валидатор работает в паре с генератором токенов **\Johncms\Security\Csrf**

### Поддерживаемые параметры

* **tokenId**: Идентификатор токена. Если не задан, используется токен по умолчанию для всего сайта.

### Примеры использования

```php
// Массив полей и значений
$data = [
    'test' => 'token',
];

// Настройки валидатора
$rules = [
    'test' => [
        'Csrf',
    ],
];

// Валидация
$validator = new \Johncms\Validator\Validator($data, $rules);
if ($validator->isValid()) {
    echo 'OK';
} else {
    d($validator->getErrors());
}
```

В указанном примере будет проверяться токен по умолчанию.

```php
// Массив полей и значений
$data = [
    'test' => 'token',
];

// Настройки валидатора
$rules = [
    'test' => [
        'Csrf' => [
            'tokenId' => 'guestbook_form'
        ],
    ],
];

// Валидация
$validator = new \Johncms\Validator\Validator($data, $rules);
if ($validator->isValid()) {
    echo 'OK';
} else {
    d($validator->getErrors());
}
```

В этом примере мы добавили идентификатор токена, который будет проверяться.

Более подробно работу с токенами мы рассмотрим в отдельной статье.




---

[Next Page](/llms-full.txt/1)

