October CMS простая сортировка. (simple sortable)

370

Я коснусь только простой сортировки, пока что. Довольно хороший гайд есть на форуме октобера https://octoclub.ru/d/21-sortable-simple-tree-nested-tree. Я опишу чуть детальней.

Например есть тестовый проект. В нем «магазин с товарами».

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

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

Сначала указываю настройки reorder в контроллере, подключаю Behaivor и файл yaml.

<?php namespace Alex\Store\Controllers;use Backend\Classes\Controller;
use BackendMenu;class Prod extends Controller
{
    public $implement = [
        'Backend\Behaviors\ListController',
        'Backend\Behaviors\FormController',
        'Backend\Behaviors\ReorderController',   // добавил
    ];
    
    public $listConfig = 'config_list.yaml';
    public $formConfig = 'config_form.yaml';
    public $reorderConfig = 'config_reorder.yaml';  // добавил    public function __construct()
    {
        parent::__construct();
        BackendMenu::setContext('Alex.Store', 'main-menu-item');
    }
}

Далее вставляю config_reorder.yaml в папке /controllers/prod. Соотв указываю заголовок, модель. NameForm скажу чуть позже для чего.

title: 'Сортировка продуктов'                 
modelClass: Alex\Store\Models\Prod
nameFrom: title
toolbar:
    buttons: reorder_toolbar

Добавляем собственно вьюшку reorder.htm в туже папку с нашим контроллером /controllers/prod, так как сортировка происходит на отдельной странице.

<?php Block::put('breadcrumb') ?>
    <ul>
        <li><a href="<?= Backend::url('alex/store/prod/reorder') ?>">Category</a></li>
        <li><?= e($this->pageTitle) ?></li>
    </ul>
<?php Block::endPut() ?><?= $this->reorderRender() ?>

Далее в list tool bar добавляем кнопку на вьюшку.

<a href="<?= Backend::url('alex/store/prod/reorder') ?>" class="btn btn-default oc-icon-list"><?= e(trans('backend::lang.reorder.default_title')) ?></a>

Так отлично. Появилась кнопка а админке.

Собственно этой всей настройки можно было избежать и октобер бы сам сгенерировал как надо, при указании галочки ReorderBehavior, о чем говорил в самом начале.

Кнопка появилась, но при переходе с нее возникает ошибка, говорящая о том, что мы еще не добавили в модель Трейт сортировки.

Добавляем в нашу модель.

use \October\Rain\Database\Traits\Sortable; // Для Sortable

И далее, чтобы этот трейт корректно отрабатывал нашей моделе и соотв таблице нужно поле sort_order. Добавляем его. Тип число, nullable true.

Отлично. Заходим в сортировку. И лично у меня это выглядит так. Есть итемы, но они пустые. Это собственно поле nameForm, которое указывается в config_reorder.yaml. У меня было написано title, хотя исходя из моей таблицы нужно указать name.

Указал name. Результат

!Есть один нюанс. Он касается поля sort_order. В идеале это все делать на новой сущности. Но если мы редактируем существующую, то у поля sort_order будет стоять 0 или Null в таблице. Как было подмечено в статье https://octoclub.ru/d/21-sortable-simple-tree-nested-tree надо в ручном режиме проставить валидные значения для текущих итемов.

В моем случае все не сложно и выглядит так.

Отлично. Все почти готово. Для примера я хочу выделить классный Сыр и поставить его первым. При стандартном получении из модели, сортировка происходит автоматически.

// это из компонента Prod
public function init()
{
    $this->items =  ProdModel::get();
}

Результат

Если же вы получаете итемы другими способами, то может указать дополнительно orderBy по полю sort_order.

0
 

October CMS перевод сайта. (Translate site). Часть 3

799

В прошлой статье мы добились перевода контента путем локализации моделей. Продолжим дальше дорабатывать наш сайт. Как сказал, я долокализировал валюту и цену. Цену мне пришлось перевести из типа Number в строку, чтобы плагин отработал. Тк в идеале цена 60 рублей идет как 1 доллар, то есть числовые значения разные. Можно было решить разными путями, я сделал так.

Локализация ссылок

В нашем случае рассматриваем ссылки, которые находятся в шапке. RainlabTranslate предусмотрено, форматирование ссылки при назначении ее через фильтр page.

