Merge "Add <!DOCTYPE html> to HTML responses"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Fri, 16 Dec 2016 07:16:45 +0000 (07:16 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Fri, 16 Dec 2016 07:16:45 +0000 (07:16 +0000)
1  2 
includes/OutputPage.php
includes/WebRequest.php

diff --combined includes/OutputPage.php
@@@ -21,7 -21,6 +21,7 @@@
   */
  
  use MediaWiki\Logger\LoggerFactory;
 +use MediaWiki\MediaWikiServices;
  use MediaWiki\Session\SessionManager;
  use WrappedString\WrappedString;
  use WrappedString\WrappedStringList;
@@@ -108,10 -107,7 +108,10 @@@ class OutputPage extends ContextSource 
        protected $mCategoryLinks = [];
  
        /** @var array */
 -      protected $mCategories = [];
 +      protected $mCategories = [
 +              'hidden' => [],
 +              'normal' => [],
 +      ];
  
        /** @var array */
        protected $mIndicators = [];
         */
        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
                if ( $title->isRedirect() ) {
                        $query['redirect'] = 'no';
                }
 +              $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
                return wfMessage( 'backlinksubtitle' )
 -                      ->rawParams( Linker::link( $title, null, [], $query ) );
 +                      ->rawParams( $linkRenderer->makeLink( $title, null, [], $query ) );
        }
  
        /**
        /**
         * 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;
        /**
         * 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;
        /**
         * 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;
                        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' );
                        'OutputPageMakeCategoryLinks',
                        [ &$this, $categories, &$this->mCategoryLinks ] )
                ) {
 +                      $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
                        foreach ( $categories as $category => $type ) {
                                // array keys will cast numeric category names to ints, so cast back to string
                                $category = (string)$category;
                                        continue;
                                }
                                $text = $wgContLang->convertHtml( $title->getText() );
 -                              $this->mCategories[] = $title->getText();
 -                              $this->mCategoryLinks[$type][] = Linker::link( $title, $text );
 +                              $this->mCategories[$type][] = $title->getText();
 +                              $this->mCategoryLinks[$type][] = $linkRenderer->makeLink( $title, new HtmlArmor( $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
         *
        }
  
        /**
 -       * 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];
        }
  
        /**
                $popts->setTidy( $oldTidy );
  
                $this->addParserOutput( $parserOutput );
 -
        }
  
        /**
                        }
                }
  
 -              // Enable OOUI if requested via ParserOutput
 +              // enable OOUI if requested via ParserOutput
                if ( $parserOutput->getEnableOOUI() ) {
                        $this->enableOOUI();
                }
  
 -              // Include profiling data
 -              $this->setLimitReportData( $parserOutput->getLimitReportData() );
 -
                // Link flags are ignored for now, but may in the future be
                // used to mark individual language links.
                $linkFlags = [];
                $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
         *
                                        # 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
         * @throws MWException
         */
        public function output( $return = false ) {
 +              global $wgContLang;
 +
                if ( $this->mDoNothing ) {
                        return $return ? '' : null;
                }
                                $response->header( "Content-Type: text/html; charset=utf-8" );
                                if ( $config->get( 'DebugRedirects' ) ) {
                                        $url = htmlspecialchars( $redirect );
-                                       print "<html>\n<head>\n<title>Redirect</title>\n</head>\n<body>\n";
+                                       print "<!DOCTYPE html>\n<html>\n<head>\n<title>Redirect</title>\n</head>\n<body>\n";
                                        print "<p>Location: <a href=\"$url\">$url</a></p>\n";
                                        print "</body>\n</html>\n";
                                } else {
                ob_start();
  
                $response->header( 'Content-type: ' . $config->get( 'MimeType' ) . '; charset=UTF-8' );
 -              $response->header( 'Content-language: ' . $config->get( 'ContLang' )->getHtmlCode() );
 +              $response->header( 'Content-language: ' . $wgContLang->getHtmlCode() );
  
                // Avoid Internet Explorer "compatibility view" in IE 8-10, so that
                // jQuery etc. can work correctly.
         * 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 );
         *
        /**
         * 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
         * @param array $options Options array to pass to Linker
         */
        public function addReturnTo( $title, array $query = [], $text = null, $options = [] ) {
 +              $linkRenderer = MediaWikiServices::getInstance()
 +                      ->getLinkRendererFactory()->createFromLegacyOptions( $options );
                $link = $this->msg( 'returnto' )->rawParams(
 -                      Linker::link( $title, $text, [], $query, $options ) )->escaped();
 +                      $linkRenderer->makeLink( $title, $text, [], $query ) )->escaped();
                $this->addHTML( "<p id=\"mw-returnto\">{$link}</p>\n" );
        }
  
                                'site.styles',
                                'noscript',
                                'user.styles',
 -                              'user.cssprefs',
                        ] );
                        $this->getSkin()->setupSkinUserCss( $this );
  
                        $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,
                        // 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' ] );
                }
  
                $bodyClasses[] = $userdir;
                $bodyClasses[] = "sitedir-$sitedir";
  
 +              $underline = $this->getUser()->getOption( 'underline' );
 +              if ( $underline < 2 ) {
 +                      // The following classes can be used here:
 +                      // * mw-underline-always
 +                      // * mw-underline-never
 +                      $bodyClasses[] = 'mw-underline-' . ( $underline ? 'always' : 'never' );
 +              }
 +
                if ( $this->getLanguage()->capitalizeAllNouns() ) {
                        # A <body> class is probably not the best way to do this . . .
                        $bodyClasses[] = 'capitalize-all-nouns';
                        }
                }
  
 -              $chunks[] = ResourceLoader::makeInlineScript(
 -                      ResourceLoader::makeConfigSetScript(
 -                              [ 'wgPageParseReport' => $this->limitReportData ],
 -                              true
 -                      )
 -              );
 -
                return self::combineWrappedStrings( $chunks );
        }
  
        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
                        'mediawiki.widgets.styles',
                ] );
        }
 -
 -      /**
 -       * @param array $data Data from ParserOutput::getLimitReportData()
 -       * @since 1.28
 -       */
 -      public function setLimitReportData( array $data ) {
 -              $this->limitReportData = $data;
 -      }
  }
