add own logic
This commit is contained in:
commit
5cabe99d1e
64 changed files with 6031 additions and 0 deletions
446
src/AdvancedSearchQuery.php
Normal file
446
src/AdvancedSearchQuery.php
Normal file
|
|
@ -0,0 +1,446 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\advanced_search;
|
||||
|
||||
use Drupal\block\Entity\Block;
|
||||
use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
|
||||
use Drupal\Core\Form\FormBuilderInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\advanced_search\Form\SettingsForm;
|
||||
use Drupal\advanced_search\Plugin\Block\AdvancedSearchBlock;
|
||||
use Drupal\search_api\Query\QueryInterface as DrupalQueryInterface;
|
||||
use Drupal\views\ViewExecutable;
|
||||
use Solarium\Core\Query\QueryInterface as SolariumQueryInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Drupal\search_api_solr\Utility\Utility as SearchAPISolrUtility;
|
||||
|
||||
/**
|
||||
* Alter current search query / view from using URL parameters.
|
||||
*/
|
||||
class AdvancedSearchQuery {
|
||||
|
||||
use GetConfigTrait;
|
||||
|
||||
// User can set this configuration for the module.
|
||||
const DEFAULT_QUERY_PARAM = 'a';
|
||||
const DEFAULT_RECURSE_PARAM = 'r';
|
||||
|
||||
/**
|
||||
* The query parameter is how terms are passed to the query.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $queryParameter;
|
||||
|
||||
/**
|
||||
* The recurse parameter indicates the search should be recursive or not.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $recurseParameter;
|
||||
|
||||
/**
|
||||
* Constructs a FacetBlockAjaxController object.
|
||||
*
|
||||
* @param string $query_parameter
|
||||
* The field to search against.
|
||||
* @param string $recurse_parameter
|
||||
* The field that signifies the search should be recursive.
|
||||
*/
|
||||
public function __construct(string $query_parameter = self::DEFAULT_QUERY_PARAM, string $recurse_parameter = self::DEFAULT_RECURSE_PARAM) {
|
||||
$this->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->all()[$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;
|
||||
}
|
||||
|
||||
// Get the indexed fields from /admin/config/search/search-api/index/..../fields
|
||||
$boostedFields = [];
|
||||
foreach ($index->getFields() as $field_id => $field) {
|
||||
$boostedFields[$field_id] = $field->getBoost();
|
||||
}
|
||||
|
||||
$str_fields_with_boost = "";
|
||||
// Adding a boost number for each field)
|
||||
foreach($query_fields as $solr_field) {
|
||||
foreach($boostedFields as $indexed_field => $boostnum) {
|
||||
if(strpos($str_fields_with_boost, $indexed_field) == false && strpos($solr_field, $indexed_field) !== false) {
|
||||
$str_fields_with_boost .= $solr_field . "^" . $boostnum . " ";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$dismax->setQueryFields($str_fields_with_boost);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
406
src/AdvancedSearchQueryTerm.php
Normal file
406
src/AdvancedSearchQueryTerm.php
Normal file
|
|
@ -0,0 +1,406 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\advanced_search;
|
||||
|
||||
use Drupal\advanced_search\Form\AdvancedSearchForm;
|
||||
use Drupal\advanced_search\Form\SettingsForm;
|
||||
|
||||
/**
|
||||
* Defines a single search term.
|
||||
*
|
||||
* Used for parsing query parameters as well as form submission and generating
|
||||
* search queries.
|
||||
*/
|
||||
class AdvancedSearchQueryTerm {
|
||||
// Conjunctions.
|
||||
// @see https://lucene.apache.org/solr/guide/7_1/the-standard-query-parser.html#TheStandardQueryParser-BooleanOperatorsSupportedbytheStandardQueryParser
|
||||
const CONJUNCTION_AND = 'AND';
|
||||
const CONJUNCTION_OR = 'OR';
|
||||
|
||||
// Used for serializing / deserializing query parameters.
|
||||
// These are also hard-coded in advanced_search.form.js.
|
||||
const CONJUNCTION_QUERY_PARAMETER = 'c';
|
||||
const FIELD_QUERY_PARAMETER = 'f';
|
||||
const INCLUDE_QUERY_PARAMETER = 'i';
|
||||
const VALUE_QUERY_PARAMETER = 'v';
|
||||
|
||||
// Defaults.
|
||||
const DEFAULT_CONJUNCTION = self::CONJUNCTION_AND;
|
||||
const DEFAULT_INCLUDE = TRUE;
|
||||
|
||||
/**
|
||||
* The field to search.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $field;
|
||||
|
||||
/**
|
||||
* Include / exclude results where 'value' is in the 'search' term.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $include = TRUE;
|
||||
|
||||
/**
|
||||
* The value to filter with.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $value;
|
||||
|
||||
/**
|
||||
* The conjunction to use for the condition group – either 'AND' or 'OR'.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $conjunction;
|
||||
|
||||
/**
|
||||
* Constructs a FacetBlockAjaxController object.
|
||||
*
|
||||
* @param string $field
|
||||
* The field to search against.
|
||||
* @param string $value
|
||||
* The value to search the field with.
|
||||
* @param bool $include
|
||||
* Limit results to records whose field contains or does not contain the
|
||||
* given value.
|
||||
* @param string $conjunction
|
||||
* The conjunction to apply when combining this search term along with
|
||||
* others.
|
||||
*/
|
||||
public function __construct(string $field, string $value, bool $include = self::DEFAULT_INCLUDE, string $conjunction = self::DEFAULT_CONJUNCTION) {
|
||||
$this->field = $field;
|
||||
$this->value = $value;
|
||||
switch ($conjunction) {
|
||||
case self::CONJUNCTION_AND:
|
||||
case self::CONJUNCTION_OR:
|
||||
$this->conjunction = $conjunction;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new \InvalidArgumentException('Invalid value given for argument "conjunction": $conjunction');
|
||||
}
|
||||
if ($this->conjunction == self::CONJUNCTION_OR && !$include) {
|
||||
throw new \InvalidArgumentException('Excluding terms with the conjunction "OR" is not supported');
|
||||
}
|
||||
$this->include = $include;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate 'include' or fallback to default value.
|
||||
*
|
||||
* @param string $include
|
||||
* The value to cast to a boolean if possible.
|
||||
*
|
||||
* @return bool
|
||||
* The normalized input for 'include' or its default.
|
||||
*/
|
||||
protected static function normalizeInclude(string $include) {
|
||||
switch (strtoupper($include)) {
|
||||
case AdvancedSearchForm::IS_OP:
|
||||
return TRUE;
|
||||
|
||||
case AdvancedSearchForm::NOT_OP:
|
||||
return FALSE;
|
||||
|
||||
default:
|
||||
$include = filter_var($include, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
|
||||
// Ignore include parameter if invalid and fallback to the default.
|
||||
return is_bool($include) ? $include : self::DEFAULT_INCLUDE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate 'conjunction' or fallback to default value.
|
||||
*
|
||||
* @param string $conjunction
|
||||
* The conjunction to validate.
|
||||
*
|
||||
* @return string
|
||||
* The normalized input for 'include' or its default.
|
||||
*/
|
||||
protected static function normalizeConjunction(string $conjunction) {
|
||||
switch (strtoupper($conjunction)) {
|
||||
case self::CONJUNCTION_AND:
|
||||
return self::CONJUNCTION_AND;
|
||||
|
||||
case self::CONJUNCTION_OR:
|
||||
return self::CONJUNCTION_OR;
|
||||
|
||||
default:
|
||||
return self::DEFAULT_CONJUNCTION;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a AdvancedSearchQueryTerm from the given parameters if possible.
|
||||
*
|
||||
* @param array $params
|
||||
* An array representing the query parameters for a single search term.
|
||||
*
|
||||
* @return \Drupal\advanced_search\AdvancedSearchQueryTerm|null
|
||||
* An object which represents a valid search term.
|
||||
*/
|
||||
public static function fromQueryParams(array $params) {
|
||||
// Field & value are required values. We do not check if field is a valid
|
||||
// value only that it is non-empty. All other fields will be cast to
|
||||
// defaults if they are not valid / missing.
|
||||
$has_required_params = isset($params[self::FIELD_QUERY_PARAMETER], $params[self::VALUE_QUERY_PARAMETER]);
|
||||
$search_value_empty = isset($params[self::VALUE_QUERY_PARAMETER]) && empty($params[self::VALUE_QUERY_PARAMETER]);
|
||||
if (!$has_required_params || $search_value_empty) {
|
||||
return NULL;
|
||||
}
|
||||
$field = $params[self::FIELD_QUERY_PARAMETER];
|
||||
$value = $params[self::VALUE_QUERY_PARAMETER];
|
||||
$include = isset($params[self::INCLUDE_QUERY_PARAMETER]) ?
|
||||
$include = self::normalizeInclude($params[self::INCLUDE_QUERY_PARAMETER]) :
|
||||
self::DEFAULT_INCLUDE;
|
||||
$conjunction = isset($params[self::CONJUNCTION_QUERY_PARAMETER]) ?
|
||||
self::normalizeConjunction($params[self::CONJUNCTION_QUERY_PARAMETER]) :
|
||||
self::DEFAULT_CONJUNCTION;
|
||||
return new self($field, $value, $include, $conjunction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a AdvancedSearchQueryTerm from user submitted form values.
|
||||
*
|
||||
* @param array $input
|
||||
* An array representing the submitted form values for a single search term.
|
||||
*
|
||||
* @return \Drupal\advanced_search\AdvancedSearchQueryTerm|null
|
||||
* An object which represents a valid search term.
|
||||
*/
|
||||
public static function fromUserInput(array $input) {
|
||||
// Search field & value are required values we do not check if field is a
|
||||
// valid value only that it is non-empty. All other fields will use
|
||||
// defaults if they are not valid / missing.
|
||||
$has_required_inputs = isset($input[AdvancedSearchForm::SEARCH_FORM_FIELD], $input[AdvancedSearchForm::VALUE_FORM_FIELD]);
|
||||
$search_value_empty = isset($input[AdvancedSearchForm::VALUE_FORM_FIELD]) && empty($input[AdvancedSearchForm::VALUE_FORM_FIELD]);
|
||||
if (!$has_required_inputs || $search_value_empty) {
|
||||
return NULL;
|
||||
}
|
||||
$field = $input[AdvancedSearchForm::SEARCH_FORM_FIELD];
|
||||
$value = $input[AdvancedSearchForm::VALUE_FORM_FIELD];
|
||||
$include = self::DEFAULT_INCLUDE;
|
||||
$conjunction = self::DEFAULT_CONJUNCTION;
|
||||
if (isset($input[AdvancedSearchForm::CONJUNCTION_FORM_FIELD])) {
|
||||
switch ($input[AdvancedSearchForm::CONJUNCTION_FORM_FIELD]) {
|
||||
case AdvancedSearchForm::AND_OP:
|
||||
$conjunction = self::CONJUNCTION_AND;
|
||||
break;
|
||||
|
||||
case AdvancedSearchForm::OR_OP:
|
||||
$conjunction = self::CONJUNCTION_OR;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Only allow users to specify include when using 'AND' conjunction.
|
||||
if (
|
||||
$conjunction == self::CONJUNCTION_AND
|
||||
&& isset($input[AdvancedSearchForm::INCLUDE_FORM_FIELD])
|
||||
) {
|
||||
switch ($input[AdvancedSearchForm::INCLUDE_FORM_FIELD]) {
|
||||
case AdvancedSearchForm::IS_OP:
|
||||
$include = TRUE;
|
||||
break;
|
||||
|
||||
case AdvancedSearchForm::NOT_OP:
|
||||
$include = FALSE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return new self($field, $value, $include, $conjunction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get query parameter representation of this search term.
|
||||
*
|
||||
* @return array
|
||||
* Representation of this search term which can be serialized to a query
|
||||
* parameter.
|
||||
*/
|
||||
public function toQueryParams() {
|
||||
$params = [
|
||||
self::FIELD_QUERY_PARAMETER => $this->field,
|
||||
self::VALUE_QUERY_PARAMETER => $this->value,
|
||||
];
|
||||
// No need to specify conjunction if it is equivalent to the default.
|
||||
if ($this->conjunction != self::DEFAULT_CONJUNCTION) {
|
||||
$params[self::CONJUNCTION_QUERY_PARAMETER] = $this->conjunction;
|
||||
}
|
||||
if ($this->include != self::DEFAULT_CONJUNCTION) {
|
||||
$params[self::INCLUDE_QUERY_PARAMETER] = $this->include ? '1' : '0';
|
||||
}
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user input of search form representation of this search term.
|
||||
*
|
||||
* @return array
|
||||
* Representation of this search term which can be used as input to the
|
||||
* advanced search form.
|
||||
*/
|
||||
public function toUserInput() {
|
||||
return [
|
||||
AdvancedSearchForm::SEARCH_FORM_FIELD => $this->field,
|
||||
AdvancedSearchForm::VALUE_FORM_FIELD => $this->value,
|
||||
AdvancedSearchForm::INCLUDE_FORM_FIELD => $this->include ? AdvancedSearchForm::IS_OP : AdvancedSearchForm::NOT_OP,
|
||||
AdvancedSearchForm::CONJUNCTION_FORM_FIELD => $this->conjunction == self::CONJUNCTION_AND ? AdvancedSearchForm::AND_OP : AdvancedSearchForm::OR_OP,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets if this term should be included / excluded from results.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the term should be include in results, FALSE otherwise.
|
||||
*/
|
||||
public function getInclude() {
|
||||
return $this->include;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the conjunction for this term.
|
||||
*
|
||||
* @return string
|
||||
* The conjunction to use for this term.
|
||||
*/
|
||||
public function getConjunction() {
|
||||
return $this->conjunction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Using the provided field mapping create a Solr Query string.
|
||||
*
|
||||
* @param array $solr_field_mapping
|
||||
* An array that maps search api fields to one or more solr fields.
|
||||
*
|
||||
* @return string
|
||||
* The conjunction to use for this term conjunction.
|
||||
*/
|
||||
public function toSolrQuery(array $solr_field_mapping) {
|
||||
$terms = [];
|
||||
$query_helper = \Drupal::service('solarium.query_helper');
|
||||
$value = $query_helper->escapePhrase(trim($this->value));
|
||||
|
||||
$config = \Drupal::config(SettingsForm::CONFIG_NAME);
|
||||
$isDismax = $config->get(SettingsForm::EDISMAX_SEARCH_FLAG);
|
||||
if (!isset($isDismax)) {
|
||||
$isDismax = TRUE;
|
||||
}
|
||||
|
||||
if ($isDismax || $this->field === "all") {
|
||||
|
||||
// Case 1: if keyword contains one word or a phrase.
|
||||
if (strpos(trim($value), ' ') !== FALSE) {
|
||||
// Add Or for the search case "scarborough bulletin" show no results.
|
||||
$isNot = $this->getInclude() ? "" : "-";
|
||||
if (substr_count($value, '\"') == 2) {
|
||||
$value = str_replace('\"', "", trim($value));
|
||||
return $isNot . $value;
|
||||
}
|
||||
else {
|
||||
return $isNot . "(" . $value . " OR " . str_replace('"', "", trim($value)) . ")";
|
||||
}
|
||||
}
|
||||
if (!$this->getInclude()) {
|
||||
$value = "!" . str_replace('"', "", trim($value));
|
||||
}
|
||||
else {
|
||||
// Case 2: keywords is one word
|
||||
// if there is quotation (with backslash) surrounded,.
|
||||
if (strpos(trim($value), '\"') !== FALSE) {
|
||||
$value = str_replace('\"', "", trim($value));
|
||||
}
|
||||
else {
|
||||
// If there is quotation (without backslash) surrounded.
|
||||
$value = str_replace('"', "", trim($value));
|
||||
}
|
||||
}
|
||||
// Fixed for https://github.com/digitalutsc/advanced_search/issues/4
|
||||
if ($this->field !== "all"){
|
||||
$search_fields = "";
|
||||
foreach ($solr_field_mapping[$this->field] as $field) {
|
||||
$search_fields .= " $field:$value";
|
||||
}
|
||||
return $search_fields;
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
else {
|
||||
$isTitleSearch = FALSE;
|
||||
foreach ($solr_field_mapping[$this->field] as $field) {
|
||||
// If field fulltext title is selected.
|
||||
if (strpos($field, "fulltext_title") !== FALSE) {
|
||||
$isTitleSearch = TRUE;
|
||||
if (strpos(trim($value), " AND ") !== FALSE) {
|
||||
// Handle keyword with 'Orientation AND games'.
|
||||
$keyword = str_replace('"', '', $value);
|
||||
$keys = explode(" AND ", $keyword);
|
||||
$str = "(";
|
||||
$i = 0;
|
||||
foreach ($keys as $key) {
|
||||
|
||||
if ($i != count($keys) - 1) {
|
||||
$str .= $field . ':"' . $key . '" AND ';
|
||||
}
|
||||
else {
|
||||
$str .= $field . ':"' . $key . '")';
|
||||
}
|
||||
$i++;
|
||||
}
|
||||
$terms[] = $str;
|
||||
}
|
||||
else {
|
||||
if ($isTitleSearch) {
|
||||
$terms[] = 'tm_lowercase_title:' . $value;
|
||||
}
|
||||
else {
|
||||
$terms[] = "$field:$value";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
else {
|
||||
$terms[] = "$field:$value";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
$terms = implode(' ', $terms);
|
||||
return $this->include ? "($terms)" : "-($terms)";
|
||||
}
|
||||
|
||||
/**
|
||||
* Using the provided field mapping create a Solr Fields string.
|
||||
*
|
||||
* @param array $solr_field_mapping
|
||||
* An array that maps search api fields to one or more solr fields.
|
||||
*
|
||||
* @return string
|
||||
* The conjunction to use for this term conjunction.
|
||||
*/
|
||||
public function toSolrFields(array $solr_field_mapping) {
|
||||
$terms = [];
|
||||
$query_helper = \Drupal::service('solarium.query_helper');
|
||||
|
||||
if ($this->field !== "all") {
|
||||
foreach ($solr_field_mapping[$this->field] as $field) {
|
||||
$terms[] = "$field";
|
||||
}
|
||||
}
|
||||
$terms = implode(' ', $terms);
|
||||
return $terms;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Field search.
|
||||
*/
|
||||
public function getField() {
|
||||
return $this->field;
|
||||
}
|
||||
|
||||
}
|
||||
165
src/Controller/AjaxBlocksController.php
Normal file
165
src/Controller/AjaxBlocksController.php
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\advanced_search\Controller;
|
||||
|
||||
use Drupal\Core\Ajax\AjaxResponse;
|
||||
use Drupal\Core\Ajax\ReplaceCommand;
|
||||
use Drupal\Core\Controller\ControllerBase;
|
||||
use Drupal\Core\Path\CurrentPathStack;
|
||||
use Drupal\Core\PathProcessor\PathProcessorManager;
|
||||
use Drupal\Core\Render\RendererInterface;
|
||||
use Drupal\Core\Routing\CurrentRouteMatch;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
use Symfony\Component\Routing\RouterInterface;
|
||||
|
||||
/**
|
||||
* Defines a controller to load a facet via AJAX.
|
||||
*/
|
||||
class AjaxBlocksController extends ControllerBase {
|
||||
|
||||
/**
|
||||
* The entity storage for block.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityStorageInterface
|
||||
*/
|
||||
protected $storage;
|
||||
|
||||
/**
|
||||
* The renderer.
|
||||
*
|
||||
* @var \Drupal\Core\Render\RendererInterface
|
||||
*/
|
||||
protected $renderer;
|
||||
|
||||
/**
|
||||
* The current path.
|
||||
*
|
||||
* @var \Drupal\Core\Path\CurrentPathStack
|
||||
*/
|
||||
protected $currentPath;
|
||||
|
||||
/**
|
||||
* The dynamic router service.
|
||||
*
|
||||
* @var \Symfony\Component\Routing\Matcher\RequestMatcherInterface
|
||||
*/
|
||||
protected $router;
|
||||
|
||||
/**
|
||||
* The path processor service.
|
||||
*
|
||||
* @var \Drupal\Core\PathProcessor\InboundPathProcessorInterface
|
||||
*/
|
||||
protected $pathProcessor;
|
||||
|
||||
/**
|
||||
* The current route match service.
|
||||
*
|
||||
* @var \Drupal\Core\Routing\CurrentRouteMatch
|
||||
*/
|
||||
protected $currentRouteMatch;
|
||||
|
||||
/**
|
||||
* The service container this instance should use.
|
||||
*
|
||||
* @var \Symfony\Component\DependencyInjection\ContainerInterface
|
||||
*/
|
||||
protected $container;
|
||||
|
||||
/**
|
||||
* Constructs a FacetBlockAjaxController object.
|
||||
*
|
||||
* @param \Drupal\Core\Render\RendererInterface $renderer
|
||||
* The renderer service.
|
||||
* @param \Drupal\Core\Path\CurrentPathStack $currentPath
|
||||
* The current path service.
|
||||
* @param \Symfony\Component\Routing\RouterInterface $router
|
||||
* The router service.
|
||||
* @param \Drupal\Core\PathProcessor\PathProcessorManager $pathProcessor
|
||||
* The path processor manager.
|
||||
* @param \Drupal\Core\Routing\CurrentRouteMatch $currentRouteMatch
|
||||
* The current route match service.
|
||||
* @param \Symfony\Component\DependencyInjection\ContainerInterface $container
|
||||
* The drupal container.
|
||||
*/
|
||||
final public function __construct(RendererInterface $renderer, CurrentPathStack $currentPath, RouterInterface $router, PathProcessorManager $pathProcessor, CurrentRouteMatch $currentRouteMatch, ContainerInterface $container) {
|
||||
$this->storage = $this->entityTypeManager()->getStorage('block');
|
||||
$this->renderer = $renderer;
|
||||
$this->currentPath = $currentPath;
|
||||
$this->router = $router;
|
||||
$this->pathProcessor = $pathProcessor;
|
||||
$this->currentRouteMatch = $currentRouteMatch;
|
||||
$this->container = $container;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('renderer'),
|
||||
$container->get('path.current'),
|
||||
$container->get('router'),
|
||||
$container->get('path_processor_manager'),
|
||||
$container->get('current_route_match'),
|
||||
$container
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads and renders the facet blocks via AJAX.
|
||||
*
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* The current request object.
|
||||
*
|
||||
* @return \Drupal\Core\Ajax\AjaxResponse
|
||||
* The ajax response.
|
||||
*
|
||||
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
|
||||
* Thrown when the view was not found.
|
||||
*/
|
||||
public function respond(Request $request) {
|
||||
$response = new AjaxResponse();
|
||||
|
||||
// Rebuild the request and the current path, needed for facets.
|
||||
$path = $request->request->get('link');
|
||||
$blocks = $request->request->all('blocks');
|
||||
|
||||
// Make sure we are not updating blocks multiple times.
|
||||
$blocks = array_unique($blocks);
|
||||
|
||||
if (empty($path) || empty($blocks)) {
|
||||
throw new NotFoundHttpException('No facet link or facet blocks found.');
|
||||
}
|
||||
|
||||
$new_request = Request::create($path);
|
||||
$new_request->setSession($request->getSession());
|
||||
$request_stack = \Drupal::requestStack();
|
||||
$processed = $this->pathProcessor->processInbound($new_request->getPathInfo(), $new_request);
|
||||
|
||||
$this->currentPath->setPath($processed);
|
||||
$request->attributes->add($this->router->matchRequest($new_request));
|
||||
$this->currentRouteMatch->resetRouteMatch();
|
||||
$request_stack->push($new_request);
|
||||
$this->container->set('request_stack', $request_stack);
|
||||
|
||||
// Build the facets blocks found for the current request and update.
|
||||
foreach ($blocks as $block_id => $block_selector) {
|
||||
$block_entity = $this->storage->load($block_id);
|
||||
|
||||
if ($block_entity) {
|
||||
// Render a block, then add it to the response as a replace command.
|
||||
$block_view = $this->entityTypeManager
|
||||
->getViewBuilder('block')
|
||||
->view($block_entity);
|
||||
|
||||
$block_view = (string) $this->renderer->renderPlain($block_view);
|
||||
$response->addCommand(new ReplaceCommand($block_selector, $block_view));
|
||||
}
|
||||
}
|
||||
return $response;
|
||||
}
|
||||
|
||||
}
|
||||
43
src/EventSubscriber/PostConvertedQueryEventSubscriber.php
Normal file
43
src/EventSubscriber/PostConvertedQueryEventSubscriber.php
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\advanced_search\EventSubscriber;
|
||||
|
||||
use Drupal\advanced_search\AdvancedSearchQuery;
|
||||
use Drupal\search_api_solr\Event\PostConvertedQueryEvent;
|
||||
use Drupal\search_api_solr\Event\SearchApiSolrEvents;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
/**
|
||||
* Subscribes to PostConvertedQueryEvents.
|
||||
*
|
||||
* @package Drupal\advanced_search\EventSubscriber
|
||||
*/
|
||||
class PostConvertedQueryEventSubscriber implements EventSubscriberInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function getSubscribedEvents() {
|
||||
$events[SearchAPISolrEvents::POST_CONVERT_QUERY][] = ['alter'];
|
||||
|
||||
return $events;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Alter the query.
|
||||
*/
|
||||
public function alter(PostConvertedQueryEvent $event) {
|
||||
$search_api_query = $event->getSearchApiQuery();
|
||||
$solarium_query = $event->getSolariumQuery();
|
||||
|
||||
// We must modify the query itself rather than the representation the
|
||||
// search_api presents as it is not possible to use the 'OR' operator
|
||||
// with it as it converts conditions into separate filter queries.
|
||||
// Additionally filter queries do not affect the score so are not
|
||||
// suitable for use in the advanced search queries.
|
||||
$advanced_search_query = new AdvancedSearchQuery();
|
||||
$advanced_search_query->alterQuery(\Drupal::request(), $solarium_query, $search_api_query);
|
||||
}
|
||||
|
||||
}
|
||||
487
src/Form/AdvancedSearchForm.php
Normal file
487
src/Form/AdvancedSearchForm.php
Normal file
|
|
@ -0,0 +1,487 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\advanced_search\Form;
|
||||
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Core\Form\FormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
use Drupal\advanced_search\AdvancedSearchQuery;
|
||||
use Drupal\advanced_search\AdvancedSearchQueryTerm;
|
||||
use Drupal\advanced_search\GetConfigTrait;
|
||||
use Drupal\views\DisplayPluginCollection;
|
||||
use Drupal\views\Entity\View;
|
||||
use Drupal\views\Plugin\views\display\PathPluginBase;
|
||||
use Drupal\views\Views;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* Form for building and Advanced Search Query.
|
||||
*/
|
||||
class AdvancedSearchForm extends FormBase {
|
||||
use GetConfigTrait;
|
||||
|
||||
// Users can customize the operator to use font-awesome or some other icons.
|
||||
// Its a limitation in the use of `input type=submit` rather than buttons in
|
||||
// Drupal that we couldn't just rely on CSS.
|
||||
// This is exposed in the module settings.
|
||||
// @see https://www.drupal.org/project/drupal/issues/1671190
|
||||
const DEFAULT_ADD_OP = '+';
|
||||
const DEFAULT_REMOVE_OP = '-';
|
||||
|
||||
const AND_OP = 'AND';
|
||||
const IS_OP = 'IS';
|
||||
const NOT_OP = 'NOT';
|
||||
const OR_OP = 'OR';
|
||||
|
||||
// These are also hard-coded in advanced_search.form.js.
|
||||
const CONJUNCTION_FORM_FIELD = 'conjunction';
|
||||
const SEARCH_FORM_FIELD = 'search';
|
||||
const INCLUDE_FORM_FIELD = 'include';
|
||||
const VALUE_FORM_FIELD = 'value';
|
||||
|
||||
const AJAX_WRAPPER = 'advanced-search-ajax';
|
||||
|
||||
/**
|
||||
* The current request.
|
||||
*
|
||||
* @var \Symfony\Component\HttpFoundation\Request
|
||||
*/
|
||||
protected $request;
|
||||
|
||||
/**
|
||||
* The current route match.
|
||||
*
|
||||
* @var \Drupal\Core\Routing\RouteMatchInterface
|
||||
*/
|
||||
protected $currentRouteMatch;
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*/
|
||||
final public function __construct(Request $request, RouteMatchInterface $current_route_match) {
|
||||
$this->request = $request;
|
||||
$this->currentRouteMatch = $current_route_match;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('request_stack')->getMainRequest(),
|
||||
$container->get('current_route_match')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'advanced_search_form';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the character to use for adding a facet to the query.
|
||||
*
|
||||
* @return string
|
||||
* The character to use for adding an facet to the query.
|
||||
*/
|
||||
public static function getAddOperator() {
|
||||
return self::getConfig(SettingsForm::SEARCH_ADD_OPERATOR, self::DEFAULT_ADD_OP);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the character to use for removing a facet from the query.
|
||||
*
|
||||
* @return string
|
||||
* The character to use for removing an facet to the query.
|
||||
*/
|
||||
public static function getRemoveOperator() {
|
||||
return self::getConfig(SettingsForm::SEARCH_REMOVE_OPERATOR, self::DEFAULT_REMOVE_OP);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get if Search All Fields checkbox is enabled or disable.
|
||||
*
|
||||
* @return bool
|
||||
* the enable or disable for Search All Fields checkbox
|
||||
*/
|
||||
public static function getSearchAllFields() {
|
||||
return self::getConfig(SettingsForm::SEARCH_ALL_FIELDS_FLAG, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get if Edismax Search checkbox is enabled or disable.
|
||||
*
|
||||
* @return bool
|
||||
* the enable or disable for Edismax Search checkbox
|
||||
*/
|
||||
public static function getEdismaxSearch() {
|
||||
return self::getConfig(SettingsForm::EDISMAX_SEARCH_FLAG, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the character to use for removing a facet from the query.
|
||||
*
|
||||
* @return string
|
||||
* The character to use for removing an facet to the query.
|
||||
*/
|
||||
public static function getEdismaxSearchLabel() {
|
||||
return self::getConfig(SettingsForm::EDISMAX_SEARCH_LABEL, "All");
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the list of fields to select options.
|
||||
*
|
||||
* @param \Drupal\search_api\Item\FieldInterface[] $fields
|
||||
* The fields to convert to select options.
|
||||
*
|
||||
* @return array
|
||||
* Array of fields which can be searched where the key is the search field
|
||||
* identifier and the value is its human readable label.
|
||||
*/
|
||||
protected function fieldOptions(array $fields) {
|
||||
$options = [];
|
||||
foreach ($fields as $field) {
|
||||
$id = $field->getFieldIdentifier();
|
||||
$options[$id] = $field->getLabel();
|
||||
}
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets possible include options for the given conjunction.
|
||||
*/
|
||||
protected function includeOptions(string $conjunction) {
|
||||
switch ($conjunction) {
|
||||
case self::AND_OP:
|
||||
return;
|
||||
|
||||
case self::OR_OP:
|
||||
return [
|
||||
self::IS_OP => $this->t('is'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Default values to for a term.
|
||||
*/
|
||||
protected function defaultTermValues(array $options) {
|
||||
return [
|
||||
self::CONJUNCTION_FORM_FIELD => self::AND_OP,
|
||||
// First item in list is default.
|
||||
self::SEARCH_FORM_FIELD => key($options),
|
||||
self::INCLUDE_FORM_FIELD => self::IS_OP,
|
||||
self::VALUE_FORM_FIELD => NULL,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Process input to the from either URL parameters or from the form input.
|
||||
*/
|
||||
protected function processInput(FormStateInterface $form_state, array $term_default_values) {
|
||||
$input = $form_state->getUserInput();
|
||||
$recursive = $input['recursive'] ?? NULL;
|
||||
$term_values = isset($input['terms']) && is_array($input['terms']) ? $input['terms'] : [];
|
||||
// Form was not submitted see if we can rebuild from query parameters.
|
||||
$advanced_search_query = new AdvancedSearchQuery();
|
||||
if (empty($term_values)) {
|
||||
$terms = $advanced_search_query->getTerms($this->request);
|
||||
foreach ($terms as $term) {
|
||||
$term_values[] = $term->toUserInput();
|
||||
}
|
||||
}
|
||||
if (!isset($input['recursive'])) {
|
||||
$recursive = $advanced_search_query->shouldRecurse($this->request);
|
||||
}
|
||||
// Form was submitted via +/- operators.
|
||||
$trigger = $form_state->getTriggeringElement();
|
||||
if ($trigger != NULL) {
|
||||
$term_index = $trigger['#term_index'] ?? 0;
|
||||
$value = $trigger['#value'] instanceof TranslatableMarkup ?
|
||||
$trigger['#value']->getUntranslatedString() :
|
||||
$trigger['#value'];
|
||||
switch ($value) {
|
||||
case $this->getAddOperator():
|
||||
// Insert after the term listed.
|
||||
array_splice($term_values, $term_index + 1, 0, [$term_default_values]);
|
||||
break;
|
||||
|
||||
case $this->getRemoveOperator():
|
||||
array_splice($term_values, $term_index, 1);
|
||||
break;
|
||||
|
||||
case "Reset":
|
||||
$recursive = FALSE;
|
||||
$term_values = [];
|
||||
break;
|
||||
|
||||
// Ignore unknown value for trigger.
|
||||
}
|
||||
// Place user input with updated values.
|
||||
$input['terms'] = $term_values;
|
||||
$input['recursive'] = $recursive;
|
||||
$form_state->setUserInput($input);
|
||||
}
|
||||
return [$recursive, $term_values];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the route name for the view display used to derive this forms block.
|
||||
*
|
||||
* @return string|null
|
||||
* The route name for the view display that was used to create this
|
||||
* forms block.
|
||||
*/
|
||||
protected function getRouteName(FormStateInterface $form_state) {
|
||||
$view = $form_state->get('view');
|
||||
$display = $form_state->get('display');
|
||||
$display_handlers = new DisplayPluginCollection($view->getExecutable(), Views::pluginManager('display'));
|
||||
$display_handler = $display_handlers->get($display['id']);
|
||||
if ($display_handler instanceof PathPluginBase) {
|
||||
return $display_handler->getRouteName();
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state, View $view = NULL, array $display = [], array $fields = [], string $context_filter = NULL) {
|
||||
// Keep reference to view and display as the submit handler may use them
|
||||
// to redirect the user to the search page.
|
||||
$form_state->set('view', $view);
|
||||
$form_state->set('display', $display);
|
||||
$route_name = $this->getRouteName($form_state);
|
||||
$requires_redirect = $route_name ? $this->currentRouteMatch->getRouteName() !== $route_name : FALSE;
|
||||
|
||||
$form['#attached']['library'][] = 'advanced_search/advanced.search.form';
|
||||
$form['#attached']['drupalSettings']['advanced_search_form'] = [
|
||||
'id' => Html::getId($this->getFormId()),
|
||||
'redirect' => $requires_redirect,
|
||||
'query_parameter' => AdvancedSearchQuery::getQueryParameter(),
|
||||
'recurse_parameter' => AdvancedSearchQuery::getRecurseParameter(),
|
||||
'mapping' => [
|
||||
self::CONJUNCTION_FORM_FIELD => AdvancedSearchQueryTerm::CONJUNCTION_QUERY_PARAMETER,
|
||||
self::SEARCH_FORM_FIELD => AdvancedSearchQueryTerm::FIELD_QUERY_PARAMETER,
|
||||
self::INCLUDE_FORM_FIELD => AdvancedSearchQueryTerm::INCLUDE_QUERY_PARAMETER,
|
||||
self::VALUE_FORM_FIELD => AdvancedSearchQueryTerm::VALUE_QUERY_PARAMETER,
|
||||
],
|
||||
];
|
||||
|
||||
$options = (self::getEdismaxSearch() && self::getSearchAllFields()) ? ["all" => $this->t("@label", ["@label" => self::getEdismaxSearchLabel()])] + $this->fieldOptions($fields) : $this->fieldOptions($fields);
|
||||
$term_default_values = $this->defaultTermValues($options);
|
||||
[$recursive, $term_values] = $this->processInput($form_state, $term_default_values);
|
||||
$i = 0;
|
||||
$term_elements = [];
|
||||
$total_terms = count($term_values);
|
||||
$block_class_prefix = str_replace('_', '-', $this->getFormId());
|
||||
do {
|
||||
// Either specified by the user in the request or use the default.
|
||||
$first = $i == 0;
|
||||
$term_value = !empty($term_values) ? array_shift($term_values) : $term_default_values;
|
||||
$conjunction = $term_value[self::CONJUNCTION_FORM_FIELD] ?? $term_default_values[self::CONJUNCTION_FORM_FIELD];
|
||||
$term_elements[] = [
|
||||
// Only show on terms after the first.
|
||||
self::CONJUNCTION_FORM_FIELD => $first ? NULL : [
|
||||
'#type' => 'select',
|
||||
'#attributes' => [
|
||||
'aria-label' => $this->t("Select search condition"),
|
||||
],
|
||||
'#options' => [
|
||||
self::AND_OP => $this->t('and'),
|
||||
self::OR_OP => $this->t('or'),
|
||||
],
|
||||
'#default_value' => $conjunction,
|
||||
'#theme_wrappers' => [],
|
||||
],
|
||||
'entity' => [
|
||||
'#type' => 'select',
|
||||
'#attributes' => [
|
||||
'aria-label' => $this->t("Select Entity"),
|
||||
'class' => [$block_class_prefix . '-entity'],
|
||||
],
|
||||
'#options' => [
|
||||
'alles' => $this->t('Alles'),
|
||||
'b48556e79962e0a3c8d0041317c853b9' => $this->t('Archivalie'),
|
||||
'ba419826c9014f40126565bf413f7a59' => $this->t('Auktion'),
|
||||
'b1d559f7b6af224a3f6f3b9a12e6b161' => $this->t('Institution'),
|
||||
'b65c3a85d16724d84a5eb0d2268629a6' => $this->t('Objekt'),
|
||||
'b1afe1fa9a31c7622ab2ae8ef1d29673' => $this->t('Person'),
|
||||
],
|
||||
//'#default_value' => 'alles',
|
||||
],
|
||||
self::SEARCH_FORM_FIELD => [
|
||||
'#type' => 'select',
|
||||
'#attributes' => [
|
||||
'aria-label' => $this->t("Select search field"),
|
||||
],
|
||||
'#options' => $options,
|
||||
'#default_value' => $term_value[self::SEARCH_FORM_FIELD],
|
||||
'#theme_wrappers' => [],
|
||||
],
|
||||
self::INCLUDE_FORM_FIELD => [
|
||||
'#type' => 'select',
|
||||
'#attributes' => [
|
||||
'aria-label' => $this->t("Select search operator"),
|
||||
],
|
||||
'#options' => [
|
||||
self::IS_OP => $this->t('is'),
|
||||
self::NOT_OP => $this->t('is not'),
|
||||
],
|
||||
'#default_value' => $term_value[self::INCLUDE_FORM_FIELD],
|
||||
// Show only when conjunction is 'AND' as 'OR NOT' is not supported
|
||||
// by solr and will be converted to 'AND NOT'.
|
||||
'#states' => [
|
||||
'visible' => [
|
||||
':input[name="terms[' . $i . '][' . self::CONJUNCTION_FORM_FIELD . ']"]' => ['value' => self::AND_OP],
|
||||
],
|
||||
],
|
||||
'#theme_wrappers' => [],
|
||||
],
|
||||
// Just markup to show when 'include' is not alterable due to the
|
||||
// selected 'conjunction'. Hide for the first term.
|
||||
'is' => $first ? NULL : [
|
||||
'#type' => 'container',
|
||||
'#attributes' => ['style' => 'display:inline;'],
|
||||
'#states' => [
|
||||
'visible' => [
|
||||
':input[name="terms[' . $i . '][' . self::CONJUNCTION_FORM_FIELD . ']"]' => ['value' => self::OR_OP],
|
||||
],
|
||||
],
|
||||
/*'content' => [
|
||||
'#markup' => $this->t('is'),
|
||||
],*/
|
||||
'#theme_wrappers' => [],
|
||||
],
|
||||
self::VALUE_FORM_FIELD => [
|
||||
'#type' => 'textfield',
|
||||
'#attributes' => [
|
||||
'aria-label' => $this->t("Enter a search term"),
|
||||
],
|
||||
'#default_value' => $term_value[self::VALUE_FORM_FIELD],
|
||||
'#theme_wrappers' => [],
|
||||
],
|
||||
'actions' => [
|
||||
'#type' => 'container',
|
||||
'add' => [
|
||||
'#type' => 'button',
|
||||
'#value' => $this->getAddOperator(),
|
||||
'#name' => 'add-term-' . $i,
|
||||
'#term_index' => $i,
|
||||
'#attributes' => [
|
||||
'class' => [$block_class_prefix . '__add', 'fa'],
|
||||
],
|
||||
'#ajax' => [
|
||||
'callback' => [$this, 'ajaxCallback'],
|
||||
'wrapper' => self::AJAX_WRAPPER,
|
||||
'progress' => [
|
||||
'type' => 'none',
|
||||
],
|
||||
],
|
||||
],
|
||||
'remove' => $total_terms <= 1 ? NULL : [
|
||||
'#type' => 'button',
|
||||
'#value' => $this->getRemoveOperator(),
|
||||
'#name' => 'remove-term-' . $i,
|
||||
'#term_index' => $i,
|
||||
'#attributes' => [
|
||||
'class' => [$block_class_prefix . '__remove', 'fa'],
|
||||
'aria-label' => $this->t("Remove"),
|
||||
],
|
||||
'#ajax' => [
|
||||
'callback' => [$this, 'ajaxCallback'],
|
||||
'wrapper' => self::AJAX_WRAPPER,
|
||||
'progress' => [
|
||||
'type' => 'none',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
$i++;
|
||||
} while (!empty($term_values));
|
||||
|
||||
$form['ajax'] = [
|
||||
'#type' => 'container',
|
||||
'#attributes' => ['id' => self::AJAX_WRAPPER],
|
||||
'terms' => array_merge([
|
||||
'#tree' => TRUE,
|
||||
'#type' => 'container',
|
||||
], $term_elements),
|
||||
];
|
||||
|
||||
if ($context_filter != NULL) {
|
||||
$form['ajax']['recursive'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Include Sub-Collections'),
|
||||
'#default_value' => $recursive,
|
||||
];
|
||||
}
|
||||
$form['reset'] = [
|
||||
'#type' => 'button',
|
||||
'#value' => $this->t('Reset'),
|
||||
'#attributes' => [
|
||||
'class' => [$block_class_prefix . '__reset'],
|
||||
],
|
||||
'#ajax' => [
|
||||
'callback' => [$this, 'ajaxCallback'],
|
||||
'wrapper' => self::AJAX_WRAPPER,
|
||||
'progress' => [
|
||||
'type' => 'none',
|
||||
],
|
||||
],
|
||||
];
|
||||
$form['submit'] = [
|
||||
'#type' => 'submit',
|
||||
'#value' => $this->t('Search'),
|
||||
'#attributes' => [
|
||||
'class' => [$block_class_prefix . '__search'],
|
||||
],
|
||||
];
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds an Advanced Search Query Url from the submitted form values.
|
||||
*/
|
||||
protected function buildUrl(FormStateInterface $form_state) {
|
||||
$terms = [];
|
||||
$values = $form_state->getValues();
|
||||
foreach ($values['terms'] as $term) {
|
||||
$terms[] = AdvancedSearchQueryTerm::fromUserInput($term);
|
||||
}
|
||||
$terms = array_filter($terms);
|
||||
$recurse = filter_var($values['recursive'] ?? FALSE, FILTER_VALIDATE_BOOLEAN);
|
||||
$route = $this->getRouteName($form_state);
|
||||
$advanced_search_query = new AdvancedSearchQuery();
|
||||
return $advanced_search_query->toUrl($this->request, $terms, $recurse, $route);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for adding / removing terms from the search.
|
||||
*/
|
||||
public function ajaxCallback(array &$form, FormStateInterface $form_state) {
|
||||
return $form['ajax'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
$trigger = (string) $form_state->getTriggeringElement()['#value'];
|
||||
switch ($trigger) {
|
||||
case $this->t('Search'):
|
||||
$form_state->setRedirectUrl($this->buildUrl($form_state));
|
||||
break;
|
||||
|
||||
default:
|
||||
$form_state->setRebuild();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
120
src/Form/SearchForm.php
Normal file
120
src/Form/SearchForm.php
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\advanced_search\Form;
|
||||
|
||||
use Drupal\block\Entity\Block;
|
||||
use Drupal\Core\Form\FormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Url;
|
||||
|
||||
/**
|
||||
* Form for building and Simple Search.
|
||||
*/
|
||||
class SearchForm extends FormBase {
|
||||
|
||||
/**
|
||||
* The Block ID.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $blockId;
|
||||
|
||||
/**
|
||||
* The constructor.
|
||||
*
|
||||
* @param string $block_id
|
||||
* Passing the block_id.
|
||||
*/
|
||||
public function __construct($block_id) {
|
||||
$this->blockId = $block_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Block Id.
|
||||
*
|
||||
* @return mixed
|
||||
* Return the Block ID
|
||||
*/
|
||||
public function getBlockId() {
|
||||
return $this->blockId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Block ID.
|
||||
*
|
||||
* @param mixed $blockId
|
||||
* Set the block ID.
|
||||
*/
|
||||
public function setBlockId($blockId): void {
|
||||
$this->blockId = $blockId;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'search_form';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state) {
|
||||
$config = \Drupal::config(SettingsForm::CONFIG_NAME);
|
||||
|
||||
if (!$config->get(SettingsForm::SEARCH_ALL_FIELDS_FLAG)) {
|
||||
$form['search-attributes'][SettingsForm::SEARCH_ALL_FIELDS_FLAG] = [
|
||||
'#markup' => $this
|
||||
->t('<strong>This block is required to enable searching all fields for the Advanced Search.
|
||||
To proceed, please enable the Search All fields in
|
||||
<a href="/admin/config/search/advanced" target="_blank">Advanced Seach Configuration</a></strong>.'),
|
||||
];
|
||||
}
|
||||
else {
|
||||
$block = Block::load($this->blockId);
|
||||
|
||||
if ($block) {
|
||||
$settings = $block->get('settings');
|
||||
$view_machine_name = $settings['search_view_machine_name'];
|
||||
|
||||
}
|
||||
$form['search-textfield'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => (!empty($settings['search_textfield_label']) ? $settings['search_textfield_label'] : ''),
|
||||
'#attributes' => [
|
||||
'placeholder' => isset($settings['search_placeholder']) ? $this->t("@placeholder", ["@placeholder" => $settings['search_placeholder']]) : $this->t("Search collections"),
|
||||
'aria-label' => (isset($settings['search_textfield_label']) ? $this->t("@label", ["@label" => $settings['search_textfield_label']]) : $this->t('Enter Keyword')),
|
||||
],
|
||||
'#theme_wrappers' => [],
|
||||
];
|
||||
|
||||
$form['actions']['#type'] = 'actions';
|
||||
$form['actions']['submit'] = [
|
||||
'#type' => 'submit',
|
||||
'#value' => (!empty($settings['search_submit_label']) ? $settings['search_submit_label'] : 'Search'),
|
||||
'#button_type' => 'primary',
|
||||
];
|
||||
}
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
$block = Block::load($this->blockId);
|
||||
if ($block) {
|
||||
$settings = $block->get('settings');
|
||||
$view_machine_name = $settings['search_view_machine_name'];
|
||||
}
|
||||
$url = Url::fromRoute($view_machine_name, [
|
||||
'a[0][f]' => 'all',
|
||||
'a[0][i]' => 'IS',
|
||||
'a[0][v]' => $form_state->getValues()['search-textfield'],
|
||||
|
||||
]);
|
||||
$form_state->setRedirectUrl($url);
|
||||
}
|
||||
|
||||
}
|
||||
218
src/Form/SettingsForm.php
Normal file
218
src/Form/SettingsForm.php
Normal file
|
|
@ -0,0 +1,218 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\advanced_search\Form;
|
||||
|
||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||
use Drupal\Core\Form\ConfigFormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\advanced_search\AdvancedSearchQuery;
|
||||
use Drupal\advanced_search\GetConfigTrait;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Config form for Islandora Advanced Search settings.
|
||||
*/
|
||||
class SettingsForm extends ConfigFormBase {
|
||||
|
||||
use GetConfigTrait;
|
||||
|
||||
const CONFIG_NAME = 'advanced_search.settings';
|
||||
const SEARCH_QUERY_PARAMETER = 'search_query_parameter';
|
||||
const SEARCH_RECURSIVE_PARAMETER = 'search_recursive_parameter';
|
||||
const SEARCH_ADD_OPERATOR = 'search_add_operator';
|
||||
const SEARCH_REMOVE_OPERATOR = 'search_remove_operator';
|
||||
const FACET_TRUNCATE = 'facet_truncate';
|
||||
const EDISMAX_SEARCH_FLAG = 'lucene_on_off';
|
||||
const EDISMAX_SEARCH_LABEL = 'lucene_label';
|
||||
const SEARCH_ALL_FIELDS_FLAG = 'all_fields_on_off';
|
||||
const DISPLAY_LIST_FLAG = 'list_on_off';
|
||||
const DISPLAY_GRID_FLAG = 'grid_on_off';
|
||||
const DISPLAY_DEFAULT = 'default-display-mode';
|
||||
|
||||
/**
|
||||
* Constructs a \Drupal\system\ConfigFormBase object.
|
||||
*
|
||||
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
|
||||
* The factory for configuration objects.
|
||||
*/
|
||||
final public function __construct(ConfigFactoryInterface $config_factory) {
|
||||
$this->setConfigFactory($config_factory);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static($container->get('config.factory'));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'advanced_search_settings_form';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getEditableConfigNames() {
|
||||
return [
|
||||
self::CONFIG_NAME,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state) {
|
||||
$form['eDisMax'] = [
|
||||
'#type' => 'fieldset',
|
||||
'#title' => $this->t('Advanced Search Block'),
|
||||
'#weight' => -1,
|
||||
];
|
||||
$form['eDisMax']['advanced-search-block-description'] = [
|
||||
'#markup' => $this->t("Advanced Search Blocks are available in the Blocks interface for each Search API view. When placing an Advanced Search Block, you can configure the fields that are used for field-based search and whether a “recursive” search is available. The following settings apply to all Advanced Search blocks."),
|
||||
'#weight' => -2,
|
||||
];
|
||||
|
||||
$isEDismax = \Drupal::config(SettingsForm::CONFIG_NAME)->get(self::EDISMAX_SEARCH_FLAG);
|
||||
$form['eDisMax'][self::EDISMAX_SEARCH_FLAG] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this
|
||||
->t('Enable Extended DisMax Query.'),
|
||||
'#description' => $this->t('<ul> <li>When enabled, all queries using an Advanced Search Block use the Extended Dismax (eDisMax) query processor.</li>
|
||||
<li>This setting must be enabled for the “Simple Search Block” to function. </li>
|
||||
<li>If enabled, the “Simple Search Block”/”Advanced Search Blocks” support:
|
||||
<ul>
|
||||
<li>queries that include AND, OR, NOT, -, and + (user documentation needed)</li>
|
||||
<li>Wildcard operator *</li>
|
||||
<li>Words in query are treated as distinct words. They are combined using OR unless the user specifies using AND/NOT in their query.</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>'),
|
||||
'#default_value' => $isEDismax ?? 1,
|
||||
];
|
||||
|
||||
$form['eDisMax']['textfields_container'] = [
|
||||
'#type' => 'container',
|
||||
'#attributes' => ['id' => 'edismax-container'],
|
||||
];
|
||||
|
||||
$form['eDisMax']['textfields_container'][self::SEARCH_ALL_FIELDS_FLAG] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this
|
||||
->t('Enable searching all fields'),
|
||||
'#description' => $this->t('<ul>
|
||||
<li>This makes an additional option visible in all Advanced Search Blocks, which searches across all fields. Its label is configured below.</li>
|
||||
<li>This setting must be enabled for the “Simple Search Block” to function.</li>
|
||||
</ul>'),
|
||||
'#default_value' => self::getConfig(self::SEARCH_ALL_FIELDS_FLAG, 0),
|
||||
];
|
||||
$form['eDisMax']['textfields_container'][self::EDISMAX_SEARCH_LABEL] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('If enabled, set the label for the option of searching all fields'),
|
||||
'#description' => $this->t('E.g. keyword.'),
|
||||
'#default_value' => self::getConfig(self::EDISMAX_SEARCH_LABEL, "Keyword"),
|
||||
];
|
||||
|
||||
$form['display-mode'] = [
|
||||
'#type' => 'fieldset',
|
||||
'#title' => $this->t("Pager Block"),
|
||||
];
|
||||
$form['display-mode']['pager-block-description'] = [
|
||||
'#markup' => $this->t("Pager blocks are available in the Blocks interface for each Search API view. The following settings apply for all Pager blocks."),
|
||||
];
|
||||
|
||||
$form['display-mode'][self::DISPLAY_LIST_FLAG] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this
|
||||
->t('Expose "List view" option.'),
|
||||
'#default_value' => self::getConfig(self::DISPLAY_LIST_FLAG, 0),
|
||||
];
|
||||
|
||||
$form['display-mode'][self::DISPLAY_GRID_FLAG] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this
|
||||
->t('Expose "Grid view" option.'),
|
||||
'#default_value' => self::getConfig(self::DISPLAY_GRID_FLAG, 0),
|
||||
];
|
||||
|
||||
$form['display-mode'][self::DISPLAY_DEFAULT] = [
|
||||
'#type' => 'select',
|
||||
'#title' => $this
|
||||
->t('Default view mode:'),
|
||||
'#options' => [
|
||||
'list' => 'List',
|
||||
'grid' => 'Grid',
|
||||
],
|
||||
'#default_value' => self::getConfig(self::DISPLAY_DEFAULT, 'grid'),
|
||||
];
|
||||
|
||||
$form += [
|
||||
'search' => [
|
||||
'#type' => 'fieldset',
|
||||
'#title' => $this->t('Advanced Search'),
|
||||
self::SEARCH_QUERY_PARAMETER => [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Search Query Parameter'),
|
||||
'#description' => $this->t('The url parameter in which the advanced search query is stored.'),
|
||||
'#default_value' => AdvancedSearchQuery::getQueryParameter(),
|
||||
],
|
||||
self::SEARCH_RECURSIVE_PARAMETER => [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Recurse Query Parameter'),
|
||||
'#description' => $this->t('The url parameter which can toggle recursive search.'),
|
||||
'#default_value' => AdvancedSearchQuery::getRecurseParameter(),
|
||||
],
|
||||
self::SEARCH_ADD_OPERATOR => [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Facet Add Operator'),
|
||||
'#description' => $this->t('Users can customize the operator for adding facets to use font-awesome or some other icon, etc.'),
|
||||
'#default_value' => AdvancedSearchForm::getAddOperator(),
|
||||
],
|
||||
self::SEARCH_REMOVE_OPERATOR => [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Facet Remove Operator'),
|
||||
'#description' => $this->t('Users can customize the operator for removing facets to use font-awesome or some other icon, etc.'),
|
||||
'#default_value' => AdvancedSearchForm::getRemoveOperator(),
|
||||
],
|
||||
],
|
||||
'facets' => [
|
||||
'#type' => 'fieldset',
|
||||
'#title' => $this->t('Facets'),
|
||||
self::FACET_TRUNCATE => [
|
||||
'#type' => 'number',
|
||||
'#title' => $this->t('Truncate Facet'),
|
||||
'#description' => $this->t('Optionally truncate the length of facets titles in the display. If unspecified they will not be truncated.'),
|
||||
'#default_value' => self::getConfig(self::FACET_TRUNCATE, 32),
|
||||
'#min' => 1,
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
return parent::buildForm($form, $form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
$config = $this->configFactory->getEditable(self::CONFIG_NAME);
|
||||
$config
|
||||
->set(self::SEARCH_QUERY_PARAMETER, $form_state->getValue(self::SEARCH_QUERY_PARAMETER))
|
||||
->set(self::SEARCH_RECURSIVE_PARAMETER, $form_state->getValue(self::SEARCH_RECURSIVE_PARAMETER))
|
||||
->set(self::SEARCH_ADD_OPERATOR, $form_state->getValue(self::SEARCH_ADD_OPERATOR))
|
||||
->set(self::SEARCH_REMOVE_OPERATOR, $form_state->getValue(self::SEARCH_REMOVE_OPERATOR))
|
||||
->set(self::FACET_TRUNCATE, $form_state->getValue(self::FACET_TRUNCATE))
|
||||
->set(self::EDISMAX_SEARCH_FLAG, $form_state->getValue(self::EDISMAX_SEARCH_FLAG))
|
||||
->set(self::EDISMAX_SEARCH_LABEL, $form_state->getValue(self::EDISMAX_SEARCH_LABEL))
|
||||
->set(self::SEARCH_ALL_FIELDS_FLAG, $form_state->getValue(self::SEARCH_ALL_FIELDS_FLAG))
|
||||
->set(self::DISPLAY_LIST_FLAG, $form_state->getValue(self::DISPLAY_LIST_FLAG))
|
||||
->set(self::DISPLAY_GRID_FLAG, $form_state->getValue(self::DISPLAY_GRID_FLAG))
|
||||
->set(self::DISPLAY_DEFAULT, $form_state->getValue(self::DISPLAY_DEFAULT))
|
||||
->save();
|
||||
parent::submitForm($form, $form_state);
|
||||
}
|
||||
|
||||
}
|
||||
24
src/GetConfigTrait.php
Normal file
24
src/GetConfigTrait.php
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\advanced_search;
|
||||
|
||||
use Drupal\advanced_search\Form\SettingsForm;
|
||||
|
||||
/**
|
||||
* Simple trait for accessing this modules configuration.
|
||||
*/
|
||||
trait GetConfigTrait {
|
||||
|
||||
/**
|
||||
* Get a config setting or returns a default.
|
||||
*
|
||||
* @return string
|
||||
* The config setting or default value.
|
||||
*/
|
||||
protected static function getConfig($config, $default) {
|
||||
$settings = \Drupal::config(SettingsForm::CONFIG_NAME);
|
||||
$value = $settings->get($config);
|
||||
return !empty($value) ? $value : $default;
|
||||
}
|
||||
|
||||
}
|
||||
394
src/Plugin/Block/AdvancedSearchBlock.php
Normal file
394
src/Plugin/Block/AdvancedSearchBlock.php
Normal file
|
|
@ -0,0 +1,394 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\advanced_search\Plugin\Block;
|
||||
|
||||
use Drupal\Core\Block\BlockBase;
|
||||
use Drupal\Core\Form\FormBuilderInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\search_api\Display\DisplayPluginManager;
|
||||
use Drupal\views\Entity\View;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* Provides an Islandora Advanced Search block.
|
||||
*
|
||||
* @Block(
|
||||
* id = "advanced_search_block",
|
||||
* deriver = "Drupal\advanced_search\Plugin\Block\AdvancedSearchBlockDeriver",
|
||||
* admin_label = @Translation("Islandora Advanced Search"),
|
||||
* category = @Translation("Islandora"),
|
||||
* )
|
||||
*/
|
||||
class AdvancedSearchBlock extends BlockBase implements ContainerFactoryPluginInterface {
|
||||
use ViewAndDisplayIdentifiersTrait;
|
||||
|
||||
// CSS classes used to bind table-drag behavior to.
|
||||
const WEIGHT_FIELD_CLASS = 'field-weight';
|
||||
const DISPLAY_FIELD_CLASS = 'field-display';
|
||||
|
||||
// Regions in the table which denote if a given field
|
||||
// is visible in the Advanced Search Form or not.
|
||||
const REGION_VISIBLE = 'visible';
|
||||
const REGION_HIDDEN = 'hidden';
|
||||
|
||||
// Keys for settings.
|
||||
const SETTING_FIELDS = 'fields';
|
||||
const SETTING_CONTEXTUAL_FILTER = 'context_filter';
|
||||
|
||||
/**
|
||||
* The display plugin manager.
|
||||
*
|
||||
* @var \Drupal\search_api\Display\DisplayPluginManager
|
||||
*/
|
||||
protected $displayPluginManager;
|
||||
|
||||
/**
|
||||
* The clone of the current request object.
|
||||
*
|
||||
* @var \Symfony\Component\HttpFoundation\Request
|
||||
*/
|
||||
protected $request;
|
||||
|
||||
/**
|
||||
* The view this block affects.
|
||||
*
|
||||
* @var \Drupal\views\Entity\View
|
||||
*/
|
||||
protected $view;
|
||||
|
||||
/**
|
||||
* The view display this block affects.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $display;
|
||||
|
||||
/**
|
||||
* Form Builder.
|
||||
*
|
||||
* @var \Drupal\Core\Form\FormBuilderInterface
|
||||
*/
|
||||
protected $formBuilder;
|
||||
|
||||
/**
|
||||
* Construct a AdvancedSearchBlock instance.
|
||||
*
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin_id for the plugin instance.
|
||||
* @param string $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param \Drupal\search_api\Display\DisplayPluginManager $display_plugin_manager
|
||||
* The display plugin manager.
|
||||
* @param \Drupal\Core\Form\FormBuilderInterface $form_builder
|
||||
* The form builder service used to build the search form.
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* A request object for the current request.
|
||||
*/
|
||||
final public function __construct(array $configuration, $plugin_id, $plugin_definition, DisplayPluginManager $display_plugin_manager, FormBuilderInterface $form_builder, Request $request) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
$this->displayPluginManager = $display_plugin_manager;
|
||||
[$view_id, $display_id] = preg_split('/__/', $this->getDerivativeId(), 2);
|
||||
$this->view = View::Load($view_id);
|
||||
$this->display = $this->view->getDisplay($display_id);
|
||||
$this->formBuilder = $form_builder;
|
||||
$this->request = clone $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('plugin.manager.search_api.display'),
|
||||
$container->get('form_builder'),
|
||||
$container->get('request_stack')->getMainRequest()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function defaultConfiguration() {
|
||||
return [
|
||||
self::SETTING_FIELDS => [],
|
||||
self::SETTING_CONTEXTUAL_FILTER => NULL,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Fields which can be enabled / disabled for display in the search form.
|
||||
*
|
||||
* @return \Drupal\search_api\Item\FieldInterface[]
|
||||
* The $fields sorted by label.
|
||||
*/
|
||||
protected function getFields() {
|
||||
$fields = $this->getIndex()->getFields();
|
||||
// First pass sort on label, secondary sort will be used
|
||||
// when looking at existing configuration for this block.
|
||||
uasort($fields, function ($a, $b) {
|
||||
return strcmp($a->getLabel(), $b->getLabel());
|
||||
});
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get regions of table to display.
|
||||
*
|
||||
* @return array
|
||||
* The properties of each region used for building the table of fields.
|
||||
*/
|
||||
protected function getRegions() {
|
||||
// Classes for select fields like 'weight' and 'display' are hard-coded
|
||||
// and used in js/islandora-advanced-search.admin.js.
|
||||
return [
|
||||
'visible' => [
|
||||
'title' => $this->t('Visible'),
|
||||
'invisible' => TRUE,
|
||||
'message' => $this->t('No search field is visible.'),
|
||||
'weight' => self::WEIGHT_FIELD_CLASS . '-visible',
|
||||
'display' => self::DISPLAY_FIELD_CLASS . '-visible',
|
||||
],
|
||||
'hidden' => [
|
||||
'title' => $this->t('Hidden'),
|
||||
'invisible' => FALSE,
|
||||
'message' => $this->t('No search field is hidden.'),
|
||||
'weight' => self::WEIGHT_FIELD_CLASS . '-hidden',
|
||||
'display' => self::DISPLAY_FIELD_CLASS . '-hidden',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for field display derived from the available regions.
|
||||
*
|
||||
* @return array
|
||||
* Display select field options.
|
||||
*/
|
||||
protected function getDisplayOptions() {
|
||||
$options = [];
|
||||
foreach ($this->getRegions() as $region => $settings) {
|
||||
$options[$region] = $settings['title'];
|
||||
}
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function blockForm($form, FormStateInterface $form_state) {
|
||||
// At most we will have one row per field.
|
||||
$fields = $this->getFields();
|
||||
$weight_delta = round(count($fields) / 2);
|
||||
|
||||
// Group each field into a region given our current configuration.
|
||||
$visible_fields = $this->configuration[self::SETTING_FIELDS];
|
||||
$regions = $this->getRegions();
|
||||
$display_options = $this->getDisplayOptions();
|
||||
|
||||
// Field rows are grouped by the region in which they are displayed.
|
||||
$field_rows = array_fill_keys(array_keys($regions), []);
|
||||
foreach ($fields as $field) {
|
||||
// If a field exists in the blocks configuration than it is 'visible' and
|
||||
// its weight is equivalent to its order in the configuration,
|
||||
// i.e. its index.
|
||||
$identifier = $field->getFieldIdentifier();
|
||||
$weight = array_search($identifier, $visible_fields);
|
||||
$visible = $weight !== FALSE;
|
||||
$region = $visible ? self::REGION_VISIBLE : self::REGION_HIDDEN;
|
||||
$field_rows[$region][$identifier] = [
|
||||
'#attributes' => [
|
||||
'class' => ['draggable'],
|
||||
],
|
||||
'label' => ['#plain_text' => $field->getLabel()],
|
||||
'identifier' => ['#plain_text' => $identifier],
|
||||
'weight' => [
|
||||
'#type' => 'weight',
|
||||
'#title' => $this->t('Weight'),
|
||||
'#title_display' => 'invisible',
|
||||
'#default_value' => $visible ? $weight : 0,
|
||||
'#delta' => $weight_delta,
|
||||
'#attributes' => [
|
||||
'class' => [self::WEIGHT_FIELD_CLASS, $regions[$region]['weight']],
|
||||
],
|
||||
],
|
||||
'display' => [
|
||||
'#type' => 'select',
|
||||
'#title' => $this->t('Display'),
|
||||
'#title_display' => 'invisible',
|
||||
'#options' => $display_options,
|
||||
'#default_value' => $region,
|
||||
'#attributes' => [
|
||||
'class' => [self::DISPLAY_FIELD_CLASS, $regions[$region]['display']],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
// Sort the visible rows by their weight.
|
||||
uasort($field_rows[self::REGION_VISIBLE], function ($a, $b) {
|
||||
$a = $a['weight']['#default_value'];
|
||||
$b = $b['weight']['#default_value'];
|
||||
if ($a == $b) {
|
||||
return 0;
|
||||
}
|
||||
return ($a < $b) ? -1 : 1;
|
||||
});
|
||||
|
||||
// Build Rows.
|
||||
$rows = [];
|
||||
$table_drag = [];
|
||||
foreach ($regions as $region => $properties) {
|
||||
$rows += [
|
||||
// Conditionally display region title as a row.
|
||||
"region-$region" => $properties['invisible'] ? NULL : [
|
||||
'#attributes' => [
|
||||
'class' => ['region-title', "region-title-$region"],
|
||||
],
|
||||
'label' => [
|
||||
'#plain_text' => $properties['title'],
|
||||
'#wrapper_attributes' => [
|
||||
'colspan' => 4,
|
||||
],
|
||||
],
|
||||
],
|
||||
// Will dynamically display if the region has fields or not controlled
|
||||
// by Drupal behaviors in js/islandora-advanced-search.admin.js.
|
||||
"region-$region-message" => [
|
||||
'#attributes' => [
|
||||
'class' => [
|
||||
'region-message',
|
||||
"region-$region-message",
|
||||
empty($field_rows[$region]) ? 'region-empty' : 'region-populated',
|
||||
],
|
||||
],
|
||||
'message' => [
|
||||
'#markup' => '<em>' . $properties['message'] . '</em>',
|
||||
'#wrapper_attributes' => [
|
||||
'colspan' => 4,
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
// Include field rows in this region.
|
||||
$rows += $field_rows[$region];
|
||||
|
||||
// Configure order by weight field in region.
|
||||
$table_drag[] = [
|
||||
'action' => 'order',
|
||||
'relationship' => 'sibling',
|
||||
'group' => self::WEIGHT_FIELD_CLASS,
|
||||
'subgroup' => $properties['weight'],
|
||||
'source' => self::WEIGHT_FIELD_CLASS,
|
||||
];
|
||||
|
||||
// Configure drag action for display field in region.
|
||||
$table_drag[] = [
|
||||
'action' => 'match',
|
||||
'relationship' => 'sibling',
|
||||
'group' => self::DISPLAY_FIELD_CLASS,
|
||||
'subgroup' => $properties['display'],
|
||||
'source' => self::DISPLAY_FIELD_CLASS,
|
||||
];
|
||||
}
|
||||
|
||||
$form[self::SETTING_FIELDS] = [
|
||||
'#type' => 'table',
|
||||
'#attributes' => [
|
||||
// Identifier is hard-coded and used in
|
||||
// js/islandora-advanced-search.admin.js.
|
||||
'id' => 'advanced-search-fields',
|
||||
],
|
||||
'#header' => [
|
||||
$this->t('Label'),
|
||||
$this->t('Field'),
|
||||
$this->t('Weight'),
|
||||
$this->t('Display'),
|
||||
],
|
||||
'#empty' => $this->t('No search fields, please check search index configuration.'),
|
||||
'#tabledrag' => $table_drag,
|
||||
] + $rows;
|
||||
|
||||
// If there is contextual filters associated with the display that means
|
||||
// we can filter on collection / sub-collection. Allow the user to choose
|
||||
// which filters collections.
|
||||
$id = NULL;
|
||||
$field = NULL;
|
||||
$options = [];
|
||||
if (isset($this->display['display_options']['arguments'])) {
|
||||
foreach ($this->display['display_options']['arguments'] as $context_filter) {
|
||||
$id = $context_filter['id'];
|
||||
$field = $context_filter['field'];
|
||||
if (isset($fields[$field])) {
|
||||
$options[$id] = $fields[$field]->getLabel() . ':' . $id;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (count($options) > 0) {
|
||||
$form[self::SETTING_CONTEXTUAL_FILTER] = [
|
||||
'#type' => 'select',
|
||||
'#title' => $this->t('Context Filter'),
|
||||
'#description' => $this->t('If more than one <strong>Context Filter</strong> is defined, specify which is used to <strong>include</strong> only <strong>direct children</strong> of the Collection as it will disabled to allow recursive searching.'),
|
||||
'#options' => $options,
|
||||
'#default_value' => $this->configuration[self::SETTING_CONTEXTUAL_FILTER],
|
||||
'#multiple' => FALSE,
|
||||
'#required' => FALSE,
|
||||
'#size' => count($options) + 1,
|
||||
];
|
||||
}
|
||||
$form['#attributes']['class'][] = 'clearfix';
|
||||
$form['#attached']['library'][] = 'advanced_search/advanced.search.admin';
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function blockSubmit($form, FormStateInterface $form_state) {
|
||||
$values = $form_state->getValues();
|
||||
$fields = array_filter($values[self::SETTING_FIELDS], function ($field) {
|
||||
return $field['display'] == 'visible';
|
||||
});
|
||||
uasort($fields, '\Drupal\Component\Utility\SortArray::sortByWeightElement');
|
||||
$this->configuration[self::SETTING_FIELDS] = array_keys($fields);
|
||||
if (isset($values[self::SETTING_CONTEXTUAL_FILTER])) {
|
||||
$this->configuration[self::SETTING_CONTEXTUAL_FILTER] = $values[self::SETTING_CONTEXTUAL_FILTER];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function build() {
|
||||
$fields = $this->getIndex()->getFields();
|
||||
$configured_fields = [];
|
||||
foreach ($this->configuration[self::SETTING_FIELDS] as $identifier) {
|
||||
$configured_fields[$identifier] = $fields[$identifier];
|
||||
}
|
||||
return $this->formBuilder->getForm('Drupal\advanced_search\Form\AdvancedSearchForm', $this->view, $this->display, $configured_fields, $this->configuration[self::SETTING_CONTEXTUAL_FILTER]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheMaxAge() {
|
||||
// The block cannot be cached, because it must always match the current
|
||||
// search results.
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Search Index.
|
||||
*/
|
||||
protected function getIndex() {
|
||||
$id = $this->getDerivativeId();
|
||||
return $this->displayPluginManager->createInstance("views_{$this->display['display_plugin']}:{$id}")->getIndex();
|
||||
}
|
||||
|
||||
}
|
||||
17
src/Plugin/Block/AdvancedSearchBlockDeriver.php
Normal file
17
src/Plugin/Block/AdvancedSearchBlockDeriver.php
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\advanced_search\Plugin\Block;
|
||||
|
||||
/**
|
||||
* Deriver for AdvancedSearchBlock.
|
||||
*/
|
||||
class AdvancedSearchBlockDeriver extends SearchApiDisplayBlockDeriver {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function label() {
|
||||
return $this->t('Advanced Search');
|
||||
}
|
||||
|
||||
}
|
||||
102
src/Plugin/Block/SearchApiDisplayBlockDeriver.php
Normal file
102
src/Plugin/Block/SearchApiDisplayBlockDeriver.php
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\advanced_search\Plugin\Block;
|
||||
|
||||
use Drupal\Component\Plugin\PluginBase;
|
||||
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* This deriver creates a block for every search_api.display.
|
||||
*/
|
||||
abstract class SearchApiDisplayBlockDeriver implements ContainerDeriverInterface {
|
||||
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* List of derivative definitions.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $derivatives = [];
|
||||
|
||||
/**
|
||||
* The entity storage for the view.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityStorageInterface
|
||||
*/
|
||||
protected $storage;
|
||||
|
||||
/**
|
||||
* The display manager for the search_api.
|
||||
*
|
||||
* @var \Drupal\search_api\Display\DisplayPluginManager
|
||||
*/
|
||||
protected $displayPluginManager;
|
||||
|
||||
/**
|
||||
* Label for the SearchApiDisplayBlockDriver.
|
||||
*/
|
||||
abstract protected function label();
|
||||
|
||||
/**
|
||||
* The constructor.
|
||||
*/
|
||||
final public function __construct(ContainerInterface $container, $base_plugin_id) {}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, $base_plugin_id) {
|
||||
$deriver = new static($container, $base_plugin_id);
|
||||
$deriver->storage = $container->get('entity_type.manager')->getStorage('view');
|
||||
$deriver->displayPluginManager = $container->get('plugin.manager.search_api.display');
|
||||
return $deriver;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDerivativeDefinition($derivative_id, $base_plugin_definition) {
|
||||
$derivatives = $this->getDerivativeDefinitions($base_plugin_definition);
|
||||
return $derivatives[$derivative_id] ?? NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDerivativeDefinitions($base_plugin_definition) {
|
||||
$base_plugin_id = $base_plugin_definition['id'];
|
||||
|
||||
if (!isset($this->derivatives[$base_plugin_id])) {
|
||||
$plugin_derivatives = [];
|
||||
|
||||
foreach ($this->displayPluginManager->getDefinitions() as $display_definition) {
|
||||
$view_id = $display_definition['view_id'];
|
||||
$view_display = $display_definition['view_display'];
|
||||
// The derived block needs both the view / display identifiers to
|
||||
// construct the pager.
|
||||
$machine_name = "{$view_id}__{$view_display}";
|
||||
|
||||
/** @var \Drupal\views\ViewEntityInterface $view */
|
||||
$view = $this->storage->load($view_id);
|
||||
$display = $view->getDisplay($view_display);
|
||||
|
||||
$plugin_derivatives[$machine_name] = [
|
||||
'id' => $base_plugin_id . PluginBase::DERIVATIVE_SEPARATOR . $machine_name,
|
||||
'label' => $this->label(),
|
||||
'admin_label' => $this->t(':view: :label for :display', [
|
||||
':view' => $view->label(),
|
||||
':label' => $this->label(),
|
||||
':display' => $display['display_title'],
|
||||
]),
|
||||
] + $base_plugin_definition;
|
||||
}
|
||||
|
||||
$this->derivatives[$base_plugin_id] = $plugin_derivatives;
|
||||
}
|
||||
return $this->derivatives[$base_plugin_id];
|
||||
}
|
||||
|
||||
}
|
||||
107
src/Plugin/Block/SearchBlock.php
Normal file
107
src/Plugin/Block/SearchBlock.php
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\advanced_search\Plugin\Block;
|
||||
|
||||
use Drupal\advanced_search\Form\SearchForm;
|
||||
use Drupal\Core\Block\BlockBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\advanced_search\Form\SettingsForm;
|
||||
|
||||
/**
|
||||
* Provides a 'SearchBlock' block.
|
||||
*
|
||||
* @Block(
|
||||
* id = "search_block",
|
||||
* admin_label = @Translation("Search"),
|
||||
* )
|
||||
*/
|
||||
class SearchBlock extends BlockBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function defaultConfiguration() {
|
||||
return [] + parent::defaultConfiguration();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function blockForm($form, FormStateInterface $form_state) {
|
||||
$config = \Drupal::config(SettingsForm::CONFIG_NAME);
|
||||
$form['search-attributes'] = [
|
||||
'#type' => 'fieldset',
|
||||
'#title' => $this->t('Configure Search Block'),
|
||||
];
|
||||
|
||||
if (!$config->get(SettingsForm::SEARCH_ALL_FIELDS_FLAG)) {
|
||||
$form['search-attributes'][SettingsForm::SEARCH_ALL_FIELDS_FLAG] = [
|
||||
'#markup' => $this
|
||||
->t('<strong>This block is required to enable searching all fields for the Advanced Search.
|
||||
To proceed, please enable "Enable searching all fields" in
|
||||
<a href="/admin/config/search/advanced" target="_blank">Advanced Seach Configuration</a></strong>.'),
|
||||
];
|
||||
}
|
||||
else {
|
||||
$views = \Drupal::EntityTypeManager()->getStorage('view')->loadMultiple();
|
||||
$options = [];
|
||||
foreach ($views as $view_name => $view) {
|
||||
$displays = $view->get("display");
|
||||
foreach ($displays as $display) {
|
||||
if ($display['display_plugin'] === "page") {
|
||||
$options["view.$view_name" . "." . $display['id']] = "view.$view_name" . "." . $display['id'];
|
||||
}
|
||||
}
|
||||
}
|
||||
$form['search-attributes']['view_machine_name'] = [
|
||||
'#type' => 'select',
|
||||
'#title' => $this->t('Select the machine name of Search Results Page:'),
|
||||
'#default_value' => $this->configuration['search_view_machine_name'],
|
||||
'#options' => $options,
|
||||
];
|
||||
$form['search-attributes']['search_textfield'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Search Keyword Text field label:'),
|
||||
'#default_value' => $this->configuration['search_textfield_label'],
|
||||
'#maxlength' => 255,
|
||||
];
|
||||
$form['search-attributes']['search_placeholder_textfield'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Search Keyword text field placeholder:'),
|
||||
'#default_value' => $this->configuration['search_placeholder'],
|
||||
'#maxlength' => 255,
|
||||
];
|
||||
|
||||
$form['search-attributes']['search_submit'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Search Button Label:'),
|
||||
'#default_value' => $this->configuration['search_submit_label'],
|
||||
'#maxlength' => 255,
|
||||
];
|
||||
}
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function blockSubmit($form, FormStateInterface $form_state) {
|
||||
$this->configuration['block_id'] = $form_state->getBuildInfo()['callback_object']->getEntity()->id();
|
||||
$this->configuration['search_view_machine_name'] = $form_state->getValues()['search-attributes']['view_machine_name'];
|
||||
$this->configuration['search_textfield_label'] = $form_state->getValues()['search-attributes']['search_textfield'];
|
||||
$this->configuration['search_placeholder'] = $form_state->getValues()['search-attributes']['search_placeholder_textfield'];
|
||||
$this->configuration['search_submit_label'] = $form_state->getValues()['search-attributes']['search_submit'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function build() {
|
||||
$config = $this->getConfiguration();
|
||||
$blockId = $config['block_id'];
|
||||
$searchForm = new SearchForm($blockId);
|
||||
return \Drupal::formBuilder()->getForm($searchForm);
|
||||
}
|
||||
|
||||
}
|
||||
328
src/Plugin/Block/SearchResultsPagerBlock.php
Normal file
328
src/Plugin/Block/SearchResultsPagerBlock.php
Normal file
|
|
@ -0,0 +1,328 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\advanced_search\Plugin\Block;
|
||||
|
||||
use Drupal\Core\Block\BlockBase;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\Core\Render\Markup;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\advanced_search\AdvancedSearchQuery;
|
||||
use Drupal\views\Entity\View;
|
||||
use Drupal\views\Plugin\views\pager\SqlBase;
|
||||
use Drupal\views\ViewExecutable;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Drupal\advanced_search\Form\SettingsForm;
|
||||
|
||||
/**
|
||||
* Provides a 'AjaxViewBlock' block.
|
||||
*
|
||||
* @Block(
|
||||
* id = "advanced_search_result_pager",
|
||||
* deriver = "Drupal\advanced_search\Plugin\Block\SearchResultsPagerBlockDeriver",
|
||||
* admin_label = @Translation("Search Results Pager"),
|
||||
* category = @Translation("Islandora"),
|
||||
* )
|
||||
*/
|
||||
class SearchResultsPagerBlock extends BlockBase implements ContainerFactoryPluginInterface {
|
||||
use ViewAndDisplayIdentifiersTrait;
|
||||
|
||||
/**
|
||||
* The clone of the current request object.
|
||||
*
|
||||
* @var \Symfony\Component\HttpFoundation\Request
|
||||
*/
|
||||
protected $request;
|
||||
|
||||
/**
|
||||
* Construct a FacetBlock instance.
|
||||
*
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin_id for the plugin instance.
|
||||
* @param string $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* A request object for the current request.
|
||||
*/
|
||||
final public function __construct(array $configuration, $plugin_id, $plugin_definition, Request $request) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
$this->request = clone $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('request_stack')->getMainRequest()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function build() {
|
||||
$id = $this->getDerivativeId();
|
||||
[$view_id, $display_id] = $this->getViewAndDisplayIdentifiers();
|
||||
$view = View::Load($view_id);
|
||||
$view_executable = $view->getExecutable();
|
||||
$view_executable->setDisplay($display_id);
|
||||
// Allow advanced search to alter the query.
|
||||
$advanced_search_query = new AdvancedSearchQuery();
|
||||
$advanced_search_query->alterView($this->request, $view_executable, $display_id);
|
||||
$view_executable->execute();
|
||||
$pager = $view_executable->getPager();
|
||||
$exposed_input = $view_executable->getExposedInput();
|
||||
$query_parameters = $this->request->query->all();
|
||||
$build = [
|
||||
'#attached' => [
|
||||
'drupalSettings' => [
|
||||
'advanced_search_pager_views_ajax' => [
|
||||
$id => [
|
||||
'view_id' => $view_id,
|
||||
'current_display_id' => $display_id,
|
||||
'ajax_path' => '/views/ajax',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'#attributes' => [
|
||||
'class' => ['advanced_search_result_pager'],
|
||||
'data-drupal-pager-id' => $id,
|
||||
],
|
||||
'result_summary' => $this->buildResultsSummary($view_executable),
|
||||
'container' => [
|
||||
'#prefix' => '<div class="pager__group">',
|
||||
'#suffix' => '</div>',
|
||||
'results_per_page_links' => $this->buildResultsPerPageLinks($pager, $query_parameters),
|
||||
'display_links' => $this->buildDisplayLinks($query_parameters),
|
||||
'sort_by' => $this->buildSortByForm($view_executable->sort, $query_parameters),
|
||||
'pager' => array_merge($pager->render($exposed_input), ['#wrapper_attributes' => ['class' => ['container']]]),
|
||||
],
|
||||
];
|
||||
return $build;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the results summary portion of the pager.
|
||||
*
|
||||
* @param Drupal\views\ViewExecutable $view_executable
|
||||
* The view to build the summary for.
|
||||
*
|
||||
* @return array
|
||||
* A renderable array that represents the current page, and number of
|
||||
* results in the view.
|
||||
*/
|
||||
protected function buildResultsSummary(ViewExecutable $view_executable) {
|
||||
$current_page = (int) $view_executable->getCurrentPage() + 1;
|
||||
$per_page = (int) $view_executable->getItemsPerPage();
|
||||
$total = $view_executable->total_rows ?? count($view_executable->result);
|
||||
// If there is no result the "start" and "current_record_count" should be
|
||||
// equal to 0. To have the same calculation logic, we use a "start offset"
|
||||
// to handle all the cases.
|
||||
$start_offset = empty($total) ? 0 : 1;
|
||||
if ($per_page === 0) {
|
||||
$start = $start_offset;
|
||||
$end = $total;
|
||||
}
|
||||
else {
|
||||
$total_count = $current_page * $per_page;
|
||||
if ($total_count > $total) {
|
||||
$total_count = $total;
|
||||
}
|
||||
$start = ($current_page - 1) * $per_page + $start_offset;
|
||||
$end = $total_count;
|
||||
}
|
||||
if (!empty($total)) {
|
||||
// Return as render array.
|
||||
return [
|
||||
'#prefix' => '<div class="pager__summary">',
|
||||
'#suffix' => '</div>',
|
||||
'#markup' => $this->t('Displaying @start - @end of @total', [
|
||||
'@start' => $start,
|
||||
'@end' => $end,
|
||||
'@total' => $total,
|
||||
]),
|
||||
];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the results per page portion of the pager.
|
||||
*
|
||||
* @param Drupal\views\Plugin\views\pager\SqlBase $pager
|
||||
* The pager for the view.
|
||||
* @param array $query_parameters
|
||||
* The query parameters used to change the number of results per page.
|
||||
*
|
||||
* @return array
|
||||
* A renderable array representing the results per page portion of pager.
|
||||
*/
|
||||
protected function buildResultsPerPageLinks(SqlBase $pager, array $query_parameters) {
|
||||
$active_items_per_page = $query_parameters['items_per_page'] ?? $pager->options['items_per_page'];
|
||||
$items_per_page_options = array_map(function ($value) {
|
||||
return trim($value);
|
||||
}, explode(',', $pager->options['expose']['items_per_page_options']));
|
||||
$items = [];
|
||||
foreach ($items_per_page_options as $items_per_page) {
|
||||
$url = Url::fromRoute('<current>', [], [
|
||||
// When changing the number of items displayed always return the user
|
||||
// to the first page.
|
||||
'query' => array_merge($query_parameters, [
|
||||
'items_per_page' => $items_per_page,
|
||||
'page' => 0,
|
||||
]),
|
||||
'absolute' => TRUE,
|
||||
]);
|
||||
$active = $items_per_page == $active_items_per_page;
|
||||
$items[] = [
|
||||
'#type' => 'link',
|
||||
'#url' => $url,
|
||||
'#title' => $items_per_page,
|
||||
'#attributes' => [
|
||||
'aria-label' => $this->t("@item items per page", ["@item" => $items_per_page]),
|
||||
'class' => $active ?
|
||||
['pager__link', 'pager__link--is-active', 'pager__itemsperpage'] :
|
||||
['pager__link', 'pager__itemsperpage'],
|
||||
],
|
||||
'#wrapper_attributes' => [
|
||||
'class' => $active ? ['pager__item', 'is-active'] : ['pager__item'],
|
||||
],
|
||||
];
|
||||
}
|
||||
return [
|
||||
'#theme' => 'item_list',
|
||||
'#title' => $this->t('Results per page'),
|
||||
'#list_type' => 'ul',
|
||||
'#items' => $items,
|
||||
'#attributes' => [],
|
||||
'#wrapper_attributes' => ['class' => ['pager__results', 'container']],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the display links portion of the pager (list/grid).
|
||||
*
|
||||
* @param array $query_parameters
|
||||
* The query parameters used to change the display format.
|
||||
*
|
||||
* @return array
|
||||
* A renderable array representing the display links portion of pager.
|
||||
*/
|
||||
protected function buildDisplayLinks(array $query_parameters) {
|
||||
$config = \Drupal::config(SettingsForm::CONFIG_NAME);
|
||||
$display_options = [];
|
||||
|
||||
if ($config->get(SettingsForm::DISPLAY_LIST_FLAG) == 1) {
|
||||
$display_options['list'] = [
|
||||
'icon' => 'fa-list',
|
||||
'title' => $this->t('List'),
|
||||
];
|
||||
}
|
||||
|
||||
if ($config->get(SettingsForm::DISPLAY_GRID_FLAG) == 1) {
|
||||
$display_options['grid'] = [
|
||||
'icon' => 'fa-th',
|
||||
'title' => $this->t('Grid'),
|
||||
];
|
||||
}
|
||||
|
||||
$active_display = $query_parameters['display'] ?? $config->get(SettingsForm::DISPLAY_DEFAULT);
|
||||
$items = [];
|
||||
foreach ($display_options as $display => $options) {
|
||||
$url = Url::fromRoute('<current>', [], [
|
||||
'query' => array_merge($query_parameters, ['display' => $display]),
|
||||
'absolute' => TRUE,
|
||||
]);
|
||||
$text = "<i class='fa {$options['icon']}' aria-hidden='true'> </i><span class='display-mode'>{$options['title']}</span>";
|
||||
$active = $active_display == $display;
|
||||
$items[] = [
|
||||
'#type' => 'link',
|
||||
'#url' => $url,
|
||||
'#title' => Markup::create($text),
|
||||
'#attributes' => [
|
||||
'class' => $active ?
|
||||
['pager__link', 'pager__link--is-active', 'pager__display'] :
|
||||
['pager__link', 'pager__display'],
|
||||
'aria-label' => $this->t("Display as @link", ["@link" => Markup::create($text)]),
|
||||
],
|
||||
'#wrapper_attributes' => [
|
||||
'class' => $active ? ['pager__item', 'is-active'] : ['pager__item'],
|
||||
],
|
||||
];
|
||||
}
|
||||
return [
|
||||
'#theme' => 'item_list',
|
||||
'#list_type' => 'ul',
|
||||
'#items' => $items,
|
||||
'#attributes' => [],
|
||||
'#wrapper_attributes' => ['class' => ['pager__display', 'container']],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the sort by portion of the pager.
|
||||
*
|
||||
* @param array $sort_criteria
|
||||
* The search fields which can be sorted.
|
||||
* @param array $query_parameters
|
||||
* The query parameters used to change the display format.
|
||||
*
|
||||
* @return array
|
||||
* A renderable array representing the sort by portion of pager.
|
||||
*/
|
||||
protected function buildSortByForm(array $sort_criteria, array $query_parameters) {
|
||||
$default_order = $query_parameters['sort_order'] ?? 'DESC';
|
||||
$default_sort_by = $query_parameters['sort_by'] ?? 'search_api_relevance';
|
||||
$default_value = $default_sort_by . '_' . strtolower($default_order);
|
||||
$options = [];
|
||||
$options_attributes = [];
|
||||
// Not sure if this will work without defining a sort per direction.
|
||||
foreach ($sort_criteria as $sort) {
|
||||
if ($sort->options['exposed'] == TRUE) {
|
||||
$id = $sort->options['id'];
|
||||
// Label should be translated via views already.
|
||||
$label = $sort->options['expose']['label'];
|
||||
$asc = "{$id}_asc";
|
||||
$desc = "{$id}_desc";
|
||||
$options[$asc] = "{$label} ↓";
|
||||
$options[$desc] = "{$label} ↑";
|
||||
$options_attributes[$asc] = [
|
||||
'data-sort_by' => $id,
|
||||
'data-sort_order' => 'ASC',
|
||||
];
|
||||
$options_attributes[$desc] = [
|
||||
'data-sort_by' => $id,
|
||||
'data-sort_order' => 'DESC',
|
||||
];
|
||||
}
|
||||
}
|
||||
return [
|
||||
'#type' => 'select',
|
||||
'#title' => 'Sort',
|
||||
'#title_display' => 'invisible',
|
||||
'#options' => $options,
|
||||
'#options_attributes' => $options_attributes,
|
||||
'#attributes' => ['autocomplete' => 'off', "aria-label" => "Sort By"],
|
||||
'#wrapper_attributes' => ['class' => ['pager__sort', 'container']],
|
||||
'#name' => 'order',
|
||||
'#value' => $default_value,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheMaxAge() {
|
||||
// The block cannot be cached, because it must always match the current
|
||||
// search results.
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
17
src/Plugin/Block/SearchResultsPagerBlockDeriver.php
Normal file
17
src/Plugin/Block/SearchResultsPagerBlockDeriver.php
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\advanced_search\Plugin\Block;
|
||||
|
||||
/**
|
||||
* This deriver creates a block for every search_api.display.
|
||||
*/
|
||||
class SearchResultsPagerBlockDeriver extends SearchApiDisplayBlockDeriver {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function label() {
|
||||
return $this->t('Search Results Pager');
|
||||
}
|
||||
|
||||
}
|
||||
30
src/Plugin/Block/ViewAndDisplayIdentifiersTrait.php
Normal file
30
src/Plugin/Block/ViewAndDisplayIdentifiersTrait.php
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\advanced_search\Plugin\Block;
|
||||
|
||||
/**
|
||||
* Gets the view and display identifiers used to create this block.
|
||||
*
|
||||
* @see Drupal\Component\Plugin\Discovery\DiscoveryInterface
|
||||
*/
|
||||
trait ViewAndDisplayIdentifiersTrait {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
abstract public function getDerivativeId();
|
||||
|
||||
/**
|
||||
* Gets the View and View Display identifiers used to derive this block.
|
||||
*
|
||||
* @return string[]
|
||||
* Returns an array of two strings where the first is the View identifier
|
||||
* and the second is the View Display identifier associated with the view
|
||||
* used to derive this block.
|
||||
*/
|
||||
public function getViewAndDisplayIdentifiers() {
|
||||
$id = $this->getDerivativeId();
|
||||
return preg_split('/__/', $id, 2);
|
||||
}
|
||||
|
||||
}
|
||||
63
src/Plugin/Field/FieldFormatter/EntityReferenceCountFormatter.php
Executable file
63
src/Plugin/Field/FieldFormatter/EntityReferenceCountFormatter.php
Executable file
|
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\advanced_search\Plugin\Field\FieldFormatter;
|
||||
|
||||
use Drupal\Core\Field\FieldItemListInterface;
|
||||
use Drupal\Core\Field\Plugin\Field\FieldFormatter\EntityReferenceFormatterBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
/**
|
||||
* Plugin implementation of the 'entity reference ID' formatter.
|
||||
*
|
||||
* @FieldFormatter(
|
||||
* id = "entity_reference_url_title",
|
||||
* label = @Translation("Children Entity Count, Label."),
|
||||
* description = @Translation("Children Entity Count, Label."),
|
||||
* field_types = {
|
||||
* "entity_reference"
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
class EntityReferenceCountFormatter extends EntityReferenceFormatterBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function defaultSettings() {
|
||||
return [
|
||||
'label' => 'Items in Collection',
|
||||
] + parent::defaultSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function settingsForm(array $form, FormStateInterface $form_state) {
|
||||
$elements['separator'] = [
|
||||
'#title' => $this->t("Text to appear next to the children's count"),
|
||||
'#type' => 'textfield',
|
||||
'#default_value' => $this->getSetting('label'),
|
||||
];
|
||||
return $elements;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function settingsSummary(): array {
|
||||
$summary = [];
|
||||
$summary[] = $this->getSetting('label') ? 'Label : ' . $this->getSetting('label') : $this->t('No label');
|
||||
return $summary;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function viewElements(FieldItemListInterface $items, $langcode): array {
|
||||
$element = [];
|
||||
$total_items = count($this->getEntitiesToView($items, $langcode));
|
||||
$element[] = ['#markup' => "<span class='collection_children__total_count'>{$total_items}<span>" . ' ' . $this->formatPlural($total_items, '1 Item in Collection', '@count Items in Collection')];
|
||||
return $element;
|
||||
}
|
||||
|
||||
}
|
||||
100
src/Plugin/Field/FieldFormatter/SearchableEntityFormatter.php
Normal file
100
src/Plugin/Field/FieldFormatter/SearchableEntityFormatter.php
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\advanced_search\Plugin\Field\FieldFormatter;
|
||||
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Field\FieldItemListInterface;
|
||||
use Drupal\Core\Field\Plugin\Field\FieldFormatter\EntityReferenceLabelFormatter;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
/**
|
||||
* Plugin implementation of the 'searchable_entity_formatter' formatter.
|
||||
*
|
||||
* @FieldFormatter(
|
||||
* id = "searchable_entity_formatter",
|
||||
* label = @Translation("Searchable entity formatter"),
|
||||
* field_types = {
|
||||
* "entity_reference"
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
class SearchableEntityFormatter extends EntityReferenceLabelFormatter {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function defaultSettings() {
|
||||
return [
|
||||
'search_link' => 'search?f[0]',
|
||||
'search_var' => 'all_subjects',
|
||||
'search_term' => FALSE,
|
||||
] + parent::defaultSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function settingsForm(array $form, FormStateInterface $form_state) {
|
||||
$elements['search_link'] = [
|
||||
'#title' => t('Search base path'),
|
||||
'#type' => 'textfield',
|
||||
'#required' => TRUE,
|
||||
'#default_value' => $this->getSetting('search_link'),
|
||||
];
|
||||
$elements['search_var'] = [
|
||||
'#title' => t('Search variable'),
|
||||
'#type' => 'textfield',
|
||||
'#required' => TRUE,
|
||||
'#default_value' => $this->getSetting('search_var'),
|
||||
];
|
||||
$elements['search_term'] = [
|
||||
'#title' => t('Use label as search term'),
|
||||
'#type' => 'checkbox',
|
||||
'#default_value' => $this->getSetting('search_term'),
|
||||
];
|
||||
return $elements;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function settingsSummary() {
|
||||
$summary = [];
|
||||
$summary[] = t('Search path: @search_link', ['@search_link' => $this->getSetting('search_link')]);
|
||||
$summary[] = t('Variable: @search_var', ['@search_var' => $this->getSetting('search_var')]);
|
||||
$summary[] = $this->getSetting('search_term') ? t('Use label as search term') : t('Use ID as search term');
|
||||
return $summary;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function viewElements(FieldItemListInterface $items, $langcode) {
|
||||
$par_elements = parent::viewElements($items, $langcode);
|
||||
foreach ($this->getEntitiesToView($items, $langcode) as $delta => $entity) {
|
||||
$search_var = $this->getSetting('search_var');
|
||||
$search_term = $this->getSetting('search_term');
|
||||
|
||||
if ($search_term == TRUE) {
|
||||
$param = $par_elements[$delta]['#title'];
|
||||
}
|
||||
else {
|
||||
$param = $entity->id();
|
||||
}
|
||||
|
||||
$url = \Drupal::service('facets.utility.url_generator')->getUrl([$search_var => [$param]]);
|
||||
|
||||
$par_elements[$delta]['#url'] = $url;
|
||||
|
||||
}
|
||||
return $par_elements;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function checkAccess(EntityInterface $entity) {
|
||||
return $entity->access('view label', NULL, TRUE);
|
||||
}
|
||||
|
||||
}
|
||||
42
src/Plugin/facets_summary/processor/ResetRemovePage.php
Normal file
42
src/Plugin/facets_summary/processor/ResetRemovePage.php
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\advanced_search\Plugin\facets_summary\processor;
|
||||
|
||||
use Drupal\facets_summary\FacetsSummaryInterface;
|
||||
use Drupal\facets_summary\Processor\BuildProcessorInterface;
|
||||
|
||||
/**
|
||||
* Reset should also remove the page query attribute.
|
||||
*
|
||||
* @SummaryProcessor(
|
||||
* id = "reset_remove_page",
|
||||
* label = @Translation("Remove page from query when resetting facets/query."),
|
||||
* description = @Translation("Remove page from query when resetting facets/query."),
|
||||
* stages = {
|
||||
* "build" = 45
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
class ResetRemovePage extends ShowSearchQueryProcessor implements BuildProcessorInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function build(FacetsSummaryInterface $facets_summary, array $build, array $facets) {
|
||||
// This processor is weighted to occur after the reset facets link.
|
||||
// Which leaves two cases:
|
||||
// - No facets selected so no reset link (we must add one).
|
||||
// - Reset link exists at the top of the list (we must remove the
|
||||
// search term from the link as well).
|
||||
$reset_index = $this->getResetLinkIndex($build);
|
||||
if ($reset_index !== NULL) {
|
||||
$reset = &$build['#items'][$reset_index];
|
||||
// Remove query from reset url as well.
|
||||
$query_params = $reset['#url']->getOption('query');
|
||||
unset($query_params['page']);
|
||||
$reset['#url']->setOption('query', $query_params);
|
||||
}
|
||||
return $build;
|
||||
}
|
||||
|
||||
}
|
||||
89
src/Plugin/facets_summary/processor/ShowActiveFacets.php
Normal file
89
src/Plugin/facets_summary/processor/ShowActiveFacets.php
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\advanced_search\Plugin\facets_summary\processor;
|
||||
|
||||
use Drupal\Core\Link;
|
||||
use Drupal\facets_summary\FacetsSummaryInterface;
|
||||
use Drupal\facets_summary\Processor\BuildProcessorInterface;
|
||||
use Drupal\facets_summary\Processor\ProcessorPluginBase;
|
||||
use Drupal\facets\FacetInterface;
|
||||
use Drupal\facets\Result\ResultInterface;
|
||||
|
||||
/**
|
||||
* Provides a processor that shows the search query.
|
||||
*
|
||||
* @SummaryProcessor(
|
||||
* id = "show_active_facets",
|
||||
* label = @Translation("Shows active hidden facets."),
|
||||
* description = @Translation("When checked, undoes 'hide_active_items_processor', etc."),
|
||||
* stages = {
|
||||
* "build" = 20
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
class ShowActiveFacets extends ProcessorPluginBase implements BuildProcessorInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function build(FacetsSummaryInterface $facets_summary, array $build, array $facets) {
|
||||
// Rebuild list of results, add back ones that have been removed.
|
||||
$facet_manager = \Drupal::service('facets.manager');
|
||||
$facet_source_id = $facets_summary->getFacetSourceId();
|
||||
$facet_manager->updateResults($facet_source_id);
|
||||
$facet_manager->processFacets($facet_source_id);
|
||||
$facets_config = $facets_summary->getFacets();
|
||||
foreach ($facets as $facet) {
|
||||
$processors = $facet->getProcessors();
|
||||
/** @var \Drupal\facets\Processor\BuildProcessorInterface $url_handler */
|
||||
$url_handler = $processors['url_processor_handler'];
|
||||
$results = $url_handler->build($facet, $facet->getResults());
|
||||
foreach ($results as $result) {
|
||||
if ($result->isActive() && $this->resultMissing($facet, $result, $build['#items'])) {
|
||||
$item = [
|
||||
'#theme' => 'facets_result_item__summary',
|
||||
'#value' => $result->getDisplayValue(),
|
||||
'#show_count' => $facets_config[$facet->id()]['show_count'],
|
||||
'#count' => $result->getCount(),
|
||||
'#is_active' => TRUE,
|
||||
'#facet' => $result->getFacet(),
|
||||
'#raw_value' => $result->getRawValue(),
|
||||
];
|
||||
$item = (new Link($item, $result->getUrl()))->toRenderable();
|
||||
$item['#wrapper_attributes'] = [
|
||||
'class' => [
|
||||
'facet-summary-item--facet',
|
||||
],
|
||||
];
|
||||
$build['#items'][] = $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $build;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the results are missing for the given facet.
|
||||
*
|
||||
* @param \Drupal\facets\FacetInterface $facet
|
||||
* The facet to check.
|
||||
* @param \Drupal\facets\Result\ResultInterface $result
|
||||
* The result of the facet to check.
|
||||
* @param array $items
|
||||
* The already completed render array of facets to check against.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the result is missing FALSE otherwise.
|
||||
*/
|
||||
protected function resultMissing(FacetInterface $facet, ResultInterface $result, array $items) {
|
||||
foreach ($items as $item) {
|
||||
$item_facet = $item['#title']['#facet'];
|
||||
$raw_value = $item['#title']['#raw_value'];
|
||||
if ($item_facet === $facet && $raw_value === $result->getRawValue()) {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
}
|
||||
67
src/Plugin/facets_summary/processor/ShowFacetsTrait.php
Normal file
67
src/Plugin/facets_summary/processor/ShowFacetsTrait.php
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\advanced_search\Plugin\facets_summary\processor;
|
||||
|
||||
use Drupal\Core\Link;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\facets\FacetInterface;
|
||||
|
||||
/**
|
||||
* Common logic to toggle the display of facets given a condition.
|
||||
*/
|
||||
trait ShowFacetsTrait {
|
||||
|
||||
/**
|
||||
* Checks if the facet should be shown or not.
|
||||
*/
|
||||
abstract protected function condition(FacetInterface $facet);
|
||||
|
||||
/**
|
||||
* Classes to include on the shown facet.
|
||||
*/
|
||||
abstract protected function classes();
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function buildHelper(array $build, array $facets) {
|
||||
$request = \Drupal::request();
|
||||
$query_params = $request->query->all();
|
||||
foreach ($facets as $facet) {
|
||||
if ($this->condition($facet)) {
|
||||
$url_alias = $facet->getUrlAlias();
|
||||
$filter_key = $facet->getFacetSourceConfig()->getFilterKey() ?: 'f';
|
||||
$active_items = $facet->getActiveItems();
|
||||
foreach ($active_items as $active_item) {
|
||||
$url = Url::createFromRequest($request);
|
||||
$modified_query_params = $query_params;
|
||||
$modified_query_params[$filter_key] = array_filter($query_params[$filter_key], function ($query_param) use ($url_alias, $active_item) {
|
||||
$pos = strpos($query_param, ':');
|
||||
$alias = substr($query_param, 0, $pos);
|
||||
$value = substr($query_param, $pos + 1);
|
||||
return !($alias == $url_alias && $value == $active_item);
|
||||
});
|
||||
$url->setOption('query', $modified_query_params);
|
||||
$item = [
|
||||
'#theme' => 'facets_result_item__summary',
|
||||
'#value' => $active_item,
|
||||
// We do not have counts for excluded/missing facets...
|
||||
'#show_count' => FALSE,
|
||||
// Do not know the count.
|
||||
'#count' => 0,
|
||||
'#is_active' => TRUE,
|
||||
'#facet' => $facet,
|
||||
'#raw_value' => $active_item,
|
||||
];
|
||||
$item = (new Link($item, $url))->toRenderable();
|
||||
$item['#wrapper_attributes'] = [
|
||||
'class' => $this->classes(),
|
||||
];
|
||||
$build['#items'][] = $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $build;
|
||||
}
|
||||
|
||||
}
|
||||
47
src/Plugin/facets_summary/processor/ShowMissingFacets.php
Normal file
47
src/Plugin/facets_summary/processor/ShowMissingFacets.php
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\advanced_search\Plugin\facets_summary\processor;
|
||||
|
||||
use Drupal\facets_summary\FacetsSummaryInterface;
|
||||
use Drupal\facets_summary\Processor\BuildProcessorInterface;
|
||||
use Drupal\facets_summary\Processor\ProcessorPluginBase;
|
||||
use Drupal\facets\FacetInterface;
|
||||
|
||||
/**
|
||||
* Provides a processor that shows the search query.
|
||||
*
|
||||
* @SummaryProcessor(
|
||||
* id = "show_missing_facets",
|
||||
* label = @Translation("Shows facets from the url that are missing from the results."),
|
||||
* description = @Translation("When checked, show facets not included in the solr result but specified in the URL."),
|
||||
* stages = {
|
||||
* "build" = 20
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
class ShowMissingFacets extends ProcessorPluginBase implements BuildProcessorInterface {
|
||||
|
||||
use ShowFacetsTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function condition(FacetInterface $facet) {
|
||||
return !$facet->getExclude() && empty($facet->getResults());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function classes() {
|
||||
return ['facet-summary-item--facet'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function build(FacetsSummaryInterface $facets_summary, array $build, array $facets) {
|
||||
return $this->buildHelper($build, $facets);
|
||||
}
|
||||
|
||||
}
|
||||
101
src/Plugin/facets_summary/processor/ShowSearchQueryProcessor.php
Normal file
101
src/Plugin/facets_summary/processor/ShowSearchQueryProcessor.php
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\advanced_search\Plugin\facets_summary\processor;
|
||||
|
||||
use Drupal\Core\Link;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\facets_summary\FacetsSummaryInterface;
|
||||
use Drupal\facets_summary\Processor\BuildProcessorInterface;
|
||||
use Drupal\facets_summary\Processor\ProcessorPluginBase;
|
||||
|
||||
/**
|
||||
* Provides a processor that shows the search query.
|
||||
*
|
||||
* @SummaryProcessor(
|
||||
* id = "show_search_query",
|
||||
* label = @Translation("Show the current search query"),
|
||||
* description = @Translation("When checked, this facet will show the search query."),
|
||||
* stages = {
|
||||
* "build" = 40
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
class ShowSearchQueryProcessor extends ProcessorPluginBase implements BuildProcessorInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function build(FacetsSummaryInterface $facets_summary, array $build, array $facets) {
|
||||
$request = \Drupal::request();
|
||||
$query_params = $request->query->all();
|
||||
if (!empty($query_params['search_api_fulltext'])) {
|
||||
$text = $query_params['search_api_fulltext'];
|
||||
unset($query_params['search_api_fulltext']);
|
||||
$url = Url::createFromRequest($request);
|
||||
$url->setOption('query', $query_params);
|
||||
$item = [
|
||||
'#theme' => 'facets_result_item__summary',
|
||||
'#is_active' => FALSE,
|
||||
'#value' => $text,
|
||||
'#show_count' => FALSE,
|
||||
];
|
||||
$item = Link::fromTextAndUrl($item, $url)->toRenderable();
|
||||
$item['#wrapper_attributes'] = [
|
||||
'class' => [
|
||||
'facet-summary-item--query',
|
||||
],
|
||||
];
|
||||
// This processor is weighted to occur after the reset facets link.
|
||||
// Which leaves two cases:
|
||||
// - No facets selected so no reset link (we must add one).
|
||||
// - Reset link exists at the top of the list (we must remove the search
|
||||
// term from the link as well).
|
||||
$reset_index = $this->getResetLinkIndex($build);
|
||||
if ($reset_index !== NULL) {
|
||||
$reset = $build['#items'][$reset_index];
|
||||
// Remove query from reset url as well.
|
||||
$query_params = $reset['#url']->getOption('query');
|
||||
unset($query_params['search_api_fulltext']);
|
||||
$reset['#url']->setOption('query', $query_params);
|
||||
array_splice($build['#items'], $reset_index + 1, 0, [$item]);
|
||||
}
|
||||
else {
|
||||
array_unshift($build['#items'], $item);
|
||||
$text = $this->t('Reset');
|
||||
if (isset($facets_summary->getProcessorConfigs()['reset_facets']['settings']['link_text'])) {
|
||||
$text = $facets_summary->getProcessorConfigs()['reset_facets']['settings']['link_text'];
|
||||
}
|
||||
$reset = Link::fromTextAndUrl($text, $url)->toRenderable();
|
||||
$reset['#wrapper_attributes'] = [
|
||||
'class' => [
|
||||
'facet-summary-item--clear',
|
||||
],
|
||||
];
|
||||
array_unshift($build['#items'], $reset);
|
||||
}
|
||||
return $build;
|
||||
}
|
||||
return $build;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the index in the $build render array of the reset link.
|
||||
*
|
||||
* @param array $build
|
||||
* The render array of the FacetSummary block.
|
||||
*
|
||||
* @return mixed|null
|
||||
* The index of the reset link the $build render array.
|
||||
*/
|
||||
protected function getResetLinkIndex(array $build) {
|
||||
if (isset($build['#items'])) {
|
||||
foreach ($build['#items'] as $index => $item) {
|
||||
if (isset($item['#wrapper_attributes']['class']) && in_array('facet-summary-item--clear', $item['#wrapper_attributes']['class'])) {
|
||||
return $index;
|
||||
}
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
}
|
||||
63
src/Utilities.php
Normal file
63
src/Utilities.php
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\advanced_search;
|
||||
|
||||
use Drupal\advanced_search\Plugin\Block\AdvancedSearchBlock;
|
||||
use Drupal\advanced_search\Plugin\Block\SearchResultsPagerBlock;
|
||||
|
||||
/**
|
||||
* Helper functions.
|
||||
*/
|
||||
class Utilities {
|
||||
|
||||
/**
|
||||
* Gets the list of views for which pager blocks have been created.
|
||||
*
|
||||
* @return array
|
||||
* List of view and display ids which have that have been used to
|
||||
* derive a SearchResultsPagerBlock.
|
||||
*/
|
||||
public static function getPagerViewDisplays() {
|
||||
$views = &drupal_static(__FUNCTION__);
|
||||
if (!isset($views)) {
|
||||
$block_storage = \Drupal::entityTypeManager()->getStorage('block');
|
||||
$active_theme = \Drupal::theme()->getActiveTheme();
|
||||
$views = [];
|
||||
/** @var \Drupal\block\Entity\Block $block */
|
||||
foreach ($block_storage->loadByProperties(['theme' => $active_theme->getName()]) as $block) {
|
||||
$plugin = $block->getPlugin();
|
||||
if ($plugin instanceof SearchResultsPagerBlock) {
|
||||
[$view_id, $display_id] = $plugin->getViewAndDisplayIdentifiers();
|
||||
$views[$block->id()] = [$view_id, $display_id];
|
||||
}
|
||||
}
|
||||
}
|
||||
return $views;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of views for which advanced search blocks have been created.
|
||||
*
|
||||
* @return array
|
||||
* List of view and display ids which have that have been used to
|
||||
* derive a SearchResultsPagerBlock.
|
||||
*/
|
||||
public static function getAdvancedSearchViewDisplays() {
|
||||
$views = &drupal_static(__FUNCTION__);
|
||||
if (!isset($views)) {
|
||||
$block_storage = \Drupal::entityTypeManager()->getStorage('block');
|
||||
$active_theme = \Drupal::theme()->getActiveTheme();
|
||||
$views = [];
|
||||
/** @var \Drupal\block\Entity\Block $block */
|
||||
foreach ($block_storage->loadByProperties(['theme' => $active_theme->getName()]) as $block) {
|
||||
$plugin = $block->getPlugin();
|
||||
if ($plugin instanceof AdvancedSearchBlock) {
|
||||
[$view_id, $display_id] = $plugin->getViewAndDisplayIdentifiers();
|
||||
$views[$block->id()] = [$view_id, $display_id];
|
||||
}
|
||||
}
|
||||
}
|
||||
return $views;
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue