Merge "Don't fallback from uk to ru"
[lhc/web/wiklou.git] / includes / OutputPage.php
index 50f9eb9..43d71ab 100644 (file)
@@ -107,7 +107,10 @@ class OutputPage extends ContextSource {
        protected $mCategoryLinks = [];
 
        /** @var array */
-       protected $mCategories = [];
+       protected $mCategories = [
+               'hidden' => [],
+               'normal' => [],
+       ];
 
        /** @var array */
        protected $mIndicators = [];
@@ -295,9 +298,6 @@ class OutputPage extends ContextSource {
         */
        private $copyrightUrl;
 
-       /** @var array Profiling data */
-       private $limitReportData = [];
-
        /**
         * Constructor for OutputPage. This should not be called directly.
         * Instead a new RequestContext should be created and it will implicitly create
@@ -1214,8 +1214,8 @@ class OutputPage extends ContextSource {
        /**
         * Add new language links
         *
-        * @param array $newLinkArray Associative array mapping language code to the page
-        *                      name
+        * @param string[] $newLinkArray Array of interwiki-prefixed (non DB key) titles
+        *                               (e.g. 'fr:Test page')
         */
        public function addLanguageLinks( array $newLinkArray ) {
                $this->mLanguageLinks += $newLinkArray;
@@ -1224,8 +1224,8 @@ class OutputPage extends ContextSource {
        /**
         * Reset the language links and add new language links
         *
-        * @param array $newLinkArray Associative array mapping language code to the page
-        *                      name
+        * @param string[] $newLinkArray Array of interwiki-prefixed (non DB key) titles
+        *                               (e.g. 'fr:Test page')
         */
        public function setLanguageLinks( array $newLinkArray ) {
                $this->mLanguageLinks = $newLinkArray;
@@ -1234,7 +1234,7 @@ class OutputPage extends ContextSource {
        /**
         * Get the list of language links
         *
-        * @return array Array of Interwiki Prefixed (non DB key) Titles (e.g. 'fr:Test page')
+        * @return string[] Array of interwiki-prefixed (non DB key) titles (e.g. 'fr:Test page')
         */
        public function getLanguageLinks() {
                return $this->mLanguageLinks;
@@ -1252,31 +1252,7 @@ class OutputPage extends ContextSource {
                        return;
                }
 
-               # Add the links to a LinkBatch
-               $arr = [ NS_CATEGORY => $categories ];
-               $lb = new LinkBatch;
-               $lb->setArray( $arr );
-
-               # Fetch existence plus the hiddencat property
-               $dbr = wfGetDB( DB_REPLICA );
-               $fields = array_merge(
-                       LinkCache::getSelectFields(),
-                       [ 'page_namespace', 'page_title', 'pp_value' ]
-               );
-
-               $res = $dbr->select( [ 'page', 'page_props' ],
-                       $fields,
-                       $lb->constructSet( 'page', $dbr ),
-                       __METHOD__,
-                       [],
-                       [ 'page_props' => [ 'LEFT JOIN', [
-                               'pp_propname' => 'hiddencat',
-                               'pp_page = page_id'
-                       ] ] ]
-               );
-
-               # Add the results to the link cache
-               $lb->addResultToCache( LinkCache::singleton(), $res );
+               $res = $this->addCategoryLinksToLBAndGetResult( $categories );
 
                # Set all the values to 'normal'.
                $categories = array_fill_keys( array_keys( $categories ), 'normal' );
@@ -1306,12 +1282,46 @@ class OutputPage extends ContextSource {
                                        continue;
                                }
                                $text = $wgContLang->convertHtml( $title->getText() );
-                               $this->mCategories[] = $title->getText();
+                               $this->mCategories[$type][] = $title->getText();
                                $this->mCategoryLinks[$type][] = Linker::link( $title, $text );
                        }
                }
        }
 
+       /**
+        * @param array $categories
+        * @return bool|ResultWrapper
+        */
+       protected function addCategoryLinksToLBAndGetResult( array $categories ) {
+               # Add the links to a LinkBatch
+               $arr = [ NS_CATEGORY => $categories ];
+               $lb = new LinkBatch;
+               $lb->setArray( $arr );
+
+               # Fetch existence plus the hiddencat property
+               $dbr = wfGetDB( DB_REPLICA );
+               $fields = array_merge(
+                       LinkCache::getSelectFields(),
+                       [ 'page_namespace', 'page_title', 'pp_value' ]
+               );
+
+               $res = $dbr->select( [ 'page', 'page_props' ],
+                       $fields,
+                       $lb->constructSet( 'page', $dbr ),
+                       __METHOD__,
+                       [],
+                       [ 'page_props' => [ 'LEFT JOIN', [
+                               'pp_propname' => 'hiddencat',
+                               'pp_page = page_id'
+                       ] ] ]
+               );
+
+               # Add the results to the link cache
+               $lb->addResultToCache( LinkCache::singleton(), $res );
+
+               return $res;
+       }
+
        /**
         * Reset the category links (but not the category list) and add $categories
         *
@@ -1335,12 +1345,26 @@ class OutputPage extends ContextSource {
        }
 
        /**
-        * Get the list of category names this page belongs to
+        * Get the list of category names this page belongs to.
         *
+        * @param string $type The type of categories which should be returned. Possible values:
+        *  * all: all categories of all types
+        *  * hidden: only the hidden categories
+        *  * normal: all categories, except hidden categories
         * @return array Array of strings
         */
-       public function getCategories() {
-               return $this->mCategories;
+       public function getCategories( $type = 'all' ) {
+               if ( $type === 'all' ) {
+                       $allCategories = [];
+                       foreach ( $this->mCategories as $categories ) {
+                               $allCategories = array_merge( $allCategories, $categories );
+                       }
+                       return $allCategories;
+               }
+               if ( !isset( $this->mCategories[$type] ) ) {
+                       throw new InvalidArgumentException( 'Invalid category type given: ' . $type );
+               }
+               return $this->mCategories[$type];
        }
 
        /**
@@ -1713,7 +1737,6 @@ class OutputPage extends ContextSource {
                $popts->setTidy( $oldTidy );
 
                $this->addParserOutput( $parserOutput );
-
        }
 
        /**
@@ -1776,16 +1799,11 @@ class OutputPage extends ContextSource {
                        }
                }
 
-               // Enable OOUI if requested via ParserOutput
+               // enable OOUI if requested via ParserOutput
                if ( $parserOutput->getEnableOOUI() ) {
                        $this->enableOOUI();
                }
 
-               // Include profiling data
-               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.
                $linkFlags = [];
@@ -1933,6 +1951,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
         *
@@ -2168,22 +2216,26 @@ class OutputPage extends ContextSource {
                                        # We'll purge the proxy cache explicitly, but require end user agents
                                        # to revalidate against the proxy on each visit.
                                        # Surrogate-Control controls our CDN, Cache-Control downstream caches
-                                       wfDebug( __METHOD__ . ": proxy caching with ESI; {$this->mLastModified} **", 'private' );
+                                       wfDebug( __METHOD__ .
+                                               ": proxy caching with ESI; {$this->mLastModified} **", 'private' );
                                        # start with a shorter timeout for initial testing
                                        # header( 'Surrogate-Control: max-age=2678400+2678400, content="ESI/1.0"');
-                                       $response->header( 'Surrogate-Control: max-age=' . $config->get( 'SquidMaxage' )
-                                               . '+' . $this->mCdnMaxage . ', content="ESI/1.0"' );
+                                       $response->header(
+                                               "Surrogate-Control: max-age={$config->get( 'SquidMaxage' )}" .
+                                               "+{$this->mCdnMaxage}, content=\"ESI/1.0\""
+                                       );
                                        $response->header( 'Cache-Control: s-maxage=0, must-revalidate, max-age=0' );
                                } else {
                                        # We'll purge the proxy cache for anons explicitly, but require end user agents
                                        # to revalidate against the proxy on each visit.
                                        # IMPORTANT! The CDN needs to replace the Cache-Control header with
                                        # Cache-Control: s-maxage=0, must-revalidate, max-age=0
-                                       wfDebug( __METHOD__ . ": local proxy caching; {$this->mLastModified} **", 'private' );
+                                       wfDebug( __METHOD__ .
+                                               ": local proxy caching; {$this->mLastModified} **", 'private' );
                                        # start with a shorter timeout for initial testing
                                        # header( "Cache-Control: s-maxage=2678400, must-revalidate, max-age=0" );
-                                       $response->header( 'Cache-Control: s-maxage=' . $this->mCdnMaxage
-                                               . ', must-revalidate, max-age=0' );
+                                       $response->header( "Cache-Control: " .
+                                               "s-maxage={$this->mCdnMaxage}, must-revalidate, max-age=0" );
                                }
                        } else {
                                # We do want clients to cache if they can, but they *must* check for updates
@@ -2392,10 +2444,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
@@ -2688,15 +2744,13 @@ class OutputPage extends ContextSource {
                        $exemptStates = [];
                        $moduleStyles = $this->getModuleStyles( /*filter*/ true );
 
-                       // Batch preload getTitleInfo for isKnownEmpty() calls below
-                       $exemptModules = array_filter( $moduleStyles,
-                               function ( $name ) use ( $rl, &$exemptGroups ) {
-                                       $module = $rl->getModule( $name );
-                                       return $module && isset( $exemptGroups[ $module->getGroup() ] );
-                               }
-                       );
-                       ResourceLoaderWikiModule::preloadTitleInfo(
-                               $context, wfGetDB( DB_REPLICA ), $exemptModules );
+                       // 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,
@@ -2775,8 +2829,8 @@ class OutputPage extends ContextSource {
                        // The spec recommends defining XHTML5's charset using the XML declaration
                        // instead of meta.
                        // Our XML declaration is output by Html::htmlHeader.
-                       // http://www.whatwg.org/html/semantics.html#attr-meta-http-equiv-content-type
-                       // http://www.whatwg.org/html/semantics.html#charset
+                       // https://html.spec.whatwg.org/multipage/semantics.html#attr-meta-http-equiv-content-type
+                       // https://html.spec.whatwg.org/multipage/semantics.html#charset
                        $pieces[] = Html::element( 'meta', [ 'charset' => 'UTF-8' ] );
                }
 
@@ -2931,15 +2985,6 @@ class OutputPage extends ContextSource {
                        }
                }
 
-               if ( $this->limitReportData ) {
-                       $chunks[] = ResourceLoader::makeInlineScript(
-                               ResourceLoader::makeConfigSetScript(
-                                       [ 'wgPageParseReport' => $this->limitReportData ],
-                                       true
-                               )
-                       );
-               }
-
                return self::combineWrappedStrings( $chunks );
        }
 
@@ -3660,7 +3705,7 @@ class OutputPage extends ContextSource {
        public static function transformCssMedia( $media ) {
                global $wgRequest;
 
-               // http://www.w3.org/TR/css3-mediaqueries/#syntax
+               // https://www.w3.org/TR/css3-mediaqueries/#syntax
                $screenMediaQueryRegex = '/^(?:only\s+)?screen\b/i';
 
                // Switch in on-screen display for media testing
@@ -3841,12 +3886,4 @@ class OutputPage extends ContextSource {
                        'mediawiki.widgets.styles',
                ] );
        }
-
-       /**
-        * @param array $data Data from ParserOutput::getLimitReportData()
-        * @since 1.28
-        */
-       public function setLimitReportData( array $data ) {
-               $this->limitReportData = $data;
-       }
 }