MediaWiki  master
DeletedContribsPager.php
Go to the documentation of this file.
1 <?php
32 
34 
39 
43  public $messages;
44 
48  public $target;
49 
53  public $namespace = '';
54 
58  public $mDb;
59 
63  protected $mNavigationBar;
64 
67  ) {
68  parent::__construct( $context, $linkRenderer );
69  $msgs = [ 'deletionlog', 'undeleteviewlink', 'diff' ];
70  foreach ( $msgs as $msg ) {
71  $this->messages[$msg] = $this->msg( $msg )->text();
72  }
73  $this->target = $target;
74  $this->namespace = $namespace;
75  $this->mDb = wfGetDB( DB_REPLICA, 'contributions' );
76  }
77 
78  public function getDefaultQuery() {
79  $query = parent::getDefaultQuery();
80  $query['target'] = $this->target;
81 
82  return $query;
83  }
84 
85  public function getQueryInfo() {
86  $userCond = [
87  // ->getJoin() below takes care of any joins needed
88  ActorMigration::newMigration()->getWhere(
89  wfGetDB( DB_REPLICA ), 'ar_user', User::newFromName( $this->target, false ), false
90  )['conds']
91  ];
92  $conds = array_merge( $userCond, $this->getNamespaceCond() );
93  $user = $this->getUser();
94  $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
95  // Paranoia: avoid brute force searches (T19792)
96  if ( !$permissionManager->userHasRight( $user, 'deletedhistory' ) ) {
97  $conds[] = $this->mDb->bitAnd( 'ar_deleted', RevisionRecord::DELETED_USER ) . ' = 0';
98  } elseif ( !$permissionManager->userHasAnyRight( $user, 'suppressrevision', 'viewsuppressed' ) ) {
99  $conds[] = $this->mDb->bitAnd( 'ar_deleted', RevisionRecord::SUPPRESSED_USER ) .
100  ' != ' . RevisionRecord::SUPPRESSED_USER;
101  }
102 
103  $commentQuery = CommentStore::getStore()->getJoin( 'ar_comment' );
104  $actorQuery = ActorMigration::newMigration()->getJoin( 'ar_user' );
105 
106  return [
107  'tables' => [ 'archive' ] + $commentQuery['tables'] + $actorQuery['tables'],
108  'fields' => [
109  'ar_rev_id', 'ar_id', 'ar_namespace', 'ar_title', 'ar_timestamp',
110  'ar_minor_edit', 'ar_deleted'
111  ] + $commentQuery['fields'] + $actorQuery['fields'],
112  'conds' => $conds,
113  'options' => [],
114  'join_conds' => $commentQuery['joins'] + $actorQuery['joins'],
115  ];
116  }
117 
127  public function reallyDoQuery( $offset, $limit, $order ) {
128  $data = [ parent::reallyDoQuery( $offset, $limit, $order ) ];
129 
130  // This hook will allow extensions to add in additional queries, nearly
131  // identical to ContribsPager::reallyDoQuery.
132  $this->getHookRunner()->onDeletedContribsPager__reallyDoQuery(
133  $data, $this, $offset, $limit, $order );
134 
135  $result = [];
136 
137  // loop all results and collect them in an array
138  foreach ( $data as $query ) {
139  foreach ( $query as $i => $row ) {
140  // use index column as key, allowing us to easily sort in PHP
141  $result[$row->{$this->getIndexField()} . "-$i"] = $row;
142  }
143  }
144 
145  // sort results
146  if ( $order === self::QUERY_ASCENDING ) {
147  ksort( $result );
148  } else {
149  krsort( $result );
150  }
151 
152  // enforce limit
153  $result = array_slice( $result, 0, $limit );
154 
155  // get rid of array keys
156  $result = array_values( $result );
157 
158  return new FakeResultWrapper( $result );
159  }
160 
161  public function getIndexField() {
162  return 'ar_timestamp';
163  }
164 
168  public function getTarget() {
169  return $this->target;
170  }
171 
175  public function getNamespace() {
176  return $this->namespace;
177  }
178 
179  protected function getStartBody() {
180  return "<ul>\n";
181  }
182 
183  protected function getEndBody() {
184  return "</ul>\n";
185  }
186 
187  public function getNavigationBar() {
188  if ( isset( $this->mNavigationBar ) ) {
189  return $this->mNavigationBar;
190  }
191 
192  $linkTexts = [
193  'prev' => $this->msg( 'pager-newer-n' )->numParams( $this->mLimit )->escaped(),
194  'next' => $this->msg( 'pager-older-n' )->numParams( $this->mLimit )->escaped(),
195  'first' => $this->msg( 'histlast' )->escaped(),
196  'last' => $this->msg( 'histfirst' )->escaped()
197  ];
198 
199  $pagingLinks = $this->getPagingLinks( $linkTexts );
200  $limitLinks = $this->getLimitLinks();
201  $lang = $this->getLanguage();
202  $limits = $lang->pipeList( $limitLinks );
203 
204  $firstLast = $lang->pipeList( [ $pagingLinks['first'], $pagingLinks['last'] ] );
205  $firstLast = $this->msg( 'parentheses' )->rawParams( $firstLast )->escaped();
206  $prevNext = $this->msg( 'viewprevnext' )
207  ->rawParams(
208  $pagingLinks['prev'],
209  $pagingLinks['next'],
210  $limits
211  )->escaped();
212  $separator = $this->msg( 'word-separator' )->escaped();
213  $this->mNavigationBar = $firstLast . $separator . $prevNext;
214 
215  return $this->mNavigationBar;
216  }
217 
218  private function getNamespaceCond() {
219  if ( $this->namespace !== '' ) {
220  return [ 'ar_namespace' => (int)$this->namespace ];
221  } else {
222  return [];
223  }
224  }
225 
233  public function formatRow( $row ) {
234  $ret = '';
235  $classes = [];
236  $attribs = [];
237 
238  $revFactory = MediaWikiServices::getInstance()->getRevisionFactory();
239 
240  /*
241  * There may be more than just revision rows. To make sure that we'll only be processing
242  * revisions here, let's _try_ to build a revision out of our row (without displaying
243  * notices though) and then trying to grab data from the built object. If we succeed,
244  * we're definitely dealing with revision data and we may proceed, if not, we'll leave it
245  * to extensions to subscribe to the hook to parse the row.
246  */
247  Wikimedia\suppressWarnings();
248  try {
249  $revRecord = $revFactory->newRevisionFromArchiveRow( $row );
250  $validRevision = (bool)$revRecord->getId();
251  } catch ( Exception $e ) {
252  $validRevision = false;
253  }
254  Wikimedia\restoreWarnings();
255 
256  if ( $validRevision ) {
257  $attribs['data-mw-revid'] = $revRecord->getId();
258  $ret = $this->formatRevisionRow( $row );
259  }
260 
261  // Let extensions add data
262  $this->getHookRunner()->onDeletedContributionsLineEnding(
263  $this, $ret, $row, $classes, $attribs );
264  $attribs = array_filter( $attribs,
265  [ Sanitizer::class, 'isReservedDataAttribute' ],
266  ARRAY_FILTER_USE_KEY
267  );
268 
269  if ( $classes === [] && $attribs === [] && $ret === '' ) {
270  wfDebug( "Dropping Special:DeletedContribution row that could not be formatted" );
271  $ret = "<!-- Could not format Special:DeletedContribution row. -->\n";
272  } else {
273  $attribs['class'] = $classes;
274  $ret = Html::rawElement( 'li', $attribs, $ret ) . "\n";
275  }
276 
277  return $ret;
278  }
279 
292  private function formatRevisionRow( $row ) {
293  $page = Title::makeTitle( $row->ar_namespace, $row->ar_title );
294 
295  $linkRenderer = $this->getLinkRenderer();
296 
297  $revRecord = MediaWikiServices::getInstance()
298  ->getRevisionFactory()
299  ->newRevisionFromArchiveRow(
300  $row,
301  RevisionFactory::READ_NORMAL,
302  $page
303  );
304 
305  $undelete = SpecialPage::getTitleFor( 'Undelete' );
306 
307  $logs = SpecialPage::getTitleFor( 'Log' );
308  $dellog = $linkRenderer->makeKnownLink(
309  $logs,
310  $this->messages['deletionlog'],
311  [],
312  [
313  'type' => 'delete',
314  'page' => $page->getPrefixedText()
315  ]
316  );
317 
318  $reviewlink = $linkRenderer->makeKnownLink(
319  SpecialPage::getTitleFor( 'Undelete', $page->getPrefixedDBkey() ),
320  $this->messages['undeleteviewlink']
321  );
322 
323  $user = $this->getUser();
324  $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
325 
326  if ( $permissionManager->userHasRight( $user, 'deletedtext' ) ) {
327  $last = $linkRenderer->makeKnownLink(
328  $undelete,
329  $this->messages['diff'],
330  [],
331  [
332  'target' => $page->getPrefixedText(),
333  'timestamp' => $revRecord->getTimestamp(),
334  'diff' => 'prev'
335  ]
336  );
337  } else {
338  $last = htmlspecialchars( $this->messages['diff'] );
339  }
340 
341  $comment = Linker::revComment( $revRecord );
342  $date = $this->getLanguage()->userTimeAndDate( $revRecord->getTimestamp(), $user );
343 
344  if ( !$permissionManager->userHasRight( $user, 'undelete' ) ||
345  !RevisionRecord::userCanBitfield(
346  $revRecord->getVisibility(),
347  RevisionRecord::DELETED_TEXT,
348  $user
349  )
350  ) {
351  $link = htmlspecialchars( $date ); // unusable link
352  } else {
353  $link = $linkRenderer->makeKnownLink(
354  $undelete,
355  $date,
356  [ 'class' => 'mw-changeslist-date' ],
357  [
358  'target' => $page->getPrefixedText(),
359  'timestamp' => $revRecord->getTimestamp()
360  ]
361  );
362  }
363  // Style deleted items
364  if ( $revRecord->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
365  $link = '<span class="history-deleted">' . $link . '</span>';
366  }
367 
368  $pagelink = $linkRenderer->makeLink(
369  $page,
370  null,
371  [ 'class' => 'mw-changeslist-title' ]
372  );
373 
374  if ( $revRecord->isMinor() ) {
375  $mflag = ChangesList::flag( 'minor' );
376  } else {
377  $mflag = '';
378  }
379 
380  // Revision delete link
381  $del = Linker::getRevDeleteLink( $user, $revRecord, $page );
382  if ( $del ) {
383  $del .= ' ';
384  }
385 
386  $tools = Html::rawElement(
387  'span',
388  [ 'class' => 'mw-deletedcontribs-tools' ],
389  $this->msg( 'parentheses' )->rawParams( $this->getLanguage()->pipeList(
390  [ $last, $dellog, $reviewlink ] ) )->escaped()
391  );
392 
393  $separator = '<span class="mw-changeslist-separator">. .</span>';
394  $ret = "{$del}{$link} {$tools} {$separator} {$mflag} {$pagelink} {$comment}";
395 
396  # Denote if username is redacted for this edit
397  if ( $revRecord->isDeleted( RevisionRecord::DELETED_USER ) ) {
398  $ret .= " <strong>" . $this->msg( 'rev-deleted-user-contribs' )->escaped() . "</strong>";
399  }
400 
401  return $ret;
402  }
403 
409  public function getDatabase() {
410  return $this->mDb;
411  }
412 }
ContextSource\$context
IContextSource $context
Definition: ContextSource.php:34
DeletedContribsPager\$namespace
string int $namespace
A single namespace number, or an empty string for all namespaces.
Definition: DeletedContribsPager.php:53
DeletedContribsPager\getDatabase
getDatabase()
Get the Database object in use.
Definition: DeletedContribsPager.php:409
Revision\RevisionRecord
Page revision base class.
Definition: RevisionRecord.php:46
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:154
$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:48
DeletedContribsPager
Definition: DeletedContribsPager.php:33
MediaWiki\Linker\LinkRenderer
Class that generates HTML links for pages.
Definition: LinkRenderer.php:41
getUser
getUser()
DeletedContribsPager\formatRow
formatRow( $row)
Generates each row in the contributions list.
Definition: DeletedContribsPager.php:233
DeletedContribsPager\$mNavigationBar
string $mNavigationBar
Navigation bar with paging links.
Definition: DeletedContribsPager.php:63
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:1605
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:2195
User\newFromName
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition: User.php:538
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:92
DeletedContribsPager\$messages
string[] $messages
Local cache for escaped messages.
Definition: DeletedContribsPager.php:43
ContextSource\getUser
getUser()
Stable to override.
Definition: ContextSource.php:131
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
ActorMigration\newMigration
static newMigration()
Static constructor.
Definition: ActorMigration.php:140
Wikimedia\Rdbms\IDatabase
Basic database interface for live and lazy-loaded relation database handles.
Definition: IDatabase.php:38
DeletedContribsPager\getNavigationBar
getNavigationBar()
Definition: DeletedContribsPager.php:187
DeletedContribsPager\getNamespace
getNamespace()
Definition: DeletedContribsPager.php:175
Wikimedia\Rdbms\IResultWrapper
Result wrapper for grabbing data queried from an IDatabase object.
Definition: IResultWrapper.php:24
wfGetDB
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
Definition: GlobalFunctions.php:2467
DeletedContribsPager\__construct
__construct(IContextSource $context, $target, $namespace, LinkRenderer $linkRenderer)
Definition: DeletedContribsPager.php:65
DeletedContribsPager\getEndBody
getEndBody()
Hook into getBody() for the end of the list.
Definition: DeletedContribsPager.php:183
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:592
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:909
DeletedContribsPager\getStartBody
getStartBody()
Hook into getBody(), allows text to be inserted at the start.
Definition: DeletedContribsPager.php:179
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:127
ContextSource\msg
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
Definition: ContextSource.php:184
DeletedContribsPager\getDefaultQuery
getDefaultQuery()
Get an array of query parameters that should be put into self-links.
Definition: DeletedContribsPager.php:78
DeletedContribsPager\getTarget
getTarget()
Definition: DeletedContribsPager.php:168
DeletedContribsPager\$mDefaultDirection
bool $mDefaultDirection
Default direction for pager.
Definition: DeletedContribsPager.php:38
IContextSource
Interface for objects which can provide a MediaWiki context on request.
Definition: IContextSource.php:55
IndexPager\DIR_DESCENDING
const DIR_DESCENDING
Backwards-compatible constant for $mDefaultDirection field (do not change)
Definition: IndexPager.php:80
Html\rawElement
static rawElement( $element, $attribs=[], $contents='')
Returns an HTML element in a string.
Definition: Html.php:209
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:218
DeletedContribsPager\$mDb
IDatabase $mDb
Definition: DeletedContribsPager.php:58
CommentStore\getStore
static getStore()
Definition: CommentStore.php:120
DeletedContribsPager\formatRevisionRow
formatRevisionRow( $row)
Generates each row in the contributions list for archive entries.
Definition: DeletedContribsPager.php:292
DeletedContribsPager\getQueryInfo
getQueryInfo()
Provides all parameters needed for the main paged query.
Definition: DeletedContribsPager.php:85
DeletedContribsPager\getIndexField
getIndexField()
Returns the name of the index field.
Definition: DeletedContribsPager.php:161