mirror of
https://github.com/Oreolek/ifhub.club.git
synced 2024-06-16 23:00:51 +03:00
Новый стандартный поиск без Sphinx
This commit is contained in:
parent
0abce2371a
commit
1035a1dc37
|
@ -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']));
|
||||
}
|
||||
}
|
||||
?>
|
228
application/classes/modules/search/Search.class.php
Normal file
228
application/classes/modules/search/Search.class.php
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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}
|
|
@ -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>
|
||||
|
|
|
@ -94,3 +94,4 @@
|
|||
.user.inactive { color: #aaa; cursor: help; }
|
||||
|
||||
.hide { display: none; }
|
||||
.searched-item { background: #fff999; border-bottom: 1px dotted #999; }
|
|
@ -6,4 +6,6 @@
|
|||
|
||||
{foreach $aComments as $oComment}
|
||||
{include file='comments/comment.tpl' bList=true}
|
||||
{/foreach}
|
||||
{/foreach}
|
||||
|
||||
{include 'components/pagination/pagination.tpl' aPaging=$aPaging}
|
|
@ -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
|
Loading…
Reference in a new issue