Как установить модуль vQMod для OpenCart

1. Скачиваем архив с файлами модуля vQMod из официального источника [скачать]
2. Распаковываем архив, получаем папку vqmod
3. Копируем папку vqmod в корень сайта, там где расположены файлы вашего магазина OpenCart
4. Устанавливаем права 777 следующим папкам: /vqmod и /vqmod/vqcache
5. В браузере, в адресной строке пишем http://www.ВАШ_САЙТ.ru/vqmod/install/
6. При успешной установке видим сообщение “VQMOD HAS BEEN INSTALLED ON YOUR SYSTEM!”
7. Модуль vQMod установлен! Папку установки удалять не нужно, т.к. при обновлении OpenCart, придется повторно производить инсталляцию vQMod

Добавлено: 01 Марта 2015 09:51:04 Добавил: Андрей Ковальчук

Русская локализация Opencart 1.5.6 (rus)

Скачать русификатор для Opencart 1.5.6

Как установить русский язык на Opencart 1.5.6

1) Скопируйте содержимое архива (папки admin и catalog) в каталог интернет-магазина.
2) Зайдите в админку и добавьте русский язык: System / Localisation / Languages / Insert.

Заполните, как показано ниже:

Language Name: Русский
Code: ru
Locale: ru,ru_RU,ru_RU.UTF-8
Image: ru.png
Directory: russian
Filename: russian
Status: Enabled
Sort Order: 1


3) Перейдите на вкладку System / Settings / Edit / Local и выберите в выпадающих списках Language и Administration Language параметр «Русский»

Добавлено: 27 Февраля 2015 20:00:34 Добавил: Андрей Ковальчук

Оптимизация OpenCart: Исправление недочётов в формировании списка категорий

В перечисленных ниже файлах вычисляются количество продуктов для каждой категории. Количество будет показано только в том случае, если включена соответствующая опция 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 . ')';
}


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

Добавлено: 26 Февраля 2015 07:08:51 Добавил: Андрей Ковальчук

Оптимизация OpenCart: Избыточная сортировка продуктов по наименованию

По умолчанию в 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";
}


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

Добавлено: 26 Февраля 2015 07:07:07 Добавил: Андрей Ковальчук

Производительность OpenCart: Подгрузка изображений по необходимости

В данной статье речь пойдёт об оптимизации сетевого трафика при работе с магазином 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.

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

Добавлено: 26 Февраля 2015 07:05:15 Добавил: Андрей Ковальчук

OpenCart BlackList: Доработка механизма Чёрного списка

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

Для начала вспомним как работает BlackList. После того, как покупатель заполнил все необходимые формы, ему будет предложено подтвердить заказ. После нажатия соответствующей кнопки в интерфейсе магазина, в базе данных будет создан журнал истории заказа со статусом по умолчанию. Этот статус выставляется в настройках магазина через переменную config_order_status_id. Проверка в Черном списке как раз осуществляется на этапе создания журнала, однако как показала практика, она бесполезна - при обнаружении в Черном списке записи о покупателе, заказу присваивается тот же статус, по умолчанию. Т.е. владелец магазина не сможет определить по статусу заказа его фиктивность. Возможно разработчики OpenCart недоработали этот механизм или он реализован в коммерческих версиях, в любом случае, будем это исправлять.

На примере переменной config_order_status_id в файлах ./admin/controller/setting/setting.php и ./admin/view/template/setting/setting.tpl создадим аналогичную переменную, например config_blocked_order_status_id. Далее в файле ./catalog/model/checkout/order.php находим следующий код:

if ($status) {
  $order_status_id = $this->config->get(' config_order_status_id ');
}


и заменяем его на код ниже:

if ($status) {
  $order_status_id = $this->config->get('config_blocked_order_status_id');
}


В настройках магазина определим новый статус для подозрительных заказов, например Блокирован. Теперь владелец магазина или менеджер будут видеть статус у заказа, если IP-адрес покупателя находится в стоплисте. На основе этой информации можно отказать в заказе или провести тщательную проверку заказа и самого покупателя.

