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

27

Я коснусь только простой сортировки, пока что. Довольно хороший гайд есть на форуме октобера 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). Часть 2

107

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

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

Давайте установим Билдер, чтобы сделать небольшой плагин. 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

126

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

Данный гайд основывается на версии 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)

170

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

Мы идем в директорию с плагином и собственно с контроллером, который модифицируем.
У меня для примера /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 установка поля загрузки картинки

164

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

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

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

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

<?php namespace Webfamily\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 = 'webfamily_content_brands';

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

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

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

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

Ну и чтобы картинка отображался в на странице Контроллера прописываем image. Значение поля я ставлю partial и путь указываю $/webfamily/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)

187

Допустим у вас появилась задача похожую на мою. Мне было необходимо подделать метод 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
 

October CMS System\Models\MailSetting, Mail not found

214

У меня в компоненте возникла ошибка, что данные классы не были найдены.

Соответсвенно помогли импорты. Хотя в примерах импорты не указывались.



use System\Models\MailSetting as MailSetting;
use Mail;
0
 

October Сms как получить все посты в шаблоне

350

Пример на стандартном плагине Blog от RainLab.
Допустим надо вывести посты не на страничке «все посты», а на другой и допустим не все количество? а только 15-ть.

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

==
use RainLab\Blog\Models\Post;

function onStart()
{
    $this['posts'] = Post::orderBy('created_at', 'desc')->get();
}
==

Далее с этой переменной делаем что нам необходимо.
Во первых получим первые 15ть постов, во вторых через twig выведем их название в цикле.

==
use RainLab\Blog\Models\Post;

function onStart()
{
    $this['posts'] = Post::orderBy('created_at', 'desc')->limit(15)->get();
}
==

 {% for post in posts %}
  {{post.title}}
 {% endfor %}

0
 

Деплой October CMS с Docker

581

October — это бесплатная самостоятельная платформа CMS с открытым исходным кодом, основанная на PHP-фреймворке Laravel. Эта статья научит вас, как развернуть October CMS с помощью docker, но если вы хотите получить дополнительную информацию об использовании October CMS, то я бы рекомендовал перейти на страницу туториалов.

Разверните сервер Ubuntu 18.04 с объемом оперативной памяти не менее 1 ГБ. Добавьте немного подкачки, если вы находитесь на чем-то вроде DigitalOcean, который использует локальные твердотельные накопители.

Шаги

Разверните сервер Ubuntu 18.04 с объемом оперативной памяти не менее 1 ГБ.

Установите docker и docker-compose.

Скачайте October CMS последней версии:

wget https://github.com/octobercms/october/archive/v1.1.0.tar.gz
tar --extract --gzip --file v1.1.0.tar.gz
rm v1.1.0.tar.gz
mv -i october-1.1.0 octobercms

Создайте файл docker-compose.yml на вашем сервере в папке с проектом (отредактируйте DB_PASSWORD и MYSQL_PASSWORD на что-то другое).

version: '2.2'
services:
  web:
    container_name: web
    restart: always
    image: aspendigital/octobercms:latest
    ports:
      - 80:80
    environment:
      - DB_TYPE=mysql
      - DB_HOST=db #DB_HOST should match the service name of the database container
      - DB_DATABASE=octobercms
      - DB_USERNAME=octobercms
      - DB_PASSWORD=octobercms
    volumes:
      - $HOME/octobercms/plugins:/var/www/html/plugins
      - $HOME/octobercms/storage/app:/var/www/html/storage/app
      - $HOME/octobercms/storage/logs:/var/www/html/storage/logs
      - $HOME/octobercms/themes:/var/www/html/themes

  db:
    image: mariadb
    container_name: db
    restart: always
    environment:
      MYSQL_RANDOM_ROOT_PASSWORD: 1
      MYSQL_DATABASE: octobercms
      MYSQL_USER: octobercms
      MYSQL_PASSWORD: octobercms
    volumes:
       - $HOME/volumes/mysql/data:/var/lib/mysql

Старт сервисов по порядку

Для начала поднимаем базу данных, чтобы убедиться, что она готова, поэтому запустите ее самостоятельно:

docker-compose up db

Когда она запустится то консоль выведет:

[Note] mysqld: ready for connections.

Далее запускаем приложение:

docker-compose up web

Настраиваем права

Веб-пользователь должен иметь возможность редактировать содержимое папок plugins, storage и themes, поэтому выполните следующие команды:

docker-compose exec web chown -R www-data /var/www/html/plugins
docker-compose exec web chown -R www-data /var/www/html/storage/app
docker-compose exec web chown -R www-data /var/www/html/storage/logs
docker-compose exec web chown -R www-data /var/www/html/themes

Запускаем миграции

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

docker-compose exec web php artisan october:up

Заходим в админку

Переходим по ссылке /backend к URL-адресу. Затем войдите в систему с вашем именем пользователя и паролем.

Перевод статьи: https://blog.programster.org/deploy-october-cms-with-docker

+2