MediaWiki  master
BlockListPager.php
Go to the documentation of this file.
1 <?php
38 use Wikimedia\IPUtils;
41 
45 class BlockListPager extends TablePager {
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.
Definition: IndexPager.php:248
const DIR_DESCENDING
Backwards-compatible constant for $mDefaultDirection field (do not change)
Definition: IndexPager.php:80
Library for creating and parsing MW-style timestamps.
Definition: MWTimestamp.php:40
Defines the actions that can be blocked by a partial block.
Backend class for blocking utils.
Definition: BlockUtils.php:46
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:65
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.
Definition: TablePager.php:30
stdClass $mCurrentRow
Definition: TablePager.php:35
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.
const DB_REPLICA
Definition: defines.php:26