Поиск по tv-параметрам в CatalogView
В одном из проектов передо мной стояла задача сделать поиск по каталогу Shopkeeper, где к каждому из товаров присваивается множество различных tv-параметров. Товары выводятся с помощью сниппета CatalogView.
В чем собственно была проблема, в данном сниппете можно сортировать товары через параметр filter или выводить только необходимы через where. Но так как значение для каждого из tv-параметров хранится в отдельной ячейке таблицы, не получалось задавать сразу несколько условий поиска. Происходила сортировка по логическому «ИЛИ», но не удавалось сделать по логическому «И». Это в общем и понятно, получалось что даже если использовался объединяющий два условия AND, поиск обоих частей условия происходил в одной ячейке таблицы базы данных.
Выход был найден такой – были написаны подзапросы к базе данных, результаты которых группируются в отдельную временную таблицу, и из этой таблицы выбираются необходимые данные. В случае если поиск идет по 2 tv, то выбираются товары, встречающиеся в данной таблице более чем 1 раз, если по 2 tv, то более 2 раз и так далее. Почему именно так? Получается, что при поиске по параметрам находятся ID товаров, которым эти tv с необходимыми значениями присвоены. При группировке двух и более результатов подзапросов в таблицу попадают все товары с tv из первого подзапроса и из последующих. Но нам-то нужно, что бы все условия выполнялись, поэтому мы и выбираем только те товары, которые встречаются во временной таблице такое же количество раз, сколько и было подзапросов минус один.
А теперь код.
В том месте, где будет находиться поиск, вставляется форма, например такая:
<form action="ID_текущей_страницы" method="get">
<select name="tip_bluda">
<option value="">Тип блюда</option>
<option value="5">Супы</option>
<option value="16">Салаты</option>
<option value="6">Мясные блюда</option>
</select>
<select name="vid_kuhni">
<option value="">Вид кухни</option>
<option value="32">Русская</option>
<option value="35">Грузинская</option>
</select>
<select name="slojnost">
<option value="">Сложность</option>
<option value="1">Элементарно</option>
<option value="2">Легко</option>
<option value="3">Требует времени</option>
</select>
<input type="submit" value="Найти " />
</form>
Обязательно устанавливается method="get" – он нужен, что бы передавать выбранные значения. Name в select`те соответствует имени tv-параметра. Value в option значению, записанному в базе данных. На данном сайте в большинство tv параметров записываются ID страниц, к которым принадлежат товары, это нужно для сортировки. Если значения в базе хранятся на русском языке, то необходимо в сниппете будет присваивать им численные значения, из-за того, что значения на русском языке некорректно работают.
После формы пишем вызов сниппета: [ !filter! ]
И создаем сам спиппет с именем filter:
<?php
$output = '';
$filter = array();
$sl = 0;
if($_GET['slojnost'] == 1) $sl = 'Элементарно';
if($_GET['slojnost'] == 2) $sl = 'Легко';
if($_GET['slojnost'] == 3) $sl = 'Требует времени';
if($_GET['slojnost'] == 4) $sl = 'Нужно постараться ';
if($_GET['slojnost'] == 5) $sl = ‘Шедевр кулинарного искусства';
if(isset($_GET['tip_bluda']) && !is_array($_GET['tip_bluda']) && $_GET['tip_bluda'] != 0) $filter[] = '(SELECT contentid FROM modx_catalog_tmplvar_contentvalues WHERE value = "__'.$_GET['tip_bluda'].'")';
if(isset($_GET['vid_kuhni']) && !is_array($_GET['vid_kuhni']) && $_GET['vid_kuhni'] != 0) $filter[] = '(SELECT contentid FROM modx_catalog_tmplvar_contentvalues WHERE value = "__'.$_GET['vid_kuhni'].'")';
if(isset($_GET['slojnost']) && !is_array($_GET['slojnost']) && $_GET['slojnost'] != 0) $filter[] = '(SELECT contentid FROM modx_catalog_tmplvar_contentvalues WHERE value like "'.$sl.'")';
$params['parents'] = 'ID ресусрсов';
$params['tpl'] = 'имя чанка краткого описания товара';
$params['descTpl'] = 'имя чанка подробного описания товара';
$params['sqlFilter'] = '1';
$params['sortDir'] = 'desc';
$params['display'] = '20';
$params['noResult'] = '<p>Рецептов, соответствующих условиям поиска нет. Попробуйте задать более мягкие условия.</p>';
$filt = implode(' UNION ALL ',$filter);
$n = count($filter);
if ($n==0) $params['where'] = '';
if ($n==1) $params['where'] = "sc.id IN (SELECT contentid FROM ($filt) AS vremennaia GROUP BY contentid)";
if ($n>1) $params['where'] = "sc.id IN (SELECT contentid FROM ($filt) AS vremennaia GROUP BY contentid HAVING count(contentid)>($n-1))";
$output = $modx->runSnippet('catalogView',$params);
return $output;
?>
Теперь поподробнее о самом сниппете. В самом начале создается массив с параметрами вызова catalogView. Далее идет пример перевода текстовых значений в числовые. Назначается параметр, и при соотвествие одному из условий ему присваивается значение, по которому уже идет выборка из базы данных.
Теперь идет самая важная часть – если была передана переменная с помощью формы, то формируется подзапрос к базе данных. Здесь необходимо дописать все переменные, используемые в форме поиска. $_GET['tip_bluda'] – в скобках пишется имя tv параметра, которое совпадает с name select`а. В последнем условие, вместо выбора по tv параметру, происходит выбор по переменной, присвоенной нами вначале сниппета.
$params['parents'] = 'ID ресусрсов'; - перечисляются дополнительные параметры сниппета catalogView. В скобках название параметра, в кавычках напротив знака равно его значение.
$filt = implode(' UNION ALL ',$filter); - этой строкой все подзапросы объединяются в единую временную таблицу.
$n = count($filter); - подсчитывается количество подзапросов к базе данных.
if ($n==0) $params['where'] – определяется, сколько условий поиска было заданно, и формируется окончательный вызов сниппета catalogView. Если запросов более 1, то выбираются из временной таблицы значения, встречающиеся $n-1 раз.
Вот, в общем, и всё. Возможно, это не очень рационально, или некорректно. Но оно работает. Посмотрим, как будет себя вести при большом количестве товаров. Надеюсь, кому-нибудь поможет.
