Rewrote r69339 etc. to clean up API cache header handling.
authorTim Starling <tstarling@users.mediawiki.org>
Fri, 23 Jul 2010 07:17:56 +0000 (07:17 +0000)
committerTim Starling <tstarling@users.mediawiki.org>
Fri, 23 Jul 2010 07:17:56 +0000 (07:17 +0000)
* Introduced a "cache mode" concept to simplify the header generation code, and to avoid odd results when conflicting cache header requests are received from submodules, or at least to formalise the handling of such cases.
* Made the cache mode private by default, so that code written in ignorance of caching tends to be safe. If different query modules are used in a single request, private caching is preferred over public caching.
* Removed the "must-revalidate" option from all CC headers, this is really specific to page views with a hacked squid in front, I don't think it's applicable here.
* Made the watchlist module private. This is really the definition of private data. There's nothing in the HTTP spec that says the URL for a CC:public request is private and can't be leaked. CC:private provides protection against unknown proxy behaviour.
* In ApiQueryAllmessages: avoid calling $wgLang->getCode() to check if it's necessary to make a new $wgLang when lang= is specified, since this is the only thing that unstubs $wgUser.
* Removed "FIXME: should this check $user instead of $wgUser?" Answer is no.

46 files changed:
includes/api/ApiBase.php
includes/api/ApiExpandTemplates.php
includes/api/ApiLogout.php
includes/api/ApiMain.php
includes/api/ApiOpenSearch.php
includes/api/ApiParse.php
includes/api/ApiPatrol.php
includes/api/ApiPurge.php
includes/api/ApiQuery.php
includes/api/ApiQueryAllCategories.php
includes/api/ApiQueryAllLinks.php
includes/api/ApiQueryAllUsers.php
includes/api/ApiQueryAllimages.php
includes/api/ApiQueryAllmessages.php
includes/api/ApiQueryAllpages.php
includes/api/ApiQueryBacklinks.php
includes/api/ApiQueryBase.php
includes/api/ApiQueryBlocks.php
includes/api/ApiQueryCategories.php
includes/api/ApiQueryCategoryInfo.php
includes/api/ApiQueryCategoryMembers.php
includes/api/ApiQueryDeletedrevs.php
includes/api/ApiQueryDuplicateFiles.php
includes/api/ApiQueryExtLinksUsage.php
includes/api/ApiQueryExternalLinks.php
includes/api/ApiQueryFilearchive.php
includes/api/ApiQueryIWBacklinks.php
includes/api/ApiQueryIWLinks.php
includes/api/ApiQueryImageInfo.php
includes/api/ApiQueryImages.php
includes/api/ApiQueryInfo.php
includes/api/ApiQueryLangLinks.php
includes/api/ApiQueryLinks.php
includes/api/ApiQueryLogEvents.php
includes/api/ApiQueryProtectedTitles.php
includes/api/ApiQueryRandom.php
includes/api/ApiQueryRecentChanges.php
includes/api/ApiQueryRevisions.php
includes/api/ApiQuerySearch.php
includes/api/ApiQuerySiteinfo.php
includes/api/ApiQueryTags.php
includes/api/ApiQueryUserContributions.php
includes/api/ApiQueryUserInfo.php
includes/api/ApiQueryUsers.php
includes/api/ApiQueryWatchlist.php
includes/api/ApiWatch.php

