[Из песочницы] Используем Yii2. Пишем очередную CMS или попытка значительно ускорить разработку при минимальных накладных расходах

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

Идея проекта yicms заключалась в том, чтобы собрать все наработки для фреймворка Yii2, которые казались удобными в некую систему, во главе угла которой должно ставиться удобство использования, гибкость и возможность с помощью ее инструментов значительно ускорить разработку типовых сайтов. Данный проект разрабатывался мной для «души», однако в настоящий момент он уже находится в стадии беты.
Суть yicms находиться где-то посередине между возможностями фреймворка и обычными возможностями CMS. Это не CMS в обычном понимании этого слова, а просто набор модулей, которые позволяют значительно ускорить разработку сайта при помощи своих инструментов, при этом имея полный «доступ» к возможностям фреймворка. Основные фичи которые заложены в yicms это админка, генерируемая на лету и автоаннотируемые классы, которые позволяют использовать автодополнение IDE и иметь интуитивно понятный интерфейс.

Установка и настройка


Итак, начнем знакомство с yicms с его установки. Для знакомства проще всего воспользоваться Open Server или любой другой аналогичной серверной платформой. Поскольку yicms имеет жесткую зависимость от Yii для начала установим его. Для простоты будем использовать шаблон basic:

composer create-project --prefer-dist yiisoft/yii2-app-basic basic

После того, как composer установит фреймворк дополним раздел require файла composer.json четырьмя модулями yicms и запустим composer update.

  "require": {
      "php": ">=5.4.0",
      "yiisoft/yii2": "~2.0.14",
      "yiisoft/yii2-bootstrap": "~2.0.0",
      "yiisoft/yii2-swiftmailer": "~2.0.0 || ~2.1.0",
      "iliich246/yii2-yicms-common": "dev-master",
      "iliich246/yii2-yicms-pages": "dev-master",
      "iliich246/yii2-yicms-essences": "dev-master",
      "iliich246/yii2-yicms-feedback": "dev-master"
  },


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

Далее нужно не забыть сконфигурировать сервер. В Open Server используем Apache и сконфигурируем .htaccess следующим образом:

Для корневого файла .htaccess:

