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