Дальнейшее описание не следует расценивать как рекламу VirtueMart (далее VM). Мне не доводилось заниматься торговлей, поэтому пришлось потратить некоторое время на исследование готовых решений для интернет-магазинов. Из бесплатных продуктов приглянулись OpenCart и VM. Окончательный выбор пал на второй вариант по нескольким причинам, одна из которых простота сопровождения, т.к. мой сайт работает на Joomla! По-возможности, внимательно рассмотрите код этого приложения. Установите PHP 7 на свой локальный сервер и оцените работоспособность VM. Возможно, материал данной статьи также поможет вам принять окончательное решение о том, какой программный продукт использовать в своем интернет-магазине.

Установка VM крайне проста, поэтому я пропускаю этот шаг. Добавлю только, что т.к. сайт многоязычный, то необходимо загрузить и установить файлы поддержки соответствующих языков. Далее описывается работа с версией VM 3.0.14.

ПРИМЕЧАНИЕ: после установки всех языков определите язык по-умолчанию, например, English, в диалоге Joomla! 'Extensions → Languages'. Дело в том, что корневым языком в VM является английский, и т.к. не все пакеты интернационализации полностью переведены, то в случае отсутствия какой-то трансляции, можно разрешить вывод сообщения на английском языке. Вообще-то поддержка многоязычности в VM имеет нюансы в некоторых случаях, но об этом позже.

Начинаем с диалога Конфигурация (VirtueMart → Configuration), где обязательно разрешим установленные языки:

VM Languages
На следующем шаге описываем Магазин (VirtueMart → Shop). Диалог простой, но следует все же отметить, что размер картинки логотипа вашей компании, который будет отображаться на инвойсах, должен быть в пределах 200х38 пикселов и желательно в формате 'GIF', хотя объявлены поддерживаемыми и другие форматы. Форматы других изображений также желательно стандартизировать для своего сайта. Например, для фотографий изделий я буду использовать размер 200 х 149, 96 точек на дюйм.

После этого определяемся с валютами, допустимыми для магазина, и прежде всего создаем замещения их названий в диалоге 'Extensions → Languages → Overrides' как для административной панели, так и для сайта. Теперь необходимо открыть 'VirtueMart → Currencies', найти требуемую валюту и произвести следующие настройки:

VM Currency Setup

Следует иметь в виду, что для основной валюты магазина, которая будет использоваться при указании цен на товары, поле 'Exchange Rate' должно иметь значение 0. В то же время, это поле обязательно должно быть заполнено для других используемых валют:

VM Currency Setup USD

ПРИМЕЧАНИЕ: основная валюта должна оставаться неизменной на протяжении существования магазина. Неиспользуемые валюты можно запретить для публикации или удалить:

VM Currencies

Теперь добавляем эти валюты в диалоге 'VirtueMart → Shop':

VM Shop Currencies

Как видно из рисунка замещение произошло, но при смене языка остается неизменным (см. список вверху слева). В панели администратора это терпимо. Но на стороне сайта подстановки вообще не происходит:

VM Currency Translation Fail

Исходя из того, что это вероятно будет в дальнейшем исправлено, я решил обойти проблему простой заменой словесного описания валюты на ее 3-х символьный код в диалоге 'VirtueMart → Currencies'. Также можно удалить ненужные теперь замещения 'VM_CURRENCY...', которые были внесены в 'Extensions → Languages → Overrides'. При этом, я так же удалил кнопку 'Change currency'. Для этого создал замещение модуля 'mod_virtuemart_currencies' и внес в код его файла 'default.php' изменения, представленные на следующем рисунке:

VM Currency Selector Override

Цветовая легенда:

Желтый: код закомментирован
Синий: изменено название
Красный: добавленный код

ПРИМЕЧАНИЕ: подробнее о методике создания замещений см. в статье 'Поддержка микроразметки "Статья" Joomla! 3.x'

Теперь модуль 'VM-Currencies Selector' выглядит на сайте так:

VM New Currency Selector

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

VM Manufacturer

Теперь нам нужно создать корневую категорию товаров 'Electronics' и вложить в нее 'Door Bells'. Для этого открываем диалог 'Product Categories' и последовательно описываем их для каждого языка, поддерживаемого сайтом.

ПРИМЕЧАНИЕ: список языков появляется после первого сохранения каждой категории, что отражено на следующем скриншоте. В дальнейшем, кнопку 'Save' необходимо нажимать для сохранения описания категории на каждом языке.

