MediaWiki  master
DeletedContribsPager.php
Go to the documentation of this file.
1 <?php
30 
35 
36  public $mGroupByDate = true;
37 
41  public $messages;
42 
46  public $target;
47 
51  public $namespace = '';
52 
54  private $commentStore;
55 
57  private $hookRunner;
58 
60  private $revisionFactory;
61 
72  public function __construct(
73  IContextSource $context,
74  CommentStore $commentStore,
75  HookContainer $hookContainer,
76  LinkRenderer $linkRenderer,
77  ILoadBalancer $loadBalancer,
78  RevisionFactory $revisionFactory,
79  $target,
81  ) {
82  // Set database before parent constructor to avoid setting it there with wfGetDB
83  $this->mDb = $loadBalancer->getConnectionRef( ILoadBalancer::DB_REPLICA, 'contributions' );
84  parent::__construct( $context, $linkRenderer );
85  $msgs = [ 'deletionlog', 'undeleteviewlink', 'diff' ];
86  foreach ( $msgs as $msg ) {
87  $this->messages[$msg] = $this->msg( $msg )->text();
88  }
89  $this->target = $target;
90  $this->namespace = $namespace;
91  $this->hookRunner = new HookRunner( $hookContainer );
92  $this->commentStore = $commentStore;
93  $this->revisionFactory = $revisionFactory;
94  }
95 
96  public function getDefaultQuery() {
97  $query = parent::getDefaultQuery();
98  $query['target'] = $this->target;
99 
100  return $query;
101  }
102 
103  public function getQueryInfo() {
104  $dbr = $this->getDatabase();
105  $userCond = [ 'actor_name' => $this->target ];
106  $conds = array_merge( $userCond, $this->getNamespaceCond() );
107  // Paranoia: avoid brute force searches (T19792)
108  if ( !$this->getAuthority()->isAllowed( 'deletedhistory' ) ) {
109  $conds[] = $dbr->bitAnd( 'ar_deleted', RevisionRecord::DELETED_USER ) . ' = 0';
110  } elseif ( !$this->getAuthority()->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
111  $conds[] = $dbr->bitAnd( 'ar_deleted', RevisionRecord::SUPPRESSED_USER ) .
112  ' != ' . RevisionRecord::SUPPRESSED_USER;
113  }
114 
115  $queryInfo = $this->revisionFactory->getArchiveQueryInfo();
116  $queryInfo['conds'] = $conds;
117  $queryInfo['options'] = [];
118 
119  // rename the "joins" field to "join_conds" as expected by the base class.
120  $queryInfo['join_conds'] = $queryInfo['joins'];
121  unset( $queryInfo['joins'] );
122 
124  $queryInfo['tables'],
125  $queryInfo['fields'],
126  $queryInfo['conds'],
127  $queryInfo['join_conds'],
128  $queryInfo['options'],
129  ''
130  );
131 
132  return $queryInfo;
133  }
134 
144  public function reallyDoQuery( $offset, $limit, $order ) {
145  $data = [ parent::reallyDoQuery( $offset, $limit, $order ) ];
146 
147  // This hook will allow extensions to add in additional queries, nearly
148  // identical to ContribsPager::reallyDoQuery.
149  $this->hookRunner->onDeletedContribsPager__reallyDoQuery(
150  $data, $this, $offset, $limit, $order );
151 
152  $result = [];
153 
154  // loop all results and collect them in an array
155  foreach ( $data as $query ) {
156  foreach ( $query as $i => $row ) {
157  // use index column as key, allowing us to easily sort in PHP
158  $result[$row->{$this->getIndexField()} . "-$i"] = $row;
159  }
160  }
161 
162  // sort results
163  if ( $order === self::QUERY_ASCENDING ) {
164  ksort( $result );
165  } else {
166  krsort( $result );
167  }
168 
169  // enforce limit
170  $result = array_slice( $result, 0, $limit );
171 
172  // get rid of array keys
173  $result = array_values( $result );
174 
175  return new FakeResultWrapper( $result );
176  }
177 
181  protected function getExtraSortFields() {
182  return [ 'ar_id' ];
183  }
184 
185  public function getIndexField() {
186  return 'ar_timestamp';
187  }
188 
192  public function getTarget() {
193  return $this->target;
194  }
195 
199  public function getNamespace() {
200  return $this->namespace;
201  }
202 
206  protected function getStartBody() {
207  return "<section class='mw-pager-body'>\n";
208  }
209 
213  protected function getEndBody() {
214  return "</section>\n";
215  }
216 
217  private function getNamespaceCond() {
218  if ( $this->namespace !== '' ) {
219  return [ 'ar_namespace' => (int)$this->namespace ];
220  } else {
221  return [];
222  }
223  }
224 
232  public function formatRow( $row ) {
233  $ret = '';
234  $classes = [];
235  $attribs = [];
236 
237  if ( $this->revisionFactory->isRevisionRow( $row, 'archive' ) ) {
238  $revRecord = $this->revisionFactory->newRevisionFromArchiveRow( $row );
239  $revId = $revRecord->getId();
240  if ( $revId ) {
241  $attribs['data-mw-revid'] = $revId;
242  [ $ret, $classes ] = $this->formatRevisionRow( $row );
243  }
244  }
245 
246  // Let extensions add data
247  $this->hookRunner->onDeletedContributionsLineEnding(
248  $this, $ret, $row, $classes, $attribs );
249  $attribs = array_filter( $attribs,
250  [ Sanitizer::class, 'isReservedDataAttribute' ],
251  ARRAY_FILTER_USE_KEY
252  );
253 
254  if ( $classes === [] && $attribs === [] && $ret === '' ) {
255  wfDebug( "Dropping Special:DeletedContribution row that could not be formatted" );
256  $ret = "<!-- Could not format Special:DeletedContribution row. -->\n";
257  } else {
258  $attribs['class'] = $classes;
259  $ret = Html::rawElement( 'li', $attribs, $ret ) . "\n";
260  }
261 
262  return $ret;
263  }
264 
277  private function formatRevisionRow( $row ) {
278  $page = Title::makeTitle( $row->ar_namespace, $row->ar_title );
279 
280  $linkRenderer = $this->getLinkRenderer();
281 
282  $revRecord = $this->revisionFactory->newRevisionFromArchiveRow(
283  $row,
284  RevisionFactory::READ_NORMAL,
285  $page
286  );
287 
288  $undelete = SpecialPage::getTitleFor( 'Undelete' );
289 
290  $logs = SpecialPage::getTitleFor( 'Log' );
291  $dellog = $linkRenderer->makeKnownLink(
292  $logs,
293  $this->messages['deletionlog'],
294  [],
295  [
296  'type' => 'delete',
297  'page' => $page->getPrefixedText()
298  ]
299  );
300 
301  $reviewlink = $linkRenderer->makeKnownLink(
302  SpecialPage::getTitleFor( 'Undelete', $page->getPrefixedDBkey() ),
303  $this->messages['undeleteviewlink']
304  );
305 
306  $user = $this->getUser();
307 
308  if ( $this->getAuthority()->isAllowed( 'deletedtext' ) ) {
309  $last = $linkRenderer->makeKnownLink(
310  $undelete,
311  $this->messages['diff'],
312  [],
313  [
314  'target' => $page->getPrefixedText(),
315  'timestamp' => $revRecord->getTimestamp(),
316  'diff' => 'prev'
317  ]
318  );
319  } else {
320  $last = htmlspecialchars( $this->messages['diff'] );
321  }
322 
323  $comment = Linker::revComment( $revRecord );
324  $date = $this->getLanguage()->userTimeAndDate( $revRecord->getTimestamp(), $user );
325 
326  if ( !$this->getAuthority()->isAllowed( 'undelete' ) ||
327  !$revRecord->userCan( RevisionRecord::DELETED_TEXT, $this->getAuthority() )
328  ) {
329  $link = htmlspecialchars( $date ); // unusable link
330  } else {
331  $link = $linkRenderer->makeKnownLink(
332  $undelete,
333  $date,
334  [ 'class' => 'mw-changeslist-date' ],
335  [
336  'target' => $page->getPrefixedText(),
337  'timestamp' => $revRecord->getTimestamp()
338  ]
339  );
340  }
341  // Style deleted items
342  if ( $revRecord->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
343  $class = Linker::getRevisionDeletedClass( $revRecord );
344  $link = '<span class="' . $class . '">' . $link . '</span>';
345  }
346 
347  $pagelink = $linkRenderer->makeLink(
348  $page,
349  null,
350  [ 'class' => 'mw-changeslist-title' ]
351  );
352 
353  if ( $revRecord->isMinor() ) {
354  $mflag = ChangesList::flag( 'minor' );
355  } else {
356  $mflag = '';
357  }
358 
359  // Revision delete link
360  $del = Linker::getRevDeleteLink( $user, $revRecord, $page );
361  if ( $del ) {
362  $del .= ' ';
363  }
364 
365  $tools = Html::rawElement(
366  'span',
367  [ 'class' => 'mw-deletedcontribs-tools' ],
368  $this->msg( 'parentheses' )->rawParams( $this->getLanguage()->pipeList(
369  [ $last, $dellog, $reviewlink ] ) )->escaped()
370  );
371 
372  // Tags, if any.
373  [ $tagSummary, $classes ] = ChangeTags::formatSummaryRow(
374  $row->ts_tags,
375  'deletedcontributions',
376  $this->getContext()
377  );
378 
379  $separator = '<span class="mw-changeslist-separator">. .</span>';
380  $ret = "{$del}{$link} {$tools} {$separator} {$mflag} {$pagelink} {$comment} {$tagSummary}";
381 
382  # Denote if username is redacted for this edit
383  if ( $revRecord->isDeleted( RevisionRecord::DELETED_USER ) ) {
384  $ret .= " <strong>" . $this->msg( 'rev-deleted-user-contribs' )->escaped() . "</strong>";
385  }
386 
387  return [ $ret, $classes ];
388  }
389 }
getUser()
getAuthority()
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
static modifyDisplayQuery(&$tables, &$fields, &$conds, &$join_conds, &$options, $filter_tag='', bool $exclude=false)
Applies all tags-related changes to a query.
Definition: ChangeTags.php:906
static formatSummaryRow( $tags, $page, MessageLocalizer $localizer=null)
Creates HTML for the given tags.
Definition: ChangeTags.php:193
static flag( $flag, IContextSource $context=null)
Make an "<abbr>" element for a given change flag.
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()
string int $namespace
A single namespace number, or an empty string for all namespaces.
__construct(IContextSource $context, CommentStore $commentStore, HookContainer $hookContainer, LinkRenderer $linkRenderer, ILoadBalancer $loadBalancer, RevisionFactory $revisionFactory, $target, $namespace)
string[] $messages
Local cache for escaped messages.
string $target
User name, or a string describing an IP address range.
getDefaultQuery()
Get an array of query parameters that should be put into self-links.
getIndexField()
Returns the name of the index field.
formatRow( $row)
Generates each row in the contributions list.
reallyDoQuery( $offset, $limit, $order)
This method basically executes the exact same code as the parent class, though with a hook added,...
getEndBody()
Hook into getBody() for the end of the list.@stable to overridestring
getQueryInfo()
Provides all parameters needed for the main paged query.
getStartBody()
Hook into getBody(), allows text to be inserted at the start.This will be called even if there are no...
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
static getRevisionDeletedClass(RevisionRecord $revisionRecord)
Returns css class of a deleted revision.
Definition: Linker.php:1295
static revComment(RevisionRecord $revRecord, $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:1551
static getRevDeleteLink(Authority $performer, RevisionRecord $revRecord, LinkTarget $title)
Get a revision-deletion link, or disabled link, or nothing, depending on user permissions & the setti...
Definition: Linker.php:2097
This class provides an implementation of the core hook interfaces, forwarding hook calls to HookConta...
Definition: HookRunner.php:560
Class that generates HTML anchor link elements for pages.
Page revision base class.
IndexPager with a formatted navigation bar.
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,...
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:641
Overloads the relevant methods of the real ResultWrapper so it doesn't go anywhere near an actual dat...
Interface for objects which can provide a MediaWiki context on request.
Service for constructing RevisionRecord objects.
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