Merge "statsd: Rename MediawikiStatsdDataFactory to IBufferingStatsdDataFactory"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Sat, 8 Jul 2017 03:26:42 +0000 (03:26 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Sat, 8 Jul 2017 03:26:42 +0000 (03:26 +0000)
17 files changed:
includes/DefaultSettings.php
includes/api/ApiErrorFormatter.php
includes/exception/LocalizedException.php
includes/installer/CliInstaller.php
includes/specials/SpecialRecentchanges.php
languages/i18n/en.json
languages/i18n/qqq.json
resources/Resources.php
resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.ChangesListViewModel.js
resources/src/mediawiki.rcfilters/mw.rcfilters.Controller.js
resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.FilterWrapperWidget.less
resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.LiveUpdateButtonWidget.less [new file with mode: 0644]
resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterWrapperWidget.js
resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.LiveUpdateButtonWidget.js [new file with mode: 0644]
tests/parser/ParserTestRunner.php
tests/parser/parserTests.php
tests/phpunit/suites/ParserTestTopLevelSuite.php

index 1459ab6..11f08b2 100644 (file)
@@ -6333,15 +6333,16 @@ $wgSiteStatsAsyncFactor = false;
  * Parser test suite files to be run by parserTests.php when no specific
  * filename is passed to it.
  *
- * Extensions may add their own tests to this array, or site-local tests
- * may be added via LocalSettings.php
+ * Extensions using extension.json will have any *.txt file in a
+ * tests/parser/ directory automatically run.
+ *
+ * Core tests can be added to ParserTestRunner::$coreTestFiles.
  *
  * Use full paths.
+ *
+ * @deprecated since 1.30
  */
-$wgParserTestFiles = [
-       "$IP/tests/parser/parserTests.txt",
-       "$IP/tests/parser/extraParserTests.txt"
-];
+$wgParserTestFiles = [];
 
 /**
  * Allow running of javascript test suites via [[Special:JavaScriptTest]] (such as QUnit).
@@ -6778,6 +6779,11 @@ $wgStructuredChangeFiltersEnableSaving = true;
  */
 $wgStructuredChangeFiltersEnableExperimentalViews = false;
 
+/**
+ * Whether to allow users to use the experimental live update feature in the new RecentChanges UI
+ */
+$wgStructuredChangeFiltersEnableLiveUpdate = false;
+
 /**
  * Use new page patrolling to check new pages on Special:Newpages
  */
index 5484a78..7fb1352 100644 (file)
@@ -254,7 +254,7 @@ class ApiErrorFormatter {
                $ret = preg_replace( '!</?(var|kbd|samp|code)>!', '"', $text );
 
                // Strip tags and decode.
-               $ret = html_entity_decode( strip_tags( $ret ), ENT_QUOTES | ENT_HTML5 );
+               $ret = Sanitizer::stripAllTags( $ret );
 
                return $ret;
        }
index cbdb53e..d2cb5d1 100644 (file)
@@ -56,7 +56,7 @@ class LocalizedException extends Exception implements ILocalizedException {
                // customizations, and make a basic attempt to turn markup into text.
                $msg = $this->getMessageObject()->inLanguage( 'en' )->useDatabase( false )->text();
                $msg = preg_replace( '!</?(var|kbd|samp|code)>!', '"', $msg );
-               $msg = html_entity_decode( strip_tags( $msg ), ENT_QUOTES | ENT_HTML5 );
+               $msg = Sanitizer::stripAllTags( $msg );
                parent::__construct( $msg, $code, $previous );
        }
 
index 661c3ec..af55dbb 100644 (file)
@@ -180,7 +180,7 @@ class CliInstaller extends Installer {
 
                $text = preg_replace( '/<a href="(.*?)".*?>(.*?)<\/a>/', '$2 &lt;$1&gt;', $text );
 
-               return html_entity_decode( strip_tags( $text ), ENT_QUOTES );
+               return Sanitizer::stripAllTags( $text );
        }
 
        /**
index d856d4b..2fe56f9 100644 (file)
@@ -138,9 +138,6 @@ class SpecialRecentChanges extends ChangesListSpecialPage {
         * @param string $subpage
         */
        public function execute( $subpage ) {
-               global $wgStructuredChangeFiltersEnableSaving,
-                       $wgStructuredChangeFiltersEnableExperimentalViews;
-
                // Backwards-compatibility: redirect to new feed URLs
                $feedFormat = $this->getRequest()->getVal( 'feed' );
                if ( !$this->including() && $feedFormat ) {
@@ -180,19 +177,28 @@ class SpecialRecentChanges extends ChangesListSpecialPage {
                                )
                        );
 
+                       $experimentalStructuredChangeFilters =
+                               $this->getConfig()->get( 'StructuredChangeFiltersEnableExperimentalViews' );
+
                        $out->addJsConfigVars( 'wgStructuredChangeFilters', $jsData['groups'] );
                        $out->addJsConfigVars(
                                'wgStructuredChangeFiltersEnableSaving',
-                               $wgStructuredChangeFiltersEnableSaving
+                               $this->getConfig()->get( 'StructuredChangeFiltersEnableSaving' )
                        );
                        $out->addJsConfigVars(
                                'wgStructuredChangeFiltersEnableExperimentalViews',
-                               $wgStructuredChangeFiltersEnableExperimentalViews
+                               $experimentalStructuredChangeFilters
                        );
                        $out->addJsConfigVars(
-                               'wgRCFiltersChangeTags',
-                               $this->buildChangeTagList()
+                               'wgStructuredChangeFiltersEnableLiveUpdate',
+                               $this->getConfig()->get( 'StructuredChangeFiltersEnableLiveUpdate' )
                        );
+                       if ( $experimentalStructuredChangeFilters ) {
+                               $out->addJsConfigVars(
+                                       'wgRCFiltersChangeTags',
+                                       $this->buildChangeTagList()
+                               );
+                       }
                }
        }
 
