queryParameter = $query_parameter; $this->recurseParameter = $recurse_parameter; } /** * Gets the query parameter to use that stores the search terms. * * @return string * The query parameter to use that stores the search terms. */ public static function getQueryParameter() { return self::getConfig(SettingsForm::SEARCH_QUERY_PARAMETER, self::DEFAULT_QUERY_PARAM); } /** * Gets the query parameter to use that stores the search terms. * * @return string * The recurse parameter used to indicate that the search should be * recursive. */ public static function getRecurseParameter() { return self::getConfig(SettingsForm::SEARCH_RECURSIVE_PARAMETER, self::DEFAULT_RECURSE_PARAM); } /** * Extracts a list of AdvancedSearchQueryTerms from the given request. * * @param \Symfony\Component\HttpFoundation\Request $request * The request to parse terms from. * * @return \Drupal\advanced_search\AdvancedSearchQueryTerm[] * A list of search terms. */ public function getTerms(Request $request) { $terms = []; if ($request->query->has($this->queryParameter)) { $query_params = $request->query->get($this->queryParameter); if (is_array($query_params)) { foreach ($query_params as $params) { $terms[] = AdvancedSearchQueryTerm::fromQueryParams($params); } } } return array_filter($terms); } /** * Checks if the query should recursively include sub-collections. * * @param \Symfony\Component\HttpFoundation\Request $request * The request to parse. * * @return bool * TRUE if the search should recurse FALSE otherwise. */ public function shouldRecurse(Request $request) { if ($request->query->has($this->recurseParameter)) { $recurse_param = $request->query->get($this->recurseParameter); return filter_var($recurse_param, FILTER_VALIDATE_BOOLEAN); } return FALSE; } /** * Checks if the all of the given terms are negations or not. * * @param \Drupal\advanced_search\AdvancedSearchQueryTerm[] $terms * The terms to search for. * * @return bool * TRUE if all terms are to be excluded otherwise FALSE. */ protected function negativeQuery(array $terms) { foreach ($terms as $term) { if ($term->getInclude()) { return FALSE; } } return TRUE; } /** * Alters the given query using search terms provided in the given request. * * @param \Symfony\Component\HttpFoundation\Request $request * The request to parse terms from. * @param \Solarium\Core\Query\QueryInterface $solarium_query * The solr query to modify. * @param \Drupal\search_api\Query\QueryInterface $search_api_query * The search api query from which the solr query was build. */ public function alterQuery(Request $request, SolariumQueryInterface &$solarium_query, DrupalQueryInterface $search_api_query) { // Only apply if a Advanced Search Query was made. $terms = $this->getTerms($request); if (!empty($terms)) { $index = $search_api_query->getIndex(); /** @var \Drupal\search_api_solr\Plugin\search_api\backend\SearchApiSolrBackend $backend */ $backend = $index->getServerInstance()->getBackend(); $language_ids = $search_api_query->getLanguages(); $field_mapping = $backend->getSolrFieldNamesKeyedByLanguage($language_ids, $index); // Disable for Lucene and wildcard // $q[] = "{!boost b=boost_document}"; // Create a flag for active/inactive dismax. $config = \Drupal::config(SettingsForm::CONFIG_NAME); $isDismax = $config->get(SettingsForm::EDISMAX_SEARCH_FLAG); if (!isset($isDismax)) { $isDismax = TRUE; } $isSearchAllFields = FALSE; $fields_list = []; if (!$isDismax) { // To support negative queries we must first bring in all documents. $q[] = $this->negativeQuery($terms) ? "*:*" : ""; } $term = array_shift($terms); $q[] = $term->toSolrQuery($field_mapping); // New. $fields_list[] = $term->toSolrFields($field_mapping); // Set edismax is enabled if the field set to "all". if ($term->getField() === "all") { $isSearchAllFields = TRUE; } // For multiple conditions. foreach ($terms as $term) { $q[] = $term->getConjunction(); $q[] = $term->toSolrQuery($field_mapping); // New. $fields_list[] = $term->toSolrFields($field_mapping); // Set dismax is enabled if the field set to "all". if ($term->getField() === "all") { $isSearchAllFields = TRUE; } } $q = implode(' ', $q); // Limit extra processing if Luncene Search is enable. if ($isDismax) { if ((strpos($q, "*") !== FALSE || strpos($q, "?") !== FALSE)) { // If the query string contain '*','?',a single world,enable wildcard. $tmp = str_replace('"', "", trim($q)); $query_fields = []; if ($isSearchAllFields) { foreach ($field_mapping as $key => $field) { foreach ($field as $f => $item) { // bs_ are boolean fields, do not work well with text search. if (substr($item, 0, 3) !== "bs_" && !in_array($item, ['score', 'random', 'boost_document']) && ((strpos($item, "sm_") === 0) || (strpos($item, "tm_") === 0) || (strpos($item, "sort_ss_") === 0) || (strpos($item, "ts_") === 0) || (strpos($item, "ss_") === 0) )) { array_push($query_fields, '(' . $item . ':' . $tmp . ')'); } } } } else { foreach ($fields_list as $f) { $parts = explode(" ", $f); foreach ($parts as $p) { array_push($query_fields, '(' . $p . ':' . $tmp . ')'); } } } $q = implode(" ", array_unique($query_fields)); } else { // Enable dismax search query option. /** @var Solarium\QueryType\Select\Query\Component\DisMax $dismax */ $dismax = $solarium_query->getEDisMax(); $dismax->setQueryParser('edismax'); $query_fields = []; if ($isSearchAllFields) { foreach ($field_mapping as $key => $field) { foreach ($field as $f => $item) { // bs_ are boolean fields, do not work well with text search. if (substr($item, 0, 3) !== "bs_") { array_push($query_fields, $item); } } } } else { $query_fields = $fields_list; } $query_fields = implode(" ", array_unique($query_fields)); $dismax->setQueryFields($query_fields); } } if ($backend->getConfiguration()['highlight_data']) { // Just highlight string and text fields to avoid Solr exceptions. $highlighted_fields = array_filter(array_unique($fields_list), function ($v) { return preg_match('/^t.*?[sm]_/', $v) || preg_match('/^s[sm]_/', $v); }); if (empty($highlighted_fields)) { $highlighted_fields = ['*']; } $this->setHighlighting($solarium_query, $search_api_query, $highlighted_fields); // The Search API Highlight processor checks if the 'keys' field of // the Search API Query is non-empty before creating an excerpt. // Since we are getting the highlighting result from Solr instead // of using the Search API processor to create one, we just need // make this field non-empty. $search_api_query->keys("advanced search"); } $solarium_query->setQuery($q); } } /** * Alters the given view to be recursive if applicable. * * @param \Symfony\Component\HttpFoundation\Request $request * The request to parse terms from. * @param \Drupal\views\ViewExecutable $view * The view to modify. * @param string $display_id * The view display to potentially alter. */ public function alterView(Request $request, ViewExecutable $view, $display_id) { $views = Utilities::getAdvancedSearchViewDisplays(); // Only specify contextual filters for views which the advanced search // blocks are derived from. $block_id = array_search([$view->id(), $display_id], $views); if ($block_id !== FALSE) { $block = Block::load($block_id); $settings = $block->get('settings'); // Ignore the immediate children contextual filter in the query to allow // for recursive search. if (isset($settings[AdvancedSearchBlock::SETTING_CONTEXTUAL_FILTER])) { $display = $view->getDisplay(); $display_arguments = $display->getOption('arguments'); $immediate_children_contextual_filter = $settings[AdvancedSearchBlock::SETTING_CONTEXTUAL_FILTER]; $index = array_search($immediate_children_contextual_filter, array_keys($display_arguments)); if ($this->shouldRecurse($request)) { // Change the argument to the exception value which should cause the // contextual filter to be ignored. $view->args[$index] = $display_arguments[$immediate_children_contextual_filter]['exception']['value']; } else { // Explicitly set the default argument for AJAX requests. // We need to restore the default as that functionality is currently // broken. @see https://www.drupal.org/project/drupal/issues/3173778 // // We fake the current request from the refer only to set the default // argument in case it is build from the URL. If this is not an AJAX // request this logic can be ignored. if ($request->isXmlHttpRequest()) { $view->initHandlers(); $request_stack = \Drupal::requestStack(); $refer = Request::create($request->server->get('HTTP_REFERER')); $refer->getPathInfo(); $refer->attributes->add(\Drupal::getContainer()->get('router')->matchRequest($refer)); $request_stack->push($refer); if (isset($view->argument[$immediate_children_contextual_filter])) { $plugin = $view->argument[$immediate_children_contextual_filter]->getPlugin('argument_default'); if ($plugin) { $view->args[$index] = $plugin->getArgument(); } } $request_stack->pop(); } } } } } /** * Get query parameter for all search terms. * * @return \Drupal\Core\Url * Url for the given request combined with search query parameters. */ public function toUrl(Request $request, array $terms, bool $recurse, $route = NULL) { $query_params = $request->query->all(); if ($route) { $url = Url::fromRoute($route); // The form that built the url may use AJAX, but we are redirecting to a // new page, so it should be disabled. unset($query_params[FormBuilderInterface::AJAX_FORM_REQUEST]); unset($query_params[MainContentViewSubscriber::WRAPPER_FORMAT]); } else { $url = Url::createFromRequest($request); } unset($query_params[$this->queryParameter]); foreach ($terms as $term) { $query_params[$this->queryParameter][] = $term->toQueryParams(); } if ($recurse) { $query_params[$this->recurseParameter] = '1'; } else { unset($query_params[$this->recurseParameter]); } $url->setOptions(['query' => $query_params]); return $url; } /** * Sets the highlighting parameters. * * @param \Solarium\Core\Query\QueryInterface $solarium_query * The Solarium select query object. * @param \Drupal\search_api\Query\QueryInterface $search_api_query * The query object. * @param array $highlighted_fields * (optional) The solr fields to be highlighted. */ protected function setHighlighting(SolariumQueryInterface $solarium_query, DrupalQueryInterface $search_api_query, array $highlighted_fields = []) { $index = $search_api_query->getIndex(); $settings = SearchAPISolrUtility::getIndexSolrSettings($index); $highlighter = $settings['highlighter']; $hl = $solarium_query->getHighlighting(); $hl->setSimplePrefix('[HIGHLIGHT]'); $hl->setSimplePostfix('[/HIGHLIGHT]'); $hl->setSnippets($highlighter['highlight']['snippets']); $hl->setFragSize($highlighter['highlight']['fragsize']); $hl->setMergeContiguous($highlighter['highlight']['mergeContiguous']); $hl->setRequireFieldMatch($highlighter['highlight']['requireFieldMatch']); // Overwrite Solr default values only if required to have shorter request // strings. if (51200 != $highlighter['maxAnalyzedChars']) { $hl->setMaxAnalyzedChars($highlighter['maxAnalyzedChars']); } if ('gap' !== $highlighter['fragmenter']) { $hl->setFragmenter($highlighter['fragmenter']); if ('regex' !== $highlighter['fragmenter']) { $hl->setRegexPattern($highlighter['regex']['pattern']); if (0.5 != $highlighter['regex']['slop']) { $hl->setRegexSlop($highlighter['regex']['slop']); } if (10000 != $highlighter['regex']['maxAnalyzedChars']) { $hl->setRegexMaxAnalyzedChars($highlighter['regex']['maxAnalyzedChars']); } } } if (!$highlighter['usePhraseHighlighter']) { $hl->setUsePhraseHighlighter(FALSE); } if (!$highlighter['highlightMultiTerm']) { $hl->setHighlightMultiTerm(FALSE); } if ($highlighter['preserveMulti']) { $hl->setPreserveMulti(TRUE); } foreach ($highlighted_fields as $highlighted_field) { // We must not set the fields at once using setFields() to not break // the altered queries. $hl->addField($highlighted_field); } } }