Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
95.77% |
68 / 71 |
|
62.50% |
5 / 8 |
CRAP | |
0.00% |
0 / 1 |
FullTextCirrusSearchResultBuilder | |
95.77% |
68 / 71 |
|
62.50% |
5 / 8 |
33 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
newBuilder | |
75.00% |
3 / 4 |
|
0.00% |
0 / 1 |
2.06 | |||
build | |
100.00% |
20 / 20 |
|
100.00% |
1 / 1 |
4 | |||
getTitleHelper | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
doTitleSnippet | |
100.00% |
19 / 19 |
|
100.00% |
1 / 1 |
11 | |||
doMainSnippet | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
6 | |||
doHeadings | |
85.71% |
6 / 7 |
|
0.00% |
0 / 1 |
4.05 | |||
doCategory | |
83.33% |
5 / 6 |
|
0.00% |
0 / 1 |
4.07 |
1 | <?php |
2 | |
3 | namespace CirrusSearch\Search; |
4 | |
5 | use CirrusSearch\Search\Fetch\HighlightedField; |
6 | use CirrusSearch\Search\Fetch\HighlightingTrait; |
7 | use MWTimestamp; |
8 | use Title; |
9 | |
10 | class FullTextCirrusSearchResultBuilder { |
11 | use HighlightingTrait; |
12 | |
13 | /** @var CirrusSearchResultBuilder|null */ |
14 | private $builder; |
15 | |
16 | /** @var TitleHelper */ |
17 | private $titleHelper; |
18 | |
19 | /** @var HighlightedField[][] indexed per target and ordered by priority */ |
20 | private $highligtedFields; |
21 | |
22 | /** @var string[] */ |
23 | private $extraFields; |
24 | |
25 | /** |
26 | * @param TitleHelper $titleHelper |
27 | * @param HighlightedField[][] $hlFieldsPerTarget list of highlighted field indexed per target and sorted by priority |
28 | * @param string[] $extraFields list of extra fields to extract from the source doc |
29 | */ |
30 | public function __construct( TitleHelper $titleHelper, array $hlFieldsPerTarget, array $extraFields = [] ) { |
31 | $this->titleHelper = $titleHelper; |
32 | $this->highligtedFields = $hlFieldsPerTarget; |
33 | $this->extraFields = $extraFields; |
34 | } |
35 | |
36 | /** |
37 | * @param Title $title |
38 | * @param string $docId |
39 | * @return CirrusSearchResultBuilder |
40 | */ |
41 | private function newBuilder( Title $title, $docId ): CirrusSearchResultBuilder { |
42 | if ( $this->builder === null ) { |
43 | $this->builder = new CirrusSearchResultBuilder( $title, $docId ); |
44 | } else { |
45 | $this->builder->reset( $title, $docId ); |
46 | } |
47 | return $this->builder; |
48 | } |
49 | |
50 | /** |
51 | * @param \Elastica\Result $result |
52 | * @return CirrusSearchResult |
53 | */ |
54 | public function build( \Elastica\Result $result ): CirrusSearchResult { |
55 | $title = $this->getTitleHelper()->makeTitle( $result ); |
56 | $fields = $result->getFields(); |
57 | $builder = $this->newBuilder( $title, $result->getId() ) |
58 | ->wordCount( $fields['text.word_count'][0] ?? 0 ) |
59 | ->byteSize( $result->text_bytes ?? 0 ) |
60 | ->timestamp( new MWTimestamp( $result->timestamp ) ) |
61 | ->score( $result->getScore() ) |
62 | ->explanation( $result->getExplanation() ); |
63 | |
64 | if ( isset( $result->namespace_text ) ) { |
65 | $builder->interwikiNamespaceText( $result->namespace_text ); |
66 | } |
67 | |
68 | $highlights = $result->getHighlights(); |
69 | $this->doTitleSnippet( $title, $result, $highlights ); |
70 | $this->doMainSnippet( $highlights ); |
71 | $this->doHeadings( $title, $highlights ); |
72 | $this->doCategory( $highlights ); |
73 | $source = $result->getData(); |
74 | |
75 | foreach ( $this->extraFields as $field ) { |
76 | if ( isset( $source[$field] ) ) { |
77 | $builder->addExtraField( $field, $source[$field] ); |
78 | } |
79 | } |
80 | |
81 | return $builder->build(); |
82 | } |
83 | |
84 | /** |
85 | * @return TitleHelper |
86 | */ |
87 | protected function getTitleHelper(): TitleHelper { |
88 | return $this->titleHelper; |
89 | } |
90 | |
91 | private function doTitleSnippet( Title $title, \Elastica\Result $result, array $highlights ) { |
92 | $matched = false; |
93 | foreach ( $this->highligtedFields[ArrayCirrusSearchResult::TITLE_SNIPPET] as $hlField ) { |
94 | if ( isset( $highlights[$hlField->getFieldName()] ) ) { |
95 | $nstext = $title->getNamespace() === 0 ? '' : |
96 | $this->titleHelper->getNamespaceText( $title ) . ':'; |
97 | $this->builder->titleSnippet( $nstext . $this->escapeHighlightedText( $highlights[ $hlField->getFieldName() ][ 0 ] ) ); |
98 | $matched = true; |
99 | break; |
100 | } |
101 | } |
102 | if ( !$matched && $title->isExternal() ) { |
103 | // Interwiki searches are weird. They won't have title highlights by design, but |
104 | // if we don't return a title snippet we'll get weird display results. |
105 | $this->builder->titleSnippet( $title->getText() ); |
106 | } |
107 | |
108 | if ( !$matched && isset( $this->highligtedFields[ArrayCirrusSearchResult::REDIRECT_SNIPPET] ) ) { |
109 | foreach ( $this->highligtedFields[ArrayCirrusSearchResult::REDIRECT_SNIPPET] as $hlField ) { |
110 | // Make sure to find the redirect title before escaping because escaping breaks it.... |
111 | if ( !isset( $highlights[$hlField->getFieldName()][0] ) ) { |
112 | continue; |
113 | } |
114 | $redirTitle = $this->findRedirectTitle( $result, $highlights[$hlField->getFieldName()][0] ); |
115 | if ( $redirTitle !== null ) { |
116 | $this->builder->redirectTitle( $redirTitle ) |
117 | ->redirectSnippet( $this->escapeHighlightedText( $highlights[$hlField->getFieldName()][0] ) ); |
118 | break; |
119 | } |
120 | } |
121 | } |
122 | } |
123 | |
124 | private function doMainSnippet( $highlights ) { |
125 | $hasTextSnippet = false; |
126 | foreach ( $this->highligtedFields[ArrayCirrusSearchResult::TEXT_SNIPPET] as $hlField ) { |
127 | if ( isset( $highlights[$hlField->getFieldName()][0] ) ) { |
128 | $snippet = $highlights[$hlField->getFieldName()][0]; |
129 | if ( $this->containsMatches( $snippet ) ) { |
130 | $this->builder->textSnippet( $this->escapeHighlightedText( $snippet ) )-> |
131 | fileMatch( $hlField->getFieldName() === 'file_text' ); |
132 | $hasTextSnippet = true; |
133 | break; |
134 | } |
135 | } |
136 | } |
137 | |
138 | // Hardcode the fallback to the "text" highlight, it generally contains the beginning of the |
139 | // text content if nothing has matched. |
140 | if ( !$hasTextSnippet && isset( $highlights['text'][0] ) ) { |
141 | $this->builder->textSnippet( $this->escapeHighlightedText( $highlights['text'][0] ) ); |
142 | } |
143 | } |
144 | |
145 | private function doHeadings( Title $title, $highlights ) { |
146 | if ( !isset( $this->highligtedFields[ArrayCirrusSearchResult::SECTION_SNIPPET] ) ) { |
147 | return; |
148 | } |
149 | foreach ( $this->highligtedFields[ArrayCirrusSearchResult::SECTION_SNIPPET] as $hlField ) { |
150 | if ( isset( $highlights[$hlField->getFieldName()] ) ) { |
151 | $this->builder->sectionSnippet( $this->escapeHighlightedText( $highlights[$hlField->getFieldName()][0] ) ) |
152 | ->sectionTitle( $this->findSectionTitle( $highlights[$hlField->getFieldName()][0], $title ) ); |
153 | break; |
154 | } |
155 | } |
156 | } |
157 | |
158 | private function doCategory( $highlights ) { |
159 | if ( !isset( $this->highligtedFields[ArrayCirrusSearchResult::CATEGORY_SNIPPET] ) ) { |
160 | return; |
161 | } |
162 | foreach ( $this->highligtedFields[ArrayCirrusSearchResult::CATEGORY_SNIPPET] as $hlField ) { |
163 | if ( isset( $highlights[$hlField->getFieldName()] ) ) { |
164 | $this->builder->categorySnippet( $this->escapeHighlightedText( $highlights[$hlField->getFieldName()][0] ) ); |
165 | } |
166 | break; |
167 | } |
168 | } |
169 | } |