follow-up r59522, r59523, r59527, r59529, r59530.
[lhc/web/wiklou.git] / includes / OutputPage.php
index 36ca6ae..2f69ab8 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;
 
@@ -46,6 +50,8 @@ class OutputPage {
 
        private $mIndexPolicy = 'index';
        private $mFollowPolicy = 'follow';
+       private $mVaryHeader = array( 'Accept-Encoding' => array('list-contains=gzip'),
+                                                                 'Cookie' => null );
 
        /**
         * Constructor
@@ -88,16 +94,23 @@ class OutputPage {
        function addKeyword( $text ) {
                if( is_array( $text )) {
                        $this->mKeywords = array_merge( $this->mKeywords, $text );
-               }
-               else {
+               } else {
                        array_push( $this->mKeywords, $text );
                }
        }
-       function addScript( $script ) { $this->mScripts .= "\t" . $script . "\n"; }
-       
-       function addExtensionStyle( $url ) {
-               $linkarr = array( 'rel' => 'stylesheet', 'href' => $url, 'type' => 'text/css' );
-               array_push( $this->mExtStyles, $linkarr );
+       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 );
        }
 
        /**
@@ -119,18 +132,20 @@ class OutputPage {
                                ),
                                '', 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 .= "\t\t<script type=\"$wgJsMimeType\">/*<![CDATA[*/\n\t\t$script\n\t\t/*]]>*/</script>\n";
+               $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();
        }
@@ -153,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;
@@ -184,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;
@@ -237,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 );
@@ -247,7 +262,7 @@ 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" );
@@ -281,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'] );
+               }
        }
 
        /**
@@ -339,7 +346,7 @@ class OutputPage {
         * 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 );
@@ -356,11 +363,11 @@ class OutputPage {
                # 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;
@@ -380,15 +387,31 @@ 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 ) { $this->mShowFeedLinks = $show; }
+
+       public function setFeedAppendQuery( $val ) {
+               global $wgFeedClasses;
+
+               $this->mFeedLinks = array();
+
+               foreach( $wgFeedClasses as $type => $class ) {
+                       $query = "feed=$type&".$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 ) {
@@ -416,6 +439,10 @@ class OutputPage {
                return $this->mCategoryLinks;
        }
 
+       public function getCategories() {
+               return $this->mCategories;
+       }
+
        /**
         * Add an array of categories, with names in the keys
         */
@@ -465,6 +492,7 @@ class OutputPage {
                                        if ( array_key_exists( $category, $categories ) )
                                                continue;
                                $text = $wgContLang->convertHtml( $title->getText() );
+                               $this->mCategories[] = $title->getText();
                                $this->mCategoryLinks[$type][] = $sk->link( $title, $text );
                        }
                }
@@ -510,7 +538,7 @@ class OutputPage {
                $val = is_null( $revid ) ? null : intval( $revid );
                return wfSetVar( $this->mRevisionId, $val );
        }
-       
+
        public function getRevisionId() {
                return $this->mRevisionId;
        }
