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
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->getDBLoadBalancerFactory()->getReplicaDatabase();
222 # show deletion/move log if there is an entry
223 LogEventsList::showLogExtract(
225 [
'delete',
'move',
'protect' ],
229 'conds' => [
'log_action != ' . $dbr->addQuotes(
'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()->getDBLoadBalancerFactory()->getReplicaDatabase();
358 if ( $direction === self::DIR_PREV ) {
359 [ $dirs, $oper ] = [
"ASC",
">=" ];
361 [ $dirs, $oper ] = [
"DESC",
"<=" ];
365 $offsets = [
"rev_timestamp $oper " . $dbr->addQuotes( $dbr->timestamp( $offset ) ) ];
372 $res = MediaWikiServices::getInstance()->getRevisionStore()->newSelectQueryBuilder( $dbr )
374 ->where( [
'rev_page' => $page_id ] )
375 ->andWhere( $offsets )
376 ->useIndex( [
'revision' =>
'rev_page_timestamp' ] )
377 ->orderBy( [
'rev_timestamp' ], $dirs )
379 ->caller( __METHOD__ )
390 private function feed( $type ) {
391 if ( !FeedUtils::checkFeedOutput( $type, $this->
getOutput() ) ) {
396 $feedClasses = $this->context->getConfig()->get( MainConfigNames::FeedClasses );
398 $feed =
new $feedClasses[$type](
399 $this->
getTitle()->getPrefixedText() .
' - ' .
400 $this->
msg(
'history-feed-title' )->inContentLanguage()->text(),
401 $this->
msg(
'history-feed-description' )->inContentLanguage()->text(),
402 $this->
getTitle()->getFullURL(
'action=history' )
407 $limit = $request->
getInt(
'limit', 10 );
410 $this->context->getConfig()->get( MainConfigNames::FeedLimit )
413 $items = $this->fetchRevisions( $limit, 0, self::DIR_NEXT );
416 $formattedComments = MediaWikiServices::getInstance()->getRowCommentFormatter()
417 ->formatRows( $items,
'rev_comment' );
421 if ( $items->numRows() ) {
422 foreach ( $items as $i => $row ) {
423 $feed->outItem( $this->feedItem( $row, $formattedComments[$i] ) );
426 $feed->outItem( $this->feedEmpty() );
431 private function feedEmpty() {
433 $this->
msg(
'nohistory' )->inContentLanguage()->text(),
434 $this->
msg(
'history-feed-empty' )->inContentLanguage()->parseAsBlock(),
438 $this->
getTitle()->getTalkPage()->getFullURL()
451 private function feedItem( $row, $formattedComment ) {
452 $revisionStore = MediaWikiServices::getInstance()->getRevisionStore();
453 $rev = $revisionStore->newRevisionFromRow( $row, 0, $this->
getTitle() );
454 $prevRev = $revisionStore->getPreviousRevision( $rev );
455 $revComment = $rev->getComment() ===
null ? null : $rev->getComment()->text;
456 $text = FeedUtils::formatDiffRow2(
458 $prevRev ? $prevRev->getId() : false,
463 $revUserText = $rev->getUser() ? $rev->getUser()->getName() :
'';
464 if ( $revComment ==
'' ) {
465 $contLang = MediaWikiServices::getInstance()->getContentLanguage();
466 $title = $this->
msg(
'history-feed-item-nocomment',
468 $contLang->timeanddate( $rev->getTimestamp() ),
469 $contLang->date( $rev->getTimestamp() ),
470 $contLang->time( $rev->getTimestamp() )
471 )->inContentLanguage()->text();
473 $title = $revUserText .
474 $this->
msg(
'colon-separator' )->inContentLanguage()->text() .
475 FeedItem::stripComment( $revComment );
481 $this->
getTitle()->getFullURL(
'diff=' . $rev->getId() .
'&oldid=prev' ),
482 $rev->getTimestamp(),
484 $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.