r52888 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r52887‎ | r52888 | r52889 >
Date:08:12, 8 July 2009
Author:tstarling
Status:resolved (Comments)
Tags:
Comment:
Implemented the PoolCounter feature and did some general refactoring in the areas that it touched.

* Renamed Article::outputFromWikitext() to Article::getOutputFromWikitext()
* Factored out cascade protection updates
* Removed recently-added Article::tryParserCache(): misnamed, can be done in one line of code in the caller. Deprecated OutputPage::tryParserCache().
* Made some functions public instead of protected when they could be useful from hooks.
* In ParserCache, removed PHP 4-style ampersands

In Article::view():
* Factored out robot policy logic, "redirected from" header, patrol footer, diff page, revdelete header, CSS/JS formatting, footer, namespace header, missing article error
* Removed some variables, renamed some others, fixed incorrect use of empty()
* Used the refactored footer section to do a couple of early returns and unindent a massive if(!$outputDone) block
* Removed fantasy interpretation of $this->getContent()===false in comment
* Don't try the parser cache when ArticleViewHeader specified $outputDone=true
* Move timing hack to getOutputFromWikitext()
* Stop using $wgOut->parserOptions() with save/restore nonsense every time you want to change something in it. This is meant to be OOP.
* Don't overwrite the article text with an error message and then pretend to write it to the cache, that's confusing
Modified paths:
  • /trunk/phase3/RELEASE-NOTES (modified) (history)
  • /trunk/phase3/includes/Article.php (modified) (history)
  • /trunk/phase3/includes/AutoLoader.php (modified) (history)
  • /trunk/phase3/includes/DefaultSettings.php (modified) (history)
  • /trunk/phase3/includes/OutputPage.php (modified) (history)
  • /trunk/phase3/includes/PoolCounter.php (added) (history)
  • /trunk/phase3/includes/parser/ParserCache.php (modified) (history)
  • /trunk/phase3/languages/messages/MessagesEn.php (modified) (history)

Diff [purge]

Index: trunk/phase3/includes/Article.php
@@ -40,6 +40,7 @@
4141 var $mTouched = '19700101000000'; //!<
4242 var $mUser = -1; //!< Not loaded
4343 var $mUserText = ''; //!<
 44+ var $mParserOptions; //!<
