1
0
Fork 0
mirror of https://github.com/Oreolek/ifhub.club.git synced 2024-06-16 23:00:51 +03:00

Новый стандартный поиск без Sphinx

This commit is contained in:
Mzhelskiy Maxim 2014-04-29 13:08:15 +07:00
parent 0abce2371a
commit 1035a1dc37
10 changed files with 473 additions and 257 deletions

View file

@ -1,259 +1,157 @@
<?php
/*-------------------------------------------------------
*
* LiveStreet Engine Social Networking
* Copyright © 2008 Mzhelskiy Maxim
*
*--------------------------------------------------------
*
* Official site: www.livestreet.ru
* Contact e-mail: rus.engine@gmail.com
*
* GNU General Public License, version 2:
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
---------------------------------------------------------
*/
/**
* LiveStreet CMS
* Copyright © 2013 OOO "ЛС-СОФТ"
*
* ------------------------------------------------------
*
* Official site: www.livestreetcms.com
* Contact e-mail: office@livestreetcms.com
*
* GNU General Public License, version 2:
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* ------------------------------------------------------
*
* @link http://www.livestreetcms.com
* @copyright 2014 OOO "ЛС-СОФТ"
* @author Maxim Mzhelskiy <rus.engine@gmail.com>
*
*/
/**
* Экшен обработки поиска по сайту через поисковый движок Sphinx
*
* @package actions
* @since 1.0
* Обработка основного поиска
*/
class ActionSearch extends Action {
/**
* Допустимые типы поиска с параметрами
*
* @var array
*/
protected $sTypesEnabled = array('topics' => array('topic_publish' => 1), 'comments' => array('comment_delete' => 0));
/**
* Массив результата от Сфинкса
*
* @var null|array
*/
protected $aSphinxRes = null;
/**
* Поиск вернул результат или нет
*
* @var bool
*/
protected $bIsResults = FALSE;
/**
* Инициализация
*/
public function Init() {
$this->SetDefaultEvent('index');
$this->Viewer_AddHtmlTitle($this->Lang_Get('search'));
$this->Viewer_AddHtmlTitle($this->Lang_Get('search.search'));
}
/**
* Регистрация евентов
*/
protected function RegisterEvent() {
$this->AddEvent('index','EventIndex');
$this->AddEvent('topics','EventTopics');
$this->AddEvent('comments','EventComments');
$this->AddEventPreg('/^topics$/i','/^(page([1-9]\d{0,5}))?$/i','EventTopics');
$this->AddEventPreg('/^comments$/i','/^(page([1-9]\d{0,5}))?$/i','EventComments');
$this->AddEvent('opensearch','EventOpenSearch');
}
/**
* Отображение формы поиска
* Главная страница поиска
*/
function EventIndex(){
protected function EventIndex() {
$this->SetTemplateAction('index');
}
/**
* Обработка стандарта для браузеров Open Search
*/
function EventOpenSearch(){
Router::SetIsShowStats(false);
$this->Viewer_Assign('sAdminMail', Config::Get('sys.mail.from_email'));
header('Content-type: text/xml; charset=utf-8');
}
/**
* Поиск топиков
*
* Обработка поиска топиков
*/
function EventTopics(){
/**
* Ищем
*/
$aReq = $this->PrepareRequest();
$aRes = $this->PrepareResults($aReq, Config::Get('module.topic.per_page'));
if(FALSE === $aRes) {
$this->Message_AddErrorSingle($this->Lang_Get('system_error'));
return Router::Action('error');
}
/**
* Если поиск дал результаты
*/
if($this->bIsResults){
/**
* Получаем топик-объекты по списку идентификаторов
*/
$aTopics = $this->Topic_GetTopicsAdditionalData(array_keys($this->aSphinxRes['matches']));
/**
* Конфигурируем парсер jevix
*/
$this->Text_LoadJevixConfig('search');
/**
* Делаем сниппеты
*/
foreach($aTopics AS $oTopic){
/**
* Т.к. текст в сниппетах небольшой, то можно прогнать через парсер
*/
$oTopic->setTextShort($this->Text_JevixParser($this->Sphinx_GetSnippet(
$oTopic->getText(),
'topics',
$aReq['q'],
'<span class="searched-item">',
'</span>'
)));
}
/**
* Отправляем данные в шаблон
*/
$this->Viewer_Assign('bIsResults', TRUE);
$this->Viewer_Assign('aRes', $aRes);
$this->Viewer_Assign('aTopics', $aTopics);
}
}
/**
* Поиск комментариев
*
*/
function EventComments(){
/**
* Ищем
*/
$aReq = $this->PrepareRequest();
$aRes = $this->PrepareResults($aReq, Config::Get('module.comment.per_page'));
if(FALSE === $aRes) {
$this->Message_AddErrorSingle($this->Lang_Get('system_error'));
return Router::Action('error');
}
/**
* Если поиск дал результаты
*/
if($this->bIsResults){
/**
* Получаем топик-объекты по списку идентификаторов
*/
$aComments = $this->Comment_GetCommentsAdditionalData(array_keys($this->aSphinxRes['matches']));
/**
* Конфигурируем парсер jevix
*/
$this->Text_LoadJevixConfig('search');
/**
* Делаем сниппеты
*/
foreach($aComments AS $oComment){
$oComment->setText($this->Text_JevixParser($this->Sphinx_GetSnippet(
htmlspecialchars($oComment->getText()),
'comments',
$aReq['q'],
'<span class="searched-item">',
'</span>'
)));
}
/**
* Отправляем данные в шаблон
*/
$this->Viewer_Assign('aRes', $aRes);
$this->Viewer_Assign('aComments', $aComments);
}
}
/**
* Подготовка запроса на поиск
*
* @return array
*/
private function PrepareRequest(){
$aReq['q'] = getRequestStr('q');
if (!func_check($aReq['q'],'text', 2, 255)) {
/**
* Если запрос слишком короткий перенаправляем на начальную страницу поиска
* Хотя тут лучше показывать юзеру в чем он виноват
*/
Router::Location(Router::GetPath('search'));
}
$aReq['sType'] = strtolower(Router::GetActionEvent());
/**
* Определяем текущую страницу вывода результата
*/
$aReq['iPage'] = intval(preg_replace('#^page([1-9]\d{0,5})$#', '\1', $this->getParam(0)));
if(!$aReq['iPage']) { $aReq['iPage'] = 1; }
/**
* Передача данных в шаблонизатор
*/
$this->Viewer_Assign('aReq', $aReq);
return $aReq;
}
/**
* Поиск и формирование результата
*
* @param array $aReq
* @param int $iLimit
* @return array|bool
*/
protected function PrepareResults($aReq, $iLimit){
/**
* Количество результатов по типам
*/
foreach($this->sTypesEnabled as $sType => $aExtra){
$aRes['aCounts'][$sType] = intval($this->Sphinx_GetNumResultsByType($aReq['q'], $sType, $aExtra));
}
if($aRes['aCounts'][$aReq['sType']] == 0){
/**
* Объектов необходимого типа не найдено
*/
unset($this->sTypesEnabled[$aReq['sType']]);
/**
* Проверяем отсальные типы
*/
foreach(array_keys($this->sTypesEnabled) as $sType){
if($aRes['aCounts'][$sType])
Router::Location(Router::GetPath('search').$sType.'/?q='.$aReq['q']);
}
} elseif(($aReq['iPage']-1)*$iLimit <= $aRes['aCounts'][$aReq['sType']]) {
/**
* Ищем
*/
$this->aSphinxRes = $this->Sphinx_FindContent(
$aReq['q'],
$aReq['sType'],
($aReq['iPage']-1)*$iLimit,
$iLimit,
$this->sTypesEnabled[$aReq['sType']]
);
/**
* Возможно демон Сфинкса не доступен
*/
if (FALSE === $this->aSphinxRes) {
return FALSE;
}
$this->bIsResults = TRUE;
/**
* Формируем постраничный вывод
*/
$aPaging = $this->Viewer_MakePaging(
$aRes['aCounts'][$aReq['sType']],
$aReq['iPage'],
$iLimit,
Config::Get('pagination.pages.count'),
Router::GetPath('search').$aReq['sType'],
array(
'q' => $aReq['q']
)
);
$this->Viewer_Assign('aPaging', $aPaging);
}
protected function EventTopics() {
$this->SetTemplateAction('index');
$this->Viewer_AddHtmlTitle($aReq['q']);
$this->Viewer_Assign('bIsResults', $this->bIsResults);
return $aRes;
$sSearchType=$this->sCurrentEvent;
$iPage=$this->GetParamEventMatch(0,2) ? $this->GetParamEventMatch(0,2) : 1;
/**
* Получаем список слов для поиска
*/
$aWords=$this->Search_GetWordsForSearch(getRequestStr('q'));
if (!$aWords) {
$this->Message_AddErrorSingle($this->Lang_Get('search.alerts.query_incorrect'));
return;
}
$sQuery=join(' ',$aWords);
/**
* Формируем регулярное выражение для поиска
*/
$sRegexp=$this->Search_GetRegexpForWords($aWords);
/**
* Выполняем поиск
*/
$aResult=$this->Search_SearchTopics($sRegexp,$iPage,Config::Get('module.topic.per_page'));
$aResultItems=$aResult['collection'];
/**
* Конфигурируем парсер jevix
*/
$this->Text_LoadJevixConfig('search');
/**
* Делаем сниппеты
*/
foreach($aResultItems AS $oItem){
/**
* Т.к. текст в сниппетах небольшой, то можно прогнать через парсер
*/
$oItem->setTextShort($this->Text_JevixParser($this->Search_BuildExcerpts($oItem->getTextSource(),$aWords)));
}
/**
* Формируем постраничность
*/
$aPaging=$this->Viewer_MakePaging($aResult['count'],$iPage,Config::Get('module.topic.per_page'),Config::Get('pagination.pages.count'),Router::GetPath('search').$sSearchType,array('q'=>$sQuery));
/**
* Загружаем переменные в шаблон
*/
$this->Viewer_Assign('aResultItems',$aResultItems);
$this->Viewer_Assign('aPaging',$aPaging);
$this->Viewer_Assign('sSearchType',$sSearchType);
$this->Viewer_Assign('sQuery',$sQuery);
$this->Viewer_Assign('aTypeCounts',array($sSearchType=>$aResult['count']));
}
/**
* Обработка поиска комментариев
*/
protected function EventComments() {
$this->SetTemplateAction('index');
$sSearchType=$this->sCurrentEvent;
$iPage=$this->GetParamEventMatch(0,2) ? $this->GetParamEventMatch(0,2) : 1;
/**
* Получаем список слов для поиска
*/
$aWords=$this->Search_GetWordsForSearch(getRequestStr('q'));
if (!$aWords) {
$this->Message_AddErrorSingle($this->Lang_Get('search.alerts.query_incorrect'));
return;
}
$sQuery=join(' ',$aWords);
/**
* Формируем регулярное выражение для поиска
*/
$sRegexp=$this->Search_GetRegexpForWords($aWords);
/**
* Выполняем поиск
*/
$aResult=$this->Search_SearchComments($sRegexp,$iPage,4,'topic');
$aResultItems=$aResult['collection'];
/**
* Конфигурируем парсер jevix
*/
$this->Text_LoadJevixConfig('search');
/**
* Делаем сниппеты
*/
foreach($aResultItems AS $oItem){
/**
* Т.к. текст в сниппетах небольшой, то можно прогнать через парсер
*/
$oItem->setText($this->Text_JevixParser($this->Search_BuildExcerpts($oItem->getText(),$aWords)));
}
/**
* Формируем постраничность
*/
$aPaging=$this->Viewer_MakePaging($aResult['count'],$iPage,4,Config::Get('pagination.pages.count'),Router::GetPath('search').$sSearchType,array('q'=>$sQuery));
/**
* Загружаем переменные в шаблон
*/
$this->Viewer_Assign('aResultItems',$aResultItems);
$this->Viewer_Assign('aPaging',$aPaging);
$this->Viewer_Assign('sSearchType',$sSearchType);
$this->Viewer_Assign('sQuery',$sQuery);
$this->Viewer_Assign('aTypeCounts',array($sSearchType=>$aResult['count']));
}
}
?>

View file

@ -0,0 +1,228 @@
<?php
/**
* LiveStreet CMS
* Copyright © 2013 OOO "ЛС-СОФТ"
*
* ------------------------------------------------------
*
* Official site: www.livestreetcms.com
* Contact e-mail: office@livestreetcms.com
*
* GNU General Public License, version 2:
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* ------------------------------------------------------
*
* @link http://www.livestreetcms.com
* @copyright 2014 OOO "ЛС-СОФТ"
* @author Maxim Mzhelskiy <rus.engine@gmail.com>
*
*/
/**
* Модуль поиска
*
* @package modules.search
* @since 2.0
*/
class ModuleSearch extends Module {
protected $oMapper;
/**
* Инициализация модуля
*/
public function Init() {
$this->oMapper=Engine::GetMapper(__CLASS__);
}
/**
* Выполняет поиск топиков по регулярному выражению
*
* @param $sRegexp
* @param $iCurrPage
* @param $iPerPage
*
* @return array
*/
public function SearchTopics($sRegexp,$iCurrPage,$iPerPage) {
$sCacheKey="search_topics_{$sRegexp}_{$iCurrPage}_{$iPerPage}";
if (false===($data=$this->Cache_Get($sCacheKey))) {
$data=array(
'collection'=> $this->oMapper->SearchTopics($sRegexp,$iCount,$iCurrPage,$iPerPage),
'count'=> $iCount
);
$this->Cache_Set($data,$sCacheKey,array('topic_update','topic_new'),60*60*24*1);
}
if ($data['collection']) {
$data['collection']=$this->Topic_GetTopicsAdditionalData($data['collection']);
}
return $data;
}
/**
* Выполняет поиск комментариев по регулярному выражению
*
* @param $sRegexp
* @param $iCurrPage
* @param $iPerPage
* @param $sTargetType
*
* @return array
*/
public function SearchComments($sRegexp,$iCurrPage,$iPerPage,$sTargetType) {
$sCacheKey="search_comments_{$sRegexp}_{$iCurrPage}_{$iPerPage}_".serialize($sTargetType);
if (false===($data=$this->Cache_Get($sCacheKey))) {
$data=array(
'collection'=> $this->oMapper->SearchComments($sRegexp,$iCount,$iCurrPage,$iPerPage,$sTargetType),
'count'=> $iCount
);
$this->Cache_Set($data,$sCacheKey,array('comment_new'),60*60*24*1);
}
if ($data['collection']) {
$data['collection']=$this->Comment_GetCommentsAdditionalData($data['collection']);
}
return $data;
}
/**
* Выделяет отрывки из текста с необходимыми словами (делает сниппеты)
*
* @param string $sText Исходный текст
* @param array|string $aWords Список слов
* @param array $aParams Список параметром
*
* @return string
*/
public function BuildExcerpts($sText,$aWords,$aParams=array()) {
$iMaxLengthBetweenWords=isset($aParams['iMaxLengthBetweenWords']) ? $aParams['iMaxLengthBetweenWords'] : 200;
$iLengthIndentSection=isset($aParams['iLengthIndentSection']) ? $aParams['iLengthIndentSection'] : 100;
$iMaxCountSections=isset($aParams['iMaxCountSections']) ? $aParams['iMaxCountSections'] : 3;
$sWordWrapBegin=isset($aParams['sWordWrapBegin']) ? $aParams['sWordWrapBegin'] : '<span class="searched-item">';
$sWordWrapEnd=isset($aParams['sWordWrapEnd']) ? $aParams['sWordWrapEnd'] : '</span>';
$sGlueSections=isset($aParams['sGlueSections']) ? $aParams['sGlueSections'] : "\r\n";
$sText=strip_tags($sText);
$sText=trim($sText);
if (is_string($aWords)) {
$aWords=preg_split('#[\W]+#u',$aWords);
}
$sPregWords=join('|',array_filter($aWords,'preg_quote'));
$aSections=array();
if (preg_match_all("#{$sPregWords}#i",$sText,$aMatchAll,PREG_OFFSET_CAPTURE)) {
$aSectionItems=array();
$iCountDiff=-1;
foreach($aMatchAll[0] as $aMatch) {
if ($iCountDiff==-1 or $aMatch[1]-$iCountDiff<=$iMaxLengthBetweenWords) {
$aSectionItems[]=$aMatch;
$iCountDiff=$aMatch[1];
} else {
$aSections[]=array('items'=>$aSectionItems);
$aSectionItems=array();
$aSectionItems[]=$aMatch;
$iCountDiff=$aMatch[1];
}
}
if (count($aSectionItems)) {
$aSections[]=array('items'=>$aSectionItems);
}
}
$aSections=array_slice($aSections,0,$iMaxCountSections);
$sTextResult='';
if ($aSections) {
foreach($aSections as $aSection) {
/**
* Расчитываем дополнительные данные: начало и конец фрагмента, уникальный список слов
*/
$aItem=reset($aSection['items']);
$aSection['begin']=$aItem[1];
$aItem=end($aSection['items']);
$aSection['end']=$aItem[1]+mb_strlen($aItem[0],'utf-8');
$aSection['words']=array();
foreach($aSection['items'] as $aItem) {
$sKey=mb_strtolower($aItem[0],'utf-8');
$aSection['words'][$sKey]=$aItem[0];
}
/**
* Формируем фрагменты текста
*/
/**
* Определям правую границу текста по слову
*/
$iEnd=$aSection['end'];
for($i=$iEnd;($i<=$aSection['end']+$iLengthIndentSection) and $i<mb_strlen($sText,'utf-8');$i++) {
if (preg_match('#^\s$#',mb_substr($sText,$i,1,'utf-8'))) {
$iEnd=$i;
}
}
/**
* Определям левую границу текста по слову
*/
$iBegin=$aSection['begin'];
for($i=$iBegin;($i>=$aSection['begin']-$iLengthIndentSection) and $i>=0;$i--) {
if (preg_match('#^\s$#',mb_substr($sText,$i,1,'utf-8'))) {
$iBegin=$i;
}
}
/**
* Вырезаем фрагмент текста
*/
$sTextSection=trim(mb_substr($sText,$iBegin,$iEnd-$iBegin,'utf-8'));
if ($iBegin>0) {
$sTextSection='...'.$sTextSection;
}
if ($iEnd<mb_strlen($sText,'utf-8')) {
$sTextSection.='...';
}
$sTextSection=preg_replace("#{$sPregWords}#i",$sWordWrapBegin.'\\0'.$sWordWrapEnd,$sTextSection);
$sTextResult.=$sTextSection.$sGlueSections;
}
} else {
$iLength=$iMaxLengthBetweenWords*2;
if ($iLength>mb_strlen($sText,'utf-8')) {
$iLength=mb_strlen($sText,'utf-8');
}
$sTextResult=trim(mb_substr($sText,0,$iLength-1,'utf-8'));
}
return $sTextResult;
}
/**
* Возвращает массив слов из поискового запроса
*
* @param $sQuery
*
* @return array
*/
public function GetWordsForSearch($sQuery) {
/**
* Удаляем запрещенные символы
*/
$sQuery=preg_replace('#[^\w\sа-я\-]+#iu',' ',$sQuery);
/**
* Разбиваем фразу на слова
*/
$aWords=preg_split('#[\s]+#u',$sQuery);
foreach($aWords as $k=>$sWord) {
/**
* Короткие слова удаляем
*/
if (mb_strlen($sWord,'utf-8')<3) {
unset($aWords[$k]);
}
}
return $aWords;
}
/**
* Возвращает регулярное выражение для поиска в БД по словам
*
* @param $aWords
*
* @return string
*/
public function GetRegexpForWords($aWords) {
return join('|',$aWords);
}
}

View file

@ -0,0 +1,72 @@
<?php
/**
* LiveStreet CMS
* Copyright © 2013 OOO "ЛС-СОФТ"
*
* ------------------------------------------------------
*
* Official site: www.livestreetcms.com
* Contact e-mail: office@livestreetcms.com
*
* GNU General Public License, version 2:
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* ------------------------------------------------------
*
* @link http://www.livestreetcms.com
* @copyright 2014 OOO "ЛС-СОФТ"
* @author Maxim Mzhelskiy <rus.engine@gmail.com>
*
*/
/**
* Маппер для работы с БД
*
* @package modules.search
* @since 2.0
*/
class ModuleSearch_MapperSearch extends Mapper {
public function SearchTopics($sRegexp,&$iCount,$iCurrPage,$iPerPage) {
$sql="SELECT
DISTINCT t.topic_id,
CASE WHEN (LOWER(t.topic_title) REGEXP ?) THEN 1 ELSE 0 END +
CASE WHEN (LOWER(tc.topic_text_source) REGEXP ?) THEN 1 ELSE 0 END AS weight
FROM ".Config::Get('db.table.topic')." AS t
INNER JOIN ".Config::Get('db.table.topic_content')." AS tc ON tc.topic_id=t.topic_id
WHERE
(t.topic_publish=1) AND ((LOWER(t.topic_title) REGEXP ?) OR (LOWER(tc.topic_text_source) REGEXP ?))
ORDER BY
weight DESC, t.topic_id DESC
LIMIT ?d, ?d";
$aResult=array();
if ($aRows=$this->oDb->selectPage($iCount,$sql,$sRegexp,$sRegexp,$sRegexp,$sRegexp,($iCurrPage-1)*$iPerPage,$iPerPage)) {
foreach($aRows as $aRow) {
$aResult[]=$aRow['topic_id'];
}
}
return $aResult;
}
public function SearchComments($sRegexp,&$iCount,$iCurrPage,$iPerPage,$sTargetType) {
if (!is_array($sTargetType)) {
$sTargetType=array($sTargetType);
}
$sql="SELECT
DISTINCT c.comment_id
FROM ".Config::Get('db.table.comment')." AS c
WHERE
(c.comment_delete=0 AND c.target_type IN ( ?a ) ) AND (LOWER(c.comment_text) REGEXP ?)
ORDER BY
c.comment_id DESC
LIMIT ?d, ?d";
$aResult=array();
if ($aRows=$this->oDb->selectPage($iCount,$sql,$sTargetType,$sRegexp,($iCurrPage-1)*$iPerPage,$iPerPage)) {
foreach($aRows as $aRow) {
$aResult[]=$aRow['comment_id'];
}
}
return $aResult;
}
}

View file

@ -95,10 +95,14 @@ return array(
'search' => array(
'search' => 'Поиск',
'find' => 'Найти',
'result' => array(
'topics'=>'Топики',
'comments'=>'Комментарии',
),
// Сообщения
'alerts' => array(
'empty' => 'Поиск не дал результатов',
'query_incorrect' => 'Поисковый запрос должен быть от 3-х символов',
),
),
@ -499,7 +503,7 @@ return array(
),
/**
*
*
*/
'user_list_add' => array(
// Форма добавления
@ -578,15 +582,6 @@ return array(
*/
'blog_no_topic' => 'Сюда еще никто не успел написать',
/**
* Поиск
*/
'search_submit' => 'Найти',
'search_results' => 'Результаты поиска',
'search_results_empty' => 'Удивительно, но поиск не дал результатов',
'search_results_count_topics' => 'топиков',
'search_results_count_comments' => 'комментариев',
/**
* Declensions

View file

@ -13,19 +13,38 @@
{/block}
{block name='layout_content'}
{include 'forms/form.search.main.tpl'}
{include 'forms/form.search.main.tpl'}
{if $bIsResults}
{include 'navs/nav.search.tpl'}
{if $aResultItems}
<ul class="nav nav-pills">
<li {if $sSearchType == 'topics'}class="active"{/if}>
<a href="{router page='search/topics'}?q={$_aRequest.q}">
{$aLang.search.result.topics}
{if $aTypeCounts.topics}
({$aTypeCounts.topics})
{/if}
</a>
</li>
<li {if $sSearchType == 'comments'}class="active"{/if}>
<a href="{router page='search/comments'}?q={$_aRequest.q}">
{$aLang.search.result.comments}
{if $aTypeCounts.comments}
({$aTypeCounts.comments})
{/if}
</a>
</li>
{hook run='search_result_item' sType=$sSearchType aTypeCounts=$aTypeCounts}
</ul>
{if $aReq.sType == 'topics'}
{include file='topics/topic_list.tpl'}
{elseif $aReq.sType == 'comments'}
{include file='comments/comment_list.tpl'}
{if $sSearchType == 'topics'}
{include file='topics/topic_list.tpl' aTopics=$aResultItems}
{elseif $sSearchType == 'comments'}
{include file='comments/comment_list.tpl' aComments=$aResultItems}
{else}
{hook run='search_result' sType=$aReq.sType}
{hook run='search_result' sType=$sSearchType}
{/if}
{elseif $aReq.q}
{include 'components/alert/alert.tpl' mAlerts=$aLang.search_results_empty aMods='empty'}
{elseif $_aRequest.q}
{include 'components/alert/alert.tpl' mAlerts=$aLang.search.alerts.empty aMods='empty'}
{/if}
{/block}

View file

@ -1,10 +1,10 @@
<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">
<ShortName>{cfg name='view.name'}</ShortName>
<Description>{$sHtmlTitle}</Description>
<Contact>{$sAdminMail}</Contact>
<Url type="text/html" template="{router page='search'}topics/?q={literal}{searchTerms}{/literal}" />
<LongName>{$sHtmlDescription}</LongName>
<Image height="64" width="64" type="image/png">{cfg name='path.skin.assets.web'}/images/favicons/logo.png</Image>
<Contact>{Config::Get('sys.mail.from_email')}</Contact>
<Url type="text/html" template="{router page='search/topics'}?q={literal}{searchTerms}{/literal}" />
<LongName>{$sHtmlDescription}</LongName>
<Image height="64" width="64" type="image/png">{cfg name='path.skin.assets.web'}/images/favicons/opensearch.png</Image>
<Image height="16" width="16" type="image/vnd.microsoft.icon">{cfg name='path.skin.assets.web'}/images/favicons/favicon.ico</Image>
<Developer>{cfg name='view.name'} ({cfg name='path.root.web'})</Developer>
<Attribution>

View file

@ -94,3 +94,4 @@
.user.inactive { color: #aaa; cursor: help; }
.hide { display: none; }
.searched-item { background: #fff999; border-bottom: 1px dotted #999; }

View file

@ -6,4 +6,6 @@
{foreach $aComments as $oComment}
{include file='comments/comment.tpl' bList=true}
{/foreach}
{/foreach}
{include 'components/pagination/pagination.tpl' aPaging=$aPaging}

View file

@ -7,7 +7,8 @@
{extends file='forms/form.search.base.tpl'}
{* Форма *}
{block name='search_action'}{router page='search'}topics/{/block}
{block name='search_action'}{router page='search'}{if $sSearchType}{$sSearchType}{else}topics{/if}/{/block}
{block name='search_input_value'}{$_aRequest.q}{/block}
{* Хуки *}
{block name='search_before'}{hook run='search_begin'}{/block}

@ -1 +1 @@
Subproject commit ca9d2968f88d2ce934d5d810682e726149353358
Subproject commit 56cad71e524dd4a043cae42efbb37d591efc3f4e