Update mediawiki to v1.31.1

Signed-off-by: Florian Pritz <bluewind@xinu.at>
This commit is contained in:
Jelle van der Waa 2018-09-22 22:33:12 +02:00 committed by Florian Pritz
parent 4a852a8107
commit 93d4fc9de0
47 changed files with 441 additions and 307 deletions

View File

@ -1,4 +1,28 @@
== MediaWiki 1.31 ==
== MediaWiki 1.31.1 ==
This is a security and maintenance release of the MediaWiki 1.31 branch.
=== Changes since MediaWiki 1.31.0 ===
* (T169545, CVE-2018-0503) SECURITY: $wgRateLimits entry for 'user' overrides
'newbie'.
* (T194605, CVE-2018-0505) SECURITY: BotPasswords can bypass CentralAuth's
account lock.
* (T199029, CVE-2018-13258) SECURITY: Tarball was missing .htaccess files.
* (T197229) Bundle Nuke extension, it was accidentally omitted.
* (T193995) Fix undefined patchPath() method call in parser tests.
* (T198687) Fix various selectFields methods to use the string 'NULL', not null.
* Special:BotPasswords now requires reauthentication.
* (T191608, T187638) Add 'logid' parameter to Special:Log.
* (T193829) Indicate when a Bot Password needs reset.
* (T198037) GitInfo: Don't try shelling out if it's disabled.
* (T151415) Log email changes.
* (T197206) Fix performance regression when multiple DB used without caching.
* (T197030) PHPSessionHandler: Suppress headers warnings in initialize().
* (T182377, T196793) Exif: Guard against uncountable tag values.
* (T200861) Fix total breakage of SQLite web upgrade.
* (T200864) Fix pingback over-reporting on non-MySQL databases
* (T202550) Unbreak SpecialListusersHeaderForm and SpecialListusersHeader
hooks.
=== Changes since MediaWiki 1.31.0-rc.2 ===
* (T195783) Initialize PSR-4 namespaces at same stage as normal autoloader.

1
cache/.htaccess vendored Normal file
View File

@ -0,0 +1 @@
Deny from all

9
images/.htaccess Normal file
View File

