18 апреля 2013 г.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

4 апреля 2013 г.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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