VM Product Category

В итоге получим такую структуру:

VM Product Categories

Внесем описание какого-нибудь продукта в категорию 'Door Bells'. Например, кнопку звонка:

VM Product Button

Теперь можно создать пункт Магазин в Главном Меню Joomla! На своем сайте я обычно размещаю модули дополнительных меню в правой части экрана. В случае магазина здесь будет располагаться список категорий товаров.

ПРИМЕЧАНИЕ: На данный момент VM поддерживает 2 уровня вложений в модуле списка категорий.

Чтобы в дальнейшем не оказаться в затруднительном положении, если вдруг понадобится добавить уровень вложения, создадим всплывающее меню для пункта вызова диалога магазина. Для этого надо указать тип пункта меню 'External URL' и в поле 'Link' внести символ '#', чтобы при повторных нажатиях на кнопку не происходила перезагрузка картинки:

VM StoreMenuButton

Добавим подчиненный пункт 'Electronics' и укажем для него тип 'VM Category Layout'. Повторим процедуру для всех языков и в итоге получим следующую структуру:

VM Menu Category

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

И наконец, создадим для каждого языка модуль типа 'VM Category', где выберем категорию 'Electronics' в качестве родительской:

VM Category Module

Проверим результат наших трудов:

VM Menu Front

В общем заготовка есть и на этом можно было бы закончить эту тему, чтобы поскорее заняться разработкой и изготовлением самого изделия. Но ведь нас интересует не только продажа, но даже в большей степени производство. А так как первое и второе является производной от заинтересованности и степени удовлетворения нами интересов потребителя, то важно заложить крепкий фундамент для поддержки автоматизации взаимодействия заказчика и производства изделий на всех этапах. Поэтому, давайте притормозим и попробуем на этом этапе создать любой заказ, или посмотрим как он выглядит в многочисленных примерах, любезно предоставляемых разработчиками VM. Из полезных данных для решения поставленной задачи в описателе заказа будет только поле SKU (артикул) и номер заказа. Итак, вернемся к картинке описания продукта и рассмотрим повнимательнее поле 'Product SKU'.

ПРИМЕЧАНИЕ: бытует мнение, что не надо описывать все свойства продукта в артикуле, т.к. он предназначен для обработки человеком. Для большинства магазинов с этим утверждением можно согласиться, хотя в общем случае допустимой считается его длина до 30 символов, что уже не мало. Но в данном случае нас интересует максимальная поддержка автоматизации собственного производства изделия на заказ, а не перепродажа продукции сторонних производителей.

Описание формата артикула (SKU)

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

'I' т.к. похож на '1'
'O' т.к. похож на '0'

Но т.к. наш магазин многоязычный, то ограничения будут пожестче. Разрешаем все цифры и символы латинского алфавита, кроме символов кириллицы, начертание которых похоже на латинские. Из специальных символов разрешим только знак 'минус' (-), который будем использовать для разделения групп символов. Таким образом, список разрешенных символов будет следующим:

D, F, G, J, L, N, Q, R, S, U, V, W, Z,-,0...9

Область деятельности предполагаемого предприятия ограничивается производством изделий электроники и программного обеспечения, поэтому и артикул будем разрабатывать соответствующий. Формат артикула любого изделия будет состоять из заголовка (HD), а также 2-х необязательных групп Независимых (IP) и Зависимых Параметров (DP), всегда располагаемых в следующем порядке :

HD → IP-DP

При этом, между HD и IP разделитель отсутствует, а DP всегда отделяется символом «-». Как следует из названия, к группе Независимых Параметров могут быть отнесены функции устройства, не влияющие на выбор других. В то же время, устройство может содержать, например, взаимоисключающие для конкретной модели параметры, которые должны быть выделены в группу Зависимых Параметров.

HD - заголовок имеет следующую структуру:

Код продукта: 2 цифры
например, кнопка дверного звонка: 01
Код модели: 3 цифры
например, модель 'Colibry': 001
Тип продукта: 1 символ или цифра:
D - Изделие Электроники в сборе (например, кнопка звонка)
R - Печатная Плата с установленными элементами к изделию
электроники
Z - пустая Печатная Плата к изделию электроники
S - Программное Обеспечение

 

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

