MediaWiki 1.40.4
BlockListPager.php
Go to the documentation of this file.
1<?php
38use Wikimedia\IPUtils;
41
46
47 protected $conds;
48
54 protected $restrictions = [];
55
57 private $blockActionInfo;
58
60 private $blockRestrictionStore;
61
63 private $blockUtils;
64
66 private $commentStore;
67
69 private $linkBatchFactory;
70
72 private $rowCommentFormatter;
73
75 private $specialPageFactory;
76
78 private $formattedComments = [];
79
93 public function __construct(
94 IContextSource $context,
95 BlockActionInfo $blockActionInfo,
96 BlockRestrictionStore $blockRestrictionStore,
97 BlockUtils $blockUtils,
98 CommentStore $commentStore,
99 LinkBatchFactory $linkBatchFactory,
100 LinkRenderer $linkRenderer,
101 ILoadBalancer $loadBalancer,
102 RowCommentFormatter $rowCommentFormatter,
103 SpecialPageFactory $specialPageFactory,
104 $conds
105 ) {
106 // Set database before parent constructor to avoid setting it there with wfGetDB
107 $this->mDb = $loadBalancer->getConnectionRef( ILoadBalancer::DB_REPLICA );
108 parent::__construct( $context, $linkRenderer );
109 $this->blockActionInfo = $blockActionInfo;
110 $this->blockRestrictionStore = $blockRestrictionStore;
111 $this->blockUtils = $blockUtils;
112 $this->commentStore = $commentStore;
113 $this->linkBatchFactory = $linkBatchFactory;
114 $this->rowCommentFormatter = $rowCommentFormatter;
115 $this->specialPageFactory = $specialPageFactory;
116 $this->conds = $conds;
117 $this->mDefaultDirection = IndexPager::DIR_DESCENDING;
118 }
119
120 protected function getFieldNames() {
121 static $headers = null;
122
123 if ( $headers === null ) {
124 $headers = [
125 'ipb_timestamp' => 'blocklist-timestamp',
126 'ipb_target' => 'blocklist-target',
127 'ipb_expiry' => 'blocklist-expiry',
128 'ipb_by' => 'blocklist-by',
129 'ipb_params' => 'blocklist-params',
130 'ipb_reason' => 'blocklist-reason',
131 ];
132 foreach ( $headers as $key => $val ) {
133 $headers[$key] = $this->msg( $val )->text();
134 }
135 }
136
137 return $headers;
138 }
139
146 public function formatValue( $name, $value ) {
147 static $msg = null;
148 if ( $msg === null ) {
149 $keys = [
150 'anononlyblock',
151 'createaccountblock',
152 'noautoblockblock',
153 'emailblock',
154 'blocklist-nousertalk',
155 'unblocklink',
156 'change-blocklink',
157 'blocklist-editing',
158 'blocklist-editing-sitewide',
159 ];
160
161 foreach ( $keys as $key ) {
162 $msg[$key] = $this->msg( $key )->text();
163 }
164 }
165 '@phan-var string[] $msg';
166
168 $row = $this->mCurrentRow;
169
170 $language = $this->getLanguage();
171
172 $formatted = '';
173
174 $linkRenderer = $this->getLinkRenderer();
175
176 switch ( $name ) {
177 case 'ipb_timestamp':
178 $formatted = htmlspecialchars( $language->userTimeAndDate( $value, $this->getUser() ) );
179 break;
180
181 case 'ipb_target':
182 if ( $row->ipb_auto ) {
183 $formatted = $this->msg( 'autoblockid', $row->ipb_id )->parse();
184 } else {
185 [ $target, ] = $this->blockUtils->parseBlockTarget( $row->ipb_address );
186
187 if ( is_string( $target ) ) {
188 if ( IPUtils::isValidRange( $target ) ) {
189 $target = User::newFromName( $target, false );
190 } else {
191 $formatted = $target;
192 }
193 }
194
195 if ( $target instanceof UserIdentity ) {
196 $formatted = Linker::userLink( $target->getId(), $target->getName() );
197 $formatted .= Linker::userToolLinks(
198 $target->getId(),
199 $target->getName(),
200 false,
201 Linker::TOOL_LINKS_NOBLOCK
202 );
203 }
204 }
205 break;
206
207 case 'ipb_expiry':
208 $formatted = htmlspecialchars( $language->formatExpiry(
209 $value,
210 /* User preference timezone */true,
211 'infinity',
212 $this->getUser()
213 ) );
214 if ( $this->getAuthority()->isAllowed( 'block' ) ) {
215 $links = [];
216 if ( $row->ipb_auto ) {
217 $links[] = $linkRenderer->makeKnownLink(
218 $this->specialPageFactory->getTitleForAlias( 'Unblock' ),
219 $msg['unblocklink'],
220 [],
221 [ 'wpTarget' => "#{$row->ipb_id}" ]
222 );
223 } else {
224 $links[] = $linkRenderer->makeKnownLink(
225 $this->specialPageFactory->getTitleForAlias( 'Unblock/' . $row->ipb_address ),
226 $msg['unblocklink']
227 );
228 $links[] = $linkRenderer->makeKnownLink(
229 $this->specialPageFactory->getTitleForAlias( 'Block/' . $row->ipb_address ),
230 $msg['change-blocklink']
231 );
232 }
233 $formatted .= ' ' . Html::rawElement(
234 'span',
235 [ 'class' => 'mw-blocklist-actions' ],
236 $this->msg( 'parentheses' )->rawParams(
237 $language->pipeList( $links ) )->escaped()
238 );
239 }
240 if ( $value !== 'infinity' ) {
241 $timestamp = new MWTimestamp( $value );
242 $formatted .= '<br />' . $this->msg(
243 'ipb-blocklist-duration-left',
244 $language->formatDuration(
245 (int)$timestamp->getTimestamp( TS_UNIX ) - MWTimestamp::time(),
246 // reasonable output
247 [
248 'minutes',
249 'hours',
250 'days',
251 'years',
252 ]
253 )
254 )->escaped();
255 }
256 break;
257
258 case 'ipb_by':
259 $formatted = Linker::userLink( (int)$value, $row->ipb_by_text );
260 $formatted .= Linker::userToolLinks( (int)$value, $row->ipb_by_text );
261 break;
262
263 case 'ipb_reason':
264 $formatted = $this->formattedComments[$this->getResultOffset()];
265 break;
266
267 case 'ipb_params':
268 $properties = [];
269
270 if ( $row->ipb_sitewide ) {
271 $properties[] = htmlspecialchars( $msg['blocklist-editing-sitewide'] );
272 }
273
274 if ( !$row->ipb_sitewide && $this->restrictions ) {
275 $list = $this->getRestrictionListHTML( $row );
276 if ( $list ) {
277 $properties[] = htmlspecialchars( $msg['blocklist-editing'] ) . $list;
278 }
279 }
280
281 if ( $row->ipb_anon_only ) {
282 $properties[] = htmlspecialchars( $msg['anononlyblock'] );
283 }
284 if ( $row->ipb_create_account ) {
285 $properties[] = htmlspecialchars( $msg['createaccountblock'] );
286 }
287 if ( $row->ipb_user && !$row->ipb_enable_autoblock ) {
288 $properties[] = htmlspecialchars( $msg['noautoblockblock'] );
289 }
290
291 if ( $row->ipb_block_email ) {
292 $properties[] = htmlspecialchars( $msg['emailblock'] );
293 }
294
295 if ( !$row->ipb_allow_usertalk ) {
296 $properties[] = htmlspecialchars( $msg['blocklist-nousertalk'] );
297 }
298
299 $formatted = Html::rawElement(
300 'ul',
301 [],
302 implode( '', array_map( static function ( $prop ) {
303 return Html::rawElement(
304 'li',
305 [],
306 $prop
307 );
308 }, $properties ) )
309 );
310 break;
311
312 default:
313 $formatted = "Unable to format $name";
314 break;
315 }
316
317 return $formatted;
318 }
319
327 private function getRestrictionListHTML( stdClass $row ) {
328 $items = [];
329 $linkRenderer = $this->getLinkRenderer();
330
331 foreach ( $this->restrictions as $restriction ) {
332 if ( $restriction->getBlockId() !== (int)$row->ipb_id ) {
333 continue;
334 }
335
336 switch ( $restriction->getType() ) {
337 case PageRestriction::TYPE:
338 '@phan-var PageRestriction $restriction';
339 if ( $restriction->getTitle() ) {
340 $items[$restriction->getType()][] = Html::rawElement(
341 'li',
342 [],
343 $linkRenderer->makeLink( $restriction->getTitle() )
344 );
345 }
346 break;
347 case NamespaceRestriction::TYPE:
348 $text = $restriction->getValue() === NS_MAIN
349 ? $this->msg( 'blanknamespace' )->text()
350 : $this->getLanguage()->getFormattedNsText(
351 $restriction->getValue()
352 );
353 if ( $text ) {
354 $items[$restriction->getType()][] = Html::rawElement(
355 'li',
356 [],
357 $linkRenderer->makeLink(
358 $this->specialPageFactory->getTitleForAlias( 'Allpages' ),
359 $text,
360 [],
361 [
362 'namespace' => $restriction->getValue()
363 ]
364 )
365 );
366 }
367 break;
368 case ActionRestriction::TYPE:
369 $actionName = $this->blockActionInfo->getActionFromId( $restriction->getValue() );
370 $enablePartialActionBlocks =
371 $this->getConfig()->get( MainConfigNames::EnablePartialActionBlocks );
372 if ( $actionName && $enablePartialActionBlocks ) {
373 $items[$restriction->getType()][] = Html::rawElement(
374 'li',
375 [],
376 $this->msg( 'ipb-action-' .
377 $this->blockActionInfo->getActionFromId( $restriction->getValue() ) )->escaped()
378 );
379 }
380 break;
381 }
382 }
383
384 if ( empty( $items ) ) {
385 return '';
386 }
387
388 $sets = [];
389 foreach ( $items as $key => $value ) {
390 $sets[] = Html::rawElement(
391 'li',
392 [],
393 $this->msg( 'blocklist-editing-' . $key ) . Html::rawElement(
394 'ul',
395 [],
396 implode( '', $value )
397 )
398 );
399 }
400
401 return Html::rawElement(
402 'ul',
403 [],
404 implode( '', $sets )
405 );
406 }
407
408 public function getQueryInfo() {
409 $commentQuery = $this->commentStore->getJoin( 'ipb_reason' );
410
411 $info = [
412 'tables' => array_merge(
413 [ 'ipblocks', 'ipblocks_by_actor' => 'actor' ],
414 $commentQuery['tables']
415 ),
416 'fields' => [
417 'ipb_id',
418 'ipb_address',
419 'ipb_user',
420 'ipb_by' => 'ipblocks_by_actor.actor_user',
421 'ipb_by_text' => 'ipblocks_by_actor.actor_name',
422 'ipb_timestamp',
423 'ipb_auto',
424 'ipb_anon_only',
425 'ipb_create_account',
426 'ipb_enable_autoblock',
427 'ipb_expiry',
428 'ipb_range_start',
429 'ipb_range_end',
430 'ipb_deleted',
431 'ipb_block_email',
432 'ipb_allow_usertalk',
433 'ipb_sitewide',
434 ] + $commentQuery['fields'],
435 'conds' => $this->conds,
436 'join_conds' => [
437 'ipblocks_by_actor' => [ 'JOIN', 'actor_id=ipb_by_actor' ]
438 ] + $commentQuery['joins']
439 ];
440
441 # Filter out any expired blocks
442 $db = $this->getDatabase();
443 $info['conds'][] = 'ipb_expiry > ' . $db->addQuotes( $db->timestamp() );
444
445 # Is the user allowed to see hidden blocks?
446 if ( !$this->getAuthority()->isAllowed( 'hideuser' ) ) {
447 $info['conds']['ipb_deleted'] = 0;
448 }
449
450 return $info;
451 }
452
458 public function getTotalAutoblocks() {
459 $dbr = $this->getDatabase();
460 return (int)$dbr->selectField( 'ipblocks', 'COUNT(*)',
461 [
462 'ipb_auto' => '1',
463 'ipb_expiry >= ' . $dbr->addQuotes( $dbr->timestamp() ),
464 ],
465 __METHOD__
466 );
467 }
468
469 protected function getTableClass() {
470 return parent::getTableClass() . ' mw-blocklist';
471 }
472
473 public function getIndexField() {
474 return [ [ 'ipb_timestamp', 'ipb_id' ] ];
475 }
476
477 public function getDefaultSort() {
478 return '';
479 }
480
481 protected function isFieldSortable( $name ) {
482 return false;
483 }
484
489 public function preprocessResults( $result ) {
490 // Do a link batch query
491 $lb = $this->linkBatchFactory->newLinkBatch();
492 $lb->setCaller( __METHOD__ );
493
494 $partialBlocks = [];
495 foreach ( $result as $row ) {
496 $lb->add( NS_USER, $row->ipb_address );
497 $lb->add( NS_USER_TALK, $row->ipb_address );
498
499 if ( $row->ipb_by ?? null ) {
500 $lb->add( NS_USER, $row->ipb_by_text );
501 $lb->add( NS_USER_TALK, $row->ipb_by_text );
502 }
503
504 if ( !$row->ipb_sitewide ) {
505 $partialBlocks[] = $row->ipb_id;
506 }
507 }
508
509 if ( $partialBlocks ) {
510 // Mutations to the $row object are not persisted. The restrictions will
511 // need be stored in a separate store.
512 $this->restrictions = $this->blockRestrictionStore->loadByBlockId( $partialBlocks );
513
514 foreach ( $this->restrictions as $restriction ) {
515 if ( $restriction->getType() === PageRestriction::TYPE ) {
516 '@phan-var PageRestriction $restriction';
517 $title = $restriction->getTitle();
518 if ( $title ) {
519 $lb->addObj( $title );
520 }
521 }
522 }
523 }
524
525 $lb->execute();
526
527 // Format comments
528 // The keys of formattedComments will be the corresponding offset into $result
529 $this->formattedComments = $this->rowCommentFormatter->formatRows( $result, 'ipb_reason' );
530 }
531
532}
const NS_USER
Definition Defines.php:66
const NS_MAIN
Definition Defines.php:64
const NS_USER_TALK
Definition Defines.php:67
getQueryInfo()
Provides all parameters needed for the main paged query.
isFieldSortable( $name)
Return true if the named field should be sortable by the UI, false otherwise.
getTableClass()
TablePager relies on mw-datatable for styling, see T214208.
getFieldNames()
An array mapping database field names to a textual description of the field name, for use in the tabl...
getIndexField()
Returns the name of the index field.If the pager supports multiple orders, it may return an array of ...
preprocessResults( $result)
Do a LinkBatch query to minimise database load when generating all these links.
__construct(IContextSource $context, BlockActionInfo $blockActionInfo, BlockRestrictionStore $blockRestrictionStore, BlockUtils $blockUtils, CommentStore $commentStore, LinkBatchFactory $linkBatchFactory, LinkRenderer $linkRenderer, ILoadBalancer $loadBalancer, RowCommentFormatter $rowCommentFormatter, SpecialPageFactory $specialPageFactory, $conds)
formatValue( $name, $value)
Restriction[] $restrictions
Array of restrictions.
getTotalAutoblocks()
Get total number of autoblocks at any given time.
getDefaultSort()
The database field name used as a default sort order.
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
getDatabase()
Get the Database object in use.
const DIR_DESCENDING
Backwards-compatible constant for $mDefaultDirection field (do not change)
Library for creating and parsing MW-style timestamps.
Defines the actions that can be blocked by a partial block.
Backend class for blocking utils.
Restriction for partial blocks of actions.
This is basically a CommentFormatter with a CommentStore dependency, allowing it to retrieve comment ...
Handle database storage of comments such as edit summaries and log reasons.
This class is a collection of static functions that serve two purposes:
Definition Html.php:55
Class that generates HTML for internal links.
Some internal bits split of from Skin.php.
Definition Linker.php:67
A class containing constants representing the names of configuration variables.
Factory for handling the special page list and generating SpecialPage objects.
Table-based display with a user-selectable sort order.
stdClass $mCurrentRow
static newFromName( $name, $validate='valid')
Definition User.php:592
Interface for objects which can provide a MediaWiki context on request.
Interface for objects representing user identity.
This class is a delegate to ILBFactory for a given database cluster.
getConnectionRef( $i, $groups=[], $domain=false, $flags=0)
Result wrapper for grabbing data queried from an IDatabase object.