4445 /**@}}*/
4546
4647 /**
@@ -718,38 +719,40 @@
719720 }
720721
721722 /**
722 - * This is the default action of the script: just view the page of
723 - * the given title.
 723+ * This is the default action of the index.php entry point: just view the
 724+ * page of the given title.
724725 */
725726 public function view() {
726727 global $wgUser, $wgOut, $wgRequest, $wgContLang;
727728 global $wgEnableParserCache, $wgStylePath, $wgParser;
728 - global $wgUseTrackbacks, $wgNamespaceRobotPolicies, $wgArticleRobotPolicies;
729 - global $wgDefaultRobotPolicy;
 729+ global $wgUseTrackbacks;
730730
731 - # Let the parser know if this is the printable version
732 - if( $wgOut->isPrintable() ) {
733 - $wgOut->parserOptions()->setIsPrintable( true );
734 - }
735 -
736731 wfProfileIn( __METHOD__ );
737732
738733 # Get variables from query string
739734 $oldid = $this->getOldID();
 735+ $parserCache = ParserCache::singleton();
740736
 737+ $parserOptions = clone $this->getParserOptions();
 738+ # Render printable version, use printable version cache
 739+ if ( $wgOut->isPrintable() ) {
 740+ $parserOptions->setIsPrintable( true );
 741+ }
 742+
741743 # Try client and file cache
742744 if( $oldid === 0 && $this->checkTouched() ) {
743745 global $wgUseETag;
744746 if( $wgUseETag ) {
745 - $parserCache = ParserCache::singleton();
746 - $wgOut->setETag( $parserCache->getETag($this, $wgOut->parserOptions()) );
 747+ $wgOut->setETag( $parserCache->getETag( $this, $parserOptions ) );
747748 }
748749 # Is is client cached?
749750 if( $wgOut->checkLastModified( $this->getTouched() ) ) {
 751+ wfDebug( __METHOD__.": done 304\n" );
750752 wfProfileOut( __METHOD__ );
751753 return;
752754 # Try file cache
753755 } else if( $this->tryFileCache() ) {
 756+ wfDebug( __METHOD__.": done file cache\n" );
754757 # tell wgOut that output is taken care of
755758 $wgOut->disable();
756759 $this->viewUpdates();
@@ -758,80 +761,251 @@
759762 }
760763 }
761764
762 - $ns = $this->mTitle->getNamespace(); # shortcut
763765 $sk = $wgUser->getSkin();
764766
765767 # getOldID may want us to redirect somewhere else
766768 if( $this->mRedirectUrl ) {
767769 $wgOut->redirect( $this->mRedirectUrl );
 770+ wfDebug( __METHOD__.": redirecting due to oldid\n" );
768771 wfProfileOut( __METHOD__ );
769772 return;
770773 }
771774
772 - $diff = $wgRequest->getVal( 'diff' );
773 - $rcid = $wgRequest->getVal( 'rcid' );
774 - $rdfrom = $wgRequest->getVal( 'rdfrom' );
775 - $diffOnly = $wgRequest->getBool( 'diffonly', $wgUser->getOption( 'diffonly' ) );
776 - $purge = $wgRequest->getVal( 'action' ) == 'purge';
777 - $return404 = false;
778 -
779775 $wgOut->setArticleFlag( true );
 776+ $wgOut->setRobotPolicy( $this->getRobotPolicyForView() );
 777+ # Set page title (may be overridden by DISPLAYTITLE)
 778+ $wgOut->setPageTitle( $this->mTitle->getPrefixedText() );
780779
781 - # Discourage indexing of printable versions, but encourage following
782 - if( $wgOut->isPrintable() ) {
783 - $policy = 'noindex,follow';
784 - } elseif( isset( $wgArticleRobotPolicies[$this->mTitle->getPrefixedText()] ) ) {
785 - $policy = $wgArticleRobotPolicies[$this->mTitle->getPrefixedText()];
786 - } elseif( isset( $wgNamespaceRobotPolicies[$ns] ) ) {
787 - # Honour customised robot policies for this namespace
788 - $policy = $wgNamespaceRobotPolicies[$ns];
789 - } else {
790 - $policy = $wgDefaultRobotPolicy;
 780+ # If we got diff in the query, we want to see a diff page instead of the article.
 781+ if( !is_null( $wgRequest->getVal( 'diff' ) ) ) {
 782+ wfDebug( __METHOD__.": showing diff page\n" );
 783+ $this->showDiffPage();
 784+ wfProfileOut( __METHOD__ );
 785+ return;
791786 }
792 - $wgOut->setRobotPolicy( $policy );
793787
794 - # Allow admins to see deleted content if explicitly requested
795 - $delId = $diff ? $diff : $oldid;
796 - $unhide = $wgRequest->getInt('unhide') == 1;
797 - # If we got diff and oldid in the query, we want to see a
798 - # diff page instead of the article.
799 - if( !is_null( $diff ) ) {
800 - $wgOut->setPageTitle( $this->mTitle->getPrefixedText() );
 788+ # Should the parser cache be used?
 789+ $useParserCache = $this->useParserCache( $oldid );
 790+ wfDebug( 'Article::view using parser cache: ' . ($useParserCache ? 'yes' : 'no' ) . "\n" );
 791+ if( $wgUser->getOption( 'stubthreshold' ) ) {
 792+ wfIncrStats( 'pcache_miss_stub' );
 793+ }
801794
802 - $htmldiff = $wgRequest->getVal( 'htmldiff' , false);
803 - $de = new DifferenceEngine( $this->mTitle, $oldid, $diff, $rcid, $purge, $htmldiff, $unhide );
804 - // DifferenceEngine directly fetched the revision:
805 - $this->mRevIdFetched = $de->mNewid;
806 - $de->showDiffPage( $diffOnly );
 795+ # For the main page, overwrite the <title> element with the con-
 796+ # tents of 'pagetitle-view-mainpage' instead of the default (if
 797+ # that's not empty).
 798+ if( $this->mTitle->equals( Title::newMainPage() )
 799+ && wfMsgForContent( 'pagetitle-view-mainpage' ) !== '' )
 800+ {
 801+ $wgOut->setHTMLTitle( wfMsgForContent( 'pagetitle-view-mainpage' ) );
 802+ }
807803
808 - // Needed to get the page's current revision
809 - $this->loadPageData();
810 - if( $diff == 0 || $diff == $this->mLatest ) {
811 - # Run view updates for current revision only
812 - $this->viewUpdates();
 804+ $wasRedirected = $this->showRedirectedFromHeader();
 805+ $this->showNamespaceHeader();
 806+
 807+ $outputDone = false;
 808+ wfRunHooks( 'ArticleViewHeader', array( &$this, &$outputDone, &$useParserCache ) );
 809+
 810+ # Try the parser cache
 811+ if( !$outputDone && $useParserCache ) {
 812+ $parserOutput = $parserCache->get( $this, $parserOptions );
 813+ if ( $parserOutput !== false ) {
 814+ wfDebug( __METHOD__.": showing parser cache contents\n" );
 815+ $wgOut->addParserOutput( $parserOutput );
 816+ // Ensure that UI elements requiring revision ID have
 817+ // the correct version information.
 818+ $wgOut->setRevisionId( $this->mLatest );
 819+ $outputDone = true;
813820 }
 821+ }
 822+
 823+ if ( $outputDone ) {
 824+ $this->showViewFooter();
 825+ $this->viewUpdates();
814826 wfProfileOut( __METHOD__ );
815827 return;
816828 }
817829
 830+ $text = $this->getContent();
 831+ if( $text === false || $this->getID() == 0 ) {
 832+ wfDebug( __METHOD__.": showing missing article\n" );
 833+ $this->showMissingArticle();
 834+ wfProfileOut( __METHOD__ );
 835+ return;
 836+ }
 837+
 838+ # Another whitelist check in case oldid is altering the title
 839+ if( !$this->mTitle->userCanRead() ) {
 840+ wfDebug( __METHOD__.": denied on secondary read check\n" );
 841+ $wgOut->loginToUse();
 842+ $wgOut->output();
 843+ $wgOut->disable();
 844+ wfProfileOut( __METHOD__ );
 845+ return;
 846+ }
 847+
 848+ # We're looking at an old revision
 849+ if( $oldid && !is_null( $this->mRevision ) ) {
 850+ $this->setOldSubtitle( $oldid );
 851+ if ( !$this->showDeletedRevisionHeader() ) {
 852+ wfDebug( __METHOD__.": cannot view deleted revision\n" );
 853+ wfProfileOut( __METHOD__ );
 854+ return;
 855+ }
 856+
 857+ if ( $oldid === $this->getLatest() && $this->useParserCache( false ) ) {
 858+ $parserOutput = $parserCache->get( $this, $parserOptions );
 859+ if ( $parserOutput ) {
 860+ wfDebug( __METHOD__.": showing parser cache for current rev permalink\n" );
 861+ $wgOut->addParserOutput( $parserOutput );
 862+ $this->showViewFooter();
 863+ $this->viewUpdates();
 864+ wfProfileOut( __METHOD__ );
 865+ return;
 866+ }
 867+ }
 868+ }
 869+
 870+ // Ensure that UI elements requiring revision ID have
 871+ // the correct version information.
 872+ $wgOut->setRevisionId( $this->getRevIdFetched() );
 873+
 874+ // Pages containing custom CSS or JavaScript get special treatment
 875+ if( $this->mTitle->isCssOrJsPage() || $this->mTitle->isCssJsSubpage() ) {
 876+ wfDebug( __METHOD__.": showing CSS/JS source\n" );
 877+ $this->showCssOrJsPage();
 878+ $outputDone = true;
 879+ } else if( $rt = Title::newFromRedirectArray( $text ) ) {
 880+ wfDebug( __METHOD__.": showing redirect=no page\n" );
 881+ # Viewing a redirect page (e.g. with parameter redirect=no)
 882+ # Don't append the subtitle if this was an old revision
 883+ $wgOut->addHTML( $this->viewRedirect( $rt, !$wasRedirected && $this->isCurrent() ) );
 884+ # Parse just to get categories, displaytitle, etc.
 885+ $parserOutput = $wgParser->parse( $text, $this->mTitle, $parserOptions );
 886+ $wgOut->addParserOutputNoText( $parserOutput );
 887+ $outputDone = true;
 888+ }
 889+ if ( $outputDone ) {
 890+ $this->showViewFooter();
 891+ $this->viewUpdates();
 892+ wfProfileOut( __METHOD__ );
 893+ return;
 894+ }
 895+
 896+ # Run the parse, protected by a pool counter
 897+ wfDebug( __METHOD__.": doing uncached parse\n" );
 898+ $key = $parserCache->getKey( $this, $parserOptions );
 899+ $poolCounter = PoolCounter::factory( 'Article::view', $key );
 900+ $dirtyCallback = $useParserCache ? array( $this, 'tryDirtyCache' ) : false;
 901+ $status = $poolCounter->executeProtected( array( $this, 'doViewParse' ), $dirtyCallback );
 902+
 903+ if ( !$status->isOK() ) {
 904+ # Connection or timeout error
 905+ $this->showPoolError( $status );
 906+ wfProfileOut( __METHOD__ );
 907+ return;
 908+ }
 909+
 910+ $this->showViewFooter();
 911+ $this->viewUpdates();
 912+ wfProfileOut( __METHOD__ );
 913+ }
 914+
 915+ /**
 916+ * Show a diff page according to current request variables. For use within
 917+ * Article::view() only, other callers should use the DifferenceEngine class.
 918+ */
 919+ public function showDiffPage() {
 920+ global $wgOut, $wgRequest, $wgUser;
 921+
 922+ $diff = $wgRequest->getVal( 'diff' );
 923+ $rcid = $wgRequest->getVal( 'rcid' );
 924+ $diffOnly = $wgRequest->getBool( 'diffonly', $wgUser->getOption( 'diffonly' ) );
 925+ $purge = $wgRequest->getVal( 'action' ) == 'purge';
 926+ $htmldiff = $wgRequest->getVal( 'htmldiff' , false);
 927+ $unhide = $wgRequest->getInt('unhide') == 1;
 928+ $oldid = $this->getOldID();
 929+
 930+ $de = new DifferenceEngine( $this->mTitle, $oldid, $diff, $rcid, $purge, $htmldiff, $unhide );
 931+ // DifferenceEngine directly fetched the revision:
 932+ $this->mRevIdFetched = $de->mNewid;
 933+ $de->showDiffPage( $diffOnly );
 934+
 935+ // Needed to get the page's current revision
 936+ $this->loadPageData();
 937+ if( $diff == 0 || $diff == $this->mLatest ) {
 938+ # Run view updates for current revision only
 939+ $this->viewUpdates();
 940+ }
 941+ }
 942+
 943+ /**
 944+ * Show a page view for a page formatted as CSS or JavaScript. To be called by
 945+ * Article::view() only.
 946+ *
 947+ * This is hooked by SyntaxHighlight_GeSHi to do syntax highlighting of these
 948+ * page views.
 949+ */
 950+ public function showCssOrJsPage() {
 951+ global $wgOut;
 952+ $wgOut->addHTML( wfMsgExt( 'clearyourcache', 'parse' ) );
 953+ // Give hooks a chance to customise the output
 954+ if( wfRunHooks( 'ShowRawCssJs', array( $this->mContent, $this->mTitle, $wgOut ) ) ) {
 955+ // Wrap the whole lot in a <pre> and don't parse
 956+ $m = array();
 957+ preg_match( '!\.(css|js)$!u', $this->mTitle->getText(), $m );
 958+ $wgOut->addHTML( "<pre class=\"mw-code mw-{$m[1]}\" dir=\"ltr\">\n" );
 959+ $wgOut->addHTML( htmlspecialchars( $this->mContent ) );
 960+ $wgOut->addHTML( "\n</pre>\n" );
 961+ }
 962+ }
 963+
 964+ /**
 965+ * Get the robot policy to be used for the current action=view request.
 966+ */
 967+ public function getRobotPolicyForView() {
 968+ global $wgOut, $wgArticleRobotPolicies, $wgNamespaceRobotPolicies;
 969+ global $wgDefaultRobotPolicy, $wgRequest;
 970+
 971+ $ns = $this->mTitle->getNamespace();
818972 if( $ns == NS_USER || $ns == NS_USER_TALK ) {
819 - # User/User_talk subpages are not modified. (bug 11443)
 973+ # Don't index user and user talk pages for blocked users (bug 11443)
820974 if( !$this->mTitle->isSubpage() ) {
821975 $block = new Block();
822 - if( $block->load( $this->mTitle->getBaseText() ) ) {
823 - $wgOut->setRobotpolicy( 'noindex,nofollow' );
 976+ if( $block->load( $this->mTitle->getText() ) ) {
 977+ return 'noindex,nofollow';
824978 }
825979 }
826980 }
827981
828 - # Should the parser cache be used?
829 - $pcache = $this->useParserCache( $oldid );
830 - wfDebug( 'Article::view using parser cache: ' . ($pcache ? 'yes' : 'no' ) . "\n" );
831 - if( $wgUser->getOption( 'stubthreshold' ) ) {
832 - wfIncrStats( 'pcache_miss_stub' );
 982+ if( $this->getID() === 0 || $this->getOldID() ) {
 983+ return 'noindex,nofollow';
 984+ } elseif( $wgOut->isPrintable() ) {
 985+ # Discourage indexing of printable versions, but encourage following
 986+ return 'noindex,follow';
 987+ } elseif( $wgRequest->getInt('curid') ) {
 988+ # For ?curid=x urls, disallow indexing
 989+ return 'noindex,follow';
 990+ } elseif( isset( $wgArticleRobotPolicies[$this->mTitle->getPrefixedText()] ) ) {
 991+ return $wgArticleRobotPolicies[$this->mTitle->getPrefixedText()];
 992+ } elseif( isset( $wgNamespaceRobotPolicies[$ns] ) ) {
 993+ # Honour customised robot policies for this namespace
 994+ return $wgNamespaceRobotPolicies[$ns];
 995+ } else {
 996+ return $wgDefaultRobotPolicy;
833997 }
 998+ }
834999
835 - $wasRedirected = false;
 1000+ /**
 1001+ * If this request is a redirect view, send "redirected from" subtitle to
 1002+ * $wgOut. Returns true if the header was needed, false if this is not a
 1003+ * redirect view. Handles both local and remote redirects.
 1004+ */
 1005+ public function showRedirectedFromHeader() {
 1006+ global $wgOut, $wgUser, $wgRequest, $wgRedirectSources;
 1007+
 1008+ $rdfrom = $wgRequest->getVal( 'rdfrom' );
 1009+ $sk = $wgUser->getSkin();
8361010 if( isset( $this->mRedirectedFrom ) ) {
8371011 // This is an internally redirected page view.
8381012 // We'll need a backlink to the source page for navigation.
@@ -856,226 +1030,156 @@
8571031 $wgOut->addLink( array( 'rel' => 'canonical',
8581032 'href' => $this->mTitle->getLocalURL() )
8591033 );
860 - $wasRedirected = true;
 1034+ return true;
8611035 }
862 - } elseif( !empty( $rdfrom ) ) {
 1036+ } elseif( $rdfrom ) {
8631037 // This is an externally redirected view, from some other wiki.
8641038 // If it was reported from a trusted site, supply a backlink.
865 - global $wgRedirectSources;
8661039 if( $wgRedirectSources && preg_match( $wgRedirectSources, $rdfrom ) ) {
8671040 $redir = $sk->makeExternalLink( $rdfrom, $rdfrom );
8681041 $s = wfMsgExt( 'redirectedfrom', array( 'parseinline', 'replaceafter' ), $redir );
8691042 $wgOut->setSubtitle( $s );
870 - $wasRedirected = true;
 1043+ return true;
8711044 }
8721045 }
 1046+ return false;
 1047+ }
8731048
874 - # Allow a specific header on talk pages, like [[MediaWiki:Talkpagetext]]
 1049+ /**
 1050+ * Show a header specific to the namespace currently being viewed, like
 1051+ * [[MediaWiki:Talkpagetext]]. For Article::view().
 1052+ */
 1053+ public function showNamespaceHeader() {
 1054+ global $wgOut;
8751055 if( $this->mTitle->isTalkPage() ) {
8761056 $msg = wfMsgNoTrans( 'talkpageheader' );
8771057 if ( $msg !== '-' && !wfEmptyMsg( 'talkpageheader', $msg ) ) {
8781058 $wgOut->wrapWikiMsg( "<div class=\"mw-talkpageheader\">\n$1</div>", array( 'talkpageheader' ) );
8791059 }
8801060 }
 1061+ }
8811062
882 - $outputDone = false;
883 - wfRunHooks( 'ArticleViewHeader', array( &$this, &$outputDone, &$pcache ) );
884 - if( $pcache && $wgOut->tryParserCache( $this ) ) {
885 - // Ensure that UI elements requiring revision ID have
886 - // the correct version information.
887 - $wgOut->setRevisionId( $this->mLatest );
888 - $outputDone = true;
 1063+ /**
 1064+ * Show the footer section of an ordinary page view
 1065+ */
 1066+ public function showViewFooter() {
 1067+ global $wgOut, $wgUseTrackbacks, $wgRequest;
 1068+ # check if we're displaying a [[User talk:x.x.x.x]] anonymous talk page
 1069+ if( $this->mTitle->getNamespace() == NS_USER_TALK && IP::isValid( $this->mTitle->getText() ) ) {
 1070+ $wgOut->addWikiMsg('anontalkpagetext');
8891071 }
890 - # Fetch content and check for errors
891 - if( !$outputDone ) {
892 - # If the article does not exist and was deleted/moved, show the log
893 - if( $this->getID() == 0 ) {
894 - $this->showLogs();
895 - }
896 - $text = $this->getContent();
897 - // For now, check also for ID until getContent actually returns
898 - // false for pages that do not exists
899 - if( $text === false || $this->getID() === 0 ) {
900 - # Failed to load, replace text with error message
901 - $t = $this->mTitle->getPrefixedText();
902 - if( $oldid ) {
903 - $d = wfMsgExt( 'missingarticle-rev', 'escape', $oldid );
904 - $text = wfMsgExt( 'missing-article', 'parsemag', $t, $d );
905 - // Always use page content for pages in the MediaWiki namespace
906 - // since it contains the default message
907 - } elseif ( $this->mTitle->getNamespace() != NS_MEDIAWIKI ) {
908 - $text = wfMsgExt( 'noarticletext', 'parsemag' );
909 - }
910 - }
9111072
912 - # Non-existent pages
913 - if( $this->getID() === 0 ) {
914 - $wgOut->setRobotPolicy( 'noindex,nofollow' );
915 - $text = "<div class='noarticletext'>\n$text\n</div>";
916 - if( !$this->hasViewableContent() ) {
917 - // If there's no backing content, send a 404 Not Found
918 - // for better machine handling of broken links.
919 - $return404 = true;
920 - }
921 - }
 1073+ # If we have been passed an &rcid= parameter, we want to give the user a
 1074+ # chance to mark this new article as patrolled.
 1075+ $this->showPatrolFooter();
9221076
923 - if( $return404 ) {
924 - $wgRequest->response()->header( "HTTP/1.x 404 Not Found" );
925 - }
 1077+ # Trackbacks
 1078+ if( $wgUseTrackbacks ) {
 1079+ $this->addTrackbacks();
 1080+ }
 1081+ }
9261082
927 - # Another whitelist check in case oldid is altering the title
928 - if( !$this->mTitle->userCanRead() ) {
929 - $wgOut->loginToUse();
930 - $wgOut->output();
931 - $wgOut->disable();
932 - wfProfileOut( __METHOD__ );
933 - return;
934 - }
 1083+ /**
 1084+ * If patrol is possible, output a patrol UI box. This is called from the
 1085+ * footer section of ordinary page views. If patrol is not possible or not
 1086+ * desired, does nothing.
 1087+ */
 1088+ public function showPatrolFooter() {
 1089+ global $wgOut, $wgRequest;
 1090+ $rcid = $wgRequest->getVal( 'rcid' );
9351091
936 - # For ?curid=x urls, disallow indexing
937 - if( $wgRequest->getInt('curid') )
938 - $wgOut->setRobotPolicy( 'noindex,follow' );
 1092+ if( !$rcid || !$this->mTitle->exists() || !$this->mTitle->quickUserCan( 'patrol' ) ) {
 1093+ return;
 1094+ }
9391095
940 - # We're looking at an old revision
941 - if( !empty( $oldid ) ) {
942 - $wgOut->setRobotPolicy( 'noindex,nofollow' );
943 - if( is_null( $this->mRevision ) ) {
944 - // FIXME: This would be a nice place to load the 'no such page' text.
945 - } else {
946 - $this->setOldSubtitle( $oldid );
947 - # Allow admins to see deleted content if explicitly requested
948 - if( $this->mRevision->isDeleted( Revision::DELETED_TEXT ) ) {
949 - // If the user is not allowed to see it...
950 - if( !$this->mRevision->userCan(Revision::DELETED_TEXT) ) {
951 - $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1</div>\n",
952 - 'rev-deleted-text-permission' );
953 - $wgOut->setPageTitle( $this->mTitle->getPrefixedText() );
954 - wfProfileOut( __METHOD__ );
955 - return;
956 - // If the user needs to confirm that they want to see it...
957 - } else if( !$unhide ) {
958 - # Give explanation and add a link to view the revision...
959 - $link = $this->mTitle->getFullUrl( "oldid={$oldid}&unhide=1" );
960 - $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1</div>\n",
961 - array('rev-deleted-text-unhide',$link) );
962 - $wgOut->setPageTitle( $this->mTitle->getPrefixedText() );
963 - wfProfileOut( __METHOD__ );
964 - return;
965 - // We are allowed to see...
966 - } else {
967 - $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1</div>\n",
968 - 'rev-deleted-text-view' );
969 - }
970 - }
971 - // Is this the current revision and otherwise cacheable? Try the parser cache...
972 - if( $oldid === $this->getLatest() && $this->useParserCache( false )
973 - && $wgOut->tryParserCache( $this ) )
974 - {
975 - $outputDone = true;
976 - }
977 - }
978 - }
 1096+ $wgOut->addHTML(
 1097+ "<div class='patrollink'>" .
 1098+ wfMsgHtml(
 1099+ 'markaspatrolledlink',
 1100+ $sk->link(
 1101+ $this->mTitle,
 1102+ wfMsgHtml( 'markaspatrolledtext' ),
 1103+ array(),
 1104+ array(
 1105+ 'action' => 'markpatrolled',
 1106+ 'rcid' => $rcid
 1107+ ),
 1108+ array( 'known', 'noclasses' )
 1109+ )
 1110+ ) .
 1111+ '</div>'
 1112+ );
 1113+ }
9791114
980 - // Ensure that UI elements requiring revision ID have
981 - // the correct version information.
982 - $wgOut->setRevisionId( $this->getRevIdFetched() );
 1115+ /**
 1116+ * Show the error text for a missing article. For articles in the MediaWiki
 1117+ * namespace, show the default message text. To be called from Article::view().
 1118+ */
 1119+ public function showMissingArticle() {
 1120+ global $wgOut, $wgRequest;
 1121+ # Show delete and move logs
 1122+ $this->showLogs();
9831123
984 - if( $outputDone ) {
985 - // do nothing...
986 - // Pages containing custom CSS or JavaScript get special treatment
987 - } else if( $this->mTitle->isCssOrJsPage() || $this->mTitle->isCssJsSubpage() ) {
988 - $wgOut->addHTML( wfMsgExt( 'clearyourcache', 'parse' ) );
989 - // Give hooks a chance to customise the output
990 - if( wfRunHooks( 'ShowRawCssJs', array( $this->mContent, $this->mTitle, $wgOut ) ) ) {
991 - // Wrap the whole lot in a <pre> and don't parse
992 - $m = array();
993 - preg_match( '!\.(css|js)$!u', $this->mTitle->getText(), $m );
994 - $wgOut->addHTML( "<pre class=\"mw-code mw-{$m[1]}\" dir=\"ltr\">\n" );
995 - $wgOut->addHTML( htmlspecialchars( $this->mContent ) );
996 - $wgOut->addHTML( "\n</pre>\n" );
997 - }
998 - } else if( $rt = Title::newFromRedirectArray( $text ) ) { # get an array of redirect targets
999 - # Don't append the subtitle if this was an old revision
1000 - $wgOut->addHTML( $this->viewRedirect( $rt, !$wasRedirected && $this->isCurrent() ) );
1001 - $parseout = $wgParser->parse($text, $this->mTitle, ParserOptions::newFromUser($wgUser));
1002 - $wgOut->addParserOutputNoText( $parseout );
1003 - } else if( $pcache ) {
1004 - # Display content and save to parser cache
1005 - $this->outputWikiText( $text );
1006 - } else {
1007 - # Display content, don't attempt to save to parser cache
1008 - # Don't show section-edit links on old revisions... this way lies madness.
1009 - if( !$this->isCurrent() ) {
1010 - $oldEditSectionSetting = $wgOut->parserOptions()->setEditSection( false );
1011 - }
1012 - # Display content and don't save to parser cache
1013 - # With timing hack -- TS 2006-07-26
1014 - $time = -wfTime();
1015 - $this->outputWikiText( $text, false );
1016 - $time += wfTime();
1017 -
1018 - # Timing hack
1019 - if( $time > 3 ) {
1020 - wfDebugLog( 'slow-parse', sprintf( "%-5.2f %s", $time,
1021 - $this->mTitle->getPrefixedDBkey()));
1022 - }
1023 -
1024 - if( !$this->isCurrent() ) {
1025 - $wgOut->parserOptions()->setEditSection( $oldEditSectionSetting );
1026 - }
1027 - }
 1124+ # Show error message
 1125+ $oldid = $this->getOldID();
 1126+ if( $oldid ) {
 1127+ $text = wfMsgNoTrans( 'missing-article',
 1128+ $this->mTitle->getPrefixedText(),
 1129+ wfMsgNoTrans( 'missingarticle-rev', $oldid ) );
 1130+ } elseif ( $this->mTitle->getNamespace() === NS_MEDIAWIKI ) {
 1131+ // Use the default message text
 1132+ $text = $this->getContent();
 1133+ } else {
 1134+ $text = wfMsgNoTrans( 'noarticletext' );
10281135 }
1029 - /* title may have been set from the cache */
1030 - $t = $wgOut->getPageTitle();
1031 - if( empty( $t ) ) {
1032 - $wgOut->setPageTitle( $this->mTitle->getPrefixedText() );
1033 -
1034 - # For the main page, overwrite the <title> element with the con-
1035 - # tents of 'pagetitle-view-mainpage' instead of the default (if
1036 - # that's not empty).
1037 - if( $this->mTitle->equals( Title::newMainPage() ) &&
1038 - wfMsgForContent( 'pagetitle-view-mainpage' ) !== '' ) {
1039 - $wgOut->setHTMLTitle( wfMsgForContent( 'pagetitle-view-mainpage' ) );
1040 - }
 1136+ $text = "<div class='noarticletext'>\n$text\n</div>";
 1137+ if( !$this->hasViewableContent() ) {
 1138+ // If there's no backing content, send a 404 Not Found
 1139+ // for better machine handling of broken links.
 1140+ $wgRequest->response()->header( "HTTP/1.x 404 Not Found" );
10411141 }
 1142+ $wgOut->addWikiText( $text );
 1143+ }
10421144
1043 - # check if we're displaying a [[User talk:x.x.x.x]] anonymous talk page
1044 - if( $ns == NS_USER_TALK && IP::isValid( $this->mTitle->getText() ) ) {
1045 - $wgOut->addWikiMsg('anontalkpagetext');
1046 - }
 1145+ /**
 1146+ * If the revision requested for view is deleted, check permissions.
 1147+ * Send either an error message or a warning header to $wgOut.
 1148+ * Returns true if the view is allowed, false if not.
 1149+ */
 1150+ public function showDeletedRevisionHeader() {
 1151+ global $wgOut, $wgRequest;
10471152
1048 - # If we have been passed an &rcid= parameter, we want to give the user a
1049 - # chance to mark this new article as patrolled.
1050 - if( !empty( $rcid ) && $this->mTitle->exists() && $this->mTitle->quickUserCan( 'patrol' ) ) {
1051 - $wgOut->addHTML(
1052 - "<div class='patrollink'>" .
1053 - wfMsgHtml(
1054 - 'markaspatrolledlink',
1055 - $sk->link(
1056 - $this->mTitle,
1057 - wfMsgHtml( 'markaspatrolledtext' ),
1058 - array(),
1059 - array(
1060 - 'action' => 'markpatrolled',
1061 - 'rcid' => $rcid
1062 - ),
1063 - array( 'known', 'noclasses' )
1064 - )
1065 - ) .
1066 - '</div>'
1067 - );
 1153+ if( !$this->mRevision->isDeleted( Revision::DELETED_TEXT ) ) {
 1154+ // Not deleted
 1155+ return true;
10681156 }
10691157
1070 - # Trackbacks
1071 - if( $wgUseTrackbacks ) {
1072 - $this->addTrackbacks();
 1158+ // If the user is not allowed to see it...
 1159+ if( !$this->mRevision->userCan(Revision::DELETED_TEXT) ) {
 1160+ $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1</div>\n",
 1161+ 'rev-deleted-text-permission' );
 1162+ return false;
 1163+ // If the user needs to confirm that they want to see it...
 1164+ } else if( $wgRequest->getInt('unhide') != 1 ) {
 1165+ # Give explanation and add a link to view the revision...
 1166+ $oldid = intval( $this->getOldID() );
 1167+ $link = $this->mTitle->getFullUrl( "oldid={$oldid}&unhide=1" );
 1168+ $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1</div>\n",
 1169+ array('rev-deleted-text-unhide',$link) );
 1170+ return false;
 1171+ // We are allowed to see...
 1172+ } else {
 1173+ $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1</div>\n",
 1174+ 'rev-deleted-text-view' );
 1175+ return true;
10731176 }
1074 -
1075 - $this->viewUpdates();
1076 - wfProfileOut( __METHOD__ );
10771177 }
10781178
1079 - protected function showLogs() {
 1179+ /**
 1180+ * Show an excerpt from the deletion and move logs. To be called from the
 1181+ * header section on page views of missing pages.
 1182+ */
 1183+ public function showLogs() {
10801184 global $wgUser, $wgOut;
10811185 $loglist = new LogEventsList( $wgUser->getSkin(), $wgOut );
10821186 $pager = new LogPager( $loglist, array('move', 'delete'), false,
@@ -1104,7 +1208,7 @@
11051209 /*
11061210 * Should the parser cache be used?
11071211 */
1108 - protected function useParserCache( $oldid ) {
 1212+ public function useParserCache( $oldid ) {
11091213 global $wgUser, $wgEnableParserCache;
11101214
11111215 return $wgEnableParserCache
@@ -1116,6 +1220,64 @@
11171221 }
11181222
11191223 /**
 1224+ * Execute the uncached parse for action=view
 1225+ */
 1226+ public function doViewParse() {
 1227+ global $wgOut;
 1228+ $oldid = $this->getOldID();
 1229+ $useParserCache = $this->useParserCache( $oldid );
 1230+ $parserOptions = clone $this->getParserOptions();
 1231+ # Render printable version, use printable version cache
 1232+ $parserOptions->setIsPrintable( $wgOut->isPrintable() );
 1233+ # Don't show section-edit links on old revisions... this way lies madness.
 1234+ $parserOptions->setEditSection( $this->isCurrent() );
 1235+ $useParserCache = $this->useParserCache( $oldid );
 1236+ $this->outputWikiText( $this->getContent(), $useParserCache, $parserOptions );
 1237+ }
 1238+
 1239+ /**
 1240+ * Try to fetch an expired entry from the parser cache. If it is present,
 1241+ * output it and return true. If it is not present, output nothing and
 1242+ * return false. This is used as a callback function for
 1243+ * PoolCounter::executeProtected().
 1244+ */
 1245+ public function tryDirtyCache() {
 1246+ global $wgOut;
 1247+ $parserCache = ParserCache::singleton();
 1248+ $options = $this->getParserOptions();
 1249+ $options->setIsPrintable( $wgOut->isPrintable() );
 1250+ $output = $parserCache->getDirty( $this, $options );
 1251+ if ( $output ) {
 1252+ wfDebug( __METHOD__.": sending dirty output\n" );
 1253+ wfDebugLog( 'dirty', "dirty output " . $parserCache->getKey( $this, $options ) . "\n" );
 1254+ $wgOut->setSquidMaxage( 0 );
 1255+ $wgOut->addParserOutput( $output );
 1256+ $wgOut->addHTML( "<!-- parser cache is expired, sending anyway due to pool overload-->\n" );
 1257+ return true;
 1258+ } else {
 1259+ wfDebugLog( 'dirty', "dirty missing\n" );
 1260+ wfDebug( __METHOD__.": no dirty cache\n" );
 1261+ return false;
 1262+ }
 1263+ }
 1264+
 1265+ /**
 1266+ * Show an error page for an error from the pool counter.
 1267+ * @param $status Status
 1268+ */
 1269+ public function showPoolError( $status ) {
 1270+ global $wgOut;
 1271+ $wgOut->clearHTML(); // for release() errors
 1272+ $wgOut->enableClientCache( false );
 1273+ $wgOut->setRobotPolicy( 'noindex,nofollow' );
 1274+ $wgOut->addWikiText(
 1275+ '<div class="errorbox">' .
 1276+ $status->getWikiText( false, 'view-pool-error' ) .
 1277+ '</div>'
 1278+ );
 1279+ }
 1280+
 1281+ /**
11201282 * View redirect
11211283 * @param $target Title object or Array of destination(s) to redirect
11221284 * @param $appendSubtitle Boolean [optional]
@@ -1511,7 +1673,6 @@
15121674 * @deprecated use Article::doEdit()
15131675 */
15141676 function updateArticle( $text, $summary, $minor, $watchthis, $forceBot = false, $sectionanchor = '' ) {
1515 - wfDeprecated( __METHOD__ );
15161677 $flags = EDIT_UPDATE | EDIT_DEFER_UPDATES | EDIT_AUTOSUMMARY |
15171678 ( $minor ? EDIT_MINOR : 0 ) |
15181679 ( $forceBot ? EDIT_FORCE_BOT : 0 );
@@ -2940,9 +3101,7 @@
29413102 $edit->revid = $revid;
29423103 $edit->newText = $text;
29433104 $edit->pst = $this->preSaveTransform( $text );
2944 - $options = new ParserOptions;
2945 - $options->setTidy( true );
2946 - $options->enableLimitReport();
 3105+ $options = $this->getParserOptions();
29473106 $edit->output = $wgParser->parse( $edit->pst, $this->mTitle, $options, true, true, $revid );
29483107 $edit->oldText = $this->getContent();
29493108 $this->mPreparedEdit = $edit;
@@ -2980,9 +3139,7 @@
29813140
29823141 # Save it to the parser cache
29833142 if( $wgEnableParserCache ) {
2984 - $popts = new ParserOptions;
2985 - $popts->setTidy( true );
2986 - $popts->enableLimitReport();
 3143+ $popts = $this->getParserOptions();
29873144 $parserCache = ParserCache::singleton();
29883145 $parserCache->save( $editInfo->output, $this, $popts );
29893146 }
@@ -3674,11 +3831,10 @@
36753832 * @param $text String
36763833 * @param $cache Boolean
36773834 */
3678 - public function outputWikiText( $text, $cache = true ) {
 3835+ public function outputWikiText( $text, $cache = true, $parserOptions = false ) {
36793836 global $wgOut;
36803837
3681 - $parserOutput = $this->outputFromWikitext( $text, $cache );
3682 -
 3838+ $parserOutput = $this->getOutputFromWikitext( $text, $cache, $parserOptions );
36833839 $wgOut->addParserOutput( $parserOutput );
36843840 }
36853841
@@ -3687,19 +3843,27 @@
36883844 * output instead of sending it straight to $wgOut. Makes things nice and simple for,
36893845 * say, embedding thread pages within a discussion system (LiquidThreads)
36903846 */
3691 - public function outputFromWikitext( $text, $cache = true ) {
 3847+ public function getOutputFromWikitext( $text, $cache = true, $parserOptions = false ) {
36923848 global $wgParser, $wgOut, $wgEnableParserCache, $wgUseFileCache;
36933849
3694 - $popts = $wgOut->parserOptions();
3695 - $popts->setTidy(true);
3696 - $popts->enableLimitReport();
 3850+ if ( !$parserOptions ) {
 3851+ $parserOptions = $this->getParserOptions();
 3852+ }
 3853+
 3854+ $time = -wfTime();
36973855 $parserOutput = $wgParser->parse( $text, $this->mTitle,
3698 - $popts, true, true, $this->getRevIdFetched() );
3699 - $popts->setTidy(false);
3700 - $popts->enableLimitReport( false );
 3856+ $parserOptions, true, true, $this->getRevIdFetched() );
 3857+ $time += wfTime();
 3858+
 3859+ # Timing hack
 3860+ if( $time > 3 ) {
 3861+ wfDebugLog( 'slow-parse', sprintf( "%-5.2f %s", $time,
 3862+ $this->mTitle->getPrefixedDBkey()));
 3863+ }
 3864+
37013865 if( $wgEnableParserCache && $cache && $this && $parserOutput->getCacheTime() != -1 ) {
37023866 $parserCache = ParserCache::singleton();
3703 - $parserCache->save( $parserOutput, $this, $popts );
 3867+ $parserCache->save( $parserOutput, $this, $parserOptions );
37043868 }
37053869 // Make sure file cache is not used on uncacheable content.
37063870 // Output that has magic words in it can still use the parser cache
@@ -3707,51 +3871,68 @@
37083872 if( $parserOutput->getCacheTime() == -1 || $parserOutput->containsOldMagic() ) {
37093873 $wgUseFileCache = false;
37103874 }
 3875+ $this->doCascadeProtectionUpdates( $parserOutput );
 3876+ return $parserOutput;
 3877+ }
37113878
3712 - if( $this->isCurrent() && !wfReadOnly() && $this->mTitle->areRestrictionsCascading() ) {
3713 - // templatelinks table may have become out of sync,
3714 - // especially if using variable-based transclusions.
3715 - // For paranoia, check if things have changed and if
3716 - // so apply updates to the database. This will ensure
3717 - // that cascaded protections apply as soon as the changes
3718 - // are visible.
 3879+ /**
 3880+ * Get parser options suitable for rendering the primary article wikitext
 3881+ */
 3882+ public function getParserOptions() {
 3883+ global $wgUser;
 3884+ if ( !$this->mParserOptions ) {
 3885+ $this->mParserOptions = new ParserOptions( $wgUser );
 3886+ $this->mParserOptions->setTidy( true );
 3887+ $this->mParserOptions->enableLimitReport();
 3888+ }
 3889+ return $this->mParserOptions;
 3890+ }
37193891
3720 - # Get templates from templatelinks
3721 - $id = $this->mTitle->getArticleID();
 3892+ protected function doCascadeProtectionUpdates( $parserOutput ) {
 3893+ if( !$this->isCurrent() || wfReadOnly() || !$this->mTitle->areRestrictionsCascading() ) {
 3894+ return;
 3895+ }
37223896
3723 - $tlTemplates = array();
 3897+ // templatelinks table may have become out of sync,
 3898+ // especially if using variable-based transclusions.
 3899+ // For paranoia, check if things have changed and if
 3900+ // so apply updates to the database. This will ensure
 3901+ // that cascaded protections apply as soon as the changes
 3902+ // are visible.
37243903
3725 - $dbr = wfGetDB( DB_SLAVE );
3726 - $res = $dbr->select( array( 'templatelinks' ),
3727 - array( 'tl_namespace', 'tl_title' ),
3728 - array( 'tl_from' => $id ),
3729 - __METHOD__ );
 3904+ # Get templates from templatelinks
 3905+ $id = $this->mTitle->getArticleID();
37303906
3731 - global $wgContLang;
3732 - foreach( $res as $row ) {
3733 - $tlTemplates["{$row->tl_namespace}:{$row->tl_title}"] = true;
3734 - }
 3907+ $tlTemplates = array();
37353908
3736 - # Get templates from parser output.
3737 - $poTemplates = array();
3738 - foreach ( $parserOutput->getTemplates() as $ns => $templates ) {
3739 - foreach ( $templates as $dbk => $id ) {
3740 - $poTemplates["$ns:$dbk"] = true;
3741 - }
3742 - }
 3909+ $dbr = wfGetDB( DB_SLAVE );
 3910+ $res = $dbr->select( array( 'templatelinks' ),
 3911+ array( 'tl_namespace', 'tl_title' ),
 3912+ array( 'tl_from' => $id ),
 3913+ __METHOD__ );
37433914
3744 - # Get the diff
3745 - # Note that we simulate array_diff_key in PHP <5.0.x
3746 - $templates_diff = array_diff_key( $poTemplates, $tlTemplates );
 3915+ global $wgContLang;
 3916+ foreach( $res as $row ) {
 3917+ $tlTemplates["{$row->tl_namespace}:{$row->tl_title}"] = true;
 3918+ }
37473919
3748 - if( count( $templates_diff ) > 0 ) {
3749 - # Whee, link updates time.
3750 - $u = new LinksUpdate( $this->mTitle, $parserOutput, false );
3751 - $u->doUpdate();
 3920+ # Get templates from parser output.
 3921+ $poTemplates = array();
 3922+ foreach ( $parserOutput->getTemplates() as $ns => $templates ) {
 3923+ foreach ( $templates as $dbk => $id ) {
 3924+ $poTemplates["$ns:$dbk"] = true;
37523925 }
37533926 }
3754 -
3755 - return $parserOutput;
 3927+
 3928+ # Get the diff
 3929+ # Note that we simulate array_diff_key in PHP <5.0.x
 3930+ $templates_diff = array_diff_key( $poTemplates, $tlTemplates );
 3931+
 3932+ if( count( $templates_diff ) > 0 ) {
 3933+ # Whee, link updates time.
 3934+ $u = new LinksUpdate( $this->mTitle, $parserOutput, false );
 3935+ $u->doUpdate();
 3936+ }
37563937 }
37573938
37583939 /**
@@ -3811,16 +3992,6 @@
38123993 }
38133994 }
38143995
3815 - function tryParserCache( $parserOptions ) {
3816 - $parserCache = ParserCache::singleton();
3817 - $parserOutput = $parserCache->get( $this, $parserOptions );
3818 - if ( $parserOutput !== false ) {
3819 - return $parserOutput;
3820 - } else {
3821 - return false;
3822 - }
3823 - }
3824 -
38253996 /** Lightweight method to get the parser output for a page, checking the parser cache
38263997 * and so on. Doesn't consider most of the stuff that Article::view is forced to
38273998 * consider, so it's not appropriate to use there. */
@@ -3828,26 +3999,26 @@
38294000 global $wgEnableParserCache, $wgUser, $wgOut;
38304001
38314002 // Should the parser cache be used?
3832 - $pcache = $wgEnableParserCache &&
 4003+ $useParserCache = $wgEnableParserCache &&
38334004 intval( $wgUser->getOption( 'stubthreshold' ) ) == 0 &&
38344005 $this->exists() &&
38354006 $oldid === null;
38364007
3837 - wfDebug( __METHOD__.': using parser cache: ' . ( $pcache ? 'yes' : 'no' ) . "\n" );
 4008+ wfDebug( __METHOD__.': using parser cache: ' . ( $useParserCache ? 'yes' : 'no' ) . "\n" );
38384009 if ( $wgUser->getOption( 'stubthreshold' ) ) {
38394010 wfIncrStats( 'pcache_miss_stub' );
38404011 }
38414012
38424013 $parserOutput = false;
3843 - if ( $pcache ) {
3844 - $parserOutput = $this->tryParserCache( $wgOut->parserOptions() );
 4014+ if ( $useParserCache ) {
 4015+ $parserOutput = ParserCache::singleton()->get( $this, $this->getParserOptions() );
38454016 }
38464017
38474018 if ( $parserOutput === false ) {
38484019 // Cache miss; parse and output it.
38494020 $rev = Revision::newFromTitle( $this->getTitle(), $oldid );
38504021
3851 - return $this->outputFromWikitext( $rev->getText(), $pcache );
 4022+ return $this->getOutputFromWikitext( $rev->getText(), $useParserCache );
38524023 } else {
38534024 return $parserOutput;
38544025 }
Index: trunk/phase3/includes/parser/ParserCache.php
@@ -7,7 +7,7 @@
88 /**
99 * Get an instance of this object
1010 */
11 - public static function &singleton() {
 11+ public static function singleton() {
1212 static $instance;
1313 if ( !isset( $instance ) ) {
1414 global $parserMemc;
@@ -22,11 +22,11 @@
2323 *
2424 * @param object $memCached
2525 */
26 - function __construct( &$memCached ) {
27 - $this->mMemc =& $memCached;
 26+ function __construct( $memCached ) {
 27+ $this->mMemc = $memCached;
2828 }
2929
30 - function getKey( &$article, $popts ) {
 30+ function getKey( $article, $popts ) {
3131 global $wgRequest;
3232
3333 if( $popts instanceof User ) // It used to be getKey( &$article, &$user )
@@ -47,52 +47,55 @@
4848 return $key;
4949 }
5050
51 - function getETag( &$article, $popts ) {
 51+ function getETag( $article, $popts ) {
5252 return 'W/"' . $this->getKey($article, $popts) . "--" . $article->mTouched. '"';
5353 }
5454
55 - function get( &$article, $popts ) {
 55+ function getDirty( $article, $popts ) {
 56+ $key = $this->getKey( $article, $popts );
 57+ wfDebug( "Trying parser cache $key\n" );
 58+ $value = $this->mMemc->get( $key );
 59+ return is_object( $value ) ? $value : false;
 60+ }
 61+
 62+ function get( $article, $popts ) {
5663 global $wgCacheEpoch;
57 - $fname = 'ParserCache::get';
58 - wfProfileIn( $fname );
 64+ wfProfileIn( __METHOD__ );
5965
60 - $key = $this->getKey( $article, $popts );
 66+ $value = $this->getDirty( $article, $popts );
 67+ if ( !$value ) {
 68+ wfDebug( "Parser cache miss.\n" );
 69+ wfIncrStats( "pcache_miss_absent" );
 70+ wfProfileOut( __METHOD__ );
 71+ return false;
 72+ }
6173
62 - wfDebug( "Trying parser cache $key\n" );
63 - $value = $this->mMemc->get( $key );
64 - if ( is_object( $value ) ) {
65 - wfDebug( "Found.\n" );
66 - # Delete if article has changed since the cache was made
67 - $canCache = $article->checkTouched();
68 - $cacheTime = $value->getCacheTime();
69 - $touched = $article->mTouched;
70 - if ( !$canCache || $value->expired( $touched ) ) {
71 - if ( !$canCache ) {
72 - wfIncrStats( "pcache_miss_invalid" );
73 - wfDebug( "Invalid cached redirect, touched $touched, epoch $wgCacheEpoch, cached $cacheTime\n" );
74 - } else {
75 - wfIncrStats( "pcache_miss_expired" );
76 - wfDebug( "Key expired, touched $touched, epoch $wgCacheEpoch, cached $cacheTime\n" );
77 - }
78 - $this->mMemc->delete( $key );
79 - $value = false;
 74+ wfDebug( "Found.\n" );
 75+ # Invalid if article has changed since the cache was made
 76+ $canCache = $article->checkTouched();
 77+ $cacheTime = $value->getCacheTime();
 78+ $touched = $article->mTouched;
 79+ if ( !$canCache || $value->expired( $touched ) ) {
 80+ if ( !$canCache ) {
 81+ wfIncrStats( "pcache_miss_invalid" );
 82+ wfDebug( "Invalid cached redirect, touched $touched, epoch $wgCacheEpoch, cached $cacheTime\n" );
8083 } else {
81 - if ( isset( $value->mTimestamp ) ) {
82 - $article->mTimestamp = $value->mTimestamp;
83 - }
84 - wfIncrStats( "pcache_hit" );
 84+ wfIncrStats( "pcache_miss_expired" );
 85+ wfDebug( "Key expired, touched $touched, epoch $wgCacheEpoch, cached $cacheTime\n" );
8586 }
 87+ $value = false;
8688 } else {
87 - wfDebug( "Parser cache miss.\n" );
88 - wfIncrStats( "pcache_miss_absent" );
89 - $value = false;
 89+ if ( isset( $value->mTimestamp ) ) {
 90+ $article->mTimestamp = $value->mTimestamp;
 91+ }
 92+ wfIncrStats( "pcache_hit" );
9093 }
9194
92 - wfProfileOut( $fname );
 95+ wfProfileOut( __METHOD__ );
9396 return $value;
9497 }
9598
96 - function save( $parserOutput, &$article, $popts ){
 99+ function save( $parserOutput, $article, $popts ){
97100 global $wgParserCacheExpireTime;
98101 $key = $this->getKey( $article, $popts );
99102
Index: trunk/phase3/includes/OutputPage.php
@@ -715,12 +715,13 @@
716716 * @param Article $article
717717 * @param User $user
718718 *
719 - * Now a wrapper around Article::tryParserCache()
 719+ * @deprecated
720720 *
721721 * @return bool True if successful, else false.
722722 */
723723 public function tryParserCache( &$article ) {
724 - $parserOutput = $article->tryParserCache( $this->parserOptions() );
 724+ wfDeprecated( __METHOD__ );
 725+ $parserOutput = ParserCache::singleton()->get( $article, $article->getParserOptions() );
725726
726727 if ($parserOutput !== false) {
727728 $this->addParserOutput( $parserOutput );
Index: trunk/phase3/includes/AutoLoader.php
@@ -158,6 +158,8 @@
159159 'Pager' => 'includes/Pager.php',
160160 'PasswordError' => 'includes/User.php',
161161 'PatrolLog' => 'includes/PatrolLog.php',
 162+ 'PoolCounter' => 'includes/PoolCounter.php',
 163+ 'PoolCounter_Stub' => 'includes/PoolCounter.php',
162164 'PostgresSearchResult' => 'includes/SearchPostgres.php',
163165 'PostgresSearchResultSet' => 'includes/SearchPostgres.php',
164166 'Preferences' => 'includes/Preferences.php',
Index: trunk/phase3/includes/PoolCounter.php
@@ -0,0 +1,64 @@
 2+<?php
 3+
 4+abstract class PoolCounter {
 5+ public function factory( $type, $key ) {
 6+ global $wgPoolCounterConf;
 7+ if ( !isset( $wgPoolCounterConf[$type] ) ) {
 8+ return new PoolCounter_Stub;
 9+ }
 10+ $conf = $wgPoolCounterConf[$type];
 11+ $class = $conf['class'];
 12+ return new $class( $conf, $type, $key );
 13+ }
 14+
 15+ abstract public function acquire();
 16+ abstract public function release();
 17+ abstract public function wait();
 18+
 19+ public function executeProtected( $mainCallback, $dirtyCallback = false ) {
 20+ $status = $this->acquire();
 21+ if ( !$status->isOK() ) {
 22+ return $status;
 23+ }
 24+ if ( !empty( $status->value['overload'] ) ) {
 25+ # Overloaded. Try a dirty cache entry.
 26+ if ( $dirtyCallback ) {
 27+ if ( call_user_func( $dirtyCallback ) ) {
 28+ $this->release();
 29+ return Status::newGood();
 30+ }
 31+ }
 32+
 33+ # Wait for a thread
 34+ $status = $this->wait();
 35+ if ( !$status->isOK() ) {
 36+ $this->release();
 37+ return $status;
 38+ }
 39+ }
 40+ # Call the main callback
 41+ call_user_func( $mainCallback );
 42+ return $this->release();
 43+ }
 44+}
 45+
 46+class PoolCounter_Stub extends PoolCounter {
 47+ public function acquire() {
 48+ return Status::newGood();
 49+ }
 50+
 51+ public function release() {
 52+ return Status::newGood();
 53+ }
 54+
 55+ public function wait() {
 56+ return Status::newGood();
 57+ }
 58+
 59+ public function executeProtected( $mainCallback, $dirtyCallback = false ) {
 60+ call_user_func( $mainCallback );
 61+ return Status::newGood();
 62+ }
 63+}
 64+
 65+
Property changes on: trunk/phase3/includes/PoolCounter.php
___________________________________________________________________
Added: svn:eol-style
166 + native
Index: trunk/phase3/includes/DefaultSettings.php
@@ -3884,3 +3884,20 @@
38853885 * modify the user rights of those users via Special:UserRights
38863886 */
38873887 $wgUserrightsInterwikiDelimiter = '@';
 3888+
 3889+/**
 3890+ * Configuration for processing pool control, for use in high-traffic wikis.
 3891+ * An implementation is provided in the PoolCounter extension.
 3892+ *
 3893+ * This configuration array maps pool types to an associative array. The only
 3894+ * defined key in the associative array is "class", which gives the class name.
 3895+ * The remaining elements are passed through to the class as constructor
 3896+ * parameters. Example:
 3897+ *
 3898+ * $wgPoolCounterConf = array( 'Article::view' => array(
 3899+ * 'class' => 'PoolCounter_Client',
 3900+ * ... any extension-specific options...
 3901+ * );
 3902+ */
 3903+$wgPoolCounterConf = null;
 3904+
Index: trunk/phase3/languages/messages/MessagesEn.php
@@ -846,7 +846,12 @@
847847 'jumpto' => 'Jump to:',
848848 'jumptonavigation' => 'navigation',
849849 'jumptosearch' => 'search',
 850+'view-pool-error' => 'Sorry, our servers are overloaded at the moment.
 851+Too many people are trying to view this article.
 852+Please try again in a minute or two.
850853
 854+$1',
 855+
851856 # All link text and link target definitions of links into project namespace that get used by other message strings, with the exception of user group pages (see grouppage) and the disambiguation template definition (see disambiguations).
852857 'aboutsite' => 'About {{SITENAME}}',
853858 'aboutpage' => 'Project:About',
Index: trunk/phase3/RELEASE-NOTES
@@ -110,6 +110,9 @@
111111 set $wgCacheDirectory to get a faster CDB-based implementation.
112112 * Expanded the number of variables which can be set in the extension messages
113113 files.
 114+* Added a feature to allow per-article process pool size control for the parsing
 115+ task, to limit resource usage when the cache for a heavily-viewed article is
 116+ invalidated. Requires an external daemon.
114117
115118 === Bug fixes in 1.16 ===
116119

Follow-up revisions

RevisionCommit summaryAuthorDate
r52890Follow-up to r52888:...siebrand08:21, 8 July 2009
r70751Per CR r52888, have Article::getParserOptions() clone the object itself, so t...tstarling07:41, 9 August 2010
r79752More ancient deprecated functions:...happy-melon20:40, 6 January 2011
r83424Method signatures for ParserCache changed in r52888; updated to keep...juliano23:45, 6 March 2011

Comments

#Comment by OverlordQ (talk | contribs)   02:43, 15 July 2009

showPatrolFooter tries to call the user skin without getting the value first: bug #19729

#Comment by Platonides (talk | contribs)   13:55, 5 August 2010

Why does the getParserOptions() need to be cloned?

#Comment by Tim Starling (talk | contribs)   15:16, 5 August 2010

Because the object needs to be modified in the following lines, without modifying the cached object, which is used for other things.

#Comment by Platonides (talk | contribs)   15:29, 5 August 2010

Which other things? Doesn't seem that it would affect other uses.

And in fact tryDirtyCache() is modifying the underlying object without cloning.

#Comment by Tim Starling (talk | contribs)   00:59, 6 August 2010

All the other callers. Article::view() would even interfere with itself. It should be pretty obvious that if you call this twice:

		$parserOptions = clone $this->getParserOptions();
		# Render printable version, use printable version cache
		if ( $wgOut->isPrintable() ) {
			$parserOptions->setIsPrintable( true );
		}

..and $wgOut->isPrintable() is true the first time and false the second time, then it won't work without the clone. Are you saying that you think $wgOut->isPrintable() will always be the same? Because I'm not aware of any documented guarantee that that will be the case.

I'll add a clone to tryDirtyCache() shortly, thanks for spotting that.

#Comment by Platonides (talk | contribs)   22:32, 6 August 2010

Now you express it, yes. That seems to be the underlying assumption. That there is only one (fixed) $wgOut per request.

The whole Article.php seems to be based on the unwritten assumption that it will only be run once with the same $wgOut. I don't think we could break that without large refactoring and API-breaking, though we can try on these tiny bits.


Is somewhere documented what is the PoolCounter trying to solve? There's a piece on core, the PoolCounter extension and a mention of a (unversioned?) python daemon. But I fail to see what is it exactly doing (limiting the number of concurrent parsings?) and what would break if it wasn't done that way.

#Comment by Tim Starling (talk | contribs)   03:18, 7 August 2010

There's no such assumption. But in any case, if it's unwritten, then it's unreliable. I'm not going to write fragile and bug-prone code just to save the trouble of typing the word "clone".

A pool counter is urgently needed so that site performance can be preserved when large numbers of processes attempt to parse a given article simultaneously. This was the cause of the downtime we experienced after the death of Michael Jackson. The problem remains unfixed and will probably recur when the next big news event comes along. The pool counter concept can also be extended to fix many other ops problems leading to accidental or deliberate DoS. For example, we could limit the number of threads that can run a given special page at any one time.

Domas said he would write the daemon part, but he didn't finish it. So the project is now open for someone else to take up.

Status & tagging log