Merge "Use adaptive CDN TTLs for page views"
[lhc/web/wiklou.git] / includes / OutputPage.php
index 6ae2a92..863a426 100644 (file)
@@ -67,13 +67,6 @@ class OutputPage extends ContextSource {
         */
        public $mBodytext = '';
 
-       /**
-        * Holds the debug lines that will be output as comments in page source if
-        * $wgDebugComments is enabled. See also $wgShowDebug.
-        * @deprecated since 1.20; use MWDebug class instead.
-        */
-       public $mDebugtext = '';
-
        /** @var string Stores contents of "<title>" tag */
        private $mHTMLtitle = '';
 
@@ -1789,7 +1782,9 @@ class OutputPage extends ContextSource {
                }
 
                // Include profiling data
-               $this->setLimitReportData( $parserOutput->getLimitReportData() );
+               if ( !$this->limitReportData ) {
+                       $this->setLimitReportData( $parserOutput->getLimitReportData() );
+               }
 
                // Link flags are ignored for now, but may in the future be
                // used to mark individual language links.
@@ -1938,6 +1933,36 @@ class OutputPage extends ContextSource {
                $this->setCdnMaxage( $this->mCdnMaxage );
        }
 
+       /**
+        * Get TTL in [$minTTL,$maxTTL] in pass it to lowerCdnMaxage()
+        *
+        * This sets and returns $minTTL if $mtime is false or null. Otherwise,
+        * the TTL is higher the older the $mtime timestamp is. Essentially, the
+        * TTL is 90% of the age of the object, subject to the min and max.
+        *
+        * @param string|integer|float|bool|null $mtime Last-Modified timestamp
+        * @param integer $minTTL Mimimum TTL in seconds [default: 1 minute]
+        * @param integer $maxTTL Maximum TTL in seconds [default: $wgSquidMaxage]
+        * @return integer TTL in seconds
+        * @since 1.28
+        */
+       public function adaptCdnTTL( $mtime, $minTTL = 0, $maxTTL = 0 ) {
+               $minTTL = $minTTL ?: IExpiringStore::TTL_MINUTE;
+               $maxTTL = $maxTTL ?: $this->getConfig()->get( 'SquidMaxage' );
+
+               if ( $mtime === null || $mtime === false ) {
+                       return $minTTL; // entity does not exist
+               }
+
+               $age = time() - wfTimestamp( TS_UNIX, $mtime );
+               $adaptiveTTL = max( .9 * $age, $minTTL );
+               $adaptiveTTL = min( $adaptiveTTL, $maxTTL );
+
+               $this->lowerCdnMaxage( (int)$adaptiveTTL );
+
+               return $adaptiveTTL;
+       }
+
        /**
         * Use enableClientCache(false) to force it to send nocache headers
         *
@@ -2214,10 +2239,18 @@ class OutputPage extends ContextSource {
        /**
         * Finally, all the text has been munged and accumulated into
         * the object, let's actually output it:
+        *
+        * @param bool $return Set to true to get the result as a string rather than sending it
+        * @return string|null
+        * @throws Exception
+        * @throws FatalError
+        * @throws MWException
         */
-       public function output() {
+       public function output( $return = false ) {
+               global $wgContLang;
+
                if ( $this->mDoNothing ) {
-                       return;
+                       return $return ? '' : null;
                }
 
                $response = $this->getRequest()->response();
@@ -2253,7 +2286,7 @@ class OutputPage extends ContextSource {
                                }
                        }
 
-                       return;
+                       return $return ? '' : null;
                } elseif ( $this->mStatusCode ) {
                        $response->statusHeader( $this->mStatusCode );
                }
@@ -2262,7 +2295,7 @@ class OutputPage extends ContextSource {
                ob_start();
 
                $response->header( 'Content-type: ' . $config->get( 'MimeType' ) . '; charset=UTF-8' );
-               $response->header( 'Content-language: ' . $config->get( 'LanguageCode' ) );
+               $response->header( 'Content-language: ' . $wgContLang->getHtmlCode() );
 
                // Avoid Internet Explorer "compatibility view" in IE 8-10, so that
                // jQuery etc. can work correctly.
@@ -2322,8 +2355,12 @@ class OutputPage extends ContextSource {
 
                $this->sendCacheControl();
 
-               ob_end_flush();
-
+               if ( $return ) {
+                       return ob_get_clean();
+               } else {
+                       ob_end_flush();
+                       return null;
+               }
        }
 
        /**
@@ -2353,7 +2390,7 @@ class OutputPage extends ContextSource {
         * Output a standard error page
         *
         * showErrorPage( 'titlemsg', 'pagetextmsg' );
-        * showErrorPage( 'titlemsg', 'pagetextmsg', array( 'param1', 'param2' ) );
+        * showErrorPage( 'titlemsg', 'pagetextmsg', [ 'param1', 'param2' ] );
         * showErrorPage( 'titlemsg', $messageObject );
         * showErrorPage( $titleMessageObject, $messageObject );
         *
@@ -2385,10 +2422,14 @@ class OutputPage extends ContextSource {
        /**
         * Output a standard permission error page
         *
-        * @param array $errors Error message keys
+        * @param array $errors Error message keys or [key, param...] arrays
         * @param string $action Action that was denied or null if unknown
         */
        public function showPermissionsErrorPage( array $errors, $action = null ) {
+               foreach ( $errors as $key => $error ) {
+                       $errors[$key] = (array)$error;
+               }
+
                // For some action (read, edit, create and upload), display a "login to do this action"
                // error if all of the following conditions are met:
                // 1. the user is not logged in
@@ -2679,16 +2720,27 @@ class OutputPage extends ContextSource {
                        // Prepare exempt modules for buildExemptModules()
                        $exemptGroups = [ 'site' => [], 'noscript' => [], 'private' => [], 'user' => [] ];
                        $exemptStates = [];
-                       $moduleStyles = array_filter( $this->getModuleStyles( /*filter*/ true ),
+                       $moduleStyles = $this->getModuleStyles( /*filter*/ true );
+
+                       // Preload getTitleInfo for isKnownEmpty calls below and in ResourceLoaderClientHtml
+                       // Separate user-specific batch for improved cache-hit ratio.
+                       $userBatch = [ 'user.styles', 'user' ];
+                       $siteBatch = array_diff( $moduleStyles, $userBatch );
+                       $dbr = wfGetDB( DB_REPLICA );
+                       ResourceLoaderWikiModule::preloadTitleInfo( $context, $dbr, $siteBatch );
+                       ResourceLoaderWikiModule::preloadTitleInfo( $context, $dbr, $userBatch );
+
+                       // Filter out modules handled by buildExemptModules()
+                       $moduleStyles = array_filter( $moduleStyles,
                                function ( $name ) use ( $rl, $context, &$exemptGroups, &$exemptStates ) {
                                        $module = $rl->getModule( $name );
                                        if ( $module ) {
-                                               $group = $module->getGroup();
                                                if ( $name === 'user.styles' && $this->isUserCssPreview() ) {
                                                        $exemptStates[$name] = 'ready';
                                                        // Special case in buildExemptModules()
                                                        return false;
                                                }
+                                               $group = $module->getGroup();
                                                if ( isset( $exemptGroups[$group] ) ) {
                                                        $exemptStates[$name] = 'ready';
                                                        if ( !$module->isKnownEmpty( $context ) ) {
@@ -2703,12 +2755,17 @@ class OutputPage extends ContextSource {
                        );
                        $this->rlExemptStyleModules = $exemptGroups;
 
-                       // Manually handled by getBottomScripts()
-                       $userModule = $rl->getModule( 'user' );
-                       $userState = $userModule->isKnownEmpty( $context ) && !$this->isUserJsPreview()
-                               ? 'ready'
-                               : 'loading';
-                       $this->rlUserModuleState = $exemptStates['user'] = $userState;
+                       $isUserModuleFiltered = !$this->filterModules( [ 'user' ] );
+                       // If this page filters out 'user', makeResourceLoaderLink will drop it.
+                       // Avoid indefinite "loading" state or untrue "ready" state (T145368).
+                       if ( !$isUserModuleFiltered ) {
+                               // Manually handled by getBottomScripts()
+                               $userModule = $rl->getModule( 'user' );
+                               $userState = $userModule->isKnownEmpty( $context ) && !$this->isUserJsPreview()
+                                       ? 'ready'
+                                       : 'loading';
+                               $this->rlUserModuleState = $exemptStates['user'] = $userState;
+                       }
 
                        $rlClient = new ResourceLoaderClientHtml( $context, $this->getTarget() );
                        $rlClient->setConfig( $this->getJSVars() );
@@ -2906,12 +2963,14 @@ class OutputPage extends ContextSource {
                        }
                }
 
-               $chunks[] = ResourceLoader::makeInlineScript(
-                       ResourceLoader::makeConfigSetScript(
-                               [ 'wgPageParseReport' => $this->limitReportData ],
-                               true
-                       )
-               );
+               if ( $this->limitReportData ) {
+                       $chunks[] = ResourceLoader::makeInlineScript(
+                               ResourceLoader::makeConfigSetScript(
+                                       [ 'wgPageParseReport' => $this->limitReportData ],
+                                       true
+                               )
+                       );
+               }
 
                return self::combineWrappedStrings( $chunks );
        }
@@ -3029,8 +3088,8 @@ class OutputPage extends ContextSource {
                if ( $user->isLoggedIn() ) {
                        $vars['wgUserId'] = $user->getId();
                        $vars['wgUserEditCount'] = $user->getEditCount();
-                       $userReg = wfTimestampOrNull( TS_UNIX, $user->getRegistration() );
-                       $vars['wgUserRegistration'] = $userReg !== null ? ( $userReg * 1000 ) : null;
+                       $userReg = $user->getRegistration();
+                       $vars['wgUserRegistration'] = $userReg ? wfTimestamp( TS_UNIX, $userReg ) * 1000 : null;
                        // Get the revision ID of the oldest new message on the user's talk
                        // page. This can be used for constructing new message alerts on
                        // the client side.