49 private const DIR_PREV = 0;
50 private const DIR_NEXT = 1;
68 return $this->
msg(
'history-title' )->plaintextParams( $this->
getTitle()->getPrefixedText() );
73 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
74 $subtitle = $linkRenderer->makeKnownLink(
75 SpecialPage::getTitleFor(
'Log' ),
76 $this->
msg(
'viewpagelogs' )->text(),
78 [
'page' => $this->
getTitle()->getPrefixedText() ]
86 . $this->
msg(
'word-separator' )->escaped()
87 . $this->
msg(
'parentheses' )
88 ->rawParams( $this->
getLanguage()->pipeList( $links ) )
91 return Html::rawElement(
'div', [
'class' =>
'mw-history-subtitle' ], $subtitle );
98 private function preCacheMessages() {
100 if ( !isset( $this->message ) ) {
103 'cur',
'tooltip-cur',
'last',
'tooltip-last',
'pipe-separator',
104 'changeslist-nocomment',
'updatedmarker',
106 foreach ( $msgs as $msg ) {
107 $this->message[$msg] = $this->msg( $msg )->escaped();
116 private function getTimestampFromRequest(
WebRequest $request ) {
118 $year = $request->
getInt(
'year' );
119 $month = $request->
getInt(
'month' );
121 if ( $year !== 0 || $month !== 0 ) {
123 $year = MWTimestamp::getLocalInstance()->format(
'Y' );
125 if ( $month < 1 || $month > 12 ) {
130 $day = cal_days_in_month( CAL_GREGORIAN, $month, $year );
133 $month = str_pad( (
string)$month, 2,
"0", STR_PAD_LEFT );
134 $day = str_pad( (
string)$day, 2,
"0", STR_PAD_LEFT );
137 $before = $request->
getVal(
'date-range-to' );
139 $parts = explode(
'-', $before );
142 if ( count( $parts ) === 3 ) {
147 return $year && $month && $day ? $year .
'-' . $month .
'-' . $day :
'';
157 $config = $this->context->getConfig();
158 $services = MediaWikiServices::getInstance();
167 $watchlistManager = $services->getWatchlistManager();
168 $hasUnseenRevisionMarkers = $config->get( MainConfigNames::ShowUpdatedMarker ) &&
169 $watchlistManager->getTitleNotificationTimestamp(
174 !$hasUnseenRevisionMarkers &&
175 $out->checkLastModified( $this->getWikiPage()->getTouched() )
180 $this->preCacheMessages();
182 # Fill in the file cache if not set already
183 if ( HTMLFileCache::useFileCache( $this->
getContext() ) ) {
185 if ( !$cache->isCacheGood( ) ) {
186 ob_start( [ &$cache,
'saveToFileCache' ] );
191 $out->setFeedAppendQuery(
'action=history' );
192 $out->addModules(
'mediawiki.action.history' );
193 $out->addModuleStyles( [
194 'mediawiki.interface.helpers.styles',
195 'codex-search-styles',
196 'mediawiki.action.history.styles',
197 'mediawiki.special.changeslist',
201 $feedType = $request->
getRawVal(
'feed' );
202 if ( $feedType !==
null ) {
203 $this->feed( $feedType );
208 'https://meta.wikimedia.org/wiki/Special:MyLanguage/Help:Page_history',
214 $send404Code = $config->get( MainConfigNames::Send404Code );
215 if ( $send404Code ) {
216 $out->setStatusCode( 404 );
218 $out->addWikiMsg(
'nohistory' );
220 $dbr = $services->getConnectionProvider()->getReplicaDatabase();
222 # show deletion/move log if there is an entry
223 LogEventsList::showLogExtract(
225 [
'delete',
'move',
'protect' ],
229 'conds' => [ $dbr->expr(
'log_action',
'!=',
'revision' ) ],
230 'showIfEmpty' =>
false,
231 'msgKey' => [
'moveddeleted-notice' ]
238 $ts = $this->getTimestampFromRequest( $request );
239 $tagFilter = $request->
getVal(
'tagfilter' );
244 if ( $request->
getBool(
'deleted' ) ) {
245 $conds = [
'rev_deleted != 0' ];
255 'default' =>
'history',
260 'label' => $this->
msg(
'date-range-to' )->text(),
261 'name' =>
'date-range-to',
264 'label-message' =>
'tag-filter',
265 'type' =>
'tagfilter',
267 'name' =>
'tagfilter',
268 'value' => $tagFilter,
272 'name' =>
'tagInvert',
273 'label-message' =>
'invert',
274 'hide-if' => [
'===',
'tagfilter',
'' ],
277 if ( $this->
getAuthority()->isAllowed(
'deletedhistory' ) ) {
280 'label' => $this->
msg(
'history-show-deleted' )->text(),
281 'default' => $request->
getBool(
'deleted' ),
291 ->setCollapsibleOptions(
true )
292 ->setId(
'mw-history-searchform' )
293 ->setSubmitTextMsg(
'historyaction-submit' )
294 ->setWrapperAttributes( [
'id' =>
'mw-history-search' ] )
295 ->setWrapperLegendMsg(
'history-fieldset-title' )
298 $out->addHTML( $htmlForm->getHTML(
false ) );
306 $dateComponents = explode(
'-', $ts );
307 if ( count( $dateComponents ) > 1 ) {
308 $y = (int)$dateComponents[0];
309 $m = (int)$dateComponents[1];
310 $d = (int)$dateComponents[2];
324 $services->getLinkBatchFactory(),
326 $services->getCommentFormatter(),
327 $services->getHookContainer(),
328 $services->getChangeTagsStore()
331 $pager->getNavigationBar() .
333 $pager->getNavigationBar()
335 $out->setPreventClickjacking( $pager->getPreventClickjacking() );
350 private function fetchRevisions( $limit, $offset, $direction ) {
352 if ( !$this->
getTitle()->exists() ) {
356 $dbr = MediaWikiServices::getInstance()->getConnectionProvider()->getReplicaDatabase();
358 if ( $direction === self::DIR_PREV ) {
359 [ $dirs, $oper ] = [
"ASC",
">=" ];
361 [ $dirs, $oper ] = [
"DESC",
"<=" ];
364 $queryBuilder = MediaWikiServices::getInstance()->getRevisionStore()->newSelectQueryBuilder( $dbr )
366 ->where( [
'rev_page' => $this->
getWikiPage()->getId() ] )
367 ->useIndex( [
'revision' =>
'rev_page_timestamp' ] )
368 ->orderBy( [
'rev_timestamp' ], $dirs )
371 $queryBuilder->andWhere( $dbr->expr(
'rev_timestamp', $oper, $dbr->timestamp( $offset ) ) );
374 return $queryBuilder->caller( __METHOD__ )->fetchResultSet();
382 private function feed( $type ) {
383 if ( !FeedUtils::checkFeedOutput( $type, $this->
getOutput() ) ) {
388 $feedClasses = $this->context->getConfig()->get( MainConfigNames::FeedClasses );
390 $feed =
new $feedClasses[$type](
391 $this->
getTitle()->getPrefixedText() .
' - ' .
392 $this->
msg(
'history-feed-title' )->inContentLanguage()->text(),
393 $this->
msg(
'history-feed-description' )->inContentLanguage()->text(),
394 $this->
getTitle()->getFullURL(
'action=history' )
399 $limit = $request->
getInt(
'limit', 10 );
402 $this->context->getConfig()->get( MainConfigNames::FeedLimit )
405 $items = $this->fetchRevisions( $limit, 0, self::DIR_NEXT );
408 $formattedComments = MediaWikiServices::getInstance()->getRowCommentFormatter()
409 ->formatRows( $items,
'rev_comment' );
413 if ( $items->numRows() ) {
414 foreach ( $items as $i => $row ) {
415 $feed->outItem( $this->feedItem( $row, $formattedComments[$i] ) );
418 $feed->outItem( $this->feedEmpty() );
423 private function feedEmpty() {
425 $this->
msg(
'nohistory' )->inContentLanguage()->text(),
426 $this->
msg(
'history-feed-empty' )->inContentLanguage()->parseAsBlock(),
430 $this->
getTitle()->getTalkPage()->getFullURL()
443 private function feedItem( $row, $formattedComment ) {
444 $revisionStore = MediaWikiServices::getInstance()->getRevisionStore();
445 $rev = $revisionStore->newRevisionFromRow( $row, 0, $this->
getTitle() );
446 $prevRev = $revisionStore->getPreviousRevision( $rev );
447 $revComment = $rev->getComment() ===
null ? null : $rev->getComment()->text;
448 $text = FeedUtils::formatDiffRow2(
450 $prevRev ? $prevRev->getId() : false,
455 $revUserText = $rev->getUser() ? $rev->getUser()->getName() :
'';
456 if ( $revComment ==
'' ) {
457 $contLang = MediaWikiServices::getInstance()->getContentLanguage();
458 $title = $this->
msg(
'history-feed-item-nocomment',
460 $contLang->timeanddate( $rev->getTimestamp() ),
461 $contLang->date( $rev->getTimestamp() ),
462 $contLang->time( $rev->getTimestamp() )
463 )->inContentLanguage()->text();
465 $title = $revUserText .
466 $this->
msg(
'colon-separator' )->inContentLanguage()->text() .
467 FeedItem::stripComment( $revComment );
473 $this->
getTitle()->getFullURL(
'diff=' . $rev->getId() .
'&oldid=prev' ),
474 $rev->getTimestamp(),
476 $this->getTitle()->getTalkPage()->getFullURL()
This class handles printing the history page for an article.
onView()
Print the history page for an article.
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.
getDescription()
Returns the description that goes below the <h1> element.
requiresUnblock()
Whether this action can still be executed by a blocked user.