index 12d5de1..3ce630a 100644 (file)
@@ -1105,8 +1105,6 @@ abstract class ApiBase {
                                $this->dieUsage( 'Incorrect watchlist token provided -- please set a correct token in Special:Preferences', 'bad_wltoken' );
                        }
                } else {
-                       // User not determined by URL, so don't cache
-                       $this->getMain()->setVaryCookie();
                        if ( !$wgUser->isLoggedIn() ) {
                                $this->dieUsage( 'You must be logged-in to have a watchlist', 'notloggedin' );
                        }
index 033e77f..1c20227 100644 (file)
@@ -42,6 +42,9 @@ class ApiExpandTemplates extends ApiBase {
        }
 
        public function execute() {
+               // Cache may vary on $wgUser because ParserOptions gets data from it
+               $this->getMain()->setCacheMode( 'anon-public-user-private' );
+
                // Get parameters
                $params = $this->extractRequestParams();
 
index 55adc3d..7307c0e 100644 (file)
@@ -42,7 +42,6 @@ class ApiLogout extends ApiBase {
 
        public function execute() {
                global $wgUser;
-               $this->getMain()->setCachePrivate();
                $oldName = $wgUser->getName();
                $wgUser->logout();
 
index 99a01b7..5a97fa9 100644 (file)
@@ -126,7 +126,8 @@ class ApiMain extends ApiBase {
        private $mResult, $mAction, $mShowVersions, $mEnableWrite, $mRequest;
        private $mInternalMode, $mSquidMaxage, $mModule, $mVaryCookie;
 
-       private $mCacheControl = array( 'must-revalidate' => true );
+       private $mCacheMode = 'private';
+       private $mCacheControl = array();
 
        /**
         * Constructs an instance of ApiMain that utilizes the module and format specified by $request.
@@ -216,19 +217,67 @@ class ApiMain extends ApiBase {
                        's-maxage' => $maxage
                ) );
        }
+
+       /**
+        * Set the type of caching headers which will be sent.
+        *
+        * @param $mode One of:
+        *    - 'public':     Cache this object in public caches, if the maxage or smaxage 
+        *         parameter is set, or if setCacheMaxAge() was called. If a maximum age is
+        *         not provided by any of these means, the object will be private.
+        *    - 'private':    Cache this object only in private client-side caches.
+        *    - 'anon-public-user-private': Make this object cacheable for logged-out
+        *         users, but private for logged-in users. IMPORTANT: If this is set, it must be 
+        *         set consistently for a given URL, it cannot be set differently depending on 
+        *         things like the contents of the database, or whether the user is logged in.
+        *
+        *  If the wiki does not allow anonymous users to read it, the mode set here
+        *  will be ignored, and private caching headers will always be sent. In other words, 
+        *  the "public" mode is equivalent to saying that the data sent is as public as a page
+        *  view.
+        *
+        *  For user-dependent data, the private mode should generally be used. The 
+        *  anon-public-user-private mode should only be used where there is a particularly 
+        *  good performance reason for caching the anonymous response, but where the
+        *  response to logged-in users may differ, or may contain private data. 
+        *
+        *  If this function is never called, then the default will be the private mode.
+        */
+       public function setCacheMode( $mode ) {
+               if ( !in_array( $mode, array( 'private', 'public', 'anon-public-user-private' ) ) ) {
+                       wfDebug( __METHOD__.": unrecognised cache mode \"$mode\"\n" );
+                       // Ignore for forwards-compatibility
+                       return;
+               }
+
+               if ( !in_array( 'read', User::getGroupPermissions( array( '*' ) ), true ) ) {
+                       // Private wiki, only private headers
+                       if ( $mode !== 'private' ) {
+                               wfDebug( __METHOD__.": ignoring request for $mode cache mode, private wiki\n" );
+                               return;
+                       }
+               }
+
+               wfDebug( __METHOD__.": setting cache mode $mode\n" );
+               $this->mCacheMode = $mode;
+       }
        
        /**
-        * Make sure Cache-Control: private is set. Use this when the output of a request
-        * is for the current recipient only and should not be cached in any shared cache.
+        * @deprecated Private caching is now the default, so there is usually no 
+        * need to call this function. If there is a need, you can use 
+        * $this->setCacheMode('private')
         */
        public function setCachePrivate() {
-               $this->setCacheControl( array( 'private' => true ) );
+               $this->setCacheMode( 'private' );
        }
 
        /**
         * Set directives (key/value pairs) for the Cache-Control header.
         * Boolean values will be formatted as such, by including or omitting
         * without an equals sign.
+        *
+        * Cache control values set here will only be used if the cache mode is not 
+        * private, see setCacheMode().
         */
        public function setCacheControl( $directives ) {
                $this->mCacheControl = $directives + $this->mCacheControl;
@@ -241,26 +290,11 @@ class ApiMain extends ApiBase {
         * WARNING: This function must be called CONSISTENTLY for a given URL. This means that a
         * given URL must either always or never call this function; if it sometimes does and
         * sometimes doesn't, stuff will break.
+        *
+        * @deprecated Use setCacheMode( 'anon-public-user-private' )
         */
        public function setVaryCookie() {
-               $this->mVaryCookie = true;
-       }
-       
-       /**
-        * Actually output the Vary: Cookie header and its friends, if flagged with setVaryCookie().
-        * Outputs the appropriate X-Vary-Options header and Cache-Control: private if needed.
-        */
-       private function outputVaryCookieHeader() {
-               global $wgUseXVO, $wgOut;
-               if ( $this->mVaryCookie ) {
-                       header( 'Vary: Cookie' );
-                       if ( $wgUseXVO ) {
-                               header( $wgOut->getXVO() );
-                               if ( $wgOut->haveCacheVaryCookies() ) {
-                                       $this->setCacheControl( array( 'private' => true ) );
-                               }
-                       }
-               }
+               $this->setCacheMode( 'anon-public-user-private' );
        }
 
        /**
@@ -313,8 +347,7 @@ class ApiMain extends ApiBase {
                        $errCode = $this->substituteResultWithError( $e );
 
                        // Error results should not be cached
-                       $this->setCacheMaxAge( 0 );
-                       $this->setCachePrivate();
+                       $this->setCacheMode( 'private' );
 
                        $headerStr = 'MediaWiki-API-Error: ' . $errCode;
                        if ( $e->getCode() === 0 ) {
@@ -330,10 +363,47 @@ class ApiMain extends ApiBase {
                        $this->mPrinter->safeProfileOut();
                        $this->printResult( true );
                }
-               
-               // If this wiki is private, don't cache anything ever
-               if ( ! in_array( 'read', User::getGroupPermissions( array( '*' ) ), true ) ) {
-                       $this->setCachePrivate();
+
+               // Send cache headers after any code which might generate an error, to 
+               // avoid sending public cache headers for errors.
+               $this->sendCacheHeaders();
+
+               if ( $this->mPrinter->getIsHtml() && !$this->mPrinter->isDisabled() ) {
+                       echo wfReportTime();
+               }
+
+               ob_end_flush();
+       }
+
+       protected function sendCacheHeaders() {
+               if ( $this->mCacheMode == 'private' ) {
+                       header( 'Cache-Control: private' );
+                       return;
+               }
+
+               if ( $this->mCacheMode == 'anon-public-user-private' ) {
+                       global $wgUseXVO, $wgOut;
+                       header( 'Vary: Accept-Encoding, Cookie' );
+                       if ( $wgUseXVO ) {
+                               header( $wgOut->getXVO() );
+                               if ( $wgOut->haveCacheVaryCookies() ) {
+                                       // Logged in, mark this request private
+                                       header( 'Cache-Control: private' );
+                                       return;
+                               }
+                               // Logged out, send normal public headers below
+                       } elseif ( session_id() != '' ) {
+                               // Logged in or otherwise has session (e.g. anonymous users who have edited)
+                               // Mark request private
+                               header( 'Cache-Control: private' );
+                               return;
+                       } // else no XVO and anonymous, send public headers below
+               } else /* if public */ {
+                       // Give a debugging message if the user object is unstubbed on a public request
+                       global $wgUser;
+                       if ( !( $wgUser instanceof StubUser ) ) {
+                               wfDebug( __METHOD__." \$wgUser is unstubbed on a public request!\n" );
+                       }
                }
 
                // If nobody called setCacheMaxAge(), use the (s)maxage parameters
@@ -344,11 +414,20 @@ class ApiMain extends ApiBase {
                        $this->mCacheControl['max-age'] = $this->getParameter( 'maxage' );
                }
 
-               // Set the cache expiration at the last moment, as any errors may change the expiration.
-               // if $this->mSquidMaxage == 0, the expiry time is set to the first second of unix epoch
-               $exp = min( $this->mCacheControl['s-maxage'], $this->mCacheControl['max-age'] );
-               $expires = ( $exp == 0 ? 1 : time() + $exp );
-               header( 'Expires: ' . wfTimestamp( TS_RFC2822, $expires ) );
+               if ( !$this->mCacheControl['s-maxage'] && !$this->mCacheControl['max-age'] ) {
+                       // Public cache not requested
+                       // Sending a Vary header in this case is harmless, and protects us
+                       // against conditional calls of setCacheMaxAge().
+                       header( 'Cache-Control: private' );
+                       return;
+               }
+
+               $this->mCacheControl['public'] = true;
+
+               // Send an Expires header
+               $maxAge = min( $this->mCacheControl['s-maxage'], $this->mCacheControl['max-age'] );
+               $expiryUnixTime = ( $maxAge == 0 ? 1 : time() + $maxAge );
+               header( 'Expires: ' . wfTimestamp( TS_RFC2822, $expiryUnixTime ) );
 
                // Construct the Cache-Control header
                $ccHeader = '';
@@ -366,13 +445,6 @@ class ApiMain extends ApiBase {
                }
 
                header( "Cache-Control: $ccHeader" );
-               $this->outputVaryCookieHeader();
-
-               if ( $this->mPrinter->getIsHtml() && !$this->mPrinter->isDisabled() ) {
-                       echo wfReportTime();
-               }
-
-               ob_end_flush();
        }
 
        /**
index d8dc251..022ecd1 100644 (file)
@@ -56,7 +56,7 @@ class ApiOpenSearch extends ApiBase {
                        // Open search results may be stored for a very long
                        // time
                        $this->getMain()->setCacheMaxAge( $wgSearchSuggestCacheExpiry );
-                       $this->getMain()->setCacheControl( array( 'must-revalidate' => false ) );
+                       $this->getMain()->setCacheMode( 'public' );
 
                        $srchres = PrefixSearch::titleSearch( $search, $limit,
                                $namespaces );
index ae419a8..b7b2563 100644 (file)
@@ -38,6 +38,9 @@ class ApiParse extends ApiBase {
        }
 
        public function execute() {
+               // The data is hot but user-dependent, like page views, so we set vary cookies
+               $this->getMain()->setCacheMode( 'anon-public-user-private' );
+
                // Get parameters
                $params = $this->extractRequestParams();
                $text = $params['text'];
@@ -161,7 +164,6 @@ class ApiParse extends ApiBase {
 
                        if ( $params['pst'] || $params['onlypst'] ) {
                                $text = $wgParser->preSaveTransform( $text, $titleObj, $wgUser, $popts );
-                               $this->getMain()->setVaryCookie();
                        }
                        if ( $params['onlypst'] ) {
                                // Build a result and bail out
@@ -187,7 +189,6 @@ class ApiParse extends ApiBase {
 
                if ( !is_null( $params['summary'] ) ) {
                        $result_array['parsedsummary'] = array();
-                       $this->getMain()->setVaryCookie();
                        $result->setContent( $result_array['parsedsummary'], $wgUser->getSkin()->formatComment( $params['summary'], $titleObj ) );
                }
 
@@ -222,7 +223,6 @@ class ApiParse extends ApiBase {
                if ( isset( $prop['headitems'] ) || isset( $prop['headhtml'] ) ) {
                        $out = new OutputPage;
                        $out->addParserOutputNoText( $p_result );
-                       $this->getMain()->setVaryCookie();
                        $userSkin = $wgUser->getSkin();
                }
 
@@ -468,4 +468,4 @@ class ApiParse extends ApiBase {
        public function getVersion() {
                return __CLASS__ . ': $Id$';
        }
-}
\ No newline at end of file
+}
index 361fe40..2f248af 100644 (file)
@@ -41,7 +41,6 @@ class ApiPatrol extends ApiBase {
         * Patrols the article or provides the reason the patrol failed.
         */
        public function execute() {
-               $this->getMain()->setCachePrivate();
                $params = $this->extractRequestParams();
 
                if ( !isset( $params['rcid'] ) ) {
@@ -107,4 +106,4 @@ class ApiPatrol extends ApiBase {
        public function getVersion() {
                return __CLASS__ . ': $Id$';
        }
-}
\ No newline at end of file
+}
index 4936a60..aee27e7 100644 (file)
@@ -42,7 +42,6 @@ class ApiPurge extends ApiBase {
         */
        public function execute() {
                global $wgUser;
-               $this->getMain()->setCachePrivate();
                $params = $this->extractRequestParams();
                if ( !$wgUser->isAllowed( 'purge' ) ) {
                        $this->dieUsageMsg( array( 'cantpurge' ) );
index 063b478..b3caa10 100644 (file)
@@ -232,9 +232,15 @@ class ApiQuery extends ApiBase {
                $this->instantiateModules( $modules, 'list', $this->mQueryListModules );
                $this->instantiateModules( $modules, 'meta', $this->mQueryMetaModules );
 
+               $cacheMode = 'public';
+
                // If given, execute generator to substitute user supplied data with generated data.
                if ( isset( $this->params['generator'] ) ) {
-                       $this->executeGeneratorModule( $this->params['generator'], $modules );
+                       $generator = $this->newGenerator( $this->params['generator'] );
+                       $params = $generator->extractRequestParams();
+                       $cacheMode = $this->mergeCacheMode( $cacheMode, 
+                               $generator->getCacheMode( $params ) );
+                       $this->executeGeneratorModule( $generator, $modules );
                } else {
                        // Append custom fields and populate page/revision information
                        $this->addCustomFldsToPageSet( $modules, $this->mPageSet );
@@ -246,11 +252,35 @@ class ApiQuery extends ApiBase {
 
                // Execute all requested modules.
                foreach ( $modules as $module ) {
+                       $params = $module->extractRequestParams();
+                       $cacheMode = $this->mergeCacheMode( 
+                               $cacheMode, $module->getCacheMode( $params ) );
                        $module->profileIn();
                        $module->execute();
                        wfRunHooks( 'APIQueryAfterExecute', array( &$module ) );
                        $module->profileOut();
                }
+
+               // Set the cache mode
+               $this->getMain()->setCacheMode( $cacheMode );
+       }
+
+       /**
+        * Update a cache mode string, applying the cache mode of a new module to it.
+        * The cache mode may increase in the level of privacy, but public modules 
+        * added to private data do not decrease the level of privacy.
+        */
+       protected function mergeCacheMode( $cacheMode, $modCacheMode ) {
+               if ( $modCacheMode === 'anon-public-user-private' ) {
+                       if ( $cacheMode !== 'private' ) {
+                               $cacheMode = 'anon-public-user-private';
+                       }
+               } elseif ( $modCacheMode === 'public' ) {
+                       // do nothing, if it's public already it will stay public
+               } else { // private
+                       $cacheMode = 'private';
+               }
+               return $cacheMode;
        }
 
        /**
@@ -458,12 +488,9 @@ class ApiQuery extends ApiBase {
        }
 
        /**
-        * For generator mode, execute generator, and use its output as new
-        * ApiPageSet
-        * @param $generatorName string Module name
-        * @param $modules array of module objects
+        * Create a generator object of the given type and return it
         */
-       protected function executeGeneratorModule( $generatorName, $modules ) {
+       public function newGenerator( $generatorName ) {
                // Find class that implements requested generator
                if ( isset( $this->mQueryListModules[$generatorName] ) ) {
                        $className = $this->mQueryListModules[$generatorName];
@@ -472,17 +499,23 @@ class ApiQuery extends ApiBase {
                } else {
                        ApiBase::dieDebug( __METHOD__, "Unknown generator=$generatorName" );
                }
-
-               // Generator results
-               $resultPageSet = new ApiPageSet( $this, $this->redirects, $this->convertTitles );
-
-               // Create and execute the generator
                $generator = new $className ( $this, $generatorName );
                if ( !$generator instanceof ApiQueryGeneratorBase ) {
                        $this->dieUsage( "Module $generatorName cannot be used as a generator", 'badgenerator' );
                }
-
                $generator->setGeneratorMode();
+               return $generator;
+       }
+
+       /**
+        * For generator mode, execute generator, and use its output as new
+        * ApiPageSet
+        * @param $generatorName string Module name
+        * @param $modules array of module objects
+        */
+       protected function executeGeneratorModule( $generator, $modules ) {
+               // Generator results
+               $resultPageSet = new ApiPageSet( $this, $this->redirects, $this->convertTitles );
 
                // Add any additional fields modules may need
                $generator->requestExtraData( $this->mPageSet );
index 1a57676..afdc52d 100644 (file)
@@ -44,6 +44,10 @@ class ApiQueryAllCategories extends ApiQueryGeneratorBase {
                $this->run();
        }
 
+       public function getCacheMode( $params ) {
+               return 'public';
+       }
+
        public function executeGenerator( $resultPageSet ) {
                $this->run( $resultPageSet );
        }
@@ -179,4 +183,4 @@ class ApiQueryAllCategories extends ApiQueryGeneratorBase {
        public function getVersion() {
                return __CLASS__ . ': $Id$';
        }
-}
\ No newline at end of file
+}
index 340aea3..40c63f8 100644 (file)
@@ -43,6 +43,10 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase {
                $this->run();
        }
 
+       public function getCacheMode( $params ) {
+               return 'public';
+       }
+
        public function executeGenerator( $resultPageSet ) {
                $this->run( $resultPageSet );
        }
@@ -221,4 +225,4 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase {
        public function getVersion() {
                return __CLASS__ . ': $Id$';
        }
-}
\ No newline at end of file
+}
index 9354745..1f4d858 100644 (file)
@@ -184,6 +184,10 @@ class ApiQueryAllUsers extends ApiQueryBase {
                $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'u' );
        }
 
+       public function getCacheMode( $params ) {
+               return 'public';
+       }
+
        public function getAllowedParams() {
                return array(
                        'from' => null,
@@ -241,4 +245,4 @@ class ApiQueryAllUsers extends ApiQueryBase {
        public function getVersion() {
                return __CLASS__ . ': $Id$';
        }
-}
\ No newline at end of file
+}
index 3a5c427..a63edec 100644 (file)
@@ -55,6 +55,10 @@ class ApiQueryAllimages extends ApiQueryGeneratorBase {
                $this->run();
        }
 
+       public function getCacheMode( $params ) {
+               return 'public';
+       }
+
        public function executeGenerator( $resultPageSet ) {
                if ( $resultPageSet->isResolvingRedirects() ) {
                        $this->dieUsage( 'Use "gaifilterredir=nonredirects" option instead of "redirects" when using allimages as a generator', 'params' );
index 6e5cbd2..86e2f91 100644 (file)
@@ -45,12 +45,9 @@ class ApiQueryAllmessages extends ApiQueryBase {
                global $wgLang;
                
                $oldLang = null;
-               if ( !is_null( $params['lang'] ) && $params['lang'] != $wgLang->getCode() ) {
+               if ( !is_null( $params['lang'] ) ) {
                        $oldLang = $wgLang; // Keep $wgLang for restore later
                        $wgLang = Language::factory( $params['lang'] );
-               } else if ( is_null( $params['lang'] ) ) {
-                       // Language not determined by URL but by user preferences, so don't cache
-                       $this->getMain()->setVaryCookie();
                }
 
                $prop = array_flip( (array)$params['prop'] );
@@ -131,6 +128,19 @@ class ApiQueryAllmessages extends ApiQueryBase {
                }
        }
 
+       public function getCacheMode( $params ) {
+               if ( is_null( $params['lang'] ) ) {
+                       // Language not specified, will be fetched from preferences
+                       return 'anon-public-user-private';
+               } elseif ( $params['enableparser'] ) {
+                       // User-specific parser options will be used
+                       return 'anon-public-user-private';
+               } else {
+                       // OK to cache
+                       return 'public';
+               }
+       }
+
        public function getAllowedParams() {
                return array(
                        'messages' => array(
index 3c3c949..571264a 100644 (file)
@@ -43,6 +43,10 @@ class ApiQueryAllpages extends ApiQueryGeneratorBase {
                $this->run();
        }
 
+       public function getCacheMode( $params ) {
+               return 'public';
+       }
+
        public function executeGenerator( $resultPageSet ) {
                if ( $resultPageSet->isResolvingRedirects() ) {
                        $this->dieUsage( 'Use "gapfilterredir=nonredirects" option instead of "redirects" when using allpages as a generator', 'params' );
index aed143a..1ab0c67 100644 (file)
@@ -92,6 +92,10 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
                $this->run();
        }
 
+       public function getCacheMode( $params ) {
+               return 'public';
+       }
+
        public function executeGenerator( $resultPageSet ) {
                $this->run( $resultPageSet );
        }
index 61a1072..80b2ca9 100644 (file)
@@ -46,6 +46,17 @@ abstract class ApiQueryBase extends ApiBase {
                $this->resetQueryParams();
        }
 
+       /**
+        * Get the cache mode for the data generated by this module. Override this 
+        * in the module subclass.
+        *
+        * Public caching will only be allowed if *all* the modules that supply 
+        * data for a given request return a cache mode of public.
+        */
+       public function getCacheMode( $params ) {
+               return 'private';
+       }
+
        /**
         * Blank the internal arrays with query parameters
         */
index 2db17bd..dd0e53f 100644 (file)
@@ -125,8 +125,6 @@ class ApiQueryBlocks extends ApiQueryBase {
                        ) );
                }
                
-               // Make sure private data (deleted blocks) isn't cached
-               $this->getMain()->setVaryCookie();
                if ( !$wgUser->isAllowed( 'hideuser' ) ) {
                        $this->addWhereFld( 'ipb_deleted', 0 );
                }
@@ -308,4 +306,4 @@ class ApiQueryBlocks extends ApiQueryBase {
        public function getVersion() {
                return __CLASS__ . ': $Id$';
        }
-}
\ No newline at end of file
+}
index 0d6c15f..9d95fc7 100644 (file)
@@ -43,6 +43,10 @@ class ApiQueryCategories extends ApiQueryGeneratorBase {
                $this->run();
        }
 
+       public function getCacheMode( $params ) {
+               return 'public';
+       }
+
        public function executeGenerator( $resultPageSet ) {
                $this->run( $resultPageSet );
        }
index a89a0e1..09306b4 100644 (file)
@@ -94,6 +94,10 @@ class ApiQueryCategoryInfo extends ApiQueryBase {
                }
        }
 
+       public function getCacheMode( $params ) {
+               return 'public';
+       }
+
        public function getAllowedParams() {
                return array(
                        'continue' => null,
@@ -117,4 +121,4 @@ class ApiQueryCategoryInfo extends ApiQueryBase {
        public function getVersion() {
                return __CLASS__ . ': $Id$';
        }
-}
\ No newline at end of file
+}
index 5eb65f1..d0919d0 100644 (file)
@@ -43,6 +43,10 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
                $this->run();
        }
 
+       public function getCacheMode( $params ) {
+               return 'public';
+       }
+
        public function executeGenerator( $resultPageSet ) {
                $this->run( $resultPageSet );
        }
index 5985844..e5635da 100644 (file)
@@ -41,7 +41,6 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
 
        public function execute() {
                global $wgUser;
-               $this->getMain()->setVaryCookie();
                // Before doing anything at all, let's check permissions
                if ( !$wgUser->isAllowed( 'deletedhistory' ) ) {
                        $this->dieUsage( 'You don\'t have permission to view deleted revision information', 'permissiondenied' );
@@ -210,7 +209,6 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
 
                        if ( $fld_parsedcomment ) {
                                global $wgUser;
-                               $this->getMain()->setVaryCookie();
                                $rev['parsedcomment'] = $wgUser->getSkin()->formatComment( $row->ar_comment, $title );
                        }
                        if ( $fld_minor && $row->ar_minor_edit == 1 ) {
@@ -367,4 +365,4 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
        public function getVersion() {
                return __CLASS__ . ': $Id$';
        }
-}
\ No newline at end of file
+}
index cdc63d7..72d17bb 100644 (file)
@@ -43,6 +43,10 @@ class ApiQueryDuplicateFiles extends ApiQueryGeneratorBase {
                $this->run();
        }
 
+       public function getCacheMode( $params ) {
+               return 'public';
+       }
+
        public function executeGenerator( $resultPageSet ) {
                $this->run( $resultPageSet );
        }
index 31e7bc7..f92ef41 100644 (file)
@@ -41,6 +41,10 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase {
                $this->run();
        }
 
+       public function getCacheMode( $params ) {
+               return 'public';
+       }
+
        public function executeGenerator( $resultPageSet ) {
                $this->run( $resultPageSet );
        }
@@ -226,4 +230,4 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase {
        public function getVersion() {
                return __CLASS__ . ': $Id$';
        }
-}
\ No newline at end of file
+}
index a679c47..082b340 100644 (file)
@@ -83,6 +83,10 @@ class ApiQueryExternalLinks extends ApiQueryBase {
                }
        }
 
+       public function getCacheMode( $params ) {
+               return 'public';
+       }
+
        public function getAllowedParams() {
                return array(
                        'limit' => array(
@@ -117,4 +121,4 @@ class ApiQueryExternalLinks extends ApiQueryBase {
        public function getVersion() {
                return __CLASS__ . ': $Id$';
        }
-}
\ No newline at end of file
+}
index 38ca3d9..e739345 100644 (file)
@@ -43,7 +43,6 @@ class ApiQueryFilearchive extends ApiQueryBase {
 
        public function execute() {
                global $wgUser;
-               $this->getMain()->setVaryCookie();
                // Before doing anything at all, let's check permissions
                if ( !$wgUser->isAllowed( 'deletedhistory' ) ) {
                        $this->dieUsage( 'You don\'t have permission to view deleted file information', 'permissiondenied' );
index 35baa48..ab5cbae 100644 (file)
@@ -147,6 +147,10 @@ class ApiQueryIWBacklinks extends ApiQueryGeneratorBase {
                }
        }
 
+       public function getCacheMode( $params ) {
+               return 'public';
+       }
+
        public function getAllowedParams() {
                return array(
                        'prefix' => null,
index 03288a9..90eb751 100644 (file)
@@ -108,6 +108,10 @@ class ApiQueryIWLinks extends ApiQueryBase {
                }
        }
 
+       public function getCacheMode( $params ) {
+               return 'public';
+       }
+
        public function getAllowedParams() {
                return array(
                        'url' => null,
@@ -150,4 +154,4 @@ class ApiQueryIWLinks extends ApiQueryBase {
        public function getVersion() {
                return __CLASS__ . ': $Id$';
        }
-}
\ No newline at end of file
+}
index 17cb2fa..465d8c0 100644 (file)
@@ -274,6 +274,10 @@ class ApiQueryImageInfo extends ApiQueryBase {
                return $retval;
        }
 
+       public function getCacheMode( $params ) {
+               return 'public';
+       }
+
        private function getContinueStr( $img ) {
                return $img->getOriginalTitle()->getText() .
                        '|' .  $img->getTimestamp();
index b961d88..790c45f 100644 (file)
@@ -121,6 +121,10 @@ class ApiQueryImages extends ApiQueryGeneratorBase {
                }
        }
 
+       public function getCacheMode( $params ) {
+               return 'public';
+       }
+
        public function getAllowedParams() {
                return array(
                        'limit' => array(
@@ -163,4 +167,4 @@ class ApiQueryImages extends ApiQueryGeneratorBase {
        public function getVersion() {
                return __CLASS__ . ': $Id$';
        }
-}
\ No newline at end of file
+}
index 896e200..4dfeb7f 100644 (file)
@@ -253,7 +253,6 @@ class ApiQueryInfo extends ApiQueryBase {
                }
 
                if ( $this->fld_watched ) {
-                       $this->getMain()->setVaryCookie();
                        $this->getWatchedInfo();
                }
 
@@ -304,9 +303,6 @@ class ApiQueryInfo extends ApiQueryBase {
                }
 
                if ( !is_null( $this->params['token'] ) ) {
-                       // Don't cache tokens
-                       $this->getMain()->setCachePrivate();
-                       
                        $tokenFunctions = $this->getTokenFunctions();
                        $pageInfo['starttimestamp'] = wfTimestamp( TS_ISO_8601, time() );
                        foreach ( $this->params['token'] as $t ) {
@@ -607,6 +603,28 @@ class ApiQueryInfo extends ApiQueryBase {
                }
        }
 
+       public function getCacheMode( $params ) {
+               $publicProps = array(
+                       'protection',
+                       'talkid',
+                       'subjectid',
+                       'url',
+                       'preload',
+                       'displaytitle',
+               );
+               if ( !is_null( $params['prop'] ) ) {
+                       foreach ( $params['prop'] as $prop ) {
+                               if ( !in_array( $prop, $publicProps ) ) {
+                                       return 'private';
+                               }
+                       }
+               }
+               if ( !is_null( $params['token'] ) ) {
+                       return 'private';
+               }
+               return 'public';
+       }
+
        public function getAllowedParams() {
                return array(
                        'prop' => array(
@@ -615,12 +633,14 @@ class ApiQueryInfo extends ApiQueryBase {
                                ApiBase::PARAM_TYPE => array(
                                        'protection',
                                        'talkid',
-                                       'watched',
+                                       'watched', # private
                                        'subjectid',
                                        'url',
-                                       'readable',
+                                       'readable', # private
                                        'preload',
                                        'displaytitle',
+                                       // If you add more properties here, please consider whether they 
+                                       // need to be added to getCacheMode()
                                ) ),
                        'token' => array(
                                ApiBase::PARAM_DFLT => null,
index 5e36bca..761e448 100644 (file)
@@ -95,6 +95,10 @@ class ApiQueryLangLinks extends ApiQueryBase {
                }
        }
 
+       public function getCacheMode( $params ) {
+               return 'public';
+       }
+
        public function getAllowedParams() {
                return array(
                        'limit' => array(
@@ -135,4 +139,4 @@ class ApiQueryLangLinks extends ApiQueryBase {
        public function getVersion() {
                return __CLASS__ . ': $Id$';
        }
-}
\ No newline at end of file
+}
index 9e5b75d..732f310 100644 (file)
@@ -65,6 +65,10 @@ class ApiQueryLinks extends ApiQueryGeneratorBase {
                $this->run();
        }
 
+       public function getCacheMode( $params ) {
+               return 'public';
+       }
+
        public function executeGenerator( $resultPageSet ) {
                $this->run( $resultPageSet );
        }
@@ -232,4 +236,4 @@ class ApiQueryLinks extends ApiQueryGeneratorBase {
        public function getVersion() {
                return __CLASS__ . ': $Id$';
        }
-}
\ No newline at end of file
+}
index 51ca8b6..e56b895 100644 (file)
@@ -282,7 +282,6 @@ class ApiQueryLogEvents extends ApiQueryBase {
 
                                if ( $this->fld_parsedcomment ) {
                                        global $wgUser;
-                                       $this->getMain()->setVaryCookie();
                                        $vals['parsedcomment'] = $wgUser->getSkin()->formatComment( $row->log_comment, $title );
                                }
                        }
@@ -301,6 +300,15 @@ class ApiQueryLogEvents extends ApiQueryBase {
                return $vals;
        }
 
+       public function getCacheMode( $params ) {
+               if ( !is_null( $params['prop'] ) && in_array( 'parsedcomment', $params['prop'] ) ) {
+                       // formatComment() calls wfMsg() among other things
+                       return 'anon-public-user-private';
+               } else {
+                       return 'public';
+               }
+       }
+
        public function getAllowedParams() {
                global $wgLogTypes, $wgLogActions;
                return array(
index 19b8182..e0b7616 100644 (file)
@@ -101,7 +101,6 @@ class ApiQueryProtectedTitles extends ApiQueryGeneratorBase {
 
                                if ( isset( $prop['parsedcomment'] ) ) {
                                        global $wgUser;
-                                       $this->getMain()->setVaryCookie();
                                        $vals['parsedcomment'] = $wgUser->getSkin()->formatComment( $row->pt_reason, $title );
                                }
 
@@ -131,6 +130,15 @@ class ApiQueryProtectedTitles extends ApiQueryGeneratorBase {
                }
        }
 
+       public function getCacheMode( $params ) {
+               if ( !is_null( $params['prop'] ) && in_array( 'parsedcomment', $params['prop'] ) ) {
+                       // formatComment() calls wfMsg() among other things
+                       return 'anon-public-user-private';
+               } else {
+                       return 'public';
+               }
+       }
+
        public function getAllowedParams() {
                global $wgRestrictionLevels;
                return array(
@@ -210,4 +218,4 @@ class ApiQueryProtectedTitles extends ApiQueryGeneratorBase {
        public function getVersion() {
                return __CLASS__ . ': $Id$';
        }
-}
\ No newline at end of file
+}
index 41aef1a..4ffaeac 100644 (file)
@@ -119,6 +119,10 @@ if ( !defined( 'MEDIAWIKI' ) ) {
                return $vals;
        }
 
+       public function getCacheMode( $params ) {
+               return 'public';
+       }
+
        public function getAllowedParams() {
                return array(
                        'namespace' => array(
@@ -160,4 +164,4 @@ if ( !defined( 'MEDIAWIKI' ) ) {
        public function getVersion() {
                return __CLASS__ . ': $Id: ApiQueryRandom.php overlordq$';
        }
-}
\ No newline at end of file
+}
index b0d7e83..609e86d 100644 (file)
@@ -143,7 +143,6 @@ class ApiQueryRecentChanges extends ApiQueryBase {
                        // Check permissions
                        global $wgUser;
                        if ( isset( $show['patrolled'] ) || isset( $show['!patrolled'] ) ) {
-                               $this->getMain()->setVaryCookie();
                                if ( !$wgUser->useRCPatrol() && !$wgUser->useNPPatrol() ) {
                                        $this->dieUsage( 'You need the patrol right to request the patrolled flag', 'permissiondenied' );
                                }
@@ -376,7 +375,6 @@ class ApiQueryRecentChanges extends ApiQueryBase {
 
                if ( $this->fld_parsedcomment && isset( $row->rc_comment ) ) {
                        global $wgUser;
-                       $this->getMain()->setVaryCookie();
                        $vals['parsedcomment'] = $wgUser->getSkin()->formatComment( $row->rc_comment, $title );
                }
 
@@ -413,9 +411,6 @@ class ApiQueryRecentChanges extends ApiQueryBase {
                }
 
                if ( !is_null( $this->token ) ) {
-                       // Don't cache tokens
-                       $this->getMain()->setCachePrivate();
-                       
                        $tokenFunctions = $this->getTokenFunctions();
                        foreach ( $this->token as $t ) {
                                $val = call_user_func( $tokenFunctions[$t], $row->rc_cur_id,
@@ -449,6 +444,24 @@ class ApiQueryRecentChanges extends ApiQueryBase {
                }
        }
 
+       public function getCacheMode( $params ) {
+               if ( isset( $params['show'] ) ) {
+                       foreach ( $params['show'] as $show ) {
+                               if ( $show === 'patrolled' || $show === '!patrolled' ) {
+                                       return 'private';
+                               }
+                       }
+               }
+               if ( isset( $params['token'] ) ) {
+                       return 'private';
+               }
+               if ( !is_null( $params['prop'] ) && in_array( 'parsedcomment', $params['prop'] ) ) {
+                       // formatComment() calls wfMsg() among other things
+                       return 'anon-public-user-private';
+               }
+               return 'public';
+       }
+
        public function getAllowedParams() {
                return array(
                        'start' => array(
index 1a79e7e..3130405 100644 (file)
@@ -394,7 +394,6 @@ class ApiQueryRevisions extends ApiQueryBase {
 
                                if ( $this->fld_parsedcomment ) {
                                        global $wgUser;
-                                       $this->getMain()->setVaryCookie();
                                        $vals['parsedcomment'] = $wgUser->getSkin()->formatComment( $comment, $title );
                                }
                        }
@@ -411,9 +410,6 @@ class ApiQueryRevisions extends ApiQueryBase {
                }
 
                if ( !is_null( $this->token ) ) {
-                       // Don't cache tokens
-                       $this->getMain()->setCachePrivate();
-                       
                        $tokenFunctions = $this->getTokenFunctions();
                        foreach ( $this->token as $t ) {
                                $val = call_user_func( $tokenFunctions[$t], $title->getArticleID(), $title, $revision );
@@ -487,6 +483,17 @@ class ApiQueryRevisions extends ApiQueryBase {
                return $vals;
        }
 
+       public function getCacheMode( $params ) {
+               if ( isset( $params['token'] ) ) {
+                       return 'private';
+               }
+               if ( !is_null( $params['prop'] ) && in_array( 'parsedcomment', $params['prop'] ) ) {
+                       // formatComment() calls wfMsg() among other things
+                       return 'anon-public-user-private';
+               }               
+               return 'public';
+       }
+
        public function getAllowedParams() {
                return array(
                        'prop' => array(
index 86ba895..2527307 100644 (file)
@@ -164,6 +164,10 @@ class ApiQuerySearch extends ApiQueryGeneratorBase {
                }
        }
 
+       public function getCacheMode( $params ) {
+               return 'public';
+       }
+
        public function getAllowedParams() {
                return array(
                        'search' => null,
index 0bfd3c3..2ffb700 100644 (file)
@@ -427,6 +427,10 @@ class ApiQuerySiteinfo extends ApiQueryBase {
                return $this->getResult()->addValue( 'query', $property, $data );
        }
 
+       public function getCacheMode( $params ) {
+               return 'public';
+       }
+
        public function getAllowedParams() {
                return array(
                        'prop' => array(
index ad708bd..6c5c9a4 100644 (file)
@@ -129,6 +129,10 @@ class ApiQueryTags extends ApiQueryBase {
                return true;
        }
 
+       public function getCacheMode( $params ) {
+               return 'public';
+       }
+
        public function getAllowedParams() {
                return array(
                        'continue' => array(
index c6eadb2..c62b423 100644 (file)
@@ -163,8 +163,6 @@ class ApiQueryContributions extends ApiQueryBase {
                        );
                }
 
-               // Make sure private data (deleted revisions) isn't cached
-               $this->getMain()->setVaryCookie();
                if ( !$wgUser->isAllowed( 'hideuser' ) ) {
                        $this->addWhere( $this->getDB()->bitAnd( 'rev_deleted', Revision::DELETED_USER ) . ' = 0' );
                }
@@ -216,8 +214,6 @@ class ApiQueryContributions extends ApiQueryBase {
                                 $this->fld_patrolled )
                {
                        global $wgUser;
-                       // Don't cache private data
-                       $this->getMain()->setVaryCookie();
                        if ( !$wgUser->useRCPatrol() && !$wgUser->useNPPatrol() ) {
                                $this->dieUsage( 'You need the patrol right to request the patrolled flag', 'permissiondenied' );
                        }
@@ -320,7 +316,6 @@ class ApiQueryContributions extends ApiQueryBase {
 
                                if ( $this->fld_parsedcomment ) {
                                        global $wgUser;
-                                       $this->getMain()->setVaryCookie();
                                        $vals['parsedcomment'] = $wgUser->getSkin()->formatComment( $row->rev_comment, $title );
                                }
                        }
@@ -352,6 +347,12 @@ class ApiQueryContributions extends ApiQueryBase {
                        wfTimestamp( TS_ISO_8601, $row->rev_timestamp );
        }
 
+       public function getCacheMode( $params ) {
+               // This module provides access to deleted revisions and patrol flags if
+               // the requester is logged in
+               return 'anon-public-user-private';
+       }
+
        public function getAllowedParams() {
                return array(
                        'limit' => array(
index 8599314..ffe1a0c 100644 (file)
@@ -40,7 +40,6 @@ class ApiQueryUserInfo extends ApiQueryBase {
        }
 
        public function execute() {
-               $this->getMain()->setCachePrivate();
                $params = $this->extractRequestParams();
                $result = $this->getResult();
                $r = array();
index b50fe71..23ea58d 100644 (file)
@@ -160,9 +160,6 @@ if ( !defined( 'MEDIAWIKI' ) ) {
                                }
 
                                if ( !is_null( $params['token'] ) ) {
-                                       // Don't cache tokens
-                                       $this->getMain()->setCachePrivate();
-                                       
                                        $tokenFunctions = $this->getTokenFunctions();
                                        foreach ( $params['token'] as $t ) {
                                                $val = call_user_func( $tokenFunctions[$t], $user );
@@ -235,6 +232,14 @@ if ( !defined( 'MEDIAWIKI' ) ) {
                return array_merge( $groups, Autopromote::getAutopromoteGroups( $user ) );
        }
 
+       public function getCacheMode( $params ) {
+               if ( isset( $params['token'] ) ) {
+                       return 'private';
+               } else {
+                       return 'public';
+               }
+       }
+
        public function getAllowedParams() {
                return array(
                        'prop' => array(
index 9339ac0..2d61f82 100644 (file)
@@ -74,7 +74,6 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
                        $this->fld_notificationtimestamp = isset( $prop['notificationtimestamp'] );
 
                        if ( $this->fld_patrol ) {
-                               $this->getMain()->setVaryCookie();
                                if ( !$user->useRCPatrol() && !$user->useNPPatrol() ) {
                                        $this->dieUsage( 'patrol property is not available', 'patrol' );
                                }
@@ -141,9 +140,8 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
                                $this->dieUsageMsg( array( 'show' ) );
                        }
 
-                       // Check permissions.  FIXME: should this check $user instead of $wgUser?
+                       // Check permissions.
                        if ( isset( $show['patrolled'] ) || isset( $show['!patrolled'] ) ) {
-                               $this->getMain()->setVaryCookie();
                                if ( !$wgUser->useRCPatrol() && !$wgUser->useNPPatrol() ) {
                                        $this->dieUsage( 'You need the patrol right to request the patrolled flag', 'permissiondenied' );
                                }
@@ -272,7 +270,6 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
 
                if ( $this->fld_parsedcomment && isset( $row->rc_comment ) ) {
                        global $wgUser;
-                       $this->getMain()->setVaryCookie();
                        $vals['parsedcomment'] = $wgUser->getSkin()->formatComment( $row->rc_comment, $title );
                }
 
index 6ef8691..f0532f9 100644 (file)
@@ -41,7 +41,6 @@ class ApiWatch extends ApiBase {
 
        public function execute() {
                global $wgUser;
-               $this->getMain()->setCachePrivate();
                if ( !$wgUser->isLoggedIn() ) {
                        $this->dieUsage( 'You must be logged-in to have a watchlist', 'notloggedin' );
                }