diff options
Diffstat (limited to 'includes/page')
-rw-r--r-- | includes/page/Article.php | 350 | ||||
-rw-r--r-- | includes/page/CategoryPage.php | 6 | ||||
-rw-r--r-- | includes/page/ImagePage.php | 100 | ||||
-rw-r--r-- | includes/page/WikiPage.php | 476 |
4 files changed, 456 insertions, 476 deletions
diff --git a/includes/page/Article.php b/includes/page/Article.php index 4e753817..4cde5ad8 100644 --- a/includes/page/Article.php +++ b/includes/page/Article.php @@ -30,8 +30,6 @@ * See design.txt for an overview. * Note: edit user interface and cache support functions have been * moved to separate EditPage and HTMLFileCache classes. - * - * @internal documentation reviewed 15 Mar 2010 */ class Article implements Page { /** @var IContextSource The context this Article is executed in */ @@ -120,7 +118,7 @@ class Article implements Page { } $page = null; - wfRunHooks( 'ArticleFromTitle', array( &$title, &$page, $context ) ); + Hooks::run( 'ArticleFromTitle', array( &$title, &$page, $context ) ); if ( !$page ) { switch ( $title->getNamespace() ) { case NS_FILE: @@ -226,7 +224,6 @@ class Article implements Page { * @since 1.21 */ protected function getContentObject() { - wfProfileIn( __METHOD__ ); if ( $this->mPage->getID() === 0 ) { # If this is a MediaWiki:x message, then load the messages @@ -247,7 +244,6 @@ class Article implements Page { $content = $this->mContentObject; } - wfProfileOut( __METHOD__ ); return $content; } @@ -344,12 +340,9 @@ class Article implements Page { return $this->mContent; } - wfProfileIn( __METHOD__ ); - $content = $this->fetchContentObject(); if ( !$content ) { - wfProfileOut( __METHOD__ ); return false; } @@ -357,8 +350,6 @@ class Article implements Page { $this->mContent = ContentHandler::getContentText( $content ); ContentHandler::runLegacyHooks( 'ArticleAfterFetchContent', array( &$this, &$this->mContent ) ); - wfProfileOut( __METHOD__ ); - return $this->mContent; } @@ -379,8 +370,6 @@ class Article implements Page { return $this->mContentObject; } - wfProfileIn( __METHOD__ ); - $this->mContentLoaded = true; $this->mContent = null; @@ -389,7 +378,7 @@ class Article implements Page { # Pre-fill content with error message so that if something # fails we'll have something telling us what we intended. //XXX: this isn't page content but a UI message. horrible. - $this->mContentObject = new MessageContent( 'missing-revision', array( $oldid ), array() ); + $this->mContentObject = new MessageContent( 'missing-revision', array( $oldid ) ); if ( $oldid ) { # $this->mRevision might already be fetched by getOldIDFromRequest() @@ -397,24 +386,24 @@ class Article implements Page { $this->mRevision = Revision::newFromId( $oldid ); if ( !$this->mRevision ) { wfDebug( __METHOD__ . " failed to retrieve specified revision, id $oldid\n" ); - wfProfileOut( __METHOD__ ); return false; } } } else { - if ( !$this->mPage->getLatest() ) { + $oldid = $this->mPage->getLatest(); + if ( !$oldid ) { wfDebug( __METHOD__ . " failed to find page data for title " . $this->getTitle()->getPrefixedText() . "\n" ); - wfProfileOut( __METHOD__ ); return false; } + # Update error message with correct oldid + $this->mContentObject = new MessageContent( 'missing-revision', array( $oldid ) ); + $this->mRevision = $this->mPage->getRevision(); if ( !$this->mRevision ) { - wfDebug( __METHOD__ . " failed to retrieve current page, rev_id " . - $this->mPage->getLatest() . "\n" ); - wfProfileOut( __METHOD__ ); + wfDebug( __METHOD__ . " failed to retrieve current page, rev_id $oldid\n" ); return false; } } @@ -422,15 +411,21 @@ class Article implements Page { // @todo FIXME: Horrible, horrible! This content-loading interface just plain sucks. // We should instead work with the Revision object when we need it... // Loads if user is allowed - $this->mContentObject = $this->mRevision->getContent( + $content = $this->mRevision->getContent( Revision::FOR_THIS_USER, $this->getContext()->getUser() ); - $this->mRevIdFetched = $this->mRevision->getId(); - wfRunHooks( 'ArticleAfterFetchContentObject', array( &$this, &$this->mContentObject ) ); + if ( !$content ) { + wfDebug( __METHOD__ . " failed to retrieve content of revision " . + $this->mRevision->getId() . "\n" ); + return false; + } + + $this->mContentObject = $content; + $this->mRevIdFetched = $this->mRevision->getId(); - wfProfileOut( __METHOD__ ); + Hooks::run( 'ArticleAfterFetchContentObject', array( &$this, &$this->mContentObject ) ); return $this->mContentObject; } @@ -482,8 +477,6 @@ class Article implements Page { public function view() { global $wgUseFileCache, $wgUseETag, $wgDebugToolbar, $wgMaxRedirects; - wfProfileIn( __METHOD__ ); - # Get variables from query string # As side effect this will load the revision and update the title # in a revision ID is passed in the request, so this should remain @@ -495,7 +488,6 @@ class Article implements Page { $permErrors = $this->getTitle()->getUserPermissionsErrors( 'read', $user ); if ( count( $permErrors ) ) { wfDebug( __METHOD__ . ": denied on secondary read check\n" ); - wfProfileOut( __METHOD__ ); throw new PermissionsError( 'read', $permErrors ); } @@ -504,7 +496,6 @@ class Article implements Page { if ( $this->mRedirectUrl ) { $outputPage->redirect( $this->mRedirectUrl ); wfDebug( __METHOD__ . ": redirecting due to oldid\n" ); - wfProfileOut( __METHOD__ ); return; } @@ -513,7 +504,6 @@ class Article implements Page { if ( $this->getContext()->getRequest()->getCheck( 'diff' ) ) { wfDebug( __METHOD__ . ": showing diff page\n" ); $this->showDiffPage(); - wfProfileOut( __METHOD__ ); return; } @@ -568,7 +558,6 @@ class Article implements Page { # Is it client cached? if ( $outputPage->checkLastModified( $timestamp ) ) { wfDebug( __METHOD__ . ": done 304\n" ); - wfProfileOut( __METHOD__ ); return; # Try file cache @@ -577,7 +566,6 @@ class Article implements Page { # tell wgOut that output is taken care of $outputPage->disable(); $this->mPage->doViewUpdates( $user, $oldid ); - wfProfileOut( __METHOD__ ); return; } @@ -587,7 +575,7 @@ class Article implements Page { $useParserCache = $this->mPage->isParserCacheUsed( $parserOptions, $oldid ); wfDebug( 'Article::view using parser cache: ' . ( $useParserCache ? 'yes' : 'no' ) . "\n" ); if ( $user->getStubThreshold() ) { - wfIncrStats( 'pcache_miss_stub' ); + $this->getContext()->getStats()->increment( 'pcache_miss_stub' ); } $this->showRedirectedFromHeader(); @@ -602,7 +590,7 @@ class Article implements Page { while ( !$outputDone && ++$pass ) { switch ( $pass ) { case 1: - wfRunHooks( 'ArticleViewHeader', array( &$this, &$outputDone, &$useParserCache ) ); + Hooks::run( 'ArticleViewHeader', array( &$this, &$outputDone, &$useParserCache ) ); break; case 2: # Early abort if the page doesn't exist @@ -610,7 +598,6 @@ class Article implements Page { wfDebug( __METHOD__ . ": showing missing article\n" ); $this->showMissingArticle(); $this->mPage->doViewUpdates( $user ); - wfProfileOut( __METHOD__ ); return; } @@ -649,7 +636,6 @@ class Article implements Page { if ( !$this->showDeletedRevisionHeader() ) { wfDebug( __METHOD__ . ": cannot view deleted revision\n" ); - wfProfileOut( __METHOD__ ); return; } } @@ -665,7 +651,7 @@ class Article implements Page { wfDebug( __METHOD__ . ": showing CSS/JS source\n" ); $this->showCssOrJsPage(); $outputDone = true; - } elseif ( !wfRunHooks( 'ArticleContentViewCustom', + } elseif ( !Hooks::run( 'ArticleContentViewCustom', array( $this->fetchContentObject(), $this->getTitle(), $outputPage ) ) ) { # Allow extensions do their own custom view for certain pages @@ -696,16 +682,14 @@ class Article implements Page { $outputPage->addWikiText( '<div class="errorbox">' . $errortext . '</div>' ); } # Connection or timeout error - wfProfileOut( __METHOD__ ); return; } $this->mParserOutput = $poolArticleView->getParserOutput(); $outputPage->addParserOutput( $this->mParserOutput ); if ( $content->getRedirectTarget() ) { - $outputPage->addSubtitle( - "<span id=\"redirectsub\">" . wfMessage( 'redirectpagesub' )->parse() . "</span>" - ); + $outputPage->addSubtitle( "<span id=\"redirectsub\">" . + $this->getContext()->msg( 'redirectpagesub' )->parse() . "</span>" ); } # Don't cache a dirty ParserOutput object @@ -724,7 +708,7 @@ class Article implements Page { } # Get the ParserOutput actually *displayed* here. - # Note that $this->mParserOutput is the *current* version output. + # Note that $this->mParserOutput is the *current*/oldid version output. $pOutput = ( $outputDone instanceof ParserOutput ) ? $outputDone // object fetched by hook : $this->mParserOutput; @@ -755,7 +739,6 @@ class Article implements Page { $outputPage->addModules( 'mediawiki.action.view.postEdit' ); - wfProfileOut( __METHOD__ ); } /** @@ -774,9 +757,8 @@ class Article implements Page { * Show a diff page according to current request variables. For use within * Article::view() only, other callers should use the DifferenceEngine class. * - * @todo Make protected */ - public function showDiffPage() { + protected function showDiffPage() { $request = $this->getContext()->getRequest(); $user = $this->getContext()->getUser(); $diff = $request->getVal( 'diff' ); @@ -790,7 +772,11 @@ class Article implements Page { if ( !$rev ) { $this->getContext()->getOutput()->setPageTitle( wfMessage( 'errorpagetitle' ) ); - $this->getContext()->getOutput()->addWikiMsg( 'difference-missing-revision', $oldid, 1 ); + $msg = $this->getContext()->msg( 'difference-missing-revision' ) + ->params( $oldid ) + ->numParams( 1 ) + ->parseAsBlock(); + $this->getContext()->getOutput()->addHtml( $msg ); return; } @@ -831,7 +817,7 @@ class Article implements Page { if ( $showCacheHint ) { $dir = $this->getContext()->getLanguage()->getDir(); - $lang = $this->getContext()->getLanguage()->getCode(); + $lang = $this->getContext()->getLanguage()->getHtmlCode(); $outputPage->wrapWikiMsg( "<div id='mw-clearyourcache' lang='$lang' dir='$dir' class='mw-content-$dir'>\n$1\n</div>", @@ -973,9 +959,10 @@ class Article implements Page { */ public function showRedirectedFromHeader() { global $wgRedirectSources; - $outputPage = $this->getContext()->getOutput(); - $request = $this->getContext()->getRequest(); + $context = $this->getContext(); + $outputPage = $context->getOutput(); + $request = $context->getRequest(); $rdfrom = $request->getVal( 'rdfrom' ); // Construct a URL for the current page view, but with the target title @@ -991,7 +978,7 @@ class Article implements Page { if ( isset( $this->mRedirectedFrom ) ) { // This is an internally redirected page view. // We'll need a backlink to the source page for navigation. - if ( wfRunHooks( 'ArticleViewRedirect', array( &$this ) ) ) { + if ( Hooks::run( 'ArticleViewRedirect', array( &$this ) ) ) { $redir = Linker::linkKnown( $this->mRedirectedFrom, null, @@ -999,7 +986,9 @@ class Article implements Page { array( 'redirect' => 'no' ) ); - $outputPage->addSubtitle( wfMessage( 'redirectedfrom' )->rawParams( $redir ) ); + $outputPage->addSubtitle( "<span class=\"mw-redirectedfrom\">" . + $context->msg( 'redirectedfrom' )->rawParams( $redir )->parse() + . "</span>" ); // Add the script to update the displayed URL and // set the fragment if one was specified in the redirect @@ -1021,7 +1010,9 @@ class Article implements Page { // If it was reported from a trusted site, supply a backlink. if ( $wgRedirectSources && preg_match( $wgRedirectSources, $rdfrom ) ) { $redir = Linker::makeExternalLink( $rdfrom, $rdfrom ); - $outputPage->addSubtitle( wfMessage( 'redirectedfrom' )->rawParams( $redir ) ); + $outputPage->addSubtitle( "<span class=\"mw-redirectedfrom\">" . + $context->msg( 'redirectedfrom' )->rawParams( $redir )->parse() + . "</span>" ); // Add the script to update the displayed URL $outputPage->addJsConfigVars( array( @@ -1065,7 +1056,7 @@ class Article implements Page { // Show a footer allowing the user to patrol the shown revision or page if possible $patrolFooterShown = $this->showPatrolFooter(); - wfRunHooks( 'ArticleViewFooter', array( $this, $patrolFooterShown ) ); + Hooks::run( 'ArticleViewFooter', array( $this, $patrolFooterShown ) ); } /** @@ -1092,8 +1083,6 @@ class Article implements Page { return false; } - wfProfileIn( __METHOD__ ); - // New page patrol: Get the timestamp of the oldest revison which // the revision table holds for the given page. Then we look // whether it's within the RC lifespan and if it is, we try @@ -1102,7 +1091,6 @@ class Article implements Page { // Check for cached results if ( $cache->get( wfMemcKey( 'NotPatrollablePage', $this->getTitle()->getArticleID() ) ) ) { - wfProfileOut( __METHOD__ ); return false; } @@ -1111,7 +1099,6 @@ class Article implements Page { ) { // The current revision is already older than what could be in the RC table // 6h tolerance because the RC might not be cleaned out regularly - wfProfileOut( __METHOD__ ); return false; } @@ -1147,14 +1134,12 @@ class Article implements Page { // Don't cache in case we can patrol as this could change $cache->set( wfMemcKey( 'NotPatrollablePage', $this->getTitle()->getArticleID() ), '1' ); - wfProfileOut( __METHOD__ ); return false; } - if ( $rc->getPerformer()->getName() == $user->getName() ) { + if ( $rc->getPerformer()->equals( $user ) ) { // Don't show a patrol link for own creations. If the user could // patrol them, they already would be patrolled - wfProfileOut( __METHOD__ ); return false; } @@ -1184,7 +1169,6 @@ class Article implements Page { '</div>' ); - wfProfileOut( __METHOD__ ); return true; } @@ -1214,7 +1198,8 @@ class Article implements Page { if ( !( $user && $user->isLoggedIn() ) && !$ip ) { # User does not exist $outputPage->wrapWikiMsg( "<div class=\"mw-userpage-userdoesnotexist error\">\n\$1\n</div>", array( 'userpage-userdoesnotexist-view', wfEscapeWikiText( $rootPart ) ) ); - } elseif ( !is_null( $block ) && $block->getType() != Block::TYPE_AUTO ) { # Show log extract if the user is currently blocked + } elseif ( !is_null( $block ) && $block->getType() != Block::TYPE_AUTO ) { + # Show log extract if the user is currently blocked LogEventsList::showLogExtract( $outputPage, 'block', @@ -1235,24 +1220,20 @@ class Article implements Page { } } - wfRunHooks( 'ShowMissingArticle', array( $this ) ); + Hooks::run( 'ShowMissingArticle', array( $this ) ); // Give extensions a chance to hide their (unrelated) log entries $logTypes = array( 'delete', 'move' ); $conds = array( "log_action != 'revision'" ); - wfRunHooks( 'Article::MissingArticleConditions', array( &$conds, $logTypes ) ); + Hooks::run( 'Article::MissingArticleConditions', array( &$conds, $logTypes ) ); # Show delete and move logs - $member = $title->getNamespace() . ':' . $title->getDBkey(); - // @todo: move optimization to showLogExtract()? - if ( BloomCache::get( 'main' )->check( wfWikiId(), 'TitleHasLogs', $member ) ) { - LogEventsList::showLogExtract( $outputPage, $logTypes, $title, '', - array( 'lim' => 10, - 'conds' => $conds, - 'showIfEmpty' => false, - 'msgKey' => array( 'moveddeleted-notice' ) ) - ); - } + LogEventsList::showLogExtract( $outputPage, $logTypes, $title, '', + array( 'lim' => 10, + 'conds' => $conds, + 'showIfEmpty' => false, + 'msgKey' => array( 'moveddeleted-notice' ) ) + ); if ( !$this->mPage->hasViewableContent() && $wgSend404Code && !$validUserPage ) { // If there's no backing content, send a 404 Not Found @@ -1265,7 +1246,7 @@ class Article implements Page { $outputPage->setIndexPolicy( $policy['index'] ); $outputPage->setFollowPolicy( $policy['follow'] ); - $hookResult = wfRunHooks( 'BeforeDisplayNoArticleText', array( $this ) ); + $hookResult = Hooks::run( 'BeforeDisplayNoArticleText', array( $this ) ); if ( !$hookResult ) { return; @@ -1341,11 +1322,12 @@ class Article implements Page { * @param int $oldid Revision ID of this article revision */ public function setOldSubtitle( $oldid = 0 ) { - if ( !wfRunHooks( 'DisplayOldSubtitle', array( &$this, &$oldid ) ) ) { + if ( !Hooks::run( 'DisplayOldSubtitle', array( &$this, &$oldid ) ) ) { return; } - $unhide = $this->getContext()->getRequest()->getInt( 'unhide' ) == 1; + $context = $this->getContext(); + $unhide = $context->getRequest()->getInt( 'unhide' ) == 1; # Cascade unhide param in links for easy deletion browsing $extraParams = array(); @@ -1362,8 +1344,8 @@ class Article implements Page { $timestamp = $revision->getTimestamp(); $current = ( $oldid == $this->mPage->getLatest() ); - $language = $this->getContext()->getLanguage(); - $user = $this->getContext()->getUser(); + $language = $context->getLanguage(); + $user = $context->getUser(); $td = $language->userTimeAndDate( $timestamp, $user ); $tddate = $language->userDate( $timestamp, $user ); @@ -1372,28 +1354,33 @@ class Article implements Page { # Show user links if allowed to see them. If hidden, then show them only if requested... $userlinks = Linker::revUserTools( $revision, !$unhide ); - $infomsg = $current && !wfMessage( 'revision-info-current' )->isDisabled() + $infomsg = $current && !$context->msg( 'revision-info-current' )->isDisabled() ? 'revision-info-current' : 'revision-info'; - $outputPage = $this->getContext()->getOutput(); - $outputPage->addSubtitle( "<div id=\"mw-{$infomsg}\">" . wfMessage( $infomsg, - $td )->rawParams( $userlinks )->params( $revision->getID(), $tddate, - $tdtime, $revision->getUserText() )->rawParams( Linker::revComment( $revision, true, true ) )->parse() . "</div>" ); + $outputPage = $context->getOutput(); + $outputPage->addSubtitle( "<div id=\"mw-{$infomsg}\">" . + $context->msg( $infomsg, $td ) + ->rawParams( $userlinks ) + ->params( $revision->getID(), $tddate, $tdtime, $revision->getUserText() ) + ->rawParams( Linker::revComment( $revision, true, true ) ) + ->parse() . + "</div>" + ); $lnk = $current - ? wfMessage( 'currentrevisionlink' )->escaped() + ? $context->msg( 'currentrevisionlink' )->escaped() : Linker::linkKnown( $this->getTitle(), - wfMessage( 'currentrevisionlink' )->escaped(), + $context->msg( 'currentrevisionlink' )->escaped(), array(), $extraParams ); $curdiff = $current - ? wfMessage( 'diff' )->escaped() + ? $context->msg( 'diff' )->escaped() : Linker::linkKnown( $this->getTitle(), - wfMessage( 'diff' )->escaped(), + $context->msg( 'diff' )->escaped(), array(), array( 'diff' => 'cur', @@ -1404,30 +1391,30 @@ class Article implements Page { $prevlink = $prev ? Linker::linkKnown( $this->getTitle(), - wfMessage( 'previousrevision' )->escaped(), + $context->msg( 'previousrevision' )->escaped(), array(), array( 'direction' => 'prev', 'oldid' => $oldid ) + $extraParams ) - : wfMessage( 'previousrevision' )->escaped(); + : $context->msg( 'previousrevision' )->escaped(); $prevdiff = $prev ? Linker::linkKnown( $this->getTitle(), - wfMessage( 'diff' )->escaped(), + $context->msg( 'diff' )->escaped(), array(), array( 'diff' => 'prev', 'oldid' => $oldid ) + $extraParams ) - : wfMessage( 'diff' )->escaped(); + : $context->msg( 'diff' )->escaped(); $nextlink = $current - ? wfMessage( 'nextrevision' )->escaped() + ? $context->msg( 'nextrevision' )->escaped() : Linker::linkKnown( $this->getTitle(), - wfMessage( 'nextrevision' )->escaped(), + $context->msg( 'nextrevision' )->escaped(), array(), array( 'direction' => 'next', @@ -1435,10 +1422,10 @@ class Article implements Page { ) + $extraParams ); $nextdiff = $current - ? wfMessage( 'diff' )->escaped() + ? $context->msg( 'diff' )->escaped() : Linker::linkKnown( $this->getTitle(), - wfMessage( 'diff' )->escaped(), + $context->msg( 'diff' )->escaped(), array(), array( 'diff' => 'next', @@ -1452,7 +1439,7 @@ class Article implements Page { } $outputPage->addSubtitle( "<div id=\"mw-revision-nav\">" . $cdel . - wfMessage( 'revision-nav' )->rawParams( + $context->msg( 'revision-nav' )->rawParams( $prevdiff, $prevlink, $lnk, $curdiff, $nextlink, $nextdiff )->escaped() . "</div>" ); } @@ -1472,7 +1459,7 @@ class Article implements Page { $lang = $this->getTitle()->getPageLanguage(); $out = $this->getContext()->getOutput(); if ( $appendSubtitle ) { - $out->addSubtitle( wfMessage( 'redirectpagesub' )->parse() ); + $out->addSubtitle( wfMessage( 'redirectpagesub' ) ); } $out->addModuleStyles( 'mediawiki.action.view.redirectPage' ); return static::getRedirectHeaderHtml( $lang, $target, $forceKnown ); @@ -1508,8 +1495,9 @@ class Article implements Page { ( $forceKnown ? array( 'known', 'noclasses' ) : array() ) ) . '</li>'; } + $html .= '</ul>'; - $redirectToText = wfMessage( 'redirectto' )->inLanguage( $lang )->text(); + $redirectToText = wfMessage( 'redirectto' )->inLanguage( $lang )->escaped(); return '<div class="redirectMsg">' . '<p>' . $redirectToText . '</p>' . @@ -1518,6 +1506,28 @@ class Article implements Page { } /** + * Adds help link with an icon via page indicators. + * Link target can be overridden by a local message containing a wikilink: + * the message key is: 'namespace-' + namespace number + '-helppage'. + * @param string $to Target MediaWiki.org page title or encoded URL. + * @param bool $overrideBaseUrl Whether $url is a full URL, to avoid MW.o. + * @since 1.25 + */ + public function addHelpLink( $to, $overrideBaseUrl = false ) { + $msg = wfMessage( + 'namespace-' . $this->getTitle()->getNamespace() . '-helppage' + ); + + $out = $this->getContext()->getOutput(); + if ( !$msg->isDisabled() ) { + $helpUrl = Skin::makeUrl( $msg->plain() ); + $out->addHelpLink( $helpUrl, true ); + } else { + $out->addHelpLink( $to, $overrideBaseUrl ); + } + } + + /** * Handle action=render */ public function render() { @@ -1549,7 +1559,8 @@ class Article implements Page { # This code desperately needs to be totally rewritten $title = $this->getTitle(); - $user = $this->getContext()->getUser(); + $context = $this->getContext(); + $user = $context->getUser(); # Check permissions $permissionErrors = $title->getUserPermissionsErrors( 'delete', $user ); @@ -1566,8 +1577,8 @@ class Article implements Page { $this->mPage->loadPageData( 'fromdbmaster' ); if ( !$this->mPage->exists() ) { $deleteLogPage = new LogPage( 'delete' ); - $outputPage = $this->getContext()->getOutput(); - $outputPage->setPageTitle( wfMessage( 'cannotdelete-title', $title->getPrefixedText() ) ); + $outputPage = $context->getOutput(); + $outputPage->setPageTitle( $context->msg( 'cannotdelete-title', $title->getPrefixedText() ) ); $outputPage->wrapWikiMsg( "<div class=\"error mw-error-cannotdelete\">\n$1\n</div>", array( 'cannotdelete', wfEscapeWikiText( $title->getPrefixedText() ) ) ); @@ -1583,7 +1594,7 @@ class Article implements Page { return; } - $request = $this->getContext()->getRequest(); + $request = $context->getRequest(); $deleteReasonList = $request->getText( 'wpDeleteReasonList', 'other' ); $deleteReason = $request->getText( 'wpReason' ); @@ -1615,7 +1626,7 @@ class Article implements Page { if ( !$reason ) { try { $reason = $this->generateReason( $hasHistory ); - } catch ( MWException $e ) { + } catch ( Exception $e ) { # if a page is horribly broken, we still want to be able to # delete it. So be lenient about errors here. wfDebug( "Error while building auto delete summary: $e" ); @@ -1627,10 +1638,11 @@ class Article implements Page { if ( $hasHistory ) { $title = $this->getTitle(); - // The following can use the real revision count as this is only being shown for users that can delete - // this page. - // This, as a side-effect, also makes sure that the following query isn't being run for pages with a - // larger history, unless the user has the 'bigdelete' right (and is about to delete this page). + // The following can use the real revision count as this is only being shown for users + // that can delete this page. + // This, as a side-effect, also makes sure that the following query isn't being run for + // pages with a larger history, unless the user has the 'bigdelete' right + // (and is about to delete this page). $dbr = wfGetDB( DB_SLAVE ); $revisions = $edits = (int)$dbr->selectField( 'revision', @@ -1640,21 +1652,22 @@ class Article implements Page { ); // @todo FIXME: i18n issue/patchwork message - $this->getContext()->getOutput()->addHTML( '<strong class="mw-delete-warning-revisions">' . - wfMessage( 'historywarning' )->numParams( $revisions )->parse() . - wfMessage( 'word-separator' )->plain() . Linker::linkKnown( $title, - wfMessage( 'history' )->escaped(), - array( 'rel' => 'archives' ), + $context->getOutput()->addHTML( + '<strong class="mw-delete-warning-revisions">' . + $context->msg( 'historywarning' )->numParams( $revisions )->parse() . + $context->msg( 'word-separator' )->escaped() . Linker::linkKnown( $title, + $context->msg( 'history' )->escaped(), + array(), array( 'action' => 'history' ) ) . '</strong>' ); if ( $title->isBigDeletion() ) { global $wgDeleteRevisionsLimit; - $this->getContext()->getOutput()->wrapWikiMsg( "<div class='error'>\n$1\n</div>\n", + $context->getOutput()->wrapWikiMsg( "<div class='error'>\n$1\n</div>\n", array( 'delete-warning-toobig', - $this->getContext()->getLanguage()->formatNum( $wgDeleteRevisionsLimit ) + $context->getLanguage()->formatNum( $wgDeleteRevisionsLimit ) ) ); } @@ -1672,7 +1685,9 @@ class Article implements Page { wfDebug( "Article::confirmDelete\n" ); $title = $this->getTitle(); - $outputPage = $this->getContext()->getOutput(); + $ctx = $this->getContext(); + $outputPage = $ctx->getOutput(); + $useMediaWikiUIEverywhere = $ctx->getConfig()->get( 'UseMediaWikiUIEverywhere' ); $outputPage->setPageTitle( wfMessage( 'delete-confirm', $title->getPrefixedText() ) ); $outputPage->addBacklinkSubtitle( $title ); $outputPage->setRobotPolicy( 'noindex,nofollow' ); @@ -1683,80 +1698,72 @@ class Article implements Page { } $outputPage->addWikiMsg( 'confirmdeletetext' ); - wfRunHooks( 'ArticleConfirmDelete', array( $this, $outputPage, &$reason ) ); + Hooks::run( 'ArticleConfirmDelete', array( $this, $outputPage, &$reason ) ); $user = $this->getContext()->getUser(); if ( $user->isAllowed( 'suppressrevision' ) ) { - $suppress = "<tr id=\"wpDeleteSuppressRow\"> - <td></td> - <td class='mw-input'><strong>" . + $suppress = Html::openElement( 'div', array( 'id' => 'wpDeleteSuppressRow' ) ) . + "<strong>" . Xml::checkLabel( wfMessage( 'revdelete-suppress' )->text(), 'wpSuppress', 'wpSuppress', false, array( 'tabindex' => '4' ) ) . - "</strong></td> - </tr>"; + "</strong>" . + Html::closeElement( 'div' ); } else { $suppress = ''; } $checkWatch = $user->getBoolOption( 'watchdeletion' ) || $user->isWatched( $title ); - $form = Xml::openElement( 'form', array( 'method' => 'post', + $form = Html::openElement( 'form', array( 'method' => 'post', 'action' => $title->getLocalURL( 'action=delete' ), 'id' => 'deleteconfirm' ) ) . - Xml::openElement( 'fieldset', array( 'id' => 'mw-delete-table' ) ) . - Xml::tags( 'legend', null, wfMessage( 'delete-legend' )->escaped() ) . - Xml::openElement( 'table', array( 'id' => 'mw-deleteconfirm-table' ) ) . - "<tr id=\"wpDeleteReasonListRow\"> - <td class='mw-label'>" . - Xml::label( wfMessage( 'deletecomment' )->text(), 'wpDeleteReasonList' ) . - "</td> - <td class='mw-input'>" . - Xml::listDropDown( - 'wpDeleteReasonList', - wfMessage( 'deletereason-dropdown' )->inContentLanguage()->text(), - wfMessage( 'deletereasonotherlist' )->inContentLanguage()->text(), - '', - 'wpReasonDropDown', - 1 - ) . - "</td> - </tr> - <tr id=\"wpDeleteReasonRow\"> - <td class='mw-label'>" . - Xml::label( wfMessage( 'deleteotherreason' )->text(), 'wpReason' ) . - "</td> - <td class='mw-input'>" . - Html::input( 'wpReason', $reason, 'text', array( - 'size' => '60', - 'maxlength' => '255', - 'tabindex' => '2', - 'id' => 'wpReason', - 'autofocus' - ) ) . - "</td> - </tr>"; + Html::openElement( 'fieldset', array( 'id' => 'mw-delete-table' ) ) . + Html::element( 'legend', null, wfMessage( 'delete-legend' )->text() ) . + Html::openElement( 'div', array( 'id' => 'mw-deleteconfirm-table' ) ) . + Html::openElement( 'div', array( 'id' => 'wpDeleteReasonListRow' ) ) . + Html::label( wfMessage( 'deletecomment' )->text(), 'wpDeleteReasonList' ) . + ' ' . + Xml::listDropDown( + 'wpDeleteReasonList', + wfMessage( 'deletereason-dropdown' )->inContentLanguage()->text(), + wfMessage( 'deletereasonotherlist' )->inContentLanguage()->text(), + '', + 'wpReasonDropDown', + 1 + ) . + Html::closeElement( 'div' ) . + Html::openElement( 'div', array( 'id' => 'wpDeleteReasonRow' ) ) . + Html::label( wfMessage( 'deleteotherreason' )->text(), 'wpReason' ) . + ' ' . + Html::input( 'wpReason', $reason, 'text', array( + 'size' => '60', + 'maxlength' => '255', + 'tabindex' => '2', + 'id' => 'wpReason', + 'class' => 'mw-ui-input-inline', + 'autofocus' + ) ) . + Html::closeElement( 'div' ); # Disallow watching if user is not logged in if ( $user->isLoggedIn() ) { - $form .= " - <tr> - <td></td> - <td class='mw-input'>" . + $form .= Xml::checkLabel( wfMessage( 'watchthis' )->text(), - 'wpWatch', 'wpWatch', $checkWatch, array( 'tabindex' => '3' ) ) . - "</td> - </tr>"; + 'wpWatch', 'wpWatch', $checkWatch, array( 'tabindex' => '3' ) ); } - $form .= " - $suppress - <tr> - <td></td> - <td class='mw-submit'>" . + $form .= + Html::openElement( 'div' ) . + $suppress . Xml::submitButton( wfMessage( 'deletepage' )->text(), - array( 'name' => 'wpConfirmB', 'id' => 'wpConfirmB', 'tabindex' => '5' ) ) . - "</td> - </tr>" . - Xml::closeElement( 'table' ) . + array( + 'name' => 'wpConfirmB', + 'id' => 'wpConfirmB', + 'tabindex' => '5', + 'class' => $useMediaWikiUIEverywhere ? 'mw-ui-button mw-ui-destructive' : '', + ) + ) . + Html::closeElement( 'div' ) . + Html::closeElement( 'div' ) . Xml::closeElement( 'fieldset' ) . Html::hidden( 'wpEditToken', @@ -1801,6 +1808,9 @@ class Article implements Page { $loglink = '[[Special:Log/delete|' . wfMessage( 'deletionlog' )->text() . ']]'; $outputPage->addWikiMsg( 'deletedtext', wfEscapeWikiText( $deleted ), $loglink ); + + Hooks::run( 'ArticleDeleteAfterSuccess', array( $this->getTitle(), $outputPage ) ); + $outputPage->returnToMain( false ); } else { $outputPage->setPageTitle( @@ -1873,7 +1883,7 @@ class Article implements Page { && !$this->mRedirectedFrom && !$this->getTitle()->isRedirect(); // Extension may have reason to disable file caching on some pages. if ( $cacheable ) { - $cacheable = wfRunHooks( 'IsFileCacheable', array( &$this ) ); + $cacheable = Hooks::run( 'IsFileCacheable', array( &$this ) ); } } diff --git a/includes/page/CategoryPage.php b/includes/page/CategoryPage.php index 9abc6a89..caebcd7d 100644 --- a/includes/page/CategoryPage.php +++ b/includes/page/CategoryPage.php @@ -61,7 +61,7 @@ class CategoryPage extends Article { return; } - if ( !wfRunHooks( 'CategoryPageView', array( &$this ) ) ) { + if ( !Hooks::run( 'CategoryPageView', array( &$this ) ) ) { return; } @@ -113,6 +113,8 @@ class CategoryPage extends Article { $until, $reqArray ); - $this->getContext()->getOutput()->addHTML( $viewer->getHTML() ); + $out = $this->getContext()->getOutput(); + $out->addHTML( $viewer->getHTML() ); + $this->addHelpLink( 'Help:Categories' ); } } diff --git a/includes/page/ImagePage.php b/includes/page/ImagePage.php index d06c8191..8f635cfa 100644 --- a/includes/page/ImagePage.php +++ b/includes/page/ImagePage.php @@ -76,7 +76,7 @@ class ImagePage extends Article { $this->fileLoaded = true; $this->displayImg = $img = false; - wfRunHooks( 'ImagePageFindFile', array( $this, &$img, &$this->displayImg ) ); + Hooks::run( 'ImagePageFindFile', array( $this, &$img, &$this->displayImg ) ); if ( !$img ) { // not set by hook? $img = wfFindFile( $this->getTitle() ); if ( !$img ) { @@ -140,7 +140,7 @@ class ImagePage extends Article { if ( $wgShowEXIF && $this->displayImg->exists() ) { // @todo FIXME: Bad interface, see note on MediaHandler::formatMetadata(). - $formattedMetadata = $this->displayImg->formatMetadata(); + $formattedMetadata = $this->displayImg->formatMetadata( $this->getContext() ); $showmeta = $formattedMetadata !== false; } else { $showmeta = false; @@ -175,7 +175,7 @@ class ImagePage extends Article { # Show shared description, if needed if ( $this->mExtraDescription ) { - $fol = wfMessage( 'shareddescriptionfollows' ); + $fol = $this->getContext()->msg( 'shareddescriptionfollows' ); if ( !$fol->isDisabled() ) { $out->addWikiText( $fol->plain() ); } @@ -188,7 +188,7 @@ class ImagePage extends Article { $out->addHTML( Xml::element( 'h2', array( 'id' => 'filelinks' ), - wfMessage( 'imagelinks' )->text() ) . "\n" ); + $this->getContext()->msg( 'imagelinks' )->text() ) . "\n" ); $this->imageDupes(); # @todo FIXME: For some freaky reason, we can't redirect to foreign images. # Yet we return metadata about the target. Definitely an issue in the FileRepo @@ -196,7 +196,7 @@ class ImagePage extends Article { # Allow extensions to add something after the image links $html = ''; - wfRunHooks( 'ImagePageAfterImageLinks', array( $this, &$html ) ); + Hooks::run( 'ImagePageAfterImageLinks', array( $this, &$html ) ); if ( $html ) { $out->addHTML( $html ); } @@ -205,7 +205,7 @@ class ImagePage extends Article { $out->addHTML( Xml::element( 'h2', array( 'id' => 'metadata' ), - wfMessage( 'metadata' )->text() ) . "\n" ); + $this->getContext()->msg( 'metadata' )->text() ) . "\n" ); $out->addWikiText( $this->makeMetadataTable( $formattedMetadata ) ); $out->addModules( array( 'mediawiki.action.view.metadata' ) ); } @@ -237,16 +237,17 @@ class ImagePage extends Article { */ protected function showTOC( $metadata ) { $r = array( - '<li><a href="#file">' . wfMessage( 'file-anchor-link' )->escaped() . '</a></li>', - '<li><a href="#filehistory">' . wfMessage( 'filehist' )->escaped() . '</a></li>', - '<li><a href="#filelinks">' . wfMessage( 'imagelinks' )->escaped() . '</a></li>', + '<li><a href="#file">' . $this->getContext()->msg( 'file-anchor-link' )->escaped() . '</a></li>', + '<li><a href="#filehistory">' . $this->getContext()->msg( 'filehist' )->escaped() . '</a></li>', + '<li><a href="#filelinks">' . $this->getContext()->msg( 'imagelinks' )->escaped() . '</a></li>', ); + + Hooks::run( 'ImagePageShowTOC', array( $this, &$r ) ); + if ( $metadata ) { - $r[] = '<li><a href="#metadata">' . wfMessage( 'metadata' )->escaped() . '</a></li>'; + $r[] = '<li><a href="#metadata">' . $this->getContext()->msg( 'metadata' )->escaped() . '</a></li>'; } - wfRunHooks( 'ImagePageShowTOC', array( $this, &$r ) ); - return '<ul id="filetoc">' . implode( "\n", $r ) . '</ul>'; } @@ -260,7 +261,7 @@ class ImagePage extends Article { */ protected function makeMetadataTable( $metadata ) { $r = "<div class=\"mw-imagepage-section-metadata\">"; - $r .= wfMessage( 'metadata-help' )->plain(); + $r .= $this->getContext()->msg( 'metadata-help' )->plain(); $r .= "<table id=\"mw_metadata\" class=\"mw_metadata\">\n"; foreach ( $metadata as $type => $stuff ) { foreach ( $stuff as $v ) { @@ -336,19 +337,19 @@ class ImagePage extends Article { $filename = wfEscapeWikiText( $this->displayImg->getName() ); $linktext = $filename; - wfRunHooks( 'ImageOpenShowImageInlineBefore', array( &$this, &$out ) ); + Hooks::run( 'ImageOpenShowImageInlineBefore', array( &$this, &$out ) ); if ( $this->displayImg->allowInlineDisplay() ) { # image # "Download high res version" link below the image - # $msgsize = wfMessage( 'file-info-size', $width_orig, $height_orig, + # $msgsize = $this->getContext()->msg( 'file-info-size', $width_orig, $height_orig, # Linker::formatSize( $this->displayImg->getSize() ), $mime )->escaped(); # We'll show a thumbnail of this image if ( $width > $maxWidth || $height > $maxHeight || $this->displayImg->isVectorized() ) { list( $width, $height ) = $this->getDisplayWidthHeight( $maxWidth, $maxHeight, $width, $height ); - $linktext = wfMessage( 'show-big-image' )->escaped(); + $linktext = $this->getContext()->msg( 'show-big-image' )->escaped(); $thumbSizes = $this->getThumbSizes( $width, $height, $width_orig, $height_orig ); # Generate thumbnails or thumbnail links as needed... @@ -377,14 +378,14 @@ class ImagePage extends Article { $msgsmall = ''; $sizeLinkBigImagePreview = $this->makeSizeLink( $params, $width, $height ); if ( $sizeLinkBigImagePreview ) { - $msgsmall .= wfMessage( 'show-big-image-preview' )-> + $msgsmall .= $this->getContext()->msg( 'show-big-image-preview' )-> rawParams( $sizeLinkBigImagePreview )-> parse(); } if ( count( $otherSizes ) ) { $msgsmall .= ' ' . Html::rawElement( 'span', array( 'class' => 'mw-filepage-other-resolutions' ), - wfMessage( 'show-big-image-other' )->rawParams( $lang->pipeList( $otherSizes ) )-> + $this->getContext()->msg( 'show-big-image-other' )->rawParams( $lang->pipeList( $otherSizes ) )-> params( count( $otherSizes ) )->parse() ); } @@ -394,7 +395,7 @@ class ImagePage extends Article { $msgsmall = ''; } else { # Image is small enough to show full size on image page - $msgsmall = wfMessage( 'file-nohires' )->parse(); + $msgsmall = $this->getContext()->msg( 'file-nohires' )->parse(); } $params['width'] = $width; @@ -428,7 +429,7 @@ class ImagePage extends Article { $count = $this->displayImg->pageCount(); if ( $page > 1 ) { - $label = $out->parse( wfMessage( 'imgmultipageprev' )->text(), false ); + $label = $out->parse( $this->getContext()->msg( 'imgmultipageprev' )->text(), false ); // on the client side, this link is generated in ajaxifyPageNavigation() // in the mediawiki.page.image.pagination module $link = Linker::linkKnown( @@ -450,7 +451,7 @@ class ImagePage extends Article { } if ( $page < $count ) { - $label = wfMessage( 'imgmultipagenext' )->text(); + $label = $this->getContext()->msg( 'imgmultipagenext' )->text(); $link = Linker::linkKnown( $this->getTitle(), $label, @@ -487,8 +488,8 @@ class ImagePage extends Article { '</td><td><div class="multipageimagenavbox">' . Xml::openElement( 'form', $formParams ) . Html::hidden( 'title', $this->getTitle()->getPrefixedDBkey() ) . - wfMessage( 'imgmultigoto' )->rawParams( $select )->parse() . - Xml::submitButton( wfMessage( 'imgmultigo' )->text() ) . + $this->getContext()->msg( 'imgmultigoto' )->rawParams( $select )->parse() . + Xml::submitButton( $this->getContext()->msg( 'imgmultigo' )->text() ) . Xml::closeElement( 'form' ) . "<hr />$thumb1\n$thumb2<br style=\"clear: both\" /></div></td></tr></table>" ); @@ -502,12 +503,28 @@ class ImagePage extends Article { "</div>\n" ); } - $longDesc = wfMessage( 'parentheses', $this->displayImg->getLongDesc() )->text(); + $longDesc = $this->getContext()->msg( 'parentheses', $this->displayImg->getLongDesc() )->text(); + + $handler = $this->displayImg->getHandler(); + + // If this is a filetype with potential issues, warn the user. + if ( $handler ) { + $warningConfig = $handler->getWarningConfig( $this->displayImg ); + + if ( $warningConfig !== null ) { + // The warning will be displayed via CSS and JavaScript. + // We just need to tell the client side what message to use. + $output = $this->getContext()->getOutput(); + $output->addJsConfigVars( 'wgFileWarning', $warningConfig ); + $output->addModules( $warningConfig['module'] ); + $output->addModules( 'mediawiki.filewarning' ); + } + } $medialink = "[[Media:$filename|$linktext]]"; if ( !$this->displayImg->isSafeFile() ) { - $warning = wfMessage( 'mediawarning' )->plain(); + $warning = $this->getContext()->msg( 'mediawarning' )->plain(); // dirmark is needed here to separate the file name, which // most likely ends in Latin characters, from the description, // which may begin with the file type. In RTL environment @@ -619,7 +636,7 @@ EOT return Html::rawElement( 'a', array( 'href' => $thumbnail->getUrl(), 'class' => 'mw-thumbnail-link' - ), wfMessage( 'show-big-image-size' )->numParams( + ), $this->getContext()->msg( 'show-big-image-size' )->numParams( $thumbnail->getWidth(), $thumbnail->getHeight() )->parse() ); } else { @@ -645,9 +662,9 @@ EOT $wrap = "<div class=\"sharedUploadNotice\">\n$1\n</div>\n"; $repo = $this->mPage->getFile()->getRepo()->getDisplayName(); - if ( $descUrl && $descText && wfMessage( 'sharedupload-desc-here' )->plain() !== '-' ) { + if ( $descUrl && $descText && $this->getContext()->msg( 'sharedupload-desc-here' )->plain() !== '-' ) { $out->wrapWikiMsg( $wrap, array( 'sharedupload-desc-here', $repo, $descUrl ) ); - } elseif ( $descUrl && wfMessage( 'sharedupload-desc-there' )->plain() !== '-' ) { + } elseif ( $descUrl && $this->getContext()->msg( 'sharedupload-desc-there' )->plain() !== '-' ) { $out->wrapWikiMsg( $wrap, array( 'sharedupload-desc-there', $repo, $descUrl ) ); } else { $out->wrapWikiMsg( $wrap, array( 'sharedupload', $repo ), ''/*BACKCOMPAT*/ ); @@ -694,7 +711,7 @@ EOT ) { $ulink = Linker::makeExternalLink( $this->getUploadUrl(), - wfMessage( 'uploadnewversion-linktext' )->text() + $this->getContext()->msg( 'uploadnewversion-linktext' )->text() ); $out->addHTML( "<li id=\"mw-imagepage-reupload-link\">" . "<div class=\"plainlinks\">{$ulink}</div></li>\n" ); @@ -832,7 +849,7 @@ EOT $liContents = $link; } elseif ( count( $redirects[$element->page_title] ) === 0 ) { # Redirect without usages - $liContents = wfMessage( 'linkstoimage-redirect' )->rawParams( $link, '' )->parse(); + $liContents = $this->getContext()->msg( 'linkstoimage-redirect' )->rawParams( $link, '' )->parse(); } else { # Redirect with usages $li = ''; @@ -855,7 +872,7 @@ EOT array( 'class' => 'mw-imagepage-redirectstofile' ), $li ) . "\n"; - $liContents = wfMessage( 'linkstoimage-redirect' )->rawParams( + $liContents = $this->getContext()->msg( 'linkstoimage-redirect' )->rawParams( $link, $ul )->parse(); } $out->addHTML( Html::rawElement( @@ -901,7 +918,7 @@ EOT } else { $link = Linker::makeExternalLink( $file->getDescriptionUrl(), $file->getTitle()->getPrefixedText() ); - $fromSrc = wfMessage( 'shared-repo-from', $file->getRepo()->getDisplayName() )->text(); + $fromSrc = $this->getContext()->msg( 'shared-repo-from', $file->getRepo()->getDisplayName() )->text(); } $out->addHTML( "<li>{$link} {$fromSrc}</li>\n" ); } @@ -930,7 +947,7 @@ EOT */ function showError( $description ) { $out = $this->getContext()->getOutput(); - $out->setPageTitle( wfMessage( 'internalerror' ) ); + $out->setPageTitle( $this->getContext()->msg( 'internalerror' ) ); $out->setRobotPolicy( 'noindex,nofollow' ); $out->setArticleRelated( false ); $out->enableClientCache( false ); @@ -1008,7 +1025,7 @@ EOT $code = wfBCP47( $lang ); $name = Language::fetchLanguageName( $code, $this->getContext()->getLanguage()->getCode() ); if ( $name !== '' ) { - $display = wfMessage( 'img-lang-opt', $code, $name )->text(); + $display = $this->getContext()->msg( 'img-lang-opt', $code, $name )->text(); } else { $display = $code; } @@ -1024,7 +1041,7 @@ EOT // Its hard to know if the content is really in the default language, or // if its just unmarked content that could be in any language. $opts = Xml::option( - wfMessage( 'img-lang-default' )->text(), + $this->getContext()->msg( 'img-lang-default' )->text(), $defaultLang, $defaultLang === $curLang ) . $opts; @@ -1032,7 +1049,7 @@ EOT if ( !$haveCurrentLang && $defaultLang !== $curLang ) { $name = Language::fetchLanguageName( $curLang, $this->getContext()->getLanguage()->getCode() ); if ( $name !== '' ) { - $display = wfMessage( 'img-lang-opt', $curLang, $name )->text(); + $display = $this->getContext()->msg( 'img-lang-opt', $curLang, $name )->text(); } else { $display = $curLang; } @@ -1044,9 +1061,9 @@ EOT array( 'id' => 'mw-imglangselector', 'name' => 'lang' ), $opts ); - $submit = Xml::submitButton( wfMessage( 'img-lang-go' )->text() ); + $submit = Xml::submitButton( $this->getContext()->msg( 'img-lang-go' )->text() ); - $formContents = wfMessage( 'img-lang-info' )->rawParams( $select, $submit )->parse() + $formContents = $this->getContext()->msg( 'img-lang-info' )->rawParams( $select, $submit )->parse() . Html::hidden( 'title', $this->getTitle()->getPrefixedDBkey() ); $langSelectLine = Html::rawElement( 'div', array( 'id' => 'mw-imglangselector-line' ), @@ -1199,9 +1216,9 @@ class ImageHistoryList extends ContextSource { . $this->msg( 'filehist-help' )->parseAsBlock() . $navLinks . "\n" . Xml::openElement( 'table', array( 'class' => 'wikitable filehistory' ) ) . "\n" - . '<tr><td></td>' + . '<tr><th></th>' . ( $this->current->isLocal() - && ( $this->getUser()->isAllowedAny( 'delete', 'deletedhistory' ) ) ? '<td></td>' : '' ) + && ( $this->getUser()->isAllowedAny( 'delete', 'deletedhistory' ) ) ? '<th></th>' : '' ) . '<th>' . $this->msg( 'filehist-datetime' )->escaped() . '</th>' . ( $this->showThumb ? '<th>' . $this->msg( 'filehist-thumb' )->escaped() . '</th>' : '' ) . '<th>' . $this->msg( 'filehist-dimensions' )->escaped() . '</th>' @@ -1364,7 +1381,6 @@ class ImageHistoryList extends ContextSource { } else { if ( $local ) { $row .= Linker::userLink( $userId, $userText ); - $row .= $this->msg( 'word-separator' )->escaped(); $row .= '<span style="white-space: nowrap;">'; $row .= Linker::userToolLinks( $userId, $userText ); $row .= '</span>'; @@ -1384,7 +1400,7 @@ class ImageHistoryList extends ContextSource { } $rowClass = null; - wfRunHooks( 'ImagePageFileHistoryLine', array( $this, $file, &$row, &$rowClass ) ); + Hooks::run( 'ImagePageFileHistoryLine', array( $this, $file, &$row, &$rowClass ) ); $classAttr = $rowClass ? " class='$rowClass'" : ''; return "<tr{$classAttr}>{$row}</tr>\n"; diff --git a/includes/page/WikiPage.php b/includes/page/WikiPage.php index 9ade16e5..7c789249 100644 --- a/includes/page/WikiPage.php +++ b/includes/page/WikiPage.php @@ -31,8 +31,6 @@ interface Page { * * Some fields are public only for backwards-compatibility. Use accessors. * In the past, this class was part of Article.php and everything was public. - * - * @internal documentation reviewed 15 Mar 2010 */ class WikiPage implements Page, IDBAccessObject { // Constants for $mDataLoadedFrom and related @@ -50,7 +48,7 @@ class WikiPage implements Page, IDBAccessObject { public $mLatest = false; // !< Integer (false means "not loaded") /**@}}*/ - /** @var stdclass Map of cache fields (text, parser output, ect) for a proposed/new edit */ + /** @var stdClass Map of cache fields (text, parser output, ect) for a proposed/new edit */ public $mPreparedEdit = false; /** @@ -89,11 +87,6 @@ class WikiPage implements Page, IDBAccessObject { protected $mLinksUpdated = '19700101000000'; /** - * @var int|null - */ - protected $mCounter = null; - - /** * Constructor and clear the article * @param Title $title Reference to a Title object. */ @@ -247,7 +240,6 @@ class WikiPage implements Page, IDBAccessObject { */ protected function clearCacheFields() { $this->mId = null; - $this->mCounter = null; $this->mRedirectTarget = null; // Title object if set $this->mLastRevision = null; // Latest revision $this->mTouched = '19700101000000'; @@ -284,7 +276,6 @@ class WikiPage implements Page, IDBAccessObject { 'page_namespace', 'page_title', 'page_restrictions', - 'page_counter', 'page_is_redirect', 'page_is_new', 'page_random', @@ -315,11 +306,11 @@ class WikiPage implements Page, IDBAccessObject { protected function pageData( $dbr, $conditions, $options = array() ) { $fields = self::selectFields(); - wfRunHooks( 'ArticlePageDataBefore', array( &$this, &$fields ) ); + Hooks::run( 'ArticlePageDataBefore', array( &$this, &$fields ) ); $row = $dbr->selectRow( 'page', $fields, $conditions, __METHOD__, $options ); - wfRunHooks( 'ArticlePageDataAfter', array( &$this, &$row ) ); + Hooks::run( 'ArticlePageDataAfter', array( &$this, &$row ) ); return $row; } @@ -352,8 +343,7 @@ class WikiPage implements Page, IDBAccessObject { } /** - * Set the general counter, title etc data loaded from - * some source. + * Load the object from a given source by title * * @param object|string|int $from One of the following: * - A DB query result object. @@ -377,14 +367,12 @@ class WikiPage implements Page, IDBAccessObject { $data = $this->pageDataFromTitle( wfGetDB( DB_MASTER ), $this->mTitle ); } elseif ( $from === self::READ_NORMAL ) { $data = $this->pageDataFromTitle( wfGetDB( DB_SLAVE ), $this->mTitle ); - // Use a "last rev inserted" timestamp key to diminish the issue of slave lag. - // Note that DB also stores the master position in the session and checks it. - $touched = $this->getCachedLastEditTime(); - if ( $touched ) { // key set - if ( !$data || $touched > wfTimestamp( TS_MW, $data->page_touched ) ) { - $from = self::READ_LATEST; - $data = $this->pageDataFromTitle( wfGetDB( DB_MASTER ), $this->mTitle ); - } + if ( !$data + && wfGetLB()->getServerCount() > 1 + && wfGetLB()->hasOrMadeRecentMasterChanges() + ) { + $from = self::READ_LATEST; + $data = $this->pageDataFromTitle( wfGetDB( DB_MASTER ), $this->mTitle ); } } else { // No idea from where the caller got this data, assume slave database. @@ -403,7 +391,7 @@ class WikiPage implements Page, IDBAccessObject { * @param string|int $from One of the following: * - "fromdb" or WikiPage::READ_NORMAL if the data comes from a slave DB * - "fromdbmaster" or WikiPage::READ_LATEST if the data comes from the master DB - * - "forupdate" or WikiPage::READ_LOCKING if the data comes from from + * - "forupdate" or WikiPage::READ_LOCKING if the data comes from * the master DB using SELECT FOR UPDATE */ public function loadFromRow( $data, $from ) { @@ -419,7 +407,6 @@ class WikiPage implements Page, IDBAccessObject { $this->mTitle->loadRestrictions( $data->page_restrictions ); $this->mId = intval( $data->page_id ); - $this->mCounter = intval( $data->page_counter ); $this->mTouched = wfTimestamp( TS_MW, $data->page_touched ); $this->mLinksUpdated = wfTimestampOrNull( TS_MW, $data->page_links_updated ); $this->mIsRedirect = intval( $data->page_is_redirect ); @@ -477,17 +464,6 @@ class WikiPage implements Page, IDBAccessObject { } /** - * @return int The view count for the page - */ - public function getCount() { - if ( !$this->mDataLoaded ) { - $this->loadPageData(); - } - - return $this->mCounter; - } - - /** * Tests if the article content represents a redirect * * @return bool @@ -577,7 +553,6 @@ class WikiPage implements Page, IDBAccessObject { * @return Revision|null */ public function getOldestRevision() { - wfProfileIn( __METHOD__ ); // Try using the slave database first, then try the master $continue = 2; @@ -608,7 +583,6 @@ class WikiPage implements Page, IDBAccessObject { } } - wfProfileOut( __METHOD__ ); return $row ? Revision::newFromRow( $row ) : null; } @@ -626,13 +600,23 @@ class WikiPage implements Page, IDBAccessObject { return; // page doesn't exist or is missing page_latest info } - // Bug 37225: if session S1 loads the page row FOR UPDATE, the result always includes the - // latest changes committed. This is true even within REPEATABLE-READ transactions, where - // S1 normally only sees changes committed before the first S1 SELECT. Thus we need S1 to - // also gets the revision row FOR UPDATE; otherwise, it may not find it since a page row - // UPDATE and revision row INSERT by S2 may have happened after the first S1 SELECT. - // http://dev.mysql.com/doc/refman/5.0/en/set-transaction.html#isolevel_repeatable-read. - $flags = ( $this->mDataLoadedFrom == self::READ_LOCKING ) ? Revision::READ_LOCKING : 0; + if ( $this->mDataLoadedFrom == self::READ_LOCKING ) { + // Bug 37225: if session S1 loads the page row FOR UPDATE, the result always + // includes the latest changes committed. This is true even within REPEATABLE-READ + // transactions, where S1 normally only sees changes committed before the first S1 + // SELECT. Thus we need S1 to also gets the revision row FOR UPDATE; otherwise, it + // may not find it since a page row UPDATE and revision row INSERT by S2 may have + // happened after the first S1 SELECT. + // http://dev.mysql.com/doc/refman/5.0/en/set-transaction.html#isolevel_repeatable-read. + $flags = Revision::READ_LOCKING; + } elseif ( $this->mDataLoadedFrom == self::READ_LATEST ) { + // Bug T93976: if page_latest was loaded from the master, fetch the + // revision from there as well, as it may not exist yet on a slave DB. + // Also, this keeps the queries in the same REPEATABLE-READ snapshot. + $flags = Revision::READ_LATEST; + } else { + $flags = 0; + } $revision = Revision::newFromPageId( $this->getId(), $latest, $flags ); if ( $revision ) { // sanity $this->setLastEdit( $revision ); @@ -825,29 +809,6 @@ class WikiPage implements Page, IDBAccessObject { } /** - * Get the cached timestamp for the last time the page changed. - * This is only used to help handle slave lag by comparing to page_touched. - * @return string MW timestamp - */ - protected function getCachedLastEditTime() { - global $wgMemc; - $key = wfMemcKey( 'page-lastedit', md5( $this->mTitle->getPrefixedDBkey() ) ); - return $wgMemc->get( $key ); - } - - /** - * Set the cached timestamp for the last time the page changed. - * This is only used to help handle slave lag by comparing to page_touched. - * @param string $timestamp - * @return void - */ - public function setCachedLastEditTime( $timestamp ) { - global $wgMemc; - $key = wfMemcKey( 'page-lastedit', md5( $this->mTitle->getPrefixedDBkey() ) ); - $wgMemc->set( $key, wfTimestamp( TS_MW, $timestamp ), 60 * 15 ); - } - - /** * Determine whether a page would be suitable for being counted as an * article in the site_stats table based on the title & its content * @@ -995,7 +956,7 @@ class WikiPage implements Page, IDBAccessObject { $source = $this->mTitle->getFullURL( 'redirect=no' ); return $rt->getFullURL( array( 'rdfrom' => $source ) ); } else { - // External pages pages without "local" bit set are not valid + // External pages without "local" bit set are not valid // redirect targets return false; } @@ -1075,7 +1036,6 @@ class WikiPage implements Page, IDBAccessObject { * @return array Array of authors, duplicates not removed */ public function getLastNAuthors( $num, $revLatest = 0 ) { - wfProfileIn( __METHOD__ ); // First try the slave // If that doesn't have the latest revision, try the master $continue = 2; @@ -1096,7 +1056,6 @@ class WikiPage implements Page, IDBAccessObject { ); if ( !$res ) { - wfProfileOut( __METHOD__ ); return array(); } @@ -1116,7 +1075,6 @@ class WikiPage implements Page, IDBAccessObject { $authors[] = $row->rev_user_text; } - wfProfileOut( __METHOD__ ); return $authors; } @@ -1149,7 +1107,6 @@ class WikiPage implements Page, IDBAccessObject { * @return ParserOutput|bool ParserOutput or false if the revision was not found */ public function getParserOutput( ParserOptions $parserOptions, $oldid = null ) { - wfProfileIn( __METHOD__ ); $useParserCache = $this->isParserCacheUsed( $parserOptions, $oldid ); wfDebug( __METHOD__ . ': using parser cache: ' . ( $useParserCache ? 'yes' : 'no' ) . "\n" ); @@ -1160,7 +1117,6 @@ class WikiPage implements Page, IDBAccessObject { if ( $useParserCache ) { $parserOutput = ParserCache::singleton()->get( $this, $parserOptions ); if ( $parserOutput !== false ) { - wfProfileOut( __METHOD__ ); return $parserOutput; } } @@ -1172,8 +1128,6 @@ class WikiPage implements Page, IDBAccessObject { $pool = new PoolWorkArticleView( $this, $parserOptions, $oldid, $useParserCache ); $pool->execute(); - wfProfileOut( __METHOD__ ); - return $pool->getParserOutput(); } @@ -1183,17 +1137,11 @@ class WikiPage implements Page, IDBAccessObject { * @param int $oldid The revision id being viewed. If not given or 0, latest revision is assumed. */ public function doViewUpdates( User $user, $oldid = 0 ) { - global $wgDisableCounters; if ( wfReadOnly() ) { return; } - // Don't update page view counters on views from bot users (bug 14044) - if ( !$wgDisableCounters && !$user->isAllowed( 'bot' ) && $this->exists() ) { - DeferredUpdates::addUpdate( new ViewCountUpdate( $this->getId() ) ); - DeferredUpdates::addUpdate( new SiteStatsUpdate( 1, 0, 0 ) ); - } - + Hooks::run( 'PageViewUpdates', array( $this, $user ) ); // Update newtalk / watchlist notification status $user->clearNotification( $this->mTitle, $oldid ); } @@ -1205,7 +1153,7 @@ class WikiPage implements Page, IDBAccessObject { public function doPurge() { global $wgUseSquid; - if ( !wfRunHooks( 'ArticlePurge', array( &$this ) ) ) { + if ( !Hooks::run( 'ArticlePurge', array( &$this ) ) ) { return false; } @@ -1255,14 +1203,12 @@ class WikiPage implements Page, IDBAccessObject { * @return int The newly created page_id key, or false if the title already existed */ public function insertOn( $dbw ) { - wfProfileIn( __METHOD__ ); $page_id = $dbw->nextSequenceValue( 'page_page_id_seq' ); $dbw->insert( 'page', array( 'page_id' => $page_id, 'page_namespace' => $this->mTitle->getNamespace(), 'page_title' => $this->mTitle->getDBkey(), - 'page_counter' => 0, 'page_restrictions' => '', 'page_is_redirect' => 0, // Will set this shortly... 'page_is_new' => 1, @@ -1279,7 +1225,6 @@ class WikiPage implements Page, IDBAccessObject { $this->mId = $newid; $this->mTitle->resetArticleID( $newid ); } - wfProfileOut( __METHOD__ ); return $affected ? $newid : false; } @@ -1302,7 +1247,12 @@ class WikiPage implements Page, IDBAccessObject { ) { global $wgContentHandlerUseDB; - wfProfileIn( __METHOD__ ); + // Assertion to try to catch T92046 + if ( (int)$revision->getId() === 0 ) { + throw new InvalidArgumentException( + __METHOD__ . ': Revision has ID ' . var_export( $revision->getId(), 1 ) + ); + } $content = $revision->getContent(); $len = $content ? $content->getSize() : 0; @@ -1337,7 +1287,6 @@ class WikiPage implements Page, IDBAccessObject { if ( $result ) { $this->updateRedirectOn( $dbw, $rt, $lastRevIsRedirect ); $this->setLastEdit( $revision ); - $this->setCachedLastEditTime( $now ); $this->mLatest = $revision->getId(); $this->mIsRedirect = (bool)$rt; // Update the LinkCache. @@ -1345,7 +1294,6 @@ class WikiPage implements Page, IDBAccessObject { $this->mLatest, $revision->getContentModel() ); } - wfProfileOut( __METHOD__ ); return $result; } @@ -1370,7 +1318,6 @@ class WikiPage implements Page, IDBAccessObject { return true; } - wfProfileIn( __METHOD__ ); if ( $isRedirect ) { $this->insertRedirectEntry( $redirectTitle ); } else { @@ -1382,7 +1329,6 @@ class WikiPage implements Page, IDBAccessObject { if ( $this->getTitle()->getNamespace() == NS_FILE ) { RepoGroup::singleton()->getLocalRepo()->invalidateImageRedirect( $this->getTitle() ); } - wfProfileOut( __METHOD__ ); return ( $dbw->affectedRows() != 0 ); } @@ -1398,7 +1344,6 @@ class WikiPage implements Page, IDBAccessObject { * @return bool */ public function updateIfNewerOn( $dbw, $revision ) { - wfProfileIn( __METHOD__ ); $row = $dbw->selectRow( array( 'revision', 'page' ), @@ -1410,7 +1355,6 @@ class WikiPage implements Page, IDBAccessObject { if ( $row ) { if ( wfTimestamp( TS_MW, $row->rev_timestamp ) >= $revision->getTimestamp() ) { - wfProfileOut( __METHOD__ ); return false; } $prev = $row->rev_id; @@ -1423,7 +1367,6 @@ class WikiPage implements Page, IDBAccessObject { $ret = $this->updateRevisionOn( $dbw, $revision, $prev, $lastRevIsRedirect ); - wfProfileOut( __METHOD__ ); return $ret; } @@ -1542,18 +1485,26 @@ class WikiPage implements Page, IDBAccessObject { */ public function replaceSectionContent( $sectionId, Content $sectionContent, $sectionTitle = '', $edittime = null ) { - wfProfileIn( __METHOD__ ); $baseRevId = null; if ( $edittime && $sectionId !== 'new' ) { - $dbw = wfGetDB( DB_MASTER ); - $rev = Revision::loadFromTimestamp( $dbw, $this->mTitle, $edittime ); + $dbr = wfGetDB( DB_SLAVE ); + $rev = Revision::loadFromTimestamp( $dbr, $this->mTitle, $edittime ); + // Try the master if this thread may have just added it. + // This could be abstracted into a Revision method, but we don't want + // to encourage loading of revisions by timestamp. + if ( !$rev + && wfGetLB()->getServerCount() > 1 + && wfGetLB()->hasOrMadeRecentMasterChanges() + ) { + $dbw = wfGetDB( DB_MASTER ); + $rev = Revision::loadFromTimestamp( $dbw, $this->mTitle, $edittime ); + } if ( $rev ) { $baseRevId = $rev->getId(); } } - wfProfileOut( __METHOD__ ); return $this->replaceSectionAtRev( $sectionId, $sectionContent, $sectionTitle, $baseRevId ); } @@ -1573,14 +1524,12 @@ class WikiPage implements Page, IDBAccessObject { public function replaceSectionAtRev( $sectionId, Content $sectionContent, $sectionTitle = '', $baseRevId = null ) { - wfProfileIn( __METHOD__ ); if ( strval( $sectionId ) === '' ) { // Whole-page edit; let the whole text through $newContent = $sectionContent; } else { if ( !$this->supportsSections() ) { - wfProfileOut( __METHOD__ ); throw new MWException( "sections not supported for content model " . $this->getContentHandler()->getModelID() ); } @@ -1589,14 +1538,10 @@ class WikiPage implements Page, IDBAccessObject { if ( is_null( $baseRevId ) || $sectionId === 'new' ) { $oldContent = $this->getContent(); } else { - // TODO: try DB_SLAVE first - $dbw = wfGetDB( DB_MASTER ); - $rev = Revision::loadFromId( $dbw, $baseRevId ); - + $rev = Revision::newFromId( $baseRevId ); if ( !$rev ) { wfDebug( __METHOD__ . " asked for bogus section (page: " . $this->getId() . "; section: $sectionId)\n" ); - wfProfileOut( __METHOD__ ); return null; } @@ -1605,14 +1550,12 @@ class WikiPage implements Page, IDBAccessObject { if ( !$oldContent ) { wfDebug( __METHOD__ . ": no page text\n" ); - wfProfileOut( __METHOD__ ); return null; } $newContent = $oldContent->replaceSection( $sectionId, $sectionContent, $sectionTitle ); } - wfProfileOut( __METHOD__ ); return $newContent; } @@ -1662,7 +1605,9 @@ class WikiPage implements Page, IDBAccessObject { * error will be returned. These two conditions are also possible with * auto-detection due to MediaWiki's performance-optimised locking strategy. * - * @param bool|int $baseRevId The revision ID this edit was based off, if any + * @param bool|int $baseRevId The revision ID this edit was based off, if any. + * This is not the parent revision ID, rather the revision ID for older + * content used as the source for a rollback, for example. * @param User $user The user doing the edit * * @throws MWException @@ -1722,9 +1667,11 @@ class WikiPage implements Page, IDBAccessObject { * error will be returned. These two conditions are also possible with * auto-detection due to MediaWiki's performance-optimised locking strategy. * - * @param bool|int $baseRevId The revision ID this edit was based off, if any + * @param bool|int $baseRevId The revision ID this edit was based off, if any. + * This is not the parent revision ID, rather the revision ID for older + * content used as the source for a rollback, for example. * @param User $user The user doing the edit - * @param string $serialisation_format Format for storing the content in the + * @param string $serialFormat Format for storing the content in the * database. * * @throws MWException @@ -1745,7 +1692,7 @@ class WikiPage implements Page, IDBAccessObject { * @since 1.21 */ public function doEditContent( Content $content, $summary, $flags = 0, $baseRevId = false, - User $user = null, $serialisation_format = null + User $user = null, $serialFormat = null ) { global $wgUser, $wgUseAutomaticEditSummaries, $wgUseRCPatrol, $wgUseNPPatrol; @@ -1754,10 +1701,7 @@ class WikiPage implements Page, IDBAccessObject { throw new MWException( 'Something is trying to edit an article with an empty title' ); } - wfProfileIn( __METHOD__ ); - if ( !$content->getContentHandler()->canBeUsedOn( $this->getTitle() ) ) { - wfProfileOut( __METHOD__ ); return Status::newFatal( 'content-not-allowed-here', ContentHandler::getLocalizedName( $content->getModel() ), $this->getTitle()->getPrefixedText() ); @@ -1777,7 +1721,7 @@ class WikiPage implements Page, IDBAccessObject { $hook_args = array( &$this, &$user, &$content, &$summary, $flags & EDIT_MINOR, null, null, &$flags, &$status ); - if ( !wfRunHooks( 'PageContentSave', $hook_args ) + if ( !Hooks::run( 'PageContentSave', $hook_args ) || !ContentHandler::runLegacyHooks( 'ArticleSave', $hook_args ) ) { wfDebug( __METHOD__ . ": ArticleSave or ArticleSaveContent hook aborted save!\n" ); @@ -1786,7 +1730,6 @@ class WikiPage implements Page, IDBAccessObject { $status->fatal( 'edit-hook-aborted' ); } - wfProfileOut( __METHOD__ ); return $status; } @@ -1811,7 +1754,7 @@ class WikiPage implements Page, IDBAccessObject { $summary = $handler->getAutosummary( $old_content, $content, $flags ); } - $editInfo = $this->prepareContentForEdit( $content, null, $user, $serialisation_format ); + $editInfo = $this->prepareContentForEdit( $content, null, $user, $serialFormat ); $serialized = $editInfo->pst; /** @@ -1833,11 +1776,9 @@ class WikiPage implements Page, IDBAccessObject { wfDebug( __METHOD__ . ": EDIT_UPDATE specified but article doesn't exist\n" ); $status->fatal( 'edit-gone-missing' ); - wfProfileOut( __METHOD__ ); return $status; } elseif ( !$old_content ) { // Sanity check for bug 37225 - wfProfileOut( __METHOD__ ); throw new MWException( "Could not find text for current revision {$oldid}." ); } @@ -1853,27 +1794,21 @@ class WikiPage implements Page, IDBAccessObject { 'user_text' => $user->getName(), 'timestamp' => $now, 'content_model' => $content->getModel(), - 'content_format' => $serialisation_format, + 'content_format' => $serialFormat, ) ); // XXX: pass content object?! $changed = !$content->equals( $old_content ); if ( $changed ) { - if ( !$content->isValid() ) { - wfProfileOut( __METHOD__ ); - throw new MWException( "New content failed validity check!" ); - } - $dbw->begin( __METHOD__ ); try { - $prepStatus = $content->prepareSave( $this, $flags, $baseRevId, $user ); + $prepStatus = $content->prepareSave( $this, $flags, $oldid, $user ); $status->merge( $prepStatus ); if ( !$status->isOK() ) { $dbw->rollback( __METHOD__ ); - wfProfileOut( __METHOD__ ); return $status; } $revisionId = $revision->insertOn( $dbw ); @@ -1889,11 +1824,10 @@ class WikiPage implements Page, IDBAccessObject { $dbw->rollback( __METHOD__ ); - wfProfileOut( __METHOD__ ); return $status; } - wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $revision, $baseRevId, $user ) ); + Hooks::run( 'NewRevisionFromEditComplete', array( $this, $revision, $baseRevId, $user ) ); // Update recentchanges if ( !( $flags & EDIT_SUPPRESS_RC ) ) { // Mark as patrolled if the user can do so @@ -1911,7 +1845,7 @@ class WikiPage implements Page, IDBAccessObject { } } $user->incEditCount(); - } catch ( MWException $e ) { + } catch ( Exception $e ) { $dbw->rollback( __METHOD__ ); // Question: Would it perhaps be better if this method turned all // exceptions into $status's? @@ -1948,13 +1882,12 @@ class WikiPage implements Page, IDBAccessObject { $dbw->begin( __METHOD__ ); try { - $prepStatus = $content->prepareSave( $this, $flags, $baseRevId, $user ); + $prepStatus = $content->prepareSave( $this, $flags, $oldid, $user ); $status->merge( $prepStatus ); if ( !$status->isOK() ) { $dbw->rollback( __METHOD__ ); - wfProfileOut( __METHOD__ ); return $status; } @@ -1968,7 +1901,6 @@ class WikiPage implements Page, IDBAccessObject { $dbw->rollback( __METHOD__ ); $status->fatal( 'edit-already-exists' ); - wfProfileOut( __METHOD__ ); return $status; } @@ -1984,7 +1916,7 @@ class WikiPage implements Page, IDBAccessObject { 'user_text' => $user->getName(), 'timestamp' => $now, 'content_model' => $content->getModel(), - 'content_format' => $serialisation_format, + 'content_format' => $serialFormat, ) ); $revisionId = $revision->insertOn( $dbw ); @@ -1998,7 +1930,7 @@ class WikiPage implements Page, IDBAccessObject { // Update the page record with revision data $this->updateRevisionOn( $dbw, $revision, 0 ); - wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $revision, false, $user ) ); + Hooks::run( 'NewRevisionFromEditComplete', array( $this, $revision, false, $user ) ); // Update recentchanges if ( !( $flags & EDIT_SUPPRESS_RC ) ) { @@ -2016,7 +1948,7 @@ class WikiPage implements Page, IDBAccessObject { } $user->incEditCount(); - } catch ( MWException $e ) { + } catch ( Exception $e ) { $dbw->rollback( __METHOD__ ); throw $e; } @@ -2029,7 +1961,7 @@ class WikiPage implements Page, IDBAccessObject { $flags & EDIT_MINOR, null, null, &$flags, $revision ); ContentHandler::runLegacyHooks( 'ArticleInsertComplete', $hook_args ); - wfRunHooks( 'PageContentInsertComplete', $hook_args ); + Hooks::run( 'PageContentInsertComplete', $hook_args ); } // Do updates right now unless deferral was requested @@ -2044,14 +1976,14 @@ class WikiPage implements Page, IDBAccessObject { $flags & EDIT_MINOR, null, null, &$flags, $revision, &$status, $baseRevId ); ContentHandler::runLegacyHooks( 'ArticleSaveComplete', $hook_args ); - wfRunHooks( 'PageContentSaveComplete', $hook_args ); + Hooks::run( 'PageContentSaveComplete', $hook_args ); // Promote user to any groups they meet the criteria for $dbw->onTransactionIdle( function () use ( $user ) { $user->addAutopromoteOnceGroups( 'onEdit' ); + $user->addAutopromoteOnceGroups( 'onView' ); // b/c } ); - wfProfileOut( __METHOD__ ); return $status; } @@ -2083,7 +2015,7 @@ class WikiPage implements Page, IDBAccessObject { /** * Prepare text which is about to be saved. - * Returns a stdclass with source, pst and output members + * Returns a stdClass with source, pst and output members * * @deprecated since 1.21: use prepareContentForEdit instead. * @return object @@ -2096,54 +2028,109 @@ class WikiPage implements Page, IDBAccessObject { /** * Prepare content which is about to be saved. - * Returns a stdclass with source, pst and output members + * Returns a stdClass with source, pst and output members * * @param Content $content - * @param int|null $revid + * @param Revision|int|null $revision Revision object. For backwards compatibility, a + * revision ID is also accepted, but this is deprecated. * @param User|null $user - * @param string|null $serialization_format + * @param string|null $serialFormat + * @param bool $useCache Check shared prepared edit cache * - * @return bool|object + * @return object * * @since 1.21 */ - public function prepareContentForEdit( Content $content, $revid = null, User $user = null, - $serialization_format = null + public function prepareContentForEdit( + Content $content, $revision = null, User $user = null, $serialFormat = null, $useCache = true ) { - global $wgContLang, $wgUser; + global $wgContLang, $wgUser, $wgAjaxEditStash; + + if ( is_object( $revision ) ) { + $revid = $revision->getId(); + } else { + $revid = $revision; + // This code path is deprecated, and nothing is known to + // use it, so performance here shouldn't be a worry. + if ( $revid !== null ) { + $revision = Revision::newFromId( $revid, Revision::READ_LATEST ); + } else { + $revision = null; + } + } + $user = is_null( $user ) ? $wgUser : $user; //XXX: check $user->getId() here??? - // Use a sane default for $serialization_format, see bug 57026 - if ( $serialization_format === null ) { - $serialization_format = $content->getContentHandler()->getDefaultFormat(); + // Use a sane default for $serialFormat, see bug 57026 + if ( $serialFormat === null ) { + $serialFormat = $content->getContentHandler()->getDefaultFormat(); } if ( $this->mPreparedEdit && $this->mPreparedEdit->newContent && $this->mPreparedEdit->newContent->equals( $content ) && $this->mPreparedEdit->revid == $revid - && $this->mPreparedEdit->format == $serialization_format + && $this->mPreparedEdit->format == $serialFormat // XXX: also check $user here? ) { // Already prepared return $this->mPreparedEdit; } + // The edit may have already been prepared via api.php?action=stashedit + $cachedEdit = $useCache && $wgAjaxEditStash + ? ApiStashEdit::checkCache( $this->getTitle(), $content, $user ) + : false; + $popts = ParserOptions::newFromUserAndLang( $user, $wgContLang ); - wfRunHooks( 'ArticlePrepareTextForEdit', array( $this, $popts ) ); + Hooks::run( 'ArticlePrepareTextForEdit', array( $this, $popts ) ); $edit = (object)array(); + if ( $cachedEdit ) { + $edit->timestamp = $cachedEdit->timestamp; + } else { + $edit->timestamp = wfTimestampNow(); + } + // @note: $cachedEdit is not used if the rev ID was referenced in the text $edit->revid = $revid; - $edit->timestamp = wfTimestampNow(); - $edit->pstContent = $content ? $content->preSaveTransform( $this->mTitle, $user, $popts ) : null; + if ( $cachedEdit ) { + $edit->pstContent = $cachedEdit->pstContent; + } else { + $edit->pstContent = $content + ? $content->preSaveTransform( $this->mTitle, $user, $popts ) + : null; + } - $edit->format = $serialization_format; + $edit->format = $serialFormat; $edit->popts = $this->makeParserOptions( 'canonical' ); - $edit->output = $edit->pstContent - ? $edit->pstContent->getParserOutput( $this->mTitle, $revid, $edit->popts ) - : null; + if ( $cachedEdit ) { + $edit->output = $cachedEdit->output; + } else { + if ( $revision ) { + // We get here if vary-revision is set. This means that this page references + // itself (such as via self-transclusion). In this case, we need to make sure + // that any such self-references refer to the newly-saved revision, and not + // to the previous one, which could otherwise happen due to slave lag. + $oldCallback = $edit->popts->setCurrentRevisionCallback( + function ( $title, $parser = false ) use ( $revision, &$oldCallback ) { + if ( $title->equals( $revision->getTitle() ) ) { + return $revision; + } else { + return call_user_func( + $oldCallback, + $title, + $parser + ); + } + } + ); + } + $edit->output = $edit->pstContent + ? $edit->pstContent->getParserOutput( $this->mTitle, $revid, $edit->popts ) + : null; + } $edit->newContent = $content; $edit->oldContent = $this->getContent( Revision::RAW ); @@ -2151,7 +2138,7 @@ class WikiPage implements Page, IDBAccessObject { // NOTE: B/C for hooks! don't use these fields! $edit->newText = $edit->newContent ? ContentHandler::getContentText( $edit->newContent ) : ''; $edit->oldText = $edit->oldContent ? ContentHandler::getContentText( $edit->oldContent ) : ''; - $edit->pst = $edit->pstContent ? $edit->pstContent->serialize( $serialization_format ) : ''; + $edit->pst = $edit->pstContent ? $edit->pstContent->serialize( $serialFormat ) : ''; $this->mPreparedEdit = $edit; return $edit; @@ -2168,17 +2155,23 @@ class WikiPage implements Page, IDBAccessObject { * @param array $options Array of options, following indexes are used: * - changed: boolean, whether the revision changed the content (default true) * - created: boolean, whether the revision created the page (default false) - * - oldcountable: boolean or null (default null): + * - moved: boolean, whether the page was moved (default false) + * - oldcountable: boolean, null, or string 'no-change' (default null): * - boolean: whether the page was counted as an article before that * revision, only used in changed is true and created is false - * - null: don't change the article count + * - null: if created is false, don't update the article count; if created + * is true, do update the article count + * - 'no-change': don't update the article count, ever */ public function doEditUpdates( Revision $revision, User $user, array $options = array() ) { global $wgEnableParserCache; - wfProfileIn( __METHOD__ ); - - $options += array( 'changed' => true, 'created' => false, 'oldcountable' => null ); + $options += array( + 'changed' => true, + 'created' => false, + 'moved' => false, + 'oldcountable' => null + ); $content = $revision->getContent(); // Parse the text @@ -2186,7 +2179,7 @@ class WikiPage implements Page, IDBAccessObject { // already pre-save transformed once. if ( !$this->mPreparedEdit || $this->mPreparedEdit->output->getFlag( 'vary-revision' ) ) { wfDebug( __METHOD__ . ": No prepared edit or vary-revision is set...\n" ); - $editInfo = $this->prepareContentForEdit( $content, $revision->getId(), $user ); + $editInfo = $this->prepareContentForEdit( $content, $revision, $user ); } else { wfDebug( __METHOD__ . ": No vary-revision, using prepared edit...\n" ); $editInfo = $this->mPreparedEdit; @@ -2208,18 +2201,18 @@ class WikiPage implements Page, IDBAccessObject { DataUpdate::runUpdates( $updates ); } - wfRunHooks( 'ArticleEditUpdates', array( &$this, &$editInfo, $options['changed'] ) ); + Hooks::run( 'ArticleEditUpdates', array( &$this, &$editInfo, $options['changed'] ) ); - if ( wfRunHooks( 'ArticleEditUpdatesDeleteFromRecentchanges', array( &$this ) ) ) { - if ( 0 == mt_rand( 0, 99 ) ) { - // Flush old entries from the `recentchanges` table; we do this on - // random requests so as to avoid an increase in writes for no good reason - RecentChange::purgeExpiredChanges(); - } + if ( Hooks::run( 'ArticleEditUpdatesDeleteFromRecentchanges', array( &$this ) ) ) { + JobQueueGroup::singleton()->push( array( + // Flush old entries from the `recentchanges` table + RecentChangesUpdateJob::newPurgeJob(), + // Update the cached list of active users + RecentChangesUpdateJob::newCacheUpdateJob() + ) ); } if ( !$this->exists() ) { - wfProfileOut( __METHOD__ ); return; } @@ -2227,7 +2220,9 @@ class WikiPage implements Page, IDBAccessObject { $title = $this->mTitle->getPrefixedDBkey(); $shortTitle = $this->mTitle->getDBkey(); - if ( !$options['changed'] ) { + if ( $options['oldcountable'] === 'no-change' || + ( !$options['changed'] && !$options['moved'] ) + ) { $good = 0; } elseif ( $options['created'] ) { $good = (int)$this->isCountable( $editInfo ); @@ -2255,7 +2250,7 @@ class WikiPage implements Page, IDBAccessObject { wfDebug( __METHOD__ . ": invalid username\n" ); } else { // Allow extensions to prevent user notification when a new message is added to their talk page - if ( wfRunHooks( 'ArticleEditUpdateNewTalk', array( &$this, $recipient ) ) ) { + if ( Hooks::run( 'ArticleEditUpdateNewTalk', array( &$this, $recipient ) ) ) { if ( User::isIP( $shortTitle ) ) { // An anonymous user $recipient->setNewtalk( true, $revision ); @@ -2284,7 +2279,6 @@ class WikiPage implements Page, IDBAccessObject { self::onArticleEdit( $this->mTitle ); } - wfProfileOut( __METHOD__ ); } /** @@ -2315,14 +2309,13 @@ class WikiPage implements Page, IDBAccessObject { * @param User $user The relevant user * @param string $comment Comment submitted * @param bool $minor Whereas it's a minor modification - * @param string $serialisation_format Format for storing the content in the database + * @param string $serialFormat Format for storing the content in the database */ public function doQuickEditContent( Content $content, User $user, $comment = '', $minor = false, - $serialisation_format = null + $serialFormat = null ) { - wfProfileIn( __METHOD__ ); - $serialized = $content->serialize( $serialisation_format ); + $serialized = $content->serialize( $serialFormat ); $dbw = wfGetDB( DB_MASTER ); $revision = new Revision( array( @@ -2338,9 +2331,8 @@ class WikiPage implements Page, IDBAccessObject { $revision->insertOn( $dbw ); $this->updateRevisionOn( $dbw, $revision ); - wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $revision, false, $user ) ); + Hooks::run( 'NewRevisionFromEditComplete', array( $this, $revision, false, $user ) ); - wfProfileOut( __METHOD__ ); } /** @@ -2437,7 +2429,7 @@ class WikiPage implements Page, IDBAccessObject { $logRelationsField = null; if ( $id ) { // Protection of existing page - if ( !wfRunHooks( 'ArticleProtect', array( &$this, &$user, $limit, $reason ) ) ) { + if ( !Hooks::run( 'ArticleProtect', array( &$this, &$user, $limit, $reason ) ) ) { return Status::newGood(); } @@ -2517,8 +2509,8 @@ class WikiPage implements Page, IDBAccessObject { __METHOD__ ); - wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $nullRevision, $latest, $user ) ); - wfRunHooks( 'ArticleProtectComplete', array( &$this, &$user, $limit, $reason ) ); + Hooks::run( 'NewRevisionFromEditComplete', array( $this, $nullRevision, $latest, $user ) ); + Hooks::run( 'ArticleProtectComplete', array( &$this, &$user, $limit, $reason ) ); } else { // Protection of non-existing page (also known as "title protection") // Cascade protection is meaningless in this case $cascade = false; @@ -2780,7 +2772,7 @@ class WikiPage implements Page, IDBAccessObject { } $user = is_null( $user ) ? $wgUser : $user; - if ( !wfRunHooks( 'ArticleDelete', array( &$this, &$user, &$reason, &$error, &$status ) ) ) { + if ( !Hooks::run( 'ArticleDelete', array( &$this, &$user, &$reason, &$error, &$status ) ) ) { if ( $status->isOK() ) { // Hook aborted but didn't set a fatal status $status->fatal( 'delete-hook-aborted' ); @@ -2896,7 +2888,7 @@ class WikiPage implements Page, IDBAccessObject { $this->doDeleteUpdates( $id, $content ); - wfRunHooks( 'ArticleDeleteComplete', array( &$this, &$user, $reason, $id, $content, $logEntry ) ); + Hooks::run( 'ArticleDeleteComplete', array( &$this, &$user, $reason, $id, $content, $logEntry ) ); $status->value = $logid; return $status; } @@ -3031,8 +3023,8 @@ class WikiPage implements Page, IDBAccessObject { // Get the last edit not by this guy... // Note: these may not be public values - $user = intval( $current->getRawUser() ); - $user_text = $dbw->addQuotes( $current->getRawUserText() ); + $user = intval( $current->getUser( Revision::RAW ) ); + $user_text = $dbw->addQuotes( $current->getUserText( Revision::RAW ) ); $s = $dbw->selectRow( 'revision', array( 'rev_id', 'rev_timestamp', 'rev_deleted' ), array( 'rev_page' => $current->getPage(), @@ -3075,7 +3067,7 @@ class WikiPage implements Page, IDBAccessObject { } // Generate the edit summary if necessary - $target = Revision::newFromId( $s->rev_id ); + $target = Revision::newFromId( $s->rev_id, Revision::READ_LATEST ); if ( empty( $summary ) ) { if ( $from == '' ) { // no public user name $summary = wfMessage( 'revertpage-nouser' ); @@ -3138,7 +3130,7 @@ class WikiPage implements Page, IDBAccessObject { $revId = $status->value['revision']->getId(); - wfRunHooks( 'ArticleRollbackComplete', array( $this, $guser, $target, $current ) ); + Hooks::run( 'ArticleRollbackComplete', array( $this, $guser, $target, $current ) ); $resultDetails = array( 'summary' => $summary, @@ -3161,13 +3153,9 @@ class WikiPage implements Page, IDBAccessObject { * * @param Title $title */ - public static function onArticleCreate( $title ) { + public static function onArticleCreate( Title $title ) { // Update existence markers on article/talk tabs... - if ( $title->isTalkPage() ) { - $other = $title->getSubjectPage(); - } else { - $other = $title->getTalkPage(); - } + $other = $title->getOtherPage(); $other->invalidateCache(); $other->purgeSquid(); @@ -3182,13 +3170,9 @@ class WikiPage implements Page, IDBAccessObject { * * @param Title $title */ - public static function onArticleDelete( $title ) { + public static function onArticleDelete( Title $title ) { // Update existence markers on article/talk tabs... - if ( $title->isTalkPage() ) { - $other = $title->getSubjectPage(); - } else { - $other = $title->getTalkPage(); - } + $other = $title->getOtherPage(); $other->invalidateCache(); $other->purgeSquid(); @@ -3227,10 +3211,8 @@ class WikiPage implements Page, IDBAccessObject { * Purge caches on page update etc * * @param Title $title - * @todo Verify that $title is always a Title object (and never false or - * null), add Title hint to parameter $title. */ - public static function onArticleEdit( $title ) { + public static function onArticleEdit( Title $title ) { // Invalidate caches of articles which include this page DeferredUpdates::addHTMLCacheUpdate( $title, 'templatelinks' ); @@ -3390,82 +3372,52 @@ class WikiPage implements Page, IDBAccessObject { foreach ( $added as $catName ) { $cat = Category::newFromName( $catName ); - wfRunHooks( 'CategoryAfterPageAdded', array( $cat, $that ) ); + Hooks::run( 'CategoryAfterPageAdded', array( $cat, $that ) ); } foreach ( $deleted as $catName ) { $cat = Category::newFromName( $catName ); - wfRunHooks( 'CategoryAfterPageRemoved', array( $cat, $that ) ); + Hooks::run( 'CategoryAfterPageRemoved', array( $cat, $that ) ); } } ); } /** - * Updates cascading protections + * Opportunistically enqueue link update jobs given fresh parser output if useful * - * @param ParserOutput $parserOutput ParserOutput object for the current version + * @param ParserOutput $parserOutput Current version page output + * @since 1.25 */ - public function doCascadeProtectionUpdates( ParserOutput $parserOutput ) { - if ( wfReadOnly() || !$this->mTitle->areRestrictionsCascading() ) { + public function triggerOpportunisticLinksUpdate( ParserOutput $parserOutput ) { + if ( wfReadOnly() ) { return; } - // templatelinks or imagelinks tables may have become out of sync, - // especially if using variable-based transclusions. - // For paranoia, check if things have changed and if - // so apply updates to the database. This will ensure - // that cascaded protections apply as soon as the changes - // are visible. - - // Get templates from templatelinks and images from imagelinks - $id = $this->getId(); - - $dbLinks = array(); - - $dbr = wfGetDB( DB_SLAVE ); - $res = $dbr->select( array( 'templatelinks' ), - array( 'tl_namespace', 'tl_title' ), - array( 'tl_from' => $id ), - __METHOD__ - ); - - foreach ( $res as $row ) { - $dbLinks["{$row->tl_namespace}:{$row->tl_title}"] = true; + if ( !Hooks::run( 'OpportunisticLinksUpdate', array( $this, $this->mTitle, $parserOutput ) ) ) { + return; } - $dbr = wfGetDB( DB_SLAVE ); - $res = $dbr->select( array( 'imagelinks' ), - array( 'il_to' ), - array( 'il_from' => $id ), - __METHOD__ - ); - - foreach ( $res as $row ) { - $dbLinks[NS_FILE . ":{$row->il_to}"] = true; + if ( $this->mTitle->areRestrictionsCascading() ) { + // If the page is cascade protecting, the links should really be up-to-date + $params = array( 'prioritize' => true ); + } elseif ( $parserOutput->hasDynamicContent() ) { + // Assume the output contains time/random based magic words + $params = array(); + } else { + // If the inclusions are deterministic, the edit-triggered link jobs are enough + return; } - // Get templates and images from parser output. - $poLinks = array(); - foreach ( $parserOutput->getTemplates() as $ns => $templates ) { - foreach ( $templates as $dbk => $id ) { - $poLinks["$ns:$dbk"] = true; - } - } - foreach ( $parserOutput->getImages() as $dbk => $id ) { - $poLinks[NS_FILE . ":$dbk"] = true; + // Check if the last link refresh was before page_touched + if ( $this->getLinksTimestamp() < $this->getTouched() ) { + JobQueueGroup::singleton()->push( EnqueueJob::newFromLocalJobs( + new JobSpecification( 'refreshLinks', $params, array(), $this->mTitle ) + ) ); + return; } - // Get the diff - $links_diff = array_diff_key( $poLinks, $dbLinks ); - - if ( count( $links_diff ) > 0 ) { - // Whee, link updates time. - // Note: we are only interested in links here. We don't need to get - // other DataUpdate items from the parser output. - $u = new LinksUpdate( $this->mTitle, $parserOutput, false ); - $u->doUpdate(); - } + return; } /** @@ -3548,7 +3500,7 @@ class WikiPage implements Page, IDBAccessObject { $updates = $content->getDeletionUpdates( $this ); } - wfRunHooks( 'WikiPageDeletionUpdates', array( $this, $content, &$updates ) ); + Hooks::run( 'WikiPageDeletionUpdates', array( $this, $content, &$updates ) ); return $updates; } } |