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

2 Comments

  1. Хм. Корпортад это «обычный» Битрикс с определенным решением. Даже по пути видно, что используется стандартный модуль поиска, который достаточно гибко настраивается. И уж точно просто настройками можно «отсечь» из поиска стандартные модули. Кроме того поиск может быть настроен на использование sphinx и тогда ваше ограничение не будет работать. К тому же в таблице будут постоянно храниться и индексироваться данные, которые вам для поиска не нужны. Так зачем хранить лишнее. Просто настройте модуль поиска

  2. Александр, стандартные настройки не помогали. Элементарные решения я не игнорю, и не стремлюсь изобретать велосипеды.

Leave a comment

Your email address will not be published.


*