<ul class="nav navbar-nav">
    <li class="separator hidden-xs"></li>
    <li class="{% if this.page.id == 'home' %}active{% endif %}"><a href="{{ 'home'|page }}">Basic concepts</a></li>
    <li class="{% if this.page.id == 'ajax' %}active{% endif %}"><a href="{{ 'ajax'|page }}">AJAX framework</a></li>
    <li class="{% if this.page.id == 'plugins' %}active{% endif %}"><a href="{{ 'plugins'|page }}">Plugin components</a></li>
    <li class="{% if this.page.id == 'plugins' %}active{% endif %}"><a href="{{'store'|page}}">Store</a></li>
</ul>

При переключении языка, автоматически меняется локаль ссылки. В принципе это охватывает большинство кейсов связанных с роутингом внутри сайта. Те ссылки что присутствуют в контенте, мы можем менять через локализацию модели (см. статью 2).

Переменные локализации

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

{{'Store' |_}}

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

Далее заполняем ее для всех локалей.

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

Локализация SEO

0
 

October CMS перевод сайта. (Translate site). Часть 2

406

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

Перевод кастомных компонентов и данных из моделей

Давайте установим Билдер, чтобы сделать небольшой плагин. https://octobercms.com/plugin/rainlab-builder

В нашей демо версии создадим мини каталог-магазин.

Не будем заморачиваться, добавим простые поля товара. Нам этого хватит.


Отлично. Уж извините 🙂 ничего быстрее не придумал, но буду продавать картошку и помидоры и еще что-нибудь. НО! интернационально на всех языках. Поэтому это круто.
Сделал пару товаров.

Добавлю компонентик с помощью которого выведу информацию на фронтенд.
Код:

<?php
namespace Alex\Store\Components;

use Cms\Classes\ComponentBase;
use Alex\Store\Models\Prod as ProdModel;


class Prod extends ComponentBase
{

    public $items;

    public function init()
    {
        
    }

    public function onRun()
    {

    }

    public function componentDetails()
    {
        return [
            'name' => 'Products',
            'description' => 'Some Products'
        ];
    }

    public function defineProperties()
    {
        return [
        ];
    }

}

Регистрируем компонент и выводим.

Plugin.php

<?php namespace Alex\Store;

use System\Classes\PluginBase;

class Plugin extends PluginBase
{
    public function registerComponents()
    { 
        return [
        'Alex\Store\Components\Prod' => 'prods'
        ];
    }

    public function registerSettings()
    {
    }
}

Шаблон:

title = "Store"
url = "/store"
layout = "default"

[prods]
==
<div class="jumbotron">
    <div class="container">
        {% component 'contenteditor' file="lang/home/store" class='' fixture='div'%}
    </div>
</div>

<div class="container">
    <div class="prods">
        {% for prod in prods.items %}
        <div class="prod">
            <div class="name">{{prod.name}}</div>
            <div class="descr">{{prod.descr}}</div>
            <div class="price">{{prod.price}}</div>
        </div>
        {% endfor %}
    </div>
</div>

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

public function onRun()
{
    $this->items =  ProdModel::get();
}

Как то так.

Все хорошо. Только продукты я вывел на русском. Хотя мы договорились, что дефолтный язык будет английским. Это я поправлю. Теперь надо подумать как локализовать данные из компонента.
Точнее как локализовать модель. Изначально я делал не столь гибко, тк не до конца разобрался. Я пошел по пути дополнения полей в модель. Это раздуло таблицу и к тому же вышло не гибко. У меня был случай на 2 языка. А что если их будет 2-5 десять?

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


class Prod extends Model
{
    use \October\Rain\Database\Traits\Validation;

    public $implement = ['RainLab.Translate.Behaviors.TranslatableModel'];
    public $translatable = ['name','descr'];

Добавили трейт и указали поля для перевода. В итоге получили дефолтный функционал.

И при этом наша таблица осталась с теми же полями, но добавилась отдельная таблица Rainlab с атрибутами.

Отлично. Теперь под дефолтной локалью, кою я поменял на En выводятся товары на английском, под ru соответственно на русском.

Стоит добавить локализацию для валюты и цены. Тогда будет совсем ок.

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

0
 

October CMS перевод сайта. (Translate site). Часть 1

510

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

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

Вот список на который я опирался:
https://medium.com/@octobercms/building-responsive-multi-language-website-with-blog-and-static-pages-using-october-cms-94151610f1ff
https://habr.com/ru/post/305802/
https://medium.com/@octobercms/building-responsive-multi-language-website-with-blog-and-static-pages-using-october-cms-part-2-63ba31243f6a

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

Постановка задачи

