ez-pro/core/bitrix/modules/search/tools/sphinx.php
2025-11-13 19:04:05 +03:00

1410 lines
30 KiB
PHP

<?php
IncludeModuleLangFile(__FILE__);
class CSearchSphinx extends CSearchFullText
{
public $arForumTopics = [];
public $db = false;
private static $fields = [
'title' => 'field',
'body' => 'field',
'module_id' => 'uint',
'module' => 'string',
'item_id' => 'uint',
'item' => 'string',
'param1_id' => 'uint',
'param1' => 'string',
'param2_id' => 'uint',
'param2' => 'string',
'date_change' => 'timestamp',
'date_from' => 'timestamp',
'date_to' => 'timestamp',
'custom_rank' => 'uint',
'tags' => 'mva',
'right' => 'mva',
'site' => 'mva',
'param' => 'mva',
];
private static $typesMap = [
'timestamp' => 'rt_attr_timestamp',
'string' => 'rt_attr_string',
'bigint' => 'rt_attr_bigint',
'uint' => 'rt_attr_uint',
'field' => 'rt_field',
'mva' => 'rt_attr_multi',
];
private $errorText = '';
private $errorNumber = 0;
public $tags = '';
public $query = '';
public $SITE_ID = '';
public $connectionIndex = '';
public $indexName = '';
public function connect($connectionIndex, $indexName = '', $ignoreErrors = false)
{
global $APPLICATION;
if (!preg_match('/^[a-zA-Z0-9_]+$/', $indexName))
{
if ($ignoreErrors)
{
$APPLICATION->ThrowException(GetMessage('SEARCH_SPHINX_CONN_ERROR_INDEX_NAME'));
}
else
{
throw new \Bitrix\Main\Db\ConnectionException('Sphinx connect error', GetMessage('SEARCH_SPHINX_CONN_ERROR_INDEX_NAME'));
}
return false;
}
if (!$this->canConnect())
{
if ($ignoreErrors)
{
$APPLICATION->ThrowException(GetMessage('SEARCH_SPHINX_CONN_EXT_IS_MISSING'));
}
else
{
throw new \Bitrix\Main\Db\ConnectionException('Sphinx connect error', GetMessage('SEARCH_SPHINX_CONN_EXT_IS_MISSING'));
}
return false;
}
$error = '';
$this->db = $this->internalConnect($connectionIndex, $error);
if (!$this->db)
{
if ($ignoreErrors)
{
$APPLICATION->ThrowException(GetMessage('SEARCH_SPHINX_CONN_ERROR', ['#ERRSTR#' => $error]));
}
else
{
throw new \Bitrix\Main\Db\ConnectionException('Sphinx connect error', GetMessage('SEARCH_SPHINX_CONN_ERROR', ['#ERRSTR#' => $error]));
}
return false;
}
if ($ignoreErrors)
{
$result = $this->query('SHOW TABLES');
if (!$result)
{
$APPLICATION->ThrowException(GetMessage('SEARCH_SPHINX_CONN_ERROR', ['#ERRSTR#' => $this->getError()]));
return false;
}
if ($indexName == '')
{
$APPLICATION->ThrowException(GetMessage('SEARCH_SPHINX_CONN_NO_INDEX'));
return false;
}
$indexType = '';
while ($res = $this->fetch($result))
{
if ($indexName === $res['Index'])
{
$indexType = $res['Type'];
}
}
if ($indexType == '')
{
$APPLICATION->ThrowException(GetMessage('SEARCH_SPHINX_CONN_INDEX_NOT_FOUND'));
return false;
}
if ($indexType != 'rt')
{
$APPLICATION->ThrowException(GetMessage('SEARCH_SPHINX_CONN_INDEX_WRONG_TYPE'));
return false;
}
$indexColumns = [];
$result = $this->query('DESCRIBE `' . $indexName . '`');
if (!$result)
{
$APPLICATION->ThrowException(GetMessage('SEARCH_SPHINX_DESCR_ERROR', ['#ERRSTR#' => $this->getError()]));
return false;
}
while ($res = $this->fetch($result))
{
$indexColumns[$res['Field']] = $res['Type'];
}
$missed = [];
foreach (self::$fields as $name => $type)
{
if (!isset($indexColumns[$name]) || $indexColumns[$name] !== $type)
{
$missed[] = self::$typesMap[$type] . ' = ' . $name;
}
}
if (!empty($missed))
{
$APPLICATION->ThrowException(GetMessage('SEARCH_SPHINX_NO_FIELDS', ['#FIELD_LIST#' => implode(', ', $missed)]));
return false;
}
}
$this->indexName = $indexName;
$this->connectionIndex = $connectionIndex;
return true;
}
public function truncate()
{
$this->query('truncate rtindex ' . $this->indexName);
$this->connect($this->connectionIndex, $this->indexName);
}
public function deleteById($ID)
{
$this->query('delete from ' . $this->indexName . ' where id = ' . intval($ID));
}
public function replace($ID, $arFields)
{
$DB = CDatabase::GetModuleConnection('search');
if (array_key_exists('~DATE_CHANGE', $arFields))
{
$arFields['DATE_CHANGE'] = $arFields['~DATE_CHANGE'];
unset($arFields['~DATE_CHANGE']);
}
elseif (array_key_exists('LAST_MODIFIED', $arFields))
{
$arFields['DATE_CHANGE'] = $arFields['LAST_MODIFIED'];
unset($arFields['LAST_MODIFIED']);
}
elseif (array_key_exists('DATE_CHANGE', $arFields))
{
$arFields['DATE_CHANGE'] = $DB->FormatDate($arFields['DATE_CHANGE'], 'DD.MM.YYYY HH:MI:SS', CLang::GetDateFormat());
}
$DATE_FROM = intval(MakeTimeStamp($arFields['DATE_FROM']));
if ($DATE_FROM > 0)
{
$DATE_FROM -= CTimeZone::GetOffset();
}
$DATE_TO = intval(MakeTimeStamp($arFields['DATE_TO']));
if ($DATE_TO > 0)
{
$DATE_TO -= CTimeZone::GetOffset();
}
$DATE_CHANGE = intval(MakeTimeStamp($arFields['DATE_CHANGE']));
if ($DATE_CHANGE > 0)
{
$DATE_CHANGE -= CTimeZone::GetOffset();
}
$BODY = CSearch::KillEntities($arFields['BODY']) . "\r\n" . $arFields['TAGS'];
$sql = '
REPLACE INTO ' . $this->indexName . ' (
id
,module_id
,module
,item_id
,item
,param1_id
,param1
,param2_id
,param2
,date_change
,date_from
,date_to
,custom_rank
,tags
,right
,site
,param
,title
,body
) VALUES (
' . $ID . '
,' . sprintf('%u', crc32($arFields['MODULE_ID'])) . "
,'" . $this->Escape($arFields['MODULE_ID']) . "'
," . sprintf('%u', crc32($arFields['ITEM_ID'])) . "
,'" . $this->Escape($arFields['ITEM_ID']) . "'
," . sprintf('%u', crc32($arFields['PARAM1'])) . "
,'" . $this->Escape($arFields['PARAM1']) . "'
," . sprintf('%u', crc32($arFields['PARAM2'])) . "
,'" . $this->Escape($arFields['PARAM2']) . "'
," . $DATE_CHANGE . '
,' . $DATE_FROM . '
,' . $DATE_TO . '
,' . intval($arFields['CUSTOM_RANK']) . '
,(' . $this->tags($arFields['SITE_ID'], $arFields['TAGS']) . ')
,(' . $this->rights($arFields['PERMISSIONS']) . ')
,(' . $this->sites($arFields['SITE_ID']) . ')
,(' . $this->params($arFields['PARAMS']) . ")
,'" . $this->Escape($arFields['TITLE']) . "'
,'" . $this->Escape($BODY) . "'
)
";
$result = $this->query($sql);
if ($result)
{
$this->tagsRegister($arFields['SITE_ID'], $arFields['TAGS']);
}
else
{
throw new \Bitrix\Main\Db\SqlQueryException('Sphinx select error', $this->getError(), $sql);
}
}
public function update($ID, $arFields)
{
$DB = CDatabase::GetModuleConnection('search');
$ID = intval($ID);
$arUpdate = [];
$bReplace = array_key_exists('TITLE', $arFields)
|| array_key_exists('BODY', $arFields)
|| array_key_exists('MODULE_ID', $arFields)
|| array_key_exists('ITEM_ID', $arFields)
|| array_key_exists('PARAM1', $arFields)
|| array_key_exists('PARAM2', $arFields)
;
if (array_key_exists('~DATE_CHANGE', $arFields))
{
$arFields['DATE_CHANGE'] = $arFields['~DATE_CHANGE'];
unset($arFields['~DATE_CHANGE']);
}
elseif (array_key_exists('LAST_MODIFIED', $arFields))
{
$arFields['DATE_CHANGE'] = $arFields['LAST_MODIFIED'];
unset($arFields['LAST_MODIFIED']);
}
elseif (array_key_exists('DATE_CHANGE', $arFields))
{
$arFields['DATE_CHANGE'] = $DB->FormatDate($arFields['DATE_CHANGE'], 'DD.MM.YYYY HH:MI:SS', CLang::GetDateFormat());
}
if (array_key_exists('DATE_CHANGE', $arFields))
{
$DATE_CHANGE = intval(MakeTimeStamp($arFields['DATE_CHANGE']));
if ($DATE_CHANGE > 0)
{
$DATE_CHANGE -= CTimeZone::GetOffset();
}
$arUpdate['date_change'] = $DATE_CHANGE;
}
if (array_key_exists('DATE_FROM', $arFields))
{
$DATE_FROM = intval(MakeTimeStamp($arFields['DATE_FROM']));
if ($DATE_FROM > 0)
{
$DATE_FROM -= CTimeZone::GetOffset();
}
$arUpdate['date_from'] = $DATE_FROM;
}
if (array_key_exists('DATE_TO', $arFields))
{
$DATE_TO = intval(MakeTimeStamp($arFields['DATE_TO']));
if ($DATE_TO > 0)
{
$DATE_TO -= CTimeZone::GetOffset();
}
$arUpdate['date_to'] = $DATE_TO;
}
if (array_key_exists('CUSTOM_RANK', $arFields))
{
$arUpdate['custom_rank'] = '' . intval($arFields['CUSTOM_RANK']) . '';
}
if (array_key_exists('TAGS', $arFields))
{
$arUpdate['tags'] = '(' . $this->tags($arFields['SITE_ID'], $arFields['TAGS']) . ')';
}
if (array_key_exists('PERMISSIONS', $arFields))
{
$arUpdate['right'] = '(' . $this->rights($arFields['PERMISSIONS']) . ')';
}
if (array_key_exists('SITE_ID', $arFields))
{
$arUpdate['site'] = '(' . $this->sites($arFields['SITE_ID']) . ')';
}
if (array_key_exists('PARAMS', $arFields))
{
$arUpdate['param'] = '(' . $this->params($arFields['PARAMS']) . ')';
}
if (!empty($arUpdate) && !$bReplace)
{
foreach ($arUpdate as $columnName => $sqlValue)
{
$arUpdate[$columnName] = $columnName . '=' . $sqlValue;
}
$this->query('
UPDATE ' . $this->indexName . ' SET
' . implode(', ', $arUpdate) . '
WHERE id = ' . $ID
);
}
elseif ($bReplace)
{
$dbItem = $DB->Query('
SELECT
*
,' . $DB->DateToCharFunction('DATE_CHANGE') . ' as LAST_MODIFIED
,' . $DB->DateToCharFunction('DATE_FROM') . ' as DATE_FROM
,' . $DB->DateToCharFunction('DATE_TO') . ' as DATE_TO
FROM b_search_content
WHERE ID = ' . $ID
);
$searchItem = $dbItem->fetch();
if ($searchItem)
{
$dbTags = $DB->Query('SELECT * from b_search_tags WHERE SEARCH_CONTENT_ID=' . $ID);
while ($tag = $dbTags->fetch())
{
$searchItem['TAGS'] .= $tag['NAME'] . ',';
}
$dbRights = $DB->Query('SELECT * from b_search_content_right WHERE SEARCH_CONTENT_ID=' . $ID);
while ($right = $dbRights->fetch())
{
$searchItem['PERMISSIONS'][] = $right['GROUP_CODE'];
}
$dbSites = $DB->Query('SELECT * from b_search_content_site WHERE SEARCH_CONTENT_ID=' . $ID);
while ($site = $dbSites->fetch())
{
$searchItem['SITE_ID'][$site['SITE_ID']] = $site['URL'];
}
$dbParams = $DB->Query('SELECT * from b_search_content_param WHERE SEARCH_CONTENT_ID=' . $ID);
while ($param = $dbParams->fetch())
{
$searchItem['PARAMS'][$param['PARAM_NAME']][] = $param['PARAM_VALUE'];
}
$this->replace($ID, $searchItem);
}
}
}
function tags($arLID, $sContent)
{
$tags = [];
if (is_array($arLID))
{
foreach ($arLID as $site_id => $url)
{
$arTags = tags_prepare($sContent, $site_id);
foreach ($arTags as $tag)
{
$tags[] = sprintf('%u', crc32($tag));
}
}
}
return implode(',', $tags);
}
function tagsRegister($arLID, $sContent)
{
$DB = CDatabase::GetModuleConnection('search');
static $tagMap = [];
if (is_array($arLID))
{
foreach ($arLID as $site_id => $url)
{
$arTags = tags_prepare($sContent, $site_id);
foreach ($arTags as $tag)
{
$tag_id = sprintf('%u', crc32($tag));
if ($tag_id > 0x7FFFFFFF)
{
$tag_id = -(0xFFFFFFFF - $tag_id + 1);
}
if (!isset($tagMap[$tag_id]))
{
$rs = $DB->Query('select * from b_search_tags where SEARCH_CONTENT_ID=' . $tag_id . " AND SITE_ID='??'");
$tagMap[$tag_id] = $rs->fetch();
if (!$tagMap[$tag_id])
{
$DB->Query('insert into b_search_tags values (' . $tag_id . ", '??', '" . $DB->ForSql($tag) . "')");
}
}
}
}
}
}
function tagsFromArray($arTags)
{
$tags = [];
if (is_array($arTags))
{
foreach ($arTags as $tag)
{
$tags[] = sprintf('%u', crc32($tag));
}
}
return implode(',', $tags);
}
function rights($arRights)
{
$rights = [];
if (is_array($arRights))
{
foreach ($arRights as $group_id)
{
if (is_numeric($group_id))
{
$rights[$group_id] = sprintf('%u', crc32('G' . intval($group_id)));
}
else
{
$rights[$group_id] = sprintf('%u', crc32($group_id));
}
}
}
return implode(',', $rights);
}
function sites($arSites)
{
$sites = [];
if (is_array($arSites))
{
foreach ($arSites as $site_id => $url)
{
$sites[$site_id] = sprintf('%u', crc32($site_id));
}
}
else
{
$sites[$arSites] = sprintf('%u', crc32($arSites));
}
return implode(',', $sites);
}
function params($arParams)
{
$params = [];
if (is_array($arParams))
{
foreach ($arParams as $k1 => $v1)
{
$name = trim($k1);
if ($name != '')
{
if (!is_array($v1))
{
$v1 = [$v1];
}
foreach ($v1 as $v2)
{
$value = trim($v2);
if ($value != '')
{
$params[] = sprintf('%u', crc32(urlencode($name) . '=' . urlencode($value)));
}
}
}
}
}
return implode(',', $params);
}
public function getErrorText()
{
return $this->errorText;
}
public function getErrorNumber()
{
return $this->errorNumber;
}
public function search($arParams, $aSort, $aParamsEx, $bTagsCloud)
{
$result = [];
$this->errorText = '';
$this->errorNumber = 0;
$this->tags = trim($arParams['TAGS']);
$limit = 0;
if (is_array($aParamsEx) && isset($aParamsEx['LIMIT']))
{
$limit = intval($aParamsEx['LIMIT']);
unset($aParamsEx['LIMIT']);
}
$offset = 0;
if (is_array($aParamsEx) && isset($aParamsEx['OFFSET']))
{
$offset = intval($aParamsEx['OFFSET']);
unset($aParamsEx['OFFSET']);
}
if (is_array($aParamsEx) && !empty($aParamsEx))
{
$aParamsEx['LOGIC'] = 'OR';
$arParams[] = $aParamsEx;
}
$this->SITE_ID = $arParams['SITE_ID'];
$arWhere = [];
$cond1 = implode("\n\t\t\t\t\t\tand ", $this->prepareFilter($arParams, true));
$rights = $this->CheckPermissions();
if ($rights)
{
$arWhere[] = 'right in (' . $rights . ')';
}
$strQuery = trim($arParams['QUERY']);
if ($strQuery != '')
{
$arWhere[] = "MATCH('" . $this->Escape($strQuery) . "')";
$this->query = $strQuery;
}
if ($cond1 != '')
{
$arWhere[] = 'cond1 = 1';
}
if ($strQuery || $this->tags || $bTagsCloud)
{
if ($limit <= 0)
{
$limit = intval(COption::GetOptionInt('search', 'max_result_size'));
}
if ($limit <= 0)
{
$limit = 500;
}
$ts = time() - CTimeZone::GetOffset();
if ($bTagsCloud)
{
$sql = '
select groupby() tag_id
,count(*) cnt
,max(date_change) dc_tmp
,if(date_to, date_to, ' . $ts . ') date_to_nvl
,if(date_from, date_from, ' . $ts . ') date_from_nvl
' . ($cond1 != '' ? ',' . $cond1 . ' as cond1' : '') . '
from ' . $this->indexName . '
where ' . implode("\nand\t", $arWhere) . '
group by tags
order by cnt desc
limit 0, ' . $limit . '
option max_matches = ' . $limit . '
';
$DB = CDatabase::GetModuleConnection('search');
$startTime = microtime(true);
$r = $this->query($sql);
if ($DB->ShowSqlStat)
{
$DB->addDebugQuery($sql, microtime(true) - $startTime);
}
if (!$r)
{
throw new \Bitrix\Main\Db\SqlQueryException('Sphinx select error', $this->getError(), $sql);
}
else
{
while ($res = $this->fetch($r))
{
$result[] = $res;
}
}
}
else
{
$sql = '
select id
,item
,param1
,param2
,module_id
,param2_id
,date_change
,custom_rank
,weight() as rank
' . ($cond1 != '' ? ',' . $cond1 . ' as cond1' : '') . '
,if(date_to, date_to, ' . $ts . ') date_to_nvl
,if(date_from, date_from, ' . $ts . ') date_from_nvl
from ' . $this->indexName . '
where ' . implode("\nand\t", $arWhere) . '
' . $this->__PrepareSort($aSort) . '
limit ' . $offset . ', ' . $limit . '
option max_matches = ' . ($offset + $limit) . '
';
$DB = CDatabase::GetModuleConnection('search');
$startTime = microtime(true);
$r = $this->query($sql);
if ($DB->ShowSqlStat)
{
$DB->addDebugQuery($sql, microtime(true) - $startTime);
}
if (!$r)
{
throw new \Bitrix\Main\Db\SqlQueryException('Sphinx select error', $this->getError(), $sql);
}
else
{
$forum = sprintf('%u', crc32('forum'));
while ($res = $this->fetch($r))
{
if ($res['module_id'] == $forum)
{
if (array_key_exists($res['param2_id'], $this->arForumTopics))
{
continue;
}
$this->arForumTopics[$res['param2_id']] = true;
}
$result[] = $res;
}
}
}
}
else
{
$this->errorText = GetMessage('SEARCH_ERROR3');
$this->errorNumber = 3;
}
return $result;
}
function searchTitle($phrase = '', $arPhrase = [], $nTopCount = 5, $arParams = [], $bNotFilter = false, $order = '')
{
$sqlWords = [];
foreach (array_reverse($arPhrase, true) as $word => $pos)
{
$word = $this->Escape($word);
if (empty($sqlWords) && !preg_match("/[\\n\\r \\t]$/", $phrase))
{
$sqlWords[] = $word . '*';
}
else
{
$sqlWords[] = $word;
}
}
$match = '@title ' . implode(' ', array_reverse($sqlWords));
$checkDates = false;
if (array_key_exists('CHECK_DATES', $arParams))
{
if ($arParams['CHECK_DATES'] == 'Y')
{
$checkDates = true;
}
unset($arParams['CHECK_DATES']);
}
$arWhere = $this->prepareFilter($arParams);
$cond1 = '';
if (isset($arWhere['cond1']))
{
$cond1 = $arWhere['cond1'];
unset($arWhere['cond1']);
}
$ts = time() - CTimeZone::GetOffset();
if ($checkDates)
{
$arWhere[] = 'date_from_nvl <= ' . $ts;
$arWhere[] = 'date_to_nvl >= ' . $ts;
}
$rights = $this->CheckPermissions();
if ($rights)
{
$arWhere[] = 'right in (' . $rights . ')';
}
$arWhere[] = 'site = ' . sprintf('%u', crc32(SITE_ID));
$arWhere[] = "match('" . $match . "')";
$sql = '
select id
,weight() as rank
' . ($cond1 != '' ? ',' . $cond1 . ' as cond1' : '') . '
,if(date_to, date_to, ' . $ts . ') date_to_nvl
,if(date_from, date_from, ' . $ts . ') date_from_nvl
from ' . $this->indexName . '
where ' . implode("\nand\t", $arWhere) . '
' . ($cond1 != '' ? ' and cond1 = ' . intval(!$bNotFilter) : '') . '
' . $this->__PrepareSort($order) . '
limit 0, ' . $nTopCount . '
option max_matches = ' . $nTopCount . '
';
$DB = CDatabase::GetModuleConnection('search');
$startTime = microtime(true);
$r = $this->query($sql);
if ($DB->ShowSqlStat)
{
$DB->addDebugQuery($sql, microtime(true) - $startTime);
}
if (!$r)
{
throw new \Bitrix\Main\Db\SqlQueryException('Sphinx select error', $this->getError(), $sql);
}
else
{
$result = [];
while ($res = $this->fetch($r))
{
$result[] = $res['id'];
}
return $result;
}
}
function getRowFormatter()
{
return new CSearchSphinxFormatter($this);
}
function filterField($field, $value, $inSelect)
{
$arWhere = [];
if (is_array($value))
{
if (!empty($value))
{
$s = '';
if ($inSelect)
{
foreach ($value as $i => $v)
{
$s .= ',' . sprintf('%u', crc32($v));
}
$arWhere[] = 'in(' . $field . ' ' . $s . ')';
}
else
{
foreach ($value as $i => $v)
{
$s .= ($s ? ' or ' : '') . $field . ' = ' . sprintf('%u', crc32($v));
}
$arWhere[] = '(' . $s . ')';
}
}
}
else
{
if ($value !== false)
{
$arWhere[] = $field . ' = ' . sprintf('%u', crc32($value));
}
}
return $arWhere;
}
function prepareFilter($arFilter, $inSelect = false)
{
$arWhere = [];
if (!is_array($arFilter))
{
$arFilter = [];
}
$orLogic = false;
if (array_key_exists('LOGIC', $arFilter))
{
$orLogic = ($arFilter['LOGIC'] == 'OR');
unset($arFilter['LOGIC']);
}
foreach ($arFilter as $field => $val)
{
$field = mb_strtoupper($field);
if (
is_array($val)
&& count($val) == 1
&& $field !== 'URL'
&& $field !== 'PARAMS'
&& !is_numeric($field)
)
{
$val = $val[0];
}
switch ($field)
{
case 'ITEM_ID':
case '=ITEM_ID':
$arWhere = array_merge($arWhere, $this->filterField('item_id', $val, $inSelect));
break;
case '!ITEM_ID':
if ($val !== false)
{
$arWhere[] = 'item_id <> ' . sprintf('%u', crc32($val));
}
break;
case 'MODULE_ID':
case '=MODULE_ID':
if ($val !== false && $val !== 'no')
{
$arWhere[] = 'module_id = ' . sprintf('%u', crc32($val));
}
break;
case 'PARAM1':
case '=PARAM1':
$arWhere = array_merge($arWhere, $this->filterField('param1_id', $val, $inSelect));
break;
case '!PARAM1':
case '!=PARAM1':
if ($val !== false)
{
$arWhere[] = 'param1_id <> ' . sprintf('%u', crc32($val));
}
break;
case 'PARAM2':
case '=PARAM2':
$arWhere = array_merge($arWhere, $this->filterField('param2_id', $val, $inSelect));
break;
case '!PARAM2':
case '!=PARAM2':
if ($val !== false)
{
$arWhere[] = 'param2_id <> ' . sprintf('%u', crc32($val));
}
break;
case 'DATE_CHANGE':
if ($val <> '')
{
$arWhere[] = 'date_change >= ' . intval(MakeTimeStamp($val) - CTimeZone::GetOffset());
}
break;
case '<=DATE_CHANGE':
if ($val <> '')
{
$arWhere[] = 'date_change <= ' . intval(MakeTimeStamp($val) - CTimeZone::GetOffset());
}
break;
case '>=DATE_CHANGE':
if ($val <> '')
{
$arWhere[] = 'date_change >= ' . intval(MakeTimeStamp($val) - CTimeZone::GetOffset());
}
break;
case 'SITE_ID':
if ($val !== false)
{
if ($inSelect)
{
$arWhere[] = 'in(site, ' . sprintf('%u', crc32($val)) . ')';
}
else
{
$arWhere[] = 'site = ' . sprintf('%u', crc32($val));
}
}
break;
case 'CHECK_DATES':
if ($val == 'Y')
{
$ts = time() - CTimeZone::GetOffset();
if ($inSelect)
{
$arWhere[] = 'if(date_from, date_from, ' . $ts . ') <= ' . $ts;
$arWhere[] = 'if(date_to, date_to, ' . $ts . ') >= ' . $ts;
}
else
{
$arWhere[] = 'date_from_nvl <= ' . $ts;
$arWhere[] = 'date_to_nvl >= ' . $ts;
}
}
break;
case 'TAGS':
$arTags = explode(',', $val);
foreach ($arTags as $i => &$strTag)
{
$strTag = trim($strTag, " \n\r\t\"");
if ($strTag == '')
{
unset($arTags[$i]);
}
}
unset($strTag);
$arWhere = array_merge($arWhere, $this->filterField('tags', $arTags, $inSelect));
break;
case 'PARAMS':
if (is_array($val))
{
$params = $this->params($val);
if ($params != '')
{
if ($inSelect)
{
$arWhere[] = 'in(param, ' . $params . ')';
}
else
{
foreach (explode(',', $params) as $param)
{
$arWhere[] = 'param = ' . $param;
}
}
}
}
break;
case 'URL': //TODO
case 'QUERY':
case 'LIMIT':
case 'USE_TF_FILTER':
break;
default:
if (is_numeric($field) && is_array($val))
{
$subFilter = $this->prepareFilter($val, true);
if (!empty($subFilter))
{
if (isset($subFilter['cond1']))
{
$arWhere['cond1'][] = '(' . implode(')and(', $subFilter) . ')';
}
else
{
$arWhere[] = '(' . implode(')and(', $subFilter) . ')';
}
}
}
else
{
//AddMessage2Log("field: $field; val: ".print_r($val, 1));
}
break;
}
}
if (isset($arWhere['cond1']))
{
$arWhere['cond1'] = '(' . implode(')and(', $arWhere['cond1']) . ')';
}
if ($orLogic && !empty($arWhere))
{
$arWhere = [
'cond1' => '(' . implode(')or(', $arWhere) . ')'
];
}
return $arWhere;
}
function CheckPermissions()
{
global $USER;
$DB = CDatabase::GetModuleConnection('search');
$arResult = [];
if (!$USER->IsAdmin())
{
if ($USER->GetID() > 0)
{
CSearchUser::CheckCurrentUserGroups();
$rs = $DB->Query('SELECT GROUP_CODE FROM b_search_user_right WHERE USER_ID = ' . $USER->GetID());
while ($ar = $rs->Fetch())
{
$arResult[] = $ar['GROUP_CODE'];
}
}
else
{
$arResult[] = 'G2';
}
}
return $this->rights($arResult);
}
function __PrepareSort($aSort = [])
{
$arOrder = [];
if (!is_array($aSort))
{
$aSort = [$aSort => 'ASC'];
}
$this->flagsUseRatingSort = 0;
foreach ($aSort as $key => $ord)
{
$ord = mb_strtoupper($ord) <> 'ASC' ? 'DESC' : 'ASC';
$key = mb_strtolower($key);
switch ($key)
{
case 'date_change':
case 'custom_rank':
case 'id':
case 'param1':
case 'param2':
case 'date_from':
case 'date_to':
$arOrder[] = $key . ' ' . $ord;
break;
case 'item_id':
$arOrder[] = 'item ' . $ord;
break;
case 'module_id':
$arOrder[] = 'module ' . $ord;
break;
case 'rank':
$arOrder[] = 'rank ' . $ord;
break;
}
}
if (count($arOrder) == 0)
{
$arOrder[] = 'custom_rank DESC';
$arOrder[] = 'rank DESC';
$arOrder[] = 'date_change DESC';
}
return ' ORDER BY ' . implode(', ',$arOrder);
}
public function Escape($str)
{
static $search = [
'\\',
"'",
'/',
')',
'(',
'$',
'~',
'!',
'@',
'^',
'-',
'|',
'<',
"\x0",
'=',
];
static $replace = [
'\\\\',
"\\'",
'\\\\/',
'\\\\)',
'\\\\(',
'\\\\$',
'\\\\~',
'\\\\!',
'\\\\@',
'\\\\^',
'\\\\-',
'\\\\|',
'\\\\<',
' ',
' ',
];
$str = str_replace($search, $replace, $str);
$stat = count_chars($str, 1);
if (isset($stat[ord('"')]) && $stat[ord('"')] % 2 === 1)
{
$str = str_replace('"', '\\\"', $str);
}
return $str;
}
public function Escape2($str)
{
static $search = [
'\\',
"'",
'"',
"\x0",
];
static $replace = [
'\\\\',
"\\'",
'\\\\"',
' ',
];
return str_replace($search, $replace, $str);
}
protected function canConnect()
{
return function_exists('mysqli_connect');
}
protected function internalConnect($connectionIndex, &$error)
{
$error = '';
if (function_exists('mysqli_connect'))
{
$result = mysqli_init();
if (mb_strpos($connectionIndex, ':') !== false)
{
list($host, $port) = explode(':', $connectionIndex, 2);
$port = intval($port);
}
else
{
$host = $connectionIndex;
$port = 0;
}
if ($port > 0)
{
if (!$result->real_connect($host, '', '', '', $port))
{
$error = mysqli_connect_error();
$result = false;
}
}
else
{
if (!$result->real_connect($host, '', '', ''))
{
$error = mysqli_connect_error();
$result = false;
}
}
}
else
{
$result = false;
$error = 'No MySql connection function has been found.';
}
return $result;
}
public function query($query)
{
if (is_object($this->db))
{
$result = $this->db->query($query);
}
else
{
$result = false;
}
return $result;
}
public function fetch($queryResult)
{
if (is_object($this->db))
{
$result = mysqli_fetch_assoc($queryResult);
}
else
{
$result = false;
}
return $result;
}
public function getError()
{
if (is_object($this->db))
{
$result = '[' . $this->db->errno . '] ' . $this->db->error;
}
else
{
$result = '';
}
return $result;
}
}
class CSearchSphinxFormatter extends CSearchFormatter
{
/** @var CSearchSphinx */
private $sphinx = null;
function __construct($sphinx)
{
$this->sphinx = $sphinx;
}
function format($r)
{
if ($r)
{
if (array_key_exists('tag_id', $r))
{
return $this->formatTagsRow($r);
}
elseif (array_key_exists('id', $r))
{
return $this->formatRow($r);
}
}
}
function formatTagsRow($r)
{
$DB = CDatabase::GetModuleConnection('search');
$tag_id = $r['tag_id'];
if ($tag_id > 0x7FFFFFFF)
{
$tag_id = -(0xFFFFFFFF - $tag_id + 1);
}
$rs = $DB->Query('
select
st.NAME
from b_search_tags st
where st.SEARCH_CONTENT_ID = ' . $tag_id . "
and st.SITE_ID = '??'
");
$rt = $rs->Fetch();
if ($rt)
{
$rt['NAME'] = htmlspecialcharsex($rt['NAME']);
$rt['CNT'] = $r['cnt'];
$rt['FULL_DATE_CHANGE'] = ConvertTimeStamp($r['dc_tmp'] + CTimeZone::GetOffset(), 'FULL');
$rt['DATE_CHANGE'] = ConvertTimeStamp($r['dc_tmp'] + CTimeZone::GetOffset(), 'SHORT');
}
return $rt;
}
function formatRow($r)
{
$DB = CDatabase::GetModuleConnection('search');
if ($this->sphinx->SITE_ID)
{
$rs = $DB->Query('
select
sc.ID
,sc.MODULE_ID
,sc.ITEM_ID
,sc.TITLE
,sc.TAGS
,sc.BODY
,sc.PARAM1
,sc.PARAM2
,sc.UPD
,sc.DATE_FROM
,sc.DATE_TO
,sc.URL
,sc.CUSTOM_RANK
,' . $DB->DateToCharFunction('sc.DATE_CHANGE') . ' as FULL_DATE_CHANGE
,' . $DB->DateToCharFunction('sc.DATE_CHANGE', 'SHORT') . ' as DATE_CHANGE
,scsite.SITE_ID
,scsite.URL SITE_URL
,sc.USER_ID
from b_search_content sc
INNER JOIN b_search_content_site scsite ON sc.ID=scsite.SEARCH_CONTENT_ID
where ID = ' . $r['id'] . "
and scsite.SITE_ID = '" . $DB->ForSql($this->sphinx->SITE_ID) . "'
");
}
else
{
$rs = $DB->Query('
select
sc.ID
,sc.MODULE_ID
,sc.ITEM_ID
,sc.TITLE
,sc.TAGS
,sc.BODY
,sc.PARAM1
,sc.PARAM2
,sc.UPD
,sc.DATE_FROM
,sc.DATE_TO
,sc.URL
,sc.CUSTOM_RANK
,' . $DB->DateToCharFunction('sc.DATE_CHANGE') . ' as FULL_DATE_CHANGE
,' . $DB->DateToCharFunction('sc.DATE_CHANGE', 'SHORT') . ' as DATE_CHANGE
,\'\' as SITE_ID
from b_search_content sc
where ID = ' . $r['id'] . '
');
}
$r = $rs->Fetch();
if ($r)
{
$r['TITLE_FORMATED'] = $this->buildExcerpts(htmlspecialcharsex($r['TITLE']));
$r['TITLE_FORMATED_TYPE'] = 'html';
$r['TAGS_FORMATED'] = tags_prepare($r['TAGS'], SITE_ID);
$r['BODY_FORMATED'] = $this->buildExcerpts(htmlspecialcharsex($r['BODY']));
$r['BODY_FORMATED_TYPE'] = 'html';
}
return $r;
}
public function buildExcerpts($str)
{
$sql = "CALL SNIPPETS(
'" . $this->sphinx->Escape2($str) . "'
,'" . $this->sphinx->Escape($this->sphinx->indexName) . "'
,'" . $this->sphinx->Escape($this->sphinx->query . ' ' . $this->sphinx->tags) . "'
,500 as limit
,1 as query_mode
)";
$result = $this->sphinx->query($sql);
if ($result)
{
$res = $this->sphinx->fetch($result);
if ($res)
{
return $res['snippet'];
}
else
{
return '';
}
}
else
{
throw new \Bitrix\Main\Db\SqlQueryException('Sphinx select error', $this->sphinx->getError(), $sql);
}
}
}