Options +FollowSymLinks
IndexIgnore */*
RewriteEngine on

RewriteCond %{REQUEST_URI} !^/(web)
RewriteRule ^assets/(.*)$ /web/assets/$1 [L]
RewriteRule ^css/(.*)$ web/css/$1 [L]
RewriteRule ^js/(.*)$ web/js/$1 [L]
RewriteRule ^images/(.*)$ web/images/$1 [L]
RewriteRule ^files/(.*)$ web/files/$1 [L]
RewriteRule (.*) /web/$1

RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . web/index.php


Для файла .htaccess в директории web

RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . index.php


Следующим шагом будет накатить миграции каждого модуля yicms в базу данных. В консоли последовательно выполняем следующие команды:

yii migrate/up --migrationPath=@vendor/iliich246/yii2-yicms-common/Migrations

yii migrate/up --migrationPath=@vendor/iliich246/yii2-yicms-pages/Migrations

yii migrate/up --migrationPath=@vendor/iliich246/yii2-yicms-essences/Migrations

yii migrate/up --migrationPath=@vendor/iliich246/yii2-yicms-feedback/Migrations

Теперь приступим к конфигурации в файле config/web.php.

Раздел modules настроим следующим образом:

'modules' => [
   'common' => [
      'class' => 'Iliich246\YicmsCommon\CommonModule',
   ],
   'pages' => [
      'class' => 'Iliich246\YicmsPages\PagesModule',
   ],
   'essences' => [
      'class' => 'Iliich246\YicmsEssences\EssencesModule',
   ],
   'feedback' => [
      'class' => 'Iliich246\YicmsFeedback\FeedbackModule',
   ],
   'redactor' => 'yii\redactor\RedactorModule',
],


Раздел bootstrap настроим следующим образом:

'bootstrap' => ['log', 'common', 'pages', 'essences', 'feedback'],


Раздел user настроим следующим образом:

'user' => [
    'identityClass' => 'Iliich246\YicmsCommon\Base\CommonUser',
    'enableAutoLogin' => true,
],


Снимем комментирование раздела urlManager:

'urlManager' => [
    'enablePrettyUrl' => true,
    'showScriptName' => false,
    'rules' => [
    ],
],


Фреймворк Yii2 и система yicms успешно настроены.

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

wszcn6cnsn9mjmguj_cq5mwkt8k.jpeg

Описание модулей Common и Pages


Система yicms состоит из двух разделов. Dev раздела, который должен быть доступен только разработчику (в ней можно поломать код) и Admin раздела должен быть доступен администратору сайта (в ней осуществляется управление контентом). Для начала работы необходимо перейти в Dev раздел, для этого надо использовать следующий URL:

http://<ваш тестовый домен>/web/common/dev/login-as-dev?hash=123456&asDev

После этого должна открыться Dev панель:

hy2mzrfv8ln9m2jtpk_xbnx4b1q.jpeg

Теперь начнем разбираться с функционалом yicms. Для начала в модуле Pages мы создадим страницу с именем index, который будет представлять собой главную страницу сайта. Зайдем в List of pages и создадим новую страницу с именем index. По умолчанию для страницы будут созданы поля title, meta_description и meta_keywords. Дополнительно создадим еще одно поле через кнопку add new field с именем text, все его параметры оставим по умолчанию.

Теперь если пройти по ссылке

http://<ваш тестовый домен>/web/pages/admin

откроется админ раздел. В нем будет доступна страница index для заполнения контентом. Заполним все поля произвольным текстом.

В момент создания в dev разделе страницы index был создан класс Index в директории yicms/Pages/Models в котором так же были созданы аннотации для полей title, meta_description, meta_keywords, а также для поля text, которое мы создали на лету. Объект этого класса мы сможем использовать для получения значения полей. Рассмотрим, как это можно сделать. В стандартном контроллере SiteController шаблона basic в действии actionIndex напишем следующее:

/**
 * Displays homepage.
 *
 * @return string
 * @throws \Iliich246\YicmsPages\Base\PagesException
 */
public function actionIndex()
{
    $indexPage = \app\yicms\Pages\Models\Index::getInstance();

    return $this->render('index', [
        'indexPage' => $ indexPage
    ]);
}


Поскольку класс \app\yicms\Pages\Models\Index был сгенерирован автоматически, в IDE будет работать автодополнение для него. Теперь $indexPage можно использовать в виде index. Теперь откроем вид index. Удалим оттуда все лишнее и напишем:

title = $indexPage->title;

$this->registerMetaTag([
    'name' => 'description',
    'content' => $indexPage->meta_description
]);

$this->registerMetaTag([
    'name' => 'keywords',
    'content' => $indexPage->meta_keywords
]);
?>

text ?>


Аннотация переменной $indexPage необходимо для работы автодополнения IDE. Теперь используем объект $indexPage. Все поля, которые мы создавали в dev разделе для страницы index доступны в объекте класса Index, которые, благодаря автоаннотации, дополняются IDE после ввода стрелочного оператора.

Итого мы за короткое время создали страницу Index для которой автоматически создалась админ панель для редактирования ее контента. Мы использовали программирование только для создания объекта страницы в стандартном контроллере фреймворка, и в виде, в котором мы могли быстро использовать объект страницы благодаря автозаполнению и интуитивно понятному интерфейсу.

Мы рассмотрели лишь минимальную часть возможностей yicms. Теперь мы немного усложним главную страницу сайта используя yicms.

yicms поддерживает мультиязычность из коробки. Для активации второго языка в dev разделе common модуля выберем list of languages, далее выберем русский язык и в нем активируем его:

kaehuwkq6y1qeubwmue9obzqtv4.jpeg

Теперь в admin панели мы сможем заполнять поля для нескольких языков. Можно создать новые языки в системе. Однако надо будет переводить админку, используя штатные средства фреймворка. Поэтому при создании нового языка надо придерживаться ISO. Для элементов управления контентом просто добавится новая плашка с новым языком.