  • локализация статического контента
  • локализация динамического контента в плагинах и компонентах
  • локализация интерфейса (меню, футер, функциональные элементы)
  • локализация ссылок

Что нам понадобиться

В моем случае я использовал смежный функционал двух плагинов: RainlabTranslate и ContentEditor.
Далее я объясню как их использовать и для чего каждый.

Локализация статики

Я буду показывать на demo теме октября. Ее переводом мы и займемся.

Локализации статики — это самое простое. Для этого я использовал ContentEditor. Во первых это удобно для клиента, а во вторых он замечательно расширяется RainlabTranslate.

После установки переходим в настройку Транслейта и устанавливаем языки. Их название и локали.

Я добавил русский и сделал его по умолчанию.

Отлично. Теперь у нас доступен режим подраздела для наших локалей.
Можем перейти и попробовать: http://localhost:8888/ru и http://localhost:8888/en. Пока что никаких изменений.

Теперь задаем контент эдитор. Суть плагина в том, что мы оборачиваем контент в паршил, которые хранятся в директории контента themes/demo/content. Расширение транслейта происходит за счет того, что у нас создаются поддиректории для каждого языка автоматом.

Для контент эдитора я использую твиг тег такого вида:

{% component 'contenteditor' file="lang/home/numbers" class='' fixture='div'%}

Создаем директорию themes/demo/content/lang. И так же в главном layout обяъявляем компонент контент эдитора — [contenteditor]

И далее для примера переведем кусок контента на главной. Перенесем контент блок wellcome.htm в lang и импортируем в виде паршила контент эдитора.

В таком виде:

title = "Demonstration"
url = "/"
layout = "default"

==
<div class="jumbotron">
    <div class="container">
        {% component 'contenteditor' file="lang/home/welcome" class='' fixture='div'%}
    </div>
</div>

Отлично у нас появилась кнопка редактирования и область контента стала подсвеченной.

Теперь можем отредактировать контент под разными локалями http://localhost:8888/ru и http://localhost:8888/en. Кстати я передумал 🙂 основной язык все таки будет английский а переводить будем на русский. Поэтому на локаль en оставляем без изменений, а на локали ru переводим.
Как то так.

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

Плюсы:
— удобно для пользователя
— игнорируется сложность верстки
— редактирование любых страниц без использования StaticPages плагина
— сколько угодно языков

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

Дальнейший перевод рассмотрим в следующих частях.

0
 

October CMS добавляем экшн в контроллер лист (Controller list action)

491

Есть список, в моем случае например товаров. Необходимо добавить какое-либо действие для обработки списка. Мне было необхдимо сделать функционал клонирования товара.

Мы идем в директорию с плагином и собственно с контроллером, который модифицируем.
У меня для примера /alex/catalog/controllers/Products.php
Так же нам понадобиться шаблон тулбара.

Тут уже кнопка у меня добавлена. Изначально шаблон был без нее.
Шаблон находим /alex/catalog/controllers/products/_list_toolbar.htm

Сам шаблон

<div data-control="toolbar">
    <a href="<?= Backend::url('webfamily/catalog/products/create') ?>"
       class="btn btn-primary oc-icon-plus"><?= e(trans('backend::lang.form.create')) ?></a>
    <button
            class="btn btn-default oc-icon-trash-o"
            disabled="disabled"
            onclick="$(this).data('request-data', {
            checked: $('.control-list').listWidget('getChecked')
        })"
            data-request="onDelete"
            data-request-confirm="<?= e(trans('backend::lang.list.delete_selected_confirm')) ?>"
            data-trigger-action="enable"
            data-trigger=".control-list input[type=checkbox]"
            data-trigger-condition="checked"
            data-request-success="$(this).prop('disabled', true)"
            data-stripe-load-indicator>
        <?= e(trans('backend::lang.list.delete_selected')) ?>
    </button>


    <button
            class="btn btn-primary oc-icon-copy"
            onclick="$(this).data('request-data', {
            checked: $('.control-list').listWidget('getChecked')
        })"
            data-request="onCopy"

    >Скопировать
    </button>
    <a href="<?= Backend::url('webfamily/catalog/products/import') ?>"
       class="btn btn-primary"><?= e(trans('webfamily.catalog::lang.common.import')) ?></a>
