AjaxMegaSearch — живой поиск по сайту, по выбранным полям (заготовка)

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

По наводке Dmi3yy на третий метод ajax в MODX от bumkaka, сразу получилось простое решение поиска, которое можно расширить под свои требования.

Итак, по порядку.

Создаёте плагин AJAX на событие OnPageNotFound с кодом
switch($_GET['q']){     
	case 'ajaxmegasearch':
	echo $modx->runSnippet('ajaxMegaSearch');
	die();
	break;
}

как видно по коду, плагин выводит содержимое сниппета ajaxMegaSearch.

Дальше создаём сниппет ajaxMegaSearch с кодом
<?php
$out = '';
$text = isset($_POST['text']) ? $_POST['text'] : ''; // текст запроса
$fields = isset($_POST['fields']) ? $_POST['fields'] : 'pagetitle'; // поля для поиска через запятую - pagetitle,longtitle
$length = isset($_POST['text']) ? strlen($_POST['text']) : 0; // количество символов в запросе
$minlength = isset($_POST['minlength']) ? (int) $_POST['minlength'] : 3; // минимальное количество в запросе
$maxlength = isset($_POST['maxlength']) ? (int) $_POST['maxlength'] : 50; // максимальное количество в запросе
$limit = isset($_POST['limit']) ? (int) $_POST['limit'] : 50; // количество выводимых результатов поиска
$empty = '<span>ничего не нашлось</span>'; // вывод пустого ответа поиска
$template = 'template=4 AND'; // шаблон документов для поиска // 4 - можно поменять на свой шаблон либо весь $template удалить.
$like = addcslashes($text, '\%_');
$like = $modx->db->escape($like);
$like = '%' . $like . '%';

$fields = explode(',', $fields);
$fields_array = explode(',', 'pagetitle,longtitle,description,alias,introtext,content'); // разрешённые поля через запятую
$fields = array_intersect($fields_array, $fields);

$fields_where = array();
foreach($fields as $v) {
	$fields_where[] = $v . " LIKE '" . $like . "'";
}
$fields_where = implode(' OR ', $fields_where);

if($minlength <= $length && $maxlength >= $length) {
	$res = $modx->db->query("
			SELECT * FROM modx_site_content 
			WHERE " . $template . " (" . $fields_where . ")
			ORDER BY pagetitle ASC
			LIMIT " . $limit . "
		");
		foreach($modx->db->makeArray($res) as $doc) {
			if(!$modx->config['use_alias_path']) {
				$url =  $modx->config['friendly_url_prefix'] . $doc['alias'] . $modx->config['friendly_url_suffix'];
			} else {
				$url =  $modx->makeUrl($doc['id']);
			}
			$out .= '<a href="' . $url . '">' . str_ireplace($text, '<b>'.$text.'</b>', $doc['pagetitle']) . '</a>';
		}
		$out = $out ? $out : $empty;
	}

return $out;

Обработка запросов готова!

Далее, ставим поле ввода и вывода результата поиска на странице сайта.
<input type="text" id="search" name="search" value="" placeholder="поиск по сайту">
<div id="search-result"><div class="search-inner"></div></div>

и далее добавляем код js в теге HEAD на странице
<script type="text/javascript">
$(document).ready(function() {
	function LiveAjaxMegaSearch(as) {
		if (as['minlength'] > as['text'].length) {
			$('#search-result .search-inner').empty();
			$('#search-result').hide();
			return false;
		}

		$.ajax({
			url: 'ajaxmegasearch',
			type: 'post',
			data: {
				text: as['text'],
				minlength: as['minlength'],
				fields: as['fields']
			},
			success: function(data) {
				if (data) {
					$('#search-result .search-inner').html(data);
					$('#search-result').show();
				}
			}
		});

	}
	var as = [];
	as['minlength'] = 3;
	as['fields'] = 'pagetitle,longtitle';
	as['text'] = $('input#search').val();
	$('#search').keyup(function() {
		as['text'] = $(this).val();
		LiveAjaxMegaSearch(as);
	});
});
</script>

Потом уже меняете под себя css для поля ввода поиска и показа результатов.
Вот собственно и всё))
И так всё выглядит

54 комментария

комментарий был удален
комментарий был удален
комментарий был удален
комментарий был удален
комментарий был удален
комментарий был удален
комментарий был удален
avatar
Доброго дня, очень классно работает. А как можно добавить несколько шаблонов для поиска?
avatar
то есть, несколько шаблонов?
расскажите поподробнее…
avatar
извиняюсь, что не понятно написал
имел ввиду про это
$template = 'template=4 AND'; // шаблон документов для поиска // 4 - можно поменять на свой шаблон либо весь $template удалить.
avatar

