Woocommerce шпараглка по корзине. Get cart data

31

Еще одна шпаргалочка, только уже по корзине

// $cart conditionals (if)
 WC()->cart->is_empty()
 WC()->cart->needs_payment()
 WC()->cart->show_shipping()
 WC()->cart->needs_shipping()
 WC()->cart->needs_shipping_address()
 WC()->cart->display_prices_including_tax()
 // Get $cart totals
 WC()->cart->get_cart_contents_count();
 WC()->cart->get_cart_subtotal();
 WC()->cart->subtotal_ex_tax;
 WC()->cart->subtotal;
 WC()->cart->get_displayed_subtotal();
 WC()->cart->get_taxes_total();
 WC()->cart->get_shipping_total();
 WC()->cart->get_coupons();
 WC()->cart->get_coupon_discount_amount( 'coupon_code' );
 WC()->cart->get_fees();
 WC()->cart->get_discount_total();
 WC()->cart->get_total();
 WC()->cart->total;
 WC()->cart->get_tax_totals();
 WC()->cart->get_cart_contents_tax();
 WC()->cart->get_fee_tax();
 WC()->cart->get_discount_tax();
 WC()->cart->get_shipping_total();
 WC()->cart->get_shipping_taxes();
 // Loop over $cart items
 foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) {
    $product = $cart_item['data'];
    $product_id = $cart_item['product_id'];
    $quantity = $cart_item['quantity'];
    $price = WC()->cart->get_product_price( $product );
    $subtotal = WC()->cart->get_product_subtotal( $product, $cart_item['quantity'] );
    $link = $product->get_permalink( $cart_item );
    // Anything related to $product, check $product tutorial
    $attributes = $product->get_attributes();
    $whatever_attribute = $product->get_attribute( 'whatever' );
    $whatever_attribute_tax = $product->get_attribute( 'pa_whatever' );
    $any_attribute = $cart_item['variation']['attribute_whatever'];
    $meta = wc_get_formatted_cart_item_data( $cart_item );
 }
 // Get $cart customer billing / shipping
 WC()->cart->get_customer()->get_billing_first_name();
 WC()->cart->get_customer()->get_billing_last_name();
 WC()->cart->get_customer()->get_billing_company();
 WC()->cart->get_customer()->get_billing_email();
 WC()->cart->get_customer()->get_billing_phone();
 WC()->cart->get_customer()->get_billing_country();
 WC()->cart->get_customer()->get_billing_state();
 WC()->cart->get_customer()->get_billing_postcode();
 WC()->cart->get_customer()->get_billing_city();
 WC()->cart->get_customer()->get_billing_address();
 WC()->cart->get_customer()->get_billing_address_2();
 WC()->cart->get_customer()->get_shipping_first_name();
 WC()->cart->get_customer()->get_shipping_last_name();
 WC()->cart->get_customer()->get_shipping_company();
 WC()->cart->get_customer()->get_shipping_country();
 WC()->cart->get_customer()->get_shipping_state();
 WC()->cart->get_customer()->get_shipping_postcode();
 WC()->cart->get_customer()->get_shipping_city();
 WC()->cart->get_customer()->get_shipping_address();
 WC()->cart->get_customer()->get_shipping_address_2();
 // Other stuff
 WC()->cart->get_cross_sells();
 WC()->cart->get_cart_item_tax_classes_for_shipping();
 WC()->cart->get_cart_hash();
 WC()->cart->get_customer();
0
 

Woocommerce шпаргалка по Orders. Get orders data.

31

В некоторых задачах требуется получить объект заказа.
Вот некоторая шпаргалка.

