X-Git-Url: https://git.heureux-cyclage.org/?a=blobdiff_plain;f=includes%2FArticle.php;h=4b231598a4db47c3b38b742fd387f6d92322c9e7;hb=7a7cf1a2f9468e0ac27dcd078da4a53e10cbe017;hp=a8d4a81d732118fa355b91ac7fba38f72de21f16;hpb=28e2821e27e5deac1e3d090ab655a663e1f7674e;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/Article.php b/includes/Article.php index a8d4a81d73..4b231598a4 100644 --- a/includes/Article.php +++ b/includes/Article.php @@ -40,6 +40,7 @@ class Article { var $mTouched = '19700101000000'; //!< var $mUser = -1; //!< Not loaded var $mUserText = ''; //!< + var $mParserOptions; //!< /**@}}*/ /** @@ -108,11 +109,11 @@ class Article { return null; } $dbw = wfGetDB( DB_MASTER ); - $dbw->replace( 'redirect', array('rd_from'), + $dbw->replace( 'redirect', array('rd_from'), array( 'rd_from' => $this->getID(), 'rd_namespace' => $retval->getNamespace(), - 'rd_title' => $retval->getDBKey() + 'rd_title' => $retval->getDBkey() ), __METHOD__ ); @@ -228,7 +229,7 @@ class Article { return $this->mContent; } } - + /** * Get the text of the current revision. No side-effects... * @@ -260,12 +261,12 @@ class Article { global $wgParser; return $wgParser->getSection( $text, $section ); } - + /** * Get the text that needs to be saved in order to undo all revisions * between $undo and $undoafter. Revisions must belong to the same page, * must exist and must not be deleted - * @param $undo Revision + * @param $undo Revision * @param $undoafter Revision Must be an earlier revision than $undo * @return mixed string on success, false on failure */ @@ -545,7 +546,7 @@ class Article { public function exists() { return $this->getId() > 0; } - + /** * Check if this page is something we're going to be showing * some sort of sensible content for. If we return false, page @@ -568,10 +569,10 @@ class Article { $this->mCounter = 0; } else { $dbr = wfGetDB( DB_SLAVE ); - $this->mCounter = $dbr->selectField( 'page', - 'page_counter', - array( 'page_id' => $id ), - __METHOD__, + $this->mCounter = $dbr->selectField( 'page', + 'page_counter', + array( 'page_id' => $id ), + __METHOD__, $this->getSelectOptions() ); } @@ -697,18 +698,18 @@ class Article { $user = $this->getUser(); $pageId = $this->getId(); - $hideBit = Revision::DELETED_USER; // username hidden? + $deletedBit = $dbr->bitAnd('rev_deleted', Revision::DELETED_USER); // username hidden? $sql = "SELECT {$userTable}.*, MAX(rev_timestamp) as timestamp FROM $revTable LEFT JOIN $userTable ON rev_user = user_id WHERE rev_page = $pageId AND rev_user != $user - AND rev_deleted & $hideBit = 0 + AND $deletedBit = 0 GROUP BY rev_user, rev_user_text, user_real_name ORDER BY timestamp DESC"; - if($limit > 0) { $sql .= ' LIMIT '.$limit; } - if($offset > 0) { $sql .= ' OFFSET '.$offset; } + if($limit > 0) + $sql = $dbr->limitResult($sql, $limit, $offset); $sql .= ' '. $this->getSelectOptions(); @@ -718,33 +719,40 @@ class Article { } /** - * This is the default action of the script: just view the page of - * the given title. + * This is the default action of the index.php entry point: just view the + * page of the given title. */ public function view() { global $wgUser, $wgOut, $wgRequest, $wgContLang; global $wgEnableParserCache, $wgStylePath, $wgParser; - global $wgUseTrackbacks, $wgNamespaceRobotPolicies, $wgArticleRobotPolicies; - global $wgDefaultRobotPolicy; + global $wgUseTrackbacks; wfProfileIn( __METHOD__ ); # Get variables from query string $oldid = $this->getOldID(); + $parserCache = ParserCache::singleton(); + + $parserOptions = clone $this->getParserOptions(); + # Render printable version, use printable version cache + if ( $wgOut->isPrintable() ) { + $parserOptions->setIsPrintable( true ); + } # Try client and file cache if( $oldid === 0 && $this->checkTouched() ) { global $wgUseETag; if( $wgUseETag ) { - $parserCache = ParserCache::singleton(); - $wgOut->setETag( $parserCache->getETag($this,$wgUser) ); + $wgOut->setETag( $parserCache->getETag( $this, $parserOptions ) ); } # Is is client cached? if( $wgOut->checkLastModified( $this->getTouched() ) ) { + wfDebug( __METHOD__.": done 304\n" ); wfProfileOut( __METHOD__ ); return; # Try file cache } else if( $this->tryFileCache() ) { + wfDebug( __METHOD__.": done file cache\n" ); # tell wgOut that output is taken care of $wgOut->disable(); $this->viewUpdates(); @@ -753,86 +761,262 @@ class Article { } } - $ns = $this->mTitle->getNamespace(); # shortcut $sk = $wgUser->getSkin(); # getOldID may want us to redirect somewhere else if( $this->mRedirectUrl ) { $wgOut->redirect( $this->mRedirectUrl ); + wfDebug( __METHOD__.": redirecting due to oldid\n" ); wfProfileOut( __METHOD__ ); return; } - $diff = $wgRequest->getVal( 'diff' ); - $rcid = $wgRequest->getVal( 'rcid' ); - $rdfrom = $wgRequest->getVal( 'rdfrom' ); - $diffOnly = $wgRequest->getBool( 'diffonly', $wgUser->getOption( 'diffonly' ) ); - $purge = $wgRequest->getVal( 'action' ) == 'purge'; - $return404 = false; - $wgOut->setArticleFlag( true ); + $wgOut->setRobotPolicy( $this->getRobotPolicyForView() ); + # Set page title (may be overridden by DISPLAYTITLE) + $wgOut->setPageTitle( $this->mTitle->getPrefixedText() ); + + # If we got diff in the query, we want to see a diff page instead of the article. + if( !is_null( $wgRequest->getVal( 'diff' ) ) ) { + wfDebug( __METHOD__.": showing diff page\n" ); + $this->showDiffPage(); + wfProfileOut( __METHOD__ ); + return; + } - # Discourage indexing of printable versions, but encourage following - if( $wgOut->isPrintable() ) { - $policy = 'noindex,follow'; - } elseif( isset( $wgArticleRobotPolicies[$this->mTitle->getPrefixedText()] ) ) { - $policy = $wgArticleRobotPolicies[$this->mTitle->getPrefixedText()]; - } elseif( isset( $wgNamespaceRobotPolicies[$ns] ) ) { - # Honour customised robot policies for this namespace - $policy = $wgNamespaceRobotPolicies[$ns]; - } else { - $policy = $wgDefaultRobotPolicy; + # Should the parser cache be used? + $useParserCache = $this->useParserCache( $oldid ); + wfDebug( 'Article::view using parser cache: ' . ($useParserCache ? 'yes' : 'no' ) . "\n" ); + if( $wgUser->getOption( 'stubthreshold' ) ) { + wfIncrStats( 'pcache_miss_stub' ); } - $wgOut->setRobotPolicy( $policy ); - # Allow admins to see deleted content if explicitly requested - $delId = $diff ? $diff : $oldid; - $unhide = $wgRequest->getInt('unhide') == 1 && $wgUser->matchEditToken( $wgRequest->getVal('token'), $delId ); - # If we got diff and oldid in the query, we want to see a - # diff page instead of the article. + # For the main page, overwrite the element with the con- + # tents of 'pagetitle-view-mainpage' instead of the default (if + # that's not empty). + if( $this->mTitle->equals( Title::newMainPage() ) + && wfMsgForContent( 'pagetitle-view-mainpage' ) !== '' ) + { + $wgOut->setHTMLTitle( wfMsgForContent( 'pagetitle-view-mainpage' ) ); + } - if( !is_null( $diff ) ) { - $wgOut->setPageTitle( $this->mTitle->getPrefixedText() ); + $wasRedirected = $this->showRedirectedFromHeader(); + $this->showNamespaceHeader(); - $htmldiff = $wgRequest->getVal( 'htmldiff' , false); - $de = new DifferenceEngine( $this->mTitle, $oldid, $diff, $rcid, $purge, $htmldiff, $unhide ); - // DifferenceEngine directly fetched the revision: - $this->mRevIdFetched = $de->mNewid; - $de->showDiffPage( $diffOnly ); + $outputDone = false; + wfRunHooks( 'ArticleViewHeader', array( &$this, &$outputDone, &$useParserCache ) ); - // Needed to get the page's current revision - $this->loadPageData(); - if( $diff == 0 || $diff == $this->mLatest ) { - # Run view updates for current revision only - $this->viewUpdates(); + # Try the parser cache + if( !$outputDone && $useParserCache ) { + $parserOutput = $parserCache->get( $this, $parserOptions ); + if ( $parserOutput !== false ) { + wfDebug( __METHOD__.": showing parser cache contents\n" ); + $wgOut->addParserOutput( $parserOutput ); + // Ensure that UI elements requiring revision ID have + // the correct version information. + $wgOut->setRevisionId( $this->mLatest ); + $outputDone = true; + } + } + + if ( $outputDone ) { + $this->showViewFooter(); + $this->viewUpdates(); + wfProfileOut( __METHOD__ ); + return; + } + + $text = $this->getContent(); + if( $text === false || $this->getID() == 0 ) { + wfDebug( __METHOD__.": showing missing article\n" ); + $this->showMissingArticle(); + wfProfileOut( __METHOD__ ); + return; + } + + # Another whitelist check in case oldid is altering the title + if( !$this->mTitle->userCanRead() ) { + wfDebug( __METHOD__.": denied on secondary read check\n" ); + $wgOut->loginToUse(); + $wgOut->output(); + $wgOut->disable(); + wfProfileOut( __METHOD__ ); + return; + } + + # We're looking at an old revision + if( $oldid && !is_null( $this->mRevision ) ) { + $this->setOldSubtitle( $oldid ); + if ( !$this->showDeletedRevisionHeader() ) { + wfDebug( __METHOD__.": cannot view deleted revision\n" ); + wfProfileOut( __METHOD__ ); + return; } + + if ( $oldid === $this->getLatest() && $this->useParserCache( false ) ) { + $parserOutput = $parserCache->get( $this, $parserOptions ); + if ( $parserOutput ) { + wfDebug( __METHOD__.": showing parser cache for current rev permalink\n" ); + $wgOut->addParserOutput( $parserOutput ); + $this->showViewFooter(); + $this->viewUpdates(); + wfProfileOut( __METHOD__ ); + return; + } + } + } + + // Ensure that UI elements requiring revision ID have + // the correct version information. + $wgOut->setRevisionId( $this->getRevIdFetched() ); + + // Pages containing custom CSS or JavaScript get special treatment + if( $this->mTitle->isCssOrJsPage() || $this->mTitle->isCssJsSubpage() ) { + wfDebug( __METHOD__.": showing CSS/JS source\n" ); + $this->showCssOrJsPage(); + $outputDone = true; + } else if( $rt = Title::newFromRedirectArray( $text ) ) { + wfDebug( __METHOD__.": showing redirect=no page\n" ); + # Viewing a redirect page (e.g. with parameter redirect=no) + # Don't append the subtitle if this was an old revision + $wgOut->addHTML( $this->viewRedirect( $rt, !$wasRedirected && $this->isCurrent() ) ); + # Parse just to get categories, displaytitle, etc. + $parserOutput = $wgParser->parse( $text, $this->mTitle, $parserOptions ); + $wgOut->addParserOutputNoText( $parserOutput ); + $outputDone = true; + } + if ( $outputDone ) { + $this->showViewFooter(); + $this->viewUpdates(); wfProfileOut( __METHOD__ ); return; } + + # Run the parse, protected by a pool counter + wfDebug( __METHOD__.": doing uncached parse\n" ); + $key = $parserCache->getKey( $this, $parserOptions ); + $poolCounter = PoolCounter::factory( 'Article::view', $key ); + $dirtyCallback = $useParserCache ? array( $this, 'tryDirtyCache' ) : false; + $status = $poolCounter->executeProtected( array( $this, 'doViewParse' ), $dirtyCallback ); + + if ( !$status->isOK() ) { + # Connection or timeout error + $this->showPoolError( $status ); + wfProfileOut( __METHOD__ ); + return; + } + + $this->showViewFooter(); + $this->viewUpdates(); + wfProfileOut( __METHOD__ ); + } + + /** + * Show a diff page according to current request variables. For use within + * Article::view() only, other callers should use the DifferenceEngine class. + */ + public function showDiffPage() { + global $wgOut, $wgRequest, $wgUser; + $diff = $wgRequest->getVal( 'diff' ); + $rcid = $wgRequest->getVal( 'rcid' ); + $diffOnly = $wgRequest->getBool( 'diffonly', $wgUser->getOption( 'diffonly' ) ); + $purge = $wgRequest->getVal( 'action' ) == 'purge'; + $htmldiff = $wgRequest->getVal( 'htmldiff' , false); + $unhide = $wgRequest->getInt('unhide') == 1; + $oldid = $this->getOldID(); + + $de = new DifferenceEngine( $this->mTitle, $oldid, $diff, $rcid, $purge, $htmldiff, $unhide ); + // DifferenceEngine directly fetched the revision: + $this->mRevIdFetched = $de->mNewid; + $de->showDiffPage( $diffOnly ); + + // Needed to get the page's current revision + $this->loadPageData(); + if( $diff == 0 || $diff == $this->mLatest ) { + # Run view updates for current revision only + $this->viewUpdates(); + } + } + + /** + * Show a page view for a page formatted as CSS or JavaScript. To be called by + * Article::view() only. + * + * This is hooked by SyntaxHighlight_GeSHi to do syntax highlighting of these + * page views. + */ + public function showCssOrJsPage() { + global $wgOut; + $wgOut->addHTML( wfMsgExt( 'clearyourcache', 'parse' ) ); + // Give hooks a chance to customise the output + if( wfRunHooks( 'ShowRawCssJs', array( $this->mContent, $this->mTitle, $wgOut ) ) ) { + // Wrap the whole lot in a <pre> and don't parse + $m = array(); + preg_match( '!\.(css|js)$!u', $this->mTitle->getText(), $m ); + $wgOut->addHTML( "<pre class=\"mw-code mw-{$m[1]}\" dir=\"ltr\">\n" ); + $wgOut->addHTML( htmlspecialchars( $this->mContent ) ); + $wgOut->addHTML( "\n</pre>\n" ); + } + } + + /** + * Get the robot policy to be used for the current action=view request. + */ + public function getRobotPolicyForView() { + global $wgOut, $wgArticleRobotPolicies, $wgNamespaceRobotPolicies; + global $wgDefaultRobotPolicy, $wgRequest; + + $ns = $this->mTitle->getNamespace(); if( $ns == NS_USER || $ns == NS_USER_TALK ) { - # User/User_talk subpages are not modified. (bug 11443) + # Don't index user and user talk pages for blocked users (bug 11443) if( !$this->mTitle->isSubpage() ) { $block = new Block(); - if( $block->load( $this->mTitle->getBaseText() ) ) { - $wgOut->setRobotpolicy( 'noindex,nofollow' ); + if( $block->load( $this->mTitle->getText() ) ) { + return 'noindex,nofollow'; } } } - # Should the parser cache be used? - $pcache = $this->useParserCache( $oldid ); - wfDebug( 'Article::view using parser cache: ' . ($pcache ? 'yes' : 'no' ) . "\n" ); - if( $wgUser->getOption( 'stubthreshold' ) ) { - wfIncrStats( 'pcache_miss_stub' ); + if( $this->getID() === 0 || $this->getOldID() ) { + return 'noindex,nofollow'; + } elseif( $wgOut->isPrintable() ) { + # Discourage indexing of printable versions, but encourage following + return 'noindex,follow'; + } elseif( $wgRequest->getInt('curid') ) { + # For ?curid=x urls, disallow indexing + return 'noindex,follow'; + } elseif( isset( $wgArticleRobotPolicies[$this->mTitle->getPrefixedText()] ) ) { + return $wgArticleRobotPolicies[$this->mTitle->getPrefixedText()]; + } elseif( isset( $wgNamespaceRobotPolicies[$ns] ) ) { + # Honour customised robot policies for this namespace + return $wgNamespaceRobotPolicies[$ns]; + } else { + return $wgDefaultRobotPolicy; } + } + + /** + * If this request is a redirect view, send "redirected from" subtitle to + * $wgOut. Returns true if the header was needed, false if this is not a + * redirect view. Handles both local and remote redirects. + */ + public function showRedirectedFromHeader() { + global $wgOut, $wgUser, $wgRequest, $wgRedirectSources; - $wasRedirected = false; + $rdfrom = $wgRequest->getVal( 'rdfrom' ); + $sk = $wgUser->getSkin(); 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 ) ) ) { - $redir = $sk->makeKnownLinkObj( $this->mRedirectedFrom, '', 'redirect=no' ); + $redir = $sk->link( + $this->mRedirectedFrom, + null, + array(), + array( 'redirect' => 'no' ), + array( 'known', 'noclasses' ) + ); $s = wfMsgExt( 'redirectedfrom', array( 'parseinline', 'replaceafter' ), $redir ); $wgOut->setSubtitle( $s ); @@ -846,204 +1030,164 @@ class Article { $wgOut->addLink( array( 'rel' => 'canonical', 'href' => $this->mTitle->getLocalURL() ) ); - $wasRedirected = true; + return true; } - } elseif( !empty( $rdfrom ) ) { + } elseif( $rdfrom ) { // This is an externally redirected view, from some other wiki. // If it was reported from a trusted site, supply a backlink. - global $wgRedirectSources; if( $wgRedirectSources && preg_match( $wgRedirectSources, $rdfrom ) ) { $redir = $sk->makeExternalLink( $rdfrom, $rdfrom ); $s = wfMsgExt( 'redirectedfrom', array( 'parseinline', 'replaceafter' ), $redir ); $wgOut->setSubtitle( $s ); - $wasRedirected = true; - } - } - - $outputDone = false; - wfRunHooks( 'ArticleViewHeader', array( &$this, &$outputDone, &$pcache ) ); - if( $pcache && $wgOut->tryParserCache( $this, $wgUser ) ) { - // Ensure that UI elements requiring revision ID have - // the correct version information. - $wgOut->setRevisionId( $this->mLatest ); - $outputDone = true; - } - # Fetch content and check for errors - if( !$outputDone ) { - # If the article does not exist and was deleted, show the log - if( $this->getID() == 0 ) { - $this->showDeletionLog(); - } - $text = $this->getContent(); - // For now, check also for ID until getContent actually returns - // false for pages that do not exists - if( $text === false || $this->getID() === 0 ) { - # Failed to load, replace text with error message - $t = $this->mTitle->getPrefixedText(); - if( $oldid ) { - $d = wfMsgExt( 'missingarticle-rev', 'escape', $oldid ); - $text = wfMsgExt( 'missing-article', 'parsemag', $t, $d ); - // Always use page content for pages in the MediaWiki namespace - // since it contains the default message - } elseif ( $this->mTitle->getNamespace() != NS_MEDIAWIKI ) { - $text = wfMsgExt( 'noarticletext', 'parsemag' ); - } - } - - # Non-existent pages - if( $this->getID() === 0 ) { - $wgOut->setRobotPolicy( 'noindex,nofollow' ); - $text = "<div class='noarticletext'>\n$text\n</div>"; - if( !$this->hasViewableContent() ) { - // If there's no backing content, send a 404 Not Found - // for better machine handling of broken links. - $return404 = true; - } - } - - if( $return404 ) { - $wgRequest->response()->header( "HTTP/1.x 404 Not Found" ); - } - - # Another whitelist check in case oldid is altering the title - if( !$this->mTitle->userCanRead() ) { - $wgOut->loginToUse(); - $wgOut->output(); - $wgOut->disable(); - wfProfileOut( __METHOD__ ); - return; - } - - # For ?curid=x urls, disallow indexing - if( $wgRequest->getInt('curid') ) - $wgOut->setRobotPolicy( 'noindex,follow' ); - - # We're looking at an old revision - if( !empty( $oldid ) ) { - $wgOut->setRobotPolicy( 'noindex,nofollow' ); - if( is_null( $this->mRevision ) ) { - // FIXME: This would be a nice place to load the 'no such page' text. - } else { - $this->setOldSubtitle( isset($this->mOldId) ? $this->mOldId : $oldid ); - # Allow admins to see deleted content if explicitly requested - if( $this->mRevision->isDeleted( Revision::DELETED_TEXT ) ) { - if( !$unhide || !$this->mRevision->userCan(Revision::DELETED_TEXT) ) { - $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1</div>\n", 'rev-deleted-text-permission' ); - $wgOut->setPageTitle( $this->mTitle->getPrefixedText() ); - wfProfileOut( __METHOD__ ); - return; - } else { - $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1</div>\n", 'rev-deleted-text-view' ); - // and we are allowed to see... - } - } - // Is this the current revision and otherwise cacheable? Try the parser cache... - if( $oldid === $this->getLatest() && $this->useParserCache( false ) - && $wgOut->tryParserCache( $this, $wgUser ) ) - { - $outputDone = true; - } - } - } - - // Ensure that UI elements requiring revision ID have - // the correct version information. - $wgOut->setRevisionId( $this->getRevIdFetched() ); - - if( $outputDone ) { - // do nothing... - // Pages containing custom CSS or JavaScript get special treatment - } else if( $this->mTitle->isCssOrJsPage() || $this->mTitle->isCssJsSubpage() ) { - $wgOut->addHTML( wfMsgExt( 'clearyourcache', 'parse' ) ); - // Give hooks a chance to customise the output - if( wfRunHooks( 'ShowRawCssJs', array( $this->mContent, $this->mTitle, $wgOut ) ) ) { - // Wrap the whole lot in a <pre> and don't parse - $m = array(); - preg_match( '!\.(css|js)$!u', $this->mTitle->getText(), $m ); - $wgOut->addHTML( "<pre class=\"mw-code mw-{$m[1]}\" dir=\"ltr\">\n" ); - $wgOut->addHTML( htmlspecialchars( $this->mContent ) ); - $wgOut->addHTML( "\n</pre>\n" ); - } - } else if( $rt = Title::newFromRedirectArray( $text ) ) { # get an array of redirect targets - # Don't append the subtitle if this was an old revision - $wgOut->addHTML( $this->viewRedirect( $rt, !$wasRedirected && $this->isCurrent() ) ); - $parseout = $wgParser->parse($text, $this->mTitle, ParserOptions::newFromUser($wgUser)); - $wgOut->addParserOutputNoText( $parseout ); - } else if( $pcache ) { - # Display content and save to parser cache - $this->outputWikiText( $text ); - } else { - # Display content, don't attempt to save to parser cache - # Don't show section-edit links on old revisions... this way lies madness. - if( !$this->isCurrent() ) { - $oldEditSectionSetting = $wgOut->parserOptions()->setEditSection( false ); - } - # Display content and don't save to parser cache - # With timing hack -- TS 2006-07-26 - $time = -wfTime(); - $this->outputWikiText( $text, false ); - $time += wfTime(); - - # Timing hack - if( $time > 3 ) { - wfDebugLog( 'slow-parse', sprintf( "%-5.2f %s", $time, - $this->mTitle->getPrefixedDBkey())); - } - - if( !$this->isCurrent() ) { - $wgOut->parserOptions()->setEditSection( $oldEditSectionSetting ); - } + return true; } } - /* title may have been set from the cache */ - $t = $wgOut->getPageTitle(); - if( empty( $t ) ) { - $wgOut->setPageTitle( $this->mTitle->getPrefixedText() ); + return false; + } - # For the main page, overwrite the <title> element with the con- - # tents of 'pagetitle-view-mainpage' instead of the default (if - # that's not empty). - if( $this->mTitle->equals( Title::newMainPage() ) && - wfMsgForContent( 'pagetitle-view-mainpage' ) !== '' ) { - $wgOut->setHTMLTitle( wfMsgForContent( 'pagetitle-view-mainpage' ) ); + /** + * Show a header specific to the namespace currently being viewed, like + * [[MediaWiki:Talkpagetext]]. For Article::view(). + */ + public function showNamespaceHeader() { + global $wgOut; + if( $this->mTitle->isTalkPage() ) { + $msg = wfMsgNoTrans( 'talkpageheader' ); + if ( $msg !== '-' && !wfEmptyMsg( 'talkpageheader', $msg ) ) { + $wgOut->wrapWikiMsg( "<div class=\"mw-talkpageheader\">\n$1</div>", array( 'talkpageheader' ) ); } } + } + /** + * Show the footer section of an ordinary page view + */ + public function showViewFooter() { + global $wgOut, $wgUseTrackbacks, $wgRequest; # check if we're displaying a [[User talk:x.x.x.x]] anonymous talk page - if( $ns == NS_USER_TALK && IP::isValid( $this->mTitle->getText() ) ) { + if( $this->mTitle->getNamespace() == NS_USER_TALK && IP::isValid( $this->mTitle->getText() ) ) { $wgOut->addWikiMsg('anontalkpagetext'); } # If we have been passed an &rcid= parameter, we want to give the user a # chance to mark this new article as patrolled. - if( !empty($rcid) && $this->mTitle->exists() && $this->mTitle->quickUserCan('patrol') ) { - $wgOut->addHTML( - "<div class='patrollink'>" . - wfMsgHtml( 'markaspatrolledlink', - $sk->makeKnownLinkObj( $this->mTitle, wfMsgHtml('markaspatrolledtext'), - "action=markpatrolled&rcid=$rcid" ) - ) . - '</div>' - ); - } + $this->showPatrolFooter(); # Trackbacks if( $wgUseTrackbacks ) { $this->addTrackbacks(); } + } - $this->viewUpdates(); - wfProfileOut( __METHOD__ ); + /** + * If patrol is possible, output a patrol UI box. This is called from the + * footer section of ordinary page views. If patrol is not possible or not + * desired, does nothing. + */ + public function showPatrolFooter() { + global $wgOut, $wgRequest; + $rcid = $wgRequest->getVal( 'rcid' ); + + if( !$rcid || !$this->mTitle->exists() || !$this->mTitle->quickUserCan( 'patrol' ) ) { + return; + } + + $wgOut->addHTML( + "<div class='patrollink'>" . + wfMsgHtml( + 'markaspatrolledlink', + $sk->link( + $this->mTitle, + wfMsgHtml( 'markaspatrolledtext' ), + array(), + array( + 'action' => 'markpatrolled', + 'rcid' => $rcid + ), + array( 'known', 'noclasses' ) + ) + ) . + '</div>' + ); + } + + /** + * Show the error text for a missing article. For articles in the MediaWiki + * namespace, show the default message text. To be called from Article::view(). + */ + public function showMissingArticle() { + global $wgOut, $wgRequest; + # Show delete and move logs + $this->showLogs(); + + # Show error message + $oldid = $this->getOldID(); + if( $oldid ) { + $text = wfMsgNoTrans( 'missing-article', + $this->mTitle->getPrefixedText(), + wfMsgNoTrans( 'missingarticle-rev', $oldid ) ); + } elseif ( $this->mTitle->getNamespace() === NS_MEDIAWIKI ) { + // Use the default message text + $text = $this->getContent(); + } else { + $text = wfMsgNoTrans( 'noarticletext' ); + } + $text = "<div class='noarticletext'>\n$text\n</div>"; + if( !$this->hasViewableContent() ) { + // If there's no backing content, send a 404 Not Found + // for better machine handling of broken links. + $wgRequest->response()->header( "HTTP/1.x 404 Not Found" ); + } + $wgOut->addWikiText( $text ); } - - protected function showDeletionLog() { + + /** + * If the revision requested for view is deleted, check permissions. + * Send either an error message or a warning header to $wgOut. + * Returns true if the view is allowed, false if not. + */ + public function showDeletedRevisionHeader() { + global $wgOut, $wgRequest; + + if( !$this->mRevision->isDeleted( Revision::DELETED_TEXT ) ) { + // Not deleted + return true; + } + + // If the user is not allowed to see it... + if( !$this->mRevision->userCan(Revision::DELETED_TEXT) ) { + $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1</div>\n", + 'rev-deleted-text-permission' ); + return false; + // If the user needs to confirm that they want to see it... + } else if( $wgRequest->getInt('unhide') != 1 ) { + # Give explanation and add a link to view the revision... + $oldid = intval( $this->getOldID() ); + $link = $this->mTitle->getFullUrl( "oldid={$oldid}&unhide=1" ); + $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1</div>\n", + array('rev-deleted-text-unhide',$link) ); + return false; + // We are allowed to see... + } else { + $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1</div>\n", + 'rev-deleted-text-view' ); + return true; + } + } + + /** + * Show an excerpt from the deletion and move logs. To be called from the + * header section on page views of missing pages. + */ + public function showLogs() { global $wgUser, $wgOut; $loglist = new LogEventsList( $wgUser->getSkin(), $wgOut ); - $pager = new LogPager( $loglist, 'delete', false, $this->mTitle->getPrefixedText() ); + $pager = new LogPager( $loglist, array('move', 'delete'), false, + $this->mTitle->getPrefixedText(), '', array( "log_action != 'revision'" ) ); if( $pager->getNumRows() > 0 ) { $pager->mLimit = 10; $wgOut->addHTML( '<div class="mw-warning-with-logexcerpt">' ); - $wgOut->addWikiMsg( 'deleted-notice' ); + $wgOut->addWikiMsg( 'moveddeleted-notice' ); $wgOut->addHTML( $loglist->beginLogEventsList() . $pager->getBody() . @@ -1052,9 +1196,9 @@ class Article { if( $pager->getNumRows() > 10 ) { $wgOut->addHTML( $wgUser->getSkin()->link( SpecialPage::getTitleFor( 'Log' ), - wfMsgHtml( 'deletelog-fulllog' ), + wfMsgHtml( 'log-fulllog' ), array(), - array( 'type' => 'delete', 'page' => $this->mTitle->getPrefixedText() ) + array( 'page' => $this->mTitle->getPrefixedText() ) ) ); } $wgOut->addHTML( '</div>' ); @@ -1064,7 +1208,7 @@ class Article { /* * Should the parser cache be used? */ - protected function useParserCache( $oldid ) { + public function useParserCache( $oldid ) { global $wgUser, $wgEnableParserCache; return $wgEnableParserCache @@ -1075,6 +1219,64 @@ class Article { && !$this->mTitle->isCssJsSubpage(); } + /** + * Execute the uncached parse for action=view + */ + public function doViewParse() { + global $wgOut; + $oldid = $this->getOldID(); + $useParserCache = $this->useParserCache( $oldid ); + $parserOptions = clone $this->getParserOptions(); + # Render printable version, use printable version cache + $parserOptions->setIsPrintable( $wgOut->isPrintable() ); + # Don't show section-edit links on old revisions... this way lies madness. + $parserOptions->setEditSection( $this->isCurrent() ); + $useParserCache = $this->useParserCache( $oldid ); + $this->outputWikiText( $this->getContent(), $useParserCache, $parserOptions ); + } + + /** + * Try to fetch an expired entry from the parser cache. If it is present, + * output it and return true. If it is not present, output nothing and + * return false. This is used as a callback function for + * PoolCounter::executeProtected(). + */ + public function tryDirtyCache() { + global $wgOut; + $parserCache = ParserCache::singleton(); + $options = $this->getParserOptions(); + $options->setIsPrintable( $wgOut->isPrintable() ); + $output = $parserCache->getDirty( $this, $options ); + if ( $output ) { + wfDebug( __METHOD__.": sending dirty output\n" ); + wfDebugLog( 'dirty', "dirty output " . $parserCache->getKey( $this, $options ) . "\n" ); + $wgOut->setSquidMaxage( 0 ); + $wgOut->addParserOutput( $output ); + $wgOut->addHTML( "<!-- parser cache is expired, sending anyway due to pool overload-->\n" ); + return true; + } else { + wfDebugLog( 'dirty', "dirty missing\n" ); + wfDebug( __METHOD__.": no dirty cache\n" ); + return false; + } + } + + /** + * Show an error page for an error from the pool counter. + * @param $status Status + */ + public function showPoolError( $status ) { + global $wgOut; + $wgOut->clearHTML(); // for release() errors + $wgOut->enableClientCache( false ); + $wgOut->setRobotPolicy( 'noindex,nofollow' ); + $wgOut->addWikiText( + '<div class="errorbox">' . + $status->getWikiText( false, 'view-pool-error' ) . + '</div>' + ); + } + /** * View redirect * @param $target Title object or Array of destination(s) to redirect @@ -1091,7 +1293,7 @@ class Article { $imageUrl = $wgStylePath . '/common/images/redirect' . $imageDir . '.png'; $imageUrl2 = $wgStylePath . '/common/images/nextredirect' . $imageDir . '.png'; $alt2 = $wgContLang->isRTL() ? '←' : '→'; // should -> and <- be used instead of entities? - + if( $appendSubtitle ) { $wgOut->appendSubtitle( wfMsgHtml( 'redirectpagesub' ) ); } @@ -1099,18 +1301,30 @@ class Article { // the loop prepends the arrow image before the link, so the first case needs to be outside $title = array_shift( $target ); if( $forceKnown ) { - $link = $sk->makeKnownLinkObj( $title, htmlspecialchars( $title->getFullText() ) ); + $link = $sk->link( + $title, + htmlspecialchars( $title->getFullText() ), + array(), + array(), + array( 'known', 'noclasses' ) + ); } else { - $link = $sk->makeLinkObj( $title, htmlspecialchars( $title->getFullText() ) ); + $link = $sk->link( $title, htmlspecialchars( $title->getFullText() ) ); } // automatically append redirect=no to each link, since most of them are redirect pages themselves foreach( $target as $rt ) { if( $forceKnown ) { $link .= '<img src="'.$imageUrl2.'" alt="'.$alt2.' " />' - . $sk->makeKnownLinkObj( $rt, htmlspecialchars( $rt->getFullText() ) ); + . $sk->link( + $rt, + htmlspecialchars( $rt->getFullText() ), + array(), + array(), + array( 'known', 'noclasses' ) + ); } else { $link .= '<img src="'.$imageUrl2.'" alt="'.$alt2.' " />' - . $sk->makeLinkObj( $rt, htmlspecialchars( $rt->getFullText() ) ); + . $sk->link( $rt, htmlspecialchars( $rt->getFullText() ) ); } } return '<img src="'.$imageUrl.'" alt="#REDIRECT " />' . @@ -1148,7 +1362,7 @@ class Article { } public function deletetrackback() { - global $wgUser, $wgRequest, $wgOut, $wgTitle; + global $wgUser, $wgRequest, $wgOut; if( !$wgUser->matchEditToken($wgRequest->getVal('token')) ) { $wgOut->addWikiMsg( 'sessionfailure' ); return; @@ -1422,10 +1636,10 @@ class Article { } /** - * @deprecated use Article::doEdit() + * This function is not deprecated until somebody fixes the core not to use + * it. Nevertheless, use Article::doEdit() instead. */ function insertNewArticle( $text, $summary, $isminor, $watchthis, $suppressRC=false, $comment=false, $bot=false ) { - wfDeprecated( __METHOD__ ); $flags = EDIT_NEW | EDIT_DEFER_UPDATES | EDIT_AUTOSUMMARY | ( $isminor ? EDIT_MINOR : 0 ) | ( $suppressRC ? EDIT_SUPPRESS_RC : 0 ) | @@ -1459,7 +1673,6 @@ class Article { * @deprecated use Article::doEdit() */ function updateArticle( $text, $summary, $minor, $watchthis, $forceBot = false, $sectionanchor = '' ) { - wfDeprecated( __METHOD__ ); $flags = EDIT_UPDATE | EDIT_DEFER_UPDATES | EDIT_AUTOSUMMARY | ( $minor ? EDIT_MINOR : 0 ) | ( $forceBot ? EDIT_FORCE_BOT : 0 ); @@ -1518,9 +1731,9 @@ class Article { * Fill in blank summaries with generated text where possible * * If neither EDIT_NEW nor EDIT_UPDATE is specified, the status of the article will be detected. - * If EDIT_UPDATE is specified and the article doesn't exist, the function will an - * edit-gone-missing error. If EDIT_NEW is specified and the article does exist, an - * edit-already-exists error will be returned. These two conditions are also possible with + * If EDIT_UPDATE is specified and the article doesn't exist, the function will an + * edit-gone-missing error. If EDIT_NEW is specified and the article does exist, an + * edit-already-exists error will be returned. These two conditions are also possible with * auto-detection due to MediaWiki's performance-optimised locking strategy. * * @param $baseRevId the revision ID this edit was based off, if any @@ -1555,7 +1768,7 @@ class Article { $status = Status::newGood( array() ); # Load $this->mTitle->getArticleID() and $this->mLatest if it's not already - $this->loadPageData(); + $this->loadPageData(); if( !($flags & EDIT_NEW) && !($flags & EDIT_UPDATE) ) { $aid = $this->mTitle->getArticleID(); @@ -1636,9 +1849,9 @@ class Article { # Update page # - # Note that we use $this->mLatest instead of fetching a value from the master DB - # during the course of this function. This makes sure that EditPage can detect - # edit conflicts reliably, either by $ok here, or by $article->getTimestamp() + # Note that we use $this->mLatest instead of fetching a value from the master DB + # during the course of this function. This makes sure that EditPage can detect + # edit conflicts reliably, either by $ok here, or by $article->getTimestamp() # before this function is called. A previous function used a separate query, this # creates a window where concurrent edits can cause an ignored edit conflict. $ok = $this->updateRevisionOn( $dbw, $revision, $this->mLatest ); @@ -1771,7 +1984,7 @@ class Article { $status->value['revision'] = $revision; wfRunHooks( 'ArticleSaveComplete', array( &$this, &$user, $text, $summary, - $flags & EDIT_MINOR, null, null, &$flags, $revision, &$status ) ); + $flags & EDIT_MINOR, null, null, &$flags, $revision, &$status, $baseRevId ) ); wfProfileOut( __METHOD__ ); return $status; @@ -1831,12 +2044,12 @@ class Article { $wgOut->showErrorPage( 'rcpatroldisabled', 'rcpatroldisabledtext' ); return; } - + if( in_array(array('hookaborted'), $errors) ) { // The hook itself has handled any output return; } - + if( in_array(array('markedaspatrollederror-noautopatrol'), $errors) ) { $wgOut->setPageTitle( wfMsg( 'markedaspatrollederror' ) ); $wgOut->addWikiMsg( 'markedaspatrollederror-noautopatrol' ); @@ -1962,12 +2175,12 @@ class Article { wfDebug( "updateRestrictions failed: $id <= 0\n" ); return false; } - + if ( wfReadOnly() ) { wfDebug( "updateRestrictions failed: read-only\n" ); return false; } - + if ( !$this->mTitle->userCan( 'protect' ) ) { wfDebug( "updateRestrictions failed: insufficient permissions\n" ); return false; @@ -2012,7 +2225,7 @@ class Article { if( wfRunHooks( 'ArticleProtect', array( &$this, &$wgUser, $limit, $reason ) ) ) { $dbw = wfGetDB( DB_MASTER ); - + # Prepare a null revision to be added to the history $modified = $current != '' && $protect; if( $protect ) { @@ -2028,9 +2241,9 @@ class Article { # The schema allows multiple restrictions if(!in_array('protect', $editrestriction) && !in_array('sysop', $editrestriction)) $cascade = false; - $cascade_description = ''; + $cascade_description = ''; if( $cascade ) { - $cascade_description = ' ['.wfMsgForContent('protect-summary-cascade').']'; + $cascade_description = ' ['.wfMsgForContent('protect-summary-cascade').']'; } if( $reason ) @@ -2042,15 +2255,15 @@ class Article { foreach( $limit as $action => $restrictions ) { if ( !isset($expiry[$action]) ) $expiry[$action] = 'infinite'; - + $encodedExpiry[$action] = Block::encodeExpiry($expiry[$action], $dbw ); if( $restrictions != '' ) { $protect_description .= "[$action=$restrictions] ("; if( $encodedExpiry[$action] != 'infinity' ) { - $protect_description .= wfMsgForContent( 'protect-expiring', + $protect_description .= wfMsgForContent( 'protect-expiring', $wgContLang->timeanddate( $expiry[$action], false, false ) , $wgContLang->date( $expiry[$action], false, false ) , - $wgContLang->time( $expiry[$action], false, false ) ); + $wgContLang->time( $expiry[$action], false, false ) ); } else { $protect_description .= wfMsgForContent( 'protect-expiry-indefinite' ); } @@ -2058,7 +2271,7 @@ class Article { } } $protect_description = trim($protect_description); - + if( $protect_description && $protect ) $editComment .= " ($protect_description)"; if( $cascade ) @@ -2067,9 +2280,9 @@ class Article { foreach( $limit as $action => $restrictions ) { if($restrictions != '' ) { $dbw->replace( 'page_restrictions', array(array('pr_page', 'pr_type')), - array( 'pr_page' => $id, - 'pr_type' => $action, - 'pr_level' => $restrictions, + array( 'pr_page' => $id, + 'pr_type' => $action, + 'pr_level' => $restrictions, 'pr_cascade' => ($cascade && $action == 'edit') ? 1 : 0, 'pr_expiry' => $encodedExpiry[$action] ), __METHOD__ ); } else { @@ -2159,18 +2372,16 @@ class Article { // Find out if there was only one contributor // Only scan the last 20 revisions - $limit = 20; $res = $dbw->select( 'revision', 'rev_user_text', - array( 'rev_page' => $this->getID() ), __METHOD__, - array( 'LIMIT' => $limit ) + array( 'rev_page' => $this->getID(), $dbw->bitAnd('rev_deleted', Revision::DELETED_USER) . ' = 0' ), + __METHOD__, + array( 'LIMIT' => 20 ) ); if( $res === false ) // This page has no revisions, which is very weird return false; - if( $res->numRows() > 1 ) - $hasHistory = true; - else - $hasHistory = false; + + $hasHistory = ( $res->numRows() > 1 ); $row = $dbw->fetchObject( $res ); $onlyAuthor = $row->rev_user_text; // Try to find a second contributor @@ -2193,7 +2404,7 @@ class Article { else $reason = wfMsgForContent( 'excontent', '$1' ); } - + if( $reason == '-' ) { // Allow these UI messages to be blanked out cleanly return ''; @@ -2380,24 +2591,31 @@ class Article { wfDebug( "Article::confirmDelete\n" ); - $wgOut->setSubtitle( wfMsgHtml( 'delete-backlink', $wgUser->getSkin()->makeKnownLinkObj( $this->mTitle ) ) ); + $deleteBackLink = $wgUser->getSkin()->link( + $this->mTitle, + null, + array(), + array(), + array( 'known', 'noclasses' ) + ); + $wgOut->setSubtitle( wfMsgHtml( 'delete-backlink', $deleteBackLink ) ); $wgOut->setRobotPolicy( 'noindex,nofollow' ); $wgOut->addWikiMsg( 'confirmdeletetext' ); if( $wgUser->isAllowed( 'suppressrevision' ) ) { $suppress = "<tr id=\"wpDeleteSuppressRow\" name=\"wpDeleteSuppressRow\"> <td></td> - <td class='mw-input'>" . + <td class='mw-input'><strong>" . Xml::checkLabel( wfMsg( 'revdelete-suppress' ), 'wpSuppress', 'wpSuppress', false, array( 'tabindex' => '4' ) ) . - "</td> + "</strong></td> </tr>"; } else { $suppress = ''; } $checkWatch = $wgUser->getBoolOption( 'watchdeletion' ) || $this->mTitle->userIsWatching(); - $form = Xml::openElement( 'form', array( 'method' => 'post', + $form = Xml::openElement( 'form', array( 'method' => 'post', 'action' => $this->mTitle->getLocalURL( 'action=delete' ), 'id' => 'deleteconfirm' ) ) . Xml::openElement( 'fieldset', array( 'id' => 'mw-delete-table' ) ) . Xml::tags( 'legend', null, wfMsgExt( 'delete-legend', array( 'parsemag', 'escapenoentities' ) ) ) . @@ -2417,7 +2635,7 @@ class Article { Xml::label( wfMsg( 'deleteotherreason' ), 'wpReason' ) . "</td> <td class='mw-input'>" . - Xml::input( 'wpReason', 60, $reason, array( 'type' => 'text', 'maxlength' => '255', + Xml::input( 'wpReason', 60, $reason, array( 'type' => 'text', 'maxlength' => '255', 'tabindex' => '2', 'id' => 'wpReason' ) ) . "</td> </tr> @@ -2443,11 +2661,18 @@ class Article { if( $wgUser->isAllowed( 'editinterface' ) ) { $skin = $wgUser->getSkin(); - $link = $skin->makeLink ( 'MediaWiki:Deletereason-dropdown', wfMsgHtml( 'delete-edit-reasonlist' ) ); + $title = Title::makeTitle( NS_MEDIAWIKI, 'Deletereason-dropdown' ); + $link = $skin->link( + $title, + wfMsgHtml( 'delete-edit-reasonlist' ), + array(), + array( 'action' => 'edit' ) + ); $form .= '<p class="mw-delete-editreasons">' . $link . '</p>'; } $wgOut->addHTML( $form ); + $wgOut->addHTML( Xml::element( 'h2', null, LogPage::logName( 'delete' ) ) ); LogEventsList::showLogExtract( $wgOut, 'delete', $this->mTitle->getPrefixedText() ); } @@ -2561,7 +2786,7 @@ class Article { $dbw->rollback(); return false; } - + # Fix category table counts $cats = array(); $res = $dbw->select( 'categorylinks', 'cl_to', array( 'cl_from' => $id ), __METHOD__ ); @@ -2591,9 +2816,9 @@ class Article { if( !$dbw->cleanupTriggers() ) { # Clean up recentchanges entries... $dbw->delete( 'recentchanges', - array( 'rc_type != '.RC_LOG, + array( 'rc_type != '.RC_LOG, 'rc_namespace' => $this->mTitle->getNamespace(), - 'rc_title' => $this->mTitle->getDBKey() ), + 'rc_title' => $this->mTitle->getDBkey() ), __METHOD__ ); $dbw->delete( 'recentchanges', array( 'rc_type != '.RC_LOG, 'rc_cur_id' => $id ), @@ -2802,7 +3027,7 @@ class Article { if( isset( $details['current'] ) ){ $current = $details['current']; if( $current->getComment() != '' ) { - $wgOut->addWikiMsgArray( 'editcomment', array( + $wgOut->addWikiMsgArray( 'editcomment', array( $wgUser->getSkin()->formatComment( $current->getComment() ) ), array( 'replaceafter' ) ); } } @@ -2876,9 +3101,7 @@ class Article { $edit->revid = $revid; $edit->newText = $text; $edit->pst = $this->preSaveTransform( $text ); - $options = new ParserOptions; - $options->setTidy( true ); - $options->enableLimitReport(); + $options = $this->getParserOptions(); $edit->output = $wgParser->parse( $edit->pst, $this->mTitle, $options, true, true, $revid ); $edit->oldText = $this->getContent(); $this->mPreparedEdit = $edit; @@ -2916,14 +3139,15 @@ class Article { # Save it to the parser cache if( $wgEnableParserCache ) { + $popts = $this->getParserOptions(); $parserCache = ParserCache::singleton(); - $parserCache->save( $editInfo->output, $this, $wgUser ); + $parserCache->save( $editInfo->output, $this, $popts ); } # Update the links tables $u = new LinksUpdate( $this->mTitle, $editInfo->output ); $u->doUpdate(); - + wfRunHooks( 'ArticleEditUpdates', array( &$this, &$editInfo, $changed ) ); if( wfRunHooks( 'ArticleEditUpdatesDeleteFromRecentchanges', array( &$this ) ) ) { @@ -3016,26 +3240,79 @@ class Article { $current = ( $oldid == $this->mLatest ); $td = $wgLang->timeanddate( $this->mTimestamp, true ); + $tddate = $wgLang->date( $this->mTimestamp, true ); + $tdtime = $wgLang->time( $this->mTimestamp, true ); $sk = $wgUser->getSkin(); $lnk = $current ? wfMsgHtml( 'currentrevisionlink' ) - : $sk->makeKnownLinkObj( $this->mTitle, wfMsgHtml( 'currentrevisionlink' ) ); + : $sk->link( + $this->mTitle, + wfMsgHtml( 'currentrevisionlink' ), + array(), + array(), + array( 'known', 'noclasses' ) + ); $curdiff = $current ? wfMsgHtml( 'diff' ) - : $sk->makeKnownLinkObj( $this->mTitle, wfMsgHtml( 'diff' ), 'diff=cur&oldid='.$oldid ); + : $sk->link( + $this->mTitle, + wfMsgHtml( 'diff' ), + array(), + array( + 'diff' => 'cur', + 'oldid' => $oldid + ), + array( 'known', 'noclasses' ) + ); $prev = $this->mTitle->getPreviousRevisionID( $oldid ) ; $prevlink = $prev - ? $sk->makeKnownLinkObj( $this->mTitle, wfMsgHtml( 'previousrevision' ), 'direction=prev&oldid='.$oldid ) + ? $sk->link( + $this->mTitle, + wfMsgHtml( 'previousrevision' ), + array(), + array( + 'direction' => 'prev', + 'oldid' => $oldid + ), + array( 'known', 'noclasses' ) + ) : wfMsgHtml( 'previousrevision' ); $prevdiff = $prev - ? $sk->makeKnownLinkObj( $this->mTitle, wfMsgHtml( 'diff' ), 'diff=prev&oldid='.$oldid ) + ? $sk->link( + $this->mTitle, + wfMsgHtml( 'diff' ), + array(), + array( + 'diff' => 'prev', + 'oldid' => $oldid + ), + array( 'known', 'noclasses' ) + ) : wfMsgHtml( 'diff' ); $nextlink = $current ? wfMsgHtml( 'nextrevision' ) - : $sk->makeKnownLinkObj( $this->mTitle, wfMsgHtml( 'nextrevision' ), 'direction=next&oldid='.$oldid ); + : $sk->link( + $this->mTitle, + wfMsgHtml( 'nextrevision' ), + array(), + array( + 'direction' => 'next', + 'oldid' => $oldid + ), + array( 'known', 'noclasses' ) + ); $nextdiff = $current ? wfMsgHtml( 'diff' ) - : $sk->makeKnownLinkObj( $this->mTitle, wfMsgHtml( 'diff' ), 'diff=next&oldid='.$oldid ); + : $sk->link( + $this->mTitle, + wfMsgHtml( 'diff' ), + array(), + array( + 'diff' => 'next', + 'oldid' => $oldid + ), + array( 'known', 'noclasses' ) + ); $cdel=''; if( $wgUser->isAllowed( 'deleterevision' ) ) { @@ -3047,10 +3324,17 @@ class Article { // If revision was hidden from sysops $cdel = wfMsgHtml( 'rev-delundel' ); } else { - $cdel = $sk->makeKnownLinkObj( $revdel, + $cdel = $sk->link( + $revdel, wfMsgHtml('rev-delundel'), - 'target=' . urlencode( $this->mTitle->getPrefixedDbkey() ) . - '&oldid=' . urlencode( $oldid ) ); + array(), + array( + 'type' => 'revision', + 'target' => urlencode( $this->mTitle->getPrefixedDbkey() ), + 'ids' => urlencode( $oldid ) + ), + array( 'known', 'noclasses' ) + ); // Bolden oversighted content if( $revision->isDeleted( Revision::DELETED_RESTRICTED ) ) $cdel = "<strong>$cdel</strong>"; @@ -3066,11 +3350,19 @@ class Article { ? 'revision-info-current' : 'revision-info'; - $r = "\n\t\t\t\t<div id=\"mw-{$infomsg}\">" . wfMsgExt( $infomsg, array( 'parseinline', 'replaceafter' ), - $td, $userlinks, $revision->getID() ) . "</div>\n" . - - "\n\t\t\t\t<div id=\"mw-revision-nav\">" . $cdel . wfMsgExt( 'revision-nav', array( 'escapenoentities', 'parsemag', 'replaceafter' ), - $prevdiff, $prevlink, $lnk, $curdiff, $nextlink, $nextdiff ) . "</div>\n\t\t\t"; + $r = "\n\t\t\t\t<div id=\"mw-{$infomsg}\">" . + wfMsgExt( + $infomsg, + array( 'parseinline', 'replaceafter' ), + $td, + $userlinks, + $revision->getID(), + $tddate, + $tdtime + ) . + "</div>\n" . + "\n\t\t\t\t<div id=\"mw-revision-nav\">" . $cdel . wfMsgExt( 'revision-nav', array( 'escapenoentities', 'parsemag', 'replaceafter' ), + $prevdiff, $prevlink, $lnk, $curdiff, $nextlink, $nextdiff ) . "</div>\n\t\t\t"; $wgOut->setSubtitle( $r ); } @@ -3410,7 +3702,7 @@ class Article { * @param $title Title object * @return array */ - protected function pageCountInfo( $title ) { + public function pageCountInfo( $title ) { $id = $title->getArticleId(); if( $id == 0 ) { return false; @@ -3539,19 +3831,39 @@ class Article { * @param $text String * @param $cache Boolean */ - public function outputWikiText( $text, $cache = true ) { - global $wgParser, $wgUser, $wgOut, $wgEnableParserCache, $wgUseFileCache; + public function outputWikiText( $text, $cache = true, $parserOptions = false ) { + global $wgOut; + + $parserOutput = $this->getOutputFromWikitext( $text, $cache, $parserOptions ); + $wgOut->addParserOutput( $parserOutput ); + } + + /** + * This does all the heavy lifting for outputWikitext, except it returns the parser + * output instead of sending it straight to $wgOut. Makes things nice and simple for, + * say, embedding thread pages within a discussion system (LiquidThreads) + */ + public function getOutputFromWikitext( $text, $cache = true, $parserOptions = false ) { + global $wgParser, $wgOut, $wgEnableParserCache, $wgUseFileCache; - $popts = $wgOut->parserOptions(); - $popts->setTidy(true); - $popts->enableLimitReport(); + if ( !$parserOptions ) { + $parserOptions = $this->getParserOptions(); + } + + $time = -wfTime(); $parserOutput = $wgParser->parse( $text, $this->mTitle, - $popts, true, true, $this->getRevIdFetched() ); - $popts->setTidy(false); - $popts->enableLimitReport( false ); + $parserOptions, true, true, $this->getRevIdFetched() ); + $time += wfTime(); + + # Timing hack + if( $time > 3 ) { + wfDebugLog( 'slow-parse', sprintf( "%-5.2f %s", $time, + $this->mTitle->getPrefixedDBkey())); + } + if( $wgEnableParserCache && $cache && $this && $parserOutput->getCacheTime() != -1 ) { $parserCache = ParserCache::singleton(); - $parserCache->save( $parserOutput, $this, $wgUser ); + $parserCache->save( $parserOutput, $this, $parserOptions ); } // Make sure file cache is not used on uncacheable content. // Output that has magic words in it can still use the parser cache @@ -3559,52 +3871,68 @@ class Article { if( $parserOutput->getCacheTime() == -1 || $parserOutput->containsOldMagic() ) { $wgUseFileCache = false; } + $this->doCascadeProtectionUpdates( $parserOutput ); + return $parserOutput; + } - if( $this->isCurrent() && !wfReadOnly() && $this->mTitle->areRestrictionsCascading() ) { - // templatelinks table 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 parser options suitable for rendering the primary article wikitext + */ + public function getParserOptions() { + global $wgUser; + if ( !$this->mParserOptions ) { + $this->mParserOptions = new ParserOptions( $wgUser ); + $this->mParserOptions->setTidy( true ); + $this->mParserOptions->enableLimitReport(); + } + return $this->mParserOptions; + } - # Get templates from templatelinks - $id = $this->mTitle->getArticleID(); + protected function doCascadeProtectionUpdates( $parserOutput ) { + if( !$this->isCurrent() || wfReadOnly() || !$this->mTitle->areRestrictionsCascading() ) { + return; + } - $tlTemplates = array(); + // templatelinks table 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. - $dbr = wfGetDB( DB_SLAVE ); - $res = $dbr->select( array( 'templatelinks' ), - array( 'tl_namespace', 'tl_title' ), - array( 'tl_from' => $id ), - __METHOD__ ); + # Get templates from templatelinks + $id = $this->mTitle->getArticleID(); - global $wgContLang; - foreach( $res as $row ) { - $tlTemplates["{$row->tl_namespace}:{$row->tl_title}"] = true; - } + $tlTemplates = array(); - # Get templates from parser output. - $poTemplates = array(); - foreach ( $parserOutput->getTemplates() as $ns => $templates ) { - foreach ( $templates as $dbk => $id ) { - $key = $row->tl_namespace . ':'. $row->tl_title; - $poTemplates["$ns:$dbk"] = true; - } - } + $dbr = wfGetDB( DB_SLAVE ); + $res = $dbr->select( array( 'templatelinks' ), + array( 'tl_namespace', 'tl_title' ), + array( 'tl_from' => $id ), + __METHOD__ ); - # Get the diff - # Note that we simulate array_diff_key in PHP <5.0.x - $templates_diff = array_diff_key( $poTemplates, $tlTemplates ); + global $wgContLang; + foreach( $res as $row ) { + $tlTemplates["{$row->tl_namespace}:{$row->tl_title}"] = true; + } - if( count( $templates_diff ) > 0 ) { - # Whee, link updates time. - $u = new LinksUpdate( $this->mTitle, $parserOutput, false ); - $u->doUpdate(); + # Get templates from parser output. + $poTemplates = array(); + foreach ( $parserOutput->getTemplates() as $ns => $templates ) { + foreach ( $templates as $dbk => $id ) { + $poTemplates["$ns:$dbk"] = true; } } - $wgOut->addParserOutput( $parserOutput ); + # Get the diff + # Note that we simulate array_diff_key in PHP <5.0.x + $templates_diff = array_diff_key( $poTemplates, $tlTemplates ); + + if( count( $templates_diff ) > 0 ) { + # Whee, link updates time. + $u = new LinksUpdate( $this->mTitle, $parserOutput, false ); + $u->doUpdate(); + } } /** @@ -3663,4 +3991,36 @@ class Article { ); } } + + /** Lightweight method to get the parser output for a page, checking the parser cache + * and so on. Doesn't consider most of the stuff that Article::view is forced to + * consider, so it's not appropriate to use there. */ + function getParserOutput( $oldid = null ) { + global $wgEnableParserCache, $wgUser, $wgOut; + + // Should the parser cache be used? + $useParserCache = $wgEnableParserCache && + intval( $wgUser->getOption( 'stubthreshold' ) ) == 0 && + $this->exists() && + $oldid === null; + + wfDebug( __METHOD__.': using parser cache: ' . ( $useParserCache ? 'yes' : 'no' ) . "\n" ); + if ( $wgUser->getOption( 'stubthreshold' ) ) { + wfIncrStats( 'pcache_miss_stub' ); + } + + $parserOutput = false; + if ( $useParserCache ) { + $parserOutput = ParserCache::singleton()->get( $this, $this->getParserOptions() ); + } + + if ( $parserOutput === false ) { + // Cache miss; parse and output it. + $rev = Revision::newFromTitle( $this->getTitle(), $oldid ); + + return $this->getOutputFromWikitext( $rev->getText(), $useParserCache ); + } else { + return $parserOutput; + } + } }