</div>

Как видно в шаблоны указаны все кнопки в том числе и новую мной созданную «Скопировать». На нее ставим data-request=«Ваш обработчик» и фунцкию onclick, я взял с копки удаления. Суть js забирать id-ники чекнутых итемов и передавать их на экшн.

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

/**
     * @return
     * Копирование товара
     */
    public function onCopy() {
        // Проверка на чекнутые товары
        if (($checkedIds = post('checked')) && is_array($checkedIds) && count($checkedIds)) {
            // Первый чекнутый // Делаем действия либо с одним
            //  $copy_id = post("checked")[0];
            // Либо делаем действие со списком ID
              foreach ($checkedIds as $checkedId) {
                 // Делаем дела ........... 
             }       
            Flash::success('Товар скопирован');
        }
        return $this->listRefresh();
    }

Я обрабатывал только первый чекнутый. То есть у меня он в принципе должен быть выбрать один. Вы можете обработать несколько. На выходе я вывожу Флэш сообщение и перезагружаю список.
Собственно все.

0
 

Octber CMS Builder установка поля загрузки картинки

441

Когда я делал поля как обычно используя Page Builder, я наткнулся на ошибку: Model Alexdzen\Content\Models\Brands’ does not contain a definition for ‘image’.

Спросил в чате, оказалось, что добавления поля картинки не столь очевидно как хотелось бы.

Наше значение не указывается в таблице как поле.

Допустим моя задача сделать галлерею. Таблица состоит из Id и Image. Image — не указываем в таблице. Мы идем в нашу модель, в моем случае это Brands.php и прописываем поле image через attachOne (или attachMany).

<?php namespace Alexdzen\Content\Models;

use Model;

/**
 * Model
 */
class Brands extends Model
{
    use \October\Rain\Database\Traits\Validation;
    
    /*
     * Disable timestamps by default.
     * Remove this line if timestamps are defined in the database table.
     */
    public $timestamps = false;


    /**
     * @var string The database table used by the model.
     */
    public $table = 'alexdzen_content_brands';

    /**
     * @var array Validation rules
     */
    public $rules = [
        'image' => 'required',
    ];

    public $attributeNames = [
        'image' => 'Картинка',
    ];

    public $attachOne = [
        'image' => 'System\Models\File'
    ];
}

Соответсвенно в form.yml указываем наше поле из модели.

Ну и чтобы картинка отображался в на странице Контроллера прописываем image. Значение поля я ставлю partial и путь указываю $/alexdzen/content/partials/_image3.htm.

Код паршила

<?php if($value): ?>
    <img src="<?php echo $value->path; ?>" style="max-width: 300px; max-height: 150px;">
<?php endif; ?>

И соответсвенно получаю результат.

0
 

October CMS как переопределить(заоверрайдить) методы Rain Lab Users. (Override Rain Lab)

488

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

Поэтому мы идем следующем путем.

У нас есть компонент login.htm. Изначально он использует компонент Account.php Rainlab.

title = "Вход"
url = "/login"
layout = "default"

[account]
forceSecure = 0
==
<main class="page__main">
     {% partial 'user_account/singin' %}
</main>

Допустим у вас Плагин Myplugin. В нем мы создадим свой Аккаунт компонент и отнаследуем от Rainlab.

<?php namespace Alexti\Myplugin\Components;

class Account2 extends \RainLab\User\Components\Account
{
    public function componentDetails()
    {
        return [
            'name' => 'Account2',
            'description' => 'Account2'
        ];
    }

    /**
     * Activate the user
     * @param string $code Activation code
     */
    public function onActivate($code = null)
    {
       // do something
    }
}

Соответсвенно наш компонент надо зарегистрировать в Plugin.php

public function registerComponents()
{
    return [
        'Alexti\Myplugin\Components\Account2' => 'Account2',
   ]
}


Все готово. Теперь Account будет отрабатывать как обычно, но методы которые мы заоверайдили будут отрабатывать в нашем компоненте. И соответсвенно по всех шаблонах необходимо подключить наш компонент.

title = "Вход"
url = "/login"
layout = "default"

[Account2]
forceSecure = 0
==
<main class="page__main">
     {% partial 'user_account/singin' %}
</main>
0
 

Установка WordPress Мультисайт(multisite) на локальном сервере. Режим поддоменов.

554

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