ПРИМЕЧАНИЕ: хорошим тоном является наличие в заголовке префикса длиной 2-3 символа, отражающего название производителя. Например, в моем случае можно было-бы определить его как 'APO'. Но, во-первых эти символы запрещены в нашем конкретном алфавите, а во-вторых, желательно чтобы количество символов в артикуле было минимальным. Поэтому, я не использую префикс.

IP - группа Независимых Параметров для определенного типа продукта

Рассмотрим ее формат на примере Изделия Электроники (тип продукта «D» по нашей классификации), которое всегда оформлено в какой-то корпус:

Материал корпуса: 1 символ L – металл
S – пластик
W – дерево 
Цвет корпуса: 1 символ или цифра D - белый
F - черный
R - красный
G - зеленый
L – синий
N – оранжевый
Q – серый
V – желтый 
Форма корпуса: 1 символ или цифра  R – прямоугольник
G – колокол
S – сердце 

DP — группа Зависимых Параметров

Она представляет собой последнюю часть структуры артикула. Рассмотрим ее также на примере Изделия электроники 'Кнопка Дверного Звонка':

Разделитель: 1 символ "-"
Подсветка кнопки (цвет): 1 символ U – отсутствует
Z – изменяемый программно
R - красный
G - зеленый
Звук: 1 символ U – отсутствует
Q – пьезо-баззер (подходит для простых мелодий)
D – динамик (например, для полифонии)

 

Таким образом артикул 'Кнопки Дверного Звонка' модели 'Colibry' со следующими характеристиками:

Материал Корпуса: Пластик
Цвет корпуса: Красный
Форма корпуса: Сердце
Подсветка: Отсутствует
Звук: Пьезо

будет описан 12-ю символами: 01001DSRS-UQ.

Можно ли сделать короткий артикул?

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

Код продукта: 7 бит
 
Код модели продукта: 10 бит  
Тип продукта: 2 бита 00 - Изделие Электроники в сборе (например, кнопка звонка)
01 - Печатная Плата с установленными элементами к изделию
электроники
10 - пустая Печатная Плата к изделию электроники
11 - Программное Обеспечение
Материал корпуса: 2 бита 01 – металл
10 – пластик
11 – дерево
Цвет корпуса: 3 бита 000 - белый
001 - черный
010 - красный
011 - зеленый
100 – синий
101 – оранжевый
110 – серый
111 – желтый
Форма корпуса: 2 бита 00 – прямоугольник
01 – колокол
10 – сердце
Разделитель (-) В ДАННОМ ФОРМАТЕ НЕ ИСПОЛЬЗУЕМ
Подсветка кнопки (цвет): 3 бита 000 – отсутствует
001 – изменяемый программно
010 - белый
011 - красный
100 - зеленый
Звук: 2 бита 00 – отсутствует
01 – пьезо-баззер
10 – динамик

 

Итого 31 бит, которые могут быть представлены в виде семи символов по модулю 16 (Hex-format), и тогда артикул нашей 'Кнопки Дверного Звонка' модели 'Colibry' и кодом формы корпуса 0010 будет выглядеть так: 1004941. Да, мы сократили описатель на 5 символов (из которых один разделитель '-' не несет смысловой нагрузки). А теперь рассмотрим другие стороны такого подхода:

  • Если в первом случае можно со временем привыкнуть к формату, то сокращенный вариант совершенно не информативен для человека
  • Hex-format включает символы 'A, B, C, E', которые мы исключили из алфавита артикула для нашего многоязычного сайта. Значит придется использовать модуль 8, который даст только числовое представление. Это может показаться даже удобным, но увеличит размер артикула. Для нашего примера код превратится в 100044501. Т.е. сокращение составит уже не 5, а 3 символа.
  • При описании многовариантных продуктов в VM, нам придется много раз заполнять SKU для каждого варианта, что в случае битовых полей наверняка будет приводить к множественным ошибкам. Символьный формат здесь явно выигрывает.
  • Вполне вероятно, что нам придется самостоятельно составлять запросы к базе данных VM, которые будут проще и понятнее, если применять символьный формат.
  • Для автоматизации обработки заказов нам нужен будет специализированный программный парсер, сканирующий заказ на ошибки в SKU, а также распределяющий его по процессам изготовления изделия (выбор печатной платы, комплектующих и поиск предпочтительного на данный момент поставщика комплектующих, компиляция программного обеспечения согласно требованиям заказа и т.д.). В случае бинарного варианта, нам необходимо будет создавать его самостоятельно. Для символьного же варианта достаточно, например, составить JSON-схему требуемого артикула, и обработать его стандартным парсером.

