invalidQCache(); $this->q = null; if ($searchType === self::SIMPLE || $searchType === self::ADVANCED || $searchType === self::ALL_DOCS) { $this->searchType = $searchType; return; } if ($searchType === self::FACET_ONLY) { $this->searchType = self::FACET_ONLY; $this->setRows(0); return; } if ($searchType === self::LATEST_DOCS) { $this->searchType = self::LATEST_DOCS; $this->sortField = 'server_date_published'; $this->sortOrder = 'desc'; return; } if ($searchType === self::DOC_ID) { $this->searchType = self::DOC_ID; return; } } public function getSearchType() { return $this->searchType; } public function getFacetField() { return $this->facetField; } public function setFacetField($facetField) { $this->facetField = $facetField; } public function getStart() { return $this->start; } public function setStart($start) { $this->start = $start; } public static function getDefaultRows() { return SolrSearchQuery::getDefaultRows(); } public function getRows() { return $this->rows; } public function setRows($rows) { $this->rows = $rows; } public function getSortField() { return $this->sortField; } public function setSortField($sortField) { if ($sortField === self::DEFAULT_SORTFIELD) { if ($this->searchType === self::ALL_DOCS) { // change the default sortfield for searchtype all // since sorting by relevance does not make any sense here $this->sortField = 'server_date_published'; } else { $this->sortField = self::DEFAULT_SORTFIELD; } return; } $this->sortField = $sortField; if (strpos($sortField, 'doc_sort_order_for_seriesid_') !== 0 && strpos($sortField, 'server_date_published') !== 0) { // add _sort to the end of $sortField if not already done $suffix = '_sort'; if (substr($sortField, strlen($sortField) - strlen($suffix)) !== $suffix) { $this->sortField .= $suffix; } } } public function getSortOrder() { return $this->sortOrder; } public function setSortOrder($sortOrder) { $this->sortOrder = $sortOrder; } public function getSeriesId() { return $this->seriesId; } /** * * @return array An array that contains all specified filter queries. */ public function getFilterQueries() { return $this->filterQueries; } /** * * @param string $filterField The field that should be used in a filter query. * @param string $filterValue The field value that should be used in a filter query. */ public function addFilterQuery($filterField, $filterValue) { if ($filterField == 'has_fulltext') { $filterQuery = $filterField . ':' . $filterValue; } else { $filterQuery = '{!raw f=' . $filterField . '}' . $filterValue; } array_push($this->filterQueries, $filterQuery); // we need to store the ID of the requested series here, // since we need it later to build the index field name if ($filterField === 'series_ids') { $this->seriesId = $filterValue; } } /** * * @param array $filterQueries An array of queries that should be used as filter queries. */ public function setFilterQueries($filterQueries) { $this->filterQueries = $filterQueries; } public function getCatchAll() { return $this->catchAll; } public function setCatchAll($catchAll) { $this->catchAll = $catchAll; $this->invalidQCache(); } /** * * @param string $name * @param string $value * @param string $modifier */ public function setField($name, $value, $modifier = self::SEARCH_MODIFIER_CONTAINS_ALL) { if (!empty($value)) { $this->fieldValues[$name] = $value; $this->modifier[$name] = $modifier; $this->invalidQCache(); } } /** * * @param string $name * @return Returns null if no values was specified for the given field name. */ public function getField($name) { if (array_key_exists($name, $this->fieldValues)) { return $this->fieldValues[$name]; } return null; } /** * * @param string $fieldname * @return returns null if no modifier was specified for the given field name. */ public function getModifier($fieldname) { if (array_key_exists($fieldname, $this->modifier)) { return $this->modifier[$fieldname]; } return null; } public function getQ() { if (is_null($this->q)) { // earlier cached query was marked as invalid: perform new setup of query cache $this->q = $this->setupQCache(); } // return cached result (caching is done here since building q is an expensive operation) return $this->q; } private function setupQCache() { if ($this->searchType === self::SIMPLE) { if ($this->getCatchAll() === '*:*') { return $this->catchAll; } return $this->escape($this->getCatchAll()); } if ($this->searchType === self::FACET_ONLY || $this->searchType === self::LATEST_DOCS || $this->searchType === self::ALL_DOCS) { return '*:*'; } if ($this->searchType === self::DOC_ID) { return 'id:' . $this->fieldValues['id']; } return $this->buildAdvancedQString(); } private function invalidQCache() { $this->q = null; } private function buildAdvancedQString() { $q = "{!lucene q.op=AND}"; $first = true; foreach ($this->fieldValues as $fieldname => $fieldvalue) { if ($first) { $first = false; } else { $q .= ' '; } if ($this->modifier[$fieldname] === self::SEARCH_MODIFIER_CONTAINS_ANY) { $q .= $this->combineSearchTerms($fieldname, $fieldvalue, 'OR'); continue; } if ($this->modifier[$fieldname] === self::SEARCH_MODIFIER_CONTAINS_NONE) { $q .= '-' . $this->combineSearchTerms($fieldname, $fieldvalue, 'OR'); continue; } // self::SEARCH_MODIFIER_CONTAINS_ALL $q .= $this->combineSearchTerms($fieldname, $fieldvalue); } return $q; } private function combineSearchTerms($fieldname, $fieldvalue, $conjunction = null) { $result = $fieldname . ':('; $firstTerm = true; $queryTerms = preg_split("/[\s]+/", $this->escape($fieldvalue), null, PREG_SPLIT_NO_EMPTY); foreach ($queryTerms as $queryTerm) { if ($firstTerm) { $firstTerm = false; } else { $result .= is_null($conjunction) ? " " : " $conjunction "; } $result .= $queryTerm; } $result .= ')'; return $result; } public function disableEscaping() { $this->invalidQCache(); $this->escapingEnabled = false; } /** * Escape Lucene's special query characters specified in * http://lucene.apache.org/java/3_0_2/queryparsersyntax.html#Escaping%20Special%20Characters * Escaping currently ignores * and ? which are used as wildcard operators. * Additionally, double-quotes are not escaped and a double-quote is added to * the end of $query in case it contains an odd number of double-quotes. * @param string $query The query which needs to be escaped. */ private function escape($query) { if (!$this->escapingEnabled) { return $query; } $query = trim($query); // add one " to the end of $query if it contains an odd number of " if (substr_count($query, '"') % 2 == 1) { $query .= '"'; } // escape special characters (currently ignore " \* \?) outside of "" $insidePhrase = false; $result = ''; foreach (explode('"', $query) as $phrase) { if ($insidePhrase) { $result .= '"' . $phrase . '"'; } else { $result .= preg_replace( '/(\+|-|&&|\|\||!|\(|\)|\{|}|\[|]|\^|~|:|\\\)/', '\\\$1', $this->lowercaseWildcardQuery($phrase) ); } $insidePhrase = !$insidePhrase; } return $result; } private function lowercaseWildcardQuery($query) { // check if $query is a wildcard query if (strpos($query, '*') === false && strpos($query, '?') === false) { return $query; } // lowercase query return strtolower($query); } public function __toString() { if ($this->searchType === self::SIMPLE) { return 'simple search with query ' . $this->getQ(); } if ($this->searchType === self::FACET_ONLY) { return 'facet only search with query *:*'; } if ($this->searchType === self::LATEST_DOCS) { return 'search for latest documents with query *:*'; } if ($this->searchType === self::ALL_DOCS) { return 'search for all documents'; } if ($this->searchType === self::DOC_ID) { return 'search for document id ' . $this->getQ(); } return 'advanced search with query ' . $this->getQ(); } /** * * @param boolean $returnIdsOnly */ public function setReturnIdsOnly($returnIdsOnly) { $this->returnIdsOnly = $returnIdsOnly; } /** * @return boolean */ public function isReturnIdsOnly() { return $this->returnIdsOnly; } }