Merge "Change 1.26 to 1.27, mostly in doc comments"
[lhc/web/wiklou.git] / includes / OutputPage.php
index 552e181..4f99f34 100644 (file)
@@ -236,6 +236,8 @@ class OutputPage extends ContextSource {
 
        /** @var int Cache stuff. Looks like mEnableClientCache */
        protected $mSquidMaxage = 0;
+       /** @var int Upper limit on mSquidMaxage */
+       protected $mCdnMaxageLimit = INF;
 
        /**
         * @var bool Controls if anti-clickjacking / frame-breaking headers will
@@ -271,7 +273,7 @@ class OutputPage extends ContextSource {
        private $mIndexPolicy = 'index';
        private $mFollowPolicy = 'follow';
        private $mVaryHeader = array(
-               'Accept-Encoding' => array( 'list-contains=gzip' ),
+               'Accept-Encoding' => array( 'match=gzip' ),
        );
 
        /**
@@ -1945,7 +1947,18 @@ class OutputPage extends ContextSource {
         * @param int $maxage Maximum cache time on the Squid, in seconds.
         */
        public function setSquidMaxage( $maxage ) {
-               $this->mSquidMaxage = $maxage;
+               $this->mSquidMaxage = min( $maxage, $this->mCdnMaxageLimit );
+       }
+
+       /**
+        * Lower the value of the "s-maxage" part of the "Cache-control" HTTP header
+        *
+        * @param int $maxage Maximum cache time on the Squid, in seconds
+        * @since 1.27
+        */
+       public function lowerCdnMaxage( $maxage ) {
+               $this->mCdnMaxageLimit = min( $maxage, $this->mCdnMaxageLimit );
+               $this->setSquidMaxage( $this->mSquidMaxage );
        }
 
        /**
@@ -1989,14 +2002,9 @@ class OutputPage extends ContextSource {
         * @return bool
         */
        function haveCacheVaryCookies() {
-               $cookieHeader = $this->getRequest()->getHeader( 'cookie' );
-               if ( $cookieHeader === false ) {
-                       return false;
-               }
-               $cvCookies = $this->getCacheVaryCookies();
-               foreach ( $cvCookies as $cookieName ) {
-                       # Check for a simple string match, like the way squid does it
-                       if ( strpos( $cookieHeader, $cookieName ) !== false ) {
+               $request = $this->getRequest();
+               foreach ( $this->getCacheVaryCookies() as $cookieName ) {
+                       if ( $request->getCookie( $cookieName, '', '' ) !== '' ) {
                                wfDebug( __METHOD__ . ": found $cookieName\n" );
                                return true;
                        }
@@ -2009,11 +2017,9 @@ class OutputPage extends ContextSource {
         * Add an HTTP header that will influence on the cache
         *
         * @param string $header Header name
-        * @param string[]|null $option Options for X-Vary-Options. Possible options are:
-        *  - "string-contains=$XXX" varies on whether the header value as a string
-        *    contains $XXX as a substring.
-        *  - "list-contains=$XXX" varies on whether the header value as a
-        *    comma-separated list contains $XXX as one of the list items.
+        * @param string[]|null $option Options for the Key header. See
+        * https://datatracker.ietf.org/doc/draft-fielding-http-key/
+        * for the list of valid options.
         */
        public function addVaryHeader( $header, array $option = null ) {
                if ( !array_key_exists( $header, $this->mVaryHeader ) ) {
@@ -2036,16 +2042,16 @@ class OutputPage extends ContextSource {
        }
 
        /**
-        * Get a complete X-Vary-Options header
+        * Get a complete Key header
         *
         * @return string
         */
-       public function getXVO() {
+       public function getKeyHeader() {
                $cvCookies = $this->getCacheVaryCookies();
 
                $cookiesOption = array();
                foreach ( $cvCookies as $cookieName ) {
-                       $cookiesOption[] = 'string-contains=' . $cookieName;
+                       $cookiesOption[] = 'param=' . $cookieName;
                }
                $this->addVaryHeader( 'Cookie', $cookiesOption );
 
@@ -2057,13 +2063,13 @@ class OutputPage extends ContextSource {
                        }
                        $headers[] = $newheader;
                }
-               $xvo = 'X-Vary-Options: ' . implode( ',', $headers );
+               $key = 'Key: ' . implode( ',', $headers );
 
-               return $xvo;
+               return $key;
        }
 
        /**
-        * bug 21672: Add Accept-Language to Vary and XVO headers
+        * T23672: Add Accept-Language to Vary and Key headers
         * if there's no 'variant' parameter existed in GET.
         *
         * For example:
@@ -2084,14 +2090,14 @@ class OutputPage extends ContextSource {
                                if ( $variant === $lang->getCode() ) {
                                        continue;
                                } else {
-                                       $aloption[] = 'string-contains=' . $variant;
+                                       $aloption[] = 'substr=' . $variant;
 
                                        // IE and some other browsers use BCP 47 standards in
                                        // their Accept-Language header, like "zh-CN" or "zh-Hant".
                                        // We should handle these too.
                                        $variantBCP47 = wfBCP47( $variant );
                                        if ( $variantBCP47 !== $variant ) {
-                                               $aloption[] = 'string-contains=' . $variantBCP47;
+                                               $aloption[] = 'substr=' . $variantBCP47;
                                        }
                                }
                        }
@@ -2166,9 +2172,8 @@ class OutputPage extends ContextSource {
                # maintain different caches for logged-in users and non-logged in ones
                $response->header( $this->getVaryHeader() );
 
-               if ( $config->get( 'UseXVO' ) ) {
-                       # Add an X-Vary-Options header for Squid with Wikimedia patches
-                       $response->header( $this->getXVO() );
+               if ( $config->get( 'UseKeyHeader' ) ) {
+                       $response->header( $this->getKeyHeader() );
                }
 
                if ( $this->mEnableClientCache ) {
@@ -2988,7 +2993,8 @@ class OutputPage extends ContextSource {
        function getExternalHeadScripts() {
                $links = array();
 
-               // Startup - this provides the client with the module manifest and loads jquery and mediawiki base modules
+               // Startup - this provides the client with the module
+               // manifest and loads jquery and mediawiki base modules
                $links[] = $this->makeResourceLoaderLink( 'startup', ResourceLoaderModule::TYPE_SCRIPTS );
 
                return self::getHtmlFromLoaderLinks( $links );
@@ -3022,9 +3028,15 @@ class OutputPage extends ContextSource {
                // This needs to be TYPE_COMBINED so these modules are properly wrapped
                // in mw.loader.implement() calls and deferred until mw.user is available
                $embedScripts = array( 'user.options' );
-               $links[] = $this->makeResourceLoaderLink( $embedScripts, ResourceLoaderModule::TYPE_COMBINED );
+               $links[] = $this->makeResourceLoaderLink(
+                       $embedScripts,
+                       ResourceLoaderModule::TYPE_COMBINED
+               );
                // Separate user.tokens as otherwise caching will be allowed (T84960)
-               $links[] = $this->makeResourceLoaderLink( 'user.tokens', ResourceLoaderModule::TYPE_COMBINED );
+               $links[] = $this->makeResourceLoaderLink(
+                       'user.tokens',
+                       ResourceLoaderModule::TYPE_COMBINED
+               );
 
                // Modules requests - let the client calculate dependencies and batch requests as it likes
                // Only load modules that have marked themselves for loading at the top
@@ -3109,16 +3121,19 @@ class OutputPage extends ContextSource {
                        // FIXME: If the user is previewing, say, ./vector.js, his ./common.js will be loaded
                        // asynchronously and may arrive *after* the inline script here. So the previewed code
                        // may execute before ./common.js runs. Normally, ./common.js runs before ./vector.js.
-                       // Similarly, when previewing ./common.js and the user module does arrive first, it will
-                       // arrive without common.js and the inline script runs after. Thus running common after
-                       // the excluded subpage.
+                       // Similarly, when previewing ./common.js and the user module does arrive first,
+                       // it will arrive without common.js and the inline script runs after.
+                       // Thus running common after the excluded subpage.
                } else {
                        // Include the user module normally, i.e., raw to avoid it being wrapped in a closure.
                        $links[] = $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_COMBINED );
                }
 
                // Group JS is only enabled if site JS is enabled.
-               $links[] = $this->makeResourceLoaderLink( 'user.groups', ResourceLoaderModule::TYPE_COMBINED );
+               $links[] = $this->makeResourceLoaderLink(
+                       'user.groups',
+                       ResourceLoaderModule::TYPE_COMBINED
+               );
 
                return self::getHtmlFromLoaderLinks( $links );
        }
@@ -3297,22 +3312,31 @@ class OutputPage extends ContextSource {
         * @return bool
         */
        public function userCanPreview() {
-               if ( $this->getRequest()->getVal( 'action' ) != 'submit'
-                       || !$this->getRequest()->wasPosted()
-                       || !$this->getUser()->matchEditToken(
-                               $this->getRequest()->getVal( 'wpEditToken' ) )
-               ) {
+               $request = $this->getRequest();
+               if ( $request->getVal( 'action' ) !== 'submit' || !$request->wasPosted() ) {
+                       return false;
+               }
+
+               $user = $this->getUser();
+               if ( !$user->matchEditToken( $request->getVal( 'wpEditToken' ) ) ) {
                        return false;
                }
-               if ( !$this->getTitle()->isJsSubpage() && !$this->getTitle()->isCssSubpage() ) {
+
+               $title = $this->getTitle();
+               if ( !$title->isJsSubpage() && !$title->isCssSubpage() ) {
                        return false;
                }
-               if ( !$this->getTitle()->isSubpageOf( $this->getUser()->getUserPage() ) ) {
+               if ( !$title->isSubpageOf( $user->getUserPage() ) ) {
                        // Don't execute another user's CSS or JS on preview (T85855)
                        return false;
                }
 
-               return !count( $this->getTitle()->getUserPermissionsErrors( 'edit', $this->getUser() ) );
+               $errors = $title->getUserPermissionsErrors( 'edit', $user );
+               if ( count( $errors ) !== 0 ) {
+                       return false;
+               }
+
+               return true;
        }
 
        /**
@@ -3375,7 +3399,9 @@ class OutputPage extends ContextSource {
                if ( $config->get( 'UniversalEditButton' ) && $this->isArticleRelated() ) {
                        $user = $this->getUser();
                        if ( $this->getTitle()->quickUserCan( 'edit', $user )
-                               && ( $this->getTitle()->exists() || $this->getTitle()->quickUserCan( 'create', $user ) ) ) {
+                               && ( $this->getTitle()->exists() ||
+                                       $this->getTitle()->quickUserCan( 'create', $user ) )
+                       ) {
                                // Original UniversalEditButton
                                $msg = $this->msg( 'edit' )->text();
                                $tags['universal-edit-button'] = Html::element( 'link', array(
@@ -3427,8 +3453,9 @@ class OutputPage extends ContextSource {
                        $tags['rsd'] = Html::element( 'link', array(
                                'rel' => 'EditURI',
                                'type' => 'application/rsd+xml',
-                               // Output a protocol-relative URL here if $wgServer is protocol-relative
-                               // Whether RSD accepts relative or protocol-relative URLs is completely undocumented, though
+                               // Output a protocol-relative URL here if $wgServer is protocol-relative.
+                               // Whether RSD accepts relative or protocol-relative URLs is completely
+                               // undocumented, though.
                                'href' => wfExpandUrl( wfAppendQuery(
                                        wfScript( 'api' ),
                                        array( 'action' => 'rsd' ) ),
@@ -3446,7 +3473,9 @@ class OutputPage extends ContextSource {
                                        $tags["variant-$variant"] = Html::element( 'link', array(
                                                'rel' => 'alternate',
                                                'hreflang' => wfBCP47( $variant ),
-                                               'href' => $this->getTitle()->getLocalURL( array( 'variant' => $variant ) ) )
+                                               'href' => $this->getTitle()->getLocalURL(
+                                                       array( 'variant' => $variant ) )
+                                               )
                                        );
                                }
                                # x-default link per https://support.google.com/webmasters/answer/189077?hl=en
@@ -3494,7 +3523,9 @@ class OutputPage extends ContextSource {
                                        $format,
                                        $link,
                                        # Used messages: 'page-rss-feed' and 'page-atom-feed' (for an easier grep)
-                                       $this->msg( "page-{$format}-feed", $this->getTitle()->getPrefixedText() )->text()
+                                       $this->msg(
+                                               "page-{$format}-feed", $this->getTitle()->getPrefixedText()
+                                       )->text()
                                );
                        }
 
@@ -3701,7 +3732,8 @@ class OutputPage extends ContextSource {
                                continue;
                        }
                        $group = $module->getGroup();
-                       // Modules in groups other than the ones needing special treatment (see $styles assignment)
+                       // Modules in groups other than the ones needing special treatment
+                       // (see $styles assignment)
                        // will be placed in the "other" style category.
                        $styles[isset( $styles[$group] ) ? $group : 'other'][] = $name;
                }
@@ -3711,10 +3743,14 @@ class OutputPage extends ContextSource {
                // statically added styles from other modules. So the order has to be
                // other, dynamic, site, private, user. Add statically added styles for
                // other modules
-               $links[] = $this->makeResourceLoaderLink( $styles['other'], ResourceLoaderModule::TYPE_STYLES );
+               $links[] = $this->makeResourceLoaderLink(
+                       $styles['other'],
+                       ResourceLoaderModule::TYPE_STYLES
+               );
                // Add normal styles added through addStyle()/addInlineStyle() here
                $links[] = implode( "\n", $this->buildCssLinksArray() ) . $this->mInlineStyles;
-               // Add marker tag to mark the place where the client-side loader should inject dynamic styles
+               // Add marker tag to mark the place where the client-side
+               // loader should inject dynamic styles
                // We use a <meta> tag with a made-up name for this because that's valid HTML
                $links[] = Html::element(
                        'meta',
@@ -3784,7 +3820,8 @@ class OutputPage extends ContextSource {
                        $url = $style;
                } else {
                        $config = $this->getConfig();
-                       $url = $config->get( 'StylePath' ) . '/' . $style . '?' . $config->get( 'StyleVersion' );
+                       $url = $config->get( 'StylePath' ) . '/' . $style . '?' .
+                               $config->get( 'StyleVersion' );
                }
 
                $link = Html::linkedStyle( $url, $media );
@@ -3973,7 +4010,7 @@ class OutputPage extends ContextSource {
                $themes = ExtensionRegistry::getInstance()->getAttribute( 'SkinOOUIThemes' );
                // Make keys (skin names) lowercase for case-insensitive matching.
                $themes = array_change_key_case( $themes, CASE_LOWER );
-               $theme = isset( $themes[ $skinName ] ) ? $themes[ $skinName ] : 'MediaWiki';
+               $theme = isset( $themes[$skinName] ) ? $themes[$skinName] : 'MediaWiki';
                // For example, 'OOUI\MediaWikiTheme'.
                $themeClass = "OOUI\\{$theme}Theme";
                OOUI\Theme::setSingleton( new $themeClass() );