Таким образом я останавливаю выбор на первоначальном варианте описания артикула.

Реализация поддержки SKU

Вариант №1

ПРИМЕЧАНИЕ: не спешите повторять описываемые действия

Возвратимся к созданному ранее описателю продукта 'Button' и внесем следующие изменения:

  • Изменим название на 'Root-Door Bell Button-Colibry', тем самым определив этот продукт корневым, т.к. предполагается изготовление кнопок разных типов. При этом словосочетание 'Root-' специально поставлено в начале, чтобы в дальнейшем легко находить все корневые описатели других изделий сортируя или фильтруя список по полю 'Product Name'.
  • В поле SKU внесем Неизменное Значение Заголовка '01001D', указывающее на то, что это 'Изделие Электроники' из группы 'Door Bell Button', модель которого имеет идентификатор 'Colibry'. Таким образом, мы создали 2 пары 'название — цифровой идентификатор' (01-Door Bell Button и 001-Colibry), что позволяет в дальнейшем легко автоматизировать необходимые проверки при производстве, и в то же время не затрудняет чтение информации человеком.
  • Снимем отметку с чекбокса 'Published' т.к. этот корневой продукт содержит не полный описатель и нет смысла публиковать его, а значит и переводить описатель на разные языки также не требуется. В закладке 'Product Status' укажем количество товара равным 0 (поле 'In Stock'). Заметим, что при этом его 'Product Alias' также будет исключен из URL, но цена будет входить в расчет стоимости производных продуктов. Поэтому, здесь можно указать некоторую постоянную часть стоимости товара, которая будет присутствовать в любом продукте, производном от данного корневого.

Таким образом, описание родительского продукта будет иметь следующий вид:

VM Real Product Button

Теперь мы можем создавать описатели реальных продуктов, производных от данного. В этом же диалоге нажмем кнопку 'Add a Child Product' и создадим такое описание:

VM Product Child

Изменим значения в полях 'Product Name' для каждого языка, а также отметим чекбокс 'Published'. Обратите внимание, я присвоил короткое имя алиасу, т.к. VM автоматически добавляет к нему суффикс '-detail' и это надо учитывать, чтобы не превысить допустимую длину URL, а также стараться делать его осмысленным. Таким образом, относительный адрес описания данного продукта на английском языке будет выглядеть следующим образом:

'/en/store/electronics/bells/colibry-detail'

Также я оставил пустыми поля, касающиеся цены этого дочернего продукта. Дело в том, что если заполнить их здесь, то VM не будет добавлять к общей стоимости базовое значение, которое мы указали для родительского продукта. В то же время, VM будет это делать если мы введем дополнительные пользовательские поля, посредством которых сможем гибко варьировать цену. Как видите я также не заполнил поле 'Product SKU', т.к. в данном случае оно не фиксированное и его наполнение зависит от выбора пользователя.

Итак, нам необходимо найти удобные штатные средства VM для:

  • ПРОИЗВОДСТВА, чтобы легко описывать разнообразные варианты своих изделий
  • ПОКУПАТЕЛЯ, чтобы он мог легко указать свои индивидуальные требования к товару из разнообразия, предлагаемого нашим производством

Еще раз рассмотрим список из 5-ти параметров, значения которых нам необходимо видеть в артикуле и которыми пользователь должен иметь возможность управлять при заказе «Кнопки звонка»:

  • Материал корпуса (3 варианта)
  • Цвет корпуса (8 вариантов)
  • Форма корпуса (3 варианта)
  • Подсветка (4 варианта)
  • Звук (3 варианта)

Общее количество вариантов 3 х 8 х 3 х 4 х 3 = 864!

VM предлагает удобную возможность создавать многовариантные пользовательские поля по количеству параметров до 5-ти. Но в данном случае нам это не подходит, т.к. слишком велико количество вариантов. Трудоемким в плане реализации представлятся также создание иерархических списков описателей дочерних продуктов. Ведь нам нужно не только создать описатели, но и отображать многообразные зависимости в цене конечного продукта, при этом генерируя требуемый SKU.

Вариант №2

Разделим наши параметры на 2 группы:

Независимые (IP)

  • Материал корпуса
  • Цвет корпуса
  • Форма корпуса

