MediaWiki REL1_37
DeletedContribsPager.php
Go to the documentation of this file.
1<?php
30
35
40
44 public $messages;
45
49 public $target;
50
54 public $namespace = '';
55
59 protected $mNavigationBar;
60
62 private $hookRunner;
63
66
69
80 public function __construct(
81 IContextSource $context,
82 $target,
83 $namespace,
84 LinkRenderer $linkRenderer,
85 HookContainer $hookContainer,
86 ILoadBalancer $loadBalancer,
87 CommentStore $commentStore,
88 RevisionFactory $revisionFactory
89 ) {
90 // Set database before parent constructor to avoid setting it there with wfGetDB
91 $this->mDb = $loadBalancer->getConnectionRef( ILoadBalancer::DB_REPLICA, 'contributions' );
92 parent::__construct( $context, $linkRenderer );
93 $msgs = [ 'deletionlog', 'undeleteviewlink', 'diff' ];
94 foreach ( $msgs as $msg ) {
95 $this->messages[$msg] = $this->msg( $msg )->text();
96 }
97 $this->target = $target;
98 $this->namespace = $namespace;
99 $this->hookRunner = new HookRunner( $hookContainer );
100 $this->commentStore = $commentStore;
101 $this->revisionFactory = $revisionFactory;
102 }
103
104 public function getDefaultQuery() {
105 $query = parent::getDefaultQuery();
106 $query['target'] = $this->target;
107
108 return $query;
109 }
110
111 public function getQueryInfo() {
112 $dbr = $this->getDatabase();
113 $userCond = [ 'actor_name' => $this->target ];
114 $conds = array_merge( $userCond, $this->getNamespaceCond() );
115 // Paranoia: avoid brute force searches (T19792)
116 if ( !$this->getAuthority()->isAllowed( 'deletedhistory' ) ) {
117 $conds[] = $dbr->bitAnd( 'ar_deleted', RevisionRecord::DELETED_USER ) . ' = 0';
118 } elseif ( !$this->getAuthority()->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
119 $conds[] = $dbr->bitAnd( 'ar_deleted', RevisionRecord::SUPPRESSED_USER ) .
120 ' != ' . RevisionRecord::SUPPRESSED_USER;
121 }
122
123 $queryInfo = $this->revisionFactory->getArchiveQueryInfo();
124 $queryInfo['conds'] = $conds;
125 $queryInfo['options'] = [];
126
127 // rename the "joins" field to "join_conds" as expected by the base class.
128 $queryInfo['join_conds'] = $queryInfo['joins'];
129 unset( $queryInfo['joins'] );
130
131 return $queryInfo;
132 }
133
143 public function reallyDoQuery( $offset, $limit, $order ) {
144 $data = [ parent::reallyDoQuery( $offset, $limit, $order ) ];
145
146 // This hook will allow extensions to add in additional queries, nearly
147 // identical to ContribsPager::reallyDoQuery.
148 $this->hookRunner->onDeletedContribsPager__reallyDoQuery(
149 $data, $this, $offset, $limit, $order );
150
151 $result = [];
152
153 // loop all results and collect them in an array
154 foreach ( $data as $query ) {
155 foreach ( $query as $i => $row ) {
156 // use index column as key, allowing us to easily sort in PHP
157 $result[$row->{$this->getIndexField()} . "-$i"] = $row;
158 }
159 }
160
161 // sort results
162 if ( $order === self::QUERY_ASCENDING ) {
163 ksort( $result );
164 } else {
165 krsort( $result );
166 }
167
168 // enforce limit
169 $result = array_slice( $result, 0, $limit );
170
171 // get rid of array keys
172 $result = array_values( $result );
173
174 return new FakeResultWrapper( $result );
175 }
176
177 public function getIndexField() {
178 return 'ar_timestamp';
179 }
180
184 public function getTarget() {
185 return $this->target;
186 }
187
191 public function getNamespace() {
192 return $this->namespace;
193 }
194
195 protected function getStartBody() {
196 return "<ul>\n";
197 }
198
199 protected function getEndBody() {
200 return "</ul>\n";
201 }
202
203 public function getNavigationBar() {
204 if ( !$this->isNavigationBarShown() ) {
205 return '';
206 }
207
208 if ( isset( $this->mNavigationBar ) ) {
209 return $this->mNavigationBar;
210 }
211
212 $linkTexts = [
213 'prev' => $this->msg( 'pager-newer-n' )->numParams( $this->mLimit )->escaped(),
214 'next' => $this->msg( 'pager-older-n' )->numParams( $this->mLimit )->escaped(),
215 'first' => $this->msg( 'histlast' )->escaped(),
216 'last' => $this->msg( 'histfirst' )->escaped()
217 ];
218
219 $pagingLinks = $this->getPagingLinks( $linkTexts );
220 $limitLinks = $this->getLimitLinks();
221 $lang = $this->getLanguage();
222 $limits = $lang->pipeList( $limitLinks );
223
224 $firstLast = $lang->pipeList( [ $pagingLinks['first'], $pagingLinks['last'] ] );
225 $firstLast = $this->msg( 'parentheses' )->rawParams( $firstLast )->escaped();
226 $prevNext = $this->msg( 'viewprevnext' )
227 ->rawParams(
228 $pagingLinks['prev'],
229 $pagingLinks['next'],
230 $limits
231 )->escaped();
232 $separator = $this->msg( 'word-separator' )->escaped();
233 $this->mNavigationBar = $firstLast . $separator . $prevNext;
234
235 return $this->mNavigationBar;
236 }
237
238 private function getNamespaceCond() {
239 if ( $this->namespace !== '' ) {
240 return [ 'ar_namespace' => (int)$this->namespace ];
241 } else {
242 return [];
243 }
244 }
245
253 public function formatRow( $row ) {
254 $ret = '';
255 $classes = [];
256 $attribs = [];
257
258 if ( $this->revisionFactory->isRevisionRow( $row, 'archive' ) ) {
259 $revRecord = $this->revisionFactory->newRevisionFromArchiveRow( $row );
260 $validRevision = (bool)$revRecord->getId();
261 } else {
262 $validRevision = false;
263 }
264
265 if ( $validRevision ) {
266 $attribs['data-mw-revid'] = $revRecord->getId();
267 $ret = $this->formatRevisionRow( $row );
268 }
269
270 // Let extensions add data
271 $this->hookRunner->onDeletedContributionsLineEnding(
272 $this, $ret, $row, $classes, $attribs );
273 $attribs = array_filter( $attribs,
274 [ Sanitizer::class, 'isReservedDataAttribute' ],
275 ARRAY_FILTER_USE_KEY
276 );
277
278 if ( $classes === [] && $attribs === [] && $ret === '' ) {
279 wfDebug( "Dropping Special:DeletedContribution row that could not be formatted" );
280 $ret = "<!-- Could not format Special:DeletedContribution row. -->\n";
281 } else {
282 $attribs['class'] = $classes;
283 $ret = Html::rawElement( 'li', $attribs, $ret ) . "\n";
284 }
285
286 return $ret;
287 }
288
301 private function formatRevisionRow( $row ) {
302 $page = Title::makeTitle( $row->ar_namespace, $row->ar_title );
303
304 $linkRenderer = $this->getLinkRenderer();
305
306 $revRecord = $this->revisionFactory->newRevisionFromArchiveRow(
307 $row,
308 RevisionFactory::READ_NORMAL,
309 $page
310 );
311
312 $undelete = SpecialPage::getTitleFor( 'Undelete' );
313
314 $logs = SpecialPage::getTitleFor( 'Log' );
315 $dellog = $linkRenderer->makeKnownLink(
316 $logs,
317 $this->messages['deletionlog'],
318 [],
319 [
320 'type' => 'delete',
321 'page' => $page->getPrefixedText()
322 ]
323 );
324
325 $reviewlink = $linkRenderer->makeKnownLink(
326 SpecialPage::getTitleFor( 'Undelete', $page->getPrefixedDBkey() ),
327 $this->messages['undeleteviewlink']
328 );
329
330 $user = $this->getUser();
331
332 if ( $this->getAuthority()->isAllowed( 'deletedtext' ) ) {
333 $last = $linkRenderer->makeKnownLink(
334 $undelete,
335 $this->messages['diff'],
336 [],
337 [
338 'target' => $page->getPrefixedText(),
339 'timestamp' => $revRecord->getTimestamp(),
340 'diff' => 'prev'
341 ]
342 );
343 } else {
344 $last = htmlspecialchars( $this->messages['diff'] );
345 }
346
347 $comment = Linker::revComment( $revRecord );
348 $date = $this->getLanguage()->userTimeAndDate( $revRecord->getTimestamp(), $user );
349
350 if ( !$this->getAuthority()->isAllowed( 'undelete' ) ||
351 !$revRecord->userCan( RevisionRecord::DELETED_TEXT, $this->getAuthority() )
352 ) {
353 $link = htmlspecialchars( $date ); // unusable link
354 } else {
355 $link = $linkRenderer->makeKnownLink(
356 $undelete,
357 $date,
358 [ 'class' => 'mw-changeslist-date' ],
359 [
360 'target' => $page->getPrefixedText(),
361 'timestamp' => $revRecord->getTimestamp()
362 ]
363 );
364 }
365 // Style deleted items
366 if ( $revRecord->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
367 $class = Linker::getRevisionDeletedClass( $revRecord );
368 $link = '<span class="' . $class . '">' . $link . '</span>';
369 }
370
371 $pagelink = $linkRenderer->makeLink(
372 $page,
373 null,
374 [ 'class' => 'mw-changeslist-title' ]
375 );
376
377 if ( $revRecord->isMinor() ) {
378 $mflag = ChangesList::flag( 'minor' );
379 } else {
380 $mflag = '';
381 }
382
383 // Revision delete link
384 $del = Linker::getRevDeleteLink( $user, $revRecord, $page );
385 if ( $del ) {
386 $del .= ' ';
387 }
388
389 $tools = Html::rawElement(
390 'span',
391 [ 'class' => 'mw-deletedcontribs-tools' ],
392 $this->msg( 'parentheses' )->rawParams( $this->getLanguage()->pipeList(
393 [ $last, $dellog, $reviewlink ] ) )->escaped()
394 );
395
396 $separator = '<span class="mw-changeslist-separator">. .</span>';
397 $ret = "{$del}{$link} {$tools} {$separator} {$mflag} {$pagelink} {$comment}";
398
399 # Denote if username is redacted for this edit
400 if ( $revRecord->isDeleted( RevisionRecord::DELETED_USER ) ) {
401 $ret .= " <strong>" . $this->msg( 'rev-deleted-user-contribs' )->escaped() . "</strong>";
402 }
403
404 return $ret;
405 }
406}
getAuthority()
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
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 $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()
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.
getQueryInfo()
Provides all parameters needed for the main paged query.
formatRevisionRow( $row)
Generates each row in the contributions list for archive entries.
RevisionFactory $revisionFactory
__construct(IContextSource $context, $target, $namespace, LinkRenderer $linkRenderer, HookContainer $hookContainer, ILoadBalancer $loadBalancer, CommentStore $commentStore, RevisionFactory $revisionFactory)
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...
getDatabase()
Get the Database object in use.
const DIR_DESCENDING
Backwards-compatible constant for $mDefaultDirection field (do not change)
static getRevisionDeletedClass(RevisionRecord $revisionRecord)
Returns css class of a deleted revision.
Definition Linker.php:1299
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:1782
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:2360
This class provides an implementation of the core hook interfaces, forwarding hook calls to HookConta...
Class that generates HTML links for pages.
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,...
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)
Get a live database handle reference for a server index.
Result wrapper for grabbing data queried from an IDatabase object.
if(!isset( $args[0])) $lang