%PDF- %PDF-
Direktori : /www/varak.net/wiki.varak.net/extensions/CirrusSearch/includes/Search/ |
Current File : /www/varak.net/wiki.varak.net/extensions/CirrusSearch/includes/Search/ResultSet.php |
<?php namespace CirrusSearch\Search; use CirrusSearch\Searcher; use LinkBatch; use SearchResultSet; /** * A set of results from Elasticsearch. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * http://www.gnu.org/copyleft/gpl.html */ class ResultSet extends SearchResultSet { /** * @var \Elastica\ResultSet */ private $result; /** * @var int */ private $hits; /** * @var int */ private $totalHits; /** * @var string|null */ private $suggestionQuery; /** * @var string|null */ private $suggestionSnippet; /** * @var bool */ private $searchContainedSyntax; /** * @var string */ private $interwikiPrefix; /** * @var array */ private $interwikiResults = []; /** * @var string|null */ private $rewrittenQuery; /** * @var string|null */ private $rewrittenQuerySnippet; /** * @param string[] $suggestPrefixes * @param string[] $suggestSuffixes * @param \Elastica\ResultSet $res * @param bool $searchContainedSyntax * @param string $interwiki */ public function __construct( array $suggestPrefixes, array $suggestSuffixes, \Elastica\ResultSet $res, $searchContainedSyntax, $interwiki = '' ) { $this->result = $res; $this->searchContainedSyntax = $searchContainedSyntax; $this->hits = $res->count(); $this->totalHits = $res->getTotalHits(); $this->interwikiPrefix = $interwiki; $this->preCacheContainedTitles( $this->result ); $suggestion = $this->findSuggestion(); if ( $suggestion && ! $this->resultContainsFullyHighlightedMatch() ) { $this->suggestionQuery = $suggestion[ 'text' ]; $this->suggestionSnippet = $this->escapeHighlightedSuggestion( $suggestion[ 'highlighted' ] ); if ( $suggestPrefixes ) { $suggestPrefix = implode( ' ', $suggestPrefixes ); // No need to escape suggestionQuery because Linker will escape it. $this->suggestionQuery = $suggestPrefix . $this->suggestionQuery; $this->suggestionSnippet = htmlspecialchars( $suggestPrefix ) . $this->suggestionSnippet; } if ( $suggestSuffixes ) { $suggestSuffix = implode( ' ', $suggestSuffixes ); // No need to escape suggestionQuery because Linker will escape it. $this->suggestionQuery = $this->suggestionQuery . $suggestSuffix; $this->suggestionSnippet = $this->suggestionSnippet . htmlspecialchars( $suggestSuffix ); } } } /** * Is rewriting this query OK? * * @param int $threshold Minimum number of results to reach before rewriting is not allowed. * @return bool True when rewriting this query is allowed */ public function isQueryRewriteAllowed( $threshold = 1 ) { if ( $this->numRows() >= $threshold || $this->searchContainedSyntax() ) { return false; } foreach ( $this->getInterwikiResults( SearchResultSet::SECONDARY_RESULTS ) as $resultSet ) { if ( $resultSet->numRows() >= $threshold ) { return false; } } return true; } /** * @return string|null */ private function findSuggestion() { // TODO some kind of weighting? $suggest = $this->result->getResponse()->getData(); if ( !isset( $suggest[ 'suggest' ] ) ) { return null; } $suggest = $suggest[ 'suggest' ]; // Elasticsearch will send back the suggest element but no sub suggestion elements if the wiki is empty. // So we should check to see if they exist even though in normal operation they always will. if ( isset( $suggest[ 'suggest' ] ) ) { // Now just grab the first one it sent back. foreach ( $suggest[ 'suggest' ][ 0 ][ 'options' ] as $option ) { return $option; } } return null; } /** * Escape a highlighted suggestion coming back from Elasticsearch. * * @param string $suggestion suggestion from elasticsearch * @return string $suggestion with html escaped _except_ highlighting pre and post tags */ private function escapeHighlightedSuggestion( $suggestion ) { static $suggestionHighlightPreEscaped = null, $suggestionHighlightPostEscaped = null; if ( $suggestionHighlightPreEscaped === null ) { $suggestionHighlightPreEscaped = htmlspecialchars( Searcher::SUGGESTION_HIGHLIGHT_PRE ); $suggestionHighlightPostEscaped = htmlspecialchars( Searcher::SUGGESTION_HIGHLIGHT_POST ); } return str_replace( [ $suggestionHighlightPreEscaped, $suggestionHighlightPostEscaped ], [ Searcher::SUGGESTION_HIGHLIGHT_PRE, Searcher::SUGGESTION_HIGHLIGHT_POST ], htmlspecialchars( $suggestion ) ); } /** * @return bool */ private function resultContainsFullyHighlightedMatch() { foreach ( $this->result->getResults() as $result ) { $highlights = $result->getHighlights(); // If the whole string is highlighted then return true if ( isset( $highlights[ 'title' ] ) && !trim( preg_replace( Searcher::HIGHLIGHT_REGEX, '', $highlights[ 'title' ][ 0 ] ) ) ) { return true; } } return false; } /** * Loads the result set into the mediawiki LinkCache via a * batch query. By pre-caching this we ensure methods such as * Result::isMissingRevision() don't trigger a query for each and * every search result. * * @param \Elastica\ResultSet $resultSet Result set from which the titles come */ private function preCacheContainedTitles( \Elastica\ResultSet $resultSet ) { // We can only pull in information about the local wiki if ( $this->interwikiPrefix !== '' ) { return; } $lb = new LinkBatch; foreach ( $resultSet->getResults() as $result ) { $lb->add( $result->namespace, $result->title ); } if ( !$lb->isEmpty() ) { $lb->setCaller( __METHOD__ ); $lb->execute(); } } /** * @return int */ public function getTotalHits() { return $this->totalHits; } /** * @return int */ public function numRows() { return $this->hits; } /** * @return bool */ public function hasSuggestion() { return $this->suggestionQuery !== null; } /** * @return string|null */ public function getSuggestionQuery() { return $this->suggestionQuery; } /** * @return string */ public function getSuggestionSnippet() { return $this->suggestionSnippet; } /** * @return Result|false */ public function next() { $current = $this->result->current(); if ( $current ) { $this->result->next(); return new Result( $this->result, $current, $this->interwikiPrefix ); } return false; } public function rewind() { $this->result->rewind(); } /** * @param ResultSet $res * @param int $type One of SearchResultSet::* constants * @param string $interwiki */ public function addInterwikiResults( ResultSet $res, $type, $interwiki ) { $this->interwikiResults[$type][$interwiki] = $res; } /** * @param int $type * @return SearchResultSet[] */ public function getInterwikiResults( $type = SearchResultSet::SECONDARY_RESULTS ) { return isset($this->interwikiResults[$type]) ? $this->interwikiResults[$type] : []; } /** * @param int $type * @return bool */ public function hasInterwikiResults( $type = SearchResultSet::SECONDARY_RESULTS ) { return isset($this->interwikiResults[$type]); } /** * @return bool */ public function searchContainedSyntax() { return $this->searchContainedSyntax; } /** * @param string $newQuery * @param string|null $newQuerySnippet */ public function setRewrittenQuery($newQuery, $newQuerySnippet=null) { $this->rewrittenQuery = $newQuery; $this->rewrittenQuerySnippet = $newQuerySnippet ?: htmlspecialchars( $newQuery ); } /** * @return bool */ public function hasRewrittenQuery() { return $this->rewrittenQuery !== null; } /** * @return string|null */ public function getQueryAfterRewrite() { return $this->rewrittenQuery; } /** * @return string|null */ public function getQueryAfterRewriteSnippet() { return $this->rewrittenQuerySnippet; } }