Зависимые (DP)

  • Подсветка
  • Звук

Для каждой IP-группы создадим скрытую Custom Fields Group. Например, для «Материала корпуса» она будет такой:

VM CustomField Group

В каждую подобную группу вложим 2 пользовательских поля:

1. видимое типа «string» для отображения списка вариантов пользователю

VM CustomField String

2. невидимое типа «property», для хранения паттерна SKU, соответствующего данной группе

VM CustomField Property

Для DP-группы создадим стандартное видимое мультивариантное поле:

VM CustomField Multivariant

ПРИМЕЧАНИЕ: обратите внимание, что заголовки видимых групп определены в виде мультиязычных замещений, создание которых описано ранее.

На следующем скриншоте представлена часть списка созданных групп Пользовательских Полей:

VM CustomFields

Теперь можно создать описатель продукта. Для этого в диалоге 'Products' нажмем кнопку 'New' и опишем, например, базовый вариант кнопки «Colibry»:

VM Button Colibry

Обратите внимание на заполнение поля 'Product SKU'. Здесь нет данных о материале, цвете и форме корпуса, но есть сведения об отсутствии звука и подсветки в данном варианте кнопки. Сохраняем эту форму (английскую) и редактируем для каждого поддерживаемого языка.

Ну вот и настало время применить наши Группы Пользовательских Полей. Откроем соответствующую закладку на этой же форме и выберем группу или поле из списка, подготовленного нами ранее. Я выбрал первым многовариантное поле, которое позволяет описать ограничения по совместному применению звука и подсветки, хотя логичнее было бы указать его последним. Дело в том, что обработка применяемых нами многовариантных списков и списков типа «string» выполнена в коде VM разными, и на мой взгляд несколько конфликтующими способами, что приводит к сбросу указателей во всех списках типа «string» при обновлении страницы или смене элемента выбора в расположенном на этой же странице многовариантном поле. Если списков на странице несколько, то это можно не сразу заметить и в итоге сбить пользователя с толку, когда он нажмет кнопку «Поместить в корзину». Я сообщил об этом разработчикам VM, ну а пока по этой причине поставил многовариантное поле первым в списке в надежде, что пользователь работает с полями в направлении сверху-вниз. Ниже показан пример заполнения многовариантного поля:

VM ProductMultivariantField

Как видите, оно добавлено 2 раза для поддержки описателей звука и подсветки. При этом, списки количества вариантов значительно сокращены для данной модели (по 2 на каждый параметр). Обратите внимание, что для каждого варианта указывается именно цена, а не изменение к базовой цене и расширение поля «Product SKU» заполняется в строгом соответствии с описанным ранее шаблоном, и только касательно DP-группы.

Далее добавляем элементы IP-группы:

VM ProductStringField

Как видите данная модель изделия имеет только один тип материала корпуса (VM_ED_BODY_PLASTIC) и мы могли бы вообще не вводить сюда список. Но т.к. рядом на странице товаров может быть показан другой тип кнопки, имеющей расширенный список материалов (пластик, металл и т.д.), то на мой взгляд различие в подаче информации может сбить покупателя с толку. Далее мы видим описание 3-х вариантов цвета, два из которых добавляют к цене изделия 5 единиц стоимости.

Вернемся к главному. В каждой подобной группе у нас присутствует специальное поле типа «Property», содержимое которого предназначено для указания местоположения и человеко-читабельного представления данной части в общем паттерне SKU. Таким образом, например, значение «2-D» назначает материалу VM_ED_BODY_WHITE символ «D» в паттерне SKU, который должен быть размещен вторым в IP-части паттерна SKU данного типа продукта.

Откроем, финальный список «Products» касательно нашей кнопки типа «Colibry» - всего 4 позиции:

VM ProductsList

Это потому что мы определили для нее следующий список вариантов:

  • Материал корпуса (1 вариант)
  • Цвет корпуса (4 варианта)
  • Форма корпуса (1 вариант)
  • Подсветка (2 варианта)
  • Звук (2 варианта)

Общее количество вариантов 1 х 4 х 1 х 2 х 2 = 16. Но, т.к. количество продуктов в списке определяется полями в многовариантной группе, то их у нас всего 4. Таким образом, для поддержки всех 864 вариантов, определенных ранее, нам понадобится описать всего 12 продуктов, т.к.:

  • Подсветка (4 варианта)
  • Звук (3 варианта)

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

