Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 197 |
|
0.00% |
0 / 14 |
CRAP | |
0.00% |
0 / 1 |
SpecialProofreadPages | |
0.00% |
0 / 197 |
|
0.00% |
0 / 14 |
2070 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
execute | |
0.00% |
0 / 44 |
|
0.00% |
0 / 1 |
132 | |||
reallyDoQuery | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
20 | |||
preprocessResults | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
6 | |||
isExpensive | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
isSyndicated | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
isCacheable | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
linkParameters | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
2 | |||
displaySearchForm | |
0.00% |
0 / 56 |
|
0.00% |
0 / 1 |
2 | |||
getQueryInfo | |
0.00% |
0 / 32 |
|
0.00% |
0 / 1 |
72 | |||
buildValueField | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
56 | |||
sortDescending | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
formatResult | |
0.00% |
0 / 32 |
|
0.00% |
0 / 1 |
30 | |||
getGroupName | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | /** |
3 | * This program is free software; you can redistribute it and/or modify |
4 | * it under the terms of the GNU General Public License as published by |
5 | * the Free Software Foundation; either version 2 of the License, or |
6 | * (at your option) any later version. |
7 | * |
8 | * This program is distributed in the hope that it will be useful, |
9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
11 | * GNU General Public License for more details. |
12 | * |
13 | * You should have received a copy of the GNU General Public License along |
14 | * with this program; if not, write to the Free Software Foundation, Inc., |
15 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
16 | * http://www.gnu.org/copyleft/gpl.html |
17 | * |
18 | * @file |
19 | * @ingroup SpecialPage |
20 | */ |
21 | |
22 | namespace ProofreadPage\Special; |
23 | |
24 | use HTMLForm; |
25 | use ISearchResultSet; |
26 | use MediaWiki\MediaWikiServices; |
27 | use MediaWiki\SpecialPage\QueryPage; |
28 | use MediaWiki\Title\Title; |
29 | use ProofreadPage\Context; |
30 | use SearchResult; |
31 | |
32 | class SpecialProofreadPages extends QueryPage { |
33 | /** @var string|null */ |
34 | protected $searchTerm; |
35 | /** @var string[]|null */ |
36 | protected $searchList; |
37 | /** @var bool|null */ |
38 | protected $suppressSqlOffset; |
39 | /** @var string|null */ |
40 | protected $queryFilter; |
41 | /** @var string|null */ |
42 | protected $queryOrder; |
43 | /** @var bool|null */ |
44 | protected $sortAscending; |
45 | /** @var true|null */ |
46 | protected $addOne; |
47 | |
48 | /** |
49 | * @param string $name |
50 | */ |
51 | public function __construct( $name = 'IndexPages' ) { |
52 | parent::__construct( $name ); |
53 | $this->mIncludable = true; |
54 | } |
55 | |
56 | /** |
57 | * @inheritDoc |
58 | */ |
59 | public function execute( $parameters ) { |
60 | $this->setHeaders(); |
61 | if ( $this->limit == 0 && $this->offset == 0 ) { |
62 | [ $this->limit, $this->offset ] = $this->getRequest() |
63 | ->getLimitOffsetForUser( $this->getUser() ); |
64 | } |
65 | $output = $this->getOutput(); |
66 | $request = $this->getRequest(); |
67 | $output->addModules( 'ext.proofreadpage.special.indexpages' ); |
68 | $output->addWikiTextAsContent( |
69 | $this->msg( 'proofreadpage_specialpage_text' )->inContentLanguage()->plain() |
70 | ); |
71 | |
72 | $this->searchList = null; |
73 | $this->searchTerm = $request->getText( 'key' ); |
74 | $this->queryFilter = $request->getText( 'filter' ); |
75 | $this->queryOrder = $request->getText( 'order' ); |
76 | $this->sortAscending = $request->getBool( 'sortascending' ); |
77 | $this->suppressSqlOffset = false; |
78 | |
79 | // don't show navigation if included in another page |
80 | $this->shownavigation = !$this->including(); |
81 | |
82 | if ( !$this->getConfig()->get( 'DisableTextSearch' ) ) { |
83 | if ( !$this->including() ) { |
84 | // Only show the search form when not including in another page. |
85 | $this->displaySearchForm(); |
86 | } |
87 | |
88 | if ( $this->searchTerm ) { |
89 | $indexNamespaceId = Context::getDefaultContext()->getIndexNamespaceId(); |
90 | $searchEngine = MediaWikiServices::getInstance()->getSearchEngineFactory()->create(); |
91 | $searchEngine->setLimitOffset( $this->limit + 1, $this->offset ); |
92 | $searchEngine->setNamespaces( [ $indexNamespaceId ] ); |
93 | $status = $searchEngine->searchText( $this->searchTerm ); |
94 | if ( $status instanceof ISearchResultSet ) { |
95 | $textMatches = $status; |
96 | $status = null; |
97 | } elseif ( $status->isOK() ) { |
98 | $textMatches = $status->getValue(); |
99 | } else { |
100 | $textMatches = null; |
101 | } |
102 | if ( !( $textMatches instanceof ISearchResultSet ) ) { |
103 | // TODO: $searchEngine->searchText() can return status objects |
104 | // Might want to extract some information from them |
105 | $output->showErrorPage( |
106 | 'proofreadpage_specialpage_searcherror', |
107 | 'proofreadpage_specialpage_searcherrortext' |
108 | ); |
109 | } else { |
110 | $this->searchList = []; |
111 | /** @var SearchResult $result */ |
112 | foreach ( $textMatches as $result ) { |
113 | $title = $result->getTitle(); |
114 | if ( $title->inNamespace( $indexNamespaceId ) ) { |
115 | array_push( $this->searchList, $title->getDBkey() ); |
116 | } |
117 | } |
118 | $this->suppressSqlOffset = true; |
119 | } |
120 | } |
121 | } |
122 | |
123 | parent::execute( $parameters ); |
124 | } |
125 | |
126 | /** |
127 | * Wrapper function for parent function in QueryPage class |
128 | * @param int|false $limit |
129 | * @param int|false $offset |
130 | * @return \Wikimedia\Rdbms\IResultWrapper |
131 | */ |
132 | public function reallyDoQuery( $limit, $offset = false ) { |
133 | if ( $this->searchList && count( $this->searchList ) > $this->limit ) { |
134 | // Delete the last item to avoid the sort done by reallyDoQuery move it |
135 | // to another position than the last |
136 | $this->addOne = true; |
137 | array_pop( $this->searchList ); |
138 | } |
139 | if ( $this->suppressSqlOffset ) { |
140 | // Bug #27678: Do not use offset here, because it was already used in |
141 | // search performed by execute method |
142 | return parent::reallyDoQuery( $limit, false ); |
143 | } |
144 | return parent::reallyDoQuery( $limit, $offset ); |
145 | } |
146 | |
147 | /** |
148 | * Increments $numRows if the last item of the result has been deleted |
149 | * @param \Wikimedia\Rdbms\IDatabase $dbr [optional] (unused parameter) |
150 | * @param \Wikimedia\Rdbms\IResultWrapper $res [optional] (unused parameter) |
151 | */ |
152 | public function preprocessResults( $dbr, $res ) { |
153 | if ( $this->addOne !== null ) { |
154 | // there is a deleted item |
155 | $this->numRows++; |
156 | } |
157 | } |
158 | |
159 | /** @inheritDoc */ |
160 | public function isExpensive() { |
161 | // FIXME: the query does filesort, so we're kinda lying here right now |
162 | return false; |
163 | } |
164 | |
165 | /** @inheritDoc */ |
166 | public function isSyndicated() { |
167 | return false; |
168 | } |
169 | |
170 | /** @inheritDoc */ |
171 | public function isCacheable() { |
172 | // The page is not cacheable due to its search capabilities |
173 | return false; |
174 | } |
175 | |
176 | public function linkParameters() { |
177 | return [ |
178 | 'key' => $this->searchTerm, |
179 | 'filter' => $this->queryFilter, |
180 | 'order' => $this->queryOrder, |
181 | 'sortascending' => $this->sortAscending |
182 | ]; |
183 | } |
184 | |
185 | /** |
186 | * Display the HTMLForm of the search form. |
187 | */ |
188 | protected function displaySearchForm() { |
189 | $formDescriptor = [ |
190 | 'key' => [ |
191 | 'type' => 'text', |
192 | 'name' => 'key', |
193 | 'id' => 'key', |
194 | 'label-message' => 'proofreadpage_specialpage_label_key', |
195 | 'size' => 50, |
196 | 'default' => $this->searchTerm, |
197 | ], |
198 | 'filter' => [ |
199 | 'type' => 'select', |
200 | 'name' => 'filter', |
201 | 'id' => 'filter', |
202 | 'label-message' => 'proofreadpage_specialpage_label_filterby', |
203 | 'default' => $this->queryFilter, |
204 | 'options' => [ |
205 | $this->msg( 'proofreadpage_specialpage_filterby_all' )->text() => 'all', |
206 | $this->msg( 'proofreadpage_specialpage_filterby_incomplete' )->text() => 'incomplete', |
207 | $this->msg( 'proofreadpage_specialpage_filterby_proofread' )->text() => 'proofread', |
208 | $this->msg( 'proofreadpage_specialpage_filterby_proofread_or_validated' )->text() => |
209 | 'proofreadOrValidated', |
210 | $this->msg( 'proofreadpage_specialpage_filterby_validated' )->text() => 'validated' |
211 | ] |
212 | ], |
213 | 'order' => [ |
214 | 'type' => 'select', |
215 | 'name' => 'order', |
216 | 'id' => 'order', |
217 | 'label-message' => 'proofreadpage_specialpage_label_orderby', |
218 | 'default' => $this->queryOrder, |
219 | 'options' => [ |
220 | $this->msg( 'proofreadpage_index_status' )->text() => 'quality', |
221 | $this->msg( 'proofreadpage_index_size' )->text() => 'size', |
222 | $this->msg( 'proofreadpage_pages_to_proofread' )->text() => 'toProofread', |
223 | $this->msg( 'proofreadpage_pages_to_validate' )->text() => 'toValidate', |
224 | $this->msg( 'proofreadpage_pages_to_proofread_or_validate' )->text() => |
225 | 'toProofreadOrValidate', |
226 | $this->msg( 'proofreadpage_alphabeticalorder' )->text() => 'alpha', |
227 | ] |
228 | ], |
229 | 'sortascending' => [ |
230 | 'type' => 'check', |
231 | 'name' => 'sortascending', |
232 | 'id' => 'sortascending', |
233 | 'label-message' => 'proofreadpage_specialpage_label_sortascending', |
234 | 'selected' => $this->sortAscending, |
235 | ] |
236 | ]; |
237 | |
238 | $htmlForm = HTMLForm::factory( 'ooui', $formDescriptor, $this->getContext() ); |
239 | $htmlForm |
240 | ->addHiddenField( 'limit', $this->limit ) |
241 | ->setMethod( 'get' ) |
242 | ->setSubmitTextMsg( 'ilsubmit' ) |
243 | ->setWrapperLegendMsg( 'proofreadpage_specialpage_legend' ) |
244 | ->prepareForm() |
245 | ->displayForm( false ); |
246 | } |
247 | |
248 | /** |
249 | * @return mixed[] |
250 | */ |
251 | public function getQueryInfo() { |
252 | $conds = []; |
253 | if ( $this->searchTerm ) { |
254 | if ( $this->searchList !== null ) { |
255 | $conds = [ 'page_namespace' => Context::getDefaultContext()->getIndexNamespaceId() ]; |
256 | if ( $this->searchList ) { |
257 | $conds['page_title'] = $this->searchList; |
258 | } else { |
259 | // If not pages were found do not return results |
260 | $conds[] = '1=0'; |
261 | } |
262 | } |
263 | } |
264 | |
265 | switch ( $this->queryFilter ) { |
266 | case 'incomplete': |
267 | $conds[] = 'pr_count - pr_q0 - pr_q3 - pr_q4 > 0'; |
268 | break; |
269 | case 'proofread': |
270 | $conds[] = 'pr_count > 0 and pr_q3 > 0 and pr_count - pr_q0 - pr_q3 - pr_q4 = 0'; |
271 | break; |
272 | case 'proofreadOrValidated': |
273 | $conds[] = 'pr_count > 0 and pr_count - pr_q0 - pr_q3 - pr_q4 = 0'; |
274 | break; |
275 | case 'validated': |
276 | $conds[] = 'pr_count > 0 and pr_count - pr_q0 - pr_q4 = 0'; |
277 | break; |
278 | } |
279 | |
280 | return [ |
281 | 'tables' => [ 'pr_index', 'page' ], |
282 | 'fields' => [ |
283 | 'namespace' => 'page_namespace', |
284 | 'title' => 'page_title', |
285 | 'value' => $this->buildValueField(), |
286 | 'pr_count', 'pr_q0', 'pr_q1', 'pr_q2', 'pr_q3', 'pr_q4' |
287 | ], |
288 | 'conds' => $conds, |
289 | 'options' => [], |
290 | 'join_conds' => [ 'page' => [ 'INNER JOIN', 'page_id=pr_page_id' ] ] |
291 | ]; |
292 | } |
293 | |
294 | private function buildValueField() { |
295 | switch ( $this->queryOrder ) { |
296 | case 'size': |
297 | return 'pr_count'; |
298 | case 'alpha': |
299 | return 'page_title'; |
300 | case 'toProofread': |
301 | return 'pr_count - pr_q4 - pr_q3 - pr_q0'; |
302 | case 'toValidate': |
303 | return 'pr_q3'; |
304 | case 'toProofreadOrValidate': |
305 | return 'pr_count - pr_q4 - pr_q0'; |
306 | default: |
307 | return '2 * pr_q4 + pr_q3'; |
308 | } |
309 | } |
310 | |
311 | public function sortDescending() { |
312 | return !$this->sortAscending; |
313 | } |
314 | |
315 | /** |
316 | * @param \Skin $skin |
317 | * @param \stdClass $result Result row |
318 | * @return string|false false to skip the row |
319 | */ |
320 | public function formatResult( $skin, $result ) { |
321 | $title = Title::makeTitleSafe( $result->namespace, $result->title ); |
322 | if ( !$title ) { |
323 | return '<!-- Invalid title ' . |
324 | htmlspecialchars( "{$result->namespace}:{$result->title}" ) . '-->'; |
325 | } |
326 | $plink = $this->isCached() |
327 | ? $this->getLinkRenderer()->makeLink( $title, $title->getText() ) |
328 | : $this->getLinkRenderer()->makeKnownLink( $title, $title->getText() ); |
329 | |
330 | if ( !$title->exists() ) { |
331 | return "<del>{$plink}</del>"; |
332 | } |
333 | |
334 | $size = $result->pr_count; |
335 | $q0 = $result->pr_q0; |
336 | $q1 = $result->pr_q1; |
337 | $q2 = $result->pr_q2; |
338 | $q3 = $result->pr_q3; |
339 | $q4 = $result->pr_q4; |
340 | $num_void = $size - $q1 - $q2 - $q3 - $q4 - $q0; |
341 | $void_cell = $num_void |
342 | ? '<td class="qualitye" style="width:' . $num_void . 'px;"></td>' |
343 | : ''; |
344 | $textualAlternative = $this->msg( 'proofreadpage-indexquality-alt', $q4, $q3, $q1 )->escaped(); |
345 | $qualityOutput = '<table class="pr_quality" title="' . $textualAlternative . '"> |
346 | <tr> |
347 | <td class="quality4" style="width:' . $q4 . 'px;"></td> |
348 | <td class="quality3" style="width:' . $q3 . 'px;"></td> |
349 | <td class="quality2" style="width:' . $q2 . 'px;"></td> |
350 | <td class="quality1" style="width:' . $q1 . 'px;"></td> |
351 | <td class="quality0" style="width:' . $q0 . 'px;"></td> |
352 | ' . $void_cell . ' |
353 | </tr> |
354 | </table>'; |
355 | |
356 | $dirmark = $this->getLanguage()->getDirMark(); |
357 | $pages = $this->msg( 'proofreadpage_pages', $size )->numParams( $size )->text(); |
358 | |
359 | return "<div class=\"prp-indexpages-row\"><span>{$plink} {$dirmark}[$pages]</span>" . |
360 | "<div>{$qualityOutput}</div></div>"; |
361 | } |
362 | |
363 | /** |
364 | * @return string |
365 | */ |
366 | protected function getGroupName() { |
367 | return 'pages'; |
368 | } |
369 | } |