(bug 19226) First line renders differently on many UI messages
[lhc/web/wiklou.git] / includes / OutputPage.php
index f6a5601..6b2a97d 100644 (file)
@@ -12,8 +12,11 @@ class OutputPage {
        var $mHTMLtitle = '', $mIsarticle = true, $mPrintable = false;
        var $mSubtitle = '', $mRedirect = '', $mStatusCode;
        var $mLastModified = '', $mETag = false;
-       var $mCategoryLinks = array(), $mLanguageLinks = array();
+       var $mCategoryLinks = array(), $mCategories = array(), $mLanguageLinks = array();
+
        var $mScripts = '', $mLinkColours, $mPageLinkTitle = '', $mHeadItems = array();
+       var $mInlineMsg = array();
+
        var $mTemplateIds = array();
 
        var $mAllowUserJs;
@@ -23,8 +26,9 @@ class OutputPage {
        var $mContainsOldMagic = 0, $mContainsNewMagic = 0;
        var $mIsArticleRelated = true;
        protected $mParserOptions = null; // lazy initialised, use parserOptions()
-       var $mShowFeedLinks = false;
-       var $mFeedLinksAppendQuery = false;
+
+       var $mFeedLinks = array();
+
        var $mEnableClientCache = true;
        var $mArticleBodyOnly = false;
 
@@ -35,6 +39,7 @@ class OutputPage {
        var $mParseWarnings = array();
        var $mSquidMaxage = 0;
        var $mRevisionId = null;
+       protected $mTitle = null;
 
        /**
         * An array of stylesheet filenames (relative from skins path), with options
@@ -45,6 +50,8 @@ class OutputPage {
 
        private $mIndexPolicy = 'index';
        private $mFollowPolicy = 'follow';
+       private $mVaryHeader = array( 'Accept-Encoding' => array('list-contains=gzip'),
+                                                                 'Cookie' => null );
 
        /**
         * Constructor
@@ -84,12 +91,26 @@ class OutputPage {
                array_push( $this->mMetatags, array( $name, $val ) );
        }
 
-       function addKeyword( $text ) { array_push( $this->mKeywords, $text ); }
-       function addScript( $script ) { $this->mScripts .= "\t\t".$script; }
-       
-       function addExtensionStyle( $url ) {
-               $linkarr = array( 'rel' => 'stylesheet', 'href' => $url, 'type' => 'text/css' );
-               array_push( $this->mExtStyles, $linkarr );
+       function addKeyword( $text ) {
+               if( is_array( $text )) {
+                       $this->mKeywords = array_merge( $this->mKeywords, $text );
+               } else {
+                       array_push( $this->mKeywords, $text );
+               }
+       }
+       function addScript( $script ) {
+               $this->mScripts .= $script . "\n";
+       }
+
+       /**
+        * Register and add a stylesheet from an extension directory.
+        * @param $url String path to sheet.  Provide either a full url (beginning
+        *             with 'http', etc) or a relative path from the document root
+        *             (beginning with '/').  Otherwise it behaves identically to
+        *             addStyle() and draws from the /skins folder.
+        */
+       public function addExtensionStyle( $url ) {
+               array_push( $this->mExtStyles, $url );
        }
 
        /**
@@ -103,18 +124,28 @@ class OutputPage {
                } else {
                        $path =  "{$wgStylePath}/common/{$file}";
                }
-               $this->addScript( "<script type=\"{$wgJsMimeType}\" src=\"$path?$wgStyleVersion\"></script>\n" );
+               $this->addScript( 
+                       Xml::element( 'script', 
+                               array(
+                                       'type' => $wgJsMimeType,
+                                       'src' => "$path?$wgStyleVersion",
+                               ),
+                               '', false
+                       )
+               );
        }
-       
+
        /**
         * Add a self-contained script tag with the given contents
         * @param string $script JavaScript text, no <script> tags
         */
        function addInlineScript( $script ) {
-               global $wgJsMimeType;
-               $this->mScripts .= "<script type=\"$wgJsMimeType\">/*<![CDATA[*/\n$script\n/*]]>*/</script>";
+               $this->mScripts .= Html::inlineScript( "\n$script\n" ) . "\n";
        }
 
+       /**
+        * Get all registered JS and CSS tags for the header.
+        */
        function getScript() {
                return $this->mScripts . $this->getHeadItems();
        }
@@ -137,13 +168,13 @@ class OutputPage {
 
        function setETag($tag) { $this->mETag = $tag; }
        function setArticleBodyOnly($only) { $this->mArticleBodyOnly = $only; }
-       function getArticleBodyOnly($only) { return $this->mArticleBodyOnly; }
+       function getArticleBodyOnly() { return $this->mArticleBodyOnly; }
 
        function addLink( $linkarr ) {
                # $linkarr should be an associative array of attributes. We'll escape on output.
                array_push( $this->mLinktags, $linkarr );
        }
-       
+
        # Get all links added by extensions
        function getExtStyle() {
                return $this->mExtStyles;
@@ -168,7 +199,7 @@ class OutputPage {
         */
        function checkLastModified( $timestamp ) {
                global $wgCachePages, $wgCacheEpoch, $wgUser, $wgRequest;
-               
+
                if ( !$timestamp || $timestamp == '19700101000000' ) {
                        wfDebug( __METHOD__ . ": CACHE DISABLED, NO TIMESTAMP\n" );
                        return false;
@@ -221,9 +252,9 @@ class OutputPage {
                }
                $clientHeaderTime = wfTimestamp( TS_MW, $clientHeaderTime );
 
-               wfDebug( __METHOD__ . ": client sent If-Modified-Since: " . 
+               wfDebug( __METHOD__ . ": client sent If-Modified-Since: " .
                        wfTimestamp( TS_ISO_8601, $clientHeaderTime ) . "\n", false );
-               wfDebug( __METHOD__ . ": effective Last-Modified: " . 
+               wfDebug( __METHOD__ . ": effective Last-Modified: " .
                        wfTimestamp( TS_ISO_8601, $maxModified ) . "\n", false );
                if( $clientHeaderTime < $maxModified ) {
                        wfDebug( __METHOD__ . ": STALE, $info\n", false );
@@ -231,8 +262,9 @@ class OutputPage {
                }
 
                # Not modified
-               # Give a 304 response code and disable body output 
+               # Give a 304 response code and disable body output
                wfDebug( __METHOD__ . ": NOT MODIFIED, $info\n", false );
+               ini_set('zlib.output_compression', 0);
                $wgRequest->response()->header( "HTTP/1.1 304 Not Modified" );
                $this->sendCacheControl();
                $this->disable();
@@ -264,22 +296,14 @@ class OutputPage {
         * @return null
         */
        public function setRobotPolicy( $policy ) {
-               $policy = explode( ',', $policy );
-               $policy = array_map( 'trim', $policy );
-
-               # The default policy is follow, so if nothing is said explicitly, we
-               # do that.
-               if( in_array( 'nofollow', $policy ) ) {
-                       $this->mFollowPolicy = 'nofollow';
-               } else {
-                       $this->mFollowPolicy = 'follow';
-               }
+               $policy = Article::formatRobotPolicy( $policy );
 
-               if( in_array( 'noindex', $policy ) ) {
-                       $this->mIndexPolicy = 'noindex';
-               } else {
-                       $this->mIndexPolicy = 'index';
-               }
+               if( isset( $policy['index'] ) ){
+                       $this->setIndexPolicy( $policy['index'] );
+               }
+               if( isset( $policy['follow'] ) ){
+                       $this->setFollowPolicy( $policy['follow'] );
+               }
        }
 
        /**
@@ -310,18 +334,49 @@ class OutputPage {
                }
        }
 
-       public function setHTMLTitle( $name ) { $this->mHTMLtitle = $name; }
+       /**
+        * "HTML title" means the contents of <title>. It is stored as plain, unescaped text and will be run through htmlspecialchars in the skin file.
+        */
+       public function setHTMLTitle( $name ) {
+               $this->mHTMLtitle = $name;
+       }
+
+       /**
+        * "Page title" means the contents of <h1>. It is stored as a valid HTML fragment.
+        * This function allows good tags like <sup> in the <h1> tag, but not bad tags like <script>.
+        * This function automatically sets <title> to the same content as <h1> but with all tags removed.
+        * Bad tags that were escaped in <h1> will still be escaped in <title>, and good tags like <i> will be dropped entirely.
+        */
        public function setPageTitle( $name ) {
                global $wgContLang;
-               $name = $wgContLang->convert( $name, true );
-               $this->mPagetitle = $name;
+
+               # change "<script>foo&bar</script>" to "&lt;script&gt;foo&amp;bar&lt;/script&gt;"
+               # but leave "<i>foobar</i>" alone
+               $nameWithTags = Sanitizer::normalizeCharReferences( Sanitizer::removeHTMLtags( $name ) );
+               $this->mPagetitle = $nameWithTags;
 
                $taction =  $this->getPageTitleActionText();
                if( !empty( $taction ) ) {
                        $name .= ' - '.$taction;
                }
 
-               $this->setHTMLTitle( wfMsg( 'pagetitle', $name ) );
+               # change "<i>foo&amp;bar</i>" to "foo&bar"
+               $this->setHTMLTitle( wfMsg( 'pagetitle', Sanitizer::stripAllTags( $nameWithTags ) ) );
+       }
+
+       public function setTitle( $t ) {
+               $this->mTitle = $t;
+       }
+
+       public function getTitle() {
+               if ( $this->mTitle instanceof Title ) {
+                       return $this->mTitle;
+               }
+               else {
+                       wfDebug( __METHOD__ . ' called and $mTitle is null. Return $wgTitle for sanity' );
+                       global $wgTitle;
+                       return $wgTitle;
+               }
        }
 
        public function getHTMLTitle() { return $this->mHTMLtitle; }
@@ -332,15 +387,40 @@ class OutputPage {
        public function isArticle() { return $this->mIsarticle; }
        public function setPrintable() { $this->mPrintable = true; }
        public function isPrintable() { return $this->mPrintable; }
-       public function setSyndicated( $show = true ) { $this->mShowFeedLinks = $show; }
-       public function isSyndicated() { return $this->mShowFeedLinks; }
-       public function setFeedAppendQuery( $val ) { $this->mFeedLinksAppendQuery = $val; }
        public function getFeedAppendQuery() { return $this->mFeedLinksAppendQuery; }
        public function setOnloadHandler( $js ) { $this->mOnloadHandler = $js; }
        public function getOnloadHandler() { return $this->mOnloadHandler; }
        public function disable() { $this->mDoNothing = true; }
        public function isDisabled() { return $this->mDoNothing; }
 
+       public function setSyndicated( $show = true ) {
+               if ( $show ) {
+                       $this->setFeedAppendQuery( false );
+               } else {
+                       $this->mFeedLinks = array();
+               }
+       }
+
+       public function setFeedAppendQuery( $val ) {
+               global $wgFeedClasses, $wgAdvertisedFeedTypes;
+
+               $this->mFeedLinks = array();
+
+               foreach ( $wgAdvertisedFeedTypes as $type ) {
+                       $query = "feed=$type";
+                       if ( is_string( $val ) ) {
+                               $query .= '&' . $val;
+                       }
+                       $this->mFeedLinks[$type] = $this->getTitle()->getLocalURL( $query );
+               }
+       }
+
+       public function addFeedLink( $format, $href ) {
+               $this->mFeedLinks[$format] = $href;
+       }
+
+       public function isSyndicated() { return count($this->mFeedLinks); }
+
        public function setArticleRelated( $v ) {
                $this->mIsArticleRelated = $v;
                if ( !$v ) {
@@ -368,6 +448,10 @@ class OutputPage {
                return $this->mCategoryLinks;
        }
 
+       public function getCategories() {
+               return $this->mCategories;
+       }
+
        /**
         * Add an array of categories, with names in the keys
         */
@@ -417,7 +501,8 @@ class OutputPage {
                                        if ( array_key_exists( $category, $categories ) )
                                                continue;
                                $text = $wgContLang->convertHtml( $title->getText() );
-                               $this->mCategoryLinks[$type][] = $sk->makeLinkObj( $title, $text );
+                               $this->mCategories[] = $title->getText();
+                               $this->mCategoryLinks[$type][] = $sk->link( $title, $text );
                        }
                }
        }
@@ -462,7 +547,7 @@ class OutputPage {
                $val = is_null( $revid ) ? null : intval( $revid );
                return wfSetVar( $this->mRevisionId, $val );
        }
-       
+
        public function getRevisionId() {
                return $this->mRevisionId;
        }
@@ -476,8 +561,8 @@ class OutputPage {
         * @param bool   $linestart
         */
        public function addWikiText( $text, $linestart = true ) {
-               global $wgTitle;
-               $this->addWikiTextTitle($text, $wgTitle, $linestart);
+               $title = $this->getTitle(); // Work arround E_STRICT
+               $this->addWikiTextTitle( $text, $title, $linestart );
        }
 
        public function addWikiTextWithTitle($text, &$title, $linestart = true) {
@@ -513,25 +598,13 @@ class OutputPage {
         * @param ParserOutput object &$parserOutput
         */
        public function addParserOutputNoText( &$parserOutput ) {
-               global $wgTitle, $wgExemptFromUserRobotsControl, $wgContentNamespaces;
+               global $wgExemptFromUserRobotsControl, $wgContentNamespaces;
 
                $this->mLanguageLinks += $parserOutput->getLanguageLinks();
                $this->addCategoryLinks( $parserOutput->getCategories() );
                $this->mNewSectionLink = $parserOutput->getNewSection();
                $this->mHideNewSectionLink = $parserOutput->getHideNewSection();
 
-               if( is_null( $wgExemptFromUserRobotsControl ) ) {
-                       $bannedNamespaces = $wgContentNamespaces;
-               } else {
-                       $bannedNamespaces = $wgExemptFromUserRobotsControl;
-               }
-               if( !in_array( $wgTitle->getNamespace(), $bannedNamespaces ) ) {
-                       # FIXME (bug 14900): This overrides $wgArticleRobotPolicies, and it
-                       # shouldn't
-                       $this->setIndexPolicy( $parserOutput->getIndexPolicy() );
-               }
-
-               $this->addKeywords( $parserOutput );
                $this->mParseWarnings = $parserOutput->getWarnings();
                if ( $parserOutput->getCacheTime() == -1 ) {
                        $this->enableClientCache( false );
@@ -547,10 +620,14 @@ class OutputPage {
                        }
                }
                // Page title
-               if( ( $dt = $parserOutput->getDisplayTitle() ) !== false )
+               $dt = $parserOutput->getDisplayTitle();
+               $title = $parserOutput->getTitleText();
+               if ( $dt !== false ) {
                        $this->setPageTitle( $dt );
-               else if ( ( $title = $parserOutput->getTitleText() ) != '' )
+               }
+               else if ( $title != '' ) {
                        $this->setPageTitle( $title );
+               }
 
                // Hooks registered in the object
                global $wgParserOutputHooks;
@@ -570,7 +647,7 @@ class OutputPage {
         */
        function addParserOutput( &$parserOutput ) {
                $this->addParserOutputNoText( $parserOutput );
-               $text = $parserOutput->getText();
+               $text = $parserOutput->getText();
                wfRunHooks( 'OutputPageBeforeHTML',array( &$this, &$text ) );
                $this->addHTML( $text );
        }
@@ -585,7 +662,7 @@ class OutputPage {
         * @deprecated Use Article::outputWikitext
         */
        public function addPrimaryWikiText( $text, $article, $cache = true ) {
-               global $wgParser, $wgUser;
+               global $wgParser;
 
                wfDeprecated( __METHOD__ );
 
@@ -596,7 +673,7 @@ class OutputPage {
                $popts->setTidy(false);
                if ( $cache && $article && $parserOutput->getCacheTime() != -1 ) {
                        $parserCache = ParserCache::singleton();
-                       $parserCache->save( $parserOutput, $article, $wgUser );
+                       $parserCache->save( $parserOutput, $article, $popts);
                }
 
                $this->addParserOutput( $parserOutput );
@@ -606,17 +683,16 @@ class OutputPage {
         * @deprecated use addWikiTextTidy()
         */
        public function addSecondaryWikiText( $text, $linestart = true ) {
-               global $wgTitle;
                wfDeprecated( __METHOD__ );
-               $this->addWikiTextTitleTidy($text, $wgTitle, $linestart);
+               $this->addWikiTextTitleTidy($text, $this->getTitle(), $linestart);
        }
 
        /**
         * Add wikitext with tidy enabled
         */
        public function addWikiTextTidy(  $text, $linestart = true ) {
-               global $wgTitle;
-               $this->addWikiTextTitleTidy($text, $wgTitle, $linestart);
+               $title = $this->getTitle();
+               $this->addWikiTextTitleTidy($text, $title, $linestart);
        }
 
 
@@ -640,13 +716,13 @@ class OutputPage {
         * @param bool   $interface ??
         */
        public function parse( $text, $linestart = true, $interface = false ) {
-               global $wgParser, $wgTitle;
-               if( is_null( $wgTitle ) ) {
-                       throw new MWException( 'Empty $wgTitle in ' . __METHOD__ );
+               global $wgParser;
+               if( is_null( $this->getTitle() ) ) {
+                       throw new MWException( 'Empty $mTitle in ' . __METHOD__ );
                }
                $popts = $this->parserOptions();
                if ( $interface) { $popts->setInterfaceMessage(true); }
-               $parserOutput = $wgParser->parse( $text, $wgTitle, $popts,
+               $parserOutput = $wgParser->parse( $text, $this->getTitle(), $popts,
                        $linestart, true, $this->mRevisionId );
                if ( $interface) { $popts->setInterfaceMessage(false); }
                return $parserOutput->getText();
@@ -668,12 +744,15 @@ class OutputPage {
         * @param Article $article
         * @param User    $user
         *
+        * @deprecated
+        *
         * @return bool True if successful, else false.
         */
-       public function tryParserCache( &$article, $user ) {
-               $parserCache = ParserCache::singleton();
-               $parserOutput = $parserCache->get( $article, $user );
-               if ( $parserOutput !== false ) {
+       public function tryParserCache( &$article ) {
+               wfDeprecated( __METHOD__ );
+               $parserOutput = ParserCache::singleton()->get( $article, $article->getParserOptions() );
+
+               if ($parserOutput !== false) {
                        $this->addParserOutput( $parserOutput );
                        return true;
                } else {
@@ -741,35 +820,83 @@ class OutputPage {
                return false;
        }
 
+       public function addVaryHeader( $header, $option = null ) {
+               if ( !array_key_exists( $header, $this->mVaryHeader ) ) {
+                       $this->mVaryHeader[$header] = $option;
+               }
+               elseif( is_array( $option ) ) {
+                       if( is_array( $this->mVaryHeader[$header] ) ) {
+                               $this->mVaryHeader[$header] = array_merge( $this->mVaryHeader[$header], $option );
+                       }
+                       else {
+                               $this->mVaryHeader[$header] = $option;
+                       }
+               }
+               $this->mVaryHeader[$header] = array_unique( $this->mVaryHeader[$header] );
+       }
+
        /** Get a complete X-Vary-Options header */
        public function getXVO() {
                $cvCookies = $this->getCacheVaryCookies();
-               $xvo = 'X-Vary-Options: Accept-Encoding;list-contains=gzip,Cookie;';
-               $first = true;
+               
+               $cookiesOption = array();
                foreach ( $cvCookies as $cookieName ) {
-                       if ( $first ) {
-                               $first = false;
-                       } else {
-                               $xvo .= ';';
-                       }
-                       $xvo .= 'string-contains=' . $cookieName;
+                       $cookiesOption[] = 'string-contains=' . $cookieName;
                }
+               $this->addVaryHeader( 'Cookie', $cookiesOption );
+               
+               $headers = array();
+               foreach( $this->mVaryHeader as $header => $option ) {
+                       $newheader = $header;
+                       if( is_array( $option ) )
+                               $newheader .= ';' . implode( ';', $option );
+                       $headers[] = $newheader;
+               }
+               $xvo = 'X-Vary-Options: ' . implode( ',', $headers );
+               
                return $xvo;
        }
 
+       /** bug 21672: Add Accept-Language to Vary and XVO headers
+               if there's no 'variant' parameter existed in GET.
+               
+               For example:
+                /w/index.php?title=Main_page should always be served; but
+                /w/index.php?title=Main_page&variant=zh-cn should never be served.
+               
+               patched by Liangent and Philip */
+       function addAcceptLanguage() {
+               global $wgRequest, $wgContLang;
+               if( !$wgRequest->getCheck('variant') && $wgContLang->hasVariants() ) {
+                       $variants = $wgContLang->getVariants();
+                       $aloption = array();
+                       foreach ( $variants as $variant ) {
+                               if( $variant === $wgContLang->getCode() )
+                                       continue;
+                               else
+                                       $aloption[] = "string-contains=$variant";
+                       }
+                       $this->addVaryHeader( 'Accept-Language', $aloption );
+               }
+       }
+
        public function sendCacheControl() {
-               global $wgUseSquid, $wgUseESI, $wgUseETag, $wgSquidMaxage, $wgRequest;
+               global $wgUseSquid, $wgUseESI, $wgUseETag, $wgSquidMaxage, $wgRequest, $wgUseXVO;
 
                $response = $wgRequest->response();
                if ($wgUseETag && $this->mETag)
                        $response->header("ETag: $this->mETag");
 
+               $this->addAcceptLanguage();
+
                # don't serve compressed data to clients who can't handle it
                # maintain different caches for logged-in users and non-logged in ones
-               $response->header( 'Vary: Accept-Encoding, Cookie' );
+               $response->header( 'Vary: ' . join( ', ', array_keys( $this->mVaryHeader ) ) );
 
-               # Add an X-Vary-Options header for Squid with Wikimedia patches
-               $response->header( $this->getXVO() );
+               if ( $wgUseXVO ) {
+                       # Add an X-Vary-Options header for Squid with Wikimedia patches
+                       $response->header( $this->getXVO() );
+               }
 
                if( !$this->uncacheableBecauseRequestVars() && $this->mEnableClientCache ) {
                        if( $wgUseSquid && session_id() == '' &&
@@ -813,6 +940,66 @@ class OutputPage {
                        $response->header( 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate' );
                        $response->header( 'Pragma: no-cache' );
                }
+               wfRunHooks('CacheHeadersAfterSet', array( $this ) );
+       }
+
+       /**
+        * Get the message associed with the HTTP response code $code
+        *
+        * @param $code Integer: status code
+        * @return String or null: message or null if $code is not in the list of
+        *         messages
+        */
+       public static function getStatusMessage( $code ) {
+               static $statusMessage = array(
+                       100 => 'Continue',
+                       101 => 'Switching Protocols',
+                       102 => 'Processing',
+                       200 => 'OK',
+                       201 => 'Created',
+                       202 => 'Accepted',
+                       203 => 'Non-Authoritative Information',
+                       204 => 'No Content',
+                       205 => 'Reset Content',
+                       206 => 'Partial Content',
+                       207 => 'Multi-Status',
+                       300 => 'Multiple Choices',
+                       301 => 'Moved Permanently',
+                       302 => 'Found',
+                       303 => 'See Other',
+                       304 => 'Not Modified',
+                       305 => 'Use Proxy',
+                       307 => 'Temporary Redirect',
+                       400 => 'Bad Request',
+                       401 => 'Unauthorized',
+                       402 => 'Payment Required',
+                       403 => 'Forbidden',
+                       404 => 'Not Found',
+                       405 => 'Method Not Allowed',
+                       406 => 'Not Acceptable',
+                       407 => 'Proxy Authentication Required',
+                       408 => 'Request Timeout',
+                       409 => 'Conflict',
+                       410 => 'Gone',
+                       411 => 'Length Required',
+                       412 => 'Precondition Failed',
+                       413 => 'Request Entity Too Large',
+                       414 => 'Request-URI Too Large',
+                       415 => 'Unsupported Media Type',
+                       416 => 'Request Range Not Satisfiable',
+                       417 => 'Expectation Failed',
+                       422 => 'Unprocessable Entity',
+                       423 => 'Locked',
+                       424 => 'Failed Dependency',
+                       500 => 'Internal Server Error',
+                       501 => 'Not Implemented',
+                       502 => 'Bad Gateway',
+                       503 => 'Service Unavailable',
+                       504 => 'Gateway Timeout',
+                       505 => 'HTTP Version Not Supported',
+                       507 => 'Insufficient Storage'
+               );
+               return isset( $statusMessage[$code] ) ? $statusMessage[$code] : null;
        }
 
        /**
@@ -822,93 +1009,41 @@ class OutputPage {
        public function output() {
                global $wgUser, $wgOutputEncoding, $wgRequest;
                global $wgContLanguageCode, $wgDebugRedirects, $wgMimeType;
-               global $wgJsMimeType, $wgUseAjax, $wgAjaxWatch;
+               global $wgUseAjax, $wgAjaxWatch;
                global $wgEnableMWSuggest, $wgUniversalEditButton;
-               global $wgArticle, $wgTitle;
+               global $wgArticle;
 
                if( $this->mDoNothing ){
                        return;
                }
-
                wfProfileIn( __METHOD__ );
-
-               if ( '' != $this->mRedirect ) {
+               if ( $this->mRedirect != '' ) {
                        # Standards require redirect URLs to be absolute
                        $this->mRedirect = wfExpandUrl( $this->mRedirect );
-                       if( $this->mRedirectCode == '301') {
+                       if( $this->mRedirectCode == '301' || $this->mRedirectCode == '303' ) {
                                if( !$wgDebugRedirects ) {
-                                       $wgRequest->response()->header("HTTP/1.1 {$this->mRedirectCode} Moved Permanently");
+                                       $message = self::getStatusMessage( $this->mRedirectCode );
+                                       $wgRequest->response()->header( "HTTP/1.1 {$this->mRedirectCode} $message" );
                                }
                                $this->mLastModified = wfTimestamp( TS_RFC2822 );
                        }
-
                        $this->sendCacheControl();
 
-                       $wgRequest->response()->header("Content-Type: text/html; charset=utf-8");
+                       $wgRequest->response()->header( "Content-Type: text/html; charset=utf-8" );
                        if( $wgDebugRedirects ) {
                                $url = htmlspecialchars( $this->mRedirect );
                                print "<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 {
-                               $wgRequest->response()->header( 'Location: '.$this->mRedirect );
+                               $wgRequest->response()->header( 'Location: ' . $this->mRedirect );
                        }
                        wfProfileOut( __METHOD__ );
                        return;
-               }
-               elseif ( $this->mStatusCode )
-               {
-                       $statusMessage = array(
-                               100 => 'Continue',
-                               101 => 'Switching Protocols',
-                               102 => 'Processing',
-                               200 => 'OK',
-                               201 => 'Created',
-                               202 => 'Accepted',
-                               203 => 'Non-Authoritative Information',
-                               204 => 'No Content',
-                               205 => 'Reset Content',
-                               206 => 'Partial Content',
-                               207 => 'Multi-Status',
-                               300 => 'Multiple Choices',
-                               301 => 'Moved Permanently',
-                               302 => 'Found',
-                               303 => 'See Other',
-                               304 => 'Not Modified',
-                               305 => 'Use Proxy',
-                               307 => 'Temporary Redirect',
-                               400 => 'Bad Request',
-                               401 => 'Unauthorized',
-                               402 => 'Payment Required',
-                               403 => 'Forbidden',
-                               404 => 'Not Found',
-                               405 => 'Method Not Allowed',
-                               406 => 'Not Acceptable',
-                               407 => 'Proxy Authentication Required',
-                               408 => 'Request Timeout',
-                               409 => 'Conflict',
-                               410 => 'Gone',
-                               411 => 'Length Required',
-                               412 => 'Precondition Failed',
-                               413 => 'Request Entity Too Large',
-                               414 => 'Request-URI Too Large',
-                               415 => 'Unsupported Media Type',
-                               416 => 'Request Range Not Satisfiable',
-                               417 => 'Expectation Failed',
-                               422 => 'Unprocessable Entity',
-                               423 => 'Locked',
-                               424 => 'Failed Dependency',
-                               500 => 'Internal Server Error',
-                               501 => 'Not Implemented',
-                               502 => 'Bad Gateway',
-                               503 => 'Service Unavailable',
-                               504 => 'Gateway Timeout',
-                               505 => 'HTTP Version Not Supported',
-                               507 => 'Insufficient Storage'
-                       );
-
-                       if ( $statusMessage[$this->mStatusCode] )
-                               $wgRequest->response()->header( 'HTTP/1.1 ' . $this->mStatusCode . ' ' . $statusMessage[$this->mStatusCode] );
+               } elseif ( $this->mStatusCode ) {
+                       $message = self::getStatusMessage( $this->mStatusCode );
+                       if ( $message )
+                               $wgRequest->response()->header( 'HTTP/1.1 ' . $this->mStatusCode . ' ' . $message );
                }
 
                $sk = $wgUser->getSkin();
@@ -921,35 +1056,36 @@ class OutputPage {
                        if( $wgAjaxWatch && $wgUser->isLoggedIn() ) {
                                $this->addScriptFile( 'ajaxwatch.js' );
                        }
-                       
+
                        if ( $wgEnableMWSuggest && !$wgUser->getOption( 'disablesuggest', false ) ){
                                $this->addScriptFile( 'mwsuggest.js' );
                        }
                }
-               
+
                if( $wgUser->getBoolOption( 'editsectiononrightclick' ) ) {
                        $this->addScriptFile( 'rightclickedit.js' );
                }
 
                if( $wgUniversalEditButton ) {
-                       if( isset( $wgArticle ) && isset( $wgTitle ) && $wgTitle->quickUserCan( 'edit' )
-                               && ( $wgTitle->exists() || $wgTitle->quickUserCan( 'create' ) ) ) {
+                       if( isset( $wgArticle ) && $this->getTitle() && $this->getTitle()->quickUserCan( 'edit' )
+                               && ( $this->getTitle()->exists() || $this->getTitle()->quickUserCan( 'create' ) ) ) {
                                // Original UniversalEditButton
+                               $msg = wfMsg('edit');
                                $this->addLink( array(
                                        'rel' => 'alternate',
                                        'type' => 'application/x-wiki',
-                                       'title' => wfMsg( 'edit' ),
-                                       'href' => $wgTitle->getLocalURL( 'action=edit' )
+                                       'title' => $msg,
+                                       'href' => $this->getTitle()->getLocalURL( 'action=edit' )
                                ) );
                                // Alternate edit link
                                $this->addLink( array(
                                        'rel' => 'edit',
-                                       'title' => wfMsg( 'edit' ),
-                                       'href' => $wgTitle->getLocalURL( 'action=edit' )
+                                       'title' => $msg,
+                                       'href' => $this->getTitle()->getLocalURL( 'action=edit' )
                                ) );
                        }
                }
-               
+
                # Buffer output; final headers may depend on later processing
                ob_start();
 
@@ -974,8 +1110,9 @@ class OutputPage {
        }
 
        /**
-        * @todo document
-        * @param string $ins
+        * Actually output something with print(). Performs an iconv to the
+        * output encoding, if needed.
+        * @param string $ins The string to output
         */
        public function out( $ins ) {
                global $wgInputEncoding, $wgOutputEncoding, $wgContLang;
@@ -993,7 +1130,7 @@ class OutputPage {
         */
        public static function setEncodings() {
                global $wgInputEncoding, $wgOutputEncoding;
-               global $wgUser, $wgContLang;
+               global $wgContLang;
 
                $wgInputEncoding = strtolower( $wgInputEncoding );
 
@@ -1022,7 +1159,7 @@ class OutputPage {
         * @return nothing
         */
        function blockedPage( $return = true ) {
-               global $wgUser, $wgContLang, $wgTitle, $wgLang;
+               global $wgUser, $wgContLang, $wgLang;
 
                $this->setPageTitle( wfMsg( 'blockedtitle' ) );
                $this->setRobotPolicy( 'noindex,nofollow' );
@@ -1072,7 +1209,7 @@ class OutputPage {
 
                # Don't auto-return to special pages
                if( $return ) {
-                       $return = $wgTitle->getNamespace() > -1 ? $wgTitle : NULL;
+                       $return = $this->getTitle()->getNamespace() > -1 ? $this->getTitle() : null;
                        $this->returnToMain( null, $return );
                }
        }
@@ -1085,9 +1222,8 @@ class OutputPage {
         * @param array $params Message parameters
         */
        public function showErrorPage( $title, $msg, $params = array() ) {
-               global $wgTitle;
-               if ( isset($wgTitle) ) {
-                       $this->mDebugtext .= 'Original title: ' . $wgTitle->getPrefixedText() . "\n";
+               if ( $this->getTitle() ) {
+                       $this->mDebugtext .= 'Original title: ' . $this->getTitle()->getPrefixedText() . "\n";
                }
                $this->setPageTitle( wfMsg( $title ) );
                $this->setHTMLTitle( wfMsg( 'errorpagetitle' ) );
@@ -1111,10 +1247,8 @@ class OutputPage {
         */
        public function showPermissionsErrorPage( $errors, $action = null )
        {
-               global $wgTitle;
-
                $this->mDebugtext .= 'Original title: ' .
-               $wgTitle->getPrefixedText() . "\n";
+               $this->getTitle()->getPrefixedText() . "\n";
                $this->setPageTitle( wfMsg( 'permissionserrors' ) );
                $this->setHTMLTitle( wfMsg( 'permissionserrors' ) );
                $this->setRobotPolicy( 'noindex,nofollow' );
@@ -1154,7 +1288,7 @@ class OutputPage {
         * @param string $permission key required
         */
        public function permissionRequired( $permission ) {
-               global $wgUser, $wgLang;
+               global $wgLang;
 
                $this->setPageTitle( wfMsg( 'badaccess' ) );
                $this->setHTMLTitle( wfMsg( 'errorpagetitle' ) );
@@ -1194,7 +1328,7 @@ class OutputPage {
         * Produce the stock "please login to use the wiki" page
         */
        public function loginToUse() {
-               global $wgUser, $wgTitle, $wgContLang;
+               global $wgUser, $wgContLang;
 
                if( $wgUser->isLoggedIn() ) {
                        $this->permissionRequired( 'read' );
@@ -1209,9 +1343,15 @@ class OutputPage {
                $this->setArticleFlag( false );
 
                $loginTitle = SpecialPage::getTitleFor( 'Userlogin' );
-               $loginLink = $skin->makeKnownLinkObj( $loginTitle, wfMsgHtml( 'loginreqlink' ), 'returnto=' . $wgTitle->getPrefixedUrl() );
+               $loginLink = $skin->link(
+                       $loginTitle,
+                       wfMsgHtml( 'loginreqlink' ),
+                       array(),
+                       array( 'returnto' => $this->getTitle()->getPrefixedText() ),
+                       array( 'known', 'noclasses' )
+               );
                $this->addHTML( wfMsgWikiHtml( 'loginreqpagetext', $loginLink ) );
-               $this->addHTML( "\n<!--" . $wgTitle->getPrefixedUrl() . "-->" );
+               $this->addHTML( "\n<!--" . $this->getTitle()->getPrefixedUrl() . "-->" );
 
                # Don't return to the main page if the user can't read it
                # otherwise we'll end up in a pointless loop
@@ -1234,7 +1374,7 @@ class OutputPage {
                        $text = wfMsgNoTrans( 'permissionserrorstext', count($errors)). "\n\n";
                } else {
                        global $wgLang;
-                       $action_desc = wfMsg( "action-$action" );
+                       $action_desc = wfMsgNoTrans( "action-$action" );
                        $text = wfMsgNoTrans( 'permissionserrorstext-withaction', count($errors), $action_desc ) . "\n\n";
                }
 
@@ -1249,7 +1389,7 @@ class OutputPage {
                        }
                        $text .= '</ul>';
                } else {
-                       $text .= '<div class="permissions-errors">' . call_user_func_array( 'wfMsgNoTrans', reset( $errors ) ) . '</div>';
+                       $text .= "<div class=\"permissions-errors\">\n" . call_user_func_array( 'wfMsgNoTrans', reset( $errors ) ) . "\n</div>";
                }
 
                return $text;
@@ -1275,7 +1415,7 @@ class OutputPage {
         * @param array  $reasons   List of reasons for this error, as returned by Title::getUserPermissionsErrors().
         */
        public function readOnlyPage( $source = null, $protected = false, $reasons = array(), $action = null ) {
-               global $wgUser, $wgTitle;
+               global $wgUser;
                $skin = $wgUser->getSkin();
 
                $this->setRobotPolicy( 'noindex,nofollow' );
@@ -1291,7 +1431,18 @@ class OutputPage {
                        // Permissions error
                        if( $source ) {
                                $this->setPageTitle( wfMsg( 'viewsource' ) );
-                               $this->setSubtitle( wfMsg( 'viewsourcefor', $skin->makeKnownLinkObj( $wgTitle ) ) );
+                               $this->setSubtitle(
+                                       wfMsg(
+                                               'viewsourcefor',
+                                               $skin->link(
+                                                       $this->getTitle(),
+                                                       null,
+                                                       array(),
+                                                       array(),
+                                                       array( 'known', 'noclasses' )
+                                               )
+                                       )
+                               );
                        } else {
                                $this->setPageTitle( wfMsg( 'badaccess' ) );
                        }
@@ -1300,25 +1451,25 @@ class OutputPage {
                        // Wiki is read only
                        $this->setPageTitle( wfMsg( 'readonly' ) );
                        $reason = wfReadOnlyReason();
-                       $this->wrapWikiMsg( '<div class="mw-readonly-error">$1</div>', array( 'readonlytext', $reason ) );
+                       $this->wrapWikiMsg( '<div class="mw-readonly-error">\n$1</div>', array( 'readonlytext', $reason ) );
                }
 
                // Show source, if supplied
                if( is_string( $source ) ) {
                        $this->addWikiMsg( 'viewsourcetext' );
-                       $text = Xml::openElement( 'textarea',
-                                               array( 'id'   => 'wpTextbox1',
-                                                      'name' => 'wpTextbox1',
-                                                      'cols' => $wgUser->getOption( 'cols' ),
-                                                      'rows' => $wgUser->getOption( 'rows' ),
-                                                      'readonly' => 'readonly' ) );
-                       $text .= htmlspecialchars( $source );
-                       $text .= Xml::closeElement( 'textarea' );
-                       $this->addHTML( $text );
+
+                       $params = array(
+                               'id'   => 'wpTextbox1',
+                               'name' => 'wpTextbox1',
+                               'cols' => $wgUser->getOption( 'cols' ),
+                               'rows' => $wgUser->getOption( 'rows' ),
+                               'readonly' => 'readonly'
+                       );
+                       $this->addHTML( Html::element( 'textarea', $params, $source ) );
 
                        // Show templates used by this article
                        $skin = $wgUser->getSkin();
-                       $article = new Article( $wgTitle );
+                       $article = new Article( $this->getTitle() );
                        $this->addHTML( "<div class='templatesUsed'>
 {$skin->formatTemplates( $article->getUsedTemplates() )}
 </div>
@@ -1328,8 +1479,8 @@ class OutputPage {
                # If the title doesn't exist, it's fairly pointless to print a return
                # link to it.  After all, you just tried editing it and couldn't, so
                # what's there to do there?
-               if( $wgTitle->exists() ) {
-                       $this->returnToMain( null, $wgTitle );
+               if( $this->getTitle()->exists() ) {
+                       $this->returnToMain( null, $this->getTitle() );
                }
        }
 
@@ -1401,30 +1552,37 @@ class OutputPage {
        /**
         * Add a "return to" link pointing to a specified title
         *
-        * @param Title $title Title to link
+        * @param $title Title to link
+        * @param $query String: query string
         */
-       public function addReturnTo( $title ) {
+       public function addReturnTo( $title, $query = array() ) {
                global $wgUser;
                $this->addLink( array( 'rel' => 'next', 'href' => $title->getFullUrl() ) );
-               $link = wfMsg( 'returnto', $wgUser->getSkin()->makeLinkObj( $title ) );
-               $this->addHTML( "<p>{$link}</p>\n" );
+               $link = wfMsgHtml( 'returnto', $wgUser->getSkin()->link(
+                       $title, null, array(), $query ) );
+               $this->addHTML( "<p id=\"mw-returnto\">{$link}</p>\n" );
        }
 
        /**
         * Add a "return to" link pointing to a specified title,
         * or the title indicated in the request, or else the main page
         *
-        * @param null $unused No longer used
-        * @param Title $returnto Title to return to
+        * @param $unused No longer used
+        * @param $returnto Title or String to return to
+        * @param $returntoquery String: query string for the return to link
         */
-       public function returnToMain( $unused = null, $returnto = NULL ) {
+       public function returnToMain( $unused = null, $returnto = null, $returntoquery = null ) {
                global $wgRequest;
 
-               if ( $returnto == NULL ) {
+               if ( $returnto == null ) {
                        $returnto = $wgRequest->getText( 'returnto' );
                }
 
-               if ( '' === $returnto ) {
+               if ( $returntoquery == null ) {
+                       $returntoquery = $wgRequest->getText( 'returntoquery' );
+               }
+
+               if ( $returnto === '' ) {
                        $returnto = Title::newMainPage();
                }
 
@@ -1437,87 +1595,138 @@ class OutputPage {
                        $titleObj = Title::newMainPage();
                }
 
-               $this->addReturnTo( $titleObj );
-       }
-
-       /**
-        * This function takes the title (first item of mGoodLinks), categories, existing and broken links for the page
-        * and uses the first 10 of them for META keywords
-        *
-        * @param ParserOutput &$parserOutput
-        */
-       private function addKeywords( &$parserOutput ) {
-               global $wgTitle;
-               $this->addKeyword( $wgTitle->getPrefixedText() );
-               $count = 1;
-               $links2d =& $parserOutput->getLinks();
-               if ( !is_array( $links2d ) ) {
-                       return;
-               }
-               foreach ( $links2d as $dbkeys ) {
-                       foreach( $dbkeys as $dbkey => $unused ) {
-                               $this->addKeyword( $dbkey );
-                               if ( ++$count > 10 ) {
-                                       break 2;
-                               }
-                       }
-               }
+               $this->addReturnTo( $titleObj, $returntoquery );
        }
 
        /**
         * @return string The doctype, opening <html>, and head element.
+        *
+        * @param $sk Skin The given Skin
         */
-       public function headElement( Skin $sk ) {
+       public function headElement( Skin $sk, $includeStyle = true ) {
                global $wgDocType, $wgDTD, $wgContLanguageCode, $wgOutputEncoding, $wgMimeType;
-               global $wgXhtmlDefaultNamespace, $wgXhtmlNamespaces;
-               global $wgUser, $wgContLang, $wgUseTrackbacks, $wgTitle, $wgStyleVersion;
+               global $wgXhtmlDefaultNamespace, $wgXhtmlNamespaces, $wgHtml5Version;
+               global $wgContLang, $wgUseTrackbacks, $wgStyleVersion, $wgHtml5, $wgWellFormedXml;
 
-               $this->addMeta( "http:Content-type", "$wgMimeType; charset={$wgOutputEncoding}" );
-               $this->addStyle( 'common/wikiprintable.css', 'print' );
+               $this->addMeta( "http:Content-Type", "$wgMimeType; charset={$wgOutputEncoding}" );
+               if ( $sk->commonPrintStylesheet() ) {
+                       $this->addStyle( 'common/wikiprintable.css', 'print' );
+               }
                $sk->setupUserCss( $this );
 
                $ret = '';
 
                if( $wgMimeType == 'text/xml' || $wgMimeType == 'application/xhtml+xml' || $wgMimeType == 'application/xml' ) {
-                       $ret .= "<?xml version=\"1.0\" encoding=\"$wgOutputEncoding\" ?>\n";
+                       $ret .= "<?xml version=\"1.0\" encoding=\"$wgOutputEncoding\" ?" . ">\n";
                }
 
-               $ret .= "<!DOCTYPE html PUBLIC \"$wgDocType\"\n        \"$wgDTD\">\n";
-
-               if ( '' == $this->getHTMLTitle() ) {
+               if ( $this->getHTMLTitle() == '' ) {
                        $this->setHTMLTitle(  wfMsg( 'pagetitle', $this->getPageTitle() ));
                }
 
-               $rtl = $wgContLang->isRTL() ? " dir='RTL'" : '';
-               $ret .= "<html xmlns=\"{$wgXhtmlDefaultNamespace}\" ";
-               foreach($wgXhtmlNamespaces as $tag => $ns) {
-                       $ret .= "xmlns:{$tag}=\"{$ns}\" ";
+               $dir = $wgContLang->getDir();
+
+               if ( $wgHtml5 ) {
+                       if ( $wgWellFormedXml ) {
+                               # Unknown elements and attributes are okay in XML, but unknown
+                               # named entities are well-formedness errors and will break XML
+                               # parsers.  Thus we need a doctype that gives us appropriate
+                               # entity definitions.  The HTML5 spec permits four legacy
+                               # doctypes as obsolete but conforming, so let's pick one of
+                               # those, although it makes our pages look like XHTML1 Strict.
+                               # Isn't compatibility great?
+                               $ret .= "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n";
+                       } else {
+                               # Much saner.
+                               $ret .= "<!doctype html>\n";
+                       }
+                       $ret .= "<html lang=\"$wgContLanguageCode\" dir=\"$dir\" ";
+                       if ( $wgHtml5Version ) $ret .= " version=\"$wgHtml5Version\" ";
+                       $ret .= ">\n";
+               } else {
+                       $ret .= "<!DOCTYPE html PUBLIC \"$wgDocType\" \"$wgDTD\">\n";
+                       $ret .= "<html xmlns=\"{$wgXhtmlDefaultNamespace}\" ";
+                       foreach($wgXhtmlNamespaces as $tag => $ns) {
+                               $ret .= "xmlns:{$tag}=\"{$ns}\" ";
+                       }
+                       $ret .= "xml:lang=\"$wgContLanguageCode\" lang=\"$wgContLanguageCode\" dir=\"$dir\">\n";
                }
-               $ret .= "xml:lang=\"$wgContLanguageCode\" lang=\"$wgContLanguageCode\" $rtl>\n";
-               $ret .= "<head>\n<title>" . htmlspecialchars( $this->getHTMLTitle() ) . "</title>\n\t\t";
-               $ret .= implode( "\t\t", array(
+
+               $ret .= "<head>\n";
+               $ret .= "<title>" . htmlspecialchars( $this->getHTMLTitle() ) . "</title>\n";
+               $ret .= implode( "\n", array(
                        $this->getHeadLinks(),
                        $this->buildCssLinks(),
-                       $sk->getHeadScripts( $this->mAllowUserJs ),
-                       $this->mScripts,
+                       $this->getHeadScripts( $sk ),
                        $this->getHeadItems(),
                ));
                if( $sk->usercss ){
-                       $ret .= "<style type='text/css'>{$sk->usercss}</style>";
+                       $ret .= Html::inlineStyle( $sk->usercss );
                }
 
                if ($wgUseTrackbacks && $this->isArticleRelated())
-                       $ret .= $wgTitle->trackbackRDF();
+                       $ret .= $this->getTitle()->trackbackRDF();
 
                $ret .= "</head>\n";
                return $ret;
        }
-       
+
+       /*
+        * gets the global variables and mScripts
+        *
+        * also adds userjs to the end if enabled:
+       */
+       function getHeadScripts( Skin $sk ) {
+               global $wgUser, $wgRequest, $wgJsMimeType, $wgUseSiteJs;
+               global $wgStylePath, $wgStyleVersion;
+
+               $scripts = Skin::makeGlobalVariablesScript( $sk->getSkinName() );
+               $scripts .= Html::linkedScript( "{$wgStylePath}/common/wikibits.js?$wgStyleVersion" );
+
+               //add site JS if enabled:
+               if( $wgUseSiteJs ) {
+                       $jsCache = $wgUser->isLoggedIn() ? '&smaxage=0' : '';
+                       $this->addScriptFile(  Skin::makeUrl( '-',
+                                       "action=raw$jsCache&gen=js&useskin=" .
+                                       urlencode( $sk->getSkinName() )
+                                       )
+                               );
+               }
+
+               //add user js if enabled:
+               if( $this->isUserJsAllowed() && $wgUser->isLoggedIn() ) {
+                       $action = $wgRequest->getVal( 'action', 'view' );
+                       if( $this->mTitle && $this->mTitle->isJsSubpage() and $sk->userCanPreview( $action ) ) {
+                               # XXX: additional security check/prompt?
+                               $this->addInlineScript( $wgRequest->getText( 'wpTextbox1' ) );
+                       } else {
+                               $userpage = $wgUser->getUserPage();
+                               $userjs = Skin::makeUrl(
+                                       $userpage->getPrefixedText() . '/' . $sk->getSkinName() . '.js',
+                                       'action=raw&ctype=' . $wgJsMimeType );
+                               $this->addScriptFile( $userjs );
+                       }
+               }
+
+               $scripts .= "\n" . $this->mScripts;
+               return $scripts;
+       }
+
        protected function addDefaultMeta() {
-               global $wgVersion;
-               $this->addMeta( 'http:Content-Style-Type', 'text/css' ); //bug 15835
+               global $wgVersion, $wgHtml5;
+
+               static $called = false;
+               if ( $called ) {
+                       # Don't run this twice
+                       return;
+               }
+               $called = true;
+
+               if ( !$wgHtml5 ) {
+                       $this->addMeta( 'http:Content-Style-Type', 'text/css' ); //bug 15835
+               }
                $this->addMeta( 'generator', "MediaWiki $wgVersion" );
-               
+
                $p = "{$this->mIndexPolicy},{$this->mFollowPolicy}";
                if( $p !== 'index,follow' ) {
                        // http://www.robotstxt.org/wc/meta-user.html
@@ -1527,7 +1736,7 @@ class OutputPage {
 
                if ( count( $this->mKeywords ) > 0 ) {
                        $strip = array(
-                               "/<.*?>/" => '',
+                               "/<.*?" . ">/" => '',
                                "/_/" => ' '
                        );
                        $this->addMeta( 'keywords', preg_replace(array_keys($strip), array_values($strip),implode( ",", $this->mKeywords ) ) );
@@ -1539,12 +1748,12 @@ class OutputPage {
         */
        public function getHeadLinks() {
                global $wgRequest, $wgFeed;
-               
+
                // Ideally this should happen earlier, somewhere. :P
                $this->addDefaultMeta();
-               
+
                $tags = array();
-               
+
                foreach ( $this->mMetatags as $tag ) {
                        if ( 0 == strcasecmp( 'http:', substr( $tag[0], 0, 5 ) ) ) {
                                $a = 'http-equiv';
@@ -1552,17 +1761,16 @@ class OutputPage {
                        } else {
                                $a = 'name';
                        }
-                       $tags[] = Xml::element( 'meta',
+                       $tags[] = Html::element( 'meta',
                                array(
                                        $a => $tag[0],
                                        'content' => $tag[1] ) );
                }
                foreach ( $this->mLinktags as $tag ) {
-                       $tags[] = Xml::element( 'link', $tag );
+                       $tags[] = Html::element( 'link', $tag );
                }
 
                if( $wgFeed ) {
-                       global $wgTitle;
                        foreach( $this->getSyndicationLinks() as $format => $link ) {
                                # Use the page name for the title (accessed through $wgTitle since
                                # there's no other way).  In principle, this could lead to issues
@@ -1572,30 +1780,30 @@ class OutputPage {
                                $tags[] = $this->feedLink(
                                        $format,
                                        $link,
-                                       wfMsg( "page-{$format}-feed", $wgTitle->getPrefixedText() ) ); # Used messages: 'page-rss-feed' and 'page-atom-feed' (for an easier grep)
+                                       # Used messages: 'page-rss-feed' and 'page-atom-feed' (for an easier grep)
+                                       wfMsg( "page-{$format}-feed", $this->getTitle()->getPrefixedText() ) );
                        }
 
-                       # Recent changes feed should appear on every page (except recentchanges, 
-                       # that would be redundant). Put it after the per-page feed to avoid 
-                       # changing existing behavior. It's still available, probably via a 
+                       # Recent changes feed should appear on every page (except recentchanges,
+                       # that would be redundant). Put it after the per-page feed to avoid
+                       # changing existing behavior. It's still available, probably via a
                        # menu in your browser. Some sites might have a different feed they'd
                        # like to promote instead of the RC feed (maybe like a "Recent New Articles"
                        # or "Breaking news" one). For this, we see if $wgOverrideSiteFeed is defined.
                        # If so, use it instead.
-                       
-                       global $wgOverrideSiteFeed, $wgSitename, $wgFeedClasses;
+
+                       global $wgOverrideSiteFeed, $wgSitename, $wgFeedClasses, $wgAdvertisedFeedTypes;
                        $rctitle = SpecialPage::getTitleFor( 'Recentchanges' );
-                       
+
                        if ( $wgOverrideSiteFeed ) {
-                               foreach ( $wgOverrideSiteFeed as $type => $feedUrl ) { 
+                               foreach ( $wgOverrideSiteFeed as $type => $feedUrl ) {
                                        $tags[] = $this->feedLink (
                                                $type,
                                                htmlspecialchars( $feedUrl ),
                                                wfMsg( "site-{$type}-feed", $wgSitename ) );
                                }
-                       }
-                       else if ( $wgTitle->getPrefixedText() != $rctitle->getPrefixedText() ) {
-                               foreach( $wgFeedClasses as $format => $class ) {
+                       } elseif ( $this->getTitle()->getPrefixedText() != $rctitle->getPrefixedText() ) {
+                               foreach ( $wgAdvertisedFeedTypes as $format ) {
                                        $tags[] = $this->feedLink(
                                                $format,
                                                $rctitle->getLocalURL( "feed={$format}" ),
@@ -1604,7 +1812,7 @@ class OutputPage {
                        }
                }
 
-               return implode( "\n\t\t", $tags ) . "\n";
+               return implode( "\n", $tags );
        }
 
        /**
@@ -1612,28 +1820,14 @@ class OutputPage {
         * @return array associating format keys with URLs
         */
        public function getSyndicationLinks() {
-               global $wgTitle, $wgFeedClasses;
-               $links = array();
-
-               if( $this->isSyndicated() ) {
-                       if( is_string( $this->getFeedAppendQuery() ) ) {
-                               $appendQuery = "&" . $this->getFeedAppendQuery();
-                       } else {
-                               $appendQuery = "";
-                       }
-
-                       foreach( $wgFeedClasses as $format => $class ) {
-                               $links[$format] = $wgTitle->getLocalUrl( "feed=$format{$appendQuery}" );
-                       }
-               }
-               return $links;
+               return $this->mFeedLinks;
        }
 
        /**
         * Generate a <link rel/> for an RSS feed.
         */
        private function feedLink( $type, $url, $text ) {
-               return Xml::element( 'link', array(
+               return Html::element( 'link', array(
                        'rel' => 'alternate',
                        'type' => "application/$type+xml",
                        'title' => $text,
@@ -1650,6 +1844,8 @@ class OutputPage {
         */
        public function addStyle( $style, $media='', $condition='', $dir='' ) {
                $options = array();
+               // Even though we expect the media type to be lowercase, but here we
+               // force it to lowercase to be safe.
                if( $media )
                        $options['media'] = $media;
                if( $condition )
@@ -1659,6 +1855,14 @@ class OutputPage {
                $this->styles[$style] = $options;
        }
 
+       /**
+        * Adds inline CSS styles
+        * @param $style_css Mixed: inline CSS
+        */
+       public function addInlineStyle( $style_css ){
+               $this->mScripts .= Html::inlineStyle( $style_css );
+       }
+
        /**
         * Build a set of <link>s for the stylesheets specified in the $this->styles array.
         * These will be applied to various media & IE conditionals.
@@ -1671,7 +1875,7 @@ class OutputPage {
                                $links[] = $link;
                }
 
-               return implode( "\n\t\t", $links );
+               return implode( "\n", $links );
        }
 
        protected function styleLink( $style, $options ) {
@@ -1679,7 +1883,7 @@ class OutputPage {
 
                if( isset( $options['dir'] ) ) {
                        global $wgContLang;
-                       $siteDir = $wgContLang->isRTL() ? 'rtl' : 'ltr';
+                       $siteDir = $wgContLang->getDir();
                        if( $siteDir != $options['dir'] )
                                return '';
                }
@@ -1690,7 +1894,7 @@ class OutputPage {
                                return '';
                        }
                } else {
-                       $media = '';
+                       $media = 'all';
                }
 
                if( substr( $style, 0, 1 ) == '/' ||
@@ -1702,15 +1906,7 @@ class OutputPage {
                        $url = $wgStylePath . '/' . $style . '?' . $wgStyleVersion;
                }
 
-               $attribs = array(
-                       'rel' => 'stylesheet',
-                       'href' => $url,
-                       'type' => 'text/css' );
-               if( $media ) {
-                       $attribs['media'] = $media;
-               }
-
-               $link = Xml::element( 'link', $attribs );
+               $link = Html::linkedStyle( $url, $media );
 
                if( isset( $options['condition'] ) ) {
                        $condition = htmlspecialchars( $options['condition'] );
@@ -1757,7 +1953,6 @@ class OutputPage {
         * for when rate limiting has triggered.
         */
        public function rateLimited() {
-               global $wgTitle;
 
                $this->setPageTitle(wfMsg('actionthrottled'));
                $this->setRobotPolicy( 'noindex,follow' );
@@ -1768,7 +1963,7 @@ class OutputPage {
                $this->setStatusCode(503);
                $this->addWikiMsg( 'actionthrottledtext' );
 
-               $this->returnToMain( null, $wgTitle );
+               $this->returnToMain( null, $this->getTitle() );
        }
 
        /**
@@ -1799,13 +1994,13 @@ class OutputPage {
         * @param int $lag Slave lag
         */
        public function showLagWarning( $lag ) {
-               global $wgSlaveLagWarning, $wgSlaveLagCritical;
+               global $wgSlaveLagWarning, $wgSlaveLagCritical, $wgLang;
                if( $lag >= $wgSlaveLagWarning ) {
                        $message = $lag < $wgSlaveLagCritical
                                ? 'lag-warn-normal'
                                : 'lag-warn-high';
-                       $warning = wfMsgExt( $message, 'parse', $lag );
-                       $this->addHTML( "<div class=\"mw-{$message}\">\n{$warning}\n</div>\n" );
+                       $wrap = Html::rawElement( 'div', array( 'class' => "mw-{$message}" ), "\n$1\n" );
+                       $this->wrapWikiMsg( "$wrap\n", array( $message, $wgLang->formatNum( $lag ) ) );
                }
        }
 
@@ -1840,7 +2035,7 @@ class OutputPage {
         *
         * In the $wrap, $1 is replaced with the first message, $2 with the second, and so
         * on. The subsequent arguments may either be strings, in which case they are the
-        * message names, or an arrays, in which case the first element is the message name,
+        * message names, or arrays, in which case the first element is the message name,
         * and subsequent elements are the parameters to that message.
         *
         * The special named parameter 'options' in a message specification array is passed
@@ -1850,11 +2045,13 @@ class OutputPage {
         *
         * For example:
         *
-        *    $wgOut->wrapWikiMsg( '<div class="error">$1</div>', 'some-error' );
+        *    $wgOut->wrapWikiMsg( "<div class='error'>\n$1</div>", 'some-error' );
         *
         * Is equivalent to:
         *
-        *    $wgOut->addWikiText( '<div class="error">' . wfMsgNoTrans( 'some-error' ) . '</div>' );
+        *    $wgOut->addWikiText( "<div class='error'>\n" . wfMsgNoTrans( 'some-error' ) . '</div>' );
+        *
+        * The newline after opening div is needed in some wikitext. See bug 19226.
         */
        public function wrapWikiMsg( $wrap /*, ...*/ ) {
                $msgSpecs = func_get_args();