$template = 'template IN(1,2,3,4) AND';
avatar
SQL-injection. Нет фильтрации входящих полей по которым будет идти поиск. Кратко:
$fields = isset($_POST['fields']) ? $_POST['fields'] : 'pagetitle';
$fields = explode(',', $fields);
$fields_where = array();
foreach($fields as $v) {
        $fields_where[] = $v . " LIKE '" . $like . "'";
}
$fields_where = implode(' OR ', $fields_where);
avatar
согласен проверять нужно.
в конечном варианте выглядит так

...
$fields = isset($_POST['fields']) ? $_POST['fields'] : 'pagetitle';
$fields = explode(',', $fields);
$rel_fields = array(
	'pagetitle',
	'longtitle',
	'content',
	'description',
	'link_attributes',
);
$where = array();
$where2 = array();
foreach($fields as $v) {
	if(in_array($v, $rel_fields)) {
		foreach($likes as $l) {
			if(strlen($l) >= $minlength && $maxlength >= strlen($l)) {
				$where[$l] = $v . " LIKE '%" . $l . "%'";
			}
		}
		$where2[] = implode(' OR ', $where);
	}
}

// собираем условие запроса
$where = '(' . implode(') OR (', $where2) . ')';
...
avatar
заменил, и поиск перестал работать. Вы можете внести изменения в шапку, тоесть как выглядет в конечном варианте сниппет ajaxMegaSearch
avatar
изменения вы можете внести в свою версию сниппета самостоятельно.
Agel_Nash, прав, нужно сделать проверку полей от инъекции в БД.

Ниже на основе заготовки для поиска, вполне рабочий скрипт

<?php
$out = '';
$text = isset($_POST['text']) ? preg_replace('/\s\s+/', ' ', strip_tags(trim($_POST['text']))) : '';
$fields = isset($_POST['fields']) ? $_POST['fields'] : 'pagetitle';
$length = isset($_POST['text']) ? strlen($_POST['text']) : 0;
$minlength = isset($_POST['minlength']) ? (int) $_POST['minlength'] : 3;
$maxlength = isset($_POST['maxlength']) ? (int) $_POST['maxlength'] : 50;
$limit = isset($_POST['limit']) ? (int) $_POST['limit'] : 50;
$like = addcslashes($text, '\%_');
$like = $modx->db->escape($like);
$likes = explode(' ', $like);
$empty = '<span>ничего не нашлось</span>';
$template = 'template=4 AND';
$rel_fields = array(
	'pagetitle',
	'longtitle',
	'content',
	'description',
	'link_attributes',
);

$fields = explode(',', $fields);
$where = array();
$where2 = array();
foreach($fields as $v) {
	if(in_array($v, $rel_fields)) {
		foreach($likes as $l) {
			if(strlen($l) >= $minlength && $maxlength >= strlen($l)) {
				$where[$l] = $v . " LIKE '%" . $l . "%'";
			}
		}
		$where2[] = implode(' OR ', $where);
	}
}

$where = '(' . implode(') OR (', $where2) . ')';