Для чего он нужен? Для организации подразделов или поддоменом, при этом используя только один инстанс WordPress (т.е один каталог CMS). Более подробно вы можете ознакомиться в документации.

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

Проблема

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

Итого мы имеем чисто установленный вордпресс на localhost.

Хорошо. Теперь переходим к установки мультисайта по документации.
Заходим в wp-config.php и проставляем объявление:

define( 'WP_ALLOW_MULTISITE', true );

Теперь у нас в панели инструментов появилась настройка сети.

Переходим. И тут мы и сталкиваемся с проблемой.
По идее по гайду у нас должен появится выбор между двумя режимами: поддомены и подкаталоги
https://wp-kama.ru/wp-content/uploads/2016/09/multisite-install2.png.

Но в нашем случае мы видим только настройку подкаталоги, так как мы используем localhost.

Решение

Я не нашел явного решения через локалхост и через MAMP. Возможно есть какой то подход.

Мое решение выглядет так. Не понимаю в чем ограничение localhost, возможно это связано с зарезервированым названием. Мы изменяем нашу сборку Docker.
В сервисе wordpress мы прописываем hostname — с названием нашего произвольного сайта.

version: '3'
services:
  db:
    image: mysql:8
    container_name: mysql
    restart: always
    command: "--default-authentication-plugin=mysql_native_password"
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: wpdb
      MYSQL_USER: user
      MYSQL_PASSWORD: password


  wordpress:
    image: wordpress:4.9.8
    container_name: wordpress
    restart: always
    volumes:
      - ./:/var/www/html/
    environment:
      WORDPRESS_DB_HOST: db
      WORDPRESS_DB_NAME: wpdb
      WORDPRESS_DB_USER: user
      WORDPRESS_DB_PASSWORD: password
    hostname: mysite.ru
    ports:
      - 80:80
      # - 443:443

  phpmyadmin:
    image: phpmyadmin/phpmyadmin
    restart: always
    ports:
      - 3333:80
    environment:
      PMA_HOST: db
      MYSQL_ROOT_PASSWORT: password

Перзапускаем докер.
Отлично. Но нужно прописать алиас на локальной машине для нашего сайта. В Mac/Linux заходим директорию etc и выбираем файл hosts.

sudo nano /etc/hosts

Прописываем алиас и сохраняем от имени администратора.

Отлично теперь наш сайт доступен по урлу mysite.ru (или тот который вы прописали).

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

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

Жмем установить идем дальше по инструкции

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

Для проверки давайте добавим поддомен. У меня это будет test.
Его соответсвенно надо зарегистрировать в /etc/hosts

Все поддомен активен.

0
 

Установка WordPress через Docker. Простой docker-compose. (Install wordpress with docker-compose)

540

Создаем файл docker-compose.yml

Наша сборка будет состоять из 3-ех стандартных сервисов:

  • Сервис WordPress. Будет включать в себя сервер и файлы CMS
  • Сервис для БД, в нашем случае MySQL
  • Ну и так же поставим phpMyAdmin

Службы будут описаны и указаны в файле docker-compose.yml, который сначала необходимо создать в нашем каталоге:

mkdir wordpress-docker
cd wordpress-docker
touch docker-compose.yml

Можем открыть папку в любом удобном редакторе

У нас должен быть пустой файл docker-compose.yml внутри папки wordpress-docker. Открываем файл и прописываем сервис для mysql.

version: '3'
services:
  db:
    image: mysql:8
    container_name: mysql
    restart: always
    command: "--default-authentication-plugin=mysql_native_password"
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: wpdb
      MYSQL_USER: user
      MYSQL_PASSWORD: password

В разделе сервисов мы добавили первое, которое называется db. Этот сервис связан образом mysql и, следовательно, предоставляет необходимый экземпляр базы данных MySQL.

command: "--default-authentication-plugin=mysql_native_password"

Эта команда

Эта команда позволяет нам запустить базу с включенной собственной аутентификацией по паролю.
Кроме того, нам нужно установить следующие переменные среды: MYSQL_ROOT_PASSWORD, MYSQL_DATABASE, MYSQL_USER и MYSQL_PASSWORD. Можете прописать свои.

Добавляем WordPress сервис

Добавляем соотвествующий кусок кода описывающий сервис

