/** * * @param nc_search_language_corrector_phrase $phrase * @return boolean */ public function correct(nc_search_language_corrector_phrase $phrase) { // init, required $this->analyzer = nc_search_extension_manager::get('nc_search_language_analyzer', $this->context)->first(); // «ПЕРВЫЙ ПОПАВШИЙСЯ»! предполагается, что анализатор вообще-то один $this->provider = nc_search::get_provider(); // для начала определимся, можем ли мы что-то сделать? $unknown_terms = $this->get_unknown_terms($phrase); if (!$unknown_terms) { return false; } // false or empty array $input_count = count($unknown_terms); // этап 2: пробуем исправить слово // раскладка if (nc_search::should('ChangeLayoutOnEmptyResult')) { $unknown_terms = $this->change_keyboard_layout($unknown_terms); } // разбивка на слова if (nc_search::should('BreakUpWordsOnEmptyResult')) { $unknown_terms = $this->break_up_words($unknown_terms); } // fuzzy search if (nc_search::should('PerformFuzzySearchOnEmptyResult') && nc_search::should('AllowFuzzySearch')) { $this->add_fuzzy_search($unknown_terms); $everything_corrected = true; } else { $count = count($unknown_terms); $everything_corrected = $count == 0 || $count < $input_count && nc_search::get_setting('DefaultBooleanOperator') == 'OR'; } return $everything_corrected; }
/** * @return nc_search_extension_chain */ protected function get_analyzers() { $index = $this->context->get('language') . "__" . $this->context->get('action'); if (!isset(self::$analyzers[$index])) { self::$analyzers[$index] = nc_search_extension_manager::get('nc_search_language_analyzer', $this->context); } return self::$analyzers[$index]; }
/** * @param nc_search_query $query * @return string|null */ public function translate(nc_search_query $query) { $root = $query->parse(); // empty query? if ($root instanceof nc_search_query_expression_empty) { return null; } // queries with only excluded terms are forbidden if ($root->is_excluded() || $root instanceof nc_search_query_expression_not) { return null; } // initialize variables used for a translation of the query $this->query_builder = $builder = new nc_search_provider_index_querybuilder($query, $this); $this->stack = array(); $this->unknown_required_terms = array(); $this->can_skip_fts_query = false; $this->implicit_field_match = $this->expression_requires_implicit_match($root); // get language filters chain to use in this.translate_term() etc. $language = $query->get('language'); if (!$language) { $language = nc_Core::get_object()->lang->detect_lang(); } $query_context = new nc_search_context(array("search_provider" => get_class($this->provider), "language" => $language, "action" => "searching")); $this->text_filters = nc_search_extension_manager::get('nc_search_language_filter', $query_context)->stop_on(array()); // Ready to go! // translate the expression tree $index_query = $this->dispatch_translate($root); if (!$this->can_skip_fts_query) { // interval search at the root for a numeric field // index query is required almost always if (!strlen($index_query)) { return null; } // e.g. if query string consists of stop words if ($index_query == "____" || $index_query == "(____)") { // empty query return null; } } // set query for the combined index in the query builder if ($index_query && !$root->get_field()) { $builder->set_index_match($index_query); } // there are required terms that are not in the index, so don’t make a query if ($this->unknown_required_terms) { return null; } // использовать временную таблицу для уточняющих запросов при поиске фраз? $use_temp_table = !nc_search::get_setting('DatabaseIndex_AlwaysGetTotalCount') && $this->expression_has_phrase($root); // return SQL query string $result = $builder->get_sql_query($use_temp_table); return $result; }
/** * Перед сохранением нужно прогнать список слов через фильтры */ public function save() { $mb_case = nc_search::get_setting('FilterStringCase'); $apply_filter = !$this->get('dont_filter'); $list = array(); foreach ($this->get('words') as $word) { $word = trim($word); if (strlen($word)) { // пропустить пустые значения // преобразовать регистр, если в дальнейшем не будут применены фильтры $list[] = $apply_filter ? $word : mb_convert_case($word, $mb_case); } } if ($apply_filter) { $context = new nc_search_context(array('language' => $this->get('language'))); $list = nc_search_extension_manager::get('nc_search_language_filter', $context)->until_first('nc_search_language_filter_synonyms')->apply('filter', $list); } if (sizeof($list) < 2) { throw new nc_search_data_exception(NETCAT_MODULE_SEARCH_ADMIN_SYNONYM_LIST_MUST_HAVE_AT_LEAST_TWO_WORDS); } $this->set('words', $list); parent::save(); }
/** * Получить парсер для документов типа $content_type * @param string $content_type * @throws nc_search_exception * @return nc_search_document_parser */ protected function get_parser($content_type) { if (!isset($this->parsers[$content_type])) { $context = new nc_search_context(array('content_type' => $content_type, 'search_provider' => nc_search::get_setting('SearchProvider'))); $parser = nc_search_extension_manager::get('nc_search_document_parser', $context)->first(); if (!$parser) { throw new nc_search_exception("Cannot get parser for the '{$content_type}'"); } $this->parsers[$content_type] = $parser; } return $this->parsers[$content_type]; }
/** * * @param string $query_string * @param string|array $area * @param string $params Параметры, через амперсанд * - field - поле поиска. Допустимые значения: 'title' * - interval - непустое значение, если включена фильтрация по дате * - intervalvalue - значение интервала * - intervalunit - тип интервала (hour, day, week, month) * - sortby - сортировка. Если пустое значение - сортировка по релевантности. * Допустимые значения: last_updated или имя поля, по которому разрешена сортировка * - sortdirection - desc (по умолчанию), asc * - language - язык результатов, по умолчанию определяется автоматически * - curPos - текущая позиция (номер первого результата) * - recNum - количество результатов на странице, по умолчанию 10 (берется из * настроек компонента в разделе) * - correct - пытаться исправить запросы, не давшие результатов (по умолчанию * равно соответствующей настройки модуля) * - nologging - не записывать запрос в журнал запросов (при просмотре * результатов из админки, чтобы не искажать картину запросов) * @return nc_search_data_persistent_collection */ public function get_results($query_string, $area = "", $params = "") { if (!nc_search::should('EnableSearch')) { return new nc_search_result(); } // return empty collection $start_time = microtime(true); $query_string = (string) $query_string; global $nc_core; parse_str($params, $params); if (isset($params["field"]) && $params["field"] && nc_search::should('AllowFieldSearch')) { $query_string = "{$params['field']}:({$query_string})"; } $query = new nc_search_query($query_string); $has_interval = isset($params["interval"]) && isset($params["intervalvalue"]) && isset($params["intervalunit"]) && $params["interval"] && $params["intervalvalue"] && $params["intervalunit"]; if ($has_interval) { $timestamp = strtotime("-{$params['intervalvalue']} {$params['intervalunit']}"); $query->set('modified_after', strftime("%Y%m%d%H%M%S", $timestamp)); } $allow_sort = isset($params["sortby"]) && $params["sortby"] && nc_search::should('AllowFieldSearch'); if ($allow_sort) { $query->set('sort_by', $params["sortby"]); if (isset($params["sortdirection"]) && strtoupper($params["sortdirection"]) == 'ASC') { $query->set('sort_direction', SORT_ASC); } } if (isset($params["curPos"]) && $params["curPos"]) { $query->set('offset', (int) $params["curPos"]); } if (isset($params["recNum"]) && $params["recNum"]) { $query->set('limit', (int) $params["recNum"]); } if ($area) { if (is_array($area)) { $area = join(" ", $area); } $query->set('area', $area); } $language = isset($params["language"]) && $params["language"] ? $params["language"] : $nc_core->lang->detect_lang(1); $query->set('language', $language); $shutdown_page_path = nc_folder_path($nc_core->subdivision->get_current('Subdivision_ID')); register_shutdown_function('nc_search_shutdown', $shutdown_page_path, $query_string); $query_error = false; try { $results = nc_search::find($query); } catch (Exception $e) { $query_error = true; $results = new nc_search_result(); $results->set_query($query)->set_error_message($e->getMessage()); } $results->set_output_encoding(nc_core('NC_CHARSET')); // попробуем исправить, если не было результатов? $try_to_correct = $results->get_total_count() == 0 && !$query_error && (isset($params["correct"]) && $params["correct"] || nc_search::should('TryToCorrectQueries')) && preg_match_all('/[\\pL\\pN\\?\\*]+/u', $query_string, $tmp) <= nc_search::get_setting('MaxQueryLengthForCorrection'); if ($try_to_correct) { $context = new nc_search_context(array("language" => $language, "action" => "searching")); $correctors = nc_search_extension_manager::get('nc_search_language_corrector', $context)->get_all(); if (sizeof($correctors)) { $phrase = new nc_search_language_corrector_phrase($query_string); $rewritten_query = clone $query; foreach ($correctors as $corrector) { if ($corrector->correct($phrase)) { // что-то подправили // попробуем поискать! $rewritten_query->set('query_string', $phrase->to_string()); try { $corrected_results = nc_search::find($rewritten_query); if (sizeof($corrected_results)) { $results = $corrected_results; $results->set_correction_suggestion($phrase->get_suggestion()); $results->set_output_encoding(nc_core('NC_CHARSET')); break; // exit "foreach corrector" } } catch (Exception $e) { // может упасть, например, если у изменённого слова есть несколько базовых форм... } } // of "something changed" } // of "foreach corrector" } // end of "has correctors" } // end of "if ($try_to_correct)" $will_log = true; if (isset($params['nologging']) && $params['nologging'] && strlen($query_string)) { // только очень крутым чувакам разрешается не оставлять следов if (isset($GLOBALS['AUTH_USER_ID']) && isset($GLOBALS['perm']) && $GLOBALS["perm"]->isAccess(NC_PERM_MODULE)) { $will_log = false; } } if ($will_log && nc_search::should('SaveQueryHistory') && $query->get('offset') == 0) { $ip = ip2long($_SERVER['REMOTE_ADDR']); // achtung! не будет работать с IPv6! if ($ip > 0x7fffffff) { $ip -= 0x100000000; } // produce a signed 4-byte integer on 64-bit systems $query->set('results_count', $results->get_total_count())->set('user_ip', $ip)->set('user_id', $GLOBALS['AUTH_USER_ID'])->set('site_id', $GLOBALS['catalogue'])->save(); } $results->set_search_time(microtime(true) - $start_time); return $results; }
/** * @param $doc_id * @param nc_search_field $field * @param nc_search_context $doc_context * @return string */ protected function store_field($doc_id, nc_search_field $field, nc_search_context $doc_context) { $text = $field->get('value'); if (strlen($text) == 0) { return ''; } // empty fields are not stored $content = ''; $raw_data = ''; $is_stored = $field->get('is_stored') || $field->get('is_sortable'); // 'is_indexed' → store in the `Content` field if ($field->get('is_indexed')) { $content = ""; $filters = nc_search_extension_manager::get('nc_search_language_filter', $doc_context)->except('nc_search_language_filter_case')->stop_on(array()); // Processing in chunks - compromise between performance (less // method call overhead) and memory usage $n = 0; $current_position = 0; $text_length = strlen($text); $batch_length = $this->text_batch_length; while ($current_position < $text_length) { if ($text_length < $batch_length) { $batch = $text; $current_position = $text_length; } else { $space_position = strpos($text, " ", min($text_length, $current_position + $batch_length)); if (!$space_position) { $batch = substr($text, $current_position); $current_position = $text_length; } else { $batch = substr($text, $current_position, $space_position - $current_position); $current_position = $space_position + 1; } } $tokens = $this->tokenize_text(mb_convert_case($batch, nc_search::get_setting('FilterStringCase'), 'UTF-8')); if ($field->get('is_normalized')) { // apply filters $tokens = $filters->apply('filter', $tokens); } $content .= ($n ? ' ' : '') . join(' ', $this->get_term_codes($tokens, true)); $n++; } } // 'is_stored' → store raw text as well if ($is_stored) { $raw_data = $text; } // save data in the DB $this->store_index_data($this->get_field_table_name($field), $doc_id, $content, $raw_data); return $content; }
/** * */ protected function get_highlight_regexp($language) { if (!$this->highlight_regexp) { $query_string = $this->get_query_string(); $context = new nc_search_context(array('language' => $language, 'action' => 'searching')); // Получить слова из запроса. // (Удалять из запроса термины с префиксом "-" и "NOT" не имеет особого смысла, // поскольку в результат они как правило не попадают.) $query_string = preg_replace("/[\\^~][\\d\\.]+/", '', $query_string); // операторы ^1, ~1 preg_match_all("/[\\pL\\d\\?\\*]+/u", $query_string, $matches); $terms = $matches[0]; if (strpos($query_string, "*") !== false || strpos($query_string, "?") !== false) { $wildcards_replacement = nc_search::should('AllowWildcardSearch') ? array("?" => ".", "*" => "[\\S]+") : array("?" => "", "*" => ""); foreach ($terms as $i => $term) { $terms[$i] = strtr($term, $wildcards_replacement); } } //if ( nc_Core::get_object()->NC_UNICODE ) { $terms = nc_search_extension_manager::get('nc_search_language_filter', $context)->except('nc_search_language_filter_stopwords')->apply('filter', $terms); //} $analyzer = nc_search_extension_manager::get('nc_search_language_analyzer', $context)->first(); if ($analyzer) { $regexp = $analyzer->get_highlight_regexp($terms); } else { $regexp = nc_search_util::word_regexp("(" . join("|", $terms) . ")", "Si"); } $this->highlight_regexp = $regexp; } // of "there was no 'highlight_regexp'" return $this->highlight_regexp; }
if (count($rules)) { $pending_time = time() - 12 * 60 * 60; $pending_tasks = $db->get_var("SELECT `StartTime`\n FROM `Search_Schedule`\n WHERE `StartTime` < {$pending_time}\n LIMIT 1"); if ($pending_tasks) { $error_message = NETCAT_MODULE_SEARCH_WIDGET_CHECK_CRONTAB; } } else { $error_message = NETCAT_MODULE_SEARCH_WIDGET_NO_RULES; } // Ошибки конфигурации ob_start(); // (1) Индексатор $provider->check_environment(true); // (2) Парсеры $parser_context = new nc_search_context(array('search_provider' => $provider)); $all_parsers = nc_search_extension_manager::get('nc_search_document_parser', $parser_context)->get_all(); /** @var nc_search_document_parser $parser */ foreach ($all_parsers as $parser) { $parser->check_environment(true); } $has_configuration_errors = strlen(ob_get_clean()) > 0; if ($has_configuration_errors) { $error_message = NETCAT_MODULE_SEARCH_WIDGET_CONFIGURATION_ERRORS; } // ----------------------------------------------------------------------------- $full_link = $nc_core->SUB_FOLDER . $nc_core->HTTP_ROOT_PATH . 'modules/search/admin.php?view=info'; $full_link_brokenlinks = $nc_core->SUB_FOLDER . $nc_core->HTTP_ROOT_PATH . 'modules/search/admin.php?view=brokenlinks'; ?> <table class="nc-widget-grid"> <col width="50%" />
* одну из базовых форм */ if (!class_exists("nc_system")) { die; } $ui = $this->get_ui(); $ui->add_lists_toolbar(); $ui->add_submit_button(NETCAT_MODULE_SEARCH_ADMIN_SAVE); $ui->add_back_button(); $stopword = $this->data_form('nc_search_language_stopword', 'stopwords'); $stopword->set_values($this->get_input('data'), true); $word = $stopword->get('word'); $lang = $stopword->get('language'); $input = (array) $word; $context = new nc_search_context(array('language' => $lang)); $filtered = nc_search_extension_manager::get('nc_search_language_filter', $context)->until_first('nc_search_language_filter_stopwords')->apply('filter', (array) $input); if ($filtered == $input) { // на входе базовая форма — OK! $params = array('view' => 'stopwords', 'action' => 'save', 'data_class' => 'nc_search_language_stopword', 'id' => $stopword->get_id(), 'data' => array('language' => $stopword->get('language'), 'word' => $word)); // !! на входе в cp-1251 всегда кодировка windows-1251, но сейчас в $params - в utf-8 if (!$nc_core->NC_UNICODE) { $params = $nc_core->utf8->array_utf2win($params); } $this->redirect("?" . http_build_query($params, null, '&')); } echo "<fieldset><legend>", NETCAT_MODULE_SEARCH_ADMIN_STOPWORD, "</legend><div class='stopword_variants'>"; $num_variants = sizeof($filtered); if ($num_variants == 0) { printf(NETCAT_MODULE_SEARCH_ADMIN_STOPWORD_HAS_NO_BASEFORM, $word); print $this->hidden('data[word]', $word); } elseif ($num_variants == 1) {
/** * Фильтры сделаны нестандартным для Lucene способом; оправдания следующие: * - возможно, в будущем в модуле будет свой парсер, и он будет обрабатывать * ситуации с синонимами и проч. переписыванием запросов, e.g.: * (word1 word2) → (word1 word2 syn2); "word1 word2" → ("word1 word2" OR "word1 syn2") * - чтобы сделать через addFilter(), нужно создать дублирующие классы или класс-прокси * (или отказаться от идеи возможного использования фильтров netcat с * другими поставщиками поиска, которые [*может быть*] будут) * - стандартные фильтры создают множество временных объектов * (каждый фильтр, если он что-то сделал, создает новый токен) * * @param string $term * @return array может содержать 0 (стоп-слова), 1 или несколько (синонимы) элементов * */ protected function apply_nc_filters($term) { $context = nc_search::get_current_context(); return nc_search_extension_manager::get('nc_search_language_filter', $context)->stop_on(array())->apply('filter', array($term)); }