if($minlength <= $length && $maxlength >= $length) {
	$res = $modx->db->query("
			SELECT alias, pagetitle, longtitle FROM modx_site_content 
			WHERE " . $template . " (" . $where . ")
			ORDER BY hits DESC
			LIMIT " . $limit . "
		");
	foreach($modx->db->makeArray($res) as $doc) {
		$out .= '<a href="' . $doc['alias'] . $modx->config['friendly_url_suffix'] . '">';
		$out .= preg_replace("/(" . implode('|', $likes) . ")/iu", "<b>\\1</b>", $doc['pagetitle'] . ($doc['longtitle'] ? ' — ' . $doc['longtitle'] : ''));
		$out .= '</a>';
	}
	$out = $out ? $out : $empty;
}

return $out;
?>
avatar
в строке $fields = isset($_POST['fields'])? $_POST['fields']: 'pagetitle'; // поля для поиска через запятую — pagetitle,longtitle

ставлю через пробел $fields = isset($_POST['fields'])? $_POST['fields']: 'introtext,pagetitle,longtitle,content';
и поиск все равно не ищет по introtext
Даже если использовать только поле introtext все равно не работает.
Я интротекст использую для слоформ по котором могут находить товар


сниппет (код который сообщений выше отдает 500)
<?php
$out = '';
$text = isset($_POST['text'])? $_POST['text']: ''; // текст запроса
$fields = isset($_POST['fields'])? $_POST['fields']: 'pagetitle'; // поля для поиска через запятую — pagetitle,longtitle
$length = isset($_POST['text'])? strlen($_POST['text']): 0; // количество символов в запросе
$minlength = isset($_POST['minlength'])? (int) $_POST['minlength']: 3; // минимальное количество в запросе
$maxlength = isset($_POST['maxlength'])? (int) $_POST['maxlength']: 50; // максимальное количество в запросе
$limit = isset($_POST['limit'])? (int) $_POST['limit']: 50; // количество выводимых результатов поиска
$empty = 'ничего не нашлось'; // вывод пустого ответа поиска
$template = 'template=4 AND'; // шаблон документов для поиска // 4 — можно поменять на свой шаблон либо весь $template удалить.
$like = addcslashes($text, '\%_');
$like = $modx->db->escape($like);
$like = '%'. $like. '%';

$fields = explode(',', $fields);
$fields_array = explode(',', 'pagetitle,longtitle,description,alias,introtext,content'); // разрешённые поля через запятую
$fields = array_intersect($fields_array, $fields);

$fields_where = array();
foreach($fields as $v) {
$fields_where[] = $v. " LIKE '". $like. "'";
}
$fields_where = implode(' OR ', $fields_where);

if($minlength <= $length && $maxlength >= $length) {
$res = $modx->db->query("
SELECT * FROM modx_site_content
WHERE ". $template. " (". $fields_where. ")
ORDER BY pagetitle ASC
LIMIT ". $limit. "
");
foreach($modx->db->makeArray($res) as $doc) {
if(!$modx->config['use_alias_path']) {
$url = $modx->config['friendly_url_prefix']. $doc['alias']. $modx->config['friendly_url_suffix'];
} else {
$url = $modx->makeUrl($doc['id']);
}
$out .= ''. str_ireplace($text, ''.$text.'', $doc['pagetitle']). '';
}
$out = $out? $out: $empty;
}

return $out;
Комментарий отредактирован 2015-12-22 07:26:08 пользователем d-fm1301
avatar
у вас шаблон для поиска $template = 'template=4 AND';
поставьте пустое значение
$template = '';
avatar
Сделал вот так
<?php
$out = '';
$text = isset($_POST['text'])? $_POST['text']: ''; // текст запроса
$fields = isset($_POST['fields'])? $_POST['fields']: 'pagetitle,longtitle,description,alias,introtext,content'; // поля для поиска через запятую — pagetitle,longtitle
$length = isset($_POST['text'])? strlen($_POST['text']): 0; // количество символов в запросе
$minlength = isset($_POST['minlength'])? (int) $_POST['minlength']: 3; // минимальное количество в запросе
$maxlength = isset($_POST['maxlength'])? (int) $_POST['maxlength']: 50; // максимальное количество в запросе
$limit = isset($_POST['limit'])? (int) $_POST['limit']: 50; // количество выводимых результатов поиска
$empty = 'ничего не нашлось'; // вывод пустого ответа поиска
$template = ''; // шаблон документов для поиска // 4 — можно поменять на свой шаблон либо весь $template удалить.
$like = addcslashes($text, '\%_');
$like = $modx->db->escape($like);
$like = '%'. $like. '%';

$fields = explode(',', $fields);
$fields_array = explode(',', 'pagetitle,longtitle,description,alias,introtext,content'); // разрешённые поля через запятую
$fields = array_intersect($fields_array, $fields);

$fields_where = array();
foreach($fields as $v) {
$fields_where[] = $v. " LIKE '". $like. "'";
}
$fields_where = implode(' OR ', $fields_where);

if($minlength <= $length && $maxlength >= $length) {
$res = $modx->db->query("
SELECT * FROM modx_site_content
WHERE ". $template. " (". $fields_where. ")
ORDER BY pagetitle ASC
LIMIT ". $limit. "
");
foreach($modx->db->makeArray($res) as $doc) {
if(!$modx->config['use_alias_path']) {
$url = $modx->config['friendly_url_prefix']. $doc['alias']. $modx->config['friendly_url_suffix'];
} else {
$url = $modx->makeUrl($doc['id']);
}
$out .= ''. str_ireplace($text, ''.$text.'', $doc['pagetitle']). '';
}
$out = $out? $out: $empty;
}

return $out;

и все равно не ищет по интротексту
avatar
обратите внимание, что вы через пост передаёте
as['fields'] = 'pagetitle,longtitle';

и оформите пожалуйста код, из-за двух кавычек, необязательно копировать всё.
avatar
извините конечно я еще не совсем оптыный в modx и пхп.

$fields = isset($_POST['fields'])? $_POST['fields']: 'pagetitle,longtitle,description,alias,introtext,content'; // поля для поиска через запятую — pagetitle,longtitle

Подскажите пжл как должна выглядить эта страка
и что означает «обратите внимание, что вы через пост передаёте „
avatar
вы передаёте через POST значения
as['fields'] = 'pagetitle,longtitle';

дальше на PHP проверяются данные, если не передали, то используются по умолчанию «pagetitle,longtitle,description,alias,introtext,content»…
вы же передаёте только два поля 'pagetitle,longtitle', поэтому и не ищет по 'description'
$fields = isset($_POST['fields'])? $_POST['fields']: 'pagetitle,longtitle,description,alias,introtext,content';

это проверка
avatar
Я то как раз и пишу что у меня стоит
$fields = isset($_POST['fields'])? $_POST['fields']: 'pagetitle,longtitle,description,alias,introtext,content';

ну даже как вы говорите используется по умолчанию «pagetitle,longtitle,description,alias,introtext,content»
но я сейчас попробовал вставил в description и introtext, ключевые слова по котором должны находить товары все равно не находит а если вставить в pagetitle или longtitle все работает.

Пример titan.megaplan-kazan.ru/ru/catalog/
в поиске должен находится товар по vf800, vf 800, вф800, вф 800, ма800, ма 800 но поиск работает если я эти данные вставлю в pagetitle и longtitle
Комментарий отредактирован 2015-12-22 09:44:18 пользователем d-fm1301
avatar
попробуйте заменить строчку
$fields = isset($_POST['fields'])? $_POST['fields']: 'pagetitle,longtitle,description,alias,introtext,content';

на эту
$fields = 'pagetitle,longtitle,description,introtext';
avatar
Да все заработало, огромное спасибо!
avatar
Спасибо за отличное решение, давно такое искал.
У меня вопрос, а можно ли прикрутить tv параметр с изображением товара, чтобы в выпадающем списке был pagetitle и image?
avatar
хм… в принципе можно и не сложно, нужно изменить запрос.

...
LEFT JOIN modx_site_tmplvar_contentvalues AS tv ON tv.contentid=c.id
WHERE .... tv.tmplvarid="ID - тв с картинкой"

и изменить вывод…
покажите php код который взяли?
avatar
Для его случая наверно будет лучше не where, а and

ON tv.contentid=c.id AND tv.tmplvarid="ID - тв с картинкой" WHERE ...


Чтоб не только ресурсы с картинкой туда попали :)
avatar
ну в любом случае придётся весь запрос переписать))
avatar
Я вот так сделал, просто дернул изображение по id ресурса
$docid = $doc['id']; 
$tv_img = $modx->getTemplateVar('tovar_img', '*', $docid); 
$img = $tv_img['value'];