Собственно мы на этом не остановились и решили продолжить доработку Черного списка. В частности, в расширенной версии можно указывать не только IP-адреса, но и имена хостов, причём как для IP, так и для хоста допустимо указывать их части, т.е. будет сравнение по частям строк. Это позволяет "подозревать" целые подсети и страны по TLD (домен верхнего уровня). В поле IP можно также указывать и адрес в формате CIDR:

Добавлено: 26 Февраля 2015 07:02:40 Добавил: Андрей Ковальчук

Forcing SSL for OpenCart: "Навязываем" защищенный протокол HTTPS

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


В OpenCart есть соответствующая опция, отвечающая за использование протокола SSL на сайте. Разумеется сам магазин протоколом не управляет, этим занимается веб-сервер, мы всего лишь можем задать схему для ссылки (протокол HTTP или HTTPS). Опция доступна через меню панели управления (Система -> Настройки -> Магазин) в настройках магазина на закладке Сервер. Важно отметить, что "безопасный" формат ссылки не будет таковым, если не определено соответствующее значение константы HTTPS_SERVER в файле ./config.php.

"Безопасные" ссылки доступны для кабинета покупателя, аккаунта партнера и при оформлении заказа. Если пользователь переходит по ссылкам на странице, то защищенный HTTP будет определен по самой ссылке, но если пользователь перейдёт к закрытому разделу магазина через адресную строку, то он может указать адрес в укороченном виде, без схемы (или ему подменят схему в случае перехвата трафика). В OpenCart при указании адреса без схемы, протокол будет принят по умолчанию как HTTP, что недопустимо для страниц, требующих шифрования.

Чтобы решить эту проблему и сохранить максимально возможную совместимость с разными видами веб-серверов (IIS, Apache, Nginx) нам потребуется написать универсальную функцию определения SSL-соединения средствами PHP:

 public function is_ssl() {
  if ( isset($_SERVER['HTTP_SSL']) ) {
   if ( 'on' == strtolower($_SERVER['HTTP_SSL']) )
    return true;
   if ( '1' == $_SERVER['HTTP_SSL'] )
    return true;
  } elseif ( isset($_SERVER['HTTPS']) ) {
   if ( 'on' == strtolower($_SERVER['HTTPS']) )
    return true;
   if ( '1' == $_SERVER['HTTPS'] )
    return true;
  } elseif ( isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && ( 'https' == strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) ) ) {
   return true;
  } elseif ( isset($_SERVER['SERVER_PORT']) && ( '443' == $_SERVER['SERVER_PORT'] ) ) {
   return true;
  } elseif ( isset($_SERVER['HTTP_X_FORWARDED_PORT']) && ( '443' == $_SERVER['HTTP_X_FORWARDED_PORT'] ) ) {
   return true;
  }
  return false;
 }


Эту функцию необходимо добавить в файл ./system/library/request.php. Кстати, хорошая альтернатива для замены одноименной функции в блогах WordPress. Функция учитывает как собственные переменные веб-сервера, так и переменные, передаваемые в заголовках от прокси-сервера.

Следующую функцию необходимо поместить в файл ./system/library/url.php:

public function is_https() {
 return preg_match('/https:\/\//', $this->ssl);
}


Далее во всех файлах-контроллерах, где встречаются строки вида

if ($this->customer->isLogged()) {
     ...
    $this->redirect($this->url->link('account/login', '', 'SSL'));
}


или

if ($this->affiliate->isLogged()) {
     ...
    $this->redirect($this->url->link('affiliate/login', '', 'SSL'));
}


необходимо после добавить следующий код:

elseif ($this->url->is_https() != $this->request->is_ssl()) {
    $this->redirect($this->url->link('account/*', '', 'SSL'));
}


или

elseif ($this->url->is_https() != $this->request->is_ssl()) {
    $this->redirect($this->url->link('affiliate/*', '', 'SSL'));
}


