Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 205 |
|
0.00% |
0 / 9 |
CRAP | |
0.00% |
0 / 1 |
ProtectedPagesPager | |
0.00% |
0 / 204 |
|
0.00% |
0 / 9 |
1722 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
6 | |||
preprocessResults | |
0.00% |
0 / 17 |
|
0.00% |
0 / 1 |
20 | |||
getFieldNames | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
12 | |||
formatValue | |
0.00% |
0 / 97 |
|
0.00% |
0 / 1 |
420 | |||
getQueryInfo | |
0.00% |
0 / 60 |
|
0.00% |
0 / 1 |
72 | |||
getTableClass | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getIndexField | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getDefaultSort | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
isFieldSortable | |
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 Pager |
20 | */ |
21 | |
22 | namespace MediaWiki\Pager; |
23 | |
24 | use LogEventsList; |
25 | use LogPage; |
26 | use MediaWiki\Cache\LinkBatchFactory; |
27 | use MediaWiki\CommentFormatter\RowCommentFormatter; |
28 | use MediaWiki\CommentStore\CommentStore; |
29 | use MediaWiki\Context\IContextSource; |
30 | use MediaWiki\Html\Html; |
31 | use MediaWiki\Linker\Linker; |
32 | use MediaWiki\Linker\LinkRenderer; |
33 | use MediaWiki\Title\Title; |
34 | use UnexpectedValueException; |
35 | use Wikimedia\Rdbms\FakeResultWrapper; |
36 | use Wikimedia\Rdbms\IConnectionProvider; |
37 | |
38 | class ProtectedPagesPager extends TablePager { |
39 | |
40 | /** @var string */ |
41 | private $type; |
42 | /** @var string */ |
43 | private $level; |
44 | /** @var int|null */ |
45 | private $namespace; |
46 | /** @var string */ |
47 | private $sizetype; |
48 | /** @var int */ |
49 | private $size; |
50 | /** @var bool */ |
51 | private $indefonly; |
52 | /** @var bool */ |
53 | private $cascadeonly; |
54 | /** @var bool */ |
55 | private $noredirect; |
56 | |
57 | private CommentStore $commentStore; |
58 | private LinkBatchFactory $linkBatchFactory; |
59 | private RowCommentFormatter $rowCommentFormatter; |
60 | |
61 | /** @var string[] */ |
62 | private $formattedComments = []; |
63 | |
64 | /** |
65 | * @param IContextSource $context |
66 | * @param CommentStore $commentStore |
67 | * @param LinkBatchFactory $linkBatchFactory |
68 | * @param LinkRenderer $linkRenderer |
69 | * @param IConnectionProvider $dbProvider |
70 | * @param RowCommentFormatter $rowCommentFormatter |
71 | * @param string $type |
72 | * @param string $level |
73 | * @param int|null $namespace |
74 | * @param string $sizetype |
75 | * @param int|null $size |
76 | * @param bool $indefonly |
77 | * @param bool $cascadeonly |
78 | * @param bool $noredirect |
79 | */ |
80 | public function __construct( |
81 | IContextSource $context, |
82 | CommentStore $commentStore, |
83 | LinkBatchFactory $linkBatchFactory, |
84 | LinkRenderer $linkRenderer, |
85 | IConnectionProvider $dbProvider, |
86 | RowCommentFormatter $rowCommentFormatter, |
87 | $type, |
88 | $level, |
89 | $namespace, |
90 | $sizetype, |
91 | $size, |
92 | $indefonly, |
93 | $cascadeonly, |
94 | $noredirect |
95 | ) { |
96 | // Set database before parent constructor to avoid setting it there |
97 | $this->mDb = $dbProvider->getReplicaDatabase(); |
98 | parent::__construct( $context, $linkRenderer ); |
99 | $this->commentStore = $commentStore; |
100 | $this->linkBatchFactory = $linkBatchFactory; |
101 | $this->rowCommentFormatter = $rowCommentFormatter; |
102 | $this->type = $type ?: 'edit'; |
103 | $this->level = $level; |
104 | $this->namespace = $namespace; |
105 | $this->sizetype = $sizetype; |
106 | $this->size = intval( $size ); |
107 | $this->indefonly = (bool)$indefonly; |
108 | $this->cascadeonly = (bool)$cascadeonly; |
109 | $this->noredirect = (bool)$noredirect; |
110 | } |
111 | |
112 | public function preprocessResults( $result ) { |
113 | # Do a link batch query |
114 | $lb = $this->linkBatchFactory->newLinkBatch(); |
115 | $rowsWithComments = []; |
116 | |
117 | foreach ( $result as $row ) { |
118 | $lb->add( $row->page_namespace, $row->page_title ); |
119 | // for old protection rows, user and comment are missing |
120 | if ( $row->actor_name !== null ) { |
121 | $lb->add( NS_USER, $row->actor_name ); |
122 | $lb->add( NS_USER_TALK, $row->actor_name ); |
123 | } |
124 | if ( $row->log_timestamp !== null ) { |
125 | $rowsWithComments[] = $row; |
126 | } |
127 | } |
128 | |
129 | $lb->execute(); |
130 | |
131 | // Format the comments |
132 | $this->formattedComments = $this->rowCommentFormatter->formatRows( |
133 | new FakeResultWrapper( $rowsWithComments ), |
134 | 'log_comment', |
135 | null, |
136 | null, |
137 | 'pr_id' |
138 | ); |
139 | } |
140 | |
141 | protected function getFieldNames() { |
142 | static $headers = null; |
143 | |
144 | if ( $headers === null ) { |
145 | $headers = [ |
146 | 'log_timestamp' => 'protectedpages-timestamp', |
147 | 'pr_page' => 'protectedpages-page', |
148 | 'pr_expiry' => 'protectedpages-expiry', |
149 | 'actor_user' => 'protectedpages-performer', |
150 | 'pr_params' => 'protectedpages-params', |
151 | 'log_comment' => 'protectedpages-reason', |
152 | ]; |
153 | foreach ( $headers as $key => $val ) { |
154 | $headers[$key] = $this->msg( $val )->text(); |
155 | } |
156 | } |
157 | |
158 | return $headers; |
159 | } |
160 | |
161 | /** |
162 | * @param string $field |
163 | * @param string|null $value |
164 | * @return string HTML |
165 | */ |
166 | public function formatValue( $field, $value ) { |
167 | /** @var stdClass $row */ |
168 | $row = $this->mCurrentRow; |
169 | $linkRenderer = $this->getLinkRenderer(); |
170 | |
171 | switch ( $field ) { |
172 | case 'log_timestamp': |
173 | // when timestamp is null, this is a old protection row |
174 | if ( $value === null ) { |
175 | $formatted = Html::rawElement( |
176 | 'span', |
177 | [ 'class' => 'mw-protectedpages-unknown' ], |
178 | $this->msg( 'protectedpages-unknown-timestamp' )->escaped() |
179 | ); |
180 | } else { |
181 | $formatted = htmlspecialchars( $this->getLanguage()->userTimeAndDate( |
182 | $value, $this->getUser() ) ); |
183 | } |
184 | break; |
185 | |
186 | case 'pr_page': |
187 | $title = Title::makeTitleSafe( $row->page_namespace, $row->page_title ); |
188 | if ( !$title ) { |
189 | $formatted = Html::element( |
190 | 'span', |
191 | [ 'class' => 'mw-invalidtitle' ], |
192 | Linker::getInvalidTitleDescription( |
193 | $this->getContext(), |
194 | $row->page_namespace, |
195 | $row->page_title |
196 | ) |
197 | ); |
198 | } else { |
199 | $formatted = $linkRenderer->makeLink( $title ); |
200 | } |
201 | $formatted = Html::rawElement( 'bdi', [ |
202 | 'dir' => $this->getLanguage()->getDir() |
203 | ], $formatted ); |
204 | if ( $row->page_len !== null ) { |
205 | $formatted .= ' ' . Html::rawElement( |
206 | 'span', |
207 | [ 'class' => 'mw-protectedpages-length' ], |
208 | Linker::formatRevisionSize( $row->page_len ) |
209 | ); |
210 | } |
211 | break; |
212 | |
213 | case 'pr_expiry': |
214 | $formatted = htmlspecialchars( $this->getLanguage()->formatExpiry( |
215 | $value, /* User preference timezone */true, 'infinity', $this->getUser() ) ); |
216 | $title = Title::makeTitleSafe( $row->page_namespace, $row->page_title ); |
217 | if ( $title && $this->getAuthority()->isAllowed( 'protect' ) ) { |
218 | $changeProtection = $linkRenderer->makeKnownLink( |
219 | $title, |
220 | $this->msg( 'protect_change' )->text(), |
221 | [], |
222 | [ 'action' => 'unprotect' ] |
223 | ); |
224 | $formatted .= ' ' . Html::rawElement( |
225 | 'span', |
226 | [ 'class' => 'mw-protectedpages-actions' ], |
227 | $this->msg( 'parentheses' )->rawParams( $changeProtection )->escaped() |
228 | ); |
229 | } |
230 | break; |
231 | |
232 | case 'actor_user': |
233 | // when timestamp is null, this is a old protection row |
234 | if ( $row->log_timestamp === null ) { |
235 | $formatted = Html::rawElement( |
236 | 'span', |
237 | [ 'class' => 'mw-protectedpages-unknown' ], |
238 | $this->msg( 'protectedpages-unknown-performer' )->escaped() |
239 | ); |
240 | } else { |
241 | $username = $row->actor_name; |
242 | if ( LogEventsList::userCanBitfield( |
243 | $row->log_deleted, |
244 | LogPage::DELETED_USER, |
245 | $this->getAuthority() |
246 | ) ) { |
247 | $formatted = Linker::userLink( (int)$value, $username ) |
248 | . Linker::userToolLinks( (int)$value, $username ); |
249 | } else { |
250 | $formatted = $this->msg( 'rev-deleted-user' )->escaped(); |
251 | } |
252 | if ( LogEventsList::isDeleted( $row, LogPage::DELETED_USER ) ) { |
253 | $formatted = '<span class="history-deleted">' . $formatted . '</span>'; |
254 | } |
255 | } |
256 | break; |
257 | |
258 | case 'pr_params': |
259 | $params = []; |
260 | // Messages: restriction-level-sysop, restriction-level-autoconfirmed |
261 | $params[] = $this->msg( 'restriction-level-' . $row->pr_level )->escaped(); |
262 | if ( $row->pr_cascade ) { |
263 | $params[] = $this->msg( 'protect-summary-cascade' )->escaped(); |
264 | } |
265 | $formatted = $this->getLanguage()->commaList( $params ); |
266 | break; |
267 | |
268 | case 'log_comment': |
269 | // when timestamp is null, this is an old protection row |
270 | if ( $row->log_timestamp === null ) { |
271 | $formatted = Html::rawElement( |
272 | 'span', |
273 | [ 'class' => 'mw-protectedpages-unknown' ], |
274 | $this->msg( 'protectedpages-unknown-reason' )->escaped() |
275 | ); |
276 | } else { |
277 | if ( LogEventsList::userCanBitfield( |
278 | $row->log_deleted, |
279 | LogPage::DELETED_COMMENT, |
280 | $this->getAuthority() |
281 | ) ) { |
282 | $formatted = $this->formattedComments[$row->pr_id]; |
283 | } else { |
284 | $formatted = $this->msg( 'rev-deleted-comment' )->escaped(); |
285 | } |
286 | if ( LogEventsList::isDeleted( $row, LogPage::DELETED_COMMENT ) ) { |
287 | $formatted = '<span class="history-deleted">' . $formatted . '</span>'; |
288 | } |
289 | } |
290 | break; |
291 | |
292 | default: |
293 | throw new UnexpectedValueException( "Unknown field '$field'" ); |
294 | } |
295 | |
296 | return $formatted; |
297 | } |
298 | |
299 | public function getQueryInfo() { |
300 | $dbr = $this->getDatabase(); |
301 | $conds = [ |
302 | $dbr->expr( 'pr_expiry', '>', $dbr->timestamp() ) |
303 | ->or( 'pr_expiry', '=', null ), |
304 | 'page_id=pr_page', |
305 | $dbr->expr( 'pr_type', '=', $this->type ), |
306 | ]; |
307 | |
308 | if ( $this->sizetype == 'min' ) { |
309 | $conds[] = 'page_len>=' . $this->size; |
310 | } elseif ( $this->sizetype == 'max' ) { |
311 | $conds[] = 'page_len<=' . $this->size; |
312 | } |
313 | |
314 | if ( $this->indefonly ) { |
315 | $conds['pr_expiry'] = [ $dbr->getInfinity(), null ]; |
316 | } |
317 | if ( $this->cascadeonly ) { |
318 | $conds['pr_cascade'] = 1; |
319 | } |
320 | if ( $this->noredirect ) { |
321 | $conds['page_is_redirect'] = 0; |
322 | } |
323 | |
324 | if ( $this->level ) { |
325 | $conds[] = $dbr->expr( 'pr_level', '=', $this->level ); |
326 | } |
327 | if ( $this->namespace !== null ) { |
328 | $conds[] = $dbr->expr( 'page_namespace', '=', $this->namespace ); |
329 | } |
330 | |
331 | $commentQuery = $this->commentStore->getJoin( 'log_comment' ); |
332 | |
333 | return [ |
334 | 'tables' => [ |
335 | 'page', 'page_restrictions', 'log_search', |
336 | 'logparen' => [ 'logging', 'actor' ] + $commentQuery['tables'], |
337 | ], |
338 | 'fields' => [ |
339 | 'pr_id', |
340 | 'page_namespace', |
341 | 'page_title', |
342 | 'page_len', |
343 | 'pr_type', |
344 | 'pr_level', |
345 | 'pr_expiry', |
346 | 'pr_cascade', |
347 | 'log_timestamp', |
348 | 'log_deleted', |
349 | 'actor_name', |
350 | 'actor_user' |
351 | ] + $commentQuery['fields'], |
352 | 'conds' => $conds, |
353 | 'join_conds' => [ |
354 | 'log_search' => [ |
355 | 'LEFT JOIN', [ |
356 | 'ls_field' => 'pr_id', 'ls_value = ' . $dbr->buildStringCast( 'pr_id' ) |
357 | ] |
358 | ], |
359 | 'logparen' => [ |
360 | 'LEFT JOIN', [ |
361 | 'ls_log_id = log_id' |
362 | ] |
363 | ], |
364 | 'actor' => [ |
365 | 'JOIN', [ |
366 | 'actor_id=log_actor' |
367 | ] |
368 | ] |
369 | ] + $commentQuery['joins'] |
370 | ]; |
371 | } |
372 | |
373 | protected function getTableClass() { |
374 | return parent::getTableClass() . ' mw-protectedpages'; |
375 | } |
376 | |
377 | public function getIndexField() { |
378 | return 'pr_id'; |
379 | } |
380 | |
381 | public function getDefaultSort() { |
382 | return 'pr_id'; |
383 | } |
384 | |
385 | protected function isFieldSortable( $field ) { |
386 | // no index for sorting exists |
387 | return false; |
388 | } |
389 | } |
390 | |
391 | /** |
392 | * Retain the old class name for backwards compatibility. |
393 | * @deprecated since 1.41 |
394 | */ |
395 | class_alias( ProtectedPagesPager::class, 'ProtectedPagesPager' ); |