/** * */ public function get_query_string() { if (!$this->query) { return null; } return $this->query->get('query_string'); }
/** * @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; }
/** * * @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; }
/** * Получить массив с заголовками страниц для autocomplete * @param string $input * @param string $language * @param integer $site_id * @return array элементы массива: array("label" => "Page Title", "url" => "/path/") */ public function suggest_titles($input, $language, $site_id) { $limit = nc_search::get_setting('NumberOfSuggestions'); $index_search_results = ""; $document_order_by_id = "1"; // поиск в индексе (то есть будут варианты после обработки фильтрами - базовая форма) if (nc_search::should('SearchTitleBaseformsForSuggestions')) { $last_space = strrpos($input, " "); $as_phrase = nc_search::should('SearchTitleAsPhraseForSuggestions'); $b1 = $as_phrase ? '"' : '('; $b2 = $as_phrase ? '"' : ')'; /* @todo сделать проверку на то, что последнее слово является правильным/полным? */ $query_string = "(title:{$b1}{$input}{$b2}" . ($last_space ? " OR title:{$b1}" . trim(substr($input, 0, $last_space)) . $b2 : '') . ")"; $query = new nc_search_query($query_string); $query->set('limit', $limit)->set('options_to_fetch', array('title', 'site_id', 'path'))->set('language', $language)->set('area', "site" . (int) $site_id); // some lower level magic $translator = new nc_search_provider_index_translator($this); $document_ids = $this->get_db()->get_col($query->translate($translator)); if ($document_ids) { $document_ids = join(", ", $document_ids); $index_search_results = "OR `Document_ID` IN ({$document_ids})\n"; $document_order_by_id = "FIELD(`Document_ID`, {$document_ids})"; } } // поиск точного соответствия в таблице с документами $like_expression = '`Title` LIKE "' . nc_search_util::db_escape($input) . '%" '; $query = "SELECT `Catalogue_ID`, `Path`, `Title` FROM `%t%`\n" . "WHERE {$like_expression}\n" . $index_search_results . "ORDER BY {$like_expression}, {$document_order_by_id}\n" . "LIMIT {$limit}"; $documents = new nc_search_result(); $documents->select_from_database($query); $suggestions = array(); // собственно подсказки /** @var $doc nc_search_document */ foreach ($documents as $doc) { $suggestions[] = array("label" => $doc->get('title'), "url" => $doc->get('url')); } return $suggestions; }
/** * @param bool $use_temp_table * @return string|array */ public function get_sql_query($use_temp_table) { $index = $this->index_table; $has_conditions = count($this->condition_stack[0]) > 0; $create_temp_table = $use_temp_table && $has_conditions; if (!$create_temp_table) { foreach ($this->condition_joins as $table_name => $join_condition) { $this->add_left_join($table_name); } } // prepare ORDER BY and value for the `Score` column $ranking_select = "1 AS `Score` "; $order_by = ""; $sort_field_name = $this->query->get('sort_by'); if ($sort_field_name == "last_modified") { $this->add_join($this->document_table); $order_by = "ORDER BY `{$this->document_table}`.`LastModified` DESC"; } elseif ($sort_field_name) { // some other field /** @var $sort_fields nc_search_provider_index_field_manager */ $sort_fields = $this->translator->get_fields('name', $sort_field_name)->where('is_sortable', true); if (count($sort_fields)) { $order_by = array(); /** @var $f nc_search_provider_index_field */ foreach ($sort_fields as $f) { $field_table_name = $f->get_field_table_name(); $this->add_left_join($field_table_name); $order_by[] = "`{$field_table_name}`.`RawData`"; } $order_by = "ORDER BY IFNULL(" . join(", ", $order_by) . ", 0) " . ($this->query->get('sort_direction') == SORT_DESC ? "DESC" : "ASC"); } } if (!$order_by) { // standard sorting by relevance $ranking_select = $this->term_ranking_calculation(); $order_by = "ORDER BY `Score` DESC"; } // Prepare area condition (will need join) $area_condition = ""; $area = $this->query->get('area'); if ($area) { $this->add_join($this->document_table); if (!$area instanceof nc_search_area) { $area = new nc_search_area($area); } $area_condition = "AND " . $area->get_sql_condition() . "\n"; } // Compose query // SELECT $query_string = "SELECT " . ($create_temp_table ? "" : "SQL_CALC_FOUND_ROWS ") . "`{$index}`.`Document_ID`,\n" . $ranking_select; // FROM $query_string .= "FROM `{$index}` FORCE INDEX (`Content`)\n"; // INNER JOINs foreach ($this->joins as $j) { $query_string .= "{$j}\n"; } // LEFT JOINs foreach ($this->left_joins as $j) { $query_string .= "{$j}\n"; } // WHERE [index_match_condition] // (1) Index match; (2) site/path filter $query_string .= "WHERE {$this->index_match}\n{$area_condition}"; // (3) Extra conditions if ($has_conditions && !$use_temp_table) { $query_string .= "AND " . join("\nAND ", $this->condition_stack[0]); } // ORDER $query_string .= "\n{$order_by}"; if ($use_temp_table) { $t = $this->get_temporary_table_name(); $queries = array("temp_table" => $t, "prefilter" => $query_string, "refinement" => "SELECT t.`Document_ID`, t.`Rank`\n" . "FROM `{$t}` AS t\n" . join("\n", $this->condition_joins) . "\n" . "WHERE t.`Rank` >= IFNULL(@rank_value,0) AND" . join("\nAND ", $this->condition_stack[0]) . "\n" . "ORDER BY t.`Rank`\n"); return $queries; } else { // LIMIT, OFFSET $query_string .= "\nLIMIT " . (int) $this->query->get('limit') . "\nOFFSET " . (int) $this->query->get('offset'); return $query_string; } }
/** * @param string $input * @param string $language * @param integer $site_id * @return array */ public function suggest_titles($input, $language, $site_id) { $suggestions = array(); // собственно подсказки $titles = array(); $limit = nc_search::get_setting('NumberOfSuggestions'); // поиск в индексе (то есть будут варианты после обработки фильтрами - базовая форма) if (nc_search::should('SearchTitleBaseformsForSuggestions')) { $last_space = strrpos($input, " "); $as_phrase = nc_search::should('SearchTitleAsPhraseForSuggestions'); $b1 = $as_phrase ? '"' : '('; $b2 = $as_phrase ? '"' : ')'; /* @todo сделать проверку на то, что последнее слово является правильным/полным? */ $query_string = "(title:{$b1}{$input}{$b2}" . ($last_space ? " OR title:{$b1}" . trim(substr($input, 0, $last_space)) . $b2 : '') . ") AND site_id:{$site_id}"; $query = new nc_search_query($query_string); $query->set('limit', $limit)->set('options_to_fetch', array('title', 'site_id', 'path'))->set('language', $language); $documents = $this->find($query, false); foreach ($documents as $doc) { $suggestions[] = array("label" => $doc->get('title'), "url" => $doc->get('url')); $titles[] = '"' . nc_search_util::db_escape($doc->get('title')) . '"'; } $titles = array_unique($titles); } // поиск точного соответствия в таблице с документами // по-хорошему следовало бы сначала сделать запрос к БД, а потом к индексу, однако // в случае запроса к индексу не получится так же просто отфильтровать уже совпавшие запросы $query = "SELECT `Catalogue_ID`, `Path`, `Title` FROM `%t%` " . ' WHERE `Title` LIKE "' . nc_search_util::db_escape($input) . '%" ' . ($titles ? " AND `Title` NOT IN (" . join(", ", $titles) . ") " : "") . " ORDER BY `Title` " . " LIMIT {$limit}"; $documents = new nc_search_result(); $documents->select_from_database($query); foreach ($documents as $doc) { array_unshift($suggestions, array("label" => $doc->get('title'), "url" => $doc->get('url'))); } $suggestions = array_slice($suggestions, 0, $limit); return $suggestions; }
/** * @param nc_search_query $query * @return mixed */ public function translate(nc_search_query $query) { return $this->dispatch_translate($query->parse()); }
/** * * @param string|nc_search_query $query * @param boolean $highlight_matches * @throws nc_search_exception * @return nc_search_result */ public static function find($query, $highlight_matches = true) { if (self::should('EnableSearch')) { if (is_string($query)) { $query = new nc_search_query($query); } nc_search_util::set_utf_locale($query->get('language')); $result = self::get_provider()->find($query, $highlight_matches); nc_search_util::restore_locale(); return $result; } else { throw new nc_search_exception("Search module is disabled"); } }