Компонент file blocks


Далее рассмотрим элемент file blocks. В dev панели страницы index создадим файловый блок с программным именем document. Остальные настройки блока оставим по умолчанию. Теперь в админ панели для страницы индекс стало доступно редактирование файла document. Загрузим произвольный файл. Теперь ссылку для его загрузки можно вывести на index странице сайта:

Document

document ?>


Теперь на странице по ссылке будет загружаться файл, который мы загрузили в админ палели. По умолчанию файловый блок работает в режиме одного файла на все языки. Однако в dev панели можно переключить Language type на Translateable type и тогда для каждого языка необходимо будет загружать свой файл. В этом случае загружаться по ссылке будет тот файл, язык которого в данный момент активен на сайте. Разумеется, можно загружать и для конкретного языка, что будет рассмотрено в дальнейшем.

Следующим шагом в dev панели создадим файловый блок recipes для которого укажем type как multiple type. В этом режиме для одного файлового блока можно загружать много файлов. Для этого в админ панели, в появившейся вкладке recipes загрузим несколько произвольных файлов.
После этого в виде index выводим список файлов:

Recipes

recipes as $recipe): ?>


В режиме multiple type файловый блок можно обходить через foreach или итератор.

Компонент images block


Теперь рассмотрим элемент images block. Создадим для страницы index блок picture, оставив все параметры по умолчанию. Аналогично файловым блокам в админ панели появилась возможность загружать изображение picture. Загрузим произвольное изображение. Теперь в виде index напишем следующее:

Image


Аналогично файлам изображения по умолчанию загружаются одно на все языки, однако и для изображений это поведение можно изменять в dev панели и загружать свое изображение для каждого языка. Так же можно в один блок загружать много изображений.

Создадим в dev панели блок изображений gallery, тип которого установим как Multiple images. Для изображений можно так же на лету навешивать текстовые поля. Поэтому в модальном окне настройки блока gallery зайдем в раздел «View image block field». В этом разделе для изображений создадим поля с именем alt и name. Теперь в админ панели для каждого изображения появилась возможность задать текстовые поля alt и name. Так же в yicms для изображений из коробки доступна возможность «кропа» и создания миниатюр. Для примера создадим шаблон миниатюр для блока gallery. В модальном окне блока изображений зайдем в раздел «Config images thumbnails». В нем создадим конфигуратор миниатюр с именем «x2» и параметрами divider = 2 и quality = 80. По имени мы сможем обращаться к миниатюре, divider определят насколько уменьшать размеры оригинального изображения, quality определяет качество сжатия миниатюры. Далее создадим еще один шаблон миниатюры с параметрами: program_name = «x5», divider = 5, quality = 50. Теперь, когда мы загружаем изображение из админ панели, для него автоматически будут создаваться две миниатюры с выбранными нами параметрами.

Теперь загрузим несколько изображений в админ панели для группы gallery. После того, как изображение создано и загружено можно заполнить поля alt и name, шаблоны которых мы создали в dev панели. Посмотрим, как это все выводить на сайт.

gallery->getImages() as $image): ?>
<?= $image->alt ?> <?= $image->alt ?> <?= $image->alt ?>

name ?>


Здесь метод getImages () отдает массив объектов автоаннотированного класса app\yicms\Pages\Models\Index\Images\GalleryImage в котором были созданы аннотации для полей alt и name, которые мы создали на лету и для них доступно автодополнение IDE. Так же вызывая у объекта метод outputThumbnail ()мы переключаем объект в режим вывода миниатюр.

Для блоков изображений возможно создавать не только поля, но и условия (conditions) которые будут нами рассмотрены немного позднее. Так же поля и условия можно создавать для блоков файлов. Работает по аналогичному принципу.

Компонент conditions


