Bitrix24 как я переделывал модуль поиска
Стандартный модуль поиска bitrix search вполне хорош, но есть один недостаток, если вы используете его в решении Bitrix24 корпоративный портал. Дело в том что результаты поиска выдают личную переписку!
Итак, я начал копаться в коде этого модуля. Путем метода тыка нашел место запроса. Запрос не цельная строка, а куски динамического кода. Все происходит в методе MakeSQL() класса CSearch скрипта bitrix/modules/search/classes/mysql/search.php
Поля выборки задаются в массиве $arSelect
$arSelect = array(
"ID" => "sc.ID",
"MODULE_ID" => "sc.MODULE_ID",
"ITEM_ID" => "sc.ITEM_ID",
"TITLE" => "sc.TITLE",
"TAGS" => "sc.TAGS",
"BODY" => "sc.BODY",
"PARAM1" => "sc.PARAM1",
"PARAM2" => "sc.PARAM2",
"UPD" => "sc.UPD",
"DATE_FROM" => "sc.DATE_FROM",
"DATE_TO" => "sc.DATE_TO",
"URL" => "sc.URL",
"CUSTOM_RANK" => "sc.CUSTOM_RANK",
"FULL_DATE_CHANGE" => $DB->DateToCharFunction("sc.DATE_CHANGE")." as FULL_DATE_CHANGE",
"DATE_CHANGE" => $DB->DateToCharFunction("sc.DATE_CHANGE", "SHORT")." as DATE_CHANGE",
);
Блок FROM и ограничитель WHERE тут
$strSql = "
FROM b_search_content sc
".($this->Query->bText? "INNER JOIN b_search_content_text sct ON sct.SEARCH_CONTENT_ID = sc.ID": "")."
INNER JOIN b_search_content_site scsite ON sc.ID=scsite.SEARCH_CONTENT_ID
".(count($this->Query->m_stemmed_words) > 1?
"INNER JOIN (
select search_content_id, max(st.TF) TF, ".($bWordPos? "if(STDDEV(st.PS)-".$this->normdev(count($this->Query->m_stemmed_words))." between -0.000001 and 1, 1/STDDEV(st.PS), 0) + ": "")."sum(st.TF/sf.FREQ) as RANK
from b_search_content_stem st, b_search_content_freq sf
where st.language_id = '".$this->Query->m_lang."'
and st.stem = sf.stem
and sf.language_id = st.language_id
and st.stem in (".$strStemList.")
".($this->tf_hwm > 0? "and st.TF >= ".number_format($this->tf_hwm, 2, ".", ""): "")."
".(strlen($this->tf_hwm_site_id) > 0? "and sf.SITE_ID = '".$DB->ForSQL($this->tf_hwm_site_id, 2)."'": "and sf.SITE_ID IS NULL")."
group by st.search_content_id
having (".$query.")
) stt ON sc.id = stt.search_content_id"
:"INNER JOIN b_search_content_stem stt ON sc.id = stt.search_content_id"
)."
WHERE
".CSearch::CheckPermissions("sc.ID")."
".(count($this->Query->m_stemmed_words) > 1? "": "
and stt.language_id = '".$this->Query->m_lang."'
and stt.stem in (".$strStemList.")
".($this->tf_hwm > 0? "and stt.TF >= ".number_format($this->tf_hwm, 2, ".", ""): "")."")."
".$strSqlWhere."
";
В итоге получается следующая строка
return $strSelect."\n".$strSql.$strSort."\nLIMIT ".$limit;
// где
$strSelect = "SELECT ".($bDistinct? "DISTINCT": "")."\n".implode("\n,", $arSelect);
Проанализировав ситуацию, я пришел к выводу что сообщения с MODULE_ID = tasks, blog, forum паляться, их надо фильтровать. Все это делается отсечением и обратным присоединением отсеченного, уже отфильтрованного.
Отсечь легко
and sc.MODULE_ID != 'forum'
and sc.MODULE_ID != 'blog'
and sc.MODULE_ID != 'tasks'
, да и присоединить вроде как тож, с помощью UNION.
Но union должен быть практически идентичным первичному запросу, поля выборки, их количество.
Поступим хитро — заменим алиас sc (FROM b_search_content sc), не тронув сами выборки $arSelect и органичители $strSql
function replace_alias($a, $b, $str){
$pattern[0] = "/$a\s/";
$pattern[1] = "/$a\./";
$replace[0] = "$b\n";
$replace[1] = "$b.";
$new_str = preg_replace($pattern, $replace, $str);
return $new_str;
}
/* ------------------- f by user--------------------- */
$strSql2 = replace_alias("sc", "se", $strSql);
/* ------------------- f by blog--------------------- */
$strSql3 = replace_alias("sc", "sa", $strSql);
/* ------------------- f by tasks--------------------- */
$strSql4 = replace_alias("sc", "su", $strSql);
function replace_alias3($a, $b, $arSelect){
$arSelect2 = array();
foreach ($arSelect as $key => $value) {
$value2 = str_replace("{$a}.", "{$b}.", $value);
$arSelect2 = array_merge($arSelect2, array($key => $value2));
}
return "SELECT ".($bDistinct? "DISTINCT": "")."\n".implode("\n,", $arSelect2);
}
$strSelect2 = replace_alias3("sc", "se", $arSelect);
$strSelect3 = replace_alias3("sc", "sa", $arSelect);
$strSelect4 = replace_alias3("sc", "su", $arSelect);
Вот и все? Блок SELECT и блок FROM готовы, осталось написать ограничители WHERE. Модуль tasks выдает результаты из личных задач и задач группы, из модуля blog достаточно ограничить ‘POST’, ‘COMMENT’.
where
sa.MODULE_ID = 'blog'
and sa.PARAM1 in ('POST', 'COMMENT')
Из модуля forum — по $USER->GetId()
Итак, как отфильтровать данные из модуля tasks? Решил отсеивать по столбцу URL. Данные там выглядят примерно так:
Личные задачи:
/company/personal/user/1/tasks/task/view/1/
Задачи группы:
/workgroups/group/5/tasks/task/view/6/
В личных есть ИД самого текущего юзера, в группе — ИД группы. Проверим на соответствие $url[4] == $user_id и на принадлежность группе CSocNetGroup::CanUserReadGroup( $user_id, $group_id)
Написал фу-ю temp_table($task_query) к-я создает временную таблицу с ID тех записей, к-е разрешены для польз-ля. В параметре она получает запрос к таблице b_search_content где MODULE_ID = tasks. Результат запроса проверяем на соот-ие и совпавшие значения заносим во временную табл.
/**
* ====================================== temp_table
*/
function temp_table($task_query){
global $DB, $USER;
$res2 = array();
while($res = $task_query->Fetch()){
array_push($res2, $res);
}
/* %%%%%%%% ids %%%%%%%%%% */
$user_ids = array();
$group_ids = array();
$user_id = intval($USER->GetId());
foreach ($res2 as $key => $value) {
if (empty($value["URL"])) {
}else{
$url = explode("/", $value["URL"]);
}
if($url[1] == "company"){
if( $url[4] == $user_id ){
$user_ids[] = $value["ID"];
}
}elseif ($url[1] == "workgroups") {
foreach ($url[3] as $key => $value) {
if ( (CSocNetGroup::CanUserReadGroup( $user_id, $value)) ) {
$group_ids[] = $value["ID"];
}
}
}
}
$gr_and_usr = array_merge($user_ids, $group_ids);
if (empty($gr_and_usr)) {
}else{
$DB->Query("
create TEMPORARY TABLE `temp`(
`id` int(10) auto_increment primary key,
`gr_and_usr` VARCHAR(24)
)
");
foreach ($gr_and_usr as $key => $value) {
$DB->Query("
insert into `temp`(gr_and_usr) values('".$value."')
");
}
return $gr_and_usr;
}
}
Довольно хитро получилось, согласитесь.
$task_query2 = $DB->Query(
$strSelect."\n".$strSql
);
$gr_and_usr = temp_table($task_query2);
Итак, временная таблица есть, к-я содержит ИД нужных записей из основного запроса. Склеим ее перекрестным joinом.
INNER JOIN temp t ON su.ID = t.gr_and_usr
надо вставить до ограничителя. $strSql уже с WHERE.
Вот этот трюк с заменой алиаса оказывается бесполезым
/* ------------------- f by tasks--------------------- */
$strSql4 = replace_alias("sc", "su", $strSql);
Поэтому копируем $strSql и вставляем INNER JOIN, не забыв заменить алиас на su
/* ------------------- f by tasks2 --------------------- */
$strSql5 = "
FROM b_search_content su
".($this->Query->bText? "INNER JOIN b_search_content_text sct ON sct.SEARCH_CONTENT_ID = su.ID": "")."
INNER JOIN b_search_content_site scsite ON su.ID=scsite.SEARCH_CONTENT_ID
".(count($this->Query->m_stemmed_words) > 1?
"INNER JOIN (
select search_content_id, max(st.TF) TF, ".($bWordPos? "if(STDDEV(st.PS)-".$this->normdev(count($this->Query->m_stemmed_words))." between -0.000001 and 1, 1/STDDEV(st.PS), 0) + ": "")."sum(st.TF/sf.FREQ) as RANK
from b_search_content_stem st, b_search_content_freq sf
where st.language_id = '".$this->Query->m_lang."'
and st.stem = sf.stem
and sf.language_id = st.language_id
and st.stem in (".$strStemList.")
".($this->tf_hwm > 0? "and st.TF >= ".number_format($this->tf_hwm, 2, ".", ""): "")."
".(strlen($this->tf_hwm_site_id) > 0? "and sf.SITE_ID = '".$DB->ForSQL($this->tf_hwm_site_id, 2)."'": "and sf.SITE_ID IS NULL")."
group by st.search_content_id
having (".$query.")
) stt ON su.id = stt.search_content_id"
:"INNER JOIN b_search_content_stem stt ON su.id = stt.search_content_id"
)."
INNER JOIN temp t ON su.ID = t.gr_and_usr
WHERE
".CSearch::CheckPermissions("su.ID")."
".(count($this->Query->m_stemmed_words) > 1? "": "
and stt.language_id = '".$this->Query->m_lang."'
and stt.stem in (".$strStemList.")
".($this->tf_hwm > 0? "and stt.TF >= ".number_format($this->tf_hwm, 2, ".", ""): "")."")."
".$strSqlWhere."
";
Остальные реплейсы отлично работают.
Наконец финальный запрос
/* *************************************** */
return "
".$strSelect."\n".$strSql."\n"."and sc.MODULE_ID != 'forum' and sc.MODULE_ID != 'blog' and sc.MODULE_ID != 'tasks'
UNION"."\n".$strSelect2."\n".$strSql2."\n"."and se.USER_ID = ".intval($USER->GetId())." /* user */
UNION"."\n".$strSelect3."\n".$strSql3."\n"."and sa.MODULE_ID = 'blog' and sa.PARAM1 in ('POST', 'COMMENT')/* blog */
".(empty($gr_and_usr)? "" : "
UNION"."\n".$strSelect4."\n".$strSql5."\n"."and su.MODULE_ID = 'tasks' /* tasks */
")."
ORDER BY DATE_FORMAT(DATE_CHANGE,'%d.%m.%y') DESC, CUSTOM_RANK DESC, RANK DESC
\nLIMIT ".$limit."
";
}
На этом не все. Запрос поиска меняется когда я поменял систему лайков с Мне нравиться на Мне нравиться/Не нравиться. Срабатывает условие.
if($RATING_MAX != 0 || $RATING_MIN != 0)
Замена алиасов по-прежнему работает, и даже для задач.
Временная таблица формируется несколько иначе.
$task_query = $DB->Query("
".$strSelectOuter.", sc0.RANK +
if(rv.TOTAL_VALUE > 0, ".($RATING_MAX > 0? "rv.TOTAL_VALUE/".$RATING_MAX: "0").",
if(rv.TOTAL_VALUE < 0, ".($RATING_MIN < 0? "rv.TOTAL_VALUE/".abs($RATING_MIN): "0").", 0 )) SRANK ,".$DB->IsNull('rvv.VALUE', '0')." RATING_USER_VOTE_VALUE
,sc.ENTITY_TYPE_ID RATING_TYPE_ID
,sc.ENTITY_ID RATING_ENTITY_ID
,rv.TOTAL_VOTES RATING_TOTAL_VOTES
,rv.TOTAL_POSITIVE_VOTES RATING_TOTAL_POSITIVE_VOTES
,rv.TOTAL_NEGATIVE_VOTES RATING_TOTAL_NEGATIVE_VOTES
,rv.TOTAL_VALUE RATING_TOTAL_VALUE
FROM (
".$strSelectInner."
".$strSql.$strSort."\nLIMIT ".$limit."
) sc0
INNER JOIN b_search_content sc ON sc.ID = sc0.ID
LEFT JOIN b_rating_voting rv ON rv.ENTITY_TYPE_ID = sc.ENTITY_TYPE_ID AND rv.ENTITY_ID = sc.ENTITY_ID
LEFT JOIN b_rating_vote rvv ON rvv.ENTITY_TYPE_ID = sc.ENTITY_TYPE_ID AND rvv.ENTITY_ID = sc.ENTITY_ID AND rvv.USER_ID = ".intval($USER->GetId())."
where
sc.MODULE_ID = 'tasks'
");
$gr_and_usr = temp_table($task_query);
/* ------------------------------------------------------------------------рейтинг-------------------------------------------------------------- */
if($RATING_MAX != 0 || $RATING_MIN != 0)
{
function replace_alias2($a, $b, $str){
$pattern[0] = "/$a\s/";
$pattern[1] = "/$a\./";
$pattern[2] = "/$a(0)\./";
$pattern[3] = "/$a(0)\s/";
$replace[0] = "$b\n";
$replace[1] = "{$b}.";
$replace[2] = "{$b}0.";
$replace[3] = "{$b}0\n";
$new_str = preg_replace($pattern, $replace, $str);
return $new_str;
}
$sel1 = "
".$strSelectOuter.", sc0.RANK +
if(rv.TOTAL_VALUE > 0, ".($RATING_MAX > 0? "rv.TOTAL_VALUE/".$RATING_MAX: "0").",
if(rv.TOTAL_VALUE < 0, ".($RATING_MIN < 0? "rv.TOTAL_VALUE/".abs($RATING_MIN): "0").", 0 )) SRANK ,".$DB->IsNull('rvv.VALUE', '0')." RATING_USER_VOTE_VALUE
,sc.ENTITY_TYPE_ID RATING_TYPE_ID
,sc.ENTITY_ID RATING_ENTITY_ID
,rv.TOTAL_VOTES RATING_TOTAL_VOTES
,rv.TOTAL_POSITIVE_VOTES RATING_TOTAL_POSITIVE_VOTES
,rv.TOTAL_NEGATIVE_VOTES RATING_TOTAL_NEGATIVE_VOTES
,rv.TOTAL_VALUE RATING_TOTAL_VALUE
FROM (
".$strSelectInner."
".$strSql.$strSort."\nLIMIT ".$limit."
) sc0
INNER JOIN b_search_content sc ON sc.ID = sc0.ID
LEFT JOIN b_rating_voting rv ON rv.ENTITY_TYPE_ID = sc.ENTITY_TYPE_ID AND rv.ENTITY_ID = sc.ENTITY_ID
LEFT JOIN b_rating_vote rvv ON rvv.ENTITY_TYPE_ID = sc.ENTITY_TYPE_ID AND rvv.ENTITY_ID = sc.ENTITY_ID AND rvv.USER_ID = ".intval($USER->GetId())."
";
$sel2 = replace_alias2("sc", "se", $sel1);
$sel3 = replace_alias2("sc", "sa", $sel1);
$sel4 = replace_alias2("sc", "su", $sel1);
return "
".$sel1."
where
sc.MODULE_ID != 'forum'
and sc.MODULE_ID != 'blog'
and sc.MODULE_ID != 'tasks'
/*".str_replace(" RANK", " SRANK", $strSort)."*/
/* user %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% */
UNION
".$sel2."
where
se.USER_ID = ".intval($USER->GetId())."
/* blog %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% */
UNION
".$sel3."
where
sa.MODULE_ID = 'blog'
and sa.PARAM1 in ('POST', 'COMMENT')
/* tasks %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% */
".(empty($gr_and_usr)? "" : "
UNION
".$sel4."
INNER JOIN temp t ON su.ID = t.gr_and_usr
where
su.MODULE_ID = 'tasks'
")."
ORDER BY CUSTOM_RANK DESC, SRANK DESC, DATE_CHANGE DESC
"
/*.str_replace(" RANK", " SRANK", $strSort2)*/
;
} // конец if для рейтинга
} // end рейтинг
Вот и все! Как видите, после замены стандартных настроек, а этот запрос делался под такие, некоторые части кода меняются. Поэтому будьте внимательны!
Скачать полный скрипт тут
searchNEW.php.zip
Хм. Корпортад это «обычный» Битрикс с определенным решением. Даже по пути видно, что используется стандартный модуль поиска, который достаточно гибко настраивается. И уж точно просто настройками можно «отсечь» из поиска стандартные модули. Кроме того поиск может быть настроен на использование sphinx и тогда ваше ограничение не будет работать. К тому же в таблице будут постоянно храниться и индексироваться данные, которые вам для поиска не нужны. Так зачем хранить лишнее. Просто настройте модуль поиска
Александр, стандартные настройки не помогали. Элементарные решения я не игнорю, и не стремлюсь изобретать велосипеды.