wordpress:
    image: wordpress
    container_name: wordpress
    restart: always
    volumes:
      - ./:/var/www/html/
    environment:
      WORDPRESS_DB_HOST: db
      WORDPRESS_DB_NAME: wpdb
      WORDPRESS_DB_USER: user
      WORDPRESS_DB_PASSWORD: password
    ports:
      - 8080:80
      - 443:443

Здесь мы используем образ wordpress. Вы можете указать любую версию вида wordpress:4.9.8. Я буду использовать последнюю актуальную.


Свойство volumes используется для подключения каталога /var/www/html контейнера к локальной папке приложения. Все изменения, которые мы будем вносит в файлы папки нашего приложения буду отображать сразу в контейнере.


Кроме этого, мы снова используем раздел environment для установки значений для четырех переменных: WORDPRESS_DB_HOST, WORDPRESS_DB_NAME, WORDPRESS_DB_USER и WORDPRESS_DB_PASSWORD.
Значение, присвоенное WORDPRESS_DB_HOST, должно быть идентификатором сервиса базы данных ( в нашем случае db).

И мы обязательно сопоставим внутренний порт 80 с внешним портом 8080, чтобы WordPress был доступен через http://localhost:8080.

Добавляем phpMyAdmin

Вставляем код

phpmyadmin:
    image: phpmyadmin/phpmyadmin
    restart: always
    ports:
      - 3333:80
    environment:
      PMA_HOST: db
      MYSQL_ROOT_PASSWORT: password