А дальше в шаблоне выводи картинку
<img alt="" src="' . $img . '"></span>
Комментарий отредактирован 2016-01-07 09:38:08 пользователем proud
avatar
Первый код — это сниппет?
avatar
getTemplateVar делает два запроса, итого 1 запрос для поиска и два запроса на каждый результат)
100 результатов = 200 запросов для вывода картинки
avatar
Наверное это многовато. Решения, как полностью переписать запрос, нет?
avatar
Спасибо 64j и proud за помощь, решил остановиться на варианте предложеным proud , работает на Ура.
avatar


То самое выражение лица клиента, когда ему вместо «подумать и сделать 1 запрос» сделали «200 запросов» и назвали это «решение работает на Ура» :)) Отличный аватар, у вас :))
avatar
Очень рад, что вам понравился аватар :-) Пусть 200, но это работает. Я понимаю, что 1 лучше, но пока нет этого одного и ещё рабочего.
Завтра проверю вариант Agel_Nash
avatar
Ну я поделился тем, как делал я на быструю руку)
Действительно лучше все по уму переделать, так что мой вариант не панацея, а всего-лишь пример.
avatar
<?php
if(!is_array($modx->event->params)) $modx->event->params = array();
	
$text = isset($_POST['text']) && is_scalar($_POST['text']) ? $_POST['text'] : ''; // текст запроса
$text = preg_replace('/[ \t]+/', ' ', $text); //Заменяем множественную последовательсноть пробелов и табуляции на 1 пробел
$text = preg_replace('/[^a-zа-я0-9-\s]+/isu','',$text); //Оставляем только символы русского и английского алфавита, тере, цифры и пробел

