* @return array
*/
public function getBlockErrorParams( IContextSource $context ) {
+ $lang = $context->getLanguage();
+
$blocker = $this->getBlocker();
if ( $blocker instanceof User ) { // local user
$blockerUserpage = $blocker->getUserPage();
- $link = "[[{$blockerUserpage->getPrefixedText()}|{$blockerUserpage->getText()}]]";
+ $blockerText = $lang->embedBidi( $blockerUserpage->getText() );
+ $link = "[[{$blockerUserpage->getPrefixedText()}|{$blockerText}]]";
} else { // foreign user
$link = $blocker;
}
/* $ip returns who *is* being blocked, $intended contains who was meant to be blocked.
* This could be a username, an IP range, or a single IP. */
- $intended = $this->getTarget();
- $lang = $context->getLanguage();
+ $intended = (string)$this->getTarget();
return [
$link,
$reason,
$context->getRequest()->getIP(),
- $this->getByName(),
+ $lang->embedBidi( $this->getByName() ),
// TODO: SystemBlock replaces this with the system block type. Clean up
// error params so that this is not necessary.
$this->getId(),
$lang->formatExpiry( $this->getExpiry() ),
- (string)$intended,
+ $lang->embedBidi( $intended ),
$lang->userTimeAndDate( $this->getTimestamp(), $context->getUser() ),
];
}
/** @var bool Whether all zones should be private (e.g. private wiki repo) */
protected $isPrivate;
- /** @var array callable Override these in the base class */
+ /** @var callable Override these in the base class */
protected $fileFactory = [ UnregisteredLocalFile::class, 'newFromTitle' ];
- /** @var array callable|bool Override these in the base class */
+ /** @var callable|false Override these in the base class */
protected $oldFileFactory = false;
- /** @var array callable|bool Override these in the base class */
+ /** @var callable|false Override these in the base class */
protected $fileFactoryKey = false;
- /** @var array callable|bool Override these in the base class */
+ /** @var callable|false Override these in the base class */
protected $oldFileFactoryKey = false;
/** @var string URL of where to proxy thumb.php requests to.
/**
* Check if a single zone or list of zones is defined for usage
*
- * @param array $doZones Only do a particular zones
+ * @param string[]|string $doZones Only do a particular zones
* @throws MWException
* @return Status
*/
/**
* Make an url to this repo
*
- * @param string $query Query string to append
+ * @param string|string[] $query Query string to append
* @param string $entry Entry point; defaults to index
* @return string|bool False on failure
*/
/**
* Create a new good result
*
- * @param null|string $value
+ * @param null|mixed $value
* @return Status
*/
public function newGood( $value = null ) {
/**
* @param array $query
- * @return string
+ * @return array|null
*/
function fetchImageQuery( $query ) {
global $wgLanguageCode;
use MediaWiki\Logger\LoggerFactory;
use Wikimedia\Rdbms\Database;
use Wikimedia\Rdbms\IDatabase;
+use Wikimedia\Rdbms\IResultWrapper;
use MediaWiki\MediaWikiServices;
/**
/** @var int Number of line to return by nextHistoryLine() (constructor) */
private $historyLine;
- /** @var int Result of the query for the file's history (nextHistoryLine) */
+ /** @var IResultWrapper|null Result of the query for the file's history (nextHistoryLine) */
private $historyRes;
/** @var string Major MIME type */
protected $mFieldTree = [];
protected $mShowReset = false;
protected $mShowSubmit = true;
+ /** @var string[] */
protected $mSubmitFlags = [ 'primary', 'progressive' ];
protected $mShowCancel = false;
protected $mCancelTarget;
* or the input's default value if it has not been set.
*
* @param WebRequest $request
- * @return string The value
+ * @return mixed The value
*/
public function loadDataFromRequest( $request ) {
if ( $request->getCheck( $this->mName ) ) {
/**
* Get a FieldLayout (or subclass thereof) to wrap this field in when using OOUI output.
- * @param string $inputField
+ * @param OOUI\Widget $inputField
* @param array $config
* @return OOUI\FieldLayout|OOUI\ActionFieldLayout
*/
* Recursively forces values in an array to strings, because issues arise
* with integer 0 as a value.
*
- * @param array $array
+ * @param array|string $array
* @return array|string
*/
public static function forceToStringRecursive( $array ) {
* line above the options in the case of a checkbox matrix, i.e. it's always
* a "vertical-label".
*
- * @param string $value The value to set the input to
+ * @param string|array $value The value to set the input to
*
* @return string Complete HTML table row
*/
/** @var string $mButtonType Value for the button in this field */
protected $mButtonValue;
- /** @var string $mButtonType Value for the button in this field */
+ /** @var string[] $mButtonType Value for the button in this field */
protected $mButtonFlags = [ 'progressive' ];
public function __construct( $info ) {
use InvalidArgumentException;
/**
- * Helper class to handle automatically marking connections as reusable (via RAII pattern)
- * as well handling deferring the actual network connection until the handle is used
+ * Helper class used for automatically marking an IDatabase connection as reusable (once it no
+ * longer matters which DB domain is selected) and for deferring the actual network connection
+ *
+ * This uses an RAII-style pattern where calling code is expected to keep the returned reference
+ * handle as a function variable that falls out of scope when no longer needed. This avoids the
+ * need for matching reuseConnection() calls for every "return" statement as well as the tedious
+ * use of try/finally.
+ *
+ * @par Example:
+ * @code
+ * function getRowData() {
+ * $conn = $this->lb->getConnectedRef( DB_REPLICA );
+ * $row = $conn->select( ... );
+ * return $row ? (array)$row : false;
+ * // $conn falls out of scope and $this->lb->reuseConnection() gets called
+ * }
+ * @endcode
*
* @ingroup Database
* @since 1.22
namespace Wikimedia\Rdbms;
use Exception;
+use LogicException;
use InvalidArgumentException;
/**
/** @var string Domain specifier when no specific database needs to be selected */
const DOMAIN_ANY = '';
+ /** @var bool The generic query group (bool gives b/c with 1.33 method signatures) */
+ const GROUP_GENERIC = false;
/** @var int DB handle should have DBO_TRX disabled and the caller will leave it as such */
const CONN_TRX_AUTOCOMMIT = 1;
* Construct a manager of IDatabase connection objects
*
* @param array $params Parameter map with keys:
- * - servers : Required. Array of server info structures.
- * - localDomain: A DatabaseDomain or domain ID string.
- * - loadMonitor : Name of a class used to fetch server lag and load.
+ * - servers : List of server info structures
+ * - localDomain: A DatabaseDomain or domain ID string
+ * - loadMonitor : Name of a class used to fetch server lag and load
* - readOnlyReason : Reason the master DB is read-only if so [optional]
* - waitTimeout : Maximum time to wait for replicas for consistency [optional]
* - maxLag: Try to avoid DB replicas with lag above this many seconds [optional]
* - srvCache : BagOStuff object for server cache [optional]
* - wanCache : WANObjectCache object [optional]
* - chronologyCallback: Callback to run before the first connection attempt [optional]
+ * - defaultGroup: Default query group; the generic group if not specified [optional]
* - hostname : The name of the current server [optional]
- * - cliMode: Whether the execution context is a CLI script. [optional]
- * - profiler : Class name or instance with profileIn()/profileOut() methods. [optional]
- * - trxProfiler: TransactionProfiler instance. [optional]
- * - replLogger: PSR-3 logger instance. [optional]
- * - connLogger: PSR-3 logger instance. [optional]
- * - queryLogger: PSR-3 logger instance. [optional]
- * - perfLogger: PSR-3 logger instance. [optional]
- * - errorLogger : Callback that takes an Exception and logs it. [optional]
- * - deprecationLogger: Callback to log a deprecation warning. [optional]
+ * - cliMode: Whether the execution context is a CLI script [optional]
+ * - profiler : Class name or instance with profileIn()/profileOut() methods [optional]
+ * - trxProfiler: TransactionProfiler instance [optional]
+ * - replLogger: PSR-3 logger instance [optional]
+ * - connLogger: PSR-3 logger instance [optional]
+ * - queryLogger: PSR-3 logger instance [optional]
+ * - perfLogger: PSR-3 logger instance [optional]
+ * - errorLogger : Callback that takes an Exception and logs it [optional]
+ * - deprecationLogger: Callback to log a deprecation warning [optional]
* - roundStage: STAGE_POSTCOMMIT_* class constant; for internal use [optional]
* - ownerId: integer ID of an LBFactory instance that manages this instance [optional]
* @throws InvalidArgumentException
* Subsequent calls with the same $group will not need to make new connection attempts
* since the acquired connection for each group is preserved.
*
- * @param string|bool $group Query group, or false for the generic group
- * @param string|bool $domain Domain ID, or false for the current domain
- * @throws DBError
+ * @param string|bool $group Query group or false for the generic group
+ * @param string|bool $domain DB domain ID or false for the local domain
+ * @throws DBError If no live handle can be obtained
* @return bool|int|string
*/
public function getReaderIndex( $group = false, $domain = false );
* Get any open connection to a given server index, local or foreign
*
* Use CONN_TRX_AUTOCOMMIT to only look for connections opened with that flag.
- * Avoid the use of begin() or startAtomic() on any such connections.
+ * Avoid the use of transaction methods like IDatabase::begin() or IDatabase::startAtomic()
+ * on any such connections.
*
* @param int $i Server index or DB_MASTER/DB_REPLICA
* @param int $flags Bitfield of CONN_* class constants
public function getAnyOpenConnection( $i, $flags = 0 );
/**
- * Get a connection handle by server index
- *
- * The CONN_TRX_AUTOCOMMIT flag is ignored for databases with ATTR_DB_LEVEL_LOCKING
- * (e.g. sqlite) in order to avoid deadlocks. ILoadBalancer::getServerAttributes()
- * can be used to check such flags beforehand.
- *
- * If the caller uses $domain or sets CONN_TRX_AUTOCOMMIT in $flags, then it must
- * also call ILoadBalancer::reuseConnection() on the handle when finished using it.
- * In all other cases, this is not necessary, though not harmful either.
- * Avoid the use of begin() or startAtomic() on any such connections.
+ * Get a live handle for a real or virtual (DB_MASTER/DB_REPLICA) server index
+ *
+ * The server index, $i, can be one of the following:
+ * - DB_REPLICA: a server index will be selected by the load balancer based on read
+ * weight, connectivity, and replication lag. Note that the master server might be
+ * configured with read weight. If $groups is empty then it means "the generic group",
+ * in which case all servers defined with read weight will be considered. Additional
+ * query groups can be configured, having their own list of server indexes and read
+ * weights. If a query group list is provided in $groups, then each recognized group
+ * will be tried, left-to-right, until server index selection succeeds or all groups
+ * have been tried, in which case the generic group will be tried.
+ * - DB_MASTER: the master server index will be used; the same as getWriterIndex().
+ * The value of $groups should be [] when using this server index.
+ * - Specific server index: a positive integer can be provided to use the server with
+ * that index. An error will be thrown in no such server index is recognized. This
+ * server selection method is usually only useful for internal load balancing logic.
+ * The value of $groups should be [] when using a specific server index.
+ *
+ * Handles acquired by this method, getConnectionRef(), getLazyConnectionRef(), and
+ * getMaintenanceConnectionRef() use the same set of shared connection pools. Callers that
+ * get a *local* DB domain handle for the same server will share one handle for all of those
+ * callers using CONN_TRX_AUTOCOMMIT (via $flags) and one handle for all of those callers not
+ * using CONN_TRX_AUTOCOMMIT. Callers that get a *foreign* DB domain handle (via $domain) will
+ * share any handle that has the right CONN_TRX_AUTOCOMMIT mode and is already on the right
+ * DB domain. Otherwise, one of the "free for reuse" handles will be claimed or a new handle
+ * will be made if there are none.
+ *
+ * Handle sharing is particularly useful when callers get local DB domain (the default),
+ * transaction round aware (the default), DB_MASTER handles. All such callers will operate
+ * within a single database transaction as a consequence. Handle sharing is also useful when
+ * callers get local DB domain (the default), transaction round aware (the default), samely
+ * query grouped (the default), DB_REPLICA handles. All such callers will operate within a
+ * single database transaction as a consequence.
+ *
+ * Calling functions that use $domain must call reuseConnection() once the last query of the
+ * function is executed. This lets the load balancer share this handle with other callers
+ * requesting connections on different database domains.
+ *
+ * Use CONN_TRX_AUTOCOMMIT to use a separate pool of only auto-commit handles. This flag
+ * is ignored for databases with ATTR_DB_LEVEL_LOCKING (e.g. sqlite) in order to avoid
+ * deadlocks. getServerAttributes() can be used to check such attributes beforehand. Avoid
+ * using IDatabase::begin() and IDatabase::commit() on such handles. If it is not possible
+ * to avoid using methods like IDatabase::startAtomic() and IDatabase::endAtomic(), callers
+ * should at least make sure that the atomic sections are closed on failure via try/catch
+ * and IDatabase::cancelAtomic().
+ *
+ * @see ILoadBalancer::reuseConnection()
+ * @see ILoadBalancer::getServerAttributes()
*
* @param int $i Server index (overrides $groups) or DB_MASTER/DB_REPLICA
- * @param array|string|bool $groups Query group(s), or false for the generic reader
- * @param string|bool $domain Domain ID, or false for the current domain
+ * @param string[]|string $groups Query group(s) or [] to use the default group
+ * @param string|bool $domain DB domain ID or false for the local domain
* @param int $flags Bitfield of CONN_* class constants
- *
- * @note This method throws DBAccessError if ILoadBalancer::disable() was called
- *
- * @throws DBError If any error occurs that prevents the yielding of a (live) IDatabase
- * @return IDatabase|bool This returns false on failure if CONN_SILENCE_ERRORS is set
+ * @return IDatabase|bool Live connection handle or false on failure
+ * @throws DBError If no live handle can be obtained and CONN_SILENCE_ERRORS is not set
+ * @throws DBAccessError If disable() was previously called
*/
public function getConnection( $i, $groups = [], $domain = false, $flags = 0 );
/**
- * Mark a foreign connection as being available for reuse under a different DB domain
+ * Mark a live handle as being available for reuse under a different database domain
+ *
+ * This mechanism is reference-counted, and must be called the same number of times as
+ * getConnection() to work. Never call this on handles acquired via getConnectionRef(),
+ * getLazyConnectionRef(), and getMaintenanceConnectionRef(), as they already manage
+ * the logic of calling this method when they fall out of scope in PHP.
*
- * This mechanism is reference-counted, and must be called the same number of times
- * as getConnection() to work.
+ * @see ILoadBalancer::getConnection()
*
* @param IDatabase $conn
- * @throws InvalidArgumentException
+ * @throws LogicException
*/
public function reuseConnection( IDatabase $conn );
/**
- * Get a database connection handle reference
- *
- * The handle's methods simply wrap those of a Database handle
+ * Get a live database handle reference for a real or virtual (DB_MASTER/DB_REPLICA) server index
*
* The CONN_TRX_AUTOCOMMIT flag is ignored for databases with ATTR_DB_LEVEL_LOCKING
- * (e.g. sqlite) in order to avoid deadlocks. ILoadBalancer::getServerAttributes()
+ * (e.g. sqlite) in order to avoid deadlocks. getServerAttributes()
* can be used to check such flags beforehand. Avoid the use of begin() or startAtomic()
* on any CONN_TRX_AUTOCOMMIT connections.
*
* @see ILoadBalancer::getConnection() for parameter information
*
* @param int $i Server index or DB_MASTER/DB_REPLICA
- * @param array|string|bool $groups Query group(s), or false for the generic reader
- * @param string|bool $domain Domain ID, or false for the current domain
+ * @param string[]|string $groups Query group(s) or [] to use the default group
+ * @param string|bool $domain DB domain ID or false for the local domain
* @param int $flags Bitfield of CONN_* class constants (e.g. CONN_TRX_AUTOCOMMIT)
* @return DBConnRef
*/
public function getConnectionRef( $i, $groups = [], $domain = false, $flags = 0 );
/**
- * Get a database connection handle reference without connecting yet
+ * Get a database handle reference for a real or virtual (DB_MASTER/DB_REPLICA) server index
*
- * The handle's methods simply wrap those of a Database handle
+ * The handle's methods simply proxy to those of an underlying IDatabase handle which
+ * takes care of the actual connection and query logic.
*
* The CONN_TRX_AUTOCOMMIT flag is ignored for databases with ATTR_DB_LEVEL_LOCKING
- * (e.g. sqlite) in order to avoid deadlocks. ILoadBalancer::getServerAttributes()
+ * (e.g. sqlite) in order to avoid deadlocks. getServerAttributes()
* can be used to check such flags beforehand. Avoid the use of begin() or startAtomic()
* on any CONN_TRX_AUTOCOMMIT connections.
*
* @see ILoadBalancer::getConnection() for parameter information
*
* @param int $i Server index or DB_MASTER/DB_REPLICA
- * @param array|string|bool $groups Query group(s), or false for the generic reader
- * @param string|bool $domain Domain ID, or false for the current domain
+ * @param string[]|string $groups Query group(s) or [] to use the default group
+ * @param string|bool $domain DB domain ID or false for the local domain
* @param int $flags Bitfield of CONN_* class constants (e.g. CONN_TRX_AUTOCOMMIT)
* @return DBConnRef
*/
public function getLazyConnectionRef( $i, $groups = [], $domain = false, $flags = 0 );
/**
- * Get a maintenance database connection handle reference for migrations and schema changes
+ * Get a live database handle for a real or virtual (DB_MASTER/DB_REPLICA) server index
+ * that can be used for data migrations and schema changes
*
- * The handle's methods simply wrap those of a Database handle
+ * The handle's methods simply proxy to those of an underlying IDatabase handle which
+ * takes care of the actual connection and query logic.
*
* The CONN_TRX_AUTOCOMMIT flag is ignored for databases with ATTR_DB_LEVEL_LOCKING
- * (e.g. sqlite) in order to avoid deadlocks. ILoadBalancer::getServerAttributes()
+ * (e.g. sqlite) in order to avoid deadlocks. getServerAttributes()
* can be used to check such flags beforehand. Avoid the use of begin() or startAtomic()
* on any CONN_TRX_AUTOCOMMIT connections.
*
* @see ILoadBalancer::getConnection() for parameter information
*
* @param int $i Server index or DB_MASTER/DB_REPLICA
- * @param array|string|bool $groups Query group(s), or false for the generic reader
- * @param string|bool $domain Domain ID, or false for the current domain
+ * @param string[]|string $groups Query group(s) or [] to use the default group
+ * @param string|bool $domain DB domain ID or false for the local domain
* @param int $flags Bitfield of CONN_* class constants (e.g. CONN_TRX_AUTOCOMMIT)
* @return MaintainableDBConnRef
*/
public function getServerName( $i );
/**
- * Return the server info structure for a given index, or false if the index is invalid.
+ * Return the server info structure for a given index or false if the index is invalid.
* @param int $i
* @return array|bool
* @since 1.31
/**
* @note This method will trigger a DB connection if not yet done
- * @param string|bool $domain Domain ID, or false for the current domain
+ * @param string|bool $domain DB domain ID or false for the local domain
* @return bool Whether the database for generic connections this request is highly "lagged"
*/
public function getLaggedReplicaMode( $domain = false );
/**
* @note This method may trigger a DB connection if not yet done
- * @param string|bool $domain Domain ID, or false for the current domain
+ * @param string|bool $domain DB domain ID or false for the local domain
* @param IDatabase|null $conn DB master connection; used to avoid loops [optional]
* @return string|bool Reason the master is read-only or false if it is not
*/
* May attempt to open connections to replica DBs on the default DB. If there is
* no lag, the maximum lag will be reported as -1.
*
- * @param bool|string $domain Domain ID, or false for the default database
+ * @param bool|string $domain Domain ID or false for the default database
* @return array ( host, max lag, index of max lagged host )
*/
public function getMaxLag( $domain = false );
use EmptyBagOStuff;
use WANObjectCache;
use ArrayUtils;
+use LogicException;
use UnexpectedValueException;
use InvalidArgumentException;
use RuntimeException;
private $loadMonitorConfig;
/** @var string Alternate local DB domain instead of DatabaseDomain::getId() */
private $localDomainIdAlias;
- /** @var int */
+ /** @var int Amount of replication lag, in seconds, that is considered "high" */
private $maxLag;
+ /** @var string|bool The query group list to be used by default */
+ private $defaultGroup;
/** @var string Current server name */
private $hostname;
/** @var array[] Map of (name => callable) */
private $trxRecurringCallbacks = [];
- /** @var Database DB connection object that caused a problem */
+ /** @var Database Connection handle that caused a problem */
private $errorConnection;
- /** @var int The generic (not query grouped) replica DB index */
+ /** @var int The generic (not query grouped) replica server index */
private $genericReadIndex = -1;
- /** @var int[] The group replica DB indexes keyed by group */
+ /** @var int[] The group replica server indexes keyed by group */
private $readIndexByGroup = [];
/** @var bool|DBMasterPos Replication sync position or false if not set */
private $waitForPos;
private $laggedReplicaMode = false;
/** @var bool Whether the generic reader fell back to a lagged replica DB */
private $allReplicasDownMode = false;
- /** @var string The last DB selection or connection error */
+ /** @var string The last DB domain selection or connection error */
private $lastError = 'Unknown error';
- /** @var string|bool Reason the LB is read-only or false if not */
+ /** @var string|bool Reason this instance is read-only or false if not */
private $readOnlyReason = false;
/** @var int Total number of new connections ever made with this instance */
private $connectionCounter = 0;
/** @var string Stage of the current transaction round in the transaction round life-cycle */
private $trxRoundStage = self::ROUND_CURSORY;
- /** @var string|null */
- private $defaultGroup = null;
-
/** @var int Warn when this many connection are held */
const CONN_HELD_WARN_THRESHOLD = 10;
}
}
- $this->defaultGroup = $params['defaultGroup'] ?? null;
+ $this->defaultGroup = $params['defaultGroup'] ?? self::GROUP_GENERIC;
$this->ownerId = $params['ownerId'] ?? null;
}
return (string)$domain;
}
+ /**
+ * @param string[]|string|bool $groups Query group list or false for the default
+ * @param int $i Specific server index or DB_MASTER/DB_REPLICA
+ * @return string[]|bool[] Query group list
+ */
+ private function resolveGroups( $groups, $i ) {
+ if ( $groups === false ) {
+ $resolvedGroups = [ $this->defaultGroup ];
+ } elseif ( is_string( $groups ) ) {
+ $resolvedGroups = [ $groups ];
+ } elseif ( is_array( $groups ) ) {
+ $resolvedGroups = $groups ?: [ $this->defaultGroup ];
+ } else {
+ throw new InvalidArgumentException( "Invalid query groups provided" );
+ }
+
+ if ( $groups && $i > 0 ) {
+ $groupList = implode( ', ', $groups );
+ throw new LogicException( "Got query groups ($groupList) with a server index (#$i)" );
+ }
+
+ return $resolvedGroups;
+ }
+
/**
* @param int $flags
* @return bool
}
/**
- * @param int $i
- * @param array $groups
+ * Get the server index to use for a specified server index and query group list
+ *
+ * @param int $i Specific server index or DB_MASTER/DB_REPLICA
+ * @param string[]|bool[] $groups Resolved query group list (non-empty)
* @param string|bool $domain
- * @return int The index of a specific server (replica DBs are checked for connectivity)
+ * @return int A specific server index (replica DBs are checked for connectivity)
*/
- private function getConnectionIndex( $i, $groups, $domain ) {
- // Check one "group" per default: the generic pool
- $defaultGroups = $this->defaultGroup ? [ $this->defaultGroup ] : [ false ];
-
- $groups = ( $groups === false || $groups === [] )
- ? $defaultGroups
- : (array)$groups;
-
+ private function getConnectionIndex( $i, array $groups, $domain ) {
if ( $i === self::DB_MASTER ) {
$i = $this->getWriterIndex();
} elseif ( $i === self::DB_REPLICA ) {
- # Try to find an available server in any the query groups (in order)
+ // Find an available server in any of the query groups (in order)
foreach ( $groups as $group ) {
$groupIndex = $this->getReaderIndex( $group, $domain );
if ( $groupIndex !== false ) {
- $i = $groupIndex;
+ $i = $groupIndex; // group connection succeeded
break;
}
}
+ } elseif ( !isset( $this->servers[$i] ) ) {
+ throw new UnexpectedValueException( "Invalid server index index #$i" );
}
- # Operation-based index
if ( $i === self::DB_REPLICA ) {
- $this->lastError = 'Unknown error'; // reset error string
- # Try the general server pool if $groups are unavailable.
- $i = ( $groups === [ false ] )
- ? false // don't bother with this if that is what was tried above
- : $this->getReaderIndex( false, $domain );
- # Couldn't find a working server in getReaderIndex()?
+ // No specific server was yet found
+ $this->lastError = 'Unknown error'; // set here in case of worse failure
+ // Either make one last connection attempt or give up
+ $i = in_array( $this->defaultGroup, $groups, true )
+ // Connection attempt already included the default query group; give up
+ ? false
+ // Connection attempt was for other query groups; try the default one
+ : $this->getReaderIndex( $this->defaultGroup, $domain );
+
if ( $i === false ) {
+ // Still coundn't find a working non-zero read load server
$this->lastError = 'No working replica DB server: ' . $this->lastError;
- // Throw an exception
$this->reportConnectionError();
return null; // unreachable due to exception
}
return $index;
}
- if ( $group !== false ) {
+ if ( $group !== self::GROUP_GENERIC ) {
// Use the server weight array for this load group
if ( isset( $this->groupLoads[$group] ) ) {
$loads = $this->groupLoads[$group];
// Cache the reader index for future DB_REPLICA handles
$this->setExistingReaderIndex( $group, $i );
// Record whether the generic reader index is in "lagged replica DB" mode
- if ( $group === false && $laggedReplicaMode ) {
+ if ( $group === self::GROUP_GENERIC && $laggedReplicaMode ) {
$this->laggedReplicaMode = true;
}
* @return int Server index or -1 if none was chosen
*/
protected function getExistingReaderIndex( $group ) {
- if ( $group === false ) {
+ if ( $group === self::GROUP_GENERIC ) {
$index = $this->genericReadIndex;
} else {
$index = $this->readIndexByGroup[$group] ?? -1;
throw new UnexpectedValueException( "Cannot set a negative read server index" );
}
- if ( $group === false ) {
+ if ( $group === self::GROUP_GENERIC ) {
$this->genericReadIndex = $index;
} else {
$this->readIndexByGroup[$group] = $index;
$i = ( $i === self::DB_MASTER ) ? $this->getWriterIndex() : $i;
$autocommit = ( ( $flags & self::CONN_TRX_AUTOCOMMIT ) == self::CONN_TRX_AUTOCOMMIT );
+ $conn = false;
foreach ( $this->conns as $connsByServer ) {
+ // Get the connection array server indexes to inspect
if ( $i === self::DB_REPLICA ) {
$indexes = array_keys( $connsByServer );
} else {
}
foreach ( $indexes as $index ) {
- foreach ( $connsByServer[$index] as $conn ) {
- if ( !$conn->isOpen() ) {
- continue; // some sort of error occured?
- }
- if ( !$autocommit || $conn->getLBInfo( 'autoCommitOnly' ) ) {
- return $conn;
- }
+ $conn = $this->pickAnyOpenConnection( $connsByServer[$index], $autocommit );
+ if ( $conn ) {
+ break;
}
}
}
- return false;
+ if ( $conn ) {
+ $this->enforceConnectionFlags( $conn, $flags );
+ }
+
+ return $conn;
+ }
+
+ /**
+ * @param IDatabase[] $candidateConns
+ * @param bool $autocommit Whether to only look for auto-commit connections
+ * @return IDatabase|false An appropriate open connection or false if none found
+ */
+ private function pickAnyOpenConnection( $candidateConns, $autocommit ) {
+ $conn = false;
+
+ foreach ( $candidateConns as $candidateConn ) {
+ if ( !$candidateConn->isOpen() ) {
+ continue; // some sort of error occured?
+ } elseif (
+ $autocommit &&
+ (
+ // Connection is transaction round aware
+ !$candidateConn->getLBInfo( 'autoCommitOnly' ) ||
+ // Some sort of error left a transaction open?
+ $candidateConn->trxLevel()
+ )
+ ) {
+ continue; // some sort of error left a transaction open?
+ }
+
+ $conn = $candidateConn;
+ }
+
+ return $conn;
}
/**
}
public function getConnection( $i, $groups = [], $domain = false, $flags = 0 ) {
- if ( !is_int( $i ) ) {
- throw new InvalidArgumentException( "Cannot connect without a server index" );
- } elseif ( $groups && $i > 0 ) {
- throw new InvalidArgumentException( "Got query groups with server index #$i" );
- }
-
+ $groups = $this->resolveGroups( $groups, $i );
$domain = $this->resolveDomainID( $domain );
$flags = $this->sanitizeConnectionFlags( $flags );
$masterOnly = ( $i === self::DB_MASTER || $i === $this->getWriterIndex() );
// Database instance to this method. Any caller passing in a DBConnRef is broken.
$this->connLogger->error(
__METHOD__ . ": got DBConnRef instance.\n" .
- ( new RuntimeException() )->getTraceAsString() );
+ ( new LogicException() )->getTraceAsString() );
return;
}
* Test if the specified index represents an open connection
*
* @param int $index Server index
- * @private
* @return bool
*/
private function isOpen( $index ) {
- if ( !is_int( $index ) ) {
- return false;
- }
-
return (bool)$this->getAnyOpenConnection( $index );
}
* @ingroup Media
*/
class ImagePage extends Article {
- /** @var File */
+ /** @var File|false */
private $displayImg;
/** @var FileRepo */
}
/**
- * @param string $target
+ * @param string|string[] $target
* @param int $limit
* @return ResultWrapper
*/
* @ingroup Media
*/
class WikiFilePage extends WikiPage {
- /** @var File */
+ /** @var File|false */
protected $mFile = false;
- /** @var LocalRepo */
+ /** @var LocalRepo|null */
protected $mRepo = null;
/** @var bool */
protected $mFileLoaded = false;
- /** @var array */
+ /** @var array|null */
protected $mDupes = null;
public function __construct( $title ) {
if ( $this->mTimestamp !== '' ) {
$this->showRevision( $this->mTimestamp );
} elseif ( $this->mFilename !== null && $this->mTargetObj->inNamespace( NS_FILE ) ) {
- $file = new ArchivedFile( $this->mTargetObj, '', $this->mFilename );
+ $file = new ArchivedFile( $this->mTargetObj, 0, $this->mFilename );
// Check if user is allowed to see this file
if ( !$file->exists() ) {
$out->addWikiMsg( 'filedelete-nofile', $this->mFilename );
$out = $this->getOutput();
$lang = $this->getLanguage();
$user = $this->getUser();
- $file = new ArchivedFile( $this->mTargetObj, '', $this->mFilename );
+ $file = new ArchivedFile( $this->mTargetObj, 0, $this->mFilename );
$out->addWikiMsg( 'undelete-show-file-confirm',
$this->mTargetObj->getText(),
$lang->userDate( $file->getTimestamp(), $user ),
$( '.dbRadio' ).on( 'click', function () {
var $checked = $( '.dbRadio:checked' ),
$wrapper = $( document.getElementById( $checked.attr( 'rel' ) ) );
+ // eslint-disable-next-line no-jquery/no-sizzle
if ( $wrapper.is( ':hidden' ) ) {
// FIXME: Use CSS transition
// eslint-disable-next-line no-jquery/no-animate-toggle
}
},
"eslint-config-wikimedia": {
- "version": "0.12.0",
- "resolved": "https://registry.npmjs.org/eslint-config-wikimedia/-/eslint-config-wikimedia-0.12.0.tgz",
- "integrity": "sha512-ZkmGLvwmoEacj55t8Z6VH6wUu4/XTlgkSCerHkj+VU4tmyCD4mlzvTeSaPzOEDmZTVWUoiKnB6mvUx06l7uIbw==",
+ "version": "0.13.0",
+ "resolved": "https://registry.npmjs.org/eslint-config-wikimedia/-/eslint-config-wikimedia-0.13.0.tgz",
+ "integrity": "sha512-l64xMCgPE949H9rfhC0Ir+UaNAh685CE6xSnWkMU5yNryNTdL91lW8KcMFgMqmmH0Q+Jq+7DIdfGaVld6nQ80w==",
"dev": true,
"requires": {
"eslint": "^5.16.0",
"eslint-plugin-json": "^1.4.0",
- "eslint-plugin-no-jquery": "^2.0.0",
+ "eslint-plugin-no-jquery": "^2.1.0",
"eslint-plugin-qunit": "^4.0.0"
},
"dependencies": {
}
},
"eslint-plugin-no-jquery": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/eslint-plugin-no-jquery/-/eslint-plugin-no-jquery-2.0.0.tgz",
- "integrity": "sha512-aFy3fMBlc630/qeasjocb9uIqmwoyOmmTQiBaDs70Aryqi9uPH0EZLPtIOshDMcGeAkyyAkcc+WuIw6bRsoLuw==",
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-no-jquery/-/eslint-plugin-no-jquery-2.1.0.tgz",
+ "integrity": "sha512-5sr5tOJRfuRviyAvFTe/mr80TXWxTteD/JHRuJtDN8q/bxAh16eSKoKLAevLC7wZCRN2iwnEfhQPQV4rp/gYtg==",
"dev": true
},
"eslint-plugin-qunit": {
"dev": true
},
"vscode-json-languageservice": {
- "version": "3.2.1",
- "resolved": "https://registry.npmjs.org/vscode-json-languageservice/-/vscode-json-languageservice-3.2.1.tgz",
- "integrity": "sha512-ee9MJ70/xR55ywvm0bZsDLhA800HCRE27AYgMNTU14RSg20Y+ngHdQnUt6OmiTXrQDI/7sne6QUOtHIN0hPQYA==",
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/vscode-json-languageservice/-/vscode-json-languageservice-3.3.0.tgz",
+ "integrity": "sha512-upq1PhwDItazdtRJ/R7uU0Fgrf9iaYa1xLK4WFLExR0DgbPojd0YgMpfyknVyXGlxsg3fJQ0H7J++QeByXHh9w==",
"dev": true,
"requires": {
- "jsonc-parser": "^2.0.2",
- "vscode-languageserver-types": "^3.13.0",
- "vscode-nls": "^4.0.0",
- "vscode-uri": "^1.0.6"
+ "jsonc-parser": "^2.1.0",
+ "vscode-languageserver-types": "^3.15.0-next.2",
+ "vscode-nls": "^4.1.1",
+ "vscode-uri": "^2.0.1"
}
},
"vscode-languageserver-types": {
- "version": "3.14.0",
- "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.14.0.tgz",
- "integrity": "sha512-lTmS6AlAlMHOvPQemVwo3CezxBp0sNB95KNPkqp3Nxd5VFEnuG1ByM0zlRWos0zjO3ZWtkvhal0COgiV1xIA4A==",
+ "version": "3.15.0-next.2",
+ "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.15.0-next.2.tgz",
+ "integrity": "sha512-2JkrMWWUi2rlVLSo9OFR2PIGUzdiowEM8NgNYiwLKnXTjpwpjjIrJbNNxDik7Rv4oo9KtikcFQZKXbrKilL/MQ==",
"dev": true
},
"vscode-nls": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/vscode-nls/-/vscode-nls-4.1.0.tgz",
- "integrity": "sha512-zKsFWVzL1wlCezgaI3XiN42IT8DIPM1Qr+G+RBhiU3U0bJCdC8pPELakRCtuVT4wF3gBZjBrUDQ8mowL7hmgwA==",
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/vscode-nls/-/vscode-nls-4.1.1.tgz",
+ "integrity": "sha512-4R+2UoUUU/LdnMnFjePxfLqNhBS8lrAFyX7pjb2ud/lqDkrUavFUTcG7wR0HBZFakae0Q6KLBFjMS6W93F403A==",
"dev": true
},
"vscode-uri": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-1.0.6.tgz",
- "integrity": "sha512-sLI2L0uGov3wKVb9EB+vIQBl9tVP90nqRvxSoJ35vI3NjxE8jfsE5DSOhWgSunHSZmKS4OCi2jrtfxK7uyp2ww==",
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-2.0.2.tgz",
+ "integrity": "sha512-VebpIxm9tG0fG2sBOhnsSPzDYuNUPP1UQW4K3mwthlca4e4f3d6HKq3HkITC2OPFomOaB7pHTSjcpdFWjfYTzg==",
"dev": true
},
"wdio-dot-reporter": {
"selenium-test": "wdio ./tests/selenium/wdio.conf.js"
},
"devDependencies": {
- "eslint-config-wikimedia": "0.12.0",
+ "eslint-config-wikimedia": "0.13.0",
"grunt": "1.0.4",
"grunt-banana-checker": "0.7.0",
"grunt-contrib-copy": "1.0.0",
// eslint-disable-next-line no-jquery/no-map-util
return $.map( node.childNodes, function ( elem ) {
if ( elem.nodeType === Node.ELEMENT_NODE ) {
+ // eslint-disable-next-line no-jquery/no-class-state
if ( $( elem ).hasClass( 'reference' ) ) {
return null;
}
while ( i < l ) {
// if this is a child row, continue to the next row (as buildCache())
+ // eslint-disable-next-line no-jquery/no-class-state
if ( rows[ rowIndex ] && !$( rows[ rowIndex ] ).hasClass( config.cssChildRow ) ) {
if ( rowIndex !== lastRowIndex ) {
lastRowIndex = rowIndex;
// if this is a child row, add it to the last row's children and
// continue to the next row
+ // eslint-disable-next-line no-jquery/no-class-state
if ( $row.hasClass( config.cssChildRow ) ) {
cache.row[ cache.row.length - 1 ] = cache.row[ cache.row.length - 1 ].add( $row );
// go to the next for loop
$cell = $( this );
columns = [];
+ // eslint-disable-next-line no-jquery/no-class-state
if ( !$cell.hasClass( config.unsortableClass ) ) {
$cell
.addClass( config.cssHeader )
$row = $rows.eq( i );
// if this is a child row, continue to the next row (as buildCache())
+ // eslint-disable-next-line no-jquery/no-class-state
if ( $row.hasClass( config.cssChildRow ) ) {
// go to the next for loop
continue;
sideMargin = 'marginLeft';
}
+ // eslint-disable-next-line no-jquery/no-class-state
if ( $element.hasClass( 'jquery-confirmable-element' ) ) {
$wrapper = $element.closest( '.jquery-confirmable-wrapper' );
$interface = $wrapper.find( '.jquery-confirmable-interface' );
if ( options.wasCollapsed !== undefined ) {
wasCollapsed = options.wasCollapsed;
} else {
+ // eslint-disable-next-line no-jquery/no-class-state
wasCollapsed = $collapsible.hasClass( 'mw-collapsed' );
}
} );
// Initial state
+ // eslint-disable-next-line no-jquery/no-class-state
if ( options.collapsed || $collapsible.hasClass( 'mw-collapsed' ) ) {
// One toggler can hook to multiple elements, and one element can have
// multiple togglers. This is the sanest way to handle that.
context.data.prevText = '';
} else if (
val !== context.data.prevText ||
+ // eslint-disable-next-line no-jquery/no-sizzle
!context.data.$container.is( ':visible' )
) {
context.data.prevText = val;
*/
function keypress( e, context, key ) {
var selected,
+ // eslint-disable-next-line no-jquery/no-sizzle
wasVisible = context.data.$container.is( ':visible' ),
preventDefault = false;
$copyAction = $copyForm.find( '> [name="action"]' );
// Remove action=historysubmit and ids[..]=..
+ // eslint-disable-next-line no-jquery/no-class-state
if ( $historySubmitter.hasClass( 'mw-history-compareselectedversions-button' ) ) {
$copyAction.remove();
$copyForm.find( 'input[name^="ids["]:checked' ).prop( 'checked', false );
// Remove diff=&oldid=, change action=historysubmit to revisiondelete, remove revisiondelete
- } else if ( $historySubmitter.hasClass( 'mw-history-revisiondelete-button' ) ||
- $historySubmitter.hasClass( 'mw-history-editchangetags-button' ) ) {
+ } else if (
+ // eslint-disable-next-line no-jquery/no-class-state
+ $historySubmitter.hasClass( 'mw-history-revisiondelete-button' ) ||
+ // eslint-disable-next-line no-jquery/no-class-state
+ $historySubmitter.hasClass( 'mw-history-editchangetags-button' )
+ ) {
$copyRadios.remove();
$copyAction.val( $historySubmitter.attr( 'name' ) );
+ // eslint-disable-next-line no-jquery/no-sizzle
$copyForm.find( ':submit' ).remove();
}
e.type === 'click' ||
e.type === 'keypress' && e.which === 13
) {
+ // eslint-disable-next-line no-jquery/no-class-state
if ( $table.hasClass( 'collapsed' ) ) {
// From collapsed to expanded. Button will now collapse.
$( this ).text( collapseText );
// From expanded to collapsed. Button will now expand.
$( this ).text( expandText );
}
+ // eslint-disable-next-line no-jquery/no-class-state
$table.toggleClass( 'collapsed' );
}
} );
};
if (
$oldErrorBox !== $errorBox &&
+ // eslint-disable-next-line no-jquery/no-class-state
( $oldErrorBox.hasClass( 'error' ) || $oldErrorBox.hasClass( 'warning' ) )
) {
// eslint-disable-next-line no-jquery/no-slide
var $element = $( this ),
deleteButton;
+ // eslint-disable-next-line no-jquery/no-class-state
if ( $element.hasClass( 'oo-ui-widget' ) ) {
deleteButton = OO.ui.infuse( $element );
deleteButton.on( 'click', function () {
deleteButton.$element.closest( 'li.mw-htmlform-cloner-li' ).remove();
} );
} else {
+ // eslint-disable-next-line no-jquery/no-sizzle
$element.filter( ':input' ).on( 'click', function ( e ) {
e.preventDefault();
$( this ).closest( 'li.mw-htmlform-cloner-li' ).remove();
}
} );
+ // eslint-disable-next-line no-jquery/no-class-state
if ( $createElement.hasClass( 'oo-ui-widget' ) ) {
createButton = OO.ui.infuse( $createElement );
createButton.on( 'click', function () {
appendToCloner( createButton.$element );
} );
} else {
+ // eslint-disable-next-line no-jquery/no-sizzle
$createElement.filter( ':input' ).on( 'click', function ( e ) {
e.preventDefault();
$link = $( this );
+ // eslint-disable-next-line no-jquery/no-class-state
if ( $link.hasClass( 'loading' ) ) {
return;
}
// Collect the relevant classes from the first nested child
firstChildClasses = activeHighlightClasses.filter( function ( className ) {
+ // eslint-disable-next-line no-jquery/no-class-state
return $table.find( 'tr:nth-child(2)' ).hasClass( className );
} );
// Filter the non-head rows and see if they all have the same classes
$this = $( this );
classesInThisRow = activeHighlightClasses.filter( function ( className ) {
+ // eslint-disable-next-line no-jquery/no-class-state
return $this.hasClass( className );
} );
FilterTagMultiselectWidget.prototype.emphasize = function () {
if (
+ // eslint-disable-next-line no-jquery/no-class-state
!this.$handle.hasClass( 'mw-rcfilters-ui-filterTagMultiselectWidget-animate' )
) {
this.$handle
.text( query );
}
+ // eslint-disable-next-line no-jquery/no-class-state
if ( $el.parent().hasClass( 'mw-searchSuggest-link' ) ) {
$el.parent().attr( 'href', formData.baseHref + $.param( formData.linkParams ) + '&fulltext=1' );
} else {
function updateImportSubprojectList() {
var $projectField = $( '#mw-import-table-interwiki #interwiki' ),
$subprojectField = $projectField.parent().find( '#subproject' ),
+ // eslint-disable-next-line no-jquery/no-sizzle
$selected = $projectField.find( ':selected' ),
oldValue = $subprojectField.val(),
option, options;
// (This function could be changed to infuse and check OOUI widgets, but that would only make it
// slower and more complicated. It works fine to treat them as HTML elements.)
function isPrefsChanged() {
+ // eslint-disable-next-line no-jquery/no-sizzle
var inputs = $( '#mw-prefs-form :input[name]' ),
input, $input, inputType,
index, optIndex,
mw.loader.load( 'mediawiki.notification' );
// Use the class to determine whether to watch or unwatch
+ // eslint-disable-next-line no-jquery/no-class-state
if ( !$subjectLink.hasClass( 'mw-watched-item' ) ) {
$link.text( mw.msg( 'watching' ) );
promise = api.watch( title ).done( function () {
// Depending on whether we are watching or unwatching, for each entry of the page (and its associated page i.e. Talk),
// change the text, tooltip, and non-JS href of the (un)watch button, and update the styling of the watchlist entry.
+ // eslint-disable-next-line no-jquery/no-class-state
if ( $unwatchLink.hasClass( 'mw-unwatch-link' ) ) {
api.unwatch( pageTitle )
.done( function () {
// Hide/show the table of contents element
function toggleToc() {
+ // eslint-disable-next-line no-jquery/no-class-state
if ( $this.hasClass( 'tochidden' ) ) {
// FIXME: Use CSS transitions
// eslint-disable-next-line no-jquery/no-slide
$portlet.removeClass( 'emptyPortlet' );
// Setup the list item (and a span if $portlet is a Vector tab)
+ // eslint-disable-next-line no-jquery/no-class-state
if ( $portlet.hasClass( 'vectorTabs' ) ) {
item = $( '<li>' ).append( $( '<span>' ).append( link )[ 0 ] )[ 0 ];
} else {
mw.widgets.MediaSearchWidget.prototype.runLayoutQueue = function () {
var i, len;
+ // eslint-disable-next-line no-jquery/no-sizzle
if ( this.$element.is( ':visible' ) ) {
for ( i = 0, len = this.layoutQueue.length; i < len; i++ ) {
this.layoutQueue.pop()();
if (
!this.isDisabled() &&
e.which === 1 &&
+ // eslint-disable-next-line no-jquery/no-class-state
$( e.target ).hasClass( targetClass )
) {
this.deactivate( true );
] );
$this->user->mBlock->mTimestamp = 0;
$this->assertEquals( [ [ 'autoblockedtext',
- '[[User:Useruser|Useruser]]', 'no reason given', '127.0.0.1',
- 'Useruser', null, 'infinite', '127.0.8.1',
+ "[[User:Useruser|\u{202A}Useruser\u{202C}]]", 'no reason given', '127.0.0.1',
+ "\u{202A}Useruser\u{202C}", null, 'infinite', '127.0.8.1',
$wgLang->timeanddate( wfTimestamp( TS_MW, $prev ), true ) ] ],
MediaWikiServices::getInstance()->getPermissionManager()->getPermissionErrors(
'move-target', $this->user, $this->title ) );
'expiry' => 10,
] );
$this->assertEquals( [ [ 'blockedtext',
- '[[User:Useruser|Useruser]]', 'no reason given', '127.0.0.1',
- 'Useruser', null, '23:00, 31 December 1969', '127.0.8.1',
+ "[[User:Useruser|\u{202A}Useruser\u{202C}]]", 'no reason given', '127.0.0.1',
+ "\u{202A}Useruser\u{202C}", null, '23:00, 31 December 1969', '127.0.8.1',
$wgLang->timeanddate( wfTimestamp( TS_MW, $now ), true ) ] ],
MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'move-target', $this->user, $this->title ) );
] );
$errors = [ [ 'systemblockedtext',
- '[[User:Useruser|Useruser]]', 'no reason given', '127.0.0.1',
- 'Useruser', 'test', 'infinite', '127.0.8.1',
+ "[[User:Useruser|\u{202A}Useruser\u{202C}]]", 'no reason given', '127.0.0.1',
+ "\u{202A}Useruser\u{202C}", 'test', 'infinite', '127.0.8.1',
$wgLang->timeanddate( wfTimestamp( TS_MW, $now ), true ) ] ];
$this->assertEquals( $errors,
] );
$errors = [ [ 'blockedtext-partial',
- '[[User:Useruser|Useruser]]', 'no reason given', '127.0.0.1',
- 'Useruser', null, '23:00, 31 December 1969', '127.0.8.1',
+ "[[User:Useruser|\u{202A}Useruser\u{202C}]]", 'no reason given', '127.0.0.1',
+ "\u{202A}Useruser\u{202C}", null, '23:00, 31 December 1969', '127.0.8.1',
$wgLang->timeanddate( wfTimestamp( TS_MW, $now ), true ) ] ];
$this->assertEquals( $errors,
] );
$errors = [ [ 'blockedtext',
- '[[User:Useruser|Useruser]]', 'no reason given', '127.0.0.1',
- 'Useruser', null, 'infinite', '127.0.8.1',
+ "[[User:Useruser|\u{202A}Useruser\u{202C}]]", 'no reason given', '127.0.0.1',
+ "\u{202A}Useruser\u{202C}", null, 'infinite', '127.0.8.1',
$wgLang->timeanddate( wfTimestamp( TS_MW, $now ), true ) ] ];
$this->assertEquals( $errors,
] );
$this->user->mBlock->setTimestamp( 0 );
$this->assertEquals( [ [ 'autoblockedtext',
- '[[User:Useruser|Useruser]]', 'no reason given', '127.0.0.1',
- 'Useruser', null, 'infinite', '127.0.8.1',
+ "[[User:Useruser|\u{202A}Useruser\u{202C}]]", 'no reason given', '127.0.0.1',
+ "\u{202A}Useruser\u{202C}", null, 'infinite', '127.0.8.1',
$wgLang->timeanddate( wfTimestamp( TS_MW, $prev ), true ) ] ],
$this->title->getUserPermissionsErrors( 'move-target',
$this->user ) );
'expiry' => 10,
] );
$this->assertEquals( [ [ 'blockedtext',
- '[[User:Useruser|Useruser]]', 'no reason given', '127.0.0.1',
- 'Useruser', null, '23:00, 31 December 1969', '127.0.8.1',
+ "[[User:Useruser|\u{202A}Useruser\u{202C}]]", 'no reason given', '127.0.0.1',
+ "\u{202A}Useruser\u{202C}", null, '23:00, 31 December 1969', '127.0.8.1',
$wgLang->timeanddate( wfTimestamp( TS_MW, $now ), true ) ] ],
$this->title->getUserPermissionsErrors( 'move-target', $this->user ) );
# $action != 'read' && $action != 'createaccount' && $user->isBlockedFrom( $this )
] );
$errors = [ [ 'systemblockedtext',
- '[[User:Useruser|Useruser]]', 'no reason given', '127.0.0.1',
- 'Useruser', 'test', 'infinite', '127.0.8.1',
+ "[[User:Useruser|\u{202A}Useruser\u{202C}]]", 'no reason given', '127.0.0.1',
+ "\u{202A}Useruser\u{202C}", 'test', 'infinite', '127.0.8.1',
$wgLang->timeanddate( wfTimestamp( TS_MW, $now ), true ) ] ];
$this->assertEquals( $errors,
] );
$errors = [ [ 'blockedtext-partial',
- '[[User:Useruser|Useruser]]', 'no reason given', '127.0.0.1',
- 'Useruser', null, '23:00, 31 December 1969', '127.0.8.1',
+ "[[User:Useruser|\u{202A}Useruser\u{202C}]]", 'no reason given', '127.0.0.1',
+ "\u{202A}Useruser\u{202C}", null, '23:00, 31 December 1969', '127.0.8.1',
$wgLang->timeanddate( wfTimestamp( TS_MW, $now ), true ) ] ];
$this->assertEquals( $errors,
] );
$errors = [ [ 'blockedtext',
- '[[User:Useruser|Useruser]]', 'no reason given', '127.0.0.1',
- 'Useruser', null, 'infinite', '127.0.8.1',
+ "[[User:Useruser|\u{202A}Useruser\u{202C}]]", 'no reason given', '127.0.0.1',
+ "\u{202A}Useruser\u{202C}", null, 'infinite', '127.0.8.1',
$wgLang->timeanddate( wfTimestamp( TS_MW, $now ), true ) ] ];
$this->assertEquals( $errors,
global $wgDBserver;
// Simulate web request with DBO_TRX
- $lb = $this->newMultiServerLocalLoadBalancer( DBO_TRX );
+ $lb = $this->newMultiServerLocalLoadBalancer( [], [ 'flags' => DBO_TRX ] );
$this->assertEquals( 8, $lb->getServerCount() );
$this->assertTrue( $lb->hasReplicaServers() );
] );
}
- private function newMultiServerLocalLoadBalancer( $flags = DBO_DEFAULT ) {
+ private function newMultiServerLocalLoadBalancer( $lbExtra = [], $srvExtra = [] ) {
global $wgDBserver, $wgDBname, $wgDBuser, $wgDBpassword, $wgDBtype, $wgSQLiteDataDir;
$servers = [
// Master DB
- 0 => [
+ 0 => $srvExtra + [
'host' => $wgDBserver,
'dbname' => $wgDBname,
'tablePrefix' => $this->dbPrefix(),
'type' => $wgDBtype,
'dbDirectory' => $wgSQLiteDataDir,
'load' => 0,
- 'flags' => $flags
],
// Main replica DBs
- 1 => [
+ 1 => $srvExtra + [
'host' => $wgDBserver,
'dbname' => $wgDBname,
'tablePrefix' => $this->dbPrefix(),
'type' => $wgDBtype,
'dbDirectory' => $wgSQLiteDataDir,
'load' => 100,
- 'flags' => $flags
],
- 2 => [
+ 2 => $srvExtra + [
'host' => $wgDBserver,
'dbname' => $wgDBname,
'tablePrefix' => $this->dbPrefix(),
'type' => $wgDBtype,
'dbDirectory' => $wgSQLiteDataDir,
'load' => 100,
- 'flags' => $flags
],
// RC replica DBs
- 3 => [
+ 3 => $srvExtra + [
'host' => $wgDBserver,
'dbname' => $wgDBname,
'tablePrefix' => $this->dbPrefix(),
'recentchanges' => 100,
'watchlist' => 100
],
- 'flags' => $flags
],
// Logging replica DBs
- 4 => [
+ 4 => $srvExtra + [
'host' => $wgDBserver,
'dbname' => $wgDBname,
'tablePrefix' => $this->dbPrefix(),
'groupLoads' => [
'logging' => 100
],
- 'flags' => $flags
],
- 5 => [
+ 5 => $srvExtra + [
'host' => $wgDBserver,
'dbname' => $wgDBname,
'tablePrefix' => $this->dbPrefix(),
'groupLoads' => [
'logging' => 100
],
- 'flags' => $flags
],
// Maintenance query replica DBs
- 6 => [
+ 6 => $srvExtra + [
'host' => $wgDBserver,
'dbname' => $wgDBname,
'tablePrefix' => $this->dbPrefix(),
'groupLoads' => [
'vslow' => 100
],
- 'flags' => $flags
],
// Replica DB that only has a copy of some static tables
- 7 => [
+ 7 => $srvExtra + [
'host' => $wgDBserver,
'dbname' => $wgDBname,
'tablePrefix' => $this->dbPrefix(),
'groupLoads' => [
'archive' => 100
],
- 'flags' => $flags,
'is static' => true
]
];
- return new LoadBalancer( [
+ return new LoadBalancer( $lbExtra + [
'servers' => $servers,
'localDomain' => new DatabaseDomain( $wgDBname, null, $this->dbPrefix() ),
'queryLogger' => MediaWiki\Logger\LoggerFactory::getInstance( 'DBQuery' ),
}
public function testQueryGroupIndex() {
- $lb = $this->newMultiServerLocalLoadBalancer();
+ $lb = $this->newMultiServerLocalLoadBalancer( [ 'defaultGroup' => false ] );
/** @var LoadBalancer $lbWrapper */
$lbWrapper = TestingAccessWrapper::newFromObject( $lb );
$lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
$lb = $lbFactory->newMainLB();
- $db = $lb->getConnection( DB_REPLICA, DBO_TRX );
+ $db = $lb->getConnection( DB_REPLICA );
// sanity
$this->assertNotSame( $this->db, $db );
'<div>' + loremIpsum + '</div>'
);
+ // eslint-disable-next-line no-jquery/no-class-state
assert.assertTrue( $collapsible.hasClass( 'mw-collapsible' ), 'mw-collapsible class present' );
} );
{ collapsed: true }
);
+ // eslint-disable-next-line no-jquery/no-class-state
assert.assertTrue( $collapsible.hasClass( 'mw-collapsed' ), 'mw-collapsed class present' );
} );
.appendTo( '#qunit-fixture' ).makeCollapsible();
$collapsible1.on( 'afterCollapse.mw-collapsible', function () {
+ // eslint-disable-next-line no-jquery/no-class-state
assert.assertTrue( $collapsible1.hasClass( 'mw-collapsed' ), 'after collapsing: parent is collapsed' );
+ // eslint-disable-next-line no-jquery/no-class-state
assert.assertFalse( $collapsible2.hasClass( 'mw-collapsed' ), 'after collapsing: child is not collapsed' );
+ // eslint-disable-next-line no-jquery/no-class-state
assert.assertTrue( $collapsible1.find( '> .mw-collapsible-toggle' ).hasClass( 'mw-collapsible-toggle-collapsed' ) );
+ // eslint-disable-next-line no-jquery/no-class-state
assert.assertFalse( $collapsible2.find( '> .mw-collapsible-toggle' ).hasClass( 'mw-collapsible-toggle-collapsed' ) );
} ).find( '> .mw-collapsible-toggle a' ).trigger( 'click' );
} );
$table.find( 'tr > th' ).eq( 1 ).trigger( 'click' );
assert.strictEqual(
+ // eslint-disable-next-line no-jquery/no-class-state
$cell.hasClass( 'headerSortUp' ) || $cell.hasClass( 'headerSortDown' ),
false,
'after sort: no class headerSortUp or headerSortDown'
mw.config.set( 'wgPageContentLanguage', 'sv' );
$table.tablesorter();
+ // eslint-disable-next-line no-jquery/no-sizzle
$table.find( '.headerSort:eq(0)' ).trigger( 'click' );
}
);
// TODO abstract the double strictEquals
// At first checkboxes are hidden
+ // eslint-disable-next-line no-jquery/no-class-state
assert.strictEqual( $( '#nsinvert' ).closest( '.mw-input-with-label' ).hasClass( 'mw-input-hidden' ), true );
+ // eslint-disable-next-line no-jquery/no-class-state
assert.strictEqual( $( '#nsassociated' ).closest( '.mw-input-with-label' ).hasClass( 'mw-input-hidden' ), true );
// Initiate the recentchanges module
rc.init();
// By default
+ // eslint-disable-next-line no-jquery/no-class-state
assert.strictEqual( $( '#nsinvert' ).closest( '.mw-input-with-label' ).hasClass( 'mw-input-hidden' ), true );
+ // eslint-disable-next-line no-jquery/no-class-state
assert.strictEqual( $( '#nsassociated' ).closest( '.mw-input-with-label' ).hasClass( 'mw-input-hidden' ), true );
// select second option...
$( '#namespace' ).trigger( 'change' );
// ... and checkboxes should be visible again
+ // eslint-disable-next-line no-jquery/no-class-state
assert.strictEqual( $( '#nsinvert' ).closest( '.mw-input-with-label' ).hasClass( 'mw-input-hidden' ), false );
+ // eslint-disable-next-line no-jquery/no-class-state
assert.strictEqual( $( '#nsassociated' ).closest( '.mw-input-with-label' ).hasClass( 'mw-input-hidden' ), false );
// select first option ( 'all' namespace)...
$( '#namespace' ).trigger( 'change' );
// ... and checkboxes should now be hidden
+ // eslint-disable-next-line no-jquery/no-class-state
assert.strictEqual( $( '#nsinvert' ).closest( '.mw-input-with-label' ).hasClass( 'mw-input-hidden' ), true );
+ // eslint-disable-next-line no-jquery/no-class-state
assert.strictEqual( $( '#nsassociated' ).closest( '.mw-input-with-label' ).hasClass( 'mw-input-hidden' ), true );
// DOM cleanup
assert.strictEqual( $toggleLink.length, 1, 'Toggle link is added to the table of contents' );
+ // eslint-disable-next-line no-jquery/no-class-state
assert.strictEqual( $toc.hasClass( 'tochidden' ), false, 'The table of contents is now visible' );
$toggleLink.trigger( 'click' );
return $tocList.promise().then( function () {
+ // eslint-disable-next-line no-jquery/no-class-state
assert.strictEqual( $toc.hasClass( 'tochidden' ), true, 'The table of contents is now hidden' );
$toggleLink.trigger( 'click' );
return $tocList.promise();
"mw": false
},
"rules": {
- "no-console": 0
+ "no-console": "off",
+ "prefer-template": "off"
}
}
}
vandalizePage( name, content ) {
- let vandalUsername = 'Evil_' + browser.options.username;
+ const vandalUsername = 'Evil_' + browser.options.username;
browser.call( function () {
return Api.edit( name, content );
} );
it( 'should be re-creatable', function () {
- let initialContent = Util.getTestString( 'initialContent-' );
+ const initialContent = Util.getTestString( 'initialContent-' );
// create
browser.call( function () {
} );
// edit
- let editContent = Util.getTestString( 'editContent-' );
+ const editContent = Util.getTestString( 'editContent-' );
EditPage.edit( name, editContent );
// check
password = browser.options.password,
baseUrl = browser.options.baseUrl
) {
- let bot = new MWBot();
+ const bot = new MWBot();
return bot.loginGetEditToken( {
apiUrl: `${baseUrl}/api.php`,
* @return {Object} Promise for API action=delete response data.
*/
delete( title, reason ) {
- let bot = new MWBot();
+ const bot = new MWBot();
return bot.loginGetEditToken( {
apiUrl: `${browser.options.baseUrl}/api.php`,
* @return {Object} Promise for API action=createaccount response data.
*/
createAccount( username, password ) {
- let bot = new MWBot();
+ const bot = new MWBot();
// Log in as admin
return bot.loginGetCreateaccountToken( {
* @return {Object} Promise for API action=block response data.
*/
blockUser( username, expiry ) {
- let bot = new MWBot();
+ const bot = new MWBot();
// Log in as admin
return bot.loginGetEditToken( {
* @return {Object} Promise for API action=unblock response data.
*/
unblockUser( username ) {
- let bot = new MWBot();
+ const bot = new MWBot();
// Log in as admin
return bot.loginGetEditToken( {
MAINPAGE_REQUESTS_MAX_RUNS = 10; // (arbitrary) safe-guard against endless execution
function getJobCount() {
- let bot = new MWBot( {
+ const bot = new MWBot( {
apiUrl: `${browser.options.baseUrl}/api.php`
} );
return bot.request( {
}
function runThroughMainPageRequests( runCount = 1 ) {
- let page = new Page();
+ const page = new Page();
log( `through requests to the main page (run ${runCount}).` );
page.openTitle( '' );
beforeTest: function ( test ) {
if ( process.env.DISPLAY && process.env.DISPLAY.startsWith( ':' ) ) {
var logBuffer;
- let videoPath = filePath( test, logPath, 'mp4' );
+ const videoPath = filePath( test, logPath, 'mp4' );
const { spawn } = require( 'child_process' );
ffmpeg = spawn( 'ffmpeg', [
'-f', 'x11grab', // grab the X11 display
] );
logBuffer = function ( buffer, prefix ) {
- let lines = buffer.toString().trim().split( '\n' );
+ const lines = buffer.toString().trim().split( '\n' );
lines.forEach( function ( line ) {
console.log( prefix + line );
} );
return;
}
// save screenshot
- let screenshotfile = filePath( test, logPath, 'png' );
+ const screenshotfile = filePath( test, logPath, 'png' );
browser.saveScreenshot( screenshotfile );
console.log( '\n\tScreenshot location:', screenshotfile, '\n' );
}