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 
61 
72  public function __construct(
75  HookContainer $hookContainer,
77  ILoadBalancer $loadBalancer,
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 
178  public function getIndexField() {
179  return 'ar_timestamp';
180  }
181 
185  public function getTarget() {
186  return $this->target;
187  }
188 
192  public function getNamespace() {
193  return $this->namespace;
194  }
195 
199  protected function getStartBody() {
200  return "<section class='mw-pager-body'>\n";
201  }
202 
206  protected function getEndBody() {
207  return "</section>\n";
208  }
209 
210  private function getNamespaceCond() {
211  if ( $this->namespace !== '' ) {
212  return [ 'ar_namespace' => (int)$this->namespace ];
213  } else {
214  return [];
215  }
216  }
217 
225  public function formatRow( $row ) {
226  $ret = '';
227  $classes = [];
228  $attribs = [];
229 
230  if ( $this->revisionFactory->isRevisionRow( $row, 'archive' ) ) {
231  $revRecord = $this->revisionFactory->newRevisionFromArchiveRow( $row );
232  $revId = $revRecord->getId();
233  if ( $revId ) {
234  $attribs['data-mw-revid'] = $revId;
235  [ $ret, $classes ] = $this->formatRevisionRow( $row );
236  }
237  }
238 
239  // Let extensions add data
240  $this->hookRunner->onDeletedContributionsLineEnding(
241  $this, $ret, $row, $classes, $attribs );
242  $attribs = array_filter( $attribs,
243  [ Sanitizer::class, 'isReservedDataAttribute' ],
244  ARRAY_FILTER_USE_KEY
245  );
246 
247  if ( $classes === [] && $attribs === [] && $ret === '' ) {
248  wfDebug( "Dropping Special:DeletedContribution row that could not be formatted" );
249  $ret = "<!-- Could not format Special:DeletedContribution row. -->\n";
250  } else {
251  $attribs['class'] = $classes;
252  $ret = Html::rawElement( 'li', $attribs, $ret ) . "\n";
253  }
254 
255  return $ret;
256  }
257 
270  private function formatRevisionRow( $row ) {
271  $page = Title::makeTitle( $row->ar_namespace, $row->ar_title );
272 
273  $linkRenderer = $this->getLinkRenderer();
274 
275  $revRecord = $this->revisionFactory->newRevisionFromArchiveRow(
276  $row,
277  RevisionFactory::READ_NORMAL,
278  $page
279  );
280 
281  $undelete = SpecialPage::getTitleFor( 'Undelete' );
282 
283  $logs = SpecialPage::getTitleFor( 'Log' );
284  $dellog = $linkRenderer->makeKnownLink(
285  $logs,
286  $this->messages['deletionlog'],
287  [],
288  [
289  'type' => 'delete',
290  'page' => $page->getPrefixedText()
291  ]
292  );
293 
294  $reviewlink = $linkRenderer->makeKnownLink(
295  SpecialPage::getTitleFor( 'Undelete', $page->getPrefixedDBkey() ),
296  $this->messages['undeleteviewlink']
297  );
298 
299  $user = $this->getUser();
300 
301  if ( $this->getAuthority()->isAllowed( 'deletedtext' ) ) {
302  $last = $linkRenderer->makeKnownLink(
303  $undelete,
304  $this->messages['diff'],
305  [],
306  [
307  'target' => $page->getPrefixedText(),
308  'timestamp' => $revRecord->getTimestamp(),
309  'diff' => 'prev'
310  ]
311  );
312  } else {
313  $last = htmlspecialchars( $this->messages['diff'] );
314  }
315 
316  $comment = Linker::revComment( $revRecord );
317  $date = $this->getLanguage()->userTimeAndDate( $revRecord->getTimestamp(), $user );
318 
319  if ( !$this->getAuthority()->isAllowed( 'undelete' ) ||
320  !$revRecord->userCan( RevisionRecord::DELETED_TEXT, $this->getAuthority() )
321  ) {
322  $link = htmlspecialchars( $date ); // unusable link
323  } else {
324  $link = $linkRenderer->makeKnownLink(
325  $undelete,
326  $date,
327  [ 'class' => 'mw-changeslist-date' ],
328  [
329  'target' => $page->getPrefixedText(),
330  'timestamp' => $revRecord->getTimestamp()
331  ]
332  );
333  }
334  // Style deleted items
335  if ( $revRecord->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
336  $class = Linker::getRevisionDeletedClass( $revRecord );
337  $link = '<span class="' . $class . '">' . $link . '</span>';
338  }
339 
340  $pagelink = $linkRenderer->makeLink(
341  $page,
342  null,
343  [ 'class' => 'mw-changeslist-title' ]
344  );
345 
346  if ( $revRecord->isMinor() ) {
347  $mflag = ChangesList::flag( 'minor' );
348  } else {
349  $mflag = '';
350  }
351 
352  // Revision delete link
353  $del = Linker::getRevDeleteLink( $user, $revRecord, $page );
354  if ( $del ) {
355  $del .= ' ';
356  }
357 
358  $tools = Html::rawElement(
359  'span',
360  [ 'class' => 'mw-deletedcontribs-tools' ],
361  $this->msg( 'parentheses' )->rawParams( $this->getLanguage()->pipeList(
362  [ $last, $dellog, $reviewlink ] ) )->escaped()
363  );
364 
365  // Tags, if any.
366  list( $tagSummary, $classes ) = ChangeTags::formatSummaryRow(
367  $row->ts_tags,
368  'deletedcontributions',
369  $this->getContext()
370  );
371 
372  $separator = '<span class="mw-changeslist-separator">. .</span>';
373  $ret = "{$del}{$link} {$tools} {$separator} {$mflag} {$pagelink} {$comment} {$tagSummary}";
374 
375  # Denote if username is redacted for this edit
376  if ( $revRecord->isDeleted( RevisionRecord::DELETED_USER ) ) {
377  $ret .= " <strong>" . $this->msg( 'rev-deleted-user-contribs' )->escaped() . "</strong>";
378  }
379 
380  return [ $ret, $classes ];
381  }
382 }
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:896
static formatSummaryRow( $tags, $page, MessageLocalizer $localizer=null)
Creates HTML for the given tags.
Definition: ChangeTags.php:182
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()
IContextSource $context
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.
formatRevisionRow( $row)
Generates each row in the contributions list for archive entries.
RevisionFactory $revisionFactory
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:248
LinkRenderer $linkRenderer
Definition: IndexPager.php:167
static getRevisionDeletedClass(RevisionRecord $revisionRecord)
Returns css class of a deleted revision.
Definition: Linker.php:1304
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:1560
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:2112
This class provides an implementation of the core hook interfaces, forwarding hook calls to HookConta...
Definition: HookRunner.php:562
Class that generates HTML anchor link elements for pages.
Page revision base class.
Efficient paging for SQL queries.
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:638
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.
Database cluster connection, tracking, load balancing, and transaction manager interface.
getConnectionRef( $i, $groups=[], $domain=false, $flags=0)
Result wrapper for grabbing data queried from an IDatabase object.
const DB_REPLICA
Definition: defines.php:26