r94031 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r94030‎ | r94031 | r94032 >
Date:19:41, 6 August 2011
Author:ialex
Status:reverted (Comments)
Tags:
Comment:
* Moved action=history to use an Action subclass
* Removed obsolete aliases PageHistory and PageHistoryPager; unused
* Maintained backward compatibility with HistoryPage; extensions using it will still work
* Use local context instead of global variables
* Removed calls to OutputPage::setPageTitleActionText() and OutputPage::setSyndicated(), the first one does nothing and the second one is overriden by the setFeedAppendQuery() call just below
* Call Linker methods statically
* Fixed bug where feedEmpty() was not called on empty history since casting a ResultWrapper object to boolean always returns true even when there's no row
Modified paths:
  • /trunk/phase3/includes/AutoLoader.php (modified) (history)
  • /trunk/phase3/includes/DefaultSettings.php (modified) (history)
  • /trunk/phase3/includes/HistoryPage.php (deleted) (history)
  • /trunk/phase3/includes/Wiki.php (modified) (history)
  • /trunk/phase3/includes/actions/HistoryAction.php (added) (history)

Diff [purge]

Index: trunk/phase3/includes/HistoryPage.php
@@ -1,775 +0,0 @@
2 -<?php
3 -/**
4 - * Page history
5 - *
6 - * Split off from Article.php and Skin.php, 2003-12-22
7 - * @file
8 - */
9 -
10 -/**
11 - * This class handles printing the history page for an article. In order to
12 - * be efficient, it uses timestamps rather than offsets for paging, to avoid
13 - * costly LIMIT,offset queries.
14 - *
15 - * Construct it by passing in an Article, and call $h->history() to print the
16 - * history.
17 - *
18 - */
19 -class HistoryPage {
20 - const DIR_PREV = 0;
21 - const DIR_NEXT = 1;
22 -
23 - /** Contains the Article object. Passed on construction. */
24 - private $article;
25 - /** The $article title object. Found on construction. */
26 - private $title;
27 - /** Shortcut to the user Skin object. */
28 - private $skin;
29 -
30 - /**
31 - * Construct a new HistoryPage.
32 - *
33 - * @param $article Article
34 - */
35 - function __construct( $article ) {
36 - global $wgUser;
37 - $this->article = $article;
38 - $this->title = $article->getTitle();
39 - $this->skin = $wgUser->getSkin();
40 - $this->preCacheMessages();
41 - }
42 -
43 - /** Get the Article object we are working on. */
44 - public function getArticle() {
45 - return $this->article;
46 - }
47 -
48 - /** Get the Title object. */
49 - public function getTitle() {
50 - return $this->title;
51 - }
52 -
53 - /**
54 - * As we use the same small set of messages in various methods and that
55 - * they are called often, we call them once and save them in $this->message
56 - */
57 - private function preCacheMessages() {
58 - // Precache various messages
59 - if ( !isset( $this->message ) ) {
60 - $msgs = array( 'cur', 'last', 'pipe-separator' );
61 - foreach ( $msgs as $msg ) {
62 - $this->message[$msg] = wfMsgExt( $msg, array( 'escapenoentities' ) );
63 - }
64 - }
65 - }
66 -
67 - /**
68 - * Print the history page for an article.
69 - * @return nothing
70 - */
71 - function history() {
72 - global $wgOut, $wgRequest, $wgScript;
73 -
74 - /**
75 - * Allow client caching.
76 - */
77 - if ( $wgOut->checkLastModified( $this->article->getTouched() ) )
78 - return; // Client cache fresh and headers sent, nothing more to do.
79 -
80 - wfProfileIn( __METHOD__ );
81 -
82 - // Setup page variables.
83 - $wgOut->setPageTitle( wfMsg( 'history-title', $this->title->getPrefixedText() ) );
84 - $wgOut->setPageTitleActionText( wfMsg( 'history_short' ) );
85 - $wgOut->setArticleFlag( false );
86 - $wgOut->setArticleRelated( true );
87 - $wgOut->setRobotPolicy( 'noindex,nofollow' );
88 - $wgOut->setSyndicated( true );
89 - $wgOut->setFeedAppendQuery( 'action=history' );
90 - $wgOut->addModules( array( 'mediawiki.legacy.history', 'mediawiki.action.history' ) );
91 -
92 - // Creation of a subtitle link pointing to [[Special:Log]]
93 - $logPage = SpecialPage::getTitleFor( 'Log' );
94 - $logLink = $this->skin->link(
95 - $logPage,
96 - wfMsgHtml( 'viewpagelogs' ),
97 - array(),
98 - array( 'page' => $this->title->getPrefixedText() ),
99 - array( 'known', 'noclasses' )
100 - );
101 - $wgOut->setSubtitle( $logLink );
102 -
103 - // Handle atom/RSS feeds.
104 - $feedType = $wgRequest->getVal( 'feed' );
105 - if ( $feedType ) {
106 - wfProfileOut( __METHOD__ );
107 - return $this->feed( $feedType );
108 - }
109 -
110 - // Fail nicely if article doesn't exist.
111 - if ( !$this->title->exists() ) {
112 - $wgOut->addWikiMsg( 'nohistory' );
113 - # show deletion/move log if there is an entry
114 - LogEventsList::showLogExtract(
115 - $wgOut,
116 - array( 'delete', 'move' ),
117 - $this->title->getPrefixedText(),
118 - '',
119 - array( 'lim' => 10,
120 - 'conds' => array( "log_action != 'revision'" ),
121 - 'showIfEmpty' => false,
122 - 'msgKey' => array( 'moveddeleted-notice' )
123 - )
124 - );
125 - wfProfileOut( __METHOD__ );
126 - return;
127 - }
128 -
129 - /**
130 - * Add date selector to quickly get to a certain time
131 - */
132 - $year = $wgRequest->getInt( 'year' );
133 - $month = $wgRequest->getInt( 'month' );
134 - $tagFilter = $wgRequest->getVal( 'tagfilter' );
135 - $tagSelector = ChangeTags::buildTagFilterSelector( $tagFilter );
136 -
137 - /**
138 - * Option to show only revisions that have been (partially) hidden via RevisionDelete
139 - */
140 - if ( $wgRequest->getBool( 'deleted' ) ) {
141 - $conds = array( "rev_deleted != '0'" );
142 - } else {
143 - $conds = array();
144 - }
145 - $checkDeleted = Xml::checkLabel( wfMsg( 'history-show-deleted' ),
146 - 'deleted', 'mw-show-deleted-only', $wgRequest->getBool( 'deleted' ) ) . "\n";
147 -
148 - // Add the general form
149 - $action = htmlspecialchars( $wgScript );
150 - $wgOut->addHTML(
151 - "<form action=\"$action\" method=\"get\" id=\"mw-history-searchform\">" .
152 - Xml::fieldset(
153 - wfMsg( 'history-fieldset-title' ),
154 - false,
155 - array( 'id' => 'mw-history-search' )
156 - ) .
157 - Html::hidden( 'title', $this->title->getPrefixedDBKey() ) . "\n" .
158 - Html::hidden( 'action', 'history' ) . "\n" .
159 - Xml::dateMenu( $year, $month ) . '&#160;' .
160 - ( $tagSelector ? ( implode( '&#160;', $tagSelector ) . '&#160;' ) : '' ) .
161 - $checkDeleted .
162 - Xml::submitButton( wfMsg( 'allpagessubmit' ) ) . "\n" .
163 - '</fieldset></form>'
164 - );
165 -
166 - wfRunHooks( 'PageHistoryBeforeList', array( &$this->article ) );
167 -
168 - // Create and output the list.
169 - $pager = new HistoryPager( $this, $year, $month, $tagFilter, $conds );
170 - $wgOut->addHTML(
171 - $pager->getNavigationBar() .
172 - $pager->getBody() .
173 - $pager->getNavigationBar()
174 - );
175 - $wgOut->preventClickjacking( $pager->getPreventClickjacking() );
176 -
177 - wfProfileOut( __METHOD__ );
178 - }
179 -
180 - /**
181 - * Fetch an array of revisions, specified by a given limit, offset and
182 - * direction. This is now only used by the feeds. It was previously
183 - * used by the main UI but that's now handled by the pager.
184 - *
185 - * @param $limit Integer: the limit number of revisions to get
186 - * @param $offset Integer
187 - * @param $direction Integer: either HistoryPage::DIR_PREV or HistoryPage::DIR_NEXT
188 - * @return ResultWrapper
189 - */
190 - function fetchRevisions( $limit, $offset, $direction ) {
191 - $dbr = wfGetDB( DB_SLAVE );
192 -
193 - if ( $direction == HistoryPage::DIR_PREV ) {
194 - list( $dirs, $oper ) = array( "ASC", ">=" );
195 - } else { /* $direction == HistoryPage::DIR_NEXT */
196 - list( $dirs, $oper ) = array( "DESC", "<=" );
197 - }
198 -
199 - if ( $offset ) {
200 - $offsets = array( "rev_timestamp $oper '$offset'" );
201 - } else {
202 - $offsets = array();
203 - }
204 -
205 - $page_id = $this->title->getArticleID();
206 -
207 - return $dbr->select( 'revision',
208 - Revision::selectFields(),
209 - array_merge( array( "rev_page=$page_id" ), $offsets ),
210 - __METHOD__,
211 - array( 'ORDER BY' => "rev_timestamp $dirs",
212 - 'USE INDEX' => 'page_timestamp', 'LIMIT' => $limit )
213 - );
214 - }
215 -
216 - /**
217 - * Output a subscription feed listing recent edits to this page.
218 - *
219 - * @param $type String: feed type
220 - */
221 - function feed( $type ) {
222 - global $wgFeedClasses, $wgRequest, $wgFeedLimit;
223 - if ( !FeedUtils::checkFeedOutput( $type ) ) {
224 - return;
225 - }
226 -
227 - $feed = new $wgFeedClasses[$type](
228 - $this->title->getPrefixedText() . ' - ' .
229 - wfMsgForContent( 'history-feed-title' ),
230 - wfMsgForContent( 'history-feed-description' ),
231 - $this->title->getFullUrl( 'action=history' )
232 - );
233 -
234 - // Get a limit on number of feed entries. Provide a sane default
235 - // of 10 if none is defined (but limit to $wgFeedLimit max)
236 - $limit = $wgRequest->getInt( 'limit', 10 );
237 - if ( $limit > $wgFeedLimit || $limit < 1 ) {
238 - $limit = 10;
239 - }
240 - $items = $this->fetchRevisions( $limit, 0, HistoryPage::DIR_NEXT );
241 -
242 - // Generate feed elements enclosed between header and footer.
243 - $feed->outHeader();
244 - if ( $items ) {
245 - foreach ( $items as $row ) {
246 - $feed->outItem( $this->feedItem( $row ) );
247 - }
248 - } else {
249 - $feed->outItem( $this->feedEmpty() );
250 - }
251 - $feed->outFooter();
252 - }
253 -
254 - function feedEmpty() {
255 - global $wgOut;
256 - return new FeedItem(
257 - wfMsgForContent( 'nohistory' ),
258 - $wgOut->parse( wfMsgForContent( 'history-feed-empty' ) ),
259 - $this->title->getFullUrl(),
260 - wfTimestamp( TS_MW ),
261 - '',
262 - $this->title->getTalkPage()->getFullUrl()
263 - );
264 - }
265 -
266 - /**
267 - * Generate a FeedItem object from a given revision table row
268 - * Borrows Recent Changes' feed generation functions for formatting;
269 - * includes a diff to the previous revision (if any).
270 - *
271 - * @param $row Object: database row
272 - * @return FeedItem
273 - */
274 - function feedItem( $row ) {
275 - $rev = new Revision( $row );
276 - $rev->setTitle( $this->title );
277 - $text = FeedUtils::formatDiffRow(
278 - $this->title,
279 - $this->title->getPreviousRevisionID( $rev->getId() ),
280 - $rev->getId(),
281 - $rev->getTimestamp(),
282 - $rev->getComment()
283 - );
284 - if ( $rev->getComment() == '' ) {
285 - global $wgContLang;
286 - $title = wfMsgForContent( 'history-feed-item-nocomment',
287 - $rev->getUserText(),
288 - $wgContLang->timeanddate( $rev->getTimestamp() ),
289 - $wgContLang->date( $rev->getTimestamp() ),
290 - $wgContLang->time( $rev->getTimestamp() )
291 - );
292 - } else {
293 - $title = $rev->getUserText() .
294 - wfMsgForContent( 'colon-separator' ) .
295 - FeedItem::stripComment( $rev->getComment() );
296 - }
297 - return new FeedItem(
298 - $title,
299 - $text,
300 - $this->title->getFullUrl( 'diff=' . $rev->getId() . '&oldid=prev' ),
301 - $rev->getTimestamp(),
302 - $rev->getUserText(),
303 - $this->title->getTalkPage()->getFullUrl()
304 - );
305 - }
306 -}
307 -
308 -/**
309 - * @ingroup Pager
310 - */
311 -class HistoryPager extends ReverseChronologicalPager {
312 - public $lastRow = false, $counter, $historyPage, $title, $buttons, $conds;
313 - protected $oldIdChecked;
314 - protected $preventClickjacking = false;
315 -
316 - function __construct( $historyPage, $year = '', $month = '', $tagFilter = '', $conds = array() ) {
317 - parent::__construct();
318 - $this->historyPage = $historyPage;
319 - $this->title = $this->historyPage->getTitle();
320 - $this->tagFilter = $tagFilter;
321 - $this->getDateCond( $year, $month );
322 - $this->conds = $conds;
323 - }
324 -
325 - // For hook compatibility...
326 - function getArticle() {
327 - return $this->historyPage->getArticle();
328 - }
329 -
330 - function getTitle() {
331 - return $this->title;
332 - }
333 -
334 - function getSqlComment() {
335 - if ( $this->conds ) {
336 - return 'history page filtered'; // potentially slow, see CR r58153
337 - } else {
338 - return 'history page unfiltered';
339 - }
340 - }
341 -
342 - function getQueryInfo() {
343 - $queryInfo = array(
344 - 'tables' => array( 'revision' ),
345 - 'fields' => Revision::selectFields(),
346 - 'conds' => array_merge(
347 - array( 'rev_page' => $this->title->getArticleID() ),
348 - $this->conds ),
349 - 'options' => array( 'USE INDEX' => array( 'revision' => 'page_timestamp' ) ),
350 - 'join_conds' => array( 'tag_summary' => array( 'LEFT JOIN', 'ts_rev_id=rev_id' ) ),
351 - );
352 - ChangeTags::modifyDisplayQuery(
353 - $queryInfo['tables'],
354 - $queryInfo['fields'],
355 - $queryInfo['conds'],
356 - $queryInfo['join_conds'],
357 - $queryInfo['options'],
358 - $this->tagFilter
359 - );
360 - wfRunHooks( 'PageHistoryPager::getQueryInfo', array( &$this, &$queryInfo ) );
361 - return $queryInfo;
362 - }
363 -
364 - function getIndexField() {
365 - return 'rev_timestamp';
366 - }
367 -
368 - function formatRow( $row ) {
369 - if ( $this->lastRow ) {
370 - $latest = ( $this->counter == 1 && $this->mIsFirst );
371 - $firstInList = $this->counter == 1;
372 - $this->counter++;
373 - $s = $this->historyLine( $this->lastRow, $row,
374 - $this->title->getNotificationTimestamp(), $latest, $firstInList );
375 - } else {
376 - $s = '';
377 - }
378 - $this->lastRow = $row;
379 - return $s;
380 - }
381 -
382 - /**
383 - * Creates begin of history list with a submit button
384 - *
385 - * @return string HTML output
386 - */
387 - function getStartBody() {
388 - global $wgScript, $wgUser, $wgOut;
389 - $this->lastRow = false;
390 - $this->counter = 1;
391 - $this->oldIdChecked = 0;
392 -
393 - $wgOut->wrapWikiMsg( "<div class='mw-history-legend'>\n$1\n</div>", 'histlegend' );
394 - $s = Html::openElement( 'form', array( 'action' => $wgScript,
395 - 'id' => 'mw-history-compare' ) ) . "\n";
396 - $s .= Html::hidden( 'title', $this->title->getPrefixedDbKey() ) . "\n";
397 - $s .= Html::hidden( 'action', 'historysubmit' ) . "\n";
398 -
399 - $s .= '<div>' . $this->submitButton( wfMsg( 'compareselectedversions' ),
400 - array( 'class' => 'historysubmit' ) ) . "\n";
401 -
402 - $this->buttons = '<div>';
403 - $this->buttons .= $this->submitButton( wfMsg( 'compareselectedversions' ),
404 - array( 'class' => 'historysubmit' )
405 - + Linker::tooltipAndAccesskeyAttribs( 'compareselectedversions' )
406 - ) . "\n";
407 -
408 - if ( $wgUser->isAllowed( 'deleterevision' ) ) {
409 - $s .= $this->getRevisionButton( 'revisiondelete', 'showhideselectedversions' );
410 - }
411 - $this->buttons .= '</div>';
412 - $s .= '</div><ul id="pagehistory">' . "\n";
413 - return $s;
414 - }
415 -
416 - private function getRevisionButton( $name, $msg ) {
417 - $this->preventClickjacking();
418 - # Note bug #20966, <button> is non-standard in IE<8
419 - $element = Html::element( 'button',
420 - array(
421 - 'type' => 'submit',
422 - 'name' => $name,
423 - 'value' => '1',
424 - 'class' => "mw-history-$name-button mw-float-end",
425 - ),
426 - wfMsg( $msg )
427 - ) . "\n";
428 - $this->buttons .= $element;
429 - return $element;
430 - }
431 -
432 - function getEndBody() {
433 - if ( $this->lastRow ) {
434 - $latest = $this->counter == 1 && $this->mIsFirst;
435 - $firstInList = $this->counter == 1;
436 - if ( $this->mIsBackwards ) {
437 - # Next row is unknown, but for UI reasons, probably exists if an offset has been specified
438 - if ( $this->mOffset == '' ) {
439 - $next = null;
440 - } else {
441 - $next = 'unknown';
442 - }
443 - } else {
444 - # The next row is the past-the-end row
445 - $next = $this->mPastTheEndRow;
446 - }
447 - $this->counter++;
448 - $s = $this->historyLine( $this->lastRow, $next,
449 - $this->title->getNotificationTimestamp(), $latest, $firstInList );
450 - } else {
451 - $s = '';
452 - }
453 - $s .= "</ul>\n";
454 - # Add second buttons only if there is more than one rev
455 - if ( $this->getNumRows() > 2 ) {
456 - $s .= $this->buttons;
457 - }
458 - $s .= '</form>';
459 - return $s;
460 - }
461 -
462 - /**
463 - * Creates a submit button
464 - *
465 - * @param $message String: text of the submit button, will be escaped
466 - * @param $attributes Array: attributes
467 - * @return String: HTML output for the submit button
468 - */
469 - function submitButton( $message, $attributes = array() ) {
470 - # Disable submit button if history has 1 revision only
471 - if ( $this->getNumRows() > 1 ) {
472 - return Xml::submitButton( $message , $attributes );
473 - } else {
474 - return '';
475 - }
476 - }
477 -
478 - /**
479 - * Returns a row from the history printout.
480 - *
481 - * @todo document some more, and maybe clean up the code (some params redundant?)
482 - *
483 - * @param $row Object: the database row corresponding to the previous line.
484 - * @param $next Mixed: the database row corresponding to the next line.
485 - * @param $notificationtimestamp
486 - * @param $latest Boolean: whether this row corresponds to the page's latest revision.
487 - * @param $firstInList Boolean: whether this row corresponds to the first displayed on this history page.
488 - * @return String: HTML output for the row
489 - */
490 - function historyLine( $row, $next, $notificationtimestamp = false,
491 - $latest = false, $firstInList = false )
492 - {
493 - global $wgUser, $wgLang;
494 - $rev = new Revision( $row );
495 - $rev->setTitle( $this->title );
496 -
497 - $curlink = $this->curLink( $rev, $latest );
498 - $lastlink = $this->lastLink( $rev, $next );
499 - $diffButtons = $this->diffButtons( $rev, $firstInList );
500 - $histLinks = Html::rawElement(
501 - 'span',
502 - array( 'class' => 'mw-history-histlinks' ),
503 - '(' . $curlink . $this->historyPage->message['pipe-separator'] . $lastlink . ') '
504 - );
505 - $s = $histLinks . $diffButtons;
506 -
507 - $link = $this->revLink( $rev );
508 - $classes = array();
509 -
510 - $del = '';
511 - // Show checkboxes for each revision
512 - if ( $wgUser->isAllowed( 'deleterevision' ) ) {
513 - $this->preventClickjacking();
514 - // If revision was hidden from sysops, disable the checkbox
515 - if ( !$rev->userCan( Revision::DELETED_RESTRICTED ) ) {
516 - $del = Xml::check( 'deleterevisions', false, array( 'disabled' => 'disabled' ) );
517 - // Otherwise, enable the checkbox...
518 - } else {
519 - $del = Xml::check( 'showhiderevisions', false,
520 - array( 'name' => 'ids[' . $rev->getId() . ']' ) );
521 - }
522 - // User can only view deleted revisions...
523 - } elseif ( $rev->getVisibility() && $wgUser->isAllowed( 'deletedhistory' ) ) {
524 - // If revision was hidden from sysops, disable the link
525 - if ( !$rev->userCan( Revision::DELETED_RESTRICTED ) ) {
526 - $cdel = $this->getSkin()->revDeleteLinkDisabled( false );
527 - // Otherwise, show the link...
528 - } else {
529 - $query = array( 'type' => 'revision',
530 - 'target' => $this->title->getPrefixedDbkey(), 'ids' => $rev->getId() );
531 - $del .= $this->getSkin()->revDeleteLink( $query,
532 - $rev->isDeleted( Revision::DELETED_RESTRICTED ), false );
533 - }
534 - }
535 - if ( $del ) {
536 - $s .= " $del ";
537 - }
538 -
539 - $dirmark = $wgLang->getDirMark();
540 -
541 - $s .= " $link";
542 - $s .= $dirmark;
543 - $s .= " <span class='history-user'>" .
544 - $this->getSkin()->revUserTools( $rev, true ) . "</span>";
545 - $s .= $dirmark;
546 -
547 - if ( $rev->isMinor() ) {
548 - $s .= ' ' . ChangesList::flag( 'minor' );
549 - }
550 -
551 - if ( !is_null( $size = $rev->getSize() ) && !$rev->isDeleted( Revision::DELETED_TEXT ) ) {
552 - $s .= ' ' . $this->getSkin()->formatRevisionSize( $size );
553 - }
554 -
555 - $s .= $this->getSkin()->revComment( $rev, false, true );
556 -
557 - if ( $notificationtimestamp && ( $row->rev_timestamp >= $notificationtimestamp ) ) {
558 - $s .= ' <span class="updatedmarker">' . wfMsgHtml( 'updatedmarker' ) . '</span>';
559 - }
560 -
561 - $tools = array();
562 -
563 - # Rollback and undo links
564 - if ( !is_null( $next ) && is_object( $next ) ) {
565 - if ( $latest && $this->title->userCan( 'rollback' ) && $this->title->userCan( 'edit' ) ) {
566 - $this->preventClickjacking();
567 - $tools[] = '<span class="mw-rollback-link">' .
568 - $this->getSkin()->buildRollbackLink( $rev ) . '</span>';
569 - }
570 -
571 - if ( $this->title->quickUserCan( 'edit' )
572 - && !$rev->isDeleted( Revision::DELETED_TEXT )
573 - && !$next->rev_deleted & Revision::DELETED_TEXT )
574 - {
575 - # Create undo tooltip for the first (=latest) line only
576 - $undoTooltip = $latest
577 - ? array( 'title' => wfMsg( 'tooltip-undo' ) )
578 - : array();
579 - $undolink = $this->getSkin()->link(
580 - $this->title,
581 - wfMsgHtml( 'editundo' ),
582 - $undoTooltip,
583 - array(
584 - 'action' => 'edit',
585 - 'undoafter' => $next->rev_id,
586 - 'undo' => $rev->getId()
587 - ),
588 - array( 'known', 'noclasses' )
589 - );
590 - $tools[] = "<span class=\"mw-history-undo\">{$undolink}</span>";
591 - }
592 - }
593 -
594 - if ( $tools ) {
595 - $s .= ' (' . $wgLang->pipeList( $tools ) . ')';
596 - }
597 -
598 - # Tags
599 - list( $tagSummary, $newClasses ) = ChangeTags::formatSummaryRow( $row->ts_tags, 'history' );
600 - $classes = array_merge( $classes, $newClasses );
601 - $s .= " $tagSummary";
602 -
603 - wfRunHooks( 'PageHistoryLineEnding', array( $this, &$row , &$s, &$classes ) );
604 -
605 - $attribs = array();
606 - if ( $classes ) {
607 - $attribs['class'] = implode( ' ', $classes );
608 - }
609 -
610 - return Xml::tags( 'li', $attribs, $s ) . "\n";
611 - }
612 -
613 - /**
614 - * Create a link to view this revision of the page
615 - *
616 - * @param $rev Revision
617 - * @return String
618 - */
619 - function revLink( $rev ) {
620 - global $wgLang;
621 - $date = $wgLang->timeanddate( wfTimestamp( TS_MW, $rev->getTimestamp() ), true );
622 - $date = htmlspecialchars( $date );
623 - if ( $rev->userCan( Revision::DELETED_TEXT ) ) {
624 - $link = $this->getSkin()->link(
625 - $this->title,
626 - $date,
627 - array(),
628 - array( 'oldid' => $rev->getId() ),
629 - array( 'known', 'noclasses' )
630 - );
631 - } else {
632 - $link = $date;
633 - }
634 - if ( $rev->isDeleted( Revision::DELETED_TEXT ) ) {
635 - $link = "<span class=\"history-deleted\">$link</span>";
636 - }
637 - return $link;
638 - }
639 -
640 - /**
641 - * Create a diff-to-current link for this revision for this page
642 - *
643 - * @param $rev Revision
644 - * @param $latest Boolean: this is the latest revision of the page?
645 - * @return String
646 - */
647 - function curLink( $rev, $latest ) {
648 - $cur = $this->historyPage->message['cur'];
649 - if ( $latest || !$rev->userCan( Revision::DELETED_TEXT ) ) {
650 - return $cur;
651 - } else {
652 - return $this->getSkin()->link(
653 - $this->title,
654 - $cur,
655 - array(),
656 - array(
657 - 'diff' => $this->title->getLatestRevID(),
658 - 'oldid' => $rev->getId()
659 - ),
660 - array( 'known', 'noclasses' )
661 - );
662 - }
663 - }
664 -
665 - /**
666 - * Create a diff-to-previous link for this revision for this page.
667 - *
668 - * @param $prevRev Revision: the previous revision
669 - * @param $next Mixed: the newer revision
670 - * @return String
671 - */
672 - function lastLink( $prevRev, $next ) {
673 - $last = $this->historyPage->message['last'];
674 - # $next may either be a Row, null, or "unkown"
675 - $nextRev = is_object( $next ) ? new Revision( $next ) : $next;
676 - if ( is_null( $next ) ) {
677 - # Probably no next row
678 - return $last;
679 - } elseif ( $next === 'unknown' ) {
680 - # Next row probably exists but is unknown, use an oldid=prev link
681 - return $this->getSkin()->link(
682 - $this->title,
683 - $last,
684 - array(),
685 - array(
686 - 'diff' => $prevRev->getId(),
687 - 'oldid' => 'prev'
688 - ),
689 - array( 'known', 'noclasses' )
690 - );
691 - } elseif ( !$prevRev->userCan( Revision::DELETED_TEXT )
692 - || !$nextRev->userCan( Revision::DELETED_TEXT ) )
693 - {
694 - return $last;
695 - } else {
696 - return $this->getSkin()->link(
697 - $this->title,
698 - $last,
699 - array(),
700 - array(
701 - 'diff' => $prevRev->getId(),
702 - 'oldid' => $next->rev_id
703 - ),
704 - array( 'known', 'noclasses' )
705 - );
706 - }
707 - }
708 -
709 - /**
710 - * Create radio buttons for page history
711 - *
712 - * @param $rev Revision object
713 - * @param $firstInList Boolean: is this version the first one?
714 - *
715 - * @return String: HTML output for the radio buttons
716 - */
717 - function diffButtons( $rev, $firstInList ) {
718 - if ( $this->getNumRows() > 1 ) {
719 - $id = $rev->getId();
720 - $radio = array( 'type' => 'radio', 'value' => $id );
721 - /** @todo: move title texts to javascript */
722 - if ( $firstInList ) {
723 - $first = Xml::element( 'input',
724 - array_merge( $radio, array(
725 - 'style' => 'visibility:hidden',
726 - 'name' => 'oldid',
727 - 'id' => 'mw-oldid-null' ) )
728 - );
729 - $checkmark = array( 'checked' => 'checked' );
730 - } else {
731 - # Check visibility of old revisions
732 - if ( !$rev->userCan( Revision::DELETED_TEXT ) ) {
733 - $radio['disabled'] = 'disabled';
734 - $checkmark = array(); // We will check the next possible one
735 - } elseif ( !$this->oldIdChecked ) {
736 - $checkmark = array( 'checked' => 'checked' );
737 - $this->oldIdChecked = $id;
738 - } else {
739 - $checkmark = array();
740 - }
741 - $first = Xml::element( 'input',
742 - array_merge( $radio, $checkmark, array(
743 - 'name' => 'oldid',
744 - 'id' => "mw-oldid-$id" ) ) );
745 - $checkmark = array();
746 - }
747 - $second = Xml::element( 'input',
748 - array_merge( $radio, $checkmark, array(
749 - 'name' => 'diff',
750 - 'id' => "mw-diff-$id" ) ) );
751 - return $first . $second;
752 - } else {
753 - return '';
754 - }
755 - }
756 -
757 - /**
758 - * This is called if a write operation is possible from the generated HTML
759 - */
760 - function preventClickjacking( $enable = true ) {
761 - $this->preventClickjacking = $enable;
762 - }
763 -
764 - /**
765 - * Get the "prevent clickjacking" flag
766 - */
767 - function getPreventClickjacking() {
768 - return $this->preventClickjacking;
769 - }
770 -}
771 -
772 -/**
773 - * Backwards-compatibility aliases
774 - */
775 -class PageHistory extends HistoryPage {}
776 -class PageHistoryPager extends HistoryPager {}
Index: trunk/phase3/includes/actions/HistoryAction.php
@@ -0,0 +1,756 @@
 2+<?php
 3+/**
 4+ * Handles action=history.
 5+ * Split off from Article.php and Skin.php, 2003-12-22
 6+ *
 7+ * @file
 8+ */
 9+
 10+/**
 11+ * This class handles printing the history page for an article. In order to
 12+ * be efficient, it uses timestamps rather than offsets for paging, to avoid
 13+ * costly LIMIT,offset queries.
 14+ */
 15+class HistoryAction extends FormlessAction {
 16+ const DIR_PREV = 0;
 17+ const DIR_NEXT = 1;
 18+
 19+ public function getName() {
 20+ return 'history';
 21+ }
 22+
 23+ public function getRestriction() {
 24+ return 'read';
 25+ }
 26+
 27+ /**
 28+ * Get the Page object we are working on.
 29+ *
 30+ * @return Page
 31+ */
 32+ public function getArticle() {
 33+ return $this->page;
 34+ }
 35+
 36+ /**
 37+ * As we use the same small set of messages in various methods and that
 38+ * they are called often, we call them once and save them in $this->message
 39+ */
 40+ private function preCacheMessages() {
 41+ // Precache various messages
 42+ if ( !isset( $this->message ) ) {
 43+ $msgs = array( 'cur', 'last', 'pipe-separator' );
 44+ foreach ( $msgs as $msg ) {
 45+ $this->message[$msg] = wfMsgExt( $msg, array( 'escapenoentities' ) );
 46+ }
 47+ }
 48+ }
 49+
 50+ /**
 51+ * Print the history page for an article.
 52+ * @return nothing
 53+ */
 54+ function onView() {
 55+ global $wgSquidMaxage, $wgScript;
 56+
 57+ $out = $this->getOutput();
 58+ $request = $this->getRequest();
 59+
 60+ // Allow client caching.
 61+ if ( $out->checkLastModified( $this->page->getTouched() ) ) {
 62+ return; // Client cache fresh and headers sent, nothing more to do.
 63+ }
 64+
 65+ if ( $request->getFullRequestURL() == $this->getTitle()->getInternalURL( 'action=history' ) ) {
 66+ $out->setSquidMaxage( $wgSquidMaxage );
 67+ }
 68+
 69+ wfProfileIn( __METHOD__ );
 70+
 71+ $this->preCacheMessages();
 72+
 73+ // Setup page variables.
 74+ $out->setPageTitle( wfMsg( 'history-title', $this->getTitle()->getPrefixedText() ) );
 75+ $out->setRobotPolicy( 'noindex,nofollow' );
 76+ $out->setFeedAppendQuery( 'action=history' );
 77+ $out->addModules( array( 'mediawiki.legacy.history', 'mediawiki.action.history' ) );
 78+
 79+ // Creation of a subtitle link pointing to [[Special:Log]]
 80+ $logPage = SpecialPage::getTitleFor( 'Log' );
 81+ $logLink = Linker::linkKnown(
 82+ $logPage,
 83+ wfMsgHtml( 'viewpagelogs' ),
 84+ array(),
 85+ array( 'page' => $this->getTitle()->getPrefixedText() )
 86+ );
 87+ $out->setSubtitle( $logLink );
 88+
 89+ // Handle atom/RSS feeds.
 90+ $feedType = $request->getVal( 'feed' );
 91+ if ( $feedType ) {
 92+ wfProfileOut( __METHOD__ );
 93+ return $this->feed( $feedType );
 94+ }
 95+
 96+ // Fail nicely if article doesn't exist.
 97+ if ( !$this->getTitle()->exists() ) {
 98+ $out->addWikiMsg( 'nohistory' );
 99+ # show deletion/move log if there is an entry
 100+ LogEventsList::showLogExtract(
 101+ $out,
 102+ array( 'delete', 'move' ),
 103+ $this->getTitle()->getPrefixedText(),
 104+ '',
 105+ array( 'lim' => 10,
 106+ 'conds' => array( "log_action != 'revision'" ),
 107+ 'showIfEmpty' => false,
 108+ 'msgKey' => array( 'moveddeleted-notice' )
 109+ )
 110+ );
 111+ wfProfileOut( __METHOD__ );
 112+ return;
 113+ }
 114+
 115+ /**
 116+ * Add date selector to quickly get to a certain time
 117+ */
 118+ $year = $request->getInt( 'year' );
 119+ $month = $request->getInt( 'month' );
 120+ $tagFilter = $request->getVal( 'tagfilter' );
 121+ $tagSelector = ChangeTags::buildTagFilterSelector( $tagFilter );
 122+
 123+ /**
 124+ * Option to show only revisions that have been (partially) hidden via RevisionDelete
 125+ */
 126+ if ( $request->getBool( 'deleted' ) ) {
 127+ $conds = array( "rev_deleted != '0'" );
 128+ } else {
 129+ $conds = array();
 130+ }
 131+ $checkDeleted = Xml::checkLabel( wfMsg( 'history-show-deleted' ),
 132+ 'deleted', 'mw-show-deleted-only', $request->getBool( 'deleted' ) ) . "\n";
 133+
 134+ // Add the general form
 135+ $action = htmlspecialchars( $wgScript );
 136+ $out->addHTML(
 137+ "<form action=\"$action\" method=\"get\" id=\"mw-history-searchform\">" .
 138+ Xml::fieldset(
 139+ wfMsg( 'history-fieldset-title' ),
 140+ false,
 141+ array( 'id' => 'mw-history-search' )
 142+ ) .
 143+ Html::hidden( 'title', $this->getTitle()->getPrefixedDBKey() ) . "\n" .
 144+ Html::hidden( 'action', 'history' ) . "\n" .
 145+ Xml::dateMenu( $year, $month ) . '&#160;' .
 146+ ( $tagSelector ? ( implode( '&#160;', $tagSelector ) . '&#160;' ) : '' ) .
 147+ $checkDeleted .
 148+ Xml::submitButton( wfMsg( 'allpagessubmit' ) ) . "\n" .
 149+ '</fieldset></form>'
 150+ );
 151+
 152+ wfRunHooks( 'PageHistoryBeforeList', array( &$this->page ) );
 153+
 154+ // Create and output the list.
 155+ $pager = new HistoryPager( $this, $year, $month, $tagFilter, $conds );
 156+ $out->addHTML(
 157+ $pager->getNavigationBar() .
 158+ $pager->getBody() .
 159+ $pager->getNavigationBar()
 160+ );
 161+ $out->preventClickjacking( $pager->getPreventClickjacking() );
 162+
 163+ wfProfileOut( __METHOD__ );
 164+ }
 165+
 166+ /**
 167+ * Fetch an array of revisions, specified by a given limit, offset and
 168+ * direction. This is now only used by the feeds. It was previously
 169+ * used by the main UI but that's now handled by the pager.
 170+ *
 171+ * @param $limit Integer: the limit number of revisions to get
 172+ * @param $offset Integer
 173+ * @param $direction Integer: either HistoryAction::DIR_PREV or HistoryAction::DIR_NEXT
 174+ * @return ResultWrapper
 175+ */
 176+ function fetchRevisions( $limit, $offset, $direction ) {
 177+ $dbr = wfGetDB( DB_SLAVE );
 178+
 179+ if ( $direction == self::DIR_PREV ) {
 180+ list( $dirs, $oper ) = array( "ASC", ">=" );
 181+ } else { /* $direction == self::DIR_NEXT */
 182+ list( $dirs, $oper ) = array( "DESC", "<=" );
 183+ }
 184+
 185+ if ( $offset ) {
 186+ $offsets = array( "rev_timestamp $oper '$offset'" );
 187+ } else {
 188+ $offsets = array();
 189+ }
 190+
 191+ $page_id = $this->getTitle()->getArticleID();
 192+
 193+ return $dbr->select( 'revision',
 194+ Revision::selectFields(),
 195+ array_merge( array( "rev_page=$page_id" ), $offsets ),
 196+ __METHOD__,
 197+ array( 'ORDER BY' => "rev_timestamp $dirs",
 198+ 'USE INDEX' => 'page_timestamp', 'LIMIT' => $limit )
 199+ );
 200+ }
 201+
 202+ /**
 203+ * Output a subscription feed listing recent edits to this page.
 204+ *
 205+ * @param $type String: feed type
 206+ */
 207+ function feed( $type ) {
 208+ global $wgFeedClasses, $wgFeedLimit;
 209+ if ( !FeedUtils::checkFeedOutput( $type ) ) {
 210+ return;
 211+ }
 212+
 213+ $feed = new $wgFeedClasses[$type](
 214+ $this->getTitle()->getPrefixedText() . ' - ' .
 215+ wfMsgForContent( 'history-feed-title' ),
 216+ wfMsgForContent( 'history-feed-description' ),
 217+ $this->getTitle()->getFullUrl( 'action=history' )
 218+ );
 219+
 220+ // Get a limit on number of feed entries. Provide a sane default
 221+ // of 10 if none is defined (but limit to $wgFeedLimit max)
 222+ $limit = $this->getRequest()->getInt( 'limit', 10 );
 223+ if ( $limit > $wgFeedLimit || $limit < 1 ) {
 224+ $limit = 10;
 225+ }
 226+ $items = $this->fetchRevisions( $limit, 0, self::DIR_NEXT );
 227+
 228+ // Generate feed elements enclosed between header and footer.
 229+ $feed->outHeader();
 230+ if ( $items->numRows() ) {
 231+ foreach ( $items as $row ) {
 232+ $feed->outItem( $this->feedItem( $row ) );
 233+ }
 234+ } else {
 235+ $feed->outItem( $this->feedEmpty() );
 236+ }
 237+ $feed->outFooter();
 238+ }
 239+
 240+ function feedEmpty() {
 241+ return new FeedItem(
 242+ wfMsgForContent( 'nohistory' ),
 243+ $this->getOutput()->parse( wfMsgForContent( 'history-feed-empty' ) ),
 244+ $this->getTitle()->getFullUrl(),
 245+ wfTimestamp( TS_MW ),
 246+ '',
 247+ $this->getTitle()->getTalkPage()->getFullUrl()
 248+ );
 249+ }
 250+
 251+ /**
 252+ * Generate a FeedItem object from a given revision table row
 253+ * Borrows Recent Changes' feed generation functions for formatting;
 254+ * includes a diff to the previous revision (if any).
 255+ *
 256+ * @param $row Object: database row
 257+ * @return FeedItem
 258+ */
 259+ function feedItem( $row ) {
 260+ $rev = new Revision( $row );
 261+ $titleObj = $this->getTitle();
 262+ $rev->setTitle( $titleObj );
 263+ $text = FeedUtils::formatDiffRow(
 264+ $titleObj,
 265+ $titleObj->getPreviousRevisionID( $rev->getId() ),
 266+ $rev->getId(),
 267+ $rev->getTimestamp(),
 268+ $rev->getComment()
 269+ );
 270+ if ( $rev->getComment() == '' ) {
 271+ global $wgContLang;
 272+ $title = wfMsgForContent( 'history-feed-item-nocomment',
 273+ $rev->getUserText(),
 274+ $wgContLang->timeanddate( $rev->getTimestamp() ),
 275+ $wgContLang->date( $rev->getTimestamp() ),
 276+ $wgContLang->time( $rev->getTimestamp() )
 277+ );
 278+ } else {
 279+ $title = $rev->getUserText() .
 280+ wfMsgForContent( 'colon-separator' ) .
 281+ FeedItem::stripComment( $rev->getComment() );
 282+ }
 283+ return new FeedItem(
 284+ $title,
 285+ $text,
 286+ $titleObj->getFullUrl( 'diff=' . $rev->getId() . '&oldid=prev' ),
 287+ $rev->getTimestamp(),
 288+ $rev->getUserText(),
 289+ $titleObj->getTalkPage()->getFullUrl()
 290+ );
 291+ }
 292+}
 293+
 294+/**
 295+ * @ingroup Pager
 296+ */
 297+class HistoryPager extends ReverseChronologicalPager {
 298+ public $lastRow = false, $counter, $history, $buttons, $conds;
 299+ protected $oldIdChecked;
 300+ protected $preventClickjacking = false;
 301+
 302+ function __construct( $history, $year = '', $month = '', $tagFilter = '', $conds = array() ) {
 303+ parent::__construct();
 304+ $this->history = $history;
 305+ $this->tagFilter = $tagFilter;
 306+ $this->getDateCond( $year, $month );
 307+ $this->conds = $conds;
 308+ }
 309+
 310+ // For hook compatibility...
 311+ function getArticle() {
 312+ return $this->history->getArticle();
 313+ }
 314+
 315+ function getSqlComment() {
 316+ if ( $this->conds ) {
 317+ return 'history page filtered'; // potentially slow, see CR r58153
 318+ } else {
 319+ return 'history page unfiltered';
 320+ }
 321+ }
 322+
 323+ function getQueryInfo() {
 324+ $queryInfo = array(
 325+ 'tables' => array( 'revision' ),
 326+ 'fields' => Revision::selectFields(),
 327+ 'conds' => array_merge(
 328+ array( 'rev_page' => $this->getTitle()->getArticleID() ),
 329+ $this->conds ),
 330+ 'options' => array( 'USE INDEX' => array( 'revision' => 'page_timestamp' ) ),
 331+ 'join_conds' => array( 'tag_summary' => array( 'LEFT JOIN', 'ts_rev_id=rev_id' ) ),
 332+ );
 333+ ChangeTags::modifyDisplayQuery(
 334+ $queryInfo['tables'],
 335+ $queryInfo['fields'],
 336+ $queryInfo['conds'],
 337+ $queryInfo['join_conds'],
 338+ $queryInfo['options'],
 339+ $this->tagFilter
 340+ );
 341+ wfRunHooks( 'PageHistoryPager::getQueryInfo', array( &$this, &$queryInfo ) );
 342+ return $queryInfo;
 343+ }
 344+
 345+ function getIndexField() {
 346+ return 'rev_timestamp';
 347+ }
 348+
 349+ function formatRow( $row ) {
 350+ if ( $this->lastRow ) {
 351+ $latest = ( $this->counter == 1 && $this->mIsFirst );
 352+ $firstInList = $this->counter == 1;
 353+ $this->counter++;
 354+ $s = $this->historyLine( $this->lastRow, $row,
 355+ $this->getTitle()->getNotificationTimestamp(), $latest, $firstInList );
 356+ } else {
 357+ $s = '';
 358+ }
 359+ $this->lastRow = $row;
 360+ return $s;
 361+ }
 362+
 363+ /**
 364+ * Creates begin of history list with a submit button
 365+ *
 366+ * @return string HTML output
 367+ */
 368+ function getStartBody() {
 369+ global $wgScript;
 370+ $this->lastRow = false;
 371+ $this->counter = 1;
 372+ $this->oldIdChecked = 0;
 373+
 374+ $this->getOutput()->wrapWikiMsg( "<div class='mw-history-legend'>\n$1\n</div>", 'histlegend' );
 375+ $s = Html::openElement( 'form', array( 'action' => $wgScript,
 376+ 'id' => 'mw-history-compare' ) ) . "\n";
 377+ $s .= Html::hidden( 'title', $this->getTitle()->getPrefixedDbKey() ) . "\n";
 378+ $s .= Html::hidden( 'action', 'historysubmit' ) . "\n";
 379+
 380+ $s .= '<div>' . $this->submitButton( wfMsg( 'compareselectedversions' ),
 381+ array( 'class' => 'historysubmit' ) ) . "\n";
 382+
 383+ $this->buttons = '<div>';
 384+ $this->buttons .= $this->submitButton( wfMsg( 'compareselectedversions' ),
 385+ array( 'class' => 'historysubmit' )
 386+ + Linker::tooltipAndAccesskeyAttribs( 'compareselectedversions' )
 387+ ) . "\n";
 388+
 389+ if ( $this->getUser()->isAllowed( 'deleterevision' ) ) {
 390+ $s .= $this->getRevisionButton( 'revisiondelete', 'showhideselectedversions' );
 391+ }
 392+ $this->buttons .= '</div>';
 393+ $s .= '</div><ul id="pagehistory">' . "\n";
 394+ return $s;
 395+ }
 396+
 397+ private function getRevisionButton( $name, $msg ) {
 398+ $this->preventClickjacking();
 399+ # Note bug #20966, <button> is non-standard in IE<8
 400+ $element = Html::element( 'button',
 401+ array(
 402+ 'type' => 'submit',
 403+ 'name' => $name,
 404+ 'value' => '1',
 405+ 'class' => "mw-history-$name-button mw-float-end",
 406+ ),
 407+ wfMsg( $msg )
 408+ ) . "\n";
 409+ $this->buttons .= $element;
 410+ return $element;
 411+ }
 412+
 413+ function getEndBody() {
 414+ if ( $this->lastRow ) {
 415+ $latest = $this->counter == 1 && $this->mIsFirst;
 416+ $firstInList = $this->counter == 1;
 417+ if ( $this->mIsBackwards ) {
 418+ # Next row is unknown, but for UI reasons, probably exists if an offset has been specified
 419+ if ( $this->mOffset == '' ) {
 420+ $next = null;
 421+ } else {
 422+ $next = 'unknown';
 423+ }
 424+ } else {
 425+ # The next row is the past-the-end row
 426+ $next = $this->mPastTheEndRow;
 427+ }
 428+ $this->counter++;
 429+ $s = $this->historyLine( $this->lastRow, $next,
 430+ $this->getTitle()->getNotificationTimestamp(), $latest, $firstInList );
 431+ } else {
 432+ $s = '';
 433+ }
 434+ $s .= "</ul>\n";
 435+ # Add second buttons only if there is more than one rev
 436+ if ( $this->getNumRows() > 2 ) {
 437+ $s .= $this->buttons;
 438+ }
 439+ $s .= '</form>';
 440+ return $s;
 441+ }
 442+
 443+ /**
 444+ * Creates a submit button
 445+ *
 446+ * @param $message String: text of the submit button, will be escaped
 447+ * @param $attributes Array: attributes
 448+ * @return String: HTML output for the submit button
 449+ */
 450+ function submitButton( $message, $attributes = array() ) {
 451+ # Disable submit button if history has 1 revision only
 452+ if ( $this->getNumRows() > 1 ) {
 453+ return Xml::submitButton( $message , $attributes );
 454+ } else {
 455+ return '';
 456+ }
 457+ }
 458+
 459+ /**
 460+ * Returns a row from the history printout.
 461+ *
 462+ * @todo document some more, and maybe clean up the code (some params redundant?)
 463+ *
 464+ * @param $row Object: the database row corresponding to the previous line.
 465+ * @param $next Mixed: the database row corresponding to the next line.
 466+ * @param $notificationtimestamp
 467+ * @param $latest Boolean: whether this row corresponds to the page's latest revision.
 468+ * @param $firstInList Boolean: whether this row corresponds to the first displayed on this history page.
 469+ * @return String: HTML output for the row
 470+ */
 471+ function historyLine( $row, $next, $notificationtimestamp = false,
 472+ $latest = false, $firstInList = false )
 473+ {
 474+ $rev = new Revision( $row );
 475+ $rev->setTitle( $this->getTitle() );
 476+
 477+ $curlink = $this->curLink( $rev, $latest );
 478+ $lastlink = $this->lastLink( $rev, $next );
 479+ $diffButtons = $this->diffButtons( $rev, $firstInList );
 480+ $histLinks = Html::rawElement(
 481+ 'span',
 482+ array( 'class' => 'mw-history-histlinks' ),
 483+ '(' . $curlink . $this->history->message['pipe-separator'] . $lastlink . ') '
 484+ );
 485+ $s = $histLinks . $diffButtons;
 486+
 487+ $link = $this->revLink( $rev );
 488+ $classes = array();
 489+
 490+ $del = '';
 491+ // Show checkboxes for each revision
 492+ if ( $this->getUser()->isAllowed( 'deleterevision' ) ) {
 493+ $this->preventClickjacking();
 494+ // If revision was hidden from sysops, disable the checkbox
 495+ if ( !$rev->userCan( Revision::DELETED_RESTRICTED ) ) {
 496+ $del = Xml::check( 'deleterevisions', false, array( 'disabled' => 'disabled' ) );
 497+ // Otherwise, enable the checkbox...
 498+ } else {
 499+ $del = Xml::check( 'showhiderevisions', false,
 500+ array( 'name' => 'ids[' . $rev->getId() . ']' ) );
 501+ }
 502+ // User can only view deleted revisions...
 503+ } elseif ( $rev->getVisibility() && $this->getUser()->isAllowed( 'deletedhistory' ) ) {
 504+ // If revision was hidden from sysops, disable the link
 505+ if ( !$rev->userCan( Revision::DELETED_RESTRICTED ) ) {
 506+ $cdel = Linker::revDeleteLinkDisabled( false );
 507+ // Otherwise, show the link...
 508+ } else {
 509+ $query = array( 'type' => 'revision',
 510+ 'target' => $this->getTitle()->getPrefixedDbkey(), 'ids' => $rev->getId() );
 511+ $del .= Linker::revDeleteLink( $query,
 512+ $rev->isDeleted( Revision::DELETED_RESTRICTED ), false );
 513+ }
 514+ }
 515+ if ( $del ) {
 516+ $s .= " $del ";
 517+ }
 518+
 519+ $dirmark = $this->getLang()->getDirMark();
 520+
 521+ $s .= " $link";
 522+ $s .= $dirmark;
 523+ $s .= " <span class='history-user'>" .
 524+ Linker::revUserTools( $rev, true ) . "</span>";
 525+ $s .= $dirmark;
 526+
 527+ if ( $rev->isMinor() ) {
 528+ $s .= ' ' . ChangesList::flag( 'minor' );
 529+ }
 530+
 531+ if ( !is_null( $size = $rev->getSize() ) && !$rev->isDeleted( Revision::DELETED_TEXT ) ) {
 532+ $s .= ' ' . Linker::formatRevisionSize( $size );
 533+ }
 534+
 535+ $s .= Linker::revComment( $rev, false, true );
 536+
 537+ if ( $notificationtimestamp && ( $row->rev_timestamp >= $notificationtimestamp ) ) {
 538+ $s .= ' <span class="updatedmarker">' . wfMsgHtml( 'updatedmarker' ) . '</span>';
 539+ }
 540+
 541+ $tools = array();
 542+
 543+ # Rollback and undo links
 544+ if ( !is_null( $next ) && is_object( $next ) ) {
 545+ if ( $latest && $this->getTitle()->userCan( 'rollback' ) && $this->getTitle()->userCan( 'edit' ) ) {
 546+ $this->preventClickjacking();
 547+ $tools[] = '<span class="mw-rollback-link">' .
 548+ Linker::buildRollbackLink( $rev ) . '</span>';
 549+ }
 550+
 551+ if ( $this->getTitle()->quickUserCan( 'edit' )
 552+ && !$rev->isDeleted( Revision::DELETED_TEXT )
 553+ && !$next->rev_deleted & Revision::DELETED_TEXT )
 554+ {
 555+ # Create undo tooltip for the first (=latest) line only
 556+ $undoTooltip = $latest
 557+ ? array( 'title' => wfMsg( 'tooltip-undo' ) )
 558+ : array();
 559+ $undolink = Linker::linkKnown(
 560+ $this->getTitle(),
 561+ wfMsgHtml( 'editundo' ),
 562+ $undoTooltip,
 563+ array(
 564+ 'action' => 'edit',
 565+ 'undoafter' => $next->rev_id,
 566+ 'undo' => $rev->getId()
 567+ )
 568+ );
 569+ $tools[] = "<span class=\"mw-history-undo\">{$undolink}</span>";
 570+ }
 571+ }
 572+
 573+ if ( $tools ) {
 574+ $s .= ' (' . $this->getLang()->pipeList( $tools ) . ')';
 575+ }
 576+
 577+ # Tags
 578+ list( $tagSummary, $newClasses ) = ChangeTags::formatSummaryRow( $row->ts_tags, 'history' );
 579+ $classes = array_merge( $classes, $newClasses );
 580+ $s .= " $tagSummary";
 581+
 582+ wfRunHooks( 'PageHistoryLineEnding', array( $this, &$row , &$s, &$classes ) );
 583+
 584+ $attribs = array();
 585+ if ( $classes ) {
 586+ $attribs['class'] = implode( ' ', $classes );
 587+ }
 588+
 589+ return Xml::tags( 'li', $attribs, $s ) . "\n";
 590+ }
 591+
 592+ /**
 593+ * Create a link to view this revision of the page
 594+ *
 595+ * @param $rev Revision
 596+ * @return String
 597+ */
 598+ function revLink( $rev ) {
 599+ $date = $this->getLang()->timeanddate( wfTimestamp( TS_MW, $rev->getTimestamp() ), true );
 600+ $date = htmlspecialchars( $date );
 601+ if ( $rev->userCan( Revision::DELETED_TEXT ) ) {
 602+ $link = Linker::linkKnown(
 603+ $this->getTitle(),
 604+ $date,
 605+ array(),
 606+ array( 'oldid' => $rev->getId() )
 607+ );
 608+ } else {
 609+ $link = $date;
 610+ }
 611+ if ( $rev->isDeleted( Revision::DELETED_TEXT ) ) {
 612+ $link = "<span class=\"history-deleted\">$link</span>";
 613+ }
 614+ return $link;
 615+ }
 616+
 617+ /**
 618+ * Create a diff-to-current link for this revision for this page
 619+ *
 620+ * @param $rev Revision
 621+ * @param $latest Boolean: this is the latest revision of the page?
 622+ * @return String
 623+ */
 624+ function curLink( $rev, $latest ) {
 625+ $cur = $this->history->message['cur'];
 626+ if ( $latest || !$rev->userCan( Revision::DELETED_TEXT ) ) {
 627+ return $cur;
 628+ } else {
 629+ return Linker::linkKnown(
 630+ $this->getTitle(),
 631+ $cur,
 632+ array(),
 633+ array(
 634+ 'diff' => $this->getTitle()->getLatestRevID(),
 635+ 'oldid' => $rev->getId()
 636+ )
 637+ );
 638+ }
 639+ }
 640+
 641+ /**
 642+ * Create a diff-to-previous link for this revision for this page.
 643+ *
 644+ * @param $prevRev Revision: the previous revision
 645+ * @param $next Mixed: the newer revision
 646+ * @return String
 647+ */
 648+ function lastLink( $prevRev, $next ) {
 649+ $last = $this->history->message['last'];
 650+ # $next may either be a Row, null, or "unkown"
 651+ $nextRev = is_object( $next ) ? new Revision( $next ) : $next;
 652+ if ( is_null( $next ) ) {
 653+ # Probably no next row
 654+ return $last;
 655+ } elseif ( $next === 'unknown' ) {
 656+ # Next row probably exists but is unknown, use an oldid=prev link
 657+ return Linker::link(
 658+ $this->getTitle(),
 659+ $last,
 660+ array(),
 661+ array(
 662+ 'diff' => $prevRev->getId(),
 663+ 'oldid' => 'prev'
 664+ )
 665+ );
 666+ } elseif ( !$prevRev->userCan( Revision::DELETED_TEXT )
 667+ || !$nextRev->userCan( Revision::DELETED_TEXT ) )
 668+ {
 669+ return $last;
 670+ } else {
 671+ return Linker::linkKnown(
 672+ $this->getTitle(),
 673+ $last,
 674+ array(),
 675+ array(
 676+ 'diff' => $prevRev->getId(),
 677+ 'oldid' => $next->rev_id
 678+ )
 679+ );
 680+ }
 681+ }
 682+
 683+ /**
 684+ * Create radio buttons for page history
 685+ *
 686+ * @param $rev Revision object
 687+ * @param $firstInList Boolean: is this version the first one?
 688+ *
 689+ * @return String: HTML output for the radio buttons
 690+ */
 691+ function diffButtons( $rev, $firstInList ) {
 692+ if ( $this->getNumRows() > 1 ) {
 693+ $id = $rev->getId();
 694+ $radio = array( 'type' => 'radio', 'value' => $id );
 695+ /** @todo: move title texts to javascript */
 696+ if ( $firstInList ) {
 697+ $first = Xml::element( 'input',
 698+ array_merge( $radio, array(
 699+ 'style' => 'visibility:hidden',
 700+ 'name' => 'oldid',
 701+ 'id' => 'mw-oldid-null' ) )
 702+ );
 703+ $checkmark = array( 'checked' => 'checked' );
 704+ } else {
 705+ # Check visibility of old revisions
 706+ if ( !$rev->userCan( Revision::DELETED_TEXT ) ) {
 707+ $radio['disabled'] = 'disabled';
 708+ $checkmark = array(); // We will check the next possible one
 709+ } elseif ( !$this->oldIdChecked ) {
 710+ $checkmark = array( 'checked' => 'checked' );
 711+ $this->oldIdChecked = $id;
 712+ } else {
 713+ $checkmark = array();
 714+ }
 715+ $first = Xml::element( 'input',
 716+ array_merge( $radio, $checkmark, array(
 717+ 'name' => 'oldid',
 718+ 'id' => "mw-oldid-$id" ) ) );
 719+ $checkmark = array();
 720+ }
 721+ $second = Xml::element( 'input',
 722+ array_merge( $radio, $checkmark, array(
 723+ 'name' => 'diff',
 724+ 'id' => "mw-diff-$id" ) ) );
 725+ return $first . $second;
 726+ } else {
 727+ return '';
 728+ }
 729+ }
 730+
 731+ /**
 732+ * This is called if a write operation is possible from the generated HTML
 733+ */
 734+ function preventClickjacking( $enable = true ) {
 735+ $this->preventClickjacking = $enable;
 736+ }
 737+
 738+ /**
 739+ * Get the "prevent clickjacking" flag
 740+ */
 741+ function getPreventClickjacking() {
 742+ return $this->preventClickjacking;
 743+ }
 744+}
 745+
 746+/**
 747+ * Backwards-compatibility aliases
 748+ */
 749+class HistoryPage extends HistoryAction {
 750+ public function __construct( Page $article ) { # Just to make it public
 751+ parent::__construct( $article );
 752+ }
 753+
 754+ public function history() {
 755+ $this->onView();
 756+ }
 757+}
