-->
<exclude-pattern>*/maintenance/mwdocgen\.php</exclude-pattern>
</rule>
+ <rule ref="MediaWiki.Commenting.MissingCovers.MissingCovers">
+ <exclude-pattern>tests/phpunit/structure/*</exclude-pattern>
+ </rule>
<file>.</file>
<arg name="encoding" value="UTF-8"/>
<arg name="extensions" value="php,php5,inc,sample"/>
getErrorsOrWarnings() instead.
* SpecialPage::getTitle(), deprecated in 1.23, has been removed. Use
SpecialPage::getPageTitle() instead.
+* jquery.ui.effect-bounce, jquery.ui.effect-explode, jquery.ui.effect-fold
+ jquery.ui.effect-pulsate, jquery.ui.effect-slide, jquery.ui.effect-transfer,
+ which are no longer used, have now been removed.
* …
=== Deprecations in 1.34 ===
'ResourceLoaderLessVarFileModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderLessVarFileModule.php',
'ResourceLoaderModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderModule.php',
'ResourceLoaderOOUIFileModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderOOUIFileModule.php',
+ 'ResourceLoaderOOUIIconPackModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderOOUIIconPackModule.php',
'ResourceLoaderOOUIImageModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderOOUIImageModule.php',
'ResourceLoaderOOUIModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderOOUIModule.php',
'ResourceLoaderSiteModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderSiteModule.php',
if ( !Hooks::run( 'EditPageBeforeEditToolbar', [ &$toolbar ] ) ) {
return null;
- };
+ }
// Don't add a pointless `<div>` to the page unless a hook caller populated it
return ( $toolbar === $startingToolbar ) ? null : $toolbar;
}
'oojs-ui.styles.indicators',
'oojs-ui.styles.textures',
'mediawiki.widgets.styles',
- 'oojs-ui.styles.icons-content',
- 'oojs-ui.styles.icons-alerts',
- 'oojs-ui.styles.icons-interactions',
+ 'oojs-ui-core.icons',
] );
}
throw new RevisionAccessException(
'Main slot of revision ' . $revId . ' not found in database!'
);
- };
+ }
return $slots;
}
];
if ( $response->message ) {
$ret['message'] = $response->message->inLanguage( 'en' )->plain();
- };
+ }
$reqs = [
'neededRequests' => $response->neededRequests,
'createRequest' => $response->createRequest,
}
$lastLogId = $this->outputLogStream( $result );
- };
+ }
}
/**
$this->db->query( $command );
} else {
$this->output( "...foreign key constraint on '$table.$field' already does not exist\n" );
- };
+ }
}
protected function changeFkeyDeferrable( $table, $field, $clause ) {
if ( $this->updateRowExists( 'patch-textsearch_bug66650.sql' ) ) {
$this->output( "...T68650 already fixed or not applicable.\n" );
return;
- };
+ }
$this->applyPatch( 'patch-textsearch_bug66650.sql', false,
'Rebuilding text search for T68650' );
}
"config-env-bad": "The environment has been checked.\nYou cannot install MediaWiki.",
"config-env-php": "PHP $1 is installed.",
"config-env-hhvm": "HHVM $1 is installed.",
- "config-unicode-using-intl": "Using the [https://pecl.php.net/intl intl PECL extension] for Unicode normalization.",
- "config-unicode-pure-php-warning": "<strong>Warning:</strong> The [https://pecl.php.net/intl intl PECL extension] is not available to handle Unicode normalization, falling back to slow pure-PHP implementation.\nIf you run a high-traffic site, you should read a little on [https://www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations Unicode normalization].",
+ "config-unicode-using-intl": "Using the [https://php.net/manual/en/book.intl.php PHP intl extension] for Unicode normalization.",
+ "config-unicode-pure-php-warning": "<strong>Warning:</strong> The [https://php.net/manual/en/book.intl.php PHP intl extension] is not available to handle Unicode normalization, falling back to slow pure-PHP implementation.\nIf you run a high-traffic site, you should read on [https://www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations Unicode normalization].",
"config-unicode-update-warning": "<strong>Warning:</strong> The installed version of the Unicode normalization wrapper uses an older version of [http://site.icu-project.org/ the ICU project's] library.\nYou should [https://www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations upgrade] if you are at all concerned about using Unicode.",
"config-no-db": "Could not find a suitable database driver! You need to install a database driver for PHP.\nThe following database {{PLURAL:$2|type is|types are}} supported: $1.\n\nIf you compiled PHP yourself, reconfigure it with a database client enabled, for example, using <code>./configure --with-mysqli</code>.\nIf you installed PHP from a Debian or Ubuntu package, then you also need to install, for example, the <code>php-mysql</code> package.",
"config-outdated-sqlite": "<strong>Warning:</strong> you have SQLite $2, which is lower than minimum required version $1. SQLite will be unavailable.",
} else {
$errorsOnlyStatusValue->errors[] = $item;
}
- };
+ }
return [ $errorsOnlyStatusValue, $warningsOnlyStatusValue ];
}
$callback = $this->guessCallback;
if ( $callback ) {
$callback( $this, $head, $tail, $file, $mime /* by reference */ );
- };
+ }
return $mime;
}
*
* @throws DBUnexpectedError
*/
- protected function assertHasConnectionHandle() {
+ final protected function assertHasConnectionHandle() {
if ( !$this->isOpen() ) {
throw new DBUnexpectedError( $this, "DB connection was already closed." );
}
/**
* Make sure that this server is not marked as a replica nor read-only as a sanity check
*
- * @throws DBUnexpectedError
+ * @throws DBReadOnlyRoleError
+ * @throws DBReadOnlyError
*/
protected function assertIsWritableMaster() {
if ( $this->getLBInfo( 'replica' ) === true ) {
/**
* Run a query and return a DBMS-dependent wrapper or boolean
*
+ * This is meant to handle the basic command of actually sending a query to the
+ * server via the driver. No implicit transaction, reconnection, nor retry logic
+ * should happen here. The higher level query() method is designed to handle those
+ * sorts of concerns. This method should not trigger such higher level methods.
+ *
+ * The lastError() and lastErrno() methods should meaningfully reflect what error,
+ * if any, occured during the last call to this method. Methods like executeQuery(),
+ * query(), select(), insert(), update(), delete(), and upsert() implement their calls
+ * to doQuery() such that an immediately subsequent call to lastError()/lastErrno()
+ * meaningfully reflects any error that occured during that public query method call.
+ *
* For SELECT queries, this returns either:
* - a) A driver-specific value/resource, only on success. This can be iterated
* over by calling fetchObject()/fetchRow() until there are no more rows.
protected function isTransactableQuery( $sql ) {
return !in_array(
$this->getQueryVerb( $sql ),
- [ 'BEGIN', 'ROLLBACK', 'COMMIT', 'SET', 'SHOW', 'CREATE', 'ALTER' ],
+ [ 'BEGIN', 'ROLLBACK', 'COMMIT', 'SET', 'SHOW', 'CREATE', 'ALTER', 'USE' ],
true
);
}
}
public function query( $sql, $fname = __METHOD__, $flags = 0 ) {
- $this->assertTransactionStatus( $sql, $fname );
- $this->assertHasConnectionHandle();
-
$flags = (int)$flags; // b/c; this field used to be a bool
- $ignoreErrors = $this->hasFlags( $flags, self::QUERY_SILENCE_ERRORS );
+ // Sanity check that the SQL query is appropriate in the current context and is
+ // allowed for an outside caller (e.g. does not break transaction/session tracking).
+ $this->assertQueryIsCurrentlyAllowed( $sql, $fname );
+
+ // Send the query to the server and fetch any corresponding errors
+ list( $ret, $err, $errno, $unignorable ) = $this->executeQuery( $sql, $fname, $flags );
+ if ( $ret === false ) {
+ $ignoreErrors = $this->hasFlags( $flags, self::QUERY_SILENCE_ERRORS );
+ // Throw an error unless both the ignore flag was set and a rollback is not needed
+ $this->reportQueryError( $err, $errno, $sql, $fname, $ignoreErrors && !$unignorable );
+ }
+
+ return $this->resultObject( $ret );
+ }
+
+ /**
+ * Execute a query, retrying it if there is a recoverable connection loss
+ *
+ * This is similar to query() except:
+ * - It does not prevent all non-ROLLBACK queries if there is a corrupted transaction
+ * - It does not disallow raw queries that are supposed to use dedicated IDatabase methods
+ * - It does not throw exceptions for common error cases
+ *
+ * This is meant for internal use with Database subclasses.
+ *
+ * @param string $sql Original SQL query
+ * @param string $fname Name of the calling function
+ * @param int $flags Bitfield of class QUERY_* constants
+ * @return array An n-tuple of:
+ * - mixed|bool: An object, resource, or true on success; false on failure
+ * - string: The result of calling lastError()
+ * - int: The result of calling lastErrno()
+ * - bool: Whether a rollback is needed to allow future non-rollback queries
+ * @throws DBUnexpectedError
+ */
+ final protected function executeQuery( $sql, $fname, $flags ) {
+ $this->assertHasConnectionHandle();
$priorTransaction = $this->trxLevel;
- $priorWritesPending = $this->writesOrCallbacksPending();
if ( $this->isWriteQuery( $sql ) ) {
# In theory, non-persistent writes are allowed in read-only mode, but due to things
# like https://bugs.mysql.com/bug.php?id=33669 that might not work anyway...
$this->assertIsWritableMaster();
- # Do not treat temporary table writes as "meaningful writes" that need committing.
- # Profile them as reads. Integration tests can override this behavior via $flags.
+ # Do not treat temporary table writes as "meaningful writes" since they are only
+ # visible to one session and are not permanent. Profile them as reads. Integration
+ # tests can override this behavior via $flags.
$pseudoPermanent = $this->hasFlags( $flags, self::QUERY_PSEUDO_PERMANENT );
$tableType = $this->registerTempTableWrite( $sql, $pseudoPermanent );
- $isEffectiveWrite = ( $tableType !== self::TEMP_NORMAL );
+ $isPermWrite = ( $tableType !== self::TEMP_NORMAL );
# DBConnRef uses QUERY_REPLICA_ROLE to enforce the replica role for raw SQL queries
- if ( $isEffectiveWrite && $this->hasFlags( $flags, self::QUERY_REPLICA_ROLE ) ) {
+ if ( $isPermWrite && $this->hasFlags( $flags, self::QUERY_REPLICA_ROLE ) ) {
throw new DBReadOnlyRoleError( $this, "Cannot write; target role is DB_REPLICA" );
}
} else {
- $isEffectiveWrite = false;
+ $isPermWrite = false;
}
- # Add trace comment to the begin of the sql string, right after the operator.
- # Or, for one-word queries (like "BEGIN" or COMMIT") add it to the end (T44598)
+ // Add trace comment to the begin of the sql string, right after the operator.
+ // Or, for one-word queries (like "BEGIN" or COMMIT") add it to the end (T44598)
$commentedSql = preg_replace( '/\s|$/', " /* $fname {$this->agent} */ ", $sql, 1 );
- # Send the query to the server and fetch any corresponding errors
- $ret = $this->attemptQuery( $sql, $commentedSql, $isEffectiveWrite, $fname );
- $lastError = $this->lastError();
- $lastErrno = $this->lastErrno();
-
- $recoverableSR = false; // recoverable statement rollback?
- $recoverableCL = false; // recoverable connection loss?
-
- if ( $ret === false && $this->wasConnectionLoss() ) {
- # Check if no meaningful session state was lost
- $recoverableCL = $this->canRecoverFromDisconnect( $sql, $priorWritesPending );
- # Update session state tracking and try to restore the connection
- $reconnected = $this->replaceLostConnection( __METHOD__ );
- # Silently resend the query to the server if it is safe and possible
- if ( $recoverableCL && $reconnected ) {
- $ret = $this->attemptQuery( $sql, $commentedSql, $isEffectiveWrite, $fname );
- $lastError = $this->lastError();
- $lastErrno = $this->lastErrno();
-
- if ( $ret === false && $this->wasConnectionLoss() ) {
- # Query probably causes disconnects; reconnect and do not re-run it
- $this->replaceLostConnection( __METHOD__ );
- } else {
- $recoverableCL = false; // connection does not need recovering
- $recoverableSR = $this->wasKnownStatementRollbackError();
- }
- }
- } else {
- $recoverableSR = $this->wasKnownStatementRollbackError();
+ // Send the query to the server and fetch any corresponding errors
+ list( $ret, $err, $errno, $recoverableSR, $recoverableCL, $reconnected ) =
+ $this->executeQueryAttempt( $sql, $commentedSql, $isPermWrite, $fname, $flags );
+ // Check if the query failed due to a recoverable connection loss
+ if ( $ret === false && $recoverableCL && $reconnected ) {
+ // Silently resend the query to the server since it is safe and possible
+ list( $ret, $err, $errno, $recoverableSR, $recoverableCL ) =
+ $this->executeQueryAttempt( $sql, $commentedSql, $isPermWrite, $fname, $flags );
}
+ $corruptedTrx = false;
+
if ( $ret === false ) {
if ( $priorTransaction ) {
if ( $recoverableSR ) {
# We're ignoring an error that caused just the current query to be aborted.
# But log the cause so we can log a deprecation notice if a caller actually
# does ignore it.
- $this->trxStatusIgnoredCause = [ $lastError, $lastErrno, $fname ];
+ $this->trxStatusIgnoredCause = [ $err, $errno, $fname ];
} elseif ( !$recoverableCL ) {
# Either the query was aborted or all queries after BEGIN where aborted.
# In the first case, the only options going forward are (a) ROLLBACK, or
# (b) ROLLBACK TO SAVEPOINT (if one was set). If the later case, the only
# option is ROLLBACK, since the snapshots would have been released.
+ $corruptedTrx = true; // cannot recover
$this->trxStatus = self::STATUS_TRX_ERROR;
$this->trxStatusCause =
- $this->getQueryExceptionAndLog( $lastError, $lastErrno, $sql, $fname );
- $ignoreErrors = false; // cannot recover
+ $this->getQueryExceptionAndLog( $err, $errno, $sql, $fname );
$this->trxStatusIgnoredCause = null;
}
}
-
- $this->reportQueryError( $lastError, $lastErrno, $sql, $fname, $ignoreErrors );
}
- return $this->resultObject( $ret );
+ return [ $ret, $err, $errno, $corruptedTrx ];
}
/**
- * Wrapper for query() that also handles profiling, logging, and affected row count updates
+ * Wrapper for doQuery() that handles DBO_TRX, profiling, logging, affected row count
+ * tracking, and reconnects (without retry) on query failure due to connection loss
*
* @param string $sql Original SQL query
* @param string $commentedSql SQL query with debugging/trace comment
- * @param bool $isEffectiveWrite Whether the query is a (non-temporary table) write
+ * @param bool $isPermWrite Whether the query is a (non-temporary table) write
* @param string $fname Name of the calling function
- * @return bool|IResultWrapper True for a successful write query, ResultWrapper
- * object for a successful read query, or false on failure
+ * @param int $flags Bitfield of class QUERY_* constants
+ * @return array An n-tuple of:
+ * - mixed|bool: An object, resource, or true on success; false on failure
+ * - string: The result of calling lastError()
+ * - int: The result of calling lastErrno()
+ * - bool: Whether a statement rollback error occured
+ * - bool: Whether a disconnect *both* happened *and* was recoverable
+ * - bool: Whether a reconnection attempt was *both* made *and* succeeded
+ * @throws DBUnexpectedError
*/
- private function attemptQuery( $sql, $commentedSql, $isEffectiveWrite, $fname ) {
- $this->beginIfImplied( $sql, $fname );
+ private function executeQueryAttempt( $sql, $commentedSql, $isPermWrite, $fname, $flags ) {
+ $priorWritesPending = $this->writesOrCallbacksPending();
+
+ if ( ( $flags & self::QUERY_IGNORE_DBO_TRX ) == 0 ) {
+ $this->beginIfImplied( $sql, $fname );
+ }
// Keep track of whether the transaction has write queries pending
- if ( $isEffectiveWrite ) {
+ if ( $isPermWrite ) {
$this->lastWriteTime = microtime( true );
if ( $this->trxLevel && !$this->trxDoneWrites ) {
$this->trxDoneWrites = true;
$this->affectedRowCount = null;
$this->lastQuery = $sql;
$ret = $this->doQuery( $commentedSql );
+ $lastError = $this->lastError();
+ $lastErrno = $this->lastErrno();
+
$this->affectedRowCount = $this->affectedRows();
unset( $ps ); // profile out (if set)
$queryRuntime = max( microtime( true ) - $startTime, 0.0 );
+ $recoverableSR = false; // recoverable statement rollback?
+ $recoverableCL = false; // recoverable connection loss?
+ $reconnected = false; // reconnection both attempted and succeeded?
+
if ( $ret !== false ) {
$this->lastPing = $startTime;
- if ( $isEffectiveWrite && $this->trxLevel ) {
+ if ( $isPermWrite && $this->trxLevel ) {
$this->updateTrxWriteQueryTime( $sql, $queryRuntime, $this->affectedRows() );
$this->trxWriteCallers[] = $fname;
}
+ } elseif ( $this->wasConnectionError( $lastErrno ) ) {
+ # Check if no meaningful session state was lost
+ $recoverableCL = $this->canRecoverFromDisconnect( $sql, $priorWritesPending );
+ # Update session state tracking and try to restore the connection
+ $reconnected = $this->replaceLostConnection( __METHOD__ );
+ } else {
+ # Check if only the last query was rolled back
+ $recoverableSR = $this->wasKnownStatementRollbackError();
}
if ( $sql === self::PING_QUERY ) {
$this->trxProfiler->recordQueryCompletion(
$generalizedSql,
$startTime,
- $isEffectiveWrite,
- $isEffectiveWrite ? $this->affectedRows() : $this->numRows( $ret )
+ $isPermWrite,
+ $isPermWrite ? $this->affectedRows() : $this->numRows( $ret )
);
// Avoid the overhead of logging calls unless debug mode is enabled
);
}
- return $ret;
+ return [ $ret, $lastError, $lastErrno, $recoverableSR, $recoverableCL, $reconnected ];
}
/**
* @param string $fname
* @throws DBTransactionStateError
*/
- private function assertTransactionStatus( $sql, $fname ) {
+ private function assertQueryIsCurrentlyAllowed( $sql, $fname ) {
$verb = $this->getQueryVerb( $sql );
if ( $verb === 'USE' ) {
throw new DBUnexpectedError( $this, "Got USE query; use selectDomain() instead." );
$database = $domain->getDatabase();
if ( $database !== $this->getDBname() ) {
- $encDatabase = $this->addIdentifierQuotes( $database );
- $res = $this->doQuery( "USE $encDatabase" );
- if ( !$res ) {
- throw new DBExpectedError( $this, "Could not select database '$database'." );
+ $sql = 'USE ' . $this->addIdentifierQuotes( $database );
+ list( $res, $err, $errno ) =
+ $this->executeQuery( $sql, __METHOD__, self::QUERY_IGNORE_DBO_TRX );
+
+ if ( $res === false ) {
+ $this->reportQueryError( $err, $errno, $sql, __METHOD__ );
+ return false; // unreachable
}
}
// Update that domain fields on success (no exception thrown)
if ( $database !== $this->getDBname() ) {
$sql = 'USE ' . $this->addIdentifierQuotes( $database );
- $ret = $this->doQuery( $sql );
- if ( $ret === false ) {
- $error = $this->lastError();
- $errno = $this->lastErrno();
- $this->reportQueryError( $error, $errno, $sql, __METHOD__ );
+ list( $res, $err, $errno ) =
+ $this->executeQuery( $sql, __METHOD__, self::QUERY_IGNORE_DBO_TRX );
+
+ if ( $res === false ) {
+ $this->reportQueryError( $err, $errno, $sql, __METHOD__ );
+ return false; // unreachable
}
}
$gtidArg = $this->addQuotes( implode( ',', $gtidsWait ) );
if ( strpos( $gtidArg, ':' ) !== false ) {
// MySQL GTIDs, e.g "source_id:transaction_id"
- $res = $this->doQuery( "SELECT WAIT_FOR_EXECUTED_GTID_SET($gtidArg, $timeout)" );
+ $sql = "SELECT WAIT_FOR_EXECUTED_GTID_SET($gtidArg, $timeout)";
} else {
// MariaDB GTIDs, e.g."domain:server:sequence"
- $res = $this->doQuery( "SELECT MASTER_GTID_WAIT($gtidArg, $timeout)" );
+ $sql = "SELECT MASTER_GTID_WAIT($gtidArg, $timeout)";
}
} else {
// Wait on the binlog coordinates
$encFile = $this->addQuotes( $pos->getLogFile() );
$encPos = intval( $pos->getLogPosition()[$pos::CORD_EVENT] );
- $res = $this->doQuery( "SELECT MASTER_POS_WAIT($encFile, $encPos, $timeout)" );
+ $sql = "SELECT MASTER_POS_WAIT($encFile, $encPos, $timeout)";
}
+ list( $res, $err ) = $this->executeQuery( $sql, __METHOD__, self::QUERY_IGNORE_DBO_TRX );
$row = $res ? $this->fetchRow( $res ) : false;
if ( !$row ) {
- throw new DBExpectedError( $this, "Replication wait failed: {$this->lastError()}" );
+ throw new DBExpectedError( $this, "Replication wait failed: {$err}" );
}
// Result can be NULL (error), -1 (timeout), or 0+ per the MySQL manual
const QUERY_PSEUDO_PERMANENT = 2;
/** @var int Enforce that a query does not make effective writes */
const QUERY_REPLICA_ROLE = 4;
+ /** @var int Ignore the current presence of any DBO_TRX flag */
+ const QUERY_IGNORE_DBO_TRX = 8;
/** @var bool Parameter to unionQueries() for UNION ALL */
const UNION_ALL = true;
) . "\n"
);
- };
+ }
$out->addHTML( Html::closeElement( 'ul' ) . "\n" );
$res->free();
foreach ( $this->config['types'] as $i => $type ) {
if ( $i == 0 ) {
continue;
- };
+ }
// Construct pseudo-hash based on params and arguments
/** @var ParameterizedPassword $passObj */
'raw',
] as $member ) {
$options[$member] = $this->{$member};
- };
+ }
$summary[] = [
'options' => $options,
* @throws InvalidArgumentException
*/
public function __construct( $options = [], $localBasePath = null ) {
- $this->localBasePath = self::extractLocalBasePath( $options, $localBasePath );
+ $this->localBasePath = static::extractLocalBasePath( $options, $localBasePath );
$this->definition = $options;
}
--- /dev/null
+<?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
+ */
+
+/**
+ * Allows loading arbitrary sets of OOUI icons.
+ *
+ * @since 1.34
+ */
+class ResourceLoaderOOUIIconPackModule extends ResourceLoaderOOUIImageModule {
+ public function __construct( $options = [], $localBasePath = null ) {
+ parent::__construct( $options, $localBasePath );
+
+ if ( !isset( $this->definition['icons'] ) || !$this->definition['icons'] ) {
+ throw new InvalidArgumentException( "Parameter 'icons' must be given." );
+ }
+
+ // A few things check for the "icons" prefix on this value, so specify it even though
+ // we don't use it for actually loading the data, like in the other modules.
+ $this->definition['themeImages'] = 'icons';
+ }
+
+ private function getIcons() {
+ return $this->definition['icons'];
+ }
+
+ protected function loadOOUIDefinition( $theme, $unused ) {
+ // This is shared between instances of this class, so we only have to load the JSON files once
+ static $data = [];
+
+ if ( !isset( $data[$theme] ) ) {
+ $data[$theme] = [];
+ // Load and merge the JSON data for all "icons-foo" modules
+ foreach ( self::$knownImagesModules as $module ) {
+ if ( substr( $module, 0, 5 ) === 'icons' ) {
+ $moreData = $this->readJSONFile( $this->getThemeImagesPath( $theme, $module ) );
+ if ( $moreData ) {
+ $data[$theme] = array_replace_recursive( $data[$theme], $moreData );
+ }
+ }
+ }
+ }
+
+ $definition = $data[$theme];
+
+ // Filter out the data for all other icons, leaving only the ones we want for this module
+ $iconsNames = $this->getIcons();
+ foreach ( array_keys( $definition['images'] ) as $iconName ) {
+ if ( !in_array( $iconName, $iconsNames ) ) {
+ unset( $definition['images'][$iconName] );
+ }
+ }
+
+ return $definition;
+ }
+
+ public static function extractLocalBasePath( $options, $localBasePath = null ) {
+ global $IP;
+ if ( $localBasePath === null ) {
+ $localBasePath = $IP;
+ }
+ // Ignore any 'localBasePath' present in $options, this always refers to files in MediaWiki core
+ return $localBasePath;
+ }
+}
*/
/**
- * Secret special sauce.
+ * Loads the module definition from JSON files in the format that OOUI uses, converting it to the
+ * format we use. (Previously known as secret special sauce.)
*
* @since 1.26
*/
$definition = [];
foreach ( $themes as $skin => $theme ) {
- // Find the path to the JSON file which contains the actual image definitions for this theme
- if ( $module ) {
- $dataPath = $this->getThemeImagesPath( $theme, $module );
- } else {
- // Backwards-compatibility for things that probably shouldn't have used this class...
- $dataPath =
- $this->definition['rootPath'] . '/' .
- strtolower( $theme ) . '/' .
- $this->definition['name'] . '.json';
- }
- $localDataPath = $this->localBasePath . '/' . $dataPath;
+ $data = $this->loadOOUIDefinition( $theme, $module );
- // If there's no file for this module of this theme, that's okay, it will just use the defaults
- if ( !file_exists( $localDataPath ) ) {
+ if ( !$data ) {
+ // If there's no file for this module of this theme, that's okay, it will just use the defaults
continue;
}
- $data = json_decode( file_get_contents( $localDataPath ), true );
-
- // Expand the paths to images (since they are relative to the JSON file that defines them, not
- // our base directory)
- $fixPath = function ( &$path ) use ( $dataPath ) {
- $path = dirname( $dataPath ) . '/' . $path;
- };
- array_walk( $data['images'], function ( &$value ) use ( $fixPath ) {
- if ( is_string( $value['file'] ) ) {
- $fixPath( $value['file'] );
- } elseif ( is_array( $value['file'] ) ) {
- array_walk_recursive( $value['file'], $fixPath );
- }
- } );
// Convert into a definition compatible with the parent vanilla ResourceLoaderImageModule
foreach ( $data as $key => $value ) {
parent::loadFromDefinition();
}
+
+ /**
+ * Load the module definition from the JSON file(s) for the given theme and module.
+ *
+ * @since 1.34
+ * @param string $theme
+ * @param string $module
+ * @return array
+ */
+ protected function loadOOUIDefinition( $theme, $module ) {
+ // Find the path to the JSON file which contains the actual image definitions for this theme
+ if ( $module ) {
+ $dataPath = $this->getThemeImagesPath( $theme, $module );
+ } else {
+ // Backwards-compatibility for things that probably shouldn't have used this class...
+ $dataPath =
+ $this->definition['rootPath'] . '/' .
+ strtolower( $theme ) . '/' .
+ $this->definition['name'] . '.json';
+ }
+
+ return $this->readJSONFile( $dataPath );
+ }
+
+ /**
+ * Read JSON from a file, and transform all paths in it to be relative to the module's base path.
+ *
+ * @since 1.34
+ * @param string $dataPath Path relative to the module's base bath
+ * @return array|false
+ */
+ protected function readJSONFile( $dataPath ) {
+ $localDataPath = $this->localBasePath . '/' . $dataPath;
+
+ if ( !file_exists( $localDataPath ) ) {
+ return false;
+ }
+
+ $data = json_decode( file_get_contents( $localDataPath ), true );
+
+ // Expand the paths to images (since they are relative to the JSON file that defines them, not
+ // our base directory)
+ $fixPath = function ( &$path ) use ( $dataPath ) {
+ $path = dirname( $dataPath ) . '/' . $path;
+ };
+ array_walk( $data['images'], function ( &$value ) use ( $fixPath ) {
+ if ( is_string( $value['file'] ) ) {
+ $fixPath( $value['file'] );
+ } elseif ( is_array( $value['file'] ) ) {
+ array_walk_recursive( $value['file'], $fixPath );
+ }
+ } );
+
+ return $data;
+ }
}
class CleanupPreferences extends Maintenance {
public function __construct() {
parent::__construct();
- $this->mDescription = 'Clean up hidden preferences, removed preferences, and normalizes values';
+ $this->addDescription(
+ 'Clean up hidden preferences, removed preferences, and normalizes values'
+ );
$this->setBatchSize( 50 );
$this->addOption( 'dry-run', 'Print debug info instead of actually deleting' );
$this->addOption( 'hidden', 'Drop hidden preferences ($wgHiddenPrefs)' );
$this->fatalError( "Fatal error: The file '$arg' does not exist!" );
}
}
- };
+ }
$count = count( $files );
$this->output( "Importing $count pages...\n" );
public function __construct() {
parent::__construct();
- $this->mDescription = "Deletes local password for users.";
+ $this->addDescription( "Deletes local password for users." );
$this->setBatchSize( 1000 );
$this->addOption( 'user', 'If specified, only checks the given user', false, true );
'dependencies' => 'jquery.effects.core',
'group' => 'jquery.ui',
],
- 'jquery.effects.bounce' => [
- 'scripts' => 'resources/lib/jquery.ui/jquery.ui.effect-bounce.js',
- 'dependencies' => 'jquery.effects.core',
- 'group' => 'jquery.ui',
- ],
'jquery.effects.clip' => [
'scripts' => 'resources/lib/jquery.ui/jquery.ui.effect-clip.js',
'dependencies' => 'jquery.effects.core',
'dependencies' => 'jquery.effects.core',
'group' => 'jquery.ui',
],
- 'jquery.effects.explode' => [
- 'scripts' => 'resources/lib/jquery.ui/jquery.ui.effect-explode.js',
- 'dependencies' => 'jquery.effects.core',
- 'group' => 'jquery.ui',
- ],
- 'jquery.effects.fade' => [
- 'scripts' => 'resources/lib/jquery.ui/jquery.ui.effect-fade.js',
- 'dependencies' => 'jquery.effects.core',
- 'group' => 'jquery.ui',
- ],
- 'jquery.effects.fold' => [
- 'scripts' => 'resources/lib/jquery.ui/jquery.ui.effect-fold.js',
- 'dependencies' => 'jquery.effects.core',
- 'group' => 'jquery.ui',
- ],
'jquery.effects.highlight' => [
'scripts' => 'resources/lib/jquery.ui/jquery.ui.effect-highlight.js',
'dependencies' => 'jquery.effects.core',
'group' => 'jquery.ui',
],
- 'jquery.effects.pulsate' => [
- 'scripts' => 'resources/lib/jquery.ui/jquery.ui.effect-pulsate.js',
- 'dependencies' => 'jquery.effects.core',
- 'group' => 'jquery.ui',
- ],
'jquery.effects.scale' => [
'scripts' => 'resources/lib/jquery.ui/jquery.ui.effect-scale.js',
'dependencies' => 'jquery.effects.core',
'dependencies' => 'jquery.effects.core',
'group' => 'jquery.ui',
],
- 'jquery.effects.slide' => [
- 'scripts' => 'resources/lib/jquery.ui/jquery.ui.effect-slide.js',
- 'dependencies' => 'jquery.effects.core',
- 'group' => 'jquery.ui',
- ],
- 'jquery.effects.transfer' => [
- 'scripts' => 'resources/lib/jquery.ui/jquery.ui.effect-transfer.js',
- 'dependencies' => 'jquery.effects.core',
- 'group' => 'jquery.ui',
- ],
/* Moment.js */
'dependencies' => [
'oojs',
'oojs-ui-core.styles',
+ 'oojs-ui-core.icons',
'oojs-ui.styles.indicators',
'oojs-ui.styles.textures',
'mediawiki.language',
- 'oojs-ui.styles.icons-content',
- 'oojs-ui.styles.icons-alerts',
- 'oojs-ui.styles.icons-interactions',
],
'messages' => [
'ooui-field-help',
'themeStyles' => 'core',
'targets' => [ 'desktop', 'mobile' ],
],
+ 'oojs-ui-core.icons' => [
+ 'class' => ResourceLoaderOOUIIconPackModule::class,
+ 'icons' => [ 'add', 'alert', 'notice', 'error', 'check', 'close', 'info', 'search', 'subtract' ],
+ 'targets' => [ 'desktop', 'mobile' ],
+ ],
// Additional widgets and layouts module.
'oojs-ui-widgets' => [
'class' => ResourceLoaderOOUIFileModule::class,
'themeStyles' => 'widgets',
'dependencies' => [
'oojs-ui-core',
- 'oojs-ui.styles.icons-interactions',
- 'oojs-ui.styles.icons-content',
- 'oojs-ui.styles.icons-editing-advanced',
- 'oojs-ui.styles.icons-movement',
- 'oojs-ui.styles.icons-moderation',
+ 'oojs-ui-widgets.icons',
],
'messages' => [
'ooui-item-remove',
'themeStyles' => 'widgets',
'targets' => [ 'desktop', 'mobile' ],
],
+ 'oojs-ui-widgets.icons' => [
+ 'class' => ResourceLoaderOOUIIconPackModule::class,
+ // Do not repeat icons already used in 'oojs-ui-core.icons'
+ 'icons' => [ 'attachment', 'collapse', 'expand', 'trash', 'upload' ],
+ 'targets' => [ 'desktop', 'mobile' ],
+ ],
// Toolbar and tools module.
'oojs-ui-toolbars' => [
'class' => ResourceLoaderOOUIFileModule::class,
'themeStyles' => 'toolbars',
'dependencies' => [
'oojs-ui-core',
- 'oojs-ui.styles.icons-movement',
+ 'oojs-ui-toolbars.icons',
],
'messages' => [
'ooui-toolbar-more',
],
'targets' => [ 'desktop', 'mobile' ],
],
+ 'oojs-ui-toolbars.icons' => [
+ 'class' => ResourceLoaderOOUIIconPackModule::class,
+ // Do not repeat icons already used in 'oojs-ui-core.icons': 'check'
+ 'icons' => [ 'collapse', 'expand' ],
+ 'targets' => [ 'desktop', 'mobile' ],
+ ],
// Windows and dialogs module.
'oojs-ui-windows' => [
'class' => ResourceLoaderOOUIFileModule::class,
'themeStyles' => 'windows',
'dependencies' => [
'oojs-ui-core',
- 'oojs-ui.styles.icons-movement',
+ 'oojs-ui-windows.icons',
],
'messages' => [
'ooui-dialog-message-accept',
],
'targets' => [ 'desktop', 'mobile' ],
],
+ 'oojs-ui-windows.icons' => [
+ 'class' => ResourceLoaderOOUIIconPackModule::class,
+ // Do not repeat icons already used in 'oojs-ui-core.icons': 'close'
+ 'icons' => [ 'previous' ],
+ 'targets' => [ 'desktop', 'mobile' ],
+ ],
'oojs-ui.styles.indicators' => [
'class' => ResourceLoaderOOUIImageModule::class,
+++ /dev/null
-/*!
- * jQuery UI Effects Bounce 1.9.2
- * http://jqueryui.com
- *
- * Copyright 2012 jQuery Foundation and other contributors
- * Released under the MIT license.
- * http://jquery.org/license
- *
- * http://api.jqueryui.com/bounce-effect/
- *
- * Depends:
- * jquery.ui.effect.js
- */
-(function( $, undefined ) {
-
-$.effects.effect.bounce = function( o, done ) {
- var el = $( this ),
- props = [ "position", "top", "bottom", "left", "right", "height", "width" ],
-
- // defaults:
- mode = $.effects.setMode( el, o.mode || "effect" ),
- hide = mode === "hide",
- show = mode === "show",
- direction = o.direction || "up",
- distance = o.distance,
- times = o.times || 5,
-
- // number of internal animations
- anims = times * 2 + ( show || hide ? 1 : 0 ),
- speed = o.duration / anims,
- easing = o.easing,
-
- // utility:
- ref = ( direction === "up" || direction === "down" ) ? "top" : "left",
- motion = ( direction === "up" || direction === "left" ),
- i,
- upAnim,
- downAnim,
-
- // we will need to re-assemble the queue to stack our animations in place
- queue = el.queue(),
- queuelen = queue.length;
-
- // Avoid touching opacity to prevent clearType and PNG issues in IE
- if ( show || hide ) {
- props.push( "opacity" );
- }
-
- $.effects.save( el, props );
- el.show();
- $.effects.createWrapper( el ); // Create Wrapper
-
- // default distance for the BIGGEST bounce is the outer Distance / 3
- if ( !distance ) {
- distance = el[ ref === "top" ? "outerHeight" : "outerWidth" ]() / 3;
- }
-
- if ( show ) {
- downAnim = { opacity: 1 };
- downAnim[ ref ] = 0;
-
- // if we are showing, force opacity 0 and set the initial position
- // then do the "first" animation
- el.css( "opacity", 0 )
- .css( ref, motion ? -distance * 2 : distance * 2 )
- .animate( downAnim, speed, easing );
- }
-
- // start at the smallest distance if we are hiding
- if ( hide ) {
- distance = distance / Math.pow( 2, times - 1 );
- }
-
- downAnim = {};
- downAnim[ ref ] = 0;
- // Bounces up/down/left/right then back to 0 -- times * 2 animations happen here
- for ( i = 0; i < times; i++ ) {
- upAnim = {};
- upAnim[ ref ] = ( motion ? "-=" : "+=" ) + distance;
-
- el.animate( upAnim, speed, easing )
- .animate( downAnim, speed, easing );
-
- distance = hide ? distance * 2 : distance / 2;
- }
-
- // Last Bounce when Hiding
- if ( hide ) {
- upAnim = { opacity: 0 };
- upAnim[ ref ] = ( motion ? "-=" : "+=" ) + distance;
-
- el.animate( upAnim, speed, easing );
- }
-
- el.queue(function() {
- if ( hide ) {
- el.hide();
- }
- $.effects.restore( el, props );
- $.effects.removeWrapper( el );
- done();
- });
-
- // inject all the animations we just queued to be first in line (after "inprogress")
- if ( queuelen > 1) {
- queue.splice.apply( queue,
- [ 1, 0 ].concat( queue.splice( queuelen, anims + 1 ) ) );
- }
- el.dequeue();
-
-};
-
-})(jQuery);
+++ /dev/null
-/*!
- * jQuery UI Effects Explode 1.9.2
- * http://jqueryui.com
- *
- * Copyright 2012 jQuery Foundation and other contributors
- * Released under the MIT license.
- * http://jquery.org/license
- *
- * http://api.jqueryui.com/explode-effect/
- *
- * Depends:
- * jquery.ui.effect.js
- */
-(function( $, undefined ) {
-
-$.effects.effect.explode = function( o, done ) {
-
- var rows = o.pieces ? Math.round( Math.sqrt( o.pieces ) ) : 3,
- cells = rows,
- el = $( this ),
- mode = $.effects.setMode( el, o.mode || "hide" ),
- show = mode === "show",
-
- // show and then visibility:hidden the element before calculating offset
- offset = el.show().css( "visibility", "hidden" ).offset(),
-
- // width and height of a piece
- width = Math.ceil( el.outerWidth() / cells ),
- height = Math.ceil( el.outerHeight() / rows ),
- pieces = [],
-
- // loop
- i, j, left, top, mx, my;
-
- // children animate complete:
- function childComplete() {
- pieces.push( this );
- if ( pieces.length === rows * cells ) {
- animComplete();
- }
- }
-
- // clone the element for each row and cell.
- for( i = 0; i < rows ; i++ ) { // ===>
- top = offset.top + i * height;
- my = i - ( rows - 1 ) / 2 ;
-
- for( j = 0; j < cells ; j++ ) { // |||
- left = offset.left + j * width;
- mx = j - ( cells - 1 ) / 2 ;
-
- // Create a clone of the now hidden main element that will be absolute positioned
- // within a wrapper div off the -left and -top equal to size of our pieces
- el
- .clone()
- .appendTo( "body" )
- .wrap( "<div></div>" )
- .css({
- position: "absolute",
- visibility: "visible",
- left: -j * width,
- top: -i * height
- })
-
- // select the wrapper - make it overflow: hidden and absolute positioned based on
- // where the original was located +left and +top equal to the size of pieces
- .parent()
- .addClass( "ui-effects-explode" )
- .css({
- position: "absolute",
- overflow: "hidden",
- width: width,
- height: height,
- left: left + ( show ? mx * width : 0 ),
- top: top + ( show ? my * height : 0 ),
- opacity: show ? 0 : 1
- }).animate({
- left: left + ( show ? 0 : mx * width ),
- top: top + ( show ? 0 : my * height ),
- opacity: show ? 1 : 0
- }, o.duration || 500, o.easing, childComplete );
- }
- }
-
- function animComplete() {
- el.css({
- visibility: "visible"
- });
- $( pieces ).remove();
- if ( !show ) {
- el.hide();
- }
- done();
- }
-};
-
-})(jQuery);
+++ /dev/null
-/*!
- * jQuery UI Effects Fold 1.9.2
- * http://jqueryui.com
- *
- * Copyright 2012 jQuery Foundation and other contributors
- * Released under the MIT license.
- * http://jquery.org/license
- *
- * http://api.jqueryui.com/fold-effect/
- *
- * Depends:
- * jquery.ui.effect.js
- */
-(function( $, undefined ) {
-
-$.effects.effect.fold = function( o, done ) {
-
- // Create element
- var el = $( this ),
- props = [ "position", "top", "bottom", "left", "right", "height", "width" ],
- mode = $.effects.setMode( el, o.mode || "hide" ),
- show = mode === "show",
- hide = mode === "hide",
- size = o.size || 15,
- percent = /([0-9]+)%/.exec( size ),
- horizFirst = !!o.horizFirst,
- widthFirst = show !== horizFirst,
- ref = widthFirst ? [ "width", "height" ] : [ "height", "width" ],
- duration = o.duration / 2,
- wrapper, distance,
- animation1 = {},
- animation2 = {};
-
- $.effects.save( el, props );
- el.show();
-
- // Create Wrapper
- wrapper = $.effects.createWrapper( el ).css({
- overflow: "hidden"
- });
- distance = widthFirst ?
- [ wrapper.width(), wrapper.height() ] :
- [ wrapper.height(), wrapper.width() ];
-
- if ( percent ) {
- size = parseInt( percent[ 1 ], 10 ) / 100 * distance[ hide ? 0 : 1 ];
- }
- if ( show ) {
- wrapper.css( horizFirst ? {
- height: 0,
- width: size
- } : {
- height: size,
- width: 0
- });
- }
-
- // Animation
- animation1[ ref[ 0 ] ] = show ? distance[ 0 ] : size;
- animation2[ ref[ 1 ] ] = show ? distance[ 1 ] : 0;
-
- // Animate
- wrapper
- .animate( animation1, duration, o.easing )
- .animate( animation2, duration, o.easing, function() {
- if ( hide ) {
- el.hide();
- }
- $.effects.restore( el, props );
- $.effects.removeWrapper( el );
- done();
- });
-
-};
-
-})(jQuery);
+++ /dev/null
-/*!
- * jQuery UI Effects Pulsate 1.9.2
- * http://jqueryui.com
- *
- * Copyright 2012 jQuery Foundation and other contributors
- * Released under the MIT license.
- * http://jquery.org/license
- *
- * http://api.jqueryui.com/pulsate-effect/
- *
- * Depends:
- * jquery.ui.effect.js
- */
-(function( $, undefined ) {
-
-$.effects.effect.pulsate = function( o, done ) {
- var elem = $( this ),
- mode = $.effects.setMode( elem, o.mode || "show" ),
- show = mode === "show",
- hide = mode === "hide",
- showhide = ( show || mode === "hide" ),
-
- // showing or hiding leaves of the "last" animation
- anims = ( ( o.times || 5 ) * 2 ) + ( showhide ? 1 : 0 ),
- duration = o.duration / anims,
- animateTo = 0,
- queue = elem.queue(),
- queuelen = queue.length,
- i;
-
- if ( show || !elem.is(":visible")) {
- elem.css( "opacity", 0 ).show();
- animateTo = 1;
- }
-
- // anims - 1 opacity "toggles"
- for ( i = 1; i < anims; i++ ) {
- elem.animate({
- opacity: animateTo
- }, duration, o.easing );
- animateTo = 1 - animateTo;
- }
-
- elem.animate({
- opacity: animateTo
- }, duration, o.easing);
-
- elem.queue(function() {
- if ( hide ) {
- elem.hide();
- }
- done();
- });
-
- // We just queued up "anims" animations, we need to put them next in the queue
- if ( queuelen > 1 ) {
- queue.splice.apply( queue,
- [ 1, 0 ].concat( queue.splice( queuelen, anims + 1 ) ) );
- }
- elem.dequeue();
-};
-
-})(jQuery);
+++ /dev/null
-/*!
- * jQuery UI Effects Slide 1.9.2
- * http://jqueryui.com
- *
- * Copyright 2012 jQuery Foundation and other contributors
- * Released under the MIT license.
- * http://jquery.org/license
- *
- * http://api.jqueryui.com/slide-effect/
- *
- * Depends:
- * jquery.ui.effect.js
- */
-(function( $, undefined ) {
-
-$.effects.effect.slide = function( o, done ) {
-
- // Create element
- var el = $( this ),
- props = [ "position", "top", "bottom", "left", "right", "width", "height" ],
- mode = $.effects.setMode( el, o.mode || "show" ),
- show = mode === "show",
- direction = o.direction || "left",
- ref = (direction === "up" || direction === "down") ? "top" : "left",
- positiveMotion = (direction === "up" || direction === "left"),
- distance,
- animation = {};
-
- // Adjust
- $.effects.save( el, props );
- el.show();
- distance = o.distance || el[ ref === "top" ? "outerHeight" : "outerWidth" ]( true );
-
- $.effects.createWrapper( el ).css({
- overflow: "hidden"
- });
-
- if ( show ) {
- el.css( ref, positiveMotion ? (isNaN(distance) ? "-" + distance : -distance) : distance );
- }
-
- // Animation
- animation[ ref ] = ( show ?
- ( positiveMotion ? "+=" : "-=") :
- ( positiveMotion ? "-=" : "+=")) +
- distance;
-
- // Animate
- el.animate( animation, {
- queue: false,
- duration: o.duration,
- easing: o.easing,
- complete: function() {
- if ( mode === "hide" ) {
- el.hide();
- }
- $.effects.restore( el, props );
- $.effects.removeWrapper( el );
- done();
- }
- });
-};
-
-})(jQuery);
+++ /dev/null
-/*!
- * jQuery UI Effects Transfer 1.9.2
- * http://jqueryui.com
- *
- * Copyright 2012 jQuery Foundation and other contributors
- * Released under the MIT license.
- * http://jquery.org/license
- *
- * http://api.jqueryui.com/transfer-effect/
- *
- * Depends:
- * jquery.ui.effect.js
- */
-(function( $, undefined ) {
-
-$.effects.effect.transfer = function( o, done ) {
- var elem = $( this ),
- target = $( o.to ),
- targetFixed = target.css( "position" ) === "fixed",
- body = $("body"),
- fixTop = targetFixed ? body.scrollTop() : 0,
- fixLeft = targetFixed ? body.scrollLeft() : 0,
- endPosition = target.offset(),
- animation = {
- top: endPosition.top - fixTop ,
- left: endPosition.left - fixLeft ,
- height: target.innerHeight(),
- width: target.innerWidth()
- },
- startPosition = elem.offset(),
- transfer = $( '<div class="ui-effects-transfer"></div>' )
- .appendTo( document.body )
- .addClass( o.className )
- .css({
- top: startPosition.top - fixTop ,
- left: startPosition.left - fixLeft ,
- height: elem.innerHeight(),
- width: elem.innerWidth(),
- position: targetFixed ? "fixed" : "absolute"
- })
- .animate( animation, o.duration, o.easing, function() {
- transfer.remove();
- done();
- });
-};
-
-})(jQuery);
} );
// Load suggestions if the value is changed because there are already
// typed characters before the JavaScript is loaded.
- if ( this.value !== this.defaultValue ) {
+ if ( $( this ).is( ':focus' ) && this.value !== this.defaultValue ) {
update( context, false );
}
}
&-body {
max-height: 70vh;
+ min-width: 100%;
}
&-footer {
/* Methods */
-/**
- * Override parent method to avoid unnecessary resize events.
- */
-FilterTagMultiselectWidget.prototype.updateIfHeightChanged = function () { };
-
/**
* Respond to view select widget choose event
*
class ResourceLoaderFileTestModule extends ResourceLoaderFileModule {
protected $lessVars = [];
- public function __construct( $options = [], $test = [] ) {
- parent::__construct( $options );
-
- foreach ( $test as $key => $value ) {
- $this->$key = $value;
+ public function __construct( $options = [] ) {
+ if ( isset( $options['lessVars'] ) ) {
+ $this->lessVars = $options['lessVars'];
+ unset( $options['lessVars'] );
}
+
+ parent::__construct( $options );
}
public function getLessVars( ResourceLoaderContext $context ) {
'actor',
];
- /**
- * Create an ActorMigration for a particular stage
- * @param int $stage
- * @return ActorMigration
- */
- protected function makeMigration( $stage ) {
- return new ActorMigration( $stage );
- }
-
/**
* @dataProvider provideConstructor
* @param int $stage
* @param array $expect
*/
public function testGetJoin( $stage, $key, $expect ) {
- $m = $this->makeMigration( $stage );
+ $m = new ActorMigration( $stage );
$result = $m->getJoin( $key );
$this->assertEquals( $expect, $result );
}
$users = reset( $users );
}
- $m = $this->makeMigration( $stage );
+ $m = new ActorMigration( $stage );
$result = $m->getWhere( $this->db, $key, $users, $useId );
$this->assertEquals( $expect, $result );
}
$extraFields['ipb_address'] = __CLASS__ . "#{$stageNames[$writeStage]}";
}
- $w = $this->makeMigration( $writeStage );
+ $w = new ActorMigration( $writeStage );
$usesTemp = $key === 'rev_user';
if ( $usesTemp ) {
}
foreach ( $possibleReadStages as $readStage ) {
- $r = $this->makeMigration( $readStage );
+ $r = new ActorMigration( $readStage );
$queryInfo = $r->getJoin( $key );
$row = $this->db->selectRow(
* @expectedExceptionMessage Must use getInsertValuesWithTempTable() for rev_user
*/
public function testInsertWrong( $stage ) {
- $m = $this->makeMigration( $stage );
+ $m = new ActorMigration( $stage );
$m->getInsertValues( $this->db, 'rev_user', $this->getTestUser()->getUser() );
}
* @expectedExceptionMessage Must use getInsertValues() for rc_user
*/
public function testInsertWithTempTableWrong( $stage ) {
- $m = $this->makeMigration( $stage );
+ $m = new ActorMigration( $stage );
$m->getInsertValuesWithTempTable( $this->db, 'rc_user', $this->getTestUser()->getUser() );
}
$wrap->formerTempTables += [ 'rc_user' => '1.30' ];
$this->hideDeprecated( 'ActorMigration::getInsertValuesWithTempTable for rc_user' );
- $m = $this->makeMigration( $stage );
+ $m = new ActorMigration( $stage );
list( $fields, $callback )
= $m->getInsertValuesWithTempTable( $this->db, 'rc_user', $this->getTestUser()->getUser() );
$this->assertTrue( is_callable( $callback ) );
* @expectedExceptionMessage $extra[rev_timestamp] is not provided
*/
public function testInsertWithTempTableCallbackMissingFields( $stage ) {
- $m = $this->makeMigration( $stage );
+ $m = new ActorMigration( $stage );
list( $fields, $callback )
= $m->getInsertValuesWithTempTable( $this->db, 'rev_user', $this->getTestUser()->getUser() );
$callback( 1, [] );
list( $cFields, $cCallback ) = MediaWikiServices::getInstance()->getCommentStore()
->insertWithTempTable( $this->db, 'rev_comment', '' );
- $m = $this->makeMigration( $stage );
+ $m = new ActorMigration( $stage );
list( $fields, $callback ) =
$m->getInsertValuesWithTempTable( $this->db, 'rev_user', $userIdentity );
$extraFields = [
(int)$row->rev_actor
);
- $m = $this->makeMigration( $stage );
+ $m = new ActorMigration( $stage );
$fields = $m->getInsertValues( $this->db, 'dummy_user', $userIdentity );
if ( $stage & SCHEMA_COMPAT_WRITE_OLD ) {
$this->assertSame( $user->getId(), $fields['dummy_user'] );
* @param string $isNotAnon
*/
public function testIsAnon( $stage, $isAnon, $isNotAnon ) {
- $m = $this->makeMigration( $stage );
+ $m = new ActorMigration( $stage );
$this->assertSame( $isAnon, $m->isAnon( 'foo' ) );
$this->assertSame( $isNotAnon, $m->isNotAnon( 'foo' ) );
}
return $row;
}
- private function getTitleArrayFromResult( $resultWrapper ) {
- return new TitleArrayFromResult( $resultWrapper );
- }
-
/**
* @covers TitleArrayFromResult::__construct
*/
$row = false;
$resultWrapper = $this->getMockResultWrapper( $row );
- $object = $this->getTitleArrayFromResult( $resultWrapper );
+ $object = new TitleArrayFromResult( $resultWrapper );
$this->assertEquals( $resultWrapper, $object->res );
$this->assertSame( 0, $object->key );
$row = $this->getRowWithTitle( $namespace, $title );
$resultWrapper = $this->getMockResultWrapper( $row );
- $object = $this->getTitleArrayFromResult( $resultWrapper );
+ $object = new TitleArrayFromResult( $resultWrapper );
$this->assertEquals( $resultWrapper, $object->res );
$this->assertSame( 0, $object->key );
* @covers TitleArrayFromResult::count
*/
public function testCountWithVaryingValues( $numRows ) {
- $object = $this->getTitleArrayFromResult( $this->getMockResultWrapper(
+ $object = new TitleArrayFromResult( $this->getMockResultWrapper(
$this->getRowWithTitle(),
$numRows
) );
$namespace = 0;
$title = 'foo';
$row = $this->getRowWithTitle( $namespace, $title );
- $object = $this->getTitleArrayFromResult( $this->getMockResultWrapper( $row ) );
+ $object = new TitleArrayFromResult( $this->getMockResultWrapper( $row ) );
$this->assertInstanceOf( Title::class, $object->current() );
$this->assertEquals( $namespace, $object->current->mNamespace );
$this->assertEquals( $title, $object->current->mTextform );
* @covers TitleArrayFromResult::valid
*/
public function testValid( $input, $expected ) {
- $object = $this->getTitleArrayFromResult( $this->getMockResultWrapper( $input ) );
+ $object = new TitleArrayFromResult( $this->getMockResultWrapper( $input ) );
$this->assertEquals( $expected, $object->valid() );
}
<?php
/**
- * FIXME Temporary disabled per T225244
* @group API
* @group medium
- * @group Broken
*
* @covers ApiQueryLanguageinfo
*/
}
}
);
+ Language::clearCaches();
}
private function doQuery( array $params, $microtimeFunction = null ): array {
*/
class ImportTest extends MediaWikiLangTestCase {
- private function getDataSource( $xml ) {
- return new ImportStringSource( $xml );
- }
-
/**
* @covers WikiImporter
* @dataProvider getUnknownTagsXML
* @param string $title
*/
public function testUnknownXMLTags( $xml, $text, $title ) {
- $source = $this->getDataSource( $xml );
+ $source = new ImportStringSource( $xml );
$importer = new WikiImporter(
$source,
* @param string|null $redirectTitle
*/
public function testHandlePageContainsRedirect( $xml, $redirectTitle ) {
- $source = $this->getDataSource( $xml );
+ $source = new ImportStringSource( $xml );
$redirect = null;
$callback = function ( Title $title, ForeignTitle $foreignTitle, $revCount,
* @param array|null $namespaces
*/
public function testSiteInfoContainsNamespaces( $xml, $namespaces ) {
- $source = $this->getDataSource( $xml );
+ $source = new ImportStringSource( $xml );
$importNamespaces = null;
$callback = function ( array $siteinfo, $innerImporter ) use ( &$importNamespaces ) {
$n = ( $assign ? 1 : 0 ) + ( $create ? 2 : 0 );
// phpcs:disable Generic.Files.LineLength
- $source = $this->getDataSource( <<<EOF
+ $source = new ImportStringSource( <<<EOF
<mediawiki xmlns="http://www.mediawiki.org/xml/export-0.10/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.mediawiki.org/xml/export-0.10/ http://www.mediawiki.org/xml/export-0.10.xsd" version="0.10" xml:lang="en">
<page>
<title>TestImportPage</title>
} catch ( MWException $e ) {
// unsupported?
// @todo What if it was another error?
- };
+ }
}
}
/**
* @expectedException \Wikimedia\Rdbms\DBTransactionStateError
- * @covers \Wikimedia\Rdbms\Database::assertTransactionStatus
+ * @covers \Wikimedia\Rdbms\Database::assertQueryIsCurrentlyAllowed
*/
public function testTransactionErrorState1() {
$wrapper = TestingAccessWrapper::newFromObject( $this->database );
$module = new ResourceLoaderFileTestModule( [
'localBasePath' => $basePath,
'styles' => [ 'styles.less' ],
- ], [
'lessVars' => [ 'foo' => '2px', 'Foo' => '#eeeeee' ]
] );
$module->setName( 'test.less' );
$this->assertStringEqualsFile( $basePath . '/styles.css', $styles['all'] );
}
+ public function provideGetVersionHash() {
+ $a = [];
+ $b = [
+ 'lessVars' => [ 'key' => 'value' ],
+ ];
+ yield 'with and without Less variables' => [ $a, $b, false ];
+
+ $a = [
+ 'lessVars' => [ 'key' => 'value1' ],
+ ];
+ $b = [
+ 'lessVars' => [ 'key' => 'value2' ],
+ ];
+ yield 'different Less variables' => [ $a, $b, false ];
+
+ $x = [
+ 'lessVars' => [ 'key' => 'value' ],
+ ];
+ yield 'identical Less variables' => [ $x, $x, true ];
+ }
+
/**
+ * @dataProvider provideGetVersionHash
* @covers ResourceLoaderFileModule::getDefinitionSummary
* @covers ResourceLoaderFileModule::getFileHashes
*/
- public function testGetVersionHash() {
+ public function testGetVersionHash( $a, $b, $isEqual ) {
$context = $this->getResourceLoaderContext();
- // Less variables
- $module = new ResourceLoaderFileTestModule();
- $version = $module->getVersionHash( $context );
- $module = new ResourceLoaderFileTestModule( [], [
- 'lessVars' => [ 'key' => 'value' ],
- ] );
- $this->assertNotEquals(
- $version,
- $module->getVersionHash( $context ),
- 'Using less variables is significant'
+ $moduleA = new ResourceLoaderFileTestModule( $a );
+ $versionA = $moduleA->getVersionHash( $context );
+ $moduleB = new ResourceLoaderFileTestModule( $b );
+ $versionB = $moduleB->getVersionHash( $context );
+
+ $this->assertSame(
+ $isEqual,
+ ( $versionA === $versionB ),
+ 'Whether versions hashes are equal'
);
}
- public function providerGetScriptPackageFiles() {
+ public function provideGetScriptPackageFiles() {
$basePath = __DIR__ . '/../../data/resourceloader';
$base = [ 'localBasePath' => $basePath ];
$commentScript = file_get_contents( "$basePath/script-comment.js" );
}
/**
- * @dataProvider providerGetScriptPackageFiles
+ * @dataProvider provideGetScriptPackageFiles
* @covers ResourceLoaderFileModule::getScript
* @covers ResourceLoaderFileModule::getPackageFiles
* @covers ResourceLoaderFileModule::expandPackageFiles
return $row;
}
- private function getUserArrayFromResult( $resultWrapper ) {
- return new UserArrayFromResult( $resultWrapper );
- }
-
/**
* @covers UserArrayFromResult::__construct
*/
$row = false;
$resultWrapper = $this->getMockResultWrapper( $row );
- $object = $this->getUserArrayFromResult( $resultWrapper );
+ $object = new UserArrayFromResult( $resultWrapper );
$this->assertEquals( $resultWrapper, $object->res );
$this->assertSame( 0, $object->key );
$row = $this->getRowWithUsername( $username );
$resultWrapper = $this->getMockResultWrapper( $row );
- $object = $this->getUserArrayFromResult( $resultWrapper );
+ $object = new UserArrayFromResult( $resultWrapper );
$this->assertEquals( $resultWrapper, $object->res );
$this->assertSame( 0, $object->key );
* @covers UserArrayFromResult::count
*/
public function testCountWithVaryingValues( $numRows ) {
- $object = $this->getUserArrayFromResult( $this->getMockResultWrapper(
+ $object = new UserArrayFromResult( $this->getMockResultWrapper(
$this->getRowWithUsername(),
$numRows
) );
public function testCurrentAfterConstruction() {
$username = 'addshore';
$userRow = $this->getRowWithUsername( $username );
- $object = $this->getUserArrayFromResult( $this->getMockResultWrapper( $userRow ) );
+ $object = new UserArrayFromResult( $this->getMockResultWrapper( $userRow ) );
$this->assertInstanceOf( User::class, $object->current() );
$this->assertEquals( $username, $object->current()->mName );
}
* @covers UserArrayFromResult::valid
*/
public function testValid( $input, $expected ) {
- $object = $this->getUserArrayFromResult( $this->getMockResultWrapper( $input ) );
+ $object = new UserArrayFromResult( $this->getMockResultWrapper( $input ) );
$this->assertEquals( $expected, $object->valid() );
}
* Checks that all API query modules, core and extensions, have unique prefixes.
*
* @group API
- * @coversNothing
*/
class ApiPrefixUniquenessTest extends MediaWikiTestCase {
* - do not have inconsistencies in the parameter definitions
*
* @group API
- * @coversNothing
*/
class ApiStructureTest extends MediaWikiTestCase {
<?php
-/**
- * @coversNothing
- */
class AutoLoaderStructureTest extends MediaWikiTestCase {
/**
* Assert that there were no classes loaded that are not registered with the AutoLoader.
return $rights;
}
- /**
- * @coversNothing
- */
public function testAvailableRights() {
$missingRights = array_diff(
$this->getAllVisibleRights(),
* Test, if for all rights a right- message exist,
* which is used on Special:ListGroupRights as help text
* Extensions and core
- *
- * @coversNothing
*/
public function testAllRightsWithMessage() {
$this->checkMessagesExist( 'right-' );
}
/**
- * @coversNothing
* @dataProvider provideHandlers
* @param ContentHandler $handler
*/
/**
* @group Database
- * @coversNothing
*/
class DatabaseIntegrationTest extends MediaWikiTestCase {
/**
/**
* Validates all loaded extensions and skins using the ExtensionRegistry
* against the extension.json schema in the docs/ folder.
- *
- * @coversNothing
*/
class ExtensionJsonValidationTest extends PHPUnit\Framework\TestCase {
<?php
-/**
- * @coversNothing
- */
class PasswordPolicyStructureTest extends MediaWikiTestCase {
public function provideChecks() {
* @copyright © 2012, Niklas Laxström
* @copyright © 2012, Santhosh Thottingal
* @copyright © 2012, Timo Tijhof
- * @coversNothing
*/
class ResourcesTest extends MediaWikiTestCase {
*
* @since 1.32
* @author Addshore
- * @coversNothing
*/
class SpecialPageFatalTest extends MediaWikiTestCase {
public function provideSpecialPages() {
* Verify all files that appear to be tests have file names ending in
* Test. If the file names do not end in Test, they will not be run.
* @group medium
- * @coversNothing
*/
public function testUnitTestFileNamesEndWithTest() {
// realpath() also normalizes directory separator on windows for prefix compares
public function __construct() {
parent::__construct();
- $this->mDescription = 'Create a specification for message parsing ini JSON format';
+ $this->addDescription( 'Create a specification for message parsing ini JSON format' );
// add any other options here
}