// Get Order ID and Key
 $order->get_id();
 $order->get_order_key();
 // Get Order Totals $0.00
 $order->get_formatted_order_total();
 $order->get_cart_tax();
 $order->get_currency();
 $order->get_discount_tax();
 $order->get_discount_to_display();
 $order->get_discount_total();
 $order->get_fees();
 $order->get_formatted_line_subtotal();
 $order->get_shipping_tax();
 $order->get_shipping_total();
 $order->get_subtotal();
 $order->get_subtotal_to_display();
 $order->get_tax_location();
 $order->get_tax_totals();
 $order->get_taxes();
 $order->get_total();
 $order->get_total_discount();
 $order->get_total_tax();
 $order->get_total_refunded();
 $order->get_total_tax_refunded();
 $order->get_total_shipping_refunded();
 $order->get_item_count_refunded();
 $order->get_total_qty_refunded();
 $order->get_qty_refunded_for_item();
 $order->get_total_refunded_for_item();
 $order->get_tax_refunded_for_item();
 $order->get_total_tax_refunded_by_rate_id();
 $order->get_remaining_refund_amount();
 // Get and Loop Over Order Items
 foreach ( $order->get_items() as $item_id => $item ) {
    $product_id = $item->get_product_id();
    $variation_id = $item->get_variation_id();
    $product = $item->get_product();
    $product_name = $item->get_name();
    $quantity = $item->get_quantity();
    $subtotal = $item->get_subtotal();
    $total = $item->get_total();
    $tax = $item->get_subtotal_tax();
    $taxclass = $item->get_tax_class();
    $taxstat = $item->get_tax_status();
    $allmeta = $item->get_meta_data();
    $somemeta = $item->get_meta( '_whatever', true );
    $product_type = $item->get_type();
 }
 // Other Secondary Items Stuff
 $order->get_items_key();
 $order->get_items_tax_classes();
 $order->get_item_count();
 $order->get_item_total();
 $order->get_downloadable_items();
 $order->get_coupon_codes();
 // Get Order Lines
 $order->get_line_subtotal();
 $order->get_line_tax();
 $order->get_line_total();
 // Get Order Shipping
 $order->get_shipping_method();
 $order->get_shipping_methods();
 $order->get_shipping_to_display();
 // Get Order Dates
 $order->get_date_created();
 $order->get_date_modified();
 $order->get_date_completed();
 $order->get_date_paid();
 // Get Order User, Billing & Shipping Addresses
 $order->get_customer_id();
 $order->get_user_id();
 $order->get_user();
 $order->get_customer_ip_address();
 $order->get_customer_user_agent();
 $order->get_created_via();
 $order->get_customer_note();
 $order->get_address_prop();
 $order->get_billing_first_name();
 $order->get_billing_last_name();
 $order->get_billing_company();
 $order->get_billing_address_1();
 $order->get_billing_address_2();
 $order->get_billing_city();
 $order->get_billing_state();
 $order->get_billing_postcode();
 $order->get_billing_country();
 $order->get_billing_email();
 $order->get_billing_phone();
 $order->get_shipping_first_name();
 $order->get_shipping_last_name();
 $order->get_shipping_company();
 $order->get_shipping_address_1();
 $order->get_shipping_address_2();
 $order->get_shipping_city();
 $order->get_shipping_state();
 $order->get_shipping_postcode();
 $order->get_shipping_country();
 $order->get_address();
 $order->get_shipping_address_map_url();
 $order->get_formatted_billing_full_name();
 $order->get_formatted_shipping_full_name();
 $order->get_formatted_billing_address();
 $order->get_formatted_shipping_address();
 // Get Order Payment Details
 $order->get_payment_method();
 $order->get_payment_method_title();
 $order->get_transaction_id();
 // Get Order URLs
 $order->get_checkout_payment_url();
 $order->get_checkout_order_received_url();
 $order->get_cancel_order_url();
 $order->get_cancel_order_url_raw();
 $order->get_cancel_endpoint();
 $order->get_view_order_url();
 $order->get_edit_order_url();
 // Get Order Status
 $order->get_status();
 // Get Thank You Page URL
 $order->get_checkout_order_received_url();
0
 

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

108

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

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

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

127

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

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

188

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

233

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

Для чего он нужен? Для организации подразделов или поддоменом, при это используя только один инстанс 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)

221

Создаем файл 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