MediaWiki  master
DeletedContribsPager.php
Go to the documentation of this file.
1 <?php
31 
36 
41 
45  public $messages;
46 
50  public $target;
51 
55  public $namespace = '';
56 
60  protected $mNavigationBar;
61 
63  private $hookRunner;
64 
67 
69  private $commentStore;
70 
72  private $actorMigration;
73 
76 
89  public function __construct(
91  $target,
92  $namespace,
94  HookContainer $hookContainer,
96  ILoadBalancer $loadBalancer,
100  ) {
101  // Set database before parent constructor to avoid setting it there with wfGetDB
102  $this->mDb = $loadBalancer->getConnectionRef( ILoadBalancer::DB_REPLICA, 'contributions' );
103  parent::__construct( $context, $linkRenderer );
104  $msgs = [ 'deletionlog', 'undeleteviewlink', 'diff' ];
105  foreach ( $msgs as $msg ) {
106  $this->messages[$msg] = $this->msg( $msg )->text();
107  }
108  $this->target = $target;
109  $this->namespace = $namespace;
110  $this->hookRunner = new HookRunner( $hookContainer );
111  $this->permissionManager = $permissionManager;
112  $this->commentStore = $commentStore;
113  $this->actorMigration = $actorMigration;
114  $this->revisionFactory = $revisionFactory;
115  }
116 
117  public function getDefaultQuery() {
118  $query = parent::getDefaultQuery();
119  $query['target'] = $this->target;
120 
121  return $query;
122  }
123 
124  public function getQueryInfo() {
125  $dbr = $this->getDatabase();
126  $userCond = [
127  // ->getJoin() below takes care of any joins needed
128  $this->actorMigration->getWhere(
129  $dbr, 'ar_user', User::newFromName( $this->target, false ), false
130  )['conds']
131  ];
132  $conds = array_merge( $userCond, $this->getNamespaceCond() );
133  $user = $this->getUser();
134  // Paranoia: avoid brute force searches (T19792)
135  if ( !$this->permissionManager->userHasRight( $user, 'deletedhistory' ) ) {
136  $conds[] = $dbr->bitAnd( 'ar_deleted', RevisionRecord::DELETED_USER ) . ' = 0';
137  } elseif ( !$this->permissionManager->userHasAnyRight( $user, 'suppressrevision', 'viewsuppressed' ) ) {
138  $conds[] = $dbr->bitAnd( 'ar_deleted', RevisionRecord::SUPPRESSED_USER ) .
139  ' != ' . RevisionRecord::SUPPRESSED_USER;
140  }
141 
142  $commentQuery = $this->commentStore->getJoin( 'ar_comment' );
143  $actorQuery = $this->actorMigration->getJoin( 'ar_user' );
144 
145  return [
146  'tables' => [ 'archive' ] + $commentQuery['tables'] + $actorQuery['tables'],
147  'fields' => [
148  'ar_rev_id', 'ar_id', 'ar_namespace', 'ar_title', 'ar_timestamp',
149  'ar_minor_edit', 'ar_deleted'
150  ] + $commentQuery['fields'] + $actorQuery['fields'],
151  'conds' => $conds,
152  'options' => [],
153  'join_conds' => $commentQuery['joins'] + $actorQuery['joins'],
154  ];
155  }
156 
166  public function reallyDoQuery( $offset, $limit, $order ) {
167  $data = [ parent::reallyDoQuery( $offset, $limit, $order ) ];
168 
169  // This hook will allow extensions to add in additional queries, nearly
170  // identical to ContribsPager::reallyDoQuery.
171  $this->hookRunner->onDeletedContribsPager__reallyDoQuery(
172  $data, $this, $offset, $limit, $order );
173 
174  $result = [];
175 
176  // loop all results and collect them in an array
177  foreach ( $data as $query ) {
178  foreach ( $query as $i => $row ) {
179  // use index column as key, allowing us to easily sort in PHP
180  $result[$row->{$this->getIndexField()} . "-$i"] = $row;
181  }
182  }
183 
184  // sort results
185  if ( $order === self::QUERY_ASCENDING ) {
186  ksort( $result );
187  } else {
188  krsort( $result );
189  }
190 
191  // enforce limit
192  $result = array_slice( $result, 0, $limit );
193 
194  // get rid of array keys
195  $result = array_values( $result );
196 
197  return new FakeResultWrapper( $result );
198  }
199 
200  public function getIndexField() {
201  return 'ar_timestamp';
202  }
203 
207  public function getTarget() {
208  return $this->target;
209  }
210 
214  public function getNamespace() {
215  return $this->namespace;
216  }
217 
218  protected function getStartBody() {
219  return "<ul>\n";
220  }
221 
222  protected function getEndBody() {
223  return "</ul>\n";
224  }
225 
226  public function getNavigationBar() {
227  if ( isset( $this->mNavigationBar ) ) {
228  return $this->mNavigationBar;
229  }
230 
231  $linkTexts = [
232  'prev' => $this->msg( 'pager-newer-n' )->numParams( $this->mLimit )->escaped(),
233  'next' => $this->msg( 'pager-older-n' )->numParams( $this->mLimit )->escaped(),
234  'first' => $this->msg( 'histlast' )->escaped(),
235  'last' => $this->msg( 'histfirst' )->escaped()
236  ];
237 
238  $pagingLinks = $this->getPagingLinks( $linkTexts );
239  $limitLinks = $this->getLimitLinks();
240  $lang = $this->getLanguage();
241  $limits = $lang->pipeList( $limitLinks );
242 
243  $firstLast = $lang->pipeList( [ $pagingLinks['first'], $pagingLinks['last'] ] );
244  $firstLast = $this->msg( 'parentheses' )->rawParams( $firstLast )->escaped();
245  $prevNext = $this->msg( 'viewprevnext' )
246  ->rawParams(
247  $pagingLinks['prev'],
248  $pagingLinks['next'],
249  $limits
250  )->escaped();
251  $separator = $this->msg( 'word-separator' )->escaped();
252  $this->mNavigationBar = $firstLast . $separator . $prevNext;
253 
254  return $this->mNavigationBar;
255  }
256 
257  private function getNamespaceCond() {
258  if ( $this->namespace !== '' ) {
259  return [ 'ar_namespace' => (int)$this->namespace ];
260  } else {
261  return [];
262  }
263  }
264 
272  public function formatRow( $row ) {
273  $ret = '';
274  $classes = [];
275  $attribs = [];
276 
277  /*
278  * There may be more than just revision rows. To make sure that we'll only be processing
279  * revisions here, let's _try_ to build a revision out of our row (without displaying
280  * notices though) and then trying to grab data from the built object. If we succeed,
281  * we're definitely dealing with revision data and we may proceed, if not, we'll leave it
282  * to extensions to subscribe to the hook to parse the row.
283  */
284  Wikimedia\suppressWarnings();
285  try {
286  $revRecord = $this->revisionFactory->newRevisionFromArchiveRow( $row );
287  $validRevision = (bool)$revRecord->getId();
288  } catch ( Exception $e ) {
289  $validRevision = false;
290  }
291  Wikimedia\restoreWarnings();
292 
293  if ( $validRevision ) {
294  $attribs['data-mw-revid'] = $revRecord->getId();
295  $ret = $this->formatRevisionRow( $row );
296  }
297 
298  // Let extensions add data
299  $this->hookRunner->onDeletedContributionsLineEnding(
300  $this, $ret, $row, $classes, $attribs );
301  $attribs = array_filter( $attribs,
302  [ Sanitizer::class, 'isReservedDataAttribute' ],
303  ARRAY_FILTER_USE_KEY
304  );
305 
306  if ( $classes === [] && $attribs === [] && $ret === '' ) {
307  wfDebug( "Dropping Special:DeletedContribution row that could not be formatted" );
308  $ret = "<!-- Could not format Special:DeletedContribution row. -->\n";
309  } else {
310  $attribs['class'] = $classes;
311  $ret = Html::rawElement( 'li', $attribs, $ret ) . "\n";
312  }
313 
314  return $ret;
315  }
316 
329  private function formatRevisionRow( $row ) {
330  $page = Title::makeTitle( $row->ar_namespace, $row->ar_title );
331 
332  $linkRenderer = $this->getLinkRenderer();
333 
334  $revRecord = $this->revisionFactory->newRevisionFromArchiveRow(
335  $row,
336  RevisionFactory::READ_NORMAL,
337  $page
338  );
339 
340  $undelete = SpecialPage::getTitleFor( 'Undelete' );
341 
342  $logs = SpecialPage::getTitleFor( 'Log' );
343  $dellog = $linkRenderer->makeKnownLink(
344  $logs,
345  $this->messages['deletionlog'],
346  [],
347  [
348  'type' => 'delete',
349  'page' => $page->getPrefixedText()
350  ]
351  );
352 
353  $reviewlink = $linkRenderer->makeKnownLink(
354  SpecialPage::getTitleFor( 'Undelete', $page->getPrefixedDBkey() ),
355  $this->messages['undeleteviewlink']
356  );
357 
358  $user = $this->getUser();
359 
360  if ( $this->permissionManager->userHasRight( $user, 'deletedtext' ) ) {
361  $last = $linkRenderer->makeKnownLink(
362  $undelete,
363  $this->messages['diff'],
364  [],
365  [
366  'target' => $page->getPrefixedText(),
367  'timestamp' => $revRecord->getTimestamp(),
368  'diff' => 'prev'
369  ]
370  );
371  } else {
372  $last = htmlspecialchars( $this->messages['diff'] );
373  }
374 
375  $comment = Linker::revComment( $revRecord );
376  $date = $this->getLanguage()->userTimeAndDate( $revRecord->getTimestamp(), $user );
377 
378  if ( !$this->permissionManager->userHasRight( $user, 'undelete' ) ||
379  !RevisionRecord::userCanBitfield(
380  $revRecord->getVisibility(),
381  RevisionRecord::DELETED_TEXT,
382  $user
383  )
384  ) {
385  $link = htmlspecialchars( $date ); // unusable link
386  } else {
387  $link = $linkRenderer->makeKnownLink(
388  $undelete,
389  $date,
390  [ 'class' => 'mw-changeslist-date' ],
391  [
392  'target' => $page->getPrefixedText(),
393  'timestamp' => $revRecord->getTimestamp()
394  ]
395  );
396  }
397  // Style deleted items
398  if ( $revRecord->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
399  $link = '<span class="history-deleted">' . $link . '</span>';
400  }
401 
402  $pagelink = $linkRenderer->makeLink(
403  $page,
404  null,
405  [ 'class' => 'mw-changeslist-title' ]
406  );
407 
408  if ( $revRecord->isMinor() ) {
409  $mflag = ChangesList::flag( 'minor' );
410  } else {
411  $mflag = '';
412  }
413 
414  // Revision delete link
415  $del = Linker::getRevDeleteLink( $user, $revRecord, $page );
416  if ( $del ) {
417  $del .= ' ';
418  }
419 
420  $tools = Html::rawElement(
421  'span',
422  [ 'class' => 'mw-deletedcontribs-tools' ],
423  $this->msg( 'parentheses' )->rawParams( $this->getLanguage()->pipeList(
424  [ $last, $dellog, $reviewlink ] ) )->escaped()
425  );
426 
427  $separator = '<span class="mw-changeslist-separator">. .</span>';
428  $ret = "{$del}{$link} {$tools} {$separator} {$mflag} {$pagelink} {$comment}";
429 
430  # Denote if username is redacted for this edit
431  if ( $revRecord->isDeleted( RevisionRecord::DELETED_USER ) ) {
432  $ret .= " <strong>" . $this->msg( 'rev-deleted-user-contribs' )->escaped() . "</strong>";
433  }
434 
435  return $ret;
436  }
437 }
ContextSource\$context
IContextSource $context
Definition: ContextSource.php:38
DeletedContribsPager\$namespace
string int $namespace
A single namespace number, or an empty string for all namespaces.
Definition: DeletedContribsPager.php:55
DeletedContribsPager\$revisionFactory
RevisionFactory $revisionFactory
Definition: DeletedContribsPager.php:75
Revision\RevisionRecord
Page revision base class.
Definition: RevisionRecord.php:45
$lang
if(!isset( $args[0])) $lang
Definition: testCompression.php:37
DeletedContribsPager\$target
string $target
User name, or a string describing an IP address range.
Definition: DeletedContribsPager.php:50
DeletedContribsPager
Definition: DeletedContribsPager.php:35
MediaWiki\Linker\LinkRenderer
Class that generates HTML links for pages.
Definition: LinkRenderer.php:41
DeletedContribsPager\$permissionManager
PermissionManager $permissionManager
Definition: DeletedContribsPager.php:66
getUser
getUser()
DeletedContribsPager\formatRow
formatRow( $row)
Generates each row in the contributions list.
Definition: DeletedContribsPager.php:272
DeletedContribsPager\$mNavigationBar
string $mNavigationBar
Navigation bar with paging links.
Definition: DeletedContribsPager.php:60
IndexPager\getDatabase
getDatabase()
Get the Database object in use.
Definition: IndexPager.php:244
Linker\revComment
static revComment( $rev, $local=false, $isPublic=false, $useParentheses=true)
Wrap and format the given revision's comment block, if the current user is allowed to view it.
Definition: Linker.php:1604
Linker\getRevDeleteLink
static getRevDeleteLink(User $user, $rev, LinkTarget $title)
Get a revision-deletion link, or disabled link, or nothing, depending on user permissions & the setti...
Definition: Linker.php:2201
CommentStore
Handle database storage of comments such as edit summaries and log reasons.
Definition: CommentStore.php:42
User\newFromName
static newFromName( $name, $validate='valid')
Definition: User.php:558
Revision\RevisionFactory
Service for constructing revision objects.
Definition: RevisionFactory.php:38
IndexPager\$linkRenderer
LinkRenderer $linkRenderer
Definition: IndexPager.php:167
SpecialPage\getTitleFor
static getTitleFor( $name, $subpage=false, $fragment='')
Get a localised Title object for a specified special page name If you don't need a full Title object,...
Definition: SpecialPage.php:106
DeletedContribsPager\$messages
string[] $messages
Local cache for escaped messages.
Definition: DeletedContribsPager.php:45
ActorMigration
This class handles the logic for the actor table migration and should always be used in lieu of direc...
Definition: ActorMigration.php:41
ContextSource\getUser
getUser()
Stable to override.
Definition: ContextSource.php:135
Wikimedia\Rdbms\FakeResultWrapper
Overloads the relevant methods of the real ResultsWrapper so it doesn't go anywhere near an actual da...
Definition: FakeResultWrapper.php:11
Wikimedia\Rdbms\ILoadBalancer\getConnectionRef
getConnectionRef( $i, $groups=[], $domain=false, $flags=0)
Get a live database handle reference for a real or virtual (DB_MASTER/DB_REPLICA) server index.
$dbr
$dbr
Definition: testCompression.php:54
DeletedContribsPager\getNavigationBar
getNavigationBar()
Definition: DeletedContribsPager.php:226
DeletedContribsPager\$actorMigration
ActorMigration $actorMigration
Definition: DeletedContribsPager.php:72
DeletedContribsPager\getNamespace
getNamespace()
Definition: DeletedContribsPager.php:214
Wikimedia\Rdbms\IResultWrapper
Result wrapper for grabbing data queried from an IDatabase object.
Definition: IResultWrapper.php:24
DeletedContribsPager\getEndBody
getEndBody()
Hook into getBody() for the end of the list.
Definition: DeletedContribsPager.php:222
ChangesList\flag
static flag( $flag, IContextSource $context=null)
Make an "<abbr>" element for a given change flag.
Definition: ChangesList.php:269
Title\makeTitle
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:624
DB_REPLICA
const DB_REPLICA
Definition: defines.php:25
wfDebug
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
Definition: GlobalFunctions.php:914
DeletedContribsPager\getStartBody
getStartBody()
Hook into getBody(), allows text to be inserted at the start.
Definition: DeletedContribsPager.php:218
DeletedContribsPager\reallyDoQuery
reallyDoQuery( $offset, $limit, $order)
This method basically executes the exact same code as the parent class, though with a hook added,...
Definition: DeletedContribsPager.php:166
ContextSource\msg
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
Definition: ContextSource.php:195
MediaWiki\Permissions\PermissionManager
A service class for checking permissions To obtain an instance, use MediaWikiServices::getInstance()-...
Definition: PermissionManager.php:51
DeletedContribsPager\getDefaultQuery
getDefaultQuery()
Get an array of query parameters that should be put into self-links.
Definition: DeletedContribsPager.php:117
DeletedContribsPager\getTarget
getTarget()
Definition: DeletedContribsPager.php:207
DeletedContribsPager\$hookRunner
HookRunner $hookRunner
Definition: DeletedContribsPager.php:63
DeletedContribsPager\$mDefaultDirection
bool $mDefaultDirection
Default direction for pager.
Definition: DeletedContribsPager.php:40
IContextSource
Interface for objects which can provide a MediaWiki context on request.
Definition: IContextSource.php:57
IndexPager\DIR_DESCENDING
const DIR_DESCENDING
Backwards-compatible constant for $mDefaultDirection field (do not change)
Definition: IndexPager.php:80
DeletedContribsPager\$commentStore
CommentStore $commentStore
Definition: DeletedContribsPager.php:69
DeletedContribsPager\__construct
__construct(IContextSource $context, $target, $namespace, LinkRenderer $linkRenderer, HookContainer $hookContainer, PermissionManager $permissionManager, ILoadBalancer $loadBalancer, CommentStore $commentStore, ActorMigration $actorMigration, RevisionFactory $revisionFactory)
Definition: DeletedContribsPager.php:89
Html\rawElement
static rawElement( $element, $attribs=[], $contents='')
Returns an HTML element in a string.
Definition: Html.php:212
IndexPager
IndexPager is an efficient pager which uses a (roughly unique) index in the data set to implement pag...
Definition: IndexPager.php:74
DeletedContribsPager\getNamespaceCond
getNamespaceCond()
Definition: DeletedContribsPager.php:257
MediaWiki\HookContainer\HookContainer
HookContainer class.
Definition: HookContainer.php:45
MediaWiki\HookContainer\HookRunner
This class provides an implementation of the core hook interfaces, forwarding hook calls to HookConta...
Definition: HookRunner.php:571
DeletedContribsPager\formatRevisionRow
formatRevisionRow( $row)
Generates each row in the contributions list for archive entries.
Definition: DeletedContribsPager.php:329
DeletedContribsPager\getQueryInfo
getQueryInfo()
Provides all parameters needed for the main paged query.
Definition: DeletedContribsPager.php:124
Wikimedia\Rdbms\ILoadBalancer
Database cluster connection, tracking, load balancing, and transaction manager interface.
Definition: ILoadBalancer.php:81
DeletedContribsPager\getIndexField
getIndexField()
Returns the name of the index field.
Definition: DeletedContribsPager.php:200