Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
7.14% covered (danger)
7.14%
2 / 28
22.22% covered (danger)
22.22%
2 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
CirrusSearchResult
7.14% covered (danger)
7.14%
2 / 28
22.22% covered (danger)
22.22%
2 / 9
220.97
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 initFromTitle
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 isBrokenTitle
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 isMissingRevision
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
42
 increment
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 getTitle
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getFile
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 initText
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 containsHighlight
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getDocId
n/a
0 / 0
n/a
0 / 0
0
 getScore
n/a
0 / 0
n/a
0 / 0
0
 getExplanation
n/a
0 / 0
n/a
0 / 0
0
 clearRedirectTitle
n/a
0 / 0
n/a
0 / 0
0
1<?php
2
3namespace CirrusSearch\Search;
4
5use CirrusSearch\Searcher;
6use CirrusSearch\Util;
7use LogicException;
8use MediaWiki\FileRepo\File\File;
9use MediaWiki\MediaWikiServices;
10use MediaWiki\Title\Title;
11use SearchResult;
12use SearchResultTrait;
13
14/**
15 * Base class for SearchResult
16 */
17abstract class CirrusSearchResult extends SearchResult {
18    use SearchResultTrait;
19
20    /**
21     * @var string Counter title for identified missing revisions
22     */
23    private const MISSING_REVISION_TOTAL = 'missing_revision_total';
24
25    /**
26     * @var Title
27     */
28    private $title;
29
30    /**
31     * @var ?File
32     */
33    private $file;
34
35    /**
36     * @var bool
37     */
38    private $checkedForFile = false;
39
40    public function __construct( Title $title ) {
41        $this->title = $title;
42    }
43
44    /**
45     * Initialize from a Title and if possible initializes a corresponding
46     * File.
47     *
48     * @param Title $title
49     */
50    final protected function initFromTitle( $title ) {
51        // Everything is done in the constructor.
52        // XXX: we do not call the SearchResultInitFromTitle hook
53        // this hook is designed to fetch a particular revision
54        // but the way cirrus works does not allow to vary the revision
55        // text being displayed at query time.
56    }
57
58    /**
59     * Check if this is result points to an invalid title
60     *
61     * @return bool
62     */
63    final public function isBrokenTitle() {
64        // Title is mandatory in the constructor it would have failed earlier if the Title was broken
65        return false;
66    }
67
68    /**
69     * Check if target page is missing, happens when index is out of date
70     *
71     * @return bool
72     */
73    final public function isMissingRevision() {
74        global $wgCirrusSearchDevelOptions;
75        if ( isset( $wgCirrusSearchDevelOptions['ignore_missing_rev'] ) ) {
76            return false;
77        }
78        if ( !$this->getTitle()->isKnown() ) {
79            $this->increment( self::MISSING_REVISION_TOTAL, 'title' );
80            return true;
81        }
82        // Similarly if we matched due to a redirect
83        if ( $this->getRedirectTitle() && !$this->getRedirectTitle()->isKnown() ) {
84            // There may be other reasons this result matched, for now keep it in the results
85            // but clear the redirect.
86            $redirectIsOnlyMatch = $this->clearRedirectTitle();
87            $this->increment(
88                self::MISSING_REVISION_TOTAL,
89                $redirectIsOnlyMatch ? 'only_redirect' : 'redirect'
90            );
91        }
92
93        return false;
94    }
95
96    private function increment( string $counter, string $problem ) {
97        Util::getStatsFactory()
98            ->getCounter( $counter )
99            ->setLabel( 'problem', $problem )
100            ->increment();
101    }
102
103    /**
104     * @return Title
105     */
106    final public function getTitle() {
107        return $this->title;
108    }
109
110    /**
111     * Get the file for this page, if one exists
112     * @return File|null
113     */
114    final public function getFile() {
115        if ( !$this->checkedForFile && $this->getTitle()->getNamespace() === NS_FILE ) {
116            $this->checkedForFile = true;
117            $this->file = MediaWikiServices::getInstance()->getRepoGroup()
118                ->findFile( $this->title );
119        }
120        return $this->file;
121    }
122
123    /**
124     * Lazy initialization of article text from DB
125     * @return never
126     */
127    final protected function initText() {
128        throw new LogicException( "initText() should not be called on CirrusSearchResult, " .
129            "content must be fetched directly from the backend at query time." );
130    }
131
132    /**
133     * @param string $text A snippet from the search highlighter
134     * @return bool True when the string contains highlight markers
135     */
136    protected function containsHighlight( string $text ): bool {
137        return strpos( $text, Searcher::HIGHLIGHT_PRE ) !== false;
138    }
139
140    /**
141     * @return string
142     */
143    abstract public function getDocId();
144
145    /**
146     * @return float
147     */
148    abstract public function getScore();
149
150    /**
151     * @return array|null
152     */
153    abstract public function getExplanation();
154
155    /**
156     * Clear any redirect match so it won't be part of the result.
157     *
158     * @return bool True if the redirect was the only snippet available
159     *  for this result.
160     */
161    abstract protected function clearRedirectTitle(): bool;
162}