},
banana: {
options: {
+ requireLowerCase: false,
disallowBlankTranslations: false
},
core: 'languages/i18n/',
* User::isBlocked() is deprecated since it does not tell you if the user is
blocked from editing a particular page. Use User::getBlock() or
PermissionManager::isBlockedFrom() or PermissionManager::userCan() instead.
-* …
+* User::isLocallyBlockedProxy and User::inDnsBlacklist are deprecated and moved
+ to the BlockManager as private helper methods.
+* User::isDnsBlacklisted is deprecated. Use BlockManager::isDnsBlacklisted
+ instead.
+* The Config argument to ChangesListSpecialPage::checkStructuredFilterUiEnabled
+ is deprecated. Pass only the User argument.
=== Other changes in 1.34 ===
* …
'MediaWikiVersionFetcher' => __DIR__ . '/includes/MediaWikiVersionFetcher.php',
'MediaWiki\\ChangeTags\\Taggable' => __DIR__ . '/includes/changetags/Taggable.php',
'MediaWiki\\Config\\ConfigRepository' => __DIR__ . '/includes/config/ConfigRepository.php',
+ 'MediaWiki\\Config\\ServiceOptions' => __DIR__ . '/includes/config/ServiceOptions.php',
'MediaWiki\\DB\\PatchFileLocation' => __DIR__ . '/includes/db/PatchFileLocation.php',
'MediaWiki\\Diff\\ComplexityException' => __DIR__ . '/includes/diff/ComplexityException.php',
'MediaWiki\\Diff\\WordAccumulator' => __DIR__ . '/includes/diff/WordAccumulator.php',
$start = Wikimedia\base_convert( $block->getRangeStart(), 16, 10 );
$size = log( $end - $start + 1, 2 );
- # This has the nice property that a /32 block is ranked equally with a
- # single-IP block, which is exactly what it is...
+ # Rank a range block covering a single IP equally with a single-IP block
$score = self::TYPE_RANGE - 1 + ( $size / 128 );
} else {
* @since 1.29
*/
class ConfiguredReadOnlyMode {
- /** @var Config */
- private $config;
-
- /** @var string|bool|null */
- private $fileReason;
+ /** @var string|boolean|null */
+ private $reason;
/** @var string|null */
- private $overrideReason;
+ private $reasonFile;
- public function __construct( Config $config ) {
- $this->config = $config;
+ /**
+ * @param string|bool|null $reason Current reason for read-only mode, if known. null means look
+ * in $reasonFile instead.
+ * @param string|null $reasonFile A file to look in for a reason, if $reason is null. If it
+ * exists and is non-empty, its contents are treated as the reason for read-only mode.
+ * Otherwise, the wiki is not read-only.
+ */
+ public function __construct( $reason, $reasonFile = null ) {
+ if ( $reason instanceof Config ) {
+ // Before 1.34 we passed a whole Config object, which was overkill
+ wfDeprecated( __METHOD__ . ' with Config passed to constructor', '1.34' );
+ $reason = $reason->get( 'ReadOnly' );
+ $reasonFile = $reason->get( 'ReadOnlyFile' );
+ }
+ $this->reason = $reason;
+ $this->reasonFile = $reasonFile;
}
/**
* @return string|bool String when in read-only mode; false otherwise
*/
public function getReason() {
- if ( $this->overrideReason !== null ) {
- return $this->overrideReason;
+ if ( $this->reason !== null ) {
+ return $this->reason;
}
- $confReason = $this->config->get( 'ReadOnly' );
- if ( $confReason !== null ) {
- return $confReason;
+ if ( $this->reasonFile === null ) {
+ return false;
}
- if ( $this->fileReason === null ) {
- // Cache for faster access next time
- $readOnlyFile = $this->config->get( 'ReadOnlyFile' );
- if ( is_file( $readOnlyFile ) && filesize( $readOnlyFile ) > 0 ) {
- $this->fileReason = file_get_contents( $readOnlyFile );
- } else {
- $this->fileReason = false;
- }
+ // Try the reason file
+ if ( is_file( $this->reasonFile ) && filesize( $this->reasonFile ) > 0 ) {
+ $this->reason = file_get_contents( $this->reasonFile );
}
- return $this->fileReason;
+ // No need to try the reason file again
+ $this->reasonFile = null;
+ return $this->reason ?? false;
}
/**
* @param string|null $msg
*/
public function setReason( $msg ) {
- $this->overrideReason = $msg;
+ $this->reason = $msg;
}
}
use Hooks;
use IBufferingStatsdDataFactory;
use Liuggio\StatsdClient\Factory\StatsdDataFactoryInterface;
+use MediaWiki\Block\BlockManager;
use MediaWiki\Block\BlockRestrictionStore;
use MediaWiki\Http\HttpRequestFactory;
use MediaWiki\Permissions\PermissionManager;
return $this->getService( 'BlobStoreFactory' );
}
+ /**
+ * @since 1.34
+ * @return BlockManager
+ */
+ public function getBlockManager() : BlockManager {
+ return $this->getService( 'BlockManager' );
+ }
+
/**
* @since 1.33
* @return BlockRestrictionStore
use Liuggio\StatsdClient\Factory\StatsdDataFactoryInterface;
use MediaWiki\Auth\AuthManager;
+use MediaWiki\Block\BlockManager;
use MediaWiki\Block\BlockRestrictionStore;
use MediaWiki\Config\ConfigRepository;
+use MediaWiki\Config\ServiceOptions;
use MediaWiki\Interwiki\ClassicInterwikiLookup;
use MediaWiki\Interwiki\InterwikiLookup;
use MediaWiki\Linker\LinkRenderer;
return new BlobStoreFactory(
$services->getDBLoadBalancerFactory(),
$services->getMainWANObjectCache(),
- $services->getMainConfig(),
+ new ServiceOptions( BlobStoreFactory::$constructorOptions,
+ $services->getMainConfig() ),
$services->getContentLanguage()
);
},
+ 'BlockManager' => function ( MediaWikiServices $services ) : BlockManager {
+ $config = $services->getMainConfig();
+ $context = RequestContext::getMain();
+ return new BlockManager(
+ $context->getUser(),
+ $context->getRequest(),
+ $config->get( 'ApplyIpBlocksToXff' ),
+ $config->get( 'CookieSetOnAutoblock' ),
+ $config->get( 'CookieSetOnIpBlock' ),
+ $config->get( 'DnsBlacklistUrls' ),
+ $config->get( 'EnableDnsBlacklist' ),
+ $config->get( 'ProxyList' ),
+ $config->get( 'ProxyWhitelist' ),
+ $config->get( 'SoftBlockRanges' )
+ );
+ },
+
'BlockRestrictionStore' => function ( MediaWikiServices $services ) : BlockRestrictionStore {
return new BlockRestrictionStore(
$services->getDBLoadBalancer()
},
'ConfiguredReadOnlyMode' => function ( MediaWikiServices $services ) : ConfiguredReadOnlyMode {
- return new ConfiguredReadOnlyMode( $services->getMainConfig() );
+ $config = $services->getMainConfig();
+ return new ConfiguredReadOnlyMode(
+ $config->get( 'ReadOnly' ),
+ $config->get( 'ReadOnlyFile' )
+ );
},
'ContentLanguage' => function ( MediaWikiServices $services ) : Language {
$lbConf = MWLBFactory::applyDefaultConfig(
$mainConfig->get( 'LBFactoryConf' ),
- $mainConfig,
+ new ServiceOptions( MWLBFactory::$applyDefaultConfigOptions, $mainConfig ),
$services->getConfiguredReadOnlyMode(),
$services->getLocalServerObjectCache(),
$services->getMainObjectStash(),
$class = MWLBFactory::getLBFactoryClass( $lbConf );
$instance = new $class( $lbConf );
- MWLBFactory::setSchemaAliases( $instance, $mainConfig );
+ MWLBFactory::setSchemaAliases( $instance, $mainConfig->get( 'DBtype' ) );
return $instance;
},
'PreferencesFactory' => function ( MediaWikiServices $services ) : PreferencesFactory {
$factory = new DefaultPreferencesFactory(
- $services->getMainConfig(),
+ new ServiceOptions(
+ DefaultPreferencesFactory::$constructorOptions, $services->getMainConfig() ),
$services->getContentLanguage(),
AuthManager::singleton(),
$services->getLinkRendererFactory()->create()
},
'ResourceLoader' => function ( MediaWikiServices $services ) : ResourceLoader {
+ // @todo This should not take a Config object, but it's not so easy to remove because it
+ // exposes it in a getter, which is actually used.
global $IP;
$config = $services->getMainConfig();
},
'SearchEngineConfig' => function ( MediaWikiServices $services ) : SearchEngineConfig {
+ // @todo This should not take a Config object, but it's not so easy to remove because it
+ // exposes it in a getter, which is actually used.
return new SearchEngineConfig( $services->getMainConfig(),
$services->getContentLanguage() );
},
},
'SpecialPageFactory' => function ( MediaWikiServices $services ) : SpecialPageFactory {
- $config = $services->getMainConfig();
- $options = [];
- foreach ( SpecialPageFactory::$constructorOptions as $key ) {
- $options[$key] = $config->get( $key );
- }
return new SpecialPageFactory(
- $options,
+ new ServiceOptions(
+ SpecialPageFactory::$constructorOptions, $services->getMainConfig() ),
$services->getContentLanguage()
);
},
namespace MediaWiki\Storage;
-use Config;
use Language;
+use MediaWiki\Config\ServiceOptions;
use WANObjectCache;
use Wikimedia\Rdbms\LBFactory;
private $cache;
/**
- * @var Config
+ * @var ServiceOptions
*/
- private $config;
+ private $options;
/**
* @var Language
*/
private $contLang;
+ /**
+ * TODO Make this a const when HHVM support is dropped (T192166)
+ *
+ * @var array
+ * @since 1.34
+ */
+ public static $constructorOptions = [
+ 'CompressRevisions',
+ 'DefaultExternalStore',
+ 'LegacyEncoding',
+ 'RevisionCacheExpiry',
+ ];
+
public function __construct(
LBFactory $lbFactory,
WANObjectCache $cache,
- Config $mainConfig,
+ ServiceOptions $options,
Language $contLang
) {
+ $options->assertRequiredOptions( self::$constructorOptions );
+
$this->lbFactory = $lbFactory;
$this->cache = $cache;
- $this->config = $mainConfig;
+ $this->options = $options;
$this->contLang = $contLang;
}
$wikiId
);
- $store->setCompressBlobs( $this->config->get( 'CompressRevisions' ) );
- $store->setCacheExpiry( $this->config->get( 'RevisionCacheExpiry' ) );
- $store->setUseExternalStore( $this->config->get( 'DefaultExternalStore' ) !== false );
+ $store->setCompressBlobs( $this->options->get( 'CompressRevisions' ) );
+ $store->setCacheExpiry( $this->options->get( 'RevisionCacheExpiry' ) );
+ $store->setUseExternalStore( $this->options->get( 'DefaultExternalStore' ) !== false );
- if ( $this->config->get( 'LegacyEncoding' ) ) {
- $store->setLegacyEncoding( $this->config->get( 'LegacyEncoding' ), $this->contLang );
+ if ( $this->options->get( 'LegacyEncoding' ) ) {
+ $store->setLegacyEncoding( $this->options->get( 'LegacyEncoding' ), $this->contLang );
}
return $store;
"Dvorapa",
"Matěj Suchánek",
"Ilimanaq29",
- "Patriccck"
+ "Patriccck",
+ "Ján Kepler"
]
},
"apihelp-main-extended-description": "<div class=\"hlist plainlinks api-main-links\">\n* [[mw:Special:MyLanguage/API:Main_page|Dokumentace]]\n* [[mw:Special:MyLanguage/API:FAQ|Otázky a odpovědi]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api E-mailová konference]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce Oznámení k API]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R Chyby a požadavky]\n</div>\n<strong>Stav:</strong> Všechny funkce uvedené na této stránce by měly fungovat, ale API se stále aktivně vyvíjí a může se kdykoli změnit. Upozornění na změny získáte přihlášením se k [https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ e-mailové konferenci mediawiki-api-announce].\n\n<strong>Chybné požadavky:</strong> Pokud jsou do API zaslány chybné požadavky, bude vrácena HTTP hlavička s klíčem „MediaWiki-API-Error“ a hodnota této hlavičky a chybový kód budou nastaveny na stejnou hodnotu. Více informací najdete [[mw:Special:MyLanguage/API:Errors_and_warnings|v dokumentaci]].\n\n<p class=\"mw-apisandbox-link\"><strong>Testování:</strong> Pro jednoduché testování požadavků na API zkuste [[Special:ApiSandbox]].</p>",
"apihelp-edit-param-pageid": "ID stránky, která se má editovat. Není možné použít společně s <var>$1title</var>.",
"apihelp-edit-param-sectiontitle": "Název nové sekce.",
"apihelp-edit-param-text": "Obsah stránky.",
- "apihelp-edit-param-minor": "Malá editace.",
+ "apihelp-edit-param-minor": "Označit toto jako malou editaci",
"apihelp-edit-param-notminor": "Nemalá editace.",
"apihelp-edit-param-bot": "Označit tuto editaci jako editaci robota.",
"apihelp-edit-param-createonly": "Needitovat stránku, pokud již existuje.",
"apihelp-edit-param-text": "Sidans innehåll.",
"apihelp-edit-param-summary": "Redigeringssammanfattning. Även avsnittets rubrik när $1section=new och $1sectiontitle inte anges.",
"apihelp-edit-param-tags": "Ändra taggar till att gälla för revideringen.",
- "apihelp-edit-param-minor": "Mindre redigering.",
- "apihelp-edit-param-notminor": "Icke-mindre redigering.",
+ "apihelp-edit-param-minor": "Markera denna redigering som en mindre redigering.",
+ "apihelp-edit-param-notminor": "Markera inte denna redigering som en mindre redigering även om användarinställningen \"{{int:tog-minordefault}}\" är inställd.",
"apihelp-edit-param-bot": "Markera denna redigering som en robotredigering.",
"apihelp-edit-param-basetimestamp": "Tidsstämpel för grundversionen, används för att upptäcka redigeringskonflikter. Kan erhållas genom [[Special:ApiHelp/query+revisions|action=query&prop=revisions&rvprop=timestamp]].",
"apihelp-edit-param-starttimestamp": "Tidsstämpel för när redigeringsprocessen började, används för att upptäcka redigeringskonflikter. Ett lämpligt värde kan erhållas via <var>[[Special:ApiHelp/main|curtimestamp]]</var> när redigeringsprocessen startas (t.ex. när sidans innehåll laddas för redigering).",
}
$ip = $this->getRequest()->getIP();
- if ( $creator->isDnsBlacklisted( $ip, true /* check $wgProxyWhitelist */ ) ) {
+ if (
+ MediaWikiServices::getInstance()->getBlockManager()
+ ->isDnsBlacklisted( $ip, true /* check $wgProxyWhitelist */ )
+ ) {
return Status::newFatal( 'sorbs_create_account_reason' );
}
--- /dev/null
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+namespace MediaWiki\Block;
+
+use Block;
+use IP;
+use User;
+use WebRequest;
+use Wikimedia\IPSet;
+use MediaWiki\User\UserIdentity;
+
+/**
+ * A service class for checking blocks.
+ * To obtain an instance, use MediaWikiServices::getInstance()->getBlockManager().
+ *
+ * @since 1.34 Refactored from User and Block.
+ */
+class BlockManager {
+ // TODO: This should be UserIdentity instead of User
+ /** @var User */
+ private $currentUser;
+
+ /** @var WebRequest */
+ private $currentRequest;
+
+ /** @var bool */
+ private $applyIpBlocksToXff;
+
+ /** @var bool */
+ private $cookieSetOnAutoblock;
+
+ /** @var bool */
+ private $cookieSetOnIpBlock;
+
+ /** @var array */
+ private $dnsBlacklistUrls;
+
+ /** @var bool */
+ private $enableDnsBlacklist;
+
+ /** @var array */
+ private $proxyList;
+
+ /** @var array */
+ private $proxyWhitelist;
+
+ /** @var array */
+ private $softBlockRanges;
+
+ /**
+ * @param User $currentUser
+ * @param WebRequest $currentRequest
+ * @param bool $applyIpBlocksToXff
+ * @param bool $cookieSetOnAutoblock
+ * @param bool $cookieSetOnIpBlock
+ * @param array $dnsBlacklistUrls
+ * @param bool $enableDnsBlacklist
+ * @param array $proxyList
+ * @param array $proxyWhitelist
+ * @param array $softBlockRanges
+ */
+ public function __construct(
+ $currentUser,
+ $currentRequest,
+ $applyIpBlocksToXff,
+ $cookieSetOnAutoblock,
+ $cookieSetOnIpBlock,
+ $dnsBlacklistUrls,
+ $enableDnsBlacklist,
+ $proxyList,
+ $proxyWhitelist,
+ $softBlockRanges
+ ) {
+ $this->currentUser = $currentUser;
+ $this->currentRequest = $currentRequest;
+ $this->applyIpBlocksToXff = $applyIpBlocksToXff;
+ $this->cookieSetOnAutoblock = $cookieSetOnAutoblock;
+ $this->cookieSetOnIpBlock = $cookieSetOnIpBlock;
+ $this->dnsBlacklistUrls = $dnsBlacklistUrls;
+ $this->enableDnsBlacklist = $enableDnsBlacklist;
+ $this->proxyList = $proxyList;
+ $this->proxyWhitelist = $proxyWhitelist;
+ $this->softBlockRanges = $softBlockRanges;
+ }
+
+ /**
+ * Get the blocks that apply to a user and return the most relevant one.
+ *
+ * TODO: $user should be UserIdentity instead of User
+ *
+ * @internal This should only be called by User::getBlockedStatus
+ * @param User $user
+ * @param bool $fromReplica Whether to check the replica DB first.
+ * To improve performance, non-critical checks are done against replica DBs.
+ * Check when actually saving should be done against master.
+ * @return Block|null The most relevant block, or null if there is no block.
+ */
+ public function getUserBlock( User $user, $fromReplica ) {
+ $isAnon = $user->getId() === 0;
+
+ // TODO: If $user is the current user, we should use the current request. Otherwise,
+ // we should not look for XFF or cookie blocks.
+ $request = $user->getRequest();
+
+ # We only need to worry about passing the IP address to the Block generator if the
+ # user is not immune to autoblocks/hardblocks, and they are the current user so we
+ # know which IP address they're actually coming from
+ $ip = null;
+ $sessionUser = $this->currentUser;
+ // the session user is set up towards the end of Setup.php. Until then,
+ // assume it's a logged-out user.
+ $globalUserName = $sessionUser->isSafeToLoad()
+ ? $sessionUser->getName()
+ : IP::sanitizeIP( $this->currentRequest->getIP() );
+ if ( $user->getName() === $globalUserName && !$user->isAllowed( 'ipblock-exempt' ) ) {
+ $ip = $this->currentRequest->getIP();
+ }
+
+ // User/IP blocking
+ // TODO: remove dependency on Block
+ $block = Block::newFromTarget( $user, $ip, !$fromReplica );
+
+ // Cookie blocking
+ if ( !$block instanceof Block ) {
+ $block = $this->getBlockFromCookieValue( $user, $request );
+ }
+
+ // Proxy blocking
+ if ( !$block instanceof Block && $ip !== null && !in_array( $ip, $this->proxyWhitelist ) ) {
+ // Local list
+ if ( $this->isLocallyBlockedProxy( $ip ) ) {
+ $block = new Block( [
+ 'byText' => wfMessage( 'proxyblocker' )->text(),
+ 'reason' => wfMessage( 'proxyblockreason' )->plain(),
+ 'address' => $ip,
+ 'systemBlock' => 'proxy',
+ ] );
+ } elseif ( $isAnon && $this->isDnsBlacklisted( $ip ) ) {
+ $block = new Block( [
+ 'byText' => wfMessage( 'sorbs' )->text(),
+ 'reason' => wfMessage( 'sorbsreason' )->plain(),
+ 'address' => $ip,
+ 'systemBlock' => 'dnsbl',
+ ] );
+ }
+ }
+
+ // (T25343) Apply IP blocks to the contents of XFF headers, if enabled
+ if ( !$block instanceof Block
+ && $this->applyIpBlocksToXff
+ && $ip !== null
+ && !in_array( $ip, $this->proxyWhitelist )
+ ) {
+ $xff = $request->getHeader( 'X-Forwarded-For' );
+ $xff = array_map( 'trim', explode( ',', $xff ) );
+ $xff = array_diff( $xff, [ $ip ] );
+ // TODO: remove dependency on Block
+ $xffblocks = Block::getBlocksForIPList( $xff, $isAnon, !$fromReplica );
+ // TODO: remove dependency on Block
+ $block = Block::chooseBlock( $xffblocks, $xff );
+ if ( $block instanceof Block ) {
+ # Mangle the reason to alert the user that the block
+ # originated from matching the X-Forwarded-For header.
+ $block->setReason( wfMessage( 'xffblockreason', $block->getReason() )->plain() );
+ }
+ }
+
+ if ( !$block instanceof Block
+ && $ip !== null
+ && $isAnon
+ && IP::isInRanges( $ip, $this->softBlockRanges )
+ ) {
+ $block = new Block( [
+ 'address' => $ip,
+ 'byText' => 'MediaWiki default',
+ 'reason' => wfMessage( 'softblockrangesreason', $ip )->plain(),
+ 'anonOnly' => true,
+ 'systemBlock' => 'wgSoftBlockRanges',
+ ] );
+ }
+
+ return $block;
+ }
+
+ /**
+ * Try to load a Block from an ID given in a cookie value.
+ *
+ * @param UserIdentity $user
+ * @param WebRequest $request
+ * @return Block|bool The Block object, or false if none could be loaded.
+ */
+ private function getBlockFromCookieValue(
+ UserIdentity $user,
+ WebRequest $request
+ ) {
+ $blockCookieVal = $request->getCookie( 'BlockID' );
+ $response = $request->response();
+
+ // Make sure there's something to check. The cookie value must start with a number.
+ if ( strlen( $blockCookieVal ) < 1 || !is_numeric( substr( $blockCookieVal, 0, 1 ) ) ) {
+ return false;
+ }
+ // Load the Block from the ID in the cookie.
+ // TODO: remove dependency on Block
+ $blockCookieId = Block::getIdFromCookieValue( $blockCookieVal );
+ if ( $blockCookieId !== null ) {
+ // An ID was found in the cookie.
+ // TODO: remove dependency on Block
+ $tmpBlock = Block::newFromID( $blockCookieId );
+ if ( $tmpBlock instanceof Block ) {
+ switch ( $tmpBlock->getType() ) {
+ case Block::TYPE_USER:
+ $blockIsValid = !$tmpBlock->isExpired() && $tmpBlock->isAutoblocking();
+ $useBlockCookie = ( $this->cookieSetOnAutoblock === true );
+ break;
+ case Block::TYPE_IP:
+ case Block::TYPE_RANGE:
+ // If block is type IP or IP range, load only if user is not logged in (T152462)
+ $blockIsValid = !$tmpBlock->isExpired() && $user->getId() === 0;
+ $useBlockCookie = ( $this->cookieSetOnIpBlock === true );
+ break;
+ default:
+ $blockIsValid = false;
+ $useBlockCookie = false;
+ }
+
+ if ( $blockIsValid && $useBlockCookie ) {
+ // Use the block.
+ return $tmpBlock;
+ }
+
+ // If the block is not valid, remove the cookie.
+ // TODO: remove dependency on Block
+ Block::clearCookie( $response );
+ } else {
+ // If the block doesn't exist, remove the cookie.
+ // TODO: remove dependency on Block
+ Block::clearCookie( $response );
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Check if an IP address is in the local proxy list
+ *
+ * @param string $ip
+ * @return bool
+ */
+ private function isLocallyBlockedProxy( $ip ) {
+ if ( !$this->proxyList ) {
+ return false;
+ }
+
+ if ( !is_array( $this->proxyList ) ) {
+ // Load values from the specified file
+ $this->proxyList = array_map( 'trim', file( $this->proxyList ) );
+ }
+
+ $resultProxyList = [];
+ $deprecatedIPEntries = [];
+
+ // backward compatibility: move all ip addresses in keys to values
+ foreach ( $this->proxyList as $key => $value ) {
+ $keyIsIP = IP::isIPAddress( $key );
+ $valueIsIP = IP::isIPAddress( $value );
+ if ( $keyIsIP && !$valueIsIP ) {
+ $deprecatedIPEntries[] = $key;
+ $resultProxyList[] = $key;
+ } elseif ( $keyIsIP && $valueIsIP ) {
+ $deprecatedIPEntries[] = $key;
+ $resultProxyList[] = $key;
+ $resultProxyList[] = $value;
+ } else {
+ $resultProxyList[] = $value;
+ }
+ }
+
+ if ( $deprecatedIPEntries ) {
+ wfDeprecated(
+ 'IP addresses in the keys of $wgProxyList (found the following IP addresses in keys: ' .
+ implode( ', ', $deprecatedIPEntries ) . ', please move them to values)', '1.30' );
+ }
+
+ $proxyListIPSet = new IPSet( $resultProxyList );
+ return $proxyListIPSet->match( $ip );
+ }
+
+ /**
+ * Whether the given IP is in a DNS blacklist.
+ *
+ * @param string $ip IP to check
+ * @param bool $checkWhitelist Whether to check the whitelist first
+ * @return bool True if blacklisted.
+ */
+ public function isDnsBlacklisted( $ip, $checkWhitelist = false ) {
+ if ( !$this->enableDnsBlacklist ||
+ ( $checkWhitelist && in_array( $ip, $this->proxyWhitelist ) )
+ ) {
+ return false;
+ }
+
+ return $this->inDnsBlacklist( $ip, $this->dnsBlacklistUrls );
+ }
+
+ /**
+ * Whether the given IP is in a given DNS blacklist.
+ *
+ * @param string $ip IP to check
+ * @param array $bases Array of Strings: URL of the DNS blacklist
+ * @return bool True if blacklisted.
+ */
+ private function inDnsBlacklist( $ip, array $bases ) {
+ $found = false;
+ // @todo FIXME: IPv6 ??? (https://bugs.php.net/bug.php?id=33170)
+ if ( IP::isIPv4( $ip ) ) {
+ // Reverse IP, T23255
+ $ipReversed = implode( '.', array_reverse( explode( '.', $ip ) ) );
+
+ foreach ( $bases as $base ) {
+ // Make hostname
+ // If we have an access key, use that too (ProjectHoneypot, etc.)
+ $basename = $base;
+ if ( is_array( $base ) ) {
+ if ( count( $base ) >= 2 ) {
+ // Access key is 1, base URL is 0
+ $host = "{$base[1]}.$ipReversed.{$base[0]}";
+ } else {
+ $host = "$ipReversed.{$base[0]}";
+ }
+ $basename = $base[0];
+ } else {
+ $host = "$ipReversed.$base";
+ }
+
+ // Send query
+ $ipList = gethostbynamel( $host );
+
+ if ( $ipList ) {
+ wfDebugLog( 'dnsblacklist', "Hostname $host is {$ipList[0]}, it's a proxy says $basename!" );
+ $found = true;
+ break;
+ }
+
+ wfDebugLog( 'dnsblacklist', "Requested $host, not found in $basename." );
+ }
+ }
+
+ return $found;
+ }
+
+}
--- /dev/null
+<?php
+
+namespace MediaWiki\Config;
+
+use Config;
+use InvalidArgumentException;
+use Wikimedia\Assert\Assert;
+
+/**
+ * A class for passing options to services. It can be constructed from a Config, and in practice
+ * most options will be taken from site configuration, but they don't have to be. The options passed
+ * are copied and will not reflect subsequent updates to site configuration (assuming they're not
+ * objects).
+ *
+ * Services that take this type as a parameter to their constructor should specify a list of the
+ * keys they expect to receive in an array. The convention is to make it a public static variable
+ * called $constructorOptions. (When we drop HHVM support -- see T192166 -- it should become a
+ * const.) In the constructor, they should call assertRequiredOptions() to make sure that they
+ * weren't passed too few or too many options. This way it's clear what each class depends on, and
+ * that it's getting passed the correct set of options. (This means there are no optional options.
+ * This makes sense for services, since they shouldn't be constructed by outside code.)
+ *
+ * @since 1.34
+ */
+class ServiceOptions {
+ private $options = [];
+
+ /**
+ * @param string[] $keys Which keys to extract from $sources
+ * @param Config|array ...$sources Each source is either a Config object or an array. If the
+ * same key is present in two sources, the first one takes precedence. Keys that are not in
+ * $keys are ignored.
+ * @throws InvalidArgumentException if one of $keys is not found in any of $sources
+ */
+ public function __construct( array $keys, ...$sources ) {
+ foreach ( $keys as $key ) {
+ foreach ( $sources as $source ) {
+ if ( $source instanceof Config ) {
+ if ( $source->has( $key ) ) {
+ $this->options[$key] = $source->get( $key );
+ continue 2;
+ }
+ } else {
+ if ( array_key_exists( $key, $source ) ) {
+ $this->options[$key] = $source[$key];
+ continue 2;
+ }
+ }
+ }
+ throw new InvalidArgumentException( "Key \"$key\" not found in input sources" );
+ }
+ }
+
+ /**
+ * Assert that the list of options provided in this instance exactly match $expectedKeys,
+ * without regard for order.
+ *
+ * @param string[] $expectedKeys
+ */
+ public function assertRequiredOptions( array $expectedKeys ) {
+ $actualKeys = array_keys( $this->options );
+ $extraKeys = array_diff( $actualKeys, $expectedKeys );
+ $missingKeys = array_diff( $expectedKeys, $actualKeys );
+ Assert::precondition( !$extraKeys && !$missingKeys,
+ (
+ $extraKeys
+ ? 'Unsupported options passed: ' . implode( ', ', $extraKeys ) . '!'
+ : ''
+ ) . ( $extraKeys && $missingKeys ? ' ' : '' ) . (
+ $missingKeys
+ ? 'Required options missing: ' . implode( ', ', $missingKeys ) . '!'
+ : ''
+ )
+ );
+ }
+
+ /**
+ * @param string $key
+ * @return mixed
+ */
+ public function get( $key ) {
+ if ( !array_key_exists( $key, $this->options ) ) {
+ throw new InvalidArgumentException( "Unrecognized option \"$key\"" );
+ }
+ return $this->options[$key];
+ }
+}
* @ingroup Database
*/
+use MediaWiki\Config\ServiceOptions;
use MediaWiki\Logger\LoggerFactory;
use Wikimedia\Rdbms\LBFactory;
use Wikimedia\Rdbms\DatabaseDomain;
/** @var array Cache of already-logged deprecation messages */
private static $loggedDeprecations = [];
+ /**
+ * TODO Make this a const when HHVM support is dropped (T192166)
+ *
+ * @var array
+ * @since 1.34
+ */
+ public static $applyDefaultConfigOptions = [
+ 'DBcompress',
+ 'DBDefaultGroup',
+ 'DBmwschema',
+ 'DBname',
+ 'DBpassword',
+ 'DBport',
+ 'DBprefix',
+ 'DBserver',
+ 'DBservers',
+ 'DBssl',
+ 'DBtype',
+ 'DBuser',
+ 'DBWindowsAuthentication',
+ 'DebugDumpSql',
+ 'ExternalServers',
+ 'SQLiteDataDir',
+ 'SQLMode',
+ ];
+
/**
* @param array $lbConf Config for LBFactory::__construct()
- * @param Config $mainConfig Main config object from MediaWikiServices
+ * @param ServiceOptions $options
* @param ConfiguredReadOnlyMode $readOnlyMode
* @param BagOStuff $srvCace
* @param BagOStuff $mainStash
*/
public static function applyDefaultConfig(
array $lbConf,
- Config $mainConfig,
+ ServiceOptions $options,
ConfiguredReadOnlyMode $readOnlyMode,
BagOStuff $srvCace,
BagOStuff $mainStash,
WANObjectCache $wanCache
) {
+ $options->assertRequiredOptions( self::$applyDefaultConfigOptions );
+
global $wgCommandLineMode;
$typesWithSchema = self::getDbTypesWithSchemas();
$lbConf += [
'localDomain' => new DatabaseDomain(
- $mainConfig->get( 'DBname' ),
- $mainConfig->get( 'DBmwschema' ),
- $mainConfig->get( 'DBprefix' )
+ $options->get( 'DBname' ),
+ $options->get( 'DBmwschema' ),
+ $options->get( 'DBprefix' )
),
'profiler' => function ( $section ) {
return Profiler::instance()->scopedProfileIn( $section );
'cliMode' => $wgCommandLineMode,
'hostname' => wfHostname(),
'readOnlyReason' => $readOnlyMode->getReason(),
- 'defaultGroup' => $mainConfig->get( 'DBDefaultGroup' ),
+ 'defaultGroup' => $options->get( 'DBDefaultGroup' ),
];
$serversCheck = [];
if ( $lbConf['class'] === Wikimedia\Rdbms\LBFactorySimple::class ) {
if ( isset( $lbConf['servers'] ) ) {
// Server array is already explicitly configured
- } elseif ( is_array( $mainConfig->get( 'DBservers' ) ) ) {
+ } elseif ( is_array( $options->get( 'DBservers' ) ) ) {
$lbConf['servers'] = [];
- foreach ( $mainConfig->get( 'DBservers' ) as $i => $server ) {
- $lbConf['servers'][$i] = self::initServerInfo( $server, $mainConfig );
+ foreach ( $options->get( 'DBservers' ) as $i => $server ) {
+ $lbConf['servers'][$i] = self::initServerInfo( $server, $options );
}
} else {
$server = self::initServerInfo(
[
- 'host' => $mainConfig->get( 'DBserver' ),
- 'user' => $mainConfig->get( 'DBuser' ),
- 'password' => $mainConfig->get( 'DBpassword' ),
- 'dbname' => $mainConfig->get( 'DBname' ),
- 'type' => $mainConfig->get( 'DBtype' ),
+ 'host' => $options->get( 'DBserver' ),
+ 'user' => $options->get( 'DBuser' ),
+ 'password' => $options->get( 'DBpassword' ),
+ 'dbname' => $options->get( 'DBname' ),
+ 'type' => $options->get( 'DBtype' ),
'load' => 1
],
- $mainConfig
+ $options
);
- $server['flags'] |= $mainConfig->get( 'DBssl' ) ? DBO_SSL : 0;
- $server['flags'] |= $mainConfig->get( 'DBcompress' ) ? DBO_COMPRESS : 0;
+ $server['flags'] |= $options->get( 'DBssl' ) ? DBO_SSL : 0;
+ $server['flags'] |= $options->get( 'DBcompress' ) ? DBO_COMPRESS : 0;
$lbConf['servers'] = [ $server ];
}
if ( !isset( $lbConf['externalClusters'] ) ) {
- $lbConf['externalClusters'] = $mainConfig->get( 'ExternalServers' );
+ $lbConf['externalClusters'] = $options->get( 'ExternalServers' );
}
$serversCheck = $lbConf['servers'];
} elseif ( $lbConf['class'] === Wikimedia\Rdbms\LBFactoryMulti::class ) {
if ( isset( $lbConf['serverTemplate'] ) ) {
if ( in_array( $lbConf['serverTemplate']['type'], $typesWithSchema, true ) ) {
- $lbConf['serverTemplate']['schema'] = $mainConfig->get( 'DBmwschema' );
+ $lbConf['serverTemplate']['schema'] = $options->get( 'DBmwschema' );
}
- $lbConf['serverTemplate']['sqlMode'] = $mainConfig->get( 'SQLMode' );
+ $lbConf['serverTemplate']['sqlMode'] = $options->get( 'SQLMode' );
}
$serversCheck = [ $lbConf['serverTemplate'] ] ?? [];
}
- self::assertValidServerConfigs( $serversCheck, $mainConfig );
+ self::assertValidServerConfigs( $serversCheck, $options->get( 'DBname' ),
+ $options->get( 'DBprefix' ) );
$lbConf = self::injectObjectCaches( $lbConf, $srvCace, $mainStash, $wanCache );
/**
* @param array $server
- * @param Config $mainConfig
+ * @param ServiceOptions $options
* @return array
*/
- private static function initServerInfo( array $server, Config $mainConfig ) {
+ private static function initServerInfo( array $server, ServiceOptions $options ) {
if ( $server['type'] === 'sqlite' ) {
$httpMethod = $_SERVER['REQUEST_METHOD'] ?? null;
// T93097: hint for how file-based databases (e.g. sqlite) should go about locking.
// See https://www.sqlite.org/lockingv3.html#shared_lock
$isHttpRead = in_array( $httpMethod, [ 'GET', 'HEAD', 'OPTIONS', 'TRACE' ] );
$server += [
- 'dbDirectory' => $mainConfig->get( 'SQLiteDataDir' ),
+ 'dbDirectory' => $options->get( 'SQLiteDataDir' ),
'trxMode' => $isHttpRead ? 'DEFERRED' : 'IMMEDIATE'
];
} elseif ( $server['type'] === 'postgres' ) {
$server += [
- 'port' => $mainConfig->get( 'DBport' ),
+ 'port' => $options->get( 'DBport' ),
// Work around the reserved word usage in MediaWiki schema
'keywordTableMap' => [ 'user' => 'mwuser', 'text' => 'pagecontent' ]
];
];
} elseif ( $server['type'] === 'mssql' ) {
$server += [
- 'port' => $mainConfig->get( 'DBport' ),
- 'useWindowsAuth' => $mainConfig->get( 'DBWindowsAuthentication' )
+ 'port' => $options->get( 'DBport' ),
+ 'useWindowsAuth' => $options->get( 'DBWindowsAuthentication' )
];
}
if ( in_array( $server['type'], self::getDbTypesWithSchemas(), true ) ) {
- $server += [ 'schema' => $mainConfig->get( 'DBmwschema' ) ];
+ $server += [ 'schema' => $options->get( 'DBmwschema' ) ];
}
$flags = DBO_DEFAULT;
- $flags |= $mainConfig->get( 'DebugDumpSql' ) ? DBO_DEBUG : 0;
+ $flags |= $options->get( 'DebugDumpSql' ) ? DBO_DEBUG : 0;
if ( $server['type'] === 'oracle' ) {
- $flags |= $mainConfig->get( 'DBOracleDRCP' ) ? DBO_PERSISTENT : 0;
+ $flags |= $options->get( 'DBOracleDRCP' ) ? DBO_PERSISTENT : 0;
}
$server += [
- 'tablePrefix' => $mainConfig->get( 'DBprefix' ),
+ 'tablePrefix' => $options->get( 'DBprefix' ),
'flags' => $flags,
- 'sqlMode' => $mainConfig->get( 'SQLMode' ),
+ 'sqlMode' => $options->get( 'SQLMode' ),
];
return $server;
/**
* @param array $servers
- * @param Config $mainConfig
+ * @param string $lbDB Local domain database name
+ * @param string $lbTP Local domain prefix
*/
- private static function assertValidServerConfigs( array $servers, Config $mainConfig ) {
- $ldDB = $mainConfig->get( 'DBname' ); // local domain DB
- $ldTP = $mainConfig->get( 'DBprefix' ); // local domain prefix
-
+ private static function assertValidServerConfigs( array $servers, $ldDB, $ldTP ) {
foreach ( $servers as $server ) {
$type = $server['type'] ?? null;
$srvDB = $server['dbname'] ?? null; // server DB
return $class;
}
- public static function setSchemaAliases( LBFactory $lbFactory, Config $config ) {
- if ( $config->get( 'DBtype' ) === 'mysql' ) {
+ /**
+ * @param LBFactory $lbFactory
+ * @param string $dbType 'mysql', 'sqlite', etc.
+ */
+ public static function setSchemaAliases( LBFactory $lbFactory, $dbType ) {
+ if ( $dbType instanceof Config ) {
+ // Before 1.34 this took a whole Config just to get $dbType
+ wfDeprecated( __METHOD__ . ' with Config argument', '1.34' );
+ $dbType = $dbType->get( 'DBtype' );
+ }
+ if ( $dbType === 'mysql' ) {
/**
* When SQLite indexes were introduced in r45764, it was noted that
* SQLite requires index names to be unique within the whole database,
return $this->$name;
}
- $qualifiedName = __CLASS__ . '::$' . $name;
- if ( $this->deprecationHelperGetPropertyOwner( $name ) ) {
+ $ownerClass = $this->deprecationHelperGetPropertyOwner( $name );
+ $qualifiedName = ( $ownerClass ?: get_class( $this ) ) . '::$' . $name;
+ if ( $ownerClass ) {
// Someone tried to access a normal non-public property. Try to behave like PHP would.
trigger_error( "Cannot access non-public property $qualifiedName", E_USER_ERROR );
} else {
return;
}
- $qualifiedName = __CLASS__ . '::$' . $name;
- if ( $this->deprecationHelperGetPropertyOwner( $name ) ) {
+ $ownerClass = $this->deprecationHelperGetPropertyOwner( $name );
+ $qualifiedName = ( $ownerClass ?: get_class( $this ) ) . '::$' . $name;
+ if ( $ownerClass ) {
// Someone tried to access a normal non-public property. Try to behave like PHP would.
trigger_error( "Cannot access non-public property $qualifiedName", E_USER_ERROR );
} else {
* Like property_exists but also check for non-visible private properties and returns which
* class in the inheritance chain declared the property.
* @param string $property
- * @return string|bool Best guess for the class in which the property is defined.
+ * @return string|bool Best guess for the class in which the property is defined. False if
+ * the object does not have such a property.
*/
private function deprecationHelperGetPropertyOwner( $property ) {
- // Easy branch: check for protected property / private property of the current class.
- if ( property_exists( $this, $property ) ) {
- // The class name is not necessarily correct here but getting the correct class
- // name would be expensive, this will work most of the time and getting it
- // wrong is not a big deal.
- return __CLASS__;
- }
- // property_exists() returns false when the property does exist but is private (and not
- // defined by the current class, for some value of "current" that differs slightly
- // between engines).
- // Since PHP triggers an error on public access of non-public properties but happily
- // allows public access to undefined properties, we need to detect this case as well.
- // Reflection is slow so use array cast hack to check for that:
+ // Returning false is a non-error path and should avoid slow checks like reflection.
+ // Use array cast hack instead.
$obfuscatedProps = array_keys( (array)$this );
$obfuscatedPropTail = "\0$property";
foreach ( $obfuscatedProps as $obfuscatedProp ) {
if ( strpos( $obfuscatedProp, $obfuscatedPropTail, 1 ) !== false ) {
$classname = substr( $obfuscatedProp, 1, -strlen( $obfuscatedPropTail ) );
if ( $classname === '*' ) {
- // sanity; this shouldn't be possible as protected properties were handled earlier
- $classname = __CLASS__;
+ // protected property; we didn't get the name, but we are on an error path
+ // now so it's fine to use reflection
+ return ( new ReflectionProperty( $this, $property ) )->getDeclaringClass()->getName();
}
return $classname;
}
"config-upgrade-key-missing": "Έχει εντοπιστεί μια υπάρχουσα εγκατάσταση του MediaWiki.\nΓια να αναβαθμίσετε αυτήν την εγκατάσταση, παρακαλούμε να βάλετε την ακόλουθη γραμμή στο κάτω μέρος του <code>LocalSettings.php</code> σας:\n\n$1",
"config-localsettings-incomplete": "Το υπάρχον <code>LocalSettings.php</code> φαίνεται να είναι ελλιπές.\nΤο $1 μεταβλητή δεν έχει οριστεί.\nΠαρακαλούμε να αλλάξετε το <code>LocalSettings.php</code> έτσι ώστε αυτή η μεταβλητή έχει οριστεί, και κάντε κλικ στο \"{{int:Config-continue}}\".",
"config-localsettings-connection-error": "Ένα σφάλμα παρουσιάστηκε κατά τη σύνδεση με τη βάση δεδομένων και με τη χρήση των ρυθμίσεων που ορίστηκαν στο <code>LocalSettings.php</code>. Παρακαλούμε διορθώστε αυτές τις ρυθμίσεις και δοκιμάστε ξανά.\n\n$1",
- "config-session-error": "ΣÏ\86άλμα καÏ\84ά Ï\84ην εκκίνηÏ\83η Ï\83Ï\85νεδÏ\81ίας: $1",
- "config-session-expired": "Τα δεδομÎνα Ï\83Ï\85νÏ\8cδοÏ\85 Ï\86αίνεÏ\84αι να ÎÏ\87οÏ\85ν λήξει.\nΣÏ\85νεδÏ\81ίεÏ\82 ÎÏ\87οÏ\85ν Ï\81Ï\85θμιÏ\83Ï\84εί για μια διάÏ\81κεια ζÏ\89ήÏ\82 $1.\nÎ\9cÏ\80οÏ\81είÏ\84ε να αÏ\85ξήÏ\83εÏ\84ε αÏ\85Ï\84Ï\8c βάζονÏ\84αÏ\82 <code>session.gc_maxlifetime</code> στο php.ini.\nΚάντε επανεκκίνηση της διαδικασίας εγκατάστασης.",
- "config-no-session": "Î\97 Ï\83Ï\85νεδÏ\81ία δεδομÎνÏ\89ν Ï\83αÏ\82 ÎÏ\87ει Ï\87αθεί!Î\95λÎγξÏ\84ε Ï\84ο αÏ\81Ï\87είο php.ini και βεβαιÏ\89θείÏ\84ε Ï\8cÏ\84ι Ï\84ο <code>session.save_path</code> ÎÏ\87ει μÏ\80ει στον κατάλληλο κατάλογο.",
+ "config-session-error": "ΣÏ\86άλμα καÏ\84ά Ï\84ην εκκίνηÏ\83η Ï\84ηÏ\82 Ï\80εÏ\81ιÏ\8cδοÏ\85 Ï\83Ï\8dνδεÏ\83ης: $1",
+ "config-session-expired": "Τα δεδομÎνα Ï\84ηÏ\82 Ï\80εÏ\81ιÏ\8cδοÏ\85 Ï\83Ï\8dνδεÏ\83ηÏ\82 Ï\86αίνεÏ\84αι να ÎÏ\87οÏ\85ν λήξει.\nÎ\9fι Ï\80εÏ\81ίοδοι Ï\83Ï\8dνδεÏ\83ηÏ\82 είναι Ï\81Ï\85θμιÏ\83μÎνεÏ\82 για διάÏ\81κεια ζÏ\89ήÏ\82 $1.\nÎ\9cÏ\80οÏ\81είÏ\84ε να Ï\84ην αÏ\85ξήÏ\83εÏ\84ε θÎÏ\84ονÏ\84αÏ\82 Ï\84ο <code>session.gc_maxlifetime</code> στο php.ini.\nΚάντε επανεκκίνηση της διαδικασίας εγκατάστασης.",
+ "config-no-session": "Τα δεδομÎνα Ï\84ηÏ\82 Ï\80εÏ\81ιÏ\8cδοÏ\85 Ï\83Ï\8dνδεÏ\83ήÏ\82 Ï\83αÏ\82 ÎÏ\87οÏ\85ν Ï\87αθεί!\nÎ\95λÎγξÏ\84ε Ï\84ο php.ini Ï\83αÏ\82 και βεβαιÏ\89θείÏ\84ε Ï\8cÏ\84ι Ï\84ο <code>session.save_path</code> ÎÏ\87ει οÏ\81ιÏ\83Ï\84εί στον κατάλληλο κατάλογο.",
"config-your-language": "Η γλώσσα σας:",
"config-your-language-help": "Επιλέξτε μία γλώσσα για τη διαδικασία της εγκατάστασης.",
"config-wiki-language": "Γλώσσα του wiki:",
"config-type-oracle": "Oracle",
"config-type-mssql": "Microsoft SQL Server",
"config-support-info": "MediaWiki подржава следеће системе база података:\n\n$1\n\nАко не видите систем који покушавате да користите на листи испод, онда пратите повезана упутства изнад како бисте омогућили подршку.",
- "config-dbsupport-mysql": "* [{{int:version-db-mariadb-url}} MariaDB] је примарна мета за MediaWiki и најбоље је подржана. MediaWiki такође ради са [{{int:version-db-mysql-url}} MySQL-ом] и [{{int:version-db-percona-url}} Percona Server-ом], који су компатибилни са MariaDB-ом. ([https://www.php.net/manual/en/mysqli.installation.php Како компајлирати PHP са подршком MySQL-а])",
+ "config-dbsupport-mysql": "* [{{int:version-db-mariadb-url}} MariaDB] је примарна мета за Медијавики и најбоље је подржана. Медијавики ради и са [{{int:version-db-mysql-url}} MySQL-ом] и [{{int:version-db-percona-url}} Percona Server-ом], који су компатибилни са MariaDB-ом. ([https://www.php.net/manual/en/mysqli.installation.php Како компајлирати PHP са подршком MySQL-а])",
"config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] је популаран систем база података отвореног кода кaо алтернатива MySQL-у. ([https://www.php.net/manual/en/pgsql.installation.php Како компајлирати PHP са подршком PostgreSQL-а])",
"config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite] је лаган систем базе података који је веома добро подржан. ([https://www.php.net/manual/en/pdo.installation.php Како компајлирати PHP са подршком SQLite-а], користи PDO)",
"config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] је база података комерцијалних предузећа. ([https://www.php.net/manual/en/oci8.installation.php Како компајлирати PHP са подршком OCI8-а])",
return [ [ 'alreadyrolled',
htmlspecialchars( $this->mTitle->getPrefixedText() ),
htmlspecialchars( $fromP ),
- htmlspecialchars( $targetEditorForPublic ? $targetEditorForPublic->getName() : '' )
+ htmlspecialchars( $currentEditorForPublic ? $currentEditorForPublic->getName() : '' )
] ];
}
use LanguageConverter;
use MediaWiki\Auth\AuthManager;
use MediaWiki\Auth\PasswordAuthenticationRequest;
+use MediaWiki\Config\ServiceOptions;
use MediaWiki\Linker\LinkRenderer;
use MediaWiki\MediaWikiServices;
use MessageLocalizer;
class DefaultPreferencesFactory implements PreferencesFactory {
use LoggerAwareTrait;
- /** @var Config */
- protected $config;
+ /** @var ServiceOptions */
+ protected $options;
/** @var Language The wiki's content language. */
protected $contLang;
protected $linkRenderer;
/**
- * @param Config $config
+ * TODO Make this a const when we drop HHVM support (T192166)
+ *
+ * @var array
+ * @since 1.34
+ */
+ public static $constructorOptions = [
+ 'AllowUserCss',
+ 'AllowUserCssPrefs',
+ 'AllowUserJs',
+ 'DefaultSkin',
+ 'DisableLangConversion',
+ 'EmailAuthentication',
+ 'EmailConfirmToEdit',
+ 'EnableEmail',
+ 'EnableUserEmail',
+ 'EnableUserEmailBlacklist',
+ 'EnotifMinorEdits',
+ 'EnotifRevealEditorAddress',
+ 'EnotifUserTalk',
+ 'EnotifWatchlist',
+ 'HiddenPrefs',
+ 'ImageLimits',
+ 'LanguageCode',
+ 'LocalTZoffset',
+ 'MaxSigChars',
+ 'RCMaxAge',
+ 'RCShowWatchingUsers',
+ 'RCWatchCategoryMembership',
+ 'SecureLogin',
+ 'ThumbLimits',
+ ];
+
+ /**
+ * @param array|Config $options Config accepted for backwards compatibility
* @param Language $contLang
* @param AuthManager $authManager
* @param LinkRenderer $linkRenderer
*/
public function __construct(
- Config $config,
+ $options,
Language $contLang,
AuthManager $authManager,
LinkRenderer $linkRenderer
) {
- $this->config = $config;
+ if ( $options instanceof Config ) {
+ wfDeprecated( __METHOD__ . ' with Config parameter', '1.34' );
+ $options = new ServiceOptions( self::$constructorOptions, $options );
+ }
+
+ $options->assertRequiredOptions( self::$constructorOptions );
+
+ $this->options = $options;
$this->contLang = $contLang;
$this->authManager = $authManager;
$this->linkRenderer = $linkRenderer;
User $user, IContextSource $context, &$defaultPreferences
) {
# # Remove preferences that wikis don't want to use
- foreach ( $this->config->get( 'HiddenPrefs' ) as $pref ) {
+ foreach ( $this->options->get( 'HiddenPrefs' ) as $pref ) {
if ( isset( $defaultPreferences[$pref] ) ) {
unset( $defaultPreferences[$pref] );
}
];
}
// Only show prefershttps if secure login is turned on
- if ( $this->config->get( 'SecureLogin' ) && $canIPUseHTTPS ) {
+ if ( $this->options->get( 'SecureLogin' ) && $canIPUseHTTPS ) {
$defaultPreferences['prefershttps'] = [
'type' => 'toggle',
'label-message' => 'tog-prefershttps',
}
$languages = Language::fetchLanguageNames( null, 'mwfile' );
- $languageCode = $this->config->get( 'LanguageCode' );
+ $languageCode = $this->options->get( 'LanguageCode' );
if ( !array_key_exists( $languageCode, $languages ) ) {
$languages[$languageCode] = $languageCode;
// Sort the array again
];
// see if there are multiple language variants to choose from
- if ( !$this->config->get( 'DisableLangConversion' ) ) {
+ if ( !$this->options->get( 'DisableLangConversion' ) ) {
foreach ( LanguageConverter::$languagesWithVariants as $langCode ) {
if ( $langCode == $this->contLang->getCode() ) {
if ( !$this->contLang->hasVariants() ) {
];
$defaultPreferences['nickname'] = [
'type' => $this->authManager->allowsPropertyChange( 'nickname' ) ? 'text' : 'info',
- 'maxlength' => $this->config->get( 'MaxSigChars' ),
+ 'maxlength' => $this->options->get( 'MaxSigChars' ),
'label-message' => 'yournick',
'validation-callback' => function ( $signature, $alldata, HTMLForm $form ) {
return $this->validateSignature( $signature, $alldata, $form );
# # Email stuff
- if ( $this->config->get( 'EnableEmail' ) ) {
+ if ( $this->options->get( 'EnableEmail' ) ) {
if ( $canViewPrivateInfo ) {
- $helpMessages[] = $this->config->get( 'EmailConfirmToEdit' )
+ $helpMessages[] = $this->options->get( 'EmailConfirmToEdit' )
? 'prefs-help-email-required'
: 'prefs-help-email';
- if ( $this->config->get( 'EnableUserEmail' ) ) {
+ if ( $this->options->get( 'EnableUserEmail' ) ) {
// additional messages when users can send email to each other
$helpMessages[] = 'prefs-help-email-others';
}
$disableEmailPrefs = false;
- if ( $this->config->get( 'EmailAuthentication' ) ) {
+ if ( $this->options->get( 'EmailAuthentication' ) ) {
$emailauthenticationclass = 'mw-email-not-authenticated';
if ( $user->getEmail() ) {
if ( $user->getEmailAuthenticationTimestamp() ) {
}
}
- if ( $this->config->get( 'EnableUserEmail' ) && $user->isAllowed( 'sendemail' ) ) {
+ if ( $this->options->get( 'EnableUserEmail' ) && $user->isAllowed( 'sendemail' ) ) {
$defaultPreferences['disablemail'] = [
'id' => 'wpAllowEmail',
'type' => 'toggle',
'disabled' => $disableEmailPrefs,
];
- if ( $this->config->get( 'EnableUserEmailBlacklist' ) ) {
+ if ( $this->options->get( 'EnableUserEmailBlacklist' ) ) {
$defaultPreferences['email-blacklist'] = [
'type' => 'usersmultiselect',
'label-message' => 'email-blacklist-label',
}
}
- if ( $this->config->get( 'EnotifWatchlist' ) ) {
+ if ( $this->options->get( 'EnotifWatchlist' ) ) {
$defaultPreferences['enotifwatchlistpages'] = [
'type' => 'toggle',
'section' => 'personal/email',
'disabled' => $disableEmailPrefs,
];
}
- if ( $this->config->get( 'EnotifUserTalk' ) ) {
+ if ( $this->options->get( 'EnotifUserTalk' ) ) {
$defaultPreferences['enotifusertalkpages'] = [
'type' => 'toggle',
'section' => 'personal/email',
'disabled' => $disableEmailPrefs,
];
}
- if ( $this->config->get( 'EnotifUserTalk' ) || $this->config->get( 'EnotifWatchlist' ) ) {
- if ( $this->config->get( 'EnotifMinorEdits' ) ) {
+ if ( $this->options->get( 'EnotifUserTalk' ) ||
+ $this->options->get( 'EnotifWatchlist' ) ) {
+ if ( $this->options->get( 'EnotifMinorEdits' ) ) {
$defaultPreferences['enotifminoredits'] = [
'type' => 'toggle',
'section' => 'personal/email',
];
}
- if ( $this->config->get( 'EnotifRevealEditorAddress' ) ) {
+ if ( $this->options->get( 'EnotifRevealEditorAddress' ) ) {
$defaultPreferences['enotifrevealaddr'] = [
'type' => 'toggle',
'section' => 'personal/email',
];
}
- $allowUserCss = $this->config->get( 'AllowUserCss' );
- $allowUserJs = $this->config->get( 'AllowUserJs' );
+ $allowUserCss = $this->options->get( 'AllowUserCss' );
+ $allowUserJs = $this->options->get( 'AllowUserJs' );
# Create links to user CSS/JS pages for all skins
# This code is basically copied from generateSkinOptions(). It'd
# be nice to somehow merge this back in there to avoid redundancy.
];
# # Page Rendering ##############################
- if ( $this->config->get( 'AllowUserCssPrefs' ) ) {
+ if ( $this->options->get( 'AllowUserCssPrefs' ) ) {
$defaultPreferences['underline'] = [
'type' => 'select',
'options' => [
'label-message' => 'tog-editondblclick',
];
- if ( $this->config->get( 'AllowUserCssPrefs' ) ) {
+ if ( $this->options->get( 'AllowUserCssPrefs' ) ) {
$defaultPreferences['editfont'] = [
'type' => 'select',
'section' => 'editing/editor',
* @param array &$defaultPreferences
*/
protected function rcPreferences( User $user, MessageLocalizer $l10n, &$defaultPreferences ) {
- $rcMaxAge = $this->config->get( 'RCMaxAge' );
+ $rcMaxAge = $this->options->get( 'RCMaxAge' );
# # RecentChanges #####################################
$defaultPreferences['rcdays'] = [
'type' => 'float',
'type' => 'api',
];
- if ( $this->config->get( 'RCWatchCategoryMembership' ) ) {
+ if ( $this->options->get( 'RCWatchCategoryMembership' ) ) {
$defaultPreferences['hidecategorization'] = [
'type' => 'toggle',
'label-message' => 'tog-hidecategorization',
];
}
- if ( $this->config->get( 'RCShowWatchingUsers' ) ) {
+ if ( $this->options->get( 'RCShowWatchingUsers' ) ) {
$defaultPreferences['shownumberswatching'] = [
'type' => 'toggle',
'section' => 'rc/advancedrc',
protected function watchlistPreferences(
User $user, IContextSource $context, &$defaultPreferences
) {
- $watchlistdaysMax = ceil( $this->config->get( 'RCMaxAge' ) / ( 3600 * 24 ) );
+ $watchlistdaysMax = ceil( $this->options->get( 'RCMaxAge' ) / ( 3600 * 24 ) );
# # Watchlist #####################################
if ( $user->isAllowed( 'editmywatchlist' ) ) {
'label-message' => 'tog-watchlisthideliu',
];
- if ( !\SpecialWatchlist::checkStructuredFilterUiEnabled(
- $this->config,
- $user
- ) ) {
+ if ( !\SpecialWatchlist::checkStructuredFilterUiEnabled( $user ) ) {
$defaultPreferences['watchlistreloadautomatically'] = [
'type' => 'toggle',
'section' => 'watchlist/advancedwatchlist',
'label-message' => 'tog-watchlistunwatchlinks',
];
- if ( $this->config->get( 'RCWatchCategoryMembership' ) ) {
+ if ( $this->options->get( 'RCWatchCategoryMembership' ) ) {
$defaultPreferences['watchlisthidecategorization'] = [
'type' => 'toggle',
'section' => 'watchlist/changeswatchlist',
}
}
- $defaultSkin = $this->config->get( 'DefaultSkin' );
- $allowUserCss = $this->config->get( 'AllowUserCss' );
- $allowUserJs = $this->config->get( 'AllowUserJs' );
+ $defaultSkin = $this->options->get( 'DefaultSkin' );
+ $allowUserCss = $this->options->get( 'AllowUserCss' );
+ $allowUserJs = $this->options->get( 'AllowUserJs' );
# Sort by the internal name, so that the ordering is the same for each display language,
# especially if some skin names are translated to use a different alphabet and some are not.
$ret = [];
$pixels = $l10n->msg( 'unit-pixel' )->text();
- foreach ( $this->config->get( 'ImageLimits' ) as $index => $limits ) {
+ foreach ( $this->options->get( 'ImageLimits' ) as $index => $limits ) {
// Note: A left-to-right marker (U+200E) is inserted, see T144386
$display = "{$limits[0]}\u{200E}×{$limits[1]}$pixels";
$ret[$display] = $index;
$ret = [];
$pixels = $l10n->msg( 'unit-pixel' )->text();
- foreach ( $this->config->get( 'ThumbLimits' ) as $index => $size ) {
+ foreach ( $this->options->get( 'ThumbLimits' ) as $index => $size ) {
$display = $size . $pixels;
$ret[$display] = $index;
}
* @return bool|string
*/
protected function validateSignature( $signature, $alldata, HTMLForm $form ) {
- $maxSigChars = $this->config->get( 'MaxSigChars' );
+ $maxSigChars = $this->options->get( 'MaxSigChars' );
if ( mb_strlen( $signature ) > $maxSigChars ) {
return Xml::element( 'span', [ 'class' => 'error' ],
$form->msg( 'badsiglength' )->numParams( $maxSigChars )->text() );
protected function getTimezoneOptions( IContextSource $context ) {
$opt = [];
- $localTZoffset = $this->config->get( 'LocalTZoffset' );
+ $localTZoffset = $this->options->get( 'LocalTZoffset' );
$timeZoneList = $this->getTimeZoneList( $context->getLanguage() );
$timestamp = MWTimestamp::getLocalInstance();
protected function saveFormData( $formData, HTMLForm $form, array $formDescriptor ) {
/** @var \User $user */
$user = $form->getModifiedUser();
- $hiddenPrefs = $this->config->get( 'HiddenPrefs' );
+ $hiddenPrefs = $this->options->get( 'HiddenPrefs' );
$result = true;
if ( !$user->isAllowedAny( 'editmyprivateinfo', 'editmyoptions' ) ) {
* @param string|string[] ...$args
* @return $this
*/
- public function params( ...$args ) {
+ public function params( ...$args ): Command {
if ( count( $args ) === 1 && is_array( reset( $args ) ) ) {
// If only one argument has been passed, and that argument is an array,
// treat it as a list of arguments
* @param string|string[] ...$args
* @return $this
*/
- public function unsafeParams( ...$args ) {
+ public function unsafeParams( ...$args ): Command {
if ( count( $args ) === 1 && is_array( reset( $args ) ) ) {
// If only one argument has been passed, and that argument is an array,
// treat it as a list of arguments
* filesize (for ulimit -f), memory, time, walltime.
* @return $this
*/
- public function limits( array $limits ) {
+ public function limits( array $limits ): Command {
if ( !isset( $limits['walltime'] ) && isset( $limits['time'] ) ) {
// Emulate the behavior of old wfShellExec() where walltime fell back on time
// if the latter was overridden and the former wasn't
* @param string[] $env array of variable name => value
* @return $this
*/
- public function environment( array $env ) {
+ public function environment( array $env ): Command {
$this->env = $env;
return $this;
* @param string $method
* @return $this
*/
- public function profileMethod( $method ) {
+ public function profileMethod( $method ): Command {
$this->method = $method;
return $this;
* @param string|null $inputString
* @return $this
*/
- public function input( $inputString ) {
+ public function input( $inputString ): Command {
$this->inputString = is_null( $inputString ) ? null : (string)$inputString;
return $this;
* @param bool $yesno
* @return $this
*/
- public function includeStderr( $yesno = true ) {
+ public function includeStderr( $yesno = true ): Command {
$this->doIncludeStderr = $yesno;
return $this;
* @param bool $yesno
* @return $this
*/
- public function logStderr( $yesno = true ) {
+ public function logStderr( $yesno = true ): Command {
$this->doLogStderr = $yesno;
return $this;
* @param string|false $cgroup Absolute file path to the cgroup, or false to not use a cgroup
* @return $this
*/
- public function cgroup( $cgroup ) {
+ public function cgroup( $cgroup ): Command {
$this->cgroup = $cgroup;
return $this;
* @param int $restrictions
* @return $this
*/
- public function restrict( $restrictions ) {
+ public function restrict( $restrictions ): Command {
$this->restrictions |= $restrictions;
return $this;
*
* @return $this
*/
- public function whitelistPaths( array $paths ) {
+ public function whitelistPaths( array $paths ): Command {
// Default implementation is a no-op
return $this;
}
*
* @return Command
*/
- public function create() {
+ public function create(): Command {
if ( $this->restrictionMethod === 'firejail' ) {
$command = new FirejailCommand( $this->findFirejail() );
$command->restrict( Shell::RESTRICT_DEFAULT );
/**
* @inheritDoc
*/
- public function whitelistPaths( array $paths ) {
+ public function whitelistPaths( array $paths ): Command {
$this->whitelistedPaths = array_merge( $this->whitelistedPaths, $paths );
return $this;
}
* Example: [ 'convert', '-font', 'font name' ] would produce "'convert' '-font' 'font name'"
* @return Command
*/
- public static function command( ...$commands ) {
+ public static function command( ...$commands ): Command {
if ( count( $commands ) === 1 && is_array( reset( $commands ) ) ) {
// If only one argument has been passed, and that argument is an array,
// treat it as a list of arguments
* 'wrapper': Path to a PHP wrapper to handle the maintenance script
* @return Command
*/
- public static function makeScriptCommand( $script, $parameters, $options = [] ) {
+ public static function makeScriptCommand( $script, $parameters, $options = [] ): Command {
global $wgPhpCli;
// Give site config file a chance to run the script in a wrapper.
// The caller may likely want to call wfBasename() on $script.
return true;
}
- return static::checkStructuredFilterUiEnabled(
- $this->getConfig(),
- $this->getUser()
- );
+ return static::checkStructuredFilterUiEnabled( $this->getUser() );
}
/**
* Static method to check whether StructuredFilter UI is enabled for the given user
*
* @since 1.31
- * @param Config $config
* @param User $user
* @return bool
*/
- public static function checkStructuredFilterUiEnabled( Config $config, User $user ) {
+ public static function checkStructuredFilterUiEnabled( $user ) {
+ if ( $user instanceof Config ) {
+ wfDeprecated( __METHOD__ . ' with Config argument', '1.34' );
+ $user = func_get_arg( 1 );
+ }
return !$user->getOption( 'rcenhancedfilters-disable' );
}
namespace MediaWiki\Special;
-use Config;
use Hooks;
use IContextSource;
use Language;
+use MediaWiki\Config\ServiceOptions;
use MediaWiki\Linker\LinkRenderer;
use Profiler;
use RequestContext;
use SpecialPage;
use Title;
use User;
-use Wikimedia\Assert\Assert;
/**
* Factory for handling the special page list and generating SpecialPage objects.
/** @var array */
private $aliases;
- /** @var Config */
+ /** @var ServiceOptions */
private $options;
/** @var Language */
];
/**
- * @param array $options
+ * @param ServiceOptions $options
* @param Language $contLang
*/
- public function __construct( array $options, Language $contLang ) {
- Assert::parameter( count( $options ) === count( self::$constructorOptions ) &&
- !array_diff( self::$constructorOptions, array_keys( $options ) ),
- '$options', 'Wrong set of options present' );
+ public function __construct( ServiceOptions $options, Language $contLang ) {
+ $options->assertRequiredOptions( self::$constructorOptions );
$this->options = $options;
$this->contLang = $contLang;
}
if ( !is_array( $this->list ) ) {
$this->list = self::$coreList;
- if ( !$this->options['DisableInternalSearch'] ) {
+ if ( !$this->options->get( 'DisableInternalSearch' ) ) {
$this->list['Search'] = \SpecialSearch::class;
}
- if ( $this->options['EmailAuthentication'] ) {
+ if ( $this->options->get( 'EmailAuthentication' ) ) {
$this->list['Confirmemail'] = \EmailConfirmation::class;
$this->list['Invalidateemail'] = \EmailInvalidation::class;
}
- if ( $this->options['EnableEmail'] ) {
+ if ( $this->options->get( 'EnableEmail' ) ) {
$this->list['ChangeEmail'] = \SpecialChangeEmail::class;
}
- if ( $this->options['EnableJavaScriptTest'] ) {
+ if ( $this->options->get( 'EnableJavaScriptTest' ) ) {
$this->list['JavaScriptTest'] = \SpecialJavaScriptTest::class;
}
- if ( $this->options['PageLanguageUseDB'] ) {
+ if ( $this->options->get( 'PageLanguageUseDB' ) ) {
$this->list['PageLanguage'] = \SpecialPageLanguage::class;
}
- if ( $this->options['ContentHandlerUseDB'] ) {
+ if ( $this->options->get( 'ContentHandlerUseDB' ) ) {
$this->list['ChangeContentModel'] = \SpecialChangeContentModel::class;
}
// Add extension special pages
- $this->list = array_merge( $this->list, $this->options['SpecialPages'] );
+ $this->list = array_merge( $this->list, $this->options->get( 'SpecialPages' ) );
// This hook can be used to disable unwanted core special pages
// or conditionally register special pages.
$username = $target->getName();
$userpage = $target->getUserPage();
$talkpage = $target->getTalkPage();
+ $isIP = IP::isValid( $username );
+ $isRange = IP::isValidRange( $username );
$linkRenderer = $sp->getLinkRenderer();
# No talk pages for IP ranges.
- if ( !IP::isValidRange( $username ) ) {
+ if ( !$isRange ) {
$tools['user-talk'] = $linkRenderer->makeLink(
$talkpage,
$sp->msg( 'sp-contributions-talk' )->text()
);
}
- if ( ( $id !== null ) || ( $id === null && IP::isIPAddress( $username ) ) ) {
- if ( $sp->getUser()->isAllowed( 'block' ) ) { # Block / Change block / Unblock links
- if ( $target->getBlock() && $target->getBlock()->getType() != Block::TYPE_AUTO ) {
- $tools['block'] = $linkRenderer->makeKnownLink( # Change block link
- SpecialPage::getTitleFor( 'Block', $username ),
- $sp->msg( 'change-blocklink' )->text()
- );
- $tools['unblock'] = $linkRenderer->makeKnownLink( # Unblock link
- SpecialPage::getTitleFor( 'Unblock', $username ),
- $sp->msg( 'unblocklink' )->text()
- );
- } else { # User is not blocked
- $tools['block'] = $linkRenderer->makeKnownLink( # Block link
- SpecialPage::getTitleFor( 'Block', $username ),
- $sp->msg( 'blocklink' )->text()
- );
- }
+ if ( $sp->getUser()->isAllowed( 'block' ) ) { # Block / Change block / Unblock links
+ if ( $target->getBlock() && $target->getBlock()->getType() != Block::TYPE_AUTO ) {
+ $tools['block'] = $linkRenderer->makeKnownLink( # Change block link
+ SpecialPage::getTitleFor( 'Block', $username ),
+ $sp->msg( 'change-blocklink' )->text()
+ );
+ $tools['unblock'] = $linkRenderer->makeKnownLink( # Unblock link
+ SpecialPage::getTitleFor( 'Unblock', $username ),
+ $sp->msg( 'unblocklink' )->text()
+ );
+ } else { # User is not blocked
+ $tools['block'] = $linkRenderer->makeKnownLink( # Block link
+ SpecialPage::getTitleFor( 'Block', $username ),
+ $sp->msg( 'blocklink' )->text()
+ );
}
+ }
- # Block log link
- $tools['log-block'] = $linkRenderer->makeKnownLink(
- SpecialPage::getTitleFor( 'Log', 'block' ),
- $sp->msg( 'sp-contributions-blocklog' )->text(),
+ # Block log link
+ $tools['log-block'] = $linkRenderer->makeKnownLink(
+ SpecialPage::getTitleFor( 'Log', 'block' ),
+ $sp->msg( 'sp-contributions-blocklog' )->text(),
+ [],
+ [ 'page' => $userpage->getPrefixedText() ]
+ );
+
+ # Suppression log link (T61120)
+ if ( $sp->getUser()->isAllowed( 'suppressionlog' ) ) {
+ $tools['log-suppression'] = $linkRenderer->makeKnownLink(
+ SpecialPage::getTitleFor( 'Log', 'suppress' ),
+ $sp->msg( 'sp-contributions-suppresslog', $username )->text(),
[],
- [ 'page' => $userpage->getPrefixedText() ]
+ [ 'offender' => $username ]
);
-
- # Suppression log link (T61120)
- if ( $sp->getUser()->isAllowed( 'suppressionlog' ) ) {
- $tools['log-suppression'] = $linkRenderer->makeKnownLink(
- SpecialPage::getTitleFor( 'Log', 'suppress' ),
- $sp->msg( 'sp-contributions-suppresslog', $username )->text(),
- [],
- [ 'offender' => $username ]
- );
- }
}
# Don't show some links for IP ranges
- if ( !IP::isValidRange( $username ) ) {
- # Uploads
- $tools['uploads'] = $linkRenderer->makeKnownLink(
- SpecialPage::getTitleFor( 'Listfiles', $username ),
- $sp->msg( 'sp-contributions-uploads' )->text()
- );
+ if ( !$isRange ) {
+ # Uploads: hide if IPs cannot upload (T220674)
+ if ( !$isIP || $target->isAllowed( 'upload' ) ) {
+ $tools['uploads'] = $linkRenderer->makeKnownLink(
+ SpecialPage::getTitleFor( 'Listfiles', $username ),
+ $sp->msg( 'sp-contributions-uploads' )->text()
+ );
+ }
# Other logs link
+ # Todo: T146628
$tools['logs'] = $linkRenderer->makeKnownLink(
SpecialPage::getTitleFor( 'Log', $username ),
$sp->msg( 'sp-contributions-logs' )->text()
);
# Add link to deleted user contributions for priviledged users
+ # Todo: T183457
if ( $sp->getUser()->isAllowed( 'deletedhistory' ) ) {
$tools['deletedcontribs'] = $linkRenderer->makeKnownLink(
SpecialPage::getTitleFor( 'DeletedContributions', $username ),
}
}
- public static function checkStructuredFilterUiEnabled( Config $config, User $user ) {
+ /**
+ * @see ChangesListSpecialPage::checkStructuredFilterUiEnabled
+ */
+ public static function checkStructuredFilterUiEnabled( $user ) {
+ if ( $user instanceof Config ) {
+ wfDeprecated( __METHOD__ . ' with Config argument', '1.34' );
+ $user = func_get_arg( 1 );
+ }
return !$user->getOption( 'wlenhancedfilters-disable' );
}
/**
* Get blocking information
+ *
+ * TODO: Move this into the BlockManager, along with block-related properties.
+ *
* @param bool $fromReplica Whether to check the replica DB first.
* To improve performance, non-critical checks are done against replica DBs.
* Check when actually saving should be done against master.
*/
private function getBlockedStatus( $fromReplica = true ) {
- global $wgProxyWhitelist, $wgApplyIpBlocksToXff, $wgSoftBlockRanges;
-
if ( $this->mBlockedby != -1 ) {
return;
}
// overwriting mBlockedby, surely?
$this->load();
- # We only need to worry about passing the IP address to the Block generator if the
- # user is not immune to autoblocks/hardblocks, and they are the current user so we
- # know which IP address they're actually coming from
- $ip = null;
- $sessionUser = RequestContext::getMain()->getUser();
- // the session user is set up towards the end of Setup.php. Until then,
- // assume it's a logged-out user.
- $globalUserName = $sessionUser->isSafeToLoad()
- ? $sessionUser->getName()
- : IP::sanitizeIP( $sessionUser->getRequest()->getIP() );
- if ( $this->getName() === $globalUserName && !$this->isAllowed( 'ipblock-exempt' ) ) {
- $ip = $this->getRequest()->getIP();
- }
-
- // User/IP blocking
- $block = Block::newFromTarget( $this, $ip, !$fromReplica );
-
- // Cookie blocking
- if ( !$block instanceof Block ) {
- $block = $this->getBlockFromCookieValue( $this->getRequest()->getCookie( 'BlockID' ) );
- }
-
- // Proxy blocking
- if ( !$block instanceof Block && $ip !== null && !in_array( $ip, $wgProxyWhitelist ) ) {
- // Local list
- if ( self::isLocallyBlockedProxy( $ip ) ) {
- $block = new Block( [
- 'byText' => wfMessage( 'proxyblocker' )->text(),
- 'reason' => wfMessage( 'proxyblockreason' )->plain(),
- 'address' => $ip,
- 'systemBlock' => 'proxy',
- ] );
- } elseif ( $this->isAnon() && $this->isDnsBlacklisted( $ip ) ) {
- $block = new Block( [
- 'byText' => wfMessage( 'sorbs' )->text(),
- 'reason' => wfMessage( 'sorbsreason' )->plain(),
- 'address' => $ip,
- 'systemBlock' => 'dnsbl',
- ] );
- }
- }
-
- // (T25343) Apply IP blocks to the contents of XFF headers, if enabled
- if ( !$block instanceof Block
- && $wgApplyIpBlocksToXff
- && $ip !== null
- && !in_array( $ip, $wgProxyWhitelist )
- ) {
- $xff = $this->getRequest()->getHeader( 'X-Forwarded-For' );
- $xff = array_map( 'trim', explode( ',', $xff ) );
- $xff = array_diff( $xff, [ $ip ] );
- $xffblocks = Block::getBlocksForIPList( $xff, $this->isAnon(), !$fromReplica );
- $block = Block::chooseBlock( $xffblocks, $xff );
- if ( $block instanceof Block ) {
- # Mangle the reason to alert the user that the block
- # originated from matching the X-Forwarded-For header.
- $block->setReason( wfMessage( 'xffblockreason', $block->getReason() )->plain() );
- }
- }
-
- if ( !$block instanceof Block
- && $ip !== null
- && $this->isAnon()
- && IP::isInRanges( $ip, $wgSoftBlockRanges )
- ) {
- $block = new Block( [
- 'address' => $ip,
- 'byText' => 'MediaWiki default',
- 'reason' => wfMessage( 'softblockrangesreason', $ip )->plain(),
- 'anonOnly' => true,
- 'systemBlock' => 'wgSoftBlockRanges',
- ] );
- }
+ $block = MediaWikiServices::getInstance()->getBlockManager()->getUserBlock(
+ $this,
+ $fromReplica
+ );
if ( $block instanceof Block ) {
wfDebug( __METHOD__ . ": Found block.\n" );
Hooks::run( 'GetBlockedStatus', [ &$thisUser ] );
}
- /**
- * Try to load a Block from an ID given in a cookie value.
- * @param string|null $blockCookieVal The cookie value to check.
- * @return Block|bool The Block object, or false if none could be loaded.
- */
- protected function getBlockFromCookieValue( $blockCookieVal ) {
- // Make sure there's something to check. The cookie value must start with a number.
- if ( strlen( $blockCookieVal ) < 1 || !is_numeric( substr( $blockCookieVal, 0, 1 ) ) ) {
- return false;
- }
- // Load the Block from the ID in the cookie.
- $blockCookieId = Block::getIdFromCookieValue( $blockCookieVal );
- if ( $blockCookieId !== null ) {
- // An ID was found in the cookie.
- $tmpBlock = Block::newFromID( $blockCookieId );
- if ( $tmpBlock instanceof Block ) {
- $config = RequestContext::getMain()->getConfig();
-
- switch ( $tmpBlock->getType() ) {
- case Block::TYPE_USER:
- $blockIsValid = !$tmpBlock->isExpired() && $tmpBlock->isAutoblocking();
- $useBlockCookie = ( $config->get( 'CookieSetOnAutoblock' ) === true );
- break;
- case Block::TYPE_IP:
- case Block::TYPE_RANGE:
- // If block is type IP or IP range, load only if user is not logged in (T152462)
- $blockIsValid = !$tmpBlock->isExpired() && !$this->isLoggedIn();
- $useBlockCookie = ( $config->get( 'CookieSetOnIpBlock' ) === true );
- break;
- default:
- $blockIsValid = false;
- $useBlockCookie = false;
- }
-
- if ( $blockIsValid && $useBlockCookie ) {
- // Use the block.
- return $tmpBlock;
- }
-
- // If the block is not valid, remove the cookie.
- Block::clearCookie( $this->getRequest()->response() );
- } else {
- // If the block doesn't exist, remove the cookie.
- Block::clearCookie( $this->getRequest()->response() );
- }
- }
- return false;
- }
-
/**
* Whether the given IP is in a DNS blacklist.
*
+ * @deprecated since 1.34 Use BlockManager::isDnsBlacklisted.
* @param string $ip IP to check
* @param bool $checkWhitelist Whether to check the whitelist first
* @return bool True if blacklisted.
*/
public function isDnsBlacklisted( $ip, $checkWhitelist = false ) {
- global $wgEnableDnsBlacklist, $wgDnsBlacklistUrls, $wgProxyWhitelist;
-
- if ( !$wgEnableDnsBlacklist ||
- ( $checkWhitelist && in_array( $ip, $wgProxyWhitelist ) )
- ) {
- return false;
- }
-
- return $this->inDnsBlacklist( $ip, $wgDnsBlacklistUrls );
+ return MediaWikiServices::getInstance()->getBlockManager()
+ ->isDnsBlacklisted( $ip, $checkWhitelist );
}
/**
* Whether the given IP is in a given DNS blacklist.
*
+ * @deprecated since 1.34 Check via BlockManager::isDnsBlacklisted instead.
* @param string $ip IP to check
* @param string|array $bases Array of Strings: URL of the DNS blacklist
* @return bool True if blacklisted.
*/
public function inDnsBlacklist( $ip, $bases ) {
+ wfDeprecated( __METHOD__, '1.34' );
+
$found = false;
// @todo FIXME: IPv6 ??? (https://bugs.php.net/bug.php?id=33170)
if ( IP::isIPv4( $ip ) ) {
/**
* Check if an IP address is in the local proxy list
*
+ * @deprecated since 1.34 Use BlockManager::getUserBlock instead.
* @param string $ip
- *
* @return bool
*/
public static function isLocallyBlockedProxy( $ip ) {
+ wfDeprecated( __METHOD__, '1.34' );
+
global $wgProxyList;
if ( !$wgProxyList ) {
"action-editmyuserjson": "рэдагаваньне вашых уласных JSON-файлаў",
"action-editmyuserjs": "рэдагаваньне вашых уласных JavaScript-файлаў",
"action-viewsuppressed": "прагляд вэрсіяў, схаваных ад усіх удзельнікаў",
+ "action-hideuser": "блякаваньне імя ўдзельніка і яго хаваньне",
"nchanges": "$1 {{PLURAL:$1|зьмена|зьмены|зьменаў}}",
"enhancedrc-since-last-visit": "$1 {{PLURAL:$1|з апошняга візыту}}",
"enhancedrc-history": "гісторыя",
"edit-gone-missing": "পাতাটি হালনাগাদ হয়নি।\nসম্ভবতঃ পাতাটি মুছে ফেলা হয়েছে।",
"edit-conflict": "সম্পাদনা সংঘাত।",
"edit-no-change": "আপনার সম্পাদনাটি উপেক্ষা করা হয়েছে, কারণ লেখাতে কোনো পরিবর্তন করা হয়নি।",
+ "edit-slots-cannot-add": "নিচের {{PLURAL:$1|পাতাটি|পাতাসমূহ}} এখানে সমর্থিত নয়: $2।",
+ "edit-slots-cannot-remove": "নিচের {{PLURAL:$1|স্লট|স্লটসমূহ}} প্রয়োজন এবং বাদ দেওয়া যাবে না: $2।",
+ "edit-slots-missing": "নিচের {{PLURAL:$1|স্লট|স্লটসমূহ}} পাওয়া যায়নি: $2।",
"postedit-confirmation-created": "পাতাটি তৈরি করা হয়েছে।",
"postedit-confirmation-restored": "পাতাটি পুনরুদ্ধার করা হয়েছে।",
"postedit-confirmation-saved": "আপনার সম্পাদনা সংরক্ষিত হয়েছে।",
"converter-manual-rule-error": "ম্যানুয়াল ভাষা রূপান্তর নিয়মে ত্রুটি পাওয়া গিয়েছে",
"undo-success": "সম্পাদনাটি বাতিল করা যাবে। অনুগ্রহ করে নিচের তুলনাটি পরীক্ষা করে দেখুন ও নিশ্চিত করুন যে এটাই আপনি করতে চান, এবং তারপর নিচের সম্পাদনাগুলি সংরক্ষণ করে সম্পাদনাটির বাতিল প্রক্রিয়া সমাপ্ত করুন।",
"undo-failure": "এ সম্পাদনা মধ্যবর্তী সম্পাদনাসমূহের কারণে পূর্বাবস্থায় ফিরিয়ে নেওয়া যাবে না।",
+ "undo-main-slot-only": "এই সম্পাদনাটি পূর্বাবস্থায় নেওয়া যাবে না কারণ এখানকার বিষয়বস্তু প্রধান স্লটের বাইরে।",
"undo-norev": "সম্পাদনাটি বাতিল করা যাচ্ছেনা কারণ এটি আর নেই বা মুছে ফেলা হয়েছে।",
"undo-nochange": "সম্পাদনাটি পূর্বেই বাতিল করা হয়েছে।",
"undo-summary": "[[Special:Contributions/$2|$2]] ([[User talk:$2|আলাপ]])-এর সম্পাদিত $1 নম্বর সংশোধনটি বাতিল করা হয়েছে",
"action-changetags": "নির্দিষ্ট সংস্করণ এবং লগ ভুক্তিগুলিতে যথেচ্ছভাবে ট্যাগ সংযোজন ও অপসারণ করা",
"action-deletechangetags": "ডাটাবেজ থেকে ট্যাগ অপসরণ করার",
"action-purge": "এই পাতাটি শোধন করুন",
+ "action-apihighlimits": "API কোয়েরি হিসাবে আরও উচ্চ লিমিট ব্যবহার করুন",
+ "action-autoconfirmed": "আইপি-ভিত্তিক রেট সীমানা দ্বারা প্রভাবিত নয়।",
+ "action-bigdelete": "বিশাল ইতিহাস সম্বলিত পাতা মুছে ফেলো",
+ "action-blockemail": "ই-মেইল পাঠাতে কোনো ব্যবহারকারীকে বাঁধা দাও",
+ "action-bot": "সয়ংক্রিয় পদ্ধতি হিসাবে চিহ্নিত করণ",
"action-editprotected": "\"{{int:protect-level-sysop}}\" হিসেবে সুরক্ষিত পাতা সম্পাদনা করার",
"action-editsemiprotected": "\"{{int:protect-level-autoconfirmed}}\" হিসেবে সুরক্ষিত পাতা সম্পাদনা করার",
+ "action-editinterface": "ব্যবহারকারী ইন্টারফেস সম্পাদনা",
+ "action-editusercss": "অন্য ব্যবহারকারীগণের CSS ফাইল সম্পাদনা",
+ "action-edituserjson": "অন্য ব্যবহারকারীগণের JSON ফাইল সম্পাদনা",
+ "action-edituserjs": "অন্য ব্যবহারকারীগণের জাভাস্ক্রিপ্ট ফাইল সম্পাদনা",
"action-editsitecss": "সাইটব্যাপী CSS সম্পাদনা করার",
"action-editsitejson": "সাইটব্যাপী JSON সম্পাদনা করার",
"action-editsitejs": "সাইটব্যাপী জাভাস্ক্রিপ্ট সম্পাদনা করার",
"action-editmyusercss": "স্ব ব্যবহারকারীর CSS ফাইল সম্পাদনা করার",
+ "action-editmyuserjson": "আপনার নিজস্ব ব্যবহারকারী JSON ফাইল সম্পাদনা করা",
+ "action-editmyuserjs": "আপনার নিজস্ব ব্যবহারকারী জাভাস্ক্রিপ্ট ফাইল সম্পাদনা করুন",
+ "action-viewsuppressed": "যেকোন ব্যবহারকারীর কাছ থেকে লুকানো সংস্করণগুলি দেখুন",
+ "action-hideuser": "ব্যবহারকারীকে বাধা দিন, এবং সর্বসাধারণের দৃষ্টিসীমা থেকে সরিয়ে নিন",
+ "action-ipblock-exempt": "আইপি বাধা, স্বয়ংক্রিয় বাধা ও পরিসীমার বাধা এড়ানো",
"action-unblockself": "নিজেকে বাধামুক্ত করার",
+ "action-noratelimit": "রেট লিমিটের ভিত্তিতে পরিবর্তন হবে না",
+ "action-reupload-own": "নিজের দ্বারা আপলোডকৃত ফাইল যা ইতিমধ্যেই বিদ্যমান, সেটি মুছে পুনরায় নতুন করে আপলোড করা",
+ "action-nominornewtalk": "বার্তা লেখার মত আলাপ পাতায় কোনো অনুল্লেখ্য সম্পাদনা নেই",
+ "action-markbotedits": "ফেরত আনা সম্পাদনাসমূহকে বট সম্পাদনা হিসেবে চিহ্নিত করে",
+ "action-patrolmarks": "সাম্প্রতিক পরিবর্তনের পরীক্ষিত চিহ্ন দেখাও",
+ "action-override-export-depth": "লিংকসহ পাতা যার গভীরতা ৫ এর মধ্যে সেগুলো রপ্তানি করুন",
+ "action-suppressredirect": "পাতা স্থানান্তরের সময় মূল পাতা থেকে পুনর্নির্দেশ তৈরী করছে না",
"nchanges": "$1টি {{PLURAL:$1|পরিবর্তন}}",
"enhancedrc-since-last-visit": "{{PLURAL:$1|সর্বশেষ প্রদর্শনের পর}} $1টি",
"enhancedrc-history": "ইতিহাস",
"rcfilters-filter-watchlist-notwatched-description": "আপনার নজরতালিকায় থাকা পাতাগুলি ব্যতীয় সবকিছু।",
"rcfilters-filtergroup-watchlistactivity": "নজরতালিকার কার্যক্রম",
"rcfilters-filter-watchlistactivity-unseen-label": "অদেখা পরিবর্তন",
+ "rcfilters-filter-watchlistactivity-unseen-description": "আপনার নজরতালিকায় থাকা পাতাগুলিতে পরিবর্তন যেগুলিতে আপনি সম্পাদনা করার পর আর যাননি।",
"rcfilters-filter-watchlistactivity-seen-label": "দেখা পরিবর্তন",
+ "rcfilters-filter-watchlistactivity-seen-description": "আপনার নজরতালিকায় থাকা পাতাগুলিতে পরিবর্তন যেগুলিতে আপনি সম্পাদনা করার পর আর যাননি।",
"rcfilters-filtergroup-changetype": "পরিবর্তনের ধরন",
"rcfilters-filter-pageedits-label": "পাতার সম্পাদনা",
"rcfilters-filter-pageedits-description": "উইকি বিষয়বস্তু, আলোচনা, বিষয়শ্রেণীর বিবরণ... ইত্যাদিতে সম্পাদনা",
"rcfilters-preference-help": "ছাঁকনিগুলি অনুসন্ধান বা আলোকপাতকরণ কার্যকারিতা ছাড়া সাম্প্রতিক পরিবর্তন লোড করে",
"rcfilters-watchlist-preference-label": "জাভাস্ক্রিপ্টহীন ইন্টারফেস ব্যবহার করুন",
"rcfilters-watchlist-preference-help": "ছাঁকনি অনুসন্ধান বা আলোকপাতকরণ বৈশিষ্ট্য ছাড়া নজরতালিকা লোড করে।",
+ "rcfilters-filter-showlinkedfrom-label": "লিংক করা এমন পাতাগুলোর পরিবর্তন দেখান",
+ "rcfilters-filter-showlinkedfrom-option-label": "নির্বাচিত পাতা থেকে <strong>পাতা লিংক করা</strong>",
+ "rcfilters-filter-showlinkedto-label": "পাতা লিংক করা এমন পাতাসমূহের পরিবর্তন দেখান",
+ "rcfilters-filter-showlinkedto-option-label": "নির্বাচিত পাতা থেকে <strong>পাতা লিংক করা</strong>",
"rcfilters-target-page-placeholder": "একটি পাতার নাম (বা বিষয়শ্রেণী) লিখুন",
"rcnotefrom": "<strong>$2</strong>টা থেকে সংঘটিত পরিবর্তনগুলি (সর্বোচ্চ <strong>$1টি</strong> দেখানো হয়েছে)।",
"rclistfromreset": "তারিখ নির্বাচন পুনঃস্থাপন করুন",
"uploaded-script-svg": "আপলোডকৃত SVG ফাইলে স্ক্রিপ্টযোগ্য উপাদান \"$1\" পাওয়া গেছে।",
"uploaded-hostile-svg": "আপলোড করা SVG ফাইলের শৈলী উপাদানে অনিরাপদ সিএসএস পাওয়া গেছে।",
"uploaded-event-handler-on-svg": "এসভিজি ফাইলের জন্য <code>$1=\"$2\"</code> ইভেন্ট-হ্যান্ডলার বৈশিষ্ট্যটি নির্ধারণ করা অনুমোদিত নয়।",
- "uploaded-href-attribute-svg": "এসভিজি ফাইলের href বৈশিষ্ট্যগুলির জন্য কেবলমাত্র http:// বা https:// লক্ষ্যগুলি অনুমোদিত; কিন্তু <code><$1 $2=\"$3\"></code> পাওয়া গেছে।",
+ "uploaded-href-attribute-svg": "এসভিজি ফাইলের href বৈশিষ্ট্যগুলির জন্য কেবলমাত্র http:// বা https:// লক্ষ্যগুলি অনুমোদিত। অন্য বিষয় যেমন, <image>, শুধুমাত্র উপাত্ত ও বৈশিষ্ঠগুলো গ্রহণযোগ্য। <code><$1 $2=\"$3\"></code> পাওয়া গেছে।",
"uploaded-href-unsafe-target-svg": "অনিরাপদ উপাত্তে href পাওয়া গেছে: আপলোডকৃত SVG ফাইলে URI লক্ষ্য ছিল <code><$1 $2=\"$3\"></code>।",
"uploaded-animate-svg": "\"animate\" ট্যাগটি পাওয়া গেছে যা আপলোডকৃত এসভিজি ফাইলের <code><$1 $2=\"$3\"></code> - এই \"from\" অ্যাট্রিবিউটটি ব্যবহার করে href পরিবর্তন করতে পারে।",
"uploaded-setting-event-handler-svg": "ইভেন্ট-হ্যান্ডলার অ্যাট্রিবিউট নির্ধারণ করতে বাধা দেওয়া হয়েছে। আপলোডকৃত এসভিজি ফাইলে <code><$1 $2=\"$3\"></code> খুঁজে পাওয়া গেছে।",
"ipb_expiry_old": "মেয়াদোত্তীর্ণের সময় অতীত হয়েছে।",
"ipb_expiry_temp": "লুকানো ব্যবহারকারীনাম বাধা চিরস্থায়ী হতে হবে।",
"ipb_hide_invalid": "এই অ্যাকাউন্ট বাধা দেয়া সম্ভব নয়; এটি {{PLURAL:$1|একের অধিক|$1টি}} সম্পাদনা করেছে।",
+ "ipb_hide_partial": "লুকায়িত ব্যবহারকারী নামের বাধাদান অবশ্যই সাইটওয়াইড হতে হবে।",
"ipb_already_blocked": "\"$1\" ইতিমধ্যে বাধাপ্রাপ্ত।",
"ipb-needreblock": "$1 ইতিমধ্যেই বাধাপ্রাপ্ত আছেন। আপনি কি সেটিংস পরিবর্তন করতে চান?",
"ipb-otherblocks-header": "অন্যান্য {{PLURAL:$1|বাধা|বাধাসমূহ}}",
"viewpagelogs": "Qeydanê na pele bımocne",
"nohistory": "Verorê vurnayışanê na perer çıni yo.",
"currentrev": "Çımraviyarnayışo rocane",
- "currentrev-asof": "$1 ra tepeya çım ra viyarnayışê cı'yo peyên",
+ "currentrev-asof": "Çımraviyarnayışê $1iyo peyên",
"revisionasof": "Çımraviyarnayışê $1",
"revision-info": "Vurnayışo ke $1 de terefê {{GENDER:$6|$2}}$7 ra biyo",
"previousrevision": "← Çımraviyarnayışo kıhanêr",
"accmailtext": "Ένας τυχαία παρηγμένος κωδικός για {{GENDER:$1|τον|την}} [[User talk:$1|$1]] έχει σταλεί στο $2.\n\nΜπορεί να αλλαχθεί από την σελίδα ''[[Special:ChangePassword|αλλαγή κωδικού]]'' μετά τη σύνδεση.",
"newarticle": "(Νέο)",
"newarticletext": "Ακολουθήσατε ένα σύνδεσμο προς μια σελίδα που δεν υπάρχει ακόμα. \nΓια να δημιουργήσετε τη σελίδα, αρχίστε να πληκτρολογείτε στο παρακάτω πλαίσιο (δείτε τη [$1 σελίδα βοήθειας] για περισσότερες πληροφορίες).\nΑν έχετε βρεθεί εδώ κατά λάθος, πατήστε το κουμπί '''πίσω''' στον περιηγητή σας.",
- "anontalkpagetext": "----''Αυτή η σελίδα συζήτησης προορίζεται για ανώνυμο χρήστη που δεν έχει δημιουργήσει ακόμα λογαριασμό ή που δεν τον χρησιμοποιεί. Έτσι για την ταυτοποίηση ενός ανώνυμου χρήστη χρησιμοποιείται η διεύθυνση IP του. Είναι όμως πιθανόν η διεύθυνση αυτή να είναι κοινή για πολλούς διαφορετικούς χρήστες. Αν είστε ανώνυμος χρήστης και νομίζετε ότι άσχετα σχόλια απευθύνθηκαν σε σας, παρακαλούμε να [[Special:CreateAccount|δημιουργήσετε ένα λογαριασμό]] ή να [[Special:UserLogin|συνδεθείτε]] για να αποφεύγεται η μελλοντική σύγχυση με άλλους ανώνυμους χρήστες.''",
+ "anontalkpagetext": "----''Αυτή η σελίδα συζήτησης προορίζεται για ανώνυμο χρήστη που δεν έχει δημιουργήσει ακόμα λογαριασμό ή που δεν τον χρησιμοποιεί. Έτσι για την ταυτοποίηση ενός ανώνυμου χρήστη χρησιμοποιείται η διεύθυνση IP τους. Είναι όμως πιθανόν η διεύθυνση αυτή να είναι κοινή για πολλούς διαφορετικούς χρήστες. Αν είστε ανώνυμος χρήστης και νομίζετε ότι άσχετα σχόλια απευθύνθηκαν σε σας, παρακαλούμε να [[Special:CreateAccount|δημιουργήσετε ένα λογαριασμό]] ή να [[Special:UserLogin|συνδεθείτε]] για να αποφεύγεται η μελλοντική σύγχυση με άλλους ανώνυμους χρήστες.''",
"noarticletext": "Δεν υπάρχει προς το παρόν κείμενο σε αυτή τη σελίδα. \nΜπορείτε να [[Special:Search/{{PAGENAME}}|αναζητήσετε αυτόν τον τίτλο σελίδας]] σε άλλες σελίδες,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} να αναζητήσετε τις σχετικές καταγραφές],\nή να [{{fullurl:{{FULLPAGENAME}}|action=edit}} δημιουργήσετε αυτή τη σελίδα]</span>.",
"noarticletext-nopermission": "Δεν υπάρχει προς το παρόν κείμενο σε αυτή τη σελίδα.\nΜπορείτε να [[Special:Search/{{PAGENAME}}|αναζητήσετε αυτόν τον τίτλο σελίδας]] σε άλλες σελίδες, ή <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} να ψάξετε στις σχετικές καταγραφές]</span>, αλλά δεν έχετε την άδεια να δημιουργήσετε αυτή τη σελίδα.",
"missing-revision": "Δεν υπάρχει αναθεώρηση με αριθμό $1 για τη σελίδα με όνομα «{{FULLPAGENAME}}».\n\nΑυτό συνήθως προκαλείται από παλιό σύνδεσμο ιστορικού προς σελίδα που έχει διαγραφεί.\nΛεπτομέρειες θα βρείτε στο [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} ημερολόγιο καταγραφής διαγραφών].",
"page_first": "πρώτη",
"page_last": "τελευταία",
"histlegend": "Επιλογή διαφορών: Μαρκάρετε τα κουτάκια επιλογής των εκδόσεων που θέλετε να συγκρίνετε και πατήστε το enter ή το κουμπί στο κάτω μέρος.<br />\nΥπόμνημα: '''({{int:cur}})''' = διαφορά από την τελευταία έκδοση, '''({{int:last}})''' = διαφορά από την προηγούμενη έκδοση, '''{{int:minoreditletter}}''' = μικροαλλαγή.",
- "history-fieldset-title": "ΠεÏ\81ιήγηÏ\83η Ï\83Ï\84ο ιÏ\83Ï\84οÏ\81ικÏ\8c αλλαγών",
+ "history-fieldset-title": "ΦιλÏ\84Ï\81άÏ\81ιÏ\83μα αλλαγών",
"history-show-deleted": "Διαγεγραμμένες μόνο",
"histfirst": "η πιο παλιά",
"histlast": "η πιο πρόσφατη",
"revertpage": "Ανάκληση των αλλαγών [[Special:Contributions/$2|$2]] ([[User talk:$2|συζήτηση]]) επιστροφή στην προηγούμενη αναθεώρηση [[User:$1|$1]]",
"revertpage-nouser": "Αναστράφηκαν οι επεξεργασίες από τον (όνομα χρήστη αφαιρέθηκε) στη τελευταία έκδοση από τον/την {{GENDER:$1|[[User:$1|$1]]}}φ",
"rollback-success": "Αναστροφή επεξεργασιών από {{GENDER:$3|τον|την}} $1, επιστροφή στην προηγούμενη έκδοση από {{GENDER:$4|τον|την}} $2.",
- "sessionfailure-title": "Î\97 Ï\83Ï\85νεδÏ\81ία αÏ\80ÎÏ\84Ï\85Ï\87ε",
- "sessionfailure": "Î¥Ï\80άÏ\81Ï\87ει Ï\80Ï\81Ï\8cβλημα με Ï\84η Ï\83Ï\8dνδεÏ\83ή Ï\83αÏ\82 -η ενÎÏ\81γεια αÏ\85Ï\84ή ακÏ\85Ï\81Ï\8eθηκε Ï\80Ï\81οληÏ\80Ï\84ικά για Ï\84ην ανÏ\84ιμεÏ\84Ï\8eÏ\80ιÏ\83η Ï\84Ï\85Ï\87Ï\8cν Ï\80ειÏ\81αÏ\84είαÏ\82 Ï\83Ï\85νÏ\8cδοÏ\85 (session hijacking). ΠαÏ\81ακαλoÏ\8dμε Ï\80αÏ\84ήÏ\83Ï\84ε \"Î\95Ï\80ιÏ\83Ï\84Ï\81οÏ\86ή\", ξαναÏ\86οÏ\81Ï\84Ï\8eÏ\83Ï\84ε Ï\84η Ï\83ελίδα αÏ\80Ï\8c Ï\84ην οÏ\80οία Ï\86θάÏ\83αÏ\84ε εδÏ\8e και Ï\80Ï\81οÏ\83Ï\80αθήÏ\83Ï\84ε ξανά.",
+ "sessionfailure-title": "Î\91Ï\80οÏ\84Ï\85Ï\87ία Ï\80εÏ\81ιÏ\8cδοÏ\85 Ï\83Ï\8dνδεÏ\83ηÏ\82",
+ "sessionfailure": "ΦαίνεÏ\84αι Ï\8cÏ\84ι Ï\85Ï\80άÏ\81Ï\87ει κάÏ\80οιο Ï\80Ï\81Ï\8cβλημα με Ï\84ην Ï\80εÏ\81ίοδο Ï\83Ï\8dνδεÏ\83ήÏ\82 Ï\83αÏ\82.\nÎ\91Ï\85Ï\84ή η ενÎÏ\81γεια ακÏ\85Ï\81Ï\8eθηκε Ï\89Ï\82 Ï\80Ï\81οÏ\86Ï\8dλαξη για Ï\84ην ανÏ\84ιμεÏ\84Ï\8eÏ\80ιÏ\83η Ï\84Ï\85Ï\87Ï\8cν Ï\83Ï\86εÏ\84εÏ\81ιÏ\83μοÏ\8d Ï\84ηÏ\82 Ï\80εÏ\81ιÏ\8cδοÏ\85 Ï\83Ï\8dνδεÏ\83ηÏ\82 αÏ\80Ï\8c κάÏ\80οιον Ï\84Ï\81ίÏ\84ο (session hijacking).\nΠαÏ\81ακαλοÏ\8dμε Ï\85Ï\80οβάλεÏ\84ε ξανά Ï\84η Ï\86Ï\8cÏ\81μα.",
"changecontentmodel": "Αλλαγή μοντέλου περιεχομένου της σελίδας",
"changecontentmodel-legend": "Μοντέλο περιεχομένου σελίδας",
"changecontentmodel-title-label": "Τίτλος σελίδας",
"expand_templates_generate_xml": "Εμφάνιση δέντρου συντακτικής ανάλυσης XML",
"expand_templates_generate_rawhtml": "Εμφάνιση ανεπεξέργαστης HTML",
"expand_templates_preview": "Προεπισκόπηση",
- "expand_templates_preview_fail_html": "<em>Επειδή το {{SITENAME}} επιτρέπει την εισαγωγή ακατέργαστου HTML και υπήρξε μια απώλεια των δεδομένων συνόδου, η προεπισκόπηση είναι κρυμμένη ως προληπτικό μέτρο κατά επιθέσεων JavaScript.</em>\n\n<strong>Αν αυτή είναι μια θεμιτή απόπειρα προεπισκόπησης, παρακαλούμε δοκιμάστε ξανά.</strong>\nΑν εξακολουθεί να μην λειτουργεί, δοκιμάστε να [[Special:UserLogout|αποσυνδεθείτε]] και να συνδεθείτε ξανά και βεβαιωθείτε ότι το πρόγραμμα περιήγησής σας επιτρέπει cookies από αυτόν τον ιστότοπο.",
+ "expand_templates_preview_fail_html": "<em>Επειδή το {{SITENAME}} επιτρέπει την εισαγωγή ακατέργαστου HTML και υπήρξε μια απώλεια δεδομένων της περιόδου σύνδεσης, η προεπισκόπηση είναι κρυμμένη ως προληπτικό μέτρο κατά επιθέσεων JavaScript.</em>\n\n<strong>Αν αυτή είναι μια θεμιτή απόπειρα προεπισκόπησης, παρακαλούμε δοκιμάστε ξανά.</strong>\nΑν εξακολουθεί να μην λειτουργεί, δοκιμάστε να [[Special:UserLogout|αποσυνδεθείτε]] και να συνδεθείτε ξανά και βεβαιωθείτε ότι το πρόγραμμα περιήγησής σας επιτρέπει cookies από αυτόν τον ιστότοπο.",
"expand_templates_preview_fail_html_anon": "<em>Επειδή το {{SITENAME}} έχει ενεργοποιημένη raw HTML και δεν είστε συνδεδεμένοι, η προεπισκόπηση είναι κρυμμένη ως ένα προληπτικό μέτρο ενάντια σε επιθέσεις JavaScript.</em>\n\n<strong>Αν αυτό είναι δικαιολογημένη απόπειρα προεπισκόπησης, παρακαλούμε να [[Special:UserLogin|συνδεθείτε]] και δοκιμάστε πάλι.</strong>",
"pagelanguage": "Αλλαγή γλώσσας σελίδας",
"pagelang-name": "Σελίδα",
"mw-widgets-titlesmultiselect-placeholder": "Προσθήκη περισσότερων...",
"date-range-from": "Από ημερομηνία:",
"date-range-to": "Έως ημερομηνία:",
- "sessionprovider-generic": "$1 συνεδρίες",
- "sessionprovider-mediawiki-session-cookiesessionprovider": "Ï\83Ï\85νεδÏ\81ίεÏ\82 με βάÏ\83η Ï\84α cookies",
+ "sessionprovider-generic": "Περίοδοι σύνδεσης $1",
+ "sessionprovider-mediawiki-session-cookiesessionprovider": "Ï\80εÏ\81ίοδοι Ï\83Ï\8dνδεÏ\83ηÏ\82 βαÏ\83ιÏ\83μÎνεÏ\82 Ï\83ε cookies",
"sessionprovider-nocookies": "Τα Cookies μπορούν να απενεργοποιηθούν. Βεβαιωθείτε ότι έχετε ενεργοποιημένα τα cookies και ξεκινήστε πάλι.",
"randomrootpage": "Τυχαία κύρια σελίδα",
"log-action-filter-block": "Τύπος φραγής:",
"exif-compression-4": "CCITT Grupa 4 faks kodiranje",
"exif-copyrighted-true": "Zaštićeno autorskim pravom",
"exif-copyrighted-false": "Status autorskih prava nije postavljen",
+ "exif-photometricinterpretation-0": "Crno-bijelo (bijela je 0)",
"exif-photometricinterpretation-1": "Crno-bijelo (crna je 0)",
+ "exif-photometricinterpretation-3": "Paleta",
+ "exif-photometricinterpretation-4": "Maska prozirnosti",
+ "exif-photometricinterpretation-5": "Separirano (vjerojatno CMYK)",
+ "exif-photometricinterpretation-8": "CIE L*a*b*",
+ "exif-photometricinterpretation-9": "CIE L*a*b* (ICC kodiranje)",
+ "exif-photometricinterpretation-10": "CIE L*a*b* (ITU kodiranje)",
"exif-unknowndate": "nepoznat datum",
"exif-orientation-1": "Normalno",
"exif-orientation-2": "Zrcaljeno po horizontali",
"mycontris": "Bydragen",
"anoncontribs": "Bydragen",
"contribsub2": "Foar {{GENDER:$3|$1}} ($2)",
+ "contributions-subtitle": "Foar {{GENDER:$3|$1}}",
"nocontribs": "Der binne gjin feroarings fûn dy't oan dizze kritearia foldwaan.",
"uctop": "lêste feroaring",
"month": "Fan moanne (en earder):",
"nolicense": "Ništa nije odabrano",
"licenses-edit": "Uredi izbor licencija",
"license-nopreview": "(Prikaz nije moguć)",
- "upload_source_url": " (izabrali ste datoteku s valjanog, javno dostupnog URL-a)",
- "upload_source_file": "(izabrali ste datoteku s Vašeg računala)",
+ "upload_source_url": "(izabrana datoteka s valjanog, javno dostupnog URL-a)",
+ "upload_source_file": "(izabrana datoteka s Vašeg računala)",
"listfiles-delete": "izbriši",
"listfiles-summary": "Ova stranica pokazuje sve postavljene datoteke.\nKad je filtriran po suradniku, popis prikazuje samo one datoteke čije je posljednje inačice postavio taj suradnik.",
"listfiles_search_for": "Traži ime slike:",
"tog-norollbackdiff": "巻き戻し後の差分を表示しない",
"tog-useeditwarning": "変更を保存せずに編集画面から離れようとしたら警告",
"tog-prefershttps": "ログインする際、常に安全な接続を使用する",
+ "tog-showrollbackconfirmation": "巻き戻しリンクをクリックした際に確認画面を表示する",
"underline-always": "常に付ける",
"underline-never": "常に付けない",
"underline-default": "外装またはブラウザーの既定値を使用",
"badretype": "入力したパスワードが一致しません。",
"usernameinprogress": "この利用者名のためのアカウント作成は、すでに進行中です。お待ちください。",
"userexists": "入力した利用者名は既に使用されています。\n別の利用者名を指定してください。",
+ "createacct-normalization": "技術的制限により指定された利用者名は「$2」として登録されます。",
"loginerror": "ログインのエラー",
"createacct-error": "アカウント作成エラー",
"createaccounterror": "アカウントを作成できませんでした: $1",
"page_first": "先頭",
"page_last": "末尾",
"histlegend": "差分の選択: 比較したい版のラジオボタンを選択し、Enterキーを押すか、下部のボタンを押します。<br />\n凡例: <strong>({{int:cur}})</strong>=最新版との比較、<strong>({{int:last}})</strong>=直前の版との比較、<strong>{{int:minoreditletter}}</strong>=細部の編集",
- "history-fieldset-title": "ç\89\88ã\81®æ¤\9cç´¢",
+ "history-fieldset-title": "ç\89\88ã\82\92ã\83\95ã\82£ã\83«ã\82¿ã\83¼",
"history-show-deleted": "削除版のみ",
"histfirst": "最古",
"histlast": "最新",
"rcfilters-savedqueries-already-saved": "これらのフィルタは既に保存されています。設定を変更して、新しい保存フィルタを作成します。",
"rcfilters-restore-default-filters": "標準設定の絞り込み条件を適用",
"rcfilters-clear-all-filters": "すべてのフィルターをクリア",
- "rcfilters-show-new-changes": "最新の変更を表示",
+ "rcfilters-show-new-changes": "$1 から最新の変更を表示",
"rcfilters-search-placeholder": "絞り込みを行う(メニューから選択、またはフィルター名で検索)",
"rcfilters-invalid-filter": "無効なフィルター",
"rcfilters-empty-filter": "絞り込みは行われていません。全ての項目が表示されます。",
"ipb-confirm": "ブロックの確認",
"ipb-sitewide": "サイト全体",
"ipb-partial": "部分的",
+ "ipb-sitewide-help": "ウィキにおける各ページとその他の投稿操作。",
"ipb-partial-help": "特定のページまたは名前空間。",
"ipb-pages-label": "ページ",
"ipb-namespaces-label": "名前空間",
"blocklist-userblocks": "アカウントのブロックを非表示",
"blocklist-tempblocks": "期限付きブロックを非表示",
"blocklist-addressblocks": "単一 IP のブロックを非表示",
+ "blocklist-type": "種類:",
+ "blocklist-type-opt-all": "すべて",
+ "blocklist-type-opt-sitewide": "サイト全体",
+ "blocklist-type-opt-partial": "部分的",
"blocklist-rangeblocks": "範囲ブロックを非表示",
"blocklist-timestamp": "日時",
"blocklist-target": "対象",
"blocklist-editing-page": "ページ",
"blocklist-editing-ns": "名前空間",
"ipblocklist-empty": "ブロック一覧は空です。",
- "ipblocklist-no-results": "æ\8c\87å®\9aã\81\95ã\82\8cã\81\9fIPã\82¢ã\83\89ã\83¬ã\82¹ã\81¾ã\81\9fã\81¯å\88©ç\94¨è\80\85å\90\8dã\81¯ã\83\96ã\83ã\83\83ã\82¯ã\81\95ã\82\8cã\81¦ã\81\84ã\81¾ã\81\9bã\82\93。",
+ "ipblocklist-no-results": "æ\8c\87å®\9aã\81\95ã\82\8cã\81\9fIPã\82¢ã\83\89ã\83¬ã\82¹ã\81¾ã\81\9fã\81¯å\88©ç\94¨è\80\85å\90\8dã\81«ä¸\80è\87´ã\81\99ã\82\8bã\83\96ã\83ã\83\83ã\82¯ã\81¯è¦\8bã\81¤ã\81\8bã\82\8aã\81¾ã\81\9bã\82\93ã\81§ã\81\97ã\81\9f。",
"blocklink": "ブロック",
"unblocklink": "ブロック解除",
"change-blocklink": "設定を変更",
"confirm-unwatch-top": "このページをウォッチリストから除去しますか?",
"confirm-rollback-button": "OK",
"confirm-rollback-top": "このページの編集を差し戻しますか?",
+ "confirm-rollback-bottom": "この操作はこのページに対する指定した変更即座に巻き戻します。",
"confirm-mcrrestore-title": "版を復帰",
"confirm-mcrundo-title": "直前の変更を取り消す",
"mcrundofailed": "取り消しに失敗しました",
"logentry-block-block": "$1 が {{GENDER:$4|$3}} を$5{{GENDER:$2|ブロックしました}} $6",
"logentry-block-unblock": "$1 が {{GENDER:$4|$3}} の{{GENDER:$2|ブロックを解除しました}}",
"logentry-block-reblock": "$1 が {{GENDER:$4|$3}} のブロックの期限を$5に{{GENDER:$2|変更しました}} $6",
+ "logentry-partialblock-block-page": "{{PLURAL:$1|ページ}} $2",
+ "logentry-partialblock-block-ns": "{{PLURAL:$1|名前空間}} $2",
+ "logentry-partialblock-block": "$1 が {{GENDER:$4|$3}} に対して $7 からの編集を $5 {{GENDER:$2||ブロックしました}} $6",
+ "logentry-partialblock-reblock": "$1 が {{GENDER:$4|$3}} に対する $7 のブロックの期限を $5 に{{GENDER:$2|変更しました}} $6",
"logentry-suppress-block": "$1 が {{GENDER:$4|$3}} を$5で{{GENDER:$2|ブロックしました}} $6",
"logentry-suppress-reblock": "$1 が {{GENDER:$4|$3}} のブロックの期限を$5に{{GENDER:$2|変更しました}} $6",
"logentry-import-upload": "$1 がファイルをアップロードして $3 を{{GENDER:$2|インポートしました}}",
"passwordpolicies-policy-passwordcannotmatchblacklist": "パスワードは、特にブラックリストに載っているものと一致するものは設定できません",
"passwordpolicies-policy-maximalpasswordlength": "パスワードは$1{{PLURAL:$1|文字}}以下でなければなりません",
"passwordpolicies-policy-passwordcannotbepopular": "パスワードは{{PLURAL:$1|一般的なものにすることはできません|一般的な$1個のパスワードのリストと一致するものにすることはできません}}",
+ "passwordpolicies-policy-passwordnotinlargeblacklist": "一般的に使われるパスワード10万項目のリストに含まれるパスワードは使用できません。",
+ "passwordpolicies-policyflag-forcechange": "ログイン時に変更を強制",
+ "passwordpolicies-policyflag-suggestchangeonlogin": "ログイン時に変更を提案",
"easydeflate-invaliddeflate": "提供されたコンテンツが適切に圧縮されていません",
- "unprotected-js": "セキュリティ上の理由から、JavaScriptは保護されていないページからは読み込みできません。MediaWiki: 名前空間内、利用者下位ページのいずれかでのみjavascriptを作成してください。"
+ "unprotected-js": "セキュリティ上の理由から、JavaScriptは保護されていないページからは読み込みできません。MediaWiki: 名前空間内、利用者下位ページのいずれかでのみjavascriptを作成してください。",
+ "userlogout-continue": "ログアウトを行いたい場合、[$1 ログアウトページから実施]してください。",
+ "userlogout-sessionerror": "セッションエラーによりログアウトに失敗しました。再度 [$1 試行して]ください。"
}
"page_first": "prva",
"page_last": "zadnja",
"histlegend": "Izbira primerjave: označite okroglo polje ob redakciji za primerjavo in stisnite enter ali gumb na dnu strani.<br />\nLegenda: '''({{int:cur}})''' = primerjava s trenutno redakcijo, '''({{int:last}})''' = primerjava s prejšnjo redakcijo, '''{{int:minoreditletter}}''' = manjše urejanje.",
- "history-fieldset-title": "Iskanje redakcij",
+ "history-fieldset-title": "Filtrirajte redakcije",
"history-show-deleted": "Samo izbrisana redakcija",
"histfirst": "najstarejše",
"histlast": "najnovejše",
"right-editsemiprotected": "Urejanje strani, zaščitenih kot »{{int:protect-level-autoconfirmed}}«",
"right-editcontentmodel": "Urejanje vsebinskega modela strani",
"right-editinterface": "Urejanje uporabniškega vmesnika",
- "right-editusercss": "Urejanje CSS datotek drugih uporabnikov",
+ "right-editusercss": "Urejanje CSS-datotek drugih uporabnikov",
"right-edituserjson": "Urejanje JSON-datotek drugih uporabnikov",
"right-edituserjs": "Urejanje JavaScript datotek drugih uporabnikov",
"right-editsitecss": "Urejanje CSS spletišča",
"right-userrights": "Urejanje vseh uporabniških pravic",
"right-userrights-interwiki": "Urejanje uporabniških pravic uporabnikov na drugih wikijih",
"right-siteadmin": "Zaklepanje in odklepanje baze podatkov",
- "right-override-export-depth": "Izvoz strani, vključno s povezaimi straneh do globine 5",
+ "right-override-export-depth": "Izvoz strani, vključno s povezanimi stranmi do globine 5",
"right-sendemail": "Pošiljanje e-pošte drugim uporabnikom",
"right-managechangetags": "Ustvarjanje in (dez)aktivacijo [[Special:Tags|oznak]]",
"right-applychangetags": "Uveljavitev [[Special:Tags|oznak]] skupaj s spremembami",
"action-changetags": "dodajanje in odstranjevanje poljubnih oznak na posameznih redakcijah in dnevniških vnosih",
"action-deletechangetags": "izbris oznak iz zbirke podatkov",
"action-purge": "počiščenje strani",
+ "action-apihighlimits": "uporabo višje omejitve poizvedb API",
+ "action-autoconfirmed": "neomejitev dejavnosti glede na IP",
+ "action-bigdelete": "brisanje strani z obsežno zgodovino",
+ "action-blockemail": "preprečite pošiljanja e-pošte drugemu uporabniku",
+ "action-bot": "obravnavo kot avtomatiziran postopek",
+ "action-editprotected": "urejanje strani, zaščitenih kot »{{int:protect-level-sysop}}«,",
+ "action-editsemiprotected": "urejanje strani, zaščitenih kot »{{int:protect-level-autoconfirmed}}«,",
+ "action-editinterface": "urejanje uporabniškega vmesnika",
+ "action-editusercss": "urejanje CSS-datotek drugih uporabnikov",
+ "action-edituserjson": "urejanje JSON-datotek drugih uporabnikov",
+ "action-edituserjs": "urejanje JavaScript datotek drugih uporabnikov",
+ "action-editsitecss": "urejanje CSS spletišča",
+ "action-editsitejson": "urejanje JSON spletišča",
+ "action-editsitejs": "urejanje JavaScripta spletišča",
+ "action-editmyusercss": "urejanje svojih uporabniških datotek CSS",
+ "action-editmyuserjson": "urejanje svojih uporabniških datotek JSON",
+ "action-editmyuserjs": "urejanje svojih uporabniških datotek JavaScript",
+ "action-viewsuppressed": "ogled redakcij skritih pred vsemi uporabniki",
+ "action-hideuser": "blokiranje uporabnika in skritje pred javnostjo",
+ "action-ipblock-exempt": "izogib blokadam IP-naslova, samodejnim blokadam in blokadam območij",
+ "action-unblockself": "odblokiranje samega sebe",
+ "action-noratelimit": "izogib omejitvam dejavnosti",
+ "action-reupload-own": "nadomeščanje obstoječih lastnih datotek",
+ "action-nominornewtalk": "to, da urejanja pogovornih strani, ki niso označena kot manjša, ne sprožijo obvestila o novem sporočilu,",
+ "action-markbotedits": "označitev vrnjenih urejanj kot urejanja botov",
+ "action-patrolmarks": "ogled oznak nadzorov v zadnjih spremembah",
+ "action-override-export-depth": "izvoz strani, vključno s povezanimi stranmi do globine 5,",
+ "action-suppressredirect": "možnost izpuščanja preusmeritve pri premikanju strani",
"nchanges": "$1 {{PLURAL:$1|sprememba|spremembi|spremembe|sprememb|sprememb}}",
"enhancedrc-since-last-visit": "$1 {{PLURAL:$1|od zadnjega obiska}}",
"enhancedrc-history": "zgodovina",
"rcfilters-savedqueries-already-saved": "Te filtre ste že shranili. Uporabite svoje nastavitve, da ustvarite nov Shranjen filter.",
"rcfilters-restore-default-filters": "Obnovi privzete filtre",
"rcfilters-clear-all-filters": "Počisti vse filtre",
- "rcfilters-show-new-changes": "Ogled najnovejših sprememb",
+ "rcfilters-show-new-changes": "Ogled novih sprememb od $1",
"rcfilters-search-placeholder": "Filtriraj zadnje spremembe (uporabi meni ali vnesi ime filtra)",
"rcfilters-invalid-filter": "Neveljaven filter",
"rcfilters-empty-filter": "Ni dejavnih filtrov. Prikazani so vsi prispevki.",
"blocklist-userblocks": "skrij blokade računov",
"blocklist-tempblocks": "skrij začasne blokade",
"blocklist-addressblocks": "skrij blokade posameznih IP-naslovov",
+ "blocklist-type": "Vrsta:",
+ "blocklist-type-opt-all": "Vse",
+ "blocklist-type-opt-sitewide": "Po celotni strani",
+ "blocklist-type-opt-partial": "Delno",
"blocklist-rangeblocks": "skrij blokade razponov",
"blocklist-timestamp": "Časovni žig",
"blocklist-target": "Cilj",
"blocklist-editing-page": "strani",
"blocklist-editing-ns": "imenski prostori",
"ipblocklist-empty": "Seznam blokad je prazen.",
- "ipblocklist-no-results": "Zahtevan IP-naslov ali uporabniško ime ni blokirano.",
+ "ipblocklist-no-results": "Ne najdemo ujemajočih blokov za zahtevan IP-naslov ali uporabniško ime.",
"blocklink": "blokiraj",
"unblocklink": "deblokiraj",
"change-blocklink": "spremeni blokado",
"passwordpolicies-policyflag-forcechange": "treba spremeniti ob prijavi",
"passwordpolicies-policyflag-suggestchangeonlogin": "predlagaj zamenjavo ob prijavi",
"easydeflate-invaliddeflate": "Dana vsebina ni pravilno stisnjena",
- "unprotected-js": "Iz varnostnih razlogov JavaScripta ni možno naložiti z nezaščitenih strani. Prosimo, da JavaScript ustvarite samo v imenskem prostoru MediaWiki ali kot uporabniško podstran."
+ "unprotected-js": "Iz varnostnih razlogov JavaScripta ni možno naložiti z nezaščitenih strani. Prosimo, da JavaScript ustvarite samo v imenskem prostoru MediaWiki ali kot uporabniško podstran.",
+ "userlogout-continue": "Če se želite odjaviti, [$1 pojdite na stran za odjavo].",
+ "userlogout-sessionerror": "Odjava je spodletela zaradi napake seje. Prosimo, [$1 poskusite znova]."
}
"diff-paragraph-moved-toold": "Пасус је премештен. Кликните да пређете на стару локацију.",
"difference-missing-revision": "{{PLURAL:$2|Једна измена|$2 измене}} ове разлике ($1) не {{PLURAL:$2|постоји|постоје}}.\n\nОво се обично дешава када пратите застарелу везу до странице која је избрисана.\nДетаље можете да пронађете у [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} дневнику брисања].",
"searchresults": "Резултати претраге",
+ "search-filter-title-prefix": "Само претражује на страницама чији наслов почиње са „$1”",
"search-filter-title-prefix-reset": "Претражи све странице",
"searchresults-title": "Резултати претраге за „$1“",
"titlematches": "Наслов странице одговара",
"right-editusercss": "уређивање туђих Це-Ес-Ес датотека",
"right-edituserjson": "уређивање туђих ЈСОН датотека",
"right-edituserjs": "уређивање туђих јаваскрипт датотека",
+ "right-editsitecss": "уређивање CSS-а на нивоу сајта",
+ "right-editsitejson": "уређивање JSON-а на нивоу сајта",
+ "right-editsitejs": "Уређивање JavaScript-а на нивоу сајта",
"right-editmyusercss": "уређивање сопствених Це-Ес-Ес датотека",
"right-editmyuserjson": "уређивање сопствених ЈСОН датотека",
"right-editmyuserjs": "уређивање сопствених јаваскрипт датотека",
"action-deletechangetags": "бришете ознаке из базе података",
"action-purge": "освежите ову страницу",
"action-blockemail": "блокирате кориснику слање е-порука",
+ "action-editsitecss": "уређујете CSS на новоу сајта",
+ "action-editsitejson": "уређујете JSON на нивоу сајта",
+ "action-editsitejs": "уређујете JavaScript на новоу сајта",
"action-hideuser": "блокирате корисничко име, сакривајући га од јавности",
"nchanges": "$1 {{PLURAL:$1|промена|промене|промена}}",
"ntimes": "$1×",
"recentchanges-network": "Због техничког проблема, није могуће учитати резултате. Покушајте да освежите страницу.",
"recentchanges-notargetpage": "Унесите име странице изнад да бисте видели промене сродне с овом страницом",
"recentchanges-feed-description": "Пратите недавне промене на викију у овом фиду.",
- "recentchanges-label-newpage": "Овом изменом направљена је нова страница.",
+ "recentchanges-label-newpage": "Овом изменом направљена је нова страница",
"recentchanges-label-minor": "Ово је мања измена",
"recentchanges-label-bot": "Ову измену је направио бот",
"recentchanges-label-unpatrolled": "Ова измена још није патролирана",
"rcfilters-savedqueries-already-saved": "Ови филтери су већ сачувани. Промените своја подешавања да бисте направили нове сачуване филтере.",
"rcfilters-restore-default-filters": "Врати подразумеване филтере",
"rcfilters-clear-all-filters": "Обришите све филтере",
- "rcfilters-show-new-changes": "Погледај нове измене од $1",
+ "rcfilters-show-new-changes": "Прикажи нове промене од $1",
"rcfilters-search-placeholder": "Филтрирајте промене (користите мени или претрагу за име филтера)",
"rcfilters-invalid-filter": "Неважећи филтер",
"rcfilters-empty-filter": "Нема активних филтера. Сви доприноси су приказани.",
"blocklist-userblocks": "Сакриј блокаде налога",
"blocklist-tempblocks": "Сакриј привремене блокаде",
"blocklist-addressblocks": "Сакриј појединачне блокаде IP-а",
+ "blocklist-type-opt-sitewide": "На новоу сајта",
+ "blocklist-type-opt-partial": "Делимично",
"blocklist-rangeblocks": "Сакриј блокаде опсега",
"blocklist-timestamp": "Временска ознака",
"blocklist-target": "Корисник",
"blocklist-editing-page": "странице",
"blocklist-editing-ns": "именски простори",
"ipblocklist-empty": "Списак блокирања је празан.",
- "ipblocklist-no-results": "Ð\9dиÑ\81Ñ\83 пÑ\80онаÑ\92ене одговаÑ\80аÑ\98Ñ\83Ñ\9bе блокаде за Ñ\82Ñ\80аженÑ\83 Ð\98Ð\9f адÑ\80еÑ\81Ñ\83 или коÑ\80иÑ\81ниÑ\87ко име.",
+ "ipblocklist-no-results": "Ð\9dиÑ\81Ñ\83 пÑ\80онаÑ\92ена одговаÑ\80аÑ\98Ñ\83Ñ\9bа блокиÑ\80аÑ\9aа Ñ\82Ñ\80ажене IP адÑ\80еÑ\81е или коÑ\80иÑ\81ниÑ\87ког имена.",
"blocklink": "блокирај",
"unblocklink": "деблокирај",
"change-blocklink": "промени блокаду",
"logentry-block-unblock": "$1 је {{GENDER:$2|деблокирао|деблокирала}} {{GENDER:$4|$3}}",
"logentry-block-reblock": "$1 је {{GENDER:$2|променио|променила}} подешавања за блокирање {{GENDER:$4|корисника|кориснице}} {{GENDER:$4|$3}} у трајању од $5 $6",
"logentry-partialblock-block-page": "{{PLURAL:$1|странице|страница}} $2",
- "logentry-partialblock-block": "$1 је {{GENDER:$2|блокирао|блокирала}} уређивање $7 {{GENDER:$4|кориснику|корисници|кориснику/ци}} $3 са временом истека од $5 $6",
+ "logentry-partialblock-block-ns": "{{PLURAL:$1|именског простора|именских простора}} $2",
+ "logentry-partialblock-block": "$1 је {{GENDER:$2|блокирао|блокирала}} уређивање $7 {{GENDER:$4|кориснику|корисници}} $3 са временом истека од $5 $6",
+ "logentry-partialblock-reblock": "$1 је {{GENDER:$2|променио}} подешавања блокирања {{GENDER:$4|корисника|кориснице}} $3 спречавањем измена $7 са временом истека од $5 $6",
"logentry-non-editing-block-block": "$1 је {{GENDER:$2|блокирао|блокирала}} одређене неуређивачке радње {{GENDER:$4|кориснику|корисници|кориснику/ци}} $3 са временом истека од $5 $6",
"logentry-non-editing-block-reblock": "$1 је {{GENDER:$2|променио|променила}} подешавања блокаде одређених неуређивачких радњи {{GENDER:$4|кориснику|корисници|кориснику/ци}} $3 са временом истека од $5 $6",
"logentry-suppress-block": "$1 је {{GENDER:$2|блокирао|блокирала}} {{GENDER:$4|$3}} у трајању од $5 $6",
"userrights-groupsmember": "สมาชิกของ:",
"userrights-groupsmember-auto": "สมาชิกโดยปริยายของ:",
"userrights-groupsmember-type": "$1",
- "userrights-groups-help": "à¸\84ุà¸\93สามารà¸\96à¹\80à¸\9bลีà¹\88ยà¸\99à¹\81à¸\9bลà¸\87à¸\81ลุà¹\88มà¸\97ีà¹\88à¸\9cูà¹\89à¹\83à¸\8aà¹\89รายà¸\99ีà¹\89à¸à¸¢à¸¹à¹\88:\n* à¸\81ลà¹\88à¸à¸\87à¸\97ีà¹\88มีà¹\80à¸\84รืà¹\88à¸à¸\87หมายà¸\96ูà¸\81 หมายà¸\84วามวà¹\88า à¸\9cูà¹\89à¹\83à¸\8aà¹\89à¸à¸¢à¸¹à¹\88à¹\83à¸\99à¸\81ลุà¹\88มà¸\99ัà¹\89à¸\99\n* à¸\81ลà¹\88à¸à¸\87à¸\97ีà¹\88à¹\84มà¹\88มีà¹\80à¸\84รืà¹\88à¸à¸\87หมายà¸\96ูà¸\81 หมายà¸\84วามวà¹\88า à¸\9cูà¹\89à¹\83à¸\8aà¹\89à¹\84มà¹\88à¹\84à¸\94à¹\89à¸à¸¢à¸¹à¹\88à¹\83à¸\99à¸\81ลุà¹\88มà¸\99ัà¹\89à¸\99\n* à¹\80à¸\84รืà¹\88à¸à¸\87หมาย * à¸\8aีà¹\89วà¹\88าà¸\84ุà¸\93à¹\84มà¹\88สามารà¸\96à¸\99ำà¸\81ลุà¹\88มà¸\99ัà¹\89à¸\99à¸à¸à¸\81à¹\84à¸\94à¹\89à¹\80มืà¹\88à¸à¸\84ุà¸\93à¹\80à¸\9eิà¹\88มà¸\81ลุà¹\88มà¸\99ัà¹\89à¸\99à¹\84à¸\9bà¹\81ลà¹\89ว หรืà¸à¸\81ลัà¸\9aà¸\81ัà¸\99\n* à¹\80à¸\84รืà¹\88à¸à¸\87หมาย # à¸\9aี้ว่าคุณสามารถแก้คืนเวลาหมดอายุของสมาชิกภาพกลุ่มนี้เท่านั้น คุณไม่สามารถร่นเวลาหมดอายุได้",
+ "userrights-groups-help": "à¸\84ุà¸\93สามารà¸\96à¹\80à¸\9bลีà¹\88ยà¸\99à¹\81à¸\9bลà¸\87à¸\81ลุà¹\88มà¸\97ีà¹\88à¸\9cูà¹\89à¹\83à¸\8aà¹\89รายà¸\99ีà¹\89à¸à¸¢à¸¹à¹\88:\n* à¸\81ลà¹\88à¸à¸\87à¸\97ีà¹\88มีà¹\80à¸\84รืà¹\88à¸à¸\87หมายà¸\96ูà¸\81 หมายà¸\84วามวà¹\88า à¸\9cูà¹\89à¹\83à¸\8aà¹\89à¸à¸¢à¸¹à¹\88à¹\83à¸\99à¸\81ลุà¹\88มà¸\99ัà¹\89à¸\99\n* à¸\81ลà¹\88à¸à¸\87à¸\97ีà¹\88à¹\84มà¹\88มีà¹\80à¸\84รืà¹\88à¸à¸\87หมายà¸\96ูà¸\81 หมายà¸\84วามวà¹\88า à¸\9cูà¹\89à¹\83à¸\8aà¹\89à¹\84มà¹\88à¹\84à¸\94à¹\89à¸à¸¢à¸¹à¹\88à¹\83à¸\99à¸\81ลุà¹\88มà¸\99ัà¹\89à¸\99\n* à¹\80à¸\84รืà¹\88à¸à¸\87หมาย * à¸\8aีà¹\89วà¹\88าà¸\84ุà¸\93à¹\84มà¹\88สามารà¸\96à¸\99ำà¸\81ลุà¹\88มà¸\99ัà¹\89à¸\99à¸à¸à¸\81à¹\84à¸\94à¹\89à¹\80มืà¹\88à¸à¸\84ุà¸\93à¹\80à¸\9eิà¹\88มà¸\81ลุà¹\88มà¸\99ัà¹\89à¸\99à¹\84à¸\9bà¹\81ลà¹\89ว หรืà¸à¸\81ลัà¸\9aà¸\81ัà¸\99\n* à¹\80à¸\84รืà¹\88à¸à¸\87หมาย # à¸\8aี้ว่าคุณสามารถแก้คืนเวลาหมดอายุของสมาชิกภาพกลุ่มนี้เท่านั้น คุณไม่สามารถร่นเวลาหมดอายุได้",
"userrights-reason": "เหตุผล:",
"userrights-no-interwiki": "คุณไม่มีสิทธิแก้ไขสิทธิผู้ใช้บนวิกิอื่น",
"userrights-nodatabase": "ไม่มีฐานข้อมูล $1 หรือฐานข้อมูลอยู่บนเครื่องอื่น",
"rcfilters-savedqueries-already-saved": "ตัวกรองเหล่านี้บันทุกแล้ว เปลี่ยนการตั้งค่าของคุณเพื่อสร้างตัวกรองที่บันทึกแล้วใหม่",
"rcfilters-restore-default-filters": "คืนค่าตัวกรองปริยาย",
"rcfilters-clear-all-filters": "ล้างตัวกรองทั้งหมด",
- "rcfilters-show-new-changes": "à¸\94ูà¸\81ารà¹\80à¸\9bลีà¹\88ยà¸\99à¹\81à¸\9bลà¸\87ลà¹\88าสุà¸\94",
+ "rcfilters-show-new-changes": "à¸\94ูà¸\81ารà¹\80à¸\9bลีà¹\88ยà¸\99à¹\81à¸\9bลà¸\87à¹\83หมà¹\88à¸\95ัà¹\89à¸\87à¹\81à¸\95à¹\88 $1",
"rcfilters-search-placeholder": "กรองการเปลี่ยนแปลง (ใช้รายการเลือกหรือค้นหาชื่อตัวกรอง)",
"rcfilters-invalid-filter": "ตัวกรองไม่ถูกต้อง",
"rcfilters-empty-filter": "ไม่มีตัวกรองเปิดใช้งาน แสดงการแก้ไขทั้งหมด",
},
"devDependencies": {
"eslint-config-wikimedia": "0.11.0",
- "grunt": "1.0.3",
- "grunt-banana-checker": "0.6.0",
+ "grunt": "1.0.4",
+ "grunt-banana-checker": "0.7.0",
"grunt-contrib-copy": "1.0.0",
"grunt-contrib-watch": "1.1.0",
"grunt-eslint": "21.0.0",
ol:lang( lrc ) li,
ol:lang( luz ) li,
ol:lang( mzn ) li {
- list-style-type: -moz-persian;
list-style-type: persian;
}
ol:lang( ckb ) li,
ol:lang( sdh ) li {
- list-style-type: -moz-arabic-indic;
list-style-type: arabic-indic;
}
ol:lang( mai ) li,
ol:lang( mr ) li,
ol:lang( ne ) li {
- list-style-type: -moz-devanagari;
list-style-type: devanagari;
}
ol:lang( as ) li,
ol:lang( bn ) li {
- list-style-type: -moz-bengali;
list-style-type: bengali;
}
ol:lang( or ) li {
- list-style-type: -moz-oriya;
list-style-type: oriya;
}
}
private function createMode( $params, $makeLB ) {
- $config = new HashConfig( [
- 'ReadOnly' => $params['confMessage'],
- 'ReadOnlyFile' => $this->createFile( $params ),
- ] );
-
- $rom = new ConfiguredReadOnlyMode( $config );
+ $rom = new ConfiguredReadOnlyMode( $params['confMessage'], $this->createFile( $params ) );
if ( $makeLB ) {
$lb = $this->createLB( $params );
$this->setMwGlobals( 'wgExtraInterlanguageLinkPrefixes', [ 'madeuplanguage' ] );
$this->tablesUsed[] = 'interwiki';
+ $this->overrideMwServices();
}
/**
* @param array $arr Extra params to add to API request
*/
private function doTestLangLinks( array $arr = [] ) {
- $this->setupInterwiki();
-
$res = $this->doApiRequest( array_merge( [
'action' => 'parse',
'title' => 'Omelette',
}
public function testLangLinks() {
+ $this->setupInterwiki();
$this->doTestLangLinks();
}
public function testLangLinksWithSkin() {
+ $this->setupInterwiki();
$this->setupSkin();
$this->doTestLangLinks( [ 'useskin' => 'testing' ] );
}
],
'wgProxyWhitelist' => [],
] );
+ $this->overrideMwServices();
$status = $this->manager->checkAccountCreatePermissions( new \User );
$this->assertFalse( $status->isOK() );
$this->assertTrue( $status->hasMessage( 'sorbs_create_account_reason' ) );
$this->setMwGlobals( 'wgProxyWhitelist', [ '127.0.0.1' ] );
+ $this->overrideMwServices();
$status = $this->manager->checkAccountCreatePermissions( new \User );
$this->assertTrue( $status->isGood() );
}
--- /dev/null
+<?php
+
+use MediaWiki\Block\BlockManager;
+
+/**
+ * @group Blocking
+ * @group Database
+ * @coversDefaultClass \MediaWiki\Block\BlockManager
+ */
+class BlockManagerTest extends MediaWikiTestCase {
+
+ /** @var User */
+ protected $user;
+
+ /** @var int */
+ protected $sysopId;
+
+ protected function setUp() {
+ parent::setUp();
+
+ $this->user = $this->getTestUser()->getUser();
+ $this->sysopId = $this->getTestSysop()->getUser()->getId();
+ }
+
+ private function getBlockManager( $overrideConfig ) {
+ $blockManagerConfig = array_merge( [
+ 'wgApplyIpBlocksToXff' => true,
+ 'wgCookieSetOnAutoblock' => true,
+ 'wgCookieSetOnIpBlock' => true,
+ 'wgDnsBlacklistUrls' => [],
+ 'wgEnableDnsBlacklist' => true,
+ 'wgProxyList' => [],
+ 'wgProxyWhitelist' => [],
+ 'wgSoftBlockRanges' => [],
+ ], $overrideConfig );
+ return new BlockManager(
+ $this->user,
+ $this->user->getRequest(),
+ ...array_values( $blockManagerConfig )
+ );
+ }
+
+ /**
+ * @dataProvider provideGetBlockFromCookieValue
+ * @covers ::getBlockFromCookieValue
+ */
+ public function testGetBlockFromCookieValue( $options, $expected ) {
+ $blockManager = $this->getBlockManager( [
+ 'wgCookieSetOnAutoblock' => true,
+ 'wgCookieSetOnIpBlock' => true,
+ ] );
+
+ $block = new Block( array_merge( [
+ 'address' => $options[ 'target' ] ?: $this->user,
+ 'by' => $this->sysopId,
+ ], $options[ 'blockOptions' ] ) );
+ $block->insert();
+
+ $class = new ReflectionClass( BlockManager::class );
+ $method = $class->getMethod( 'getBlockFromCookieValue' );
+ $method->setAccessible( true );
+
+ $user = $options[ 'loggedIn' ] ? $this->user : new User();
+ $user->getRequest()->setCookie( 'BlockID', $block->getCookieValue() );
+
+ $this->assertSame( $expected, (bool)$method->invoke(
+ $blockManager,
+ $user,
+ $user->getRequest()
+ ) );
+
+ $block->delete();
+ }
+
+ public static function provideGetBlockFromCookieValue() {
+ return [
+ 'Autoblocking user block' => [
+ [
+ 'target' => '',
+ 'loggedIn' => true,
+ 'blockOptions' => [
+ 'enableAutoblock' => true
+ ],
+ ],
+ true,
+ ],
+ 'Non-autoblocking user block' => [
+ [
+ 'target' => '',
+ 'loggedIn' => true,
+ 'blockOptions' => [],
+ ],
+ false,
+ ],
+ 'IP block for anonymous user' => [
+ [
+ 'target' => '127.0.0.1',
+ 'loggedIn' => false,
+ 'blockOptions' => [],
+ ],
+ true,
+ ],
+ 'IP block for logged in user' => [
+ [
+ 'target' => '127.0.0.1',
+ 'loggedIn' => true,
+ 'blockOptions' => [],
+ ],
+ false,
+ ],
+ 'IP range block for anonymous user' => [
+ [
+ 'target' => '127.0.0.0/8',
+ 'loggedIn' => false,
+ 'blockOptions' => [],
+ ],
+ true,
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider provideIsLocallyBlockedProxy
+ * @covers ::isLocallyBlockedProxy
+ */
+ public function testIsLocallyBlockedProxy( $proxyList, $expected ) {
+ $class = new ReflectionClass( BlockManager::class );
+ $method = $class->getMethod( 'isLocallyBlockedProxy' );
+ $method->setAccessible( true );
+
+ $blockManager = $this->getBlockManager( [
+ 'wgProxyList' => $proxyList
+ ] );
+
+ $ip = '1.2.3.4';
+ $this->assertSame( $expected, $method->invoke( $blockManager, $ip ) );
+ }
+
+ public static function provideIsLocallyBlockedProxy() {
+ return [
+ 'Proxy list is empty' => [ [], false ],
+ 'Proxy list contains IP' => [ [ '1.2.3.4' ], true ],
+ 'Proxy list contains IP as value' => [ [ 'test' => '1.2.3.4' ], true ],
+ 'Proxy list contains range that covers IP' => [ [ '1.2.3.0/16' ], true ],
+ ];
+ }
+
+ /**
+ * @covers ::isLocallyBlockedProxy
+ */
+ public function testIsLocallyBlockedProxyDeprecated() {
+ $proxy = '1.2.3.4';
+
+ $this->hideDeprecated(
+ 'IP addresses in the keys of $wgProxyList (found the following IP ' .
+ 'addresses in keys: ' . $proxy . ', please move them to values)'
+ );
+
+ $class = new ReflectionClass( BlockManager::class );
+ $method = $class->getMethod( 'isLocallyBlockedProxy' );
+ $method->setAccessible( true );
+
+ $blockManager = $this->getBlockManager( [
+ 'wgProxyList' => [ $proxy => 'test' ]
+ ] );
+
+ $ip = '1.2.3.4';
+ $this->assertSame( true, $method->invoke( $blockManager, $ip ) );
+ }
+
+ /**
+ * @dataProvider provideIsDnsBlacklisted
+ * @covers ::isDnsBlacklisted
+ * @covers ::inDnsBlacklist
+ */
+ public function testIsDnsBlacklisted( $options, $expected ) {
+ $blockManager = $this->getBlockManager( [
+ 'wgEnableDnsBlacklist' => true,
+ 'wgDnsBlacklistUrls' => $options[ 'inBlacklist' ] ? [ 'local.wmftest.net' ] : [],
+ 'wgProxyWhitelist' => $options[ 'inWhitelist' ] ? [ '127.0.0.1' ] : [],
+ ] );
+
+ $ip = '127.0.0.1';
+ $this->assertSame(
+ $expected,
+ $blockManager->isDnsBlacklisted( $ip, $options[ 'check' ] )
+ );
+ }
+
+ public static function provideIsDnsBlacklisted() {
+ return [
+ 'IP is blacklisted' => [
+ [
+ 'inBlacklist' => true,
+ 'inWhitelist' => false,
+ 'check' => false,
+ ],
+ true,
+ ],
+ 'IP is not blacklisted' => [
+ [
+ 'inBlacklist' => false,
+ 'inWhitelist' => false,
+ 'check' => false,
+ ],
+ false,
+ ],
+ 'IP is blacklisted and whitelisted; whitelist is checked' => [
+ [
+ 'inBlacklist' => true,
+ 'inWhitelist' => true,
+ 'check' => false,
+ ],
+ true,
+ ],
+ 'IP is blacklisted and whitelisted; whitelist is not checked' => [
+ [
+ 'inBlacklist' => true,
+ 'inWhitelist' => true,
+ 'check' => true,
+ ],
+ false,
+ ],
+ ];
+ }
+}
--- /dev/null
+<?php
+
+use MediaWiki\Config\ServiceOptions;
+
+/**
+ * @coversDefaultClass \MediaWiki\Config\ServiceOptions
+ */
+class ServiceOptionsTest extends MediaWikiTestCase {
+ public static $testObj;
+
+ public static function setUpBeforeClass() {
+ parent::setUpBeforeClass();
+
+ self::$testObj = new stdclass();
+ }
+
+ /**
+ * @dataProvider provideConstructor
+ * @covers ::__construct
+ * @covers ::assertRequiredOptions
+ * @covers ::get
+ */
+ public function testConstructor( $expected, $keys, ...$sources ) {
+ $options = new ServiceOptions( $keys, ...$sources );
+
+ foreach ( $expected as $key => $val ) {
+ $this->assertSame( $val, $options->get( $key ) );
+ }
+
+ // This is lumped in the same test because there's no support for depending on a test that
+ // has a data provider.
+ $options->assertRequiredOptions( array_keys( $expected ) );
+
+ // Suppress warning if no assertions were run. This is expected for empty arguments.
+ $this->assertTrue( true );
+ }
+
+ public function provideConstructor() {
+ return [
+ 'No keys' => [ [], [], [ 'a' => 'aval' ] ],
+ 'Simple array source' => [
+ [ 'a' => 'aval', 'b' => 'bval' ],
+ [ 'a', 'b' ],
+ [ 'a' => 'aval', 'b' => 'bval', 'c' => 'cval' ],
+ ],
+ 'Simple HashConfig source' => [
+ [ 'a' => 'aval', 'b' => 'bval' ],
+ [ 'a', 'b' ],
+ new HashConfig( [ 'a' => 'aval', 'b' => 'bval', 'c' => 'cval' ] ),
+ ],
+ 'Three different sources' => [
+ [ 'a' => 'aval', 'b' => 'bval' ],
+ [ 'a', 'b' ],
+ [ 'z' => 'zval' ],
+ new HashConfig( [ 'a' => 'aval', 'c' => 'cval' ] ),
+ [ 'b' => 'bval', 'd' => 'dval' ],
+ ],
+ 'null key' => [
+ [ 'a' => null ],
+ [ 'a' ],
+ [ 'a' => null ],
+ ],
+ 'Numeric option name' => [
+ [ '0' => 'nothing' ],
+ [ '0' ],
+ [ '0' => 'nothing' ],
+ ],
+ 'Multiple sources for one key' => [
+ [ 'a' => 'winner' ],
+ [ 'a' ],
+ [ 'a' => 'winner' ],
+ [ 'a' => 'second place' ],
+ ],
+ 'Object value is passed by reference' => [
+ [ 'a' => self::$testObj ],
+ [ 'a' ],
+ [ 'a' => self::$testObj ],
+ ],
+ ];
+ }
+
+ /**
+ * @covers ::__construct
+ */
+ public function testKeyNotFound() {
+ $this->setExpectedException( InvalidArgumentException::class,
+ 'Key "a" not found in input sources' );
+
+ new ServiceOptions( [ 'a' ], [ 'b' => 'bval' ], [ 'c' => 'cval' ] );
+ }
+
+ /**
+ * @covers ::__construct
+ * @covers ::assertRequiredOptions
+ */
+ public function testOutOfOrderAssertRequiredOptions() {
+ $options = new ServiceOptions( [ 'a', 'b' ], [ 'a' => '', 'b' => '' ] );
+ $options->assertRequiredOptions( [ 'b', 'a' ] );
+ $this->assertTrue( true, 'No exception thrown' );
+ }
+
+ /**
+ * @covers ::__construct
+ * @covers ::get
+ */
+ public function testGetUnrecognized() {
+ $this->setExpectedException( InvalidArgumentException::class,
+ 'Unrecognized option "b"' );
+
+ $options = new ServiceOptions( [ 'a' ], [ 'a' => '' ] );
+ $options->get( 'b' );
+ }
+
+ /**
+ * @covers ::__construct
+ * @covers ::assertRequiredOptions
+ */
+ public function testExtraKeys() {
+ $this->setExpectedException( Wikimedia\Assert\PreconditionException::class,
+ 'Precondition failed: Unsupported options passed: b, c!' );
+
+ $options = new ServiceOptions( [ 'a', 'b', 'c' ], [ 'a' => '', 'b' => '', 'c' => '' ] );
+ $options->assertRequiredOptions( [ 'a' ] );
+ }
+
+ /**
+ * @covers ::__construct
+ * @covers ::assertRequiredOptions
+ */
+ public function testMissingKeys() {
+ $this->setExpectedException( Wikimedia\Assert\PreconditionException::class,
+ 'Precondition failed: Required options missing: a, b!' );
+
+ $options = new ServiceOptions( [ 'c' ], [ 'c' => '' ] );
+ $options->assertRequiredOptions( [ 'a', 'b', 'c' ] );
+ }
+
+ /**
+ * @covers ::__construct
+ * @covers ::assertRequiredOptions
+ */
+ public function testExtraAndMissingKeys() {
+ $this->setExpectedException( Wikimedia\Assert\PreconditionException::class,
+ 'Precondition failed: Unsupported options passed: b! Required options missing: c!' );
+
+ $options = new ServiceOptions( [ 'a', 'b' ], [ 'a' => '', 'b' => '' ] );
+ $options->assertRequiredOptions( [ 'a', 'c' ] );
+ }
+}
$wrapper = TestingAccessWrapper::newFromObject( $this->testSubclass );
$this->assertSame( 1, $wrapper->privateNonDeprecated );
}, E_USER_ERROR, "Cannot access non-public property $fullName" );
+
+ $fullName = 'TestDeprecatedSubclass::$subclassPrivateNondeprecated';
+ $this->assertErrorTriggered( function () {
+ $this->assertSame( null, $this->testSubclass->subclassPrivateNondeprecated );
+ }, E_USER_ERROR, "Cannot access non-public property $fullName" );
+ $this->assertErrorTriggered( function () {
+ $this->testSubclass->subclassPrivateNondeprecated = 0;
+ $wrapper = TestingAccessWrapper::newFromObject( $this->testSubclass );
+ $this->assertSame( 1, $wrapper->subclassPrivateNondeprecated );
+ }, E_USER_ERROR, "Cannot access non-public property $fullName" );
}
protected function assertErrorTriggered( callable $callback, $level, $message ) {
class TestDeprecatedSubclass extends TestDeprecatedClass {
+ private $subclassPrivateNondeprecated = 1;
+
public function getDeprecatedPrivateParentProperty() {
return $this->privateDeprecated;
}
<?php
use MediaWiki\Auth\AuthManager;
+use MediaWiki\Config\ServiceOptions;
use MediaWiki\MediaWikiServices;
use MediaWiki\Preferences\DefaultPreferencesFactory;
use Wikimedia\TestingAccessWrapper;
*/
protected function getPreferencesFactory() {
return new DefaultPreferencesFactory(
- $this->config,
+ new ServiceOptions( DefaultPreferencesFactory::$constructorOptions, $this->config ),
new Language(),
AuthManager::singleton(),
MediaWikiServices::getInstance()->getLinkRenderer()
RequestContext::getMain()->setRequest( $request );
TestingAccessWrapper::newFromObject( $user )->mRequest = $request;
$request->getSession()->setUser( $user );
+ $this->overrideMwServices();
};
$this->setMwGlobals( 'wgSoftBlockRanges', [ '10.0.0.0/8' ] );
$this->assertFalse( $user->getExperienceLevel() );
}
- public static function provideIsLocallBlockedProxy() {
+ public static function provideIsLocallyBlockedProxy() {
return [
[ '1.2.3.4', '1.2.3.4' ],
[ '1.2.3.4', '1.2.3.0/16' ],
}
/**
- * @dataProvider provideIsLocallBlockedProxy
+ * @dataProvider provideIsLocallyBlockedProxy
* @covers User::isLocallyBlockedProxy
*/
public function testIsLocallyBlockedProxy( $ip, $blockListEntry ) {
+ $this->hideDeprecated( 'User::isLocallyBlockedProxy' );
+
$this->setMwGlobals(
'wgProxyList', []
);