Drupal modules for browsing and managing Fedora-based digital repositories.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

295 lines
9.5 KiB

<?php
namespace Drupal\islandora_advanced_search;
use Drupal\islandora_advanced_search\Form\AdvancedSearchForm;
/**
* 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 islandora_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\islandora_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\islandora_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));
foreach ($solr_field_mapping[$this->field] as $field) {
$terms[] = "$field:$value";
}
$terms = implode(' ', $terms);
return $this->include ? "($terms)" : "-($terms)";
}
}