Added sorting to module listing in OutputPage::makeResourceLoaderLink. This helps...
[lhc/web/wiklou.git] / includes / OutputPage.php
index 9722be8..a298a48 100644 (file)
@@ -9,13 +9,22 @@ if ( !defined( 'MEDIAWIKI' ) ) {
 class OutputPage {
        var $mMetatags = array(), $mKeywords = array(), $mLinktags = array();
        var $mExtStyles = array();
-       var $mPagetitle = '', $mBodytext = '', $mDebugtext = '';
+       var $mPagetitle = '', $mBodytext = '';
+
+       /**
+        * Holds the debug lines that will be outputted as comments in page source if
+        * $wgDebugComments is enabled. See also $wgShowDebug.
+        * TODO: make a getter method for this
+        */
+       public $mDebugtext = '';
+
        var $mHTMLtitle = '', $mIsarticle = true, $mPrintable = false;
        var $mSubtitle = '', $mRedirect = '', $mStatusCode;
        var $mLastModified = '', $mETag = false;
        var $mCategoryLinks = array(), $mCategories = array(), $mLanguageLinks = array();
 
        var $mScripts = '', $mLinkColours, $mPageLinkTitle = '', $mHeadItems = array();
+       var $mModules = array(), $mModuleScripts = array(), $mModuleStyles = array(), $mModuleMessages = array();
        var $mInlineMsg = array();
 
        var $mTemplateIds = array();
@@ -187,6 +196,7 @@ class OutputPage {
         */
        public function addScriptFile( $file, $version = null ) {
                global $wgStylePath, $wgStyleVersion;
+               // See if $file parameter is an absolute URL or begins with a slash
                if( substr( $file, 0, 1 ) == '/' || preg_match( '#^[a-z]*://#i', $file ) ) {
                        $path = $file;
                } else {
@@ -215,6 +225,85 @@ class OutputPage {
                return $this->mScripts . $this->getHeadItems();
        }
 
+       /**
+        * Get the list of modules to include on this page
+        *
+        * @return Array of module names
+        */
+       public function getModules() {
+               return $this->mModules;
+       }
+
+       /**
+        * Add one or more modules recognized by the resource loader. Modules added
+        * through this function will be loaded by the resource loader when the
+        * page loads.
+        *
+        * @param $modules Mixed: module name (string) or array of module names
+        */
+       public function addModules( $modules ) {
+               $this->mModules = array_merge( $this->mModules, (array)$modules );
+       }
+
+       /**
+        * Get the list of module JS to include on this page
+        * @return array of module names
+        */
+       public function getModuleScripts() {
+               return $this->mModuleScripts;
+       }
+
+       /**
+        * Add only JS of one or more modules recognized by the resource loader. Module
+        * scripts added through this function will be loaded by the resource loader when
+        * the page loads.
+        *
+        * @param $modules Mixed: module name (string) or array of module names
+        */
+       public function addModuleScripts( $modules ) {
+               $this->mModuleScripts = array_merge( $this->mModuleScripts, (array)$modules );
+       }
+
+       /**
+        * Get the list of module CSS to include on this page
+        *
+        * @return Array of module names
+        */
+       public function getModuleStyles() {
+               return $this->mModuleStyles;
+       }
+
+       /**
+        * Add only CSS of one or more modules recognized by the resource loader. Module
+        * styles added through this function will be loaded by the resource loader when
+        * the page loads.
+        *
+        * @param $modules Mixed: module name (string) or array of module names
+        */
+       public function addModuleStyles( $modules ) {
+               $this->mModuleStyles = array_merge( $this->mModuleStyles, (array)$modules );
+       }
+
+       /**
+        * Get the list of module messages to include on this page
+        *
+        * @return Array of module names
+        */
+       public function getModuleMessages() {
+               return $this->mModuleMessages;
+       }
+
+       /**
+        * Add only messages of one or more modules recognized by the resource loader.
+        * Module messages added through this function will be loaded by the resource
+        * loader when the page loads.
+        *
+        * @param $modules Mixed: module name (string) or array of module names
+        */
+       public function addModuleMessages( $modules ) {
+               $this->mModuleMessages = array_merge( $this->mModuleMessages, (array)$modules );
+       }
+
        /**
         * Get all header items in a string
         *
@@ -509,7 +598,7 @@ class OutputPage {
                if ( $this->mTitle instanceof Title ) {
                        return $this->mTitle;
                } else {
-                       wfDebug( __METHOD__ . ' called and $mTitle is null. Return $wgTitle for sanity' );
+                       wfDebug( __METHOD__ . " called and \$mTitle is null. Return \$wgTitle for sanity\n" );
                        global $wgTitle;
                        return $wgTitle;
                }
@@ -768,7 +857,7 @@ class OutputPage {
                $pageTable = $dbr->tableName( 'page' );
                $where = $lb->constructSet( 'page', $dbr );
                $propsTable = $dbr->tableName( 'page_props' );
-               $sql = "SELECT page_id, page_namespace, page_title, page_len, page_is_redirect, pp_value
+               $sql = "SELECT page_id, page_namespace, page_title, page_len, page_is_redirect, page_latest, pp_value
                        FROM $pageTable LEFT JOIN $propsTable ON pp_propname='hiddencat' AND pp_page=page_id WHERE $where";
                $res = $dbr->query( $sql, __METHOD__ );
 
@@ -1053,7 +1142,7 @@ class OutputPage {
                        $popts, true, true, $this->mRevisionId
                );
                $popts->setTidy( false );
-               if ( $cache && $article && !$parserOutput->isCacheable() ) {
+               if ( $cache && $article && $parserOutput->isCacheable() ) {
                        $parserCache = ParserCache::singleton();
                        $parserCache->save( $parserOutput, $article, $popts );
                }
@@ -1086,6 +1175,7 @@ class OutputPage {
                }
                $this->mNoGallery = $parserOutput->getNoGallery();
                $this->mHeadItems = array_merge( $this->mHeadItems, $parserOutput->getHeadItems() );
+               $this->addModules( $parserOutput->getModules() );
                // Versioning...
                foreach ( (array)$parserOutput->mTemplateIds as $ns => $dbks ) {
                        if ( isset( $this->mTemplateIds[$ns] ) ) {
@@ -1267,7 +1357,7 @@ class OutputPage {
                $cvCookies = $this->getCacheVaryCookies();
                foreach ( $cvCookies as $cookieName ) {
                        # Check for a simple string match, like the way squid does it
-                       if ( strpos( $cookieHeader, $cookieName ) ) {
+                       if ( strpos( $cookieHeader, $cookieName ) !== false ) {
                                wfDebug( __METHOD__ . ": found $cookieName\n" );
                                return true;
                        }
@@ -1479,10 +1569,10 @@ class OutputPage {
         */
        public function output() {
                global $wgUser, $wgOutputEncoding, $wgRequest;
-               global $wgContLanguageCode, $wgDebugRedirects, $wgMimeType;
+               global $wgLanguageCode, $wgDebugRedirects, $wgMimeType;
                global $wgUseAjax, $wgAjaxWatch;
                global $wgEnableMWSuggest, $wgUniversalEditButton;
-               global $wgArticle, $wgJQueryOnEveryPage;
+               global $wgArticle;
 
                if( $this->mDoNothing ) {
                        return;
@@ -1519,24 +1609,27 @@ class OutputPage {
                }
 
                $sk = $wgUser->getSkin();
-
+               
+               // Add base resources
+               $this->addModules( array( 'mediawiki.legacy.wikibits' ) );
+               
+               // Add various resources if required
                if ( $wgUseAjax ) {
-                       $this->addScriptFile( 'ajax.js' );
+                       $this->addModules( 'mediawiki.legacy.ajax' );
 
                        wfRunHooks( 'AjaxAddScript', array( &$this ) );
 
                        if( $wgAjaxWatch && $wgUser->isLoggedIn() ) {
-                               $this->includeJQuery();
-                               $this->addScriptFile( 'ajaxwatch.js' );
+                               $this->addModules( 'mediawiki.legacy.ajaxwatch' );
                        }
 
                        if ( $wgEnableMWSuggest && !$wgUser->getOption( 'disablesuggest', false ) ) {
-                               $this->addScriptFile( 'mwsuggest.js' );
+                               $this->addModules( 'mediawiki.legacy.mwsuggest' );
                        }
                }
 
                if( $wgUser->getBoolOption( 'editsectiononrightclick' ) ) {
-                       $this->addScriptFile( 'rightclickedit.js' );
+                       $this->addModules( 'mediawiki.legacy.rightclickedit' );
                }
 
                if( $wgUniversalEditButton ) {
@@ -1559,15 +1652,12 @@ class OutputPage {
                        }
                }
 
-               if ( $wgJQueryOnEveryPage ) {
-                       $this->includeJQuery();
-               }
 
                # Buffer output; final headers may depend on later processing
                ob_start();
 
                $wgRequest->response()->header( "Content-type: $wgMimeType; charset={$wgOutputEncoding}" );
-               $wgRequest->response()->header( 'Content-language: ' . $wgContLanguageCode );
+               $wgRequest->response()->header( 'Content-language: ' . $wgLanguageCode );
 
                if ( $this->mArticleBodyOnly ) {
                        $this->out( $this->mBodytext );
@@ -1792,20 +1882,6 @@ class OutputPage {
                $this->returnToMain();
        }
 
-       /**
-        * @deprecated use permissionRequired()
-        */
-       public function sysopRequired() {
-               throw new MWException( "Call to deprecated OutputPage::sysopRequired() method\n" );
-       }
-
-       /**
-        * @deprecated use permissionRequired()
-        */
-       public function developerRequired() {
-               throw new MWException( "Call to deprecated OutputPage::developerRequired() method\n" );
-       }
-
        /**
         * Produce the stock "please login to use the wiki" page
         */
@@ -1854,7 +1930,6 @@ class OutputPage {
                if ( $action == null ) {
                        $text = wfMsgNoTrans( 'permissionserrorstext', count( $errors ) ) . "\n\n";
                } else {
-                       global $wgLang;
                        $action_desc = wfMsgNoTrans( "action-$action" );
                        $text = wfMsgNoTrans(
                                'permissionserrorstext-withaction',
@@ -1919,16 +1994,7 @@ class OutputPage {
                        if( $source ) {
                                $this->setPageTitle( wfMsg( 'viewsource' ) );
                                $this->setSubtitle(
-                                       wfMsg(
-                                               'viewsourcefor',
-                                               $skin->link(
-                                                       $this->getTitle(),
-                                                       null,
-                                                       array(),
-                                                       array(),
-                                                       array( 'known', 'noclasses' )
-                                               )
-                                       )
+                                       wfMsg( 'viewsourcefor', $skin->linkKnown( $this->getTitle() ) )
                                );
                        } else {
                                $this->setPageTitle( wfMsg( 'badaccess' ) );
@@ -1971,6 +2037,28 @@ class OutputPage {
                }
        }
 
+       /**
+        * Adds JS-based password security checker
+        * @param $passwordId String ID of input box containing password
+        * @param $retypeId String ID of input box containing retyped password
+        * @return none
+        */
+       public function addPasswordSecurity( $passwordId, $retypeId ) {
+               $this->includeJQuery();
+               $data = array(
+                       'password' => '#' . $passwordId,
+                       'retype' => '#' . $retypeId,
+                       'messages' => array(),
+               );
+               foreach ( array( 'password-strength', 'password-strength-bad', 'password-strength-mediocre',
+                               'password-strength-acceptable', 'password-strength-good', 'password-retype', 'password-retype-mismatch'
+                       ) as $message ) {
+                       $data['messages'][$message] = wfMsg( $message );
+               }
+               $this->addScript( Html::inlineScript( 'var passwordSecurity=' . FormatJson::encode( $data ) ) );
+               $this->addModules( 'mediawiki.legacy.password' );
+       }
+
        /** @deprecated */
        public function errorpage( $title, $msg ) {
                wfDeprecated( __METHOD__ );
@@ -2105,18 +2193,16 @@ class OutputPage {
         * @return String: The doctype, opening <html>, and head element.
         */
        public function headElement( Skin $sk, $includeStyle = true ) {
-               global $wgContLanguageCode, $wgOutputEncoding, $wgMimeType;
-               global $wgContLang, $wgUseTrackbacks, $wgStyleVersion, $wgHtml5;
+               global $wgOutputEncoding, $wgMimeType;
+               global $wgUseTrackbacks, $wgHtml5;
                global $wgUser, $wgRequest, $wgLang;
 
                if ( $sk->commonPrintStylesheet() ) {
-                       $this->addStyle( 'common/wikiprintable.css', 'print' );
+                       $this->addModuleStyles( 'mediawiki.legacy.wikiprintable' );
                }
                $sk->setupUserCss( $this );
 
-               $dir = $wgContLang->getDir();
-               $htmlAttribs = array( 'lang' => $wgContLanguageCode, 'dir' => $dir );
-               $ret = Html::htmlHeader( $htmlAttribs );
+               $ret = Html::htmlHeader( array( 'lang' => wfUILang()->getCode() ) );
 
                if ( $this->getHTMLTitle() == '' ) {
                        $this->setHTMLTitle( wfMsg( 'pagetitle', $this->getPageTitle() ) );
@@ -2139,9 +2225,9 @@ class OutputPage {
                $ret .= Html::element( 'title', null, $this->getHTMLTitle() ) . "\n";
 
                $ret .= implode( "\n", array(
-                       $this->getHeadLinks(),
+                       $this->getHeadLinks( $sk ),
                        $this->buildCssLinks(),
-                       $this->getHeadScripts( $sk ) . $this->getHeadItems(),
+                       $this->getHeadItems(),
                ) );
                if ( $sk->usercss ) {
                        $ret .= Html::inlineStyle( $sk->usercss );
@@ -2171,6 +2257,7 @@ class OutputPage {
                }
 
                # Class bloat
+               $dir = wfUILang()->getDir();
                $bodyAttrs['class'] = "mediawiki $dir";
 
                if ( $wgLang->capitalizeAllNouns() ) {
@@ -2192,56 +2279,115 @@ class OutputPage {
 
                return $ret;
        }
-
+       
+       static function makeResourceLoaderLink( $skin, $modules, $only ) {
+               global $wgUser, $wgLang, $wgRequest, $wgLoadScript;
+               // TODO: Should this be a static function of ResourceLoader instead?
+               // TODO: Divide off modules starting with "user", and add the user parameter to them
+               $query = array(
+                       'lang' => $wgLang->getCode(),
+                       'debug' => ( $wgRequest->getBool( 'debug' ) && $wgRequest->getVal( 'debug' ) == 'true' ) ? 'true' : 'false',
+                       'skin' => $wgUser->getSkin()->getSkinName(),
+                       'only' => $only,
+               );
+               $moduleGroups = array( null => array(), 'user' => array() );
+               foreach ( (array) $modules as $name ) {
+                       $moduleGroups[strpos( $name, 'user' ) === 0 ? 'user' : null][] = $name;
+               }
+               $links = '';
+               foreach ( $moduleGroups as $group => $modules ) {
+                       if ( count( $modules ) ) {
+                               sort( $modules );
+                               $query['modules'] = implode( '|', array_unique( (array) $modules ) );
+                               if ( $group === 'user' ) {
+                                       $query['user'] = $wgUser->getName();
+                               }
+                               $context = new ResourceLoaderContext( new FauxRequest( $query ) );
+                               $timestamp = 0;
+                               foreach ( $modules as $name ) {
+                                       if ( $module = ResourceLoader::getModule( $name ) ) {
+                                               $timestamp = max( $timestamp, $module->getModifiedTime( $context ) );
+                                       }
+                               }
+                               $query['version'] = wfTimestamp( TS_ISO_8601, round( $timestamp, -2 ) );
+                               // Make queries uniform in order
+                               ksort( $query );
+                               // Automatically select style/script elements
+                               if ( $only === 'styles' ) {
+                                       $links .= Html::linkedStyle( wfAppendQuery( $wgLoadScript, $query ) );
+                               } else {
+                                       $links .= Html::linkedScript( wfAppendQuery( $wgLoadScript, $query ) );
+                               }
+                       }
+               }
+               return $links;
+       }
+       
        /**
         * Gets the global variables and mScripts; also adds userjs to the end if
-        * enabled
+        * enabled. Despite the name, these scripts are no longer put in the
+        * <head> but at the bottom of the <body>
         *
         * @param $sk Skin object to use
         * @return String: HTML fragment
         */
        function getHeadScripts( Skin $sk ) {
-               global $wgUser, $wgRequest, $wgJsMimeType, $wgUseSiteJs;
-               global $wgStylePath, $wgStyleVersion;
-
-               $scripts = Skin::makeGlobalVariablesScript( $sk->getSkinName() ) . "\n";
-               $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() )
-                               )
+               global $wgUser, $wgRequest, $wgJsMimeType;
+               global $wgUseSiteJs;
+               
+               // Statup - this will immediately load jquery and mediawiki modules
+               $scripts = self::makeResourceLoaderLink( $sk, 'startup', 'scripts' );
+               
+               // Configuration -- This could be merged together with the load and go, but makeGlobalVariablesScript returns a
+               // whole script tag -- grumble grumble...
+               $scripts .= Skin::makeGlobalVariablesScript( $sk->getSkinName() ) . "\n";
+               
+               // Script and Messages "only"
+               if ( $wgRequest->getBool( 'debug' ) && $wgRequest->getVal( 'debug' ) !== 'false' ) {
+                       // Scripts
+                       foreach ( $this->getModuleScripts() as $name ) {
+                               $scripts .= self::makeResourceLoaderLink( $sk, $name, 'scripts' );
+                       }
+                       // Messages
+                       foreach ( $this->getModuleMessages() as $name ) {
+                               $scripts .= self::makeResourceLoaderLink( $sk, $name, 'messages' );
+                       }
+               } else {
+                       // Scripts
+                       if ( count( $this->getModuleScripts() ) ) {
+                               $scripts .= self::makeResourceLoaderLink( $sk, $this->getModuleScripts(), 'scripts' );
+                       }
+                       // Messages
+                       if ( count( $this->getModuleMessages() ) ) {
+                               $scripts .= self::makeResourceLoaderLink( $sk, $this->getModuleMessages(), 'messages' );
+                       }
+               }
+               
+               // Modules - let the client calculate dependencies and batch requests as it likes
+               if ( $this->getModules() ) {
+                       $modules = FormatJson::encode( $this->getModules() );
+                       $scripts .= Html::inlineScript(
+                               "if ( mediaWiki !== undefined ) { mediaWiki.loader.load( {$modules} ); mediaWiki.loader.go(); }"
                        );
                }
-
-               // add user JS if enabled
+               
+               // Add user JS if enabled
                if( $this->isUserJsAllowed() && $wgUser->isLoggedIn() ) {
                        $action = $wgRequest->getVal( 'action', 'view' );
                        if( $this->mTitle && $this->mTitle->isJsSubpage() && $sk->userCanPreview( $action ) ) {
                                # XXX: additional security check/prompt?
                                $this->addInlineScript( $wgRequest->getText( 'wpTextbox1' ) );
                        } else {
-                               $userpage = $wgUser->getUserPage();
-                               $names = array( 'common', $sk->getSkinName() );
-                               foreach( $names as $name ) {
-                                       $scriptpage = Title::makeTitleSafe(
-                                               NS_USER,
-                                               $userpage->getDBkey() . '/' . $name . '.js'
-                                       );
-                                       if ( $scriptpage && $scriptpage->exists() ) {
-                                               $userjs = $scriptpage->getLocalURL( 'action=raw&ctype=' . $wgJsMimeType );
-                                               $this->addScriptFile( $userjs );
-                                       }
-                               }
+                               $scripts .= self::makeResourceLoaderLink( $sk, 'user', 'scripts' );
                        }
                }
-
                $scripts .= "\n" . $this->mScripts;
+               
+               // Add site JS if enabled
+               if ( $wgUseSiteJs ) {
+                       $scripts .= self::makeResourceLoaderLink( $sk, 'site', 'scripts' );
+               }
+               
                return $scripts;
        }
 
@@ -2289,8 +2435,8 @@ class OutputPage {
        /**
         * @return string HTML tag links to be put in the header.
         */
-       public function getHeadLinks() {
-               global $wgFeed;
+       public function getHeadLinks( $sk ) {
+               global $wgFeed, $wgRequest;
 
                // Ideally this should happen earlier, somewhere. :P
                $this->addDefaultMeta();
@@ -2360,6 +2506,17 @@ class OutputPage {
                        }
                }
 
+               // Support individual script requests in debug mode
+               if ( $wgRequest->getBool( 'debug' ) && $wgRequest->getVal( 'debug' ) !== 'false' ) {
+                       foreach ( $this->getModuleStyles() as $name ) {
+                               $tags[] = self::makeResourceLoaderLink( $sk, $name, 'styles' );
+                       }
+               } else {
+                       if ( count( $this->getModuleStyles() ) ) {
+                               $tags[] = self::makeResourceLoaderLink( $sk, $this->getModuleStyles(), 'styles' );
+                       }
+               }
+               
                return implode( "\n", $tags );
        }
 
@@ -2442,8 +2599,7 @@ class OutputPage {
         */
        protected function styleLink( $style, $options ) {
                if( isset( $options['dir'] ) ) {
-                       global $wgContLang;
-                       $siteDir = $wgContLang->getDir();
+                       $siteDir = wfUILang()->getDir();
                        if( $siteDir != $options['dir'] ) {
                                return '';
                        }
@@ -2631,20 +2787,10 @@ class OutputPage {
         * @param $modules Array: list of jQuery modules which should be loaded
         * @return Array: the list of modules which were not loaded.
         * @since 1.16
+        * @deprecated No longer needed as of 1.17
         */
        public function includeJQuery( $modules = array() ) {
-               global $wgStylePath, $wgStyleVersion, $wgJQueryVersion, $wgJQueryMinified;
-
-               $supportedModules = array( /** TODO: add things here */ );
-               $unsupported = array_diff( $modules, $supportedModules );
-
-               $min = $wgJQueryMinified ? '.min' : '';
-               $url = "$wgStylePath/common/jquery-$wgJQueryVersion$min.js?$wgStyleVersion";
-               if ( !$this->mJQueryDone ) {
-                       $this->mJQueryDone = true;
-                       $this->mScripts = Html::linkedScript( $url ) . "\n" . $this->mScripts;
-               }
-               return $unsupported;
+               return array();
        }
 
 }