Merge "Compare selected revisions on history page is marked with a class"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Fri, 29 Mar 2019 00:11:32 +0000 (00:11 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Fri, 29 Mar 2019 00:11:32 +0000 (00:11 +0000)
12 files changed:
HISTORY
autoload.php
includes/actions/HistoryAction.php
includes/actions/pagers/HistoryPager.php
includes/libs/rdbms/database/AtomicSectionIdentifier.php [deleted file]
includes/libs/rdbms/database/DatabaseDomain.php [deleted file]
includes/libs/rdbms/database/domain/DatabaseDomain.php [new file with mode: 0644]
includes/libs/rdbms/database/utils/AtomicSectionIdentifier.php [new file with mode: 0644]
includes/user/User.php
resources/Resources.php
tests/phpunit/MediaWikiTestCase.php
tests/phpunit/includes/specials/SpecialWatchlistTest.php

diff --git a/HISTORY b/HISTORY
index 921b881..020ac66 100644 (file)
--- a/HISTORY
+++ b/HISTORY
@@ -19540,3 +19540,294 @@ recommended on live sites. (This must be set for MathML to display properly in
 Mozilla.) The new 'MonoBook' skin is not compatible with PHP 5 due to bugs in
 the underlying PHPTAL library. It will be automatically disabled when running
 on PHP5; the older look and feel will be used instead.
+
+= MediaWiki 1.2 =
+
+== MediaWiki 1.2.6, 2004-05-24 ==
+* Spam blocker ({{wg|SpamRegex}} - refuses to save edits that match)
+* Updated documentation about {{wg|WhitelistRead}}
+* Ensure that searchindex table is created as MyISAM
+* Interwiki cache timeout (memcached)
+* Fix uploads on Windows with magic_quotes_gpc
+* Some config fixes for Windows (slashes etc)
+* Local interwiki URL redirects
+* Fixed obscure deletion problem in squid mode on corrupt entries
+* Language files updated to remove more hard-coded "Wikipedia" strings
+
+== MediaWiki 1.2.5, 2004-05-03 ==
+* Fixed install problem with blank root password
+* Fixed Special:Emailuser/Username links
+* Fixed main-page edit links on fuzzy search results
+* Fixed wikipedia-interwiki.sql
+* Fixed install with apache2filter (ugly URLs)
+* IP in 'go' search brings up contributions
+* Switch from broken & to ? on top-level wiki URL hack
+* Fix for moved pages in enhanced Recentchanges
+* Initial main page on new installs links to the online documentation
+
+== MediaWiki 1.2.4, 2004-04-13 ==
+* Fixed edit toolbar in Mozilla
+* Diff links in Contributions for 'top' edits
+* Fixed Nostalgia skin drop-down for register_globals off
+* Backported optional open proxy blocker
+* Backported {{wg|WhitelistRead}}
+* {{wg|CapitalLinks}} option to force full case sensitivity in titles
+* Cleaned up error handling when can't talk to database
+* Disabled unsafe command-line installer (remove the <code>die()</code> call to
+use)
+
+== MediaWiki 1.2.3, 2004-04-02 ==
+* Fixed an in-place install bug with non-root MySQL user
+* Fixed history diff checkboxes bug on titles with ampersands
+* Fixed printable link bug on special pages with parameters
+* Fixed bug that broke IP blocking w/o memcached
+* Turns off E_NOTICE warnings if PHP settings have them on (you can grope in
+and turn this off if you like to debug)
+
+== MediaWiki 1.2.2, 2004-03-28 ==
+* Fixed an upgrade bug introduced in 1.2.1.
+* Disabled {{wg|UseCategoryMagic}}, which feature is incomplete broken
+
+== MediaWiki 1.2.1, 2004-03-27 ==
+Installation, compatibility, security fixlets:
+* Detect use of PHP as CGI and disable <code>index.php/Title</code> URLs
+* Try to auto-create math tmp & output directories if not present
+* Disable Asksql in default install ({{wg|AllowSysopQueries}})
+* Better handling of <code>get_magic_quotes_gpc</code> (apostrophe problems)
+* French localisation no longer hard-codes "Wikipedia" name
+
+== MediaWiki 1.2.0, 2004-03-24 ==
+This is the new production release; it is more or less in sync with what is
+running on Wikipedia right now. However this software is provided with NO
+WARRANTY of fitness for any purpose; there may be some interesting bugs, it may
+eat all your data, and documentation may not be up to date. New features in 1.2:
+* In-place web-based installation [experimental!] Note that maintenance
+functions are not yet available through the web install script.
+* Image resizing/thumbnail generation
+* Stricter upload file extension blacklist and whitelist options
+* More flexible blocking system; time period may be set
+* Handier sysop account management. An account marked "bureaucrat" may assign
+sysop access to other accounts via Special:Makesysop. (The exact details of
+this may change in the future)
+* Support for a squid cache with explicit purging of cached anon pages
+* Optional compression of old revision text (requires zlib support)
+* Fuzzy title search (experimental, requires memcached)
+* Page rendering cache (experimental)
+* Editing toolbar to demonstrate wiki syntax to newbies (off by default in user
+preferences)
+* Support for authenticated SMTP outgoing e-mail (experimental)
+* It's now possible to assign sysop accounts from within the wiki. An account
+with this ability must be labeled with the "bureaucrat" privilege, such as the
+'Developer' account created by the install. Fixes and tweaks:
+* Now works with register_globals off!
+* Should work out of the box on MySQL 3.2.x again. On 4.x set
+{{wg|EnablePersistentLC}}<code> = true;</code> to turn on the link cache table
+for a slight rendering speed boost.
+* Should work on PHP 5.0 beta (not thoroughly tested)
+* Works with short tags disabled.
+* rebuildMessages.php can now selectively update new messages, or overwrite
+everything.
+* Some layout fixes for RTL languages.
+* Now includes arrow icons for enhanced recent changes.
+* Various bug fixes.
+
+=== Behavior changes ===
+* wiki.phtml and redirect.phtml are now renamed to index.php and redirect.php
+The old names are provided too for compatibility, but make sure they don't
+conflict if you've been putting other files in your wiki.
+* Uploaded filenames are more strictly checked than before. See bits in
+DefaultSettings.php to tweak this behavior to your needs.
+* Database messages are now enabled by default, so the interface messages can
+be tweaked through the wiki with a sysop account. Disable this if you don't
+want the performance hit.
+
+=== Database changes ===
+An index was added to recentchanges table to speed up Newpages
+(patch-rc-newindex.sql for manual updaters). Expiration date field has been
+added to ipblocks table ({{manual|patch-ipb_expiry.sql}} for manual updaters).
+The links tables have slightly stricter indexes. ('links' and 'brokenlinks' are
+not changed on existing installations.)
+
+=== Known problems ===
+The version 1.1.0 LocalSettings.sample file included the setting
+{{wg|CategoryMagic}}<code> = true;</code> this setting is for an experimental
+feature that _does not work correctly_. If you have it left over, turn it off
+or you'll see mysterious problems with vanishing links. There may be problems
+with session handling on some systems. Checking the "remember my password" box
+may help as a temporary workaround. If you receive "Cannot load input file"
+errors when trying to get at the wiki after installation, make the following
+changes:
+  in LocalSettings.php change the line something like this:
+    {{wg|ArticlePath}} = "/wiki/index.php/$1";
+  to:
+    {{wg|ArticlePath}} = "/wiki/index.php?title=$1";
+  in index.php, remove these lines:
+    if( isset( $_SERVER['PATH_INFO'] ) ) {
+        $title = substr( $_SERVER['PATH_INFO'], 1 );
+    } else {
+        $title = $_REQUEST['title'];
+    }
+
+= MediaWiki 1.1 =
+
+== MediaWiki 1.1.0, 2003-12-08 ==
+
+This is the new production release. Any following 1.1.x releases are expected
+to contain only bug fixes; developments of new features will go towards a 1.2.0
+release.
+New features in 1.1:
+* New wiki table syntax:
+http://meta.wikipedia.org/wiki/MediaWiki_User%27s_Guide:_Using_tables
+* User-editable interface messages:
+http://meta.wikipedia.org/wiki/MediaWiki_namespace
+* XML-wrapped page source export with optional history:
+http://meta.wikipedia.org/wiki/XML_import_and_export (There is not yet an
+import function!)
+* "Magic words" Fixes and tweaks:
+* linkscc table caches link data for rendering; faster
+{{manual|rebuildlinks.php}}
+* Numerous bugs in [[skin:Cologne Blue|Cologne Blue]] skin fixed
+* Login gives warning about missing cookies
+* Block log, protection log added; deletion log now includes undeletions
+* Deletion & upload logs now escape comment text properly
+* Problems with <nowiki><nowiki></nowiki> segments in section titles etc
+mitigated
+* Contributions offset and minor edit bugs fixed
+* Whatlinkshere now sorted alphabetically
+* Various exciting new profiling options.
+* Debug log is off by default.
+* Various small bugs fixed. Internal changes:
+* wfQuery has had a second parameter inserted, DB_READ or DB_WRITE. This value
+is not actually used so far.
+* Partial code for categories and Smarty template-based skins is in the tree
+but disabled.
+* Parts of Article.php have been moved to {{manual|EditPage.php}} and
+{{manual|ImagePage.php}}. New translations:
+* fi - Finnish
+* ia - Interlingua
+* no - Norwegian
+* sk - Slovak
+* ta - Tamil
+
+=== Database changes ===
+"linkscc" table added. If upgrading manually (rather than with
+{{manual|update.php}}), run maintenance/archives/patch-linkscc.sql to create
+the table. Older releases were dated snapshots from the old 'stable' branch:
+
+= pre-MediaWiki 1.1.0 =
+
+== Mediawiki-20031118 ==
+* Image deletion fixed.
+* Deletion of image old revisions now restricted to sysops (this is an
+irreversible action and not well logged)
+* Fixed maintenance scripts broken by last release's security fix
+* Many errors in {{manual|rebuildlinks.php|rebuildlinks}} script fixed.
+
+== Mediawiki-20031117 ==
+* SECURITY FIX: stricter checking of include path
+* Fixed user contributions next/prev bug
+* Login cookies now have the database name prefixed to allow wikis to coexist
+in the same domain. This will invalidate any old saved password cookies.
+* Update cache timestamp when talk pages are created
+* Saving the login form in Mozilla no longer blanks password in prefs.
+* Check existence of source page before performing a move.
+* Detect invalid titles in Special:Allpages
+* Q-encode headers on outgoing inter-user e-mail
+* Updates to some translations.
+* Added table of contents border/bg to Cologne Blue, Nostalgia skins
+* Protected pages no longer appear unprotected when visited via redirect
+* Swapped old Wikipedia logo for the MediaWiki sunflower logo
+* install.php, update.php print warning on old PHP versions, added
+compatibility functions that might or might not help No database changes since
+20031107; upgrading should be clean.
+
+== Mediawiki-20031107 ==
+* Fixed various bugs!
+* Some speed improvements from tweaks to the table indexes
+* Limited support for memcached (see below)
+* New translations (see below)
+* Interwiki link data now kept in database for flexibility
+* Friendlier read-only source view if asked to edit a page when the db is
+locked or the page is protected.
+* Normal IP blocks auto-expire after 24 hours
+* Optional support for blocking usernames
+* Uploads disabled by default (see below)
+
+== Mediawiki-20030829 ==
+First release under MediaWiki name.
+
+=== Security note ===
+Uploads are now disabled by default. If you've set up a secure configuration
+you can reenable uploads by putting: $wgDisableUploads = false;
+into LocalSettings.php. Earlier versions of MediaWiki included a bug that
+potentially allows logged- in users to delete arbitrary files in directories
+writable by the web server user by manually feeding false form data; this is
+now fixed. As a reminder, disable PHP script execution in the upload directory!
+You may also wish to serve HTML pages as plaintext to prevent cookie- stealing
+JavaScript attacks. Example Apache config fragment:
+<pre>
+<Directory "/Library/MediaWiki/web/upload">
+     # Ignore .htaccess files
+     AllowOverride None
+
+     # Serve HTML as plaintext
+     AddType text/plain .html .htm .shtml
+
+     # Don't run arbitrary PHP code.
+     php_admin_flag engine off
+
+     # If you've other scripting languages, disable them too.
+</Directory>
+</pre>
+
+=== Database updates ===
+If you're using {{manual|update.php}}, the necessary database changes should be
+made automatically. To manually upgrade your database from the 2003-08-29
+release, run the following SQL scripts from the maintenance subdirectory:
+archives/patch-ipblocks.sql archives/patch-interwiki.sql
+archives/patch-indexes.sql interwiki.sql To copy in the Wikipedia
+language-prefix interwikis as well, add: wikipedia-interwiki.sql
+
+=== Translations ===
+New interface localization files are included for:
+*fy - Frisian
+*ro - Romanian
+*sl - Slovene
+*sq - Albanian
+*sr - Serbian
+
+=== Memcached ===
+Memcached is a distributed cache system. See http://www.danga.com/memcached/
+MediaWiki can optionally use memcached to store some data between calls to
+reduce load on the database. Currently this is limited to user and talk page
+notification data, interwiki prefix/URL matches, and the UTF-8 conversion
+tables. MediaWiki includes version 1.0.10 of the (GPL'd) PHP memcached client
+by Ryan Gilfether; if memcached is disabled it acts as a dummy object with
+minimal overhead. To use memcached you'll need PHP installed with sockets
+support (this is not in the default configure options). See docs/memcached for
+some more details. Additionally, you can store login session data in memcached
+instead of the local filesystem, which can help to enable load-balancing by
+letting login sessions transparently work on multiple front-end web servers.
+(The primary other issue is with uploads, which requires some care in
+handling.) To enable this, set $wgSessionsInMemcached = true; and set
+$wgCookieDomain appropriately if exposing multiple hostnames. This system is
+new and may be volatile; login sessions will fail dramatically if memcached is
+unavailable when this option is turned on.
+
+=== Online documentation ===
+Documentation for both end-users and site administrators is currently being
+built up on Meta-Wikipedia, and is covered under the GNU Free Documentation
+License: http://meta.wikipedia.org/wiki/MediaWiki_User%27s_Guide
+
+=== Mailing list ===
+A MediaWiki-l mailing list has been set up distinct from the Wikipedia
+wikitech-l list: http://mail.wikipedia.org/mailman/listinfo/mediawiki-l
+
+=== UseModWiki import script ===
+A stripped-down UseModWiki import script is available in the maintenance
+subdirectory. It is incomplete and requires a lot of manual clean-up, but does
+function for the brave and pure of heart.
+
+=== Test suite removed ===
+The unmaintained Java-based test suite has been removed from the tarball
+release. If you really want it you can check it out from CVS.
index 0d2bac9..2abf2f8 100644 (file)
@@ -1625,7 +1625,7 @@ $wgAutoloadLocalClasses = [
        'WikiTextStructure' => __DIR__ . '/includes/content/WikiTextStructure.php',
        'Wikimedia\\Http\\HttpAcceptNegotiator' => __DIR__ . '/includes/libs/http/HttpAcceptNegotiator.php',
        'Wikimedia\\Http\\HttpAcceptParser' => __DIR__ . '/includes/libs/http/HttpAcceptParser.php',
-       'Wikimedia\\Rdbms\\AtomicSectionIdentifier' => __DIR__ . '/includes/libs/rdbms/database/AtomicSectionIdentifier.php',
+       'Wikimedia\\Rdbms\\AtomicSectionIdentifier' => __DIR__ . '/includes/libs/rdbms/database/utils/AtomicSectionIdentifier.php',
        'Wikimedia\\Rdbms\\Blob' => __DIR__ . '/includes/libs/rdbms/encasing/Blob.php',
        'Wikimedia\\Rdbms\\ChronologyProtector' => __DIR__ . '/includes/libs/rdbms/ChronologyProtector.php',
        'Wikimedia\\Rdbms\\ConnectionManager' => __DIR__ . '/includes/libs/rdbms/connectionmanager/ConnectionManager.php',
@@ -1644,7 +1644,7 @@ $wgAutoloadLocalClasses = [
        'Wikimedia\\Rdbms\\DBTransactionStateError' => __DIR__ . '/includes/libs/rdbms/exception/DBTransactionStateError.php',
        'Wikimedia\\Rdbms\\DBUnexpectedError' => __DIR__ . '/includes/libs/rdbms/exception/DBUnexpectedError.php',
        'Wikimedia\\Rdbms\\Database' => __DIR__ . '/includes/libs/rdbms/database/Database.php',
-       'Wikimedia\\Rdbms\\DatabaseDomain' => __DIR__ . '/includes/libs/rdbms/database/DatabaseDomain.php',
+       'Wikimedia\\Rdbms\\DatabaseDomain' => __DIR__ . '/includes/libs/rdbms/database/domain/DatabaseDomain.php',
        'Wikimedia\\Rdbms\\DatabaseMssql' => __DIR__ . '/includes/libs/rdbms/database/DatabaseMssql.php',
        'Wikimedia\\Rdbms\\DatabaseMysqlBase' => __DIR__ . '/includes/libs/rdbms/database/DatabaseMysqlBase.php',
        'Wikimedia\\Rdbms\\DatabaseMysqli' => __DIR__ . '/includes/libs/rdbms/database/DatabaseMysqli.php',
index cc11233..e9f8b6f 100644 (file)
@@ -102,6 +102,43 @@ class HistoryAction extends FormlessAction {
                }
        }
 
+       /**
+        * @param WebRequest $request
+        * @return string
+        */
+       private function getTimestampFromRequest( WebRequest $request ) {
+               // Backwards compatibility checks for URIs with only year and/or month.
+               $year = $request->getInt( 'year' );
+               $month = $request->getInt( 'month' );
+               $day = null;
+               if ( $year !== 0 || $month !== 0 ) {
+                       if ( $year === 0 ) {
+                               $year = MWTimestamp::getLocalInstance()->format( 'Y' );
+                       }
+                       if ( $month < 1 || $month > 12 ) {
+                               // month is invalid so treat as December (all months)
+                               $month = 12;
+                       }
+                       // month is valid so check day
+                       $day = cal_days_in_month( CAL_GREGORIAN, $month, $year );
+
+                       // Left pad the months and days
+                       $month = str_pad( $month, 2, "0", STR_PAD_LEFT );
+                       $day = str_pad( $day, 2, "0", STR_PAD_LEFT );
+               }
+
+               $before = $request->getVal( 'date-range-to' );
+               if ( $before ) {
+                       $parts = explode( '-', $before );
+                       $year = $parts[0];
+                       // check date input is valid
+                       if ( count( $parts ) === 3 ) {
+                               $month = $parts[1];
+                               $day = $parts[2];
+                       }
+               }
+               return $year && $month && $day ? $year . '-' . $month . '-' . $day : '';
+       }
        /**
         * Print the history page for an article.
         */
@@ -179,13 +216,8 @@ class HistoryAction extends FormlessAction {
                        return;
                }
 
-               /**
-                * Add date selector to quickly get to a certain time
-                */
-               $year = $request->getInt( 'year' );
-               $month = $request->getInt( 'month' );
+               $ts = $this->getTimestampFromRequest( $request );
                $tagFilter = $request->getVal( 'tagfilter' );
-               $tagSelector = ChangeTags::buildTagFilterSelector( $tagFilter, false, $this->getContext() );
 
                /**
                 * Option to show only revisions that have been (partially) hidden via RevisionDelete
@@ -195,41 +227,69 @@ class HistoryAction extends FormlessAction {
                } else {
                        $conds = [];
                }
+
+               // Add the general form.
+               $action = htmlspecialchars( wfScript() );
+               $fields = [
+                       [
+                               'name' => 'title',
+                               'type' => 'hidden',
+                               'default' => $this->getTitle()->getPrefixedDBkey(),
+                       ],
+                       [
+                               'name' => 'action',
+                               'type' => 'hidden',
+                               'default' => 'history',
+                       ],
+                       [
+                               'type' => 'date',
+                               'default' => $ts,
+                               'label' => $this->msg( 'date-range-to' )->text(),
+                               'name' => 'date-range-to',
+                       ],
+                       [
+                               'label-raw' => $this->msg( 'tag-filter' )->parse(),
+                               'type' => 'tagfilter',
+                               'id' => 'tagfilter',
+                               'name' => 'tagfilter',
+                               'value' => $tagFilter,
+                       ]
+               ];
                if ( $this->getUser()->isAllowed( 'deletedhistory' ) ) {
-                       $checkDeleted = Xml::checkLabel( $this->msg( 'history-show-deleted' )->text(),
-                               'deleted', 'mw-show-deleted-only', $request->getBool( 'deleted' ) ) . "\n";
-               } else {
-                       $checkDeleted = '';
+                       $fields[] = [
+                               'type' => 'check',
+                               'label' => $this->msg( 'history-show-deleted' )->text(),
+                               'default' => $request->getBool( 'deleted' ),
+                               'name' => 'deleted',
+                       ];
                }
 
-               // Add the general form
-               $action = htmlspecialchars( wfScript() );
-               $content = Html::hidden( 'title', $this->getTitle()->getPrefixedDBkey() ) . "\n";
-               $content .= Html::hidden( 'action', 'history' ) . "\n";
-               $content .= Xml::dateMenu(
-                       ( $year == null ? MWTimestamp::getLocalInstance()->format( 'Y' ) : $year ),
-                       $month
-               ) . "\u{00A0}";
-               $content .= $tagSelector ? ( implode( "\u{00A0}", $tagSelector ) . "\u{00A0}" ) : '';
-               $content .= $checkDeleted . Html::submitButton(
-                       $this->msg( 'historyaction-submit' )->text(),
-                       [],
-                       [ 'mw-ui-progressive' ]
-               );
-               $out->addHTML(
-                       "<form action=\"$action\" method=\"get\" id=\"mw-history-searchform\">" .
-                       Xml::fieldset(
-                               $this->msg( 'history-fieldset-title' )->text(),
-                               $content,
-                               [ 'id' => 'mw-history-search' ]
-                       ) .
-                       '</form>'
-               );
+               $out->enableOOUI();
+               $htmlForm = HTMLForm::factory( 'ooui', $fields, $this->getContext() );
+               $htmlForm
+                       ->setMethod( 'get' )
+                       ->setAction( $action )
+                       ->setId( 'mw-history-searchform' )
+                       ->setSubmitText( $this->msg( 'historyaction-submit' )->text() )
+                       ->setWrapperLegend( $this->msg( 'history-fieldset-title' )->text() );
+               $htmlForm->loadData();
+
+               $out->addHTML( $htmlForm->getHTML( false ) );
 
                Hooks::run( 'PageHistoryBeforeList', [ &$this->page, $this->getContext() ] );
 
                // Create and output the list.
-               $pager = new HistoryPager( $this, $year, $month, $tagFilter, $conds );
+               $dateComponents = explode( '-', $ts );
+               if ( count( $dateComponents ) > 1 ) {
+                       $y = $dateComponents[0];
+                       $m = $dateComponents[1];
+                       $d = $dateComponents[2];
+               } else {
+                       $y = '';
+                       $m = '';
+                       $d = '';
+               }
+               $pager = new HistoryPager( $this, $y, $m, $tagFilter, $conds, $d );
                $out->addHTML(
                        $pager->getNavigationBar() .
                        $pager->getBody() .
index 9c82d1c..c2fcc87 100644 (file)
@@ -55,18 +55,20 @@ class HistoryPager extends ReverseChronologicalPager {
         * @param string $month
         * @param string $tagFilter
         * @param array $conds
+        * @param string $day
         */
        public function __construct(
                HistoryAction $historyPage,
                $year = '',
                $month = '',
                $tagFilter = '',
-               array $conds = []
+               array $conds = [],
+               $day = ''
        ) {
                parent::__construct( $historyPage->getContext() );
                $this->historyPage = $historyPage;
                $this->tagFilter = $tagFilter;
-               $this->getDateCond( $year, $month );
+               $this->getDateCond( $year, $month, $day );
                $this->conds = $conds;
                $this->showTagEditUI = ChangeTags::showTagEditingUI( $this->getUser() );
        }
diff --git a/includes/libs/rdbms/database/AtomicSectionIdentifier.php b/includes/libs/rdbms/database/AtomicSectionIdentifier.php
deleted file mode 100644 (file)
index c6e3d44..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-<?php
-/**
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Database
- */
-namespace Wikimedia\Rdbms;
-
-/**
- * Class used for token representing identifiers for atomic sections from IDatabase instances
- */
-class AtomicSectionIdentifier {
-}
diff --git a/includes/libs/rdbms/database/DatabaseDomain.php b/includes/libs/rdbms/database/DatabaseDomain.php
deleted file mode 100644 (file)
index ca57938..0000000
+++ /dev/null
@@ -1,258 +0,0 @@
-<?php
-/**
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Database
- */
-namespace Wikimedia\Rdbms;
-
-use InvalidArgumentException;
-
-/**
- * Class to handle database/prefix specification for IDatabase domains
- */
-class DatabaseDomain {
-       /** @var string|null */
-       private $database;
-       /** @var string|null */
-       private $schema;
-       /** @var string */
-       private $prefix;
-
-       /** @var string Cache of convertToString() */
-       private $equivalentString;
-
-       /**
-        * @param string|null $database Database name
-        * @param string|null $schema Schema name
-        * @param string $prefix Table prefix
-        */
-       public function __construct( $database, $schema, $prefix ) {
-               if ( $database !== null && ( !is_string( $database ) || $database === '' ) ) {
-                       throw new InvalidArgumentException( 'Database must be null or a non-empty string.' );
-               }
-               $this->database = $database;
-               if ( $schema !== null && ( !is_string( $schema ) || $schema === '' ) ) {
-                       throw new InvalidArgumentException( 'Schema must be null or a non-empty string.' );
-               }
-               $this->schema = $schema;
-               if ( !is_string( $prefix ) ) {
-                       throw new InvalidArgumentException( 'Prefix must be a string.' );
-               } elseif ( $prefix !== '' && substr( $prefix, -1, 1 ) !== '_' ) {
-                       throw new InvalidArgumentException( 'A non-empty prefix must end with "_".' );
-               }
-               $this->prefix = $prefix;
-       }
-
-       /**
-        * @param DatabaseDomain|string $domain Result of DatabaseDomain::toString()
-        * @return DatabaseDomain
-        */
-       public static function newFromId( $domain ) {
-               if ( $domain instanceof self ) {
-                       return $domain;
-               }
-
-               $parts = array_map( [ __CLASS__, 'decode' ], explode( '-', $domain ) );
-
-               $schema = null;
-               $prefix = '';
-
-               if ( count( $parts ) == 1 ) {
-                       $database = $parts[0];
-               } elseif ( count( $parts ) == 2 ) {
-                       list( $database, $prefix ) = $parts;
-               } elseif ( count( $parts ) == 3 ) {
-                       list( $database, $schema, $prefix ) = $parts;
-               } else {
-                       throw new InvalidArgumentException( "Domain '$domain' has too few or too many parts." );
-               }
-
-               if ( $database === '' ) {
-                       $database = null;
-               }
-
-               if ( $schema === '' ) {
-                       $schema = null;
-               }
-
-               return new self( $database, $schema, $prefix );
-       }
-
-       /**
-        * @return DatabaseDomain
-        */
-       public static function newUnspecified() {
-               return new self( null, null, '' );
-       }
-
-       /**
-        * @param DatabaseDomain|string $other
-        * @return bool Whether the domain instances are the same by value
-        */
-       public function equals( $other ) {
-               if ( $other instanceof self ) {
-                       return (
-                               $this->database === $other->database &&
-                               $this->schema === $other->schema &&
-                               $this->prefix === $other->prefix
-                       );
-               }
-
-               return ( $this->getId() === $other );
-       }
-
-       /**
-        * Check whether the domain $other meets the specifications of this domain
-        *
-        * If this instance has a null database specifier, then $other can have any database
-        * specified, including the null, and likewise if the schema specifier is null. This
-        * is not transitive like equals() since a domain that explicitly wants a certain
-        * database or schema cannot be satisfied by one of another (nor null). If the prefix
-        * is empty and the DB and schema are both null, then the entire domain is considered
-        * unspecified, and any prefix of $other is considered compatible.
-        *
-        * @param DatabaseDomain|string $other
-        * @return bool
-        * @since 1.32
-        */
-       public function isCompatible( $other ) {
-               if ( $this->isUnspecified() ) {
-                       return true; // even the prefix doesn't matter
-               }
-
-               $other = self::newFromId( $other );
-
-               return (
-                       ( $this->database === $other->database || $this->database === null ) &&
-                       ( $this->schema === $other->schema || $this->schema === null ) &&
-                       $this->prefix === $other->prefix
-               );
-       }
-
-       /**
-        * @return bool
-        * @since 1.32
-        */
-       public function isUnspecified() {
-               return (
-                       $this->database === null && $this->schema === null && $this->prefix === ''
-               );
-       }
-
-       /**
-        * @return string|null Database name
-        */
-       public function getDatabase() {
-               return $this->database;
-       }
-
-       /**
-        * @return string|null Database schema
-        */
-       public function getSchema() {
-               return $this->schema;
-       }
-
-       /**
-        * @return string Table prefix
-        */
-       public function getTablePrefix() {
-               return $this->prefix;
-       }
-
-       /**
-        * @return string
-        */
-       public function getId() {
-               if ( $this->equivalentString === null ) {
-                       $this->equivalentString = $this->convertToString();
-               }
-
-               return $this->equivalentString;
-       }
-
-       /**
-        * @return string
-        */
-       private function convertToString() {
-               $parts = [ (string)$this->database ];
-               if ( $this->schema !== null ) {
-                       $parts[] = $this->schema;
-               }
-               if ( $this->prefix != '' || $this->schema !== null ) {
-                       // If there is a schema, then we need the prefix to disambiguate.
-                       // For engines like Postgres that use schemas, this awkwardness is hopefully
-                       // avoided since it is easy to have one DB per server (to avoid having many users)
-                       // and use schema/prefix to have wiki farms. For example, a domain schemes could be
-                       // wiki-<project>-<language>, e.g. "wiki-fitness-es"/"wiki-sports-fr"/"wiki-news-en".
-                       $parts[] = $this->prefix;
-               }
-
-               return implode( '-', array_map( [ __CLASS__, 'encode' ], $parts ) );
-       }
-
-       private static function encode( $decoded ) {
-               $encoded = '';
-
-               $length = strlen( $decoded );
-               for ( $i = 0; $i < $length; ++$i ) {
-                       $char = $decoded[$i];
-                       if ( $char === '-' ) {
-                               $encoded .= '?h';
-                       } elseif ( $char === '?' ) {
-                               $encoded .= '??';
-                       } else {
-                               $encoded .= $char;
-                       }
-               }
-
-               return $encoded;
-       }
-
-       private static function decode( $encoded ) {
-               $decoded = '';
-
-               $length = strlen( $encoded );
-               for ( $i = 0; $i < $length; ++$i ) {
-                       $char = $encoded[$i];
-                       if ( $char === '?' ) {
-                               $nextChar = $encoded[$i + 1] ?? null;
-                               if ( $nextChar === 'h' ) {
-                                       $decoded .= '-';
-                                       ++$i;
-                               } elseif ( $nextChar === '?' ) {
-                                       $decoded .= '?';
-                                       ++$i;
-                               } else {
-                                       $decoded .= $char;
-                               }
-                       } else {
-                               $decoded .= $char;
-                       }
-               }
-
-               return $decoded;
-       }
-
-       /**
-        * @return string
-        */
-       function __toString() {
-               return $this->getId();
-       }
-}
diff --git a/includes/libs/rdbms/database/domain/DatabaseDomain.php b/includes/libs/rdbms/database/domain/DatabaseDomain.php
new file mode 100644 (file)
index 0000000..ca57938
--- /dev/null
@@ -0,0 +1,258 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Database
+ */
+namespace Wikimedia\Rdbms;
+
+use InvalidArgumentException;
+
+/**
+ * Class to handle database/prefix specification for IDatabase domains
+ */
+class DatabaseDomain {
+       /** @var string|null */
+       private $database;
+       /** @var string|null */
+       private $schema;
+       /** @var string */
+       private $prefix;
+
+       /** @var string Cache of convertToString() */
+       private $equivalentString;
+
+       /**
+        * @param string|null $database Database name
+        * @param string|null $schema Schema name
+        * @param string $prefix Table prefix
+        */
+       public function __construct( $database, $schema, $prefix ) {
+               if ( $database !== null && ( !is_string( $database ) || $database === '' ) ) {
+                       throw new InvalidArgumentException( 'Database must be null or a non-empty string.' );
+               }
+               $this->database = $database;
+               if ( $schema !== null && ( !is_string( $schema ) || $schema === '' ) ) {
+                       throw new InvalidArgumentException( 'Schema must be null or a non-empty string.' );
+               }
+               $this->schema = $schema;
+               if ( !is_string( $prefix ) ) {
+                       throw new InvalidArgumentException( 'Prefix must be a string.' );
+               } elseif ( $prefix !== '' && substr( $prefix, -1, 1 ) !== '_' ) {
+                       throw new InvalidArgumentException( 'A non-empty prefix must end with "_".' );
+               }
+               $this->prefix = $prefix;
+       }
+
+       /**
+        * @param DatabaseDomain|string $domain Result of DatabaseDomain::toString()
+        * @return DatabaseDomain
+        */
+       public static function newFromId( $domain ) {
+               if ( $domain instanceof self ) {
+                       return $domain;
+               }
+
+               $parts = array_map( [ __CLASS__, 'decode' ], explode( '-', $domain ) );
+
+               $schema = null;
+               $prefix = '';
+
+               if ( count( $parts ) == 1 ) {
+                       $database = $parts[0];
+               } elseif ( count( $parts ) == 2 ) {
+                       list( $database, $prefix ) = $parts;
+               } elseif ( count( $parts ) == 3 ) {
+                       list( $database, $schema, $prefix ) = $parts;
+               } else {
+                       throw new InvalidArgumentException( "Domain '$domain' has too few or too many parts." );
+               }
+
+               if ( $database === '' ) {
+                       $database = null;
+               }
+
+               if ( $schema === '' ) {
+                       $schema = null;
+               }
+
+               return new self( $database, $schema, $prefix );
+       }
+
+       /**
+        * @return DatabaseDomain
+        */
+       public static function newUnspecified() {
+               return new self( null, null, '' );
+       }
+
+       /**
+        * @param DatabaseDomain|string $other
+        * @return bool Whether the domain instances are the same by value
+        */
+       public function equals( $other ) {
+               if ( $other instanceof self ) {
+                       return (
+                               $this->database === $other->database &&
+                               $this->schema === $other->schema &&
+                               $this->prefix === $other->prefix
+                       );
+               }
+
+               return ( $this->getId() === $other );
+       }
+
+       /**
+        * Check whether the domain $other meets the specifications of this domain
+        *
+        * If this instance has a null database specifier, then $other can have any database
+        * specified, including the null, and likewise if the schema specifier is null. This
+        * is not transitive like equals() since a domain that explicitly wants a certain
+        * database or schema cannot be satisfied by one of another (nor null). If the prefix
+        * is empty and the DB and schema are both null, then the entire domain is considered
+        * unspecified, and any prefix of $other is considered compatible.
+        *
+        * @param DatabaseDomain|string $other
+        * @return bool
+        * @since 1.32
+        */
+       public function isCompatible( $other ) {
+               if ( $this->isUnspecified() ) {
+                       return true; // even the prefix doesn't matter
+               }
+
+               $other = self::newFromId( $other );
+
+               return (
+                       ( $this->database === $other->database || $this->database === null ) &&
+                       ( $this->schema === $other->schema || $this->schema === null ) &&
+                       $this->prefix === $other->prefix
+               );
+       }
+
+       /**
+        * @return bool
+        * @since 1.32
+        */
+       public function isUnspecified() {
+               return (
+                       $this->database === null && $this->schema === null && $this->prefix === ''
+               );
+       }
+
+       /**
+        * @return string|null Database name
+        */
+       public function getDatabase() {
+               return $this->database;
+       }
+
+       /**
+        * @return string|null Database schema
+        */
+       public function getSchema() {
+               return $this->schema;
+       }
+
+       /**
+        * @return string Table prefix
+        */
+       public function getTablePrefix() {
+               return $this->prefix;
+       }
+
+       /**
+        * @return string
+        */
+       public function getId() {
+               if ( $this->equivalentString === null ) {
+                       $this->equivalentString = $this->convertToString();
+               }
+
+               return $this->equivalentString;
+       }
+
+       /**
+        * @return string
+        */
+       private function convertToString() {
+               $parts = [ (string)$this->database ];
+               if ( $this->schema !== null ) {
+                       $parts[] = $this->schema;
+               }
+               if ( $this->prefix != '' || $this->schema !== null ) {
+                       // If there is a schema, then we need the prefix to disambiguate.
+                       // For engines like Postgres that use schemas, this awkwardness is hopefully
+                       // avoided since it is easy to have one DB per server (to avoid having many users)
+                       // and use schema/prefix to have wiki farms. For example, a domain schemes could be
+                       // wiki-<project>-<language>, e.g. "wiki-fitness-es"/"wiki-sports-fr"/"wiki-news-en".
+                       $parts[] = $this->prefix;
+               }
+
+               return implode( '-', array_map( [ __CLASS__, 'encode' ], $parts ) );
+       }
+
+       private static function encode( $decoded ) {
+               $encoded = '';
+
+               $length = strlen( $decoded );
+               for ( $i = 0; $i < $length; ++$i ) {
+                       $char = $decoded[$i];
+                       if ( $char === '-' ) {
+                               $encoded .= '?h';
+                       } elseif ( $char === '?' ) {
+                               $encoded .= '??';
+                       } else {
+                               $encoded .= $char;
+                       }
+               }
+
+               return $encoded;
+       }
+
+       private static function decode( $encoded ) {
+               $decoded = '';
+
+               $length = strlen( $encoded );
+               for ( $i = 0; $i < $length; ++$i ) {
+                       $char = $encoded[$i];
+                       if ( $char === '?' ) {
+                               $nextChar = $encoded[$i + 1] ?? null;
+                               if ( $nextChar === 'h' ) {
+                                       $decoded .= '-';
+                                       ++$i;
+                               } elseif ( $nextChar === '?' ) {
+                                       $decoded .= '?';
+                                       ++$i;
+                               } else {
+                                       $decoded .= $char;
+                               }
+                       } else {
+                               $decoded .= $char;
+                       }
+               }
+
+               return $decoded;
+       }
+
+       /**
+        * @return string
+        */
+       function __toString() {
+               return $this->getId();
+       }
+}
diff --git a/includes/libs/rdbms/database/utils/AtomicSectionIdentifier.php b/includes/libs/rdbms/database/utils/AtomicSectionIdentifier.php
new file mode 100644 (file)
index 0000000..c6e3d44
--- /dev/null
@@ -0,0 +1,27 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Database
+ */
+namespace Wikimedia\Rdbms;
+
+/**
+ * Class used for token representing identifiers for atomic sections from IDatabase instances
+ */
+class AtomicSectionIdentifier {
+}
index 3fcba46..fa74cb3 100644 (file)
@@ -28,6 +28,7 @@ use MediaWiki\Auth\AuthenticationResponse;
 use MediaWiki\Auth\AuthenticationRequest;
 use MediaWiki\User\UserIdentity;
 use MediaWiki\Logger\LoggerFactory;
+use Wikimedia\Assert\Assert;
 use Wikimedia\IPSet;
 use Wikimedia\ScopedCallback;
 use Wikimedia\Rdbms\Database;
@@ -1749,6 +1750,23 @@ class User implements IDBAccessObject, UserIdentity {
                }
        }
 
+       /** @var array|null */
+       private static $defOpt = null;
+       /** @var string|null */
+       private static $defOptLang = null;
+
+       /**
+        * Reset the process cache of default user options. This is only necessary
+        * if the wiki configuration has changed since defaults were calculated,
+        * and as such should only be performed inside the testing suite that
+        * regularly changes wiki configuration.
+        */
+       public static function resetGetDefaultOptionsForTestsOnly() {
+               Assert::invariant( defined( 'MW_PHPUNIT_TEST' ), 'Unit tests only' );
+               self::$defOpt = null;
+               self::$defOptLang = null;
+       }
+
        /**
         * Combine the language default options with any site-specific options
         * and add the default language variants.
@@ -1758,26 +1776,23 @@ class User implements IDBAccessObject, UserIdentity {
        public static function getDefaultOptions() {
                global $wgNamespacesToBeSearchedDefault, $wgDefaultUserOptions, $wgDefaultSkin;
 
-               static $defOpt = null;
-               static $defOptLang = null;
-
                $contLang = MediaWikiServices::getInstance()->getContentLanguage();
-               if ( $defOpt !== null && $defOptLang === $contLang->getCode() ) {
+               if ( self::$defOpt !== null && self::$defOptLang === $contLang->getCode() ) {
                        // The content language does not change (and should not change) mid-request, but the
                        // unit tests change it anyway, and expect this method to return values relevant to the
                        // current content language.
-                       return $defOpt;
+                       return self::$defOpt;
                }
 
-               $defOpt = $wgDefaultUserOptions;
+               self::$defOpt = $wgDefaultUserOptions;
                // Default language setting
-               $defOptLang = $contLang->getCode();
-               $defOpt['language'] = $defOptLang;
+               self::$defOptLang = $contLang->getCode();
+               self::$defOpt['language'] = self::$defOptLang;
                foreach ( LanguageConverter::$languagesWithVariants as $langCode ) {
                        if ( $langCode === $contLang->getCode() ) {
-                               $defOpt['variant'] = $langCode;
+                               self::$defOpt['variant'] = $langCode;
                        } else {
-                               $defOpt["variant-$langCode"] = $langCode;
+                               self::$defOpt["variant-$langCode"] = $langCode;
                        }
                }
 
@@ -1785,13 +1800,13 @@ class User implements IDBAccessObject, UserIdentity {
                // since extensions may change the set of searchable namespaces depending
                // on user groups/permissions.
                foreach ( $wgNamespacesToBeSearchedDefault as $nsnum => $val ) {
-                       $defOpt['searchNs' . $nsnum] = (bool)$val;
+                       self::$defOpt['searchNs' . $nsnum] = (bool)$val;
                }
-               $defOpt['skin'] = Skin::normalizeKey( $wgDefaultSkin );
+               self::$defOpt['skin'] = Skin::normalizeKey( $wgDefaultSkin );
 
-               Hooks::run( 'UserGetDefaultOptions', [ &$defOpt ] );
+               Hooks::run( 'UserGetDefaultOptions', [ &self::$defOpt ] );
 
-               return $defOpt;
+               return self::$defOpt;
        }
 
        /**
index ce36105..bfa80a8 100644 (file)
@@ -1325,9 +1325,11 @@ return [
                'targets' => [ 'desktop', 'mobile' ],
        ],
        'mediawiki.checkboxtoggle' => [
+               'targets' => [ 'desktop', 'mobile' ],
                'scripts' => 'resources/src/mediawiki.checkboxtoggle.js',
        ],
        'mediawiki.checkboxtoggle.styles' => [
+               'targets' => [ 'desktop', 'mobile' ],
                'styles' => 'resources/src/mediawiki.checkboxtoggle.styles.css',
        ],
        'mediawiki.cookie' => [
index 36d66fb..f43f0a9 100644 (file)
@@ -361,6 +361,7 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase {
        public static function resetNonServiceCaches() {
                global $wgRequest, $wgJobClasses;
 
+               User::resetGetDefaultOptionsForTestsOnly();
                foreach ( $wgJobClasses as $type => $class ) {
                        JobQueueGroup::singleton()->get( $type )->delete();
                }
index 28e26a0..642ae3e 100644 (file)
@@ -59,7 +59,44 @@ class SpecialWatchlistTest extends SpecialPageTestBase {
        /**
         * @dataProvider provideFetchOptionsFromRequest
         */
-       public function testFetchOptionsFromRequest( $expectedValues, $preferences, $inputParams ) {
+       public function testFetchOptionsFromRequest(
+               $expectedValuesDefaults, $expectedValues, $preferences, $inputParams
+       ) {
+               // $defaults and $allFalse are just to make the expected values below
+               // shorter by hiding the background.
+
+               $page = TestingAccessWrapper::newFromObject(
+                       $this->newSpecialPage()
+               );
+
+               $page->registerFilters();
+
+               // Does not consider $preferences, just wiki's defaults
+               $wikiDefaults = $page->getDefaultOptions()->getAllValues();
+
+               switch ( $expectedValuesDefaults ) {
+               case 'allFalse':
+                       $allFalse = $wikiDefaults;
+
+                       foreach ( $allFalse as $key => $value ) {
+                               if ( $value === true ) {
+                                       $allFalse[$key] = false;
+                               }
+                       }
+
+                       // This is not exposed on the form (only in preferences) so it
+                       // respects the preference.
+                       $allFalse['extended'] = true;
+
+                       $expectedValues += $allFalse;
+                       break;
+               case 'wikiDefaults':
+                       $expectedValues += $wikiDefaults;
+                       break;
+               default:
+                       $this->fail( "Unknown \$expectedValuesDefaults: $expectedValuesDefaults" );
+               }
+
                $page = TestingAccessWrapper::newFromObject(
                        $this->newSpecialPage()
                );
@@ -90,43 +127,21 @@ class SpecialWatchlistTest extends SpecialPageTestBase {
        }
 
        public function provideFetchOptionsFromRequest() {
-               // $defaults and $allFalse are just to make the expected values below
-               // shorter by hiding the background.
-
-               $page = TestingAccessWrapper::newFromObject(
-                       $this->newSpecialPage()
-               );
-
-               $page->registerFilters();
-
-               // Does not consider $preferences, just wiki's defaults
-               $wikiDefaults = $page->getDefaultOptions()->getAllValues();
-
-               $allFalse = $wikiDefaults;
-
-               foreach ( $allFalse as $key => &$value ) {
-                       if ( $value === true ) {
-                               $value = false;
-                       }
-               }
-
-               // This is not exposed on the form (only in preferences) so it
-               // respects the preference.
-               $allFalse['extended'] = true;
-
                return [
-                       [
-                               [
+                       'ignores casing' => [
+                               'expectedValuesDefaults' => 'wikiDefaults',
+                               'expectedValues' => [
                                        'hideminor' => true,
-                               ] + $wikiDefaults,
-                               [],
-                               [
+                               ],
+                               'preferences' => [],
+                               'inputParams' => [
                                        'hideMinor' => 1,
                                ],
                        ],
 
-                       [
-                               [
+                       'first two same as prefs, second two overriden' => [
+                               'expectedValuesDefaults' => 'wikiDefaults',
+                               'expectedValues' => [
                                        // First two same as prefs
                                        'hideminor' => true,
                                        'hidebots' => false,
@@ -135,38 +150,38 @@ class SpecialWatchlistTest extends SpecialPageTestBase {
                                        'hideanons' => false,
                                        'hideliu' => true,
                                        'userExpLevel' => 'registered'
-                               ] + $wikiDefaults,
-                               [
+                               ],
+                               'preferences' => [
                                        'watchlisthideminor' => 1,
                                        'watchlisthidebots' => 0,
 
                                        'watchlisthideanons' => 1,
                                        'watchlisthideliu' => 0,
                                ],
-                               [
+                               'inputParams' => [
                                        'hideanons' => 0,
                                        'hideliu' => 1,
                                ],
                        ],
 
-                       // Defaults/preferences for form elements are entirely ignored for
-                       // action=submit and omitted elements become false
-                       [
-                               [
+                       'Defaults/preferences for form elements are entirely ignored for '
+                       . 'action=submit and omitted elements become false' => [
+                               'expectedValuesDefaults' => 'allFalse',
+                               'expectedValues' => [
                                        'hideminor' => false,
                                        'hidebots' => true,
                                        'hideanons' => false,
                                        'hideliu' => true,
                                        'userExpLevel' => 'unregistered'
-                               ] + $allFalse,
-                               [
+                               ],
+                               'preferences' => [
                                        'watchlisthideminor' => 0,
                                        'watchlisthidebots' => 1,
 
                                        'watchlisthideanons' => 0,
                                        'watchlisthideliu' => 1,
                                ],
-                               [
+                               'inputParams' => [
                                        'hidebots' => 1,
                                        'hideliu' => 1,
                                        'action' => 'submit',