r100534 MediaWiki - Code Review archive

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

Follow-up revisions

RevisionCommit summaryAuthorDate
r105162Fix PageHistoryBeforeList hook...hashar10:56, 5 December 2011
r105186Fix PageHistoryBeforeList hook...hashar16:15, 5 December 2011

Comments

#Comment by IAlex (talk | contribs)   09:24, 23 October 2011

And I forgot to mention that this was primarily a recommit of r94031.

#Comment by Aaron Schulz (talk | contribs)   09:53, 23 October 2011

Until we resolve http://www.mediawiki.org/wiki/Requests_for_comment/Drop_actions_in_favour_of_page_views_and_special_pages it might be best not to convert anymore classes after this one for now.

#Comment by Yaron Koren (talk | contribs)   23:02, 30 November 2011

This revision looks like it broke the 'PageHistoryBeforeList' hook, which is still called with the field "$this->article" even though that field seems to have been removed. Hopefully there's an easy fix.

#Comment by Hashar (talk | contribs)   10:57, 5 December 2011

r105162 should fix it.

#Comment by Yaron Koren (talk | contribs)   20:53, 16 December 2011

Yes, that fixed it - thanks.

Status & tagging log