Теперь рассмотрим следующий элемент. Это условия (conditions). Условия удобны для создания в админ панели списков, чекбоксов и тд. Которые будут определять определенные поведения при работе страниц. Рассмотрим на примере. Создадим в dev панели на странице index условие с именем background. Тип выберем «select dropdown type». После создания условия пройдем в раздел «Config condition options». Создадим три condition value со значениями transparent, red и green.

После этого в админ панели страницы index появится условие с выпадающим списком со значениями TRANSPARENT, RED и GREEN. Переводятся они в верхний регистр, поскольку в созданном автоаннотируемом классе они представлены в виде констант.

Теперь попробуем использовать созданное условие. Используем мы его несколько иным способом, чтобы показать другие возможности. Поскольку в сайте страница index главная, то будет семантически верно менять цвет фона сайта из ее настроек. Поэтому наше условие должно будет менять цвет фона на всем сайте, поэтому вставим мы его в layout контроллера. Откроем layout файл шаблона basic фреймворка Yii2 и создадим объект Index:


beginPage() ?>



    
    
    
    registerCsrfMetaTags() ?>
    <?= Html::encode($this->title) ?>
    head() ?>


beginBody() ?>
…


Поскольку объект класса Index является чем-то вроде синглтона, то вызывать статические метод getInstance () мы можем сколько угодно без риска осуществления лишних дублирующих запросов к БД и других накладных расходов при его создании. Теперь используя объект $pagesIndex мы изменим тег добавив в него инлайновый стиль:



Вызывая $pagesIndex→background→value ()и сравнивая с константой, которая была создана в автоаннотируемом классе \app\yicms\Pages\Models\Index\Conditions\Background мы выбираем цвет фона сайта.

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

app\yicms\Pages\Models\Index\Fields\Text


Этот файл был создан автоаннотатором во время создания нами в dev панели поля text для страницы index. Когда в шаблоне мы пишем = $indexPage→text?> создается нечто похожее на синглтон именно этого класса, который в шаблоне приводится к строке посредством магического __toString ().

Для изменения контента подпишемся на событие и обработаем его через функцию обратного вызова:

class Text extends Field
{
    public function init()
    {
        $this->on(self::EVENT_BEFORE_OUTPUT, function($event) {
            /** @var $event \Iliich246\YicmsCommon\Base\HookEvent */

        });
    }    
}


Событие EVENT_BEFORE_OUTPUT возникает перед выводом содержания поля. Попробуем изменить его. Для этого воспользуемся объектом события \Iliich246\YicmsCommon\Base\HookEvent:

$this->on(self::EVENT_BEFORE_OUTPUT, function($event) {
        /** @var $event \Iliich246\YicmsCommon\Base\HookEvent */

        $value = $event->getHook();
        $value = strrev($value);

        $event->setHook($value);
    });


Событие HookEvent получает значение поля до срабатывания события. В событии можно получить это значения через $event→getHook () изменить его, и отправить обратно в объект через событие используя $event→setHook ($value). Таким образом в событии мы перевернули задом наперед текст, который выводит поле Text.

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

Итого в рамках данной статьи мы рассмотрели два модуля yicms, а именно модули Common и Pages. В состав модуля Common входят такие компоненты как Fields, Files Blocks, Images Blocks и Conditions. Модуль Pages отвечает за создание объектов страниц, в которых присутствуют все элементы модуля Common. Т.е. все модули yicms зависят от основного модуля Common.

Модуль yii2-yicms-essences


Теперь познакомимся с модулем Essences. Данный модуль предназначен для создания сущностей типа категория→сущность к примеру категория товара→товар, тип транспортного средства (автомобили, мотоцикл) –> конкретное транспортное средство и тд. В рамках модуля эти элементы называются категории (categories) и представления (represents).

На примере покажем создание простейшего каталога типа категория→товар для интернет-магазина.

В Dev разделе модуля Essences зайдем в list of essences и там перейдем в раздел create new essence. Зададим ей программное имя products, а остальные настройки оставим по умолчанию. По умолчанию создается сущность с поведением: одно представление может принадлежать одной категории. Однако можно задать иные поведения: просто создание представлений без категорий, либо же одно представление может принадлежать нескольким категориям.

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