@ -0,0 +1,9 @@
# Protect against bug T30235
<IfModule rewrite_module>
RewriteEngine On
RewriteOptions inherit
RewriteCond %{QUERY_STRING} \.[^\\/:*?\x22<>|%]+(#|\?|$) [nocase]
RewriteRule . - [forbidden]
# Fix for bug T64289
Options +FollowSymLinks
</IfModule>

1
includes/.htaccess Normal file
View File

@ -0,0 +1 @@
Deny from all

View File

@ -224,7 +224,7 @@ class Block {
'ipb_address',
'ipb_by',
'ipb_by_text',
'ipb_by_actor' => $wgActorTableSchemaMigrationStage > MIGRATION_OLD ? 'ipb_by_actor' : null,
'ipb_by_actor' => $wgActorTableSchemaMigrationStage > MIGRATION_OLD ? 'ipb_by_actor' : 'NULL',
'ipb_timestamp',
'ipb_auto',
'ipb_anon_only',

View File

@ -71,7 +71,7 @@ $wgConfigRegistry = [
* MediaWiki version number
* @since 1.2
*/
$wgVersion = '1.31.0';
$wgVersion = '1.31.1';
/**
* Name of the site. It must be changed in LocalSettings.php

View File

@ -227,6 +227,7 @@ class GitInfo {
$date = false;
if ( is_file( $wgGitBin ) &&
is_executable( $wgGitBin ) &&
!Shell::isDisabled() &&
$this->getHead() !== false
) {
$cmd = [

View File

@ -134,7 +134,7 @@ class PHPVersionCheck {
If for some reason you are unable to upgrade your {$phpInfo['implementation']} version,
you will need to <a href="https://www.mediawiki.org/wiki/Download">download</a> an
older version of MediaWiki from our website.
See our<a href="https://www.mediawiki.org/wiki/Compatibility#PHP">compatibility page</a>
See our <a href="https://www.mediawiki.org/wiki/Compatibility#PHP">compatibility page</a>
for details of which versions are compatible with prior versions of {$phpInfo['implementation']}.
HTML;
// phpcs:enable Generic.Files.LineLength

View File

@ -99,7 +99,7 @@ class Pingback {
return $dbw->upsert(
'updatelog',
[ 'ul_key' => $this->key, 'ul_value' => $timestamp ],
[ 'ul_key' => $this->key ],
[ 'ul_key' ],
[ 'ul_value' => $timestamp ],
__METHOD__
);

View File

@ -130,7 +130,11 @@ class ApiLogin extends ApiBase {
$session = $status->getValue();
$authRes = 'Success';
$loginType = 'BotPassword';
} elseif ( !$botLoginData[2] || $status->hasMessage( 'login-throttled' ) ) {
} elseif ( !$botLoginData[2] ||
$status->hasMessage( 'login-throttled' ) ||
$status->hasMessage( 'botpasswords-needs-reset' ) ||
$status->hasMessage( 'botpasswords-locked' )
) {
$authRes = 'Failed';
$message = $status->getMessage();
LoggerFactory::getInstance( 'authentication' )->info(

View File

@ -21,10 +21,11 @@
*/
use MediaWiki\MediaWikiServices;
use Wikimedia\Rdbms\LoadBalancer;
use Wikimedia\Rdbms\ILoadBalancer;
use Wikimedia\Rdbms\IDatabase;
use Wikimedia\Rdbms\DBConnRef;
use Wikimedia\Rdbms\MaintainableDBConnRef;
use Wikimedia\Rdbms\DatabaseDomain;
/**
* DB accessible external objects.
@ -112,7 +113,7 @@ class ExternalStoreDB extends ExternalStoreMedium {
* Get a LoadBalancer for the specified cluster
*
* @param string $cluster Cluster name
* @return LoadBalancer
* @return ILoadBalancer
*/
private function getLoadBalancer( $cluster ) {
$lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
@ -128,8 +129,8 @@ class ExternalStoreDB extends ExternalStoreMedium {
public function getSlave( $cluster ) {
global $wgDefaultExternalStore;
$wiki = isset( $this->params['wiki'] ) ? $this->params['wiki'] : false;
$lb = $this->getLoadBalancer( $cluster );
$domainId = $this->getDomainId( $lb->getServerInfo( $lb->getWriterIndex() ) );
if ( !in_array( "DB://" . $cluster, (array)$wgDefaultExternalStore ) ) {
wfDebug( "read only external store\n" );
@ -138,7 +139,7 @@ class ExternalStoreDB extends ExternalStoreMedium {
wfDebug( "writable external store\n" );
}
$db = $lb->getConnectionRef( DB_REPLICA, [], $wiki );
$db = $lb->getConnectionRef( DB_REPLICA, [], $domainId );
$db->clearFlag( DBO_TRX ); // sanity
return $db;
@ -151,15 +152,42 @@ class ExternalStoreDB extends ExternalStoreMedium {
* @return MaintainableDBConnRef
*/
public function getMaster( $cluster ) {
$wiki = isset( $this->params['wiki'] ) ? $this->params['wiki'] : false;
$lb = $this->getLoadBalancer( $cluster );
$domainId = $this->getDomainId( $lb->getServerInfo( $lb->getWriterIndex() ) );
$db = $lb->getMaintenanceConnectionRef( DB_MASTER, [], $wiki );
$db = $lb->getMaintenanceConnectionRef( DB_MASTER, [], $domainId );
$db->clearFlag( DBO_TRX ); // sanity
return $db;
}
/**
* @param array $server Master DB server configuration array for LoadBalancer
* @return string|bool Database domain ID or false
*/
private function getDomainId( array $server ) {
if ( isset( $this->params['wiki'] ) ) {
return $this->params['wiki']; // explicit domain
}
if ( isset( $server['dbname'] ) ) {
// T200471: for b/c, treat any "dbname" field as forcing which database to use.
// MediaWiki/LoadBalancer previously did not enforce any concept of a local DB
// domain, but rather assumed that the LB server configuration matched $wgDBname.
// This check is useful when the external storage DB for this cluster does not use
// the same name as the corresponding "main" DB(s) for wikis.
$domain = new DatabaseDomain(
$server['dbname'],
$server['schema'] ?? null,
$server['tablePrefix'] ?? ''
);
return $domain->getId();
}
return false; // local LB domain
}
/**
* Get the 'blobs' table name for this database
*

View File

@ -246,7 +246,7 @@ class ArchivedFile {
'fa_minor_mime',
'fa_user',
'fa_user_text',
'fa_actor' => $wgActorTableSchemaMigrationStage > MIGRATION_OLD ? 'fa_actor' : null,
'fa_actor' => $wgActorTableSchemaMigrationStage > MIGRATION_OLD ? 'fa_actor' : 'NULL',
'fa_timestamp',
'fa_deleted',
'fa_deleted_timestamp', /* Used by LocalFileRestoreBatch */

View File

@ -223,7 +223,7 @@ class LocalFile extends File {
'img_minor_mime',
'img_user',
'img_user_text',
'img_actor' => $wgActorTableSchemaMigrationStage > MIGRATION_OLD ? 'img_actor' : null,
'img_actor' => $wgActorTableSchemaMigrationStage > MIGRATION_OLD ? 'img_actor' : 'NULL',
'img_timestamp',
'img_sha1',
] + CommentStore::getStore()->getFields( 'img_description' );

View File

@ -136,7 +136,7 @@ class OldLocalFile extends LocalFile {
'oi_minor_mime',
'oi_user',
'oi_user_text',
'oi_actor' => $wgActorTableSchemaMigrationStage > MIGRATION_OLD ? 'oi_actor' : null,
'oi_actor' => $wgActorTableSchemaMigrationStage > MIGRATION_OLD ? 'oi_actor' : 'NULL',
'oi_timestamp',
'oi_deleted',
'oi_sha1',

View File

@ -220,10 +220,6 @@ class DatabaseSqlite extends Database {
return false;
}
public function selectDB( $db ) {
return false; // doesn't make sense
}
/**
* @return string SQLite DB file path
* @since 1.25

View File

@ -466,6 +466,10 @@ abstract class LBFactory implements ILBFactory {
// Request opted out of using position wait logic. This is useful for requests
// done by the job queue or background ETL that do not have a meaningful session.
$this->chronProt->setWaitEnabled( false );
} elseif ( $this->memStash instanceof EmptyBagOStuff ) {
// No where to store any DB positions and wait for them to appear
$this->chronProt->setEnabled( false );
$this->replLogger->info( 'Cannot use ChronologyProtector with EmptyBagOStuff.' );
}
$this->replLogger->debug( __METHOD__ . ': using request info ' .

View File

@ -335,6 +335,14 @@ interface ILoadBalancer {
*/
public function getServerType( $i );
/**
* 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
*/
public function getServerInfo( $i );
/**
* @param int $i Server index
* @return array (Database::ATTRIBUTE_* constant => value) for all such constants

View File

@ -1173,6 +1173,14 @@ class LoadBalancer implements ILoadBalancer {
return ( $name != '' ) ? $name : 'localhost';
}
public function getServerInfo( $i ) {
if ( isset( $this->servers[$i] ) ) {
return $this->servers[$i];
} else {
return false;
}
}
public function getServerType( $i ) {
return isset( $this->servers[$i]['type'] ) ? $this->servers[$i]['type'] : 'unknown';
}

View File

@ -65,10 +65,11 @@ class LogPager extends ReverseChronologicalPager {
* @param int|bool $month The month to start from. Default: false
* @param string $tagFilter Tag
* @param string $action Specific action (subtype) requested
* @param int $logId Log entry ID, to limit to a single log entry.
*/
public function __construct( $list, $types = [], $performer = '', $title = '',
$pattern = '', $conds = [], $year = false, $month = false, $tagFilter = '',
$action = ''
$action = '', $logId = false
) {
parent::__construct( $list->getContext() );
$this->mConds = $conds;
@ -81,6 +82,7 @@ class LogPager extends ReverseChronologicalPager {
$this->limitAction( $action );
$this->getDateCond( $year, $month );
$this->mTagFilter = $tagFilter;
$this->limitLogId( $logId );
$this->mDb = wfGetDB( DB_REPLICA, 'logpager' );
}
@ -278,6 +280,17 @@ class LogPager extends ReverseChronologicalPager {
}
}
/**
* Limit to the (single) specified log ID.
* @param int $logId The log entry ID.
*/
protected function limitLogId( $logId ) {
if ( !$logId ) {
return;
}
$this->mConds['log_id'] = $logId;
}
/**
* Constructs the most part of the query. Extra conditions are sprinkled in
* all over this class.

View File

@ -742,12 +742,16 @@ class Exif {
$ecount = 1; // checking individual elements
}
}
$count = count( $val );
if ( $ecount != $count ) {
$this->debug( $val, __FUNCTION__, "Expected $ecount elements for $tag but got $count" );
return false;
$count = 1;
if ( is_array( $val ) ) {
$count = count( $val );
if ( $ecount != $count ) {
$this->debug( $val, __FUNCTION__, "Expected $ecount elements for $tag but got $count" );
return false;
}
}
// If there are multiple values, recursively validate each of them.
if ( $count > 1 ) {
foreach ( $val as $v ) {
if ( !$this->validate( $section, $tag, $v, true ) ) {

View File

@ -271,7 +271,7 @@ class FormatMetadata extends ContextSource {
// TODO: YCbCrCoefficients #p27 (see annex E)
case 'ExifVersion':
case 'FlashpixVersion':
$val = "$val" / 100;
$val = (int)$val / 100;
break;
case 'ColorSpace':

View File

@ -117,7 +117,7 @@ class VersionChecker {
}
break;
case 'extensions':
case 'skin':
case 'skins':
foreach ( $values as $dependency => $constraint ) {
$extError = $this->handleExtensionDependency(
$dependency, $constraint, $extension, $dependencyType
@ -169,7 +169,7 @@ class VersionChecker {
* @param string $dependencyName The name of the dependency
* @param string $constraint The required version constraint for this dependency
* @param string $checkedExt The Extension, which depends on this dependency
* @param string $type Either 'extension' or 'skin'
* @param string $type Either 'extensions' or 'skins'
* @return bool|array false for no errors, or an array of info
*/
private function handleExtensionDependency( $dependencyName, $constraint, $checkedExt,

View File

@ -122,22 +122,28 @@ class PHPSessionHandler implements \SessionHandlerInterface {
// Close any auto-started session, before we replace it
session_write_close();
// Tell PHP not to mess with cookies itself
ini_set( 'session.use_cookies', 0 );
ini_set( 'session.use_trans_sid', 0 );
try {
\Wikimedia\suppressWarnings();
// T124510: Disable automatic PHP session related cache headers.
// MediaWiki adds it's own headers and the default PHP behavior may
// set headers such as 'Pragma: no-cache' that cause problems with
// some user agents.
session_cache_limiter( '' );
// Tell PHP not to mess with cookies itself
ini_set( 'session.use_cookies', 0 );
ini_set( 'session.use_trans_sid', 0 );
// Also set a sane serialization handler
\Wikimedia\PhpSessionSerializer::setSerializeHandler();
// T124510: Disable automatic PHP session related cache headers.
// MediaWiki adds it's own headers and the default PHP behavior may
// set headers such as 'Pragma: no-cache' that cause problems with
// some user agents.
session_cache_limiter( '' );
// Register this as the save handler, and register an appropriate
// shutdown function.
session_set_save_handler( self::$instance, true );
// Also set a sane serialization handler
\Wikimedia\PhpSessionSerializer::setSerializeHandler();
// Register this as the save handler, and register an appropriate
// shutdown function.
session_set_save_handler( self::$instance, true );
} finally {
\Wikimedia\restoreWarnings();
}
}
/**

View File

@ -51,6 +51,10 @@ class SpecialBotPasswords extends FormSpecialPage {
return $this->getConfig()->get( 'EnableBotPasswords' );
}
protected function getLoginSecurityLevel() {
return $this->getName();
}
/**
* Main execution point
* @param string|null $par
@ -107,6 +111,9 @@ class SpecialBotPasswords extends FormSpecialPage {
'type' => 'check',
'label-message' => 'botpasswords-label-resetpassword',
];
if ( $this->botPassword->isInvalid() ) {
$fields['resetPassword']['default'] = true;
}
}
$lang = $this->getLanguage();
@ -153,22 +160,39 @@ class SpecialBotPasswords extends FormSpecialPage {
} else {
$linkRenderer = $this->getLinkRenderer();
$passwordFactory = new PasswordFactory();
$passwordFactory->init( $this->getConfig() );
$dbr = BotPassword::getDB( DB_REPLICA );
$res = $dbr->select(
'bot_passwords',
[ 'bp_app_id' ],
[ 'bp_app_id', 'bp_password' ],
[ 'bp_user' => $this->userId ],
__METHOD__
);
foreach ( $res as $row ) {
try {
$password = $passwordFactory->newFromCiphertext( $row->bp_password );
$passwordInvalid = $password instanceof InvalidPassword;
unset( $password );
} catch ( PasswordError $ex ) {
$passwordInvalid = true;
}
$text = $linkRenderer->makeKnownLink(
$this->getPageTitle( $row->bp_app_id ),
$row->bp_app_id
);
if ( $passwordInvalid ) {
$text .= $this->msg( 'word-separator' )->escaped()
. $this->msg( 'botpasswords-label-needsreset' )->parse();
}
$fields[] = [
'section' => 'existing',
'type' => 'info',
'raw' => true,
'default' => $linkRenderer->makeKnownLink(
$this->getPageTitle( $row->bp_app_id ),
$row->bp_app_id
),
'default' => $text,
];
}

View File

@ -22,6 +22,7 @@
*/
use MediaWiki\Auth\AuthManager;
use MediaWiki\Logger\LoggerFactory;
/**
* Let users change their email address.
@ -167,6 +168,14 @@ class SpecialChangeEmail extends FormSpecialPage {
return $status;
}
LoggerFactory::getInstance( 'authentication' )->info(
'Changing email address for {user} from {oldemail} to {newemail}', [
'user' => $user->getName(),
'oldemail' => $oldaddr,
'newemail' => $newaddr,
]
);
Hooks::run( 'PrefsEmailAudit', [ $user, $oldaddr, $newaddr ] );
$user->saveSettings();

View File

@ -51,6 +51,7 @@ class SpecialLog extends SpecialPage {
$opts->add( 'dir', '' );
$opts->add( 'offender', '' );
$opts->add( 'subtype', '' );
$opts->add( 'logid', '' );
// Set values
$opts->fetchValuesFromRequest( $this->getRequest() );
@ -169,6 +170,16 @@ class SpecialLog extends SpecialPage {
return $subpages;
}
/**
* Set options based on the subpage title parts:
* - One part that is a valid log type: Special:Log/logtype
* - Two parts: Special:Log/logtype/username
* - Otherwise, assume the whole subpage is a username.
*
* @param FormOptions $opts
* @param $par
* @throws ConfigException
*/
private function parseParams( FormOptions $opts, $par ) {
# Get parameters
$par = $par !== null ? $par : '';
@ -204,7 +215,8 @@ class SpecialLog extends SpecialPage {
$opts->getValue( 'year' ),
$opts->getValue( 'month' ),
$opts->getValue( 'tagfilter' ),
$opts->getValue( 'subtype' )
$opts->getValue( 'subtype' ),
$opts->getValue( 'logid' )
);
$this->addHeader( $opts->getValue( 'type' ) );

View File

@ -162,7 +162,7 @@ class SpecialRedirect extends FormSpecialPage {
/**
* Handle Special:Redirect/logid/xxx
* (by redirecting to index.php?title=Special:Log)
* (by redirecting to index.php?title=Special:Log&logid=xxx)
*
* @since 1.27
* @return string|null Url to redirect to, or null if $mValue is invalid.
@ -176,80 +176,8 @@ class SpecialRedirect extends FormSpecialPage {
if ( $logid === 0 ) {
return null;
}
$logQuery = ActorMigration::newMigration()->getJoin( 'log_user' );
$logparams = [
'log_id' => 'log_id',
'log_timestamp' => 'log_timestamp',
'log_type' => 'log_type',
'log_user_text' => $logQuery['fields']['log_user_text'],
];
$dbr = wfGetDB( DB_REPLICA );
// Gets the nested SQL statement which
// returns timestamp of the log with the given log ID
$inner = $dbr->selectSQLText(
'logging',
[ 'log_timestamp' ],
[ 'log_id' => $logid ]
);
// Returns all fields mentioned in $logparams of the logs
// with the same timestamp as the one returned by the statement above
$logsSameTimestamps = $dbr->select(
[ 'logging' ] + $logQuery['tables'],
$logparams,
[ "log_timestamp = ($inner)" ],
__METHOD__,
[],
$logQuery['joins']
);
if ( $logsSameTimestamps->numRows() === 0 ) {
return null;
}
// Stores the row with the same log ID as the one given
$rowMain = [];
foreach ( $logsSameTimestamps as $row ) {
if ( (int)$row->log_id === $logid ) {
$rowMain = $row;
}
}
array_shift( $logparams );
// Stores all the rows with the same values in each column
// as $rowMain
foreach ( $logparams as $key => $dummy ) {
$matchedRows = [];
foreach ( $logsSameTimestamps as $row ) {
if ( $row->$key === $rowMain->$key ) {
$matchedRows[] = $row;
}
}
if ( count( $matchedRows ) === 1 ) {
break;
}
$logsSameTimestamps = $matchedRows;
}
$query = [ 'title' => 'Special:Log', 'limit' => count( $matchedRows ) ];
// A map of database field names from table 'logging' to the values of $logparams
$keys = [
'log_timestamp' => 'offset',
'log_type' => 'type',
'log_user_text' => 'user'
];
foreach ( $logparams as $logKey => $dummy ) {
$query[$keys[$logKey]] = $matchedRows[0]->$logKey;
}
$query['offset'] = $query['offset'] + 1;
$url = $query;
return wfAppendQuery( wfScript( 'index' ), $url );
$query = [ 'title' => 'Special:Log', 'logid' => $logid ];
return wfAppendQuery( wfScript( 'index' ), $query );
}
/**

View File

@ -751,45 +751,36 @@ class SpecialWatchlist extends ChangesListSpecialPage {
}
function cutoffselector( $options ) {
// Cast everything to strings immediately, so that we know all of the values have the same
// precision, and can be compared with '==='. 2/24 has a few more decimal places than its
// default string representation, for example, and would confuse comparisons.
// Misleadingly, the 'days' option supports hours too.
$days = array_map( 'strval', [ 1 / 24, 2 / 24, 6 / 24, 12 / 24, 1, 3, 7 ] );
$userWatchlistOption = (string)$this->getUser()->getOption( 'watchlistdays' );
// add the user preference, if it isn't available already
if ( !in_array( $userWatchlistOption, $days ) && $userWatchlistOption !== '0' ) {
$days[] = $userWatchlistOption;
}
$maxDays = (string)$this->maxDays;
// add the maximum possible value, if it isn't available already
if ( !in_array( $maxDays, $days ) ) {
$days[] = $maxDays;
}
$selected = (string)$options['days'];
$selected = (float)$options['days'];
if ( $selected <= 0 ) {
$selected = $maxDays;
$selected = $this->maxDays;
}
// add the currently selected value, if it isn't available already
if ( !in_array( $selected, $days ) ) {
$days[] = $selected;
}
$selectedHours = round( $selected * 24 );
$select = new XmlSelect( 'days', 'days', $selected );
$hours = array_unique( array_filter( [
1,
2,
6,
12,
24,
72,
168,
24 * (float)$this->getUser()->getOption( 'watchlistdays', 0 ),
24 * $this->maxDays,
$selectedHours
] ) );
asort( $hours );
asort( $days );
foreach ( $days as $value ) {
if ( $value < 1 ) {
$name = $this->msg( 'hours' )->numParams( $value * 24 )->text();
$select = new XmlSelect( 'days', 'days', (float)( $selectedHours / 24 ) );
foreach ( $hours as $value ) {
if ( $value < 24 ) {
$name = $this->msg( 'hours' )->numParams( $value )->text();
} else {
$name = $this->msg( 'days' )->numParams( $value )->text();
$name = $this->msg( 'days' )->numParams( $value / 24 )->text();
}
$select->addOption( $name, $value );
$select->addOption( $name, (float)( $value / 24 ) );
}
return $select->getHTML() . "\n<br />\n";

View File

@ -321,7 +321,7 @@ class UsersPager extends AlphabeticPager {
Hooks::run( 'SpecialListusersHeaderForm', [ $this, &$beforeSubmitButtonHookOut ] );
if ( $beforeSubmitButtonHookOut !== '' ) {
$formDescriptior[ 'beforeSubmitButtonHookOut' ] = [
$formDescriptor[ 'beforeSubmitButtonHookOut' ] = [
'class' => HTMLInfoField::class,
'raw' => true,
'default' => $beforeSubmitButtonHookOut
@ -337,7 +337,7 @@ class UsersPager extends AlphabeticPager {
Hooks::run( 'SpecialListusersHeader', [ $this, &$beforeClosingFieldsetHookOut ] );
if ( $beforeClosingFieldsetHookOut !== '' ) {
$formDescriptior[ 'beforeClosingFieldsetHookOut' ] = [
$formDescriptor[ 'beforeClosingFieldsetHookOut' ] = [
'class' => HTMLInfoField::class,
'raw' => true,
'default' => $beforeClosingFieldsetHookOut

View File

@ -261,6 +261,15 @@ class BotPassword implements IDBAccessObject {
}
}
/**
* Whether the password is currently invalid
* @since 1.32
* @return bool
*/
public function isInvalid() {
return $this->getPassword() instanceof InvalidPassword;
}
/**
* Save the BotPassword to the database
* @param string $operation 'update' or 'insert'
@ -464,6 +473,10 @@ class BotPassword implements IDBAccessObject {
return Status::newFatal( 'nosuchuser', $name );
}
if ( $user->isLocked() ) {
return Status::newFatal( 'botpasswords-locked' );
}
// Throttle
$throttle = null;
if ( !empty( $wgPasswordAttemptThrottle ) ) {
@ -491,7 +504,11 @@ class BotPassword implements IDBAccessObject {
}
// Check the password
if ( !$bp->getPassword()->equals( $password ) ) {
$passwordObj = $bp->getPassword();
if ( $passwordObj instanceof InvalidPassword ) {
return Status::newFatal( 'botpasswords-needs-reset', $name, $appId );
}
if ( !$passwordObj->equals( $password ) ) {
return Status::newFatal( 'wrongpassword' );
}

View File

@ -2108,10 +2108,6 @@ class User implements IDBAccessObject, UserIdentity {
if ( isset( $limits['user'] ) ) {
$userLimit = $limits['user'];
}
// limits for newbie logged-in users
if ( $isNewbie && isset( $limits['newbie'] ) ) {
$keys[$cache->makeKey( 'limiter', $action, 'user', $id )] = $limits['newbie'];
}
}
// limits for anons and for newbie logged-in users
@ -2143,6 +2139,11 @@ class User implements IDBAccessObject, UserIdentity {
}
}
// limits for newbie logged-in users (override all the normal user limits)
if ( $id !== 0 && $isNewbie && isset( $limits['newbie'] ) ) {
$userLimit = $limits['newbie'];
}
// Set the user limit key
if ( $userLimit !== false ) {
list( $max, $period ) = $userLimit;

1
languages/.htaccess Normal file
View File

@ -0,0 +1 @@
Deny from all

View File

@ -537,6 +537,7 @@
"botpasswords-existing": "Existing bot passwords",
"botpasswords-createnew": "Create a new bot password",
"botpasswords-editexisting": "Edit an existing bot password",
"botpasswords-label-needsreset": "(password needs reset)",
"botpasswords-label-appid": "Bot name:",
"botpasswords-label-create": "Create",
"botpasswords-label-update": "Update",
@ -560,6 +561,8 @@
"botpasswords-restriction-failed": "Bot password restrictions prevent this login.",
"botpasswords-invalid-name": "The username specified does not contain the bot password separator (\"$1\").",
"botpasswords-not-exist": "User \"$1\" does not have a bot password named \"$2\".",
"botpasswords-needs-reset": "The bot password for bot name \"$2\" of {{GENDER:$1|user}} \"$1\" must be reset.",
"botpasswords-locked": "You cannot login with a bot password as your account is locked.",
"resetpass_forbidden": "Passwords cannot be changed",
"resetpass_forbidden-reason": "Passwords cannot be changed: $1",
"resetpass-no-info": "You must be logged in to access this page directly.",

View File

@ -734,6 +734,7 @@
"botpasswords-existing": "Form section label for the part of the form listing the user's existing bot passwords.",
"botpasswords-createnew": "Form section label for the part of the form related to creating a new bot password.",
"botpasswords-editexisting": "Form section label for the part of the form related to editing an existing bot password.",
"botpasswords-label-needsreset": "Indicator for when an existing bot password is invalid and needs to be reset.",
"botpasswords-label-appid": "Form field label for the \"bot name\", internally known as the \"application ID\".",
"botpasswords-label-create": "Button label for the button to create a new bot password.\n{{Identical|Create}}",
"botpasswords-label-update": "Button label for the button to save changes to a bot password.\n{{Identical|Update}}",
@ -757,6 +758,8 @@
"botpasswords-restriction-failed": "Error message when login is rejected because the configured restrictions were not satisfied.",
"botpasswords-invalid-name": "Error message when a username lacking the separator character is passed to BotPassword. Parameters:\n* $1 - The separator character.",
"botpasswords-not-exist": "Error message when a username exists but does not a bot password for the given \"bot name\". Parameters:\n* $1 - username\n* $2 - bot name",
"botpasswords-needs-reset": "Error message when a bot password exists but needs to be reset. Parameters:\n* $1 - username\n* $2 - bot name",
"botpasswords-locked": "Shown in the event that the underlying account is locked",
"resetpass_forbidden": "Used as error message in changing password. Maybe the external auth plugin won't allow local password changes.",
"resetpass_forbidden-reason": "Like {{msg-mw|resetpass_forbidden}} but the auth provider gave a reason.\n\nParameters:\n* $1 - reason given by auth provider",
"resetpass-no-info": "Error message for [[Special:ChangePassword]].\n\nParameters:\n* $1 (unused) - a link to [[Special:UserLogin]] with {{msg-mw|loginreqlink}} as link description",

View File

@ -92,92 +92,92 @@ $namespaceAliases = [
];
$specialPageAliases = [
'Allmessages' => [ 'सर्वप्रणाली-संदेश' ],
'Allpages' => [ 'सर्वपृष्टानि' ],
'Ancientpages' => [ 'पूर्वतनपृष्टानि' ],
'Blankpage' => [ 'रिक्तपृष्ठ' ],
'Block' => [ 'सदस्यप्रतिबन्ध' ],
'Booksources' => [ 'पुस्तकस्रोत' ],
'BrokenRedirects' => [ 'खण्डीतपुनर्निर्देशन' ],
'Categories' => [ 'वर्ग' ],
'ChangePassword' => [ 'सङ्केतशब्दपुन:प्रयुक्ता' ],
'Confirmemail' => [ 'विपत्रपुष्टिकृते' ],
'Contributions' => [ 'योगदानम्' ],
'CreateAccount' => [ 'सृज्उपयोजकसंज्ञा' ],
'Deadendpages' => [ 'निराग्रपृष्टानि' ],
'DeletedContributions' => [ 'परित्यागितयोगदान' ],
'DoubleRedirects' => [ 'पुनर्निर्देशनद्वंद्व' ],
'Emailuser' => [ 'विपत्रयोजक' ],
'ExpandTemplates' => [ 'बिंबधरविस्तारकरोसि' ],
'Export' => [ 'निर्यात' ],
'Fewestrevisions' => [ 'स्वल्पपरिवर्तन' ],
'FileDuplicateSearch' => [ 'अनुकृतसंचिकाशोध' ],
'Filepath' => [ 'संचिकापथ' ],
'Import' => [ 'आयात' ],
'Invalidateemail' => [ 'अमान्यविपत्र' ],
'BlockList' => [ 'प्रतिबन्धसूची' ],
'LinkSearch' => [ 'सम्बन्धन्‌शोध' ],
'Listadmins' => [ 'प्रचालकसूची' ],
'Listbots' => [ 'स्वयंअनुकृसूची' ],
'Listfiles' => [ 'चित्रसूची', 'संचिकासूचि' ],
'Listgrouprights' => [ 'गटअधिकारसूची' ],
'Listredirects' => [ 'विचालन्‌सूची' ],
'Listusers' => [ 'सदस्यासूची' ],
'Lockdb' => [ 'विदाद्वारंबन्ध्' ],
'Log' => [ 'अङ्कन' ],
'Lonelypages' => [ 'अकलपृष्टानि' ],
'Longpages' => [ 'दीर्घपृष्टानि' ],
'MergeHistory' => [ 'इतिहाससंयोग' ],
'MIMEsearch' => [ 'विविधामाप_(माईम)_शोधसि' ],
'Mostcategories' => [ 'अधिकतमवर्ग' ],
'Mostimages' => [ 'अधिकतमसम्भन्दिन्_संचिका' ],
'Mostlinked' => [ 'अधिकतमसम्भन्दिन्_पृष्टानि', 'अधिकतमसम्भन्दिन्' ],
'Mostlinkedcategories' => [ 'अधिकतमसम्भन्दिन्_वर्ग' ],
'Mostlinkedtemplates' => [ 'अधिकतमसम्भन्दिन्_फलकानि' ],
'Mostrevisions' => [ 'अधिकतमपरिवर्तन' ],
'Movepage' => [ 'पृष्ठस्थानान्तर' ],
'Mycontributions' => [ 'मदीययोगदानम्' ],
'Mypage' => [ 'मम_पृष्टम्' ],
'Mytalk' => [ 'मदीयसंवादम्' ],
'Newimages' => [ 'नूतनसंचिका', 'नूतनचित्रानि' ],
'Newpages' => [ 'नूतनपृष्टानि' ],
'PasswordReset' => [ 'सङ्केतशब्दपुन:प्रयु्क्ता' ],
'Allmessages' => [ 'सर्वसन्देशाः', 'सर्वप्रणाली-संदेश' ],
'Allpages' => [ 'सर्वपृष्ठानि', 'सर्वपृष्टानि' ],
'Ancientpages' => [ 'पुरातनपृष्ठानि', 'पूर्वतनपृष्टानि' ],
'Blankpage' => [ 'रिक्तपृष्ठानि', 'रिक्तपृष्ठ' ],
'Block' => [ 'प्रतिबन्धः', 'सदस्यप्रतिबन्ध' ],
'Booksources' => [ 'पुस्तकस्रोतांसि', 'पुस्तकस्रोत' ],
'BrokenRedirects' => [ 'भग्नानि_अनुप्रेषणानि', 'खण्डीतपुनर्निर्देशन' ],
'Categories' => [ 'वर्गाः', 'वर्ग' ],
'ChangePassword' => [ 'कूटशब्द_परिवर्त्यताम्', 'सङ्केतशब्दपुन:प्रयुक्ता' ],
'Confirmemail' => [ 'विपत्रं_पुष्ट्यताम्', 'विपत्रपुष्टिकृते' ],
'Contributions' => [ 'योगदानानि', 'योगदानम्' ],
'CreateAccount' => [ 'लेखा_सृज्यताम्', 'सृज्उपयोजकसंज्ञा' ],
'Deadendpages' => [ 'मृतानि_पृष्ठानि', 'निराग्रपृष्टानि' ],
'DeletedContributions' => [ 'अपाकृतानि_योगदानानि', 'परित्यागितयोगदान' ],
'DoubleRedirects' => [ 'द्वैधपुनर्निर्देशनम्', 'पुनर्निर्देशनद्वंद्व' ],
'Emailuser' => [ 'विपत्रप्रयोक्ता', 'विपत्रयोजक' ],
'ExpandTemplates' => [ 'फलकानि_विस्तीर्यन्ताम्', 'बिंबधरविस्तारकरोसि' ],
'Export' => [ 'निर्यापयतु', 'निर्यात' ],
'Fewestrevisions' => [ 'स्वल्पतमपरिवर्तानानि', 'स्वल्पपरिवर्तन' ],
'FileDuplicateSearch' => [ 'समानसञ्चिकान्वेषणम्', 'अनुकृतसंचिकाशोध' ],
'Filepath' => [ 'सञ्चिकापथः', 'संचिकापथ' ],
'Import' => [ 'आयापयतु', 'आयात' ],
'Invalidateemail' => [ 'विपत्रेऽमान्यम्', 'अमान्यविपत्र' ],
'BlockList' => [ 'प्रतिबन्धावलिः', 'प्रतिबन्धसूची' ],
'LinkSearch' => [ 'परिसन्धे_अन्वेषणम्', 'सम्बन्धन्‌शोध' ],
'Listadmins' => [ 'प्रबन्धकावलिः', 'प्रचालकसूची' ],
'Listbots' => [ 'बॉटसूची', 'स्वयंअनुकृसूची' ],
'Listfiles' => [ 'सञ्चिकावलिः', 'चित्रसूची', 'संचिकासूचि' ],
'Listgrouprights' => [ 'समूहाधिकारावलिः', 'गटअधिकारसूची' ],
'Listredirects' => [ 'अनुप्रेषितावलिः', 'विचालन्‌सूची' ],
'Listusers' => [ 'सदस्यावलिः', 'सदस्यासूची' ],
'Lockdb' => [ 'दत्तांशकीलनम्', 'विदाद्वारंबन्ध्' ],
'Log' => [ 'संरक्षितावलिः', 'अङ्कन' ],
'Lonelypages' => [ 'एकाकिपृष्ठानि', 'अकलपृष्टानि' ],
'Longpages' => [ 'दीर्घपृष्ठानि', 'दीर्घपृष्टानि' ],
'MergeHistory' => [ 'इतिहासविलयः', 'इतिहाससंयोग' ],
'MIMEsearch' => [ 'MIME_अन्वेषणम्', 'विविधामाप_(माईम)_शोधसि' ],
'Mostcategories' => [ 'अधिकतमवर्गाः', 'अधिकतमवर्ग' ],
'Mostimages' => [ 'अधिकतमसञ्चिकाः', 'अधिकतमसम्भन्दिन्_संचिका' ],
'Mostlinked' => [ 'अधिकतमपरिसन्धितम्', 'अधिकतमसम्भन्दिन्_पृष्टानि', 'अधिकतमसम्भन्दिन्' ],
'Mostlinkedcategories' => [ 'अधिकतमपरिसन्धितवर्गाः', 'अधिकतमसम्भन्दिन्_वर्ग' ],
'Mostlinkedtemplates' => [ 'अधिकतमपरिसन्धितफलकानि', 'अधिकतमसम्भन्दिन्_फलकानि' ],
'Mostrevisions' => [ 'अधिकतमसंस्करणानि', 'अधिकतमपरिवर्तन' ],
'Movepage' => [ 'पृष्ठस्थानान्तरणम्', 'पृष्ठस्थानान्तर' ],
'Mycontributions' => [ 'मम_योगदानानि', 'मदीययोगदानम्' ],
'Mypage' => [ 'मम_पृष्ठम्', 'मम_पृष्टम्' ],
'Mytalk' => [ 'मम_सम्भाषणम्', 'मदीयसंवादम्' ],
'Newimages' => [ 'नवीनचित्राणि', 'नूतनसंचिका', 'नूतनचित्रानि' ],
'Newpages' => [ 'नवीनपृष्ठानि', 'नूतनपृष्टानि' ],
'PasswordReset' => [ 'कूटशब्दस्य_पुनस्स्थापनम्', 'सङ्केतशब्दपुन:प्रयु्क्ता' ],
'Preferences' => [ 'इष्टतमानि' ],
'Prefixindex' => [ 'उपसर्गअनुक्रमणी' ],
'Protectedpages' => [ 'सुरक्षितपृष्टानि' ],
'Protectedtitles' => [ 'सुरक्षितशिर्षकम्' ],
'Randompage' => [ 'अविशीष्टपृष्ठम्' ],
'RandomInCategory' => [ 'अविशिष्टवर्ग' ],
'Randomredirect' => [ 'अविशीष्टविचालन्‌' ],
'Recentchanges' => [ 'नवीनतम_परिवर्तन' ],
'Recentchangeslinked' => [ 'नवीनतमसम्भन्दिन_परिवर्त' ],
'Revisiondelete' => [ 'आवृत्तीपरित्याग' ],
'Search' => [ 'शोध' ],
'Shortpages' => [ 'लघुपृष्टानि' ],
'Specialpages' => [ 'विशेषपृष्टानि' ],
'Statistics' => [ 'सांख्यिकी' ],
'Uncategorizedcategories' => [ 'अवर्गीकृतवर्ग' ],
'Uncategorizedimages' => [ 'अवर्गीकृतसंचिका', 'अवर्गीकृतचित्रानि' ],
'Uncategorizedpages' => [ 'अवर्गीकृतपृष्टानि' ],
'Prefixindex' => [ 'उपसर्गानुक्रमणी', 'उपसर्गअनुक्रमणी' ],
'Protectedpages' => [ 'सुरक्षितपृष्ठानि', 'सुरक्षितपृष्टानि' ],
'Protectedtitles' => [ 'सुरक्षितशीर्षकाणि', 'सुरक्षितशिर्षकम्' ],
'Randompage' => [ 'यादृच्छिकपृष्ठम्', 'अविशीष्टपृष्ठम्' ],
'RandomInCategory' => [ 'वर्गे_यादृच्छिकम्', 'अविशिष्टवर्ग' ],
'Randomredirect' => [ 'यादृच्छिकानुप्रेषितम्', 'अविशीष्टविचालन्‌' ],
'Recentchanges' => [ 'नूतनपरिवर्तनानि', 'नवीनतम_परिवर्तन' ],
'Recentchangeslinked' => [ 'नूतनपरिवर्तनानां_परिसन्धय', 'नवीनतमसम्भन्दिन_परिवर्त' ],
'Revisiondelete' => [ 'संस्करणापाकरणम्', 'आवृत्तीपरित्याग' ],
'Search' => [ 'अन्वेषणम्', 'शोध' ],
'Shortpages' => [ 'लघुपृष्ठानि', 'लघुपृष्टानि' ],
'Specialpages' => [ 'विशेषपृष्ठानि', 'विशेषपृष्टानि' ],
'Statistics' => [ 'साङ्ख्यिकी', 'सांख्यिकी' ],
'Uncategorizedcategories' => [ 'अवर्गीकृतवर्गाः', 'अवर्गीकृतवर्ग' ],
'Uncategorizedimages' => [ 'अवर्गीकृतचित्राणि', 'अवर्गीकृतसंचिका', 'अवर्गीकृतचित्रानि' ],
'Uncategorizedpages' => [ 'अवर्गीकृतपृष्ठानि', 'अवर्गीकृतपृष्टानि' ],
'Uncategorizedtemplates' => [ 'अवर्गीकृतफलकानि' ],
'Undelete' => [ 'प्रत्यादिश्_परित्याग' ],
'Unlockdb' => [ 'विवृतविदाद्वारंतालक' ],
'Unusedcategories' => [ 'अप्रयूक्तवर्ग' ],
'Unusedimages' => [ 'अप्रयूक्तसंचिका' ],
'Unusedtemplates' => [ 'अप्रयूक्तबिंबधर' ],
'Unwatchedpages' => [ 'अनिरिक्षीतपृष्ठ' ],
'Upload' => [ 'भारंन्यस्यति' ],
'Userlogin' => [ 'सदस्यप्रवेशन' ],
'Userlogout' => [ 'सदस्यबहिर्गमन' ],
'Userrights' => [ 'योजकआधिकार' ],
'Version' => [ 'आवृत्ती' ],
'Wantedcategories' => [ 'प्रार्थितवर्ग' ],
'Wantedfiles' => [ 'प्रार्थितसंचिका' ],
'Wantedpages' => [ 'प्रार्थितपृष्टानि' ],
'Wantedtemplates' => [ 'प्रार्थितफलकानि' ],
'Watchlist' => [ 'निरीक्षा_सूची' ],
'Whatlinkshere' => [ 'किमपृष्ठ_सम्बद्धंकरोति' ],
'Withoutinterwiki' => [ 'आन्तरविकिहीन' ],
'Undelete' => [ 'पुनस्स्थापनम्', 'प्रत्यादिश्_परित्याग' ],
'Unlockdb' => [ 'दत्तांशोद्घाटनम्', 'विवृतविदाद्वारंतालक' ],
'Unusedcategories' => [ 'अप्रयुक्तवर्गाः', 'अप्रयूक्तवर्ग' ],
'Unusedimages' => [ 'अप्रयुक्तचित्राणि', 'अप्रयूक्तसंचिका' ],
'Unusedtemplates' => [ 'अप्रयुक्तफलकानि', 'अप्रयूक्तबिंबधर' ],
'Unwatchedpages' => [ 'अनिरीक्षितपृष्ठानि', 'अनिरिक्षीतपृष्ठ' ],
'Upload' => [ 'उपारोपणम्', 'भारंन्यस्यति' ],
'Userlogin' => [ 'सदस्यप्रवेश', 'सदस्यप्रवेशन' ],
'Userlogout' => [ 'सदस्यनिर्गमनम्', 'सदस्यबहिर्गमन' ],
'Userrights' => [ 'सदस्याधिकाराः', 'योजकआधिकार' ],
'Version' => [ 'संस्करणम्', 'आवृत्ती' ],
'Wantedcategories' => [ 'वाञ्छितवर्गाः', 'प्रार्थितवर्ग' ],
'Wantedfiles' => [ 'वाञ्छितसञ्चिकाः', 'प्रार्थितसंचिका' ],
'Wantedpages' => [ 'वाञ्छितपृष्ठानि', 'प्रार्थितपृष्टानि' ],
'Wantedtemplates' => [ 'वाञ्छितफलकानि', 'प्रार्थितफलकानि' ],
'Watchlist' => [ 'निरीक्षा_सूची', 'निरीक्षासूचिः' ],
'Whatlinkshere' => [ 'किमत्र_सँल्लग्नम्', 'किमपृष्ठ_सम्बद्धंकरोति' ],
'Withoutinterwiki' => [ 'अन्तर्विकिपरिसन्धिहीनम्', 'आन्तरविकिहीन' ],
];
$magicWords = [

1
maintenance/.htaccess Normal file
View File

@ -0,0 +1 @@
Deny from all

View File

@ -0,0 +1 @@
Deny from all

1
serialized/.htaccess Normal file
View File

@ -0,0 +1 @@
Deny from all

View File

@ -64,8 +64,12 @@ class MonoBookTemplate extends BaseTemplate {
[ 'id' => 'contentSub', 'lang' => $this->get( 'userlang' ), 'dir' => $this->get( 'dir' ) ],
$this->get( 'subtitle' )
) .
$this->getIfExists( 'undelete', [ 'wrapper' => 'div', [ 'id' => 'contentSub2' ] ] ) .
$this->getIfExists( 'newtalk', [ 'wrapper' => 'div', [ 'class' => 'usermessage' ] ] ) .
$this->getIfExists( 'undelete', [ 'wrapper' => 'div', 'parameters' => [
'id' => 'contentSub2'
] ] ) .
$this->getIfExists( 'newtalk', [ 'wrapper' => 'div', 'parameters' => [
'class' => 'usermessage'
] ] ) .
Html::rawElement( 'div', [ 'id' => 'jump-to-nav', 'class' => 'mw-jump' ],
$this->getMsg( 'jumpto' )->escaped() .
Html::element( 'a', [ 'href' => '#column-one' ],

View File

@ -33,20 +33,11 @@ $( function () {
} );
// Close menus on click outside
$( document ).click( function ( e ) {
$( document ).on( 'click touchstart', function ( e ) {
if ( $( e.target ).closest( '#menus-cover' ).length > 0 ) {
$( '#personal-inner' ).fadeOut( toggleTime );
$( '.sidebar-inner' ).fadeOut( toggleTime );
$( '#menus-cover' ).fadeOut( toggleTime );
}
} );
// Include alternative closing method for ios
$( window ).on( 'swiperight', function () {
if ( $( window ).width() < 851 ) {
$( '#personal-inner' ).fadeOut( toggleTime );
$( '.sidebar-inner' ).fadeOut( toggleTime );
$( '#menus-cover' ).fadeOut( toggleTime );
}
} );
} );

View File

@ -65,7 +65,6 @@
"skins.timeless.mobile": {
"targets": [ "desktop", "mobile" ],
"scripts": [
"resources/libraries/jquery.mobile.custom.js",
"resources/mobile.js"
]
}

1
tests/.htaccess Normal file
View File

@ -0,0 +1 @@
Deny from all

View File

@ -41,7 +41,8 @@ class DbTestRecorder extends TestRecorder {
|| !$this->db->tableExists( 'testitem' )
) {
print "WARNING> `testrun` table not found in database. Trying to create table.\n";
$this->db->sourceFile( $this->db->patchPath( 'patch-testrun.sql' ) );
$updater = DatabaseUpdater::newForDB( $this->db );
$this->db->sourceFile( $updater->patchPath( $this->db, 'patch-testrun.sql' ) );
echo "OK, resuming.\n";
}

View File

@ -17,8 +17,7 @@ class VersionCheckerTest extends PHPUnit\Framework\TestCase {
'FakeExtension' => [
'MediaWiki' => $constraint,
],
] )
);
] ) );
}
public static function provideCheck() {
@ -50,8 +49,7 @@ class VersionCheckerTest extends PHPUnit\Framework\TestCase {
*/
public function testType( $given, $expected ) {
$checker = new VersionChecker( '1.0.0' );
$checker
->setLoadedExtensionsAndSkins( [
$checker->setLoadedExtensionsAndSkins( [
'FakeDependency' => [
'version' => '1.0.0',
],
@ -59,8 +57,7 @@ class VersionCheckerTest extends PHPUnit\Framework\TestCase {
] );
$this->assertEquals( $expected, $checker->checkArray( [
'FakeExtension' => $given,
] )
);
] ) );
}
public static function provideType() {
@ -69,22 +66,22 @@ class VersionCheckerTest extends PHPUnit\Framework\TestCase {
[
[
'extensions' => [
'FakeDependency' => '1.0.0'
]
'FakeDependency' => '1.0.0',
],
],
[]
[],
],
[
[
'MediaWiki' => '1.0.0'
'MediaWiki' => '1.0.0',
],
[]
[],
],
[
[
'extensions' => [
'NoVersionGiven' => '*'
]
'NoVersionGiven' => '*',
],
],
[],
],
@ -92,39 +89,59 @@ class VersionCheckerTest extends PHPUnit\Framework\TestCase {
[
'extensions' => [
'NoVersionGiven' => '1.0',
]
],
],
[
[
'incompatible' => 'FakeExtension',
'type' => 'incompatible-extensions',
'msg' => 'NoVersionGiven does not expose its version, but FakeExtension requires: 1.0.',
],
],
[ [
'incompatible' => 'FakeExtension',
'type' => 'incompatible-extensions',
'msg' => 'NoVersionGiven does not expose its version, but FakeExtension requires: 1.0.'
] ],
],
[
[
'extensions' => [
'Missing' => '*',
]
],
],
[
[
'missing' => 'Missing',
'type' => 'missing-extensions',
'msg' => 'FakeExtension requires Missing to be installed.',
],
],
[ [
'missing' => 'Missing',
'type' => 'missing-extensions',
'msg' => 'FakeExtension requires Missing to be installed.',
] ],
],
[
[
'extensions' => [
'FakeDependency' => '2.0.0',
]
],
],
[ [
'incompatible' => 'FakeExtension',
'type' => 'incompatible-extensions',
// phpcs:ignore Generic.Files.LineLength.TooLong
'msg' => 'FakeExtension is not compatible with the current installed version of FakeDependency (1.0.0), it requires: 2.0.0.'
] ],
]
[
[
'incompatible' => 'FakeExtension',
'type' => 'incompatible-extensions',
// phpcs:ignore Generic.Files.LineLength.TooLong
'msg' => 'FakeExtension is not compatible with the current installed version of FakeDependency (1.0.0), it requires: 2.0.0.',
],
],
],
[
[
'skins' => [
'FakeSkin' => '*',
],
],
[
[
'missing' => 'FakeSkin',
'type' => 'missing-skins',
'msg' => 'FakeExtension requires FakeSkin to be installed.',
],
],
],
];
}
@ -134,29 +151,26 @@ class VersionCheckerTest extends PHPUnit\Framework\TestCase {
*/
public function testInvalidConstraint() {
$checker = new VersionChecker( '1.0.0' );
$checker
->setLoadedExtensionsAndSkins( [
$checker->setLoadedExtensionsAndSkins( [
'FakeDependency' => [
'version' => 'not really valid',
],
] );
$this->assertEquals(
[ [
$this->assertEquals( [
[
'type' => 'invalid-version',
'msg' => "FakeDependency does not have a valid version string."
] ],
$checker->checkArray( [
'FakeExtension' => [
'extensions' => [
'FakeDependency' => '1.24.3',
],
'msg' => "FakeDependency does not have a valid version string.",
],
], $checker->checkArray( [
'FakeExtension' => [
'extensions' => [
'FakeDependency' => '1.24.3',
],
] )
);
],
] ) );
$checker = new VersionChecker( '1.0.0' );
$checker
->setLoadedExtensionsAndSkins( [
$checker->setLoadedExtensionsAndSkins( [
'FakeDependency' => [
'version' => '1.24.3',
],
@ -166,7 +180,28 @@ class VersionCheckerTest extends PHPUnit\Framework\TestCase {
$checker->checkArray( [
'FakeExtension' => [
'FakeDependency' => 'not really valid',
]
],
] );
}
/**
* T197478
*/
public function testInvalidDependency() {
$checker = new VersionChecker( '1.0.0' );
$this->setExpectedException( UnexpectedValueException::class,
'Dependency type skin unknown in FakeExtension' );
$this->assertEquals( [
[
'type' => 'invalid-version',
'msg' => 'FakeDependency does not have a valid version string.',
],
], $checker->checkArray( [
'FakeExtension' => [
'skin' => [
'FakeSkin' => '*',
],
],
] ) );
}
}

1
tests/qunit/.htaccess Normal file
View File

@ -0,0 +1 @@
Allow from all

1
vendor/.htaccess vendored Normal file
View File

@ -0,0 +1 @@
Deny from all

View File

@ -0,0 +1,2 @@
.easymin
Autoloader.php