Server IP : 80.87.202.40 / Your IP : 216.73.216.169 Web Server : Apache System : Linux rospirotorg.ru 5.14.0-539.el9.x86_64 #1 SMP PREEMPT_DYNAMIC Thu Dec 5 22:26:13 UTC 2024 x86_64 User : bitrix ( 600) PHP Version : 8.2.27 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : ON Directory : /home/bitrix/ext_www/rospirotorg.ru/bitrix/modules/search/classes/mysql/ |
Upload File : |
<?php require_once $_SERVER['DOCUMENT_ROOT'] . '/bitrix/modules/search/classes/general/search.php'; class CSearch extends CAllSearch { /* var $arForumTopics = array(); function DBNavStart() { //total rows count $this->NavRecordCount = mysql_num_rows($this->result); if($this->NavRecordCount < 1) return; if($this->NavShowAll) $this->NavPageSize = $this->NavRecordCount; //calculate total pages depend on rows count. start with 1 $this->NavPageCount = floor($this->NavRecordCount/$this->NavPageSize); if($this->NavRecordCount % $this->NavPageSize > 0) $this->NavPageCount++; //page number to display. start with 1 $this->NavPageNomer = ($this->PAGEN < 1 || $this->PAGEN > $this->NavPageCount? ($_SESSION[$this->SESS_PAGEN] < 1 || $_SESSION[$this->SESS_PAGEN] > $this->NavPageCount? 1:$_SESSION[$this->SESS_PAGEN]):$this->PAGEN); //rows to skip $NavFirstRecordShow = $this->NavPageSize * ($this->NavPageNomer-1); $NavLastRecordShow = $this->NavPageSize; if($this->SqlTraceIndex) { list($usec, $sec) = explode(" ", microtime()); $start_time = ((float)$usec + (float)$sec); } while($NavFirstRecordShow > 0) { if(($res = mysql_fetch_array($this->result, MYSQL_ASSOC))) { if( $res["MODULE_ID"] == "forum" && array_key_exists($res["PARAM2"], $this->arForumTopics) ) $this->NavRecordCount--; //eat forum topic duplicates elseif( $res["module"] == "forum" && array_key_exists($res["param2"], $this->arForumTopics) ) $this->NavRecordCount--; //eat forum topic duplicates else $NavFirstRecordShow--; if($res["MODULE_ID"] == "forum") $this->arForumTopics[$res["PARAM2"]] = true; elseif($res["module"] == "forum") $this->arForumTopics[$res["param2"]] = true; } else { break; } } $temp_arrray = array(); while($NavLastRecordShow > 0) { if(($res = mysql_fetch_array($this->result, MYSQL_ASSOC))) { if( $res["MODULE_ID"] == "forum" && array_key_exists($res["PARAM2"], $this->arForumTopics) ) $this->NavRecordCount--; //eat forum topic duplicates elseif( $res["module"] == "forum" && array_key_exists($res["param2"], $this->arForumTopics) ) $this->NavRecordCount--; //eat forum topic duplicates else { if($this->arUserMultyFields) foreach($this->arUserMultyFields as $FIELD_NAME=>$flag) if($res[$FIELD_NAME]) $res[$FIELD_NAME] = unserialize($res[$FIELD_NAME]); $temp_arrray[] = $res; $NavLastRecordShow--; } if($res["MODULE_ID"] == "forum") $this->arForumTopics[$res["PARAM2"]] = true; elseif($res["module"] == "forum") $this->arForumTopics[$res["param2"]] = true; } else { break; } } //Adjust total pages depend on rows count. start with 1 $this->NavPageCount = floor($this->NavRecordCount/$this->NavPageSize); if($this->NavRecordCount % $this->NavPageSize > 0) $this->NavPageCount++; if($this->SqlTraceIndex) { list($usec, $sec) = explode(" ", microtime()); $end_time = ((float)$usec + (float)$sec); $exec_time = round($end_time-$start_time, 10); $GLOBALS["DB"]->arQueryDebug[$this->SqlTraceIndex - 1]["TIME"] += $exec_time; $GLOBALS["DB"]->timeQuery += $exec_time; } $this->nSelectedCount = $this->NavRecordCount; $this->arResult = $temp_arrray; } */ function MakeSQL($query, $strSqlWhere, $strSort, $bIncSites, $bStem) { global $USER; $DB = CDatabase::GetModuleConnection('search'); $helper = $DB->getConnection()->getSqlHelper(); $bDistinct = false; $arSelect = [ 'ID' => 'sc.ID', 'MODULE_ID' => 'sc.MODULE_ID', 'ITEM_ID' => 'sc.ITEM_ID', 'TITLE' => 'sc.TITLE', 'TAGS' => 'sc.TAGS', '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'), 'DATE_CHANGE' => $DB->DateToCharFunction('sc.DATE_CHANGE', 'SHORT'), 'DATE_CHANGE_X' => 'sc.DATE_CHANGE', ]; if ($this->Query->bText) { $arSelect['SEARCHABLE_CONTENT'] = 'sct.SEARCHABLE_CONTENT'; } $arSelect['USER_ID'] = 'sc.USER_ID'; if (mb_strpos($strSort, 'TITLE_RANK') !== false) { $strSelect = ''; if ($bStem) { foreach ($this->Query->m_stemmed_words as $stem) { if ($strSelect <> '') { $strSelect .= ' + '; } $strSelect .= "case when position('" . $stem . "' in upper(sc.TITLE)) > 0 then 1 else 0 end"; } $arSelect['TITLE_RANK'] = $strSelect; } else { foreach ($this->Query->m_words as $word) { if ($strSelect <> '') { $strSelect .= ' + '; } $strSelect .= "case when position('" . $DB->ForSql(mb_strtoupper($word)) . "' in upper(sc.TITLE)) > 0 then 1 else 0 end"; } $arSelect['TITLE_RANK'] = $strSelect; } } $strStemList = ''; if ($bStem) { $strStemList = implode(', ', $this->Query->m_stemmed_words_id); } $bWordPos = COption::GetOptionString('search', 'use_word_distance') == 'Y'; if ($bIncSites && $bStem) { $arSelect['SITE_URL'] = 'scsite.URL'; $arSelect['SITE_ID'] = 'scsite.SITE_ID'; if (!preg_match('/(sc|sct)./', $query)) { $strSqlWhere = preg_replace('#AND\\(st.TF >= [0-9\.,]+\\)#i', '', $strSqlWhere); if (count($this->Query->m_stemmed_words) > 1) { $arSelect[$helper->quote('RANK')] = 'stt.' . $helper->quote('RANK'); } else { $arSelect[$helper->quote('RANK')] = 'stt.TF'; } $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 ? 'case when STDDEV(st.PS)-' . $this->normdev(count($this->Query->m_stemmed_words)) . ' between -0.000001 and 1 then 1/STDDEV(st.PS) else 0 end + ' : '') . "sum(st.TF/sf.FREQ) as " . $helper->quote('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, '.', '') : '') . ' ' . ($this->tf_hwm_site_id <> '' ? "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 . ' '; } else { $arGroupBy = []; foreach ($arSelect as $selectAlias => $selectField) { $arGroupBy[] = $selectField; } if (count($this->Query->m_stemmed_words) > 1) { if ($bWordPos) { $arSelect[$helper->quote('RANK')] = 'case when STDDEV(st.PS)-' . $this->normdev(count($this->Query->m_stemmed_words)) . ' between -0.000001 and 1 then 1/STDDEV(st.PS) else 0 end + sum(st.TF/sf.FREQ)'; } else { $arSelect[$helper->quote('RANK')] = 'sum(st.TF/sf.FREQ)'; } } else { $arSelect[$helper->quote('RANK')] = 'st.TF'; } $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 INNER JOIN b_search_content_stem st ON sc.id = st.search_content_id+0 ' . (count($this->Query->m_stemmed_words) > 1 ? 'INNER JOIN b_search_content_freq sf ON st.language_id = sf.language_id and st.stem=sf.stem ' . ($this->tf_hwm_site_id <> '' ? "and sf.SITE_ID = '" . $DB->ForSQL($this->tf_hwm_site_id, 2) . "'" : 'and sf.SITE_ID IS NULL' ) : '' ) . ' WHERE ' . CSearch::CheckPermissions('sc.ID') . ' AND st.STEM in (' . $strStemList . ') ' . (count($this->Query->m_stemmed_words) > 1 ? 'AND sf.STEM in (' . $strStemList . ')' : '') . " AND st.language_id='" . $this->Query->m_lang . "' " . $strSqlWhere . ' GROUP BY ' . implode(', ', $arGroupBy) . ' HAVING (' . $query . ') '; } } elseif ($bIncSites && !$bStem) { $bDistinct = true; $arSelect['SITE_URL'] = 'scsite.URL'; $arSelect['SITE_ID'] = 'scsite.SITE_ID'; $arSelect[$helper->quote('RANK')] = '1'; $arGroupBy = []; foreach ($arSelect as $selectAlias => $selectField) { $arGroupBy[] = $selectField; } if ($this->Query->bTagsSearch) { $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 INNER JOIN b_search_tags stags ON (sc.ID = stags.SEARCH_CONTENT_ID) WHERE ' . CSearch::CheckPermissions('sc.ID') . ' ' . $strSqlWhere . ' ' . (is_array($this->Query->m_tags_words) && count($this->Query->m_tags_words) > 0 ? "AND stags.NAME in ('" . implode("','", $this->Query->m_tags_words) . "')" : '') . ' GROUP BY ' . implode(', ', $arGroupBy) . ' HAVING ' . $query . ' '; } else { $strSql = ' FROM ' . ($this->Query->bText ? ' b_search_content_text sct INNER JOIN b_search_content sc ON sc.ID = sct.SEARCH_CONTENT_ID INNER JOIN b_search_content_site scsite ON sc.ID = scsite.SEARCH_CONTENT_ID ' : ' b_search_content sc INNER JOIN b_search_content_site scsite ON sc.ID = scsite.SEARCH_CONTENT_ID ') . ' WHERE ' . CSearch::CheckPermissions('sc.ID') . ' AND (' . $query . ') ' . $strSqlWhere . ' '; } } elseif (!$bIncSites && $bStem) { $arGroupBy = []; foreach ($arSelect as $selectAlias => $selectField) { $arGroupBy[] = $selectField; } if (count($this->Query->m_stemmed_words) > 1) { if ($bWordPos) { $arSelect[$helper->quote('RANK')] = 'case when STDDEV(st.PS)-' . $this->normdev(count($this->Query->m_stemmed_words)) . ' between -0.000001 and 1 then 1/STDDEV(st.PS) else 0 end + sum(st.TF/sf.FREQ)'; } else { $arSelect[$helper->quote('RANK')] = 'sum(st.TF/sf.FREQ)'; } } else { $arSelect[$helper->quote('RANK')] = 'st.TF'; } $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_stem st ON sc.id = st.search_content_id ' . (count($this->Query->m_stemmed_words) > 1 ? 'INNER JOIN b_search_content_freq sf ON st.language_id = sf.language_id and st.stem=sf.stem ' . ($this->tf_hwm_site_id <> '' ? "and sf.SITE_ID = '" . $DB->ForSQL($this->tf_hwm_site_id, 2) . "'" : 'and sf.SITE_ID IS NULL' ) : '' ) . ' WHERE ' . CSearch::CheckPermissions('sc.ID') . ' AND st.STEM in (' . $strStemList . ') ' . (count($this->Query->m_stemmed_words) > 1 ? 'AND sf.STEM in (' . $strStemList . ')' : '') . " AND st.language_id='" . $this->Query->m_lang . "' " . $strSqlWhere . ' ' . (count($this->Query->m_stemmed_words) > 1 ? ' GROUP BY ' . implode(', ', $arGroupBy) . ' HAVING (' . $query . ') ' : '') . ' '; } else //if(!$bIncSites && !$bStem) { $bDistinct = true; $arSelect[$helper->quote('RANK')] = '1'; $arGroupBy = []; foreach ($arSelect as $selectAlias => $selectField) { $arGroupBy[] = $selectField; } $strSql = ' FROM b_search_content sc ' . ($this->Query->bText ? 'INNER JOIN b_search_content_text sct ON sct.SEARCH_CONTENT_ID = sc.ID' : '') . ' ' . ($this->Query->bTagsSearch ? 'INNER JOIN b_search_tags stags ON (sc.ID = stags.SEARCH_CONTENT_ID) WHERE ' . CSearch::CheckPermissions('sc.ID') . ' ' . $strSqlWhere . ' ' . (is_array($this->Query->m_tags_words) && count($this->Query->m_tags_words) > 0 ? "AND stags.NAME in ('" . implode("','", $this->Query->m_tags_words) . "')" : '') . ' GROUP BY ' . implode(', ', $arGroupBy) . ' HAVING (' . $query . ')' : ' WHERE (' . $query . ') ' . $strSqlWhere . ' ') . ' '; } if ($this->offset === false) { $limit = $this->limit; } else { $limit = $this->offset . ', ' . $this->limit; } $strRatingJoin = ''; $RATING_MAX = 0; $RATING_MIN = 0; if ( ($this->flagsUseRatingSort & 0x01) && COption::GetOptionString('search', 'use_social_rating') == 'Y' && COption::GetOptionString('search', 'dbnode_id') <= 0 ) { $rsMinMax = $DB->Query('select max(TOTAL_VALUE) RATING_MAX, min(TOTAL_VALUE) RATING_MIN from b_rating_voting'); $arMinMax = $rsMinMax->Fetch(); if ($arMinMax) { $RATING_MAX = doubleval($arMinMax['RATING_MAX']); if ($RATING_MAX < 0) { $RATING_MAX = 0; } $RATING_MIN = doubleval($arMinMax['RATING_MIN']); if ($RATING_MIN > 0) { $RATING_MIN = 0; } } if ($RATING_MAX != 0 || $RATING_MIN != 0) { $arSelectOuter = []; $arSelectOuterFields = [ 'BODY', ]; foreach ($arSelectOuterFields as $outerField) { if (isset($arSelect[$outerField])) { $arSelectOuter[$outerField] = $arSelect[$outerField]; } unset($arSelect[$outerField]); } $strSelectOuter = 'SELECT sc0.*' . ($arSelectOuter ? ', ' . implode(', ', $arSelectOuter) : ''); $strSelectInner = 'SELECT ' . ($bDistinct ? 'DISTINCT' : '') . "\n" . implode("\n,", $arSelect); return ' ' . $strSelectOuter . ', sc0.' . $helper->quote('RANK') . ' + case when rv.TOTAL_VALUE > 0 then ' . ($RATING_MAX > 0 ? 'rv.TOTAL_VALUE/' . $RATING_MAX : '0') . ' when rv.TOTAL_VALUE < 0 then ' . ($RATING_MIN < 0 ? 'rv.TOTAL_VALUE/' . abs($RATING_MIN) : '0') . ' else 0 end 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()) . ' ' . str_replace(' ' . $helper->quote('RANK'), ' SRANK', $strSort); } } $strSelect = ''; foreach ($arSelect as $selectAlias => $selectField) { $strSelect .= ($strSelect ? ',' : ' ') . $selectField . ' as ' . $selectAlias . "\n"; } $strSql = 'SELECT ' . ($bDistinct ? 'DISTINCT' : '') . $strSelect . "\n" . $strSql . $strSort . "\nLIMIT " . $limit; return $strSql; } function tagsMakeSQL($query, $strSqlWhere, $strSort, $bIncSites, $bStem, $limit = 100) { $DB = CDatabase::GetModuleConnection('search'); $limit = intval($limit); if ($bStem && count($this->Query->m_stemmed_words) > 1) { //We have to make some magic in case quotes was used in query //We have to move (sc.searchable_content LIKE '%".ToUpper($word)."%') from $query to $strSqlWhere $arMatches = []; while (preg_match("/(AND\s+\([sct]+.searchable_content LIKE \'\%.+?\%\'\))/", $query, $arMatches)) { $strSqlWhere .= $arMatches[0]; $query = str_replace($arMatches[0], '', $query); $arMatches = []; } } if ($bStem) { $strStemList = implode(', ', $this->Query->m_stemmed_words_id); } if ($bIncSites && $bStem) { $strSql = ' SELECT stags.NAME ,COUNT(DISTINCT stags.SEARCH_CONTENT_ID) as CNT ,MAX(sc.DATE_CHANGE) DC_TMP ,' . $DB->DateToCharFunction('MAX(sc.DATE_CHANGE)') . ' as FULL_DATE_CHANGE ,' . $DB->DateToCharFunction('MAX(sc.DATE_CHANGE)', 'SHORT') . ' as DATE_CHANGE ' . (count($this->Query->m_stemmed_words) > 1 && mb_strpos($query, 'searchable_content') !== false ? ',sct.SEARCHABLE_CONTENT' : '') . ' FROM b_search_tags stags INNER JOIN b_search_content sc ON (stags.SEARCH_CONTENT_ID=sc.ID) ' . ($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 INNER JOIN b_search_content_stem st ON sc.id = st.search_content_id ' . (count($this->Query->m_stemmed_words) > 1 ? 'INNER JOIN b_search_content_freq sf ON st.language_id = sf.language_id and st.stem=sf.stem ' . ($this->tf_hwm_site_id <> '' ? "and sf.SITE_ID = '" . $DB->ForSQL($this->tf_hwm_site_id, 2) . "'" : 'and sf.SITE_ID IS NULL' ) : '' ) . ' WHERE ' . CSearch::CheckPermissions('sc.ID') . ' AND st.STEM in (' . $strStemList . ') ' . (count($this->Query->m_stemmed_words) > 1 ? 'AND sf.STEM in (' . $strStemList . ')' : '') . " AND st.language_id='" . $this->Query->m_lang . "' AND stags.SITE_ID = scsite.SITE_ID " . $strSqlWhere . ' GROUP BY stags.NAME ' . ((count($this->Query->m_stemmed_words) > 1) ? ' HAVING (' . $query . ') ' : '') . ' ' . $strSort . ' '; } elseif ($bIncSites && !$bStem) { //Copy first exists into inner join in hopeless try to defeat MySQL optimizer $strSqlJoin2 = ''; $match = []; if ($strSqlWhere && preg_match('#\\s*EXISTS \\(SELECT \\* FROM b_search_content_param WHERE (SEARCH_CONTENT_ID = sc\\.ID AND PARAM_NAME = \'[^\']+\' AND PARAM_VALUE(\\s*= \'[^\']+\'|\\s+in \\(\'[^\']+\'\\)))\\)#', $strSqlWhere, $match)) { $strSqlJoin2 = 'INNER JOIN b_search_content_param scp ON scp.' . $match[1]; } if ($query == '1=1') { $strSql = ' SELECT stags2.NAME ,COUNT(DISTINCT stags2.SEARCH_CONTENT_ID) as CNT ,MAX(sc.DATE_CHANGE) DC_TMP ,' . $DB->DateToCharFunction('MAX(sc.DATE_CHANGE)') . ' as FULL_DATE_CHANGE ,' . $DB->DateToCharFunction('MAX(sc.DATE_CHANGE)', 'SHORT') . ' as DATE_CHANGE FROM b_search_tags stags2 INNER JOIN b_search_content sc ON (stags2.SEARCH_CONTENT_ID=sc.ID) ' . ($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 AND stags2.SITE_ID=scsite.SITE_ID) ' . $strSqlJoin2 . ' WHERE ' . CSearch::CheckPermissions('sc.ID') . ' AND ' . ($this->Query->bTagsSearch ? ( //Index range scan optimization (make it for other queries ???) is_array($this->Query->m_tags_words) && count($this->Query->m_tags_words) ? "stags.name in ('" . implode("', '", $this->Query->m_tags_words) . "')" : '(1=1)' ) : '(' . $query . ')') . ' ' . $strSqlWhere . ' GROUP BY stags2.NAME ' . $strSort . ' '; } else { $strSql = ' SELECT stags2.NAME ,COUNT(DISTINCT stags.SEARCH_CONTENT_ID) as CNT ,MAX(sc.DATE_CHANGE) DC_TMP ,' . $DB->DateToCharFunction('MAX(sc.DATE_CHANGE)') . ' as FULL_DATE_CHANGE ,' . $DB->DateToCharFunction('MAX(sc.DATE_CHANGE)', 'SHORT') . ' as DATE_CHANGE FROM b_search_tags stags2 INNER JOIN b_search_tags stags ON (stags.SEARCH_CONTENT_ID=stags2.SEARCH_CONTENT_ID and stags.SITE_ID=stags2.SITE_ID) INNER JOIN b_search_content sc ON (stags.SEARCH_CONTENT_ID=sc.ID) ' . ($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 AND stags.SITE_ID=scsite.SITE_ID) ' . $strSqlJoin2 . ' WHERE ' . CSearch::CheckPermissions('sc.ID') . ' AND ' . ($this->Query->bTagsSearch ? ( //Index range scan optimization (make it for other queries ???) is_array($this->Query->m_tags_words) && count($this->Query->m_tags_words) ? "stags.name in ('" . implode("', '", $this->Query->m_tags_words) . "')" : '(1=1)' ) : '(' . $query . ')') . ' ' . $strSqlWhere . ' GROUP BY stags2.NAME ' . ($this->Query->bTagsSearch ? ' HAVING (' . $query . ')' : '') . ' ' . $strSort . ' '; } } elseif (!$bIncSites && $bStem) { $strSql = ' SELECT stags.NAME ,COUNT(DISTINCT stags.SEARCH_CONTENT_ID) as CNT ,MAX(sc.DATE_CHANGE) DC_TMP , ' . $DB->DateToCharFunction('MAX(sc.DATE_CHANGE)') . ' as FULL_DATE_CHANGE , ' . $DB->DateToCharFunction('MAX(sc.DATE_CHANGE)', 'SHORT') . ' as DATE_CHANGE ' . (count($this->Query->m_stemmed_words) > 1 && mb_strpos($query, 'searchable_content') !== false ? ',sct.SEARCHABLE_CONTENT' : '') . ' FROM b_search_tags stags INNER JOIN b_search_content sc ON (stags.SEARCH_CONTENT_ID=sc.ID) ' . ($this->Query->bText ? 'INNER JOIN b_search_content_text sct ON sct.SEARCH_CONTENT_ID = sc.ID' : '') . ' INNER JOIN b_search_content_stem st ON sc.id = st.search_content_id ' . (count($this->Query->m_stemmed_words) > 1 ? 'INNER JOIN b_search_content_freq sf ON st.language_id = sf.language_id and st.stem=sf.stem ' . ($this->tf_hwm_site_id <> '' ? "and sf.SITE_ID = '" . $DB->ForSQL($this->tf_hwm_site_id, 2) . "'" : 'and sf.SITE_ID IS NULL' ) : '' ) . ' WHERE ' . CSearch::CheckPermissions('sc.ID') . ' AND st.STEM in (' . $strStemList . ') ' . (count($this->Query->m_stemmed_words) > 1 ? 'AND sf.STEM in (' . $strStemList . ')' : '') . " AND st.language_id='" . $this->Query->m_lang . "' " . $strSqlWhere . ' GROUP BY stags.NAME ' . (count($this->Query->m_stemmed_words) > 1 ? ' ,sc.ID HAVING (' . $query . ') ' : '') . ' ' . $strSort . ' '; } else // if(!$bIncSites && !$bStem) { $strSql = ' SELECT stags2.NAME ,COUNT(DISTINCT stags.SEARCH_CONTENT_ID) as CNT ,MAX(sc.DATE_CHANGE) DC_TMP ,' . $DB->DateToCharFunction('MAX(sc.DATE_CHANGE)') . ' as FULL_DATE_CHANGE ,' . $DB->DateToCharFunction('MAX(sc.DATE_CHANGE)', 'SHORT') . ' as DATE_CHANGE FROM b_search_tags stags2 INNER JOIN b_search_tags stags ON (stags.SEARCH_CONTENT_ID=stags2.SEARCH_CONTENT_ID and stags.SITE_ID=stags2.SITE_ID) INNER JOIN b_search_content sc ON (stags.SEARCH_CONTENT_ID=sc.ID) ' . ($this->Query->bText ? 'INNER JOIN b_search_content_text sct ON sct.SEARCH_CONTENT_ID = sc.ID' : '') . ' WHERE ' . CSearch::CheckPermissions('sc.ID') . ' AND ' . ($this->Query->bTagsSearch ? ( //Index range scan optimization (make it for other queries ???) is_array($this->Query->m_tags_words) && count($this->Query->m_tags_words) ? "stags.name in ('" . implode("', '", $this->Query->m_tags_words) . "')" : '(1=1)' ) : '(' . $query . ')') . ' ' . $strSqlWhere . ' GROUP BY stags2.NAME ' . ($this->Query->bTagsSearch ? ' HAVING (' . $query . ')' : '') . ' ' . $strSort . ' '; } if ($limit < 1) { $limit = 150; } return $strSql . 'LIMIT ' . $limit; } public static function ReindexLock() { //do not lock for mysql database } public static function OnLangDelete($lang) { $DB = CDatabase::GetModuleConnection('search'); $DB->Query(" DELETE FROM b_search_content_site WHERE SITE_ID='" . $DB->ForSql($lang) . "' "); CSearchTags::CleanCache(); } public static function FormatDateString($strField) { $DB = CDatabase::GetModuleConnection('search'); return $DB->DateFormatToDB('DD.MM.YYYY HH:MI:SS', $strField); } public static function CleanFreqCache($ID) { $DB = CDatabase::GetModuleConnection('search'); $DB->Query(' UPDATE b_search_content_freq SET TF = null WHERE TF is not null AND (LANGUAGE_ID, STEM) IN ( SELECT LANGUAGE_ID, STEM FROM b_search_content_stem WHERE SEARCH_CONTENT_ID = ' . intval($ID) . ' ) '); } public static function IndexTitle($arLID, $ID, $sTitle) { $DB = CDatabase::GetModuleConnection('search'); $helper = $DB->getConnection()->getSqlHelper(); static $CACHE_SITE_LANGS = []; $ID = intval($ID); $arLang = []; if (!is_array($arLID)) { $arLID = []; } foreach ($arLID as $site => $url) { $sql_site = $DB->ForSql($site); if (!array_key_exists($site, $CACHE_SITE_LANGS)) { $db_site_tmp = CSite::GetByID($site); if ($ar_site_tmp = $db_site_tmp->Fetch()) { $CACHE_SITE_LANGS[$site] = [ 'LANGUAGE_ID' => $ar_site_tmp['LANGUAGE_ID'], 'CHARSET' => $ar_site_tmp['CHARSET'], 'SERVER_NAME' => $ar_site_tmp['SERVER_NAME'] ]; } else { $CACHE_SITE_LANGS[$site] = false; } } if (is_array($CACHE_SITE_LANGS[$site])) { $lang = $CACHE_SITE_LANGS[$site]['LANGUAGE_ID']; $dbTitle = []; $rs = $DB->Query('select WORD, POS from b_search_content_title where SEARCH_CONTENT_ID = ' . $ID . " and SITE_ID='" . $sql_site . "'"); while ($ar = $rs->Fetch()) { $dbTitle[$ar['WORD']] = intval($ar['POS']); } $arTitle = stemming_split($sTitle, $lang); if (!empty($arTitle)) { $maxValues = 100; $arInsert = []; foreach ($arTitle as $word => $pos) { if (isset($dbTitle[$word]) && $dbTitle[$word] === $pos) { //Already in the db unset($dbTitle[$word]); } else { //New value $arInsert[] = [ 'SEARCH_CONTENT_ID' => $ID, 'SITE_ID' => $site, 'WORD' => $word, 'POS' => $pos, ]; if (count($arInsert) > $maxValues) { $merge = $helper->prepareMergeMultiple('b_search_content_title', ['SITE_ID', 'WORD', 'SEARCH_CONTENT_ID', 'POS'], $arInsert); if ($merge && $merge[0]) { $DB->Query($merge[0]); } $arInsert = []; } } } if ($arInsert) { $merge = $helper->prepareMergeMultiple('b_search_content_title', ['SITE_ID', 'WORD', 'SEARCH_CONTENT_ID', 'POS'], $arInsert); if ($merge && $merge[0]) { $DB->Query($merge[0]); } } //Delete obsolete db values foreach ($dbTitle as $word => $pos) { $DB->Query(' delete from b_search_content_title where SEARCH_CONTENT_ID = ' . $ID . " and SITE_ID='" . $sql_site . "' and WORD = '" . $DB->ForSql($word) . "' and POS = " . $pos . ' '); } } } } } public static function RegisterStem($stem) { $DB = CDatabase::GetModuleConnection('search'); static $cache = []; if (is_array($stem)) //This is batch check of the already exist stems { ksort($stem); $strSqlPrefix = 'select * from b_search_stem where stem in ('; $maxValuesLen = 4096; $maxValuesCnt = 1500; $strSqlValues = ''; $i = 0; foreach ($stem as $word => $count) { $strSqlValues .= ",'" . $DB->ForSQL($word) . "'"; $i++; if (mb_strlen($strSqlValues) > $maxValuesLen || $i > $maxValuesCnt) { $rs = $DB->Query($strSqlPrefix . mb_substr($strSqlValues, 1) . ')'); while ($ar = $rs->Fetch()) { $cache[$ar['STEM']] = $ar['ID']; } $strSqlValues = ''; $i = 0; } } if ($strSqlValues <> '') { $rs = $DB->Query($strSqlPrefix . mb_substr($strSqlValues, 1) . ')'); while ($ar = $rs->Fetch()) { $cache[$ar['STEM']] = $ar['ID']; } } return; } if (!isset($cache[$stem])) { $rs = $DB->Query("insert into b_search_stem (STEM) values ('" . $DB->ForSQL($stem) . "')", true); if ($rs === false) { $rs = $DB->Query("select ID from b_search_stem WHERE STEM = '" . $DB->ForSQL($stem) . "'"); $ar = $rs->Fetch(); $cache[$stem] = $ar['ID']; } else { $cache[$stem] = $DB->LastID(); } } return $cache[$stem]; } public static function StemIndex($arLID, $ID, $sContent) { $DB = CDatabase::GetModuleConnection('search'); $helper = $DB->getConnection()->getSqlHelper(); static $CACHE_SITE_LANGS = []; $ID = intval($ID); $arLang = []; if (!is_array($arLID)) { $arLID = []; } foreach ($arLID as $site => $url) { if (!array_key_exists($site, $CACHE_SITE_LANGS)) { $db_site_tmp = CSite::GetByID($site); if ($ar_site_tmp = $db_site_tmp->Fetch()) { $CACHE_SITE_LANGS[$site] = [ 'LANGUAGE_ID' => $ar_site_tmp['LANGUAGE_ID'], 'CHARSET' => $ar_site_tmp['CHARSET'], 'SERVER_NAME' => $ar_site_tmp['SERVER_NAME'] ]; } else { $CACHE_SITE_LANGS[$site] = false; } } if (is_array($CACHE_SITE_LANGS[$site])) { $arLang[$CACHE_SITE_LANGS[$site]['LANGUAGE_ID']] = true; } } foreach ($arLang as $lang => $value) { $sql_lang = $DB->ForSql($lang); $arDoc = stemming($sContent, $lang); $docLength = array_sum($arDoc); $arPos = stemming($sContent, $lang, false, true); CSearch::RegisterStem($arDoc); if ($docLength > 0) { $doc = ''; $logDocLength = log($docLength < 20 ? 20 : $docLength); $maxValues = 100; $arInsert = []; foreach ($arDoc as $word => $count) { $stem_id = CSearch::RegisterStem($word); //This is almost impossible, but happens if ($stem_id > 0) { $arInsert[] = [ 'SEARCH_CONTENT_ID' => $ID, 'LANGUAGE_ID' => $lang, 'STEM' => $stem_id, 'TF' => number_format(log($count + 1) / $logDocLength, 4, '.', ''), 'PS' => number_format($arPos[$word] / $count, 4, '.', ''), ]; } if (count($arInsert) > $maxValues) { $merge = $helper->prepareMergeMultiple('b_search_content_stem', ['STEM', 'LANGUAGE_ID', 'TF', 'PS', 'SEARCH_CONTENT_ID'], $arInsert); if ($merge && $merge[0]) { $DB->Query($merge[0]); } $arInsert = []; } } if ($arInsert) { $merge = $helper->prepareMergeMultiple('b_search_content_stem', ['STEM', 'LANGUAGE_ID', 'TF', 'PS', 'SEARCH_CONTENT_ID'], $arInsert); if ($merge && $merge[0]) { $DB->Query($merge[0]); } } } } } public static function TagsIndex($arLID, $ID, $sContent) { $DB = CDatabase::GetModuleConnection('search'); $helper = $DB->getConnection()->getSqlHelper(); $ID = intval($ID); if (!is_array($arLID)) { $arLID = []; } $sContent = str_replace("\x00", '', $sContent); foreach ($arLID as $site_id => $url) { $arTags = tags_prepare($sContent, $site_id); if ($arTags) { CSearchTags::CleanCache($arTags); $maxValues = 100; $arInsert = []; foreach ($arTags as $tag) { $arInsert[] = [ 'SEARCH_CONTENT_ID' => $ID, 'SITE_ID' => $site_id, 'NAME' => $tag, ]; if (count($arInsert) > $maxValues) { $merge = $helper->prepareMergeMultiple('b_search_tags', ['SEARCH_CONTENT_ID', 'SITE_ID', 'NAME'], $arInsert); if ($merge && $merge[0]) { $DB->Query($merge[0]); } $arInsert = []; } } if ($arInsert) { $merge = $helper->prepareMergeMultiple('b_search_tags', ['SEARCH_CONTENT_ID', 'SITE_ID', 'NAME'], $arInsert); if ($merge && $merge[0]) { $DB->Query($merge[0]); } } } } } public static function UpdateSite($ID, $arSITE_ID) { $DB = CDatabase::GetModuleConnection('search'); $helper = $DB->getConnection()->getSqlHelper(); $ID = intval($ID); if (!is_array($arSITE_ID)) { $DB->Query(' DELETE FROM b_search_content_site WHERE SEARCH_CONTENT_ID = ' . $ID . ' '); } else { $rsSite = $DB->Query(' SELECT SITE_ID, URL FROM b_search_content_site WHERE SEARCH_CONTENT_ID = ' . $ID . ' '); while ($arSite = $rsSite->Fetch()) { if (!array_key_exists($arSite['SITE_ID'], $arSITE_ID)) { $DB->Query(' DELETE FROM b_search_content_site WHERE SEARCH_CONTENT_ID = ' . $ID . " AND SITE_ID = '" . $DB->ForSql($arSite['SITE_ID']) . "' "); } else { if ($arSite['URL'] !== $arSITE_ID[$arSite['SITE_ID']]) { $DB->Query(" UPDATE b_search_content_site SET URL = '" . $DB->ForSql($arSITE_ID[$arSite['SITE_ID']], 2000) . "' WHERE SEARCH_CONTENT_ID = " . $ID . " AND SITE_ID = '" . $DB->ForSql($arSite['SITE_ID']) . "' "); } unset($arSITE_ID[$arSite['SITE_ID']]); } } $arInsert = []; foreach ($arSITE_ID as $site => $url) { $arInsert[] = [ 'SEARCH_CONTENT_ID' => $ID, 'SITE_ID' => $site, 'URL' => $url, ]; } $merge = $helper->prepareMergeMultiple('b_search_content_site', ['SEARCH_CONTENT_ID', 'SITE_ID'], $arInsert); if ($merge && $merge[0]) { $DB->Query($merge[0]); } } } } class CSearchQuery extends CAllSearchQuery { var $cnt = 0; function BuildWhereClause($word) { $DB = CDatabase::GetModuleConnection('search'); $this->cnt++; if ($this->cnt > 10) { return '1=1'; } if (isset($this->m_kav[$word])) { $word = $this->m_kav[$word]; $bInQuotes = true; } else { $bInQuotes = false; } $this->m_words[] = $word; $word = $DB->ForSql($word, 100); if ($this->bTagsSearch) { if (mb_strpos($word, '%') === false) { //We can optimize query by doing range scan if (is_array($this->m_tags_words)) { $this->m_tags_words[] = $word; } $op = '='; } else { //Optimization is not possible $this->m_tags_words = false; $op = 'like'; } return '(sum(case when stags.name ' . $op . " '" . $word . "' then 1 else 0 end)>0)"; } elseif ($this->bStemming && !$bInQuotes) { $word = mb_strtoupper($word); $this->m_stemmed_words[] = $word; $rs = $DB->Query("select ID from b_search_stem where STEM='" . $DB->ForSQL($word) . "'"); $ar = $rs->Fetch(); $this->m_stemmed_words_id[] = intval($ar['ID']); return '(sum(case when st.stem = ' . intval($ar['ID']) . ' then 1 else 0 end)>0)'; } else { $this->bText = true; return "(sct.searchable_content LIKE '%" . str_replace(['%', '_'], ['\\%', '\\_'], mb_strtoupper($word)) . "%')"; } } }