Итого сущность products создана. Для нее создались автоаннотируемые классы в директории app\yicms\Essences\Models. Далее в dev панели мы должны создать шаблоны класса и шаблоны представления. Все это аналогично тому, как мы создавали их для страницы index. Для них доступны в режиме создания на лету элементы Fields, Files Blocks, Images Blocks и Conditions. Как только мы закончили с созданием шаблонов в админ панели становится доступным создание категорий и представлений с элементами шаблонов, которые мы задали в dev панели.

Итак, создадим несколько категорий товаров. И для каждой категории товаров несколько товаров. Как только это сделано все это дело можно выводить на основной сайт. Рассмотрим, как это делается. В SiteController создадим действие actionProcuts:

/**
 *  
 * @return string
 */
public function actionProducts()
{
    $productsEssence = Products::getInstance();

    return $this->render('products', [
        'productsEssence' => $productsEssence
    ]);
}


Для него создадим вид products




categories as $category): ?>

Категория: name ?>

represents as $product): ?>

Товар: name ?>


Благодаря механизму автоаннотации медод $productsEssence→categories возвращает массив объектов автоаннотированного класса app\yicms\Essences\Models\ProductsCategory. Как и в предыдущих автоаннотированых классах в этом классе имеются аннотации на поля, которые мы создали на лету, IDE сможет использовать автодополнение. Так же стоит заметить, что автоаннотируемые классы разрабатывались с той идеей, что пользователь yicms сможет в них размещать собственную бизнес логику, т.е. в них можно писать свой код, а механизм автоаннотации изменяет специально помеченные области и никакой пользовательский код затронут не будет.

Метод $category→represents так же возвращает массив объектов app\yicms\Essences\Models\ProductsRepresents которые имеют все те же преимущества автоаннотации, что и для категорий. Разумеется существуют и более гибкие методы получения представлений. К примеру $category→getRepresentsQuery ()вернет объект ActiveQuery фреймворка Yii2 который можно дополнительно настроить, для уточнения запроса к БД, использовать для создания пагинации или ленивой загрузки.

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

Модуль yii2-yicms-feedback


Данный модуль предназначен для конструирования на сайте форм ввода информации гостями и пользователями сайта. Идеология работы осталась прежней, сначала мы в dev панели набираем требуемые для формы поля.

Рассмотрим работу модуля на примере. Создадим в dev панели в модуле feedback запись с программным именем messages. На странице настроек messages мы можем увидеть кнопку Feedback pages templates. По этой ссылке мы можем создать элементы типа field, files block, images block, conditions, которые смогут применяться для оформления контента страницы messages. Они ничем не отличаются от тех, что мы рассматривали для модуля Pages. Сейчас нас больше интересует раздел Feedback input templates. В этом разделе мы сможем создавать такие сущности как input fields, input files, input images и input conditions. Эти сущности как раз и необходимы для создания пользовательских форм ввода.

Создадим два input field с именами name и surname. Input image с именем photo. Input condition с именем agreement. Пока что оставим все настройки этих сущностей по умолчанию. В дальнейшем в этом примере мы настроим валидаторы, которые тоже навешиваются на эти сущности на лету (Валидаторы так же доступны и для сущностей модуля Common и работают по той же логике, как и для feedback модуля. Её мы рассмотрим немного позже).

Теперь применим модуль в нашем сайте. Для начала создадим в SiteController новое действие:

/**
 * @return string
 */
public function actionMessage()
{
    $messages = \app\yicms\Feedback\Models\Messages::getInstance();

    return $this->render('message', [
        'messages' => $messages
    ]);
}


Далее создадим вид message и напишем в нем следующее:



 'some-form',
    'options' => [
        'data-pjax' => true,
    ],
]);
?>
'btn btn-success']) ?>


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

input_name->isActive): ?>
   field($messages->input_name, $messages->input_name->key) ?>


input_surname->isActive): ?>
 field($messages->input_surname, $messages->input_surname->key) ?>