соответственно, где * - имя файла-контроллера. Теперь нельзя будет использовать защищенный протокол, если он не включен (лишняя нагрузка на сервер при прямом наборе адреса со схемой https) и наоборот, будет перенаправление на защищенный протокол, если того требует тот или иной раздел сайта.

В Интернете есть похожие решения, но выполнены они через mod_rewrite веб-сервера Apache в файле .htaccess. Эти решения грубо говоря "прошиты" и не позволяют включать/отключать SSL через панель администрирования OpenCart.

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

Добавлено: 26 Февраля 2015 07:00:08 Добавил: Андрей Ковальчук

Уведомление об истекающей сессии в панели управления OpenCart

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

Причина всем известна, на сервере через определенный момент времени при отсутствии к нему обращений истекает сессия. Что же делать? Можно увеличить время её жизни, изменив значение параметра session.gc_maxlifetime в php.ini или задав его в файле .htaccess через директиву php_value. Но не на каждом хостинге это возможно, к тому же с точки зрения безопасности это неправильно, поскольку время жизни сессии направлено не только на защиту от переполнения файловой системы, но и на забывчивого пользователя, который может оставить компьютер с авторизованной панелью администрирования.

Можно периодически обращаться к серверу в фоновом режиме по технологии Ajax, используя следующую конструкцию в файле ./admin/view/template/common/header.tpl:

<script type="text/javascript">
$(document).ready(function() {
setInterval(function() {$.ajax({ url: location.href });
}, 20000);
});
</script>


Но лучше реализовать компромиссное решение, когда и безопасность учитывается и пользователь, вносящий товары с некоторой задержкой, не будет время от времени "расстраиваться". Это решение основано на использовании срока жизни cookie, которое в нашем случае будет сопоставимо со сроком жизни сессии.

1. В конце файла ./admin/index.php перед строкой $response->output(); добавьте следующий код:

if (isset($session->data['user_id'])) {
 setcookie('session_status', true, time() + ini_get('session.gc_maxlifetime') - 5);
} elseif (isset($_COOKIE['session_status']))  {
 setcookie('session_status', false);
}


2. В соответствующих языку файлах ./admin/language/russian/common/header.php и ./admin/language/english/common/header.php добавьте перевод:

$_['text_session_expire'] = 'Ваша сессия истекла! Если Вы не сохранили данные, то при следующем запросе они могут быть утеряны!';
$_['text_session_expire']  = 'Your session has expired! If you haven\'t saved data, they will be lost at next request!';


3. В файле ./admin/controller/common/header.php найдите строку $this->data['stores'] = array(); и перед ней добавьте следующие строки:

$this->data['text_session_expire'] = $this->language->get('text_session_expire');
$this->data['token'] = $this->session->data['token'];

