MediaWiki REL1_33
HistoryPager.php
Go to the documentation of this file.
1<?php
25
34 public $lastRow = false;
35
37
38 protected $oldIdChecked;
39
40 protected $preventClickjacking = false;
44 protected $parentLens;
45
47 protected $showTagEditUI;
48
50 private $tagFilter;
51
59 public function __construct(
61 $year = '',
62 $month = '',
63 $tagFilter = '',
64 array $conds = []
65 ) {
66 parent::__construct( $historyPage->getContext() );
67 $this->historyPage = $historyPage;
68 $this->tagFilter = $tagFilter;
69 $this->getDateCond( $year, $month );
70 $this->conds = $conds;
71 $this->showTagEditUI = ChangeTags::showTagEditingUI( $this->getUser() );
72 }
73
74 // For hook compatibility...
75 function getArticle() {
76 return $this->historyPage->getArticle();
77 }
78
79 function getSqlComment() {
80 if ( $this->conds ) {
81 return 'history page filtered'; // potentially slow, see CR r58153
82 } else {
83 return 'history page unfiltered';
84 }
85 }
86
87 function getQueryInfo() {
88 $revQuery = Revision::getQueryInfo( [ 'user' ] );
89 $queryInfo = [
90 'tables' => $revQuery['tables'],
91 'fields' => $revQuery['fields'],
92 'conds' => array_merge(
93 [ 'rev_page' => $this->getWikiPage()->getId() ],
94 $this->conds ),
95 'options' => [ 'USE INDEX' => [ 'revision' => 'page_timestamp' ] ],
96 'join_conds' => $revQuery['joins'],
97 ];
99 $queryInfo['tables'],
100 $queryInfo['fields'],
101 $queryInfo['conds'],
102 $queryInfo['join_conds'],
103 $queryInfo['options'],
104 $this->tagFilter
105 );
106
107 // Avoid PHP 7.1 warning of passing $this by reference
108 $historyPager = $this;
109 Hooks::run( 'PageHistoryPager::getQueryInfo', [ &$historyPager, &$queryInfo ] );
110
111 return $queryInfo;
112 }
113
114 function getIndexField() {
115 return 'rev_timestamp';
116 }
117
122 function formatRow( $row ) {
123 if ( $this->lastRow ) {
124 $latest = ( $this->counter == 1 && $this->mIsFirst );
125 $firstInList = $this->counter == 1;
126 $this->counter++;
127
128 $notifTimestamp = $this->getConfig()->get( 'ShowUpdatedMarker' )
129 ? $this->getTitle()->getNotificationTimestamp( $this->getUser() )
130 : false;
131
132 $s = $this->historyLine(
133 $this->lastRow, $row, $notifTimestamp, $latest, $firstInList );
134 } else {
135 $s = '';
136 }
137 $this->lastRow = $row;
138
139 return $s;
140 }
141
142 protected function doBatchLookups() {
143 if ( !Hooks::run( 'PageHistoryPager::doBatchLookups', [ $this, $this->mResult ] ) ) {
144 return;
145 }
146
147 # Do a link batch query
148 $this->mResult->seek( 0 );
149 $batch = new LinkBatch();
150 $revIds = [];
151 foreach ( $this->mResult as $row ) {
152 if ( $row->rev_parent_id ) {
153 $revIds[] = $row->rev_parent_id;
154 }
155 if ( $row->user_name !== null ) {
156 $batch->add( NS_USER, $row->user_name );
157 $batch->add( NS_USER_TALK, $row->user_name );
158 } else { # for anons or usernames of imported revisions
159 $batch->add( NS_USER, $row->rev_user_text );
160 $batch->add( NS_USER_TALK, $row->rev_user_text );
161 }
162 }
163 $this->parentLens = Revision::getParentLengths( $this->mDb, $revIds );
164 $batch->execute();
165 $this->mResult->seek( 0 );
166 }
167
173 protected function getStartBody() {
174 $this->lastRow = false;
175 $this->counter = 1;
176 $this->oldIdChecked = 0;
177
178 $this->getOutput()->wrapWikiMsg( "<div class='mw-history-legend'>\n$1\n</div>", 'histlegend' );
179 $s = Html::openElement( 'form', [ 'action' => wfScript(),
180 'id' => 'mw-history-compare' ] ) . "\n";
181 $s .= Html::hidden( 'title', $this->getTitle()->getPrefixedDBkey() ) . "\n";
182 $s .= Html::hidden( 'action', 'historysubmit' ) . "\n";
183 $s .= Html::hidden( 'type', 'revision' ) . "\n";
184
185 // Button container stored in $this->buttons for re-use in getEndBody()
186 $this->buttons = Html::openElement( 'div', [ 'class' => 'mw-history-compareselectedversions' ] );
187 $className = 'historysubmit mw-history-compareselectedversions-button';
188 $attrs = [ 'class' => $className ]
189 + Linker::tooltipAndAccesskeyAttribs( 'compareselectedversions' );
190 $this->buttons .= $this->submitButton( $this->msg( 'compareselectedversions' )->text(),
191 $attrs
192 ) . "\n";
193
194 $user = $this->getUser();
195 $actionButtons = '';
196 if ( $user->isAllowed( 'deleterevision' ) ) {
197 $actionButtons .= $this->getRevisionButton( 'revisiondelete', 'showhideselectedversions' );
198 }
199 if ( $this->showTagEditUI ) {
200 $actionButtons .= $this->getRevisionButton( 'editchangetags', 'history-edit-tags' );
201 }
202 if ( $actionButtons ) {
203 $this->buttons .= Xml::tags( 'div', [ 'class' =>
204 'mw-history-revisionactions' ], $actionButtons );
205 }
206
207 if ( $user->isAllowed( 'deleterevision' ) || $this->showTagEditUI ) {
208 $this->buttons .= ( new ListToggle( $this->getOutput() ) )->getHTML();
209 }
210
211 $this->buttons .= '</div>';
212
214 $s .= '<ul id="pagehistory">' . "\n";
215
216 return $s;
217 }
218
219 private function getRevisionButton( $name, $msg ) {
220 $this->preventClickjacking();
221 # Note T22966, <button> is non-standard in IE<8
222 $element = Html::element(
223 'button',
224 [
225 'type' => 'submit',
226 'name' => $name,
227 'value' => '1',
228 'class' => "historysubmit mw-history-$name-button",
229 ],
230 $this->msg( $msg )->text()
231 ) . "\n";
232 return $element;
233 }
234
235 protected function getEndBody() {
236 if ( $this->lastRow ) {
237 $latest = $this->counter == 1 && $this->mIsFirst;
238 $firstInList = $this->counter == 1;
239 if ( $this->mIsBackwards ) {
240 # Next row is unknown, but for UI reasons, probably exists if an offset has been specified
241 if ( $this->mOffset == '' ) {
242 $next = null;
243 } else {
244 $next = 'unknown';
245 }
246 } else {
247 # The next row is the past-the-end row
248 $next = $this->mPastTheEndRow;
249 }
250 $this->counter++;
251
252 $notifTimestamp = $this->getConfig()->get( 'ShowUpdatedMarker' )
253 ? $this->getTitle()->getNotificationTimestamp( $this->getUser() )
254 : false;
255
256 $s = $this->historyLine(
257 $this->lastRow, $next, $notifTimestamp, $latest, $firstInList );
258 } else {
259 $s = '';
260 }
261 $s .= "</ul>\n";
262 # Add second buttons only if there is more than one rev
263 if ( $this->getNumRows() > 2 ) {
265 }
266 $s .= '</form>';
267
268 return $s;
269 }
270
278 function submitButton( $message, $attributes = [] ) {
279 # Disable submit button if history has 1 revision only
280 if ( $this->getNumRows() > 1 ) {
281 return Html::submitButton( $message, $attributes );
282 } else {
283 return '';
284 }
285 }
286
301 function historyLine( $row, $next, $notificationtimestamp = false,
302 $latest = false, $firstInList = false ) {
303 $rev = new Revision( $row, 0, $this->getTitle() );
304
305 if ( is_object( $next ) ) {
306 $prevRev = new Revision( $next, 0, $this->getTitle() );
307 } else {
308 $prevRev = null;
309 }
310
311 $curlink = $this->curLink( $rev, $latest );
312 $lastlink = $this->lastLink( $rev, $next );
313 $curLastlinks = Html::rawElement( 'span', [], $curlink ) .
314 Html::rawElement( 'span', [], $lastlink );
315 $histLinks = Html::rawElement(
316 'span',
317 [ 'class' => 'mw-history-histlinks mw-changeslist-links' ],
318 $curLastlinks
319 );
320
321 $diffButtons = $this->diffButtons( $rev, $firstInList );
322 $s = $histLinks . $diffButtons;
323
324 $link = $this->revLink( $rev );
325 $classes = [];
326
327 $del = '';
328 $user = $this->getUser();
329 $canRevDelete = $user->isAllowed( 'deleterevision' );
330 // Show checkboxes for each revision, to allow for revision deletion and
331 // change tags
332 if ( $canRevDelete || $this->showTagEditUI ) {
333 $this->preventClickjacking();
334 // If revision was hidden from sysops and we don't need the checkbox
335 // for anything else, disable it
336 if ( !$this->showTagEditUI && !$rev->userCan( Revision::DELETED_RESTRICTED, $user ) ) {
337 $del = Xml::check( 'deleterevisions', false, [ 'disabled' => 'disabled' ] );
338 // Otherwise, enable the checkbox...
339 } else {
340 $del = Xml::check( 'showhiderevisions', false,
341 [ 'name' => 'ids[' . $rev->getId() . ']' ] );
342 }
343 // User can only view deleted revisions...
344 } elseif ( $rev->getVisibility() && $user->isAllowed( 'deletedhistory' ) ) {
345 // If revision was hidden from sysops, disable the link
346 if ( !$rev->userCan( Revision::DELETED_RESTRICTED, $user ) ) {
347 $del = Linker::revDeleteLinkDisabled( false );
348 // Otherwise, show the link...
349 } else {
350 $query = [ 'type' => 'revision',
351 'target' => $this->getTitle()->getPrefixedDBkey(), 'ids' => $rev->getId() ];
353 $rev->isDeleted( Revision::DELETED_RESTRICTED ), false );
354 }
355 }
356 if ( $del ) {
357 $s .= " $del ";
358 }
359
360 $lang = $this->getLanguage();
361 $dirmark = $lang->getDirMark();
362
363 $s .= " $link";
364 $s .= $dirmark;
365 $s .= " <span class='history-user'>" .
366 Linker::revUserTools( $rev, true, false ) . "</span>";
367 $s .= $dirmark;
368
369 if ( $rev->isMinor() ) {
370 $s .= ' ' . ChangesList::flag( 'minor', $this->getContext() );
371 }
372
373 # Sometimes rev_len isn't populated
374 if ( $rev->getSize() !== null ) {
375 # Size is always public data
376 $prevSize = $this->parentLens[$row->rev_parent_id] ?? 0;
377 $sDiff = ChangesList::showCharacterDifference( $prevSize, $rev->getSize() );
378 $fSize = Linker::formatRevisionSize( $rev->getSize(), false );
379 $s .= ' <span class="mw-changeslist-separator"></span> ' . "$fSize $sDiff";
380 }
381
382 # Text following the character difference is added just before running hooks
383 $s2 = Linker::revComment( $rev, false, true, false );
384
385 if ( $notificationtimestamp && ( $row->rev_timestamp >= $notificationtimestamp ) ) {
386 $s2 .= ' <span class="updatedmarker">' . $this->msg( 'updatedmarker' )->escaped() . '</span>';
387 $classes[] = 'mw-history-line-updated';
388 }
389
390 $tools = [];
391
392 # Rollback and undo links
393 if ( $prevRev && $this->getTitle()->quickUserCan( 'edit', $user ) ) {
394 if ( $latest && $this->getTitle()->quickUserCan( 'rollback', $user ) ) {
395 // Get a rollback link without the brackets
396 $rollbackLink = Linker::generateRollback(
397 $rev,
398 $this->getContext(),
399 [ 'verify', 'noBrackets' ]
400 );
401 if ( $rollbackLink ) {
402 $this->preventClickjacking();
403 $tools[] = $rollbackLink;
404 }
405 }
406
407 if ( !$rev->isDeleted( Revision::DELETED_TEXT )
408 && !$prevRev->isDeleted( Revision::DELETED_TEXT )
409 ) {
410 # Create undo tooltip for the first (=latest) line only
411 $undoTooltip = $latest
412 ? [ 'title' => $this->msg( 'tooltip-undo' )->text() ]
413 : [];
414 $undolink = MediaWikiServices::getInstance()->getLinkRenderer()->makeKnownLink(
415 $this->getTitle(),
416 $this->msg( 'editundo' )->text(),
417 $undoTooltip,
418 [
419 'action' => 'edit',
420 'undoafter' => $prevRev->getId(),
421 'undo' => $rev->getId()
422 ]
423 );
424 $tools[] = "<span class=\"mw-history-undo\">{$undolink}</span>";
425 }
426 }
427 // Allow extension to add their own links here
428 Hooks::run( 'HistoryRevisionTools', [ $rev, &$tools, $prevRev, $user ] );
429
430 if ( $tools ) {
431 $s2 .= ' ' . Html::openElement( 'span', [ 'class' => 'mw-changeslist-links' ] );
432 foreach ( $tools as $tool ) {
433 $s2 .= Html::rawElement( 'span', [], $tool );
434 }
435 $s2 .= Html::closeElement( 'span' );
436 }
437
438 # Tags
439 list( $tagSummary, $newClasses ) = ChangeTags::formatSummaryRow(
440 $row->ts_tags,
441 'history',
442 $this->getContext()
443 );
444 $classes = array_merge( $classes, $newClasses );
445 if ( $tagSummary !== '' ) {
446 $s2 .= " $tagSummary";
447 }
448
449 # Include separator between character difference and following text
450 if ( $s2 !== '' ) {
451 $s .= ' <span class="mw-changeslist-separator"></span> ' . $s2;
452 }
453
454 $attribs = [ 'data-mw-revid' => $rev->getId() ];
455
456 Hooks::run( 'PageHistoryLineEnding', [ $this, &$row, &$s, &$classes, &$attribs ] );
458 [ Sanitizer::class, 'isReservedDataAttribute' ],
459 ARRAY_FILTER_USE_KEY
460 );
461
462 if ( $classes ) {
463 $attribs['class'] = implode( ' ', $classes );
464 }
465
466 return Xml::tags( 'li', $attribs, $s ) . "\n";
467 }
468
475 function revLink( $rev ) {
476 return ChangesList::revDateLink( $rev, $this->getUser(), $this->getLanguage(),
477 $this->getTitle() );
478 }
479
487 function curLink( $rev, $latest ) {
488 $cur = $this->historyPage->message['cur'];
489 if ( $latest || !$rev->userCan( Revision::DELETED_TEXT, $this->getUser() ) ) {
490 return $cur;
491 } else {
492 return MediaWikiServices::getInstance()->getLinkRenderer()->makeKnownLink(
493 $this->getTitle(),
494 new HtmlArmor( $cur ),
495 [],
496 [
497 'diff' => $this->getWikiPage()->getLatest(),
498 'oldid' => $rev->getId()
499 ]
500 );
501 }
502 }
503
513 function lastLink( $prevRev, $next ) {
514 $last = $this->historyPage->message['last'];
515
516 if ( $next === null ) {
517 # Probably no next row
518 return $last;
519 }
520
521 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
522 if ( $next === 'unknown' ) {
523 # Next row probably exists but is unknown, use an oldid=prev link
524 return $linkRenderer->makeKnownLink(
525 $this->getTitle(),
526 new HtmlArmor( $last ),
527 [],
528 [
529 'diff' => $prevRev->getId(),
530 'oldid' => 'prev'
531 ]
532 );
533 }
534
535 $nextRev = new Revision( $next, 0, $this->getTitle() );
536
537 if ( !$prevRev->userCan( Revision::DELETED_TEXT, $this->getUser() )
538 || !$nextRev->userCan( Revision::DELETED_TEXT, $this->getUser() )
539 ) {
540 return $last;
541 }
542
543 return $linkRenderer->makeKnownLink(
544 $this->getTitle(),
545 new HtmlArmor( $last ),
546 [],
547 [
548 'diff' => $prevRev->getId(),
549 'oldid' => $next->rev_id
550 ]
551 );
552 }
553
562 function diffButtons( $rev, $firstInList ) {
563 if ( $this->getNumRows() > 1 ) {
564 $id = $rev->getId();
565 $radio = [ 'type' => 'radio', 'value' => $id ];
567 if ( $firstInList ) {
568 $first = Xml::element( 'input',
569 array_merge( $radio, [
570 'style' => 'visibility:hidden',
571 'name' => 'oldid',
572 'id' => 'mw-oldid-null' ] )
573 );
574 $checkmark = [ 'checked' => 'checked' ];
575 } else {
576 # Check visibility of old revisions
577 if ( !$rev->userCan( Revision::DELETED_TEXT, $this->getUser() ) ) {
578 $radio['disabled'] = 'disabled';
579 $checkmark = []; // We will check the next possible one
580 } elseif ( !$this->oldIdChecked ) {
581 $checkmark = [ 'checked' => 'checked' ];
582 $this->oldIdChecked = $id;
583 } else {
584 $checkmark = [];
585 }
586 $first = Xml::element( 'input',
587 array_merge( $radio, $checkmark, [
588 'name' => 'oldid',
589 'id' => "mw-oldid-$id" ] ) );
590 $checkmark = [];
591 }
592 $second = Xml::element( 'input',
593 array_merge( $radio, $checkmark, [
594 'name' => 'diff',
595 'id' => "mw-diff-$id" ] ) );
596
597 return $first . $second;
598 } else {
599 return '';
600 }
601 }
602
607 function preventClickjacking( $enable = true ) {
608 $this->preventClickjacking = $enable;
609 }
610
618
619}
This list may contain false positives That usually means there is additional text with links below the first Each row contains links to the first and second as well as the first line of the second redirect text
and that you know you can do these things To protect your we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights These restrictions translate to certain responsibilities for you if you distribute copies of the or if you modify it For if you distribute copies of such a whether gratis or for a you must give the recipients all the rights that you have You must make sure that receive or can get the source code And you must show them these terms so they know their rights We protect your rights with two and(2) offer you this license which gives you legal permission to copy
wfScript( $script='index')
Get the path to a specified script file, respecting file extensions; this is a wrapper around $wgScri...
static modifyDisplayQuery(&$tables, &$fields, &$conds, &$join_conds, &$options, $filter_tag='')
Applies all tags-related changes to a query.
static formatSummaryRow( $tags, $page, IContextSource $context=null)
Creates HTML for the given tags.
static showTagEditingUI(User $user)
Indicate whether change tag editing UI is relevant.
static showCharacterDifference( $old, $new, IContextSource $context=null)
Show formatted char difference.
static flag( $flag, IContextSource $context=null)
Make an "<abbr>" element for a given change flag.
static revDateLink(Revision $rev, User $user, Language $lang, $title=null)
Render the date and time of a revision in the current user language based on whether the user is able...
msg( $key)
Get a Message object with context set Parameters are the same as wfMessage()
getWikiPage()
Get the WikiPage object.
getContext()
Get the base IContextSource object.
This class handles printing the history page for an article.
preventClickjacking( $enable=true)
This is called if a write operation is possible from the generated HTML.
getRevisionButton( $name, $msg)
doBatchLookups()
Called from getBody(), before getStartBody() is called and after doQuery() was called.
string $tagFilter
lastLink( $prevRev, $next)
Create a diff-to-previous link for this revision for this page.
getEndBody()
Hook into getBody() for the end of the list.
diffButtons( $rev, $firstInList)
Create radio buttons for page history.
submitButton( $message, $attributes=[])
Creates a submit button.
historyLine( $row, $next, $notificationtimestamp=false, $latest=false, $firstInList=false)
Returns a row from the history printout.
__construct(HistoryAction $historyPage, $year='', $month='', $tagFilter='', array $conds=[])
revLink( $rev)
Create a link to view this revision of the page.
bool $showTagEditUI
Whether to show the tag editing UI.
getSqlComment()
Get some text to go in brackets in the "function name" part of the SQL comment.
curLink( $rev, $latest)
Create a diff-to-current link for this revision for this page.
getPreventClickjacking()
Get the "prevent clickjacking" flag.
getIndexField()
This function should be overridden to return the name of the index fi- eld.
getQueryInfo()
This function should be overridden to provide all parameters needed for the main paged query.
getStartBody()
Creates begin of history list with a submit button.
bool stdClass $lastRow
Marks HTML that shouldn't be escaped.
Definition HtmlArmor.php:28
bool $mIsFirst
True if the current result set is the first one.
getNumRows()
Get the number of rows in the result set.
stdClass bool null $mPastTheEndRow
Extra row fetched at the end to see if the end was reached.
Class representing a list of titles The execute() method checks them all for existence and adds them ...
Definition LinkBatch.php:34
static generateRollback( $rev, IContextSource $context=null, $options=[ 'verify'])
Generate a rollback link for a given revision.
Definition Linker.php:1750
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:1510
static revDeleteLinkDisabled( $delete=true)
Creates a dead (show/hide) link for deleting revisions/log entries.
Definition Linker.php:2111
static formatRevisionSize( $size)
Definition Linker.php:1535
static revUserTools( $rev, $isPublic=false, $useParentheses=true)
Generate a user tool link cluster if the current user is allowed to view it.
Definition Linker.php:1086
static tooltipAndAccesskeyAttribs( $name, array $msgParams=[], $options=null)
Returns the attributes for the tooltip and access key.
Definition Linker.php:2130
static revDeleteLink( $query=[], $restricted=false, $delete=true)
Creates a (show/hide) link for deleting revisions/log entries.
Definition Linker.php:2089
Class for generating clickable toggle links for a list of checkboxes.
MediaWikiServices is the service locator for the application scope of MediaWiki.
Efficient paging for SQL queries.
getDateCond( $year, $month, $day=-1)
Set and return the mOffset timestamp such that we can get all revisions with a timestamp up to the sp...
static getQueryInfo( $options=[])
Return the tables, fields, and join conditions to be selected to create a new revision object.
Definition Revision.php:511
const DELETED_TEXT
Definition Revision.php:46
const DELETED_RESTRICTED
Definition Revision.php:49
static getParentLengths( $db, array $revIds)
Do a batched query to get the parent revision lengths.
Definition Revision.php:538
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global list
Definition deferred.txt:11
usually copyright or history_copyright This message must be in HTML not wikitext & $link
Definition hooks.txt:3069
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return an< a > element with HTML attributes $attribs and contents $html will be returned If you return $ret will be returned and may include noclasses after processing & $attribs
Definition hooks.txt:2012
null for the local wiki Added should default to null in handler for backwards compatibility add a value to it if you want to add a cookie that have to vary cache options can modify $query
Definition hooks.txt:1617
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return an< a > element with HTML attributes $attribs and contents $html will be returned If you return $ret will be returned and may include noclasses after processing after in associative array form before processing starts Return false to skip default processing and return $ret $linkRenderer
Definition hooks.txt:2054
presenting them properly to the user as errors is done by the caller return true use this to change the list i e etc $rev
Definition hooks.txt:1779
const NS_USER_TALK
Definition Defines.php:76
$batch
Definition linkcache.txt:23
The wiki should then use memcached to cache various data To use multiple just add more items to the array To increase the weight of a make its entry a array("192.168.0.1:11211", 2))
$last
if(!isset( $args[0])) $lang