input_photo->isActive): ?>
 field($messages->input_photo, $messages->input_photo->key)
            ->fileInput() ?>


input_agreement->isActive): ?>
   field($messages->input_agreement, $messages->input_agreement->key)->checkbox() ?>


Для сущностей типа input необходимо использовать префикс input_, в остальном автоаннотации работают для них так же, как и для остальных вещей в yicms.

В начале идет проверка на активность поля, если поле неактивно, то оно не должно рендериться, а даже если и отрендерится, то данные, которые в него были заполнены не будут загружены на сервер. Активность поля можно включать/отключать в dev панели.

Далее идет стандартная форма фреймворка где нужно писать так

$form->field($messages->input_name, $messages->input_name->key) 


$messages→input_name вернет объект класса app\yicms\Feedback\Models\Message\InputFields\Name который является наследником Model фреймворка Yii2 и может быть использован в формах, $messages→input_name→key вернет корректное значение ключа, для того, чтобы форма могла быть загружена.

Теперь, когда мы заходим на страницу с формой, все ее поля рендерятся, но пока еще не работает загрузка на сервер. Сделать это очень просто. Я постарался интерфейс работы с feedback сделать максимально похожим на интерфейс работы моделей в фреймворке. Да, пришлось немного «пожертвовать» архитектурой, но в целом вышло так:

public function actionMessage()
{
    $messages = \app\yicms\Feedback\Models\Messages::getInstance();

    if ($messages->load(Yii::$app->request->post()) && $messages->validate()) {
        $messages->handle();
    }

    return $this->render('message', [
        'messages' => $messages
    ]);
}


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

Разумеется, можно использовать автоаннотируемый класс:

\app\yicms\Feedback\Models\Messages


class Messages extends Feedback
{
    /**
    * @return self instance .
    */
    public static function getInstance()
    {
        return self::getByName('messages');
    }

    /**
     * @inheritdoc
     */
    public function init()
    {
        $this->on(self::EVENT_AFTER_HANDLE, function($event) {

            Yii::$app->mailer->compose('message', [
                'message' => $this
            ])  ->setFrom('from@domain.com')
                ->setTo('to@domain.com')
                ->setSubject('Message subject')
                ->send();
        });
    }
}


В данном примере мы подписались на событие EVENT_AFTER_HANDLE. Событие возникает после того, как данные формы были сохранены на сервер. В данном случае мы отправляем письмо, в шаблон которого можем вставить сохраненные данные формы:



Mail on message

Name: input_name->value ?>

Surname: input_surname->value ?>


$message→input_name→value просто возвращает текущие данные, которые были сохранены в БД.

Теперь посмотрим как работают валидаторы. В dev панели на сущность input field с именем name добавим валидатор Required. Это стандартный валидатор yii\validators\RequiredValidator фреймворка. Когда мы его добавили, он еще не активен, поскольку кнопка с его именем белого цвета. Для того чтобы его активировать, надо нажать на эту кнопку, откроется окно настройки валидатора. Там надо установать чекбокс и активировать его. Заодно там можно настроить все остальные параметры валидатора, такие как сообщения об ошибках на всех языках системы и т.д. После сохранения, иконка валидатора становится зеленой, это значит, он стал активным и станет применяться при валидации форм.

На input_condition c именем agreement навесим валидатор compare. И настроим его на сравнение с 1, тип number и оператор ==. Дополнительно в текстовые поля ошибки можно написать сообщение, типа «Вы должны обязательно согласиться с условиями оферты». Теперь на форме валидация будет проходить только в случае, когда чекбокс agreement выбран.

Итого в этой статье мы рассмотрели основные возможности системы yicms.

P.S. Работа над yicms была очень интересна и увлекательна для меня, и лично мне система кажется достаточно удобной. Однако, это мое субъективное мнение) Мне интересно, будет ли она полезна комьюнити, стоит ли мне привести в порядок репозитории, написать документацию, покрыть систему тестами и т.д.

© Habrahabr.ru