Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
58.46% |
38 / 65 |
|
33.33% |
2 / 6 |
CRAP | |
0.00% |
0 / 1 |
Filters | |
58.46% |
38 / 65 |
|
33.33% |
2 / 6 |
74.45 | |
0.00% |
0 / 1 |
booleanOr | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
30 | |||
unify | |
100.00% |
26 / 26 |
|
100.00% |
1 / 1 |
10 | |||
unifyNonScript | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
6 | |||
insource | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
intitle | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
12 | |||
insourceOrIntitle | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | namespace CirrusSearch\Search; |
4 | |
5 | use Elastica\Query\AbstractQuery; |
6 | use Elastica\Query\BoolQuery; |
7 | use Elastica\Query\MatchAll; |
8 | |
9 | /** |
10 | * Utilities for dealing with filters. |
11 | * |
12 | * This program is free software; you can redistribute it and/or modify |
13 | * it under the terms of the GNU General Public License as published by |
14 | * the Free Software Foundation; either version 2 of the License, or |
15 | * (at your option) any later version. |
16 | * |
17 | * This program is distributed in the hope that it will be useful, |
18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
20 | * GNU General Public License for more details. |
21 | * |
22 | * You should have received a copy of the GNU General Public License along |
23 | * with this program; if not, write to the Free Software Foundation, Inc., |
24 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
25 | * http://www.gnu.org/copyleft/gpl.html |
26 | */ |
27 | class Filters { |
28 | /** |
29 | * Turns a list of queries into a boolean OR, requiring only one |
30 | * of the provided queries to match. |
31 | * |
32 | * @param AbstractQuery[] $queries |
33 | * @param bool $matchAll When true (default) function never returns null, |
34 | * when no queries are provided a MatchAll is returned. |
35 | * @return AbstractQuery|null The resulting OR query. Only returns null when |
36 | * no queries are passed and $matchAll is false. |
37 | */ |
38 | public static function booleanOr( array $queries, $matchAll = true ) { |
39 | if ( !$queries ) { |
40 | return $matchAll ? new MatchAll() : null; |
41 | } elseif ( count( $queries ) === 1 ) { |
42 | return reset( $queries ); |
43 | } else { |
44 | $bool = new BoolQuery(); |
45 | foreach ( $queries as $query ) { |
46 | $bool->addShould( $query ); |
47 | } |
48 | $bool->setMinimumShouldMatch( 1 ); |
49 | return $bool; |
50 | } |
51 | } |
52 | |
53 | /** |
54 | * Merges lists of include/exclude filters into a single filter that |
55 | * Elasticsearch will execute efficiently. |
56 | * |
57 | * @param AbstractQuery[] $mustFilters filters that must match all returned documents |
58 | * @param AbstractQuery[] $mustNotFilters filters that must not match all returned documents |
59 | * @return null|AbstractQuery null if there are no filters or one that will execute |
60 | * all of the provided filters |
61 | */ |
62 | public static function unify( array $mustFilters, array $mustNotFilters ) { |
63 | // We want to make sure that we execute script filters last. So we do these steps: |
64 | // 1. Strip script filters from $must and $mustNot. |
65 | // 2. Unify the non-script filters. |
66 | // 3. Build a BoolAnd filter out of the script filters if there are any. |
67 | $scriptFilters = []; |
68 | $nonScriptMust = []; |
69 | $nonScriptMustNot = []; |
70 | foreach ( $mustFilters as $must ) { |
71 | if ( $must->hasParam( 'script' ) ) { |
72 | $scriptFilters[] = $must; |
73 | } else { |
74 | $nonScriptMust[] = $must; |
75 | } |
76 | } |
77 | $scriptMustNotFilter = new BoolQuery(); |
78 | foreach ( $mustNotFilters as $mustNot ) { |
79 | if ( $mustNot->hasParam( 'script' ) ) { |
80 | $scriptMustNotFilter->addMustNot( $mustNot ); |
81 | } else { |
82 | $nonScriptMustNot[] = $mustNot; |
83 | } |
84 | } |
85 | if ( $scriptMustNotFilter->hasParam( 'must_not' ) ) { |
86 | $scriptFilters[] = $scriptMustNotFilter; |
87 | } |
88 | |
89 | $nonScript = self::unifyNonScript( $nonScriptMust, $nonScriptMustNot ); |
90 | $scriptFiltersCount = count( $scriptFilters ); |
91 | if ( $scriptFiltersCount === 0 ) { |
92 | return $nonScript; |
93 | } |
94 | |
95 | $bool = new BoolQuery(); |
96 | if ( $nonScript === null ) { |
97 | if ( $scriptFiltersCount === 1 ) { |
98 | return $scriptFilters[ 0 ]; |
99 | } |
100 | } else { |
101 | $bool->addFilter( $nonScript ); |
102 | } |
103 | foreach ( $scriptFilters as $scriptFilter ) { |
104 | $bool->addFilter( $scriptFilter ); |
105 | } |
106 | return $bool; |
107 | } |
108 | |
109 | /** |
110 | * Unify non-script filters into a single filter. |
111 | * |
112 | * @param AbstractQuery[] $mustFilters filters that must be found |
113 | * @param AbstractQuery[] $mustNotFilters filters that must not be found |
114 | * @return null|AbstractQuery null if there are no filters or one that will execute |
115 | * all of the provided filters |
116 | */ |
117 | private static function unifyNonScript( array $mustFilters, array $mustNotFilters ) { |
118 | $mustFilterCount = count( $mustFilters ); |
119 | $mustNotFilterCount = count( $mustNotFilters ); |
120 | if ( $mustFilterCount + $mustNotFilterCount === 0 ) { |
121 | return null; |
122 | } |
123 | if ( $mustFilterCount === 1 && $mustNotFilterCount == 0 ) { |
124 | return $mustFilters[ 0 ]; |
125 | } |
126 | $bool = new BoolQuery(); |
127 | foreach ( $mustFilters as $must ) { |
128 | $bool->addMust( $must ); |
129 | } |
130 | foreach ( $mustNotFilters as $mustNot ) { |
131 | $bool->addMustNot( $mustNot ); |
132 | } |
133 | return $bool; |
134 | } |
135 | |
136 | /** |
137 | * Create a query for insource: queries. This function is pure, deferring |
138 | * state changes to the reference-updating return function. |
139 | * |
140 | * @param Escaper $escaper |
141 | * @param string $value |
142 | * @return AbstractQuery |
143 | */ |
144 | public static function insource( Escaper $escaper, $value ) { |
145 | return self::insourceOrIntitle( $escaper, $value, static function () { |
146 | return [ 'source_text.plain' ]; |
147 | } ); |
148 | } |
149 | |
150 | /** |
151 | * Create a query for intitle: queries. |
152 | * |
153 | * @param Escaper $escaper |
154 | * @param string $value |
155 | * @param bool $plain Only search plain fields |
156 | * @return AbstractQuery |
157 | */ |
158 | public static function intitle( Escaper $escaper, $value, $plain = false ) { |
159 | return self::insourceOrIntitle( $escaper, $value, static function ( $queryString ) use ( $plain ) { |
160 | if ( $plain || preg_match( '/[?*]/u', $queryString ) ) { |
161 | return [ 'title.plain', 'redirect.title.plain' ]; |
162 | } else { |
163 | return [ 'title', 'title.plain', 'redirect.title', 'redirect.title.plain' ]; |
164 | } |
165 | } ); |
166 | } |
167 | |
168 | /** |
169 | * @param Escaper $escaper |
170 | * @param string $value |
171 | * @param callable $fieldF |
172 | * @return AbstractQuery |
173 | */ |
174 | private static function insourceOrIntitle( Escaper $escaper, $value, $fieldF ) { |
175 | $queryString = $escaper->fixupWholeQueryString( |
176 | $escaper->fixupQueryStringPart( $value ) ); |
177 | $field = $fieldF( $queryString ); |
178 | $query = new \Elastica\Query\QueryString( $queryString ); |
179 | $query->setFields( $field ); |
180 | $query->setDefaultOperator( 'AND' ); |
181 | $query->setAllowLeadingWildcard( $escaper->getAllowLeadingWildcard() ); |
182 | $query->setFuzzyPrefixLength( 2 ); |
183 | $query->setRewrite( 'top_terms_boost_1024' ); |
184 | |
185 | return $query; |
186 | } |
187 | } |