MediaWiki REL1_34
HistoryAction.php
Go to the documentation of this file.
1<?php
27
39 const DIR_PREV = 0;
40 const DIR_NEXT = 1;
41
43 public $message;
44
45 public function getName() {
46 return 'history';
47 }
48
49 public function requiresWrite() {
50 return false;
51 }
52
53 public function requiresUnblock() {
54 return false;
55 }
56
57 protected function getPageTitle() {
58 return $this->msg( 'history-title', $this->getTitle()->getPrefixedText() )->text();
59 }
60
61 protected function getDescription() {
62 // Creation of a subtitle link pointing to [[Special:Log]]
63 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
64 $subtitle = $linkRenderer->makeKnownLink(
65 SpecialPage::getTitleFor( 'Log' ),
66 $this->msg( 'viewpagelogs' )->text(),
67 [],
68 [ 'page' => $this->getTitle()->getPrefixedText() ]
69 );
70
71 $links = [];
72 // Allow extensions to add more links
73 Hooks::run( 'HistoryPageToolLinks', [ $this->getContext(), $linkRenderer, &$links ] );
74 if ( $links ) {
75 $subtitle .= ''
76 . $this->msg( 'word-separator' )->escaped()
77 . $this->msg( 'parentheses' )
78 ->rawParams( $this->getLanguage()->pipeList( $links ) )
79 ->escaped();
80 }
81 return Html::rawElement( 'div', [ 'class' => 'mw-history-subtitle' ], $subtitle );
82 }
83
87 public function getArticle() {
88 return $this->page;
89 }
90
95 private function preCacheMessages() {
96 // Precache various messages
97 if ( !isset( $this->message ) ) {
98 $this->message = [];
99 $msgs = [ 'cur', 'last', 'pipe-separator' ];
100 foreach ( $msgs as $msg ) {
101 $this->message[$msg] = $this->msg( $msg )->escaped();
102 }
103 }
104 }
105
110 private function getTimestampFromRequest( WebRequest $request ) {
111 // Backwards compatibility checks for URIs with only year and/or month.
112 $year = $request->getInt( 'year' );
113 $month = $request->getInt( 'month' );
114 $day = null;
115 if ( $year !== 0 || $month !== 0 ) {
116 if ( $year === 0 ) {
117 $year = MWTimestamp::getLocalInstance()->format( 'Y' );
118 }
119 if ( $month < 1 || $month > 12 ) {
120 // month is invalid so treat as December (all months)
121 $month = 12;
122 }
123 // month is valid so check day
124 $day = cal_days_in_month( CAL_GREGORIAN, $month, $year );
125
126 // Left pad the months and days
127 $month = str_pad( $month, 2, "0", STR_PAD_LEFT );
128 $day = str_pad( $day, 2, "0", STR_PAD_LEFT );
129 }
130
131 $before = $request->getVal( 'date-range-to' );
132 if ( $before ) {
133 $parts = explode( '-', $before );
134 $year = $parts[0];
135 // check date input is valid
136 if ( count( $parts ) === 3 ) {
137 $month = $parts[1];
138 $day = $parts[2];
139 }
140 }
141 return $year && $month && $day ? $year . '-' . $month . '-' . $day : '';
142 }
143
148 function onView() {
149 $out = $this->getOutput();
150 $request = $this->getRequest();
151
152 // Allow client-side HTTP caching of the history page.
153 // But, always ignore this cache if the (logged-in) user has this page on their watchlist
154 // and has one or more unseen revisions. Otherwise, we might be showing stale update markers.
155 // The Last-Modified for the history page does not change when user's markers are cleared,
156 // so going from "some unseen" to "all seen" would not clear the cache.
157 // But, when all of the revisions are marked as seen, then only way for new unseen revision
158 // markers to appear, is for the page to be edited, which updates page_touched/Last-Modified.
159 if (
160 !$this->hasUnseenRevisionMarkers() &&
161 $out->checkLastModified( $this->page->getTouched() )
162 ) {
163 return null; // Client cache fresh and headers sent, nothing more to do.
164 }
165
166 $this->preCacheMessages();
167 $config = $this->context->getConfig();
168
169 # Fill in the file cache if not set already
170 if ( HTMLFileCache::useFileCache( $this->getContext() ) ) {
171 $cache = new HTMLFileCache( $this->getTitle(), 'history' );
172 if ( !$cache->isCacheGood( /* Assume up to date */ ) ) {
173 ob_start( [ &$cache, 'saveToFileCache' ] );
174 }
175 }
176
177 // Setup page variables.
178 $out->setFeedAppendQuery( 'action=history' );
179 $out->addModules( 'mediawiki.action.history' );
180 $out->addModuleStyles( [
181 'mediawiki.interface.helpers.styles',
182 'mediawiki.action.history.styles',
183 'mediawiki.special.changeslist',
184 ] );
185 if ( $config->get( 'UseMediaWikiUIEverywhere' ) ) {
186 $out = $this->getOutput();
187 $out->addModuleStyles( [
188 'mediawiki.ui.input',
189 'mediawiki.ui.checkbox',
190 ] );
191 }
192
193 // Handle atom/RSS feeds.
194 $feedType = $request->getRawVal( 'feed' );
195 if ( $feedType !== null ) {
196 $this->feed( $feedType );
197 return null;
198 }
199
200 $this->addHelpLink(
201 'https://meta.wikimedia.org/wiki/Special:MyLanguage/Help:Page_history',
202 true
203 );
204
205 // Fail nicely if article doesn't exist.
206 if ( !$this->page->exists() ) {
207 global $wgSend404Code;
208 if ( $wgSend404Code ) {
209 $out->setStatusCode( 404 );
210 }
211 $out->addWikiMsg( 'nohistory' );
212
214
215 # show deletion/move log if there is an entry
217 $out,
218 [ 'delete', 'move', 'protect' ],
219 $this->getTitle(),
220 '',
221 [ 'lim' => 10,
222 'conds' => [ 'log_action != ' . $dbr->addQuotes( 'revision' ) ],
223 'showIfEmpty' => false,
224 'msgKey' => [ 'moveddeleted-notice' ]
225 ]
226 );
227
228 return null;
229 }
230
231 $ts = $this->getTimestampFromRequest( $request );
232 $tagFilter = $request->getVal( 'tagfilter' );
233
237 if ( $request->getBool( 'deleted' ) ) {
238 $conds = [ 'rev_deleted != 0' ];
239 } else {
240 $conds = [];
241 }
242
243 // Add the general form.
244 $fields = [
245 [
246 'name' => 'title',
247 'type' => 'hidden',
248 'default' => $this->getTitle()->getPrefixedDBkey(),
249 ],
250 [
251 'name' => 'action',
252 'type' => 'hidden',
253 'default' => 'history',
254 ],
255 [
256 'type' => 'date',
257 'default' => $ts,
258 'label' => $this->msg( 'date-range-to' )->text(),
259 'name' => 'date-range-to',
260 ],
261 [
262 'label-raw' => $this->msg( 'tag-filter' )->parse(),
263 'type' => 'tagfilter',
264 'id' => 'tagfilter',
265 'name' => 'tagfilter',
266 'value' => $tagFilter,
267 ]
268 ];
269 $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
270 if ( $permissionManager->userHasRight( $this->getUser(), 'deletedhistory' ) ) {
271 $fields[] = [
272 'type' => 'check',
273 'label' => $this->msg( 'history-show-deleted' )->text(),
274 'default' => $request->getBool( 'deleted' ),
275 'name' => 'deleted',
276 ];
277 }
278
279 $out->enableOOUI();
280 $htmlForm = HTMLForm::factory( 'ooui', $fields, $this->getContext() );
281 $htmlForm
282 ->setMethod( 'get' )
283 ->setAction( wfScript() )
284 ->setCollapsibleOptions( true )
285 ->setId( 'mw-history-searchform' )
286 ->setSubmitText( $this->msg( 'historyaction-submit' )->text() )
287 ->setWrapperAttributes( [ 'id' => 'mw-history-search' ] )
288 ->setWrapperLegend( $this->msg( 'history-fieldset-title' )->text() );
289 $htmlForm->loadData();
290
291 $out->addHTML( $htmlForm->getHTML( false ) );
292
293 Hooks::run( 'PageHistoryBeforeList', [ &$this->page, $this->getContext() ] );
294
295 // Create and output the list.
296 $dateComponents = explode( '-', $ts );
297 if ( count( $dateComponents ) > 1 ) {
298 $y = $dateComponents[0];
299 $m = $dateComponents[1];
300 $d = $dateComponents[2];
301 } else {
302 $y = '';
303 $m = '';
304 $d = '';
305 }
306 $pager = new HistoryPager( $this, $y, $m, $tagFilter, $conds, $d );
307 $out->addHTML(
308 $pager->getNavigationBar() .
309 $pager->getBody() .
310 $pager->getNavigationBar()
311 );
312 $out->preventClickjacking( $pager->getPreventClickjacking() );
313
314 return null;
315 }
316
320 private function hasUnseenRevisionMarkers() {
321 return (
322 $this->getContext()->getConfig()->get( 'ShowUpdatedMarker' ) &&
323 $this->getTitle()->getNotificationTimestamp( $this->getUser() )
324 );
325 }
326
337 function fetchRevisions( $limit, $offset, $direction ) {
338 // Fail if article doesn't exist.
339 if ( !$this->getTitle()->exists() ) {
340 return new FakeResultWrapper( [] );
341 }
342
344
345 if ( $direction === self::DIR_PREV ) {
346 list( $dirs, $oper ) = [ "ASC", ">=" ];
347 } else { /* $direction === self::DIR_NEXT */
348 list( $dirs, $oper ) = [ "DESC", "<=" ];
349 }
350
351 if ( $offset ) {
352 $offsets = [ "rev_timestamp $oper " . $dbr->addQuotes( $dbr->timestamp( $offset ) ) ];
353 } else {
354 $offsets = [];
355 }
356
357 $page_id = $this->page->getId();
358
359 $revQuery = Revision::getQueryInfo();
360 return $dbr->select(
361 $revQuery['tables'],
362 $revQuery['fields'],
363 array_merge( [ 'rev_page' => $page_id ], $offsets ),
364 __METHOD__,
365 [
366 'ORDER BY' => "rev_timestamp $dirs",
367 'USE INDEX' => [ 'revision' => 'page_timestamp' ],
368 'LIMIT' => $limit
369 ],
370 $revQuery['joins']
371 );
372 }
373
379 function feed( $type ) {
381 return;
382 }
383 $request = $this->getRequest();
384
385 $feedClasses = $this->context->getConfig()->get( 'FeedClasses' );
387 $feed = new $feedClasses[$type](
388 $this->getTitle()->getPrefixedText() . ' - ' .
389 $this->msg( 'history-feed-title' )->inContentLanguage()->text(),
390 $this->msg( 'history-feed-description' )->inContentLanguage()->text(),
391 $this->getTitle()->getFullURL( 'action=history' )
392 );
393
394 // Get a limit on number of feed entries. Provide a sane default
395 // of 10 if none is defined (but limit to $wgFeedLimit max)
396 $limit = $request->getInt( 'limit', 10 );
397 $limit = min(
398 max( $limit, 1 ),
399 $this->context->getConfig()->get( 'FeedLimit' )
400 );
401
402 $items = $this->fetchRevisions( $limit, 0, self::DIR_NEXT );
403
404 // Generate feed elements enclosed between header and footer.
405 $feed->outHeader();
406 if ( $items->numRows() ) {
407 foreach ( $items as $row ) {
408 $feed->outItem( $this->feedItem( $row ) );
409 }
410 } else {
411 $feed->outItem( $this->feedEmpty() );
412 }
413 $feed->outFooter();
414 }
415
416 function feedEmpty() {
417 return new FeedItem(
418 $this->msg( 'nohistory' )->inContentLanguage()->text(),
419 $this->msg( 'history-feed-empty' )->inContentLanguage()->parseAsBlock(),
420 $this->getTitle()->getFullURL(),
421 wfTimestamp( TS_MW ),
422 '',
423 $this->getTitle()->getTalkPage()->getFullURL()
424 );
425 }
426
435 function feedItem( $row ) {
436 $revisionStore = MediaWikiServices::getInstance()->getRevisionStore();
437 $rev = $revisionStore->newRevisionFromRow( $row, 0, $this->getTitle() );
438 $prevRev = $revisionStore->getPreviousRevision( $rev );
439 $revComment = $rev->getComment() === null ? null : $rev->getComment()->text;
441 $this->getTitle(),
442 $prevRev ? $prevRev->getId() : false,
443 $rev->getId(),
444 $rev->getTimestamp(),
445 $revComment
446 );
447 $revUserText = $rev->getUser() ? $rev->getUser()->getName() : '';
448 if ( $revComment == '' ) {
449 $contLang = MediaWikiServices::getInstance()->getContentLanguage();
450 $title = $this->msg( 'history-feed-item-nocomment',
451 $revUserText,
452 $contLang->timeanddate( $rev->getTimestamp() ),
453 $contLang->date( $rev->getTimestamp() ),
454 $contLang->time( $rev->getTimestamp() )
455 )->inContentLanguage()->text();
456 } else {
457 $title = $revUserText .
458 $this->msg( 'colon-separator' )->inContentLanguage()->text() .
459 FeedItem::stripComment( $revComment );
460 }
461
462 return new FeedItem(
463 $title,
464 $text,
465 $this->getTitle()->getFullURL( 'diff=' . $rev->getId() . '&oldid=prev' ),
466 $rev->getTimestamp(),
467 $revUserText,
468 $this->getTitle()->getTalkPage()->getFullURL()
469 );
470 }
471}
$wgSend404Code
Some web hosts attempt to rewrite all responses with a 404 (not found) status code,...
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
wfScript( $script='index')
Get the path to a specified script file, respecting file extensions; this is a wrapper around $wgScri...
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
$page
Page on which we're performing the action.
Definition Action.php:46
addHelpLink( $to, $overrideBaseUrl=false)
Adds help link with an icon via page indicators.
Definition Action.php:395
getTitle()
Shortcut to get the Title object from the page.
Definition Action.php:247
getContext()
Get the IContextSource in use here.
Definition Action.php:179
getOutput()
Get the OutputPage being used for this instance.
Definition Action.php:208
getUser()
Shortcut to get the User being used for this instance.
Definition Action.php:218
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
Definition Action.php:259
static exists( $name)
Check if a given action is recognised, even if it's disabled.
Definition Action.php:170
$fields
The fields used to create the HTMLForm.
Definition Action.php:60
getLanguage()
Shortcut to get the user Language being used for this instance.
Definition Action.php:237
getRequest()
Get the WebRequest being used for this instance.
Definition Action.php:198
A base class for outputting syndication feeds (e.g.
Definition FeedItem.php:33
static stripComment( $text)
Quickie hack... strip out wikilinks to more legible form from the comment.
Definition FeedItem.php:218
static checkFeedOutput( $type)
Check whether feeds can be used and that $type is a valid feed type.
Definition FeedUtils.php:39
static formatDiffRow( $title, $oldid, $newid, $timestamp, $comment, $actiontext='')
Really format a diff for the newsfeed.
Definition FeedUtils.php:91
An action which just does something, without showing a form first.
Page view caching in the file system.
static useFileCache(IContextSource $context, $mode=self::MODE_NORMAL)
Check if pages can be cached for this request/user.
This class handles printing the history page for an article.
onView()
Print the history page for an article.
feed( $type)
Output a subscription feed listing recent edits to this page.
preCacheMessages()
As we use the same small set of messages in various methods and that they are called often,...
array $message
Array of message keys and strings.
getName()
Return the name of the action this object responds to.
requiresWrite()
Whether this action requires the wiki not to be locked.
getPageTitle()
Returns the name that goes in the <h1> page title.
fetchRevisions( $limit, $offset, $direction)
Fetch an array of revisions, specified by a given limit, offset and direction.
feedItem( $row)
Generate a FeedItem object from a given revision table row Borrows Recent Changes' feed generation fu...
getTimestampFromRequest(WebRequest $request)
getDescription()
Returns the description that goes below the <h1> tag.
requiresUnblock()
Whether this action can still be executed by a blocked user.
static showLogExtract(&$out, $types=[], $page='', $user='', $param=[])
Show log extract.
MediaWikiServices is the service locator for the application scope of MediaWiki.
The WebRequest class encapsulates getting at data passed in the URL or via a POSTed form stripping il...
getVal( $name, $default=null)
Fetch a scalar from the input or return $default if it's not set.
getInt( $name, $default=0)
Fetch an integer value from the input or return $default if not set.
Overloads the relevant methods of the real ResultsWrapper so it doesn't go anywhere near an actual da...
Result wrapper for grabbing data queried from an IDatabase object.
$cache
Definition mcc.php:33
const DB_REPLICA
Definition defines.php:25