@@ -568,18 +596,6 @@ class OutputPage {
                $this->mNewSectionLink = $parserOutput->getNewSection();
                $this->mHideNewSectionLink = $parserOutput->getHideNewSection();
 
-               if( is_null( $wgExemptFromUserRobotsControl ) ) {
-                       $bannedNamespaces = $wgContentNamespaces;
-               } else {
-                       $bannedNamespaces = $wgExemptFromUserRobotsControl;
-               }
-               if( !in_array( $this->getTitle()->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 );
@@ -618,7 +634,7 @@ class OutputPage {
         */
        function addParserOutput( &$parserOutput ) {
                $this->addParserOutputNoText( $parserOutput );
-               $text = $parserOutput->getText();
+               $text = $parserOutput->getText();
                wfRunHooks( 'OutputPageBeforeHTML',array( &$this, &$text ) );
                $this->addHTML( $text );
        }
@@ -714,14 +730,15 @@ class OutputPage {
        /**
         * @param Article $article
         * @param User    $user
-        * 
-        * Now a wrapper around Article::tryParserCache()
+        *
+        * @deprecated
         *
         * @return bool True if successful, else false.
         */
        public function tryParserCache( &$article ) {
-               $parserOutput = $article->tryParserCache( $this->parserOptions() );
-               
+               wfDeprecated( __METHOD__ );
+               $parserOutput = ParserCache::singleton()->get( $article, $article->getParserOptions() );
+
                if ($parserOutput !== false) {
                        $this->addParserOutput( $parserOutput );
                        return true;
@@ -790,24 +807,45 @@ 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;
        }
 
        public function sendCacheControl() {
-               global $wgUseSquid, $wgUseESI, $wgUseETag, $wgSquidMaxage, $wgRequest;
+               global $wgUseSquid, $wgUseESI, $wgUseETag, $wgSquidMaxage, $wgRequest, $wgUseXVO;
 
                $response = $wgRequest->response();
                if ($wgUseETag && $this->mETag)
@@ -815,10 +853,12 @@ class OutputPage {
 
                # 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() == '' &&
@@ -862,6 +902,7 @@ class OutputPage {
                        $response->header( 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate' );
                        $response->header( 'Pragma: no-cache' );
                }
+               wfRunHooks('CacheHeadersAfterSet', array( $this ) );
        }
 
        /**
@@ -871,16 +912,14 @@ 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;
 
                if( $this->mDoNothing ){
                        return;
                }
-
                wfProfileIn( __METHOD__ );
-
                if ( '' != $this->mRedirect ) {
                        # Standards require redirect URLs to be absolute
                        $this->mRedirect = wfExpandUrl( $this->mRedirect );
@@ -890,7 +929,6 @@ class OutputPage {
                                }
                                $this->mLastModified = wfTimestamp( TS_RFC2822 );
                        }
-
                        $this->sendCacheControl();
 
                        $wgRequest->response()->header("Content-Type: text/html; charset=utf-8");
@@ -970,12 +1008,12 @@ 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' );
                }
@@ -984,21 +1022,22 @@ class OutputPage {
                        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' ),
+                                       'title' => $msg,
                                        'href' => $this->getTitle()->getLocalURL( 'action=edit' )
                                ) );
                                // Alternate edit link
                                $this->addLink( array(
                                        'rel' => 'edit',
-                                       'title' => wfMsg( 'edit' ),
+                                       'title' => $msg,
                                        'href' => $this->getTitle()->getLocalURL( 'action=edit' )
                                ) );
                        }
                }
-               
+
                # Buffer output; final headers may depend on later processing
                ob_start();
 
@@ -1370,15 +1409,15 @@ class OutputPage {
                // 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();
@@ -1466,11 +1505,13 @@ class OutputPage {
         * Add a "return to" link pointing to a specified title
         *
         * @param Title $title Title to link
+        * @param string $query Query string
         */
-       public function addReturnTo( $title ) {
+       public function addReturnTo( $title, $query = array() ) {
                global $wgUser;
                $this->addLink( array( 'rel' => 'next', 'href' => $title->getFullUrl() ) );
-               $link = wfMsgHtml( 'returnto', $wgUser->getSkin()->link( $title ) );
+               $link = wfMsgHtml( 'returnto', $wgUser->getSkin()->link(
+                       $title, null, array(), $query ) );
                $this->addHTML( "<p id=\"mw-returnto\">{$link}</p>\n" );
        }
 
@@ -1481,13 +1522,17 @@ class OutputPage {
         * @param null $unused No longer used
         * @param Title $returnto Title to return to
         */
-       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 ( $returntoquery == null ) {
+                       $returntoquery = $wgRequest->getText( 'returntoquery' );
+               }
+
                if ( '' === $returnto ) {
                        $returnto = Title::newMainPage();
                }
@@ -1501,52 +1546,18 @@ 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 $wgContLang;
-               // Get an array of keywords if there are more than one
-               // variant of the site language
-               $text = $wgContLang->autoConvertToAllVariants( $this->getTitle()->getPrefixedText());
-               // array_values: We needn't to merge variant's code name
-               // into $this->mKeywords;
-               // array_unique: We should insert a keyword just for once
-               if( is_array( $text ))
-                       $text = array_unique( array_values( $text ));
-               $this->addKeyword( $text );
-               $count = 1;
-               $links2d =& $parserOutput->getLinks();
-               if ( !is_array( $links2d ) ) {
-                       return;
-               }
-               foreach ( $links2d as $dbkeys ) {
-                       foreach( $dbkeys as $dbkey => $unused ) {
-                               $dbkey = $wgContLang->autoConvertToAllVariants( $dbkey );
-                               if( is_array( $dbkey ))
-                                       $dbkey = array_unique( array_values( $dbkey ));
-                               $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 $wgContLang, $wgUseTrackbacks, $wgStyleVersion;
+               global $wgXhtmlDefaultNamespace, $wgXhtmlNamespaces, $wgHtml5Version;
+               global $wgContLang, $wgUseTrackbacks, $wgStyleVersion, $wgHtml5;
 
                $this->addMeta( "http:Content-Type", "$wgMimeType; charset={$wgOutputEncoding}" );
                if ( $sk->commonPrintStylesheet() ) {
@@ -1560,27 +1571,36 @@ class OutputPage {
                        $ret .= "<?xml version=\"1.0\" encoding=\"$wgOutputEncoding\" ?" . ">\n";
                }
 
-               $ret .= "<!DOCTYPE html PUBLIC \"$wgDocType\" \"$wgDTD\">\n";
-
                if ( '' == $this->getHTMLTitle() ) {
                        $this->setHTMLTitle(  wfMsg( 'pagetitle', $this->getPageTitle() ));
                }
 
-               $dir = $wgContLang->isRTL() ? 'rtl' : 'ltr';
-               $ret .= "<html xmlns=\"{$wgXhtmlDefaultNamespace}\" ";
-               foreach($wgXhtmlNamespaces as $tag => $ns) {
-                       $ret .= "xmlns:{$tag}=\"{$ns}\" ";
+               $dir = $wgContLang->getDir();
+
+               if ( $wgHtml5 ) {
+                       $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\" dir=\"$dir\">\n";
-               $ret .= "<head>\n\t<title>" . htmlspecialchars( $this->getHTMLTitle() ) . "</title>\n\t";
+
+               $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())
@@ -1589,9 +1609,50 @@ class OutputPage {
                $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;
+               global $wgVersion, $wgHtml5;
 
                static $called = false;
                if ( $called ) {
@@ -1600,9 +1661,11 @@ class OutputPage {
                }
                $called = true;
 
-               $this->addMeta( 'http:Content-Style-Type', 'text/css' ); //bug 15835
+               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
@@ -1624,12 +1687,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';
@@ -1637,13 +1700,13 @@ 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 ) {
@@ -1659,19 +1722,19 @@ class OutputPage {
                                        wfMsg( "page-{$format}-feed", $this->getTitle()->getPrefixedText() ) ); # Used messages: 'page-rss-feed' and 'page-atom-feed' (for an easier grep)
                        }
 
-                       # 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;
                        $rctitle = SpecialPage::getTitleFor( 'Recentchanges' );
-                       
+
                        if ( $wgOverrideSiteFeed ) {
-                               foreach ( $wgOverrideSiteFeed as $type => $feedUrl ) { 
+                               foreach ( $wgOverrideSiteFeed as $type => $feedUrl ) {
                                        $tags[] = $this->feedLink (
                                                $type,
                                                htmlspecialchars( $feedUrl ),
@@ -1688,7 +1751,7 @@ class OutputPage {
                        }
                }
 
-               return implode( "\n\t", $tags ) . "\n";
+               return implode( "\n", $tags );
        }
 
        /**
@@ -1696,28 +1759,14 @@ class OutputPage {
         * @return array associating format keys with URLs
         */
        public function getSyndicationLinks() {
-               global $wgFeedClasses;
-               $links = array();
-
-               if( $this->isSyndicated() ) {
-                       if( is_string( $this->getFeedAppendQuery() ) ) {
-                               $appendQuery = "&" . $this->getFeedAppendQuery();
-                       } else {
-                               $appendQuery = "";
-                       }
-
-                       foreach( $wgFeedClasses as $format => $class ) {
-                               $links[$format] = $this->getTitle()->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,
@@ -1734,6 +1783,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 )
@@ -1743,6 +1794,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.
@@ -1755,7 +1814,7 @@ class OutputPage {
                                $links[] = $link;
                }
 
-               return "\t" . implode( "\n\t", $links );
+               return implode( "\n", $links );
        }
 
        protected function styleLink( $style, $options ) {
@@ -1763,7 +1822,7 @@ class OutputPage {
 
                if( isset( $options['dir'] ) ) {
                        global $wgContLang;
-                       $siteDir = $wgContLang->isRTL() ? 'rtl' : 'ltr';
+                       $siteDir = $wgContLang->getDir();
                        if( $siteDir != $options['dir'] )
                                return '';
                }
@@ -1774,7 +1833,7 @@ class OutputPage {
                                return '';
                        }
                } else {
-                       $media = '';
+                       $media = 'all';
                }
 
                if( substr( $style, 0, 1 ) == '/' ||
@@ -1786,15 +1845,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'] );
@@ -1882,13 +1933,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 ) ) );
                }
        }