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