Здесь мы используем образ phpmyadmin/phpmyadmin (https://hub.docker.com/r/phpmyadmin/phpmyadmin).
Включаем доступ к базе данных для phpMyAdmin. Устанавливаем две переменные среды для этой службы:
PMA_HOST: необходимо указать имя службы базы данных (в нашем случае db)
MYSQL_ROOT_PASSWORD: необходимо установить пароль root MySQL (в нашем случае это просто password)

Внутренний порт 80 (на котором по умолчанию доступен phpMyAdmin) сопоставлен с внешним портом 3333, чтобы мы могли получить доступ к веб-интерфейсу пользователя phpMyAdmin с помощью URL http://localhost:3333 позже.

Итоговый файл docker-compose.yml

Это полная версия получившегося файла:

version: '3'
services:
  db:
    image: mysql:8
    container_name: mysql
    restart: always
    command: "--default-authentication-plugin=mysql_native_password"
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: wpdb
      MYSQL_USER: user
      MYSQL_PASSWORD: password


  wordpress:
    image: wordpress:4.9.8
    container_name: wordpress
    restart: always
    volumes:
      - ./:/var/www/html/
    environment:
      WORDPRESS_DB_HOST: db
      WORDPRESS_DB_NAME: wpdb
      WORDPRESS_DB_USER: user
      WORDPRESS_DB_PASSWORD: password
    ports:
      - 8080:80
      - 443:443

  phpmyadmin:
    image: phpmyadmin/phpmyadmin
    restart: always
    ports:
      - 3333:80
    environment:
      PMA_HOST: db
      MYSQL_ROOT_PASSWORT: password

В целом все. Нам остается только запустить установщик наших сервисов:

docker-compose up

Заходим на наш http://localhost:8080 и получаем стартовую страницу установки:

Отлично все получилось.

0
 

Простой node.js cli

535

Привет. Сделаем простой node.js cli. У меня к примеру были задачи, что для какого скрипта надо было установить ряд настроек перед запуском. Собственно для сбора настроек я и решил воспользоваться простым терминальным интерфейсом.

Вначале я использовал стандартный модуль https://nodejsdev.ru/api/readline/. Но мне захотелось чуть более продвинутый интерфейс.

Давайте создадим простое приложение для расчета ИМТ(индекс массы тела).

Для этого мы будем использовать модуль inquirer.

Создаем проект. Устанавливаем модуль и вставляем изначальный шаблон. У меня это будет файл imt.js.

npm init
npm install inquirer
var inquirer = require('inquirer');
inquirer
  .prompt([
    /* Pass your questions in here */
  ])
  .then((answers) => {
    // Use user feedback for... whatever!!
  })

Отлично.
Первый блок это блок вопросов. В нем мы описываем наши вопросы в виде массива объектов. Вопросы бывают разного типа.

Допустим мы спросим про имя (это будет строка), про пол (селект), рост (будет число) и вес (тоже будет число).

Вот что у нас получается.

var inquirer = require('inquirer');
inquirer
  .prompt([
    {
        type: 'input',
        name: 'name',
        message: 'What your name?',
        default: 'noname'
    }, 
    {
        type: 'list',
        name: 'gender',
        message: 'Choose a gender',
        choices: ['male', 'female'],
        default: 'nogender'
    },
    {
        type: 'number',
        name: 'height',
        message: 'What is your height',
        default: '0'
    }, 
    {
        type: 'number',
        name: 'weight',
        message: 'What is your weight(kg)?',
        default: '0'
    }, 
    

  ])
  .then((answers) => {
    // Use user feedback for... whatever!!
    console.log(answers)
  })
  

Можем запустить код и посмотреть.

node imt.js

Мой результат

Отлично. На выходе мы получили console.log c объектом данных которые мы заполнили. У нас в вопросах есть поле default, как не сложно догадаться это значение, которое выставляет, если ответ был пропущен. Что ж давайте сделаем простую валидацию, чтобы все поля были обязательны для заполнения.

Для этого измени некоторые default значения, и воспользуемся методом validate. Это функция, параметром которого является наш ответ на вопрос, а тело собственно проверка. Если проверка пройдена то возвращаем true.

var inquirer = require('inquirer');
inquirer
  .prompt([
    {
        type: 'input',
        name: 'name',
        message: 'What your name?',
        default: '',
        validate: async (input) => {
            if (input == '' ) {
               return 'Please tell me your name';
            }
            return true;
         }
    }, 
    {
        type: 'list',
        name: 'gender',
        message: 'Choose a gender',
        choices: ['male', 'female'],
        default: null,
    },
    {
        type: 'number',
        name: 'height',
        message: 'What is your height(cm)',
        default: 0,
        validate: async (input) => {
            if (input == 0 ) {
               return 'Please indicate your height in centimeters';
            }
            return true;
         }
    }, 
    {
        type: 'number',
        name: 'weight',
        message: 'What is your weight(kg)?',
        default: 0,
        validate: async (input) => {
            if (input == 0 ) {
               return 'Please indicate your weight in kilograms';
            }
            return true;
         }
    }, 
    

  ])
  .then((answers) => {
    // Use user feedback for... whatever!!
    console.log(answers)
  })
  

Теперь у нас появилась проверка

Почти все. Осталось добавить формулу рассчета массы тела из получившихся данных.

  1. Необходимо рассчитать индекс массы тела для мужчины ростом 185 см, вес которого равен 94 кг.
  2. Расчет ИМТ начнем с возведения роста в метрах в квадрат (для этого необходимо умножить число на само себя): 1,85×1,85=3,4225.
  3. Разделим вес в килограммах на полученное значение: I=94/3,4225=27,47.

Выведем индекс и заключение.

var inquirer = require('inquirer');
inquirer
  .prompt([
    {
        type: 'input',
        name: 'name',
        message: 'What your name?',
        default: '',
        validate: async (input) => {
            if (input == '' ) {
               return 'Please tell me your name';
            }
            return true;
         }
    }, 
    {
        type: 'list',
        name: 'gender',
        message: 'Choose a gender',
        choices: ['male', 'female'],
        default: null,
    },
    {
        type: 'number',
        name: 'height',
        message: 'What is your height(cm)',
        default: 0,
        validate: async (input) => {
            if (input == 0 ) {
               return 'Please indicate your height in centimeters';
            }
            return true;
         }
    }, 
    {
        type: 'number',
        name: 'weight',
        message: 'What is your weight(kg)?',
        default: 0,
        validate: async (input) => {
            if (input == 0 ) {
               return 'Please indicate your weight in kilograms';
            }
            return true;
         }
    }, 
    

  ])
  .then((answers) => {
    // Use user feedback for... whatever!!
    console.log(answers)
    let squareHeight =  Math.pow((answers.height/100), 2)
    let index = answers.weight/squareHeight

    console.log('Your index ', index)
    if(index < 16) console.log('Pronounced lack of body weight')
    if(index > 16 && index < 18.5) console.log('Body weight deficit')
    if(index > 18.5 && index < 25) console.log('Normal weight')
    if(index > 25 && index < 30) console.log('Pre-obesity')
    if(index > 30 && index < 35) console.log('First-degree obesity')
    if(index > 35 && index < 42) console.log('Second-degree obesity')
    if(index > 42 ) console.log('Third-degree obesity')
   
  })
  

Наш итоговый результат

0