9 декабря 2013 г.

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

3 декабря 2013 г.

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

7 ноября 2013 г.

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

30 октября 2013 г.

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

Покупатели, как правило, используют атрибуты для сравнения схожих друг с другом товаров, чтобы определиться, какой из них лучше и доступный по цене. Немаловажным также является и поиск товаров по их атрибутам, однако в OpenCart по умолчанию это не предусмотрено. Решением может стать модуль фильтрации товаров по их атрибутам Attfilter.

8 октября 2013 г.

Использовать защищённый доступ к магазину через протокол SSL необходимо и важно не только с точки зрения законодательства той или иной страны (Федеральный закон РФ от 27 июля 2006 года № 152-ФЗ «О персональных данных»), но и с точки зрения собственной безопасности, когда трафик в публичных сетях постоянно прослушивается, как со стороны хакеров-любителей, так и реальных конкурентов. Особенно это актуально для точек бесплатного доступа Wi-Fi в больших городах.

1 октября 2013 г.

В OpenCart есть довольно полезный инструмент BlackList (Чёрный список). Правда он немного примитивен, но как минимум позволяет по IP-адресу выявить недобросовестного покупателя. Конечно список IP-адресов необходимо знать заранее, что не совсем удобно, поскольку современные мошенники редко используют одни и те же адреса. Однако некоторые не стесняются открыто заявлять о себе и рассчитывают, прежде всего, на невнимательность владельца магазина. Целью мошенников обычно является сам заказ, а точнее его сумма. Зная программные недоработки или уязвимости в OpenCart, они могут подменить стоимость покупки. В этой статье речь пойдёт о решении, которое можно использовать поверх стандартного Черного списка.

28 сентября 2013 г.

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

6 сентября 2013 г.

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

20 августа 2013 г.

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

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

20 июля 2013 г.

В этой статье речь пойдёт о расширении возможностей одного из стандартных модулей OpenCart, модуля показа баннеров. Цель доработки – получить возможность показывать на страницах магазина интерактивные баннеры в виде flash-объектов, java-апплетов, блоков javascript или любого другого кода в формате гипертекстовой разметки. Основная ставка сделана на размещение рекламных баннеров программ Google AdWords или Яндекс.Директ, также будет не менее интересно и полезно внедрить новые сервисы для покупателей на базе технологий Ajax.

9 июля 2013 г.

В каналах продвижения OpenCart по умолчанию доступен модуль Google Sitemap, отвечающий за формирование и показ карты сайта в виде файла sitemap.xml. Однако у него есть существенные недостатки, основными из которых являются высокая нагрузка на базу данных и неоптимизированная структура данных с точки зрения SEO.

Для начала стоит отметить, для чего необходима карта сайта в формате XML, и что получает владелец сайта при её использовании.

31 мая 2013 г.

В данной статье будут периодически появляться небольшие решения в плане улучшения эргономики интерфейса и функциональной логики OpenCart. Базовой версией для улучшения ПО магазина выбрана версия 1.5.4.

Предисловие

Не стоит гнаться за обновлениями успешного продукта, т.к. рано или поздно такой продукт станет неповоротливым и тяжёлым во всех смыслах. Помню, как начинал работать с ПО Bitrix, довольно быстрым и функциональным веб-приложением, сейчас же это программное обеспечение сильно нагружает не только сервер, но и людей. По тому же пути пошли и разработчики CS-Cart, а именно, стали добавлять много ненужных функций и модификаций, которые вроде и доставляют комфорт в работе (на мощных серверах), но на продажи магазинов существенно не влияют. Озвученные выше продукты коммерческие и они вынуждены наращивать избыточную функциональность, чтобы к ним не был потерян интерес.

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

25 мая 2013 г.


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

Часто провайдер уверяет, что сайт подключён к высокоскоростному порту, однако на деле может использоваться один сетевой интерфейс для нескольких клиентов и с меньшей пропускной способностью (10Мбит/c вместо 100Мбит/c), а это ничто иное, как снижение производительности при работе с OpenCart.

Проблема в том, что за один переход по каталогу магазина, сервер отдаст в среднем от 500 до 1000 килобайт графики (речь идёт об оптимизированных изображениях, по 50Кб). Теперь представьте активного пользователя и не одного, а с десяток, которые быстро перемещаются по категориям в поисках подходящего товара или просто из любопытства. Последним особенно не стоит отдавать все изображения сразу, т.к. они могут их и не увидеть.

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

