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  Hooks::run(
133  'DeletedContribsPager::reallyDoQuery',
134  [ &$data, $this, $offset, $limit, $order ]
135  );
136 
137  $result = [];
138 
139  // loop all results and collect them in an array
140  foreach ( $data as $query ) {
141  foreach ( $query as $i => $row ) {
142  // use index column as key, allowing us to easily sort in PHP
143  $result[$row->{$this->getIndexField()} . "-$i"] = $row;
144  }
145  }
146 
147  // sort results
148  if ( $order === self::QUERY_ASCENDING ) {
149  ksort( $result );
150  } else {
151  krsort( $result );
152  }
153 
154  // enforce limit
155  $result = array_slice( $result, 0, $limit );
156 
157  // get rid of array keys
158  $result = array_values( $result );
159 
160  return new FakeResultWrapper( $result );
161  }
162 
163  public function getIndexField() {
164  return 'ar_timestamp';
165  }
166 
170  public function getTarget() {
171  return $this->target;
172  }
173 
177  public function getNamespace() {
178  return $this->namespace;
179  }
180 
181  protected function getStartBody() {
182  return "<ul>\n";
183  }
184 
185  protected function getEndBody() {
186  return "</ul>\n";
187  }
188 
189  public function getNavigationBar() {
190  if ( isset( $this->mNavigationBar ) ) {
191  return $this->mNavigationBar;
192  }
193 
194  $linkTexts = [
195  'prev' => $this->msg( 'pager-newer-n' )->numParams( $this->mLimit )->escaped(),
196  'next' => $this->msg( 'pager-older-n' )->numParams( $this->mLimit )->escaped(),
197  'first' => $this->msg( 'histlast' )->escaped(),
198  'last' => $this->msg( 'histfirst' )->escaped()
199  ];
200 
201  $pagingLinks = $this->getPagingLinks( $linkTexts );
202  $limitLinks = $this->getLimitLinks();
203  $lang = $this->getLanguage();
204  $limits = $lang->pipeList( $limitLinks );
205 
206  $firstLast = $lang->pipeList( [ $pagingLinks['first'], $pagingLinks['last'] ] );
207  $firstLast = $this->msg( 'parentheses' )->rawParams( $firstLast )->escaped();
208  $prevNext = $this->msg( 'viewprevnext' )
209  ->rawParams(
210  $pagingLinks['prev'],
211  $pagingLinks['next'],
212  $limits
213  )->escaped();
214  $separator = $this->msg( 'word-separator' )->escaped();
215  $this->mNavigationBar = $firstLast . $separator . $prevNext;
216 
217  return $this->mNavigationBar;
218  }
219 
220  private function getNamespaceCond() {
221  if ( $this->namespace !== '' ) {
222  return [ 'ar_namespace' => (int)$this->namespace ];
223  } else {
224  return [];
225  }
226  }
227 
235  public function formatRow( $row ) {
236  $ret = '';
237  $classes = [];
238  $attribs = [];
239 
240  $revFactory = MediaWikiServices::getInstance()->getRevisionFactory();
241 
242  /*
243  * There may be more than just revision rows. To make sure that we'll only be processing
244  * revisions here, let's _try_ to build a revision out of our row (without displaying
245  * notices though) and then trying to grab data from the built object. If we succeed,
246  * we're definitely dealing with revision data and we may proceed, if not, we'll leave it
247  * to extensions to subscribe to the hook to parse the row.
248  */
249  Wikimedia\suppressWarnings();
250  try {
251  $revRecord = $revFactory->newRevisionFromArchiveRow( $row );
252  $validRevision = (bool)$revRecord->getId();
253  } catch ( Exception $e ) {
254  $validRevision = false;
255  }
256  Wikimedia\restoreWarnings();
257 
258  if ( $validRevision ) {
259  $attribs['data-mw-revid'] = $revRecord->getId();
260  $ret = $this->formatRevisionRow( $row );
261  }
262 
263  // Let extensions add data
264  Hooks::run( 'DeletedContributionsLineEnding', [ $this, &$ret, $row, &$classes, &$attribs ] );
265  $attribs = array_filter( $attribs,
266  [ Sanitizer::class, 'isReservedDataAttribute' ],
267  ARRAY_FILTER_USE_KEY
268  );
269 
270  if ( $classes === [] && $attribs === [] && $ret === '' ) {
271  wfDebug( "Dropping Special:DeletedContribution row that could not be formatted\n" );
272  $ret = "<!-- Could not format Special:DeletedContribution row. -->\n";
273  } else {
274  $attribs['class'] = $classes;
275  $ret = Html::rawElement( 'li', $attribs, $ret ) . "\n";
276  }
277 
278  return $ret;
279  }
280 
293  private function formatRevisionRow( $row ) {
294  $page = Title::makeTitle( $row->ar_namespace, $row->ar_title );
295 
296  $linkRenderer = $this->getLinkRenderer();
297 
298  $revRecord = MediaWikiServices::getInstance()
299  ->getRevisionFactory()
300  ->newRevisionFromArchiveRow(
301  $row,
302  RevisionFactory::READ_NORMAL,
303  $page
304  );
305 
306  $undelete = SpecialPage::getTitleFor( 'Undelete' );
307 
308  $logs = SpecialPage::getTitleFor( 'Log' );
309  $dellog = $linkRenderer->makeKnownLink(
310  $logs,
311  $this->messages['deletionlog'],
312  [],
313  [
314  'type' => 'delete',
315  'page' => $page->getPrefixedText()
316  ]
317  );
318 
319  $reviewlink = $linkRenderer->makeKnownLink(
320  SpecialPage::getTitleFor( 'Undelete', $page->getPrefixedDBkey() ),
321  $this->messages['undeleteviewlink']
322  );
323 
324  $user = $this->getUser();
325  $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
326 
327  if ( $permissionManager->userHasRight( $user, 'deletedtext' ) ) {
328  $last = $linkRenderer->makeKnownLink(
329  $undelete,
330  $this->messages['diff'],
331  [],
332  [
333  'target' => $page->getPrefixedText(),
334  'timestamp' => $revRecord->getTimestamp(),
335  'diff' => 'prev'
336  ]
337  );
338  } else {
339  $last = htmlspecialchars( $this->messages['diff'] );
340  }
341 
342  $comment = Linker::revComment( $revRecord );
343  $date = $this->getLanguage()->userTimeAndDate( $revRecord->getTimestamp(), $user );
344 
345  if ( !$permissionManager->userHasRight( $user, 'undelete' ) ||
346  !RevisionRecord::userCanBitfield(
347  $revRecord->getVisibility(),
348  RevisionRecord::DELETED_TEXT,
349  $user
350  )
351  ) {
352  $link = htmlspecialchars( $date ); // unusable link
353  } else {
354  $link = $linkRenderer->makeKnownLink(
355  $undelete,
356  $date,
357  [ 'class' => 'mw-changeslist-date' ],
358  [
359  'target' => $page->getPrefixedText(),
360  'timestamp' => $revRecord->getTimestamp()
361  ]
362  );
363  }
364  // Style deleted items
365  if ( $revRecord->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
366  $link = '<span class="history-deleted">' . $link . '</span>';
367  }
368 
369  $pagelink = $linkRenderer->makeLink(
370  $page,
371  null,
372  [ 'class' => 'mw-changeslist-title' ]
373  );
374 
375  if ( $revRecord->isMinor() ) {
376  $mflag = ChangesList::flag( 'minor' );
377  } else {
378  $mflag = '';
379  }
380 
381  // Revision delete link
382  $del = Linker::getRevDeleteLink( $user, $revRecord, $page );
383  if ( $del ) {
384  $del .= ' ';
385  }
386 
387  $tools = Html::rawElement(
388  'span',
389  [ 'class' => 'mw-deletedcontribs-tools' ],
390  $this->msg( 'parentheses' )->rawParams( $this->getLanguage()->pipeList(
391  [ $last, $dellog, $reviewlink ] ) )->escaped()
392  );
393 
394  $separator = '<span class="mw-changeslist-separator">. .</span>';
395  $ret = "{$del}{$link} {$tools} {$separator} {$mflag} {$pagelink} {$comment}";
396 
397  # Denote if username is redacted for this edit
398  if ( $revRecord->isDeleted( RevisionRecord::DELETED_USER ) ) {
399  $ret .= " <strong>" . $this->msg( 'rev-deleted-user-contribs' )->escaped() . "</strong>";
400  }
401 
402  return $ret;
403  }
404 
410  public function getDatabase() {
411  return $this->mDb;
412  }
413 }
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:53
DeletedContribsPager\getDatabase
getDatabase()
Get the Database object in use.
Definition: DeletedContribsPager.php:410
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:144
$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:42
getUser
getUser()
DeletedContribsPager\formatRow
formatRow( $row)
Generates each row in the contributions list.
Definition: DeletedContribsPager.php:235
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:1607
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:2197
User\newFromName
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition: User.php:533
Revision\RevisionFactory
Service for constructing revision objects.
Definition: RevisionFactory.php:38
IndexPager\$linkRenderer
LinkRenderer $linkRenderer
Definition: IndexPager.php:163
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:43
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:189
DeletedContribsPager\getNamespace
getNamespace()
Definition: DeletedContribsPager.php:177
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:2463
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:185
ChangesList\flag
static flag( $flag, IContextSource $context=null)
Make an "<abbr>" element for a given change flag.
Definition: ChangesList.php:268
Title\makeTitle
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:595
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:181
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:168
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:170
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:53
IndexPager\DIR_DESCENDING
const DIR_DESCENDING
Backwards-compatible constant for $mDefaultDirection field (do not change)
Definition: IndexPager.php:76
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:72
DeletedContribsPager\getNamespaceCond
getNamespaceCond()
Definition: DeletedContribsPager.php:220
DeletedContribsPager\$mDb
IDatabase $mDb
Definition: DeletedContribsPager.php:58
CommentStore\getStore
static getStore()
Definition: CommentStore.php:109
Hooks\run
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:133
DeletedContribsPager\formatRevisionRow
formatRevisionRow( $row)
Generates each row in the contributions list for archive entries.
Definition: DeletedContribsPager.php:293
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:163