Property changes on: trunk/phase3/includes/actions/HistoryAction.php
___________________________________________________________________
Added: svn:mergeinfo
1758 Merged /branches/resourceloader/phase3/includes/HistoryPage.php:r68366-69676,69678-70682,70684-71999,72001-72255,72257-72305,72307-72342
2759 Merged /branches/REL1_15/phase3/includes/PageHistory.php:r51646
3760 Merged /branches/REL1_17/phase3/includes/HistoryPage.php:r81448
Added: svn:eol-style
4761 + native
Added: svn:keywords
5762 + Author Date Id Revision
Index: trunk/phase3/includes/AutoLoader.php
@@ -93,8 +93,6 @@
9494 'HistoryBlob' => 'includes/HistoryBlob.php',
9595 'HistoryBlobCurStub' => 'includes/HistoryBlob.php',
9696 'HistoryBlobStub' => 'includes/HistoryBlob.php',
97 - 'HistoryPage' => 'includes/HistoryPage.php',
98 - 'HistoryPager' => 'includes/HistoryPage.php',
9997 'Hooks' => 'includes/Hooks.php',
10098 'Html' => 'includes/Html.php',
10199 'HTMLCheckField' => 'includes/HTMLForm.php',
@@ -159,8 +157,6 @@
160158 'OldChangesList' => 'includes/ChangesList.php',
161159 'OutputPage' => 'includes/OutputPage.php',
162160 'Page' => 'includes/WikiPage.php',
163 - 'PageHistory' => 'includes/HistoryPage.php',
164 - 'PageHistoryPager' => 'includes/HistoryPage.php',
165161 'PageQueryPage' => 'includes/PageQueryPage.php',
166162 'Pager' => 'includes/Pager.php',
167163 'PasswordError' => 'includes/User.php',
@@ -258,6 +254,9 @@
259255 # includes/actions
260256 'CreditsAction' => 'includes/actions/CreditsAction.php',
261257 'DeletetrackbackAction' => 'includes/actions/DeletetrackbackAction.php',
 258+ 'HistoryAction' => 'includes/actions/HistoryAction.php',
 259+ 'HistoryPage' => 'includes/actions/HistoryAction.php',
 260+ 'HistoryPager' => 'includes/actions/HistoryAction.php',