В файл ./catalog/view/theme/default/template/common/header.tpl в тело тега <head> добавьте следующее:

<style type="text/css">.product-list img[rel] { width: <?php echo $this->config->get('config_image_category_width'); ?>px; height: <?php echo $this->config->get('config_image_category_height'); ?>px; display: block; background: url('catalog/view/theme/default/image/loading.gif') center center no-repeat; }</style>

Данный код задаёт стиль контейнера изображений с атрибутом relationship до того, как в него будет загружено реальное изображение при помощи кода JavaScript, текст которого приведён ниже. Этот код необходимо поместить в шаблон ./catalog/view/theme/default/template/product/category.tpl

<script type="text/javascript"><!--
function isScrolledIntoView(element) {
var docViewTop = $(window).scrollTop();
var docViewBottom = docViewTop + $(window).height();
var elementTop = $(element).offset().top;
var elementBottom = elementTop + $(element).height();
return ((elementBottom <= docViewBottom) && (elementTop >= docViewTop));
}
$(window).ready(function() {
$(window).resize(function() {
ImageIntoView();
});
$(window).scroll(function() {
ImageIntoView();
});
ImageIntoView();
});
function ImageIntoView() {
$('img[rel]:not([src])').each(function() {
if (isScrolledIntoView(this)) {
$(this).attr('src', $(this).attr('rel')).load(function() {;
$(this).removeAttr('rel');
});
}
});
 }
//--></script> 

В этом же шаблоне находим слой <div class="product-list"> и в его теле для тега <img> заменяем атрибут src на rel и убираем атрибут alt.

Готово, список товаров с картинками для категории теперь с элементами интерактива. Аналогичные действия можно выполнить для результатов поиска, списка товаров, участвующих в акциях и списка товаров производителей.

Если у Вас возникли трудности с реализацией данного решения, то мы готовы вам в этом помочь.

UPDATE

OpenCart Image Preloader
Рисунок 1. Модуль подгрузки изображений OpenCart Image Preloader v1.0
Решение получило продолжение в виде отдельного модуля Image Preloader без необходимости внесения каких-либо изменений в основной код OpenCart и без использования vQmod.

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

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

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

Состав архива (OpenCart v1.5+):

./admin/language/english/module/image_preloader.php
./admin/language/russian/module/image_preloader.php
./admin/controller/module/image_preloader.php
./admin/view/template/module/image_preloader.tpl
./catalog/controller/module/image_preloader.php
./catalog/view/theme/default/template/module/image_preloader.tpl
./catalog/view/theme/default/image/loading.gif
Представленный ниже список файлов необходимо поместить в каталог OpenCart согласно их директориям.

Условия приобретения: Модуль может быть использован только в личных целях, не допускается распространение в составе ПО OpenCart без согласия авторов модуля или публикация его в общедоступном виде. Модуль возврату не подлежит!

Условия поддержки: Бесплатная поддержка в рамках базовой версии 1.x, в том числе выпуск возможных обновлений, осуществляется в течении года с момента приобретения через зарегистрированный электронный адрес, с использованием которого была осуществлена сделка.

Стоимость: $5


18 апреля 2013 г.

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

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

Но вернёмся к статье. Чтобы мультисортировка заработала на уровне модели, необходимо в файле .../catalog/model/catalog/product.php в теле функции getProducts весь код между переменной $sort_data = array( ... ) и условием if (isset($data['start']) || isset($data['limit'])) { ... } заключить в тело оператора else следующей конструкции:

if (isset($data['sort']) && $intersected = array_intersect((array)$data['sort'], $sort_data)) {
$sql .= " ORDER BY ";
foreach($intersected as $key => $value) {
$order = 'ASC';
if (isset($data['order'][$key])) {
$_order = strtoupper($data['order'][$key]);
if ($_order == 'ASC' || $_order == 'DESC') $order = $_order;

if ($value == 'pd.name' || $value == 'p.model') {
$sql .= "LCASE($value) $order,"; 
} else {
$sql .= $value . " $order,"; 
}
}
$sql = rtrim($sql, ",");
} else {
// Вырезанный код вставить сюда
}

В файле контроллера .../catalog/controller/product/category.php перед строкой с условием if (isset($this->request->get['page'])) { ... } добавьте следующий код:

if ($config_multisort = $this->config->get('config_multisort')) {
$sort = explode(';', $sort);
$order = explode(';', $order);
$this->data['text_multisort'] = $this->language->get('text_multisort');
$multisort = array();
foreach($sort as $key => $type) {
$direction = isset($order[$key]) ? $order[$key] : 'ASC';
$multisort[] = "$type-$direction"; 
}
$this->data['config_multisort'] = $config_multisort;
$this->data['multisort'] = $multisort;
}

В этом же файле после заполнения массива переменной $this->data['sorts'] добавить следующий код:

if ($config_multisort) {
$default = $selected = $unselected = array(); 
foreach ($this->data['sorts'] as $sorts) {
$checked = false;
preg_match('/^(\w+\.)?(\w+)-(\w+)$/', $sorts['value'], $matches);
$sorts['group'] = $matches[2];
foreach($multisort as $key => $sorted) {
if ($sorted == $sorts['value']) { 
$this->data['text_sorting'] = $sorts['text'];
$checked = true; break; 
}  elseif (strpos($sorted, $sorts['group']) !== false) {
$checked = true; break; 
}
}
if ($sorts['group'] == 'sort_order') {
$default[] = $sorts;
} elseif($checked) { 
$selected[$key][] = $sorts;
} else { 
$unselected[] = $sorts;
}
}
$_selected = array();
for($n=0; $n < count($selected); $n++) {
$_selected = array_merge($_selected, $selected[$n]);
}
$this->data['sorts'] = array_merge($default, $_selected, $unselected);
}

Далее в шаблоне файла .../catalog/view/theme/default/template/product/category.tpl весь код тега <select onchange="location = this.value;"> включите в тело оператора else следующей конструкции:

<?php if ($config_multisort) { ?>
<div class="select"><span><?php if(count($multisort) > 1) { 
echo sprintf($text_multisort, count($multisort)); } 
else { echo $text_sorting; } ?>
</span><div class="option">
<?php $counter = 0.5;
foreach ($sorts as $sorts) { 
if (in_array($sorts['value'], $multisort)) { ?>
<label<?php if(intval($counter) % 2) { ?> class="group" <?php } ?>>
<input type="radio" name="<?php echo $sorts['group']; ?>" 
    value="<?php echo $sorts['value']; ?>" checked />
<span><?php echo $sorts['text']; ?></span></label>
<?php } else { ?>
<label<?php if(intval($counter) % 2) { ?> class="group" <?php } ?>>
<input type="radio" name="<?php echo $sorts['group']; ?>" 
    value="<?php echo $sorts['value']; ?>" />
<span><?php echo $sorts['text']; ?></span></label>
<?php } $counter += 0.5; } ?>
</div>
<div style="display: none;" id="multisort"><?php echo $text_multisort; ?></div>
</div>
<?php } else { ?>
// Вырезанный код вставить сюда
<?php } ?>

Теперь позаботимся о стиле и интерактивности. Для этого в файле стилей .../catalog/view/theme/default/stylesheet/stylesheet.css добавим следующее:

.select {
    display: inline-block; 
    border:solid 1px #CCCCCC;
}
.select > span {
    cursor: default; display: block;
    white-space: nowrap; padding: 2px 20px 2px 2px;
    background: #F8F8F8 url('../image/button-dropdown.png') right center no-repeat;
    line-height: 1.45em; height: 1.45em;
}
.select .option {
    color: black; border:solid 1px #CCCCCC;
    margin-left: -1px; position: absolute; 
    background-color:#F8F8F8;
    z-index: 10; display: none;
}
.select .option label {
    display: table-row;
}
.select .option label span{
    display: table-cell; vertical-align: middle;
    padding-right: 4px; padding-left: 2px;
}
.select .option label.group  {
    background-color:#E9E9E9; 
}
.select .option label:hover {
    color:#fff;
    background-color:#000080;
}

Интерактивность мы реализуем через JavaScript в файле .../catalog/view/javascript/common.js.
После $(document).ready(function() { добавьте следующий код:

$(document).click(function(event) { if ($(event.target).next().attr('class') !='option' && !$(event.target).parents('.option').length) { $('.option').hide(); });

jQuery.fn.option = function() { 
 var url = 'http://' + location.host + location.pathname; var submitted = false; 
$(this).parent().width($(this).width()); $(this).parent().click(function(event) {
if ($(this).find('.option').is(":visible")) { 
var tagName = event.target.tagName.toLowerCase();
var parentTag = event.target.parentNode.tagName.toLowerCase();
if( parentTag == 'label') { if( tagName == 'span' ) submitted = true;
if( tagName == 'input' ) {
var radioboxes = $(this).find('input:radio'); var group = $(event.target).attr('name');
if(group == 'sort_order') { radioboxes.not('[name="sort_order"]').removeAttr('checked');
} else { radioboxes.filter('[name="sort_order"]').removeAttr('checked');}
var filters = new Array(); var sorts = new Array(); var orders = new Array();
radioboxes.filter(':checked').each(function() {
filters.push($(this).next('span').text()); var sort_order = $(this).val().split('-');
  sorts.push(sort_order[0]); orders.push(sort_order[1]);
});
if (filters.length > 1) {
var multiple = $(this).children('#multisort').text().replace('%s', filters.length);
$(this).children('span').text(multiple);
} else $(this).children('span').text($(event.target).next('span').text());
if(submitted) { var search = location.search.replace(/[?&](sort|order)=((\w+\.?\w+);?)+/ig, '');
$(this).unbind(); $(this).find('.option').hide();
if(sorts.length) search += '&sort=' + sorts.join(';');
if(orders.length) search += '&order=' + orders.join(';');
location.href = url + search.replace(/^&/,'?');
}
}} else $(this).find('.option').hide(); } else { $(this).find('.option').show().css('display', 'table'); }});};
$('.option').option();

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

Чтобы можно было включать/отключать данный вариант сортировки, в панель администрирования добавлена опция config_multisort.

Не забудьте также определить текстовое значение переменной text_multisort для своего языка. В данном примере в файле .../catalog/language/russian/russian.php оно следующее: "Выбрано %s условия".

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

4 апреля 2013 г.

По умолчанию сортировка в OpenCart доступна по названию, модели, цене, количеству (если в панели администрирования включена опция "Показывать остаток") и рейтингу продукта. Можно расширить этот список, добавив сортировку, например, по дате публикации, по количеству отзывов и по количеству просмотров. Однако все возможные способы сортировки применяются независимо друг от друга, т.е. они не связаны между собой. Это не совсем удобно, когда в магазине много товаров с одинаковыми ценами и наименованиями, но разными моделями. Здесь нужна одновременная сортировка в нескольких направлениях, в этой статье будет описано как её реализовать.

Начнём с добавления дополнительных способов сортировки:

1. Сортировка по дате размещения. Удобно отслеживать новые поступления или наоборот, "залежавшиеся" товары.
2. Сортировка по количеству отзывов. Отзывы это немаловажный атрибут как успешно продаваемого продукта, так и (в некоторых случаях) проблемного продукта с точки зрения, например, эксплуатации.
3. Сортировка по количеству просмотров. Этот способ сортировки покажет насколько товар популярен.

Изменения необходимо внести в модель продукта ./catalog/model/catalog/product.php, в функции getProducts и getProductSpecials. В этих функциях в массив, присваеваемый переменной $sort_data, необходимо добавить поля p.date_added (в функции getProducts это поле уже есть), p.viewed и reviews. Таким образом мы получим:

$sort_data = array('pd.name',
  'p.model',
  'p.quantity',
  'p.price',
  'rating',
  'p.sort_order',
  'p.date_added',
  'p.viewed',
  'reviews');

Теперь необходимо изменить текст запросов к базе данных. Для функции getProducts переменную $sql и её содержимое замените на следующий код:

if (isset($data['sort']) && array_intersect( (array)$data['sort'], array('rating', 'reviews') ) ) {
$sql = "SELECT p.product_id, AVG(r1.rating) AS rating, COUNT(r1.rating) AS reviews 
             FROM " . DB_PREFIX . "product p 
             LEFT JOIN " . DB_PREFIX . "product_description pd 
             ON (p.product_id = pd.product_id) 
             LEFT JOIN " . DB_PREFIX . "product_to_store p2s 
             ON (p.product_id = p2s.product_id) 
             LEFT OUTER JOIN " . DB_PREFIX . "review r1 
             ON (r1.product_id = p.product_id AND r1.status = 1)"; 
} else {

$sql = "SELECT p.product_id 
             FROM " . DB_PREFIX . "product p 
             LEFT JOIN " . DB_PREFIX . "product_description pd 
             ON (p.product_id = pd.product_id) 
             LEFT JOIN " . DB_PREFIX . "product_to_store p2s 
             ON (p.product_id = p2s.product_id)"; 
}

Обратите внимание, условие вводится для того, чтобы не выполнять объединения таблиц в запросе, если нет сортировки по рейтингу и количеству обзоров. Это ещё один шаг в области оптимизации OpenCart.

Далее в контроллере категории ./catalog/controller/product/category.php после строки $this->data['sorts'] = array(); необходимо добавить следующее:

$this->data['sorts'][] = array(
  'text'  => $this->language->get('text_date_added_asc'),
  'value' => 'p.date_added-ASC',
  'href'  => $this->url->link('product/category', 'path=' . $this->request->get['path'] . '&sort=p.date_added&order=ASC' . $url)
);

$this->data['sorts'][] = array(
  'text'  => $this->language->get('text_date_added_desc'),
  'value' => 'p.date_added-DESC',
  'href'  => $this->url->link('product/category', 'path=' . $this->request->get['path'] . '&sort=p.date_added&order=DESC' . $url)
);

$this->data['sorts'][] = array(
  'text'  => $this->language->get('text_viewed_asc'),
  'value' => 'p.viewed-ASC',
  'href'  => $this->url->link('product/category', 'path=' . $this->request->get['path'] . '&sort=p.viewed&order=ASC' . $url)
);

$this->data['sorts'][] = array(
  'text'  => $this->language->get('text_viewed_desc'),
  'value' => 'p.viewed-DESC',
  'href'  => $this->url->link('product/category', 'path=' . $this->request->get['path'] . '&sort=p.viewed&order=DESC' . $url)
);

$this->data['sorts'][] = array(
  'text'  => $this->language->get('text_reviews_asc'),
  'value' => 'reviews-ASC',
  'href'  => $this->url->link('product/category', 'path=' . $this->request->get['path'] . '&sort=reviews&order=ASC' . $url)
);

$this->data['sorts'][] = array(
  'text'  => $this->language->get('text_reviews_desc'),
  'value' => 'reviews-DESC',
  'href'  => $this->url->link('product/category', 'path=' . $this->request->get['path'] . '&sort=reviews&order=DESC' . $url)
);

Последнюю пару элементов массива заключите в тело условия if ($this->config->get('config_review_status')) { ... }, которое скроет сортировку по количеству отзывов, если они будут отключены в настройках магазина.

Не забудьте определить значения языковых констант (text_date_added_asc, text_date_added_desc, text_viewed_asc, text_viewed_desc, text_reviews_asc, text_reviews_desc) для локалей интерфейса:

./catalog/language/russian/product/category.php
./catalog/language/english/product/category.php
. . .

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

В следующей части будет описана непосредственно сама мультисортировка.

13 марта 2013 г.

SEO URL важная функциональная часть в программном обеспечении OpenCart, объяснять её востребованность нет смысла, достаточно вспомнить технологии SEO. Режим SEO-ссылок включается в панели администрирования в настройках магазина (закладка Сервер).

Вроде всё понятно, но есть одна проблема - это существенная нагрузка на базу данных! Это, пожалуй, самая затратная часть в плане расхода вычислительных ресурсов сервера. С ростом количества товаров и посетителей замедление в работе магазина становится настоящей проблемой! Можно, конечно, выбрать оборудование получше, но если вы собираетесь серьёзно заниматься онлайн-коммерцией, то в какой-то момент времени посмотрите на счета за услуги хостинга и удивитесь сумме ежемесячных платежей. Именно решением этой проблемы мы и занимаемся.

3 марта 2013 г.


По умолчанию в OpenCart доступна сортировка четырёх видов: по умолчанию (по порядку), по наименованию продукта, по цене и по наименованию модели.

Сортировка продуктов (программно) выполняется путём выполнения сформированного SQL-запроса в модели ./catalog/model/catalog/product.php в функциях getProducts и getProductSpecials.

Избыточность сортировки заключается в том, что при выборе сортировки по наименованию, сортировка в запросе получится следующая: ORDER BY LCASE(pd.name) ASC, LCASE(pd.name) ASC.

Чтобы исключить дополнительную, пусть и незначительную, нагрузку на базу, часть кода в перечисленных выше функциях
...
if (isset($data['order']) && ($data['order'] == 'DESC')) {
   $sql .= " DESC, LCASE(pd.name) DESC";
} else {
   $sql .= " ASC, LCASE(pd.name) ASC";
}
...

необходимо заменить на

if (isset($data['order']) && ($data['order'] == 'DESC')) {
   $sql .= " DESC";
   if (isset($data['sort']) && $data['sort'] != 'pd.name') $sql .= ", LCASE(pd.name) DESC";
} else {
   $sql .= " ASC";
   if (isset($data['sort']) && $data['sort'] != 'pd.name') $sql .= ", LCASE(pd.name) ASC";
}

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

Программное обеспечение CS-Cart как наглядный пример неоптимизированного продолжения OpenCart.

24 февраля 2013 г.

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

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

./catalog/controller/module/category.php
./catalog/controller/product/category.php
./catalog/controller/common/header.php

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

$product_total = $this->model_catalog_product->getTotalProducts($data);
...
'name'  => $result['name'] . ($this->config->get('config_product_count') ? ' (' . $product_total . ')' : ''),
...

Очевидно обращение к модели не имеет смысла, если показ отключён. Более верным решением будут следующие строчки кода:

$product_total = '';
...

if ($this->config->get('config_product_count')) {
$product_total = $this->model_catalog_product->getTotalProducts($data);
$product_total = ' (' . $product_total . ')';
}

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

23 февраля 2013 г.

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

Управление кэшем происходит в файле ./system/library/cache.php, если обратить внимание на функцию get, то видно, что результат возвращается функцией php unserialize когда файл кэша есть и null, когда его нет. Это значит, что если в кэше сохранён результат пустой выборки в одной из моделей, то при очередном запросе кэша будет возвращён ноль.

Практически во всех моделях условие проверки кэша следующее:

$product_data = $this->cache->get( ...

if (!$product_data) { ...

что в корне неверно, так как содержание кэша, например, строка s:1:"0";, может сообщить модулю об его "отсутствии" и будет выполнено повторное обращение к базе.

Если создать новые категории, то это будет хорошо заметно. Товаров нет, каждый раз запрос из базы возвращает пустой результат и сохраняет его в кэше. В этом конкретном примере необходимо открыть файл ./catalog/model/catalog/product.php найти функцию getTotalProducts и в её теле заменить условие

if (!$product_data) { ...

на

if ($product_data === null) { ...

Теперь существование кэша будет правильно воспринято отдельно взятой функцией того или иного модуля. Исправления актуальны для всех версий OpenCart, включая последнюю v1.5.5.1.

Update

Работая с библиотекой кэша ./system/library/cache.php обратил внимание, что принцип выдачи кэшированной информации организован неверно. Модулю, запросившему кэш, может быть выдана устаревшая информация, если обратите внимание на код, то данные с файла считываются раньше проверки на время.

Дабы не создавать отдельную статью, приведу решение здесь. Функцию get() необходимо заменить на код ниже.

public function get($key) {
$data = null;
$files = glob(DIR_CACHE . 'cache.' . preg_replace('/[^A-Z0-9\._-]/i', '', $key) . '.*');
if ($files) {
for ($n=0, $lenght = count($files); $n < $lenght; $n++) {
$file = $files[$n];
$time = substr(strrchr($file, '.'), 1);
      if ($time < time()) {
if (file_exists($file)) { unlink($file);
      } elseif (!$n) { $cache = file_get_contents($file); $data = unserialize($cache);
} } return $data; }

Кстати, обновлённная функция работает немного быстрее, т.к. используется цикл for next (призываю использовать в PHP вместо foreach везде) и unserialize выполняется не по умолчанию, а только если кэш не устарел.

Если у Вас недостаточно опыта по внесению изменений в программный код OpenCart, то мы готовы предложить со своей стороны помощь.
  • RSS
  • Twitter
  • Youtube