Буквально вчера писал про организацию поиска на scomedy.com, и вот решил все-таки осветить техническую реализацию. Понятное дело, что Друпал, и все делается методичным нажатием на далее-далее, но может кому пригодится.
В примере по ссылке выше поиск идет по цитатам, которые представляют из себя ни что иное как страничку, созданную во Views. Соответственно и поиск надо прикрутить к этой самой Views.
Тайтлы страниц результатов поиска
Зайдем издалека. Мы хотим, чтобы тайтлы страниц результатов поиска не дублировали основной тайтл, а создавались динамически.
Другими словами, если тайтл самой вьюс:
Stand-Up Comedians' Quotes & Jokes
То страница с результатами поиска по запросу, скажем, "stuff", должна иметь тайтл вида:
Stand-Up Comedians' Quotes & Jokes [Search Results for "stuff"]
Что вполне логично.
Для этого качаем и устанавливаем модуль Page Title.
После его активации нам надо создать новое представление Views, точную копию существующего (по которому мы хотим организовать поиск), но с той лишь разницей, что тип создаваемого представления надо выбрать не Page, а Page (with Page Title). Создавать новую вьюшку придется ручками, что очень утомительно при большом количестве настроек, но по-другому у меня не получилось. После этого деактивируем старую Page (или удаляем) и работаем уже исключительно с новой.
В блоке настроек Views PAGE SETTINGS должно быть поле Page Title Pattern. Это как раз то, что нам нужно. Кликаем на него и редактируем.
Для себя я указал следующий шаблон:
Stand-Up Comedians' Quotes & Jokes [Search Results for "[current-page:query:keys]"] | SComedy
Где keys - это имя GET параметра, содержащего поисковую фразу (т. е. quotes?keys=stuff) и может быть любым на ваше усмотрение.
Тайтл переписывается полностью, поэтому если в конце хотите видеть слоган - вписывайте его вручную.
ВАЖНО
На момент написания поста, модуль Page Title имеет несколько багов (версия модуля 7.x-2.7).
Первый критический. При обращении к странице нашей Views без использования поиска (т. е. в нашем случае просто к /quotes), в тайтл пойдет следующее:
Stand-Up Comedians' Quotes & Jokes [Search Results for "[current-page:query:keys]"]
Да-да, именно так, выводится системная информация (сам шаблон для замены). А нам нужно, чтобы при отсутствии поиска использовался дефолтный тайтл, указанный в блоке TITLE нашей Views. Ничего не поделаешь, надо патчить.
Нужный патч лежит здесь. Как там ниже отписались, The patch in #8 worked like a charm for me, thanks! Подписываюсь под отзывом.
Второй баг не настолько критичный, просто неприятный. При пустом поиске (т. е. не /quotes, а /quotes?keys=) тайтл выдается таким:
Stand-Up Comedians' Quotes & Jokes [Search Results for ""]
Хотя правильней было бы в этом случае тоже выдавать дефолтное значение тайтла.
Вообще, что делать с пустыми запросами вида page?search= вопрос таки сложный. Каждый поступает, как хочет. На вскидку я вижу четыре варианта:
1. Ничего не делать.
Так поступает deviantart.com/?q=, например. Что с ?q=, что без, выдается один и тот же контент, один и тот же тайтл, никаких ошибок.
В принципе, изначально по этому пути я и хотел пойти. Да так оно у нас и есть, за исключением разве что некрасивого тайтла. Проблема решаема. И лежит, кстати, не в модуле Page Title, а в модуле Token, который нам и надо будет пропатчить. Собственно, поправить надо всего-лишь одну строчку в файле token.tokens.inc, заменив isset на !empty.
Т. е. этот код (начиная с 696-ой строчки):
// [current-page:query] dynamic tokens.
if ($query_tokens = token_find_with_prefix($tokens, 'query')) {
foreach ($query_tokens as $name => $original) {
// @todo Should this use filter_input()?
if (isset($_GET[$name])) {
$replacements[$original] = $sanitize ? check_plain($_GET[$name]) : $_GET[$name];
}
}
}
заменить на этот:
// [current-page:query] dynamic tokens.
if ($query_tokens = token_find_with_prefix($tokens, 'query')) {
foreach ($query_tokens as $name => $original) {
// @todo Should this use filter_input()?
if (!empty($_GET[$name])) {
$replacements[$original] = $sanitize ? check_plain($_GET[$name]) : $_GET[$name];
}
}
}
Вполне себе решение. Теперь пустой поисковый запрос будет считаться отсутствием запроса как такового с откатом тайтла до дефолтного, что и требовалось. Но я решил пойти по другому пути, об этом ниже.
2. Можно при пустом запросе выдавать на весь экран поисковую строчку, как сделано здесь stackoverflow.com/search?q=. Но в моем случае это не подходит, т. к. поисковая строка у меня находится в фиксированном месте и уже на видном месте. Была бы где-нибудь в сайдбаре, то сошло бы за вариант, а так нет.
3. При пустом поисковом запросе выдавать подсказку, что дескать что-то надо ввести, как сделали тут imdb.com/find?q=&s=all. Как реализовать на Друпале этот и предыдущий варианты, не подскажу, не разбирался, т. к. пошел по последнему пути.
4. При пустом поисковом запросе редиректить пользователя на главную страницу, как это делает ютуб youtube.com/results?search_query=. Я же редиректить буду не на главную сайта, а на главную раздела, по которому производится поиск, другими словами на саму Views, т. е. со страницы /quotes?keys= на страницу /quotes.
Здесь бы идеально было использовать какой-нибудь модуль редиректинга, но, увы, тот же Redirect считает страницу с параметрами и без (/quotes и /qutoes?keys) за одну и ту же, и, следовательно, не дает создать редирект. Ок, будем делать сами, хотя это и хак своего рода.
Добавим к нашей вьюс HEADER или FOOTER типа Global: Text area, не забыв выставить текстовый формат в PHP code и поставить галочку напротив Display even if view has no result. Осталось ввести только следующий код:
Это для случая, когда GET параметр имеет имя keys и у нас нет других параметров. Если есть - то читаем документацию по drupal_goto(), какие опции и как передавать.
Редирект у меня, как можно заметить, 303, который See Other. Как и у Ютуба, собственно. Он говорит о том, что при запросе к quotes?keys= результаты есть, но их надо смотреть по другому адресу (/quotes). Подходит как нельзя лучше тут. 301-ый это все-таки переезд, а я никуда не переезжал этой страницей.
Полнотекстовый поиск с построением индекса
Непосредственно сам поиск. Здесь уже будет сильно проще.
Заходим в раздел модулей, активируем входящий в ядро модуль Search, переходим к его настройке, указываем нужные параметры, заказываем индексацию контента, запускаем несколько раз крон (индексация контента происходит исключительно по крону, за раз индексируется строго определенное кол-во материалов).
После этого переходим к нашей Views и добавляем там новый FILTER CRITERIA, который называется Search: Search Terms. Ставим галочку напротив Expose this filter to visitors, to allow them to change it, остальное по желанию. Я, например, также отключил показ Label и вывожу строку Show quotes and jokes about: над формой поиска с помощью добавления HEADER'а. Не забываем также в разделе MORE указать Filter identifier, если требуется что-то, отличное от дефолта (keys).
Все! Поиск работает. Форма показывается над контентом, но при желании ее можно вынести и в отдельный блок (EXPOSED FORM, Exposed form in block: yes).
ВАЖНО
Модуль Search строит индекс не по полям, а по всей ноде сразу. Т. е. возьмем вот такую ноду:
В ней есть поля:
- Body - непосредственно сама цитата.
- Comedian - комик, кому принадлежит цитата.
- Title - генерируется автоматически из Comedian и Body.
- Rate - рейтинг.
И если мы заглянем в БД в табличку search_dataset, то увидим следующую картину:
bill hicks i m a heavy smoker i go through two lighters a day i m a heavy smoker i go through two lighters a day 84 reads bill hicks select rating give it 110 give it 210 give it 310 give it 410 give it 510 give it 610 give it 710 give it 810 give it 910 give it 1010 average 75 2 votes
И вот по этому и происходит поиск. Т. е. присутствует куча мусора, хотя искать хочется только по полю Body.
Плюс ко всему, модуль Search не работает с морфологией, по "book" и "books" выдача получается разная.
Если все это является существенными минусами, то стоит отказаться от стандартного модуля Search в пользу чего-нибудь более мощного, дабы вариантов масса. Там, если покопаться, есть и модули для работы с базовой морфологией при использовании стандартного поиска, и модули для поиска по отдельным полям, и подключение всяких Apache Solr, чего только нет. Но со всем этим надо разбираться и смотреть, какой именно вариант будет оптимальным в нашем конкретном случае. Я пока оставил это на потом, ограничившись стандартным поиском.
Поиск без построения индекса
Вполне возможно, что индекс и не нужен. Если материалов не десятки и сотни тысяч, они короткие (поиск не по статьям, а, к примеру, по заголовкам материалов), нет особых требований к релевантности и точности поиска, то можно воспользоваться банальным LIKE('%key%').
Никаких модулей в этом случае подключать не придется. Плюс ко всему, искать можно по любому полю.
Заходим в настройки нашей Views, добавляем в FILTER CRITERIA нужное нам поле, например Content: Title. В настройках этого поля ставим галочку напротив Expose this filter to visitors, to allow them to change it и в качестве оператора указываем Contains (который таки банальный LIKE). Имя GET параметра, содержащего строку запроса, находится в разделе MORE (Filter identifier) и по умолчанию совпадает с системным именем поля.
После этих несложных действий на нашей страничке появится форма поиска. Именно по этому пути я пошел для организации поиска по комикам:
Ищется по именам, самих комиков несколько сотен, в перспективе не больше нескольких тысяч. Индекс не нужен.
Да, из-за LIKE'а можно искать по запросам вида "rg", результатом чего будет вывод таких комиков, как George Carlin и Mitch Hedberg. Но в данном случае это не смертельно, как по мне.
Косметические работы
Все работает, можно заняться теперь и прихорашиванием всего и вся.
Кастомизация кнопок
В настройке нашей Views переходим в EXPOSED FORM/Exposed form style:/Settings, указываем надписи для кнопок и добавляем кнопку Reset, если требуется.
ВАЖНО
Данная проблема возникает только при организации поиска с использованием модуля Search. В его настройках мы указали (или оставили значение по умолчанию) минимально допустимое кол-во символов для поиска (т. е. разрешать искать по словам только длиннее 3-х букв, например). Плюс нельзя искать по незначащим словам ("and" и т. д.). В этих случаях будет выпадать такая вот ошибка:
И да, бага. Кнопка Reset не срабатывает. Проблему уже озвучили, но пока тишина.
Я решил это самописным редиректом:
Код вставляется аналогичным способом, как и в примере с редиректом выше по тексту.
Вывод количества найденных материалов
Необходимая же функция! Делается просто - в раздел HEADER нашей Views надо добавить поле Global: Result summary и вывести на отображение нужную информацию с использованием предоставляемых токенов.
Сообщение об отсутствии результатов
Все просто, добавляем в раздел NO RESULTS BEHAVIOR абсолютно любую информацию. Я выбрал поле Global: Text area, чтобы иметь возможность включить PHP фильтр, куда вставил следующий код:
$path = request_uri();
if (stripos($path, 'quotes?keys')) {
$suggestions = array(
'marriage', 'job', 'guns', 'alcohol', 'hell',
'lie', 'women', 'phone', 'people', 'difference',
'life', 'time', 'movie', 'music', 'idea',
'home', 'dad', 'money', 'bank', 'attitude',
'family', 'drunk', 'power', 'history', 'hate',
'meal', 'stuff', 'public', 'school', 'holidays',
'experience', 'goal', 'society', 'logic', 'reason',
'friend', 'date', 'democracy', 'fool', 'mind',
'theory', 'heaven', 'believe', 'help', 'car',
'dog', 'truth', 'question', 'art', 'age'
);
shuffle($suggestions);
echo <<
< p="">
<>
Sorry, no quotes found.
Try to search for something else (e.g., quotes about
HTML;
} else {
$keys = addslashes(substr($path, strpos($path, '?keys=') + 6));
echo <<
< p="">
<>
Sorry, no quotes found for this comedian.
Try to search it in all quotes instead.
HTML;
}
?>
Таким образом, при поиске с дополнительным фильтром на автора цитаты при отсутствии результатов поиска предлагается поискать во всех цитатах (Try to search it in all quotes instead., где "all quotes" - ссылка на результаты поиска по всем цитатам).
А при отсутствии результатов поиска по всем цитатам предлагается поискать по другим ключевым словам (Try to search for something else (e.g., quotes about experience, help or history)., где ключевые слова являются ссылками на соответствующие результаты поиска, и каждый раз показываются три случайных запроса из пятидесяти ранее подобранных).
Но можно ограничиться, конечно, и базовым No results found., здесь уже больше от личных предпочтений зависит. В конкретно моем случае я посчитал это явно недостаточным.
Подсказка по поиску
Хотел сделать примерно вот так (вопросик кликабелен):
Search?:
Чтобы воспользоваться поиском, вы должны воспользоваться поиском.
СВЕРНУТЬ
font-style: italic;
color: #6FC0CC;
font-size: 14px;
}
sup.search-help-toggle:hover {
color: #3377A0;
cursor: pointer;
}
#search-help {
display: none;
margin-bottom: 10px;
padding-top: 10px;
color: #6C6C6C;
background: #F9F9F9;
border-radius: 5px;
}
#search-help p {
padding: 0
}
#search-help-text {
padding: 10px;
}
#search-help span.search-help-toggle {
display: block;
height: 28px;
text-align: center;
font-size: 14px;
line-height: 28px;
border-radius: 0 0 5px 5px;
background: #E9E9E9;
}
#search-help span.search-help-toggle:hover {
background: #D7EDF2;
cursor: pointer;
}
Но потом что-то передумал. Делается просто и, так-то, это уже к Друпалу мало имеет отношения. Просто добавьте в HEADER поле Global: Text area с PHP фильтром (или HTML Full) примерно следующего содержания:
Search?:
Чтобы воспользоваться поиском, вы должны воспользоваться поиском.
СВЕРНУТЬ
font-style: italic;
color: #6FC0CC;
font-size: 14px;
}
sup.search-help-toggle:hover {
color: #3377A0;
cursor: pointer;
}
#search-help {
display: none;
margin-bottom: 10px;
padding-top: 10px;
color: #6C6C6C;
background: #F9F9F9;
border-radius: 5px;
}
#search-help p {
padding: 0
}
#search-help-text {
padding: 10px;
}
#search-help span.search-help-toggle {
display: block;
height: 28px;
text-align: center;
font-size: 14px;
line-height: 28px;
border-radius: 0 0 5px 5px;
background: #E9E9E9;
}
#search-help span.search-help-toggle:hover {
background: #D7EDF2;
cursor: pointer;
}
Вот таким вот незамысловатым способом можно добавить к нашей Views функцию поиска. Понятное дело, что усовершенствовать все это можно до бесконечности (чем и планирую заняться, правда явно не сегодня и не завтра), но как базовый вариант на скорую руку и точка отсчета - вполне себе сойдет.