262261 'InfoAction' => 'includes/actions/InfoAction.php',
263262 'MarkpatrolledAction' => 'includes/actions/MarkpatrolledAction.php',
264263 'PurgeAction' => 'includes/actions/PurgeAction.php',
Index: trunk/phase3/includes/Wiki.php
@@ -503,13 +503,6 @@
504504 }
505505 }
506506 break;
507 - case 'history':
508 - if ( $request->getFullRequestURL() == $title->getInternalURL( 'action=history' ) ) {
509 - $output->setSquidMaxage( $wgSquidMaxage );
510 - }
511 - $history = new HistoryPage( $article );
512 - $history->history();
513 - break;
514507 default:
515508 if ( wfRunHooks( 'UnknownAction', array( $act, $article ) ) ) {
516509 $output->showErrorPage( 'nosuchaction', 'nosuchactiontext' );
Index: trunk/phase3/includes/DefaultSettings.php
@@ -5135,6 +5135,7 @@
51365136 $wgActions = array(
51375137 'credits' => true,
51385138 'deletetrackback' => true,
 5139+ 'history' => true,
51395140 'info' => true,
51405141 'markpatrolled' => true,
51415142 'purge' => true,

Follow-up revisions

RevisionCommit summaryAuthorDate
r94043Temporaray revert of r94031; forgot that this was depending of some other wor...ialex14:29, 7 August 2011

Comments

#Comment by Aaron Schulz (talk | contribs)   02:52, 7 August 2011

I remember Chad floating the idea of using special pages instead of the Action stuff. Come to thing of it, why aren't we just doing that, especially with setRelevantTitle()?

#Comment by 😂 (talk | contribs)   13:16, 9 August 2011

Can we please make this crap into Special Pages? Leave the action= urls for redirects.

#Comment by Wikinaut (talk | contribs)   14:14, 7 August 2011

Your commit brakes all ( &action=history ) Page history function with:

PHP Fatal error: Call to undefined method HistoryPager::getOutput() in /includes/actions/HistoryAction.php on line 373, referer: index.php/Special:RecentChanges

#Comment by Wikinaut (talk | contribs)   14:27, 7 August 2011

Please revert your commit. Previous to your commit ( rev94030 ), action=history, is working, after your commit it does not.

#Comment by IAlex (talk | contribs)   14:30, 7 August 2011

Done.

Status & tagging log