4. В файле-шаблоне ./admin/view/template/common/header.tpl до строки $(document).ready(function(){ добавьте <?php if ($logged) { ?>var sessionControl = {is_typed: false, intervalId: 0};<?php } ?>
после неё:

 <?php if ($logged) { ?>
 sessionControl.intervalId = setInterval(function(){
  if($.cookie('session_status') === null) {
   if (sessionControl.is_typed) {
    $.get('?route=tool/error_log&token=<?php echo $token; ?>');
    sessionControl.is_typed = false;
   } else {
    clearInterval(sessionControl.intervalId);
    alert('<?php echo $text_session_expire; ?>');   
   }
  } 
 }, 2500);

  $(document).keypress(function(e) {
  sessionControl.is_typed = true;
 });
 <?php } ?>


Приведенное выше решение с интервалом в 2,5 секунды будет опрашивать наличие cookie
session_status. Если данный cookie будет аннулирован браузером, то значит время сессии фактически истекло и об этом пользователь будет уведомлен. Если в период действия cookie будет нажата клавиша (процесс набора текста), то к моменту анулирования cookie будет выполнено фоновое обращение к серверу с целью продления жизни сессии. Обратите внимание, что в первом пункте срок существования cookie сокращён на 5 секунд, которого достаточно для покрытия интервала функции setInterval и отправки асинхронного запроса (при необходимости разницу можно увеличить).

Добавлено: 26 Февраля 2015 06:10:33 Добавил: Андрей Ковальчук

Мультисортировка в OpenCart: Часть 2

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

Сразу хочу принести извинения за задержку статьи, к сожалению, в России начинают сбываться опасения правозащитников относительно цензуры в Интернете. Недавно Ростелеком по решению регионального суда вместо выборочной блокировки одного из ресурсов заблокировал всю площадку 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 условия".

Добавлено: 26 Февраля 2015 06:03:49 Добавил: Андрей Ковальчук

Мультисортировка в OpenCart: Часть 1

По умолчанию сортировка в 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 . . .

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

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

Добавлено: 26 Февраля 2015 05:57:50 Добавил: Андрей Ковальчук

OpenCart Banner Module: расширение возможностей стандартного модуля

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

В общем виде одна из форм модуля баннеров (см. меню Система/Дизайн/Баннеры) будет дополнена новой кнопкой, при нажатии которой можно выбрать один из типов баннера: изображение или код HTML.



Приоритет при показе имеет тип баннера с заполненным полем кода, т.е. если у баннера в текстовом поле есть данные, то они будут выведены на экран, в противном случае будет показано изображение.

Внимание! Перед каждой модификацией сайта или базы данных не забывайте делать резервные копиии.

Порядок внесения изменений

1. Используя веб-приложение PhpMyAdmin или консоль MySQL в структуру таблицы banner_image добавьте поле code с типом данных TEXT.

2. В файле ~/admin/model/design/banner.php для функций addBanner() и editBanner() в конструкции INSERT INTO для таблицы banner_image в конец текста запроса добавьте code = '" . $this->db->escape($banner_image['code']) . "'
2.1 Здесь же для функции getBannerImages() в переменную массива данных $banner_image_data[] добавьте 'code' => $banner_image['code']

3. В файлах ~/admin/language/russian/design/banner.php и ~/admin/language/english/design/banner.php добавим соответствующие им переменные перевода:

$_['text_empty'] = 'Очистить поле';
$_['entry_code'] = 'Код HTML:';
$_['button_code'] = 'Код HTML';
$_['button_image'] = 'Изображение';	$_['text_empty'] = 'Clear Field';
$_['entry_code'] = 'HTML Code:';
$_['button_code'] = 'HTML Code';
$_['button_image'] = 'Image';


4. В файле контроллера модуля баннера ~/admin/controller/design/banner.php для функции getForm() определяем переменные перевода из п.3:

$this->data['text_empty'] = $this->language->get('text_empty'); $this->data['entry_code'] = $this->language->get('entry_code'); $this->data['button_code'] = $this->language->get('button_code'); $this->data['button_image'] = $this->language->get('button_image');

Для этой же функции в переменную массива данных $this->data['banner_images'][] добавляем: 'code' => $banner_image['code']

5. Шаблон формы ~/admin/view/template/design/banner_form.tpl полностью заменяем на файл, размещенный по указанной ссылке.

6. В файле ~/catalog/controller/module/banner.php после строки foreach ($results as $result) { добавляем следующий код:

   if (!empty($result['code'])) {
    $this->data['banners'][] = array(
     'code' => $result['code']
    );
   } else


7. В шаблоне ~/catalog/view/theme/default/template/module/banner.tpl находим строку <?php if ($banner['link']) { ?> и заменяем на код ниже:

  <?php if (!empty($banner['code'])) { ?>
  <?php echo htmlspecialchars_decode($banner['code']); ?>
  <?php } elseif ($banner['link']) { ?>


Готово! Дополнительных настроек не требуется, все ранее сохранённые баннеры в системе останутся, для них станет доступно новое поле для ввода кода.

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

Добавлено: 25 Февраля 2015 20:42:01 Добавил: Андрей Ковальчук

Значения переменных OpenCart в вопросах и ответах

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

Как узнать текущий язык магазина?

Идентификатор текущего языка (целое число):

$current_language_id = $config->get('config_language_id');


Код текущего языка (двухсимвольный):

$current_language_code = $this->config->get('config_language'); 


или

$current_language_code = $this->language->get('code');


Как определить выбранную валюту?

Код текущей валюты (трехсимвольный):

$current_currency_code = $this->currency->getCode();


В какой категории находится просматриваемый товар?

Довольно актуально, если страница товара открыта по прямой ссылке, т.е. не через категории. Идентификатор текущей категории (ноль будет означать, что пользователь в разделе, не связанном с категорией, например на главной странице):

$parts = array(0);
if (isset($this->request->get['path'])) {
 $parts = explode('_', (string)$this->request->get['path']);
} elseif (isset($this->request->get['product_id'])) { 
 if ($rows = $this->model_catalog_category->getCategoriesByProductId($this->request->get['product_id'])) {
 $parts = array_values($rows[0]);
 }
}
$current_category_id = (int)array_pop($parts);


Как узнать идентификатор действующего магазина?

$current_store_id = $this->config->get('config_store_id');

Какой шаблон используется для магазина?

$current_template = $this->config->get('config_template');


Как определить текущего пользователя?

Если требуется получить имя авторизованного пользователя в панели управления OpenCart и его идентификатор:

$this->load->library('user'); $user_data = new User($this->registry);
$current_username = $user_data->getUserName();
$current_user_id = $user_data->getId(); 


Для идентификатора бывает достаточно узнать значение одной из переменных сессии (ноль будет означать, что пользователь не авторизован):

$current_user_id = isset($this->session->data['user_id']) ?  $this->session->data['user_id'] : 0;


Как определить авторизованного покупателя?

Аналогично вопросу выше, только имя подключаемой библиотеки customer, а идентификатор покупателя в сессии customer_id.

Как узнать содержимое переменной в процессе отладки?

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

$this->log->write($variable);


Как добавить скриптовый файл JavaScript или файл стилей CSS?

Файлы скриптов и стилей лучше подключать по мере их необходимости, вместо того, чтобы прописывать в шаблонах типа header.tpl. Это ускоряет процесс окончательной загрузки тех страниц, где они не используются:

 $this->document->addStyle('catalog/view/theme/default/stylesheet/style.css');
 $this->document->addScript('catalog/view/javascript/script.js');


Как узнать количество товаров в корзине без выполнения запроса к серверу?

Очевидно это можно сделать только через интерфейс магазина средствами jQuery. Разумеется решение справедливо для стандартной версии OpenCart, где модуль корзины покупок выполнен по всем правилам.

<script type="text/javascript">var cart_total = $('#cart-total').text().replace( /^\D+/g, ''); if (parseInt(cart_total)) { alert('Cart is not empty'); }</script>


Как узнать текущую страницу (layout)?

Многие переменные можно брать непосредственно из параметров запроса типа GET. Чтобы получить текущую страницу (схему), используйте следующую запись:

$route = $this->request->get['route'];

Добавлено: 25 Февраля 2015 20:29:05 Добавил: Андрей Ковальчук

OpenCart Multi-Level Menu: Создание многоуровневого меню (CSS + PHP)

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

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

Для начала стоит задуматься о формировании категорий в иерархическом виде, для этого в файл модели категории ~/catalog/model/catalog/category.php добавим функцию getHierarchyCategories():

public function getHierarchyCategories() {

$categories = $this->cache->get('categories.hierarchy.' . (int)$this->config->get('config_language_id') . '.' . (int)$this->config->get('config_store_id'));
if(!$categories) {
$query = $this->db->query("SELECT * FROM " . DB_PREFIX . "category c LEFT JOIN " . DB_PREFIX . "category_description cd ON (c.category_id = cd.category_id) LEFT JOIN " . DB_PREFIX . "category_to_store c2s ON (c.category_id = c2s.category_id) WHERE cd.language_id = '" . (int)$this->config->get('config_language_id') . "' AND c2s.store_id = '" . (int)$this->config->get('config_store_id') . "'  AND c.status = '1' ORDER BY c.sort_order, LCASE(cd.name)");
$rows = $query->rows;
$num_rows = $query->num_rows;
$categories = array();
while($num_rows > 0) {
     foreach ($rows as $key => $row) {
      if ((int)$row['parent_id']) {
       if ($this->addSubcategory($categories, $row)) {
        unset($rows[$key]);
        $num_rows--;
       }
      } else {
       $categories[(int)$row['category_id']] = $row;
        unset($rows[$key]);
        $num_rows--;
      }
     }
    $num_rows--;
   }
   $this->cache->set('categories.hierarchy.' . (int)$this->config->get('config_language_id') . '.' . (int)$this->config->get('config_store_id'), $categories);
  }
  return $categories;
 }


Роль рекурсивной функции здесь выполняет функция addSubcategory(), её код представлен ниже, его также необходимо добавить в файл модели:

 protected function addSubcategory(&$categories, $row) {
  foreach($categories as $category) {
   if ($category['category_id'] == $row['parent_id']) {
    $categories[$row['parent_id']]['children'][$row['category_id']] = $row;
       return true;
   } elseif (!empty($category['children'])) {
      if ($this->addSubcategory($category['children'], $row)) {
       $categories[$category['category_id']]['children'] = $category['children'];
       return true;
      }  
   }
  }
  return false;
 }


Обратите внимание, что функцией getHierarchyCategories() используется кэширование, что не даёт ей злоупотреблять производительностью OpenCart.

Далее в контроллере "шапки" ~/catalog/controller/common/header.php добавляем функции, которые формируют массив данных для выпадающего меню из полученной иерархической структуры категорий:

 protected function addCategories(&$data, $categories, $path = '') {
   foreach($categories as $category) {
        $_path = $path ? ($path . '_' . $category['category_id']) : $category['category_id'];
     $item = array(
       'name'     => $category['name'],
       'column'   => $category['column'] ? $category['column'] : 1,
       'href'     => $this->url->link('product/category', 'path=' . $_path),
       'sort_order'     => $category['sort_order']
      );
     if (!empty($category['children'])) {
      $item['children'] = $this->addCategories($data, $category['children'], $_path);
     }
     if ($category['top']) {
      unset($categories[$category['category_id']]);
      $data[] = $item;
     } else {
      $categories[$category['category_id']] = $item;
     }
   }
    usort($categories, array($this, 'compareSortOrder'));
   return $categories;
 }

 function compareSortOrder($a, $b) {
  return $a['sort_order'] - $b['sort_order'];
 }



Примечание: Функция compareSortOrder($a, $b) используется для сортировки пунктов меню по порядку сортировки категорий.

В этом же файле строку кода $categories = $this->model_catalog_category->getCategories(0); заменяем на код ниже:

$categories = $this->model_catalog_category->getHierarchyCategories();
$this->addCategories($this->data['categories'], $categories);


Блок кода цикла foreach ($categories as $category) полностью удаляем или комментируем. На этом основные работы завершены, остаётся внести соответствующие изменения в шаблон меню ~/catalog/view/theme/default/template/common/header.tpl и файл стилей ~/catalog/view/theme/default/stylesheet/stylesheet.css.

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

Добавлено: 25 Февраля 2015 20:23:10 Добавил: Андрей Ковальчук

Оптимизация OpenCart: Исправление работы кэша с количеством продуктов для категорий

Использование кэша в 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 выполняется не по умолчанию, а только если кэш не устарел.

Добавлено: 25 Февраля 2015 20:16:41 Добавил: Андрей Ковальчук

Доработка OpenCart: эргономика интерфейса и функциональная логика

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

Предисловие

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

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

Решения

1. Скрытие продуктов для закрытых категорий.

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

В файле модели ./catalog/model/catalog/product.php для функций getProduct, getProducts, getProductSpecials, getLatestProducts, getPopularProducts, getBestSellerProducts, getProductRelated, getTotalProducts, getTotalProductSpecials в тело условия sql-запроса WHERE после p.status = '1' везде необходимо добавить следующий код: AND (SELECT IFNULL(SUM(cat.status),1) FROM category cat INNER JOIN product_to_category p2cat ON (cat.category_id = p2cat.category_id) WHERE p2cat.product_id = p.product_id) > 0
Теперь ссылающиеся товары, товары производителей и товары в результатах поиска будут также скрыты, если скрыты их категории.

2. Увеличение "веса" ссылки для категории продуктов

Если включён режим SEO URL, то для продуктов в тело тега <head> добавляется каноническое имя страницы <link href="http://your.domain.com/seo_url" rel="canonical" />, котороё сообщает поисковому роботу приоритетный адрес этой страницы.

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

В файл ./catalog/controller/product/category.php после строки $this->document->setKeywords($category_info['meta_keyword']); добавьте $this->document->addLink($this->url->link('product/category', 'path=' . $this->request->get['path']), 'canonical');

3. Сообщаем поисковым роботам о запрете индексирования несуществующих страниц сайта

Для начала необходимо привести в соостветствие заголовок об ошибке 404 (см. RFC 2616). В файле ./catalog/controller/error/not_found.php наидите строку $this->response->addHeader($this->request->server['SERVER_PROTOCOL'] . '/1.1 404 Not Found'); и удалите из аргумента /1.1. Дело в том, что в переменной SERVER_PROTOCOL уже содержится (реальная) версия протокола.

Теперь стоит обратить внимание на запрет индексации подобных страниц, чтобы поисковый робот не отслеживал их. Для этого в файл ./system/library/document.php добавим следующие строки кода:

private $robots;
 public function setRobots($robots) {
  $this->robots = $robots;
 } 
 public function getRobots() {
  return $this->robots;
 }


Далее в тело функции index() файла ./catalog/controller/common/header.php добавим $this->data['robots'] = $this->document->getRobots(); и соответственно внесём изменения для шаблона ./catalog/view/theme/default/template/common/header.tpl добавив следующий код:

<?php if ($robots) { ?>
<meta name="Robots" content="<?php echo $robots; ?>" />
<?php } ?>


Вернёмся в контроллер not_found.php и после добавления заголовка страницы поместим строку $this->document->setRobots('noindex');

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

4. Сообщаем поисковым роботам о режиме обслуживания

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

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

Для этого в файл /catalog/controller/common/maintenance.php до передачи страницы на рендеринг добавьте строку $this->response->addHeader($this->request->server['SERVER_PROTOCOL'] . ' 503 Service Temporarily Unavailable');.

Можно дополнить это решение, сообщить роботу спустя какой промежуток времени сайт станет доступным.

5. Выгрузка базы данных больших размеров из панели администрирования OpenCart

При работе с базой данных через пункт меню "Резервное копирование", браузер может неожиданно сбросить соединение. Одна из основных причин - нехватка памяти, выделенной скрипту. В журнале ошибок Apache могут быть сообщения вида Fatal error: Allowed memory size exhausted, что подтвердит описанную выше проблему.

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

6. Доработка менеджера файлов или окна управления изображениями

В OpenCart управление изображениями осуществляется через файл ~/admin/controller/common/filemanager.php при помощи мода jsTree для библиотеки jQuery. Мод отличается от других ему подобных своей простотой и элегантностью, но каждый раз открывая менеджер файлов в панели управления OpenCart, приходится раскрывать всё дерево в поисках текущего каталога для сохраненного файла, а потом уже и поиска самого файла среди множества других. Это очень неудобно и отнимает большое количество времени при наличии сложной иерархической структуры файлового хранилища. Однако было найдено решение и для этой проблемы!

7. Обращаем внимание на список рекомендуемых товаров

На главной странице магазина при помощи модуля Featured можно выводить список рекомендуемых товаров. Для постоянных посетителей этот список может быстро потерять актуальность, поскольку они могут к нему "привыкнуть". Хорошо бы данный список перемешивать каждый раз при открытии страницы. Поручить эту задачу стоит самому модулю (~/catalog/controller/module/featured.php), добавив в код функцию перемешивания ассоциативного массива.

8. Массовая рассылка сообщений покупателям OpenCart

В OpenCart есть сервис рассылки электронных писем покупателям и партнерам магазина (меню Продажи > Почта). Довольно полезная утилита, всегда есть возможность сообщить о ключевых новостях непосредственно по E-mail. Однако при большом количестве зарегистрированных пользователей в системе, такая рассылка попадает под определение массовой, на которую могут быть наложены санкции со стороны провайдера. Речь идёт об ограничениях на количество рассылаемых писем в единицу времени на хостинге. Функционал этой утилиты был успешно доработан, теперь появилась возможность задавать очередь сообщений с определенным интервалом в интерактивном режиме (отображение информации о ходе рассылки в реальном времени).

9. Определение "красной" цены в OpenCart

По умолчанию в OpenCart если товар участвует в акции, то визуально его старая цена перечеркнута в красном цвете, а новая стоит рядом (в черном). Однако это в корне неверно, поскольку в науке о продажах сказано, что "красная" цена - это минимально возможная цена, которая удовлетворяет как покупателя, так и продавца. В файле стилей stylesheet.css для классов .price-new и .price-old цвета нужно поменять местами.

10. Не учитывается часовой пояс при формировании дат.

Это больше касается настроек сервера, нежели OpenCart. Суть проблемы в том, что в журнал ошибок OpenCart записываются записи с временем отличным от времени сервера. Тоже самое касается начала действия акций, срока существования корзины покупателя и т.д. Основная причина - это отсутствие временной зоны по умолчанию в файле php.ini. Исправить можно несколькими способами:
1) В файле php.ini найти секцию
 и прописать действующую временную зону, например, [b]date.timezone = Europe/Moscow

2) В файле .htaccess через переменные окружения задать SetEnv TZ Europe/Moscow
3) В начале индексных файлов OpenCart добавить строку date_default_timezone_set(" Europe/Moscow");
11. Показ скрытых цен для авторизованных пользователей OpenCart.

Если в магазине скрыты цены для гостей, то при редактировании карточек товаров не всегда удобно проверять страницу товара под видом рядового посетителя. Приходится проходить авторизацию под тестовой учетной записью покупателя или отключать соответствующую опцию в настройках магазина. Чтобы об этом не думать, достаточно в файле ./index.php после строки $config->set('config_language', $languages[$code]['code']); добавить следующий код:

if (empty($session->data['user_id']) == false) { $config->set('config_customer_price', false); }


12. Показ того или иного модуля на всех возможных страницах сайта без задания этих страниц по отдельности.

Есть модули, которые необходимо показывать на любой из существующих страниц сайта. Чтобы это сделать, необходимо сначала определить все эти страницы в справочнике Схемы (Система -> Дизайн -> Схемы), а затем задать их в настройках конкретного модуля. В каком-то смысле это не совсем удобно, можно упустить какой-нибудь из разделов сайта, а в памяти процесса появится много настроек (актуально для большого количества типовых модулей).

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

./catalog/controller/common/column_left.php
./catalog/controller/common/column_right.php
./catalog/controller/common/content_bottom.php
./catalog/controller/common/content_top.php


В этих файлах найдите запись $module['layout_id'] == $layout_id и замените на ($module['layout_id'] == $layout_id || $module['layout_id'] == $this->config->get('config_layout_id')). Теперь если для модуля выбрать одну из схем по умолчанию, которая задаётся в настройках магазина, то он будет показан при открытии любой страницы сайта.

Добавлено: 25 Февраля 2015 20:05:55 Добавил: Андрей Ковальчук