1434 lines
31 KiB
PHP
1434 lines
31 KiB
PHP
<?php
|
|
IncludeModuleLangFile(__FILE__);
|
|
|
|
class CSearchOpenSearch extends CSearchFullText
|
|
{
|
|
public $arForumTopics = [];
|
|
private $errorText = '';
|
|
private $errorNumber = 0;
|
|
public $tags = '';
|
|
public $SITE_ID = '';
|
|
public $connectionString = '';
|
|
protected $user = '';
|
|
protected $password = '';
|
|
public $indexName = '';
|
|
public $siteAnalyzerMap = [];
|
|
|
|
public function connect($connectionString, $user = '', $password = '', $indexName = '', $ignoreErrors = false, $siteAnalyzerMap = '')
|
|
{
|
|
global $APPLICATION;
|
|
|
|
if (!preg_match('/^[a-zA-Z0-9_-]+$/', $indexName))
|
|
{
|
|
if ($ignoreErrors)
|
|
{
|
|
$APPLICATION->ThrowException(GetMessage('SEARCH_OPENSEARCH_CONN_ERROR_INDEX_NAME'));
|
|
}
|
|
else
|
|
{
|
|
throw new \Bitrix\Main\Db\ConnectionException('OpenSearch connect error', GetMessage('SEARCH_OPENSEARCH_CONN_ERROR_INDEX_NAME'));
|
|
}
|
|
return false;
|
|
}
|
|
|
|
$error = '';
|
|
$server = new Bitrix\Main\Web\HttpClient([
|
|
'disableSslVerification' => true,
|
|
]);
|
|
|
|
$server->setAuthorization($user, $password);
|
|
$strJson = $server->get($connectionString);
|
|
if ($server->getStatus() !== 200 || !is_array(json_decode($strJson, true)))
|
|
{
|
|
$error = $strJson;
|
|
if ($ignoreErrors)
|
|
{
|
|
$APPLICATION->ThrowException(GetMessage('SEARCH_OPENSEARCH_CONN_ERROR', ['#ERRSTR#' => $error]));
|
|
}
|
|
else
|
|
{
|
|
throw new \Bitrix\Main\Db\ConnectionException('OpenSearch connect error', GetMessage('SEARCH_OPENSEARCH_CONN_ERROR', ['#ERRSTR#' => $error]));
|
|
}
|
|
return false;
|
|
}
|
|
|
|
$this->connectionString = $connectionString;
|
|
$this->user = $user;
|
|
$this->password = $password;
|
|
|
|
$this->siteAnalyzerMap = $siteAnalyzerMap ?: [];
|
|
if (!$this->siteAnalyzerMap)
|
|
{
|
|
$langs = CLang::GetList();
|
|
while ($site = $langs->Fetch())
|
|
{
|
|
$analyzer = COption::GetOptionString('search', 'opensearch_analyzer_' . $site['ID']);
|
|
if (!$analyzer)
|
|
{
|
|
$analyzer = array_search($site['LANGUAGE_ID'], static::getLanguageAnalyzers()) ?: 'english';
|
|
}
|
|
$this->siteAnalyzerMap[$site['ID']] = $analyzer;
|
|
}
|
|
}
|
|
|
|
if ($ignoreErrors)
|
|
{
|
|
foreach ($this->siteAnalyzerMap as $siteId => $analyzer)
|
|
{
|
|
if (!$this->checkIndexTemplate($indexName, $siteId, $analyzer))
|
|
{
|
|
$APPLICATION->ThrowException(GetMessage('SEARCH_OPENSEARCH_CONN_ERROR', ['#ERRSTR#' => $this->getError()]));
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
$this->indexName = $indexName;
|
|
$this->connectionIndex = $connectionIndex;
|
|
|
|
return true;
|
|
}
|
|
|
|
// https://opensearch.org/docs/latest/analyzers/supported-analyzers/index/
|
|
public static function getLanguageAnalyzers()
|
|
{
|
|
return [
|
|
'arabic' => 'ar',
|
|
'armenian' => '',
|
|
'basque' => '',
|
|
'bengali' => '',
|
|
'brazilian' => 'br',
|
|
'bulgarian' => '',
|
|
'catalan' => '',
|
|
'czech' => '',
|
|
'danish' => '',
|
|
'dutch' => '',
|
|
'english' => 'en',
|
|
'estonian' => '',
|
|
'finnish' => '',
|
|
'french' => 'fr',
|
|
'galician' => '',
|
|
'german' => 'de',
|
|
'greek' => '',
|
|
'hindi' => '',
|
|
'hungarian' => '',
|
|
'indonesian' => 'ms',
|
|
'irish' => '',
|
|
'italian' => 'it',
|
|
'latvian' => '',
|
|
'lithuanian' => 'lt',
|
|
'norwegian' => '',
|
|
'persian' => '',
|
|
'portuguese' => '',
|
|
'romanian' => '',
|
|
'russian' => 'ru',
|
|
'sorani' => '',
|
|
'spanish' => 'la',
|
|
'swedish' => '',
|
|
'thai' => 'th',
|
|
'turkish' => 'tr',
|
|
];
|
|
}
|
|
|
|
protected $version = 27;
|
|
protected function checkIndexTemplate($indexName, $siteId, $analyzer, $adminNotify = true)
|
|
{
|
|
$templateName = $indexName . '-' . $siteId. '-template';
|
|
$result = $this->query('GET', '/_index_template/' . $templateName);
|
|
if (!$result && $this->errorNumber !== 404)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
$versionMatch = (
|
|
isset($result['index_templates'][0]['index_template']['version'])
|
|
&& $result['index_templates'][0]['index_template']['version'] === $this->version
|
|
);
|
|
|
|
$analyzerMatch = true;
|
|
if ($analyzer)
|
|
{
|
|
$analyzerMatch = (
|
|
isset($result['index_templates'][0]['index_template']['template']['mappings']['properties']['body']['analyzer'])
|
|
&& $result['index_templates'][0]['index_template']['template']['mappings']['properties']['body']['analyzer'] === $analyzer
|
|
);
|
|
}
|
|
else
|
|
{
|
|
$analyzerMatch = !isset($result['index_templates'][0]['index_template']['template']['mappings']['properties']['body']['analyzer']);
|
|
}
|
|
|
|
$updateNeeded = !$versionMatch || !$analyzerMatch;
|
|
|
|
if ($updateNeeded)
|
|
{
|
|
$result = $this->query('DELETE', '/_index_template/' . $templateName);
|
|
}
|
|
|
|
if ($this->errorNumber === 404 || $updateNeeded)
|
|
{
|
|
$template = [
|
|
'mappings' => [
|
|
'properties' => [
|
|
'id' => [
|
|
'type' => 'integer',
|
|
],
|
|
'date_change' => [
|
|
'type' => 'date',
|
|
'format' => 'date_time_no_millis',
|
|
],
|
|
'date_change_ts' => [
|
|
'type' => 'integer',
|
|
],
|
|
'module_id' => [
|
|
'type' => 'keyword',
|
|
],
|
|
'item_id' => [
|
|
'type' => 'keyword',
|
|
],
|
|
'custom_rank' => [
|
|
'type' => 'integer',
|
|
],
|
|
// user_id
|
|
// entity_type_id
|
|
// entity_id
|
|
// url
|
|
'title' => [
|
|
'type' => 'text',
|
|
],
|
|
'body' => [
|
|
'type' => 'text',
|
|
],
|
|
// tags
|
|
'param1' => [
|
|
'type' => 'keyword',
|
|
],
|
|
'param2' => [
|
|
'type' => 'keyword',
|
|
],
|
|
// upd
|
|
'date_from' => [
|
|
'type' => 'date',
|
|
'format' => 'date_time_no_millis',
|
|
],
|
|
'date_from_ts' => [
|
|
'type' => 'integer',
|
|
],
|
|
'date_to' => [
|
|
'type' => 'date',
|
|
'format' => 'date_time_no_millis',
|
|
],
|
|
'date_to_ts' => [
|
|
'type' => 'integer',
|
|
],
|
|
'tag' => [
|
|
'type' => 'keyword', // array
|
|
],
|
|
'xright' => [ // Can not use right here because sql plugin makes it uppercase
|
|
'type' => 'keyword', // array
|
|
],
|
|
'param' => [
|
|
'properties' => [],
|
|
'dynamic' => true,
|
|
],
|
|
]
|
|
]
|
|
];
|
|
|
|
if ($analyzer)
|
|
{
|
|
$template['mappings']['properties']['body']['analyzer'] = $analyzer;
|
|
}
|
|
|
|
$result = $this->query('PUT', '/_index_template/' . $templateName, [
|
|
'index_patterns' => [
|
|
$indexName . '-' . $siteId,
|
|
],
|
|
'version' => $this->version,
|
|
'template' => $template
|
|
]);
|
|
|
|
if ($adminNotify)
|
|
{
|
|
$error = [
|
|
'MESSAGE' => GetMessage("SEARCH_OPENSEARCH_REINDEX", ['#LINK#' => '/bitrix/admin/search_reindex.php?lang=' . LANGUAGE_ID]),
|
|
'TAG' => 'SEARCH_REINDEX',
|
|
'MODULE_ID' => 'SEARCH',
|
|
'NOTIFY_TYPE' => CAdminNotify::TYPE_ERROR,
|
|
];
|
|
CAdminNotify::Add($error);
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static $siteIdChecked = [];
|
|
protected function checkTemplateBySiteId($siteId)
|
|
{
|
|
global $CACHE_MANAGER;
|
|
|
|
if (isset(static::$siteIdChecked[$siteId]))
|
|
{
|
|
return;
|
|
}
|
|
static::$siteIdChecked[$siteId] = true;
|
|
|
|
$cacheId = 'opensearch-template-' . $siteId;
|
|
if ($CACHE_MANAGER->Read(CACHED_opensearch_template, $cacheId, 'opensearch'))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (isset($this->siteAnalyzerMap[$siteId]))
|
|
{
|
|
$this->checkIndexTemplate($this->indexName, $siteId, $this->siteAnalyzerMap[$siteId], false);
|
|
}
|
|
|
|
$CACHE_MANAGER->Set($cacheId, true);
|
|
}
|
|
|
|
public function truncate()
|
|
{
|
|
$this->query('DELETE', '/' . $this->indexName . '-*?expand_wildcards=all');
|
|
}
|
|
|
|
public function deleteById($ID)
|
|
{
|
|
foreach ($this->siteAnalyzerMap as $siteId => $_)
|
|
{
|
|
$this->query('DELETE', '/' . $this->indexName . '-' . $siteId . '/_doc/' . 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']));
|
|
$DATE_TO = intval(MakeTimeStamp($arFields['DATE_TO']));
|
|
$DATE_CHANGE = intval(MakeTimeStamp($arFields['DATE_CHANGE']));
|
|
|
|
$BODY = ($arFields['TITLE'] ? CSearch::KillEntities($arFields['TITLE']) . "\n" : '')
|
|
. CSearch::KillEntities($arFields['BODY'])
|
|
. ($arFields['TAGS'] ? "\n" . $arFields['TAGS'] : '')
|
|
;
|
|
|
|
$sites = $this->sites($arFields['SITE_ID']);
|
|
foreach ($this->siteAnalyzerMap as $siteId => $_)
|
|
{
|
|
if (in_array($siteId, $sites))
|
|
{
|
|
$doc = [
|
|
'id' => $ID,
|
|
'date_change' => $DATE_CHANGE ? date('c', $DATE_CHANGE) : null,
|
|
'date_change_ts' => $DATE_CHANGE ? $DATE_CHANGE - CTimeZone::GetOffset() : 0,
|
|
'module_id' => $arFields['MODULE_ID'],
|
|
'item_id' => $arFields['ITEM_ID'],
|
|
'custom_rank' => intval($arFields['CUSTOM_RANK']),
|
|
'title' => $arFields['TITLE'],
|
|
'body' => $BODY,
|
|
'param1' => $arFields['PARAM1'],
|
|
'param2' => $arFields['PARAM2'],
|
|
'date_from' => $DATE_FROM ? date('c', $DATE_FROM) : null,
|
|
'date_from_ts' => $DATE_FROM ? $DATE_FROM - CTimeZone::GetOffset() : 0,
|
|
'date_to' => $DATE_TO ? date('c', $DATE_TO) : null,
|
|
'date_to_ts' => $DATE_TO ? $DATE_TO - CTimeZone::GetOffset() : 0,
|
|
'tag' => $this->tags($arFields['SITE_ID'], $arFields['TAGS']),
|
|
'xright' => $this->rights($arFields['PERMISSIONS']),
|
|
'param' => $this->params($arFields['PARAMS']),
|
|
];
|
|
$this->checkTemplateBySiteId($siteId);
|
|
$result = $this->query('PUT', '/' . $this->indexName . '-' . $siteId . '/_doc/' . intval($ID), $doc);
|
|
if (!$result)
|
|
{
|
|
throw new \Bitrix\Main\Db\SqlQueryException('OpenSearch index error', $this->getError(), '');
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$result = $this->query('DELETE', '/' . $this->indexName . '-' . $siteId . '/_doc/' . intval($ID));
|
|
}
|
|
}
|
|
}
|
|
|
|
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']));
|
|
$arUpdate['date_change'] = $DATE_CHANGE > 0 ? date('c', $DATE_CHANGE - CTimeZone::GetOffset()) : null;
|
|
$arUpdate['date_change_ts'] = $DATE_CHANGE > 0 ? $DATE_CHANGE : 0;
|
|
}
|
|
|
|
if (array_key_exists('DATE_FROM', $arFields))
|
|
{
|
|
$DATE_FROM = intval(MakeTimeStamp($arFields['DATE_FROM']));
|
|
$arUpdate['date_from'] = $DATE_FROM > 0 ? date('c', $DATE_FROM - CTimeZone::GetOffset()) : null;
|
|
$arUpdate['date_from_ts'] = $DATE_FROM > 0 ? $DATE_FROM : 0;
|
|
}
|
|
|
|
if (array_key_exists('DATE_TO', $arFields))
|
|
{
|
|
$DATE_TO = intval(MakeTimeStamp($arFields['DATE_TO']));
|
|
$arUpdate['date_to'] = $DATE_TO > 0 ? date('c', $DATE_TO - CTimeZone::GetOffset()) : null;
|
|
$arUpdate['date_to_ts'] = $DATE_TO > 0 ? $DATE_TO : 0;
|
|
}
|
|
|
|
if (array_key_exists('CUSTOM_RANK', $arFields))
|
|
{
|
|
$arUpdate['custom_rank'] = $arFields['CUSTOM_RANK'] > 0 ? intval($arFields['CUSTOM_RANK']) : 0;
|
|
}
|
|
|
|
if (array_key_exists('TAGS', $arFields))
|
|
{
|
|
$arUpdate['tag'] = $this->tags($arFields['SITE_ID'], $arFields['TAGS']);
|
|
}
|
|
|
|
if (array_key_exists('PERMISSIONS', $arFields))
|
|
{
|
|
$arUpdate['xright'] = $this->rights($arFields['PERMISSIONS']);
|
|
}
|
|
|
|
if (array_key_exists('PARAMS', $arFields))
|
|
{
|
|
$arUpdate['param'] = $this->params($arFields['PARAMS']);
|
|
}
|
|
|
|
if (array_key_exists('SITE_ID', $arFields))
|
|
{
|
|
$sites = $this->sites($arFields['SITE_ID']);
|
|
}
|
|
else
|
|
{
|
|
$sites = [];
|
|
$dbSites = $DB->Query('SELECT * from b_search_content_site WHERE SEARCH_CONTENT_ID=' . $ID);
|
|
while ($site = $dbSites->fetch())
|
|
{
|
|
$sites[$site['SITE_ID']] = $site['SITE_ID'];
|
|
}
|
|
}
|
|
|
|
if (!empty($arUpdate) && !$bReplace)
|
|
{
|
|
foreach ($this->siteAnalyzerMap as $siteId => $_)
|
|
{
|
|
if (in_array($siteId, $sites))
|
|
{
|
|
$result = $this->query('POST', '/' . $this->indexName . '-' . $siteId . '/_update/' . intval($ID), [
|
|
'doc' => $arUpdate,
|
|
]);
|
|
}
|
|
else
|
|
{
|
|
$result = $this->query('DELETE', '/' . $this->indexName . '-' . $siteId . '/_doc/' . intval($ID));
|
|
}
|
|
}
|
|
}
|
|
elseif ($bReplace)
|
|
{
|
|
$dbItem = $DB->Query('SELECT * FROM b_search_content WHERE ID = ' . $ID);
|
|
$searchItem = $dbItem->fetch();
|
|
if ($searchItem)
|
|
{
|
|
$arTags = [];
|
|
$dbTags = $DB->Query('SELECT * from b_search_tags WHERE SEARCH_CONTENT_ID=' . $ID);
|
|
while ($tag = $dbTags->fetch())
|
|
{
|
|
$arTags[] = $tag['NAME'];
|
|
}
|
|
$searchItem['TAGS'] = $arTags ? implode(',', $arTags) : null;
|
|
|
|
$searchItem['PERMISSIONS'] = [];
|
|
$dbRights = $DB->Query('SELECT * from b_search_content_right WHERE SEARCH_CONTENT_ID=' . $ID);
|
|
while ($right = $dbRights->fetch())
|
|
{
|
|
$searchItem['PERMISSIONS'][] = $right['GROUP_CODE'];
|
|
}
|
|
|
|
$searchItem['SITE_ID'] = [];
|
|
$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'];
|
|
}
|
|
|
|
$searchItem['PARAMS'] = [];
|
|
$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[$tag] = $tag;
|
|
}
|
|
}
|
|
}
|
|
|
|
return array_values($tags);
|
|
}
|
|
|
|
function rights($arRights)
|
|
{
|
|
$rights = [];
|
|
if (is_array($arRights))
|
|
{
|
|
foreach ($arRights as $group_id)
|
|
{
|
|
if (is_numeric($group_id))
|
|
{
|
|
$rights[$group_id] = 'G' . intval($group_id);
|
|
}
|
|
else
|
|
{
|
|
$rights[$group_id] = $group_id;
|
|
}
|
|
}
|
|
}
|
|
|
|
return array_values($rights);
|
|
}
|
|
|
|
function sites($arSites)
|
|
{
|
|
$sites = [];
|
|
if (is_array($arSites))
|
|
{
|
|
foreach ($arSites as $site_id => $url)
|
|
{
|
|
$sites[$site_id] = $site_id;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$sites[$arSites] = $arSites;
|
|
}
|
|
|
|
return array_values($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[$name] = $value;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return $params;
|
|
}
|
|
|
|
public function getErrorText()
|
|
{
|
|
return $this->errorText;
|
|
}
|
|
|
|
public function getErrorNumber()
|
|
{
|
|
return $this->errorNumber;
|
|
}
|
|
|
|
public function search($arParams, $aSort, $aParamsEx, $bTagsCloud)
|
|
{
|
|
$DB = CDatabase::GetModuleConnection('search');
|
|
|
|
$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']);
|
|
}
|
|
if ($limit <= 0)
|
|
{
|
|
$limit = intval(COption::GetOptionInt('search', 'max_result_size'));
|
|
}
|
|
if ($limit <= 0)
|
|
{
|
|
$limit = 500;
|
|
}
|
|
|
|
$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'];
|
|
unset($arParams['SITE_ID']);
|
|
|
|
$query = [
|
|
'_source' => false,
|
|
'query' => [
|
|
'bool' => [
|
|
'must' => [
|
|
],
|
|
],
|
|
],
|
|
'from' => $offset,
|
|
'size' => $limit,
|
|
];
|
|
|
|
$strQuery = trim($arParams['QUERY']);
|
|
if ($strQuery != '')
|
|
{
|
|
$query['query']['bool']['must'][] = [
|
|
'match' => [
|
|
'body' => [
|
|
'query' => $strQuery,
|
|
],
|
|
],
|
|
];
|
|
}
|
|
|
|
$rights = $this->CheckPermissions();
|
|
if ($rights)
|
|
{
|
|
$query['query']['bool']['filter'] = [
|
|
'bool' => [
|
|
'must' => [
|
|
[
|
|
'terms' => [
|
|
'xright' => $rights,
|
|
],
|
|
],
|
|
],
|
|
],
|
|
];
|
|
}
|
|
|
|
$arWhere = $this->prepareFilter($arParams);
|
|
if ($arWhere)
|
|
{
|
|
$query['query']['bool']['must'][] = $arWhere;
|
|
}
|
|
|
|
if ($strQuery || $this->tags || $bTagsCloud)
|
|
{
|
|
if ($bTagsCloud)
|
|
{
|
|
$query['size'] = 0; // we don't need hits
|
|
$query['aggregations'] = [
|
|
'tags' => [
|
|
'terms' => [
|
|
'field' => 'tag',
|
|
'size' => $limit,
|
|
],
|
|
'aggregations' => [
|
|
'max_dc' => [
|
|
'max' => [
|
|
'field' => 'date_change_ts',
|
|
],
|
|
],
|
|
],
|
|
],
|
|
];
|
|
|
|
$r = $this->query('GET', '/' . $this->indexName . '-' . $this->SITE_ID . '/_search', $query);
|
|
if (!$r)
|
|
{
|
|
throw new \Bitrix\Main\Db\SqlQueryException('OpenSearch select error', $this->getError(), $sql);
|
|
}
|
|
else
|
|
{
|
|
if (
|
|
is_array($r)
|
|
&& isset($r['aggregations'])
|
|
&& is_array($r['aggregations'])
|
|
&& isset($r['aggregations']['tags'])
|
|
&& is_array($r['aggregations']['tags'])
|
|
)
|
|
{
|
|
foreach ($r['aggregations']['tags']['buckets'] as $searcBucket)
|
|
{
|
|
$result[] = [
|
|
'NAME' => $searcBucket['key'],
|
|
'CNT' => $searcBucket['doc_count'],
|
|
'FULL_DATE_CHANGE' => ConvertTimeStamp($searcBucket['max_dc']['value'] + CTimeZone::GetOffset(), 'FULL'),
|
|
'DATE_CHANGE' => ConvertTimeStamp($searcBucket['max_dc']['value'] + CTimeZone::GetOffset(), 'SHORT'),
|
|
];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$order = $this->__PrepareSort($order);
|
|
if ($order)
|
|
{
|
|
$query['sort'] = $order;
|
|
}
|
|
|
|
$query['fields'] = ['id', 'module_id', 'param2'];
|
|
|
|
$r = $this->query('GET', '/' . $this->indexName . '-' . $this->SITE_ID . '/_search', $query);
|
|
if (!$r)
|
|
{
|
|
throw new \Bitrix\Main\Db\SqlQueryException('OpenSearch select error', $this->getError(), json_encode($query));
|
|
}
|
|
else
|
|
{
|
|
$this->arForumTopics = [];
|
|
if (
|
|
is_array($r)
|
|
&& isset($r['hits'])
|
|
&& is_array($r['hits'])
|
|
&& isset($r['hits']['hits'])
|
|
&& is_array($r['hits']['hits'])
|
|
)
|
|
{
|
|
foreach ($r['hits']['hits'] as $searchHit)
|
|
{
|
|
if ($searchHit['fields']['module_id'][0] == 'FORUM')
|
|
{
|
|
if (array_key_exists($searchHit['fields']['param2'][0], $this->arForumTopics))
|
|
{
|
|
continue;
|
|
}
|
|
$this->arForumTopics[$searchHit['fields']['param2'][0]] = true;
|
|
}
|
|
$result[] = [
|
|
'ID' => $searchHit['fields']['id'][0],
|
|
];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$this->errorText = GetMessage('SEARCH_ERROR3');
|
|
$this->errorNumber = 3;
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
function searchTitle($phrase = '', $arPhrase = [], $nTopCount = 5, $arParams = [], $bNotFilter = false, $order = '')
|
|
{
|
|
$query = [
|
|
'_source' => false,
|
|
'fields' => ['id'],
|
|
'query' => [
|
|
'bool' => [
|
|
'must' => [
|
|
[
|
|
'match_phrase_prefix' => [
|
|
'title' => $phrase,
|
|
],
|
|
],
|
|
],
|
|
],
|
|
],
|
|
'size' => $nTopCount,
|
|
];
|
|
|
|
$site_id = SITE_ID;
|
|
if (is_array($arParams) && isset($arParams['SITE_ID']))
|
|
{
|
|
$site_id = $arParams['SITE_ID'];
|
|
unset($arParams['SITE_ID']);
|
|
}
|
|
|
|
$arWhere = $this->prepareFilter($arParams);
|
|
if ($arWhere)
|
|
{
|
|
$query['query']['bool']['must'][] = $arWhere;
|
|
}
|
|
|
|
$rights = $this->CheckPermissions();
|
|
if ($rights)
|
|
{
|
|
$query['query']['bool']['filter'] = [
|
|
'bool' => [
|
|
'must' => [
|
|
[
|
|
'terms' => [
|
|
'xright' => $rights,
|
|
],
|
|
],
|
|
],
|
|
],
|
|
];
|
|
}
|
|
|
|
$order = $this->__PrepareSort($order);
|
|
if ($order)
|
|
{
|
|
$query['sort'] = $order;
|
|
}
|
|
|
|
$r = $this->query('GET', '/' . $this->indexName . '-' . $site_id . '/_search', $query);
|
|
if (!$r)
|
|
{
|
|
throw new \Bitrix\Main\Db\SqlQueryException('OpenSearch query error', $this->getError(), json_encode($query));
|
|
}
|
|
else
|
|
{
|
|
$result = [];
|
|
if (
|
|
is_array($r)
|
|
&& isset($r['hits'])
|
|
&& is_array($r['hits'])
|
|
&& isset($r['hits']['hits'])
|
|
&& is_array($r['hits']['hits'])
|
|
)
|
|
{
|
|
foreach ($r['hits']['hits'] as $searchHit)
|
|
{
|
|
$result[] = $searchHit['fields']['id'][0];
|
|
}
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
}
|
|
|
|
function getRowFormatter()
|
|
{
|
|
return new CSearchOpenSearchFormatter($this);
|
|
}
|
|
|
|
function filterField($field, $value, $logic = 'should')
|
|
{
|
|
$DB = CDatabase::GetModuleConnection('search');
|
|
$arWhere = [];
|
|
|
|
if (is_array($value))
|
|
{
|
|
if (!empty($value))
|
|
{
|
|
$arWhere = [
|
|
'bool' => [
|
|
$logic => [
|
|
]
|
|
]
|
|
];
|
|
foreach ($value as $i => $v)
|
|
{
|
|
$arWhere['bool'][$logic][] = [
|
|
'term' => [
|
|
$field => [
|
|
'value' => $v
|
|
]
|
|
]
|
|
];
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ($value !== false)
|
|
{
|
|
$arWhere = [
|
|
'bool' => [
|
|
$logic => [
|
|
'term' => [
|
|
$field => [
|
|
'value' => $value
|
|
]
|
|
]
|
|
]
|
|
]
|
|
];
|
|
}
|
|
}
|
|
|
|
return $arWhere;
|
|
}
|
|
|
|
function prepareFilter($arFilter)
|
|
{
|
|
$DB = CDatabase::GetModuleConnection('search');
|
|
|
|
if (!is_array($arFilter))
|
|
{
|
|
$arFilter = [];
|
|
}
|
|
|
|
if (array_key_exists('LOGIC', $arFilter) && $arFilter['LOGIC'] == 'OR')
|
|
{
|
|
$logic = 'should';
|
|
unset($arFilter['LOGIC']);
|
|
}
|
|
else
|
|
{
|
|
$logic = 'must';
|
|
}
|
|
|
|
$arWhere = [
|
|
'bool' => [
|
|
$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':
|
|
$cond = $this->filterField('item_id', $val);
|
|
if ($cond)
|
|
{
|
|
array_push($arWhere['bool'][$logic], $cond);
|
|
}
|
|
break;
|
|
case '!ITEM_ID':
|
|
if (
|
|
$val !== false
|
|
&& !is_array($val)
|
|
)
|
|
{
|
|
array_push($arWhere['bool'][$logic], $this->filterField('item_id', $val, 'must_not'));
|
|
}
|
|
break;
|
|
case 'MODULE_ID':
|
|
case '=MODULE_ID':
|
|
if ($val !== false && $val !== 'no')
|
|
{
|
|
$cond = $this->filterField('module_id', $val);
|
|
if ($cond)
|
|
{
|
|
array_push($arWhere['bool'][$logic], $cond);
|
|
}
|
|
}
|
|
break;
|
|
case '!MODULE_ID':
|
|
case '!=MODULE_ID':
|
|
if (
|
|
$val !== false
|
|
&& !is_array($val)
|
|
)
|
|
{
|
|
array_push($arWhere['bool'][$logic], $this->filterField('module_id', $val, 'must_not'));
|
|
}
|
|
break;
|
|
case 'PARAM1':
|
|
case '=PARAM1':
|
|
$cond = $this->filterField('param1', $val);
|
|
if ($cond)
|
|
{
|
|
array_push($arWhere['bool'][$logic], $cond);
|
|
}
|
|
break;
|
|
case '!PARAM1':
|
|
case '!=PARAM1':
|
|
if (
|
|
$val !== false
|
|
&& !is_array($val)
|
|
)
|
|
{
|
|
array_push($arWhere['bool'][$logic], $this->filterField('param1', $val, 'must_not'));
|
|
}
|
|
break;
|
|
case 'PARAM2':
|
|
case '=PARAM2':
|
|
$cond = $this->filterField('param2', $val);
|
|
if ($cond)
|
|
{
|
|
array_push($arWhere['bool'][$logic], $cond);
|
|
}
|
|
break;
|
|
case '!PARAM2':
|
|
case '!=PARAM2':
|
|
if (
|
|
$val !== false
|
|
&& !is_array($val)
|
|
)
|
|
{
|
|
array_push($arWhere['bool'][$logic], $this->filterField('param2', $val, 'must_not'));
|
|
}
|
|
break;
|
|
case 'DATE_CHANGE':
|
|
case '>=DATE_CHANGE':
|
|
if ($val <> '')
|
|
{
|
|
$ts = MakeTimeStamp($val) - CTimeZone::GetOffset();
|
|
$arWhere['bool'][$logic][] = [
|
|
'range' => [
|
|
'date_change_ts' => [
|
|
'from' => $ts,
|
|
'to' => null,
|
|
'include_lower' => true,
|
|
'include_upper' => true,
|
|
]
|
|
]
|
|
];
|
|
}
|
|
break;
|
|
case '<=DATE_CHANGE':
|
|
if ($val <> '')
|
|
{
|
|
$ts = MakeTimeStamp($val) - CTimeZone::GetOffset();
|
|
$arWhere['bool'][$logic][] = [
|
|
'range' => [
|
|
'date_change_ts' => [
|
|
'from' => null,
|
|
'to' => $ts,
|
|
'include_lower' => true,
|
|
'include_upper' => true,
|
|
]
|
|
]
|
|
];
|
|
}
|
|
break;
|
|
case 'CHECK_DATES':
|
|
if ($val == 'Y')
|
|
{
|
|
$ts = time() - CTimeZone::GetOffset();
|
|
$arWhere['bool'][$logic][] = [
|
|
'bool' => [
|
|
'must' => [
|
|
[
|
|
'bool' => [
|
|
'should' => [
|
|
[
|
|
'term' => [
|
|
'date_from_ts' => [
|
|
'value' => 0,
|
|
]
|
|
]
|
|
],
|
|
[
|
|
'range' => [
|
|
'date_from_ts' => [
|
|
'from' => null,
|
|
'to' => $ts,
|
|
'include_lower' => true,
|
|
'include_upper' => true,
|
|
]
|
|
]
|
|
],
|
|
]
|
|
]
|
|
],
|
|
[
|
|
'bool' => [
|
|
'should' => [
|
|
[
|
|
'term' => [
|
|
'date_to_ts' => [
|
|
'value' => 0,
|
|
]
|
|
]
|
|
],
|
|
[
|
|
'range' => [
|
|
'date_to_ts' => [
|
|
'from' => $ts,
|
|
'to' => null,
|
|
'include_lower' => true,
|
|
'include_upper' => true,
|
|
]
|
|
]
|
|
],
|
|
]
|
|
]
|
|
],
|
|
]
|
|
]
|
|
];
|
|
}
|
|
break;
|
|
case 'TAGS':
|
|
$arTags = explode(',', $val);
|
|
foreach ($arTags as $i => &$strTag)
|
|
{
|
|
$strTag = trim($strTag, " \n\r\t\"");
|
|
if ($strTag == '')
|
|
{
|
|
unset($arTags[$i]);
|
|
}
|
|
}
|
|
unset($strTag);
|
|
|
|
$cond = $this->filterField('tag', $arTags, 'must');
|
|
if ($cond)
|
|
{
|
|
array_push($arWhere['bool'][$logic], $cond);
|
|
}
|
|
break;
|
|
case 'PARAMS':
|
|
if (is_array($val))
|
|
{
|
|
$params = [];
|
|
foreach ($this->params($val) as $key => $value)
|
|
{
|
|
$params[] = [
|
|
'term' => [
|
|
'param.' . $key => [
|
|
'value' => $value,
|
|
]
|
|
]
|
|
];
|
|
}
|
|
if ($params)
|
|
{
|
|
array_push($arWhere['bool'][$logic], [
|
|
'bool' => [
|
|
'must' => $params
|
|
]
|
|
]);
|
|
}
|
|
}
|
|
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);
|
|
if ($subFilter)
|
|
{
|
|
$arWhere['bool'][$logic][] = $subFilter;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//AddMessage2Log("field: $field; val: ".print_r($val, 1));
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!$arWhere['bool'][$logic])
|
|
{
|
|
return [];
|
|
}
|
|
|
|
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_strtoupper($key);
|
|
switch ($key)
|
|
{
|
|
case 'ID':
|
|
case 'DATE_CHANGE':
|
|
case 'MODULE_ID':
|
|
case 'ITEM_ID':
|
|
case 'CUSTOM_RANK':
|
|
case 'PARAM1':
|
|
case 'PARAM2':
|
|
case 'DATE_FROM':
|
|
case 'DATE_TO':
|
|
$arOrder[mb_strtolower($key)] = ['order' => $ord];
|
|
break;
|
|
case 'RANK':
|
|
$arOrder['_score'] = ['order' => $ord];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (count($arOrder) == 0)
|
|
{
|
|
return $this->__PrepareSort([
|
|
'CUSTOM_RANK' => 'DESC',
|
|
'RANK' => 'DESC',
|
|
'DATE_CHANGE' => 'DESC',
|
|
]);
|
|
}
|
|
|
|
return $arOrder;
|
|
}
|
|
|
|
public function query($verb, $url, $params = [])
|
|
{
|
|
$this->errorText = '';
|
|
$this->errorNumber = 0;
|
|
|
|
$result = false;
|
|
|
|
$server = new Bitrix\Main\Web\HttpClient([
|
|
'disableSslVerification' => true,
|
|
]);
|
|
|
|
if ($this->user)
|
|
{
|
|
$server->setAuthorization($this->user, $this->password);
|
|
}
|
|
|
|
if ($params)
|
|
{
|
|
$server->setHeader('Content-Type', 'application/json');
|
|
}
|
|
$server->query($verb, $this->connectionString . $url, $params ? json_encode($params) : false);
|
|
if (
|
|
$server->getStatus() === 200
|
|
|| $server->getStatus() === 201
|
|
)
|
|
{
|
|
$result = json_decode($server->getResult(), true);
|
|
}
|
|
// AddMessage2Log([$verb . ' ' . $url, $server->getStatus(), $result ?: $server->getResult()]);
|
|
if (!is_array($result))
|
|
{
|
|
if ($server->getStatus())
|
|
{
|
|
$this->errorText = $server->getResult();
|
|
$this->errorNumber = $server->getStatus();
|
|
}
|
|
else
|
|
{
|
|
$errors = $server->getError();
|
|
$this->errorText = $errors ? implode(' ', $errors) : '-1';
|
|
}
|
|
}
|
|
elseif (isset($result['error']))
|
|
{
|
|
$this->errorText = $result['error'];
|
|
$this->errorNumber = $result['status'];
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
public function getError()
|
|
{
|
|
if ($this->errorText)
|
|
{
|
|
$result = '[' . $this->errorNumber . '] ' . $this->errorText;
|
|
}
|
|
else
|
|
{
|
|
$result = '';
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
}
|
|
|
|
class CSearchOpenSearchFormatter extends CSearchFormatter
|
|
{
|
|
/** @var CSearchOpenSearch */
|
|
private $search = null;
|
|
|
|
function __construct($search)
|
|
{
|
|
$this->search = $search;
|
|
}
|
|
|
|
function format($r)
|
|
{
|
|
if ($r)
|
|
{
|
|
if (array_key_exists('CNT', $r))
|
|
{
|
|
return $r;
|
|
}
|
|
elseif (array_key_exists('ID', $r))
|
|
{
|
|
return $this->formatRow($r);
|
|
}
|
|
}
|
|
}
|
|
|
|
function formatRow($r)
|
|
{
|
|
$DB = CDatabase::GetModuleConnection('search');
|
|
$ID = intval($r['ID']);
|
|
|
|
if ($this->search->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 = ' . $ID . "
|
|
and scsite.SITE_ID = '" . $DB->ForSql($this->search->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 = ' . $ID . '
|
|
');
|
|
}
|
|
|
|
$r = $rs->Fetch();
|
|
|
|
return $r;
|
|
}
|
|
}
|