my changes

This commit is contained in:
Arno Kaimbacher 2018-08-06 14:30:51 +02:00
parent 28301e4312
commit 8dc1f1b048
263 changed files with 36882 additions and 4453 deletions

View 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;
}
}

View 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);
}
}
}

View 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;
}
}