Merge "Remove silly reference ampersands from database functions in maint"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Mon, 5 Jan 2015 20:55:22 +0000 (20:55 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Mon, 5 Jan 2015 20:55:22 +0000 (20:55 +0000)
includes/OutputPage.php
includes/exception/MWExceptionHandler.php
includes/filerepo/file/LocalFile.php
includes/specials/SpecialActiveusers.php
includes/specials/SpecialJavaScriptTest.php
includes/specials/SpecialListusers.php
languages/i18n/en.json
languages/i18n/qqq.json
resources/Resources.php
resources/src/mediawiki.special/mediawiki.special.javaScriptTest.js
tests/qunit/QUnitTestResources.php

index 07fa94b..f8d5ab7 100644 (file)
@@ -2729,7 +2729,7 @@ class OutputPage extends ContextSource {
         *   call rather than a "<script src='...'>" tag.
         * @return string The html "<script>", "<link>" and "<style>" tags
         */
-       protected function makeResourceLoaderLink( $modules, $only, $useESI = false,
+       public function makeResourceLoaderLink( $modules, $only, $useESI = false,
                array $extraQuery = array(), $loadCall = false
        ) {
                $modules = (array)$modules;
@@ -3138,7 +3138,7 @@ class OutputPage extends ContextSource {
         * have to be purged on configuration changes.
         * @return array
         */
-       private function getJSVars() {
+       public function getJSVars() {
                global $wgContLang;
 
                $curRevisionId = 0;
index 83801b6..ad462f2 100644 (file)
@@ -493,5 +493,10 @@ TXT;
                } else {
                        wfDebugLog( 'error', $log );
                }
+
+               $json = self::jsonSerializeException( $e, false, FormatJson::ALL_OK );
+               if ( $json !== false ) {
+                       wfDebugLog( 'error-json', $json, 'private' );
+               }
        }
 }
index eb0f654..94ef306 100644 (file)
@@ -1411,7 +1411,10 @@ class LocalFile extends File {
                if ( $exists ) {
                        # Create a null revision
                        $latest = $descTitle->getLatestRevID();
-                       $editSummary = LogFormatter::newFromEntry( $logEntry )->getPlainActionText();
+                       // Use own context to get the action text in content language
+                       $formatter = LogFormatter::newFromEntry( $logEntry );
+                       $formatter->setContext( RequestContext::newExtraneousContext( $descTitle ) );
+                       $editSummary = $formatter->getPlainActionText();
 
                        $nullRevision = Revision::newNullRevision(
                                $dbw,
index fe06375..ff63243 100644 (file)
@@ -124,6 +124,8 @@ class ActiveUsersPager extends UsersPager {
        }
 
        function doBatchLookups() {
+               parent::doBatchLookups();
+
                $uids = array();
                foreach ( $this->mResult as $row ) {
                        $uids[] = $row->user_id;
@@ -172,7 +174,8 @@ class ActiveUsersPager extends UsersPager {
                // Note: This is a different loop than for user rights,
                // because we're reusing it to build the group links
                // at the same time
-               foreach ( $user->getGroups() as $group ) {
+               $groups_list = self::getGroups( intval( $row->user_id ), $this->userGroupCache );
+               foreach ( $groups_list as $group ) {
                        if ( in_array( $group, $this->hideGroups ) ) {
                                return '';
                        }
index 65ddb31..3dc4410 100644 (file)
  */
 class SpecialJavaScriptTest extends SpecialPage {
        /**
-        * @var array Mapping of framework ids and their initilizer methods
-        * in this class. If a framework is requested but not in this array,
-        * the 'unknownframework' error is served.
+        * @var array Supported frameworks.
         */
        private static $frameworks = array(
-               'qunit' => 'initQUnitTesting',
+               'qunit',
        );
 
        public function __construct() {
@@ -44,43 +42,70 @@ class SpecialJavaScriptTest extends SpecialPage {
                $this->setHeaders();
                $out->disallowUserJs();
 
-               $out->addModules( 'mediawiki.special.javaScriptTest' );
-
-               // Determine framework
-               $pars = explode( '/', $par );
-               $framework = strtolower( $pars[0] );
-
-               // No framework specified
-               if ( $par == '' ) {
+               if ( $par === null ) {
+                       // No framework specified
+                       $out->setStatusCode( 404 );
                        $out->setPageTitle( $this->msg( 'javascripttest' ) );
-                       $summary = $this->wrapSummaryHtml(
-                               $this->msg( 'javascripttest-pagetext-noframework' )->escaped() .
-                                       $this->getFrameworkListHtml(),
-                               'noframework'
+                       $out->addHTML(
+                               $this->msg( 'javascripttest-pagetext-noframework' )->parseAsBlock()
+                               . $this->getFrameworkListHtml()
                        );
-                       $out->addHtml( $summary );
-               } elseif ( isset( self::$frameworks[$framework] ) ) {
-                       // Matched! Display proper title and initialize the framework
-                       $out->setPageTitle( $this->msg(
-                               'javascripttest-title',
-                               // Messages: javascripttest-qunit-name
-                               $this->msg( "javascripttest-$framework-name" )->plain()
-                       ) );
-                       $out->setSubtitle( $this->msg( 'javascripttest-backlink' )
-                               ->rawParams( Linker::linkKnown( $this->getPageTitle() ) ) );
-                       $this->{self::$frameworks[$framework]}();
-               } else {
-                       // Framework not found, display error
-                       $out->setPageTitle( $this->msg( 'javascripttest' ) );
-                       $summary = $this->wrapSummaryHtml(
-                               '<p class="error">' .
-                                       $this->msg( 'javascripttest-pagetext-unknownframework', $par )->escaped() .
-                                       '</p>' .
-                                       $this->getFrameworkListHtml(),
-                               'unknownframework'
+                       return;
+               }
+
+               // Determine framework and mode
+               $pars = explode( '/', $par, 2 );
+
+               $framework = $pars[0];
+               if ( !in_array( $framework, self::$frameworks ) ) {
+                       // Framework not found
+                       $out->setStatusCode( 404 );
+                       $out->addHTML(
+                               '<div class="error">'
+                               . $this->msg( 'javascripttest-pagetext-unknownframework' )
+                                       ->plaintextParams( $par )->parseAsBlock()
+                               . '</div>'
+                               . $this->getFrameworkListHtml()
                        );
-                       $out->addHtml( $summary );
+                       return;
+               }
+
+               // This special page is disabled by default ($wgEnableJavaScriptTest), and contains
+               // no sensitive data. In order to allow TestSwarm to embed it into a test client window,
+               // we need to allow iframing of this page.
+               $out->allowClickjacking();
+               $out->setSubtitle(
+                       $this->msg( 'javascripttest-backlink' )
+                               ->rawParams( Linker::linkKnown( $this->getPageTitle() ) )
+               );
+
+               // Custom actions
+               if ( isset( $pars[1] ) ) {
+                       $action = $pars[1];
+                       if ( !in_array( $action, array( 'export', 'plain' ) ) ) {
+                               $out->setStatusCode( 404 );
+                               $out->addHTML(
+                                       '<div class="error">'
+                                       . $this->msg( 'javascripttest-pagetext-unknownaction' )
+                                               ->plaintextParams( $action )->parseAsBlock()
+                                       . '</div>'
+                               );
+                               return;
+                       }
+                       $method = $action . ucfirst( $framework );
+                       $this->$method();
+                       return;
                }
+
+               $out->addModules( 'mediawiki.special.javaScriptTest' );
+
+               $method = 'view' . ucfirst( $framework );
+               $this->$method();
+               $out->setPageTitle( $this->msg(
+                       'javascripttest-title',
+                       // Messages: javascripttest-qunit-name
+                       $this->msg( "javascripttest-$framework-name" )->plain()
+               ) );
        }
 
        /**
@@ -91,7 +116,7 @@ class SpecialJavaScriptTest extends SpecialPage {
         */
        private function getFrameworkListHtml() {
                $list = '<ul>';
-               foreach ( self::$frameworks as $framework => $initFn ) {
+               foreach ( self::$frameworks as $framework ) {
                        $list .= Html::rawElement(
                                'li',
                                array(),
@@ -109,59 +134,132 @@ class SpecialJavaScriptTest extends SpecialPage {
        }
 
        /**
-        * Function to wrap the summary.
-        * It must be given a valid state as a second parameter or an exception will
-        * be thrown.
-        * @param string $html The raw HTML.
-        * @param string $state State, one of 'noframework', 'unknownframework' or 'frameworkfound'
-        * @throws MWException
-        * @return string
+        * Wrap HTML contents in a summary container.
+        *
+        * @param string $html HTML contents to be wrapped
+        * @return string HTML
         */
-       private function wrapSummaryHtml( $html, $state ) {
-               $validStates = array( 'noframework', 'unknownframework', 'frameworkfound' );
-
-               if ( !in_array( $state, $validStates ) ) {
-                       throw new MWException( __METHOD__
-                               . ' given an invalid state. Must be one of "'
-                               . join( '", "', $validStates ) . '".'
-                       );
-               }
-
-               return "<div id=\"mw-javascripttest-summary\" class=\"mw-javascripttest-$state\">$html</div>";
+       private function wrapSummaryHtml( $html ) {
+               return "<div id=\"mw-javascripttest-summary\">$html</div>";
        }
 
        /**
-        * Initialize the page for QUnit.
+        * Run the test suite on the Special page.
+        *
+        * Rendered by OutputPage and Skin.
         */
-       private function initQUnitTesting() {
+       private function viewQUnit() {
                $out = $this->getOutput();
 
-               $out->addModules( 'test.mediawiki.qunit.testrunner' );
-               $qunitTestModules = $out->getResourceLoader()->getTestModuleNames( 'qunit' );
-               $out->addModules( $qunitTestModules );
+               $modules = $out->getResourceLoader()->getTestModuleNames( 'qunit' );
 
                $summary = $this->msg( 'javascripttest-qunit-intro' )
                        ->params( 'https://www.mediawiki.org/wiki/Manual:JavaScript_unit_testing' )
                        ->parseAsBlock();
-               $header = $this->msg( 'javascripttest-qunit-heading' )->escaped();
-               $userDir = $this->getLanguage()->getDir();
 
                $baseHtml = <<<HTML
 <div class="mw-content-ltr">
-<div id="qunit-header"><span dir="$userDir">$header</span></div>
-<div id="qunit-banner"></div>
-<div id="qunit-testrunner-toolbar"></div>
-<div id="qunit-userAgent"></div>
-<ol id="qunit-tests"></ol>
-<div id="qunit-fixture">test markup, will be hidden</div>
+<div id="qunit"></div>
+<div id="qunit-fixture"></div>
 </div>
 HTML;
-               $out->addHtml( $this->wrapSummaryHtml( $summary, 'frameworkfound' ) . $baseHtml );
 
-               // This special page is disabled by default ($wgEnableJavaScriptTest), and contains
-               // no sensitive data. In order to allow test frameworks to embed it into a test client window,
-               // we need to allow iframing of this page.
-               $out->allowClickjacking();
+               $out->addHtml( $this->wrapSummaryHtml( $summary ) . $baseHtml );
+
+               // The testrunner configures QUnit and essentially depends on it. However, test suites
+               // are reusable in environments that preload QUnit (or a compatibility interface to
+               // another framework). Therefore we have to load it ourselves.
+               $out->addHtml( Html::inlineScript(
+                       ResourceLoader::makeLoaderConditionalScript(
+                               Xml::encodeJsCall( 'mw.loader.using', array(
+                                       array( 'jquery.qunit', 'jquery.qunit.completenessTest' ),
+                                       new XmlJsCode(
+                                               'function () {' . Xml::encodeJsCall( 'mw.loader.load', array( $modules ) ) . '}'
+                                       )
+                               ) )
+                       )
+               ) );
+       }
+
+       /**
+        * Generate self-sufficient JavaScript payload to run the tests elsewhere.
+        *
+        * Includes startup module to request modules from ResourceLoader.
+        *
+        * Note: This modifies the registry to replace 'jquery.qunit' with an
+        * empty module to allow external environment to preload QUnit with any
+        * neccecary framework adapters (e.g. Karma). Loading it again would
+        * re-define QUnit and dereference event handlers from Karma.
+        */
+       private function exportQUnit() {
+               $out = $this->getOutput();
+
+               $out->disable();
+
+               $rl = $out->getResourceLoader();
+
+               $query = array(
+                       'lang' => $this->getLanguage()->getCode(),
+                       'skin' => $this->getSkin()->getSkinName(),
+                       'debug' => ResourceLoader::inDebugMode() ? 'true' : 'false',
+               );
+               $embedContext = new ResourceLoaderContext( $rl, new FauxRequest( $query ) );
+               $query['only'] = 'scripts';
+               $startupContext = new ResourceLoaderContext( $rl, new FauxRequest( $query ) );
+
+               $modules = $rl->getTestModuleNames( 'qunit' );
+
+               // The below is essentially a pure-javascript version of OutputPage::getHeadScripts.
+               $startup = $rl->makeModuleResponse( $startupContext, array(
+                       'startup' => $rl->getModule( 'startup' ),
+               ) );
+               // Embed page-specific mw.config variables.
+               // The current Special page shouldn't be relevant to tests, but various modules (which
+               // are loaded before the test suites), reference mw.config while initialising.
+               $code = ResourceLoader::makeConfigSetScript( $out->getJSVars() );
+               // Embed private modules as they're not allowed to be loaded dynamically
+               $code .= $rl->makeModuleResponse( $embedContext, array(
+                       'user.options' => $rl->getModule( 'user.options' ),
+                       'user.tokens' => $rl->getModule( 'user.tokens' ),
+               ) );
+               $code .= Xml::encodeJsCall( 'mw.loader.load', array( $modules ) );
+
+               header( 'Content-Type: text/javascript; charset=utf-8' );
+               header( 'Cache-Control: private, no-cache, must-revalidate' );
+               header( 'Pragma: no-cache' );
+               echo $startup;
+               echo "\n";
+               // Note: The following has to be wrapped in a script tag because the startup module also
+               // writes a script tag (the one loading mediawiki.js). Script tags are synchronous, block
+               // each other, and run in order. But they don't nest. The code appended after the startup
+               // module runs before the added script tag is parsed and executed.
+               echo Xml::encodeJsCall( 'document.write', array( Html::inlineScript( $code  ) ) );
+       }
+
+       private function plainQUnit() {
+               $out = $this->getOutput();
+               $out->disable();
+
+               $url = $this->getPageTitle( 'qunit/export' )->getFullURL( array(
+                       'debug' => ResourceLoader::inDebugMode() ? 'true' : 'false',
+               ) );
+
+               $styles = $out->makeResourceLoaderLink( 'jquery.qunit', ResourceLoaderModule::TYPE_STYLES, false );
+               // Use 'raw' since this is a plain HTML page without ResourceLoader
+               $scripts = $out->makeResourceLoaderLink( 'jquery.qunit', ResourceLoaderModule::TYPE_SCRIPTS, false, array( 'raw' => 'true' ) );
+
+               $head = trim( $styles['html'] . $scripts['html'] );
+               $html = <<<HTML
+<!DOCTYPE html>
+<title>QUnit</title>
+$head
+<div id="qunit"></div>
+<div id="qunit-fixture"></div>
+HTML;
+               $html .= "\n" . Html::linkedScript( $url );
+
+               header( 'Content-Type: text/html; charset=utf-8' );
+               echo $html;
        }
 
        /**
@@ -170,7 +268,7 @@ HTML;
         * @return string[] subpages
         */
        public function getSubpagesForPrefixSearch() {
-               return array_keys( self::$frameworks );
+               return self::$frameworks;
        }
 
        protected function getGroupName() {
index 0b40d2f..28e980c 100644 (file)
@@ -37,7 +37,7 @@ class UsersPager extends AlphabeticPager {
        /**
         * @var array A array with user ids as key and a array of groups as value
         */
-       private $userGroupCache;
+       protected $userGroupCache;
 
        /**
         * @param IContextSource $context
index fddc975..8712f44 100644 (file)
        "import-logentry-interwiki-detail": "$1 {{PLURAL:$1|revision|revisions}} imported from $2",
        "javascripttest": "JavaScript testing",
        "javascripttest-backlink": "< $1",
-       "javascripttest-title": "Running $1 tests",
+       "javascripttest-title": "$1",
        "javascripttest-pagetext-noframework": "This page is reserved for running JavaScript tests.",
        "javascripttest-pagetext-unknownframework": "Unknown testing framework \"$1\".",
+       "javascripttest-pagetext-unknownaction": "Unknown action \"$1\".",
        "javascripttest-pagetext-frameworks": "Please choose one of the following testing frameworks: $1",
        "javascripttest-pagetext-skins": "Choose a skin to run the tests with:",
        "javascripttest-qunit-name": "QUnit",
        "javascripttest-qunit-intro": "See [$1 testing documentation] on mediawiki.org.",
-       "javascripttest-qunit-heading": "MediaWiki JavaScript QUnit test suite",
        "accesskey-pt-userpage": ".",
        "accesskey-pt-anonuserpage": ".",
        "accesskey-pt-mytalk": "n",
index 0005055..2b383ec 100644 (file)
        "import-logentry-interwiki-detail": "Used as success message and log entry. Parameters:\n* $1 - number of succeeded revisions\n* $2 - interwiki name\nSee also:\n* {{msg-mw|Import-logentry-upload-detail}}",
        "javascripttest": "Title of the special page [[Special:JavaScriptTest]].\n\nSee also:\n* {{msg-mw|Javascripttest|title}}\n* {{msg-mw|Javascripttest-pagetext-noframework|summary}}\n* {{msg-mw|Javascripttest-pagetext-unknownframework|error message}}",
        "javascripttest-backlink": "{{optional}}\nUsed as subtitle in [[Special:JavaScriptTest]]. Parameters:\n* $1 - page title",
-       "javascripttest-title": "Title of the special page when running a test suite. Parameters:\n* $1 is the name of the framework, for example QUnit.",
+       "javascripttest-title": "{{Ignore}}",
        "javascripttest-pagetext-noframework": "Used as summary when no framework specified.\n\nSee also:\n* {{msg-mw|Javascripttest|title}}\n* {{msg-mw|Javascripttest-pagetext-noframework|summary}}\n* {{msg-mw|Javascripttest-pagetext-unknownframework|error message}}",
        "javascripttest-pagetext-unknownframework": "Error message when given framework ID is not found. Parameters:\n* $1 - the ID of the framework\nSee also:\n* {{msg-mw|Javascripttest|title}}\n* {{msg-mw|Javascripttest-pagetext-noframework|summary}}\n* {{msg-mw|Javascripttest-pagetext-unknownframework|error message}}",
+       "javascripttest-pagetext-unknownaction": "Error message when url specifies an unknown action. Parameters:\n* $1 - the action specified in the url.",
        "javascripttest-pagetext-frameworks": "Parameters:\n* $1 - frameworks list which contain a link text {{msg-mw|Javascripttest-qunit-name}}",
        "javascripttest-pagetext-skins": "Used as label in [[Special:JavaScriptTest]].",
        "javascripttest-qunit-name": "{{Ignore}}",
        "javascripttest-qunit-intro": "Used as summary. Parameters:\n* $1 - the configured URL to the documentation\nSee also:\n* {{msg-mw|Javascripttest-qunit-heading}}",
-       "javascripttest-qunit-heading": "See also:\n* {{msg-mw|Javascripttest-qunit-intro}}",
        "accesskey-pt-userpage": "{{doc-accesskey}}\nSee also:\n<!--* username-->\n* {{msg-mw|Accesskey-pt-userpage}}\n* {{msg-mw|Tooltip-pt-userpage}}",
        "accesskey-pt-anonuserpage": "{{doc-accesskey}}",
        "accesskey-pt-mytalk": "{{doc-accesskey}}\nSee also:\n* {{msg-mw|Mytalk}}\n* {{msg-mw|Accesskey-pt-mytalk}}\n* {{msg-mw|Tooltip-pt-mytalk}}",
index e240667..ad83e16 100644 (file)
@@ -1466,7 +1466,9 @@ return array(
                        'colon-separator',
                        'javascripttest-pagetext-skins',
                ) ),
-               'dependencies' => array( 'jquery.qunit' ),
+               'dependencies' => array(
+                       'mediawiki.Uri',
+               ),
                'position' => 'top',
                'targets' => array( 'desktop', 'mobile' ),
        ),
index 3dd65fb..fb74e4e 100644 (file)
@@ -6,7 +6,7 @@
 
                // Create useskin dropdown menu and reload onchange to the selected skin
                // (only if a framework was found, not on error pages).
-               $( '#mw-javascripttest-summary.mw-javascripttest-frameworkfound' ).append( function () {
+               $( '#mw-javascripttest-summary' ).append( function () {
 
                        var $html = $( '<p><label for="useskin">'
                                        + mw.message( 'javascripttest-pagetext-skins' ).escaped()
@@ -25,7 +25,8 @@
                        // Bind onchange event handler and append to form
                        $html.append(
                                $( select ).change( function () {
-                                       location.href = QUnit.url( { useskin: $( this ).val() } );
+                                       var url = new mw.Uri();
+                                       location.href = url.extend( { useskin: $( this ).val() } );
                                } )
                        );
 
index a6fbfac..29834c1 100644 (file)
@@ -25,9 +25,9 @@ return array(
                        'tests/qunit/data/testrunner.js',
                ),
                'dependencies' => array(
+                       // Test runner configures QUnit but can't have it as dependency,
+                       // see SpecialJavaScriptTest::viewQUnit.
                        'jquery.getAttrs',
-                       'jquery.qunit',
-                       'jquery.qunit.completenessTest',
                        'mediawiki.page.ready',
                        'mediawiki.page.startup',
                        'test.sinonjs',