Index: trunk/phase3/includes/Article.php |
— | — | @@ -40,6 +40,7 @@ |
41 | 41 | var $mTouched = '19700101000000'; //!< |
42 | 42 | var $mUser = -1; //!< Not loaded |
43 | 43 | var $mUserText = ''; //!< |
| 44 | + var $mParserOptions; //!< |
44 | 45 | /**@}}*/ |
45 | 46 | |
46 | 47 | /** |
— | — | @@ -718,38 +719,40 @@ |
719 | 720 | } |
720 | 721 | |
721 | 722 | /** |
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. |
724 | 725 | */ |
725 | 726 | public function view() { |
726 | 727 | global $wgUser, $wgOut, $wgRequest, $wgContLang; |
727 | 728 | global $wgEnableParserCache, $wgStylePath, $wgParser; |
728 | | - global $wgUseTrackbacks, $wgNamespaceRobotPolicies, $wgArticleRobotPolicies; |
729 | | - global $wgDefaultRobotPolicy; |
| 729 | + global $wgUseTrackbacks; |
730 | 730 | |
731 | | - # Let the parser know if this is the printable version |
732 | | - if( $wgOut->isPrintable() ) { |
733 | | - $wgOut->parserOptions()->setIsPrintable( true ); |
734 | | - } |
735 | | - |
736 | 731 | wfProfileIn( __METHOD__ ); |
737 | 732 | |
738 | 733 | # Get variables from query string |
739 | 734 | $oldid = $this->getOldID(); |
| 735 | + $parserCache = ParserCache::singleton(); |
740 | 736 | |
| 737 | + $parserOptions = clone $this->getParserOptions(); |
| 738 | + # Render printable version, use printable version cache |
| 739 | + if ( $wgOut->isPrintable() ) { |
| 740 | + $parserOptions->setIsPrintable( true ); |
| 741 | + } |
| 742 | + |
741 | 743 | # Try client and file cache |
742 | 744 | if( $oldid === 0 && $this->checkTouched() ) { |
743 | 745 | global $wgUseETag; |
744 | 746 | if( $wgUseETag ) { |
745 | | - $parserCache = ParserCache::singleton(); |
746 | | - $wgOut->setETag( $parserCache->getETag($this, $wgOut->parserOptions()) ); |
| 747 | + $wgOut->setETag( $parserCache->getETag( $this, $parserOptions ) ); |
747 | 748 | } |
748 | 749 | # Is is client cached? |
749 | 750 | if( $wgOut->checkLastModified( $this->getTouched() ) ) { |
| 751 | + wfDebug( __METHOD__.": done 304\n" ); |
750 | 752 | wfProfileOut( __METHOD__ ); |
751 | 753 | return; |
752 | 754 | # Try file cache |
753 | 755 | } else if( $this->tryFileCache() ) { |
| 756 | + wfDebug( __METHOD__.": done file cache\n" ); |
754 | 757 | # tell wgOut that output is taken care of |
755 | 758 | $wgOut->disable(); |
756 | 759 | $this->viewUpdates(); |
— | — | @@ -758,80 +761,251 @@ |
759 | 762 | } |
760 | 763 | } |
761 | 764 | |
762 | | - $ns = $this->mTitle->getNamespace(); # shortcut |
763 | 765 | $sk = $wgUser->getSkin(); |
764 | 766 | |
765 | 767 | # getOldID may want us to redirect somewhere else |
766 | 768 | if( $this->mRedirectUrl ) { |
767 | 769 | $wgOut->redirect( $this->mRedirectUrl ); |
| 770 | + wfDebug( __METHOD__.": redirecting due to oldid\n" ); |
768 | 771 | wfProfileOut( __METHOD__ ); |
769 | 772 | return; |
770 | 773 | } |
771 | 774 | |
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 | | - |
779 | 775 | $wgOut->setArticleFlag( true ); |
| 776 | + $wgOut->setRobotPolicy( $this->getRobotPolicyForView() ); |
| 777 | + # Set page title (may be overridden by DISPLAYTITLE) |
| 778 | + $wgOut->setPageTitle( $this->mTitle->getPrefixedText() ); |
780 | 779 | |
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; |
791 | 786 | } |
792 | | - $wgOut->setRobotPolicy( $policy ); |
793 | 787 | |
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 | + } |
801 | 794 | |
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 | + } |
807 | 803 | |
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; |
813 | 820 | } |
| 821 | + } |
| 822 | + |
| 823 | + if ( $outputDone ) { |
| 824 | + $this->showViewFooter(); |
| 825 | + $this->viewUpdates(); |
814 | 826 | wfProfileOut( __METHOD__ ); |
815 | 827 | return; |
816 | 828 | } |
817 | 829 | |
| 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(); |
818 | 972 | 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) |
820 | 974 | if( !$this->mTitle->isSubpage() ) { |
821 | 975 | $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'; |
824 | 978 | } |
825 | 979 | } |
826 | 980 | } |
827 | 981 | |
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; |
833 | 997 | } |
| 998 | + } |
834 | 999 | |
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(); |
836 | 1010 | if( isset( $this->mRedirectedFrom ) ) { |
837 | 1011 | // This is an internally redirected page view. |
838 | 1012 | // We'll need a backlink to the source page for navigation. |
— | — | @@ -856,226 +1030,156 @@ |
857 | 1031 | $wgOut->addLink( array( 'rel' => 'canonical', |
858 | 1032 | 'href' => $this->mTitle->getLocalURL() ) |
859 | 1033 | ); |
860 | | - $wasRedirected = true; |
| 1034 | + return true; |
861 | 1035 | } |
862 | | - } elseif( !empty( $rdfrom ) ) { |
| 1036 | + } elseif( $rdfrom ) { |
863 | 1037 | // This is an externally redirected view, from some other wiki. |
864 | 1038 | // If it was reported from a trusted site, supply a backlink. |
865 | | - global $wgRedirectSources; |
866 | 1039 | if( $wgRedirectSources && preg_match( $wgRedirectSources, $rdfrom ) ) { |
867 | 1040 | $redir = $sk->makeExternalLink( $rdfrom, $rdfrom ); |
868 | 1041 | $s = wfMsgExt( 'redirectedfrom', array( 'parseinline', 'replaceafter' ), $redir ); |
869 | 1042 | $wgOut->setSubtitle( $s ); |
870 | | - $wasRedirected = true; |
| 1043 | + return true; |
871 | 1044 | } |
872 | 1045 | } |
| 1046 | + return false; |
| 1047 | + } |
873 | 1048 | |
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; |
875 | 1055 | if( $this->mTitle->isTalkPage() ) { |
876 | 1056 | $msg = wfMsgNoTrans( 'talkpageheader' ); |
877 | 1057 | if ( $msg !== '-' && !wfEmptyMsg( 'talkpageheader', $msg ) ) { |
878 | 1058 | $wgOut->wrapWikiMsg( "<div class=\"mw-talkpageheader\">\n$1</div>", array( 'talkpageheader' ) ); |
879 | 1059 | } |
880 | 1060 | } |
| 1061 | + } |
881 | 1062 | |
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'); |
889 | 1071 | } |
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 | | - } |
911 | 1072 | |
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(); |
922 | 1076 | |
923 | | - if( $return404 ) { |
924 | | - $wgRequest->response()->header( "HTTP/1.x 404 Not Found" ); |
925 | | - } |
| 1077 | + # Trackbacks |
| 1078 | + if( $wgUseTrackbacks ) { |
| 1079 | + $this->addTrackbacks(); |
| 1080 | + } |
| 1081 | + } |
926 | 1082 | |
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' ); |
935 | 1091 | |
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 | + } |
939 | 1095 | |
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 | + } |
979 | 1114 | |
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(); |
983 | 1123 | |
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' ); |
1028 | 1135 | } |
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" ); |
1041 | 1141 | } |
| 1142 | + $wgOut->addWikiText( $text ); |
| 1143 | + } |
1042 | 1144 | |
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; |
1047 | 1152 | |
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; |
1068 | 1156 | } |
1069 | 1157 | |
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; |
1073 | 1176 | } |
1074 | | - |
1075 | | - $this->viewUpdates(); |
1076 | | - wfProfileOut( __METHOD__ ); |
1077 | 1177 | } |
1078 | 1178 | |
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() { |
1080 | 1184 | global $wgUser, $wgOut; |
1081 | 1185 | $loglist = new LogEventsList( $wgUser->getSkin(), $wgOut ); |
1082 | 1186 | $pager = new LogPager( $loglist, array('move', 'delete'), false, |
— | — | @@ -1104,7 +1208,7 @@ |
1105 | 1209 | /* |
1106 | 1210 | * Should the parser cache be used? |
1107 | 1211 | */ |
1108 | | - protected function useParserCache( $oldid ) { |
| 1212 | + public function useParserCache( $oldid ) { |
1109 | 1213 | global $wgUser, $wgEnableParserCache; |
1110 | 1214 | |
1111 | 1215 | return $wgEnableParserCache |
— | — | @@ -1116,6 +1220,64 @@ |
1117 | 1221 | } |
1118 | 1222 | |
1119 | 1223 | /** |
| 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 | + /** |
1120 | 1282 | * View redirect |
1121 | 1283 | * @param $target Title object or Array of destination(s) to redirect |
1122 | 1284 | * @param $appendSubtitle Boolean [optional] |
— | — | @@ -1511,7 +1673,6 @@ |
1512 | 1674 | * @deprecated use Article::doEdit() |
1513 | 1675 | */ |
1514 | 1676 | function updateArticle( $text, $summary, $minor, $watchthis, $forceBot = false, $sectionanchor = '' ) { |
1515 | | - wfDeprecated( __METHOD__ ); |
1516 | 1677 | $flags = EDIT_UPDATE | EDIT_DEFER_UPDATES | EDIT_AUTOSUMMARY | |
1517 | 1678 | ( $minor ? EDIT_MINOR : 0 ) | |
1518 | 1679 | ( $forceBot ? EDIT_FORCE_BOT : 0 ); |
— | — | @@ -2940,9 +3101,7 @@ |
2941 | 3102 | $edit->revid = $revid; |
2942 | 3103 | $edit->newText = $text; |
2943 | 3104 | $edit->pst = $this->preSaveTransform( $text ); |
2944 | | - $options = new ParserOptions; |
2945 | | - $options->setTidy( true ); |
2946 | | - $options->enableLimitReport(); |
| 3105 | + $options = $this->getParserOptions(); |
2947 | 3106 | $edit->output = $wgParser->parse( $edit->pst, $this->mTitle, $options, true, true, $revid ); |
2948 | 3107 | $edit->oldText = $this->getContent(); |
2949 | 3108 | $this->mPreparedEdit = $edit; |
— | — | @@ -2980,9 +3139,7 @@ |
2981 | 3140 | |
2982 | 3141 | # Save it to the parser cache |
2983 | 3142 | if( $wgEnableParserCache ) { |
2984 | | - $popts = new ParserOptions; |
2985 | | - $popts->setTidy( true ); |
2986 | | - $popts->enableLimitReport(); |
| 3143 | + $popts = $this->getParserOptions(); |
2987 | 3144 | $parserCache = ParserCache::singleton(); |
2988 | 3145 | $parserCache->save( $editInfo->output, $this, $popts ); |
2989 | 3146 | } |
— | — | @@ -3674,11 +3831,10 @@ |
3675 | 3832 | * @param $text String |
3676 | 3833 | * @param $cache Boolean |
3677 | 3834 | */ |
3678 | | - public function outputWikiText( $text, $cache = true ) { |
| 3835 | + public function outputWikiText( $text, $cache = true, $parserOptions = false ) { |
3679 | 3836 | global $wgOut; |
3680 | 3837 | |
3681 | | - $parserOutput = $this->outputFromWikitext( $text, $cache ); |
3682 | | - |
| 3838 | + $parserOutput = $this->getOutputFromWikitext( $text, $cache, $parserOptions ); |
3683 | 3839 | $wgOut->addParserOutput( $parserOutput ); |
3684 | 3840 | } |
3685 | 3841 | |
— | — | @@ -3687,19 +3843,27 @@ |
3688 | 3844 | * output instead of sending it straight to $wgOut. Makes things nice and simple for, |
3689 | 3845 | * say, embedding thread pages within a discussion system (LiquidThreads) |
3690 | 3846 | */ |
3691 | | - public function outputFromWikitext( $text, $cache = true ) { |
| 3847 | + public function getOutputFromWikitext( $text, $cache = true, $parserOptions = false ) { |
3692 | 3848 | global $wgParser, $wgOut, $wgEnableParserCache, $wgUseFileCache; |
3693 | 3849 | |
3694 | | - $popts = $wgOut->parserOptions(); |
3695 | | - $popts->setTidy(true); |
3696 | | - $popts->enableLimitReport(); |
| 3850 | + if ( !$parserOptions ) { |
| 3851 | + $parserOptions = $this->getParserOptions(); |
| 3852 | + } |
| 3853 | + |
| 3854 | + $time = -wfTime(); |
3697 | 3855 | $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 | + |
3701 | 3865 | if( $wgEnableParserCache && $cache && $this && $parserOutput->getCacheTime() != -1 ) { |
3702 | 3866 | $parserCache = ParserCache::singleton(); |
3703 | | - $parserCache->save( $parserOutput, $this, $popts ); |
| 3867 | + $parserCache->save( $parserOutput, $this, $parserOptions ); |
3704 | 3868 | } |
3705 | 3869 | // Make sure file cache is not used on uncacheable content. |
3706 | 3870 | // Output that has magic words in it can still use the parser cache |
— | — | @@ -3707,51 +3871,68 @@ |
3708 | 3872 | if( $parserOutput->getCacheTime() == -1 || $parserOutput->containsOldMagic() ) { |
3709 | 3873 | $wgUseFileCache = false; |
3710 | 3874 | } |
| 3875 | + $this->doCascadeProtectionUpdates( $parserOutput ); |
| 3876 | + return $parserOutput; |
| 3877 | + } |
3711 | 3878 | |
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 | + } |
3719 | 3891 | |
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 | + } |
3722 | 3896 | |
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. |
3724 | 3903 | |
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(); |
3730 | 3906 | |
3731 | | - global $wgContLang; |
3732 | | - foreach( $res as $row ) { |
3733 | | - $tlTemplates["{$row->tl_namespace}:{$row->tl_title}"] = true; |
3734 | | - } |
| 3907 | + $tlTemplates = array(); |
3735 | 3908 | |
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__ ); |
3743 | 3914 | |
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 | + } |
3747 | 3919 | |
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; |
3752 | 3925 | } |
3753 | 3926 | } |
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 | + } |
3756 | 3937 | } |
3757 | 3938 | |
3758 | 3939 | /** |
— | — | @@ -3811,16 +3992,6 @@ |
3812 | 3993 | } |
3813 | 3994 | } |
3814 | 3995 | |
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 | | - |
3825 | 3996 | /** Lightweight method to get the parser output for a page, checking the parser cache |
3826 | 3997 | * and so on. Doesn't consider most of the stuff that Article::view is forced to |
3827 | 3998 | * consider, so it's not appropriate to use there. */ |
— | — | @@ -3828,26 +3999,26 @@ |
3829 | 4000 | global $wgEnableParserCache, $wgUser, $wgOut; |
3830 | 4001 | |
3831 | 4002 | // Should the parser cache be used? |
3832 | | - $pcache = $wgEnableParserCache && |
| 4003 | + $useParserCache = $wgEnableParserCache && |
3833 | 4004 | intval( $wgUser->getOption( 'stubthreshold' ) ) == 0 && |
3834 | 4005 | $this->exists() && |
3835 | 4006 | $oldid === null; |
3836 | 4007 | |
3837 | | - wfDebug( __METHOD__.': using parser cache: ' . ( $pcache ? 'yes' : 'no' ) . "\n" ); |
| 4008 | + wfDebug( __METHOD__.': using parser cache: ' . ( $useParserCache ? 'yes' : 'no' ) . "\n" ); |
3838 | 4009 | if ( $wgUser->getOption( 'stubthreshold' ) ) { |
3839 | 4010 | wfIncrStats( 'pcache_miss_stub' ); |
3840 | 4011 | } |
3841 | 4012 | |
3842 | 4013 | $parserOutput = false; |
3843 | | - if ( $pcache ) { |
3844 | | - $parserOutput = $this->tryParserCache( $wgOut->parserOptions() ); |
| 4014 | + if ( $useParserCache ) { |
| 4015 | + $parserOutput = ParserCache::singleton()->get( $this, $this->getParserOptions() ); |
3845 | 4016 | } |
3846 | 4017 | |
3847 | 4018 | if ( $parserOutput === false ) { |
3848 | 4019 | // Cache miss; parse and output it. |
3849 | 4020 | $rev = Revision::newFromTitle( $this->getTitle(), $oldid ); |
3850 | 4021 | |
3851 | | - return $this->outputFromWikitext( $rev->getText(), $pcache ); |
| 4022 | + return $this->getOutputFromWikitext( $rev->getText(), $useParserCache ); |
3852 | 4023 | } else { |
3853 | 4024 | return $parserOutput; |
3854 | 4025 | } |
Index: trunk/phase3/includes/parser/ParserCache.php |
— | — | @@ -7,7 +7,7 @@ |
8 | 8 | /** |
9 | 9 | * Get an instance of this object |
10 | 10 | */ |
11 | | - public static function &singleton() { |
| 11 | + public static function singleton() { |
12 | 12 | static $instance; |
13 | 13 | if ( !isset( $instance ) ) { |
14 | 14 | global $parserMemc; |
— | — | @@ -22,11 +22,11 @@ |
23 | 23 | * |
24 | 24 | * @param object $memCached |
25 | 25 | */ |
26 | | - function __construct( &$memCached ) { |
27 | | - $this->mMemc =& $memCached; |
| 26 | + function __construct( $memCached ) { |
| 27 | + $this->mMemc = $memCached; |
28 | 28 | } |
29 | 29 | |
30 | | - function getKey( &$article, $popts ) { |
| 30 | + function getKey( $article, $popts ) { |
31 | 31 | global $wgRequest; |
32 | 32 | |
33 | 33 | if( $popts instanceof User ) // It used to be getKey( &$article, &$user ) |
— | — | @@ -47,52 +47,55 @@ |
48 | 48 | return $key; |
49 | 49 | } |
50 | 50 | |
51 | | - function getETag( &$article, $popts ) { |
| 51 | + function getETag( $article, $popts ) { |
52 | 52 | return 'W/"' . $this->getKey($article, $popts) . "--" . $article->mTouched. '"'; |
53 | 53 | } |
54 | 54 | |
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 ) { |
56 | 63 | global $wgCacheEpoch; |
57 | | - $fname = 'ParserCache::get'; |
58 | | - wfProfileIn( $fname ); |
| 64 | + wfProfileIn( __METHOD__ ); |
59 | 65 | |
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 | + } |
61 | 73 | |
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" ); |
80 | 83 | } 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" ); |
85 | 86 | } |
| 87 | + $value = false; |
86 | 88 | } 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" ); |
90 | 93 | } |
91 | 94 | |
92 | | - wfProfileOut( $fname ); |
| 95 | + wfProfileOut( __METHOD__ ); |
93 | 96 | return $value; |
94 | 97 | } |
95 | 98 | |
96 | | - function save( $parserOutput, &$article, $popts ){ |
| 99 | + function save( $parserOutput, $article, $popts ){ |
97 | 100 | global $wgParserCacheExpireTime; |
98 | 101 | $key = $this->getKey( $article, $popts ); |
99 | 102 | |
Index: trunk/phase3/includes/OutputPage.php |
— | — | @@ -715,12 +715,13 @@ |
716 | 716 | * @param Article $article |
717 | 717 | * @param User $user |
718 | 718 | * |
719 | | - * Now a wrapper around Article::tryParserCache() |
| 719 | + * @deprecated |
720 | 720 | * |
721 | 721 | * @return bool True if successful, else false. |
722 | 722 | */ |
723 | 723 | public function tryParserCache( &$article ) { |
724 | | - $parserOutput = $article->tryParserCache( $this->parserOptions() ); |
| 724 | + wfDeprecated( __METHOD__ ); |
| 725 | + $parserOutput = ParserCache::singleton()->get( $article, $article->getParserOptions() ); |
725 | 726 | |
726 | 727 | if ($parserOutput !== false) { |
727 | 728 | $this->addParserOutput( $parserOutput ); |
Index: trunk/phase3/includes/AutoLoader.php |
— | — | @@ -158,6 +158,8 @@ |
159 | 159 | 'Pager' => 'includes/Pager.php', |
160 | 160 | 'PasswordError' => 'includes/User.php', |
161 | 161 | 'PatrolLog' => 'includes/PatrolLog.php', |
| 162 | + 'PoolCounter' => 'includes/PoolCounter.php', |
| 163 | + 'PoolCounter_Stub' => 'includes/PoolCounter.php', |
162 | 164 | 'PostgresSearchResult' => 'includes/SearchPostgres.php', |
163 | 165 | 'PostgresSearchResultSet' => 'includes/SearchPostgres.php', |
164 | 166 | '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 |
1 | 66 | + native |
Index: trunk/phase3/includes/DefaultSettings.php |
— | — | @@ -3884,3 +3884,20 @@ |
3885 | 3885 | * modify the user rights of those users via Special:UserRights |
3886 | 3886 | */ |
3887 | 3887 | $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 @@ |
847 | 847 | 'jumpto' => 'Jump to:', |
848 | 848 | 'jumptonavigation' => 'navigation', |
849 | 849 | '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. |
850 | 853 | |
| 854 | +$1', |
| 855 | + |
851 | 856 | # 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). |
852 | 857 | 'aboutsite' => 'About {{SITENAME}}', |
853 | 858 | 'aboutpage' => 'Project:About', |
Index: trunk/phase3/RELEASE-NOTES |
— | — | @@ -110,6 +110,9 @@ |
111 | 111 | set $wgCacheDirectory to get a faster CDB-based implementation. |
112 | 112 | * Expanded the number of variables which can be set in the extension messages |
113 | 113 | 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. |
114 | 117 | |
115 | 118 | === Bug fixes in 1.16 === |
116 | 119 | |