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