VM ProductButtonColibry

Попробуем оформить заказ:

VM OrderError
Как видим информации достаточно для того, чтобы покупатель мог убедиться, что все заказанные свойства подтверждены в словесной форме, и по большому счету ему не суть важно как выглядит SKU. Для нас же его точное оформление крайне важно, т.к. у нас нет сотрудников, которые читали бы каждый заказ и планировали его исполнение, разбивая на подпроцессы. Поэтому, необходимо создать замещение кода VM, которое обработает описанные ранее поля типа «Property», и автоматически модифицирует SKU соответствующим образом в момент оформления заказа. На первый взгляд это можно сделать в коде компонента, отмеченного на следующем скриншоте:

VM TemplateCart

Небольшое отступление касательно правила, которому я стараюсь следовать в случае необходимости внесения изменений в сторонний код:

«Код изменения или дополнения должен быть максимально кратким и желательно оформлен в один цельный блок (т.е. присутствовать только в одном месте)»

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

Piano

К сожалению, мне не удалось найти в этом компоненте подходящего места, соответствующего указанному правилу внесения изменений. Пришлось сделать это в коде ядра VM и т.к. это не стандартное решение, то приводить его здесь не стану. Скажу только, что дополнение сделано в виде одного короткого блока кода, который в итоге позволяет правильно показать пользователю всю информацию, включая SKU:

VM OrderOk

К сожалению, VM не производит подстановку многоязыковых определений при показе заказов администратору, но в данном случае это не большая проблема:

VM OrdersError
Итак, представленный подход к описанию продукта позволяет:

  • оформлять структуру SKU в единственном стандартном диалоге VM
  • легко изменять порядок следования элементов в паттерне SKU и их значения, вне зависимости от физического положения списков на форме
  • максимально исключить ошибки при описании продукта, т.к. SKU заполняется автоматически при оформлении заказа
  • совмещать в описателе товара большое количество списков различного типа
  • значительно уменьшить трудозатраты на описание различных вариантов продукта и его метатегов

Эмуляция почтового агента

При подтверждении заказа, VM отправляет электронные письма покупателю и продавцу. Т.к. мы ведем разработку на локальном сервере, то в этот момент произойдет ошибка отправки почты. Необходимо самостоятельно установить и настроить соответствующее программное обеспечение. В случае Linux это обычно 'sendmail'. Но я для этой цели использую простейший эмулятор неизвестного программиста, который нашел в комментариях к одной из статей в Интернете. Вот его код:

#!/bin/sh
prefix="/var/www/sendmail"
date='date \+\%Y\%m\%d\%H\%M\%N'

name="$prefix/$date.eml"
while IFS=read line
do
   echo "$line" >> $name
done
chmod 666 $name

Поместите его в файл, например, под названием 'fake_sendmail.sh' и сохраните в директории '/usr/sbin/sendmail', присвоив ему атрибут 'executable'. Создайте директорию 'sendmail' как указано в переменной 'prefix' кода. Укажите значение пути к данному файлу 'sendmail' в 'php.ini':

sendmail_path = /usr/sbin/sendmail/fake_sendmail.sh

Откройте закладку 'Server' в диалоге Joomla! 'Global Configuration', и выберите 'Sendmail' в качестве почтового агента:

VM Mailer
Теперь почтовые сообщения будут сохраняться в директории 'sendmail' и их можно просматривать стандартными средствами. Например, так выглядит письмо подтверждения отправки заказа в 'Thunderbird Mail':

VM Order Shipped
Инструменты и документы

Операционная система: Linux (например http://www.ubuntu.com/download)
Программы: LAMP (например https://bitnami.com/stack/lamp)
CMS Joomla! (https://www.joomla.org/)
VirtueMart (http://extensions.joomla.org/extensions/extension/e-commerce/shopping-cart/virtuemart)
Опционально: Netbeans (https://netbeans.org/downloads/)
Дополнительное чтение:

Apache HTTP Server (https://httpd.apache.org/docs/2.4)
MySQL (http://dev.mysql.com/doc)
MariaDB (https://mariadb.com/kb/en/mariadb/documentation/)
PHP (http://php.net/manual/en/index.php)
JavaScript (https://www.javascript.com/resources)
jQuery (https://jquery.com/)
The Linux Command Line (http://sourceforge.net/projects/linuxcommand/files/TLCL/13.07/TLCL-13.07.pdf/download)