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