my changes
This commit is contained in:
parent
28301e4312
commit
8dc1f1b048
263 changed files with 36882 additions and 4453 deletions
68
app/Library/Search/Navigation.php
Normal file
68
app/Library/Search/Navigation.php
Normal file
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
namespace App\Library\Search;
|
||||
|
||||
use App\Library\Util\QueryBuilder;
|
||||
use App\Library\Util\Searchtypes;
|
||||
use App\Library\Util\SolrSearchQuery;
|
||||
|
||||
/**
|
||||
* Class for navigation in search results.
|
||||
*/
|
||||
class Navigation
|
||||
{
|
||||
|
||||
/**
|
||||
* Builds query for Solr search.
|
||||
* @return SolrSearchQuery|void
|
||||
* @throws Application_Exception, Application_Util_BrowsingParamsException, Application_Util_QueryBuilderException
|
||||
*/
|
||||
public static function getQueryUrl(\Illuminate\Http\Request $request) : SolrSearchQuery
|
||||
{
|
||||
$queryBuilder = new QueryBuilder();
|
||||
$queryBuilderInput = $queryBuilder->createQueryBuilderInputFromRequest($request);
|
||||
|
||||
if (is_null($request->input('sortfield')) &&
|
||||
($request->input('browsing') === 'true' || $request->input('searchtype') === 'collection')) {
|
||||
$queryBuilderInput['sortField'] = 'server_date_published';
|
||||
}
|
||||
|
||||
if ($request->input('searchtype') === Searchtypes::LATEST_SEARCH) {
|
||||
return $queryBuilder->createSearchQuery(self::validateInput($queryBuilderInput, 10, 100));
|
||||
}
|
||||
|
||||
$solrSearchQuery = $queryBuilder->createSearchQuery(self::validateInput($queryBuilderInput, 1, 100));
|
||||
return $solrSearchQuery;
|
||||
//$queryBuilder->createSearchQuery(self::validateInput($queryBuilderInput,1, 100));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust the actual rows parameter value if it is not between $min
|
||||
* and $max (inclusive). In case the actual value is smaller (greater)
|
||||
* than $min ($max) it is adjusted to $min ($max).
|
||||
*
|
||||
* Sets the actual start parameter value to 0 if it is negative.
|
||||
*
|
||||
* @param array $data An array that contains the request parameters.
|
||||
* @param int $lowerBoundInclusive The lower bound.
|
||||
* @param int $upperBoundInclusive The upper bound.
|
||||
* @return int Returns the actual rows parameter value or an adjusted value if
|
||||
* it is not in the interval [$lowerBoundInclusive, $upperBoundInclusive].
|
||||
*
|
||||
*/
|
||||
private static function validateInput(array $input, $min = 1, $max = 100) : array
|
||||
{
|
||||
if ($input['rows'] > $max) {
|
||||
// $logger->warn("Values greater than $max are currently not allowed for the rows paramter.");
|
||||
$input['rows'] = $max;
|
||||
}
|
||||
if ($input['rows'] < $min) {
|
||||
// $logger->warn("rows parameter is smaller than $min: adjusting to $min.");
|
||||
$input['rows'] = $min;
|
||||
}
|
||||
if ($input['start'] < 0) {
|
||||
// $logger->warn("A negative start parameter is ignored.");
|
||||
$input['start'] = 0;
|
||||
}
|
||||
return $input;
|
||||
}
|
||||
}
|
313
app/Library/Search/SearchResult.php
Normal file
313
app/Library/Search/SearchResult.php
Normal file
|
@ -0,0 +1,313 @@
|
|||
<?php
|
||||
namespace App\Library\Search;
|
||||
|
||||
/**
|
||||
* Implements API for describing successful response to search query.
|
||||
*/
|
||||
|
||||
use App\Library\Util\SearchResultMatch;
|
||||
|
||||
class SearchResult
|
||||
{
|
||||
protected $data = array(
|
||||
'matches' => null,
|
||||
'count' => null,
|
||||
'querytime' => null,
|
||||
'facets' => null,
|
||||
);
|
||||
protected $validated = false;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @return SearchResult
|
||||
*/
|
||||
public static function create()
|
||||
{
|
||||
return new static();
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns matches returned in response to search query.
|
||||
*
|
||||
* @param mixed $documentId ID of document considered match of related search query
|
||||
* @return SearchResultMatch
|
||||
*/
|
||||
public function addMatch($documentId)
|
||||
{
|
||||
if (!is_array($this->data['matches'])) {
|
||||
$this->data['matches'] = array();
|
||||
}
|
||||
$match = SearchResultMatch::create($documentId);
|
||||
$this->data['matches'][] = $match;
|
||||
|
||||
return $match;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets number of all matching documents.
|
||||
*
|
||||
* @note This may include documents not listed as matches here due to using
|
||||
* paging parameters on query.
|
||||
*
|
||||
* @param int $allMatchesCount number of all matching documents
|
||||
* @return $this fluent interface
|
||||
*/
|
||||
public function setAllMatchesCount($allMatchesCount)
|
||||
{
|
||||
if (!is_null($this->data['count'])) {
|
||||
throw new RuntimeException('must not set count of all matches multiple times');
|
||||
}
|
||||
|
||||
if (!ctype_digit(trim($allMatchesCount))) {
|
||||
throw new InvalidArgumentException('invalid number of overall matches');
|
||||
}
|
||||
$this->data['count'] = intval($allMatchesCount);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets information on time taken for querying search engine.
|
||||
*
|
||||
* @param string $time
|
||||
* @return $this fluent interface
|
||||
*/
|
||||
public function setQueryTime($time)
|
||||
{
|
||||
if (!is_null($this->data['querytime'])) {
|
||||
throw new RuntimeException('must not set query time multiple times');
|
||||
}
|
||||
if (!is_null($time)) {
|
||||
$this->data['querytime'] = trim($time);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds another result of faceted search to current result set.
|
||||
*
|
||||
* @param string $facetField name of field result of faceted search is related to
|
||||
* @param string $text description on particular faceted result on field (e.g. single value in field)
|
||||
* @param int $count number of occurrences of facet on field in all matches
|
||||
* @return $this fluent interface
|
||||
*
|
||||
* TODO special year_inverted facet handling should be moved to separate class
|
||||
*/
|
||||
public function addFacet($facetField, $text, $count)
|
||||
{
|
||||
$facetField = strval($facetField);
|
||||
|
||||
// remove inverted sorting prefix from year values
|
||||
if ($facetField === 'year_inverted') {
|
||||
$text = explode(':', $text, 2)[1];
|
||||
|
||||
// treat 'year_inverted' as if it was 'year'
|
||||
$facetField = 'year';
|
||||
}
|
||||
|
||||
// treat 'year_inverted' as if it was 'year'
|
||||
if ($facetField === 'year_inverted') {
|
||||
$facetField = 'year';
|
||||
}
|
||||
|
||||
if (!is_array($this->data['facets'])) {
|
||||
$this->data['facets'] = array();
|
||||
}
|
||||
|
||||
if (!array_key_exists($facetField, $this->data['facets'])) {
|
||||
$this->data['facets'][$facetField] = array();
|
||||
}
|
||||
|
||||
$this->data['facets'][$facetField][] = new Opus_Search_Result_Facet($text, $count);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves results of faceted search.
|
||||
*
|
||||
* @return Opus_Search_Result_Facet[][] map of fields' names into sets of facet result per field
|
||||
*/
|
||||
public function getFacets()
|
||||
{
|
||||
return is_null($this->data['facets']) ? array() : $this->data['facets'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves set of facet results on single field selected by name.
|
||||
*
|
||||
* @param string $fieldName name of field returned facet result is related to
|
||||
* @return Opus_Search_Result_Facet[] set of facet results on selected field
|
||||
*/
|
||||
public function getFacet($fieldName)
|
||||
{
|
||||
if ($this->data['facets'] && array_key_exists($fieldName, $this->data['facets'])) {
|
||||
return $this->data['facets'][$fieldName];
|
||||
}
|
||||
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves set of matching and locally existing documents returned in
|
||||
* response to some search query.
|
||||
*
|
||||
* @return Opus_Search_Result_Match[]
|
||||
*/
|
||||
public function getReturnedMatches()
|
||||
{
|
||||
if (is_null($this->data['matches'])) {
|
||||
return array();
|
||||
}
|
||||
|
||||
// map AND FILTER set of returned matches ensuring to list related
|
||||
// documents existing locally, only
|
||||
$matches = array();
|
||||
|
||||
foreach ($this->data['matches'] as $match) {
|
||||
try {
|
||||
/** @var SearchResultMatch $match */
|
||||
// $match->getDocument();
|
||||
$matches[] = $match;
|
||||
} catch (Opus_Document_Exception $e) {
|
||||
Opus_Log::get()->warn('skipping matching but locally missing document #' . $match->getId());
|
||||
}
|
||||
}
|
||||
return $matches;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves set of matching documents' IDs returned in response to some
|
||||
* search query.
|
||||
*
|
||||
* @note If query was requesting to retrieve non-qualified matches this set
|
||||
* might include IDs of documents that doesn't exist locally anymore.
|
||||
*
|
||||
* @return int[]
|
||||
*/
|
||||
public function getReturnedMatchingIds()
|
||||
{
|
||||
if (is_null($this->data['matches'])) {
|
||||
return array();
|
||||
}
|
||||
|
||||
return array_map(function ($match) {
|
||||
/** @var SearchResultMatch $match */
|
||||
return $match->getId();
|
||||
}, $this->data['matches']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves set of matching documents.
|
||||
*
|
||||
* @note This is provided for downward compatibility, though it's signature
|
||||
* has changed in that it's returning set of Opus_Document instances
|
||||
* rather than set of Opus_SolrSearch_Result instances.
|
||||
*
|
||||
* @note The wording is less specific in that all information in response to
|
||||
* search query may considered results of search. Thus this new API
|
||||
* prefers "matches" over "results".
|
||||
*
|
||||
* @deprecated
|
||||
* @return Opus_Document[]
|
||||
*/
|
||||
public function getResults()
|
||||
{
|
||||
return $this->getReturnedMatches();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all returned matches referring to Opus documents missing in local
|
||||
* database.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function dropLocallyMissingMatches()
|
||||
{
|
||||
if (!$this->validated) {
|
||||
$finder = new Opus_DocumentFinder();
|
||||
|
||||
$returnedIds = $this->getReturnedMatchingIds();
|
||||
$existingIds = $finder
|
||||
->setServerState('published')
|
||||
->setIdSubset($returnedIds)
|
||||
->ids();
|
||||
|
||||
if (count($returnedIds) !== count($existingIds)) {
|
||||
Opus_Log::get()->err(sprintf(
|
||||
"inconsistency between db and search index: index returns %d documents, but only %d found in db",
|
||||
count($returnedIds),
|
||||
count($existingIds)
|
||||
));
|
||||
|
||||
// update set of returned matches internally
|
||||
$this->data['matches'] = array();
|
||||
foreach ($existingIds as $id) {
|
||||
$this->addMatch($id);
|
||||
}
|
||||
// set mark to prevent validating matches again
|
||||
$this->validated = true;
|
||||
}
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves overall number of matches.
|
||||
*
|
||||
* @note This number includes matches not included in fetched subset of
|
||||
* matches.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getAllMatchesCount()
|
||||
{
|
||||
if (is_null($this->data['count'])) {
|
||||
throw new RuntimeException('count of matches have not been provided yet');
|
||||
}
|
||||
return $this->data['count'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves overall number of matches.
|
||||
*
|
||||
* @note This is provided for downward compatibility.
|
||||
*
|
||||
* @deprecated
|
||||
* @return int
|
||||
*/
|
||||
public function getNumberOfHits()
|
||||
{
|
||||
return $this->getAllMatchesCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves information on search query's processing time.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getQueryTime()
|
||||
{
|
||||
return $this->data['querytime'];
|
||||
}
|
||||
|
||||
public function __get($name)
|
||||
{
|
||||
switch (strtolower(trim($name))) {
|
||||
case 'matches':
|
||||
return $this->getReturnedMatches();
|
||||
|
||||
case 'allmatchescount':
|
||||
return $this->getAllMatchesCount();
|
||||
|
||||
case 'querytime':
|
||||
return $this->getQueryTime();
|
||||
default:
|
||||
throw new RuntimeException('invalid request for property ' . $name);
|
||||
}
|
||||
}
|
||||
}
|
218
app/Library/Search/SolariumAdapter.php
Normal file
218
app/Library/Search/SolariumAdapter.php
Normal file
|
@ -0,0 +1,218 @@
|
|||
<?php
|
||||
|
||||
namespace App\Library\Search;
|
||||
|
||||
//use App\Library\Util\SolrSearchQuery;
|
||||
use App\Library\Util\SearchParameter;
|
||||
use App\Library\Search\SearchResult;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class SolariumAdapter
|
||||
{
|
||||
protected $options;
|
||||
|
||||
/**
|
||||
* @var \Solarium\Core\Client\Client
|
||||
*/
|
||||
protected $client;
|
||||
|
||||
public function __construct($serviceName, $options)
|
||||
{
|
||||
$this->options = $options;
|
||||
$this->client = new \Solarium\Client($options);
|
||||
|
||||
// ensure service is basically available
|
||||
$ping = $this->client->createPing();
|
||||
$this->execute($ping, 'failed pinging service ' . $serviceName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps name of field returned by search engine into name of asset to use
|
||||
* on storing field's value in context of related match.
|
||||
*
|
||||
* This mapping relies on runtime configuration. Mapping is defined per
|
||||
* service in
|
||||
*
|
||||
* @param string $fieldName
|
||||
* @return string
|
||||
*/
|
||||
protected function mapResultFieldToAsset($fieldName)
|
||||
{
|
||||
//if ( $this->options->fieldToAsset instanceof Zend_Config )
|
||||
//{
|
||||
// return $this->options->fieldToAsset->get( $fieldName, $fieldName );
|
||||
//}
|
||||
return $fieldName;
|
||||
}
|
||||
|
||||
public function getDomain()
|
||||
{
|
||||
return 'solr';
|
||||
}
|
||||
|
||||
public function createQuery() : SearchParameter
|
||||
{
|
||||
return new SearchParameter();
|
||||
}
|
||||
|
||||
public function customSearch(SearchParameter $queryParameter)
|
||||
{
|
||||
$search = $this->client->createSelect();
|
||||
$solariumQuery = $this->applyParametersToSolariumQuery($search, $queryParameter, false);
|
||||
$searchResult = $this->processQuery($solariumQuery);
|
||||
return $searchResult;
|
||||
}
|
||||
|
||||
protected function applyParametersToSolariumQuery(\Solarium\QueryType\Select\Query\Query $query, SearchParameter $parameters = null, $preferOriginalQuery = false)
|
||||
{
|
||||
if ($parameters) {
|
||||
//$subfilters = $parameters->getSubFilters();
|
||||
//if ( $subfilters !== null ) {
|
||||
// foreach ( $subfilters as $name => $subfilter ) {
|
||||
// if ( $subfilter instanceof Opus_Search_Solr_Filter_Raw || $subfilter instanceof Opus_Search_Solr_Solarium_Filter_Complex ) {
|
||||
// $query->createFilterQuery( $name )
|
||||
// ->setQuery( $subfilter->compile( $query ) );
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
// $filter = $parameters->getFilter();//"aa"
|
||||
// if ( $filter instanceof Opus_Search_Solr_Filter_Raw || $filter instanceof Opus_Search_Solr_Solarium_Filter_Complex ) {
|
||||
// if ( !$query->getQuery() || !$preferOriginalQuery ) {
|
||||
// $compiled = $filter->compile( $query );
|
||||
// if ( $compiled !== null ) {
|
||||
// // compile() hasn't implicitly assigned query before
|
||||
// $query->setQuery( $compiled );
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
$filter = $parameters->getFilter();//"aa" all: '*:*'
|
||||
if ($filter !== null) {
|
||||
//$query->setStart( intval( $start ) );
|
||||
//$query->setQuery('%P1%', array($filter));
|
||||
$query->setQuery($filter);
|
||||
}
|
||||
|
||||
|
||||
$start = $parameters->getStart();
|
||||
if ($start !== null) {
|
||||
$query->setStart(intval($start));
|
||||
}
|
||||
|
||||
$rows = $parameters->getRows();
|
||||
if ($rows !== null) {
|
||||
$query->setRows(intval($rows));
|
||||
}
|
||||
|
||||
$union = $parameters->getUnion();
|
||||
if ($union !== null) {
|
||||
$query->setQueryDefaultOperator($union ? 'OR' : 'AND');
|
||||
}
|
||||
|
||||
$fields = $parameters->getFields();
|
||||
if ($fields !== null) {
|
||||
$query->setFields($fields);
|
||||
}
|
||||
|
||||
$sortings = $parameters->getSort();
|
||||
if ($sortings !== null) {
|
||||
$query->setSorts($sortings);
|
||||
}
|
||||
|
||||
$facet = $parameters->getFacet();
|
||||
if ($facet !== null) {
|
||||
$facetSet = $query->getFacetSet();
|
||||
foreach ($facet->getFields() as $field) {
|
||||
$facetSet->createFacetField($field->getName())
|
||||
->setField($field->getName())
|
||||
->setMinCount($field->getMinCount())
|
||||
->setLimit($field->getLimit())
|
||||
->setSort($field->getSort() ? 'index' : null);
|
||||
}
|
||||
if ($facet->isFacetOnly()) {
|
||||
$query->setFields(array());
|
||||
}
|
||||
}
|
||||
}
|
||||
return $query;
|
||||
}
|
||||
|
||||
protected function execute($query, $actionText)
|
||||
{
|
||||
$result = null;
|
||||
try {
|
||||
$result = $this->client->execute($query);
|
||||
} catch (\Solarium\Exception\HttpException $e) {
|
||||
sprintf('%s: %d %s', $actionText, $e->getCode(), $e->getStatusMessage());
|
||||
} finally {
|
||||
return $result;
|
||||
}
|
||||
|
||||
// if ( $result->getStatus() ) {
|
||||
// throw new Opus_Search_Exception( $actionText, $result->getStatus() );
|
||||
// }
|
||||
}
|
||||
|
||||
protected function processQuery(\Solarium\QueryType\Select\Query\Query $query) : SearchResult
|
||||
{
|
||||
// send search query to service
|
||||
$request = $this->execute($query, 'failed querying search engine');
|
||||
|
||||
//$count = $request->getDocuments();
|
||||
// create result descriptor
|
||||
$result = SearchResult::create()
|
||||
->setAllMatchesCount($request->getNumFound())
|
||||
->setQueryTime($request->getQueryTime());
|
||||
|
||||
// add description on every returned match
|
||||
$excluded = 0;
|
||||
foreach ($request->getDocuments() as $document) {
|
||||
/** @var \Solarium\QueryType\Select\Result\Document $document */
|
||||
$fields = $document->getFields();
|
||||
|
||||
if (array_key_exists('id', $fields)) {
|
||||
$match = $result->addMatch($fields['id']);
|
||||
|
||||
foreach ($fields as $fieldName => $fieldValue) {
|
||||
switch ($fieldName) {
|
||||
case 'id':
|
||||
break;
|
||||
|
||||
case 'score':
|
||||
$match->setScore($fieldValue);
|
||||
break;
|
||||
|
||||
case 'server_date_modified':
|
||||
$match->setServerDateModified($fieldValue);
|
||||
break;
|
||||
|
||||
case 'fulltext_id_success':
|
||||
$match->setFulltextIDsSuccess($fieldValue);
|
||||
break;
|
||||
|
||||
case 'fulltext_id_failure':
|
||||
$match->setFulltextIDsFailure($fieldValue);
|
||||
break;
|
||||
|
||||
default:
|
||||
$match->setAsset($fieldName, $fieldValue);
|
||||
//$match->setAsset( $this->mapResultFieldToAsset( $fieldName ), $fieldValue );
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$excluded++;
|
||||
}
|
||||
}
|
||||
|
||||
if ($excluded > 0) {
|
||||
Log::warning(sprintf(
|
||||
'search yielded %d matches not available in result set for missing ID of related document',
|
||||
$excluded
|
||||
));
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
Loading…
Add table
editor.link_modal.header
Reference in a new issue