$fields = isset($_POST['fields']) ? $_POST['fields'] : 'pagetitle'; // поля для поиска через запятую - pagetitle,longtitle
$fields = explode(',', $fields);
$fields_array = explode(',', 'pagetitle,longtitle,description,alias,introtext,content'); // разрешённые поля через запятую
$fields = array_intersect($fields_array, $fields);

$limit = isset($_POST['limit']) ? (int) $_POST['limit'] : 50; // количество выводимых результатов поиска

if(!empty($text)) {
	$filters = array();
	foreach($fields as $field){
		$filters[] = 'content:'.$field.':containsOne:'.$text;
	}
}
$noneTPL = isset($modx->event->params['noneTPL']) ? $modx->event->params['noneTPL'] : '@CODE: <span>ничего не нашлось</span>';
if(!empty($filters)){
	$filters = (!empty($modx->event->params['filters'])) ? rtrim(trim($modx->event->params['filters']), ";") . ";" : "";
	
	$out = $modx->runSnippet('DocLister', array_merge(array(
		'idType' => 'documents',
		'documents' => '',
		'ignoreEmpty' => 1,
		'addWhereList' => 'template=4',
		'noneTPL' => $noneTPL,
	), $modx->event->params, array(
		'filter_delimiter' => ' ',
		'filters' => "AND(".$filters.implode(":", $filters).")",
	)));
}else{
	include_once(MODX_BASE_PATH."assets/snippets/DocLister/snippet.DLTemplate.php");
	$out = DLTemplate::getInstance($modx)->parseChunk($noneTPL, array());
}
return $out;

Не тестировал. Но должно работать. Вызываем как обычно
$modx->runSnippet('ajaxMegaSearch');
вот только теперь можно дополнительно передавать параметры для DocLister'a. Как-то так:
$modx->runSnippet('ajaxMegaSearch', array(
    'addWhereList' => "template IN (4,5)",
    'parents' => '10',
    'idType' => 'parents',
    'tpl' => '@CODE: <a href="[+url+]" title="[+e.title+]"><img src="[+tv.img+] alt="[+e.title+]" /></a>',
    'tvList' => 'img',
    'filters' => 'AND(tv:rate:>=:10)'
));
avatar
Блин, все сделал как в описании, но в скриптах выдает ошибку
jquery-latest.min.js:4 POST http://runo3.888x.by/ajaxmegasearch 500 (Internal Server Error)
. В чем может быть дело?
Комментарий отредактирован 2016-05-15 16:26:34 пользователем aid13_92
avatar
значит, что то сделали не так
avatar
А что если нужно прикрутить на сайт с мультиязычностью)
avatar
значит нужно расширить запрос поиска
avatar
Ладно с мультиязычностью разобрался а как сделать чтобы по артикулу тоже искало?
avatar
можно указать свои поля для поиска
$fields = isset($_POST['fields']) ? $_POST['fields'] : 'pagetitle'; // поля для поиска через запятую - pagetitle,longtitle
avatar
Добрый день.
Пытаюсь запустить ваг поиск но при попытки что-то найти выдает
Fatal error: Call to a member function escape() on null in
ругается на данную строчку
$like = $modx->$db->escape($like);

В чем может быть проблема?
Комментарий отредактирован 2017-11-21 16:46:25 пользователем SaKrAt91
avatar
вы похоже сами уже что то добавили…
опечатка у вас
$like = $modx->db->escape($like);
avatar
Да в том что написал ранее и правда была правка, но если оригинал вставляю все равно эта же ошибка.
avatar
Fatal error: Call to a member function escape() on null in

таже ошибка?
avatar
Fatal error: Call to a member function escape() on null in /.../core/cache/includes/elements/modsnippet/67.include.cache.php on line 10
Ошибка вылезает в место где приходит ответ ajax, после введения 3-х символов
avatar
смотрите внимательнее, я не могу угадать, что вы там изменили.
avatar
Единственное что сделал не так как у вас, это то что скрипт вставил не в HEAD а перед поскольку у библиотеки подгружаются в конце самом, вот после них и вставил скрипт.
avatar
Может глупый вопрос, но на какую версию modx этот поиск?
avatar
работал на 1.0.16…
на 1.4 не проверял
Дата публикации посмотрите, 3 года назад))
avatar
Вот увидел и решил узнать) меня revo интересует) видимо промахнулся я малость)
Но огромное спасибо за то что откликнулись!
avatar
В Revo есть mSearch2 c живым поиском из коробки.
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.