├── .gitignore ├── README.md └── documentation ├── AddOnStructure.md ├── AppendixScotchBox.md ├── ControllerBasics.md ├── Criteria.md ├── DesigningStyles.md ├── DevelopmentTools.md ├── EntitiesFindersAndRepositories.md ├── GeneralConcepts.md ├── GettingStarted.md ├── LetsBuildAnAddOn.md ├── RestAPI.md ├── RoutingBasics.md ├── SchemaManagement.md └── TemplateSyntax.md /.gitignore: -------------------------------------------------------------------------------- 1 | .vs/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # XenForo-2.0-Russian-Documentation 2 | Русский перевод документации XenForo 2.0 3 | 4 | **Переводчик:** Нет тут ничего такого занятного, мне просто захотелось перевести документацию XF2. Я уже более 3 месяцев использую XenForo 2.0 и за это время уже достаточно в ней разобрался и свыкся с её работой, но все же остаются темные моменты или моменты которые к сожалнию не запомнились, в следствие чего приходится обращатся за помощью к документации, каждый раз переводя тот или иной участок текста заново. Мне это немного надоело я решил выполнить перевод и выложить его в свободный доступ. Как говорится, "просто захотелось", но просто не бывает :). 5 | 6 | Я не так хорошо знаю английский, а если быть на чистоту, то я в нем полный "чайник" и перевод выполняется при помощи Google переводчика, а результат приводится вручную уже к более читабельному виду. Так что буду не против вашего вмешательства в перевод путем отправки запросов и пулреквестов. 7 | 8 | **Приветствуются правки ошибок и описаний, а так же дополнение информации.** 9 | 10 | ---------------- 11 | 12 | **Состояние перевода:** 13 | 14 | |Общее состояние |82,68% |Состояние перевода | 15 | |:-- |:-- |:-- | 16 | |Начало работы |100% |Завершено. | 17 | |Структура плагина |100% |Завершено. | 18 | |Инструменты разработки |100% |Завершено. | 19 | |Общие понятия |98% |Обработка перевода. | 20 | |Основы маршрутизации |99% |Уточнение деталей. | 21 | |Основы контроллера |100% |Завершено. | 22 | |Сущности, Поисковики и Репозитории |1% |Английская версия. | 23 | |Управление схемой |99% |Уточнение деталей. | 24 | |Давайте построим плагин |16.5% |Английская версия. | 25 | |Проектирование стилей |98% |Обработка перевода. | 26 | |Приложение: Scotch Box |98% |Обработка перевода. | 27 | 28 | 29 | ---------------- 30 | 31 | **Содержание** 32 | * [Начало работы](/documentation/GettingStarted.md#part0) 33 | * [Что нового для разработчиков](/documentation/GettingStarted.md#part1) 34 | * [Начало работы](/documentation/GettingStarted.md#part2) 35 | * [Загрузка XF 2.0](/documentation/GettingStarted.md#part3) 36 | * [Системные требования XF 2.0](/documentation/GettingStarted.md#part4) 37 | * [Настройка локального сервера](/documentation/GettingStarted.md#part5) 38 | * [Предварительно построенная виртуальная машина](/documentation/GettingStarted.md#part6) 39 | * [Готовые сборки](/documentation/GettingStarted.md#part7) 40 | * [Загрузка](/documentation/GettingStarted.md#part8) 41 | * [Создание src/config.php](/documentation/GettingStarted.md#part9) 42 | * [Замечание по правам доступа к файлам](/documentation/GettingStarted.md#part10) 43 | * [Установка](/documentation/GettingStarted.md#part11) 44 | * [Переустановка](/documentation/GettingStarted.md#part12) 45 | * [Провека целостности файлов](/documentation/GettingStarted.md#part13) 46 | * [Команды управления плагинами](/documentation/GettingStarted.md#part14) 47 | * [Установка](/documentation/GettingStarted.md#part15) 48 | * [Обновление](/documentation/GettingStarted.md#part16) 49 | * [Перестроение](/documentation/GettingStarted.md#part17) 50 | * [Удаление](/documentation/GettingStarted.md#part18) 51 | * [Структура плагина](/documentation/AddOnStructure.md#part0) 52 | * [Идентификаторы плагина и дополнительные надстройки](/documentation/AddOnStructure.md#part1) 53 | * [Рекомендуемый формат версии](/documentation/AddOnStructure.md#part2) 54 | * [Рекомендации к идентификатору версии](/documentation/AddOnStructure.md#part3) 55 | * [Основные файлы и директории плагина](/documentation/AddOnStructure.md#part4) 56 | * [Файл addon.json](/documentation/AddOnStructure.md#part5) 57 | * [Файл hashes.json](/documentation/AddOnStructure.md#part6) 58 | * [Файл Setup.php](/documentation/AddOnStructure.md#part7) 59 | * [Директория _data](/documentation/AddOnStructure.md#part8) 60 | * [Директория _output](/documentation/AddOnStructure.md#part9) 61 | * [Класс установки](/documentation/AddOnStructure.md#part10) 62 | * [Инструменты разработки](/documentation/DevelopmentTools.md#part0) 63 | * [Режим отладки](/documentation/DevelopmentTools.md#part1) 64 | * [Включение режима разработки](/documentation/DevelopmentTools.md#part2) 65 | * [Команды разработки](/documentation/DevelopmentTools.md#part3) 66 | * [Дополнительные команды плагинов](/documentation/DevelopmentTools.md#part4) 67 | * [Создание нового плагина](/documentation/DevelopmentTools.md#part5) 68 | * [Экспорт .XML файлов _data](/documentation/DevelopmentTools.md#part6) 69 | * [Повышение версии плагина](/documentation/DevelopmentTools.md#part7) 70 | * [Синхронизация addon.json с базой данных](/documentation/DevelopmentTools.md#part8) 71 | * [Проверка файла addon.json](/documentation/DevelopmentTools.md#part9) 72 | * [Запустить отдельный шаг установки](/documentation/DevelopmentTools.md#part10) 73 | * [Запустить шаг установки](/documentation/DevelopmentTools.md#part11) 74 | * [Запустить шаг обновления](/documentation/DevelopmentTools.md#part12) 75 | * [Запустить шаг удаления](/documentation/DevelopmentTools.md#part13) 76 | * [Выпуск сборки плагина](/documentation/DevelopmentTools.md#part14) 77 | * [Расширеный процесс сборки](/documentation/DevelopmentTools.md#part15) 78 | * [Команды разработки](/documentation/DevelopmentTools.md#part16) 79 | * [Импорт вывода разработки](/documentation/DevelopmentTools.md#part17) 80 | * [Экспорт вывода разработки](/documentation/DevelopmentTools.md#part18) 81 | * [Отладка кода](/documentation/DevelopmentTools.md#part19) 82 | * [Отладка переменной](/documentation/DevelopmentTools.md#part20) 83 | * [Общие понятия](/documentation/GeneralConcepts.md#part0) 84 | * [Компоненты поставщика](/documentation/GeneralConcepts.md#part1) 85 | * [Интегрированная среда разработки (IDE)](/documentation/GeneralConcepts.md#part2) 86 | * [АвтоЗагрузчик](/documentation/GeneralConcepts.md#part3) 87 | * [Пространства имён](/documentation/GeneralConcepts.md#part4) 88 | * [Короткие имена классов](/documentation/GeneralConcepts.md#part5) 89 | * [Расширение классов](/documentation/GeneralConcepts.md#part6) 90 | * [Типовые подсказки](/documentation/GeneralConcepts.md#part7) 91 | * [Основы маршрутизации](/documentation/RoutingBasics.md#part0) 92 | * [Простой пример](/documentation/RoutingBasics.md#part1) 93 | * [Префикс маршрута](/documentation/RoutingBasics.md#part2) 94 | * [Раздел контекста](/documentation/RoutingBasics.md#part3) 95 | * [Контроллер](/documentation/RoutingBasics.md#part4) 96 | * [Экшен контроллера](/documentation/RoutingBasics.md#part5) 97 | * [Более продвинутый пример (форматы маршрута)](/documentation/RoutingBasics.md#part6) 98 | * [Параметры маршрута](/documentation/RoutingBasics.md#part7) 99 | * [Суб-имена](/documentation/RoutingBasics.md#part8) 100 | * [Основы контроллера](/documentation/ControllerBasics.md#part0) 101 | * [Ответ "Просмотр"](/documentation/ControllerBasics.md#part1) 102 | * [Ответ "Перенаправление"](/documentation/ControllerBasics.md#part2) 103 | * [Ответ "Ошибка"](/documentation/ControllerBasics.md#part3) 104 | * [Ответ "Сообщение"](/documentation/ControllerBasics.md#part4) 105 | * [Ответ "Исключение"](/documentation/ControllerBasics.md#part5) 106 | * [Ответ "Перенаправление маршрута"](/documentation/ControllerBasics.md#part6) 107 | * [Изменение ответа на действие контроллера (правильно)](/documentation/ControllerBasics.md#part7) 108 | * [Сущности, Поисковики и Репозитории](/documentation/EntitiesFindersAndRepositories.md#part0) 109 | * [Поисковики (Finder)](/documentation/EntitiesFindersAndRepositories.md#part1) 110 | * [Метод where](/documentation/EntitiesFindersAndRepositories.md#part2) 111 | * [Метод whereOr](/documentation/EntitiesFindersAndRepositories.md#part3) 112 | * [Метод with](/documentation/EntitiesFindersAndRepositories.md#part4) 113 | * [Метод order](/documentation/EntitiesFindersAndRepositories.md#part5) 114 | * [Метод limitByPage](/documentation/EntitiesFindersAndRepositories.md#part6) 115 | * [Метод limit](/documentation/EntitiesFindersAndRepositories.md#part7) 116 | * [Метод getQuery](/documentation/EntitiesFindersAndRepositories.md#part8) 117 | * [Расширение методов поисковика](/documentation/EntitiesFindersAndRepositories.md#part9) 118 | * [Система сущностей (Entity)](/documentation/EntitiesFindersAndRepositories.md#part10) 119 | * [Структура сущностей](/documentation/EntitiesFindersAndRepositories.md#part11) 120 | * [Таблица](/documentation/EntitiesFindersAndRepositories.md#part12) 121 | * [Короткое имя](/documentation/EntitiesFindersAndRepositories.md#part13) 122 | * [Тип контента](/documentation/EntitiesFindersAndRepositories.md#part14) 123 | * [Основной ключ](/documentation/EntitiesFindersAndRepositories.md#part15) 124 | * [Колонки](/documentation/EntitiesFindersAndRepositories.md#part16) 125 | * [Поведения](/documentation/EntitiesFindersAndRepositories.md#part17) 126 | * [Геттеры](/documentation/EntitiesFindersAndRepositories.md#part18) 127 | * [Связи](/documentation/EntitiesFindersAndRepositories.md#part19) 128 | * [Опции](/documentation/EntitiesFindersAndRepositories.md#part20) 129 | * [Жизненный цикл сущностей](/documentation/EntitiesFindersAndRepositories.md#part21) 130 | * [Репозитории (Repository)](/documentation/EntitiesFindersAndRepositories.md#part22) 131 | * [Управление схемой](/documentation/SchemaManagement.md#part0) 132 | * [Адаптер базы данных](/documentation/SchemaManagement.md#part1) 133 | * [Управление схемой](/documentation/SchemaManagement.md#part2) 134 | * [Давайте построим плагин](/documentation/LetsBuildAnAddOn.md#part0) 135 | * [Создание плагина](/documentation/LetsBuildAnAddOn.md#part1) 136 | * [Создание класса установки](/documentation/LetsBuildAnAddOn.md#part2) 137 | * [Расширение сущности XF:Forum](/documentation/LetsBuildAnAddOn.md#part3) 138 | * [Расширение сущности XF:Thread](/documentation/LetsBuildAnAddOn.md#part4) 139 | * [Создание новой сущности](/documentation/LetsBuildAnAddOn.md#part5) 140 | * [Изменение формы редактирования форума](/documentation/LetsBuildAnAddOn.md#part6) 141 | * [Расширение процесса сохранения XF:Forum](/documentation/LetsBuildAnAddOn.md#part7) 142 | * [Настройка потока, которая будет отображаться автоматически](/documentation/LetsBuildAnAddOn.md#part8) 143 | * [Создание страницы портала](/documentation/LetsBuildAnAddOn.md#part9) 144 | * [Создание пункта навигации](/documentation/LetsBuildAnAddOn.md#part10) 145 | * [Manually featuring (or unfeaturing) threads](/documentation/LetsBuildAnAddOn.md#part11) 146 | * [Улучшение страницы портала](/documentation/LetsBuildAnAddOn.md#part12) 147 | * [Создание разрешений и оптимизация](/documentation/LetsBuildAnAddOn.md#part13) 148 | * [Создание некоторых параметров](/documentation/LetsBuildAnAddOn.md#part14) 149 | * [Не возможно изменить видимость](/documentation/LetsBuildAnAddOn.md#part15) 150 | * [Последние штрихи](/documentation/LetsBuildAnAddOn.md#part16) 151 | * [Сборка плагина](/documentation/LetsBuildAnAddOn.md#part17) 152 | * [Проектирование стилей](/documentation/DesigningStyles.md#part0) 153 | * [Включение режима дизайнера](/documentation/DesigningStyles.md#part1) 154 | * [Включение режима дизайнера для стиля](/documentation/DesigningStyles.md#part2) 155 | * [Отключение режима дизайнера для стиля](/documentation/DesigningStyles.md#part3) 156 | * [Что выводится и где?](/documentation/DesigningStyles.md#part4) 157 | * [Шаблоны](/documentation/DesigningStyles.md#part5) 158 | * [Группы и параметры стилей](/documentation/DesigningStyles.md#part6) 159 | * [Изменение конкретного шаблона](/documentation/DesigningStyles.md#part7) 160 | * [Другие полезные команды](/documentation/DesigningStyles.md#part8) 161 | * [Экспорт в базу данных](/documentation/DesigningStyles.md#part9) 162 | * [Импорт в файловую систему](/documentation/DesigningStyles.md#part10) 163 | * [Синхронизация шаблонов](/documentation/DesigningStyles.md#part11) 164 | * [Обратный шаблон](/documentation/AppendixScotchBox.md#part12) 165 | * [Приложение: Scotch Box](/documentation/AppendixScotchBox.md#part0) 166 | * [Установка Scotch Box](/documentation/AppendixScotchBox.md#part1) 167 | * [Куда идут файлы?](/documentation/AppendixScotchBox.md#part2) 168 | * [Остановка и перезапуск сервера](/documentation/AppendixScotchBox.md#part3) 169 | * [Официальная документация](/documentation/AppendixScotchBox.md#part4) 170 | * [Scotch Box Профессионал](/documentation/AppendixScotchBox.md#part5) 171 | -------------------------------------------------------------------------------- /documentation/AddOnStructure.md: -------------------------------------------------------------------------------- 1 | # Структура плагина 2 | В предыдущих версиях XF было очень мало стандартов и соглашений, касающихся разработки плагинов. Мы многое сделали, чтобы изменить это в XF 2.0. Давайте посмотрим на некоторые изменения: 3 | 4 | ## Идентификаторы и пути плагина 5 | 6 | Каждый установленный плагин должен иметь уникальный идентификатор, и этот идентификатор определяет, где плагин хранит свои файлы. Существует два формата идентификатора дополнения. 7 | 8 | Первый тип - «простой» состоит из одного слова. Это слово не содержит какие-либо специальные символы! Например, `Demo` 9 | 10 | Данный идентификатор плагина должен соответствовать следующим правилам: 11 | 12 | * Должен содержать только a-z или A-Z 13 | * Может содержать 0-9, но не в начале идентификатора 14 | * Не может содержать никаких специальных символов, таких как слэши, тире или символы подчеркивания 15 | 16 | Второй тип - содержит префикс, если вы выпускаете плагины под конкретным брендом или компанией, на это указывает дополнительный идентификатор. Например, `SomeVendor/Demo`. 17 | 18 | Идентификатор плагина с префиксом должен придерживаться следующих правил: 19 | 20 | * Должен содержать только a-z или A-Z 21 | * Может содержать один символ `/`, но не в начале или в конце 22 | * Может содержать 0-9, но не в начале любой части идентификатора плагина 23 | 24 | После выбора своего префикса, мы будем знать, где хранятся файлы для этого плагина. Все плагины XF 2.0 находятся по пути `src / addons`. 25 | 26 | Если ваш идентификатор плагина, например, `Demo`, файлы вашего плагина будут находится в следующем расположении: `src/addons/Demo`. 27 | 28 | Если ваш идентификатор плагина с префиксом, например. `SomeVendor/Demo`, файлы будут находится в следующем расположении:`src/addons/SomeVendor/Demo`. 29 | 30 | Выбранный идентификатор также станет вашим префиксом пространства имен классов (см. [Пространства имен](../documentation/GeneralConcepts.md#part4) для получения дополнительной информации). 31 | 32 | ## Рекомендуемый формат версии 33 | 34 | XF для нумерации версий использует принцип MAJOR.MINOR.PATCH (пример, 2.0.0 для первой стабильной версии XF2), мы рекомендуем использовать аналогичный подход к версированию собственных дополнений. Увеличивайте 35 | 36 | * MAJOR версию, когда вы делаете большие изменения функционала, особенно изменения, которые нарушают обратную совместимость 37 | * MINOR версию, когда вы добавляете обратно совместимый функционал 38 | * PATCH версию, когда вы делаете исправления ошибок с обратной совместимостью 39 | 40 | ## Рекомендованный формат версирования 41 | 42 | Идентификатор версии плагина - это целое число, которое используются для внутреннего сравнения версий. Это позволяет более легко обнаруживать, когда одна версия старше другой. Каждая новая версия вашего дополнения должна увеличивать идентификатор версии как минимум на 1, но правила, которое мы используем внутри самой XF, применимы также и для плагинов. Мы используем идентификаторы версий в формате `aabbccde`. 43 | 44 | * `aa` представляет основную версию 45 | * `bb` представляет второстепенную версию 46 | * `cc` представляет версию патча 47 | * `d` представляет состояние, например. `1` для альфа-релизов,`3` для бета-релизов, `5` для кандидатов на выпуск и`7` для стабильных выпусков 48 | * `e` представляет состояние версии 49 | 50 | Например, плагин с версией 1.7.3 кандидат на выпуск 4 будет иметь идентификатор `1070354`. Окончательный стабильный релиз XF2 имеет идентификатор `2000070`. Версия 1.5.0 Beta 3 из XF имела идентификатор `1050033`. Стабильная версия `99.99.99` будет иметь идентификатор `99999970` ... и, думаю, вам стоит остановится 😉 51 | 52 | ## Основные файлы и директории плагина 53 | 54 | В директории плагина есть файлы и директории, которые имеют особое значение. 55 | 56 | ### Файл addon.json 57 | 58 | `addon.json` - это файл, содержащий информацию, которая идентифицирует плагин и отображает информацию о нём в Admin CP. Как минимум, ваш `addon.json` должен выглядеть так: 59 | 60 | ```json 61 | { 62 | "title": "My Add-on by Some Company", 63 | "version_string": "2.0.0", 64 | "version_id": 2000070, 65 | "dev": "Some Company" 66 | } 67 | ``` 68 | 69 | Базовый файл будет создан для вас автоматически при создании плагина. 70 | 71 | Вы также можете [проверить ваш addon.json файл](#). 72 | 73 | #### Свойства плагина 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 |
СвойстваОписание
legacy_addon_idИспользуется для обработки изменений идентификатора плагина при обновлении с XenForo 1 до XenForo 2.
titleНазвание плагина. Оно отображается в ACP.
descriptionОписние вашего плагина. Оно отображается в ACP
version_idИдентификатор, используемый XenForo для отслеживания обновлений вашего плагина. Он должен увеличиватся при каждом релизе.
version_stringЧеловеко-читаемая версия аддона. Будет отображаться в ACP вместо свойства version_id.
devИмя разработчика плагина. Оно отображается в ACP.
dev_urlЕсли задано, имя разработчика будет отображаться в ACP как гиперссылка.
faq_urlЕсли задано, гиперссылка "FAQ" будет отображаться в панели ACP
support_urlЕсли задано, гиперссылка "поддержка" будет отображаться в ACP.
extra_urlsПозволяет отображать другие ссылки, связанные с плагином (ссылки на сообщения об ошибках, руководство - все, что вам хочется).
Массив объектов JSON, где ключ - текст ссылки, а значение - ссылка.
requireНабор требований, которые должны быть выполнены, перед установкой плагина. Смотрите 'Свойства требований' для дополнительной информации.
iconTЗначок плагина. Это имя иконки FontAwesome (например, fa-shopping-bag, или путь до файла картинки.)
142 | 143 | ##### Свойства требований 144 | Свойство require - это стандартный способ блокировки установки или обновления плагина, если среда не поддерживает или не соответствует требованиям. 145 | Вы можете использовать его для того, чтобы сначала были установлены другие плагины, присутствовали или были включены определенные расширения PHP, и/или для указания минимальной версии PHP. 146 | 147 | Вот пример фрагмента: 148 | ```json 149 | ... 150 | "require": { 151 | "XF": [2000010, "XenForo 2.0.0+"], 152 | "php": ["5.4.0", "PHP 5.4.0+"], 153 | "php-ext/json": ["*", "JSON extension"] 154 | } 155 | ... 156 | ``` 157 | 158 | Каждое требование - это именованный массив: 159 | 160 | - Имя массива является идентификатором продукта (например, `XF` или `php`). 161 | - Первый элемент массива - это версия продукта (например, `2000010` или `5.4.0`). Вы можете использовать `*`, чтобы ссылаться на любую версию продукта. 162 | - Второй элемент - человеко-читаемый текст требования, это используется в сообщениях. (например, `XenForo 2.0.0+` или `PHP 5.4.0+`). 163 | 164 | Вот сводка поддерживаемых идентификаторов: 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 |
Название продукта/требованияОтносится к...Значение
XFУстановленная версия XenForo.Идентификатор версии XenForo, например 200010.
Вы можете узнать текущую версию XenForo, проверив верхнюю часть файла/src/XF.php для определения $versionIdили напечатав значение\XF::$versionId.
phpВерсия PHP.Версия PHP, например, 5.4.0.
Рекомендуется держать это как можно ниже; Обновление версии PHP может быть довольно сложной задачей, особенно если другие плагины конфликтуют с более новыми версиями PHP.
php-ext/(extension name)Расширение PHP - где (extension name) это имя расширения.Версия расширения PHP.
Это проверяется с помощью функции PHP version_compareтак что это работает даже для строк версии в официальном полном формате PHP, как 7.1.19-1+ubuntu16.04.1+deb.sury.org+1.
(any addon ID)Плагин XenForo, такой как Demo/Addon.
Если вы не уверены относительно идентификатора плагина проверьте его файлaddon.json.
Идентификатор версии плагина.
Вы можете обратиться к рекомендованному формату версирования для получения дополнительной информации.
199 | 200 | ### Файл hashes.json 201 | 202 | `hashes.json` - это новый способ добавить поддержку системы проверки работоспособности файлов, и приятная новость - он генерируется автоматически! 203 | 204 | Как часть процесса сборки (подробнее об этом позже), мы быстро выполним инвентаризацию всех ваших файлов плагина и вычислим хэш содержимого файлов. 205 | 206 | ### Файл Setup.php 207 | 208 | `Setup.php` - это новый дом для любого кода, который запускается во время установки, обновления или удаления вашего плагина. 209 | 210 | Мы поговорим подробнее о том, как создать класс установки [ниже](/documentation/AddOnStructure.md#part12). 211 | 212 | ### Директория _data 213 | 214 | В каталоге `_data` хранятся основные данные для вашего плагина. Каждый дополнительный тип данных будет иметь свой собственный XML-файл (а не один для всех типов). Хэши для этих файлов так же находятся внутри `hashes.json`, поэтому мы можем гарантировать, что все файлы будут проверены перед установкой. 215 | 216 | ### Директория _output 217 | 218 | Каталог `_output` не требуется для успешной установки плагина и не должен включаться при выпуске плагина. Этот каталог предназначен исключительно для целей разработки и используется только в том случае, если включен режим разработки (см. [Включение режима разработчика](.../documentation/DevelopmentTools.md#part2)). 219 | 220 | Каждый элемент дополнительных данных хранится в отдельном файле. В основном они хранятся в виде файлов JSON, но в случае фраз они хранятся в виде файлов TXT, а для шаблонов они хранятся в виде файлов HTML/CSS/LESS. Все типы шаблонов редактируются непосредственно в файловой системе, а изменения, внесенные в эти файлы, автоматически записываются в базу данных при загрузке. 221 | 222 | ## Класс установки 223 | Чтобы создать класс установки для вашего плагина, все, что вам нужно сделать, это создать файл с именем `Setup.php` в корне каталога вашего дополнения. 224 | 225 | Класс Setup должен расширять `\XF\AddOn\AbstractSetup`, который требует, как минимум, реализацию методов `install()`, `upgrade()` и `uninstall()`. Простой класс установки плагина выглядит так: 226 | ```php 227 | schemaManager()->createTable('xf_demo', function(\XF\Db\Schema\Create $table) 236 | { 237 | $table->addColumn('demo_id', 'int'); 238 | }); 239 | } 240 | 241 | public function upgrade(array $stepParams = []) 242 | { 243 | if ($this->addOn->version_id < 1000170) 244 | { 245 | $this->schemaManager()->alterTable('xf_demo', function(\XF\Db\Schema\Alter $table) 246 | { 247 | $table->addColumn('foo', 'varchar', 10)->setDefault(''); 248 | }); 249 | } 250 | } 251 | 252 | public function uninstall(array $stepParams = []) 253 | { 254 | $this->schemaManager()->dropTable('xf_demo'); 255 | } 256 | } 257 | ``` 258 | Класс Setup также поддерживает выполнение каждого из действий в разных шагах. Чтобы реализовать это поведение, ваш класс Setup может использовать свойства «StepRunnerInstallTrait», «StepRunnerUpgradeTrait» и/или «StepRunnerUninstallTrait». 259 | Они автоматически реализуют требуемые методы, и вам просто нужно добавить соответствующие шаги, например. `installStep1()`, `upgrade1000170Step1()`, `upgrade1000170Step2()` и `uninstallStep1()`, где `1000170` и т. д. в методах обновления являются дополнительными идентификаторами версий (см. [Рекомендуемый формат версии](/documentation/AddOnStructure.md#part3)). -------------------------------------------------------------------------------- /documentation/AppendixScotchBox.md: -------------------------------------------------------------------------------- 1 | # Приложение: Scotch Box 2 | Ниже приведено описание того, как установить превосходный [Scotch Box](https://box.scotch.io/) на свой компьютер, чтобы иметь полностью работоспособную среду разработки для XenForo всего за несколько минут с помощью нескольких простых команд. 3 | 4 | XenForo имеет настраиваемую конфигурацию Scotch Box, которая предоставляет все необходимое для запуска XenForo, включая отладчик и кеш данных с улучшением производительности. 5 | 6 | Scotch Box работает в среде [VirtualBox](https://www.virtualbox.org/)/[Vagrant](https://www.vagrantup.com/). 7 | 8 | ## Установка Scotch Box 9 | Начните с определения, где на вашем компьютере вы хотите, чтобы ваш виртуальный веб-сервер сохранял свои файлы. Рекомендуется выбрать место в домашнем каталоге вашего собственного пользователя. 10 | 11 | В следующих примерах мы будем использовать каталог под названием *MyServer*, расположенный в корне вашей собственной пользовательской директории, идентифицированный вашим именем *{username}*: 12 | 13 | * `/Users/{username}/MyServer` (Mac) 14 | * `C:\Users\{username}\MyServer` (Windows) 15 | * `/home/{username}/MyServer` (некоторые дистрибутивы Linux) 16 | * `/users/{username}/MyServer` (другие дистрибутивы Linux) 17 | 18 | После того, как вы выбрали местоположение, выполните следующие действия: 19 | 20 | 1. Установить [VirtualBox](https://www.virtualbox.org/) на свой компьютер 21 | 2. Установить [Vagrant](https://www.vagrantup.com/) на свой компьютер 22 | 3. Использовать git клиент, clone `https://github.com/scotch-io/scotch-box` в ваш каталог MyServer. Используя клиент командной строки с приведенным выше примером Mac, команда будет: 23 | 24 | `git clone https://github.com/scotch-io/scotch-box /Users/{username}/MyServer` 25 | 26 | 4. После завершения процесса клонирования загрузите этот пользовательский Vagrantfile и перезапишите Vagrantfile, который был создан в * /Users/{username}/MyServer/Vagrantfile: [Загрузить пользовательский Vagrantfile](https://xenforo.com/xf2-docs/dev/files/scotchbox/Vagrantfile). 27 | 28 | 5. Когда пользовательский Vagrantfile находится на своем месте, выполните следующие команды: 29 | ```bash 30 | cd /Users/{username}/MyServer 31 | vagrant up 32 | ``` 33 | 34 | Теперь ваша виртуальная машина Scotch Box создана и готова к использованию. 35 | 36 | > **Предупреждение** 37 | > Scotch Box также предоставляет версию «[Scotch Box Pro] (https://box.scotch.io/pro/)» их виртуальной машины для разумной цены покупки. Если вы предпочитаете запускать Scotch Box Pro, см. [Раздел ниже, описывающий различия между настройкой и запуском Scotch Box и Scotch Box Pro] (/documentation/AnnexScotchBox.md#part5). 38 | 39 | ## Куда идут файлы? 40 | После того, как ваша Scotch Box запущена и работает, вы можете хранить ваши XenForo PHP и JS-файлы на своем хост-компьютере, позволяя использовать ваш текстовый редактор или IDE по выбору, в то время как виртуальная машина отвечает за компиляцию и обслуживание этих файлов через свою сеть сервер. 41 | 42 | Вы сможете посетить свой новый веб-сервер в своем веб-браузере по следующему адресу: 43 | 44 | `http://192.168.33.10` 45 | 46 | Веб-сервер будет извлекать файлы, которые будут 47 | 48 | `/Users/{username}/MyServer/public` 49 | 50 | Если вы хотите, чтобы ваш XenForo был установлен на `http://192.168.33.10/xenforo`, вы должны поместить содержимое папки для загрузки из пакета XenForo в `/Users/{username}/MyServer/public/xenforo`. 51 | 52 | ## Остановка и перезапуск сервера 53 | Вы можете остановить сервер Scotch Box в любое время, запустив 54 | ```bash 55 | cd /Users/{username}/MyServer 56 | vagrant halt 57 | ``` 58 | and you can restart it by running 59 | ``` 60 | cd /Users/{username}/MyServer 61 | vagrant up 62 | ``` 63 | 64 | > **Предупреждение** 65 | > Хотя Vagrant/Scotch Box автоматически отключается при перезагрузке компьютера, он автоматически не запускается автоматически. 66 | > 67 | > Всякий раз, когда вы перезагружаетесь, вам нужно снова запустить команду `vagrant up`, чтобы использовать сервер. 68 | 69 | ## Официальная документация 70 | Данное руководство получено из официальной документации Scotch Box, которая находится по адресу [https://box.scotch.io] (https://box.scotch.io/) 71 | ## Scotch Box Профессионал 72 | Хотя основной Scotch Box требует некоторой дополнительной конфигурации (которая передается через пользовательский Vagrantfile) для запуска XenForo 2, Scotch Box Pro не требует дополнительной настройки и готов к запуску XenForo 2 без загрузки дополнительных пакетов. 73 | 74 | Чтобы запустить [Scotch Box Pro] (https://box.scotch.io/pro/), купите его с веб-сайта Scotch Box Pro, затем запустите команду git clone, предоставленную в качестве части инструкций, которые вы получите после покупки. 75 | 76 | Теперь вы можете установить с помощью тех же инструкций, что и выше, с единственным исключением, которое вы должны загрузить [этот пользовательский Vagrantfile] (https://xenforo.com/xf2-docs/dev/files/scotchboxpro/Vagrantfile) вместо указанного в списке в инструкциях для Scotch Box. -------------------------------------------------------------------------------- /documentation/ControllerBasics.md: -------------------------------------------------------------------------------- 1 | # Основы контроллеров 2 | На базовом уровне контроллеры - это код, который выполняется при посещении страницы в XF. Контроллеры обычно отвечают за обработку пользовательских данных и передачу этих данных в соответствующий метод, который, как правило, должно выполнить какое-либо действие с базой данных(моделью) или загрузить визуальное содержимое(представление). 3 | 4 | Когда пользователь переходит по ссылке, запрошенный URL-адрес вызывает определенный контроллер и метод контроллера. Смотрите [Основы маршрутизации](/documentation/RoutingBasics.md#part0). Например, в XF, если посетить URL-адрес `index.php?conversations/add` будет вызван контоллер `XF\Pub\Controller\Conversation` и метод(action) `add`. 5 | 6 | Если вы посмотрите на содержимое этого класса (описание сопоставления классов и путей к файлам см.В разделе [АвтоЗагрузчик](/documentation/GeneralConcepts.md#part3)) вы заметите, что методы именуются с префиксом `action`. Эти методы вызывают определенное действие контроллера. Таким образом, чтобы увидеть метод, участвующий при просмотре страницы `conversations/add` , смотрите `public function actionAdd()`. 7 | 8 | Контроллеры XF возвращают объект ответа, который может быть следующих типов: 9 | 10 | ## Ответ "Представление(view)" 11 | Это один из наиболее распространенных ответов, с которым вы будете иметь дело во время разработки XF. Контроллер, который возвращает ответ представление(view), обычно требует передачу трех аргументов. Класс представления (подробнее об этом ниже), имя шаблона и массив `$viewParams` - это переменные, которые будут доступны в шаблоне. 12 | 13 | Это пример действия контроллера, который возвращает ответ представление(view): 14 | ```php 15 | public function actionExample() 16 | { 17 | $hello = 'Hello'; 18 | $world = 'world!'; 19 | 20 | $viewParams = [ 21 | 'hello' => $hello, 22 | 'world' => $world 23 | ]; 24 | return $this->view('Demo:Example', 'demo_example', $viewParams); 25 | } 26 | ``` 27 | Первый аргумент - это короткое имя класса представления. Этот класс может даже не существовать (часто он не должен существовать, мы рассмотрим классы представлений позже), но он должен иметь уникальное имя для контроллера и метода. Подобно другим [Коротким именам классов](/documentation/GeneralConcepts.md#part5), приведенное выше,также преобразуется в `Demo\Pub\View\Example`. Опять же, `Pub` выводится автоматически из типа контроллера. 28 | 29 | Второй аргумент - имя шаблона. В этом случае мы ищем шаблон с именем `demo_example`. 30 | 31 | Третий аргумент представляет собой массив параметров/переменных шаблона, которые доступны представлению(view). Этот массив должен быть парой `key => value` (`ключ => значение`). В приведенном выше примере в шаблон передаются два параметра. Ключ (`key`) указывает имя переменной, доступной в шаблоне. Значение массива (`value`) указывает на значение параметра. 32 | 33 | Итак, если бы в шаблоне `demo_example` было следующее содержимое: 34 | 35 | ```php 36 | {$hello} {$world} 37 | ``` 38 | Шаблон выведет следующее: 39 | 40 | ```Hello world!``` 41 | ## Ответ "Перенаправление(redirect)" 42 | Этот ответ возвращают, когда вы хотите перенаправить пользователя на другой URL-адрес после выполнения какого-либо действия. 43 | 44 | Обычно, после того, как пользователь отправил данные через форму, вы можете перенаправить его на другую страницу, например, вернуть пользователя к списку элементов. 45 | 46 | Это пример действия контроллера, который возвращает перенаправление(redirect): 47 | ```php 48 | public function actionRedirect() 49 | { 50 | return $this->redirect($this->buildLink('demo/example'), 'This is a redirect message.', 'permanent'); 51 | } 52 | ``` 53 | Первый аргумент - URL для перенаправления. Данный пример перенаправит пользователя к `index.php?demo/example` . 54 | 55 | Второй аргумент будет отображаться только в том случае, если форма передается по AJAX-запросу, который запрещает перенаправление. Результатом будет "flash-сообщение", которое появится в верхней части экрана. Можно не указывать собственное сообщение. Тогда, по умолчанию будет "Ваши изменения были сохранены". 56 | 57 | Третий аргумент по умолчанию имеет значение `temporary`, но вы также можете установить его в `permanent` в соответствии с примером выше. Единственное отличие здесь - это код ответа HTTP сервера. `Temporary` идеально подходит в большинстве случаев, и он будет отвечать 303 кодом. `permanent` будет отвечать 301 кодом. 58 | 59 | Также можно вызвать постоянную переадресацию иным образом, существует определенный метод, он может быть использован следующим образом. Он также принимает аргумент "сообщение", но, как указано выше, это необязательно. 60 | 61 | ```php 62 | public function actionRedirect() 63 | { 64 | return $this->redirectPermanently($this->buildLink('demo/example')); 65 | } 66 | ``` 67 | ## Ответ "Ошибка(error)" 68 | Как следует из названия, этот ответ - отобразит пользователю ошибку. 69 | Вот небольшой пример: 70 | ```php 71 | public function actionError() 72 | { 73 | return $this->error('Unfortunately the thing you are looking for could not be found.', 404); 74 | } 75 | ``` 76 | Здесь поддерживаются только два аргумента. Первое - это сообщение об ошибке, которое вы хотите отобразить, а второе - код ответа HTTP сервера. 404 код представляет ответ, когда что-то не было найдено. 77 | 78 | ## Ответ "Сообщение(message)" 79 | Этот ответ очень похож на ответ об ошибке и поддерживает те же аргументы. Основное различие заключается в том, что отображаемое сообщение не представляется как ошибка. 80 | 81 | ## Ответ "Исключение(exception)" 82 | Иногда необходимо прервать нормальный поток кода вашего контроллера и вместо этого ответить Исключением. Ответы на исключения не обязательно должны представлять ошибку; например, они могут использоваться для принудительного перенаправления контроллера. Однако, как правило, они часто используются для остановки потока контроллера и отображения ошибки, как это показано в следующем примере: 83 | ```php 84 | public function actionException() 85 | { 86 | throw $this->exception($this->error('An unexpected error occurred')); 87 | } 88 | ``` 89 | Исключения принимают только один аргумент, и он должен быть каким-либо другим объектом ответа, такой как [Ответ "Ошибка"](/documentation/ControllerBasics.md#part3). Данный конкретный пример бросает исключение, и весь код контроллера в той точке остановится, и будет отображена стандартная ошибка. 90 | 91 | ***Обратите внимание, что ответы на исключения должны быть "брошены" с помощью `throw`, а не "возвращены" `return` .*** 92 | 93 | ## Ответ "Перенаправление маршрута(reroute)" 94 | Иногда необходимо перенаправить пользователя на совершенно другой контроллер или действие в том же контроллере, не выполняя полного перенаправления, не изменяя URL-адрес пользователя, и не дублируя код целевого действия. 95 | 96 | Это выглядит примерно так: 97 | ```php 98 | public function actionReroute() 99 | { 100 | return $this->rerouteController(__CLASS__, 'error'); 101 | } 102 | 103 | public function actionError() 104 | { 105 | return $this->error('Oops! Something went wrong!'); 106 | } 107 | ``` 108 | В данном примере, если пользователь посетил `index.php?demo/reroute`, они увидят ответ об ошибке из метода `actionError()`. Они не будут перенаправлены, и URL-адрес в их браузере не изменится; они просто получат ответ от `actionError()`. 109 | 110 | Ответ reroute также поддерживает третий аргумент, который позволяет передавать различные параметры от одного метода контроллера к другому. Это может быть массив или объект `ParameterBag` (об этом позже). 111 | 112 | ## Правильное изменение ответа на действие контроллера 113 | Из раздела [Расширения классов](/documentation/GeneralConcepts.md#part6), мы уже знаем, как просто расширить класс, но необходимо проявлять особую осторожность при расширении методов контроллера, которые уже существуют. 114 | 115 | Если у вас нет задачи полностью переопределить/изменить существующий метод(что не рекомендуется), вместо этого вы должны изменить существующий ответ родительского класса. Это делается довольно просто, в качестве примера давайте изменим код [Ответ "Просмотр"](/documentation/ControllerBasics.md#part1) из примера выше. 116 | ```php 117 | public function actionExample() 118 | { 119 | $reply = parent::actionExample(); 120 | 121 | return $reply; 122 | } 123 | ``` 124 | Предполагая, что вышеупомянутое добавлено к расширенному контроллеру, где метод `actionExample()` уже существует, вышеприведенный код ничего не делает, кроме возврата исходного ответа. Теперь изменим существующий параметр `hello` на `Bonjour`. 125 | ```php 126 | public function actionExample() 127 | { 128 | $reply = parent::actionExample(); 129 | 130 | if ($reply instanceof \XF\Mvc\Reply\View) 131 | { 132 | $reply->setParam('hello', 'Bonjour'); 133 | } 134 | 135 | return $reply; 136 | } 137 | ``` 138 | Поскольку ответ контроллера может фактически представлять несколько различных объектов с различным поведением и методами, крайне важно, чтобы мы только попытались расширить правильный тип ответа. Мы делаем это в приведенном выше примере, проверяя, является ли родительский объект `$reply` типом представления(view). Если бы мы не сделали этого, и 139 | расширили метод, а метод отвечал бы перенаправлением - была бы ошибка. 140 | 141 | Перед расширением этого действия, посетив эту страницу будет отображаться "Hello world!". После его расширения, будет отображаться "Bonjour world!". -------------------------------------------------------------------------------- /documentation/Criteria.md: -------------------------------------------------------------------------------- 1 | When XenForo needs to test something (user/page/post...) against some **user selected** conditions (criteria), it uses the Criteria system. 2 | 3 | Some places, where the Criteria system is used: 4 | 5 | - Trophies 6 | - User-group promotions 7 | - Forum notices 8 | 9 | Addons can also use this system. 10 | 11 | ## Criteria types 12 | 13 | Consider the following criteria: 14 | 15 | - User has/has no avatar 16 | - User has more than 300 messages 17 | - User is creating a thread right now 18 | - Current user's selected navigation tab is "Members" 19 | 20 | The first two criteria refer to the user himself. The remaining ones refer to his current location on the forum. It appears we have different categories or **types** of criteria. 21 | 22 | There are two criteria types in XenForo out of the box: 23 | 24 | - User criteria — handling criteria about the user himself 25 | - Page criteria — handling criteria about user's current location + time criteria 26 | 27 | Some addons may also add their own criteria types. 28 | 29 | From the code perspective, criteria types are simply children of an abstract `AbstractCriteria` class. They contain code for handling the selected criteria of certain type. 30 | 31 | `AbstractCriteria`, in turn, provides a general methods to work with criteria regardless of their meaning. 32 | 33 | ## Criterion 34 | 35 | Criterion is a user selectable predefined condition. 36 | 37 | **Why selectable?** Because admins/users can select them (remember trophy creation process). 38 | 39 | **Why predefined?** Because XenForo already knows how to handle them (using criteria classes methods). 40 | 41 | Every criterion consists of two parts: **rule** and (optionally) **data**. 42 | 43 | ### Rule 44 | 45 | The criterion rule is simply a sting in [snake case](https://en.wikipedia.org/wiki/Snake_case) (words_are_separated_with_underscore_character). 46 | 47 | It has two essential purposes: 48 | 49 | 1. It is used to distinguish criteria 50 | 2. When performing matching, the rule is converted into a [camel case](https://en.wikipedia.org/wiki/Camel_case) name of a method that handles this criterion (see ["How criteria works"](#how-criteria-works)). 51 | 52 | ### Data 53 | 54 | It is just an optional array of additional criterion data. For example, "User has posted at least X messages" criterion has a data array with one element: a number of messages. 55 | 56 | ## How criteria system works 57 | 58 | In this sections, we describe how criteria system works from A to Z. 59 | 60 | ### Template 61 | 62 | It all starts from template code. Here is how criteria look inside templates: 63 | 64 | ```html 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | ``` 78 | 79 | As you can see, criterion is simply a checkbox with optional input fields inside (criterion data). Let's analyze the code: 80 | 81 | - `foo_criteria` and `bar_criteria` are the input containers and usually `foo` and `bar` parts refer to criteria type. For example, `user_criteria[...]` lets us know that this criteria belong to User criteria. 82 | - `value="criterion_1_rule"` and `value="criterion_2_rule"` are, obviously, the rules of criteria. 83 | 84 | !!! note 85 | Keep in mind that `criterion_1/2_rule` in `name` attributes may not have to be criteria rules! These are just names for input containers. You can easily write `` and it will work correctly. The criterion rule will be `criterion_rule`, not `bar`. 86 | 87 | ### (Optionally) Storing selected criteria 88 | 89 | Inside the controller, the criteria form data from the previous section can be filtered, encoded and saved in database columns of `mediumblob` type for better days: 90 | 91 | ```php 92 | $fooCriteriaInput = $this->filter('foo_criteria', 'array'); 93 | $barCriteriaInput = $this->filter('bar_criteria', 'array'); 94 | 95 | $form->basicEntitySave($bazEntity, [ 96 | 'foo_criteria' => $fooCriteriaInput, 97 | 'bar_criteria' => $barCriteriaInput 98 | ]); 99 | ``` 100 | 101 | The example `$bazEntity` structure: 102 | 103 | ```php 104 | public static function getStructure(Structure $structure) 105 | { 106 | $structure->table = 'xf_baz'; 107 | $structure->shortName = 'XF:Baz'; 108 | $structure->primaryKey = 'baz_id'; 109 | $structure->columns = [ 110 | 'baz_id' => ['type' => self::UINT, 'autoIncrement' => true], 111 | 'foo_criteria' => ['type' => self::JSON_ARRAY, 'default' => [], 'required' => 'please_select_criteria_that_must_be_met'], 112 | 'bar_criteria' => ['type' => self::JSON_ARRAY, 'default' => []] 113 | ]; 114 | 115 | return $structure; 116 | } 117 | ``` 118 | 119 | ### Criteria object 120 | 121 | For using criteria system we need to create a criteria object from selected criteria form data. This can be done via app's `criteria()` method: 122 | 123 | ```php 124 | /** @var \Qux\Criteria\Foo $fooCriteria */ 125 | $fooCriteria = \XF::app()->criteria('Qux:Foo', $bazEntity->foo_criteria); 126 | 127 | /** @var \Qux\Criteria\Bar $barCriteria */ 128 | $barCriteria = \XF::app()->criteria('Qux:Bar', $bazEntity->bar_criteria); 129 | ``` 130 | 131 | From now, we can use all `AbstractCriteria` functionality plus everything we have additionally written in child `Foo`/`Bar` classes. 132 | 133 | ### Matching 134 | 135 | When we want to check, whether something (User) matches the selected criteria or not, we use `isMatched` method: 136 | 137 | ```php 138 | $visitor= \XF::visitor(); 139 | 140 | if ($fooCriteria->isMatched($visitor)) 141 | { 142 | // Visitor matches all selected criteria 143 | } 144 | else 145 | { 146 | // Visitor does not match one or more criteria 147 | } 148 | ``` 149 | 150 | `isMacthed()` converts criterion rule into camel case name of a method with `_match` prefix: `criterion_1_rule` > `_matchCriterion1Rule` and tries to find such a method inside criteria type class (`Foo` class in our example): 151 | 152 | ```php 153 | // Qux/Criteria/Foo.php 154 | 155 | protected function _matchCriterion1Rule(array $data, \XF\Entity\User $user) 156 | { 157 | /* ... Handling criteria ... */ 158 | 159 | return true; // User matches current criteria 160 | 161 | /* OR */ 162 | 163 | return false; // User does not match current criteria 164 | } 165 | ``` 166 | 167 | If some method can't be found in class, `isMatched()` calls `isUnknownMatched()` which behaviour can be set in `AbstractCriteria` ancestors (returns `false` by default). 168 | 169 | If none criteria were selected, `isMatched()` returns `$matchOnEmpty` variable which equals `true` by default. You can change this behaviour by calling `$crteriaObj->setMatchOnEmpty(false)` **before** using `isMatched()` method: 170 | 171 | ```php 172 | $visitor= \XF::visitor(); 173 | 174 | $fooCriteria->setMatchOnEmpty(false); 175 | 176 | if ($fooCriteria->isMatched($visitor)) 177 | { 178 | // Visitor matches all selected criteria 179 | } 180 | else 181 | { 182 | // Visitor does not match one or more criteria 183 | } 184 | ``` 185 | 186 | ## How criteria works (example) 187 | 188 | Imagine you want to award with a trophy all users who have an avatar and have received at least 5 likes. 189 | 190 | When creating a trophy, you select "User has an avatar" (rule `has_avatar`) and "User has received at least X likes" (rule `like_count`) criteria. The last one also has a data array with one element: a number of likes. 191 | 192 | Your selected criteria stores in `user_criteria` column in `xf_trophy` table. 193 | 194 | When XenForo decides to check, whether to award a user with a trophy or not, it converts rules into camel case method names: 195 | 196 | - `like_count` > `_matchLikeCount()` 197 | - `has_avatar` > `_matchHasAvatar()` 198 | 199 | Since both of selected criteria are User criteria, XenForo addresses the User criteria class and tries to find such methods in it: 200 | 201 | ```php 202 | // XF/Criteria/User.php 203 | 204 | //... 205 | protected function _matchLikeCount(array $data, \XF\Entity\User $user) 206 | { 207 | return ($user->like_count && $user->like_count >= $data['likes']); 208 | } 209 | //... 210 | protected function _matchHasAvatar(array $data, \XF\Entity\User $user) 211 | { 212 | return $user->user_id && ($user->avatar_date || $user->gravatar); 213 | } 214 | //... 215 | ``` 216 | 217 | If **all** addressed methods return `true`, our user matches the selected criteria and therefore will be awarded with a trophy. 218 | 219 | If some methods can't be found in User criteria class, XenForo calls `isUnknownMatched()` method, which in turn fires `criteria_user` event, allowing addon makers to add their custom criteria handlers (see ["Custom User/Page criterion example"](#custom-userpage-criterion-example)). 220 | 221 | ## Extra criteria data 222 | 223 | Sometimes, when writing criteria template code, you need to access extra data, that is not passed with view params. 224 | 225 | This is what `getExtraTemplateData()` method exists. By default, it contains existing user groups, languages, styles, time zones. 226 | 227 | You can override this method in you custom criteria type class . 228 | 229 | ### Adding data in custom criteria type 230 | 231 | Override `getExtraTemplateData()` method in your custom criteria class: 232 | 233 | ```php 234 | public function getExtraTemplateData() 235 | { 236 | $templateData = parent::getExtraTemplateData(); 237 | 238 | $additionalData = []; 239 | 240 | /** @var \XF\Repository\Smilie $smilieRepo */ 241 | $smilieRepo = \XF::repository('XF:Smilie'); 242 | 243 | $additionalData['smilies'] = $smilieRepo->findSmiliesForList()->fetch(); 244 | 245 | return array_merge($templateData, $additionalData); 246 | } 247 | ``` 248 | 249 | ### Adding data to existing criteria types 250 | 251 | You can use `criteria_template_data` event listener to add you own extra criteria data: 252 | 253 | ```php 254 | public static function criteriaTemplateData(array &$templateData) 255 | { 256 | /** @var \XF\Repository\Smilie $smilieRepo */ 257 | $smilieRepo = \XF::repository('XF:Smilie'); 258 | 259 | $templateData['smilies'] = $smilieRepo->findSmiliesForList()->fetch(); 260 | } 261 | ``` 262 | 263 | ## "helper_criteria" template 264 | 265 | Whenever you as addon maker want to get a target user/admin a way to select User/Page/other addon's criteria (or even all at once), you can simply use `helper_criteria`. 266 | 267 | In short, `helper_criteria` is an admin template that allows to use criteria types checkbox-based interface in multiply places without copy-pasting the same code. 268 | 269 | `helper_criteria` contains macros of **two** types: `*criteria_name*_tabs` and `*criteria_name*_panes` for every criteria type. Example: `user_tabs` and `user_panes` macros for User criteria type. 270 | 271 | ### Tabs 272 | 273 | Tabs are used to distinguish different criteria types within the template they are used: 274 | 275 | ![Criteria tabs demonstration.](files/images/helper_criteria_tabs_example.png) 276 | 277 | When using tabs, the first one often contains fields/options that are not related to criteria. Then goes criteria tabs. 278 | 279 | In the image above, the first tab contains options for notice. First two tabs in the red box are related to User criteria type. The last one is related to Page criteria type. 280 | 281 | Tabs in `helper_criteria` are grouped under criteria types macros: 282 | 283 | ```html 284 | 285 | 286 | Foo criteria 288 | Foo criteria extra 290 | 291 | 292 |
293 | {$tabs|raw} 294 |
295 | 296 | {$tabs|raw} 297 |
298 |
299 | ``` 300 | 301 | In the code above, `foo` is a criteria type. It has two tabs, one for general foo criteria and another for extra foo criteria. 302 | 303 | ### Panes 304 | 305 | Panes simply contain criteria. 306 | 307 | Just like tabs, panes in `helper_criteria` are grouped under criteria types macros: 308 | 309 | ```html 310 | 311 | 312 |
  • 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 |
  • 325 |
    326 | 327 | 328 |
      329 | {$panes|raw} 330 |
    331 | 332 | {$panes|raw} 333 |
    334 |
    335 | ``` 336 | 337 | ### Using "helper_criteria" 338 | 339 | To use "helper_criteria" functionality, you need to include its macros. 340 | 341 | #### Preparing data 342 | 343 | This section can be skipped if you **don't have** your selected criteria saved somewhere in database or the criteria type you want to use **does't** require any extra data. 344 | 345 | First of all, you need to retrieve saved selected criteria and create a criteria object from them. In this section, we will be using Page criteria as an example: 346 | 347 | ```php 348 | $savedCriteria = /* Retrieve it somehow... */ 349 | 350 | // Criteria object 351 | $criteria = $this->app()->criteria('XF:Page', $savedCriteria)->getCriteriaForTemplate(); 352 | 353 | // Criteria extra data 354 | $criteriaData = $criteria->getExtraTemplateData(); 355 | 356 | $viewParams = [ 357 | /* ... */ 358 | 'criteria' => $criteria, 359 | 'criteriaData' => $criteriaData 360 | ]; 361 | 362 | return $this->view(/* ... */, $viewParams); 363 | ``` 364 | 365 | #### Including without tabs 366 | 367 | To include criteria without tabs you need to use an ` 371 | ``` 372 | 373 | If you don't have saved criteria, you can just pass empty array `{{ [] }}` to an `arg-criteria` attribute. Don't forget to replace `page` in `page_panes` to the name of criteria type you want to use. 374 | 375 | Keep in mind that all criteria is wrapped with `
  • ` tag so you will need to apply some CSS styling (`list-style-type: none;` for example). 376 | 377 | #### With tabs 378 | 379 | In order to use criteria tabs, you will need to organise the page. Stick to the following example structure: 380 | 381 | ```html 382 | 383 |
    384 | 385 | 386 |

    387 | 388 | 389 | Main tab title 390 | 391 | 392 | 393 | 394 |

    395 | 396 | 397 |
      398 | 399 |
    • 400 | 401 |
    • 402 | 403 | 404 | 407 |
    408 | 409 | 410 |
    411 |
    412 | ``` 413 | 414 | Again, if you don't have any saved or even don't suppose to have it, pass `{{ [] }}` to an `arg-criteria` attribute. 415 | 416 | ### Adding custom criteria type to "helper_criteria" 417 | 418 | If you want to add a custom criteria type to `helper_criteira` template, you will need to create a template modification of `helper_criteria` template. 419 | 420 | Go to "Appearance > Template modifications" in ACP, switch to "Admin" tab and hit "Add template modification" button. 421 | 422 | We want to add our tab and pane at the very bottom of the template so switch "Search type" to "Regular expression". 423 | 424 | Type `/$/` in "Find" field. 425 | 426 | Finally, add the tab and the pane macros code in "Replace" field. Example: 427 | 428 | ```html 429 | 430 | 431 | Foo criteria 433 | Foo criteria extra 435 | 436 | 437 |
    438 | {$tabs|raw} 439 |
    440 | 441 | {$tabs|raw} 442 |
    443 |
    444 | 445 | 446 | 447 |
  • 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 |
  • 460 | 461 | 462 | 463 |
      464 | {$panes|raw} 465 |
    466 | 467 | {$panes|raw} 468 |
    469 | 470 | ``` 471 | 472 | Now, you can use your criteria everywhere (see ["Using helper_criteria"](#using-helper_criteria)). 473 | 474 | ## Custom User/Page criterion example 475 | 476 | Let's say we want to create a criterion for checking whether our user has X or more likes on single message or not. 477 | 478 | Since our criterion refers to user, we will be creating a criterion which belongs to User criteria. 479 | 480 | ### Adding template modification 481 | 482 | First of all, we need to add our criterion to User criteria list. Go to "Template modifications" page in ACP, select "Admin" tab and hit "Add template modification" button in the upper right corner. 483 | 484 | !!! warning 485 | If there is no "Admin" tab make sure you have enabled the [development mode](development-tools.md#enabling-development-mode)! 486 | 487 | We will be modifying the `helper_criteria` template so write it to the "Template" field. In this example I will be using `likes_on_single_message` "Modification key" for this template modification. 488 | 489 | Our criterion is about likes on messages. This means it should be under "Content and achievements" section. This means we simply need to find `` and replace it with the following code: 490 | 491 | ```html 492 | 493 | 494 | 495 | 496 | $0 497 | ``` 498 | 499 | From this moment we can already see and even set a value for our criterion when creating trophies, notices and user-group promotions. 500 | 501 | ### Adding code event listener 502 | 503 | We have created our criterion. But it is unknown for XenForo, which will always return `false` when matching such criteria. We need to tell XenForo, what to do when it meets unknown criteria. 504 | 505 | Go to "Development > Code event listener" page and hit "Add code event listener" button. 506 | 507 | Select `criteria_user` in "Listen to event" field (`user` because our criterion belongs to User criteria). In "Execute callback" field we should specify class and method to be called when matching criteria. 508 | 509 | Create a file `Listener.php` in addon root folder if you haven't already and add a new method `criteriaUser` there: 510 | 511 | ```php 512 | fetchOne($query, [$user->user_id]); 570 | 571 | // Checking that we have a result from database (we do expect a number) 572 | if (is_int($likes)) { 573 | // Returning true if user has a message with X or more likes or false if he has not 574 | $returnValue = ($likes >= $data['likes']); 575 | } else { 576 | $returnValue = false; 577 | } 578 | 579 | break; 580 | } 581 | } 582 | ``` 583 | 584 | Pay attention to the following: 585 | 586 | - We are using `$user` variable for retrieving currently matching user. We can use this variable since our criterion belongs to **User** criteria. 587 | - We can access data via `$data` array. It contains data from fields [we have added](#adding-template-modification) in template modification. We have only added one ` Cron entries" and run "Update user trophies" cron by hitting arrows-circle button. 598 | 599 | !["All for one" trophy awarded notification.](files/images/example-custom-criteria-awarded.png) 600 | 601 | Nice! 602 | 603 | !!! warning 604 | If you are not awarded with "All for one" trophy, try to sign out, sign in and re-running "Update user trophies" cron. 605 | 606 | ### Testing (notice) 607 | 608 | Go to "Communication > Notices" and hit "Add notice" button. On "User criteria" tab, set "Likes on single message" field with, again, 5. Save the notice. 609 | 610 | Next, create a test message somewhere on you forum and then like it five times with five different users (or just set manually set a value of `likes` column). 611 | 612 | Now, you should see a notice: 613 | 614 | ![Notice demonstration.](files/images/example-custom-criteria-notice.png) 615 | 616 | You can [download](files/example-sources/all-for-one-criterion-2.0.10.zip) addon sources built based on this example (2.0.10). 617 | 618 | ## Custom criteria type example 619 | 620 | Imagine we are creating an addon (addon ID: `PostsRemover`) for removing all posts that match selected criteria. A list of available criteria: 621 | 622 | - Post has at least X likes 623 | - Post author has an X username 624 | - Post was edited at least X times 625 | - Post was edited no more than X times 626 | - Post was published before X 627 | - Post was published after X 628 | 629 | Obviously, for such criteria we need a new criteria type: Post criteria. 630 | 631 | ### Criteria type class 632 | 633 | We should start by creating a new class `Post` that inherits `AbstractCriteria` within `Criteria` directory of our addon: 634 | 635 | ```php 636 | likes && $post->likes >= $data['likes']); 663 | } 664 | 665 | // Post author has an X username 666 | protected function _matchUsername(array $data, \XF\Entity\Post $post) 667 | { 668 | return $post->username === $data['name']; 669 | } 670 | 671 | // Post was edited at least X times 672 | protected function _matchEditedCount(array $data, \XF\Entity\Post $post) 673 | { 674 | return $post->edit_count && $post->edit_count >= $data['count']; 675 | } 676 | 677 | /* ================ Handling other criteria ================ */ 678 | } 679 | ``` 680 | 681 | `isMatched(...)` method used to call `_match` methods we just created accepts only User entity, we are to write a custom variation of `isMatched()`, `isUnknownMatched()` and `isSpecialMatched()` methods. 682 | 683 | Since we are creating Post criteria, we need to create our own `isMatchedPost()` method: 684 | 685 | ```php 686 | public function isMatchedPost(\XF\Entity\Post $post) 687 | { 688 | if (!$this->criteria) 689 | { 690 | return $this->matchOnEmpty; 691 | } 692 | 693 | foreach ($this->criteria AS $criterion) 694 | { 695 | $rule = $criterion['rule']; 696 | $data = $criterion['data']; 697 | 698 | $specialResult = $this->isSpecialMatchedPost($rule, $data, $post); 699 | if ($specialResult === false) 700 | { 701 | return false; 702 | } 703 | else if ($specialResult === true) 704 | { 705 | continue; 706 | } 707 | 708 | $method = '_match' . \XF\Util\Php::camelCase($rule); 709 | if (method_exists($this, $method)) 710 | { 711 | $result = $this->$method($data, $post); 712 | if (!$result) 713 | { 714 | return false; 715 | } 716 | } 717 | else 718 | { 719 | if (!$this->isUnknownMatched($rule, $data, $post)) 720 | { 721 | return false; 722 | } 723 | } 724 | } 725 | 726 | return true; 727 | } 728 | 729 | protected function isSpecialMatchedPost($rule, array $data, \XF\Entity\Post $post) 730 | { 731 | return null; 732 | } 733 | 734 | protected function isUnknownMatchedPost($rule, array $data, \XF\Entity\Post $post) 735 | { 736 | return false; 737 | } 738 | ``` 739 | 740 | We simply used `isMatched(...)` method code replacing `$user` variable of User entity type with `$post` variable of Post entity type. 741 | 742 | As we do not plan to handle special and unknown criteria we return null in `isSpecialMatchedPost` and `false` in `isUnknownMathcedPost` methods. 743 | 744 | 745 | ### Template 746 | 747 | Leaving the process of adding an admin route, writing a controller and doing other actions behind the scenes, let's jump right to our page's template code: 748 | 749 | ```html 750 | Posts Remover 751 | 752 | 753 |
    754 | 755 | 756 | 757 | 758 | 759 | 760 | 761 | 762 | 763 | 764 | 765 | 766 | 767 | 768 | 769 | 770 | 771 | 772 | 773 |
    774 |
    775 | ``` 776 | 777 | ### Matching the criteria 778 | 779 | In the controller of our page, we need to create a method called `actionRemove` for handling "Remove" button click: 780 | 781 | ```php 782 | public function actionRemove() 783 | { 784 | } 785 | ``` 786 | 787 | Firstly, let's retrieve `post_criteria` array from page form: 788 | 789 | ```php 790 | public function actionRemove() 791 | { 792 | $postCriteriaInput = $this->filter('post_criteria', 'array'); 793 | } 794 | ``` 795 | 796 | Secondly, we need to create a criteria object from retrieved page form data: 797 | 798 | ```php 799 | public function actionRemove() 800 | { 801 | $postCriteriaInput = $this->filter('post_criteria', 'array'); 802 | 803 | /** @var \PostsRemover\Criteria\Post $postCriteria */ 804 | $postCriteria = $this->app()->criteria('PostsRemover:Post', $postCriteriaInput); 805 | } 806 | ``` 807 | 808 | By default, out post **will match** the empty criteria (when nothing has been selected) which will result in deletion of all forum posts. To avoid this we need to manually set the result of matching the empty criteria via `setMatchOnEmpty()` method: 809 | 810 | ```php 811 | public function actionRemove() 812 | { 813 | $postCriteriaInput = $this->filter('post_criteria', 'array'); 814 | 815 | /** @var \PostsRemover\Criteria\Post $postCriteria */ 816 | $postCriteria = $this->app()->criteria('PostsRemover:Post', $postCriteriaInput); 817 | 818 | $postCriteria->setMatchOnEmpty(false); // If no criteria selected, nothing will be removed 819 | } 820 | ``` 821 | 822 | Finally, we need to match all forum posts against selected criteria. If the post matches the criteria, we will delete it: 823 | 824 | ```php 825 | public function actionRemove() 826 | { 827 | $postCriteriaInput = $this->filter('post_criteria', 'array'); 828 | 829 | /** @var \PostsRemover\Criteria\Post $postCriteria */ 830 | $postCriteria = $this->app()->criteria('PostsRemover:Post', $postCriteriaInput); 831 | 832 | $postCriteria->setMatchOnEmpty(false); // If no criteria selected, nothing will be removed 833 | 834 | // Getting all forum posts 835 | $posts = $this->finder('XF:Post')->fetch(); 836 | 837 | $deletedCounter = 0; 838 | 839 | /** @var \XF\Entity\Post $post */ 840 | foreach ($posts as $post) 841 | { 842 | if ($postCriteria->isMatchedPost($post)) // Checking the post against selected criteria 843 | { 844 | $post->delete(); // Deleting it if the post matches the selected criteria 845 | $deletedCounter++; 846 | } 847 | } 848 | 849 | return $this->message('Done! ' . $deletedCounter . ' posts were removed!'); 850 | } 851 | ``` 852 | 853 | !!! note 854 | Keep in mind that we use `isMatchedPost($post)` method for XenForo versions below 2.1! 855 | 856 | !!! warning 857 | It is generally a bad practice to retrieve all entities from database at once (`$this->finder('XF:Post')->fetch();` in the code above). There could be millions of forum posts and selecting them all at once is going to be a very long process, which might end up with an error. 858 | Consider using a Job system for working with dozens (100+) of database items. 859 | 860 | ### Testing 861 | 862 | Time to test our custom criteria type! 863 | 864 | I have created three posts on my test forum. The first one was liked 500 times, the second one was edited 5 times. The third one is just an ordinary untouched post without likes. 865 | 866 | ![Before deleting demonstration.](files/images/example-custom-criteria-type-messages-before.png) 867 | 868 | Now, on our "Posts Remover" ACP page, let's select "Post has at least X likes" (with value of 250) and "Post was edited at least X times" (wih value of 5): 869 | 870 | ![Selected criteria.](files/images/example-custom-criteria-type-remover.png) 871 | 872 | When I hit "Delete" button, I saw a flash message telling me that nothing was deleted. Why? Obviously, because there are no posts with at least 250 likes and at least 5 edits **in the same time**. 873 | 874 | That is why we need to select the first criterion only, then hit "Delete". This will delete a post with 500 likes. Next, we need to select the last criterion only and preform deletion. The post with 5 edits will be removed. 875 | 876 | As a result, only one test post survived out test: 877 | 878 | ![After deleting demonstration.](files/images/example-custom-criteria-type-messages-after.png) 879 | 880 | You can [download](files/example-sources/posts-remover-2.0.10.zip) addon sources built based on this example (2.0.10). You will find "Posts Remover" ACP page under "Tools" section. 881 | -------------------------------------------------------------------------------- /documentation/DesigningStyles.md: -------------------------------------------------------------------------------- 1 | # Проектирование стилей 2 | В XF2 мы ввели совершенно новый способ построения и редактирования стилей под названием "Режим дизайнера". Режим дизайнера - это набор CLI инструментов, которые позволяют изменять определенные шаблоны в стиле непосредственно в файловой системе. Он также выводит различные метаданные и информацию о свойствах стиля, что полезно для управления версиями и совместной работой. 3 | 4 | ## Включение режима дизайнера 5 | Первый шаг к включению режима дизайнера - внести в `config.php` запись: 6 | ```php 7 | $config['designer']['enabled'] = true; 8 | ``` 9 | При необходимости можно указать другой путь для файлов режима дизайнера, которые должны существовать в файловой системе. Ниже представлено расположение по умолчанию. Чтобы изменить расположение, добавьте в `config.php` следующую запись и соответственно измените путь: 10 | ```php 11 | $config['designer']['basePath'] = 'src/styles'; 12 | ``` 13 | 14 | ## Включение режима дизайнера для стиля 15 | Режим дизайнера должен быть явно включен для каждого стиля. Чтобы включить режим дизайнера для стиля, используйте следующую CLI команду и укажите идентификатор стиля, и выберите "ID режима дизайнера": 16 | ```bash 17 | php cmd.php xf-designer:enable [style_id] [designer_mode_id] 18 | ``` 19 | ID режима дизайнера - это идентификатор, который будет использоваться для будущих команд, связанных с режимом дизайнера. После включения текущие измененные компоненты стиля будут экспортированы в директории `[basePath]/[designer_mode_id]`. 20 | 21 | При включении режима дизайнера для этого стиля, если этот каталог уже существует, вам будет предложено выбрать, следует ли перезаписать текущее содержимое этого каталога из стиля или следует перезаписать текущий стиль из текущего содержимого этого каталога. 22 | 23 | ## Отключение режима дизайнера для стиля 24 | Чтобы отключить режим дизайнера для стиля, просто выполните следующую CLI команду: 25 | ```bash 26 | php cmd.php xf-designer:disable [designer_mode_id] 27 | ``` 28 | Чтобы удалить данные, можно выполнить ту же команду с параметром `--clear`: 29 | ```bash 30 | php cmd.php xf-designer:disable [designer_mode_id] --clear 31 | ``` 32 | 33 | ## Что выводится и где? 34 | Важно помнить, что стиль в XF состоит только из того, что *изменяется в этом стиле*. Это означает, что выходные данные режима дизайнера будут состоять только из того, что было изменено в этом стиле. Шаблоны и свойства стиля, измененные в Родительском стиле, не выводятся. 35 | 36 | ### Шаблоны 37 | Шаблоны стиля будут находится в директории `[basePath]/[designer_mode_id]/templates`. В этой директории вы можете видеть другие каталоги для каждого типа (например, admin, email и public). 38 | 39 | Шаблоны будут выводиться в формате HTML и доступны для непосредственного редактирования в файловой системе. Изменения, внесенные в файловой системе, импортируются и компилируются при загрузке шаблона на страницу. Аналогично, вы можете вернуть шаблон, удалив его из файловой системы (если он был ранее изменен). 40 | 41 | ### Группы и параметры стилей 42 | Параметры стиля и группы будут находится в директориях `[basePath]/[designer_mode_id]/style_properties` и `[basePath]/[designer_mode_id]/style_property_groups` соответсвенно. Они экспортируются в формате JSON и служат полезным способом для отслеживания изменений в этих файлах с помощью системы управления версиями. 43 | 44 | Не рекомендуется изменять эти файлы напрямую, так как изменения в них *не* будут импортироваться автоматически, как в шаблонах. 45 | 46 | ## Изменение конкретного шаблона 47 | Принимая во внимание, что стиль представляет собой компоненты, которые изменяются только в этом стиле, когда включен режим дизайнера, файловая система также будет содержать только компоненты, которые изменяются только в этом стиле. Невозможно будет вывести эффективную версию каждого шаблона и свойства стиля. 48 | 49 | Чтобы пометить шаблон как измененный в стиле, вы можете сделать это обычным способом, отредактировав его в админ-панели. Шаблоны и свойства стиля, измененные в админ-панели, будут автоматически записаны в файловую систему, если включен режим дизайнера. Однако, было бы более удобно модифицировать "коснуться" шаблона с помощью CLI команды: 50 | ```bash 51 | php cmd.php xf-designer:touch_template [designer_mode_id] [template_type:template_title] 52 | ``` 53 | Пока указанный шаблон существует в Родительском или главном стиле, он будет скопирован в текущий стиль и выведен в файловую систему. 54 | 55 | Если вы хотите создать новый шаблон в своем стиле (который не существует в любом другом стиле в дереве), можно использовать ту же команду, но нужно просто передать параметр `--custom`: 56 | ```bash 57 | php cmd.php xf-designer:touch_template [designer_mode_id] [template_type:template_title] --custom 58 | ``` 59 | 60 | ## Другие полезные команды 61 | Существует ряд других полезных команд, связанных с режимом дизайнера: 62 | 63 | ### Экспорт в базу данных 64 | Эта команда обычно выполняется автоматически, если для стиля включен режим дизайнера, но если по какой-то причине вы хотите перезаписать копию файловой системы тем, что в настоящее время находится в базе данных, можно выполнить следующую команду: 65 | ```bash 66 | php cmd.php xf-designer:export [designer_mode_id] 67 | ``` 68 | Также можно экспортировать только определенные типы, например, `xf-designer:export-templates`. 69 | 70 | ### Импорт в файловую систему 71 | Эта команда перезапишет копию базы данных стиля тем, что находится в файловой системе: 72 | ```bash 73 | php cmd.php xf-designer:import [designer_mode_id] 74 | ``` 75 | Также можно экспортировать только определенные типы, например, `xf-designer:import-templates`. 76 | 77 | ### Синхронизация шаблонов 78 | Эта команда аналогична импорту шаблонов (см. выше), но вместо перезаписи всего она будет импортировать только шаблоны и перекомпилировать их, если метаданные изменились. Он также будет применять обновления номера версии соответственно. 79 | ```bash 80 | php cmd.php xf-designer:sync-templates [designer_mode_id] 81 | ``` 82 | 83 | ### Обратный шаблон 84 | Эту команду можно использовать для возврата шаблона, фактически удалив пользовательскую версию из текущего стиля. 85 | ```bash 86 | php cmd.php xf-designer:revert-template [designer_mode_id] [template_type:template_title] 87 | ``` 88 | Также можно инициировать возврат, удалив шаблон из файловой системы. -------------------------------------------------------------------------------- /documentation/DevelopmentTools.md: -------------------------------------------------------------------------------- 1 | # Инструменты разработки 2 | XF2 предоставляет разработчикам ряд встроенных инструментов, которые вы можете использовать для ускорения разработки плагинов, и мы рассмотрим некоторые из них ниже. 3 | 4 | ## Режим отладки 5 | Режим отладки может быть включен в вашем `config.php`, который позволит вам получить доступ к определенным инструментам разработки в ACP (например, создавать маршруты, разрешения, навигацию ACP и т.д.), также в нижней части каждой страницы будет отображена отладочная информация в которой подробно описывается, сколько времени потребовалось для обработки страницы, сколько запросов было выполнено для отображения страницы и количества памяти. При наведении на шестеренку, будет показана информация о текущем контроллере, экшене и названии шаблона. Вы также можете нажать на время, и это даст вам подробный обзор того, какие запросы выполнялись, и трассировка стека, которая привела к выполнению этого запроса. 6 | 7 | Чтобы включить режим отладки, добавьте следующую строку в `config.php`: 8 | 9 | $config['debug'] = true; 10 | 11 | ## Включение режима разработки 12 | Режим разработки - это специальный режим, который включается в файле `config.php`, в этом режиме XF автоматически будет записывать ваши действия при разработке в ваш каталог` _output`. Чтобы редактировать шаблоны прямо в файловой системе, нужно включить этот режим. Поскольку режим разработки будет записывать файлы в вашу файловую систему, важно убедиться, что у вас есть соответствующие права доступа к файлам. Это может варьироваться в зависимости от среды, но в большинстве случаев достаточно убедится, что для любого плагина, над которым вы работаете, у вас есть _output-каталог и установленный chmod на `0777`. Например, если вы работаете над плагином с идентификатором «Demo», его вывод разработки будет выписан в `src/addons/Demo/_output`, и поэтому этот каталог должен быть полностью доступен для записи. 13 | 14 | Включение режима разработки автоматически включает [режим отладки](/documentation/DevelopmentTools.md#part1). 15 | 16 | Чтобы включить режим разработки, добавьте следующие строки в файл `config.php`: 17 | 18 | $config['development']['enabled'] = true; 19 | $config['development']['defaultAddOn'] = 'SomeCompany/MyAddOn'; 20 | 21 | Значение `defaultAddOn` является необязательным, но если добавить этот параметр, при создании нового контента в ACP, он будет автоматически связан с указанным дополнением . 22 | 23 | В дополнение к вышесказанному, вам может потребоваться добавить дополнительную конфигурацию, особенно если вы используете более одной установки XF. 24 | 25 | $config['enableMail'] = false; 26 | 27 | Это отключит отправку всей почты с вашей установки XF. Это особенно важно, если вы используете копию живых данных с реальными пользователями и реальными адресами электронной почты (не советуем так делать!). 28 | 29 | В качестве альтернативы отключению почты напрямую вы можете по желанию использовать сервис [MailTrap.io](https://mailtrap.io/). Он дает вам бесплатный почтовый ящик, который будет получать все электронные письма, отправленные с вашей установки XF, что очень полезно для тестирования любых электронных писем, которые может отправлять ваше новое дополнение. 30 | 31 | $config['cookie']['prefix'] = 'anything_'; 32 | 33 | Если вы используете две или более установки XF на одном домене, могут возникнуть проблемы с перезаписыванием файлов cookie, из-за использования несколькими установками одинаковых префиксов cookie. Поэтому рекомендуется убедиться, что вы изменили префикс cookie для каждой установленной вами установки XF. Без этого вы столкнетесь с проблемами, например, при выходе из одной установки XF при входе в другую. 34 | 35 | ## Команды разработки 36 | XF 2.0 поставляется с несколькими стандартными командами CLI для плагинов, которые направлены на то, чтобы ускорить процесс разработки, возможно, автоматизировать/запустить некоторые обычные процессы. 37 | 38 | В этом разделе мы рассмотрим некоторые инструменты и объясним, что они делают. 39 | 40 | ## Дополнительные команды плагинов 41 | ### Создание нового плагина 42 | $ php cmd.php xf-addon:create 43 | 44 | Команда `xf-addon:create` позволяет создать и настроить новое дополнение. Вам следует ответить на несколько основных вопросов: 45 | 46 | * Введите идентификатор (ID) этого дополнения 47 | * Введите название 48 | * Введите идентификатор версии (ID) (т.е. 1000010) 49 | * Введите строку версии (т.е. 1.0.0 Alpha) 50 | 51 | Затем вам будет предоставлена возможность создать плагин и файл addon.json в его каталоге и заданы некоторые вопросы о том, хотите ли вы добавить файл Setup.php. 52 | 53 | ### Экспорт .XML файлов _data 54 | $ php cmd.php xf-addon:export [addon_id] 55 | 56 | Эта команда предназначена для экспорта всех данных вашего плагина в XML-файлы внутри каталога `_data`. Он экспортирует данные из того, что в настоящее время находится в базе данных (а не из файлов вывода разработки). 57 | 58 | ### Повышение версии плагина 59 | $ php cmd.php xf-addon:bump-version [addon_id] --version-id 1020370 --version-string 1.2.3 60 | 61 | Эта команда позволяет изменить идентификатор и строку версии без необходимости обновления плагина. Вышеуказанные опции являются необязательными, и если они не предоставлены, вам будет предложено указать их. Если вы укажете только идентификатор версии, мы попытаемся вывести правильную строку версии из этого автоматически, если она соответствует нашему [рекомендуемому формату версии](/documentation/AddOnStructure.md#part2). По завершению выполнения команда автоматически обновляет файл `addon.json` и базу данных правильной информацией о версии. 62 | 63 | ### Синхронизация addon.json с базой данных 64 | $ php cmd.php xf-addon:sync-json [addon_id] 65 | 66 | Иногда вы можете изменить некоторые параметры в JSON файле напрямую. Это может быть версия, или новый значок, или новое название или описание. Таким образом, изменение JSON может привести к тому, что система плагинов будет думать, что есть ожидающие изменения или что плагин можно обновить. Восстановление или обновление могут вызвать проблемы (например, потеря данных), если вы еще не экспортировали свои текущие данные. Поэтому выполнение этой команды рекомендуется как способ импорта этих данных, не затрагивая ваши существующие данные. 67 | 68 | ### Проверка файла addon.json 69 | $ php cmd.php xf-addon:validate-json [addon_id] 70 | 71 | Эта команда позволяет вам проверить, что ваш JSON-файл содержит правильное содержимое и в правильном формате. Валидатор проверяет, может ли контент быть декодирован, что он содержит все необходимые поля (например, название и идентификатор версии), а также проверяет наличие дополнительных ключей (например, описание и значок). Если какие-либо ключи отсутствуют, вам будут предложены исправления. Мы также проверяем, есть ли какие-либо неожиданные поля в файле JSON. Они могут быть преднамеренными или представлять опечатки. Вы можете запустить команду вручную, или команда будет запущена автоматически при создании вашей версии. 72 | 73 | ### Запустить отдельный шаг установки 74 | Иногда бывает полезно проверить правильность шагов вашего класса установки, не выполняя процесс удаления и переустановки. 75 | 76 | Есть три команды, которые помогают в этом. Эти команды будут работать только с классами Setup, которые созданы с использованием стандартных параметров `StepRunner`. 77 | 78 | #### Запустить шаг установки 79 | $ php cmd.php xf-addon:install-step [addon_id] [step] 80 | 81 | #### Запустить шаг обновления 82 | $ php cmd.php xf-addon:upgrade-step [addon_id] [version] [step] 83 | 84 | #### Запустить шаг удаления 85 | $ php cmd.php xf-addon:uninstall-step [addon_id] [step] 86 | 87 | ## Выпуск сборки плагина 88 | После того, как все тяжелые работы были выполнены, стыдно проходить через ряд других процессов, прежде чем вы сможете их освободиться. Даже процесс сбора всех файлов в нужном месте и создания ZIP файла вручную может занять много времени и подвержен ошибкам. Мы можем позаботиться об этом автоматически, включая создание файла hashes.json, с помощью одной простой команды. 89 | 90 | $ php cmd.php xf-addon:build-release [addon_id] 91 | 92 | Когда вы запустите эту команду, сначала запустся команда «xf-addon:export», а затем соберет все ваши файлы во временный каталог `_build` и запишет их в ZIP-файл. Готовый ZIP также будет содержать файл `hashes.json`. Как только ZIP будет создан, он будет сохранен в вашем каталоге `_releases` и называется ` - .zip`. 93 | 94 | ### Расширенный процесс сборки 95 | Помимо создания ZIP-файла выпуска могут быть дополнительные файлы, которые вы хотите включить в свои ZIP-архивы, другие более сложные процессы сборки, которые вы хотите запустить, например, для минимизации или конкатенации JS или выполнения определенных команд оболочки. Обо всем этом можно позаботиться в файле `build.json`. Это типичный файл `build.json`: 96 | ```json 97 | { 98 | "additional_files": [ 99 | "js/demo/portal" 100 | ], 101 | "minify": [ 102 | "js/demo/portal/a.js", 103 | "js/demo/portal/b.js" 104 | ], 105 | "rollup": { 106 | "js/demo/portal/ab-rollup.js": [ 107 | "js/demo/portal/a.min.js", 108 | "js/demo/portal/b.min.js" 109 | ] 110 | }, 111 | "exec": [ 112 | "echo '{title} version {version_string} ({version_id}) has been built successfully!' > 'src/addons/Demo/Portal/_build/built.txt'" 113 | ] 114 | } 115 | ``` 116 | Если у вас есть компоненты, такие как JavaScript, которые хранятся вне вашего каталога плагина, вы можете сообщить процессу сборки для копирования файлов или каталогов с помощью массива `additional_files` в` build.json`. Во время разработки не всегда возможно хранить файлы вне вашего каталога плагина, поэтому, вы можете хранить файлы в своем каталоге плагина `_files`. При копировании дополнительных файлов мы сначала проверим их. 117 | 118 | Если вы отправляете некоторые JS-файлы с плагином, возможно, вы захотите минифицировать эти файлы по соображениям производительности. Вы можете указать, какие файлы вы хотите минимизировать прямо внутри `build.json`. Вы можете перечислить их в виде массива или просто указать `*`, который минифицирует все в вашем каталоге js, если по этому пути будут JS-файлы, после копирования дополнительных файлов в сборку. Все минифицированные файлы, будут иметь суффикс `.min.js` вместо` .js`, и исходные файлы так же останутся в пакете. 119 | 120 | Вы можете сгрупировать несколько JS-файлов в один файл. Если вы захотите сделать это, используйте массив `rollup`. Ключ - это имя конечного файла, а элементы внутри этого массива - это пути к файлам JS, которые будут объединены. 121 | 122 | Наконец, у вас могут быть определенные процессы, которые необходимо запустить непосредственно перед тем, как пакет будет построен и завершен. Это может быть что угодно. Если это команда, которую можно запустить из оболочки (включая скрипты PHP), вы можете указать ее здесь. Вышеприведенный пример, конечно, бесполезен, но, по крайней мере, демонстрирует, что можно использовать некоторые переменные. Эти переменные заменяются скалярными значениями, которые вы можете получить из объекта `XF\AddOn\AddOn`, который обычно представляет собой любое значение, доступное в файле` addon.json`, или сущности `AddOn`. 123 | 124 | ## Команды разработки 125 | На данный момент существует несколько команд, связанных с разработкой, но здесь рассматриваются только две самые важные. 126 | 127 | Чтобы использовать эти команды, обязательно нужно включить [режим разработки](/documentation/DevelopmentTools.md#part2). 128 | 129 | > **Предупреждение** 130 | > Обе команды могут привести к потере данных по причине рассинхронизации базы данных и директории `_output`. Мы рекомендуем использовать СКВ (Систему Контроля Версий), к примеру [GitHub](https://github.com/) чтобы смягчить последствия, в случае подобной ситуации. 131 | 132 | ### Импорт вывода разработки 133 | $ php cmd.php xf-dev:import --addon [addon_id] 134 | 135 | Выполнение этой команды будет импортировать все выходные файлы разработки из вашего дополнительного каталога `_output` в базу данных. 136 | 137 | ### Экспорт вывода разработки 138 | $ php cmd.php xf-dev:export --addon [addon_id] 139 | 140 | Эта команда экспортирует данные вашего плагина из базы данных в каталог `_output`. 141 | 142 | ## Отладка кода 143 | Для работы с XF2 вы можете настроить ваш любимый инструмент отладчика (XDebug, Zend Debugger и т.д.). Хотя, иногда, отладка кода может быть столь же элементарной, как просто быстро увидеть, какое значение (или тип значения) переменная держит в данный момент времени 144 | 145 | ### Отладка переменной 146 | PHP, конечно, имеет встроенный инструмент для отладки. Вероятно, вы знаете его как `var_dump()`. XF поставляется с двумя заменами этого инструмента: 147 | ```php 148 | \XF::dump($var); 149 | \XF::dumpSimple($var); 150 | ``` 151 | Вариант `Simple` просто выгружает значение переменной в виде обычного текста. Например, если вы используете его для просмотра значения массива, в верхней части страниц вы увидите такой вывод: 152 | ``` 153 | array(2) { 154 | ["user_id"] => int(1) 155 | ["username"] => string(5) "Admin" 156 | } 157 | ``` 158 | Фактически, этот инструмент делает то же, что и стандартный var_dump, но его вывод слегка изменен для удобочитаемости и обернутый внутри тегов `
    `, чтобы обеспечить сохранение пробелов при рендеринге.
    159 | 
    160 | Альтернативой на самом деле является компонент VarDumper из проекта Symfony. Он выводит HTML, CSS и JS для создания гораздо более функционального и потенциально более легкого для чтения вывода. Он позволяет свернуть определенные разделы, а для определенных значений, которые могут выводить значительный объем данных, таких как объекты, он может автоматически свернуть эти разделы.
    161 | 
    
    
    --------------------------------------------------------------------------------
    /documentation/EntitiesFindersAndRepositories.md:
    --------------------------------------------------------------------------------
      1 | # Сущности, Поисковики и Репозитории
      2 | There are a number of ways to interact with data within XF2. In XF1 this was mostly geared towards writing out raw SQL statements inside Model files. The approach in XF2 has moved away from this, and we have added a number of new ways in its place. We'll first look at the preferred method for performing database queries - the finder.
      3 | 
      4 | ## Поисковики
      5 | 
      6 | We have introduced a new "Finder" system which allows queries to be built up programmatically in a object oriented way so that raw database queries do not need to be written. The Finder system works hand in hand with the Entity system, which we talk about in more detail below. The first argument passed into the finder method is the short class name for the Entity you want to work with. Let's just convert some of the queries mentioned in the section above to use the Finder system instead. For example, to access a single user record:
      7 | 
      8 | ```php
      9 | $finder = \XF::finder('XF:User');
     10 | $user = $finder->where('user_id', 1)->fetchOne();
     11 | ```
     12 | 
     13 | One of the main differences between the direct query approach and using the Finder is that the base unit of data returned by the Finder is not an array. In the case of a Finder object which calls the `fetchOne` method (which only returns a single row from the database), a single Entity object will be returned.
     14 | 
     15 | Let's look at a slightly different approach which will return multiple rows:
     16 | 
     17 | ```php
     18 | $finder = \XF::finder('XF:User');
     19 | $users = $finder->limit(10)->fetch();
     20 | ```
     21 | 
     22 | This example will query 10 records from the xf_user table, and it will return them as an `ArrayCollection` object. This is a special object which acts similarly to an array, in that it is traversable (you can loop through it) and it has some special methods that can tell you the total number of entries it has, grouping by certain values, or other array like operations such as filtering, merging, getting the first or last entry etc.
     23 | 
     24 | Finder queries generally should be expected to retrieve all columns from a table, so there's no specific equivalent to fetch only certain values certain columns.
     25 | 
     26 | Instead, to get a single value, you would just fetch one entity and read the value directly from that:
     27 | 
     28 | ```php
     29 | $finder = \XF::finder('XF:User');
     30 | $username = $finder->where('user_id', 1)->fetchOne()->username;
     31 | ```
     32 | 
     33 | Similarly, to get an array of values from a single column, you can use the `pluckFrom` method:
     34 | 
     35 | ```php
     36 | $finder = \XF::finder('XF:User');
     37 | $usernames = $finder->limit(10)->pluckFrom('username')->fetch();
     38 | ```
     39 | 
     40 | So far we've seen the Finder apply somewhat simple where and limit constraints. So let's look at the Finder in more detail, including a bit more detail about the `where` method itself.
     41 | 
     42 | 
     43 | ### Метод where
     44 | 
     45 | The `where` method can support up to three arguments. The first being the condition itself, e.g. the column you are querying. The second would ordinarily be the operator. The third is the value being searched for. If you supply only two arguments, as you have seen above, then it automatically implies the operator is `=`. Below is a list of the other operators which are valid:
     46 | 
     47 | * `=`
     48 | * `<>`
     49 | * `!=`
     50 | * `>`
     51 | * `>=`
     52 | * `<`
     53 | * `<=`
     54 | * `LIKE`
     55 | * `BETWEEN`
     56 | 
     57 | So, we could get a list of the valid users who registered in the last 7 days:
     58 | 
     59 | ```php
     60 | $finder = \XF::finder('XF:User');
     61 | $users = $finder->where('user_state', 'valid')->where('register_date', '>=', time() - 86400 * 7)->fetch();
     62 | ```
     63 | 
     64 | As you can see you can call the `where` method as many times as you like, but in addition to that, you can choose to pass in an array as the only argument of the method, and build up your conditions in a single call. The array method supports two types, both of which we can use on the query we built above:
     65 | 
     66 | ```php
     67 | $finder = \XF::finder('XF:User');
     68 | $users = $finder->where([
     69 |     'user_state' => 'valid',
     70 |     ['register_date', '>=', time() - 86400 * 7]
     71 | ])
     72 | ->fetch();
     73 | ```
     74 | 
     75 | It wouldn't usually be recommended or clear to mix the usage like this, but it does demonstrate the flexibility of the method somewhat. Now that the conditions are in an array, we can either specify the column name (as the array key) and value for an implied `=` operator or we can actually define another array containing the column, operator and value.
     76 | 
     77 | 
     78 | ### Метод whereOr
     79 | 
     80 | With the above examples, both conditions need to be met, i.e. each condition is joined by the `AND` operator. However, sometimes it is necessary to only meet part of your condition, and this is possible by using the `whereOr` method. For example, if you wanted to search for users who are either not valid or have posted zero messages, you can build that as follows:
     81 | 
     82 | ```php
     83 | $finder = \XF::finder('XF:User');
     84 | $users = $finder->whereOr(
     85 |     ['user_state', '<>', 'valid'],
     86 |     ['message_count', 0]
     87 | )->fetch();
     88 | ```
     89 | 
     90 | Similar to the example in the previous section, as well as passing up to two conditions as separate arguments, you can also just pass an array of conditions to the first argument:
     91 | 
     92 | ```php
     93 | $finder = \XF::finder('XF:User');
     94 | $users = $finder->whereOr([
     95 |     ['user_state', '<>', 'valid'],
     96 |     ['message_count', 0],
     97 |     ['is_banned', 1]
     98 | ])->fetch();
     99 | ```
    100 | 
    101 | ### Метод with
    102 | 
    103 | The `with` method is essentially equivalent to using the `INNER|LEFT JOIN` syntax, though it relies upon the Entity having had its "Relations" defined. We won't go into that until the next page, but this should just give you an understanding of how it works. Let's now use the Thread finder to retrieve a specific thread:
    104 | 
    105 | ```php
    106 | $finder = \XF::finder('XF:Thread');
    107 | $thread = $finder->with('Forum', true)->where('thread_id', 123)->fetchOne();
    108 | ```
    109 | 
    110 | This query will fetch the Thread entity where the `thread_id = 123` but it will also do a join with the xf_forum table, behind the scenes. In terms of controlling how to do an `INNER JOIN` rather than a `LEFT JOIN`, that is what the second argument is for. In this case we've set the "must exist" argument to true, so it will flip the join syntax to using `INNER` rather than the default `LEFT`.
    111 | 
    112 | We'll go into more detail about how to access the data fetched from this join in the next section.
    113 | 
    114 | It's also possible to pass an array of relations into the `with` method to do multiple joins.
    115 | 
    116 | ```php
    117 | $finder = \XF::finder('XF:Thread');
    118 | $thread = $finder->with(['Forum', 'User'], true)->where('thread_id', 123)->fetchOne();
    119 | ```
    120 | 
    121 | This would join to the xf_user table to get the thread author too. However, with the second argument there still being `true`, we might not need to do an `INNER` join for the user join, so, we could just chain the methods instead:
    122 | 
    123 | ```php
    124 | $finder = \XF::finder('XF:Thread');
    125 | $thread = $finder->with('Forum', true)->with('User')->where('thread_id', 123)->fetchOne();
    126 | ```
    127 | ### Методы order, limit и limitByPage
    128 | 
    129 | ### Метод order
    130 | 
    131 | This method allows you to modify your query so the results are fetched in a specific order. It takes two arguments, the first is the column name, and the second is, optionally, the direction of the sort. So, if you wanted to list the 10 users who have the most messages, you could build the query like this:
    132 | 
    133 | ```php
    134 | $finder = \XF::finder('XF:User');
    135 | $users = $finder->order('message_count', 'DESC')->limit(10);
    136 | ```
    137 | 
    138 | !!! Примечание
    139 |     Now is probably a good time to mention that finder methods can mostly be called in any order. For example: `$threads = $finder->limit(10)->where('thread_id', '>', 123)->order('post_date')->with('User')->fetch();`
    140 |     Although if you wrote a MySQL query in that order you'd certainly encounter some syntax issues, the Finder system will still build it all in the correct order and the above code, although odd looking and probably not recommended, is perfectly valid.
    141 |     
    142 | As with a standard MySQL query, it is possible to order a result set on multiple columns. To do that, you can just call the order method again. It's also possible to pass multiple order clauses into the order method using an array. 
    143 | 
    144 | ```php
    145 | $finder = \XF::finder('XF:User');
    146 | $users = $finder->order('message_count', 'DESC')->order('register_date')->limit(10);
    147 | ```
    148 | ### Метод limit
    149 | 
    150 | We've already seen how to limit a query to a specific number of records being returned:
    151 | 
    152 | ```php
    153 | $finder = \XF::finder('XF:User');
    154 | $users = $finder->limit(10)->fetch();
    155 | ```
    156 | 
    157 | However, there's actually an alternative to calling the limit method directly:
    158 | 
    159 | ```php
    160 | $finder = \XF::finder('XF:User');
    161 | $users = $finder->fetch(10);
    162 | ```
    163 | 
    164 | It's possible to pass your limit directly into the `fetch()` method. It's also worth noting that the `limit` (and `fetch`) method supports two arguments. The first obviously being the limit, the second being the offset.
    165 | 
    166 | ```php
    167 | $finder = \XF::finder('XF:User');
    168 | $users = $finder->limit(10, 100)->fetch();
    169 | ```
    170 | 
    171 | The offset value here essentially means the first 100 results will be discarded, and the first 10 after that will be returned. This kind of approach is useful for providing paginated results, though we actually also have an easier way to do that...
    172 | 
    173 | ### Метод limitByPage
    174 | 
    175 | This method is a sort of helper method which ultimately sets the appropriate limit and offset based on the "page" you're currently viewing and how many "per page" you require.
    176 | 
    177 | ```php
    178 | $finder = \XF::finder('XF:User');
    179 | $users = $finder->limitByPage(3, 20);
    180 | ```
    181 | 
    182 | In this case, the limit is going to be set to 20 (which is our per page value) and the offset is going to be set to 40 because we're starting on page 3.
    183 | 
    184 | Occasionally, it is necessary for us to grab additional more data than the limit. Over-fetching can be useful to help detect whether you have additional data to display after the current page, or if you have a need to filter the initial result set down based on permissions. We can do that with the third argument: 
    185 | 
    186 | ```php
    187 | $finder = \XF::finder('XF:User');
    188 | $users = $finder->limitByPage(3, 20, 1);
    189 | ```
    190 | 
    191 | This will get a total of up to **21** users (20 + 1) starting at page 3.
    192 | 
    193 | 
    194 | ### Метод getQuery
    195 | 
    196 | When you first start working with the finder, as intuitive as it is, you may occasionally wonder whether you're using it correctly, and whether it is going to build the query you expect it to. We have a method named `getQuery` which can tell us the current query that will be built with the current finder object. For example:
    197 | 
    198 | ```php
    199 | $finder = \XF::finder('XF:User')
    200 |     ->where('user_id', 1);
    201 | 
    202 | \XF::dumpSimple($finder->getQuery());
    203 | ```
    204 | 
    205 | This will output something similar to:
    206 | 
    207 | ```plain
    208 | string(67) "SELECT `xf_user`.*
    209 | FROM `xf_user`
    210 | WHERE (`xf_user`.`user_id` = 1)"
    211 | ```
    212 | 
    213 | You probably won't need it very often, but it can be useful if the finder isn't quite returning the results you expected. Read more about the `dumpSimple` method in the [Dump a variable](/documentation/DevelopmentTools.md#part20) section.
    214 | 
    215 | ### Расширение методов поисковика
    216 | 
    217 | So far we have seen the finder object get setup with an argument similar to `XF:User` and `XF:Thread`. For the most part, this identifies the Entity class the finder is working with and will resolve to, for example, `XF\Entity\User`. However, it can additionally represent a finder class. Finder classes are optional, but they serve as a way to add custom finder methods to specific finder types. To see this in action, let's look at the finder class that relates to `XF:User` which can be found in the `XF\Finder\User` class.
    218 | 
    219 | Here's an example finder method from that class:
    220 | 
    221 | ```php
    222 | public function isRecentlyActive($days = 180)
    223 | {
    224 |     $this->where('last_activity', '>', time() - ($days * 86400));
    225 |     return $this;
    226 | }
    227 | ```
    228 | 
    229 | What this allows us to do is to now call that method on any User finder object. So if we take an example earlier:
    230 | 
    231 | ```php
    232 | $finder = \XF::finder('XF:User');
    233 | $users = $finder->isRecentlyActive(20)->order('message_count', 'DESC')->limit(10);
    234 | ```
    235 | 
    236 | This query, which earlier just returned 10 users in descending message count order, will now return the 10 users in that order who have been recently active in the last 20 days.
    237 | 
    238 | Even though for a lot of entity types a finder class doesn't exist, it is still possible to extend these non existent classes in the same way as mentioned in the [Extending classes](/documentation/GeneralConcepts.md#part6) section.
    239 | 
    240 | ## Система сущностей
    241 | 
    242 | If you're familiar with XF1, you may be familiar with some of the concepts behind Entities because they have ultimately derived from the DataWriter system there. In case you're not so familiar with them, the following section should give you some idea.
    243 | 
    244 | ### Структура сущностей
    245 | 
    246 | The `Structure` object consists of a number of properties which define the structure of the Entity and the database table it relates to. The structure object itself is setup inside the entity it relates to. Let's look at some of the common properties from the User entity:
    247 | 
    248 | #### Таблица
    249 | ```php
    250 | $structure->table = 'xf_user';
    251 | ```
    252 | This tells the Entity which database table to use when updating and inserting records, and also tells the Finder which table to read from when building queries to execute. Additionally, it plays a part in knowing which other tables your query needs to join to.
    253 | 
    254 | 
    255 | #### Короткое имя
    256 | ```php
    257 | $structure->shortName = 'XF:User';
    258 | ```
    259 | This is the just the short class name of both the Entity itself and the Finder class (if applicable).
    260 | 
    261 | #### Тип контента
    262 | ```php
    263 | $structure->contentType = 'user';
    264 | ```
    265 | This defines what content type this Entity represents. This will not be needed in most entity structures. It is used to connect to specific things used by the "content type" system (which will be covered in another section).
    266 | 
    267 | #### Основной ключ
    268 | ```php
    269 | $structure->primaryKey = 'user_id';
    270 | ```
    271 | Defines the column which represents the primary key in the database table. If a table supports more than a single column as a primary key, then this can be defined as an array.
    272 | 
    273 | #### Колонки
    274 | ```php
    275 | $structure->columns = [
    276 |     'user_id' => ['type' => self::UINT, 'autoIncrement' => true, 'nullable' => true, 'changeLog' => false],
    277 |     'username' => ['type' => self::STR, 'maxLength' => 50,
    278 |         'required' => 'please_enter_valid_name'
    279 |     ]
    280 |     // and many more columns ...
    281 | ];
    282 | ```
    283 | This is a key part of the configuration of the entity as this goes into a lot of detail to explain the specifics of each database column that the Entity is responsible for. This tells us the type of data that is expected, whether a value is required, what format it should match, whether it should be a unique value, what its default value is, and much more.
    284 | 
    285 | Based on the `type`, the entity manager knows whether to encode or decode a value in a certain way. This may be a somewhat simple process of casting a value to a string or an integer, or slightly more complicated such as using `json_encode()` on an array when writing to the database or using `json_decode()` on a JSON string when reading from the database so that the value is correctly returned to the entity object as an array without us needing to manually do that. It can also support comma separated values being encoded/decoded appropriately.
    286 | 
    287 | Occasionally it is necessary to do some additional verification or modification of a value before it is written. As an example, in the User entity, look at the `verifyStyleId()` method. When a value is set on the `style_id` field, we automatically check to see if a method named `verifyStyleId()` exists, and if it does, we run the value through that first.
    288 | 
    289 | #### Поведения
    290 | ```php
    291 | $structure->behaviors = [
    292 |     'XF:ChangeLoggable' => []
    293 | ];
    294 | ```
    295 | This is an array of behavior classes which should be used by this entity. Behavior classes are a way of allowing certain code to be reused generically across multiple entity types (only when the entity changes, not on reads). A good example of this is the `XF:Likeable` behavior which is able to automatically execute certain actions on entities which support content which can be "liked". This includes automatically recalculating counts when visibility changes occur within the content and automatically deleting likes when the content is deleted. 
    296 |  
    297 | #### Геттеры
    298 | ```php
    299 | $structure->getters = [
    300 |     'is_super_admin' => true,
    301 |     'last_activity' => true
    302 | ];
    303 | ```
    304 | Getter methods are automatically called when the named fields are called. For example, if we request `is_super_admin` from a User entity, this will automatically check for, and use the `getIsSuperAdmin()` method. The interesting thing to note about this is that the xf_user table doesn't actually have a field named `is_super_admin`. This actually exists on the Admin entity, but we have added it as a getter method as a shorthand way of accessing that value. Getter methods can also be used to override the values of existing fields directly, which is the case for the `last_activity` value here. `last_activity` is actually a cached value which is updated usually when a user logs out. However, we store the user's latest activity date in the xf_session_activity table, so we can use this `getLastActivity` method to return that value instead of the cached last activity value. Should you ever have a need to bypass the getter method entirely, and just get the true entity value, just suffix the column name with an underscore, e.g. `$user->last_activity_`.
    305 | 
    306 | Because an entity is just like any other PHP object, you can add more methods to them. A common use case for this is for adding things like permission check methods that can be called on the entity itself.
    307 | 
    308 | #### Связи
    309 | ```php
    310 | $structure->relations = [
    311 |     'Admin' => [
    312 |         'entity' => 'XF:Admin',
    313 |         'type' => self::TO_ONE,
    314 |         'conditions' => 'user_id',
    315 |         'primary' => true
    316 |     ]
    317 | ];
    318 | ```
    319 | This is how Relations are defined. What are relations? They define the relationship between entities which can be used to perform join queries to other tables or fetch records associated to an entity on the fly. If we remember the `with` method on the finder, if we wanted to fetch a specific user and preemptively fetch the user's Admin record (if it exists) then we would do something like the following:
    320 |  
    321 | ```php
    322 | $finder = \XF::finder('XF:User');
    323 | $user = $finder->where('user_id', 1)->with('Admin')->fetchOne();
    324 | ```
    325 |  
    326 | This will use the information defined in the user entity for the `Admin` relation and the details of the `XF:Admin` entity structure to know that this user query should perform a `LEFT JOIN` on the xf_admin table and the `user_id` column. To access the admin last login date from the user entity:
    327 |   
    328 | ```php
    329 | $lastLogin = $user->Admin->last_login; // returns timestamp of the last admin login
    330 | ```
    331 | 
    332 | However, it's not always necessary to do a join in a finder to get related information for an entity. For example, if we take the above example without the `with` method call:
    333 | 
    334 | ```php
    335 | $finder = \XF::finder('XF:User');
    336 | $user = $finder->where('user_id', 1)->fetchOne();
    337 | $lastLogin = $user->Admin->last_login; // returns timestamp of the last admin login
    338 | ```
    339 | 
    340 | We still get the `last_login` value here. It does this by performing the additional query to get the Admin entity on the fly.
    341 | 
    342 | The example above uses the `TO_ONE` type, and this relation, therefore, relates one entity to one other entity. We also have a `TO_MANY` type.
    343 | 
    344 | It is not possible to fetch an entire `TO_MANY` relation (e.g. with a join / `with` method on the finder), but at the cost of a query it is possible to read that at any time on the fly, such as in the final `last_login` example above.
    345 | 
    346 | One such relation that is defined on the User entity is the `ConnectedAccounts` relation:
    347 | 
    348 | ```php
    349 | $structure->relations = [
    350 |     'ConnectedAccounts' => [
    351 |         'entity' => 'XF:UserConnectedAccount',
    352 |         'type' => self::TO_MANY,
    353 |         'conditions' => 'user_id',
    354 |         'key' => 'provider'
    355 |     ]
    356 | ];
    357 | ```
    358 | 
    359 | This relation is able to return the records from the xf_user_connected_account table that match the current user ID as a `FinderCollection`. This is similar to the `ArrayCollection` object we mentioned in [The Finder](#the-finder) section above. The relation definition specifies that the collection should be keyed by the `provider` field.
    360 | 
    361 | Although it isn't possible to fetch multiple records while performing a finder query, it is possible to use a `TO_MANY` relation to fetch a **single** record from that relation. As an example, if we wanted to see if the user was associated to a specific connected account provider, we can at least fetch that while querying:
    362 | 
    363 | ```php
    364 | $finder = \XF::finder('XF:User');
    365 | $user = $finder->where('user_id', 1)->with('ConnectedAccounts|facebook')->fetchOne();
    366 | ```
    367 | 
    368 | #### Опции
    369 | ```php
    370 | $structure->options = [
    371 |     'custom_title_disallowed' => preg_split('/\r?\n/', $options->disallowedCustomTitles),
    372 |     'admin_edit' => false,
    373 |     'skip_email_confirm' => false
    374 | ];
    375 | ```
    376 | Entity options are a way of modifying the behavior of the entity under certain conditions. For example, if we set `admin_edit` to true (which is the case when editing a user in the Admin CP), then certain checks will be skipped such as to allow a user's email address to be empty.
    377 | 
    378 | ### Жизненный цикл сущностей
    379 | 
    380 | The Entity plays a significant job in terms of managing the life cycle of a record within the database. As well as reading values from it, and writing values to it, the Entity can be used to delete records and trigger certain events when all of these actions occur so that certain tasks can be performed, or certain associated records can be updated as well. Let's look at some of these events that happen when an entity is saving:
    381 | 
    382 | * `_preSave()` - This happens before the save process begins, and is primarily used to perform any additional pre-save validations or to set additional data before the save happens.
    383 | * `_postSave()` - After the data has been saved, but before any transactions are committed, this method is called and you can use it to perform any additional work that should trigger after an entity has been saved.
    384 | 
    385 | There are additionally `_preDelete()` and `_postDelete()` which work in a similar way, but when a delete is happening.
    386 | 
    387 | The Entity is also able to give information on its current state. For example, there is an `isInsert()` and `isUpdate()` method so you can detect whether this is a new record being inserted or an existing record being updated. There is an `isChanged()` method which can tell you whether a specific field has changed since the last save.
    388 |  
    389 |  Let's look at some real examples of these methods in action, in the User entity.
    390 |  
    391 | ```php
    392 |  protected function _preSave()
    393 |  {
    394 |     if ($this->isChanged('user_group_id') || $this->isChanged('secondary_group_ids'))
    395 |     {
    396 |         $groupRepo = $this->getUserGroupRepo();
    397 |         $this->display_style_group_id = $groupRepo->getDisplayGroupIdForUser($this);
    398 |     }
    399 |     
    400 |     // ...
    401 |  }
    402 |  
    403 |  protected function _postSave()
    404 |  {
    405 |     // ...
    406 |     
    407 |     if ($this->isUpdate() && $this->isChanged('username') && $this->getExistingValue('username') != null)
    408 |     {
    409 |         $this->app()->jobManager()->enqueue('XF:UserRenameCleanUp', [
    410 |             'originalUserId' => $this->user_id,
    411 |             'originalUserName' => $this->getExistingValue('username'),
    412 |             'newUserName' => $this->username
    413 |         ]);
    414 |     }
    415 |     
    416 |     // ...
    417 | ```
    418 |  
    419 | In the `_preSave()` example we fetch and cache the new display group ID for a user based on their changed user groups. In the `_postSave()` example, we trigger a job to run after a user's name has been changed.
    420 | 
    421 | ## Репозитории
    422 | 
    423 | Repositories are a new concept for XF2, but you might not be blamed for comparing them to the "Model" objects from XF1. We don't have a model object in XF2 because we have much better places and ways to fetch and write data to the database. So, rather than having a massive class which contains all of the queries your add-on needs, and all of the various different ways to manipulate those queries, we have the finder which adds a lot more flexibility.
    424 | 
    425 | It's also worth bearing in mind that in XF1 the Model objects were a bit of a "dumping ground" for so many things. Many of which are now redundant. For example, in XF1 all of the permission rebuilding code was in the permission model. In XF2, we have specific services and objects which handle this.
    426 | 
    427 | So, what are Repositories? They correspond with an entity and a finder and hold methods which generally return a finder object setup for a specific purpose. Why not just return the result of the finder query? Well, if we return the finder object itself then it serves as a useful extension point for add-ons to extend that and modify the finder object before the entity or collection is returned.
    428 | 
    429 | Repositories may also contain some specific methods for things like cache rebuilding.
    
    
    --------------------------------------------------------------------------------
    /documentation/GeneralConcepts.md:
    --------------------------------------------------------------------------------
      1 | 
      2 | # Общие понятия
      3 | В следующих разделах подробно рассказывается о некоторых общих системах и концепциях, с которыми вы столкнетесь при разработке дополнения XenForo. Если вы знакомы с разработкой XenForo 1.x, то многие из этих концепций будут вам знакомы, хотя стоит их рассмотреть, так как есть несколько отличных новых инструментов и функций, которые помогут вам разрабатывать надстройки.
      4 | 
      5 | ## Компоненты поставщика
      6 | XF2 не разработан на каком-то конкретном фреймворке, как XF1, однако, мы использовали популярные, хорошо протестированных пакеты с открытым исходным кодом, для решения конкретных задач. Например, мы используем проект SwiftMailer для отправки электронной почты и проект с именем Guzzle в качестве HTTP-клиента. Все сторонние проекты загружаются из каталога `src/vendor`.
      7 | 
      8 | В настоящее время разработчики дополнений не могут добавлять свои собственные зависимости к этому местоположению.
      9 | 
     10 | ## Интегрированная среда разработки (IDE)
     11 | Прежде чем приступать к работе над разработкой XF2, вы можете потратить некоторое время на оценку приложения, с которым вы действительно будете создавать и редактировать файлы PHP. Это обычно называют IDE. Существует ряд вариантов, начиная от базового Блокнота и заканчивая чем-то вроде Sublime Text, который может быть расширен, чтобы иметь лучшую поддержку PHP с дополнениями, вплоть до среды разработки, такой как PhpStorm. Внутри мы в основном используем PhpStorm. Это премиальный и коммерческий продукт, но могут быть доступны свободные альтернативы. В любом случае, никто не может вам указывать, какую IDE вам использовать и вам придется потратить некоторое время, чтобы подобрать наиболее удобную среду.
     12 | 
     13 | ## Автозагрузчик
     14 | XF2 использует автозагрузчик, который автоматически генерирует Composer. Это позволяет автоматически загружать весь код XF, сторонний код и любой код разработчика дополнений для всего проекта без необходимости `include/require` ваши классы вручную.
     15 | 
     16 | Корень автозагрузки для всех дополнений XF является каталогом `src/addons`. Это означает, что все ваши имена классов будут относиться к этому базовому местоположению. Также стоит отметить, что XF2 использует строгий шаблон «один класс - один файл». Каждый файл должен содержать только один класс, а имя этого класса должно определять точное местоположение файла PHP класса в файловой системе.
     17 | 
     18 | Например, если вы хотите создать новый класс в файле с именем `src/addons/Demo/Setup.php` (где `Demo` - ваш дополнительный код), этот класс будет называться `Demo\Setup`. И наоборот, если бы у вас был класс с именем `Demo\Entity\Thing`, вы узнаете, что файл для этого класса находится в пути `src/addons/Demo/Entity/Thing.php`.
     19 | 
     20 | ## Пространства имён
     21 | Всюду по XF мы используем [пространства имён](http://php.net/manual/en/language.namespaces.rationale.php) так что мы можем более кратко ссылаться на классы в одном и том же пространстве имен. Рекомендуется, чтобы все плагины также использовали пространства имен. В приведенном выше примере мы говорили о классе с именем `Demo\Setup`. Используя пространства имен, класс фактически будет называться просто «Setup», но пространство имен будет установлено в `Demo`. В качестве более конкретного примера мы также говорили выше о классе с именем `Demo\Entity\Thing`. Давайте посмотрим, как будет выглядеть код PHP для этого класса:
     22 | ```
     23 | Короткие имена классов
     35 | В некоторых местах, в XF мы сокращаем имена классов. Например, если вы хотите вызвать сущность `User` (подробнее о сущностях мы расскажем ниже), вы можете сослаться на неё по короткому имени - `XF:User`. Использование коротких имен классов и полного имени класса полностью зависит от контекста. Поэтому в контексте вызова сущности короткое имя класса будет указывать на полное имя -  `XF\Entity\User`. Часть `XF` указывает путь к файлу (на основе ID дополнения), часть `Entity` подразумевает вызов сущности, а часть `User` указывает на конкретную сущность. Аналогично, когда вы начинаете создавать свои собственные классы, вы также будете использовать короткие имена классов для ссылки на свои собственные классы. Например, если вам нужно создать новую сущность `Thing` для вашего дополнения `Demo`, вы должны написать следующее:
     36 | ```php
     37 | \XF::em()->create('Demo:Thing');
     38 | ```
     39 | Это вызвало бы класс `Demo\Entity\Thing`. Аналогичным образом, если вы хотите получить доступ к репозиторию `Thing`, вы должны написать его следующим образом:
     40 | ```php
     41 | \XF::repository('Demo:Thing');
     42 | ```
     43 | Обратите внимание, что короткие имена классов идентичны. Вызов репозитория фактически вызовет `Demo\Repository\Thing`.
     44 | 
     45 | ## Расширение классов
     46 | Большое количество классов в XF2 является расширяемым, что позволяет разработчикам расширять и переопределять основной код без необходимости его прямого редактирования. Если вы знакомы с разработкой XF1, вы будете знакомы со следующим процессом:
     47 | 
     48 | 1. Создание PHP-файла Listener
     49 | 2. Создайть класс, который в конечном итоге расширит исходный класс
     50 | 3. Написать функцию, которая соответствует ожидаемой сигнатуре вызова для одного из событий `load_class` и добавляет имя вашего расширенного класса
     51 | 4. Добавить в ACP «обработчик событий», который определяет класс и имя обработчика для функции, упомянутой выше, и необязательно указать подсказку, какой класс будет расширен
     52 | 
     53 | В XF2 мы удалили эти события в пользу определенной системы под названием «Расширения класса». Процесс выглядит следующим образом:
     54 | 
     55 | 1. Создайте класс, который в конечном итоге расширит исходный класс
     56 | 2. Добавьте «расширение класса» в ACP, которое указывает имя расширяемого класса и имя класса, который его расширяет
     57 | 3. Это явно сокращает часть шаблона, требуемого для расширения классов, а также предоставляет специальный пользовательский интерфейс для просмотра и управления этими расширениями. Давайте посмотрим на процесс, расширив публичный контроллер `Member` и добавив новое действие, которое отображает простое сообщение.
     58 | 
     59 | Первое, что нужно сделать, это создать дополнение. Мы ранее [описывали](/documentation/DevelopmentTools.md#part5), как это сделать, используя команду `xf-addon:create`. В этом примере мы предположим, что вы создали дополнение с идентификатором и названием «Demo».
     60 | 
     61 | Теперь у вас будет файл addon.json для этого дополнения в следующем расположении `src/addons/Demo/addon.json`.
     62 | 
     63 | > **Примечание**
     64 | > Хотя, строго говоря, вы можете размещать свои расширенные классы везде, где захотите, в своем дополнительном каталоге, рекомендуется поместить расширенные классы в каталог, который легко идентифицирует: a) дополнение, коорому принадлежит класс b) тип расширяемого класса и c) название расширяемого класса. В следующих примерах мы расширяем публичный контроллер XF - Member, поэтому мы разместим наш расширенный класс по следующему пути: `src/addons/Demo/XF/Pub/Controller/Member.php`.
     65 | 
     66 | Расширенный класс должен существовать до того, как мы добавим расширение класса в ACP. Итак, следуйте следующим инструкциям:
     67 | 
     68 | 1. Создайте новый каталог с именем `XF` внутри `src/addons/Demo`
     69 | 2. Создайте новый каталог с именем `Pub` внутри `src/addons/Demo/XF`
     70 | 3. Создайте новый каталог с именем `Controller` внутри `src/addons/Demo/XF/Pub`
     71 | 4. Создайте новый файл с именем `Member.php` внутри `src/addons/Demo/XF/Pub/Controller`. 
     72 | 
     73 | Для начала, содержимое вашего файла должно быть таким:
     74 | ```php
     75 |  Development > Class > Add` class extension.
     87 | 
     88 | Все, что вам нужно сделать, это ввести имя базового класса (`XF\Pub\Controller\Member`) в первом поле и имя расширенного класса (которое вы только что создали) во втором поле (`Demo\XF\Pub\Controller\Member`) и нажмите кнопку «Сохранить».
     89 | 
     90 | Расширение вашего класса теперь должно быть активным, но в настоящее время оно ничего не делает. Чтобы что-то произошло, нам необходимо либо переопределить существующий метод в этом классе, создав метод с тем же именем, что и существующий, либо полностью добавив новый метод. Давайте сделаем последнее:
     91 | ```php
     92 | message('Hello world!');
    101 | 	}
    102 | }
    103 | ```
    104 | Мы больше говорим о контроллерах, действиях и ответах на странице [Основы контроллеров](/documentation/ControllerBasics.md#part0), поэтому не беспокойтесь об этом прямо сейчас.
    105 | 
    106 | Теперь мы добавили некоторый код в наш расширенный контроллер, давайте посмотрим его в действии. Просто введите следующий URL-адрес (относительно URL-адреса вашей доски): `index.php?members/hello-world`. Теперь вы должны увидеть сообщение «Hello world!»!
    107 | 
    108 | Как упоминалось ранее, также возможно переопределить существующие методы внутри класса. Например, если мы изменим `actionHelloWorld()` на `actionIndex()`, вместо списка «Полезные пользователи», отобразится «Hello world!». Это не совсем правильный способ расширения существующего действия контроллера (или любого метода класса), но мы подробно рассмотрим это в [Изменение ответа действие контроллера (правильно)](/documentation/ControllerBasics.md#part7).
    109 | 
    110 | ## Типовые подсказки
    111 | Многие объекты в XF создаются с помощью методов XF. Например, если мы хотим создать экземпляр конкретного репозитория, мы напишем следующее:
    112 | ```php
    113 |     $repo = \XF::repository('Demo:Thing');
    114 | ```
    115 | Это очень удобный способ создания объекта. Мы знаем, только посмотрев на него, какой объект будет создан. Полученный код в этом методе знает, как вернуть правильный объект для того, что мы запросили.
    116 | 
    117 | Однако, к сожалению, ваша IDE, вероятно, не имеет понятия (по крайней мере, по умолчанию). Что касается IDE, этот метод вернет экземпляр объекта `XF\Mvc\Entity\Repository`. Это полезно в определенной степени, но существует множество методов, доступных в конкретном объекте `Demo\Repository\Thing`, о которых ваша IDE не знает. Это в конечном итоге означает, что, когда вы попытаетесь использовать свой объект `$repo` в коде, ваша среда IDE не сможет делать предложения или автоматически заполнять имена методов и требуемые аргументы.
    118 | 
    119 | В таких случаях будет полезен тайп-хинтинг, такой синтаксис должен поддерживаться большинством IDE,  и текстоыми редакторами, которые "знакомы" с PHP. Мы просто изменим вызов репозитория следующим образом:
    120 | ```php
    121 | /** @var \Demo\Repository\Thing $repo */
    122 | $repo = \XF::repository('Demo:Thing');
    123 | ```
    124 | Такая подсказка сообщает IDE, что `$repo` относится к объекту, представленному классом `Demo\Repository\Thing`, а не к объекту, который он автоматически выводил изначально.
    125 | 
    126 | Тайп-хинтинг так же очень полезен при расширении классов. Потенциальная проблема с нашими методами расширения класса заключается в том, что по существу ваши классы не расширяют исходный класс, который вы хотите расширить, но вместо этого проксируется через класс, который фактически не существует, например. `XFCP_Member`, как в [пример выше] (/documentation/GeneralConcepts.md#part6).
    127 | 
    128 | Чтобы исправить эту проблему, мы автоматически создаем файл с именем `extension_hint.php` и сохраняем его в вашем каталоге` _output`.
    129 | 
    130 | Таким образом, создается ссылка, которую видит ваша IDE. Когда вы будете использовать `$this` в любом из методов в расширенном классе, ваша IDE предложит автодополнение методов и свойств доступных в контроллере Member, или его родителях.
    
    
    --------------------------------------------------------------------------------
    /documentation/GettingStarted.md:
    --------------------------------------------------------------------------------
      1 | 
      2 | 
      3 | 
      4 | # Начало работы
      5 | 
      6 | Эта документация предназначена для ознакомления вас с разработкой под XenForo 2.0. Предполагается, что перед началом работы с этой документацией вы уже знакомы с базовыми вещами, вроде PHP и MySQL. Опыт работы с предыдущими версиями XenForo не требуется, но предоставит ряд преимуществ.
      7 | 
      8 | На последующих страницах документации мы расскажем об установке локального сервера, подготовке к установке и самом процессе чистой установки XenForo 2.0 и объясним некоторые базовые концепции разработки под XF2.
      9 | 
     10 | ## Что нового для разработчиков?
     11 | 
     12 | Хотя XenForo 2.0 добавляет много улучшений для ваших форумов и его участников, значительные усилия были направлены на улучшение базовой структуры XenForo. Вы можете прочитать дополнительную информацию об этих изменениях в следующих темах:
     13 | 
     14 | [Что нового для разработчиков в XenForo 2 (часть 1)](https://xf2demo.xenforo.com/threads/whats-new-for-developers-in-xenforo-2-part-1.1297/)1
     15 | 
     16 | [Что нового для разработчиков в XenForo 2 (часть 2)](https://xf2demo.xenforo.com/threads/whats-new-for-developers-in-xenforo-2-part-2.1409/)1
     17 | ## Начало работы
     18 | Начать разработку на XF легко. Вам просто нужно загрузить файлы, выгрузить их на веб-сервер и запустить установку.
     19 | 
     20 | Если у вас еще нет веб-сервера, не беспокойтесь, вы легко сможете настроить его в локальном окружении.
     21 | 
     22 | ## Загрузка XF 2.0
     23 | Для загрузки XF 2.0, просто зайдите в [Customer Area] (https://xenforo.com/customers) и войдите в учетную запись как обычно. Найдите правильную лицензию и нажмите ссылку «Загрузить XenForo». Выберите версию, которую вы хотите загрузить, тип пакета и принять лицензионное соглашение. Наконец, нажмите кнопку «Загрузить», чтобы загрузить файлы.
     24 | 
     25 | ## Системные требования XF 2.0
     26 | Требования к запуску XF 2.0 изменились с XF 1.5. Рекомендуемые требования:
     27 | 
     28 | * PHP: 5.4.0+
     29 | * MySQL: 5.5+
     30 | * PHP расширения: MySQLi, GD (with JPEG support), PCRE, SPL, SimpleXML, DOM, JSON, iconv, ctype, cURL
     31 | 
     32 | [Загрузить скрипт проверки требований.](https://xenforo.com/xf2-docs/dev/files/xenforo2-requirements-test.zip)
     33 | 
     34 | ## Настройка локального сервера
     35 | Часто более удобно настраивать локальный веб-сервер для разработки. Как правило, для этого существуют два подхода:
     36 | 
     37 | * Установка Apache (или nginx), MySQL (или MariaDB) и сам PHP.
     38 | * Установка [предварительно построенной виртуальной машины](/documentation/GettingStarted.md#part6).
     39 | * Установка [готовых сборок](/documentation/GettingStarted.md#part7).
     40 | 
     41 | Локальная установка сама по себе может быть сложной, но, как правило, дает вам больше контроля, над тем, как всё настроено.
     42 | 
     43 | ### Предварительно построенная виртуальная машина
     44 | В Интернете есть множество готовых виртуальных машин, которые обеспечивают наличия почти всех необходимых сервисов для запуска XenForo, аккуратно упакованного в одно место, без необходимости их установки и обслуживания непосредственно на вашем собственном компьютере.
     45 | 
     46 | Некоторые из разработчиков XenForo используют виртуальную машину под названием [Scotch Box](https://box.scotch.io/), которая включает в себя все необходимое для запуска XenForo с настройками по умолчанию. У нас есть [Пошаговое руководство](https://github.com/spark108/XenForo-2.0-Russian-Documentation/blob/master/documentation/AppendixScotchBox.md) для запуска сервера разработки XenForo - вы получите рабочий виртуальный веб-сервер и сервер баз данных выполня всего несколько команд.
     47 | 
     48 | [Установка Scotch Box для использования с XenForo](https://xenforo.com/xf2-docs/dev/scotchbox/)
     49 | 
     50 | ### Готовые сборки
     51 | Существует много готовых сборок, и они могут различаться в наборе функций, производительности и надежности. Bitnami поддерживает несколько сборок, включая [LAMP](https://bitnami.com/stack/lamp), [MAMP](https://bitnami.com/stack/mamp) и [WAMP](https:// bitnami.com/stack/wamp) для использования в Linux, Mac и Windows соответственно. Все они включают полностью настроенную установку Apache, MySQL и PHP, а так же PhpMyAdmin для управления MySQL.
     52 | 
     53 | ## Загрузка
     54 | Чтобы установить XF 2.0, вам просто нужно извлечь ZIP-файл, загруженный ранее, и выгрузить некоторые из файлов и каталогов на ваш сервер.
     55 | 
     56 | После извлечения вы увидите каталог с именем `upload`. Вам нужно зайти в этот каталог и загрузить файлы и каталоги в корневой каталог вашего сервера. Обычно это каталог с именем `public_html`,` htdocs` или `www`.
     57 | 
     58 | ## Создание src/config.php
     59 | Если вы используете CLI для установки XF 2.0, вам нужно будет создать файл `config.php` вручную. Для этого войдите каталог `src` в файлах XF 2.0, загруженные на ваш сервер. Создайте новый файл с именем `config.php` и заполните его именем хоста, порта, имени пользователя, пароля и базы данных для вашего сервера MySQL.
     60 | > **Примечание**
     61 | >
     62 | > Убедитесь, что вы создали файл конфигурации в каталоге `src`. Каталог `library` используется только для устаревших целей.
     63 | 
     64 | После этого он должен выглядеть следующим образом:
     65 | ```php
     66 | Замечание по правам доступа к файлам
     82 | Во время работы, XenForo записывает файлы в разные места. При нормальной работе это ограничивается каталогами `data` и` internal_data` (и их подкаталогами). Эти записи файлов будут инициироваться такими приложениями, например, вложения, поэтому они, как правило, запускаются пользователем PHP на вашем веб-сервере. Поэтому необходимо обеспечить, чтобы в этих каталогах были установлены разрешения, чтобы веб-сервер мог изменять их. Вам нужно будет сделать это до начала установки.
     83 | 
     84 | Когда CLI задействован, эта ситуация становится более сложной, так как теперь есть потенциально два пользователя, которые должны иметь возможность записывать файлы. Таким образом, важно принять меры, чтобы избежать проблем с записью этих файлов. Вот несколько вариантов.
     85 | 
     86 | 1. Используйте одного и того же пользователя для CLI и веб-сервера. Это значит, что вы должны переключится на пользователя веб-сервера перед запуском любой команды установки или обновления (или любого другого, который будет записывать файлы).
     87 | 2. Если доступно, рассмотрите возможность применения ACL к каталогам `data` и` internal_data`. 2. Эта концепция зависит от ОС и конфигурации, но общая идея описывается [здесь](http://symfony.com/doc/current/setup/file_permissions.html).
     88 | 3. Принудительная установка разрешения на то, что установлено PHP. Это можно сделать через файл `src/config.php` с такой строкой: `$config['chmodWritableValue'] = 0666;` Этот подход потенциально является самым простым в целях развития.
     89 | 
     90 | Обратите внимание: если вы разрабатываете плагины, могут быть другие местоположения, которые должны быть доступны для записи пользователям CLI и веб-сервером. Примечательно, что это включает в себя каталог `_output` внутри плагина. В этой ситуации, когда ваш веб-сервер работает как пользователь CLI, может возникнуть наименьшее число проблем. Если вы будете использовать любые другие адреса, вам может потребоваться обеспечить, чтобы ваш веб-сервер мог вести запись во все расположения вашей установки XenForo; это не рекомендуется в производстве.
     91 | 
     92 | ## Установка
     93 | Текущий способ установки XF 2.0 - это новая система CLI. Многие процессы разработки могут выполняться только с помощью CLI, поэтому давайте закрепим знания о нем, чтобы установить XF 2.0. Чтобы выполнить эти команды, вам понадобится доступ к терминалу/оболочке, команде php и текущему рабочему каталогу, которым должен быть корневой каталог, куда вы загружали файлы XF 2.0.
     94 | 
     95 | > **Предупреждение** 
     96 | > Чтобы устранить проблемы с разрешениеми файлов, мы рекомендуем запускать установщик как тот же пользователь, что и PHP, как через ваш веб-сервер. Если вы этого не сделаете, вы должны предпринять шаги для обеспечения правильной установки разрешений. См. Выше раздел для более подробной информации.
     97 | 
     98 | Для начала установки используйте следующюю команду:
     99 | 
    100 |     $ php cmd.php xf:install
    101 | 
    102 | Вам будет задан ряд вопросов, таких как `имя пользователя`, `пароль администратора`, `название сайта` и т.д.. После этого будут импортированы таблицы базы данных XF 2.0 и настройка основных данныъ.
    103 | 
    104 | XF 2.0 теперь установлен!
    105 | 
    106 | ## Переустановка
    107 | Иногда может потребоваться переустановка XF2. Это особенно актуально на этапе предварительного просмотра разработки, который не поддерживает обновление. Если вы готовы сделать переустановку, загрузите новые файлы (если возможно) в соответствии с разделом [Загрузка XF 2.0](/documentation/GettingStarted.md#part8) выше. Как правило, достаточно просто загрузить новые файлы и перезаписать существующие. Если вы делаете полную чистую переустановку, вы можете сохранить копию файла `config.php` или заново создать его в соответствии с инструкциями в [Создание src/config.php](/documentation/GettingStarted.md#part9).
    108 | Перед загрузкой новых файлов необходимо удалить содержимое каталогов `data` и `internal_data`.
    109 | 
    110 | Наконец, вам просто нужно будет начать установку, как описано выше. Вам нужно будет использовать опцию `--clear`, которая удалит все существующие таблицы `xf_`.
    111 | 
    112 |     $ php cmd.php xf:install --clear
    113 | 
    114 | Как только переустановка завершена, вы можете снова войти в систему.
    115 | 
    116 | Если вы разрабатывали плагины, и вы решили сохранить или создать резервную копию существующего каталога `src/addons`, вы можете восстановить свои данные плагинов с помощью команды [Импорт вывода разработки](https://xenforo.com/xf2-docs/dev/development-tools/#import-development-output).
    117 | 
    118 | > **Предупреждение** 
    119 | > Будьте осторожны, если вы решите создать резервную копию и восстановить каталог `src/addons`. Каталог `XF` внутри содержит основные данные XF и не может быть восстановлен из резервной копии, чтобы гарантировать, что у вас всегда самая последняя версия файлов.
    120 | > 
    121 | > Выполнение переустановки таким образом является деструктивной операцией и приведет к удалению всех данных, которые вы создали. Кроме того, имейте в виду, что только таблицы с префиксом `xf_` очищаются. Это является существенной причиной для рекомендации, что все таблицы, даже для плагинов, должны иметь префикс `xf_`.
    122 | 
    123 | ## Провека целостности файлов
    124 | Когда вы устанавливаете XF2, мы выполняем проверку целостности файла при установке. Если необходимо, но нет возможности выполнить проверку через АСР, вы можете использовать CLI для выполнения этой проверки.
    125 | 
    126 |     $ php cmd.php xf:file-check [addon_id]
    127 | 
    128 | Если вы хотите выполнить проверку целостности всех файллв, включая файлы XF, просто опустите аргумент `[addon_id]`. Только для XF просто используйте `XF` вместо аргумента или для определенного дополнения, просто укажите идентификатор плагина, который вы хотите проверить.
    129 | 
    130 | ## Команды управления плагинами
    131 | В дополнение к вышеприведенным командам для установки XF2 есть также несколько команд для управления плагинами.
    132 | 
    133 | ### Установка
    134 |     $ php cmd.php xf:addon-install [addon_id]
    135 | 
    136 | Устанавливает указанное дополнение, если оно доступно, и производит проверку работоспособности файлов. Если выход разработки доступен, вам будет предложено подтвердить, хотите ли вы использовать его для установки вместо экспортированных XML-файлов данных.
    137 | 
    138 | ### Обновление
    139 |     $ php cmd.php xf:addon-upgrade [addon_id]
    140 | 
    141 | Обновляет указанное дополнение, если его можно обновить, и производит проверку работоспособности файлов. Может произвольно выполнять импорт из вывода разработки.
    142 | 
    143 | ### Перестроение
    144 |     $ php cmd.php xf:addon-rebuild [addon_id]
    145 | 
    146 | Перестраивает основные данные для указанного плагина и производит проверку работоспособности файла. Данная операция повторно импортирует данные плагина. Может опционально выполнять импорт из вывода разработки.
    147 | 
    148 | ### Удаление
    149 |     $ php cmd.php xf:addon-uninstall [addon_id]
    150 | 
    151 | Удаляет указанное дополнение, если его можно удалить.
    152 | 
    153 | # Примечание
    154 | 1. Не рабочие ссылки так и указаны в документации. Если поправят - сообщите.
    155 | 
    
    
    --------------------------------------------------------------------------------
    /documentation/RestAPI.md:
    --------------------------------------------------------------------------------
     1 | # REST API
     2 | In XenForo 2.1, a REST API was added. This allows you to programmatically interact with many areas of a XenForo installation.
     3 | 
     4 | Accessing the API requires generating a key via the admin control panel. There is no unauthenticated access to the API and users cannot generate their own keys to access the API at this time.
     5 | 
     6 | The API for a specific XenForo installation is accessible at `/api/`. All endpoints are prefixed by this URL. For example, if XenForo is installed at `https://example.com/community/`, then the API URLs will start with `https://example.com/community/api/`. In this example, accessing a list of threads would be done via `https://example.com/community/api/threads/`.
     7 | 
     8 | The API is enabled by default. If necessary, all API access can quickly be disabled by adding the following to src/config.php:
     9 | 
    10 | ```php
    11 | $config['enableApi'] = false;
    12 | ```
    13 | 
    14 | ### API keys
    15 | API keys are created via the admin control panel by going to **Setup > API keys**. As generating API keys can allow access to highly privileged data, only super administrators may access this system. All super admins will receive an email when an API key is generated to ensure that the request is valid.
    16 | 
    17 | When a key is created, a random string will be generated and this will be used to authenticate yourself with the API. It is important that this key is kept secret. If you believe an API key has been compromised, you should immediately regenerate the key and update any code using the old key.
    
    
    --------------------------------------------------------------------------------
    /documentation/RoutingBasics.md:
    --------------------------------------------------------------------------------
     1 | # Основы маршрутизации
     2 | В PHP-приложении, таком как XF2, нам нужен способ принять запрос определенного URL-адреса от пользователя, понять, какой контроллер, метод и данные использует этот URL-адрес, чтобы предоставить соответствующий ответ пользователю. Концепция преобразования URL-адреса в местоположение в коде известна как "маршрутизация"(routing).
     3 | 
     4 | В XF2 маршрутизация почти полностью управляется из Админ-панели(далее ACP). Найти его можно по пути `ACP > Development > Routes`. Маршруты делятся на два типа, Публичный(далее Public) и Админ(далее Admin) они обеспечивают маршрутизацию запросов в приложениях соответственно.
     5 | 
     6 | # Простой пример
     7 | На странице маршрутов (см. выше), найдите маршрут `account/`. Это Public маршрут, обеспечивающий маршрутизацию запросов к URL-адресу `index.php?account/`. Этот маршрут простой; он состоит из небольшого количества конфигураций. Он состоит из "префикса маршрута"(Route prefix), "контекста раздела"(section context) и класса контроллера(controller class). Давайте разберемся в этом более подробно:
     8 | 
     9 | ### Префикс маршрута
    10 | 
    11 | Префикс маршрута идёт после `index.php?` и перед первым `/`. Это первый шаг в определении контроллера, к которому идёт запрос.
    12 | 
    13 | ### Раздел контекста
    14 | 
    15 | Контекст раздела сообщает навигационным системам в XF, какая вкладка навигации должна быть выбрана, когда посетитель просматривает страницу, по этому маршруту. Для `Public` маршрутов контекст раздела должен быть идентификатором вкладки верхнего уровня. Для маршрутов `Admin` должен обратиться к идентификатору самой вкладки навигации в ACP(независимо от глубины).
    16 | 
    17 | В случае с маршрутом `index.php?account/`, контекст раздела не обязателен, поскольку у нас нет вкладки навигации "account". Чтобы увидеть это в действии, просто измените значение "контекст раздела" на "forums", сохраните изменения и перейдите в свою учетную запись `index.php?account/`. Теперь вы должны увидеть, что выбрана вкладка навигации "Форумы"!
    18 | 
    19 | ### Контроллер
    20 | 
    21 | Это имя класса контроллера, которое должно быть вызвано, когда запрос совпадает с этим маршрутом. В случае с маршрутом "account/", у нас есть `XF:Account`. Это загрузит контроллер учетной записи. (Смотрите [Короткие имена классов](/documentation/GeneralConcepts.md#part5) для дополнительной информации). Код этого контроллера располагается по этому пути `src/XF/Pub/Controller/Account.php`. Обратите внимание, как короткие имена классов могут преобразовываться в «инфикс», а также в префикс (XF) и суффикс (Account). В этом случае инфикс для этого контроллера (Pub) выводится из типа маршрута учетной записи (public).
    22 | 
    23 | ### Экшен контроллера
    24 | 
    25 | Выше мы объяснили, как маршрут сопоставляется с определенным контроллером, но мы еще не знаем, как вызывается определенный метод в этом контроллере. Контроллеры являются классами, которые содержат ряд методов, часть URL после [префикса маршрута](/documentation/RoutingBasics.md#part2) указывает на метод контроллера. Данный URL `index.php?account/account-details`, обращается к классу контроллера `XF\Pub\Controller\Account` с именем метода `actionAccountDetails()`. Если маршрут ничего не указывает, то вызывается метод `actionIndex()`.
    26 | 
    27 | Вы можете больше узнать о контроллерах в разделе [Основы контроллера](/documentation/ControllerBasics.md#part0).
    28 | 
    29 | ### Более продвинутый пример (форматы маршрута)
    30 | 
    31 | Давайте посмотрим на маршрут `members/`. Этот маршрут простой, также как и маршрут `account/`, но у него есть заполненное поле; "формат маршрута"(Route format). Чтобы понять, как это работает, посмотрите на свой профиль. URL этого профиля будет выглядеть примерно так `index.php?members/your-name.1`. Обратите внимание на `your-name.1`. Это та часть, которую мы пытаемся сопоставить, используя "формат маршрута".
    32 | 
    33 | "Формат маршрута" позволяет нам извлекать данные из URL запроса, так мы можем передать данные в действие контроллера, чтобы оно могло загрузить определенную информацию; в этом случае загружается профиль запрошенного пользователя. Это также помогает нам создавать ссылки из передаваемых данных. Вот синтаксис:
    34 | ```
    35 | :int/:page
    36 | ```
    37 | 
    38 | Интересно отметить, что важной частью URL-адреса профиля для поиска профиля является не `your-name`, а идентификатор пользователя (`1`). Чтобы продемонстрировать это, измените часть URL `your-name` на `not-your-name`.Вы увидите, что правильный профиль найден, и перенаправление выполнено к правильному URL.
    39 | 
    40 | Приведенный выше формат указывает, что это целочисленный параметр. Для построения исходящей ссылки мы извлекаем целое число из ключа `user_id` . Если ключ имени пользователя передается в данные, он будет "слагифицирован" и добавлен к целочисленному идентификатору, как вы видите в URL-адресе вашего профиля. Для сопоставления входящего URL-адреса это преобразуется в регулярное выражение, соответствующее целочисленному формату параметра.
    41 | 
    42 | `:page` это ярлык для генерации части ссылки на странице - 123. В этом случае он ищет страницу в параметрах ссылки. Если он найден, он помещается в URL-адрес, а затем удаляется из параметров. Для входящего парсинга, если он совпадает (он может быть пустым), добавит номер страницы к параметрам, передаваемым в контроллер.
    43 | 
    44 | ### Параметры маршрута
    45 | 
    46 | Когда маршрут совпадает с определенным контроллером и методом, любые параметры в URL-адресе обернуты в специальный объект, который мы назвали `ParameterBag`. Этот объект специально разработан, чтобы отделить обычные параметры URL с теми, которые прибывают из соответствия маршрута. Объект `ParameterBag` передается в каждое действие контроллера и используется следующим образом:
    47 | ```
    48 | $userId = $params->user_id;
    49 | ```
    50 | 
    51 | ### Суб-имена
    52 | 
    53 | Также можно разделять маршруты на дополнительные суб-имена. Вы можете увидеть это в действии, глядя на маршрут `members/following`. В этом примере, `following` является суб-именем маршрута `members`. Обычно URL-адрес выглядит следующим образом `index.php?members/following`, где часть `following` будет указывать на действие и просто соответствовать нормальному маршруту `members/`. Однако, если есть маршрут, который соответствует префиксу «members» и «sub-name», указанному ниже, он будет использоваться вместо `members/following`. Это верно, поэтому он создает ссылку, как показано ниже:
    54 | ```
    55 | members/:int/following/:page
    56 | ```
    57 | 
    58 | Для сопоставления входящего маршрута, этот маршрут будет проверен перед `members`; если он совпадает, он будет использоваться.
    59 | 
    60 | Эта система суб-имён позволяет изменять поведение, например, изменять положение параметров или подгруппировать маршруты на разных контроллерах или с разными параметрами. Пример последнего можно увидеть в менеджере ресурсов и надстройках Media Gallery.
    61 | 
    
    
    --------------------------------------------------------------------------------
    /documentation/SchemaManagement.md:
    --------------------------------------------------------------------------------
      1 | 
      2 | # Управление схемой
      3 | Мы рассмотрели некоторые новые подходы, доступные для взаимодействия с данными. Конечно, существуют конкретные обстоятельства, при которых взаимодействие с базой данных может быть непосредственно необходимым.
      4 | 
      5 | ## Адаптер базы данных
      6 | Адаптер базы данных по умолчанию в XF2 основан на расширении MySQL и PHP mysqli. Конфигурируемый адаптер базы данных доступен в любом классе XF, используя следующее:
      7 | 
      8 |     $db = \XF::db();
      9 | Адаптер имеет несколько доступных методов, которые будут выполнять SQL-запрос, а затем форматировать результаты в массив. Например, для доступа к одной записи пользователя:
     10 | 
     11 |     $db = \XF::db();
     12 |     $user = $db->fetchRow('SELECT * FROM xf_user WHERE user_id = ?', 1);
     13 | Теперь переменная `$ user` будет содержать массив всех значений из первой строки, найденной в результате запроса. Чтобы получить одно значение из этого запроса, например, имя пользователя, вы можете сделать следующее:
     14 | 
     15 |     $username = $user['username'];
     16 | 
     17 | > **Предупреждение**
     18 | > Запросы базы данных, написанные напрямую и переданные в адаптер базы данных, не являются автоматически «безопасными». Таким образом увеличивается риск подвергнутся SQL-инъекции, если пользовательский ввод будет передан без предварительной проверки. Способ сделать это правильно - это использовать подготовленные операторы, как в приведенном выше примере. Параметры представлены в самом запросе с использованием `?` заполнителя. Затем эти заполнители заменяются значениями в следующем аргументе после того, как они были соответствующим образом экранированы. Если вам нужно использовать более одного параметра, это должно быть передано в метод в виде массива. Если возникнет такая необходимость, вы можете избежать значений котировки напрямую, используя `$db->quote($value)`.
     19 | >
     20 | > Вы можете найти дополнительную информацию о подготовленных запросах [тут](http://php.net/manual/ru/mysqli.quickstart.prepared-statements.php).
     21 | 
     22 | Также можно запросить одно значение из записи. Например:
     23 | 
     24 |     $db = \XF::db();
     25 |     $username = $db->fetchOne('SELECT username FROM xf_user WHERE user_id = ?', 1);
     26 | Если у вас есть запрос, который должен возвращать несколько строк, вы можете использовать либо `fetchAll`:
     27 | 
     28 |     $db = \XF::db();
     29 |     $users = $db->fetchAll('SELECT * FROM xf_user LIMIT 10');
     30 | либо  `fetchAllKeyed`:
     31 | 
     32 |     $db = \XF::db();
     33 |     $users = $db->fetchAllKeyed('SELECT * FROM xf_user LIMIT 10', 'user_id');
     34 | Оба этих метода возвращают многомерный массив, который представляет каждую пользовательскую запись. Разница между методами `fetchAll` и `fetchAllKeyed` заключается в том, что возвращаемый массив будет вводиться по-разному. С помощью `fetchAll` массив будет вводиться с числовыми последовательными целыми числами. С помощью `fetchAllKeyed` массив будет зависеть от имени поля, указанного во втором аргументе.
     35 | 
     36 | > **Примечание**
     37 | > Если вы используете `fetchAllKeyed` обратите внимание, что вторым аргументом является поле для ввода массива, но ** третий ** аргумент - это то, где вы передаете значения параметра, соответствующие совпадениям `?`.
     38 | 
     39 | Существуют и другие методы типа выборки, включая `fetchAllColumn` для захвата массива значений определенного столбца из всех возвращенных строк:
     40 | 
     41 |     $db = \XF::db();
     42 |     $usernames = $db->fetchAllColumn('SELECT username FROM xf_user LIMIT 10');
     43 | В приведенном выше примере будет возвращен массив из 10 имен пользователей, найденных из полученного запроса.
     44 | 
     45 | Наконец, вам более не нужные какие-либо данные. В этом случае вы можете просто выполнить простой запрос:
     46 | 
     47 |     $db = \XF::db();
     48 |     $db->query('DELETE FROM xf_user WHERE user_id = ?', 1);
     49 | ## Управление схемой
     50 | XF2 включает в себя все новые способы управления схемой базы данных, которая использует объектно-ориентированный подход для выполнения определенных операций с таблицами. Давайте сначала рассмотрим традиционный `Alter`, используя адаптер базы данных, как мы уже говорили выше:
     51 | 
     52 |     $db = \XF::db();
     53 |     $db->query("
     54 |         ALTER TABLE xf_some_existing_table
     55 |         ADD COLUMN new_column INT(10) UNSIGNED NOT NULL DEFAULT 0,
     56 |         MODIFY COLUMN some_existing_column varchar(250) NOT NULL DEFAULT ''
     57 | ");
     58 | А также рассмотрим типичный запрос создания таблицы:
     59 | 
     60 |     $db = \XF::db();
     61 |     $sm = $db->getSchemaManager();
     62 | 
     63 |     $defaultTableConfig = $sm->getTableConfigSql();
     64 | 
     65 |     $db->query("
     66 |         CREATE TABLE xf_some_table (
     67 |             some_id INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
     68 |             some_name VARCHAR(50) NOT NULL,
     69 |             PRIMARY KEY (user_id)
     70 |         ) {$defaultTableConfig}
     71 |     ");
     72 | Альтернативный и предпочтительный подход в XF2 использует новый объект `SchemaManager`. Давайте посмотрим на оба этих запроса, как это делает диспетчер схем, начиная с `Alter`:
     73 | 
     74 |     $sm = \XF::db()->getSchemaManager();
     75 |     $sm->alterTable('xf_some_existing_table',     function(\XF\Db\Schema\Alter $table)
     76 |     {
     77 |         $table->addColumn('new_column', 'int')->setDefault(0);
     78 |         $table->changeColumn('some_existing_column')->length(250);
     79 |     });
     80 | И создание таблицы:
     81 | 
     82 |     $sm = \XF::db()->getSchemaManager();
     83 |     $sm->createTable('xf_some_table', function(\XF\Db\Schema\Create $table)
     84 |     {
     85 |         $table->addColumn('some_id', 'int')->autoIncrement();
     86 |         $table->addColumn('some_name', 'varchar', 50);
     87 |     });
     88 | 
     89 | !!! warning
     90 |     When you alter the existing XenForo tables, or create your own tables, you **MUST** specify a default value otherwise you will encounter problems when querying the table.
     91 | 
     92 | 
     93 | Оба этих примера дают тот же самый результаты, что и их более прямые аналоги. Хотя вы можете заметить, что некоторые вещи (намеренно) отсутствуют. Например, ни один из примеров не указывает длину для полей `int`. Это просто потому, что, опуская это, MySQL предоставит ему значение по умолчанию, которое равно `10` для целых чисел без знака. Говоря об этом, мы также не указываем, что столбец `some_id` не имеет знака. Использование целых чисел без знака в XF является наиболее распространенным вариантом , поэтому он автоматически добавляется. Если вам действительно нужна поддержка отрицательных целых чисел, вы можете отменить это с помощью метода `->unsigned(false)`. Другое упущение - отсутствие определения «NOT NULL для всего». Опять же, это применяется автоматически, но вы можете отменить это с помощью `->nullable (true)`.
     94 | 
     95 | Это может быть непонятно из приведенного примера, но при изменении существующих полей существующее определение поля автоматически сохраняется. Это означает, что вместо того, чтобы указывать полное определение столбца, включая все биты, которые на самом деле не были изменены, вы можете просто указать части, которые хотите изменить.
     96 | 
     97 | Существует и другой автоматический вывод, который происходит в отношении первичных ключей. Вы можете явно определить первичный ключ (или любой другой тип ключа), если хотите, но часто автоматически увеличиваются поля, которые обычно являются вашим основным ключом для таблицы. Поэтому в примере создания таблицы поле «some_id» автоматически назначается в качестве первичного ключа для этой таблицы.
     98 | 
     99 | Наконец, для подхода `create table` мы можем автоматически добавить правильную конфигурацию таблицы для указанного механизма хранения (который по умолчанию относится к `InnoDB`, но может быть легко изменен для других типов двигателей).
    100 | 
    
    
    --------------------------------------------------------------------------------
    /documentation/TemplateSyntax.md:
    --------------------------------------------------------------------------------
      1 | The XenForo 2 template syntax is a powerful tool for both developers and forum administrators, giving you complete control over the layout of your XenForo pages.
      2 | 
      3 | ## Best practices
      4 | - XenForo tags, by convention, are `lowercase`.
      5 | - All XenForo tags are prefixed with the `xf:` namespace.
      6 | 
      7 | ## Useful information
      8 | 
      9 | ### Commenting up your templates
     10 | If you want to comment out some template code (or an inspirational message) that you don't want viewable in the final page source, you can use the `xf:comment` tag.
     11 | 
     12 | ```html
     13 | 
     14 | If you stop seeing the world in terms of what you like
     15 | and what you dislike and saw things for what they truly are in themselves,
     16 | you will find a great deal more peace in your life.
     17 | 
     18 | ```
     19 | 
     20 | ### Including another template in a template
     21 | 
     22 | The `xf:include` tag allows you to include a different template in your current template.
     23 | 
     24 | ```html
     25 | 
     26 | ```
     27 | 
     28 | Simply set the `template  ` attribute to the name of the template you want to include.
     29 | 
     30 | 
     31 | 
     32 | ## Template macros
     33 | Template macros are a very powerful aspect of the XenForo template syntax.
     34 | 
     35 | You should generally use a macro any place you would use a function or subroutine in a programming language.
     36 | For non-programmers, I'd summarize this as: **either** use a macro any place you want to produce the same thing multiple times in multiple different files **or** to produce something different under different circumstances (this would probably make more sense if you check the guide on defining a macro).
     37 | 
     38 | !!! warning
     39 | 	For readability reasons, you should not use a macro tag as a variable. You should instead use the Set tag and treat the variable as you would any template variable.
     40 | 
     41 | ### Defining a macro
     42 | ```html
     43 | 
     45 | 
     46 |     
     47 | 
     48 | 
     49 | ```
     50 | At its simplest, a macro can be defined with a `name` attribute and the content you want repeated inside the macro tag.
     51 | 
     52 | !!! note
     53 | 	When you're using a macro in multiple files, it's best practice to put the macro in it's own template.
     54 | 
     55 | #### Macro arguments
     56 | ```html
     57 | 
     60 | 
     61 |     

    Message

    62 |

    {$message}

    63 | 64 |
    65 | ``` 66 | In this example, a macro is defined with a default value for `arg-message` (`My amazing macro message!`). 67 | This value would be overridden if the macro was called with the message argument. 68 | 69 | Sometimes it's necessary to mark an argument as required. This can be done by setting the argument value to `!` in the macro definition. 70 | 71 | ### Including & using macros 72 | ```html 73 | 74 | ``` 75 | At it's simplest, you include a macro by setting the `name` attribute and leaving the tag empty. 76 | 77 | !!! note 78 | When using a macro tag, you should use the self-closing form of the tag to allow someone to more easily distinguish the difference between a definition and usage of a macro. 79 | 80 | #### Macro arguments 81 | You can also provide arguments to the macro: 82 | 83 | ```html 84 | 85 | ``` 86 | 87 | Where `argName` is the name of the macro argument. 88 | 89 | !!! note 90 | You should use `lowerCamelCase` for your macro argument names. 91 | 92 | ## Template control structures 93 | 94 | The XenForo 2 template syntax supports certain control structures to make certain tasks easier to achieve. 95 | 96 | ### If tag 97 | 98 | The if template tag can be used to conditionally display some HTML or a part of a template. 99 | 100 | ```html 101 | 102 | 103 | 104 | 105 | ``` 106 | 107 | The if tag takes the following attributes: 108 | 109 | - `is` - The condition which, when met, the tags contents should be shown. 110 | 111 | #### Conditions 112 | 113 | The `is` attribute supports a few logical operators: 114 | 115 | - `OR` - Used to link alternative conditions. (Alternatives: `||`) 116 | - `AND` - Used to link additional conditions. (Alternatives: `&&`) 117 | - `!` - Place before a condition to invert it. (Known as: 'not') 118 | - `XOR` - Returns true if only one of two conditions is true. (Known as: Exclusive OR) 119 | 120 | ### Else/Else-If tag 121 | 122 | The else and else-if tags are used in conjunction with the if tag to conditionally display HTML in the way that the name suggests. 123 | 124 | **Example usage of else:** 125 | 126 | ```html 127 | 128 | 129 | 130 | 131 | 132 | ``` 133 | 134 | **Example usage of else-if:** 135 | 136 | ```html 137 | 138 | 139 | 140 | 141 | 142 | 146 | 147 | 151 | 152 | ``` 153 | 154 | As you can see, once a condition has been met, the rest of the if statement is ignored. (So, in this case, if the user is an Administrator, the top `xf:if` section is run but then the rest of the if statement is ignored.) 155 | 156 | ### For-each tag 157 | 158 | The for-each tag allows you to loop over an array of items, printing a block of HTML for each item. 159 | 160 | ```html 161 | 162 | 163 | 164 |

    Hello there, {$name}. This is name number {$i}. Array key of this element: {$key}

    165 |
    166 | ``` 167 | 168 | The for-each tag takes the following attributes: 169 | 170 | - `loop` - The array to loop over. 171 | - `key` - A variable name to use in the loop to get current element's array key. Can be integer (ordinary array) or string (associative array). 172 | - `value` - A variable name to use within the loop, containing the current array item. 173 | - `i` - A variable name to use in the loop for the current index. 174 | 175 | #### Example Output 176 | 177 | > Hello there, Patrick. This is name number 1. 178 | > 179 | > Hello there, Theresa. This is name number 2. 180 | > 181 | > Hello there, Kimball. This is name number 3. 182 | > 183 | > Hello there, Wayne. This is name number 4. 184 | > 185 | > Hello there, Grace. This is name number 5. 186 | 187 | ## Template tags 188 | 189 | ### Avatar tag 190 | 191 | Inserts a user's avatar in the page. 192 | 193 | ```html 194 | 195 | ``` 196 | 197 | The avatar tag takes the following attributes: 198 | 199 | - `user` - The XenForo User object to generate the avatar for. 200 | - `size` - The size of the image to generate. (See image sizes) 201 | - `canonical` - Whether to use the full SEO-friendly URL. This value is only respected for `custom` avatars. 202 | - `notooltip` - Whether the tool-tip displayed when hovering over the avatar should be disabled. 203 | - `forcetype` - Can be used to force getting the `gravatar` or `custom` avatars by setting the value to either of those. 204 | - `defaultname` - The username to use if the `user` attribute contains an invalid user. 205 | 206 | #### Image sizes 207 | 208 | If an avatar of invalid size is provided, the code will fallback to size '`s`'. 209 | 210 | - `o` - `384px` 211 | - `h` - `384px` 212 | - `l` - `192px` 213 | - `m` - `96px` 214 | - `s` - `48px` 215 | 216 | ### Breadcrumb tag 217 | 218 | Modifies the page breadcrumb. 219 | ```html 220 | {{ phrase('my_page_name') }} 221 | ``` 222 | The breadcrumb tag takes the following attributes: 223 | 224 | - `href` - The link to set for the final element in the breadcrumb. 225 | 226 | The value of the tag can be used to set the name of the final element in the breadcrumb. 227 | 228 | #### Alternative uses 229 | ```html 230 | 231 | ``` 232 | You can also define your own breadcrumb programmatically by calling your function in the `source` attribute of the breadcrumb tag. 233 | 234 | The `source` parameter essentially takes an array of objects with `href` and `value` attributes (multidimensional array), where each object is a breadcrumb element. 235 | 236 | !!! note 237 | If you want to change the root breadcrumb, you can change the "Root breadcrumb" option in the "Basic board information" options section. 238 | 239 | ### Button tag 240 | 241 | Adds a button element with the appropriate classes and optionally an icon. 242 | ```html 243 | 244 | ``` 245 | The button tag takes the following attributes: 246 | 247 | - `icon` - The icon class to apply to the button. (See button icons) 248 | 249 | #### Button icons 250 | 251 | By default, XenForo buttons support the following icons (created with CSS): 252 | 253 | - `add` 254 | - `confirm` 255 | - `write` 256 | - `import` 257 | - `export` 258 | - `download` 259 | - `disable` 260 | - `edit` 261 | - `save` 262 | - `reply` 263 | - `quote` 264 | - `purchase` 265 | - `payment` 266 | - `convert` 267 | - `search` 268 | - `sort` 269 | - `upload` 270 | - `attach` 271 | - `login` 272 | - `rate` 273 | - `config` 274 | - `refresh` 275 | - `translate` 276 | - `vote` 277 | - `result` 278 | - `history` 279 | - `cancel` 280 | - `preview` 281 | - `conversation` 282 | - `bolt` 283 | - `list` 284 | - `prev` 285 | - `next` 286 | - `markRead` 287 | - `notificationsOn` 288 | - `notificationsOff` 289 | - `merge` 290 | - `move` 291 | - `copy` 292 | - `approve` 293 | - `unapprove` 294 | - `delete` 295 | - `undelete` 296 | - `stick` 297 | - `unstick` 298 | - `lock` 299 | - `unlock` 300 | 301 | ### Callback tag 302 | 303 | Executes a PHP Callback method. 304 | ```html 305 | 306 | ``` 307 | The callback tag takes the following attributes: 308 | 309 | - `class` - The class (from the root namespace) containing the method to run. 310 | - `method` - The method to run. (See callback methods) 311 | - `params` - An array of parameters to provide to the method. 312 | 313 | #### Callback methods 314 | 315 | For a method to be considered a callback method, it must be named appropriately or it will throw an error '`callback_method_x_does_not_appear_to_indicate_read_only`'. For it to be considered read-only, the method name must begin with one of the following prefixes: 316 | 317 | - `are` 318 | - `can` 319 | - `count` 320 | - `data` 321 | - `display` 322 | - `does` 323 | - `exists` 324 | - `fetch` 325 | - `filter` 326 | - `find` 327 | - `get` 328 | - `has` 329 | - `is` 330 | - `pluck` 331 | - `print` 332 | - `render` 333 | - `return` 334 | - `show` 335 | - `total` 336 | - `validate` 337 | - `verify` 338 | - `view` 339 | 340 | ### CSS tag 341 | 342 | Includes a CSS or LESS template file. 343 | ```html 344 | 345 | ``` 346 | The CSS tag takes the following attributes: 347 | 348 | - `src` - The CSS or LESS template file to include. 349 | 350 | #### Alternative uses 351 | ```html 352 | 353 | html, body { 354 | font-family: "Roboto", sans-serif; 355 | } 356 | 357 | ``` 358 | If the CSS tag is not empty, anything in the tag will be converted into inline CSS. 359 | 360 | #### Further notes 361 | 362 | > For [CSS], forget about calling them as files. Copy and paste them into new templates. 363 | 364 | Chris D, XenForo developer **Source**: [https://xenforo.com/community/threads/including-external-library-js-and-css.136153/post-1185631](https://xenforo.com/community/threads/including-external-library-js-and-css.136153/post-1185631) 365 | 366 | ### JS tag 367 | 368 | Includes a JavaScript file. 369 | ```html 370 | 371 | ``` 372 | The JS tag takes the following attributes: 373 | 374 | - `src` - The JS file to include in the template. 375 | - `prod` - The JS file to include in the template, only for production mode. 376 | - `dev` - The JS file to include in the template, only for development mode. 377 | - `min` - Whether or not to include the minified version of the file. (Replaces `.js` with `.min.js`) - Respected only in production mode. 378 | - `addon` - Whether or not the development JS URL should be used. - Respected only in development mode. 379 | 380 | !!! warning 381 | The `src` tag cannot be used in conjunction with either the `prod` or `dev` tags. 382 | 383 | #### Alternative uses 384 | ```html 385 | 386 | alert("The truth hurts, I know. It's biologically based actually."); 387 | 388 | ``` 389 | If the JS tag is not empty, anything in the tag will be converted to inline JS. 390 | 391 | #### Further notes 392 | 393 | JavaScript files are served relative to the `/js` directory. Although not recommended, you can also include external resources with this tag. 394 | 395 | A good example of this tag is in the `editor` template. 396 | 397 | ### Set tag 398 | 399 | The set tag allows you to create a reference to another variable or create a new variable. You should use the set tag anywhere you would use a variable in a programming language. 400 | ```html 401 | 402 | ``` 403 | 404 | !!! warning 405 | Do not use the Set tag for a group of elements you wish to use in multiple templates, you should instead use the Macro Tag. 406 | 407 | !!! warning 408 | The variable name (`var` attribute) must begin with a `$`. 409 | 410 | The set tag takes the following attributes: 411 | 412 | - `var` - The name of the variable you wish to define (essentially, the alias). 413 | - `value` - A variable to reference to or a variable value. 414 | 415 | #### Alternative uses 416 | ```html 417 | 418 | My Variable Value! 419 | This could be a callback, or simply a group of phrases. 420 | 421 | ``` 422 | When the `value` attribute is not provided, and the tag is not empty, the variable value will be set to the contents of the tag. 423 | 424 | !!! warning 425 | When you use the Set tag in this form, the value will be escaped and the resulting value will be a string. 426 | The `value` attribute, whilst not supporting HTML or HTML-like tags does not have this limitation. 427 | 428 | ### Likes tag 429 | 430 | Displays the number of likes on a post and a few of the users who've liked the post. 431 | 432 | ```html 433 | 434 | ``` 435 | 436 | The likes tag takes the following attributes: 437 | 438 | - `content` - The `XF\Entity\Post` or `XF\Entity\ProfilePost` entity to display the 'likes' text for. 439 | - `url` - The URL to display when the 'likes' text is clicked. 440 | 441 | #### Format 442 | 443 | > You, tlisbon, kcho and 2 others 444 | 445 | The format is [👍 `abc` and x others] (where the 👍 'thumbs up' emoji represents the 'likes' icon and `abc` represents the usernames of the last three users who liked the post.) 446 | 447 | ### Sidebar tag 448 | 449 | See [Sectioned Tags](#sectioned-tags). 450 | 451 | ### SideNav tag 452 | 453 | See [Sectioned Tags](#sectioned-tags). 454 | 455 | ### Title tag 456 | 457 | Sets the page's title, both on the page in the `h1` tag and in the browser tab. 458 | ```html 459 | {{ phrase('my_page_title') }} 460 | ``` 461 | #### Further notes 462 | 463 | Whilst the title can, of course, be hardcoded, it is **highly recommended** that you use a phrase, both for internationalization and for the added customizability on the site administrator's end. 464 | 465 | ### Widget tag 466 | 467 | Includes a widget in the page, or adds a widget to a widget position. 468 | ```html 469 | 470 | ``` 471 | The widget tag takes the following attributes: 472 | 473 | - `key` - The widget key, as defined in the widget settings. 474 | - `position` - If set, changes the position that the widget will be rendered. 475 | - `class` - Not to be confused with HTML class, this is the PHP class containing the widget definition. 476 | - `title` - When the `class` attribute is used, you can use the `title` attribute to set the widget title. 477 | - You can also provide widget-specific options as attributes when the `class` attribute is used. 478 | 479 | !!! warning 480 | The `class` tag cannot be used in conjunction with the `key` tag. 481 | 482 | ### UserActivity tag 483 | 484 | Displays the status of a user, in terms of their last action and when that action occurred. 485 | ```html 486 | 487 | ``` 488 | The UserActivity tag takes the following attributes: 489 | 490 | - `user` - The user to display the status of. 491 | 492 | #### Format 493 | 494 | > Viewing page _Latest Case Files_ · 4 minutes ago 495 | 496 | The format is **[Activity Name]** **· [Time]** 497 | 498 | ### UserBanners tag 499 | 500 | Displays the user's banners in a horizontal list. 501 | ```html 502 | 503 | ``` 504 | The UserBanners tag takes the following attributes: 505 | 506 | - `user` - The user to display the user banners of. 507 | 508 | #### Example 509 | 510 | ![An example result of the UserBanners tag.](files/images/example-userbanners-tag.png) 511 | 512 | An example result of the UserBanners tag. 513 | 514 | ### UserBlurb tag 515 | 516 | Displays a one-line summary of a user's profile. 517 | ```html 518 | 519 | ``` 520 | The UserBlurb tag takes the following attributes: 521 | 522 | - `user` - The XenForo User Object to display the blurb of. 523 | 524 | #### Format 525 | 526 | > FBI Consultant · 43 · From United States of America 527 | 528 | The format is **[Role / Custom Title] · Age · Location** 529 | 530 | ### Username tag 531 | 532 | Displays the user's username, optionally with a tool-tip. 533 | ```html 534 | 535 | ``` 536 | The Username tag takes the following attributes: 537 | 538 | - `user` - The XenForo User Object to display the name of. 539 | - `notooltip` - Whether or not the tool-tip should be disabled. 540 | - `href` - The link to navigate to when the username is clicked. 541 | 542 | !!! warning 543 | The tool-tip will not be displayed if an `href` is set, as it won't work and might be misleading to users. 544 | 545 | ### UserTitle tag 546 | 547 | Displays the user's title. 548 | ```html 549 | 550 | ``` 551 | The UserTitle tag takes the following attributes: 552 | 553 | - `user` - The XenForo User Object to display the user title of. 554 | 555 | ### Sectioned tags 556 | 557 | Sectioned Tags all call the function `modifySectionedHtml`. 558 | The HTML element that they change is simply the tag name. So the `sidebar` tag will modify the sidebar HTML, etc. 559 | 560 | #### Example 561 | ```html 562 | 563 |

    My Magical Sidebar!

    564 |
    565 | ``` 566 | #### Common attributes 567 | 568 | - `mode` - The mode of the modification. (See Modification modes) 569 | 570 | #### Modification modes 571 | 572 | By default, the modification mode is `replace`. (i.e. if the attribute is not specified.) 573 | 574 | - `prepend` - Places the contents of the tag at the beginning of the element's HTML. 575 | - `append` - Places the contents of the tag at the end of the element's HTML. 576 | - `replace` - Replaces the element's HTML with the contents of the tag. 577 | --------------------------------------------------------------------------------