@@ -202,10 +208,6 @@ class SpecialRecentChanges extends ChangesListSpecialPage {
         * @return Array Tag data
         */
        protected function buildChangeTagList() {
-               function stripAllHtml( $input ) {
-                       return trim( html_entity_decode( strip_tags( $input ) ) );
-               }
-
                $explicitlyDefinedTags = array_fill_keys( ChangeTags::listExplicitlyDefinedTags(), 0 );
                $softwareActivatedTags = array_fill_keys( ChangeTags::listSoftwareActivatedTags(), 0 );
                $tagStats = ChangeTags::tagUsageStatistics();
@@ -228,8 +230,10 @@ class SpecialRecentChanges extends ChangesListSpecialPage {
 
                                $result[] = [
                                        'name' => $tagName,
-                                       'label' => stripAllHtml( ChangeTags::tagDescription( $tagName, $this->getContext() ) ),
-                                       'description' => $desc ? stripAllHtml( $desc->parse() ) : '',
+                                       'label' => Sanitizer::stripAllTags(
+                                               ChangeTags::tagDescription( $tagName, $this->getContext() )
+                                       ),
+                                       'description' => $desc ? Sanitizer::stripAllTags( $desc->parse() ) : '',
                                        'cssClass' => Sanitizer::escapeClass( 'mw-tag-' . $tagName ),
                                        'hits' => $hits,
                                ];
index 7d107d9..9447de6 100644 (file)
        "rcfilters-view-namespaces-tooltip": "Filter results by namespace",
        "rcfilters-view-tags-tooltip": "Filter results using edit tags",
        "rcfilters-view-return-to-default-tooltip": "Return to main filter menu",
+       "rcfilters-liveupdates-button": "Live updates",
        "rcnotefrom": "Below {{PLURAL:$5|is the change|are the changes}} since <strong>$3, $4</strong> (up to <strong>$1</strong> shown).",
        "rclistfromreset": "Reset date selection",
        "rclistfrom": "Show new changes starting from $2, $3",
index 4d854d9..7c995f0 100644 (file)
        "rcfilters-view-namespaces-tooltip": "Tooltip for the button that loads the namespace view in [[Special:RecentChanges]]",
        "rcfilters-view-tags-tooltip": "Tooltip for the button that loads the tags view in [[Special:RecentChanges]]",
        "rcfilters-view-return-to-default-tooltip": "Tooltip for the button that returns to the default filter view in [[Special:RecentChanges]]",
+       "rcfilters-liveupdates-button": "Label for the button to enable or disable live updates on [[Special:RecentChanges]]",
        "rcnotefrom": "This message is displayed at [[Special:RecentChanges]] when viewing recentchanges from some specific time.\n\nThe corresponding message is {{msg-mw|Rclistfrom}}.\n\nParameters:\n* $1 - the maximum number of changes that are displayed\n* $2 - (Optional) a date and time\n* $3 - a date\n* $4 - a time\n* $5 - Number of changes are displayed, for use with PLURAL",
        "rclistfromreset": "Used on [[Special:RecentChanges]] to reset a selection of a certain date range.",
        "rclistfrom": "Used on [[Special:RecentChanges]]. Parameters:\n* $1 - (Currently not use) date and time. The date and the time adds to the rclistfrom description.\n* $2 - time. The time adds to the rclistfrom link description (with split of date and time).\n* $3 - date. The date adds to the rclistfrom link description (with split of date and time).\n\nThe corresponding message is {{msg-mw|Rcnotefrom}}.",
index 8001243..a8cf91d 100644 (file)
@@ -1789,6 +1789,7 @@ return [
                        'resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FormWrapperWidget.js',
                        'resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterItemHighlightButton.js',
                        'resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.HighlightColorPickerWidget.js',
+                       'resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.LiveUpdateButtonWidget.js',
                        'resources/src/mediawiki.rcfilters/mw.rcfilters.HighlightColors.js',
                        'resources/src/mediawiki.rcfilters/mw.rcfilters.init.js',
                ],
@@ -1812,6 +1813,7 @@ return [
                        'resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.SavedLinksListWidget.less',
                        'resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.SavedLinksListItemWidget.less',
                        'resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.SaveFiltersPopupButtonWidget.less',
+                       'resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.LiveUpdateButtonWidget.less',
                ],
                'skinStyles' => [
                        'monobook' => [
@@ -1859,6 +1861,7 @@ return [
                        'rcfilters-view-namespaces-tooltip',
                        'rcfilters-view-tags-tooltip',
                        'rcfilters-view-return-to-default-tooltip',
+                       'rcfilters-liveupdates-button',
                        'blanknamespace',
                        'namespaces',
                        'invert',
@@ -1877,6 +1880,7 @@ return [
                        'oojs-ui.styles.icons-interactions',
                        'oojs-ui.styles.icons-content',
                        'oojs-ui.styles.icons-layout',
+                       'oojs-ui.styles.icons-media',
                ],
        ],
        'mediawiki.special' => [
index d6ce734..c839a13 100644 (file)
@@ -28,6 +28,7 @@
        /**
         * @event update
         * @param {jQuery|string} changesListContent
+        * @param {jQuery} $fieldset
         *
         * The list of change is now up to date
         */
index 27387c9..5858566 100644 (file)
                this._trackHighlight( 'clear', filterName );
        };
 
+       /**
+        * Enable or disable live updates.
+        * @param {boolean} enable True to enable, false to disable
+        */
+       mw.rcfilters.Controller.prototype.toggleLiveUpdate = function ( enable ) {
+               if ( enable && !this.liveUpdateTimeout ) {
+                       this._scheduleLiveUpdate();
+               } else if ( !enable && this.liveUpdateTimeout ) {
+                       clearTimeout( this.liveUpdateTimeout );
+                       this.liveUpdateTimeout = null;
+               }
+       };
+
+       /**
+        * Set a timeout for the next live update.
+        * @private
+        */
+       mw.rcfilters.Controller.prototype._scheduleLiveUpdate = function () {
+               this.liveUpdateTimeout = setTimeout( this._doLiveUpdate.bind( this ), 3000 );
+       };
+
+       /**
+        * Perform a live update.
+        * @private
+        */
+       mw.rcfilters.Controller.prototype._doLiveUpdate = function () {
+               var controller = this;
+               this.updateChangesList( {}, true )
+                       .always( function () {
+                               if ( controller.liveUpdateTimeout ) {
+                                       // Live update was not disabled in the meantime
+                                       controller._scheduleLiveUpdate();
+                               }
+                       } );
+       };
+
        /**
         * Save the current model state as a saved query
         *
         * Update the list of changes and notify the model
         *
         * @param {Object} [params] Extra parameters to add to the API call
+        * @param {boolean} [isLiveUpdate] Don't update the URL or invalidate the changes list
+        * @return {jQuery.Promise} Promise that is resolved when the update is complete
         */
-       mw.rcfilters.Controller.prototype.updateChangesList = function ( params ) {
-               this._updateURL( params );
-               this.changesListModel.invalidate();
-               this._fetchChangesList()
+       mw.rcfilters.Controller.prototype.updateChangesList = function ( params, isLiveUpdate ) {
+               if ( !isLiveUpdate ) {
+                       this._updateURL( params );
+                       this.changesListModel.invalidate();
+               }
+               return this._fetchChangesList()
                        .then(
                                // Success
                                function ( pieces ) {
index 1a29459..5aa866d 100644 (file)
@@ -7,4 +7,8 @@
        &-viewToggleButtons {
                margin-top: 1em;
        }
+
+       &-bottom {
+               margin-top: 1em;
+       }
 }
diff --git a/resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.LiveUpdateButtonWidget.less b/resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.LiveUpdateButtonWidget.less
new file mode 100644 (file)
index 0000000..63ea264
--- /dev/null
@@ -0,0 +1,37 @@
+.mw-rcfilters-ui-liveUpdateButtonWidget {
+       &.oo-ui-toggleWidget-on {
+               position: relative;
+               overflow: hidden;
+               &:after {
+                       content: '';
+                       mix-blend-mode: screen;
+                       position: absolute;
+                       width: 1.875em;
+                       height: 1.875em;
+                       top: 1.875em / 4;
+                       left: 0.46875em;
+                       background: rgba( 51, 102, 204, 0.5 );
+                       border-radius: 100%;
+                       transform-origin: 50% 50%;
+                       opacity: 0;
+                       animation: ripple 1.2s ease-out infinite;
+                       animation-delay: 1s;
+               }
+       }
+}
+
+@keyframes ripple {
+       0%,
+       35% {
+               transform: scale( 0 );
+               opacity: 1;
+       }
+       50% {
+               transform: scale( 1.5 );
+               opacity: 0.8;
+       }
+       100% {
+               opacity: 0;
+               transform: scale( 4 );
+       }
+}
index ebef62f..a748063 100644 (file)
@@ -14,6 +14,7 @@
         * @cfg {jQuery} [$overlay] A jQuery object serving as overlay for popups
         */
        mw.rcfilters.ui.FilterWrapperWidget = function MwRcfiltersUiFilterWrapperWidget( controller, model, savedQueriesModel, config ) {
+               var $bottom;
                config = config || {};
 
                // Parent
                        { $overlay: this.$overlay }
                );
 
+               this.liveUpdateButton = new mw.rcfilters.ui.LiveUpdateButtonWidget(
+                       this.controller
+               );
+
                // Initialize
                this.$element
                        .addClass( 'mw-rcfilters-ui-filterWrapperWidget' );
 
                }
 
+               $bottom = $( '<div>' )
+                       .addClass( 'mw-rcfilters-ui-filterWrapperWidget-bottom' );
+
+               if ( mw.config.get( 'wgStructuredChangeFiltersEnableLiveUpdate' ) ) {
+                       $bottom.append( this.liveUpdateButton.$element );
+               }
+
                this.$element.append(
-                       this.filterTagWidget.$element
+                       this.filterTagWidget.$element,
+                       $bottom
                );
        };
 
diff --git a/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.LiveUpdateButtonWidget.js b/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.LiveUpdateButtonWidget.js
new file mode 100644 (file)
index 0000000..8bab981
--- /dev/null
@@ -0,0 +1,42 @@
+( function ( mw ) {
+       /**
+        * Widget for toggling live updates
+        *
+        * @extends OO.ui.ToggleButtonWidget
+        *
+        * @constructor
+        * @param {mw.rcfilters.Controller} controller
+        * @param {Object} config Configuration object
+        */
+       mw.rcfilters.ui.LiveUpdateButtonWidget = function MwRcfiltersUiLiveUpdateButtonWidget( controller, config ) {
+               config = config || {};
+
+               // Parent
+               mw.rcfilters.ui.LiveUpdateButtonWidget.parent.call( this, $.extend( {
+                       icon: 'play',
+                       label: mw.message( 'rcfilters-liveupdates-button' ).text()
+               } ), config );
+
+               this.controller = controller;
+
+               // Events
+               this.connect( this, { change: 'onChange' } );
+
+               this.$element.addClass( 'mw-rcfilters-ui-liveUpdateButtonWidget' );
+       };
+
+       /* Initialization */
+
+       OO.inheritClass( mw.rcfilters.ui.LiveUpdateButtonWidget, OO.ui.ToggleButtonWidget );
+
+       /* Methods */
+
+       /**
+        * Respond to the button being toggled.
+        * @param {boolean} enable Whether the button is now pressed/enabled
+        */
+       mw.rcfilters.ui.LiveUpdateButtonWidget.prototype.onChange = function ( enable ) {
+               this.controller.toggleLiveUpdate( enable );
+       };
+
+}( mediaWiki ) );
index 9dce73f..9255733 100644 (file)
@@ -34,6 +34,18 @@ use Wikimedia\TestingAccessWrapper;
  * @ingroup Testing
  */
 class ParserTestRunner {
+
+       /**
+        * MediaWiki core parser test files, paths
+        * will be prefixed with __DIR__ . '/'
+        *
+        * @var array
+        */
+       private static $coreTestFiles = [
+               'parserTests.txt',
+               'extraParserTests.txt',
+       ];
+
        /**
         * @var bool $useTemporaryTables Use temporary tables for the temporary database
         */
@@ -147,6 +159,43 @@ class ParserTestRunner {
                }
        }
 
+       /**
+        * Get list of filenames to extension and core parser tests
+        *
+        * @return array
+        */
+       public static function getParserTestFiles() {
+               global $wgParserTestFiles;
+
+               // Add core test files
+               $files = array_map( function( $item ) {
+                       return __DIR__ . "/$item";
+               }, self::$coreTestFiles );
+
+               // Plus legacy global files
+               $files = array_merge( $files, $wgParserTestFiles );
+
+               // Auto-discover extension parser tests
+               $registry = ExtensionRegistry::getInstance();
+               foreach ( $registry->getAllThings() as $info ) {
+                       $dir = dirname( $info['path'] ) . '/tests/parser';
+                       if ( !file_exists( $dir ) ) {
+                               continue;
+                       }
+                       $dirIterator = new RecursiveIteratorIterator(
+                               new RecursiveDirectoryIterator( $dir )
+                       );
+                       foreach ( $dirIterator as $fileInfo ) {
+                               /** @var SplFileInfo $fileInfo */
+                               if ( substr( $fileInfo->getFilename(), -4 ) === '.txt' ) {
+                                       $files[] = $fileInfo->getPathname();
+                               }
+                       }
+               }
+
+               return array_unique( $files );
+       }
+
        public function getRecorder() {
                return $this->recorder;
        }
index 1d0867a..2735f93 100644 (file)
@@ -80,7 +80,7 @@ class ParserTestsMaintenance extends Maintenance {
        }
 
        public function execute() {
-               global $wgParserTestFiles, $wgDBtype;
+               global $wgDBtype;
 
                // Cases of weird db corruption were encountered when running tests on earlyish
                // versions of SQLite
@@ -167,7 +167,7 @@ class ParserTestsMaintenance extends Maintenance {
                }
 
                // Default parser tests and any set from extensions or local config
-               $files = $this->getOption( 'file', $wgParserTestFiles );
+               $files = $this->getOption( 'file', ParserTestRunner::getParserTestFiles() );
 
                $norm = $this->hasOption( 'norm' ) ? explode( ',', $this->getOption( 'norm' ) ) : [];
 
index 5d5d693..09052f3 100644 (file)
@@ -69,7 +69,7 @@ class ParserTestTopLevelSuite extends PHPUnit_Framework_TestSuite {
                if ( is_string( $flags ) ) {
                        $flags = self::CORE_ONLY;
                }
-               global $wgParserTestFiles, $IP;
+               global $IP;
 
                $mwTestDir = $IP . '/tests/';
 
@@ -81,7 +81,8 @@ class ParserTestTopLevelSuite extends PHPUnit_Framework_TestSuite {
                $filesToTest = [];
 
                # Filter out .txt files
-               foreach ( $wgParserTestFiles as $parserTestFile ) {
+               $files = ParserTestRunner::getParserTestFiles();
+               foreach ( $files as $parserTestFile ) {
                        $isCore = ( 0 === strpos( $parserTestFile, $mwTestDir ) );
 
                        if ( $isCore && $wantsCore ) {