diff --combined includes/WebRequest.php
@@@ -113,7 -113,7 +113,7 @@@ class WebRequest 
         */
        public static function getPathInfo( $want = 'all' ) {
                global $wgUsePathInfo;
 -              // PATH_INFO is mangled due to http://bugs.php.net/bug.php?id=31892
 +              // PATH_INFO is mangled due to https://bugs.php.net/bug.php?id=31892
                // And also by Apache 2.x, double slashes are converted to single slashes.
                // So we will use REQUEST_URI if possible.
                $matches = [];
                } elseif ( $wgUsePathInfo ) {
                        if ( isset( $_SERVER['ORIG_PATH_INFO'] ) && $_SERVER['ORIG_PATH_INFO'] != '' ) {
                                // Mangled PATH_INFO
 -                              // http://bugs.php.net/bug.php?id=31892
 +                              // https://bugs.php.net/bug.php?id=31892
                                // Also reported when ini_get('cgi.fix_pathinfo')==false
                                $matches['title'] = substr( $_SERVER['ORIG_PATH_INFO'], 1 );
  
         */
        private function getGPCVal( $arr, $name, $default ) {
                # PHP is so nice to not touch input data, except sometimes:
 -              # http://us2.php.net/variables.external#language.variables.external.dot-in-names
 +              # https://secure.php.net/variables.external#language.variables.external.dot-in-names
                # Work around PHP *feature* to avoid *bugs* elsewhere.
                $name = strtr( $name, '.', '_' );
                if ( isset( $arr[$name] ) ) {
                header( 'Content-Type: text/html' );
                $encUrl = htmlspecialchars( $url );
                echo <<<HTML
+ <!DOCTYPE html>
  <html>
  <head>
  <title>Security redirect</title>