MediaWiki REL1_34
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}
getUser()
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
static flag( $flag, IContextSource $context=null)
Make an "<abbr>" element for a given change flag.
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
IContextSource $context
string $mNavigationBar
Navigation bar with paging links.
string int $namespace
A single namespace number, or an empty string for all namespaces.
string[] $messages
Local cache for escaped messages.
string $target
User name, or a string describing an IP address range.
bool $mDefaultDirection
Default direction for pager.
getDefaultQuery()
Get an array of query parameters that should be put into self-links.
getIndexField()
This function should be overridden to return the name of the index fi- eld.
formatRow( $row)
Generates each row in the contributions list.
getDatabase()
Get the Database object in use.
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.
getQueryInfo()
This function should be overridden to provide all parameters needed for the main paged query.
formatRevisionRow( $row)
Generates each row in the contributions list for archive entries.
__construct(IContextSource $context, $target, $namespace=false, LinkRenderer $linkRenderer)
getStartBody()
Hook into getBody(), allows text to be inserted at the start.
IndexPager is an efficient pager which uses a (roughly unique) index in the data set to implement pag...
LinkRenderer $linkRenderer
const DIR_DESCENDING
Backwards-compatible constant for $mDefaultDirection field (do not change)
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:2110
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:1577
Class that generates HTML links for pages.
MediaWikiServices is the service locator for the application scope of MediaWiki.
Page revision base class.
static newFromArchiveRow( $row, $overrides=[])
Make a fake revision object from an archive table row.
Definition Revision.php:172
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition User.php:518
Overloads the relevant methods of the real ResultsWrapper so it doesn't go anywhere near an actual da...
Interface for objects which can provide a MediaWiki context on request.
Basic database interface for live and lazy-loaded relation database handles.
Definition IDatabase.php:38
Result wrapper for grabbing data queried from an IDatabase object.
$last
const DB_REPLICA
Definition defines.php:25
if(!isset( $args[0])) $lang