В онлайн магазинах довольно популярны разного рода акции, все они направлены на стимуляцию продаж и, как следствие, привлечение постоянных покупателей. Нет ничего лучше постоянных клиентов, они проверены и в некотором смысле лояльны. Что нужно делать, чтобы список таких клиентов пополнялся? Конечно на первом месте стоит так назваемый юзабилити, который должен подкрепляться выгодой для покупателя, в данном случае ценовой скидкой на товары.
9 декабря 2013 г.
Модуль Product of the Day: Товар дня с обратным отсчетом времени
Опубликовал Roman | 19:40 Метки: акции, модуль, обратный отсчет, предложение дня, скидки, товар дня, countdown, getProductSpecials, javascript, OpenCart v.1.5+3 декабря 2013 г.
Уведомление об истекающей сессии в панели управления OpenCart
Опубликовал Roman | 16:19 Метки: .htaccess, продление сессии, admin area, alert, cookie, expire, ping server, session, session.gc_maxlifetime, timeout
Думаю многие из вас сталкивались с такой проблемой, когда после набора текста в карточке товара и последующего сохранения (в панели администрирования) - перенаправлялись на страницу авторизации. Как же досадно, а иногда и обидно, когда потраченное время на написание полноценной статьи о товаре потрачено впустую (для копипастеров это не проблема). Потерять информацию можно не только при редактировании товаров, но и в любых других административных разделах, где данные вносятся человеком.
7 ноября 2013 г.
The Subscribers column: Модуль колонки подписчика на рассылку новостей в OpenCart
Опубликовал Roman | 16:35 Метки: акции, массовая рассылка, модуль, подписка, скидки, уведомления, email, newsletter, OpenCart v.1.5+, subscriber
Современный искушенный покупатель всегда охотится за скидками и распродажами, такой покупатель при возможности экономит на всём, даже на времени. Ему некогда заполнять учётные данные пользователя, поэтому для оформления товаров он часто прибегает к гостевой учётной записи. И конечно он заинтересован в специальных предложениях, но как такому покупателю о них сообщить и желательно заранее?
30 октября 2013 г.
Filter products by Attributes: Фильтр продуктов по аттрибутам (Модуль Attfilter)
Опубликовал Roman | 20:58 Метки: группы атрибутов, Модули, Фильтр, Attfilter, Class Override, getProducts, getTotalProducts, ocStore v1.5+, OpenCart v.1.5+, product attributes, Search
Любой товар содержит в себе определенную атрибутику, благодаря которой он может быть классифицирован, определено его назначение или потребительский спрос. К атрибутике относится множество таких атрибутов как цвет, материал, размеры, форма, электрические параметры и т.п.
Покупатели, как правило, используют атрибуты для сравнения схожих друг с другом товаров, чтобы определиться, какой из них лучше и доступный по цене. Немаловажным также является и поиск товаров по их атрибутам, однако в OpenCart по умолчанию это не предусмотрено. Решением может стать модуль фильтрации товаров по их атрибутам Attfilter.
Покупатели, как правило, используют атрибуты для сравнения схожих друг с другом товаров, чтобы определиться, какой из них лучше и доступный по цене. Немаловажным также является и поиск товаров по их атрибутам, однако в OpenCart по умолчанию это не предусмотрено. Решением может стать модуль фильтрации товаров по их атрибутам Attfilter.
8 октября 2013 г.
Forcing SSL for OpenCart: "Навязываем" защищенный протокол HTTPS
Опубликовал Roman | 19:56 Метки: Безопасность, доработка, защита персональных данных, регистрация пользователей, HTTPS, php https check, privacy, safe shopping, SSL
Использовать защищённый доступ к магазину через протокол SSL необходимо и важно не только с точки зрения законодательства той или иной страны (Федеральный закон РФ от 27 июля 2006 года № 152-ФЗ «О персональных данных»), но и с точки зрения собственной безопасности, когда трафик в публичных сетях постоянно прослушивается, как со стороны хакеров-любителей, так и реальных конкурентов. Особенно это актуально для точек бесплатного доступа Wi-Fi в больших городах.
1 октября 2013 г.
OpenCart BlackList: Доработка механизма Чёрного списка
Опубликовал Roman | 22:17 Метки: доработка, статус заказа, стоплист, черный список, blacklist, CIDR, ip
В OpenCart есть довольно полезный инструмент BlackList (Чёрный список). Правда он немного примитивен, но как минимум позволяет по IP-адресу выявить недобросовестного покупателя. Конечно список IP-адресов необходимо знать заранее, что не совсем удобно, поскольку современные мошенники редко используют одни и те же адреса. Однако некоторые не стесняются открыто заявлять о себе и рассчитывают, прежде всего, на невнимательность владельца магазина. Целью мошенников обычно является сам заказ, а точнее его сумма. Зная программные недоработки или уязвимости в OpenCart, они могут подменить стоимость покупки. В этой статье речь пойдёт о решении, которое можно использовать поверх стандартного Черного списка.
28 сентября 2013 г.
OpenCart Image Comment Module: Продвижение товаров через комментарии в изображениях
Опубликовал Roman | 23:38 Метки: каналы продвижения, метаданные, модуль, EXIF, feed, GIF, image comment, JPEG, OpenCart v.1.5+, PNG, SEO, XMP
Сегодня современный пользователь стал настолько ленивым, что не всегда вчитывается в сопроводительный текст к товарам или в текст результатов выдачи на страницах поисковых систем. Так сложилось, что визуальное восприятие объектов человеку даётся проще, чем их представление по описанию. По этой причине поиск по изображениям в Интернете стремительно набирает популярность.
6 сентября 2013 г.
Значения переменных OpenCart в вопросах и ответах
Опубликовал Roman | 17:21 Метки: переменные, текущие значения, category, code, currency, current, customer, how to get, id, language
Часто бывает, когда в том или ином решении требуется получить текущие значения ключевых переменных OpenCart. Все эти значения пользуются большим спросом у разработчиков, они известны, но иногда забываешь их способ получения. Поэтому решено составить соответствующий список в виде вопросов и ответов.
20 августа 2013 г.
OpenCart Multi-Level Menu: Создание многоуровневого меню (CSS + PHP)
Опубликовал Roman | 12:00 Метки: 3 уровня, выпадающий список, иерархическое меню, категории, css menu, hierarchy
В одном из проектов возникла необходимость реализовать многоуровневое меню OpenCart. Поскольку категорий было много, то их требовалось как-то выстроить в иерархическом порядке, чтобы посетителю было удобно перемещаться по нужным разделам сайта.
По умолчанию в OpenCart меню расчитано на два уровня с возможностью разделять список на столбцы. Но если категорий много, то выглядит это несколько громоздко, лучше информацию выдавать порциями, через выпадающие списки.
По умолчанию в OpenCart меню расчитано на два уровня с возможностью разделять список на столбцы. Но если категорий много, то выглядит это несколько громоздко, лучше информацию выдавать порциями, через выпадающие списки.
20 июля 2013 г.
OpenCart Banner Module: расширение возможностей стандартного модуля
Опубликовал Roman | 20:23 Метки: баннеры, Модули, произвольный код, banners, earnings, flash, google adwords, html-code, java, OpenCart v.1.5+
В этой статье речь пойдёт о расширении возможностей одного из стандартных модулей OpenCart, модуля показа баннеров. Цель доработки – получить возможность показывать на страницах магазина интерактивные баннеры в виде flash-объектов, java-апплетов, блоков javascript или любого другого кода в формате гипертекстовой разметки. Основная ставка сделана на размещение рекламных баннеров программ Google AdWords или Яндекс.Директ, также будет не менее интересно и полезно внедрить новые сервисы для покупателей на базе технологий Ajax.
9 июля 2013 г.
OpenCart Sitemap Pro: эффективная и расширенная настройка карты сайта sitemap.xml
Опубликовал Roman | 16:48 Метки: не формируется, оптимизация, производительность, feed, google_sitemap, modules, OpenCart v.1.5+, robots.txt, SEO URL, sitemap.xml
В каналах продвижения OpenCart по умолчанию доступен модуль Google Sitemap, отвечающий за формирование и показ карты сайта в виде файла sitemap.xml. Однако у него есть существенные недостатки, основными из которых являются высокая нагрузка на базу данных и неоптимизированная структура данных с точки зрения SEO.
Для начала стоит отметить, для чего необходима карта сайта в формате XML, и что получает владелец сайта при её использовании.
Для начала стоит отметить, для чего необходима карта сайта в формате XML, и что получает владелец сайта при её использовании.
31 мая 2013 г.
Доработка OpenCart: Эргономика интерфейса и функциональная логика (Часть I)
Опубликовал Roman | 14:59 Метки: доработка, категории, массовая рассылка, обслуживание, решения, товары, Bitrix, CS-Cart, Error 404, featured, jsTree, OpenCart v.1.5+, revision, SEO URL
В данной статье будут периодически появляться небольшие решения в плане улучшения эргономики интерфейса и функциональной логики OpenCart. Базовой версией для улучшения ПО магазина выбрана версия 1.5.4.
Предисловие
Не стоит гнаться за обновлениями успешного продукта, т.к. рано или поздно такой продукт станет неповоротливым и тяжёлым во всех смыслах. Помню, как начинал работать с ПО Bitrix, довольно быстрым и функциональным веб-приложением, сейчас же это программное обеспечение сильно нагружает не только сервер, но и людей. По тому же пути пошли и разработчики CS-Cart, а именно, стали добавлять много ненужных функций и модификаций, которые вроде и доставляют комфорт в работе (на мощных серверах), но на продажи магазинов существенно не влияют. Озвученные выше продукты коммерческие и они вынуждены наращивать избыточную функциональность, чтобы к ним не был потерян интерес.
OpenCart ПО открытое и свободное, в этом его основное преимущество, однако с каждым релизом работать с магазином становится сложнее, прежде всего в плане затрат на ресурсы сервера. Можно продолжать обновлять ПО с выходом каждого релиза, а можно остановиться на базовой версии и уже в ручном режиме вести необходимые доработки, т.к. OpenCart, как функциональный магазин, уже состоялся.
Предисловие
Не стоит гнаться за обновлениями успешного продукта, т.к. рано или поздно такой продукт станет неповоротливым и тяжёлым во всех смыслах. Помню, как начинал работать с ПО Bitrix, довольно быстрым и функциональным веб-приложением, сейчас же это программное обеспечение сильно нагружает не только сервер, но и людей. По тому же пути пошли и разработчики CS-Cart, а именно, стали добавлять много ненужных функций и модификаций, которые вроде и доставляют комфорт в работе (на мощных серверах), но на продажи магазинов существенно не влияют. Озвученные выше продукты коммерческие и они вынуждены наращивать избыточную функциональность, чтобы к ним не был потерян интерес.
OpenCart ПО открытое и свободное, в этом его основное преимущество, однако с каждым релизом работать с магазином становится сложнее, прежде всего в плане затрат на ресурсы сервера. Можно продолжать обновлять ПО с выходом каждого релиза, а можно остановиться на базовой версии и уже в ручном режиме вести необходимые доработки, т.к. OpenCart, как функциональный магазин, уже состоялся.
25 мая 2013 г.
Производительность OpenCart: Подгрузка изображений по необходимости
Опубликовал Roman | 20:08 Метки: изображение, категории, производительность, товары, хостинг, image, modules, preloader, previewВ данной статье речь пойдёт об оптимизации сетевого трафика при работе с магазином 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
Рисунок 1. Модуль подгрузки изображений OpenCart Image Preloader v1.0 |
Кроме того, улучшен сам алгоритм подгрузки изображений. Теперь картинки выстраиваются в очередь - когда следующее изображение будет подгружено после загрузки последнего.
Дополнительно между загрузками можно выставить интервал в миллисекундах, что определенно позволяет снизить нагрузку на статику сервера.
Модуль поддерживает как минимум четыре основных раздела (Категория, Поиск, Производитель, Акции), но могут быть выбраны и другие разделы, в том числе самописные, если их шаблоны построены на основе базовых разделов.
Состав архива (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 г.
Мультисортировка в OpenCart: Часть 2
Опубликовал Roman | 16:49 Метки: сортировка, getProducts, multisort, order
В предыдущей части мы рассмотрели добавление дополнительных способов сортировки в каталогах магазина, в этой части будет реализован сам принцип мильтисортировки.
Сразу хочу принести извинения за задержку статьи, к сожалению, в России начинают сбываться опасения правозащитников относительно цензуры в Интернете. Недавно Ростелеком по решению регионального суда вместо выборочной блокировки одного из ресурсов заблокировал всю площадку 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 условия".
Если у вас возникли трудности с освоением данной статьи, то всю работу по доработке вашего магазина мы готовы взять на себя.
Сразу хочу принести извинения за задержку статьи, к сожалению, в России начинают сбываться опасения правозащитников относительно цензуры в Интернете. Недавно Ростелеком по решению регионального суда вместо выборочной блокировки одного из ресурсов заблокировал всю площадку 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;
}
После $(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 реализует саму работу этого элемента управления. В результате мы получим элемент управления как на рисунке ниже.
Не забудьте также определить текстовое значение переменной text_multisort для своего языка. В данном примере в файле .../catalog/language/russian/russian.php оно следующее: "Выбрано %s условия".
Если у вас возникли трудности с освоением данной статьи, то всю работу по доработке вашего магазина мы готовы взять на себя.
4 апреля 2013 г.
Мультисортировка в OpenCart: Часть 1
Опубликовал Roman | 12:59 Метки: оптимизация, сортировка, getProducts, getProductSpecials, multisort, order
По умолчанию сортировка в 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
. . .
Сортировка в магазине это не просто удобный инструмент для пользователя, это неотъемлемая часть торговли, процесс, который держит товар в постоянном обороте.
В следующей части будет описана непосредственно сама мультисортировка.
Начнём с добавления дополнительных способов сортировки:
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 г.
Оптимизация Opencart: уменьшаем количество обращений к базе в режиме SEO URL
Опубликовал Roman | 20:20 Метки: 404 Not Found, ранжирование, ссылки, keyword, links, OpenCart v.1.5+, optimization, redirect 301, SEO URL
SEO URL важная функциональная часть в программном обеспечении OpenCart, объяснять её востребованность нет смысла, достаточно вспомнить технологии SEO. Режим SEO-ссылок включается в панели администрирования в настройках магазина (закладка Сервер).
Вроде всё понятно, но есть одна проблема - это существенная нагрузка на базу данных! Это, пожалуй, самая затратная часть в плане расхода вычислительных ресурсов сервера. С ростом количества товаров и посетителей замедление в работе магазина становится настоящей проблемой! Можно, конечно, выбрать оборудование получше, но если вы собираетесь серьёзно заниматься онлайн-коммерцией, то в какой-то момент времени посмотрите на счета за услуги хостинга и удивитесь сумме ежемесячных платежей. Именно решением этой проблемы мы и занимаемся.
Вроде всё понятно, но есть одна проблема - это существенная нагрузка на базу данных! Это, пожалуй, самая затратная часть в плане расхода вычислительных ресурсов сервера. С ростом количества товаров и посетителей замедление в работе магазина становится настоящей проблемой! Можно, конечно, выбрать оборудование получше, но если вы собираетесь серьёзно заниматься онлайн-коммерцией, то в какой-то момент времени посмотрите на счета за услуги хостинга и удивитесь сумме ежемесячных платежей. Именно решением этой проблемы мы и занимаемся.
3 марта 2013 г.
Оптимизация OpenCart: Избыточная сортировка продуктов по наименованию
Опубликовал Roman | 16:02 Метки: сортировка, CS-Cart, getProducts, getProductSpecials, optimizationПо умолчанию в 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 г.
Оптимизация OpenCart: Исправление недочётов в формировании списка категорий
Опубликовал Roman | 03:03 Метки: config_product_count, getTotalProducts, optimization
В предыдущем сообщении была разобрана одна из причин дополнительной нагрузки на базу данных из-за неправильной обработки кэша. Проверив теорию на практике, было также найдено избыточное обращение к модели категорий, что также является, в некоторой степени, причиной снижения быстродействия.
В перечисленных ниже файлах вычисляются количество продуктов для каждой категории. Количество будет показано только в том случае, если включена соответствующая опция 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 . ')';
}
Стоит отметить, что игнорирование подобных недочётов в конечном счёте приводит к значительным потерям в производительности сервера.
В перечисленных ниже файлах вычисляются количество продуктов для каждой категории. Количество будет показано только в том случае, если включена соответствующая опция 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: Исправление работы кэша с количеством продуктов для категорий
Опубликовал Roman | 00:08 Метки: cache, getTotalProducts, OpenCart v.1.5+, optimization, unserialize
Использование кэша в 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, то мы готовы предложить со своей стороны помощь.
Управление кэшем происходит в файле ./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, то мы готовы предложить со своей стороны помощь.
Подписаться на:
Сообщения (Atom)