Merge "Add SearchResultTrait"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Tue, 27 Aug 2019 00:32:25 +0000 (00:32 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Tue, 27 Aug 2019 00:32:25 +0000 (00:32 +0000)
290 files changed:
.mailmap
RELEASE-NOTES-1.34
autoload.php
docs/database.txt
docs/hooks.txt
img_auth.php
includes/AjaxDispatcher.php
includes/Autopromote.php
includes/BadFileLookup.php [new file with mode: 0644]
includes/Category.php
includes/DefaultSettings.php
includes/EditPage.php
includes/FileDeleteForm.php
includes/GlobalFunctions.php
includes/Linker.php
includes/MWNamespace.php
includes/MediaWikiServices.php
includes/MergeHistory.php
includes/MovePage.php
includes/OutputPage.php
includes/Permissions/PermissionManager.php
includes/ProtectionForm.php
includes/Rest/EntryPoint.php
includes/Revision/RevisionRenderer.php
includes/ServiceWiring.php
includes/Setup.php
includes/Storage/NameTableStore.php
includes/Title.php
includes/WebRequest.php
includes/WikiMap.php
includes/actions/HistoryAction.php
includes/actions/InfoAction.php
includes/actions/RawAction.php
includes/actions/WatchAction.php
includes/actions/pagers/HistoryPager.php
includes/api/ApiBase.php
includes/api/ApiBlock.php
includes/api/ApiComparePages.php
includes/api/ApiHelp.php
includes/api/ApiImageRotate.php
includes/api/ApiImport.php
includes/api/ApiMain.php
includes/api/ApiManageTags.php
includes/api/ApiMove.php
includes/api/ApiPageSet.php
includes/api/ApiQueryAllDeletedRevisions.php
includes/api/ApiQueryAllImages.php
includes/api/ApiQueryAllRevisions.php
includes/api/ApiQueryAllUsers.php
includes/api/ApiQueryBase.php
includes/api/ApiQueryBlocks.php
includes/api/ApiQueryContributors.php
includes/api/ApiQueryDeletedRevisions.php
includes/api/ApiQueryDeletedrevs.php
includes/api/ApiQueryFilearchive.php
includes/api/ApiQueryImageInfo.php
includes/api/ApiQueryInfo.php
includes/api/ApiQueryLogEvents.php
includes/api/ApiQueryRecentChanges.php
includes/api/ApiQueryRevisions.php
includes/api/ApiQueryUserContribs.php
includes/api/ApiQueryUserInfo.php
includes/api/ApiQueryUsers.php
includes/api/ApiUnblock.php
includes/api/ApiUserrights.php
includes/api/i18n/ar.json
includes/api/i18n/en.json
includes/api/i18n/qqq.json
includes/api/i18n/sv.json
includes/api/i18n/zh-hans.json
includes/api/i18n/zh-hant.json
includes/auth/AuthManager.php
includes/auth/Throttler.php
includes/block/AbstractBlock.php
includes/block/BlockManager.php
includes/block/DatabaseBlock.php
includes/cache/FileCacheBase.php
includes/changes/RecentChange.php
includes/changetags/ChangeTags.php
includes/content/AbstractContent.php
includes/content/Content.php
includes/content/ContentHandler.php
includes/content/WikitextContent.php
includes/db/MWLBFactory.php
includes/db/ORAField.php [deleted file]
includes/db/ORAResult.php [deleted file]
includes/deferred/DeferrableCallback.php
includes/deferred/DeferredUpdates.php
includes/diff/DifferenceEngine.php
includes/editpage/TextboxBuilder.php
includes/exception/PermissionsError.php
includes/filebackend/FileBackendGroup.php
includes/filerepo/file/File.php
includes/gallery/TraditionalImageGallery.php
includes/installer/SqliteInstaller.php
includes/installer/i18n/da.json
includes/installer/i18n/id.json
includes/installer/i18n/ro.json
includes/installer/i18n/sh.json
includes/jobqueue/JobQueueDB.php
includes/jobqueue/JobRunner.php
includes/language/ConverterRule.php [new file with mode: 0644]
includes/libs/filebackend/FSFileBackend.php
includes/libs/filebackend/FileBackend.php
includes/libs/filebackend/FileBackendMultiWrite.php
includes/libs/filebackend/FileBackendStore.php
includes/libs/filebackend/FileOpBatch.php
includes/libs/filebackend/HTTPFileStreamer.php
includes/libs/filebackend/MemoryFileBackend.php
includes/libs/filebackend/SwiftFileBackend.php
includes/libs/filebackend/fileop/FileOp.php
includes/libs/filebackend/fileop/StoreFileOp.php
includes/libs/filebackend/fsfile/FSFile.php
includes/libs/filebackend/fsfile/TempFSFile.php
includes/libs/filebackend/fsfile/TempFSFileFactory.php [new file with mode: 0644]
includes/libs/objectcache/APCBagOStuff.php
includes/libs/objectcache/APCUBagOStuff.php
includes/libs/objectcache/BagOStuff.php
includes/libs/objectcache/CachedBagOStuff.php
includes/libs/objectcache/EmptyBagOStuff.php
includes/libs/objectcache/HashBagOStuff.php
includes/libs/objectcache/MediumSpecificBagOStuff.php
includes/libs/objectcache/MemcachedClient.php [deleted file]
includes/libs/objectcache/MemcachedPeclBagOStuff.php
includes/libs/objectcache/MemcachedPhpBagOStuff.php
includes/libs/objectcache/MultiWriteBagOStuff.php
includes/libs/objectcache/RESTBagOStuff.php
includes/libs/objectcache/RedisBagOStuff.php
includes/libs/objectcache/ReplicatedBagOStuff.php
includes/libs/objectcache/WinCacheBagOStuff.php
includes/libs/objectcache/utils/MemcachedClient.php [new file with mode: 0644]
includes/libs/objectcache/wancache/WANObjectCache.php
includes/libs/rdbms/ChronologyProtector.php
includes/libs/rdbms/database/Database.php
includes/libs/rdbms/database/DatabaseMysqlBase.php
includes/libs/rdbms/database/IDatabase.php
includes/libs/rdbms/database/domain/DatabaseDomain.php
includes/libs/rdbms/database/resultwrapper/MssqlResultWrapper.php [deleted file]
includes/libs/rdbms/encasing/MssqlBlob.php [deleted file]
includes/libs/rdbms/field/MssqlField.php [deleted file]
includes/libs/rdbms/lbfactory/LBFactory.php
includes/libs/rdbms/loadbalancer/ILoadBalancer.php
includes/libs/rdbms/loadbalancer/LoadBalancer.php
includes/logging/LogEventsList.php
includes/logging/LogPager.php
includes/media/ThumbnailImage.php
includes/objectcache/SqlBagOStuff.php
includes/page/ImageHistoryList.php
includes/page/WikiPage.php
includes/pager/IndexPager.php
includes/pager/TablePager.php
includes/parser/Parser.php
includes/parser/ParserCache.php
includes/parser/ParserFactory.php
includes/resourceloader/ResourceLoader.php
includes/resourceloader/ResourceLoaderClientHtml.php
includes/resourceloader/ResourceLoaderContext.php
includes/resourceloader/ResourceLoaderStartUpModule.php
includes/skins/Skin.php
includes/skins/SkinTemplate.php
includes/specialpage/ChangesListSpecialPage.php
includes/specialpage/RedirectSpecialArticle.php
includes/specialpage/SpecialPage.php
includes/specials/SpecialCreateAccount.php
includes/specials/SpecialEditTags.php
includes/specials/SpecialImport.php
includes/specials/SpecialLog.php
includes/specials/SpecialMute.php
includes/specials/SpecialNewSection.php
includes/specials/SpecialNewpages.php
includes/specials/SpecialRecentChanges.php
includes/specials/SpecialWatchlist.php
includes/specials/forms/PreferencesFormOOUI.php
includes/specials/pagers/AllMessagesTablePager.php
includes/specials/pagers/BlockListPager.php
includes/specials/pagers/CategoryPager.php
includes/specials/pagers/ContribsPager.php
includes/specials/pagers/DeletedContribsPager.php
includes/specials/pagers/ImageListPager.php
includes/specials/pagers/NewFilesPager.php
includes/specials/pagers/NewPagesPager.php
includes/specials/pagers/ProtectedPagesPager.php
includes/specials/pagers/UsersPager.php
includes/title/NamespaceInfo.php
includes/upload/UploadFromChunks.php
includes/upload/UploadFromUrl.php
includes/user/User.php
languages/ConverterRule.php [deleted file]
languages/data/Names.php
languages/i18n/ace.json
languages/i18n/ar.json
languages/i18n/awa.json
languages/i18n/ban.json
languages/i18n/bcc.json
languages/i18n/bjn.json
languages/i18n/bn.json
languages/i18n/bs.json
languages/i18n/cs.json
languages/i18n/de.json
languages/i18n/el.json
languages/i18n/es.json
languages/i18n/exif/ro.json
languages/i18n/exif/szl.json
languages/i18n/exif/tt-cyrl.json
languages/i18n/fa.json
languages/i18n/fi.json
languages/i18n/fr.json
languages/i18n/gl.json
languages/i18n/glk.json
languages/i18n/he.json
languages/i18n/ia.json
languages/i18n/ig.json
languages/i18n/io.json
languages/i18n/it.json
languages/i18n/ja.json
languages/i18n/ko.json
languages/i18n/lb.json
languages/i18n/min.json
languages/i18n/mk.json
languages/i18n/ml.json
languages/i18n/nap.json
languages/i18n/nb.json
languages/i18n/ne.json
languages/i18n/nl.json
languages/i18n/nqo.json
languages/i18n/pl.json
languages/i18n/pt-br.json
languages/i18n/pt.json
languages/i18n/qqq.json
languages/i18n/roa-tara.json
languages/i18n/ru.json
languages/i18n/sd.json
languages/i18n/sh.json
languages/i18n/skr-arab.json
languages/i18n/sl.json
languages/i18n/sv.json
languages/i18n/szl.json
languages/i18n/te.json
languages/i18n/tl.json
languages/i18n/tr.json
languages/i18n/uk.json
languages/i18n/zh-hans.json
languages/i18n/zh-hant.json
languages/i18n/zh-hk.json
languages/messages/MessagesMni.php
maintenance/includes/BackupDumper.php
maintenance/namespaceDupes.php
maintenance/rebuildrecentchanges.php
maintenance/rebuildtextindex.php
maintenance/sql.php
maintenance/sqlite.php
resources/src/mediawiki.widgets/mw.widgets.CalendarWidget.js
resources/src/mediawiki.widgets/mw.widgets.CalendarWidget.less
resources/src/startup/mediawiki.js
tests/common/TestsAutoLoader.php
tests/phpunit/MediaWikiIntegrationTestCase.php
tests/phpunit/MediaWikiTestCaseTrait.php [new file with mode: 0644]
tests/phpunit/MediaWikiUnitTestCase.php
tests/phpunit/includes/GlobalFunctions/GlobalWithDBTest.php
tests/phpunit/includes/OutputPageTest.php
tests/phpunit/includes/Permissions/PermissionManagerTest.php
tests/phpunit/includes/Rest/BasicAccess/MWBasicRequestAuthorizerTest.php
tests/phpunit/includes/Rest/EntryPointTest.php [new file with mode: 0644]
tests/phpunit/includes/Storage/NameTableStoreTest.php
tests/phpunit/includes/TitlePermissionTest.php
tests/phpunit/includes/actions/WatchActionTest.php
tests/phpunit/includes/api/ApiOptionsTest.php
tests/phpunit/includes/api/ApiTokensTest.php
tests/phpunit/includes/block/BlockManagerTest.php
tests/phpunit/includes/db/DatabaseTestHelper.php
tests/phpunit/includes/deferred/DeferredUpdatesTest.php
tests/phpunit/includes/filebackend/lockmanager/LockManagerGroupIntegrationTest.php [new file with mode: 0644]
tests/phpunit/includes/language/ConverterRuleTest.php [new file with mode: 0644]
tests/phpunit/includes/libs/filebackend/fsfile/TempFSFileIntegrationTest.php
tests/phpunit/includes/libs/objectcache/BagOStuffTest.php
tests/phpunit/includes/libs/objectcache/WANObjectCacheTest.php
tests/phpunit/includes/page/WikiPageDbTestBase.php
tests/phpunit/includes/parser/ParserMethodsTest.php
tests/phpunit/includes/resourceloader/ResourceLoaderContextTest.php
tests/phpunit/includes/resourceloader/ResourceLoaderStartUpModuleTest.php
tests/phpunit/includes/resourceloader/ResourceLoaderTest.php
tests/phpunit/includes/specials/SpecialPreferencesTest.php
tests/phpunit/includes/title/NamespaceInfoTest.php
tests/phpunit/includes/user/UserTest.php
tests/phpunit/integration/includes/db/DatabaseSqliteTest.php
tests/phpunit/unit/includes/BadFileLookupTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/Rest/EntryPointTest.php [deleted file]
tests/phpunit/unit/includes/libs/filebackend/fsfile/TempFSFileTest.php [new file with mode: 0644]
tests/qunit/suites/resources/mediawiki/mediawiki.loader.test.js
thumb.php

index 1265bd2..0f5413e 100644 (file)
--- a/.mailmap
+++ b/.mailmap
@@ -59,7 +59,7 @@ Ariel Glenn <ariel@wikimedia.org> <ariel@wikimedia.org>
 Arlo Breault <abreault@wikimedia.org>
 Arthur Richards <arichards@wikimedia.org>
 Arthur Richards <arichards@wikimedia.org> <awjrichards@users.mediawiki.org>
-Aryeh Gregor <simetrical+mw@gmail.com> <simetrical@users.mediawiki.org>
+Aryeh Gregor <ayg@aryeh.name> <simetrical@users.mediawiki.org>
 Asher Feldman <afeldman@wikimedia.org>
 Asher Feldman <afeldman@wikimedia.org> <asher@users.mediawiki.org>
 aude <aude.wiki@gmail.com>
index 6e78948..620a6f5 100644 (file)
@@ -98,6 +98,10 @@ For notes on 1.33.x and older releases, see HISTORY.
   to add fields to Special:Mute.
 * (T100896) Skin authors can define custom OOUI themes using OOUIThemePaths.
   See <https://www.mediawiki.org/wiki/OOUI/Themes> for details.
+* (T229035) The GetUserBlock hook was added. Use this instead of
+  GetBlockedStatus.
+* ObjectFactory is available as a service. When used as a service, the object
+  specs can now specify needed DI services.
 
 === External library changes in 1.34 ===
 
@@ -347,6 +351,8 @@ because of Phabricator reports.
   MediaWikiTestCase::resetServices().
 * SearchResult protected field $searchEngine is removed and no longer
   initialized after calling SearchResult::initFromTitle().
+* The UserIsBlockedFrom hook is only called if a block is found first, and
+  should only be used to unblock a blocked user.
 * …
 
 === Deprecations in 1.34 ===
@@ -423,6 +429,8 @@ because of Phabricator reports.
   engines.
 * Skin::escapeSearchLink() is deprecated. Use Skin::getSearchLink() or the skin
   template option 'searchaction' instead.
+* Skin::getRevisionId() and Skin::isRevisionCurrent() have been deprecated.
+  Use OutputPage::getRevisionId() and OutputPage::isRevisionCurrent() instead.
 * LoadBalancer::haveIndex() and LoadBalancer::isNonZeroLoad() have
   been deprecated.
 * FileBackend::getWikiId() has been deprecated.
@@ -453,6 +461,8 @@ because of Phabricator reports.
 * MessageCache::singleton() is deprecated. Use
   MediaWikiServices::getMessageCache().
 * Constructing MovePage directly is deprecated. Use MovePageFactory.
+* TempFSFile::factory() has been deprecated. Use TempFSFileFactory instead.
+* wfIsBadImage() is deprecated. Use the BadFileLookup service instead.
 
 === Other changes in 1.34 ===
 * …
index b6ec459..95297fb 100644 (file)
@@ -316,7 +316,7 @@ $wgAutoloadLocalClasses = [
        'ConvertExtensionToRegistration' => __DIR__ . '/maintenance/convertExtensionToRegistration.php',
        'ConvertLinks' => __DIR__ . '/maintenance/convertLinks.php',
        'ConvertUserOptions' => __DIR__ . '/maintenance/convertUserOptions.php',
-       'ConverterRule' => __DIR__ . '/languages/ConverterRule.php',
+       'ConverterRule' => __DIR__ . '/includes/language/ConverterRule.php',
        'Cookie' => __DIR__ . '/includes/libs/Cookie.php',
        'CookieJar' => __DIR__ . '/includes/libs/CookieJar.php',
        'CopyFileBackend' => __DIR__ . '/maintenance/copyFileBackend.php',
@@ -874,12 +874,14 @@ $wgAutoloadLocalClasses = [
        'MediaWikiSite' => __DIR__ . '/includes/site/MediaWikiSite.php',
        'MediaWikiTitleCodec' => __DIR__ . '/includes/title/MediaWikiTitleCodec.php',
        'MediaWikiVersionFetcher' => __DIR__ . '/includes/MediaWikiVersionFetcher.php',
+       'MediaWiki\\BadFileLookup' => __DIR__ . '/includes/BadFileLookup.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',
+       'MediaWiki\\FileBackend\\FSFile\\TempFSFileFactory' => __DIR__ . '/includes/libs/filebackend/fsfile/TempFSFileFactory.php',
        'MediaWiki\\HeaderCallback' => __DIR__ . '/includes/HeaderCallback.php',
        'MediaWiki\\Http\\HttpRequestFactory' => __DIR__ . '/includes/http/HttpRequestFactory.php',
        'MediaWiki\\Installer\\InstallException' => __DIR__ . '/includes/installer/InstallException.php',
@@ -972,7 +974,7 @@ $wgAutoloadLocalClasses = [
        'MediumSpecificBagOStuff' => __DIR__ . '/includes/libs/objectcache/MediumSpecificBagOStuff.php',
        'MemcLockManager' => __DIR__ . '/includes/libs/lockmanager/MemcLockManager.php',
        'MemcachedBagOStuff' => __DIR__ . '/includes/libs/objectcache/MemcachedBagOStuff.php',
-       'MemcachedClient' => __DIR__ . '/includes/libs/objectcache/MemcachedClient.php',
+       'MemcachedClient' => __DIR__ . '/includes/libs/objectcache/utils/MemcachedClient.php',
        'MemcachedPeclBagOStuff' => __DIR__ . '/includes/libs/objectcache/MemcachedPeclBagOStuff.php',
        'MemcachedPhpBagOStuff' => __DIR__ . '/includes/libs/objectcache/MemcachedPhpBagOStuff.php',
        'MemoizedCallable' => __DIR__ . '/includes/libs/MemoizedCallable.php',
@@ -1042,8 +1044,6 @@ $wgAutoloadLocalClasses = [
        'NullStatsdDataFactory' => __DIR__ . '/includes/libs/stats/NullStatsdDataFactory.php',
        'NumericUppercaseCollation' => __DIR__ . '/includes/collation/NumericUppercaseCollation.php',
        'OOUIHTMLForm' => __DIR__ . '/includes/htmlform/OOUIHTMLForm.php',
-       'ORAField' => __DIR__ . '/includes/db/ORAField.php',
-       'ORAResult' => __DIR__ . '/includes/db/ORAResult.php',
        'ObjectCache' => __DIR__ . '/includes/objectcache/ObjectCache.php',
        'OldChangesList' => __DIR__ . '/includes/changes/OldChangesList.php',
        'OldLocalFile' => __DIR__ . '/includes/filerepo/file/OldLocalFile.php',
@@ -1682,9 +1682,6 @@ $wgAutoloadLocalClasses = [
        'Wikimedia\\Rdbms\\LoadMonitorMySQL' => __DIR__ . '/includes/libs/rdbms/loadmonitor/LoadMonitorMySQL.php',
        'Wikimedia\\Rdbms\\LoadMonitorNull' => __DIR__ . '/includes/libs/rdbms/loadmonitor/LoadMonitorNull.php',
        'Wikimedia\\Rdbms\\MaintainableDBConnRef' => __DIR__ . '/includes/libs/rdbms/database/MaintainableDBConnRef.php',
-       'Wikimedia\\Rdbms\\MssqlBlob' => __DIR__ . '/includes/libs/rdbms/encasing/MssqlBlob.php',
-       'Wikimedia\\Rdbms\\MssqlField' => __DIR__ . '/includes/libs/rdbms/field/MssqlField.php',
-       'Wikimedia\\Rdbms\\MssqlResultWrapper' => __DIR__ . '/includes/libs/rdbms/database/resultwrapper/MssqlResultWrapper.php',
        'Wikimedia\\Rdbms\\MySQLField' => __DIR__ . '/includes/libs/rdbms/field/MySQLField.php',
        'Wikimedia\\Rdbms\\MySQLMasterPos' => __DIR__ . '/includes/libs/rdbms/database/position/MySQLMasterPos.php',
        'Wikimedia\\Rdbms\\NextSequenceValue' => __DIR__ . '/includes/libs/rdbms/database/utils/NextSequenceValue.php',
index 6e88d68..c09dd38 100644 (file)
@@ -176,8 +176,6 @@ MediaWiki does support the following other DBMSs to varying degrees.
 
 * PostgreSQL
 * SQLite
-* Oracle
-* MSSQL
 
 More information can be found about each of these databases (known issues,
 level of support, extra configuration) in the "databases" subdirectory in
index 6207b12..b7ea02c 100644 (file)
@@ -1726,6 +1726,11 @@ $contentHandler: ContentHandler for which the slot diff renderer is fetched.
 &$slotDiffRenderer: SlotDiffRenderer to change or replace.
 $context: IContextSource
 
+'GetUserBlock': Modify the block found by the block manager. This may be a
+single block or a composite block made from multiple blocks; the original
+blocks can be seen using CompositeBlock::getOriginalBlocks()
+&$block: AbstractBlock object
+
 'getUserPermissionsErrors': Add a permissions error when permissions errors are
 checked for. Use instead of userCan for most cases. Return false if the user
 can't do it, and populate $result with the reason in the form of
@@ -3211,6 +3216,9 @@ $request: WebRequest object for getting the value provided by the current user
 $sp: SpecialPage object, for context
 &$fields: Current HTMLForm fields descriptors
 
+'SpecialMuteSubmit': DEPRECATED since 1.34! Used only for instrumentation on SpecialMute
+$data: Array containing information about submitted options on SpecialMute form
+
 'SpecialNewpagesConditions': Called when building sql query for
 Special:NewPages.
 &$special: NewPagesPager object (subclass of ReverseChronologicalPager)
@@ -3496,6 +3504,12 @@ processing.
 &$archive: PageArchive object
 $title: Title object of the page that we're about to undelete
 
+'UndeletePageToolLinks': Add one or more links to edit page subtitle when a page
+has been previously deleted.
+$context: IContextSource (object)
+$linkRenderer: LinkRenderer instance
+&$links: Array of HTML strings
+
 'UndeleteShowRevision': Called when showing a revision in Special:Undelete.
 $title: title object related to the revision
 $rev: revision (object) that will be viewed
@@ -3697,7 +3711,7 @@ $newUGMs: An associative array (group name => UserGroupMembership object) of
 the user's current group memberships.
 
 'UserIsBlockedFrom': Check if a user is blocked from a specific page (for
-specific block exemptions).
+specific block exemptions if a user is already blocked).
 $user: User in question
 $title: Title of the page in question
 &$blocked: Out-param, whether or not the user is blocked from that page.
index 914014d..6e45e4e 100644 (file)
@@ -53,9 +53,10 @@ $mediawiki->doPostOutputShutdown( 'fast' );
 
 function wfImageAuthMain() {
        global $wgImgAuthUrlPathMap;
+       $permissionManager = \MediaWiki\MediaWikiServices::getInstance()->getPermissionManager();
 
        $request = RequestContext::getMain()->getRequest();
-       $publicWiki = in_array( 'read', User::getGroupPermissions( [ '*' ] ), true );
+       $publicWiki = in_array( 'read', $permissionManager->getGroupPermissions( [ '*' ] ), true );
 
        // Get the requested file path (source file or thumbnail)
        $matches = WebRequest::getPathInfo();
@@ -160,7 +161,6 @@ function wfImageAuthMain() {
 
                // Check user authorization for this title
                // Checks Whitelist too
-               $permissionManager = \MediaWiki\MediaWikiServices::getInstance()->getPermissionManager();
 
                if ( !$permissionManager->userCan( 'read', $user, $title ) ) {
                        wfForbidden( 'img-auth-accessdenied', 'img-auth-noread', $name );
index f6c9075..ea10a2e 100644 (file)
@@ -114,6 +114,7 @@ class AjaxDispatcher {
                        return;
                }
 
+               $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
                if ( !in_array( $this->func_name, $this->config->get( 'AjaxExportList' ) ) ) {
                        wfDebug( __METHOD__ . ' Bad Request for unknown function ' . $this->func_name . "\n" );
                        wfHttpError(
@@ -121,7 +122,8 @@ class AjaxDispatcher {
                                'Bad Request',
                                "unknown function " . $this->func_name
                        );
-               } elseif ( !User::isEveryoneAllowed( 'read' ) && !$user->isAllowed( 'read' ) ) {
+               } elseif ( !$permissionManager->isEveryoneAllowed( 'read' ) &&
+                                  !$permissionManager->userHasRight( $user, 'read' ) ) {
                        wfHttpError(
                                403,
                                'Forbidden',
index b17f1ab..2156787 100644 (file)
@@ -21,6 +21,8 @@
  * @file
  */
 
+use MediaWiki\MediaWikiServices;
+
 /**
  * This class checks if user can get extra rights
  * because of conditions specified in $wgAutopromote
@@ -200,7 +202,9 @@ class Autopromote {
                        case APCOND_BLOCKED:
                                return $user->getBlock() && $user->getBlock()->isSitewide();
                        case APCOND_ISBOT:
-                               return in_array( 'bot', User::getGroupPermissions( $user->getGroups() ) );
+                               return in_array( 'bot', MediaWikiServices::getInstance()
+                                       ->getPermissionManager()
+                                       ->getGroupPermissions( $user->getGroups() ) );
                        default:
                                $result = null;
                                Hooks::run( 'AutopromoteCondition', [ $cond[0],
diff --git a/includes/BadFileLookup.php b/includes/BadFileLookup.php
new file mode 100644 (file)
index 0000000..2f7c0ea
--- /dev/null
@@ -0,0 +1,127 @@
+<?php
+
+namespace MediaWiki;
+
+use BagOStuff;
+use Hooks;
+use MalformedTitleException;
+use MediaWiki\Linker\LinkTarget;
+use RepoGroup;
+use TitleParser;
+
+class BadFileLookup {
+       /** @var callable Returns contents of blacklist (see comment for isBadFile()) */
+       private $blacklistCallback;
+
+       /** @var BagOStuff Cache of parsed bad image list */
+       private $cache;
+
+       /** @var RepoGroup */
+       private $repoGroup;
+
+       /** @var TitleParser */
+       private $titleParser;
+
+       /** @var array|null Parsed blacklist */
+       private $badFiles;
+
+       /**
+        * Do not call directly. Use MediaWikiServices.
+        *
+        * @param callable $blacklistCallback Callback that returns wikitext of a file blacklist
+        * @param BagOStuff $cache For caching parsed versions of the blacklist
+        * @param RepoGroup $repoGroup
+        * @param TitleParser $titleParser
+        */
+       public function __construct(
+               callable $blacklistCallback,
+               BagOStuff $cache,
+               RepoGroup $repoGroup,
+               TitleParser $titleParser
+       ) {
+               $this->blacklistCallback = $blacklistCallback;
+               $this->cache = $cache;
+               $this->repoGroup = $repoGroup;
+               $this->titleParser = $titleParser;
+       }
+
+       /**
+        * Determine if a file exists on the 'bad image list'.
+        *
+        * The format of MediaWiki:Bad_image_list is as follows:
+        *    * Only list items (lines starting with "*") are considered
+        *    * The first link on a line must be a link to a bad file
+        *    * Any subsequent links on the same line are considered to be exceptions,
+        *      i.e. articles where the file may occur inline.
+        *
+        * @param string $name The file name to check
+        * @param LinkTarget|null $contextTitle The page on which the file occurs, if known
+        * @return bool
+        */
+       public function isBadFile( $name, LinkTarget $contextTitle = null ) {
+               // Handle redirects; callers almost always hit wfFindFile() anyway, so just use that method
+               // because it has a fast process cache.
+               $file = $this->repoGroup->findFile( $name );
+               // XXX If we don't find the file we also don't replace spaces by underscores or otherwise
+               // validate or normalize the title, is this right?
+               if ( $file ) {
+                       $name = $file->getTitle()->getDBkey();
+               }
+
+               // Run the extension hook
+               $bad = false;
+               if ( !Hooks::run( 'BadImage', [ $name, &$bad ] ) ) {
+                       return (bool)$bad;
+               }
+
+               if ( $this->badFiles === null ) {
+                       // Not used before in this request, try the cache
+                       $blacklist = ( $this->blacklistCallback )();
+                       $key = $this->cache->makeKey( 'bad-image-list', sha1( $blacklist ) );
+                       $this->badFiles = $this->cache->get( $key ) ?: null;
+               }
+
+               if ( $this->badFiles === null ) {
+                       // Cache miss, build the list now
+                       $this->badFiles = [];
+                       $lines = explode( "\n", $blacklist );
+                       foreach ( $lines as $line ) {
+                               // List items only
+                               if ( substr( $line, 0, 1 ) !== '*' ) {
+                                       continue;
+                               }
+
+                               // Find all links
+                               $m = [];
+                               // XXX What is the ':?' doing in the regex? Why not let the TitleParser strip it?
+                               if ( !preg_match_all( '/\[\[:?(.*?)\]\]/', $line, $m ) ) {
+                                       continue;
+                               }
+
+                               $fileDBkey = null;
+                               $exceptions = [];
+                               foreach ( $m[1] as $i => $titleText ) {
+                                       try {
+                                               $title = $this->titleParser->parseTitle( $titleText );
+                                       } catch ( MalformedTitleException $e ) {
+                                               continue;
+                                       }
+                                       if ( $i == 0 ) {
+                                               $fileDBkey = $title->getDBkey();
+                                       } else {
+                                               $exceptions[$title->getNamespace()][$title->getDBkey()] = true;
+                                       }
+                               }
+
+                               if ( $fileDBkey !== null ) {
+                                       $this->badFiles[$fileDBkey] = $exceptions;
+                               }
+                       }
+                       $this->cache->set( $key, $this->badFiles, 24 * 60 * 60 );
+               }
+
+               return isset( $this->badFiles[$name] ) && ( !$contextTitle ||
+                       !isset( $this->badFiles[$name][$contextTitle->getNamespace()]
+                               [$contextTitle->getDBkey()] ) );
+       }
+}
index 34ac0e1..229958a 100644 (file)
@@ -340,7 +340,7 @@ class Category {
                $dbw->lockForUpdate( 'category', [ 'cat_title' => $this->mName ], __METHOD__ );
 
                // Lock all the `categorylinks` records and gaps for this category;
-               // this is a separate query due to postgres/oracle limitations
+               // this is a separate query due to postgres limitations
                $dbw->selectRowCount(
                        [ 'categorylinks', 'page' ],
                        '*',
index f2446da..7e13fc2 100644 (file)
@@ -1887,6 +1887,36 @@ $wgUsersNotifiedOnAllChanges = [];
  * @{
  */
 
+/**
+ * Current wiki database name
+ *
+ * Should be alphanumeric, without spaces nor hyphens.
+ * This is used to determine the current/local wiki ID (WikiMap::getCurrentWikiDbDomain).
+ *
+ * This should still be set even if $wgLBFactoryConf is configured.
+ */
+$wgDBname = 'my_wiki';
+
+/**
+ * Current wiki database schema name
+ *
+ * Should be alphanumeric, without spaces nor hyphens.
+ * This is used to determine the current/local wiki ID (WikiMap::getCurrentWikiDbDomain).
+ *
+ * This should still be set even if $wgLBFactoryConf is configured.
+ */
+$wgDBmwschema = null;
+
+/**
+ * Current wiki database table name prefix
+ *
+ * Should be alphanumeric, without spaces nor hyphens, preferably ending in an underscore.
+ * This is used to determine the current/local wiki ID (WikiMap::getCurrentWikiDbDomain).
+ *
+ * This should still be set even if $wgLBFactoryConf is configured.
+ */
+$wgDBprefix = '';
+
 /**
  * Database host name or IP address
  */
@@ -1897,11 +1927,6 @@ $wgDBserver = 'localhost';
  */
 $wgDBport = 5432;
 
-/**
- * Name of the database; this should be alphanumeric and not contain spaces nor hyphens
- */
-$wgDBname = 'my_wiki';
-
 /**
  * Database username
  */
@@ -1964,13 +1989,6 @@ $wgSearchType = null;
  */
 $wgSearchTypeAlternatives = null;
 
-/**
- * Table name prefix.
- * Should be alphanumeric plus underscores, and not contain spaces nor hyphens.
- * Suggested format ends with an underscore.
- */
-$wgDBprefix = '';
-
 /**
  * MySQL table options to use during installation or update
  */
@@ -1984,11 +2002,6 @@ $wgDBTableOptions = 'ENGINE=InnoDB, DEFAULT CHARSET=binary';
  */
 $wgSQLMode = '';
 
-/**
- * Mediawiki schema; this should be alphanumeric and not contain spaces nor hyphens
- */
-$wgDBmwschema = null;
-
 /**
  * Default group to use when getting database connections.
  * Will be used as default query group in ILoadBalancer::getConnection.
@@ -6829,6 +6842,8 @@ $wgRCLinkLimits = [ 50, 100, 250, 500 ];
 /**
  * List of Days options to list in the Special:Recentchanges and
  * Special:Recentchangeslinked pages.
+ *
+ * @see ChangesListSpecialPage::getLinkDays
  */
 $wgRCLinkDays = [ 1, 3, 7, 14, 30 ];
 
@@ -9095,6 +9110,16 @@ $wgFeaturePolicyReportOnly = [];
  */
 $wgSpecialSearchFormOptions = [];
 
+/**
+ * Toggles native image lazy loading, via the "loading" attribute.
+ *
+ * @warning EXPERIMENTAL!
+ *
+ * @since 1.34
+ * @var array
+ */
+$wgNativeImageLazyLoading = false;
+
 /**
  * For really cool vim folding this needs to be at the end:
  * vim: foldmarker=@{,@} foldmethod=marker
index 74ec883..d0a5080 100644 (file)
@@ -1593,7 +1593,8 @@ class EditPage {
                // This is needed since PageUpdater no longer checks these rights!
 
                // Allow bots to exempt some edits from bot flagging
-               $bot = $this->context->getUser()->isAllowed( 'bot' ) && $this->bot;
+               $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
+               $bot = $permissionManager->userHasRight( $this->context->getUser(), 'bot' ) && $this->bot;
                $status = $this->internalAttemptSave( $resultDetails, $bot );
 
                Hooks::run( 'EditPage::attemptSave:after', [ $this, $status, $resultDetails ] );
@@ -1870,6 +1871,7 @@ ERROR;
        public function internalAttemptSave( &$result, $bot = false ) {
                $status = Status::newGood();
                $user = $this->context->getUser();
+               $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
 
                if ( !Hooks::run( 'EditPage::attemptSave', [ $this ] ) ) {
                        wfDebug( "Hook 'EditPage::attemptSave' aborted article saving\n" );
@@ -1918,7 +1920,7 @@ ERROR;
                # Check image redirect
                if ( $this->mTitle->getNamespace() == NS_FILE &&
                        $textbox_content->isRedirect() &&
-                       !$user->isAllowed( 'upload' )
+                       !$permissionManager->userHasRight( $user, 'upload' )
                ) {
                                $code = $user->isAnon() ? self::AS_IMAGE_REDIRECT_ANON : self::AS_IMAGE_REDIRECT_LOGGED;
                                $status->setResult( false, $code );
@@ -1968,7 +1970,7 @@ ERROR;
                        return $status;
                }
 
-               if ( $user->isBlockedFrom( $this->mTitle ) ) {
+               if ( $permissionManager->isBlockedFrom( $user, $this->mTitle ) ) {
                        // Auto-block user's IP if the account was "hard" blocked
                        if ( !wfReadOnly() ) {
                                $user->spreadAnyEditBlock();
@@ -1988,7 +1990,7 @@ ERROR;
                        return $status;
                }
 
-               if ( !$user->isAllowed( 'edit' ) ) {
+               if ( !$permissionManager->userHasRight( $user, 'edit' ) ) {
                        if ( $user->isAnon() ) {
                                $status->setResult( false, self::AS_READ_ONLY_PAGE_ANON );
                                return $status;
@@ -1999,15 +2001,13 @@ ERROR;
                        }
                }
 
-               $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
-
                $changingContentModel = false;
                if ( $this->contentModel !== $this->mTitle->getContentModel() ) {
                        if ( !$config->get( 'ContentHandlerUseDB' ) ) {
                                $status->fatal( 'editpage-cannot-use-custom-model' );
                                $status->value = self::AS_CANNOT_USE_CUSTOM_MODEL;
                                return $status;
-                       } elseif ( !$user->isAllowed( 'editcontentmodel' ) ) {
+                       } elseif ( !$permissionManager->userHasRight( $user, 'editcontentmodel' ) ) {
                                $status->setResult( false, self::AS_NO_CHANGE_CONTENT_MODEL );
                                return $status;
                        }
@@ -4159,7 +4159,8 @@ ERROR;
 
                $user = $this->context->getUser();
                // don't show the minor edit checkbox if it's a new page or section
-               if ( !$this->isNew && $user->isAllowed( 'minoredit' ) ) {
+               $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
+               if ( !$this->isNew && $permissionManager->userHasRight( $user, 'minoredit' ) ) {
                        $checkboxes['wpMinoredit'] = [
                                'id' => 'wpMinoredit',
                                'label-message' => 'minoredit',
@@ -4446,8 +4447,8 @@ ERROR;
        protected function addPageProtectionWarningHeaders() {
                $out = $this->context->getOutput();
                if ( $this->mTitle->isProtected( 'edit' ) &&
-                       MediaWikiServices::getInstance()->getNamespaceInfo()->getRestrictionLevels(
-                               $this->mTitle->getNamespace()
+                       MediaWikiServices::getInstance()->getPermissionManager()->getNamespaceRestrictionLevels(
+                               $this->getTitle()->getNamespace()
                        ) !== [ '' ]
                ) {
                        # Is the title semi-protected?
index 5aa6edf..8272ccf 100644 (file)
@@ -79,7 +79,9 @@ class FileDeleteForm {
                $this->oldimage = $wgRequest->getText( 'oldimage', false );
                $token = $wgRequest->getText( 'wpEditToken' );
                # Flag to hide all contents of the archived revisions
-               $suppress = $wgRequest->getCheck( 'wpSuppress' ) && $wgUser->isAllowed( 'suppressrevision' );
+               $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
+               $suppress = $wgRequest->getCheck( 'wpSuppress' ) &&
+                                       $permissionManager->userHasRight( $wgUser, 'suppressrevision' );
 
                if ( $this->oldimage ) {
                        $this->oldfile = RepoGroup::singleton()->getLocalRepo()->newFromArchiveName(
@@ -245,6 +247,7 @@ class FileDeleteForm {
         */
        private function showForm() {
                global $wgOut, $wgUser, $wgRequest;
+               $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
 
                $wgOut->addModules( 'mediawiki.action.delete.file' );
 
@@ -296,7 +299,7 @@ class FileDeleteForm {
                        ]
                );
 
-               if ( $wgUser->isAllowed( 'suppressrevision' ) ) {
+               if ( $permissionManager->userHasRight( $wgUser, 'suppressrevision' ) ) {
                        $fields[] = new OOUI\FieldLayout(
                                new OOUI\CheckboxInputWidget( [
                                        'name' => 'wpSuppress',
@@ -370,7 +373,7 @@ class FileDeleteForm {
                        ] )
                );
 
-               if ( $wgUser->isAllowed( 'editinterface' ) ) {
+               if ( $permissionManager->userHasRight( $wgUser, 'editinterface' ) ) {
                        $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
                        $link = $linkRenderer->makeKnownLink(
                                $wgOut->msg( 'filedelete-reason-dropdown' )->inContentLanguage()->getTitle(),
index 1741958..cc998c7 100644 (file)
@@ -24,14 +24,15 @@ if ( !defined( 'MEDIAWIKI' ) ) {
        die( "This file is part of MediaWiki, it is not a valid entry point" );
 }
 
+use MediaWiki\BadFileLookup;
 use MediaWiki\Linker\LinkTarget;
 use MediaWiki\Logger\LoggerFactory;
 use MediaWiki\MediaWikiServices;
 use MediaWiki\ProcOpenError;
 use MediaWiki\Session\SessionManager;
 use MediaWiki\Shell\Shell;
-use Wikimedia\WrappedString;
 use Wikimedia\AtEase\AtEase;
+use Wikimedia\WrappedString;
 
 /**
  * Load an extension
@@ -2907,72 +2908,27 @@ function wfUnpack( $format, $data, $length = false ) {
  *    * Any subsequent links on the same line are considered to be exceptions,
  *      i.e. articles where the image may occur inline.
  *
+ * @deprecated since 1.34, use the BadFileLookup service directly instead
+ *
  * @param string $name The image name to check
  * @param Title|bool $contextTitle The page on which the image occurs, if known
  * @param string|null $blacklist Wikitext of a file blacklist
  * @return bool
  */
 function wfIsBadImage( $name, $contextTitle = false, $blacklist = null ) {
-       # Handle redirects; callers almost always hit wfFindFile() anyway,
-       # so just use that method because it has a fast process cache.
-       $file = MediaWikiServices::getInstance()->getRepoGroup()->findFile( $name ); // get the final name
-       $name = $file ? $file->getTitle()->getDBkey() : $name;
-
-       # Run the extension hook
-       $bad = false;
-       if ( !Hooks::run( 'BadImage', [ $name, &$bad ] ) ) {
-               return (bool)$bad;
-       }
-
-       $cache = ObjectCache::getLocalServerInstance( 'hash' );
-       $key = $cache->makeKey(
-               'bad-image-list', ( $blacklist === null ) ? 'default' : md5( $blacklist )
-       );
-       $badImages = $cache->get( $key );
-
-       if ( $badImages === false ) { // cache miss
-               if ( $blacklist === null ) {
-                       $blacklist = wfMessage( 'bad_image_list' )->inContentLanguage()->plain(); // site list
-               }
-               # Build the list now
-               $badImages = [];
-               $lines = explode( "\n", $blacklist );
-               foreach ( $lines as $line ) {
-                       # List items only
-                       if ( substr( $line, 0, 1 ) !== '*' ) {
-                               continue;
-                       }
-
-                       # Find all links
-                       $m = [];
-                       if ( !preg_match_all( '/\[\[:?(.*?)\]\]/', $line, $m ) ) {
-                               continue;
-                       }
-
-                       $exceptions = [];
-                       $imageDBkey = false;
-                       foreach ( $m[1] as $i => $titleText ) {
-                               $title = Title::newFromText( $titleText );
-                               if ( !is_null( $title ) ) {
-                                       if ( $i == 0 ) {
-                                               $imageDBkey = $title->getDBkey();
-                                       } else {
-                                               $exceptions[$title->getPrefixedDBkey()] = true;
-                                       }
-                               }
-                       }
-
-                       if ( $imageDBkey !== false ) {
-                               $badImages[$imageDBkey] = $exceptions;
-                       }
-               }
-               $cache->set( $key, $badImages, 60 );
-       }
-
-       $contextKey = $contextTitle ? $contextTitle->getPrefixedDBkey() : false;
-       $bad = isset( $badImages[$name] ) && !isset( $badImages[$name][$contextKey] );
-
-       return $bad;
+       $services = MediaWikiServices::getInstance();
+       if ( $blacklist !== null ) {
+               wfDeprecated( __METHOD__ . ' with $blacklist parameter', '1.34' );
+               return ( new BadFileLookup(
+                       function () use ( $blacklist ) {
+                               return $blacklist;
+                       },
+                       $services->getLocalServerObjectCache(),
+                       $services->getRepoGroup(),
+                       $services->getTitleParser()
+               ) )->isBadFile( $name, $contextTitle ?: null );
+       }
+       return $services->getBadFileLookup()->isBadFile( $name, $contextTitle ?: null );
 }
 
 /**
index db3e2f5..a79ec3a 100644 (file)
@@ -978,7 +978,9 @@ class Linker {
 
                        $items[] = self::link( $contribsPage, wfMessage( 'contribslink' )->escaped(), $attribs );
                }
-               if ( $blockable && $wgUser->isAllowed( 'block' ) ) {
+               $userCanBlock = MediaWikiServices::getInstance()->getPermissionManager()
+                       ->userHasRight( $wgUser, 'block' );
+               if ( $blockable && $userCanBlock ) {
                        $items[] = self::blockLink( $userId, $userText );
                }
 
@@ -1320,7 +1322,7 @@ class Linker {
                                        $services->getNamespaceInfo()->getCanonicalName( NS_MEDIA ), '/' );
                                $medians .= '|';
                                $medians .= preg_quote(
-                                       MediaWikiServices::getInstance()->getContentLanguage()->getNsText( NS_MEDIA ),
+                                       $services->getContentLanguage()->getNsText( NS_MEDIA ),
                                        '/'
                                ) . '):';
 
@@ -1357,7 +1359,7 @@ class Linker {
                                        }
                                        if ( $match[1] !== false && $match[1] !== '' ) {
                                                if ( preg_match(
-                                                       MediaWikiServices::getInstance()->getContentLanguage()->linkTrail(),
+                                                       $services->getContentLanguage()->linkTrail(),
                                                        $match[3],
                                                        $submatch
                                                ) ) {
@@ -1373,7 +1375,7 @@ class Linker {
 
                                                Title::newFromText( $linkTarget );
                                                try {
-                                                       $target = MediaWikiServices::getInstance()->getTitleParser()->
+                                                       $target = $services->getTitleParser()->
                                                                parseTitle( $linkTarget );
 
                                                        if ( $target->getText() == '' && !$target->isExternal()
@@ -2103,8 +2105,10 @@ class Linker {
         * @return string HTML fragment
         */
        public static function getRevDeleteLink( User $user, Revision $rev, LinkTarget $title ) {
-               $canHide = $user->isAllowed( 'deleterevision' );
-               if ( !$canHide && !( $rev->getVisibility() && $user->isAllowed( 'deletedhistory' ) ) ) {
+               $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
+               $canHide = $permissionManager->userHasRight( $user, 'deleterevision' );
+               $canHideHistory = $permissionManager->userHasRight( $user, 'deletedhistory' );
+               if ( !$canHide && !( $rev->getVisibility() && $canHideHistory ) ) {
                        return '';
                }
 
index 0121bd5..4a911b0 100644 (file)
@@ -318,8 +318,9 @@ class MWNamespace {
         * @return array
         */
        public static function getRestrictionLevels( $index, User $user = null ) {
-               return MediaWikiServices::getInstance()->getNamespaceInfo()->
-                       getRestrictionLevels( $index, $user );
+               return MediaWikiServices::getInstance()
+                       ->getPermissionManager()
+                       ->getNamespaceRestrictionLevels( $index, $user );
        }
 
        /**
index bb9c05f..6013aaf 100644 (file)
@@ -16,6 +16,7 @@ use IBufferingStatsdDataFactory;
 use Liuggio\StatsdClient\Factory\StatsdDataFactoryInterface;
 use MediaWiki\Block\BlockManager;
 use MediaWiki\Block\BlockRestrictionStore;
+use MediaWiki\FileBackend\FSFile\TempFSFileFactory;
 use MediaWiki\Http\HttpRequestFactory;
 use MediaWiki\Page\MovePageFactory;
 use MediaWiki\Permissions\PermissionManager;
@@ -63,6 +64,7 @@ use SkinFactory;
 use TitleFormatter;
 use TitleParser;
 use VirtualRESTServiceClient;
+use Wikimedia\ObjectFactory;
 use Wikimedia\Rdbms\LBFactory;
 use Wikimedia\Services\SalvageableService;
 use Wikimedia\Services\ServiceContainer;
@@ -428,6 +430,14 @@ class MediaWikiServices extends ServiceContainer {
                return $this->getService( 'ActorMigration' );
        }
 
+       /**
+        * @since 1.34
+        * @return BadFileLookup
+        */
+       public function getBadFileLookup() : BadFileLookup {
+               return $this->getService( 'BadFileLookup' );
+       }
+
        /**
         * @since 1.31
         * @return BlobStore
@@ -731,6 +741,17 @@ class MediaWikiServices extends ServiceContainer {
                return $this->getService( 'NameTableStoreFactory' );
        }
 
+       /**
+        * ObjectFactory is intended for instantiating "handlers" from declarative definitions,
+        * such as Action API modules, special pages, or REST API handlers.
+        *
+        * @since 1.34
+        * @return ObjectFactory
+        */
+       public function getObjectFactory() {
+               return $this->getService( 'ObjectFactory' );
+       }
+
        /**
         * @since 1.32
         * @return OldRevisionImporter
@@ -964,6 +985,14 @@ class MediaWikiServices extends ServiceContainer {
                return $this->getService( 'StatsdDataFactory' );
        }
 
+       /**
+        * @since 1.34
+        * @return TempFSFileFactory
+        */
+       public function getTempFSFileFactory() : TempFSFileFactory {
+               return $this->getService( 'TempFSFileFactory' );
+       }
+
        /**
         * @since 1.28
         * @return TitleFormatter
index 6bd4471..4045a54 100644 (file)
@@ -178,7 +178,8 @@ class MergeHistory {
                }
 
                // Check mergehistory permission
-               if ( !$user->isAllowed( 'mergehistory' ) ) {
+               $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
+               if ( !$permissionManager->userHasRight( $user, 'mergehistory' ) ) {
                        // User doesn't have the right to merge histories
                        $status->fatal( 'mergehistory-fail-permission' );
                }
index a63eeae..e6faace 100644 (file)
@@ -141,8 +141,9 @@ class MovePage {
                }
 
                $tp = $this->newTitle->getTitleProtection();
-               if ( $tp !== false && !$user->isAllowed( $tp['permission'] ) ) {
-                               $status->fatal( 'cantmove-titleprotected' );
+               $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
+               if ( $tp !== false && !$permissionManager->userHasRight( $user, $tp['permission'] ) ) {
+                       $status->fatal( 'cantmove-titleprotected' );
                }
 
                Hooks::run( 'MovePageCheckPermissions',
@@ -351,7 +352,8 @@ class MovePage {
                }
 
                // Check suppressredirect permission
-               if ( !$user->isAllowed( 'suppressredirect' ) ) {
+               $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
+               if ( !$permissionManager->userHasRight( $user, 'suppressredirect' ) ) {
                        $createRedirect = true;
                }
 
index b7341e3..b2ca53a 100644 (file)
@@ -1661,6 +1661,16 @@ class OutputPage extends ContextSource {
                return $this->mRevisionId;
        }
 
+       /**
+        * Whether the revision displayed is the latest revision of the page
+        *
+        * @since 1.34
+        * @return bool
+        */
+       public function isRevisionCurrent() {
+               return $this->mRevisionId == 0 || $this->mRevisionId == $this->getTitle()->getLatestRevID();
+       }
+
        /**
         * Set the timestamp of the revision which will be displayed. This is used
         * to avoid a extra DB call in Skin::lastModified().
@@ -2666,6 +2676,8 @@ class OutputPage extends ContextSource {
         * @param string|null $action Action that was denied or null if unknown
         */
        public function showPermissionsErrorPage( array $errors, $action = null ) {
+               $services = MediaWikiServices::getInstance();
+               $permissionManager = $services->getPermissionManager();
                foreach ( $errors as $key => $error ) {
                        $errors[$key] = (array)$error;
                }
@@ -2675,11 +2687,12 @@ class OutputPage extends ContextSource {
                // 1. the user is not logged in
                // 2. the only error is insufficient permissions (i.e. no block or something else)
                // 3. the error can be avoided simply by logging in
+
                if ( in_array( $action, [ 'read', 'edit', 'createpage', 'createtalk', 'upload' ] )
                        && $this->getUser()->isAnon() && count( $errors ) == 1 && isset( $errors[0][0] )
                        && ( $errors[0][0] == 'badaccess-groups' || $errors[0][0] == 'badaccess-group0' )
-                       && ( User::groupHasPermission( 'user', $action )
-                       || User::groupHasPermission( 'autoconfirmed', $action ) )
+                       && ( $permissionManager->groupHasPermission( 'user', $action )
+                               || $permissionManager->groupHasPermission( 'autoconfirmed', $action ) )
                ) {
                        $displayReturnto = null;
 
@@ -2715,8 +2728,6 @@ class OutputPage extends ContextSource {
                                }
                        }
 
-                       $services = MediaWikiServices::getInstance();
-
                        $title = SpecialPage::getTitleFor( 'Userlogin' );
                        $linkRenderer = $services->getLinkRenderer();
                        $loginUrl = $title->getLinkURL( $query, false, PROTO_RELATIVE );
@@ -2730,8 +2741,6 @@ class OutputPage extends ContextSource {
                        $this->prepareErrorPage( $this->msg( 'loginreqtitle' ) );
                        $this->addHTML( $this->msg( $msg )->rawParams( $loginLink )->params( $loginUrl )->parse() );
 
-                       $permissionManager = $services->getPermissionManager();
-
                        # Don't return to a page the user can't read otherwise
                        # we'll end up in a pointless loop
                        if ( $displayReturnto && $permissionManager->userCan(
@@ -3218,7 +3227,7 @@ class OutputPage extends ContextSource {
 
                $title = $this->getTitle();
                $ns = $title->getNamespace();
-               $nsInfo = MediaWikiServices::getInstance()->getNamespaceInfo();
+               $nsInfo = $services->getNamespaceInfo();
                $canonicalNamespace = $nsInfo->exists( $ns )
                        ? $nsInfo->getCanonicalName( $ns )
                        : $title->getNsText();
index d256e9b..ec0157b 100644 (file)
@@ -22,6 +22,7 @@ namespace MediaWiki\Permissions;
 use Action;
 use Exception;
 use Hooks;
+use MediaWiki\Config\ServiceOptions;
 use MediaWiki\Linker\LinkTarget;
 use MediaWiki\Revision\RevisionLookup;
 use MediaWiki\Revision\RevisionRecord;
@@ -54,36 +55,36 @@ class PermissionManager {
        /** @var string Does cheap and expensive checks, using the master as needed */
        const RIGOR_SECURE = 'secure';
 
+       /**
+        * TODO Make this const when HHVM support is dropped (T192166)
+        *
+        * @since 1.34
+        * @var array
+        */
+       public static $constructorOptions = [
+               'WhitelistRead',
+               'WhitelistReadRegexp',
+               'EmailConfirmToEdit',
+               'BlockDisablesLogin',
+               'GroupPermissions',
+               'RevokePermissions',
+               'AvailableRights',
+               'NamespaceProtection',
+               'RestrictionLevels'
+       ];
+
+       /** @var ServiceOptions */
+       private $options;
+
        /** @var SpecialPageFactory */
        private $specialPageFactory;
 
        /** @var RevisionLookup */
        private $revisionLookup;
 
-       /** @var string[] List of pages names anonymous user may see */
-       private $whitelistRead;
-
-       /** @var string[] Whitelists publicly readable titles with regular expressions */
-       private $whitelistReadRegexp;
-
-       /** @var bool Require users to confirm email address before they can edit */
-       private $emailConfirmToEdit;
-
-       /** @var bool If set to true, blocked users will no longer be allowed to log in */
-       private $blockDisablesLogin;
-
        /** @var NamespaceInfo */
        private $nsInfo;
 
-       /** @var string[][] Access rights for groups and users in these groups */
-       private $groupPermissions;
-
-       /** @var string[][] Permission keys revoked from users in each group */
-       private $revokePermissions;
-
-       /** @var string[] A list of available rights, in addition to the ones defined by the core */
-       private $availableRights;
-
        /** @var string[] Cached results of getAllRights() */
        private $allRights = false;
 
@@ -189,38 +190,21 @@ class PermissionManager {
        ];
 
        /**
+        * @param ServiceOptions $options
         * @param SpecialPageFactory $specialPageFactory
         * @param RevisionLookup $revisionLookup
-        * @param string[] $whitelistRead
-        * @param string[] $whitelistReadRegexp
-        * @param bool $emailConfirmToEdit
-        * @param bool $blockDisablesLogin
-        * @param string[][] $groupPermissions
-        * @param string[][] $revokePermissions
-        * @param string[] $availableRights
         * @param NamespaceInfo $nsInfo
         */
        public function __construct(
+               ServiceOptions $options,
                SpecialPageFactory $specialPageFactory,
                RevisionLookup $revisionLookup,
-               $whitelistRead,
-               $whitelistReadRegexp,
-               $emailConfirmToEdit,
-               $blockDisablesLogin,
-               $groupPermissions,
-               $revokePermissions,
-               $availableRights,
                NamespaceInfo $nsInfo
        ) {
+               $options->assertRequiredOptions( self::$constructorOptions );
+               $this->options = $options;
                $this->specialPageFactory = $specialPageFactory;
                $this->revisionLookup = $revisionLookup;
-               $this->whitelistRead = $whitelistRead;
-               $this->whitelistReadRegexp = $whitelistReadRegexp;
-               $this->emailConfirmToEdit = $emailConfirmToEdit;
-               $this->blockDisablesLogin = $blockDisablesLogin;
-               $this->groupPermissions = $groupPermissions;
-               $this->revokePermissions = $revokePermissions;
-               $this->availableRights = $availableRights;
                $this->nsInfo = $nsInfo;
        }
 
@@ -289,7 +273,8 @@ class PermissionManager {
        }
 
        /**
-        * Check if user is blocked from editing a particular article
+        * Check if user is blocked from editing a particular article. If the user does not
+        * have a block, this will return false.
         *
         * @param User $user
         * @param LinkTarget $page Title to check
@@ -298,27 +283,29 @@ class PermissionManager {
         * @return bool
         */
        public function isBlockedFrom( User $user, LinkTarget $page, $fromReplica = false ) {
-               $blocked = $user->isHidden();
+               $block = $user->getBlock( $fromReplica );
+               if ( !$block ) {
+                       return false;
+               }
 
                // TODO: remove upon further migration to LinkTarget
                $title = Title::newFromLinkTarget( $page );
 
+               $blocked = $user->isHidden();
                if ( !$blocked ) {
-                       $block = $user->getBlock( $fromReplica );
-                       if ( $block ) {
-                               // Special handling for a user's own talk page. The block is not aware
-                               // of the user, so this must be done here.
-                               if ( $title->equals( $user->getTalkPage() ) ) {
-                                       $blocked = $block->appliesToUsertalk( $title );
-                               } else {
-                                       $blocked = $block->appliesToTitle( $title );
-                               }
+                       // Special handling for a user's own talk page. The block is not aware
+                       // of the user, so this must be done here.
+                       if ( $title->equals( $user->getTalkPage() ) ) {
+                               $blocked = $block->appliesToUsertalk( $title );
+                       } else {
+                               $blocked = $block->appliesToTitle( $title );
                        }
                }
 
                // only for the purpose of the hook. We really don't need this here.
                $allowUsertalk = $user->isAllowUsertalk();
 
+               // Allow extensions to let a blocked user access a particular page
                Hooks::run( 'UserIsBlockedFrom', [ $user, $title, &$blocked, &$allowUsertalk ] );
 
                return $blocked;
@@ -500,11 +487,12 @@ class PermissionManager {
                // TODO: remove when LinkTarget usage will expand further
                $title = Title::newFromLinkTarget( $page );
 
+               $whiteListRead = $this->options->get( 'WhitelistRead' );
                $whitelisted = false;
-               if ( User::isEveryoneAllowed( 'read' ) ) {
+               if ( $this->isEveryoneAllowed( 'read' ) ) {
                        # Shortcut for public wikis, allows skipping quite a bit of code
                        $whitelisted = true;
-               } elseif ( $user->isAllowed( 'read' ) ) {
+               } elseif ( $this->userHasRight( $user, 'read' ) ) {
                        # If the user is allowed to read pages, he is allowed to read all pages
                        $whitelisted = true;
                } elseif ( $this->isSameSpecialPage( 'Userlogin', $title )
@@ -514,20 +502,20 @@ class PermissionManager {
                        # Always grant access to the login page.
                        # Even anons need to be able to log in.
                        $whitelisted = true;
-               } elseif ( is_array( $this->whitelistRead ) && count( $this->whitelistRead ) ) {
+               } elseif ( is_array( $whiteListRead ) && count( $whiteListRead ) ) {
                        # Time to check the whitelist
                        # Only do these checks is there's something to check against
                        $name = $title->getPrefixedText();
                        $dbName = $title->getPrefixedDBkey();
 
                        // Check for explicit whitelisting with and without underscores
-                       if ( in_array( $name, $this->whitelistRead, true )
-                                || in_array( $dbName, $this->whitelistRead, true ) ) {
+                       if ( in_array( $name, $whiteListRead, true )
+                                || in_array( $dbName, $whiteListRead, true ) ) {
                                $whitelisted = true;
                        } elseif ( $title->getNamespace() == NS_MAIN ) {
                                # Old settings might have the title prefixed with
                                # a colon for main-namespace pages
-                               if ( in_array( ':' . $name, $this->whitelistRead ) ) {
+                               if ( in_array( ':' . $name, $whiteListRead ) ) {
                                        $whitelisted = true;
                                }
                        } elseif ( $title->isSpecialPage() ) {
@@ -537,18 +525,19 @@ class PermissionManager {
                                        $this->specialPageFactory->resolveAlias( $name );
                                if ( $name ) {
                                        $pure = SpecialPage::getTitleFor( $name )->getPrefixedText();
-                                       if ( in_array( $pure, $this->whitelistRead, true ) ) {
+                                       if ( in_array( $pure, $whiteListRead, true ) ) {
                                                $whitelisted = true;
                                        }
                                }
                        }
                }
 
-               if ( !$whitelisted && is_array( $this->whitelistReadRegexp )
-                        && !empty( $this->whitelistReadRegexp ) ) {
+               $whitelistReadRegexp = $this->options->get( 'WhitelistReadRegexp' );
+               if ( !$whitelisted && is_array( $whitelistReadRegexp )
+                        && !empty( $whitelistReadRegexp ) ) {
                        $name = $title->getPrefixedText();
                        // Check for regex whitelisting
-                       foreach ( $this->whitelistReadRegexp as $listItem ) {
+                       foreach ( $whitelistReadRegexp as $listItem ) {
                                if ( preg_match( $listItem, $name ) ) {
                                        $whitelisted = true;
                                        break;
@@ -636,11 +625,11 @@ class PermissionManager {
                }
 
                // Optimize for a very common case
-               if ( $action === 'read' && !$this->blockDisablesLogin ) {
+               if ( $action === 'read' && !$this->options->get( 'BlockDisablesLogin' ) ) {
                        return $errors;
                }
 
-               if ( $this->emailConfirmToEdit
+               if ( $this->options->get( 'EmailConfirmToEdit' )
                         && !$user->isEmailConfirmed()
                         && $action === 'edit'
                ) {
@@ -729,33 +718,35 @@ class PermissionManager {
                if ( $action == 'create' ) {
                        if (
                                ( $this->nsInfo->isTalk( $title->getNamespace() ) &&
-                                       !$user->isAllowed( 'createtalk' ) ) ||
+                                       !$this->userHasRight( $user, 'createtalk' ) ) ||
                                ( !$this->nsInfo->isTalk( $title->getNamespace() ) &&
-                                       !$user->isAllowed( 'createpage' ) )
+                                       !$this->userHasRight( $user, 'createpage' ) )
                        ) {
                                $errors[] = $user->isAnon() ? [ 'nocreatetext' ] : [ 'nocreate-loggedin' ];
                        }
                } elseif ( $action == 'move' ) {
-                       if ( !$user->isAllowed( 'move-rootuserpages' )
+                       if ( !$this->userHasRight( $user, 'move-rootuserpages' )
                                 && $title->getNamespace() == NS_USER && !$isSubPage ) {
                                // Show user page-specific message only if the user can move other pages
                                $errors[] = [ 'cant-move-user-page' ];
                        }
 
                        // Check if user is allowed to move files if it's a file
-                       if ( $title->getNamespace() == NS_FILE && !$user->isAllowed( 'movefile' ) ) {
+                       if ( $title->getNamespace() == NS_FILE &&
+                                       !$this->userHasRight( $user, 'movefile' ) ) {
                                $errors[] = [ 'movenotallowedfile' ];
                        }
 
                        // Check if user is allowed to move category pages if it's a category page
-                       if ( $title->getNamespace() == NS_CATEGORY && !$user->isAllowed( 'move-categorypages' ) ) {
+                       if ( $title->getNamespace() == NS_CATEGORY &&
+                                       !$this->userHasRight( $user, 'move-categorypages' ) ) {
                                $errors[] = [ 'cant-move-category-page' ];
                        }
 
-                       if ( !$user->isAllowed( 'move' ) ) {
+                       if ( !$this->userHasRight( $user, 'move' ) ) {
                                // User can't move anything
-                               $userCanMove = User::groupHasPermission( 'user', 'move' );
-                               $autoconfirmedCanMove = User::groupHasPermission( 'autoconfirmed', 'move' );
+                               $userCanMove = $this->groupHasPermission( 'user', 'move' );
+                               $autoconfirmedCanMove = $this->groupHasPermission( 'autoconfirmed', 'move' );
                                if ( $user->isAnon() && ( $userCanMove || $autoconfirmedCanMove ) ) {
                                        // custom message if logged-in users without any special rights can move
                                        $errors[] = [ 'movenologintext' ];
@@ -764,19 +755,19 @@ class PermissionManager {
                                }
                        }
                } elseif ( $action == 'move-target' ) {
-                       if ( !$user->isAllowed( 'move' ) ) {
+                       if ( !$this->userHasRight( $user, 'move' ) ) {
                                // User can't move anything
                                $errors[] = [ 'movenotallowed' ];
-                       } elseif ( !$user->isAllowed( 'move-rootuserpages' )
+                       } elseif ( !$this->userHasRight( $user, 'move-rootuserpages' )
                                           && $title->getNamespace() == NS_USER && !$isSubPage ) {
                                // Show user page-specific message only if the user can move other pages
                                $errors[] = [ 'cant-move-to-user-page' ];
-                       } elseif ( !$user->isAllowed( 'move-categorypages' )
+                       } elseif ( !$this->userHasRight( $user, 'move-categorypages' )
                                           && $title->getNamespace() == NS_CATEGORY ) {
                                // Show category page-specific message only if the user can move other pages
                                $errors[] = [ 'cant-move-to-category-page' ];
                        }
-               } elseif ( !$user->isAllowed( $action ) ) {
+               } elseif ( !$this->userHasRight( $user, $action ) ) {
                        $errors[] = $this->missingPermissionError( $action, $short );
                }
 
@@ -823,9 +814,10 @@ class PermissionManager {
                        if ( $right == '' ) {
                                continue;
                        }
-                       if ( !$user->isAllowed( $right ) ) {
+                       if ( !$this->userHasRight( $user, $right ) ) {
                                $errors[] = [ 'protectedpagetext', $right, $action ];
-                       } elseif ( $title->areRestrictionsCascading() && !$user->isAllowed( 'protect' ) ) {
+                       } elseif ( $title->areRestrictionsCascading() &&
+                                          !$this->userHasRight( $user, 'protect' ) ) {
                                $errors[] = [ 'protectedpagetext', 'protect', $action ];
                        }
                }
@@ -837,7 +829,7 @@ class PermissionManager {
         * Check restrictions on cascading pages.
         *
         * @param string $action The action to check
-        * @param User $user User to check
+        * @param UserIdentity $user User to check
         * @param array $errors List of current errors
         * @param string $rigor One of PermissionManager::RIGOR_ constants
         *   - RIGOR_QUICK  : does cheap permission checks from replica DBs (usable for GUI creation)
@@ -851,7 +843,7 @@ class PermissionManager {
         */
        private function checkCascadingSourcesRestrictions(
                $action,
-               User $user,
+               UserIdentity $user,
                $errors,
                $rigor,
                $short,
@@ -880,7 +872,7 @@ class PermissionManager {
                                        if ( $right == 'autoconfirmed' ) {
                                                $right = 'editsemiprotected';
                                        }
-                                       if ( $right != '' && !$user->isAllowedAll( 'protect', $right ) ) {
+                                       if ( $right != '' && !$this->userHasAllRights( $user, 'protect', $right ) ) {
                                                $wikiPages = '';
                                                /** @var Title $wikiPage */
                                                foreach ( $cascadingSources as $wikiPage ) {
@@ -933,7 +925,7 @@ class PermissionManager {
                        $title_protection = $title->getTitleProtection();
                        if ( $title_protection ) {
                                if ( $title_protection['permission'] == ''
-                                        || !$user->isAllowed( $title_protection['permission'] )
+                                        || !$this->userHasRight( $user, $title_protection['permission'] )
                                ) {
                                        $errors[] = [
                                                'titleprotected',
@@ -1063,23 +1055,23 @@ class PermissionManager {
                        $error = null;
                        // Sitewide CSS/JSON/JS changes, like all NS_MEDIAWIKI changes, also require the
                        // editinterface right. That's implemented as a restriction so no check needed here.
-                       if ( $title->isSiteCssConfigPage() && !$user->isAllowed( 'editsitecss' ) ) {
+                       if ( $title->isSiteCssConfigPage() && !$this->userHasRight( $user, 'editsitecss' ) ) {
                                $error = [ 'sitecssprotected', $action ];
-                       } elseif ( $title->isSiteJsonConfigPage() && !$user->isAllowed( 'editsitejson' ) ) {
+                       } elseif ( $title->isSiteJsonConfigPage() && !$this->userHasRight( $user, 'editsitejson' ) ) {
                                $error = [ 'sitejsonprotected', $action ];
-                       } elseif ( $title->isSiteJsConfigPage() && !$user->isAllowed( 'editsitejs' ) ) {
+                       } elseif ( $title->isSiteJsConfigPage() && !$this->userHasRight( $user, 'editsitejs' ) ) {
                                $error = [ 'sitejsprotected', $action ];
                        } elseif ( $title->isRawHtmlMessage() ) {
                                // Raw HTML can be used to deploy CSS or JS so require rights for both.
-                               if ( !$user->isAllowed( 'editsitejs' ) ) {
+                               if ( !$this->userHasRight( $user, 'editsitejs' ) ) {
                                        $error = [ 'sitejsprotected', $action ];
-                               } elseif ( !$user->isAllowed( 'editsitecss' ) ) {
+                               } elseif ( !$this->userHasRight( $user, 'editsitecss' ) ) {
                                        $error = [ 'sitecssprotected', $action ];
                                }
                        }
 
                        if ( $error ) {
-                               if ( $user->isAllowed( 'editinterface' ) ) {
+                               if ( $this->userHasRight( $user, 'editinterface' ) ) {
                                        // Most users / site admins will probably find out about the new, more restrictive
                                        // permissions by failing to edit something. Give them more info.
                                        // TODO remove this a few release cycles after 1.32
@@ -1096,7 +1088,7 @@ class PermissionManager {
         * Check CSS/JSON/JS sub-page permissions
         *
         * @param string $action The action to check
-        * @param User $user User to check
+        * @param UserIdentity $user User to check
         * @param array $errors List of current errors
         * @param string $rigor One of PermissionManager::RIGOR_ constants
         *   - RIGOR_QUICK  : does cheap permission checks from replica DBs (usable for GUI creation)
@@ -1110,7 +1102,7 @@ class PermissionManager {
         */
        private function checkUserConfigPermissions(
                $action,
-               User $user,
+               UserIdentity $user,
                $errors,
                $rigor,
                $short,
@@ -1130,22 +1122,22 @@ class PermissionManager {
                        // Users need editmyuser* to edit their own CSS/JSON/JS subpages.
                        if (
                                $title->isUserCssConfigPage()
-                               && !$user->isAllowedAny( 'editmyusercss', 'editusercss' )
+                               && !$this->userHasAnyRight( $user, 'editmyusercss', 'editusercss' )
                        ) {
                                $errors[] = [ 'mycustomcssprotected', $action ];
                        } elseif (
                                $title->isUserJsonConfigPage()
-                               && !$user->isAllowedAny( 'editmyuserjson', 'edituserjson' )
+                               && !$this->userHasAnyRight( $user, 'editmyuserjson', 'edituserjson' )
                        ) {
                                $errors[] = [ 'mycustomjsonprotected', $action ];
                        } elseif (
                                $title->isUserJsConfigPage()
-                               && !$user->isAllowedAny( 'editmyuserjs', 'edituserjs' )
+                               && !$this->userHasAnyRight( $user, 'editmyuserjs', 'edituserjs' )
                        ) {
                                $errors[] = [ 'mycustomjsprotected', $action ];
                        } elseif (
                                $title->isUserJsConfigPage()
-                               && !$user->isAllowedAny( 'edituserjs', 'editmyuserjsredirect' )
+                               && !$this->userHasAnyRight( $user, 'edituserjs', 'editmyuserjsredirect' )
                        ) {
                                // T207750 - do not allow users to edit a redirect if they couldn't edit the target
                                $rev = $this->revisionLookup->getRevisionByTitle( $title );
@@ -1166,17 +1158,17 @@ class PermissionManager {
                        if ( !in_array( $action, [ 'delete', 'deleterevision', 'suppressrevision' ], true ) ) {
                                if (
                                        $title->isUserCssConfigPage()
-                                       && !$user->isAllowed( 'editusercss' )
+                                       && !$this->userHasRight( $user, 'editusercss' )
                                ) {
                                        $errors[] = [ 'customcssprotected', $action ];
                                } elseif (
                                        $title->isUserJsonConfigPage()
-                                       && !$user->isAllowed( 'edituserjson' )
+                                       && !$this->userHasRight( $user, 'edituserjson' )
                                ) {
                                        $errors[] = [ 'customjsonprotected', $action ];
                                } elseif (
                                        $title->isUserJsConfigPage()
-                                       && !$user->isAllowed( 'edituserjs' )
+                                       && !$this->userHasRight( $user, 'edituserjs' )
                                ) {
                                        $errors[] = [ 'customjsprotected', $action ];
                                }
@@ -1205,6 +1197,42 @@ class PermissionManager {
                return in_array( $action, $this->getUserPermissions( $user ), true );
        }
 
+       /**
+        * Check if user is allowed to make any action
+        *
+        * @param UserIdentity $user
+        * // TODO: HHVM can't create mocks with variable params @param string ...$actions
+        * @return bool True if user is allowed to perform *any* of the given actions
+        * @since 1.34
+        */
+       public function userHasAnyRight( UserIdentity $user ) {
+               $actions = array_slice( func_get_args(), 1 );
+               foreach ( $actions as $action ) {
+                       if ( $this->userHasRight( $user, $action ) ) {
+                               return true;
+                       }
+               }
+               return false;
+       }
+
+       /**
+        * Check if user is allowed to make all actions
+        *
+        * @param UserIdentity $user
+        * // TODO: HHVM can't create mocks with variable params @param string ...$actions
+        * @return bool True if user is allowed to perform *all* of the given actions
+        * @since 1.34
+        */
+       public function userHasAllRights( UserIdentity $user ) {
+               $actions = array_slice( func_get_args(), 1 );
+               foreach ( $actions as $action ) {
+                       if ( !$this->userHasRight( $user, $action ) ) {
+                               return false;
+                       }
+               }
+               return true;
+       }
+
        /**
         * Get the permissions this user has.
         *
@@ -1243,7 +1271,7 @@ class PermissionManager {
 
                        if (
                                $user->isLoggedIn() &&
-                               $this->blockDisablesLogin &&
+                               $this->options->get( 'BlockDisablesLogin' ) &&
                                $user->getBlock()
                        ) {
                                $anon = new User;
@@ -1293,10 +1321,10 @@ class PermissionManager {
         * @return bool
         */
        public function groupHasPermission( $group, $role ) {
-               return isset( $this->groupPermissions[$group][$role] ) &&
-                          $this->groupPermissions[$group][$role] &&
-                          !( isset( $this->revokePermissions[$group][$role] ) &&
-                                 $this->revokePermissions[$group][$role] );
+               $groupPermissions = $this->options->get( 'GroupPermissions' );
+               $revokePermissions = $this->options->get( 'RevokePermissions' );
+               return isset( $groupPermissions[$group][$role] ) && $groupPermissions[$group][$role] &&
+                          !( isset( $revokePermissions[$group][$role] ) && $revokePermissions[$group][$role] );
        }
 
        /**
@@ -1311,17 +1339,17 @@ class PermissionManager {
                $rights = [];
                // grant every granted permission first
                foreach ( $groups as $group ) {
-                       if ( isset( $this->groupPermissions[$group] ) ) {
+                       if ( isset( $this->options->get( 'GroupPermissions' )[$group] ) ) {
                                $rights = array_merge( $rights,
                                        // array_filter removes empty items
-                                       array_keys( array_filter( $this->groupPermissions[$group] ) ) );
+                                       array_keys( array_filter( $this->options->get( 'GroupPermissions' )[$group] ) ) );
                        }
                }
                // now revoke the revoked permissions
                foreach ( $groups as $group ) {
-                       if ( isset( $this->revokePermissions[$group] ) ) {
+                       if ( isset( $this->options->get( 'RevokePermissions' )[$group] ) ) {
                                $rights = array_diff( $rights,
-                                       array_keys( array_filter( $this->revokePermissions[$group] ) ) );
+                                       array_keys( array_filter( $this->options->get( 'RevokePermissions' )[$group] ) ) );
                        }
                }
                return array_unique( $rights );
@@ -1337,7 +1365,7 @@ class PermissionManager {
         */
        public function getGroupsWithPermission( $role ) {
                $allowedGroups = [];
-               foreach ( array_keys( $this->groupPermissions ) as $group ) {
+               foreach ( array_keys( $this->options->get( 'GroupPermissions' ) ) as $group ) {
                        if ( $this->groupHasPermission( $group, $role ) ) {
                                $allowedGroups[] = $group;
                        }
@@ -1367,14 +1395,14 @@ class PermissionManager {
                        return $this->cachedRights[$right];
                }
 
-               if ( !isset( $this->groupPermissions['*'][$right] )
-                        || !$this->groupPermissions['*'][$right] ) {
+               if ( !isset( $this->options->get( 'GroupPermissions' )['*'][$right] )
+                        || !$this->options->get( 'GroupPermissions' )['*'][$right] ) {
                        $this->cachedRights[$right] = false;
                        return false;
                }
 
                // If it's revoked anywhere, then everyone doesn't have it
-               foreach ( $this->revokePermissions as $rights ) {
+               foreach ( $this->options->get( 'RevokePermissions' ) as $rights ) {
                        if ( isset( $rights[$right] ) && $rights[$right] ) {
                                $this->cachedRights[$right] = false;
                                return false;
@@ -1412,10 +1440,10 @@ class PermissionManager {
         */
        public function getAllPermissions() {
                if ( $this->allRights === false ) {
-                       if ( count( $this->availableRights ) ) {
+                       if ( count( $this->options->get( 'AvailableRights' ) ) ) {
                                $this->allRights = array_unique( array_merge(
                                        $this->coreRights,
-                                       $this->availableRights
+                                       $this->options->get( 'AvailableRights' )
                                ) );
                        } else {
                                $this->allRights = $this->coreRights;
@@ -1425,6 +1453,85 @@ class PermissionManager {
                return $this->allRights;
        }
 
+       /**
+        * Determine which restriction levels it makes sense to use in a namespace,
+        * optionally filtered by a user's rights.
+        *
+        * @param int $index Index to check
+        * @param UserIdentity|null $user User to check
+        * @return array
+        */
+       public function getNamespaceRestrictionLevels( $index, UserIdentity $user = null ) {
+               if ( !isset( $this->options->get( 'NamespaceProtection' )[$index] ) ) {
+                       // All levels are valid if there's no namespace restriction.
+                       // But still filter by user, if necessary
+                       $levels = $this->options->get( 'RestrictionLevels' );
+                       if ( $user ) {
+                               $levels = array_values( array_filter( $levels, function ( $level ) use ( $user ) {
+                                       $right = $level;
+                                       if ( $right == 'sysop' ) {
+                                               $right = 'editprotected'; // BC
+                                       }
+                                       if ( $right == 'autoconfirmed' ) {
+                                               $right = 'editsemiprotected'; // BC
+                                       }
+                                       return $this->userHasRight( $user, $right );
+                               } ) );
+                       }
+                       return $levels;
+               }
+
+               // $wgNamespaceProtection can require one or more rights to edit the namespace, which
+               // may be satisfied by membership in multiple groups each giving a subset of those rights.
+               // A restriction level is redundant if, for any one of the namespace rights, all groups
+               // giving that right also give the restriction level's right. Or, conversely, a
+               // restriction level is not redundant if, for every namespace right, there's at least one
+               // group giving that right without the restriction level's right.
+               //
+               // First, for each right, get a list of groups with that right.
+               $namespaceRightGroups = [];
+               foreach ( (array)$this->options->get( 'NamespaceProtection' )[$index] as $right ) {
+                       if ( $right == 'sysop' ) {
+                               $right = 'editprotected'; // BC
+                       }
+                       if ( $right == 'autoconfirmed' ) {
+                               $right = 'editsemiprotected'; // BC
+                       }
+                       if ( $right != '' ) {
+                               $namespaceRightGroups[$right] = $this->getGroupsWithPermission( $right );
+                       }
+               }
+
+               // Now, go through the protection levels one by one.
+               $usableLevels = [ '' ];
+               foreach ( $this->options->get( 'RestrictionLevels' ) as $level ) {
+                       $right = $level;
+                       if ( $right == 'sysop' ) {
+                               $right = 'editprotected'; // BC
+                       }
+                       if ( $right == 'autoconfirmed' ) {
+                               $right = 'editsemiprotected'; // BC
+                       }
+
+                       if ( $right != '' &&
+                                !isset( $namespaceRightGroups[$right] ) &&
+                                ( !$user || $this->userHasRight( $user, $right ) )
+                       ) {
+                               // Do any of the namespace rights imply the restriction right? (see explanation above)
+                               foreach ( $namespaceRightGroups as $groups ) {
+                                       if ( !array_diff( $groups, $this->getGroupsWithPermission( $right ) ) ) {
+                                               // Yes, this one does.
+                                               continue 2;
+                                       }
+                               }
+                               // No, keep the restriction level
+                               $usableLevels[] = $level;
+                       }
+               }
+
+               return $usableLevels;
+       }
+
        /**
         * Add temporary user rights, only valid for the current scope.
         * This is meant for making it possible to programatically trigger certain actions that
index 4bead34..8b5d995 100644 (file)
@@ -90,7 +90,7 @@ class ProtectionForm {
         * Loads the current state of protection into the object.
         */
        function loadData() {
-               $levels = MediaWikiServices::getInstance()->getNamespaceInfo()->getRestrictionLevels(
+               $levels = MediaWikiServices::getInstance()->getPermissionManager()->getNamespaceRestrictionLevels(
                        $this->mTitle->getNamespace(), $this->mContext->getUser()
                );
                $this->mCascade = $this->mTitle->areRestrictionsCascading();
@@ -180,7 +180,7 @@ class ProtectionForm {
         */
        function execute() {
                if (
-                       MediaWikiServices::getInstance()->getNamespaceInfo()->getRestrictionLevels(
+                       MediaWikiServices::getInstance()->getPermissionManager()->getNamespaceRestrictionLevels(
                                $this->mTitle->getNamespace()
                        ) === [ '' ]
                ) {
@@ -553,7 +553,8 @@ class ProtectionForm {
                }
                $out .= Xml::closeElement( 'fieldset' );
 
-               if ( $user->isAllowed( 'editinterface' ) ) {
+               if ( MediaWikiServices::getInstance()->getPermissionManager()
+                               ->userHasRight( $user, 'editinterface' ) ) {
                        $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
                        $link = $linkRenderer->makeKnownLink(
                                $context->msg( 'protect-dropdown' )->inContentLanguage()->getTitle(),
@@ -585,10 +586,12 @@ class ProtectionForm {
        function buildSelector( $action, $selected ) {
                // If the form is disabled, display all relevant levels. Otherwise,
                // just show the ones this user can use.
-               $levels = MediaWikiServices::getInstance()->getNamespaceInfo()->getRestrictionLevels(
-                       $this->mTitle->getNamespace(),
-                       $this->disabled ? null : $this->mContext->getUser()
-               );
+               $levels = MediaWikiServices::getInstance()
+                               ->getPermissionManager()
+                               ->getNamespaceRestrictionLevels(
+                                       $this->mTitle->getNamespace(),
+                                       $this->disabled ? null : $this->mContext->getUser()
+                               );
 
                $id = 'mwProtect-level-' . $action;
 
index a14c1a1..a4959d1 100644 (file)
@@ -3,6 +3,7 @@
 namespace MediaWiki\Rest;
 
 use ExtensionRegistry;
+use MediaWiki;
 use MediaWiki\MediaWikiServices;
 use MediaWiki\Rest\BasicAccess\MWBasicAuthorizer;
 use RequestContext;
@@ -16,6 +17,8 @@ class EntryPoint {
        private $webResponse;
        /** @var Router */
        private $router;
+       /** @var RequestContext */
+       private $context;
 
        public static function main() {
                // URL safety checks
@@ -24,10 +27,12 @@ class EntryPoint {
                        return;
                }
 
+               $context = RequestContext::getMain();
+
                // Set $wgTitle and the title in RequestContext, as in api.php
                global $wgTitle;
                $wgTitle = Title::makeTitle( NS_SPECIAL, 'Badtitle/rest.php' );
-               RequestContext::getMain()->setTitle( $wgTitle );
+               $context->setTitle( $wgTitle );
 
                $services = MediaWikiServices::getInstance();
                $conf = $services->getMainConfig();
@@ -42,7 +47,7 @@ class EntryPoint {
                        'cookiePrefix' => $conf->get( 'CookiePrefix' )
                ] );
 
-               $authorizer = new MWBasicAuthorizer( RequestContext::getMain()->getUser(),
+               $authorizer = new MWBasicAuthorizer( $context->getUser(),
                        $services->getPermissionManager() );
 
                global $IP;
@@ -56,21 +61,24 @@ class EntryPoint {
                );
 
                $entryPoint = new self(
+                       $context,
                        $request,
                        $wgRequest->response(),
                        $router );
                $entryPoint->execute();
        }
 
-       public function __construct( RequestInterface $request, WebResponse $webResponse,
-               Router $router
+       public function __construct( RequestContext $context, RequestInterface $request,
+               WebResponse $webResponse, Router $router
        ) {
+               $this->context = $context;
                $this->request = $request;
                $this->webResponse = $webResponse;
                $this->router = $router;
        }
 
        public function execute() {
+               ob_start();
                $response = $this->router->execute( $this->request );
 
                $this->webResponse->header(
@@ -90,8 +98,14 @@ class EntryPoint {
                                $cookie['options'] );
                }
 
+               // Clear all errors that might have been displayed if display_errors=On
+               ob_end_clean();
+
                $stream = $response->getBody();
                $stream->rewind();
+
+               MediaWiki::preOutputCommit( $this->context );
+
                if ( $stream instanceof CopyableStreamInterface ) {
                        $stream->copyToStream( fopen( 'php://output', 'w' ) );
                } else {
@@ -103,5 +117,8 @@ class EntryPoint {
                                echo $buffer;
                        }
                }
+
+               $mw = new MediaWiki;
+               $mw->doPostOutputShutdown( 'fast' );
        }
 }
index ca4bb73..3c3b6a9 100644 (file)
@@ -164,13 +164,9 @@ class RevisionRenderer {
        }
 
        private function getSpeculativeRevId( $dbIndex ) {
-               // Use a fresh master connection in order to see the latest data, by avoiding
+               // Use a separate master connection in order to see the latest data, by avoiding
                // stale data from REPEATABLE-READ snapshots.
-               // HACK: But don't use a fresh connection in unit tests, since it would not have
-               // the fake tables. This should be handled by the LoadBalancer!
-               $flags = defined( 'MW_PHPUNIT_TEST' ) || $dbIndex === DB_REPLICA
-                       ? 0
-                       : ILoadBalancer::CONN_TRX_AUTOCOMMIT;
+               $flags = ILoadBalancer::CONN_TRX_AUTOCOMMIT;
 
                $db = $this->loadBalancer->getConnectionRef( $dbIndex, [], $this->dbDomain, $flags );
 
@@ -183,13 +179,9 @@ class RevisionRenderer {
        }
 
        private function getSpeculativePageId( $dbIndex ) {
-               // Use a fresh master connection in order to see the latest data, by avoiding
+               // Use a separate master connection in order to see the latest data, by avoiding
                // stale data from REPEATABLE-READ snapshots.
-               // HACK: But don't use a fresh connection in unit tests, since it would not have
-               // the fake tables. This should be handled by the LoadBalancer!
-               $flags = defined( 'MW_PHPUNIT_TEST' ) || $dbIndex === DB_REPLICA
-                       ? 0
-                       : ILoadBalancer::CONN_TRX_AUTOCOMMIT;
+               $flags = ILoadBalancer::CONN_TRX_AUTOCOMMIT;
 
                $db = $this->loadBalancer->getConnectionRef( $dbIndex, [], $this->dbDomain, $flags );
 
index 0b4ce4a..b307264 100644 (file)
 
 use Liuggio\StatsdClient\Factory\StatsdDataFactoryInterface;
 use MediaWiki\Auth\AuthManager;
+use MediaWiki\BadFileLookup;
 use MediaWiki\Block\BlockManager;
 use MediaWiki\Block\BlockRestrictionStore;
 use MediaWiki\Config\ConfigRepository;
 use MediaWiki\Config\ServiceOptions;
+use MediaWiki\FileBackend\FSFile\TempFSFileFactory;
 use MediaWiki\Http\HttpRequestFactory;
 use MediaWiki\Interwiki\ClassicInterwikiLookup;
 use MediaWiki\Interwiki\InterwikiLookup;
@@ -68,6 +70,7 @@ use MediaWiki\Storage\BlobStoreFactory;
 use MediaWiki\Storage\NameTableStoreFactory;
 use MediaWiki\Storage\SqlBlobStore;
 use MediaWiki\Storage\PageEditStash;
+use Wikimedia\ObjectFactory;
 
 return [
        'ActorMigration' => function ( MediaWikiServices $services ) : ActorMigration {
@@ -76,6 +79,17 @@ return [
                );
        },
 
+       'BadFileLookup' => function ( MediaWikiServices $services ) : BadFileLookup {
+               return new BadFileLookup(
+                       function () {
+                               return wfMessage( 'bad_image_list' )->inContentLanguage()->plain();
+                       },
+                       $services->getLocalServerObjectCache(),
+                       $services->getRepoGroup(),
+                       $services->getTitleParser()
+               );
+       },
+
        'BlobStore' => function ( MediaWikiServices $services ) : BlobStore {
                return $services->getService( '_SqlBlobStore' );
        },
@@ -98,7 +112,8 @@ return [
                                BlockManager::$constructorOptions, $services->getMainConfig()
                        ),
                        $context->getUser(),
-                       $context->getRequest()
+                       $context->getRequest(),
+                       $services->getPermissionManager()
                );
        },
 
@@ -417,6 +432,10 @@ return [
                );
        },
 
+       'ObjectFactory' => function ( MediaWikiServices $services ) : ObjectFactory {
+               return new ObjectFactory( $services );
+       },
+
        'OldRevisionImporter' => function ( MediaWikiServices $services ) : OldRevisionImporter {
                return new ImportableOldRevisionImporter(
                        true,
@@ -496,17 +515,12 @@ return [
        },
 
        'PermissionManager' => function ( MediaWikiServices $services ) : PermissionManager {
-               $config = $services->getMainConfig();
                return new PermissionManager(
+                       new ServiceOptions(
+                               PermissionManager::$constructorOptions, $services->getMainConfig()
+                       ),
                        $services->getSpecialPageFactory(),
                        $services->getRevisionLookup(),
-                       $config->get( 'WhitelistRead' ),
-                       $config->get( 'WhitelistReadRegexp' ),
-                       $config->get( 'EmailConfirmToEdit' ),
-                       $config->get( 'BlockDisablesLogin' ),
-                       $config->get( 'GroupPermissions' ),
-                       $config->get( 'RevokePermissions' ),
-                       $config->get( 'AvailableRights' ),
                        $services->getNamespaceInfo()
                );
        },
@@ -715,6 +729,10 @@ return [
                );
        },
 
+       'TempFSFileFactory' => function ( MediaWikiServices $services ) : TempFSFileFactory {
+               return new TempFSFileFactory( $services->getMainConfig()->get( 'TmpDirectory' ) );
+       },
+
        'TitleFormatter' => function ( MediaWikiServices $services ) : TitleFormatter {
                return $services->getService( '_MediaWikiTitleCodec' );
        },
index 201e1a9..2267800 100644 (file)
@@ -96,7 +96,7 @@ if ( !interface_exists( 'Psr\Log\LoggerInterface' ) ) {
 // Install a header callback
 MediaWiki\HeaderCallback::register();
 
-// Set the encoding used by reading HTTP input, writing HTTP output.
+// Set the encoding used by PHP for reading HTTP input, and writing output.
 // This is also the default for mbstring functions.
 mb_internal_encoding( 'UTF-8' );
 
@@ -128,9 +128,6 @@ if ( defined( 'MW_SETUP_CALLBACK' ) ) {
  * Main setup
  */
 
-$fname = 'Setup.php';
-$ps_setup = Profiler::instance()->scopedProfileIn( $fname );
-
 // Load queued extensions
 ExtensionRegistry::getInstance()->loadFromQueue();
 // Don't let any other extensions load
@@ -141,8 +138,6 @@ putenv( "LC_ALL=$wgShellLocale" );
 setlocale( LC_ALL, $wgShellLocale );
 
 // Set various default paths sensibly...
-$ps_default = Profiler::instance()->scopedProfileIn( $fname . '-defaults' );
-
 if ( $wgScript === false ) {
        $wgScript = "$wgScriptPath/index.php";
 }
@@ -368,19 +363,6 @@ foreach ( $wgForeignFileRepos as &$repo ) {
 unset( $repo ); // no global pollution; destroy reference
 
 $rcMaxAgeDays = $wgRCMaxAge / ( 3600 * 24 );
-if ( $wgRCFilterByAge ) {
-       // Trim down $wgRCLinkDays so that it only lists links which are valid
-       // as determined by $wgRCMaxAge.
-       // Note that we allow 1 link higher than the max for things like 56 days but a 60 day link.
-       sort( $wgRCLinkDays );
-
-       foreach ( $wgRCLinkDays as $i => $days ) {
-               if ( $days >= $rcMaxAgeDays ) {
-                       array_splice( $wgRCLinkDays, $i + 1 );
-                       break;
-               }
-       }
-}
 // Ensure that default user options are not invalid, since that breaks Special:Preferences
 $wgDefaultUserOptions['rcdays'] = min(
        $wgDefaultUserOptions['rcdays'],
@@ -638,8 +620,6 @@ if ( defined( 'MW_NO_SESSION' ) ) {
        $wgPHPSessionHandling = MW_NO_SESSION === 'warn' ? 'warn' : 'disable';
 }
 
-Profiler::instance()->scopedProfileOut( $ps_default );
-
 // Disable MWDebug for command line mode, this prevents MWDebug from eating up
 // all the memory from logging SQL queries on maintenance scripts
 global $wgCommandLineMode;
@@ -669,8 +649,6 @@ foreach ( [ 'wgArticlePath', 'wgVariantArticlePath' ] as $varName ) {
        }
 }
 
-$ps_default2 = Profiler::instance()->scopedProfileIn( $fname . '-defaults2' );
-
 if ( $wgCanonicalServer === false ) {
        $wgCanonicalServer = wfExpandUrl( $wgServer, PROTO_HTTP );
 }
@@ -740,10 +718,6 @@ if ( $wgSharedDB && $wgSharedTables ) {
        );
 }
 
-Profiler::instance()->scopedProfileOut( $ps_default2 );
-
-$ps_misc = Profiler::instance()->scopedProfileIn( $fname . '-misc' );
-
 // Raise the memory limit if it's too low
 // Note, this makes use of wfDebug, and thus should not be before
 // MWDebug::init() is called.
@@ -823,13 +797,9 @@ wfDebugLog( 'caches',
        ', session: ' . get_class( ObjectCache::getInstance( $wgSessionCacheType ) )
 );
 
-Profiler::instance()->scopedProfileOut( $ps_misc );
-
 // Most of the config is out, some might want to run hooks here.
 Hooks::run( 'SetupAfterCache' );
 
-$ps_globals = Profiler::instance()->scopedProfileIn( $fname . '-globals' );
-
 /**
  * @var Language $wgContLang
  * @deprecated since 1.32, use the ContentLanguage service directly
@@ -939,9 +909,6 @@ $wgParser = new StubObject( 'wgParser', function () {
  */
 $wgTitle = null;
 
-Profiler::instance()->scopedProfileOut( $ps_globals );
-$ps_extensions = Profiler::instance()->scopedProfileIn( $fname . '-extensions' );
-
 // Extension setup functions
 // Entries should be added to this variable during the inclusion
 // of the extension file. This allows the extension to perform
@@ -974,6 +941,3 @@ if ( !$wgCommandLineMode ) {
 }
 
 $wgFullyInitialised = true;
-
-Profiler::instance()->scopedProfileOut( $ps_extensions );
-Profiler::instance()->scopedProfileOut( $ps_setup );
index 5ef0304..88f301a 100644 (file)
@@ -111,7 +111,7 @@ class NameTableStore {
         * @return IDatabase
         */
        private function getDBConnection( $index, $flags = 0 ) {
-               return $this->loadBalancer->getConnection( $index, [], $this->domain, $flags );
+               return $this->loadBalancer->getConnectionRef( $index, [], $this->domain, $flags );
        }
 
        /**
@@ -160,10 +160,7 @@ class NameTableStore {
                        if ( $id === null ) {
                                // RACE: $name was already in the db, probably just inserted, so load from master.
                                // Use DBO_TRX to avoid missing inserts due to other threads or REPEATABLE-READs.
-                               // ...but not during unit tests, because we need the fake DB tables of the default
-                               // connection.
-                               $connFlags = defined( 'MW_PHPUNIT_TEST' ) ? 0 : ILoadBalancer::CONN_TRX_AUTOCOMMIT;
-                               $table = $this->reloadMap( $connFlags );
+                               $table = $this->reloadMap( ILoadBalancer::CONN_TRX_AUTOCOMMIT );
 
                                $searchResult = array_search( $name, $table, true );
                                if ( $searchResult === false ) {
index f681852..eb19076 100644 (file)
@@ -2506,8 +2506,9 @@ class Title implements LinkTarget, IDBAccessObject {
                global $wgNamespaceProtection;
 
                if ( isset( $wgNamespaceProtection[$this->mNamespace] ) ) {
+                       $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
                        foreach ( (array)$wgNamespaceProtection[$this->mNamespace] as $right ) {
-                               if ( $right != '' && !$user->isAllowed( $right ) ) {
+                               if ( !$permissionManager->userHasRight( $user, $right ) ) {
                                        return true;
                                }
                        }
@@ -3183,7 +3184,7 @@ class Title implements LinkTarget, IDBAccessObject {
        public static function capitalize( $text, $ns = NS_MAIN ) {
                $services = MediaWikiServices::getInstance();
                if ( $services->getNamespaceInfo()->isCapitalized( $ns ) ) {
-                       return MediaWikiServices::getInstance()->getContentLanguage()->ucfirst( $text );
+                       return $services->getContentLanguage()->ucfirst( $text );
                } else {
                        return $text;
                }
index 6593e49..defe07e 100644 (file)
@@ -395,18 +395,25 @@ class WebRequest {
                # https://www.php.net/variables.external#language.variables.external.dot-in-names
                # Work around PHP *feature* to avoid *bugs* elsewhere.
                $name = strtr( $name, '.', '_' );
-               if ( isset( $arr[$name] ) ) {
-                       $data = $arr[$name];
+
+               if ( !isset( $arr[$name] ) ) {
+                       return $default;
+               }
+
+               $data = $arr[$name];
+               # Optimisation: Skip UTF-8 normalization and legacy transcoding for simple ASCII strings.
+               $isAsciiStr = ( is_string( $data ) && preg_match( '/[^\x20-\x7E]/', $data ) === 0 );
+               if ( !$isAsciiStr ) {
                        if ( isset( $_GET[$name] ) && is_string( $data ) ) {
                                # Check for alternate/legacy character encoding.
-                               $contLang = MediaWikiServices::getInstance()->getContentLanguage();
-                               $data = $contLang->checkTitleEncoding( $data );
+                               $data = MediaWikiServices::getInstance()
+                                       ->getContentLanguage()
+                                       ->checkTitleEncoding( $data );
                        }
                        $data = $this->normalizeUnicode( $data );
-                       return $data;
-               } else {
-                       return $default;
                }
+
+               return $data;
        }
 
        /**
index f2641f4..0363877 100644 (file)
@@ -260,7 +260,6 @@ class WikiMap {
         *
         * @see $wgDBmwschema
         * @see PostgresInstaller
-        * @see MssqlInstaller
         *
         * @param string|DatabaseDomain $domain
         * @return string
index 958ec06..385ccc9 100644 (file)
@@ -265,7 +265,8 @@ class HistoryAction extends FormlessAction {
                                'value' => $tagFilter,
                        ]
                ];
-               if ( $this->getUser()->isAllowed( 'deletedhistory' ) ) {
+               $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
+               if ( $permissionManager->userHasRight( $this->getUser(), 'deletedhistory' ) ) {
                        $fields[] = [
                                'type' => 'check',
                                'label' => $this->msg( 'history-show-deleted' )->text(),
index 279c13b..7bcfc88 100644 (file)
@@ -280,7 +280,7 @@ class InfoAction extends FormlessAction {
                // Language in which the page content is (supposed to be) written
                $pageLang = $title->getPageLanguage()->getCode();
 
-               $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
+               $permissionManager = $services->getPermissionManager();
 
                $pageLangHtml = $pageLang . ' - ' .
                        Language::fetchLanguageName( $pageLang, $lang->getCode() );
@@ -345,7 +345,7 @@ class InfoAction extends FormlessAction {
 
                $unwatchedPageThreshold = $config->get( 'UnwatchedPageThreshold' );
                if (
-                       $user->isAllowed( 'unwatchedpages' ) ||
+                       $services->getPermissionManager()->userHasRight( $user, 'unwatchedpages' ) ||
                        ( $unwatchedPageThreshold !== false &&
                                $pageCounts['watchers'] >= $unwatchedPageThreshold )
                ) {
@@ -360,7 +360,7 @@ class InfoAction extends FormlessAction {
                        ) {
                                $minToDisclose = $config->get( 'UnwatchedPageSecret' );
                                if ( $pageCounts['visitingWatchers'] > $minToDisclose ||
-                                       $user->isAllowed( 'unwatchedpages' ) ) {
+                                       $services->getPermissionManager()->userHasRight( $user, 'unwatchedpages' ) ) {
                                        $pageInfo['header-basic'][] = [
                                                $this->msg( 'pageinfo-visiting-watchers' ),
                                                $lang->formatNum( $pageCounts['visitingWatchers'] )
index abb8ff5..8fd4e0a 100644 (file)
@@ -111,7 +111,8 @@ class RawAction extends FormlessAction {
                        $rootPage = strtok( $title->getText(), '/' );
                        $userFromTitle = User::newFromName( $rootPage, 'usable' );
                        if ( !$userFromTitle || $userFromTitle->getId() === 0 ) {
-                               $elevated = $this->getUser()->isAllowed( 'editinterface' );
+                               $elevated = MediaWikiServices::getInstance()->getPermissionManager()
+                                       ->userHasRight( $this->getUser(), 'editinterface' );
                                $elevatedText = $elevated ? 'by elevated ' : '';
                                $log = LoggerFactory::getInstance( "security" );
                                $log->warning(
index 0eba613..e88654a 100644 (file)
@@ -20,6 +20,8 @@
  * @ingroup Actions
  */
 
+use MediaWiki\MediaWikiServices;
+
 /**
  * Page addition to a user's watchlist
  *
@@ -116,7 +118,8 @@ class WatchAction extends FormAction {
                User $user,
                $checkRights = User::CHECK_USER_RIGHTS
        ) {
-               if ( $checkRights && !$user->isAllowed( 'editmywatchlist' ) ) {
+               $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
+               if ( $checkRights && !$permissionManager->userHasRight( $user, 'editmywatchlist' ) ) {
                        return User::newFatalPermissionDeniedStatus( 'editmywatchlist' );
                }
 
@@ -140,7 +143,9 @@ class WatchAction extends FormAction {
         * @return Status
         */
        public static function doUnwatch( Title $title, User $user ) {
-               if ( !$user->isAllowed( 'editmywatchlist' ) ) {
+               if ( !MediaWikiServices::getInstance()
+                       ->getPermissionManager()
+                       ->userHasRight( $user, 'editmywatchlist' ) ) {
                        return User::newFatalPermissionDeniedStatus( 'editmywatchlist' );
                }
 
index c5c090d..fd2fbd0 100644 (file)
@@ -172,6 +172,7 @@ class HistoryPager extends ReverseChronologicalPager {
         * @return string HTML output
         */
        protected function getStartBody() {
+               $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
                $this->lastRow = false;
                $this->counter = 1;
                $this->oldIdChecked = 0;
@@ -197,7 +198,7 @@ class HistoryPager extends ReverseChronologicalPager {
 
                        $user = $this->getUser();
                        $actionButtons = '';
-                       if ( $user->isAllowed( 'deleterevision' ) ) {
+                       if ( $permissionManager->userHasRight( $user, 'deleterevision' ) ) {
                                $actionButtons .= $this->getRevisionButton(
                                        'revisiondelete', 'showhideselectedversions' );
                        }
@@ -210,7 +211,7 @@ class HistoryPager extends ReverseChronologicalPager {
                                        'mw-history-revisionactions' ], $actionButtons );
                        }
 
-                       if ( $user->isAllowed( 'deleterevision' ) || $this->showTagEditUI ) {
+                       if ( $permissionManager->userHasRight( $user, 'deleterevision' ) || $this->showTagEditUI ) {
                                $this->buttons .= ( new ListToggle( $this->getOutput() ) )->getHTML();
                        }
 
@@ -305,6 +306,7 @@ class HistoryPager extends ReverseChronologicalPager {
         */
        function historyLine( $row, $next, $notificationtimestamp = false,
                $dummy = false, $firstInList = false ) {
+               $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
                $rev = new Revision( $row, 0, $this->getTitle() );
 
                if ( is_object( $next ) ) {
@@ -332,7 +334,7 @@ class HistoryPager extends ReverseChronologicalPager {
 
                $del = '';
                $user = $this->getUser();
-               $canRevDelete = $user->isAllowed( 'deleterevision' );
+               $canRevDelete = $permissionManager->userHasRight( $user, 'deleterevision' );
                // Show checkboxes for each revision, to allow for revision deletion and
                // change tags
                if ( $canRevDelete || $this->showTagEditUI ) {
@@ -349,7 +351,8 @@ class HistoryPager extends ReverseChronologicalPager {
                                        [ 'name' => 'ids[' . $rev->getId() . ']' ] );
                        }
                // User can only view deleted revisions...
-               } elseif ( $rev->getVisibility() && $user->isAllowed( 'deletedhistory' ) ) {
+               } elseif ( $rev->getVisibility() &&
+                                  $permissionManager->userHasRight( $user, 'deletedhistory' ) ) {
                        // If revision was hidden from sysops, disable the link
                        if ( !$rev->userCan( RevisionRecord::DELETED_RESTRICTED, $user ) ) {
                                $del = Linker::revDeleteLinkDisabled( false );
@@ -419,7 +422,7 @@ class HistoryPager extends ReverseChronologicalPager {
                                $undoTooltip = $latest
                                        ? [ 'title' => $this->msg( 'tooltip-undo' )->text() ]
                                        : [];
-                               $undolink = MediaWikiServices::getInstance()->getLinkRenderer()->makeKnownLink(
+                               $undolink = $this->getLinkRenderer()->makeKnownLink(
                                        $this->getTitle(),
                                        $this->msg( 'editundo' )->text(),
                                        $undoTooltip,
@@ -499,7 +502,7 @@ class HistoryPager extends ReverseChronologicalPager {
                ) {
                        return $cur;
                } else {
-                       return MediaWikiServices::getInstance()->getLinkRenderer()->makeKnownLink(
+                       return $this->getLinkRenderer()->makeKnownLink(
                                $this->getTitle(),
                                new HtmlArmor( $cur ),
                                [],
@@ -528,7 +531,7 @@ class HistoryPager extends ReverseChronologicalPager {
                        return $last;
                }
 
-               $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
+               $linkRenderer = $this->getLinkRenderer();
                if ( $next === 'unknown' ) {
                        # Next row probably exists but is unknown, use an oldid=prev link
                        return $linkRenderer->makeKnownLink(
index a7b872c..8b6a3e5 100644 (file)
@@ -2126,7 +2126,9 @@ abstract class ApiBase extends ContextSource {
                        $user = $this->getUser();
                }
                $rights = (array)$rights;
-               if ( !$user->isAllowedAny( ...$rights ) ) {
+               if ( !$this->getPermissionManager()
+                               ->userHasAnyRight( $user, ...$rights )
+               ) {
                        $this->dieWithError( [ 'apierror-permissiondenied', $this->msg( "action-{$rights[0]}" ) ] );
                }
        }
index 4801267..2c1564e 100644 (file)
@@ -98,7 +98,8 @@ class ApiBlock extends ApiBase {
                        }
                }
 
-               if ( $params['hidename'] && !$user->isAllowed( 'hideuser' ) ) {
+               if ( $params['hidename'] &&
+                        !$this->getPermissionManager()->userHasRight( $user, 'hideuser' ) ) {
                        $this->dieWithError( 'apierror-canthide' );
                }
                if ( $params['noemail'] && !SpecialBlock::canBlockEmail( $user ) ) {
index e096915..05eb438 100644 (file)
@@ -231,7 +231,9 @@ class ApiComparePages extends ApiBase {
         */
        private function getRevisionById( $id ) {
                $rev = $this->revisionStore->getRevisionById( $id );
-               if ( !$rev && $this->getUser()->isAllowedAny( 'deletedtext', 'undelete' ) ) {
+               if ( !$rev && $this->getPermissionManager()
+                               ->userHasAnyRight( $this->getUser(), 'deletedtext', 'undelete' )
+               ) {
                        // Try the 'archive' table
                        $arQuery = $this->revisionStore->getArchiveQueryInfo();
                        $row = $this->getDB()->selectRow(
index 988957b..2627715 100644 (file)
@@ -66,6 +66,23 @@ class ApiHelp extends ApiBase {
                        ApiResult::setSubelementsList( $data, 'help' );
                        $result->addValue( null, $this->getModuleName(), $data );
                } else {
+                       // Show any errors at the top of the HTML
+                       $transform = [
+                               'Types' => [ 'AssocAsObject' => true ],
+                               'Strip' => 'all',
+                       ];
+                       $errors = array_filter( [
+                               'errors' => $this->getResult()->getResultData( [ 'errors' ], $transform ),
+                               'warnings' => $this->getResult()->getResultData( [ 'warnings' ], $transform ),
+                       ] );
+                       if ( $errors ) {
+                               $json = FormatJson::encode( $errors, true, FormatJson::UTF8_OK );
+                               // Escape any "--", some parsers might interpret that as end-of-comment.
+                               // The above already escaped any "<" and ">".
+                               $json = str_replace( '--', '-\u002D', $json );
+                               $html = "<!-- API warnings and errors:\n$json\n-->\n$html";
+                       }
+
                        $result->reset();
                        $result->addValue( null, 'text', $html, ApiResult::NO_SIZE_CHECK );
                        $result->addValue( null, 'mime', 'text/html', ApiResult::NO_SIZE_CHECK );
index 668bd0e..ccb26a8 100644 (file)
@@ -101,7 +101,8 @@ class ApiImageRotate extends ApiBase {
                                continue;
                        }
                        $ext = strtolower( pathinfo( "$srcPath", PATHINFO_EXTENSION ) );
-                       $tmpFile = TempFSFile::factory( 'rotate_', $ext, wfTempDir() );
+                       $tmpFile = MediaWikiServices::getInstance()->getTempFSFileFactory()
+                               ->newTempFSFile( 'rotate_', $ext );
                        $dstPath = $tmpFile->getPath();
                        $err = $handler->rotate( $file, [
                                'srcPath' => $srcPath,
index b36045e..e787e26 100644 (file)
@@ -29,7 +29,6 @@ class ApiImport extends ApiBase {
 
        public function execute() {
                $this->useTransactionalTimeLimit();
-
                $user = $this->getUser();
                $params = $this->extractRequestParams();
 
@@ -37,7 +36,7 @@ class ApiImport extends ApiBase {
 
                $isUpload = false;
                if ( isset( $params['interwikisource'] ) ) {
-                       if ( !$user->isAllowed( 'import' ) ) {
+                       if ( !$this->getPermissionManager()->userHasRight( $user, 'import' ) ) {
                                $this->dieWithError( 'apierror-cantimport' );
                        }
                        if ( !isset( $params['interwikipage'] ) ) {
@@ -52,7 +51,7 @@ class ApiImport extends ApiBase {
                        $usernamePrefix = $params['interwikisource'];
                } else {
                        $isUpload = true;
-                       if ( !$user->isAllowed( 'importupload' ) ) {
+                       if ( !$this->getPermissionManager()->userHasRight( $user, 'importupload' ) ) {
                                $this->dieWithError( 'apierror-cantimport-upload' );
                        }
                        $source = ImportStreamSource::newFromUpload( 'xml' );
index 554ab6a..641aa9f 100644 (file)
@@ -1410,8 +1410,8 @@ class ApiMain extends ApiBase {
         */
        protected function checkExecutePermissions( $module ) {
                $user = $this->getUser();
-               if ( $module->isReadMode() && !User::isEveryoneAllowed( 'read' ) &&
-                       !$user->isAllowed( 'read' )
+               if ( $module->isReadMode() && !$this->getPermissionManager()->isEveryoneAllowed( 'read' ) &&
+                       !$this->getPermissionManager()->userHasRight( $user, 'read' )
                ) {
                        $this->dieWithError( 'apierror-readapidenied' );
                }
@@ -1419,7 +1419,7 @@ class ApiMain extends ApiBase {
                if ( $module->isWriteMode() ) {
                        if ( !$this->mEnableWrite ) {
                                $this->dieWithError( 'apierror-noapiwrite' );
-                       } elseif ( !$user->isAllowed( 'writeapi' ) ) {
+                       } elseif ( !$this->getPermissionManager()->userHasRight( $user, 'writeapi' ) ) {
                                $this->dieWithError( 'apierror-writeapidenied' );
                        } elseif ( $this->getRequest()->getHeader( 'Promise-Non-Write-API-Action' ) ) {
                                $this->dieWithError( 'apierror-promised-nonwrite-api' );
@@ -1504,7 +1504,7 @@ class ApiMain extends ApiBase {
                                        }
                                        break;
                                case 'bot':
-                                       if ( !$user->isAllowed( 'bot' ) ) {
+                                       if ( !$this->getPermissionManager()->userHasRight( $user, 'bot' ) ) {
                                                $this->dieWithError( 'apierror-assertbotfailed' );
                                        }
                                        break;
@@ -1539,6 +1539,12 @@ class ApiMain extends ApiBase {
                        $this->dieWithErrorOrDebug( [ 'apierror-mustbeposted', $this->mAction ] );
                }
 
+               if ( $request->wasPosted() && !$request->getHeader( 'Content-Type' ) ) {
+                       $this->addDeprecation(
+                               'apiwarn-deprecation-post-without-content-type', 'post-without-content-type'
+                       );
+               }
+
                // See if custom printer is used
                $this->mPrinter = $module->getCustomPrinter();
                if ( is_null( $this->mPrinter ) ) {
@@ -1939,7 +1945,7 @@ class ApiMain extends ApiBase {
 
                        $groups = array_map( function ( $group ) {
                                return $group == '*' ? 'all' : $group;
-                       }, User::getGroupsWithPermission( $right ) );
+                       }, $this->getPermissionManager()->getGroupsWithPermission( $right ) );
 
                        $help['permissions'] .= Html::rawElement( 'dd', null,
                                $this->msg( 'api-help-permissions-granted-to' )
@@ -2052,7 +2058,8 @@ class ApiMain extends ApiBase {
         */
        public function canApiHighLimits() {
                if ( !isset( $this->mCanApiHighLimits ) ) {
-                       $this->mCanApiHighLimits = $this->getUser()->isAllowed( 'apihighlimits' );
+                       $this->mCanApiHighLimits = $this->getPermissionManager()
+                               ->userHasRight( $this->getUser(), 'apihighlimits' );
                }
 
                return $this->mCanApiHighLimits;
index 42de161..6cd717a 100644 (file)
@@ -31,10 +31,10 @@ class ApiManageTags extends ApiBase {
 
                // make sure the user is allowed
                if ( $params['operation'] !== 'delete'
-                       && !$this->getUser()->isAllowed( 'managechangetags' )
+                       && !$this->getPermissionManager()->userHasRight( $user, 'managechangetags' )
                ) {
                        $this->dieWithError( 'tags-manage-no-permission', 'permissiondenied' );
-               } elseif ( !$this->getUser()->isAllowed( 'deletechangetags' ) ) {
+               } elseif ( !$this->getPermissionManager()->userHasRight( $user, 'deletechangetags' ) ) {
                        $this->dieWithError( 'tags-delete-no-permission', 'permissiondenied' );
                }
 
index 0a788d4..d8f48b8 100644 (file)
@@ -63,9 +63,10 @@ class ApiMove extends ApiBase {
                        && !RepoGroup::singleton()->getLocalRepo()->findFile( $toTitle )
                        && MediaWikiServices::getInstance()->getRepoGroup()->findFile( $toTitle )
                ) {
-                       if ( !$params['ignorewarnings'] && $user->isAllowed( 'reupload-shared' ) ) {
+                       if ( !$params['ignorewarnings'] &&
+                                $this->getPermissionManager()->userHasRight( $user, 'reupload-shared' ) ) {
                                $this->dieWithError( 'apierror-fileexists-sharedrepo-perm' );
-                       } elseif ( !$user->isAllowed( 'reupload-shared' ) ) {
+                       } elseif ( !$this->getPermissionManager()->userHasRight( $user, 'reupload-shared' ) ) {
                                $this->dieWithError( 'apierror-cantoverwrite-sharedfile' );
                        }
                }
@@ -185,7 +186,7 @@ class ApiMove extends ApiBase {
                }
 
                // Check suppressredirect permission
-               if ( !$user->isAllowed( 'suppressredirect' ) ) {
+               if ( !$this->getPermissionManager()->userHasRight( $user, 'suppressredirect' ) ) {
                        $createRedirect = true;
                }
 
index 6b24b63..c604322 100644 (file)
@@ -971,7 +971,8 @@ class ApiPageSet extends ApiBase {
                // If the user can see deleted revisions, pull out the corresponding
                // titles from the archive table and include them too. We ignore
                // ar_page_id because deleted revisions are tied by title, not page_id.
-               if ( $goodRemaining && $this->getUser()->isAllowed( 'deletedhistory' ) ) {
+               if ( $goodRemaining &&
+                        $this->getPermissionManager()->userHasRight( $this->getUser(), 'deletedhistory' ) ) {
                        $tables = [ 'archive' ];
                        $fields = [ 'ar_rev_id', 'ar_namespace', 'ar_title' ];
                        $where = [ 'ar_rev_id' => array_keys( $goodRemaining ) ];
@@ -1252,7 +1253,7 @@ class ApiPageSet extends ApiBase {
 
                        // Need gender information
                        if (
-                               MediaWikiServices::getInstance()->getNamespaceInfo()->
+                               $services->getNamespaceInfo()->
                                        hasGenderDistinction( $titleObj->getNamespace() )
                        ) {
                                $usernames[] = $titleObj->getText();
index 85ca648..7d6d342 100644 (file)
@@ -134,7 +134,7 @@ class ApiQueryAllDeletedRevisions extends ApiQueryRevisionsBase {
                        $this->addJoinConds(
                                [ 'change_tag' => [ 'JOIN', [ 'ar_rev_id=ct_rev_id' ] ] ]
                        );
-                       $changeTagDefStore = MediaWikiServices::getInstance()->getChangeTagDefStore();
+                       $changeTagDefStore = $services->getChangeTagDefStore();
                        try {
                                $this->addWhereFld( 'ct_tag_id', $changeTagDefStore->getId( $params['tag'] ) );
                        } catch ( NameTableAccessException $exception ) {
@@ -237,9 +237,11 @@ class ApiQueryAllDeletedRevisions extends ApiQueryRevisionsBase {
                        // Paranoia: avoid brute force searches (T19342)
                        // (shouldn't be able to get here without 'deletedhistory', but
                        // check it again just in case)
-                       if ( !$user->isAllowed( 'deletedhistory' ) ) {
+                       if ( !$this->getPermissionManager()->userHasRight( $user, 'deletedhistory' ) ) {
                                $bitmask = RevisionRecord::DELETED_USER;
-                       } elseif ( !$user->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
+                       } elseif ( !$this->getPermissionManager()
+                               ->userHasAnyRight( $user, 'suppressrevision', 'viewsuppressed' )
+                       ) {
                                $bitmask = RevisionRecord::DELETED_USER | RevisionRecord::DELETED_RESTRICTED;
                        } else {
                                $bitmask = 0;
index 40cd149..b181710 100644 (file)
@@ -205,7 +205,7 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase {
                                $this->addJoinConds( [ 'user_groups' => [
                                        'LEFT JOIN',
                                        [
-                                               'ug_group' => User::getGroupsWithPermission( 'bot' ),
+                                               'ug_group' => $this->getPermissionManager()->getGroupsWithPermission( 'bot' ),
                                                'ug_user = ' . $actorQuery['fields']['img_user'],
                                                'ug_expiry IS NULL OR ug_expiry >= ' . $db->addQuotes( $db->timestamp() )
                                        ]
index 050bc0f..3751102 100644 (file)
@@ -154,9 +154,11 @@ class ApiQueryAllRevisions extends ApiQueryRevisionsBase {
 
                if ( $params['user'] !== null || $params['excludeuser'] !== null ) {
                        // Paranoia: avoid brute force searches (T19342)
-                       if ( !$this->getUser()->isAllowed( 'deletedhistory' ) ) {
+                       if ( !$this->getPermissionManager()->userHasRight( $this->getUser(), 'deletedhistory' ) ) {
                                $bitmask = RevisionRecord::DELETED_USER;
-                       } elseif ( !$this->getUser()->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
+                       } elseif ( !$this->getPermissionManager()
+                               ->userHasAnyRight( $this->getUser(), 'suppressrevision', 'viewsuppressed' )
+                       ) {
                                $bitmask = RevisionRecord::DELETED_USER | RevisionRecord::DELETED_RESTRICTED;
                        } else {
                                $bitmask = 0;
index 59e92e1..023b88f 100644 (file)
@@ -90,7 +90,8 @@ class ApiQueryAllUsers extends ApiQueryBase {
                if ( !is_null( $params['rights'] ) && count( $params['rights'] ) ) {
                        $groups = [];
                        foreach ( $params['rights'] as $r ) {
-                               $groups = array_merge( $groups, User::getGroupsWithPermission( $r ) );
+                               $groups = array_merge( $groups, $this->getPermissionManager()
+                                       ->getGroupsWithPermission( $r ) );
                        }
 
                        // no group with the given right(s) exists, no need for a query
@@ -312,7 +313,7 @@ class ApiQueryAllUsers extends ApiQueryBase {
                                }
 
                                if ( $fld_rights ) {
-                                       $data['rights'] = User::getGroupPermissions( $groups );
+                                       $data['rights'] = $this->getPermissionManager()->getGroupPermissions( $groups );
                                        ApiResult::setIndexedTagName( $data['rights'], 'r' );
                                        ApiResult::setArrayType( $data['rights'], 'array' );
                                }
index 50ca99a..10db848 100644 (file)
@@ -460,7 +460,7 @@ abstract class ApiQueryBase extends ApiBase {
                $this->addJoinConds( $joinConds );
 
                // Don't show hidden names
-               if ( !$this->getUser()->isAllowed( 'hideuser' ) ) {
+               if ( !$this->getPermissionManager()->userHasRight( $this->getUser(), 'hideuser' ) ) {
                        $this->addWhere( 'ipb_deleted = 0 OR ipb_deleted IS NULL' );
                }
        }
@@ -600,7 +600,8 @@ abstract class ApiQueryBase extends ApiBase {
         * @return bool
         */
        public function userCanSeeRevDel() {
-               return $this->getUser()->isAllowedAny(
+               return $this->getPermissionManager()->userHasAnyRight(
+                       $this->getUser(),
                        'deletedhistory',
                        'deletedtext',
                        'suppressrevision',
index 5615f46..c5a8d08 100644 (file)
@@ -176,7 +176,7 @@ class ApiQueryBlocks extends ApiQueryBase {
                        $this->addWhereIf( 'ipb_range_end > ipb_range_start', isset( $show['range'] ) );
                }
 
-               if ( !$this->getUser()->isAllowed( 'hideuser' ) ) {
+               if ( !$this->getPermissionManager()->userHasRight( $this->getUser(), 'hideuser' ) ) {
                        $this->addWhereFld( 'ipb_deleted', 0 );
                }
 
index 9057f10..fd2d199 100644 (file)
@@ -152,7 +152,8 @@ class ApiQueryContributors extends ApiQueryBase {
                } elseif ( $params['rights'] ) {
                        $excludeGroups = false;
                        foreach ( $params['rights'] as $r ) {
-                               $limitGroups = array_merge( $limitGroups, User::getGroupsWithPermission( $r ) );
+                               $limitGroups = array_merge( $limitGroups, $this->getPermissionManager()
+                                       ->getGroupsWithPermission( $r ) );
                        }
 
                        // If no group has the rights requested, no need to query
@@ -168,7 +169,8 @@ class ApiQueryContributors extends ApiQueryBase {
                } elseif ( $params['excluderights'] ) {
                        $excludeGroups = true;
                        foreach ( $params['excluderights'] as $r ) {
-                               $limitGroups = array_merge( $limitGroups, User::getGroupsWithPermission( $r ) );
+                               $limitGroups = array_merge( $limitGroups, $this->getPermissionManager()
+                                       ->getGroupsWithPermission( $r ) );
                        }
                }
 
index bbb987f..fc88499 100644 (file)
@@ -132,9 +132,11 @@ class ApiQueryDeletedRevisions extends ApiQueryRevisionsBase {
                        // Paranoia: avoid brute force searches (T19342)
                        // (shouldn't be able to get here without 'deletedhistory', but
                        // check it again just in case)
-                       if ( !$user->isAllowed( 'deletedhistory' ) ) {
+                       if ( !$this->getPermissionManager()->userHasRight( $user, 'deletedhistory' ) ) {
                                $bitmask = RevisionRecord::DELETED_USER;
-                       } elseif ( !$user->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
+                       } elseif ( !$this->getPermissionManager()
+                               ->userHasAnyRight( $this->getUser(), 'suppressrevision', 'viewsuppressed' )
+                       ) {
                                $bitmask = RevisionRecord::DELETED_USER | RevisionRecord::DELETED_RESTRICTED;
                        } else {
                                $bitmask = 0;
index a6366f2..1af4d95 100644 (file)
@@ -67,7 +67,7 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
                }
 
                // If user can't undelete, no tokens
-               if ( !$user->isAllowed( 'undelete' ) ) {
+               if ( !$this->getPermissionManager()->userHasRight( $user, 'undelete' ) ) {
                        $fld_token = false;
                }
 
@@ -197,9 +197,11 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
                        // Paranoia: avoid brute force searches (T19342)
                        // (shouldn't be able to get here without 'deletedhistory', but
                        // check it again just in case)
-                       if ( !$user->isAllowed( 'deletedhistory' ) ) {
+                       if ( !$this->getPermissionManager()->userHasRight( $user, 'deletedhistory' ) ) {
                                $bitmask = RevisionRecord::DELETED_USER;
-                       } elseif ( !$user->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
+                       } elseif ( !$this->getPermissionManager()
+                               ->userHasAnyRight( $user, 'suppressrevision', 'viewsuppressed' )
+                       ) {
                                $bitmask = RevisionRecord::DELETED_USER | RevisionRecord::DELETED_RESTRICTED;
                        } else {
                                $bitmask = 0;
index 8e464d0..f9087eb 100644 (file)
@@ -114,9 +114,11 @@ class ApiQueryFilearchive extends ApiQueryBase {
                }
 
                // Exclude files this user can't view.
-               if ( !$user->isAllowed( 'deletedtext' ) ) {
+               if ( !$this->getPermissionManager()->userHasRight( $user, 'deletedtext' ) ) {
                        $bitmask = File::DELETED_FILE;
-               } elseif ( !$user->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
+               } elseif ( !$this->getPermissionManager()
+                       ->userHasAnyRight( $user, 'suppressrevision', 'viewsuppressed' )
+               ) {
                        $bitmask = File::DELETED_FILE | File::DELETED_RESTRICTED;
                } else {
                        $bitmask = 0;
index 0791426..b97ab3c 100644 (file)
@@ -144,7 +144,8 @@ class ApiQueryImageInfo extends ApiQueryBase {
                                        $info['imagerepository'] = $img->getRepoName();
                                }
                                if ( isset( $prop['badfile'] ) ) {
-                                       $info['badfile'] = (bool)wfIsBadImage( $title, $badFileContextTitle );
+                                       $info['badfile'] = (bool)MediaWikiServices::getInstance()->getBadFileLookup()
+                                               ->isBadFile( $title, $badFileContextTitle );
                                }
 
                                $fit = $result->addValue( [ 'query', 'pages' ], (int)$pageId, $info );
index 90f1340..ac7e5cc 100644 (file)
@@ -135,7 +135,8 @@ class ApiQueryInfo extends ApiQueryBase {
                // but that's too expensive for this purpose
                // and would break caching
                global $wgUser;
-               if ( !$wgUser->isAllowed( 'edit' ) ) {
+               if ( !MediaWikiServices::getInstance()->getPermissionManager()
+                               ->userHasRight( $wgUser, 'edit' ) ) {
                        return false;
                }
 
@@ -152,7 +153,8 @@ class ApiQueryInfo extends ApiQueryBase {
         */
        public static function getDeleteToken( $pageid, $title ) {
                global $wgUser;
-               if ( !$wgUser->isAllowed( 'delete' ) ) {
+               if ( !MediaWikiServices::getInstance()->getPermissionManager()
+                               ->userHasRight( $wgUser, 'delete' ) ) {
                        return false;
                }
 
@@ -169,7 +171,8 @@ class ApiQueryInfo extends ApiQueryBase {
         */
        public static function getProtectToken( $pageid, $title ) {
                global $wgUser;
-               if ( !$wgUser->isAllowed( 'protect' ) ) {
+               if ( !MediaWikiServices::getInstance()->getPermissionManager()
+                               ->userHasRight( $wgUser, 'protect' ) ) {
                        return false;
                }
 
@@ -186,7 +189,8 @@ class ApiQueryInfo extends ApiQueryBase {
         */
        public static function getMoveToken( $pageid, $title ) {
                global $wgUser;
-               if ( !$wgUser->isAllowed( 'move' ) ) {
+               if ( !MediaWikiServices::getInstance()->getPermissionManager()
+                               ->userHasRight( $wgUser, 'move' ) ) {
                        return false;
                }
 
@@ -203,7 +207,8 @@ class ApiQueryInfo extends ApiQueryBase {
         */
        public static function getBlockToken( $pageid, $title ) {
                global $wgUser;
-               if ( !$wgUser->isAllowed( 'block' ) ) {
+               if ( !MediaWikiServices::getInstance()->getPermissionManager()
+                               ->userHasRight( $wgUser, 'block' ) ) {
                        return false;
                }
 
@@ -245,7 +250,9 @@ class ApiQueryInfo extends ApiQueryBase {
         */
        public static function getImportToken( $pageid, $title ) {
                global $wgUser;
-               if ( !$wgUser->isAllowedAny( 'import', 'importupload' ) ) {
+               if ( !MediaWikiServices::getInstance()
+                       ->getPermissionManager()
+                       ->userHasAnyRight( $wgUser, 'import', 'importupload' ) ) {
                        return false;
                }
 
@@ -808,7 +815,7 @@ class ApiQueryInfo extends ApiQueryBase {
                $user = $this->getUser();
 
                if ( $user->isAnon() || count( $this->everything ) == 0
-                       || !$user->isAllowed( 'viewmywatchlist' )
+                       || !$this->getPermissionManager()->userHasRight( $user, 'viewmywatchlist' )
                ) {
                        return;
                }
@@ -843,7 +850,7 @@ class ApiQueryInfo extends ApiQueryBase {
                }
 
                $user = $this->getUser();
-               $canUnwatchedpages = $user->isAllowed( 'unwatchedpages' );
+               $canUnwatchedpages = $this->getPermissionManager()->userHasRight( $user, 'unwatchedpages' );
                $unwatchedPageThreshold = $this->getConfig()->get( 'UnwatchedPageThreshold' );
                if ( !$canUnwatchedpages && !is_int( $unwatchedPageThreshold ) ) {
                        return;
@@ -873,7 +880,7 @@ class ApiQueryInfo extends ApiQueryBase {
                $user = $this->getUser();
                $db = $this->getDB();
 
-               $canUnwatchedpages = $user->isAllowed( 'unwatchedpages' );
+               $canUnwatchedpages = $this->getPermissionManager()->userHasRight( $user, 'unwatchedpages' );
                $unwatchedPageThreshold = $this->getConfig()->get( 'UnwatchedPageThreshold' );
                if ( !$canUnwatchedpages && !is_int( $unwatchedPageThreshold ) ) {
                        return;
index 962d956..47a6f87 100644 (file)
@@ -220,10 +220,12 @@ class ApiQueryLogEvents extends ApiQueryBase {
 
                // Paranoia: avoid brute force searches (T19342)
                if ( $params['namespace'] !== null || !is_null( $title ) || !is_null( $user ) ) {
-                       if ( !$this->getUser()->isAllowed( 'deletedhistory' ) ) {
+                       if ( !$this->getPermissionManager()->userHasRight( $this->getUser(), 'deletedhistory' ) ) {
                                $titleBits = LogPage::DELETED_ACTION;
                                $userBits = LogPage::DELETED_USER;
-                       } elseif ( !$this->getUser()->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
+                       } elseif ( !$this->getPermissionManager()
+                               ->userHasAnyRight( $this->getUser(), 'suppressrevision', 'viewsuppressed' )
+                       ) {
                                $titleBits = LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED;
                                $userBits = LogPage::DELETED_USER | LogPage::DELETED_RESTRICTED;
                        } else {
index f5952e3..143d466 100644 (file)
@@ -361,9 +361,11 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
 
                // Paranoia: avoid brute force searches (T19342)
                if ( !is_null( $params['user'] ) || !is_null( $params['excludeuser'] ) ) {
-                       if ( !$user->isAllowed( 'deletedhistory' ) ) {
+                       if ( !$this->getPermissionManager()->userHasRight( $user, 'deletedhistory' ) ) {
                                $bitmask = RevisionRecord::DELETED_USER;
-                       } elseif ( !$user->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
+                       } elseif ( !$this->getPermissionManager()
+                               ->userHasAnyRight( $user, 'suppressrevision', 'viewsuppressed' )
+                       ) {
                                $bitmask = RevisionRecord::DELETED_USER | RevisionRecord::DELETED_RESTRICTED;
                        } else {
                                $bitmask = 0;
@@ -374,9 +376,11 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
                }
                if ( $this->getRequest()->getCheck( 'namespace' ) ) {
                        // LogPage::DELETED_ACTION hides the affected page, too.
-                       if ( !$user->isAllowed( 'deletedhistory' ) ) {
+                       if ( !$this->getPermissionManager()->userHasRight( $user, 'deletedhistory' ) ) {
                                $bitmask = LogPage::DELETED_ACTION;
-                       } elseif ( !$user->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
+                       } elseif ( !$this->getPermissionManager()
+                               ->userHasAnyRight( $user, 'suppressrevision', 'viewsuppressed' )
+                       ) {
                                $bitmask = LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED;
                        } else {
                                $bitmask = 0;
index fe3ae87..d616ad4 100644 (file)
@@ -76,7 +76,8 @@ class ApiQueryRevisions extends ApiQueryRevisionsBase {
         */
        public static function getRollbackToken( $pageid, $title, $rev ) {
                global $wgUser;
-               if ( !$wgUser->isAllowed( 'rollback' ) ) {
+               if ( !MediaWikiServices::getInstance()->getPermissionManager()
+                               ->userHasRight( $wgUser, 'rollback' ) ) {
                        return false;
                }
 
@@ -332,9 +333,11 @@ class ApiQueryRevisions extends ApiQueryRevisionsBase {
                        }
                        if ( $params['user'] !== null || $params['excludeuser'] !== null ) {
                                // Paranoia: avoid brute force searches (T19342)
-                               if ( !$this->getUser()->isAllowed( 'deletedhistory' ) ) {
+                               if ( !$this->getPermissionManager()->userHasRight( $this->getUser(), 'deletedhistory' ) ) {
                                        $bitmask = RevisionRecord::DELETED_USER;
-                               } elseif ( !$this->getUser()->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
+                               } elseif ( !$this->getPermissionManager()
+                                       ->userHasAnyRight( $this->getUser(), 'suppressrevision', 'viewsuppressed' )
+                               ) {
                                        $bitmask = RevisionRecord::DELETED_USER | RevisionRecord::DELETED_RESTRICTED;
                                } else {
                                        $bitmask = 0;
index 379f1af..919c763 100644 (file)
@@ -408,9 +408,11 @@ class ApiQueryUserContribs extends ApiQueryBase {
                // Don't include any revisions where we're not supposed to be able to
                // see the username.
                $user = $this->getUser();
-               if ( !$user->isAllowed( 'deletedhistory' ) ) {
+               if ( !$this->getPermissionManager()->userHasRight( $user, 'deletedhistory' ) ) {
                        $bitmask = RevisionRecord::DELETED_USER;
-               } elseif ( !$user->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
+               } elseif ( !$this->getPermissionManager()
+                       ->userHasAnyRight( $user, 'suppressrevision', 'viewsuppressed' )
+               ) {
                        $bitmask = RevisionRecord::DELETED_USER | RevisionRecord::DELETED_RESTRICTED;
                } else {
                        $bitmask = 0;
index ba7280d..ab8d93a 100644 (file)
@@ -159,8 +159,7 @@ class ApiQueryUserInfo extends ApiQueryBase {
                }
 
                if ( isset( $this->prop['rights'] ) ) {
-                       // User::getRights() may return duplicate values, strip them
-                       $vals['rights'] = array_values( array_unique( $user->getRights() ) );
+                       $vals['rights'] = $this->getPermissionManager()->getUserPermissions( $user );
                        ApiResult::setArrayType( $vals['rights'], 'array' ); // even if empty
                        ApiResult::setIndexedTagName( $vals['rights'], 'r' ); // even if empty
                }
@@ -180,7 +179,7 @@ class ApiQueryUserInfo extends ApiQueryBase {
 
                if ( isset( $this->prop['preferencestoken'] ) &&
                        !$this->lacksSameOriginSecurity() &&
-                       $user->isAllowed( 'editmyoptions' )
+                       $this->getPermissionManager()->userHasRight( $user, 'editmyoptions' )
                ) {
                        $vals['preferencestoken'] = $user->getEditToken( '', $this->getMain()->getRequest() );
                }
@@ -201,7 +200,8 @@ class ApiQueryUserInfo extends ApiQueryBase {
                        $vals['realname'] = $user->getRealName();
                }
 
-               if ( $user->isAllowed( 'viewmyprivateinfo' ) && isset( $this->prop['email'] ) ) {
+               if ( $this->getPermissionManager()->userHasRight( $user, 'viewmyprivateinfo' ) &&
+                               isset( $this->prop['email'] ) ) {
                        $vals['email'] = $user->getEmail();
                        $auth = $user->getEmailAuthenticationTimestamp();
                        if ( $auth !== null ) {
index 66d8db4..8e26d37 100644 (file)
@@ -225,7 +225,8 @@ class ApiQueryUsers extends ApiQueryBase {
                                }
 
                                if ( isset( $this->prop['rights'] ) ) {
-                                       $data[$key]['rights'] = $user->getRights();
+                                       $data[$key]['rights'] = $this->getPermissionManager()
+                                               ->getUserPermissions( $user );
                                }
                                if ( $row->ipb_deleted ) {
                                        $data[$key]['hidden'] = true;
index 5cef194..0718ac8 100644 (file)
@@ -41,7 +41,7 @@ class ApiUnblock extends ApiBase {
 
                $this->requireOnlyOneParameter( $params, 'id', 'user', 'userid' );
 
-               if ( !$user->isAllowed( 'block' ) ) {
+               if ( !$this->getPermissionManager()->userHasRight( $user, 'block' ) ) {
                        $this->dieWithError( 'apierror-permissiondenied-unblock', 'permissiondenied' );
                }
                # T17810: blocked admins should have limited access here
index 8f3c404..89ec6cb 100644 (file)
@@ -51,7 +51,7 @@ class ApiUserrights extends ApiBase {
 
                // Deny if the user is blocked and doesn't have the full 'userrights' permission.
                // This matches what Special:UserRights does for the web UI.
-               if ( !$pUser->isAllowed( 'userrights' ) ) {
+               if ( !$this->getPermissionManager()->userHasRight( $pUser, 'userrights' ) ) {
                        $block = $pUser->getBlock();
                        if ( $block && $block->isSitewide() ) {
                                $this->dieBlocked( $block );
index af97236..dc9c516 100644 (file)
        "apiwarn-deprecation-missingparam": "نظرا لعدم تحديد <var>$1</var>; تم استخدام تنسيق قديم للإخراج، تم إيقاف هذا التنسيق، وسيتم دائما استخدام التنسيق الجديد في المستقبل.",
        "apiwarn-deprecation-parameter": "تم إيقاف الوسيط <var>$1</var>.",
        "apiwarn-deprecation-parse-headitems": "تم إيقاف <kbd>prop=headitems</kbd> منذ ميدياويكي 1.28; استخدم <kbd>prop=headhtml</kbd> عند إنشاء مستندات HTML جديدة، أو <kbd>prop=modules|jsconfigvars</kbd> عند تحديث مستند من جانب العميل.",
+       "apiwarn-deprecation-post-without-content-type": "تم تقديم طلب POST بدون عنوان <code>Content-Type</code>، هذا لا يعمل بشكل موثوق.",
        "apiwarn-deprecation-purge-get": "تم إيقاف استخدام <kbd>action=purge</kbd> عبر GET; استخدم POST بدلا من ذلك.",
        "apiwarn-deprecation-withreplacement": "تم إيقاف <kbd>$1</kbd>; الرجاء استخدام <kbd>$2</kbd> بدلا من ذلك.",
        "apiwarn-difftohidden": "لا يمكنك إجراء مقارنة مع r$1: المحتوى مخفي.",
index 6625863..8b42a07 100644 (file)
        "apiwarn-deprecation-missingparam": "Because <var>$1</var> was not specified, a legacy format has been used for the output. This format is deprecated, and in the future the new format will always be used.",
        "apiwarn-deprecation-parameter": "The parameter <var>$1</var> has been deprecated.",
        "apiwarn-deprecation-parse-headitems": "<kbd>prop=headitems</kbd> is deprecated since MediaWiki 1.28. Use <kbd>prop=headhtml</kbd> when creating new HTML documents, or <kbd>prop=modules|jsconfigvars</kbd> when updating a document client-side.",
+       "apiwarn-deprecation-post-without-content-type": "A POST request was made without a <code>Content-Type</code> header. This does not work reliably.",
        "apiwarn-deprecation-purge-get": "Use of <kbd>action=purge</kbd> via GET is deprecated. Use POST instead.",
        "apiwarn-deprecation-withreplacement": "<kbd>$1</kbd> has been deprecated. Please use <kbd>$2</kbd> instead.",
        "apiwarn-difftohidden": "Couldn't diff to r$1: content is hidden.",
index d5de23f..87f056b 100644 (file)
        "apiwarn-deprecation-missingparam": "{{doc-apierror}}\n\nParameters:\n* $1 - Parameter name.",
        "apiwarn-deprecation-parameter": "{{doc-apierror}}\n\nParameters:\n* $1 - Parameter name.",
        "apiwarn-deprecation-parse-headitems": "{{doc-apierror}}",
+       "apiwarn-deprecation-post-without-content-type": "{{doc-apierror}}",
        "apiwarn-deprecation-purge-get": "{{doc-apierror}}",
        "apiwarn-deprecation-withreplacement": "{{doc-apierror}}\n\nParameters:\n* $1 - Query string fragment that is deprecated, e.g. \"action=tokens\".\n* $2 - Query string fragment to use instead, e.g. \"action=tokens\".",
        "apiwarn-difftohidden": "{{doc-apierror}}\n\nParameters:\n* $1 - Revision ID number.\n\n\"r\" is short for \"revision\". You may translate it.",
index 75c41fc..a0fa693 100644 (file)
@@ -49,6 +49,8 @@
        "apihelp-block-param-reblock": "Skriv över befintlig blockering om användaren redan är blockerad.",
        "apihelp-block-param-watchuser": "Bevaka användarens eller IP-adressens användarsida och diskussionssida",
        "apihelp-block-param-tags": "Ändra märken att tillämpa i blockloggens post.",
+       "apihelp-block-param-pagerestrictions": "Lista över titlar att blockera användaren från att redigera. Gäller endast när <var>partial</var> är \"true\".",
+       "apihelp-block-param-namespacerestrictions": "Lista över namnrymds-ID:n att blockera användaren från att redigera. Gäller endast när <var>partial</var> är \"true\".",
        "apihelp-block-example-ip-simple": "Blockera IP-adressen <kbd>192.0.2.5</kbd> i tre dagar med motivationen <kbd>First strike</kbd>",
        "apihelp-block-example-user-complex": "Blockera användare <kbd>Vandal</kbd> på obegränsad tid med motivationen <kbd>Vandalism</kbd>, och förhindra kontoskapande och e-post.",
        "apihelp-changeauthenticationdata-summary": "Ändra autentiseringsdata för aktuell användare.",
        "apihelp-query+blocks-paramvalue-prop-expiry": "Lägger till en tidsstämpel för när blockeringen går ut.",
        "apihelp-query+blocks-paramvalue-prop-reason": "Lägger till de skäl som angetts för blockeringen.",
        "apihelp-query+blocks-paramvalue-prop-range": "Lägger till intervallet av IP-adresser som berörs av blockeringen.",
+       "apihelp-query+blocks-paramvalue-prop-restrictions": "Lägger till partiella blockeringsbegränsningar om blockeringen inte gäller för hela webbplatsen.",
        "apihelp-query+blocks-example-simple": "Lista blockeringar.",
        "apihelp-query+blocks-example-users": "Lista blockeringar av användarna <kbd>Alice</kbd> och <kbd>Bob</kbd>.",
        "apihelp-query+categories-summary": "Lista alla kategorier sidorna tillhör.",
index 83e8314..2a61360 100644 (file)
@@ -64,6 +64,9 @@
        "apihelp-block-param-reblock": "如果该用户已被封禁,则覆盖已有的封禁。",
        "apihelp-block-param-watchuser": "监视用户或该 IP 的用户页和讨论页。",
        "apihelp-block-param-tags": "要在封禁日志中应用到实体的更改标签。",
+       "apihelp-block-param-partial": "封禁用户于特定页面或名字空间而不是整个站点。",
+       "apihelp-block-param-pagerestrictions": "阻止用户编辑的标题列表。仅在<var>partial</var>设置为true时适用。",
+       "apihelp-block-param-namespacerestrictions": "用于阻止用户编辑的名字空间ID列表。仅在<var>partial</var>设置为true时适用。",
        "apihelp-block-example-ip-simple": "封禁IP地址<kbd>192.0.2.5</kbd>三天,原因<kbd>First strike</kbd>。",
        "apihelp-block-example-user-complex": "无限期封禁用户<kbd>Vandal</kbd>,原因<kbd>Vandalism</kbd>,并阻止新账户创建和电子邮件发送。",
        "apihelp-changeauthenticationdata-summary": "更改当前用户的身份验证数据。",
index b7c60ed..14a7717 100644 (file)
        "apiwarn-deprecation-missingparam": "因為未指定 <var>$1</var>,輸出內容使用到過去舊有的格式。該格式已棄用,並且往後都只會使用新格式。",
        "apiwarn-deprecation-parameter": "參數 <var>$1</var> 已棄用。",
        "apiwarn-deprecation-parse-headitems": "<kbd>prop=headitems</kbd> 自 MediaWiki 的 1.28 版本後已被棄用。當建立新 HTML 文件時請使用 <kbd>prop=headhtml</kbd>,或是當更新文件客戶端時請使用 <kbd>prop=modules|jsconfigvars</kbd>。",
+       "apiwarn-deprecation-post-without-content-type": "POST 請求不需要 <code>Content-Type</code> 標頭,這會無法可靠運作。",
        "apiwarn-deprecation-purge-get": "透過 GET 方式使用的 <kbd>action=purge</kbd> 已棄用,請以 POST 替代。",
        "apiwarn-deprecation-withreplacement": "<kbd>$1</kbd> 已棄用,請改用 <kbd>$2</kbd>。",
        "apiwarn-difftohidden": "無法對 r$1 比較差異:內容被隱蔵。",
index c871ce1..4fcaf4e 100644 (file)
@@ -1639,8 +1639,9 @@ class AuthManager implements LoggerAwareInterface {
 
                // Is the IP user able to create accounts?
                $anon = new User;
-               if ( $source !== self::AUTOCREATE_SOURCE_MAINT &&
-                       !$anon->isAllowedAny( 'createaccount', 'autocreateaccount' )
+               if ( $source !== self::AUTOCREATE_SOURCE_MAINT && !MediaWikiServices::getInstance()
+                               ->getPermissionManager()
+                               ->userHasAnyRight( $anon, 'createaccount', 'autocreateaccount' )
                ) {
                        $this->logger->debug( __METHOD__ . ': IP lacks the ability to create or autocreate accounts', [
                                'username' => $username,
index e6d9bd8..9d0175a 100644 (file)
@@ -127,14 +127,16 @@ class Throttler implements LoggerAwareInterface {
                                continue;
                        }
 
-                       $throttleKey = $this->cache->makeGlobalKey( 'throttler', $this->type, $index, $ipKey, $userKey );
+                       $throttleKey = $this->cache->makeGlobalKey(
+                               'throttler',
+                               $this->type,
+                               $index,
+                               $ipKey,
+                               $userKey
+                       );
                        $throttleCount = $this->cache->get( $throttleKey );
-
-                       if ( !$throttleCount ) { // counter not started yet
-                               $this->cache->add( $throttleKey, 1, $expiry );
-                       } elseif ( $throttleCount < $count ) { // throttle limited not yet reached
-                               $this->cache->incr( $throttleKey );
-                       } else { // throttled
+                       if ( $throttleCount && $throttleCount >= $count ) {
+                               // Throttle limited reached
                                $this->logRejection( [
                                        'throttle' => $this->type,
                                        'index' => $index,
@@ -147,13 +149,12 @@ class Throttler implements LoggerAwareInterface {
                                        // @codeCoverageIgnoreEnd
                                ] );
 
-                               return [
-                                       'throttleIndex' => $index,
-                                       'count' => $count,
-                                       'wait' => $expiry,
-                               ];
+                               return [ 'throttleIndex' => $index, 'count' => $count, 'wait' => $expiry ];
+                       } else {
+                               $this->cache->incrWithInit( $throttleKey, $expiry, 1 );
                        }
                }
+
                return false;
        }
 
index f654404..fcc624e 100644 (file)
@@ -23,6 +23,7 @@ namespace MediaWiki\Block;
 use IContextSource;
 use InvalidArgumentException;
 use IP;
+use MediaWiki\MediaWikiServices;
 use RequestContext;
 use Title;
 use User;
@@ -95,6 +96,7 @@ abstract class AbstractBlock {
         *     reason string        Reason of the block
         *     timestamp string     The time at which the block comes into effect
         *     byText string        Username of the blocker (for foreign users)
+        *     hideName bool        Hide the target user name
         */
        public function __construct( array $options = [] ) {
                $defaults = [
@@ -103,6 +105,7 @@ abstract class AbstractBlock {
                        'reason'          => '',
                        'timestamp'       => '',
                        'byText'          => '',
+                       'hideName'        => false,
                ];
 
                $options += $defaults;
@@ -119,6 +122,7 @@ abstract class AbstractBlock {
 
                $this->setReason( $options['reason'] );
                $this->setTimestamp( wfTimestamp( TS_MW, $options['timestamp'] ) );
+               $this->setHideName( (bool)$options['hideName'] );
        }
 
        /**
@@ -279,8 +283,9 @@ abstract class AbstractBlock {
                if ( !$res && $blockDisablesLogin ) {
                        // If a block would disable login, then it should
                        // prevent any right that all users cannot do
+                       $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
                        $anon = new User;
-                       $res = $anon->isAllowed( $right ) ? $res : true;
+                       $res = $permissionManager->userHasRight( $anon, $right ) ? $res : true;
                }
 
                return $res;
@@ -339,8 +344,9 @@ abstract class AbstractBlock {
                if ( !$res && $blockDisablesLogin ) {
                        // If a block would disable login, then it should
                        // prevent any action that all users cannot do
+                       $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
                        $anon = new User;
-                       $res = $anon->isAllowed( $action ) ? $res : true;
+                       $res = $permissionManager->userHasRight( $anon, $action ) ? $res : true;
                }
 
                return $res;
index b67703c..948ed93 100644 (file)
 namespace MediaWiki\Block;
 
 use DateTime;
+use DateTimeZone;
 use DeferredUpdates;
+use Hooks;
 use IP;
 use MediaWiki\Config\ServiceOptions;
+use MediaWiki\Permissions\PermissionManager;
 use MediaWiki\User\UserIdentity;
 use MWCryptHash;
 use User;
@@ -45,6 +48,9 @@ class BlockManager {
        /** @var WebRequest */
        private $currentRequest;
 
+       /** @var PermissionManager */
+       private $permissionManager;
+
        /**
         * TODO Make this a const when HHVM support is dropped (T192166)
         *
@@ -67,16 +73,19 @@ class BlockManager {
         * @param ServiceOptions $options
         * @param User $currentUser
         * @param WebRequest $currentRequest
+        * @param PermissionManager $permissionManager
         */
        public function __construct(
                ServiceOptions $options,
                User $currentUser,
-               WebRequest $currentRequest
+               WebRequest $currentRequest,
+               PermissionManager $permissionManager
        ) {
                $options->assertRequiredOptions( self::$constructorOptions );
                $this->options = $options;
                $this->currentUser = $currentUser;
                $this->currentRequest = $currentRequest;
+               $this->permissionManager = $permissionManager;
        }
 
        /**
@@ -95,6 +104,7 @@ class BlockManager {
         */
        public function getUserBlock( User $user, $fromReplica ) {
                $isAnon = $user->getId() === 0;
+               $fromMaster = !$fromReplica;
 
                // TODO: If $user is the current user, we should use the current request. Otherwise,
                // we should not look for XFF or cookie blocks.
@@ -110,14 +120,15 @@ class BlockManager {
                $globalUserName = $sessionUser->isSafeToLoad()
                        ? $sessionUser->getName()
                        : IP::sanitizeIP( $this->currentRequest->getIP() );
-               if ( $user->getName() === $globalUserName && !$user->isAllowed( 'ipblock-exempt' ) ) {
+               if ( $user->getName() === $globalUserName &&
+                        !$this->permissionManager->userHasRight( $user, 'ipblock-exempt' ) ) {
                        $ip = $this->currentRequest->getIP();
                }
 
                // User/IP blocking
                // After this, $blocks is an array of blocks or an empty array
                // TODO: remove dependency on DatabaseBlock
-               $blocks = DatabaseBlock::newListFromTarget( $user, $ip, !$fromReplica );
+               $blocks = DatabaseBlock::newListFromTarget( $user, $ip, $fromMaster );
 
                // Cookie blocking
                $cookieBlock = $this->getBlockFromCookieValue( $user, $request );
@@ -154,7 +165,7 @@ class BlockManager {
                        $xff = array_map( 'trim', explode( ',', $xff ) );
                        $xff = array_diff( $xff, [ $ip ] );
                        // TODO: remove dependency on DatabaseBlock
-                       $xffblocks = DatabaseBlock::getBlocksForIPList( $xff, $isAnon, !$fromReplica );
+                       $xffblocks = DatabaseBlock::getBlocksForIPList( $xff, $isAnon, $fromMaster );
                        $blocks = array_merge( $blocks, $xffblocks );
                }
 
@@ -175,6 +186,7 @@ class BlockManager {
                // Filter out any duplicated blocks, e.g. from the cookie
                $blocks = $this->getUniqueBlocks( $blocks );
 
+               $block = null;
                if ( count( $blocks ) > 0 ) {
                        if ( count( $blocks ) === 1 ) {
                                $block = $blocks[ 0 ];
@@ -186,10 +198,11 @@ class BlockManager {
                                        'originalBlocks' => $blocks,
                                ] );
                        }
-                       return $block;
                }
 
-               return null;
+               Hooks::run( 'GetUserBlock', [ clone $user, $ip, &$block ] );
+
+               return $block;
        }
 
        /**
@@ -218,12 +231,12 @@ class BlockManager {
                        }
                }
 
-               return array_merge( $systemBlocks, $databaseBlocks );
+               return array_values( array_merge( $systemBlocks, $databaseBlocks ) );
        }
 
        /**
         * Try to load a block from an ID given in a cookie value. If the block is invalid
-        * or doesn't exist, remove the cookie.
+        * doesn't exist, or the cookie value is malformed, remove the cookie.
         *
         * @param UserIdentity $user
         * @param WebRequest $request
@@ -233,9 +246,13 @@ class BlockManager {
                UserIdentity $user,
                WebRequest $request
        ) {
-               $blockCookieId = $this->getIdFromCookieValue( $request->getCookie( 'BlockID' ) );
+               $cookieValue = $request->getCookie( 'BlockID' );
+               if ( is_null( $cookieValue ) ) {
+                       return false;
+               }
 
-               if ( $blockCookieId !== null ) {
+               $blockCookieId = $this->getIdFromCookieValue( $cookieValue );
+               if ( !is_null( $blockCookieId ) ) {
                        // TODO: remove dependency on DatabaseBlock
                        $block = DatabaseBlock::newFromID( $blockCookieId );
                        if (
@@ -244,9 +261,10 @@ class BlockManager {
                        ) {
                                return $block;
                        }
-                       $this->clearBlockCookie( $request->response() );
                }
 
+               $this->clearBlockCookie( $request->response() );
+
                return false;
        }
 
@@ -435,7 +453,11 @@ class BlockManager {
                }
 
                // Set the cookie. Reformat the MediaWiki datetime as a Unix timestamp for the cookie.
-               $expiryValue = DateTime::createFromFormat( 'YmdHis', $expiryTime )->format( 'U' );
+               $expiryValue = DateTime::createFromFormat(
+                       'YmdHis',
+                       $expiryTime,
+                       new DateTimeZone( 'UTC' )
+               )->format( 'U' );
                $cookieOptions = [ 'httpOnly' => false ];
                $cookieValue = $this->getCookieValue( $block );
                $response->setCookie( 'BlockID', $cookieValue, $expiryValue, $cookieOptions );
index 2fd62ee..79286c5 100644 (file)
@@ -93,7 +93,6 @@ class DatabaseBlock extends AbstractBlock {
         *     anonOnly bool        Only disallow anonymous actions
         *     createAccount bool   Disallow creation of new accounts
         *     enableAutoblock bool Enable automatic blocking
-        *     hideName bool        Hide the target user name
         *     blockEmail bool      Disallow sending emails
         *     allowUsertalk bool   Allow the target to edit its own talk page
         *     sitewide bool        Disallow editing all pages and all contribution
@@ -112,7 +111,6 @@ class DatabaseBlock extends AbstractBlock {
                        'anonOnly'        => false,
                        'createAccount'   => false,
                        'enableAutoblock' => false,
-                       'hideName'        => false,
                        'blockEmail'      => false,
                        'allowUsertalk'   => false,
                        'sitewide'        => true,
@@ -129,7 +127,6 @@ class DatabaseBlock extends AbstractBlock {
 
                # Boolean settings
                $this->mAuto = (bool)$options['auto'];
-               $this->setHideName( (bool)$options['hideName'] );
                $this->isHardblock( !$options['anonOnly'] );
                $this->isAutoblocking( (bool)$options['enableAutoblock'] );
                $this->isSitewide( (bool)$options['sitewide'] );
index ce5a019..fb42539 100644 (file)
@@ -230,31 +230,26 @@ abstract class FileCacheBase {
         */
        public function incrMissesRecent( WebRequest $request ) {
                if ( mt_rand( 0, self::MISS_FACTOR - 1 ) == 0 ) {
-                       $cache = ObjectCache::getLocalClusterInstance();
                        # Get a large IP range that should include the user  even if that
                        # person's IP address changes
                        $ip = $request->getIP();
                        if ( !IP::isValid( $ip ) ) {
                                return;
                        }
+
                        $ip = IP::isIPv6( $ip )
                                ? IP::sanitizeRange( "$ip/32" )
                                : IP::sanitizeRange( "$ip/16" );
 
                        # Bail out if a request already came from this range...
+                       $cache = ObjectCache::getLocalClusterInstance();
                        $key = $cache->makeKey( static::class, 'attempt', $this->mType, $this->mKey, $ip );
-                       if ( $cache->get( $key ) ) {
+                       if ( !$cache->add( $key, 1, self::MISS_TTL_SEC ) ) {
                                return; // possibly the same user
                        }
-                       $cache->set( $key, 1, self::MISS_TTL_SEC );
 
                        # Increment the number of cache misses...
-                       $key = $this->cacheMissKey( $cache );
-                       if ( $cache->get( $key ) === false ) {
-                               $cache->set( $key, 1, self::MISS_TTL_SEC );
-                       } else {
-                               $cache->incr( $key );
-                       }
+                       $cache->incrWithInit( $this->cacheMissKey( $cache ), self::MISS_TTL_SEC );
                }
        }
 
index 95c9fa6..0c6a3d1 100644 (file)
@@ -20,6 +20,7 @@
  * @file
  */
 use MediaWiki\ChangeTags\Taggable;
+use MediaWiki\MediaWikiServices;
 
 /**
  * Utility class for creating new RC entries
@@ -390,7 +391,7 @@ class RecentChange implements Taggable {
                }
 
                # If our database is strict about IP addresses, use NULL instead of an empty string
-               $strictIPs = in_array( $dbw->getType(), [ 'oracle', 'postgres' ] ); // legacy
+               $strictIPs = $dbw->getType() === 'postgres'; // legacy
                if ( $strictIPs && $this->mAttribs['rc_ip'] == '' ) {
                        unset( $this->mAttribs['rc_ip'] );
                }
@@ -608,8 +609,9 @@ class RecentChange implements Taggable {
                }
                // Users without the 'autopatrol' right can't patrol their
                // own revisions
-               if ( $user->getName() === $this->getAttribute( 'rc_user_text' )
-                       && !$user->isAllowed( 'autopatrol' )
+               if ( $user->getName() === $this->getAttribute( 'rc_user_text' ) &&
+                               !MediaWikiServices::getInstance()->getPermissionManager()
+                                       ->userHasRight( $user, 'autopatrol' )
                ) {
                        $errors[] = [ 'markedaspatrollederror-noautopatrol' ];
                }
@@ -857,6 +859,7 @@ class RecentChange implements Taggable {
                $type, $action, $target, $logComment, $params, $newId = 0, $actionCommentIRC = '',
                $revId = 0, $isPatrollable = false ) {
                global $wgRequest;
+               $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
 
                # # Get pageStatus for email notification
                switch ( $type . '-' . $action ) {
@@ -881,7 +884,8 @@ class RecentChange implements Taggable {
                }
 
                // Allow unpatrolled status for patrollable log entries
-               $markPatrolled = $isPatrollable ? $user->isAllowed( 'autopatrol' ) : true;
+               $canAutopatrol = $permissionManager->userHasRight( $user, 'autopatrol' );
+               $markPatrolled = $isPatrollable ? $canAutopatrol : true;
 
                $rc = new RecentChange;
                $rc->mTitle = $target;
@@ -902,7 +906,8 @@ class RecentChange implements Taggable {
                        'rc_comment_data' => null,
                        'rc_this_oldid' => $revId,
                        'rc_last_oldid' => 0,
-                       'rc_bot' => $user->isAllowed( 'bot' ) ? (int)$wgRequest->getBool( 'bot', true ) : 0,
+                       'rc_bot' => $permissionManager->userHasRight( $user, 'bot' ) ?
+                               (int)$wgRequest->getBool( 'bot', true ) : 0,
                        'rc_ip' => self::checkIPAddress( $ip ),
                        'rc_patrolled' => $markPatrolled ? self::PRC_AUTOPATROLLED : self::PRC_UNPATROLLED,
                        'rc_new' => 0, # obsolete
index 0f6e232..30c2f7a 100644 (file)
@@ -520,7 +520,9 @@ class ChangeTags {
         */
        public static function canAddTagsAccompanyingChange( array $tags, User $user = null ) {
                if ( !is_null( $user ) ) {
-                       if ( !$user->isAllowed( 'applychangetags' ) ) {
+                       if ( !MediaWikiServices::getInstance()->getPermissionManager()
+                                       ->userHasRight( $user, 'applychangetags' )
+                       ) {
                                return Status::newFatal( 'tags-apply-no-permission' );
                        } elseif ( $user->getBlock() ) {
                                // @TODO Ensure that the block does not apply to the `applychangetags`
@@ -595,7 +597,9 @@ class ChangeTags {
                User $user = null
        ) {
                if ( !is_null( $user ) ) {
-                       if ( !$user->isAllowed( 'changetags' ) ) {
+                       if ( !MediaWikiServices::getInstance()->getPermissionManager()
+                                       ->userHasRight( $user, 'changetags' )
+                       ) {
                                return Status::newFatal( 'tags-update-no-permission' );
                        } elseif ( $user->getBlock() ) {
                                // @TODO Ensure that the block does not apply to the `changetags`
@@ -1015,7 +1019,9 @@ class ChangeTags {
         */
        public static function canActivateTag( $tag, User $user = null ) {
                if ( !is_null( $user ) ) {
-                       if ( !$user->isAllowed( 'managechangetags' ) ) {
+                       if ( !MediaWikiServices::getInstance()->getPermissionManager()
+                                       ->userHasRight( $user, 'managechangetags' )
+                       ) {
                                return Status::newFatal( 'tags-manage-no-permission' );
                        } elseif ( $user->getBlock() ) {
                                // @TODO Ensure that the block does not apply to the `managechangetags`
@@ -1089,7 +1095,9 @@ class ChangeTags {
         */
        public static function canDeactivateTag( $tag, User $user = null ) {
                if ( !is_null( $user ) ) {
-                       if ( !$user->isAllowed( 'managechangetags' ) ) {
+                       if ( !MediaWikiServices::getInstance()->getPermissionManager()
+                                       ->userHasRight( $user, 'managechangetags' )
+                       ) {
                                return Status::newFatal( 'tags-manage-no-permission' );
                        } elseif ( $user->getBlock() ) {
                                // @TODO Ensure that the block does not apply to the `managechangetags`
@@ -1188,7 +1196,9 @@ class ChangeTags {
         */
        public static function canCreateTag( $tag, User $user = null ) {
                if ( !is_null( $user ) ) {
-                       if ( !$user->isAllowed( 'managechangetags' ) ) {
+                       if ( !MediaWikiServices::getInstance()->getPermissionManager()
+                                       ->userHasRight( $user, 'managechangetags' )
+                       ) {
                                return Status::newFatal( 'tags-manage-no-permission' );
                        } elseif ( $user->getBlock() ) {
                                // @TODO Ensure that the block does not apply to the `managechangetags`
@@ -1308,7 +1318,9 @@ class ChangeTags {
                $tagUsage = self::tagUsageStatistics();
 
                if ( !is_null( $user ) ) {
-                       if ( !$user->isAllowed( 'deletechangetags' ) ) {
+                       if ( !MediaWikiServices::getInstance()->getPermissionManager()
+                                       ->userHasRight( $user, 'deletechangetags' )
+                       ) {
                                return Status::newFatal( 'tags-delete-no-permission' );
                        } elseif ( $user->getBlock() ) {
                                // @TODO Ensure that the block does not apply to the `deletechangetags`
@@ -1566,6 +1578,8 @@ class ChangeTags {
         * @return bool
         */
        public static function showTagEditingUI( User $user ) {
-               return $user->isAllowed( 'changetags' ) && (bool)self::listExplicitlyDefinedTags();
+               return MediaWikiServices::getInstance()->getPermissionManager()
+                                  ->userHasRight( $user, 'changetags' ) &&
+                          (bool)self::listExplicitlyDefinedTags();
        }
 }
index c82b473..eb9ef85 100644 (file)
@@ -529,7 +529,7 @@ abstract class AbstractContent implements Content {
         * @since 1.24
         *
         * @param Title $title Context title for parsing
-        * @param int|null $revId Revision ID (for {{REVISIONID}})
+        * @param int|null $revId Revision ID being rendered
         * @param ParserOptions|null $options
         * @param bool $generateHtml Whether or not to generate HTML
         *
@@ -575,7 +575,8 @@ abstract class AbstractContent implements Content {
         * @since 1.24
         *
         * @param Title $title Context title for parsing
-        * @param int|null $revId Revision ID (for {{REVISIONID}})
+        * @param int|null $revId ID of the revision being rendered.
+        *  See Parser::parse() for the ramifications.
         * @param ParserOptions $options
         * @param bool $generateHtml Whether or not to generate HTML
         * @param ParserOutput &$output The output object to fill (reference).
index 2637aa6..8596619 100644 (file)
@@ -269,7 +269,8 @@ interface Content {
         *       may call ParserOutput::recordOption() on the output object.
         *
         * @param Title $title The page title to use as a context for rendering.
-        * @param int|null $revId Optional revision ID being rendered.
+        * @param int|null $revId ID of the revision being rendered.
+        *  See Parser::parse() for the ramifications. (default: null)
         * @param ParserOptions|null $options Any parser options.
         * @param bool $generateHtml Whether to generate HTML (default: true). If false,
         *        the result of calling getText() on the ParserOutput object returned by
index 48dfc70..ea5ab78 100644 (file)
@@ -1077,7 +1077,8 @@ abstract class ContentHandler {
                }
 
                // Max content length = max comment length - length of the comment (excl. $1)
-               $text = $content ? $content->getTextForSummary( 255 - ( strlen( $reason ) - 2 ) ) : '';
+               $maxLength = CommentStore::COMMENT_CHARACTER_LIMIT - ( strlen( $reason ) - 2 );
+               $text = $content ? $content->getTextForSummary( $maxLength ) : '';
 
                // Now replace the '$1' placeholder
                $reason = str_replace( '$1', $text, $reason );
index 8e5e0a8..70b638b 100644 (file)
@@ -329,7 +329,8 @@ class WikitextContent extends TextContent {
         * using the global Parser service.
         *
         * @param Title $title
-        * @param int|null $revId Revision to pass to the parser (default: null)
+        * @param int|null $revId ID of the revision being rendered.
+        *  See Parser::parse() for the ramifications. (default: null)
         * @param ParserOptions $options (default: null)
         * @param bool $generateHtml (default: true)
         * @param ParserOutput &$output ParserOutput representing the HTML form of the text,
index 0c17840..80eb2f7 100644 (file)
@@ -168,7 +168,7 @@ abstract class MWLBFactory {
         * @return array
         */
        private static function getDbTypesWithSchemas() {
-               return [ 'postgres', 'mssql' ];
+               return [ 'postgres' ];
        }
 
        /**
@@ -193,16 +193,6 @@ abstract class MWLBFactory {
                                // Work around the reserved word usage in MediaWiki schema
                                'keywordTableMap' => [ 'user' => 'mwuser', 'text' => 'pagecontent' ]
                        ];
-               } elseif ( $server['type'] === 'oracle' ) {
-                       $server += [
-                               // Work around the reserved word usage in MediaWiki schema
-                               'keywordTableMap' => [ 'user' => 'mwuser', 'text' => 'pagecontent' ]
-                       ];
-               } elseif ( $server['type'] === 'mssql' ) {
-                       $server += [
-                               'port' => $options->get( 'DBport' ),
-                               'useWindowsAuth' => $options->get( 'DBWindowsAuthentication' )
-                       ];
                }
 
                if ( in_array( $server['type'], self::getDbTypesWithSchemas(), true ) ) {
diff --git a/includes/db/ORAField.php b/includes/db/ORAField.php
deleted file mode 100644 (file)
index df31000..0000000
+++ /dev/null
@@ -1,53 +0,0 @@
-<?php
-
-use Wikimedia\Rdbms\Field;
-
-class ORAField implements Field {
-       private $name, $tablename, $default, $max_length, $nullable,
-               $is_pk, $is_unique, $is_multiple, $is_key, $type;
-
-       function __construct( $info ) {
-               $this->name = $info['column_name'];
-               $this->tablename = $info['table_name'];
-               $this->default = $info['data_default'];
-               $this->max_length = $info['data_length'];
-               $this->nullable = $info['not_null'];
-               $this->is_pk = isset( $info['prim'] ) && $info['prim'] == 1 ? 1 : 0;
-               $this->is_unique = isset( $info['uniq'] ) && $info['uniq'] == 1 ? 1 : 0;
-               $this->is_multiple = isset( $info['nonuniq'] ) && $info['nonuniq'] == 1 ? 1 : 0;
-               $this->is_key = ( $this->is_pk || $this->is_unique || $this->is_multiple );
-               $this->type = $info['data_type'];
-       }
-
-       function name() {
-               return $this->name;
-       }
-
-       function tableName() {
-               return $this->tablename;
-       }
-
-       function defaultValue() {
-               return $this->default;
-       }
-
-       function maxLength() {
-               return $this->max_length;
-       }
-
-       function isNullable() {
-               return $this->nullable;
-       }
-
-       function isKey() {
-               return $this->is_key;
-       }
-
-       function isMultipleKey() {
-               return $this->is_multiple;
-       }
-
-       function type() {
-               return $this->type;
-       }
-}
diff --git a/includes/db/ORAResult.php b/includes/db/ORAResult.php
deleted file mode 100644 (file)
index aafd386..0000000
+++ /dev/null
@@ -1,110 +0,0 @@
-<?php
-
-use Wikimedia\Rdbms\IDatabase;
-
-/**
- * The oci8 extension is fairly weak and doesn't support oci_num_rows, among
- * other things. We use a wrapper class to handle that and other
- * Oracle-specific bits, like converting column names back to lowercase.
- * @ingroup Database
- */
-class ORAResult {
-       private $rows;
-       private $cursor;
-       private $nrows;
-
-       private $columns = [];
-
-       private function array_unique_md( $array_in ) {
-               $array_out = [];
-               $array_hashes = [];
-
-               foreach ( $array_in as $item ) {
-                       $hash = md5( serialize( $item ) );
-                       if ( !isset( $array_hashes[$hash] ) ) {
-                               $array_hashes[$hash] = $hash;
-                               $array_out[] = $item;
-                       }
-               }
-
-               return $array_out;
-       }
-
-       /**
-        * @param IDatabase &$db
-        * @param resource $stmt A valid OCI statement identifier
-        * @param bool $unique
-        */
-       function __construct( &$db, $stmt, $unique = false ) {
-               $this->db =& $db;
-
-               $this->nrows = oci_fetch_all( $stmt, $this->rows, 0, -1, OCI_FETCHSTATEMENT_BY_ROW | OCI_NUM );
-               if ( $this->nrows === false ) {
-                       $e = oci_error( $stmt );
-                       $db->reportQueryError( $e['message'], $e['code'], '', __METHOD__ );
-                       $this->free();
-
-                       return;
-               }
-
-               if ( $unique ) {
-                       $this->rows = $this->array_unique_md( $this->rows );
-                       $this->nrows = count( $this->rows );
-               }
-
-               if ( $this->nrows > 0 ) {
-                       foreach ( $this->rows[0] as $k => $v ) {
-                               $this->columns[$k] = strtolower( oci_field_name( $stmt, $k + 1 ) );
-                       }
-               }
-
-               $this->cursor = 0;
-               oci_free_statement( $stmt );
-       }
-
-       public function free() {
-               unset( $this->db );
-       }
-
-       public function seek( $row ) {
-               $this->cursor = min( $row, $this->nrows );
-       }
-
-       public function numRows() {
-               return $this->nrows;
-       }
-
-       public function numFields() {
-               return count( $this->columns );
-       }
-
-       public function fetchObject() {
-               if ( $this->cursor >= $this->nrows ) {
-                       return false;
-               }
-               $row = $this->rows[$this->cursor++];
-               $ret = new stdClass();
-               foreach ( $row as $k => $v ) {
-                       $lc = $this->columns[$k];
-                       $ret->$lc = $v;
-               }
-
-               return $ret;
-       }
-
-       public function fetchRow() {
-               if ( $this->cursor >= $this->nrows ) {
-                       return false;
-               }
-
-               $row = $this->rows[$this->cursor++];
-               $ret = [];
-               foreach ( $row as $k => $v ) {
-                       $lc = $this->columns[$k];
-                       $ret[$lc] = $v;
-                       $ret[$k] = $v;
-               }
-
-               return $ret;
-       }
-}
index 2eb0d5d..33961ed 100644 (file)
@@ -9,5 +9,5 @@ interface DeferrableCallback {
        /**
         * @return string Originating method name
         */
-       function getOrigin();
+       public function getOrigin();
 }
index d43ffbc..3380364 100644 (file)
@@ -362,11 +362,16 @@ class DeferredUpdates {
                        $update->setTransactionTicket( $ticket );
                }
 
-               $fnameTrxOwner = get_class( $update ) . '::doUpdate';
+               // Designate $update::doUpdate() as the write round owner
+               $fnameTrxOwner = ( $update instanceof DeferrableCallback )
+                       ? $update->getOrigin()
+                       : get_class( $update ) . '::doUpdate';
+               // Determine whether the write round will be explicit or implicit
                $useExplicitTrxRound = !(
                        $update instanceof TransactionRoundAwareUpdate &&
                        $update->getTransactionRoundRequirement() == $update::TRX_ROUND_ABSENT
                );
+
                // Flush any pending changes left over from an implicit transaction round
                if ( $useExplicitTrxRound ) {
                        $lbFactory->beginMasterChanges( $fnameTrxOwner ); // new explicit round
index 841daea..1d3b402 100644 (file)
@@ -401,7 +401,8 @@ class DifferenceEngine extends ContextSource {
         * @return string|bool Link HTML or false
         */
        public function deletedLink( $id ) {
-               if ( $this->getUser()->isAllowed( 'deletedhistory' ) ) {
+               $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
+               if ( $permissionManager->userHasRight( $this->getUser(), 'deletedhistory' ) ) {
                        $dbr = wfGetDB( DB_REPLICA );
                        $arQuery = Revision::getArchiveQueryInfo();
                        $row = $dbr->selectRow(
@@ -803,7 +804,8 @@ class DifferenceEngine extends ContextSource {
                        // Build the link
                        if ( $rcid ) {
                                $this->getOutput()->preventClickjacking();
-                               if ( $user->isAllowed( 'writeapi' ) ) {
+                               if ( MediaWikiServices::getInstance()->getPermissionManager()
+                                               ->userHasRight( $user, 'writeapi' ) ) {
                                        $this->getOutput()->addModules( 'mediawiki.page.patrol.ajax' );
                                }
 
index 103b3e5..8161251 100644 (file)
@@ -75,8 +75,8 @@ class TextboxBuilder {
        public function getTextboxProtectionCSSClasses( Title $title ) {
                $classes = []; // Textarea CSS
                if ( $title->isProtected( 'edit' ) &&
-                       MediaWikiServices::getInstance()->getNamespaceInfo()->
-                       getRestrictionLevels( $title->getNamespace() ) !== [ '' ]
+                       MediaWikiServices::getInstance()->getPermissionManager()
+                               ->getNamespaceRestrictionLevels( $title->getNamespace() ) !== [ '' ]
                ) {
                        # Is the title semi-protected?
                        if ( $title->isSemiProtected() ) {
index cc69a76..87a3dc2 100644 (file)
@@ -18,6 +18,8 @@
  * @file
  */
 
+use MediaWiki\MediaWikiServices;
+
 /**
  * Show an error when a user tries to do something they do not have the necessary
  * permissions for.
@@ -46,7 +48,9 @@ class PermissionsError extends ErrorPageError {
 
                if ( !count( $errors ) ) {
                        $groups = [];
-                       foreach ( User::getGroupsWithPermission( $this->permission ) as $group ) {
+                       foreach ( MediaWikiServices::getInstance()
+                                                 ->getPermissionManager()
+                                                 ->getGroupsWithPermission( $this->permission ) as $group ) {
                                $groups[] = UserGroupMembership::getLink( $group, RequestContext::getMain(), 'wiki' );
                        }
 
index 7ec2357..8b31f05 100644 (file)
@@ -186,7 +186,7 @@ class FileBackendGroup {
                                'mimeCallback' => [ $this, 'guessMimeInternal' ],
                                'obResetFunc' => 'wfResetOutputBuffers',
                                'streamMimeFunc' => [ StreamFile::class, 'contentTypeFromPath' ],
-                               'tmpDirectory' => wfTempDir(),
+                               'tmpFileFactory' => $services->getTempFSFileFactory(),
                                'statusWrapper' => [ Status::class, 'wrap' ],
                                'wanCache' => $services->getMainWANObjectCache(),
                                'srvCache' => ObjectCache::getLocalServerInstance( 'hash' ),
@@ -241,7 +241,8 @@ class FileBackendGroup {
                if ( !$type && $fsPath ) {
                        $type = $magic->guessMimeType( $fsPath, false );
                } elseif ( !$type && strlen( $content ) ) {
-                       $tmpFile = TempFSFile::factory( 'mime_', '', wfTempDir() );
+                       $tmpFile = MediaWikiServices::getInstance()->getTempFSFileFactory()
+                               ->newTempFSFile( 'mime_', '' );
                        file_put_contents( $tmpFile->getPath(), $content );
                        $type = $magic->guessMimeType( $tmpFile->getPath(), false );
                }
index ee7ee6f..5f6a0cb 100644 (file)
@@ -1352,7 +1352,8 @@ abstract class File implements IDBAccessObject {
         */
        protected function makeTransformTmpFile( $thumbPath ) {
                $thumbExt = FileBackend::extensionFromPath( $thumbPath );
-               return TempFSFile::factory( 'transform_', $thumbExt, wfTempDir() );
+               return MediaWikiServices::getInstance()->getTempFSFileFactory()
+                       ->newTempFSFile( 'transform_', $thumbExt );
        }
 
        /**
index d25d9aa..fadd587 100644 (file)
@@ -111,8 +111,8 @@ class TraditionalImageGallery extends ImageGalleryBase {
                                if ( $this->mParser instanceof Parser ) {
                                        $this->mParser->addTrackingCategory( 'broken-file-category' );
                                }
-                       } elseif ( $this->mHideBadImages
-                               && wfIsBadImage( $nt->getDBkey(), $this->getContextTitle() )
+                       } elseif ( $this->mHideBadImages && MediaWikiServices::getInstance()->getBadFileLookup()
+                               ->isBadFile( $nt->getDBkey(), $this->getContextTitle() )
                        ) {
                                # The image is blacklisted, just show it as a text link.
                                $thumbhtml = "\n\t\t\t" . '<div class="thumb" style="height: ' .
index 7c39ded..01bb30e 100644 (file)
@@ -242,27 +242,6 @@ class SqliteInstaller extends DatabaseInstaller {
                $this->setVar( 'wgDBpassword', '' );
                $this->setupSchemaVars();
 
-               # Create the global cache DB
-               try {
-                       $conn = Database::factory(
-                               'sqlite', [ 'dbname' => 'wikicache', 'dbDirectory' => $dir ] );
-                       # @todo: don't duplicate objectcache definition, though it's very simple
-                       $sql =
-<<<EOT
-       CREATE TABLE IF NOT EXISTS objectcache (
-               keyname BLOB NOT NULL default '' PRIMARY KEY,
-               value BLOB,
-               exptime TEXT
-       )
-EOT;
-                       $conn->query( $sql );
-                       $conn->query( "CREATE INDEX IF NOT EXISTS exptime ON objectcache (exptime)" );
-                       $conn->query( "PRAGMA journal_mode=WAL" ); // this is permanent
-                       $conn->close();
-               } catch ( DBConnectionError $e ) {
-                       return Status::newFatal( 'config-sqlite-connection-error', $e->getMessage() );
-               }
-
                # Create the l10n cache DB
                try {
                        $conn = Database::factory(
index 42d7bf0..498fd7c 100644 (file)
        "config-install-step-failed": "mislykkedes",
        "config-install-extensions": "Inkluderer udvidelser",
        "config-install-database": "Opsætter database",
+       "config-install-user": "Opretter databasebruger",
        "config-install-user-alreadyexists": "Brugeren \"$1\" findes allerede",
        "config-install-user-create-failed": "Oprettelse af brugeren \"$1\" mislykkedes: $2",
        "config-install-tables": "Opretter tabeller",
        "config-install-keys": "Genererer hemmelige nøgler",
        "config-install-mainpage-exists": "Forsiden findes allerede, springer over",
        "config-install-mainpage-failed": "Kunne ikke indsætte forside: $1",
+       "config-install-db-success": "Databasen blev sat op",
        "config-help": "hjælp",
        "config-help-tooltip": "klik for at udvide",
        "config-nofile": "Filen \"$1\" kunne ikke blive fundet. Er den blevet slettet?",
index d0b43ad..a14bef1 100644 (file)
        "config-restart": "Ya, nyalakan ulang",
        "config-welcome": "=== Pengecekan lingkungan ===\nPengecekan dasar kini akan dilakukan untuk melihat apakah lingkungan ini memadai untuk instalasi MediaWiki.\nIngatlah untuk menyertakan informasi ini jika Anda mencari bantuan tentang cara menyelesaikan instalasi.",
        "config-welcome-section-copyright": "=== Hak cipta dan persyaratan ===\n\n$1\n\nProgram ini adalah perangkat lunak bebas; Anda dapat mendistribusikan dan/atau memodifikasinya di bawah persyaratan GNU General Public License seperti yang diterbitkan oleh Free Software Foundation; baik versi 2 lisensi, atau (sesuai pilihan Anda) versi yang lebih baru.\n\nProgram ini didistribusikan dengan harapan bahwa itu akan berguna, tetapi <strong>tanpa jaminan apa pun</strong>; bahkan tanpa jaminan tersirat untuk <strong>dapat diperjualbelikan</strong> atau <strong>sesuai untuk tujuan tertentu</strong>.\nLihat GNU General Public License untuk lebih jelasnya.\n\nAnda seharusnya telah menerima [$2 salinan dari GNU General Public License] bersama dengan program ini; jika tidak, kirimkan surat untuk Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA, atau [https://www.gnu.org/copyleft/gpl.html baca versi daring].",
-       "config-sidebar": "* [https://www.mediawiki.org/wiki/MediaWiki/id Situs MediaWiki]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents/id Pedoman Pengguna]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents/id Pedoman Administrator]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ/id FAQ]\n----\n* <doclink href=Readme>Read me</doclink>\n* <doclink href=ReleaseNotes>Release notes</doclink>\n* <doclink href=Copying>Copying</doclink>\n* <doclink href=UpgradeDoc>Upgrading</doclink>",
+       "config-sidebar": "* [https://www.mediawiki.org Halaman depan MediaWiki]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents Panduan Pengguna]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents Panduan Pengurus]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Pertanyaan yang sering ditanyakan]",
+       "config-sidebar-readme": "Pelajari selengkapnya",
+       "config-sidebar-relnotes": "Catatan rilis",
+       "config-sidebar-license": "Menyalin",
+       "config-sidebar-upgrade": "Memperbarui",
        "config-env-good": "Kondisi telah diperiksa.\nAnda dapat menginstal MediaWiki.",
        "config-env-bad": "Kondisi telah diperiksa.\nAnda tidak dapat menginstal MediaWiki.",
        "config-env-php": "PHP $1 diinstal.",
        "config-env-hhvm": "HHVM $1 telah dipasang.",
-       "config-unicode-using-intl": "Menggunakan [https://pecl.php.net/intl ekstensi PECL intl] untuk normalisasi Unicode.",
+       "config-unicode-using-intl": "Menggunakan [https://php.net/manual/en/book.intl.php ekstensi internasional PHP] untuk normalisasi Unicode.",
        "config-unicode-pure-php-warning": "<strong>Peringatan:</strong> [https://pecl.php.net/intl intl Ekstensi PECL] tidak tersedia untuk menangani normalisasi Unicode, dikembalikan untuk melambatkan implementasi PHP asli.\nApabila Anda menjalankan situs dengan lalu-lintas tinggi, Anda harus membaca [https://www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations normalisasi Unicode].",
        "config-unicode-update-warning": "<strong>Peringatan:</strong> Versi terinstal dari pembungkus normalisasi Unicode menggunakan versi lama pustaka [http://site.icu-project.org/ proyek ICU].\nAnda harus [https://www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations meningkatkan versinya] jika ingin menggunakan Unicode.",
        "config-no-db": "Pengandar basis data yang sesuai tidak ditemukan! Anda perlu menginstal pengandar basis data untuk PHP.\n{{PLURAL:$2|Jenis|Jenis}} basis data yang didukung: $1.\n\nJika Anda mengompilasi PHP sendiri, ubahlah konfigurasinya dengan mengaktifkan klien basis data, misalnya menggunakan <code>./configure --with-mysqli</code>.\nJika Anda menginstal PHP dari paket Debian atau Ubuntu, maka Anda juga perlu menginstal seperti paket <code>php-mysql</code>.",
@@ -95,7 +99,7 @@
        "config-db-host": "Inang basis data:",
        "config-db-host-help": "Jika server basis data Anda berada di server yang berbeda, masukkan nama inang atau alamat IP di sini.\n\nJika Anda menggunakan inang web bersama, penyedia inang Anda harus memberikan nama inang yang benar di dokumentasi mereka.\n\nJika Anda menginstal pada server Windows dan menggunakan MySQL, \"localhost\" mungkin tidak dapat digunakan sebagai nama server. Jika demikian, coba \"127.0.0.1\" untuk alamat IP lokal.\n\nJika Anda menggunakan PostgreSQL, biarkan field ini kosong untuk menghubungkan lewat soket Unix.",
        "config-db-wiki-settings": "Identifikasi wiki ini",
-       "config-db-name": "Nama basis data:",
+       "config-db-name": "Nama basis data (tanpa tanda hubung):",
        "config-db-name-help": "Pilih nama yang mengidentifikasikan wiki Anda.\nNama tersebut tidak boleh mengandung spasi.\n\nJika Anda menggunakan inang web bersama, penyedia inang Anda dapat memberikan Anda nama basis data khusus untuk digunakan atau mengizinkan Anda membuat basis data melalui panel kontrol.",
        "config-db-install-account": "Akun pengguna untuk instalasi",
        "config-db-username": "Nama pengguna basis data:",
        "config-db-account-lock": "Gunakan nama pengguna dan kata sandi yang sama selama operasi normal",
        "config-db-wiki-account": "Akun pengguna untuk operasi normal",
        "config-db-wiki-help": "Masukkan nama pengguna dan sandi yang akan digunakan untuk terhubung ke basis data wiki selama operasi normal.\nJika akun tidak ada, akun instalasi memiliki hak yang memadai, akun pengguna ini akan dibuat dengan hak akses minimum yang diperlukan untuk mengoperasikan wiki.",
-       "config-db-prefix": "Prefiks tabel basis data:",
+       "config-db-prefix": "Prefiks tabel basis data (tanpa tanda hubung):",
        "config-db-prefix-help": "Jika Anda perlu berbagi satu basis data di antara beberapa wiki, atau antara MediaWiki dan aplikasi web lain, Anda dapat memilih untuk menambahkan prefiks terhadap semua nama tabel demi menghindari konflik.\nJangan gunakan spasi.\n\nPrefiks ini biasanya dibiarkan kosong.",
        "config-mysql-old": "MySQL $1 atau versi terbaru diperlukan, Anda menggunakan $2.",
        "config-db-port": "Porta basis data:",
-       "config-db-schema": "Skema untuk MediaWiki",
+       "config-db-schema": "Skema untuk MediaWiki (tanpa tanda hubung):",
        "config-db-schema-help": "Skema ini biasanya berjalan baik.\nUbah hanya jika Anda tahu Anda perlu mengubahnya.",
        "config-pg-test-error": "Tidak dapat terhubung ke basis data <strong>$1</strong>: $2",
        "config-sqlite-dir": "Direktori data SQLite:",
        "config-sqlite-dir-help": "SQLite menyimpan semua data dalam satu berkas.\n\nDirektori yang Anda berikan harus dapat ditulisi oleh server web selama instalasi.\n\nDirektori itu '''tidak''' boleh dapat diakses melalui web, inilah sebabnya kami tidak menempatkannya bersama dengan berkas PHP lain.\n\nPenginstal akan membuat berkas <code>.htaccess</code> bersamaan dengan itu, tetapi jika gagal, orang dapat memperoleh akses ke basis data mentah Anda.\nItu termasuk data mentah pengguna (alamat surel, hash sandi) serta revisi yang dihapus dan data lainnya yang dibatasi pada wiki.\n\nPertimbangkan untuk menempatkan basis data di tempat lain, misalnya di <code>/var/lib/mediawiki/yourwiki</code>.",
-       "config-type-mysql": "MySQL (atau yang kompatibel)",
+       "config-type-mysql": "MariaDB, MySQL, atau yang kompatibel",
        "config-type-postgres": "PostgreSQL",
        "config-type-sqlite": "SQLite",
        "config-support-info": "MediaWiki mendukung sistem basis data berikut:\n\n$1\n\nJika Anda tidak melihat sistem basis data yang Anda gunakan tercantum di bawah ini, ikuti petunjuk terkait di atas untuk mengaktifkan dukungan.",
        "config-dbsupport-mysql": "* [{{int:version-db-mysql-url}} MySQL] adalah target utama MediaWiki dan memiliki dukungan terbaik. MediaWiki juga berjalan dengan [{{int:version-db-mariadb-url}} MariaDB] dan [{{int:version-db-percona-url}} Server Percona], yang kompatibel dengan MySQL. ([https://www.php.net/manual/en/mysql.installation.php Cara mengompilasi PHP dengan dukungan MySQL])",
        "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] adalah sistem basis data sumber terbuka populer sebagai alternatif MySQL.([https://www.php.net/manual/en/pgsql.installation.php Bagaimana mengompilasikan PHP dengan dukungan PostgreSQL])",
-       "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite] adalah sistem basis data yang ringan yang sangat baik dukungannya. ([http://www.php.net/manual/en/pdo.installation.php cara mengompilasi PHP dengan dukungan SQLite], menggunakan PDO)",
+       "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite] adalah sistem basis data yang ringan yang sangat baik dukungannya. ([http://www.php.net/manual/en/pdo.installation.php Bagaimana mengompilasi PHP dengan dukungan SQLite], menggunakan PDO)",
        "config-header-mysql": "Pengaturan MariaDB/MySQL",
        "config-header-postgres": "Pengaturan PostgreSQL",
        "config-header-sqlite": "Pengaturan SQLite",
        "config-missing-db-host": "Anda harus memasukkan nilai untuk \"{{int:config-db-host}}\"",
        "config-invalid-db-name": "Nama basis data \"$1\" tidak sah.\nGunakan hanya huruf ASCII (a-z, A-Z), angka (0-9), garis bawah (_), dan tanda hubung (-).",
        "config-invalid-db-prefix": "Prefiks basis data \"$1\" tidak sah.\nGunakan hanya huruf ASCII (a-z, A-Z), angka (0-9), garis bawah (_), dan tanda hubung (-).",
-       "config-connection-error": "$1.\n\nPeriksa nama inang, pengguna, dan sandi di bawah ini dan coba lagi.",
+       "config-connection-error": "$1.\n\nPeriksa nama inang, pengguna, dan kata sandi dan coba lagi. Jika menggunakan \"localhost\" sebagai inang basis data, coba gunakan \"127.0.0.1\" (atau sebaliknya).",
        "config-invalid-schema": "Skema MediaWiki \"$1\" tidak sah.\nGunakan hanya huruf ASCII (a-z, A-Z), angka (0-9), dan garis bawah (_).",
        "config-postgres-old": "PostgreSQL $1 atau versi terbaru diperlukan, Anda menggunakan $2.",
        "config-sqlite-name-help": "Pilih nama yang mengidentifikasi wiki Anda.\nJangan gunakan spasi atau tanda hubung.\nNama ini akan digunakan untuk nama berkas data SQLite.",
        "config-sqlite-cant-create-db": "Tidak dapat membuat berkas basis data <code>$1</code>.",
        "config-sqlite-fts3-downgrade": "PHP tidak memiliki dukungan FTS3, tabel dituruntarafkan.",
        "config-can-upgrade": "Ada tabel MediaWiki di basis dataini.\nUntuk memperbaruinya ke MediaWiki $1, klik '''Lanjut'''.",
+       "config-upgrade-error": "Terjadi sebuah galat ketika memperbarui tabel MediaWiki dalam basis data Anda.\n\nUntuk informasi lebih lanjut, lihat catatan di atas, untuk mencoba kembali klik <strong>Lanjutkan</strong>.",
        "config-upgrade-done": "Pemutakhiran selesai.\n\nAnda sekarang dapat [$1 mulai menggunakan wiki Anda].\n\nJika Anda ingin membuat ulang berkas <code>LocalSettings.php</code>, klik tombol di bawah ini.\nTindakan ini '''tidak dianjurkan''' kecuali jika Anda mengalami masalah dengan wiki Anda.",
        "config-upgrade-done-no-regenerate": "Pemutakhiran selesai.\n\nAnda sekarang dapat [$1 mulai menggunakan wiki Anda].",
        "config-regenerate": "Regenerasi LocalSettings.php →",
        "config-db-web-create": "Buat akun jika belum ada",
        "config-db-web-no-create-privs": "Akun Anda berikan untuk instalasi tidak memiliki hak yang cukup untuk membuat akun.\nAkun yang Anda berikan harus sudah ada.",
        "config-mysql-engine": "Mesin penyimpanan:",
-       "config-mysql-innodb": "InnoDB",
+       "config-mysql-innodb": "InnoDB (disarankan)",
        "config-mysql-engine-help": "'''InnoDB''' hampir selalu merupakan pilihan terbaik karena memiliki dukungan konkurensi yang baik.\n\n'''MyISAM''' mungkin lebih cepat dalam instalasi pengguna-tunggal atau hanya-baca.\nBasis data MyISAM cenderung lebih sering rusak daripada basis data InnoDB.",
        "config-site-name": "Nama wiki:",
        "config-site-name-help": "Ini akan muncul di bilah judul peramban dan di berbagai tempat lainnya.",
        "config-install-subscribe-fail": "Tidak dapat berlangganan mediawiki-announce: $1",
        "config-install-subscribe-notpossible": "cURL tidak diinstal dan <code>allow_url_fopen</code> tidak tersedia.",
        "config-install-mainpage": "Membuat halaman utama dengan konten bawaan",
+       "config-install-mainpage-exists": "Halaman utama sudah ada, meloncati",
        "config-install-extension-tables": "Pembuatan tabel untuk ekstensi yang diaktifkan",
        "config-install-mainpage-failed": "Tidak dapat membuat halaman utama: $1",
        "config-install-done": "<strong>Selamat!</strong>\nAnda telah berhasil menginstal MediaWiki.\n\nPemasang telah membuat sebuah berkas <code>LocalSettings.php</code>.\nBerkas itu berisi semua setelan Anda.\n\nAnda perlu mengunduh berkas itu dan meletakkannya di direktori instalasi wiki (direktori yang sama dengan index.php). Pengunduhan akan dimulai secara otomatis.\n\nJika pengunduhan tidak terjadi, atau jika Anda membatalkannya, Anda dapat mengulangi pengunduhan dengan mengeklik tautan berikut:\n\n$3\n\n<strong>Catatan</strong>: Jika Anda tidak melakukannya sekarang, berkas konfigurasi yang dihasilkan ini tidak akan tersedia lagi setelah Anda keluar dari proses instalasi tanpa mengunduhnya.\n\nSetelah melakukannya, Anda dapat <strong>[$2 memasuki wiki Anda]</strong>.",
+       "config-install-success": "MediaWiki telah dipasang dengan sukses. Anda dapat mengunjungi <$1$2> untuk melihat wiki ini. Jika Anda memiliki pertanyaan, lihat daftar pertanyaan yang sering ditanyakan: <https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ> atau gunakan salah satu forum yang ada di halaman tersebut.",
+       "config-install-db-success": "Basis data telah sukses diatur",
        "config-download-localsettings": "Unduh <code>LocalSettings.php</code>",
        "config-help": "bantuan",
        "config-help-tooltip": "klik untuk memperluas",
        "config-skins-screenshots": "$1 (tangkapan layar: $2)",
        "config-extensions-requires": "$1 (memerlukan $2)",
        "config-screenshot": "tangkapan layar",
+       "config-extension-not-found": "Tidak dapat menemukan berkas registrasi untuk ekstensi \"$1\"",
        "mainpagetext": "<strong>MediaWiki telah terpasang dengan sukses.</strong>",
        "mainpagedocfooter": "Konsultasikan [https://www.mediawiki.org/wiki/Help:Contents Panduan Pengguna] untuk cara penggunaan perangkat lunak wiki ini.\n\n== Memulai ==\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Daftar pengaturan konfigurasi]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Pertanyaan yang sering diajukan mengenai MediaWiki]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Milis rilis MediaWiki]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Pelokalan MediaWiki untuk bahasa Anda]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam Belajar bagaimana menghadapi spam di wiki lokal]"
 }
index a6e9ddb..3d2311e 100644 (file)
        "config-welcome": "=== Verificări ale mediului ===\nVerificări de bază vor fi efectuate pentru a vedea dacă este potrivit pentru instalarea MediaWiki.\nNu uitați să includeți aceste informații dacă doriți asistență pentru completarea instalării.",
        "config-welcome-section-copyright": "=== Drepturi de autor și termeni ===\n\n$1\n\nAcest program este un software liber; îl puteți redistribui și / sau modifica în conformitate cu termenii Licenței Publice Generale GNU, publicată de Fundația pentru Software Liber; fie versiunea 2 a Licenței, fie (la alegere) orice versiune ulterioară.\nAcest program este distribuit în speranța că va fi util, dar <strong>fără nicio garanție</strong>; fără nici măcar garanția implicită de <strong>vandabilitate</strong> sau <strong>fitness pentru un anumit scop</strong>.\nPentru mai multe detalii, consultați Licența publică generală GNU.\nAr fi trebuit să fi primit [$2 o copie a GNU General Public License] împreună cu acest program; dacă nu, scrieți la Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, SUA, sau [https://www.gnu.org/copyleft/gpl.html citiți-o online] .",
        "config-sidebar": "* [https://www.mediawiki.org Acasă MediaWiki]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents User's Guide]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents Administrator's Guide]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ FAQ]\n----\n* <doclink href=Readme>Read me</doclink>\n* <doclink href=ReleaseNotes>Release notes</doclink>\n* <doclink href=Copying>Copying</doclink>\n* <doclink href=UpgradeDoc>Upgrading</doclink>",
+       "config-sidebar-readme": "Read me",
+       "config-sidebar-relnotes": "Note de lansare",
+       "config-sidebar-license": "Copiere",
+       "config-sidebar-upgrade": "Actualizare",
        "config-env-good": "Verificarea mediului a fost efectuată cu succes.\nPuteți instala MediaWiki.",
        "config-env-bad": "Verificarea mediului a fost efectuată.\nNu puteți instala MediaWiki.",
        "config-env-php": "PHP $1 este instalat.",
        "config-env-hhvm": "HHVM $1 este instalat.",
-       "config-unicode-using-intl": "Utilizarea extensiei [https://pecl.php.net/intl intl PECL] pentru normalizarea Unicode.",
-       "config-unicode-pure-php-warning": "<strong>Atenție:</strong> Extensia [https://pecl.php.net/intl intl PECL] nu este disponibilă pentru a face față normalizării Unicode, revenind la o implementare lentă pur PHP.\nDacă rulați un site cu trafic ridicat, ar trebui să citiți puțin în [https://www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations Normalizarea Unicode].",
+       "config-unicode-using-intl": "Se folosește extensia [https://php.net/manual/en/book.intl.php PHP intl] pentru normalizarea Unicode.",
+       "config-unicode-pure-php-warning": "<strong>Atenție:</strong> Extensia [https://php.net/manual/en/book.intl.php PHP intl] nu este disponibilă pentru a procesa normalizarea Unicode, se folosește o implementare lentă pur PHP.\nDacă rulați un site cu trafic ridicat, ar trebui să citiți despre [https://www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations Normalizarea Unicode].",
        "config-unicode-update-warning": "<strong>Avertisment:</strong> Versiunea instalată a pachetului de normalizare Unicode utilizează o versiune mai veche a bibliotecii [http://site.icu-project.org/ proiectul ICU].\nAr trebui să faceți upgrade [https://www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations] dacă sunteți preocupat de utilizarea Unicode.",
        "config-no-db": "Nu am găsit un driver de bază de date potrivit! Trebuie să instalați un driver de bază de date pentru PHP.\nUrmătoarea bază de date {{PLURAL:$2|tip este|tipuri sunt}} este acceptată: $1.\nDacă ați compilat singuri PHP, reconfigurați-l cu un client de bază de date activat, de exemplu, utilizând <code>./ configure --with-mysqli</code>.\nDacă ați instalat PHP dintr-un pachet Debian sau Ubuntu, atunci trebuie să instalați, de exemplu, pachetul <code>php-mysql</code>",
-       "config-outdated-sqlite": "<strong>Atenție:</strong> ai SQLite $1, care este mai mic decât minimul necesar pentru versiunea $2. SQLite va fi nedisponibil.",
+       "config-outdated-sqlite": "<strong>Atenție:</strong> aveții SQLite $2, care este mai mic decât versiunea minimă $1. SQLite nu va fi disponibil.",
        "config-no-fts3": "<strong>Atenție:</strong> SQLite este compus fără [//sqlite.org/fts3.html modulu FTS3], caută caracteristici care nu vor fi disponibile la finalul acesta.",
        "config-pcre-old": "<strong>Fatal:</> PCRE $1 sau mai târziu este necesar este necesar. \nPHP tău este binar este legat de PCRE $2. \n[https://www.mediawiki.org/wiki/Manual:Errors_and_symptoms/PCRE Mai multe informații].",
        "config-pcre-no-utf8": "<strong>Fatal:</strong> Modul PCRE al PHP pare să fie compilat fără suport PCRE_UTF8.\nMediaWiki necesită ca suportul UTF-8 să funcționeze corect.",
        "config-admin-name": "Numele dumneavoastră de utilizator:",
        "config-admin-password": "Parolă:",
        "config-admin-password-confirm": "Parola, din nou:",
+       "config-admin-name-blank": "Introduceți numele de utilizator al administratorului.",
        "config-admin-password-blank": "Introduceți o parolă pentru contul de administrator.",
        "config-admin-password-mismatch": "Cele două parole introduse nu corespund.",
        "config-admin-email": "Adresa de e-mail:",
        "config-license-pd": "Domeniu public",
        "config-license-cc-choose": "Alegeți o licență Creative Commons personalizată",
        "config-email-settings": "Setări pentru e-mail",
+       "config-enable-email": "Permiteți trimiterea de e-mail",
+       "config-email-user": "Permiteți e-mailurile între utilizatori",
        "config-email-usertalk": "Activați notificările pentru pagina de discuții a utilizatorului",
        "config-upload-settings": "Încărcare de imagini și fișiere",
        "config-upload-deleted": "Director pentru fișierele șterse:",
index 9bab12f..3ea2df6 100644 (file)
        "config-memcached-servers": "Memcached-serveri:",
        "config-memcached-help": "Lista IP adresa za uporabu u Memcached.\nTreba da se navede jednu u svaki red, kao i port što će se koristiti. Na primer:\n 127.0.0.1:11211\n 192.168.1.25:1234",
        "config-memcache-needservers": "Odabrali ste Memcached kao vaš tip međuspremnika (keša), ali niste naveli nijedan server.",
-       "mainpagetext": "<strong>MediaWiki je uspješno instaliran.</strong>",
+       "config-install-step-done": "gotovo",
+       "config-install-step-failed": "nije uspjelo",
+       "config-install-extensions": "Uključujem dodatke",
+       "config-install-database": "Postavljam bazu podataka",
+       "config-install-schema": "Pravim šemu",
+       "config-install-pg-schema-not-exist": "PostgreSQL-šema ne postoji.",
+       "config-install-pg-schema-failed": "Pravljenje natabela nije uspelo.\nUvjerite se da korisnik „$1” može da zapisuje u šemi „$2”.",
+       "config-install-pg-commit": "Usproveđivanje promjena",
+       "config-install-user": "Pravim korisnika baze podataka",
+       "config-install-user-alreadyexists": "Korisnik \"$1\" već postoji",
+       "config-install-user-create-failed": "Pravljenje korisnika \"$1\" nije uspjelo: $2",
+       "config-install-user-grant-failed": "Dodjeljivanje dozvola korisniku \"$1\" nije uspjelo: $2",
+       "config-install-user-missing": "Navedeni korisnik \"$1\" ne postoji.",
+       "config-install-user-missing-create": "Navedeni korisnik \"$1\" ne postoji.\nAko želite da ga otvorite, štiklirajte mogućnost „napravi račun”.",
+       "config-install-tables": "Pravim tabele",
+       "config-install-tables-exist": "<strong>Upozorenje:</strong> Izgleda da MediaWiki tabele već postoje.\nPreskočim pravljenje.",
+       "config-install-tables-failed": "<strong>Greška:</strong> Pravljenje tabele nije uspjelo zbog sljedeće greške: $1",
+       "config-install-interwiki": "Popunjavam predodređene međuprojektne tabele",
+       "config-install-interwiki-list": "Nisam mogao pronaći datoteku <code>interwiki.list</code>.",
+       "config-install-interwiki-exists": "<strong>Upozorenje:</strong> Tabela međuwikija već ima unose.\nPreskočim podrazumevano-zadanu listu.",
+       "config-install-stats": "Pokrećem statistiku",
+       "config-install-keys": "Generisanje tajnih ključeva",
+       "config-install-updates": "Spriječi vršenje nepotrebnih podnova",
+       "config-install-updates-failed": "<strong>Greška:</strong> Umetanje podnovnih klučeva u tabele nije uspjelo, zbog sljedeće greške: $1",
+       "config-install-sysop": "Otvaranje korisničkog računa administratora",
+       "config-install-subscribe-fail": "Nije moguće Vas pretplatiti se na izvješćenje mediawiki-announce: $1",
+       "config-install-subscribe-notpossible": "cURL nije instaliran, a <code>allow_url_fopen</code> nije dostupno.",
+       "config-install-mainpage": "Pravim početnu stranicu sa standardnim sadržajem",
+       "config-install-mainpage-exists": "Početna strana već postoji. Prelazim na sljedeće.",
+       "config-install-extension-tables": "Izrada tabela za omogućene dodatke",
+       "config-install-mainpage-failed": "Nisam mogao umetnuti početnu stranu: $1",
+       "config-download-localsettings": "Preuzmi <code>LocalSettings.php</code>",
+       "config-help": "pomoć",
+       "config-help-tooltip": "kliknite da rasklopite",
+       "config-nofile": "Datoteka \"$1\" nije pronađena. Da nije obrisana?",
+       "config-skins-screenshots": "$1 (ekr. snimci: $2)",
+       "config-extensions-requires": "$1 (zahtjeva $2)",
+       "config-screenshot": "ekranski snimak",
+       "config-extension-not-found": "Nisam mogao naći datoteku registracije za dodatak „$1”",
+       "config-extension-dependency": "Naišao na grešku sa zavisnošću pri instaliranju dodatka „$1”: $2",
+       "mainpagetext": "<strong>MediaWiki je instaliran.</strong>",
        "mainpagedocfooter": "Za informacije o korištenju wiki softvera konzultirajte [https://meta.wikimedia.org/wiki/Help:Contents Vodič za korisnike].\n\n== Uvod u rad ==\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Lista konfiguracije postavki]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki FAQ]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista primatelja izdanja MediaWikija]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Lokalizirajte MediaWiki za svoj jezik]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam Saznajte kako se boriti protiv spama na svojem wikiju]"
 }
index d449e8a..4be41a2 100644 (file)
@@ -424,7 +424,7 @@ class JobQueueDB extends JobQueue {
                        if ( $dbw->getType() === 'mysql' ) {
                                // Per https://bugs.mysql.com/bug.php?id=6980, we can't use subqueries on the
                                // same table being changed in an UPDATE query in MySQL (gives Error: 1093).
-                               // Oracle and Postgre have no such limitation. However, MySQL offers an
+                               // Postgres has no such limitation. However, MySQL offers an
                                // alternative here by supporting ORDER BY + LIMIT for UPDATE queries.
                                $dbw->query( "UPDATE {$dbw->tableName( 'job' )} " .
                                        "SET " .
index 21d8c7e..adb4221 100644 (file)
@@ -279,16 +279,26 @@ class JobRunner implements LoggerAwareInterface {
                ] );
                $this->debugCallback( $msg );
 
+               // Clear out title cache data from prior snapshots
+               // (e.g. from before JobRunner was invoked in this process)
+               MediaWikiServices::getInstance()->getLinkCache()->clear();
+
                // Run the job...
                $rssStart = $this->getMaxRssKb();
                $jobStartTime = microtime( true );
                try {
                        $fnameTrxOwner = get_class( $job ) . '::run'; // give run() outer scope
-                       if ( !$job->hasExecutionFlag( $job::JOB_NO_EXPLICIT_TRX_ROUND ) ) {
-                               $lbFactory->beginMasterChanges( $fnameTrxOwner );
+                       // Flush any pending changes left over from an implicit transaction round
+                       if ( $job->hasExecutionFlag( $job::JOB_NO_EXPLICIT_TRX_ROUND ) ) {
+                               $lbFactory->commitMasterChanges( $fnameTrxOwner ); // new implicit round
+                       } else {
+                               $lbFactory->beginMasterChanges( $fnameTrxOwner ); // new explicit round
                        }
+                       // Clear any stale REPEATABLE-READ snapshots from replica DB connections
+                       $lbFactory->flushReplicaSnapshots( $fnameTrxOwner );
                        $status = $job->run();
                        $error = $job->getLastError();
+                       // Commit all pending changes from this job
                        $this->commitMasterChanges( $lbFactory, $job, $fnameTrxOwner );
                        // Run any deferred update tasks; doUpdates() manages transactions itself
                        DeferredUpdates::doUpdates();
@@ -304,12 +314,6 @@ class JobRunner implements LoggerAwareInterface {
                        MWExceptionHandler::logException( $e );
                }
 
-               // Commit all outstanding connections that are in a transaction
-               // to get a fresh repeatable read snapshot on every connection.
-               // Note that jobs are still responsible for handling replica DB lag.
-               $lbFactory->flushReplicaSnapshots( __METHOD__ );
-               // Clear out title cache data from prior snapshots
-               MediaWikiServices::getInstance()->getLinkCache()->clear();
                $timeMs = intval( ( microtime( true ) - $jobStartTime ) * 1000 );
                $rssEnd = $this->getMaxRssKb();
 
diff --git a/includes/language/ConverterRule.php b/includes/language/ConverterRule.php
new file mode 100644 (file)
index 0000000..4a330ad
--- /dev/null
@@ -0,0 +1,498 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Language
+ */
+
+/**
+ * Parser for rules of language conversion, parse rules in -{ }- tag.
+ * @ingroup Language
+ * @author fdcn <fdcn64@gmail.com>, PhiLiP <philip.npc@gmail.com>
+ */
+class ConverterRule {
+       public $mText; // original text in -{text}-
+       public $mConverter; // LanguageConverter object
+       public $mRuleDisplay = '';
+       public $mRuleTitle = false;
+       public $mRules = ''; // string : the text of the rules
+       public $mRulesAction = 'none';
+       public $mFlags = [];
+       public $mVariantFlags = [];
+       public $mConvTable = [];
+       public $mBidtable = []; // array of the translation in each variant
+       public $mUnidtable = []; // array of the translation in each variant
+
+       /**
+        * @param string $text The text between -{ and }-
+        * @param LanguageConverter $converter
+        */
+       public function __construct( $text, $converter ) {
+               $this->mText = $text;
+               $this->mConverter = $converter;
+       }
+
+       /**
+        * Check if variants array in convert array.
+        *
+        * @param array|string $variants Variant language code
+        * @return string Translated text
+        */
+       public function getTextInBidtable( $variants ) {
+               $variants = (array)$variants;
+               if ( !$variants ) {
+                       return false;
+               }
+               foreach ( $variants as $variant ) {
+                       if ( isset( $this->mBidtable[$variant] ) ) {
+                               return $this->mBidtable[$variant];
+                       }
+               }
+               return false;
+       }
+
+       /**
+        * Parse flags with syntax -{FLAG| ... }-
+        * @private
+        */
+       function parseFlags() {
+               $text = $this->mText;
+               $flags = [];
+               $variantFlags = [];
+
+               $sepPos = strpos( $text, '|' );
+               if ( $sepPos !== false ) {
+                       $validFlags = $this->mConverter->mFlags;
+                       $f = StringUtils::explode( ';', substr( $text, 0, $sepPos ) );
+                       foreach ( $f as $ff ) {
+                               $ff = trim( $ff );
+                               if ( isset( $validFlags[$ff] ) ) {
+                                       $flags[$validFlags[$ff]] = true;
+                               }
+                       }
+                       $text = strval( substr( $text, $sepPos + 1 ) );
+               }
+
+               if ( !$flags ) {
+                       $flags['S'] = true;
+               } elseif ( isset( $flags['R'] ) ) {
+                       $flags = [ 'R' => true ];// remove other flags
+               } elseif ( isset( $flags['N'] ) ) {
+                       $flags = [ 'N' => true ];// remove other flags
+               } elseif ( isset( $flags['-'] ) ) {
+                       $flags = [ '-' => true ];// remove other flags
+               } elseif ( count( $flags ) == 1 && isset( $flags['T'] ) ) {
+                       $flags['H'] = true;
+               } elseif ( isset( $flags['H'] ) ) {
+                       // replace A flag, and remove other flags except T
+                       $temp = [ '+' => true, 'H' => true ];
+                       if ( isset( $flags['T'] ) ) {
+                               $temp['T'] = true;
+                       }
+                       if ( isset( $flags['D'] ) ) {
+                               $temp['D'] = true;
+                       }
+                       $flags = $temp;
+               } else {
+                       if ( isset( $flags['A'] ) ) {
+                               $flags['+'] = true;
+                               $flags['S'] = true;
+                       }
+                       if ( isset( $flags['D'] ) ) {
+                               unset( $flags['S'] );
+                       }
+                       // try to find flags like "zh-hans", "zh-hant"
+                       // allow syntaxes like "-{zh-hans;zh-hant|XXXX}-"
+                       $variantFlags = array_intersect( array_keys( $flags ), $this->mConverter->mVariants );
+                       if ( $variantFlags ) {
+                               $variantFlags = array_flip( $variantFlags );
+                               $flags = [];
+                       }
+               }
+               $this->mVariantFlags = $variantFlags;
+               $this->mRules = $text;
+               $this->mFlags = $flags;
+       }
+
+       /**
+        * Generate conversion table.
+        * @private
+        */
+       function parseRules() {
+               $rules = $this->mRules;
+               $bidtable = [];
+               $unidtable = [];
+               $variants = $this->mConverter->mVariants;
+               $varsep_pattern = $this->mConverter->getVarSeparatorPattern();
+
+               // Split according to $varsep_pattern, but ignore semicolons from HTML entities
+               $rules = preg_replace( '/(&[#a-zA-Z0-9]+);/', "$1\x01", $rules );
+               $choice = preg_split( $varsep_pattern, $rules );
+               $choice = str_replace( "\x01", ';', $choice );
+
+               foreach ( $choice as $c ) {
+                       $v = explode( ':', $c, 2 );
+                       if ( count( $v ) != 2 ) {
+                               // syntax error, skip
+                               continue;
+                       }
+                       $to = trim( $v[1] );
+                       $v = trim( $v[0] );
+                       $u = explode( '=>', $v, 2 );
+                       $vv = $this->mConverter->validateVariant( $v );
+                       // if $to is empty (which is also used as $from in bidtable),
+                       // strtr() could return a wrong result.
+                       if ( count( $u ) == 1 && $to !== '' && $vv ) {
+                               $bidtable[$vv] = $to;
+                       } elseif ( count( $u ) == 2 ) {
+                               $from = trim( $u[0] );
+                               $v = trim( $u[1] );
+                               $vv = $this->mConverter->validateVariant( $v );
+                               // if $from is empty, strtr() could return a wrong result.
+                               if ( array_key_exists( $vv, $unidtable )
+                                       && !is_array( $unidtable[$vv] )
+                                       && $from !== ''
+                                       && $vv ) {
+                                       $unidtable[$vv] = [ $from => $to ];
+                               } elseif ( $from !== '' && $vv ) {
+                                       $unidtable[$vv][$from] = $to;
+                               }
+                       }
+                       // syntax error, pass
+                       if ( !isset( $this->mConverter->mVariantNames[$vv] ) ) {
+                               $bidtable = [];
+                               $unidtable = [];
+                               break;
+                       }
+               }
+               $this->mBidtable = $bidtable;
+               $this->mUnidtable = $unidtable;
+       }
+
+       /**
+        * @private
+        *
+        * @return string
+        */
+       function getRulesDesc() {
+               $codesep = $this->mConverter->mDescCodeSep;
+               $varsep = $this->mConverter->mDescVarSep;
+               $text = '';
+               foreach ( $this->mBidtable as $k => $v ) {
+                       $text .= $this->mConverter->mVariantNames[$k] . "$codesep$v$varsep";
+               }
+               foreach ( $this->mUnidtable as $k => $a ) {
+                       foreach ( $a as $from => $to ) {
+                               $text .= $from . '⇒' . $this->mConverter->mVariantNames[$k] .
+                                       "$codesep$to$varsep";
+                       }
+               }
+               return $text;
+       }
+
+       /**
+        * Parse rules conversion.
+        * @private
+        *
+        * @param string $variant
+        *
+        * @return string
+        */
+       function getRuleConvertedStr( $variant ) {
+               $bidtable = $this->mBidtable;
+               $unidtable = $this->mUnidtable;
+
+               if ( count( $bidtable ) + count( $unidtable ) == 0 ) {
+                       return $this->mRules;
+               } else {
+                       // display current variant in bidirectional array
+                       $disp = $this->getTextInBidtable( $variant );
+                       // or display current variant in fallbacks
+                       if ( $disp === false ) {
+                               $disp = $this->getTextInBidtable(
+                                       $this->mConverter->getVariantFallbacks( $variant ) );
+                       }
+                       // or display current variant in unidirectional array
+                       if ( $disp === false && array_key_exists( $variant, $unidtable ) ) {
+                               $disp = array_values( $unidtable[$variant] )[0];
+                       }
+                       // or display first text under disable manual convert
+                       if ( $disp === false && $this->mConverter->mManualLevel[$variant] == 'disable' ) {
+                               if ( count( $bidtable ) > 0 ) {
+                                       $disp = array_values( $bidtable )[0];
+                               } else {
+                                       $disp = array_values( array_values( $unidtable )[0] )[0];
+                               }
+                       }
+                       return $disp;
+               }
+       }
+
+       /**
+        * Similar to getRuleConvertedStr(), but this prefers to use original
+        * page title if $variant === $this->mConverter->mMainLanguageCode
+        * and may return false in this case (so this title conversion rule
+        * will be ignored and the original title is shown).
+        *
+        * @since 1.22
+        * @param string $variant The variant code to display page title in
+        * @return string|bool The converted title or false if just page name
+        */
+       function getRuleConvertedTitle( $variant ) {
+               if ( $variant === $this->mConverter->mMainLanguageCode ) {
+                       // If a string targeting exactly this variant is set,
+                       // use it. Otherwise, just return false, so the real
+                       // page name can be shown (and because variant === main,
+                       // there'll be no further automatic conversion).
+                       $disp = $this->getTextInBidtable( $variant );
+                       if ( $disp ) {
+                               return $disp;
+                       }
+                       if ( array_key_exists( $variant, $this->mUnidtable ) ) {
+                               $disp = array_values( $this->mUnidtable[$variant] )[0];
+                       }
+                       // Assigned above or still false.
+                       return $disp;
+               } else {
+                       return $this->getRuleConvertedStr( $variant );
+               }
+       }
+
+       /**
+        * Generate conversion table for all text.
+        * @private
+        */
+       function generateConvTable() {
+               // Special case optimisation
+               if ( !$this->mBidtable && !$this->mUnidtable ) {
+                       $this->mConvTable = [];
+                       return;
+               }
+
+               $bidtable = $this->mBidtable;
+               $unidtable = $this->mUnidtable;
+               $manLevel = $this->mConverter->mManualLevel;
+
+               $vmarked = [];
+               foreach ( $this->mConverter->mVariants as $v ) {
+                       /* for bidirectional array
+                               fill in the missing variants, if any,
+                               with fallbacks */
+                       if ( !isset( $bidtable[$v] ) ) {
+                               $variantFallbacks =
+                                       $this->mConverter->getVariantFallbacks( $v );
+                               $vf = $this->getTextInBidtable( $variantFallbacks );
+                               if ( $vf ) {
+                                       $bidtable[$v] = $vf;
+                               }
+                       }
+
+                       if ( isset( $bidtable[$v] ) ) {
+                               foreach ( $vmarked as $vo ) {
+                                       // use syntax: -{A|zh:WordZh;zh-tw:WordTw}-
+                                       // or -{H|zh:WordZh;zh-tw:WordTw}-
+                                       // or -{-|zh:WordZh;zh-tw:WordTw}-
+                                       // to introduce a custom mapping between
+                                       // words WordZh and WordTw in the whole text
+                                       if ( $manLevel[$v] == 'bidirectional' ) {
+                                               $this->mConvTable[$v][$bidtable[$vo]] = $bidtable[$v];
+                                       }
+                                       if ( $manLevel[$vo] == 'bidirectional' ) {
+                                               $this->mConvTable[$vo][$bidtable[$v]] = $bidtable[$vo];
+                                       }
+                               }
+                               $vmarked[] = $v;
+                       }
+                       /* for unidirectional array fill to convert tables */
+                       if ( ( $manLevel[$v] == 'bidirectional' || $manLevel[$v] == 'unidirectional' )
+                               && isset( $unidtable[$v] )
+                       ) {
+                               if ( isset( $this->mConvTable[$v] ) ) {
+                                       $this->mConvTable[$v] = $unidtable[$v] + $this->mConvTable[$v];
+                               } else {
+                                       $this->mConvTable[$v] = $unidtable[$v];
+                               }
+                       }
+               }
+       }
+
+       /**
+        * Parse rules and flags.
+        * @param string|null $variant Variant language code
+        */
+       public function parse( $variant = null ) {
+               if ( !$variant ) {
+                       $variant = $this->mConverter->getPreferredVariant();
+               }
+
+               $this->parseFlags();
+               $flags = $this->mFlags;
+
+               // convert to specified variant
+               // syntax: -{zh-hans;zh-hant[;...]|<text to convert>}-
+               if ( $this->mVariantFlags ) {
+                       // check if current variant in flags
+                       if ( isset( $this->mVariantFlags[$variant] ) ) {
+                               // then convert <text to convert> to current language
+                               $this->mRules = $this->mConverter->autoConvert( $this->mRules,
+                                       $variant );
+                       } else {
+                               // if current variant no in flags,
+                               // then we check its fallback variants.
+                               $variantFallbacks =
+                                       $this->mConverter->getVariantFallbacks( $variant );
+                               if ( is_array( $variantFallbacks ) ) {
+                                       foreach ( $variantFallbacks as $variantFallback ) {
+                                               // if current variant's fallback exist in flags
+                                               if ( isset( $this->mVariantFlags[$variantFallback] ) ) {
+                                                       // then convert <text to convert> to fallback language
+                                                       $this->mRules =
+                                                               $this->mConverter->autoConvert( $this->mRules,
+                                                                       $variantFallback );
+                                                       break;
+                                               }
+                                       }
+                               }
+                       }
+                       $this->mFlags = $flags = [ 'R' => true ];
+               }
+
+               if ( !isset( $flags['R'] ) && !isset( $flags['N'] ) ) {
+                       // decode => HTML entities modified by Sanitizer::removeHTMLtags
+                       $this->mRules = str_replace( '=&gt;', '=>', $this->mRules );
+                       $this->parseRules();
+               }
+               $rules = $this->mRules;
+
+               if ( !$this->mBidtable && !$this->mUnidtable ) {
+                       if ( isset( $flags['+'] ) || isset( $flags['-'] ) ) {
+                               // fill all variants if text in -{A/H/-|text}- is non-empty but without rules
+                               if ( $rules !== '' ) {
+                                       foreach ( $this->mConverter->mVariants as $v ) {
+                                               $this->mBidtable[$v] = $rules;
+                                       }
+                               }
+                       } elseif ( !isset( $flags['N'] ) && !isset( $flags['T'] ) ) {
+                               $this->mFlags = $flags = [ 'R' => true ];
+                       }
+               }
+
+               $this->mRuleDisplay = false;
+               foreach ( $flags as $flag => $unused ) {
+                       switch ( $flag ) {
+                               case 'R':
+                                       // if we don't do content convert, still strip the -{}- tags
+                                       $this->mRuleDisplay = $rules;
+                                       break;
+                               case 'N':
+                                       // process N flag: output current variant name
+                                       $ruleVar = trim( $rules );
+                                       $this->mRuleDisplay = $this->mConverter->mVariantNames[$ruleVar] ?? '';
+                                       break;
+                               case 'D':
+                                       // process D flag: output rules description
+                                       $this->mRuleDisplay = $this->getRulesDesc();
+                                       break;
+                               case 'H':
+                                       // process H,- flag or T only: output nothing
+                                       $this->mRuleDisplay = '';
+                                       break;
+                               case '-':
+                                       $this->mRulesAction = 'remove';
+                                       $this->mRuleDisplay = '';
+                                       break;
+                               case '+':
+                                       $this->mRulesAction = 'add';
+                                       $this->mRuleDisplay = '';
+                                       break;
+                               case 'S':
+                                       $this->mRuleDisplay = $this->getRuleConvertedStr( $variant );
+                                       break;
+                               case 'T':
+                                       $this->mRuleTitle = $this->getRuleConvertedTitle( $variant );
+                                       $this->mRuleDisplay = '';
+                                       break;
+                               default:
+                                       // ignore unknown flags (but see error case below)
+                       }
+               }
+               if ( $this->mRuleDisplay === false ) {
+                       $this->mRuleDisplay = '<span class="error">'
+                               . wfMessage( 'converter-manual-rule-error' )->inContentLanguage()->escaped()
+                               . '</span>';
+               }
+
+               $this->generateConvTable();
+       }
+
+       /**
+        * Checks if there are conversion rules.
+        * @return bool
+        */
+       public function hasRules() {
+               return $this->mRules !== '';
+       }
+
+       /**
+        * Get display text on markup -{...}-
+        * @return string
+        */
+       public function getDisplay() {
+               return $this->mRuleDisplay;
+       }
+
+       /**
+        * Get converted title.
+        * @return string
+        */
+       public function getTitle() {
+               return $this->mRuleTitle;
+       }
+
+       /**
+        * Return how deal with conversion rules.
+        * @return string
+        */
+       public function getRulesAction() {
+               return $this->mRulesAction;
+       }
+
+       /**
+        * Get conversion table. (bidirectional and unidirectional
+        * conversion table)
+        * @return array
+        */
+       public function getConvTable() {
+               return $this->mConvTable;
+       }
+
+       /**
+        * Get conversion rules string.
+        * @return string
+        */
+       public function getRules() {
+               return $this->mRules;
+       }
+
+       /**
+        * Get conversion flags.
+        * @return array
+        */
+       public function getFlags() {
+               return $this->mFlags;
+       }
+}
index 593e617..c05dc28 100644 (file)
@@ -228,7 +228,7 @@ class FSFileBackend extends FileBackendStore {
                }
 
                if ( !empty( $params['async'] ) ) { // deferred
-                       $tempFile = TempFSFile::factory( 'create_', 'tmp', $this->tmpDirectory );
+                       $tempFile = $this->tmpFileFactory->newTempFSFile( 'create_', 'tmp' );
                        if ( !$tempFile ) {
                                $status->fatal( 'backend-fail-create', $params['dst'] );
 
@@ -688,7 +688,7 @@ class FSFileBackend extends FileBackendStore {
                        } else {
                                // Create a new temporary file with the same extension...
                                $ext = FileBackend::extensionFromPath( $src );
-                               $tmpFile = TempFSFile::factory( 'localcopy_', $ext, $this->tmpDirectory );
+                               $tmpFile = $this->tmpFileFactory->newTempFSFile( 'localcopy_', $ext );
                                if ( !$tmpFile ) {
                                        $tmpFiles[$src] = null;
                                } else {
@@ -795,8 +795,16 @@ class FSFileBackend extends FileBackendStore {
         * Listen for E_WARNING errors and track whether any happen
         */
        protected function trapWarnings() {
-               $this->hadWarningErrors[] = false; // push to stack
-               set_error_handler( [ $this, 'handleWarning' ], E_WARNING );
+               // push to stack
+               $this->hadWarningErrors[] = false;
+               set_error_handler( function ( $errno, $errstr ) {
+                       // more detailed error logging
+                       $this->logger->error( $errstr );
+                       $this->hadWarningErrors[count( $this->hadWarningErrors ) - 1] = true;
+
+                       // suppress from PHP handler
+                       return true;
+               }, E_WARNING );
        }
 
        /**
@@ -805,20 +813,9 @@ class FSFileBackend extends FileBackendStore {
         * @return bool
         */
        protected function untrapWarnings() {
-               restore_error_handler(); // restore previous handler
-               return array_pop( $this->hadWarningErrors ); // pop from stack
-       }
-
-       /**
-        * @param int $errno
-        * @param string $errstr
-        * @return bool
-        * @private
-        */
-       public function handleWarning( $errno, $errstr ) {
-               $this->logger->error( $errstr ); // more detailed error logging
-               $this->hadWarningErrors[count( $this->hadWarningErrors ) - 1] = true;
-
-               return true; // suppress from PHP handler
+               // restore previous handler
+               restore_error_handler();
+               // pop from stack
+               return array_pop( $this->hadWarningErrors );
        }
 }
index f65619f..4cacb7a 100644 (file)
@@ -27,6 +27,7 @@
  * @file
  * @ingroup FileBackend
  */
+use MediaWiki\FileBackend\FSFile\TempFSFileFactory;
 use Psr\Log\LoggerAwareInterface;
 use Psr\Log\LoggerInterface;
 use Wikimedia\ScopedCallback;
@@ -106,8 +107,8 @@ abstract class FileBackend implements LoggerAwareInterface {
        /** @var int How many operations can be done in parallel */
        protected $concurrency;
 
-       /** @var string Temporary file directory */
-       protected $tmpDirectory;
+       /** @var TempFSFileFactory */
+       protected $tmpFileFactory;
 
        /** @var LockManager */
        protected $lockManager;
@@ -152,8 +153,10 @@ abstract class FileBackend implements LoggerAwareInterface {
         *   - parallelize : When to do file operations in parallel (when possible).
         *      Allowed values are "implicit", "explicit" and "off".
         *   - concurrency : How many file operations can be done in parallel.
-        *   - tmpDirectory : Directory to use for temporary files. If this is not set or null,
-        *      then the backend will try to discover a usable temporary directory.
+        *   - tmpDirectory : Directory to use for temporary files.
+        *   - tmpFileFactory : Optional TempFSFileFactory object. Only has an effect if tmpDirectory is
+        *      not set. If both are unset or null, then the backend will try to discover a usable
+        *      temporary directory.
         *   - obResetFunc : alternative callback to clear the output buffer
         *   - streamMimeFunc : alternative method to determine the content type from the path
         *   - logger : Optional PSR logger object.
@@ -193,7 +196,12 @@ abstract class FileBackend implements LoggerAwareInterface {
                }
                $this->logger = $config['logger'] ?? new NullLogger();
                $this->statusWrapper = $config['statusWrapper'] ?? null;
-               $this->tmpDirectory = $config['tmpDirectory'] ?? null;
+               // tmpDirectory gets precedence for backward compatibility
+               if ( isset( $config['tmpDirectory'] ) ) {
+                       $this->tmpFileFactory = new TempFSFileFactory( $config['tmpDirectory'] );
+               } else {
+                       $this->tmpFileFactory = $config['tmpFileFactory'] ?? new TempFSFileFactory();
+               }
        }
 
        public function setLogger( LoggerInterface $logger ) {
@@ -412,7 +420,7 @@ abstract class FileBackend implements LoggerAwareInterface {
         *
         * The StatusValue will be "OK" unless:
         *   - a) unexpected operation errors occurred (network partitions, disk full...)
-        *   - b) significant operation errors occurred and 'force' was not set
+        *   - b) predicted operation errors occurred and 'force' was not set
         *
         * @param array $ops List of operations to execute in order
         * @param array $opts Batch operation options
index f92d5fa..27ad870 100644 (file)
@@ -88,7 +88,7 @@ class FileBackendMultiWrite extends FileBackend {
         *                      any checks from "syncChecks" are still synchronous.
         *
         * @param array $config
-        * @throws FileBackendError
+        * @throws LogicException
         */
        public function __construct( array $config ) {
                parent::__construct( $config );
@@ -178,9 +178,8 @@ class FileBackendMultiWrite extends FileBackend {
                $masterStatus = $mbe->doOperations( $realOps, $opts );
                $status->merge( $masterStatus );
                // Propagate the operations to the clone backends if there were no unexpected errors
-               // and if there were either no expected errors or if the 'force' option was used.
-               // However, if nothing succeeded at all, then don't replicate any of the operations.
-               // If $ops only had one operation, this might avoid backend sync inconsistencies.
+               // and everything didn't fail due to predicted errors. If $ops only had one operation,
+               // this might avoid backend sync inconsistencies.
                if ( $masterStatus->isOK() && $masterStatus->successCount > 0 ) {
                        foreach ( $this->backends as $index => $backend ) {
                                if ( $index === $this->masterIndex ) {
@@ -271,7 +270,10 @@ class FileBackendMultiWrite extends FileBackend {
                                                        continue;
                                                }
                                        }
-                                       if ( ( $this->syncChecks & self::CHECK_SHA1 ) && $cBackend->getFileSha1Base36( $cParams ) !== $mSha1 ) { // wrong SHA1
+                                       if (
+                                               ( $this->syncChecks & self::CHECK_SHA1 ) &&
+                                               $cBackend->getFileSha1Base36( $cParams ) !== $mSha1
+                                       ) { // wrong SHA1
                                                $status->fatal( 'backend-fail-synced', $path );
                                                continue;
                                        }
index e2a25fc..aa95ee4 100644 (file)
@@ -20,6 +20,7 @@
  * @file
  * @ingroup FileBackend
  */
+use Wikimedia\AtEase\AtEase;
 use Wikimedia\Timestamp\ConvertibleTimestamp;
 
 /**
@@ -376,9 +377,9 @@ abstract class FileBackendStore extends FileBackend {
                unset( $params['latest'] ); // sanity
 
                // Check that the specified temp file is valid...
-               Wikimedia\suppressWarnings();
+               AtEase::suppressWarnings();
                $ok = ( is_file( $tmpPath ) && filesize( $tmpPath ) == 0 );
-               Wikimedia\restoreWarnings();
+               AtEase::restoreWarnings();
                if ( !$ok ) { // not present or not empty
                        $status->fatal( 'backend-fail-opentemp', $tmpPath );
 
@@ -714,9 +715,9 @@ abstract class FileBackendStore extends FileBackend {
        protected function doGetFileContentsMulti( array $params ) {
                $contents = [];
                foreach ( $this->doGetLocalReferenceMulti( $params ) as $path => $fsFile ) {
-                       Wikimedia\suppressWarnings();
+                       AtEase::suppressWarnings();
                        $contents[$path] = $fsFile ? file_get_contents( $fsFile->getPath() ) : false;
-                       Wikimedia\restoreWarnings();
+                       AtEase::restoreWarnings();
                }
 
                return $contents;
index 540961e..bbcda08 100644 (file)
@@ -46,7 +46,7 @@ class FileOpBatch {
         *
         * The resulting StatusValue will be "OK" unless:
         *   - a) unexpected operation errors occurred (network partitions, disk full...)
-        *   - b) significant operation errors occurred and 'force' was not set
+        *   - b) predicted operation errors occurred and 'force' was not set
         *
         * @param FileOp[] $performOps List of FileOp operations
         * @param array $opts Batch operation options
index 7a11aeb..653a102 100644 (file)
@@ -19,6 +19,8 @@
  *
  * @file
  */
+
+use Wikimedia\AtEase\AtEase;
 use Wikimedia\Timestamp\ConvertibleTimestamp;
 
 /**
@@ -100,9 +102,9 @@ class HTTPFileStreamer {
                                is_int( $header ) ? HttpStatus::header( $header ) : header( $header );
                        };
 
-               Wikimedia\suppressWarnings();
+               AtEase::suppressWarnings();
                $info = stat( $this->path );
-               Wikimedia\restoreWarnings();
+               AtEase::restoreWarnings();
 
                if ( !is_array( $info ) ) {
                        if ( $sendErrors ) {
index 548c85c..f0cbf3b 100644 (file)
@@ -21,6 +21,8 @@
  * @ingroup FileBackend
  */
 
+use Wikimedia\AtEase\AtEase;
+
 /**
  * Simulation of a backend storage in memory.
  *
@@ -70,9 +72,9 @@ class MemoryFileBackend extends FileBackendStore {
                        return $status;
                }
 
-               Wikimedia\suppressWarnings();
+               AtEase::suppressWarnings();
                $data = file_get_contents( $params['src'] );
-               Wikimedia\restoreWarnings();
+               AtEase::restoreWarnings();
                if ( $data === false ) { // source doesn't exist?
                        $status->fatal( 'backend-fail-store', $params['src'], $params['dst'] );
 
@@ -168,7 +170,7 @@ class MemoryFileBackend extends FileBackendStore {
                        } else {
                                // Create a new temporary file with the same extension...
                                $ext = FileBackend::extensionFromPath( $src );
-                               $fsFile = TempFSFile::factory( 'localcopy_', $ext, $this->tmpDirectory );
+                               $fsFile = $this->tmpFileFactory->newTempFSFile( 'localcopy_', $ext );
                                if ( $fsFile ) {
                                        $bytes = file_put_contents( $fsFile->getPath(), $this->files[$src]['data'] );
                                        if ( $bytes !== strlen( $this->files[$src]['data'] ) ) {
index a1b2460..afd1688 100644 (file)
@@ -22,6 +22,8 @@
  * @author Russ Nelson
  */
 
+use Wikimedia\AtEase\AtEase;
+
 /**
  * @brief Class for an OpenStack Swift (or Ceph RGW) based file backend.
  *
@@ -326,9 +328,9 @@ class SwiftFileBackend extends FileBackendStore {
                        return $status;
                }
 
-               Wikimedia\suppressWarnings();
+               AtEase::suppressWarnings();
                $sha1Hash = sha1_file( $params['src'] );
-               Wikimedia\restoreWarnings();
+               AtEase::restoreWarnings();
                if ( $sha1Hash === false ) { // source doesn't exist?
                        $status->fatal( 'backend-fail-store', $params['src'], $params['dst'] );
 
@@ -1150,7 +1152,7 @@ class SwiftFileBackend extends FileBackendStore {
                        // Get source file extension
                        $ext = FileBackend::extensionFromPath( $path );
                        // Create a new temporary file...
-                       $tmpFile = TempFSFile::factory( 'localcopy_', $ext, $this->tmpDirectory );
+                       $tmpFile = $this->tmpFileFactory->newTempFSFile( 'localcopy_', $ext );
                        if ( $tmpFile ) {
                                $handle = fopen( $tmpFile->getPath(), 'wb' );
                                if ( $handle ) {
index 206048b..961fdb9 100644 (file)
@@ -77,7 +77,7 @@ abstract class FileOp {
         * @param FileBackendStore $backend
         * @param array $params
         * @param LoggerInterface $logger PSR logger instance
-        * @throws FileBackendError
+        * @throws InvalidArgumentException
         */
        final public function __construct(
                FileBackendStore $backend, array $params, LoggerInterface $logger
index 69ae47f..5783a82 100644 (file)
@@ -21,6 +21,8 @@
  * @ingroup FileBackend
  */
 
+use Wikimedia\AtEase\AtEase;
+
 /**
  * Store a file into the backend from a file on the file system.
  * Parameters for this operation are outlined in FileBackend::doOperations().
@@ -77,9 +79,9 @@ class StoreFileOp extends FileOp {
        }
 
        protected function getSourceSha1Base36() {
-               Wikimedia\suppressWarnings();
+               AtEase::suppressWarnings();
                $hash = sha1_file( $this->params['src'] );
-               Wikimedia\restoreWarnings();
+               AtEase::restoreWarnings();
                if ( $hash !== false ) {
                        $hash = Wikimedia\base_convert( $hash, 16, 36, 31 );
                }
index 553c9aa..1937e37 100644 (file)
@@ -21,6 +21,8 @@
  * @ingroup FileBackend
  */
 
+use Wikimedia\AtEase\AtEase;
+
 /**
  * Class representing a non-directory file on the file system
  *
@@ -75,9 +77,9 @@ class FSFile {
         * @return string|bool TS_MW timestamp or false on failure
         */
        public function getTimestamp() {
-               Wikimedia\suppressWarnings();
+               AtEase::suppressWarnings();
                $timestamp = filemtime( $this->path );
-               Wikimedia\restoreWarnings();
+               AtEase::restoreWarnings();
                if ( $timestamp !== false ) {
                        $timestamp = wfTimestamp( TS_MW, $timestamp );
                }
@@ -168,9 +170,9 @@ class FSFile {
                        return $this->sha1Base36;
                }
 
-               Wikimedia\suppressWarnings();
+               AtEase::suppressWarnings();
                $this->sha1Base36 = sha1_file( $this->path );
-               Wikimedia\restoreWarnings();
+               AtEase::restoreWarnings();
 
                if ( $this->sha1Base36 !== false ) {
                        $this->sha1Base36 = Wikimedia\base_convert( $this->sha1Base36, 16, 36, 31 );
index b993626..46fa5e1 100644 (file)
@@ -1,4 +1,7 @@
 <?php
+
+use MediaWiki\FileBackend\FSFile\TempFSFileFactory;
+
 /**
  * Location holder of files stored temporarily
  *
@@ -21,6 +24,8 @@
  * @ingroup FileBackend
  */
 
+use Wikimedia\AtEase\AtEase;
+
 /**
  * This class is used to hold the location and do limited manipulation
  * of files stored temporarily (this will be whatever wfTempDir() returns)
@@ -34,12 +39,19 @@ class TempFSFile extends FSFile {
        /** @var array Map of (path => 1) for paths to delete on shutdown */
        protected static $pathsCollect = null;
 
+       /**
+        * Do not call directly. Use TempFSFileFactory
+        *
+        * @param string $path
+        */
        public function __construct( $path ) {
                parent::__construct( $path );
 
                if ( self::$pathsCollect === null ) {
+                       // @codeCoverageIgnoreStart
                        self::$pathsCollect = [];
                        register_shutdown_function( [ __CLASS__, 'purgeAllOnShutdown' ] );
+                       // @codeCoverageIgnoreEnd
                }
        }
 
@@ -47,40 +59,23 @@ class TempFSFile extends FSFile {
         * Make a new temporary file on the file system.
         * Temporary files may be purged when the file object falls out of scope.
         *
+        * @deprecated since 1.34, use TempFSFileFactory directly
+        *
         * @param string $prefix
         * @param string $extension Optional file extension
         * @param string|null $tmpDirectory Optional parent directory
         * @return TempFSFile|null
         */
        public static function factory( $prefix, $extension = '', $tmpDirectory = null ) {
-               $ext = ( $extension != '' ) ? ".{$extension}" : '';
-
-               $attempts = 5;
-               while ( $attempts-- ) {
-                       $hex = sprintf( '%06x%06x', mt_rand( 0, 0xffffff ), mt_rand( 0, 0xffffff ) );
-                       if ( !is_string( $tmpDirectory ) ) {
-                               $tmpDirectory = self::getUsableTempDirectory();
-                       }
-                       $path = $tmpDirectory . '/' . $prefix . $hex . $ext;
-                       Wikimedia\suppressWarnings();
-                       $newFileHandle = fopen( $path, 'x' );
-                       Wikimedia\restoreWarnings();
-                       if ( $newFileHandle ) {
-                               fclose( $newFileHandle );
-                               $tmpFile = new self( $path );
-                               $tmpFile->autocollect();
-                               // Safely instantiated, end loop.
-                               return $tmpFile;
-                       }
-               }
-
-               // Give up
-               return null;
+               return ( new TempFSFileFactory( $tmpDirectory ) )->newTempFSFile( $prefix, $extension );
        }
 
        /**
+        * @todo Is there any useful way to test this? Would it be useful to make this non-static on
+        * TempFSFileFactory?
+        *
         * @return string Filesystem path to a temporary directory
-        * @throws RuntimeException
+        * @throws RuntimeException if no writable temporary directory can be found
         */
        public static function getUsableTempDirectory() {
                $tmpDir = array_map( 'getenv', [ 'TMPDIR', 'TMP', 'TEMP' ] );
@@ -119,9 +114,9 @@ class TempFSFile extends FSFile {
         */
        public function purge() {
                $this->canDelete = false; // done
-               Wikimedia\suppressWarnings();
+               AtEase::suppressWarnings();
                $ok = unlink( $this->path );
-               Wikimedia\restoreWarnings();
+               AtEase::restoreWarnings();
 
                unset( self::$pathsCollect[$this->path] );
 
@@ -176,12 +171,14 @@ class TempFSFile extends FSFile {
         * Try to make sure that all files are purged on error
         *
         * This method should only be called internally
+        *
+        * @codeCoverageIgnore
         */
        public static function purgeAllOnShutdown() {
                foreach ( self::$pathsCollect as $path => $unused ) {
-                       Wikimedia\suppressWarnings();
+                       AtEase::suppressWarnings();
                        unlink( $path );
-                       Wikimedia\restoreWarnings();
+                       AtEase::restoreWarnings();
                }
        }
 
diff --git a/includes/libs/filebackend/fsfile/TempFSFileFactory.php b/includes/libs/filebackend/fsfile/TempFSFileFactory.php
new file mode 100644 (file)
index 0000000..1120973
--- /dev/null
@@ -0,0 +1,56 @@
+<?php
+
+namespace MediaWiki\FileBackend\FSFile;
+
+use TempFSFile;
+
+/**
+ * @ingroup FileBackend
+ */
+class TempFSFileFactory {
+       /** @var string|null */
+       private $tmpDirectory;
+
+       /**
+        * @param string|null $tmpDirectory A directory to put the temporary files in, e.g.,
+        *   $wgTmpDirectory. If null, we'll try to find one ourselves.
+        */
+       public function __construct( $tmpDirectory = null ) {
+               $this->tmpDirectory = $tmpDirectory;
+       }
+
+       /**
+        * Make a new temporary file on the file system.
+        * Temporary files may be purged when the file object falls out of scope.
+        *
+        * @param string $prefix
+        * @param string $extension Optional file extension
+        * @return TempFSFile|null
+        */
+       public function newTempFSFile( $prefix, $extension = '' ) {
+               $ext = ( $extension != '' ) ? ".{$extension}" : '';
+               $tmpDirectory = $this->tmpDirectory;
+               if ( !is_string( $tmpDirectory ) ) {
+                       $tmpDirectory = TempFSFile::getUsableTempDirectory();
+               }
+
+               $attempts = 5;
+               while ( $attempts-- ) {
+                       $hex = sprintf( '%06x%06x', mt_rand( 0, 0xffffff ), mt_rand( 0, 0xffffff ) );
+                       $path = "$tmpDirectory/$prefix$hex$ext";
+                       \Wikimedia\suppressWarnings();
+                       $newFileHandle = fopen( $path, 'x' );
+                       \Wikimedia\restoreWarnings();
+                       if ( $newFileHandle ) {
+                               fclose( $newFileHandle );
+                               $tmpFile = new TempFSFile( $path );
+                               $tmpFile->autocollect();
+                               // Safely instantiated, end loop.
+                               return $tmpFile;
+                       }
+               }
+
+               // Give up
+               return null; // @codeCoverageIgnore
+       }
+}
index aa83b1f..e9bd7be 100644 (file)
@@ -87,11 +87,11 @@ class APCBagOStuff extends MediumSpecificBagOStuff {
                return true;
        }
 
-       public function incr( $key, $value = 1 ) {
+       public function incr( $key, $value = 1, $flags = 0 ) {
                return apc_inc( $key . self::KEY_SUFFIX, $value );
        }
 
-       public function decr( $key, $value = 1 ) {
+       public function decr( $key, $value = 1, $flags = 0 ) {
                return apc_dec( $key . self::KEY_SUFFIX, $value );
        }
 }
index 80383d1..2b26500 100644 (file)
@@ -85,7 +85,7 @@ class APCUBagOStuff extends MediumSpecificBagOStuff {
                return true;
        }
 
-       public function incr( $key, $value = 1 ) {
+       public function incr( $key, $value = 1, $flags = 0 ) {
                // https://github.com/krakjoe/apcu/issues/166
                if ( apcu_exists( $key . self::KEY_SUFFIX ) ) {
                        return apcu_inc( $key . self::KEY_SUFFIX, $value );
@@ -94,7 +94,7 @@ class APCUBagOStuff extends MediumSpecificBagOStuff {
                }
        }
 
-       public function decr( $key, $value = 1 ) {
+       public function decr( $key, $value = 1, $flags = 0 ) {
                // https://github.com/krakjoe/apcu/issues/166
                if ( apcu_exists( $key . self::KEY_SUFFIX ) ) {
                        return apcu_dec( $key . self::KEY_SUFFIX, $value );
index da60c01..42da5f0 100644 (file)
@@ -362,31 +362,36 @@ abstract class BagOStuff implements IExpiringStore, IStoreKeyEncoder, LoggerAwar
         * Increase stored value of $key by $value while preserving its TTL
         * @param string $key Key to increase
         * @param int $value Value to add to $key (default: 1) [optional]
+        * @param int $flags Bit field of class WRITE_* constants [optional]
         * @return int|bool New value or false on failure
         */
-       abstract public function incr( $key, $value = 1 );
+       abstract public function incr( $key, $value = 1, $flags = 0 );
 
        /**
         * Decrease stored value of $key by $value while preserving its TTL
         * @param string $key
         * @param int $value Value to subtract from $key (default: 1) [optional]
+        * @param int $flags Bit field of class WRITE_* constants [optional]
         * @return int|bool New value or false on failure
         */
-       abstract public function decr( $key, $value = 1 );
+       abstract public function decr( $key, $value = 1, $flags = 0 );
 
        /**
-        * Increase stored value of $key by $value while preserving its TTL
+        * Increase the value of the given key (no TTL change) if it exists or create it otherwise
         *
-        * This will create the key with value $init and TTL $ttl instead if not present
+        * This will create the key with the value $init and TTL $ttl instead if not present.
+        * Callers should make sure that both ($init - $value) and $ttl are invariants for all
+        * operations to any given key. The value of $init should be at least that of $value.
         *
-        * @param string $key
-        * @param int $ttl
-        * @param int $value
-        * @param int $init
+        * @param string $key Key built via makeKey() or makeGlobalKey()
+        * @param int $exptime Time-to-live (in seconds) or a UNIX timestamp expiration
+        * @param int $value Amount to increase the key value by [default: 1]
+        * @param int|null $init Value to initialize the key to if it does not exist [default: $value]
+        * @param int $flags Bit field of class WRITE_* constants [optional]
         * @return int|bool New value or false on failure
         * @since 1.24
         */
-       abstract public function incrWithInit( $key, $ttl, $value = 1, $init = 1 );
+       abstract public function incrWithInit( $key, $exptime, $value = 1, $init = null, $flags = 0 );
 
        /**
         * Get the "last error" registered; clearLastError() should be called manually
@@ -478,6 +483,16 @@ abstract class BagOStuff implements IExpiringStore, IStoreKeyEncoder, LoggerAwar
                return INF;
        }
 
+       /**
+        * @param int $field
+        * @param int $flags
+        * @return bool
+        * @since 1.34
+        */
+       final protected function fieldHasFlags( $field, $flags ) {
+               return ( ( $field & $flags ) === $flags );
+       }
+
        /**
         * Merge the flag maps of one or more BagOStuff objects into a "lowest common denominator" map
         *
index 9fa9a89..8b4c9c6 100644 (file)
@@ -87,7 +87,8 @@ class CachedBagOStuff extends BagOStuff {
 
        public function set( $key, $value, $exptime = 0, $flags = 0 ) {
                $this->procCache->set( $key, $value, $exptime, $flags );
-               if ( ( $flags & self::WRITE_CACHE_ONLY ) != self::WRITE_CACHE_ONLY ) {
+
+               if ( !$this->fieldHasFlags( $flags, self::WRITE_CACHE_ONLY ) ) {
                        $this->backend->set( $key, $value, $exptime, $flags );
                }
 
@@ -96,7 +97,8 @@ class CachedBagOStuff extends BagOStuff {
 
        public function delete( $key, $flags = 0 ) {
                $this->procCache->delete( $key, $flags );
-               if ( ( $flags & self::WRITE_CACHE_ONLY ) != self::WRITE_CACHE_ONLY ) {
+
+               if ( !$this->fieldHasFlags( $flags, self::WRITE_CACHE_ONLY ) ) {
                        $this->backend->delete( $key, $flags );
                }
 
@@ -166,7 +168,8 @@ class CachedBagOStuff extends BagOStuff {
 
        public function setMulti( array $data, $exptime = 0, $flags = 0 ) {
                $this->procCache->setMulti( $data, $exptime, $flags );
-               if ( ( $flags & self::WRITE_CACHE_ONLY ) != self::WRITE_CACHE_ONLY ) {
+
+               if ( !$this->fieldHasFlags( $flags, self::WRITE_CACHE_ONLY ) ) {
                        return $this->backend->setMulti( $data, $exptime, $flags );
                }
 
@@ -175,7 +178,8 @@ class CachedBagOStuff extends BagOStuff {
 
        public function deleteMulti( array $keys, $flags = 0 ) {
                $this->procCache->deleteMulti( $keys, $flags );
-               if ( ( $flags & self::WRITE_CACHE_ONLY ) != self::WRITE_CACHE_ONLY ) {
+
+               if ( !$this->fieldHasFlags( $flags, self::WRITE_CACHE_ONLY ) ) {
                        return $this->backend->deleteMulti( $keys, $flags );
                }
 
@@ -184,29 +188,30 @@ class CachedBagOStuff extends BagOStuff {
 
        public function changeTTLMulti( array $keys, $exptime, $flags = 0 ) {
                $this->procCache->changeTTLMulti( $keys, $exptime, $flags );
-               if ( ( $flags & self::WRITE_CACHE_ONLY ) != self::WRITE_CACHE_ONLY ) {
+
+               if ( !$this->fieldHasFlags( $flags, self::WRITE_CACHE_ONLY ) ) {
                        return $this->backend->changeTTLMulti( $keys, $exptime, $flags );
                }
 
                return true;
        }
 
-       public function incr( $key, $value = 1 ) {
+       public function incr( $key, $value = 1, $flags = 0 ) {
                $this->procCache->delete( $key );
 
-               return $this->backend->incr( $key, $value );
+               return $this->backend->incr( $key, $value, $flags );
        }
 
-       public function decr( $key, $value = 1 ) {
+       public function decr( $key, $value = 1, $flags = 0 ) {
                $this->procCache->delete( $key );
 
-               return $this->backend->decr( $key, $value );
+               return $this->backend->decr( $key, $value, $flags );
        }
 
-       public function incrWithInit( $key, $ttl, $value = 1, $init = 1 ) {
+       public function incrWithInit( $key, $exptime, $value = 1, $init = null, $flags = 0 ) {
                $this->procCache->delete( $key );
 
-               return $this->backend->incrWithInit( $key, $ttl, $value, $init );
+               return $this->backend->incrWithInit( $key, $exptime, $value, $init, $flags );
        }
 
        public function addBusyCallback( callable $workCallback ) {
index b2613b2..9723cad 100644 (file)
@@ -45,11 +45,15 @@ class EmptyBagOStuff extends MediumSpecificBagOStuff {
                return true;
        }
 
-       public function incr( $key, $value = 1 ) {
+       public function incr( $key, $value = 1, $flags = 0 ) {
                return false;
        }
 
-       public function incrWithInit( $key, $ttl, $value = 1, $init = 1 ) {
+       public function decr( $key, $value = 1, $flags = 0 ) {
+               return false;
+       }
+
+       public function incrWithInit( $key, $exptime, $value = 1, $init = null, $flags = 0 ) {
                return false; // faster
        }
 
index b4087be..6d0940b 100644 (file)
@@ -108,10 +108,10 @@ class HashBagOStuff extends MediumSpecificBagOStuff {
                return true;
        }
 
-       public function incr( $key, $value = 1 ) {
+       public function incr( $key, $value = 1, $flags = 0 ) {
                $n = $this->get( $key );
                if ( $this->isInteger( $n ) ) {
-                       $n = max( $n + intval( $value ), 0 );
+                       $n = max( $n + (int)$value, 0 );
                        $this->bag[$key][self::KEY_VAL] = $n;
 
                        return $n;
@@ -120,6 +120,10 @@ class HashBagOStuff extends MediumSpecificBagOStuff {
                return false;
        }
 
+       public function decr( $key, $value = 1, $flags = 0 ) {
+               return $this->incr( $key, -$value, $flags );
+       }
+
        /**
         * Clear all values in cache
         */
index 329e600..9d36187 100644 (file)
@@ -188,7 +188,7 @@ abstract class MediumSpecificBagOStuff extends BagOStuff {
         * @return bool True if the item was deleted or not found, false on failure
         */
        public function delete( $key, $flags = 0 ) {
-               if ( ( $flags & self::WRITE_PRUNE_SEGMENTS ) != self::WRITE_PRUNE_SEGMENTS ) {
+               if ( !$this->fieldHasFlags( $flags, self::WRITE_PRUNE_SEGMENTS ) ) {
                        return $this->doDelete( $key, $flags );
                }
 
@@ -208,7 +208,7 @@ abstract class MediumSpecificBagOStuff extends BagOStuff {
                        $mainValue->{SerializedValueContainer::SEGMENTED_HASHES}
                );
 
-               return $this->deleteMulti( $orderedKeys, $flags );
+               return $this->deleteMulti( $orderedKeys, $flags & ~self::WRITE_PRUNE_SEGMENTS );
        }
 
        /**
@@ -269,12 +269,12 @@ abstract class MediumSpecificBagOStuff extends BagOStuff {
        final protected function mergeViaCas( $key, callable $callback, $exptime, $attempts, $flags ) {
                $attemptsLeft = $attempts;
                do {
-                       $casToken = null; // passed by reference
+                       $token = null; // passed by reference
                        // Get the old value and CAS token from cache
                        $this->clearLastError();
                        $currentValue = $this->resolveSegments(
                                $key,
-                               $this->doGet( $key, self::READ_LATEST, $casToken )
+                               $this->doGet( $key, $flags, $token )
                        );
                        if ( $this->getLastError() ) {
                                // Don't spam slow retries due to network problems (retry only on races)
@@ -293,7 +293,7 @@ abstract class MediumSpecificBagOStuff extends BagOStuff {
                        unset( $currentValue ); // free RAM in case the value is large
 
                        $this->clearLastError();
-                       if ( $value === false ) {
+                       if ( $value === false || $exptime < 0 ) {
                                $success = true; // do nothing
                        } elseif ( $valueMatchesOldValue && $attemptsLeft !== $attempts ) {
                                $success = true; // recently set by another thread to the same value
@@ -302,7 +302,7 @@ abstract class MediumSpecificBagOStuff extends BagOStuff {
                                $success = $this->add( $key, $value, $exptime, $flags );
                        } else {
                                // Try to update the key, failing if it gets changed in the meantime
-                               $success = $this->cas( $casToken, $key, $value, $exptime, $flags );
+                               $success = $this->cas( $token, $key, $value, $exptime, $flags );
                        }
                        if ( $this->getLastError() ) {
                                // Don't spam slow retries due to network problems (retry only on races)
@@ -598,9 +598,10 @@ abstract class MediumSpecificBagOStuff extends BagOStuff {
         * @since 1.24
         */
        public function setMulti( array $data, $exptime = 0, $flags = 0 ) {
-               if ( ( $flags & self::WRITE_ALLOW_SEGMENTS ) === self::WRITE_ALLOW_SEGMENTS ) {
+               if ( $this->fieldHasFlags( $flags, self::WRITE_ALLOW_SEGMENTS ) ) {
                        throw new InvalidArgumentException( __METHOD__ . ' got WRITE_ALLOW_SEGMENTS' );
                }
+
                return $this->doSetMulti( $data, $exptime, $flags );
        }
 
@@ -615,6 +616,7 @@ abstract class MediumSpecificBagOStuff extends BagOStuff {
                foreach ( $data as $key => $value ) {
                        $res = $this->doSet( $key, $value, $exptime, $flags ) && $res;
                }
+
                return $res;
        }
 
@@ -629,9 +631,10 @@ abstract class MediumSpecificBagOStuff extends BagOStuff {
         * @since 1.33
         */
        public function deleteMulti( array $keys, $flags = 0 ) {
-               if ( ( $flags & self::WRITE_ALLOW_SEGMENTS ) === self::WRITE_ALLOW_SEGMENTS ) {
-                       throw new InvalidArgumentException( __METHOD__ . ' got WRITE_ALLOW_SEGMENTS' );
+               if ( $this->fieldHasFlags( $flags, self::WRITE_PRUNE_SEGMENTS ) ) {
+                       throw new InvalidArgumentException( __METHOD__ . ' got WRITE_PRUNE_SEGMENTS' );
                }
+
                return $this->doDeleteMulti( $keys, $flags );
        }
 
@@ -668,37 +671,16 @@ abstract class MediumSpecificBagOStuff extends BagOStuff {
                return $res;
        }
 
-       /**
-        * Decrease stored value of $key by $value while preserving its TTL
-        * @param string $key
-        * @param int $value Value to subtract from $key (default: 1) [optional]
-        * @return int|bool New value or false on failure
-        */
-       public function decr( $key, $value = 1 ) {
-               return $this->incr( $key, -$value );
-       }
-
-       /**
-        * Increase stored value of $key by $value while preserving its TTL
-        *
-        * This will create the key with value $init and TTL $ttl instead if not present
-        *
-        * @param string $key
-        * @param int $ttl
-        * @param int $value
-        * @param int $init
-        * @return int|bool New value or false on failure
-        * @since 1.24
-        */
-       public function incrWithInit( $key, $ttl, $value = 1, $init = 1 ) {
+       public function incrWithInit( $key, $exptime, $value = 1, $init = null, $flags = 0 ) {
+               $init = is_int( $init ) ? $init : $value;
                $this->clearLastError();
-               $newValue = $this->incr( $key, $value );
+               $newValue = $this->incr( $key, $value, $flags );
                if ( $newValue === false && !$this->getLastError() ) {
                        // No key set; initialize
-                       $newValue = $this->add( $key, (int)$init, $ttl ) ? $init : false;
+                       $newValue = $this->add( $key, (int)$init, $exptime, $flags ) ? $init : false;
                        if ( $newValue === false && !$this->getLastError() ) {
                                // Raced out initializing; increment
-                               $newValue = $this->incr( $key, $value );
+                               $newValue = $this->incr( $key, $value, $flags );
                        }
                }
 
@@ -768,26 +750,6 @@ abstract class MediumSpecificBagOStuff extends BagOStuff {
                $this->lastError = $err;
        }
 
-       /**
-        * Let a callback be run to avoid wasting time on special blocking calls
-        *
-        * The callbacks may or may not be called ever, in any particular order.
-        * They are likely to be invoked when something WRITE_SYNC is used used.
-        * They should follow a caching pattern as shown below, so that any code
-        * using the work will get it's result no matter what happens.
-        * @code
-        *     $result = null;
-        *     $workCallback = function () use ( &$result ) {
-        *         if ( !$result ) {
-        *             $result = ....
-        *         }
-        *         return $result;
-        *     }
-        * @endcode
-        *
-        * @param callable $workCallback
-        * @since 1.28
-        */
        final public function addBusyCallback( callable $workCallback ) {
                $this->busyCallbacks[] = $workCallback;
        }
@@ -807,7 +769,7 @@ abstract class MediumSpecificBagOStuff extends BagOStuff {
                $usable = true;
 
                if (
-                       ( $flags & self::WRITE_ALLOW_SEGMENTS ) === self::WRITE_ALLOW_SEGMENTS &&
+                       $this->fieldHasFlags( $flags, self::WRITE_ALLOW_SEGMENTS ) &&
                        !is_int( $value ) && // avoid breaking incr()/decr()
                        is_finite( $this->segmentationSize )
                ) {
@@ -919,14 +881,6 @@ abstract class MediumSpecificBagOStuff extends BagOStuff {
                return ( $value === (string)$integer );
        }
 
-       /**
-        * Construct a cache key.
-        *
-        * @param string $keyspace
-        * @param array $args
-        * @return string Colon-delimited list of $keyspace followed by escaped components of $args
-        * @since 1.27
-        */
        public function makeKeyInternal( $keyspace, $args ) {
                $key = $keyspace;
                foreach ( $args as $arg ) {
@@ -968,18 +922,10 @@ abstract class MediumSpecificBagOStuff extends BagOStuff {
                return $this->attrMap[$flag] ?? self::QOS_UNKNOWN;
        }
 
-       /**
-        * @return int|float The chunk size, in bytes, of segmented objects (INF for no limit)
-        * @since 1.34
-        */
        public function getSegmentationSize() {
                return $this->segmentationSize;
        }
 
-       /**
-        * @return int|float Maximum total segmented object size in bytes (INF for no limit)
-        * @since 1.34
-        */
        public function getSegmentedValueMaxSize() {
                return $this->segmentedValueMaxSize;
        }
diff --git a/includes/libs/objectcache/MemcachedClient.php b/includes/libs/objectcache/MemcachedClient.php
deleted file mode 100644 (file)
index eecf7ec..0000000
+++ /dev/null
@@ -1,1330 +0,0 @@
-<?php
-// phpcs:ignoreFile -- It's an external lib and it isn't. Let's not bother.
-/**
- * Memcached client for PHP.
- *
- * +---------------------------------------------------------------------------+
- * | memcached client, PHP                                                     |
- * +---------------------------------------------------------------------------+
- * | Copyright (c) 2003 Ryan T. Dean <rtdean@cytherianage.net>                 |
- * | All rights reserved.                                                      |
- * |                                                                           |
- * | Redistribution and use in source and binary forms, with or without        |
- * | modification, are permitted provided that the following conditions        |
- * | are met:                                                                  |
- * |                                                                           |
- * | 1. Redistributions of source code must retain the above copyright         |
- * |    notice, this list of conditions and the following disclaimer.          |
- * | 2. Redistributions in binary form must reproduce the above copyright      |
- * |    notice, this list of conditions and the following disclaimer in the    |
- * |    documentation and/or other materials provided with the distribution.   |
- * |                                                                           |
- * | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR      |
- * | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
- * | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.   |
- * | IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,          |
- * | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT  |
- * | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
- * | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY     |
- * | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT       |
- * | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF  |
- * | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.         |
- * +---------------------------------------------------------------------------+
- * | Author: Ryan T. Dean <rtdean@cytherianage.net>                            |
- * | Heavily influenced by the Perl memcached client by Brad Fitzpatrick.      |
- * |   Permission granted by Brad Fitzpatrick for relicense of ported Perl     |
- * |   client logic under 2-clause BSD license.                                |
- * +---------------------------------------------------------------------------+
- *
- * @file
- * $TCAnet$
- */
-
-/**
- * This is a PHP client for memcached - a distributed memory cache daemon.
- *
- * More information is available at http://www.danga.com/memcached/
- *
- * Usage example:
- *
- *     $mc = new MemcachedClient(array(
- *         'servers' => array(
- *             '127.0.0.1:10000',
- *             array( '192.0.0.1:10010', 2 ),
- *             '127.0.0.1:10020'
- *         ),
- *         'debug'   => false,
- *         'compress_threshold' => 10240,
- *         'persistent' => true
- *     ));
- *
- *     $mc->add( 'key', array( 'some', 'array' ) );
- *     $mc->replace( 'key', 'some random string' );
- *     $val = $mc->get( 'key' );
- *
- * @author Ryan T. Dean <rtdean@cytherianage.net>
- * @version 0.1.2
- */
-
-use Psr\Log\LoggerInterface;
-use Psr\Log\NullLogger;
-
-// {{{ class MemcachedClient
-/**
- * memcached client class implemented using (p)fsockopen()
- *
- * @author  Ryan T. Dean <rtdean@cytherianage.net>
- * @ingroup Cache
- */
-class MemcachedClient {
-       // {{{ properties
-       // {{{ public
-
-       // {{{ constants
-       // {{{ flags
-
-       /**
-        * Flag: indicates data is serialized
-        */
-       const SERIALIZED = 1;
-
-       /**
-        * Flag: indicates data is compressed
-        */
-       const COMPRESSED = 2;
-
-       /**
-        * Flag: indicates data is an integer
-        */
-       const INTVAL = 4;
-
-       // }}}
-
-       /**
-        * Minimum savings to store data compressed
-        */
-       const COMPRESSION_SAVINGS = 0.20;
-
-       // }}}
-
-       /**
-        * Command statistics
-        *
-        * @var array
-        * @access public
-        */
-       public $stats;
-
-       // }}}
-       // {{{ private
-
-       /**
-        * Cached Sockets that are connected
-        *
-        * @var array
-        * @access private
-        */
-       public $_cache_sock;
-
-       /**
-        * Current debug status; 0 - none to 9 - profiling
-        *
-        * @var bool
-        * @access private
-        */
-       public $_debug;
-
-       /**
-        * Dead hosts, assoc array, 'host'=>'unixtime when ok to check again'
-        *
-        * @var array
-        * @access private
-        */
-       public $_host_dead;
-
-       /**
-        * Is compression available?
-        *
-        * @var bool
-        * @access private
-        */
-       public $_have_zlib;
-
-       /**
-        * Do we want to use compression?
-        *
-        * @var bool
-        * @access private
-        */
-       public $_compress_enable;
-
-       /**
-        * At how many bytes should we compress?
-        *
-        * @var int
-        * @access private
-        */
-       public $_compress_threshold;
-
-       /**
-        * Are we using persistent links?
-        *
-        * @var bool
-        * @access private
-        */
-       public $_persistent;
-
-       /**
-        * If only using one server; contains ip:port to connect to
-        *
-        * @var string
-        * @access private
-        */
-       public $_single_sock;
-
-       /**
-        * Array containing ip:port or array(ip:port, weight)
-        *
-        * @var array
-        * @access private
-        */
-       public $_servers;
-
-       /**
-        * Our bit buckets
-        *
-        * @var array
-        * @access private
-        */
-       public $_buckets;
-
-       /**
-        * Total # of bit buckets we have
-        *
-        * @var int
-        * @access private
-        */
-       public $_bucketcount;
-
-       /**
-        * # of total servers we have
-        *
-        * @var int
-        * @access private
-        */
-       public $_active;
-
-       /**
-        * Stream timeout in seconds. Applies for example to fread()
-        *
-        * @var int
-        * @access private
-        */
-       public $_timeout_seconds;
-
-       /**
-        * Stream timeout in microseconds
-        *
-        * @var int
-        * @access private
-        */
-       public $_timeout_microseconds;
-
-       /**
-        * Connect timeout in seconds
-        */
-       public $_connect_timeout;
-
-       /**
-        * Number of connection attempts for each server
-        */
-       public $_connect_attempts;
-
-       /**
-        * @var LoggerInterface
-        */
-       private $_logger;
-
-       // }}}
-       // }}}
-       // {{{ methods
-       // {{{ public functions
-       // {{{ memcached()
-
-       /**
-        * Memcache initializer
-        *
-        * @param array $args Associative array of settings
-        */
-       public function __construct( $args ) {
-               $this->set_servers( $args['servers'] ?? array() );
-               $this->_debug = $args['debug'] ?? false;
-               $this->stats = array();
-               $this->_compress_threshold = $args['compress_threshold'] ?? 0;
-               $this->_persistent = $args['persistent'] ?? false;
-               $this->_compress_enable = true;
-               $this->_have_zlib = function_exists( 'gzcompress' );
-
-               $this->_cache_sock = array();
-               $this->_host_dead = array();
-
-               $this->_timeout_seconds = 0;
-               $this->_timeout_microseconds = $args['timeout'] ?? 500000;
-
-               $this->_connect_timeout = $args['connect_timeout'] ?? 0.1;
-               $this->_connect_attempts = 2;
-
-               $this->_logger = $args['logger'] ?? new NullLogger();
-       }
-
-       // }}}
-
-       /**
-        * @param mixed $value
-        * @return string|integer
-        */
-       public function serialize( $value ) {
-               return serialize( $value );
-       }
-
-       /**
-        * @param string $value
-        * @return mixed
-        */
-       public function unserialize( $value ) {
-               return unserialize( $value );
-       }
-
-       // {{{ add()
-
-       /**
-        * Adds a key/value to the memcache server if one isn't already set with
-        * that key
-        *
-        * @param string $key Key to set with data
-        * @param mixed $val Value to store
-        * @param int $exp (optional) Expiration time. This can be a number of seconds
-        * to cache for (up to 30 days inclusive).  Any timespans of 30 days + 1 second or
-        * longer must be the timestamp of the time at which the mapping should expire. It
-        * is safe to use timestamps in all cases, regardless of expiration
-        * eg: strtotime("+3 hour")
-        *
-        * @return bool
-        */
-       public function add( $key, $val, $exp = 0 ) {
-               return $this->_set( 'add', $key, $val, $exp );
-       }
-
-       // }}}
-       // {{{ decr()
-
-       /**
-        * Decrease a value stored on the memcache server
-        *
-        * @param string $key Key to decrease
-        * @param int $amt (optional) amount to decrease
-        *
-        * @return mixed False on failure, value on success
-        */
-       public function decr( $key, $amt = 1 ) {
-               return $this->_incrdecr( 'decr', $key, $amt );
-       }
-
-       // }}}
-       // {{{ delete()
-
-       /**
-        * Deletes a key from the server, optionally after $time
-        *
-        * @param string $key Key to delete
-        * @param int $time (optional) how long to wait before deleting
-        *
-        * @return bool True on success, false on failure
-        */
-       public function delete( $key, $time = 0 ) {
-               if ( !$this->_active ) {
-                       return false;
-               }
-
-               $sock = $this->get_sock( $key );
-               if ( !is_resource( $sock ) ) {
-                       return false;
-               }
-
-               $key = is_array( $key ) ? $key[1] : $key;
-
-               if ( isset( $this->stats['delete'] ) ) {
-                       $this->stats['delete']++;
-               } else {
-                       $this->stats['delete'] = 1;
-               }
-               $cmd = "delete $key $time\r\n";
-               if ( !$this->_fwrite( $sock, $cmd ) ) {
-                       return false;
-               }
-               $res = $this->_fgets( $sock );
-
-               if ( $this->_debug ) {
-                       $this->_debugprint( sprintf( "MemCache: delete %s (%s)", $key, $res ) );
-               }
-
-               if ( $res == "DELETED" || $res == "NOT_FOUND" ) {
-                       return true;
-               }
-
-               return false;
-       }
-
-       /**
-        * Changes the TTL on a key from the server to $time
-        *
-        * @param string $key
-        * @param int $time TTL in seconds
-        *
-        * @return bool True on success, false on failure
-        */
-       public function touch( $key, $time = 0 ) {
-               if ( !$this->_active ) {
-                       return false;
-               }
-
-               $sock = $this->get_sock( $key );
-               if ( !is_resource( $sock ) ) {
-                       return false;
-               }
-
-               $key = is_array( $key ) ? $key[1] : $key;
-
-               if ( isset( $this->stats['touch'] ) ) {
-                       $this->stats['touch']++;
-               } else {
-                       $this->stats['touch'] = 1;
-               }
-               $cmd = "touch $key $time\r\n";
-               if ( !$this->_fwrite( $sock, $cmd ) ) {
-                       return false;
-               }
-               $res = $this->_fgets( $sock );
-
-               if ( $this->_debug ) {
-                       $this->_debugprint( sprintf( "MemCache: touch %s (%s)", $key, $res ) );
-               }
-
-               if ( $res == "TOUCHED" ) {
-                       return true;
-               }
-
-               return false;
-       }
-
-       /**
-        * @param string $key
-        * @param int $timeout
-        * @return bool
-        */
-       public function lock( $key, $timeout = 0 ) {
-               /* stub */
-               return true;
-       }
-
-       /**
-        * @param string $key
-        * @return bool
-        */
-       public function unlock( $key ) {
-               /* stub */
-               return true;
-       }
-
-       // }}}
-       // {{{ disconnect_all()
-
-       /**
-        * Disconnects all connected sockets
-        */
-       public function disconnect_all() {
-               foreach ( $this->_cache_sock as $sock ) {
-                       fclose( $sock );
-               }
-
-               $this->_cache_sock = array();
-       }
-
-       // }}}
-       // {{{ enable_compress()
-
-       /**
-        * Enable / Disable compression
-        *
-        * @param bool $enable True to enable, false to disable
-        */
-       public function enable_compress( $enable ) {
-               $this->_compress_enable = $enable;
-       }
-
-       // }}}
-       // {{{ forget_dead_hosts()
-
-       /**
-        * Forget about all of the dead hosts
-        */
-       public function forget_dead_hosts() {
-               $this->_host_dead = array();
-       }
-
-       // }}}
-       // {{{ get()
-
-       /**
-        * Retrieves the value associated with the key from the memcache server
-        *
-        * @param array|string $key key to retrieve
-        * @param float $casToken [optional]
-        *
-        * @return mixed
-        */
-       public function get( $key, &$casToken = null ) {
-               if ( $this->_debug ) {
-                       $this->_debugprint( "get($key)" );
-               }
-
-               if ( !is_array( $key ) && strval( $key ) === '' ) {
-                       $this->_debugprint( "Skipping key which equals to an empty string" );
-                       return false;
-               }
-
-               if ( !$this->_active ) {
-                       return false;
-               }
-
-               $sock = $this->get_sock( $key );
-
-               if ( !is_resource( $sock ) ) {
-                       return false;
-               }
-
-               $key = is_array( $key ) ? $key[1] : $key;
-               if ( isset( $this->stats['get'] ) ) {
-                       $this->stats['get']++;
-               } else {
-                       $this->stats['get'] = 1;
-               }
-
-               $cmd = "gets $key\r\n";
-               if ( !$this->_fwrite( $sock, $cmd ) ) {
-                       return false;
-               }
-
-               $val = array();
-               $this->_load_items( $sock, $val, $casToken );
-
-               if ( $this->_debug ) {
-                       foreach ( $val as $k => $v ) {
-                               $this->_debugprint(
-                                       sprintf( "MemCache: sock %s got %s", $this->serialize( $sock ), $k ) );
-                       }
-               }
-
-               $value = false;
-               if ( isset( $val[$key] ) ) {
-                       $value = $val[$key];
-               }
-               return $value;
-       }
-
-       // }}}
-       // {{{ get_multi()
-
-       /**
-        * Get multiple keys from the server(s)
-        *
-        * @param array $keys Keys to retrieve
-        *
-        * @return array
-        */
-       public function get_multi( $keys ) {
-               if ( !$this->_active ) {
-                       return array();
-               }
-
-               if ( isset( $this->stats['get_multi'] ) ) {
-                       $this->stats['get_multi']++;
-               } else {
-                       $this->stats['get_multi'] = 1;
-               }
-               $sock_keys = array();
-               $socks = array();
-               foreach ( $keys as $key ) {
-                       $sock = $this->get_sock( $key );
-                       if ( !is_resource( $sock ) ) {
-                               continue;
-                       }
-                       $key = is_array( $key ) ? $key[1] : $key;
-                       if ( !isset( $sock_keys[$sock] ) ) {
-                               $sock_keys[intval( $sock )] = array();
-                               $socks[] = $sock;
-                       }
-                       $sock_keys[intval( $sock )][] = $key;
-               }
-
-               $gather = array();
-               // Send out the requests
-               foreach ( $socks as $sock ) {
-                       $cmd = 'gets';
-                       foreach ( $sock_keys[intval( $sock )] as $key ) {
-                               $cmd .= ' ' . $key;
-                       }
-                       $cmd .= "\r\n";
-
-                       if ( $this->_fwrite( $sock, $cmd ) ) {
-                               $gather[] = $sock;
-                       }
-               }
-
-               // Parse responses
-               $val = array();
-               foreach ( $gather as $sock ) {
-                       $this->_load_items( $sock, $val, $casToken );
-               }
-
-               if ( $this->_debug ) {
-                       foreach ( $val as $k => $v ) {
-                               $this->_debugprint( sprintf( "MemCache: got %s", $k ) );
-                       }
-               }
-
-               return $val;
-       }
-
-       // }}}
-       // {{{ incr()
-
-       /**
-        * Increments $key (optionally) by $amt
-        *
-        * @param string $key Key to increment
-        * @param int $amt (optional) amount to increment
-        *
-        * @return int|null Null if the key does not exist yet (this does NOT
-        * create new mappings if the key does not exist). If the key does
-        * exist, this returns the new value for that key.
-        */
-       public function incr( $key, $amt = 1 ) {
-               return $this->_incrdecr( 'incr', $key, $amt );
-       }
-
-       // }}}
-       // {{{ replace()
-
-       /**
-        * Overwrites an existing value for key; only works if key is already set
-        *
-        * @param string $key Key to set value as
-        * @param mixed $value Value to store
-        * @param int $exp (optional) Expiration time. This can be a number of seconds
-        * to cache for (up to 30 days inclusive).  Any timespans of 30 days + 1 second or
-        * longer must be the timestamp of the time at which the mapping should expire. It
-        * is safe to use timestamps in all cases, regardless of exipration
-        * eg: strtotime("+3 hour")
-        *
-        * @return bool
-        */
-       public function replace( $key, $value, $exp = 0 ) {
-               return $this->_set( 'replace', $key, $value, $exp );
-       }
-
-       // }}}
-       // {{{ run_command()
-
-       /**
-        * Passes through $cmd to the memcache server connected by $sock; returns
-        * output as an array (null array if no output)
-        *
-        * @param Resource $sock Socket to send command on
-        * @param string $cmd Command to run
-        *
-        * @return array Output array
-        */
-       public function run_command( $sock, $cmd ) {
-               if ( !is_resource( $sock ) ) {
-                       return array();
-               }
-
-               if ( !$this->_fwrite( $sock, $cmd ) ) {
-                       return array();
-               }
-
-               $ret = array();
-               while ( true ) {
-                       $res = $this->_fgets( $sock );
-                       $ret[] = $res;
-                       if ( preg_match( '/^END/', $res ) ) {
-                               break;
-                       }
-                       if ( strlen( $res ) == 0 ) {
-                               break;
-                       }
-               }
-               return $ret;
-       }
-
-       // }}}
-       // {{{ set()
-
-       /**
-        * Unconditionally sets a key to a given value in the memcache.  Returns true
-        * if set successfully.
-        *
-        * @param string $key Key to set value as
-        * @param mixed $value Value to set
-        * @param int $exp (optional) Expiration time. This can be a number of seconds
-        * to cache for (up to 30 days inclusive).  Any timespans of 30 days + 1 second or
-        * longer must be the timestamp of the time at which the mapping should expire. It
-        * is safe to use timestamps in all cases, regardless of exipration
-        * eg: strtotime("+3 hour")
-        *
-        * @return bool True on success
-        */
-       public function set( $key, $value, $exp = 0 ) {
-               return $this->_set( 'set', $key, $value, $exp );
-       }
-
-       // }}}
-       // {{{ cas()
-
-       /**
-        * Sets a key to a given value in the memcache if the current value still corresponds
-        * to a known, given value.  Returns true if set successfully.
-        *
-        * @param float $casToken Current known value
-        * @param string $key Key to set value as
-        * @param mixed $value Value to set
-        * @param int $exp (optional) Expiration time. This can be a number of seconds
-        * to cache for (up to 30 days inclusive).  Any timespans of 30 days + 1 second or
-        * longer must be the timestamp of the time at which the mapping should expire. It
-        * is safe to use timestamps in all cases, regardless of exipration
-        * eg: strtotime("+3 hour")
-        *
-        * @return bool True on success
-        */
-       public function cas( $casToken, $key, $value, $exp = 0 ) {
-               return $this->_set( 'cas', $key, $value, $exp, $casToken );
-       }
-
-       // }}}
-       // {{{ set_compress_threshold()
-
-       /**
-        * Set the compression threshold
-        *
-        * @param int $thresh Threshold to compress if larger than
-        */
-       public function set_compress_threshold( $thresh ) {
-               $this->_compress_threshold = $thresh;
-       }
-
-       // }}}
-       // {{{ set_debug()
-
-       /**
-        * Set the debug flag
-        *
-        * @see __construct()
-        * @param bool $dbg True for debugging, false otherwise
-        */
-       public function set_debug( $dbg ) {
-               $this->_debug = $dbg;
-       }
-
-       // }}}
-       // {{{ set_servers()
-
-       /**
-        * Set the server list to distribute key gets and puts between
-        *
-        * @see __construct()
-        * @param array $list Array of servers to connect to
-        */
-       public function set_servers( $list ) {
-               $this->_servers = $list;
-               $this->_active = count( $list );
-               $this->_buckets = null;
-               $this->_bucketcount = 0;
-
-               $this->_single_sock = null;
-               if ( $this->_active == 1 ) {
-                       $this->_single_sock = $this->_servers[0];
-               }
-       }
-
-       /**
-        * Sets the timeout for new connections
-        *
-        * @param int $seconds Number of seconds
-        * @param int $microseconds Number of microseconds
-        */
-       public function set_timeout( $seconds, $microseconds ) {
-               $this->_timeout_seconds = $seconds;
-               $this->_timeout_microseconds = $microseconds;
-       }
-
-       // }}}
-       // }}}
-       // {{{ private methods
-       // {{{ _close_sock()
-
-       /**
-        * Close the specified socket
-        *
-        * @param string $sock Socket to close
-        *
-        * @access private
-        */
-       function _close_sock( $sock ) {
-               $host = array_search( $sock, $this->_cache_sock );
-               fclose( $this->_cache_sock[$host] );
-               unset( $this->_cache_sock[$host] );
-       }
-
-       // }}}
-       // {{{ _connect_sock()
-
-       /**
-        * Connects $sock to $host, timing out after $timeout
-        *
-        * @param int $sock Socket to connect
-        * @param string $host Host:IP to connect to
-        *
-        * @return bool
-        * @access private
-        */
-       function _connect_sock( &$sock, $host ) {
-               list( $ip, $port ) = preg_split( '/:(?=\d)/', $host );
-               $sock = false;
-               $timeout = $this->_connect_timeout;
-               $errno = $errstr = null;
-               for ( $i = 0; !$sock && $i < $this->_connect_attempts; $i++ ) {
-                       Wikimedia\suppressWarnings();
-                       if ( $this->_persistent == 1 ) {
-                               $sock = pfsockopen( $ip, $port, $errno, $errstr, $timeout );
-                       } else {
-                               $sock = fsockopen( $ip, $port, $errno, $errstr, $timeout );
-                       }
-                       Wikimedia\restoreWarnings();
-               }
-               if ( !$sock ) {
-                       $this->_error_log( "Error connecting to $host: $errstr" );
-                       $this->_dead_host( $host );
-                       return false;
-               }
-
-               // Initialise timeout
-               stream_set_timeout( $sock, $this->_timeout_seconds, $this->_timeout_microseconds );
-
-               // If the connection was persistent, flush the read buffer in case there
-               // was a previous incomplete request on this connection
-               if ( $this->_persistent ) {
-                       $this->_flush_read_buffer( $sock );
-               }
-               return true;
-       }
-
-       // }}}
-       // {{{ _dead_sock()
-
-       /**
-        * Marks a host as dead until 30-40 seconds in the future
-        *
-        * @param string $sock Socket to mark as dead
-        *
-        * @access private
-        */
-       function _dead_sock( $sock ) {
-               $host = array_search( $sock, $this->_cache_sock );
-               $this->_dead_host( $host );
-       }
-
-       /**
-        * @param string $host
-        */
-       function _dead_host( $host ) {
-               $ip = explode( ':', $host )[0];
-               $this->_host_dead[$ip] = time() + 30 + intval( rand( 0, 10 ) );
-               $this->_host_dead[$host] = $this->_host_dead[$ip];
-               unset( $this->_cache_sock[$host] );
-       }
-
-       // }}}
-       // {{{ get_sock()
-
-       /**
-        * get_sock
-        *
-        * @param string $key Key to retrieve value for;
-        *
-        * @return Resource|bool Resource on success, false on failure
-        * @access private
-        */
-       function get_sock( $key ) {
-               if ( !$this->_active ) {
-                       return false;
-               }
-
-               if ( $this->_single_sock !== null ) {
-                       return $this->sock_to_host( $this->_single_sock );
-               }
-
-               $hv = is_array( $key ) ? intval( $key[0] ) : $this->_hashfunc( $key );
-               if ( $this->_buckets === null ) {
-                       $bu = array();
-                       foreach ( $this->_servers as $v ) {
-                               if ( is_array( $v ) ) {
-                                       for ( $i = 0; $i < $v[1]; $i++ ) {
-                                               $bu[] = $v[0];
-                                       }
-                               } else {
-                                       $bu[] = $v;
-                               }
-                       }
-                       $this->_buckets = $bu;
-                       $this->_bucketcount = count( $bu );
-               }
-
-               $realkey = is_array( $key ) ? $key[1] : $key;
-               for ( $tries = 0; $tries < 20; $tries++ ) {
-                       $host = $this->_buckets[$hv % $this->_bucketcount];
-                       $sock = $this->sock_to_host( $host );
-                       if ( is_resource( $sock ) ) {
-                               return $sock;
-                       }
-                       $hv = $this->_hashfunc( $hv . $realkey );
-               }
-
-               return false;
-       }
-
-       // }}}
-       // {{{ _hashfunc()
-
-       /**
-        * Creates a hash integer based on the $key
-        *
-        * @param string $key Key to hash
-        *
-        * @return int Hash value
-        * @access private
-        */
-       function _hashfunc( $key ) {
-               # Hash function must be in [0,0x7ffffff]
-               # We take the first 31 bits of the MD5 hash, which unlike the hash
-               # function used in a previous version of this client, works
-               return hexdec( substr( md5( $key ), 0, 8 ) ) & 0x7fffffff;
-       }
-
-       // }}}
-       // {{{ _incrdecr()
-
-       /**
-        * Perform increment/decriment on $key
-        *
-        * @param string $cmd Command to perform
-        * @param string|array $key Key to perform it on
-        * @param int $amt Amount to adjust
-        *
-        * @return int New value of $key
-        * @access private
-        */
-       function _incrdecr( $cmd, $key, $amt = 1 ) {
-               if ( !$this->_active ) {
-                       return null;
-               }
-
-               $sock = $this->get_sock( $key );
-               if ( !is_resource( $sock ) ) {
-                       return null;
-               }
-
-               $key = is_array( $key ) ? $key[1] : $key;
-               if ( isset( $this->stats[$cmd] ) ) {
-                       $this->stats[$cmd]++;
-               } else {
-                       $this->stats[$cmd] = 1;
-               }
-               if ( !$this->_fwrite( $sock, "$cmd $key $amt\r\n" ) ) {
-                       return null;
-               }
-
-               $line = $this->_fgets( $sock );
-               $match = array();
-               if ( !preg_match( '/^(\d+)/', $line, $match ) ) {
-                       return null;
-               }
-               return $match[1];
-       }
-
-       // }}}
-       // {{{ _load_items()
-
-       /**
-        * Load items into $ret from $sock
-        *
-        * @param Resource $sock Socket to read from
-        * @param array $ret returned values
-        * @param float $casToken [optional]
-        * @return bool True for success, false for failure
-        *
-        * @access private
-        */
-       function _load_items( $sock, &$ret, &$casToken = null ) {
-               $results = array();
-
-               while ( 1 ) {
-                       $decl = $this->_fgets( $sock );
-
-                       if ( $decl === false ) {
-                               /*
-                                * If nothing can be read, something is wrong because we know exactly when
-                                * to stop reading (right after "END") and we return right after that.
-                                */
-                               return false;
-                       } elseif ( preg_match( '/^VALUE (\S+) (\d+) (\d+) (\d+)$/', $decl, $match ) ) {
-                               /*
-                                * Read all data returned. This can be either one or multiple values.
-                                * Save all that data (in an array) to be processed later: we'll first
-                                * want to continue reading until "END" before doing anything else,
-                                * to make sure that we don't leave our client in a state where it's
-                                * output is not yet fully read.
-                                */
-                               $results[] = array(
-                                       $match[1], // rkey
-                                       $match[2], // flags
-                                       $match[3], // len
-                                       $match[4], // casToken
-                                       $this->_fread( $sock, $match[3] + 2 ), // data
-                               );
-                       } elseif ( $decl == "END" ) {
-                               if ( count( $results ) == 0 ) {
-                                       return false;
-                               }
-
-                               /**
-                                * All data has been read, time to process the data and build
-                                * meaningful return values.
-                                */
-                               foreach ( $results as $vars ) {
-                                       list( $rkey, $flags, $len, $casToken, $data ) = $vars;
-
-                                       if ( $data === false || substr( $data, -2 ) !== "\r\n" ) {
-                                               $this->_handle_error( $sock,
-                                                       'line ending missing from data block from $1' );
-                                               return false;
-                                       }
-                                       $data = substr( $data, 0, -2 );
-                                       $ret[$rkey] = $data;
-
-                                       if ( $this->_have_zlib && $flags & self::COMPRESSED ) {
-                                               $ret[$rkey] = gzuncompress( $ret[$rkey] );
-                                       }
-
-                                       /*
-                                        * This unserialize is the exact reason that we only want to
-                                        * process data after having read until "END" (instead of doing
-                                        * this right away): "unserialize" can trigger outside code:
-                                        * in the event that $ret[$rkey] is a serialized object,
-                                        * unserializing it will trigger __wakeup() if present. If that
-                                        * function attempted to read from memcached (while we did not
-                                        * yet read "END"), these 2 calls would collide.
-                                        */
-                                       if ( $flags & self::SERIALIZED ) {
-                                               $ret[$rkey] = $this->unserialize( $ret[$rkey] );
-                                       } elseif ( $flags & self::INTVAL ) {
-                                               $ret[$rkey] = intval( $ret[$rkey] );
-                                       }
-                               }
-
-                               return true;
-                       } else {
-                               $this->_handle_error( $sock, 'Error parsing response from $1' );
-                               return false;
-                       }
-               }
-       }
-
-       // }}}
-       // {{{ _set()
-
-       /**
-        * Performs the requested storage operation to the memcache server
-        *
-        * @param string $cmd Command to perform
-        * @param string $key Key to act on
-        * @param mixed $val What we need to store
-        * @param int $exp (optional) Expiration time. This can be a number of seconds
-        * to cache for (up to 30 days inclusive).  Any timespans of 30 days + 1 second or
-        * longer must be the timestamp of the time at which the mapping should expire. It
-        * is safe to use timestamps in all cases, regardless of exipration
-        * eg: strtotime("+3 hour")
-        * @param float $casToken [optional]
-        *
-        * @return bool
-        * @access private
-        */
-       function _set( $cmd, $key, $val, $exp, $casToken = null ) {
-               if ( !$this->_active ) {
-                       return false;
-               }
-
-               $sock = $this->get_sock( $key );
-               if ( !is_resource( $sock ) ) {
-                       return false;
-               }
-
-               if ( isset( $this->stats[$cmd] ) ) {
-                       $this->stats[$cmd]++;
-               } else {
-                       $this->stats[$cmd] = 1;
-               }
-
-               $flags = 0;
-
-               if ( is_int( $val ) ) {
-                       $flags |= self::INTVAL;
-               } elseif ( !is_scalar( $val ) ) {
-                       $val = $this->serialize( $val );
-                       $flags |= self::SERIALIZED;
-                       if ( $this->_debug ) {
-                               $this->_debugprint( sprintf( "client: serializing data as it is not scalar" ) );
-                       }
-               }
-
-               $len = strlen( $val );
-
-               if ( $this->_have_zlib && $this->_compress_enable
-                       && $this->_compress_threshold && $len >= $this->_compress_threshold
-               ) {
-                       $c_val = gzcompress( $val, 9 );
-                       $c_len = strlen( $c_val );
-
-                       if ( $c_len < $len * ( 1 - self::COMPRESSION_SAVINGS ) ) {
-                               if ( $this->_debug ) {
-                                       $this->_debugprint( sprintf( "client: compressing data; was %d bytes is now %d bytes", $len, $c_len ) );
-                               }
-                               $val = $c_val;
-                               $len = $c_len;
-                               $flags |= self::COMPRESSED;
-                       }
-               }
-
-               $command = "$cmd $key $flags $exp $len";
-               if ( $casToken ) {
-                       $command .= " $casToken";
-               }
-
-               if ( !$this->_fwrite( $sock, "$command\r\n$val\r\n" ) ) {
-                       return false;
-               }
-
-               $line = $this->_fgets( $sock );
-
-               if ( $this->_debug ) {
-                       $this->_debugprint( sprintf( "%s %s (%s)", $cmd, $key, $line ) );
-               }
-               if ( $line === "STORED" ) {
-                       return true;
-               } elseif ( $line === "NOT_STORED" && $cmd === "set" ) {
-                       // "Not stored" is always used as the mcrouter response with AllAsyncRoute
-                       return true;
-               }
-
-               return false;
-       }
-
-       // }}}
-       // {{{ sock_to_host()
-
-       /**
-        * Returns the socket for the host
-        *
-        * @param string $host Host:IP to get socket for
-        *
-        * @return Resource|bool IO Stream or false
-        * @access private
-        */
-       function sock_to_host( $host ) {
-               if ( isset( $this->_cache_sock[$host] ) ) {
-                       return $this->_cache_sock[$host];
-               }
-
-               $sock = null;
-               $now = time();
-               list( $ip, /* $port */) = explode( ':', $host );
-               if ( isset( $this->_host_dead[$host] ) && $this->_host_dead[$host] > $now ||
-                       isset( $this->_host_dead[$ip] ) && $this->_host_dead[$ip] > $now
-               ) {
-                       return null;
-               }
-
-               if ( !$this->_connect_sock( $sock, $host ) ) {
-                       return null;
-               }
-
-               // Do not buffer writes
-               stream_set_write_buffer( $sock, 0 );
-
-               $this->_cache_sock[$host] = $sock;
-
-               return $this->_cache_sock[$host];
-       }
-
-       /**
-        * @param string $text
-        */
-       function _debugprint( $text ) {
-               $this->_logger->debug( $text );
-       }
-
-       /**
-        * @param string $text
-        */
-       function _error_log( $text ) {
-               $this->_logger->error( "Memcached error: $text" );
-       }
-
-       /**
-        * Write to a stream. If there is an error, mark the socket dead.
-        *
-        * @param Resource $sock The socket
-        * @param string $buf The string to write
-        * @return bool True on success, false on failure
-        */
-       function _fwrite( $sock, $buf ) {
-               $bytesWritten = 0;
-               $bufSize = strlen( $buf );
-               while ( $bytesWritten < $bufSize ) {
-                       $result = fwrite( $sock, $buf );
-                       $data = stream_get_meta_data( $sock );
-                       if ( $data['timed_out'] ) {
-                               $this->_handle_error( $sock, 'timeout writing to $1' );
-                               return false;
-                       }
-                       // Contrary to the documentation, fwrite() returns zero on error in PHP 5.3.
-                       if ( $result === false || $result === 0 ) {
-                               $this->_handle_error( $sock, 'error writing to $1' );
-                               return false;
-                       }
-                       $bytesWritten += $result;
-               }
-
-               return true;
-       }
-
-       /**
-        * Handle an I/O error. Mark the socket dead and log an error.
-        *
-        * @param Resource $sock
-        * @param string $msg
-        */
-       function _handle_error( $sock, $msg ) {
-               $peer = stream_socket_get_name( $sock, true /** remote **/ );
-               if ( strval( $peer ) === '' ) {
-                       $peer = array_search( $sock, $this->_cache_sock );
-                       if ( $peer === false ) {
-                               $peer = '[unknown host]';
-                       }
-               }
-               $msg = str_replace( '$1', $peer, $msg );
-               $this->_error_log( "$msg" );
-               $this->_dead_sock( $sock );
-       }
-
-       /**
-        * Read the specified number of bytes from a stream. If there is an error,
-        * mark the socket dead.
-        *
-        * @param Resource $sock The socket
-        * @param int $len The number of bytes to read
-        * @return string|bool The string on success, false on failure.
-        */
-       function _fread( $sock, $len ) {
-               $buf = '';
-               while ( $len > 0 ) {
-                       $result = fread( $sock, $len );
-                       $data = stream_get_meta_data( $sock );
-                       if ( $data['timed_out'] ) {
-                               $this->_handle_error( $sock, 'timeout reading from $1' );
-                               return false;
-                       }
-                       if ( $result === false ) {
-                               $this->_handle_error( $sock, 'error reading buffer from $1' );
-                               return false;
-                       }
-                       if ( $result === '' ) {
-                               // This will happen if the remote end of the socket is shut down
-                               $this->_handle_error( $sock, 'unexpected end of file reading from $1' );
-                               return false;
-                       }
-                       $len -= strlen( $result );
-                       $buf .= $result;
-               }
-               return $buf;
-       }
-
-       /**
-        * Read a line from a stream. If there is an error, mark the socket dead.
-        * The \r\n line ending is stripped from the response.
-        *
-        * @param Resource $sock The socket
-        * @return string|bool The string on success, false on failure
-        */
-       function _fgets( $sock ) {
-               $result = fgets( $sock );
-               // fgets() may return a partial line if there is a select timeout after
-               // a successful recv(), so we have to check for a timeout even if we
-               // got a string response.
-               $data = stream_get_meta_data( $sock );
-               if ( $data['timed_out'] ) {
-                       $this->_handle_error( $sock, 'timeout reading line from $1' );
-                       return false;
-               }
-               if ( $result === false ) {
-                       $this->_handle_error( $sock, 'error reading line from $1' );
-                       return false;
-               }
-               if ( substr( $result, -2 ) === "\r\n" ) {
-                       $result = substr( $result, 0, -2 );
-               } elseif ( substr( $result, -1 ) === "\n" ) {
-                       $result = substr( $result, 0, -1 );
-               } else {
-                       $this->_handle_error( $sock, 'line ending missing in response from $1' );
-                       return false;
-               }
-               return $result;
-       }
-
-       /**
-        * Flush the read buffer of a stream
-        * @param Resource $f
-        */
-       function _flush_read_buffer( $f ) {
-               if ( !is_resource( $f ) ) {
-                       return;
-               }
-               $r = array( $f );
-               $w = null;
-               $e = null;
-               $n = stream_select( $r, $w, $e, 0, 0 );
-               while ( $n == 1 && !feof( $f ) ) {
-                       fread( $f, 1024 );
-                       $r = array( $f );
-                       $w = null;
-                       $e = null;
-                       $n = stream_select( $r, $w, $e, 0, 0 );
-               }
-       }
-
-       // }}}
-       // }}}
-       // }}}
-}
-
-// }}}
index 3df483d..9bf3f42 100644 (file)
@@ -250,7 +250,7 @@ class MemcachedPeclBagOStuff extends MemcachedBagOStuff {
                return $this->checkResult( $key, $result );
        }
 
-       public function incr( $key, $value = 1 ) {
+       public function incr( $key, $value = 1, $flags = 0 ) {
                $this->debug( "incr($key)" );
 
                $result = $this->acquireSyncClient()->increment( $key, $value );
@@ -258,7 +258,7 @@ class MemcachedPeclBagOStuff extends MemcachedBagOStuff {
                return $this->checkResult( $key, $result );
        }
 
-       public function decr( $key, $value = 1 ) {
+       public function decr( $key, $value = 1, $flags = 0 ) {
                $this->debug( "decr($key)" );
 
                $result = $this->acquireSyncClient()->decrement( $key, $value );
@@ -332,7 +332,7 @@ class MemcachedPeclBagOStuff extends MemcachedBagOStuff {
 
                // The PECL implementation is a naïve for-loop so use async I/O to pipeline;
                // https://github.com/php-memcached-dev/php-memcached/blob/master/php_memcached.c#L1852
-               if ( ( $flags & self::WRITE_BACKGROUND ) == self::WRITE_BACKGROUND ) {
+               if ( $this->fieldHasFlags( $flags, self::WRITE_BACKGROUND ) ) {
                        $client = $this->acquireAsyncClient();
                        $result = $client->setMulti( $data, $exptime );
                        $this->releaseAsyncClient( $client );
@@ -352,7 +352,7 @@ class MemcachedPeclBagOStuff extends MemcachedBagOStuff {
 
                // The PECL implementation is a naïve for-loop so use async I/O to pipeline;
                // https://github.com/php-memcached-dev/php-memcached/blob/7443d16d02fb73cdba2e90ae282446f80969229c/php_memcached.c#L1852
-               if ( ( $flags & self::WRITE_BACKGROUND ) == self::WRITE_BACKGROUND ) {
+               if ( $this->fieldHasFlags( $flags, self::WRITE_BACKGROUND ) ) {
                        $client = $this->acquireAsyncClient();
                        $resultArray = $client->deleteMulti( $keys ) ?: [];
                        $this->releaseAsyncClient( $client );
index 8144231..fc6deef 100644 (file)
@@ -93,13 +93,13 @@ class MemcachedPhpBagOStuff extends MemcachedBagOStuff {
                );
        }
 
-       public function incr( $key, $value = 1 ) {
+       public function incr( $key, $value = 1, $flags = 0 ) {
                $n = $this->client->incr( $this->validateKeyEncoding( $key ), $value );
 
                return ( $n !== false && $n !== null ) ? $n : false;
        }
 
-       public function decr( $key, $value = 1 ) {
+       public function decr( $key, $value = 1, $flags = 0 ) {
                $n = $this->client->decr( $this->validateKeyEncoding( $key ), $value );
 
                return ( $n !== false && $n !== null ) ? $n : false;
index d150880..d0aa380 100644 (file)
@@ -106,7 +106,7 @@ class MultiWriteBagOStuff extends BagOStuff {
        }
 
        public function get( $key, $flags = 0 ) {
-               if ( ( $flags & self::READ_LATEST ) == self::READ_LATEST ) {
+               if ( $this->fieldHasFlags( $flags, self::READ_LATEST ) ) {
                        // If the latest write was a delete(), we do NOT want to fallback
                        // to the other tiers and possibly see the old value. Also, this
                        // is used by merge(), which only needs to hit the primary.
@@ -123,9 +123,10 @@ class MultiWriteBagOStuff extends BagOStuff {
                        $missIndexes[] = $i;
                }
 
-               if ( $value !== false
-                       && $missIndexes
-                       && ( $flags & self::READ_VERIFIED ) == self::READ_VERIFIED
+               if (
+                       $value !== false &&
+                       $this->fieldHasFlags( $flags, self::READ_VERIFIED ) &&
+                       $missIndexes
                ) {
                        // Backfill the value to the higher (and often faster/smaller) cache tiers
                        $this->doWrite(
@@ -265,7 +266,7 @@ class MultiWriteBagOStuff extends BagOStuff {
                );
        }
 
-       public function incr( $key, $value = 1 ) {
+       public function incr( $key, $value = 1, $flags = 0 ) {
                return $this->doWrite(
                        $this->cacheIndexes,
                        $this->asyncWrites,
@@ -274,7 +275,7 @@ class MultiWriteBagOStuff extends BagOStuff {
                );
        }
 
-       public function decr( $key, $value = 1 ) {
+       public function decr( $key, $value = 1, $flags = 0 ) {
                return $this->doWrite(
                        $this->cacheIndexes,
                        $this->asyncWrites,
@@ -283,7 +284,7 @@ class MultiWriteBagOStuff extends BagOStuff {
                );
        }
 
-       public function incrWithInit( $key, $ttl, $value = 1, $init = 1 ) {
+       public function incrWithInit( $key, $exptime, $value = 1, $init = null, $flags = 0 ) {
                return $this->doWrite(
                        $this->cacheIndexes,
                        $this->asyncWrites,
@@ -346,7 +347,7 @@ class MultiWriteBagOStuff extends BagOStuff {
         * @return bool
         */
        protected function usesAsyncWritesGivenFlags( $flags ) {
-               return ( ( $flags & self::WRITE_SYNC ) == self::WRITE_SYNC ) ? false : $this->asyncWrites;
+               return $this->fieldHasFlags( $flags, self::WRITE_SYNC ) ? false : $this->asyncWrites;
        }
 
        public function makeKeyInternal( $keyspace, $args ) {
index b8ce38b..82b5ac0 100644 (file)
@@ -188,11 +188,11 @@ class RESTBagOStuff extends MediumSpecificBagOStuff {
                return $this->handleError( "Failed to delete $key", $rcode, $rerr, $rhdrs, $rbody );
        }
 
-       public function incr( $key, $value = 1 ) {
+       public function incr( $key, $value = 1, $flags = 0 ) {
                // @TODO: make this atomic
                $n = $this->get( $key, self::READ_LATEST );
                if ( $this->isInteger( $n ) ) { // key exists?
-                       $n = max( $n + intval( $value ), 0 );
+                       $n = max( $n + (int)$value, 0 );
                        // @TODO: respect $exptime
                        return $this->set( $key, $n ) ? $n : false;
                }
@@ -200,6 +200,10 @@ class RESTBagOStuff extends MediumSpecificBagOStuff {
                return false;
        }
 
+       public function decr( $key, $value = 1, $flags = 0 ) {
+               return $this->incr( $key, -$value, $flags );
+       }
+
        /**
         * Processes the response body.
         *
index 252b1fa..57a2507 100644 (file)
@@ -364,7 +364,7 @@ class RedisBagOStuff extends MediumSpecificBagOStuff {
                return $result;
        }
 
-       public function incr( $key, $value = 1 ) {
+       public function incr( $key, $value = 1, $flags = 0 ) {
                $conn = $this->getConnection( $key );
                if ( !$conn ) {
                        return false;
@@ -386,6 +386,28 @@ class RedisBagOStuff extends MediumSpecificBagOStuff {
                return $result;
        }
 
+       public function decr( $key, $value = 1, $flags = 0 ) {
+               $conn = $this->getConnection( $key );
+               if ( !$conn ) {
+                       return false;
+               }
+
+               try {
+                       if ( !$conn->exists( $key ) ) {
+                               return false;
+                       }
+                       // @FIXME: on races, the key may have a 0 TTL
+                       $result = $conn->decrBy( $key, $value );
+               } catch ( RedisException $e ) {
+                       $result = false;
+                       $this->handleException( $conn, $e );
+               }
+
+               $this->logRequest( 'decr', $key, $conn->getServer(), $result );
+
+               return $result;
+       }
+
        protected function doChangeTTL( $key, $exptime, $flags ) {
                $conn = $this->getConnection( $key );
                if ( !$conn ) {
index 504d515..0b5ac46 100644 (file)
@@ -76,7 +76,7 @@ class ReplicatedBagOStuff extends BagOStuff {
        }
 
        public function get( $key, $flags = 0 ) {
-               return ( ( $flags & self::READ_LATEST ) == self::READ_LATEST )
+               return $this->fieldHasFlags( $flags, self::READ_LATEST )
                        ? $this->writeStore->get( $key, $flags )
                        : $this->readStore->get( $key, $flags );
        }
@@ -118,7 +118,7 @@ class ReplicatedBagOStuff extends BagOStuff {
        }
 
        public function getMulti( array $keys, $flags = 0 ) {
-               return ( ( $flags & self::READ_LATEST ) == self::READ_LATEST )
+               return $this->fieldHasFlags( $flags, self::READ_LATEST )
                        ? $this->writeStore->getMulti( $keys, $flags )
                        : $this->readStore->getMulti( $keys, $flags );
        }
@@ -135,16 +135,16 @@ class ReplicatedBagOStuff extends BagOStuff {
                return $this->writeStore->changeTTLMulti( $keys, $exptime, $flags );
        }
 
-       public function incr( $key, $value = 1 ) {
-               return $this->writeStore->incr( $key, $value );
+       public function incr( $key, $value = 1, $flags = 0 ) {
+               return $this->writeStore->incr( $key, $value, $flags );
        }
 
-       public function decr( $key, $value = 1 ) {
-               return $this->writeStore->decr( $key, $value );
+       public function decr( $key, $value = 1, $flags = 0 ) {
+               return $this->writeStore->decr( $key, $value, $flags );
        }
 
-       public function incrWithInit( $key, $ttl, $value = 1, $init = 1 ) {
-               return $this->writeStore->incrWithInit( $key, $ttl, $value, $init );
+       public function incrWithInit( $key, $exptime, $value = 1, $init = null, $flags = 0 ) {
+               return $this->writeStore->incrWithInit( $key, $exptime, $value, $init, $flags );
        }
 
        public function getLastError() {
index 3c4efbb..5b38628 100644 (file)
@@ -100,14 +100,6 @@ class WinCacheBagOStuff extends MediumSpecificBagOStuff {
                return true;
        }
 
-       /**
-        * Construct a cache key.
-        *
-        * @since 1.27
-        * @param string $keyspace
-        * @param array $args
-        * @return string
-        */
        public function makeKeyInternal( $keyspace, $args ) {
                // WinCache keys have a maximum length of 150 characters. From that,
                // subtract the number of characters we need for the keyspace and for
@@ -136,13 +128,7 @@ class WinCacheBagOStuff extends MediumSpecificBagOStuff {
                return $keyspace . ':' . implode( ':', $args );
        }
 
-       /**
-        * Increase stored value of $key by $value while preserving its original TTL
-        * @param string $key Key to increase
-        * @param int $value Value to add to $key (Default 1)
-        * @return int|bool New value or false on failure
-        */
-       public function incr( $key, $value = 1 ) {
+       public function incr( $key, $value = 1, $flags = 0 ) {
                if ( !wincache_lock( $key ) ) { // optimize with FIFO lock
                        return false;
                }
@@ -160,4 +146,8 @@ class WinCacheBagOStuff extends MediumSpecificBagOStuff {
 
                return $n;
        }
+
+       public function decr( $key, $value = 1, $flags = 0 ) {
+               return $this->incr( $key, -$value, $flags );
+       }
 }
diff --git a/includes/libs/objectcache/utils/MemcachedClient.php b/includes/libs/objectcache/utils/MemcachedClient.php
new file mode 100644 (file)
index 0000000..2c40854
--- /dev/null
@@ -0,0 +1,1311 @@
+<?php
+// phpcs:ignoreFile -- It's an external lib and it isn't. Let's not bother.
+/**
+ * Memcached client for PHP.
+ *
+ * +---------------------------------------------------------------------------+
+ * | memcached client, PHP                                                     |
+ * +---------------------------------------------------------------------------+
+ * | Copyright (c) 2003 Ryan T. Dean <rtdean@cytherianage.net>                 |
+ * | All rights reserved.                                                      |
+ * |                                                                           |
+ * | Redistribution and use in source and binary forms, with or without        |
+ * | modification, are permitted provided that the following conditions        |
+ * | are met:                                                                  |
+ * |                                                                           |
+ * | 1. Redistributions of source code must retain the above copyright         |
+ * |    notice, this list of conditions and the following disclaimer.          |
+ * | 2. Redistributions in binary form must reproduce the above copyright      |
+ * |    notice, this list of conditions and the following disclaimer in the    |
+ * |    documentation and/or other materials provided with the distribution.   |
+ * |                                                                           |
+ * | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR      |
+ * | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
+ * | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.   |
+ * | IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,          |
+ * | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT  |
+ * | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
+ * | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY     |
+ * | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT       |
+ * | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF  |
+ * | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.         |
+ * +---------------------------------------------------------------------------+
+ * | Author: Ryan T. Dean <rtdean@cytherianage.net>                            |
+ * | Heavily influenced by the Perl memcached client by Brad Fitzpatrick.      |
+ * |   Permission granted by Brad Fitzpatrick for relicense of ported Perl     |
+ * |   client logic under 2-clause BSD license.                                |
+ * +---------------------------------------------------------------------------+
+ *
+ * @file
+ * $TCAnet$
+ */
+
+/**
+ * This is a PHP client for memcached - a distributed memory cache daemon.
+ *
+ * More information is available at http://www.danga.com/memcached/
+ *
+ * Usage example:
+ *
+ *     $mc = new MemcachedClient(array(
+ *         'servers' => array(
+ *             '127.0.0.1:10000',
+ *             array( '192.0.0.1:10010', 2 ),
+ *             '127.0.0.1:10020'
+ *         ),
+ *         'debug'   => false,
+ *         'compress_threshold' => 10240,
+ *         'persistent' => true
+ *     ));
+ *
+ *     $mc->add( 'key', array( 'some', 'array' ) );
+ *     $mc->replace( 'key', 'some random string' );
+ *     $val = $mc->get( 'key' );
+ *
+ * @author Ryan T. Dean <rtdean@cytherianage.net>
+ * @version 0.1.2
+ */
+
+use Psr\Log\LoggerInterface;
+use Psr\Log\NullLogger;
+
+// {{{ class MemcachedClient
+/**
+ * memcached client class implemented using (p)fsockopen()
+ *
+ * @author  Ryan T. Dean <rtdean@cytherianage.net>
+ * @ingroup Cache
+ */
+class MemcachedClient {
+       // {{{ properties
+       // {{{ public
+
+       // {{{ constants
+       // {{{ flags
+
+       /**
+        * Flag: indicates data is serialized
+        */
+       const SERIALIZED = 1;
+
+       /**
+        * Flag: indicates data is compressed
+        */
+       const COMPRESSED = 2;
+
+       /**
+        * Flag: indicates data is an integer
+        */
+       const INTVAL = 4;
+
+       // }}}
+
+       /**
+        * Minimum savings to store data compressed
+        */
+       const COMPRESSION_SAVINGS = 0.20;
+
+       // }}}
+
+       /**
+        * Command statistics
+        *
+        * @var array
+        * @access public
+        */
+       public $stats;
+
+       // }}}
+       // {{{ private
+
+       /**
+        * Cached Sockets that are connected
+        *
+        * @var array
+        * @access private
+        */
+       public $_cache_sock;
+
+       /**
+        * Current debug status; 0 - none to 9 - profiling
+        *
+        * @var bool
+        * @access private
+        */
+       public $_debug;
+
+       /**
+        * Dead hosts, assoc array, 'host'=>'unixtime when ok to check again'
+        *
+        * @var array
+        * @access private
+        */
+       public $_host_dead;
+
+       /**
+        * Is compression available?
+        *
+        * @var bool
+        * @access private
+        */
+       public $_have_zlib;
+
+       /**
+        * Do we want to use compression?
+        *
+        * @var bool
+        * @access private
+        */
+       public $_compress_enable;
+
+       /**
+        * At how many bytes should we compress?
+        *
+        * @var int
+        * @access private
+        */
+       public $_compress_threshold;
+
+       /**
+        * Are we using persistent links?
+        *
+        * @var bool
+        * @access private
+        */
+       public $_persistent;
+
+       /**
+        * If only using one server; contains ip:port to connect to
+        *
+        * @var string
+        * @access private
+        */
+       public $_single_sock;
+
+       /**
+        * Array containing ip:port or array(ip:port, weight)
+        *
+        * @var array
+        * @access private
+        */
+       public $_servers;
+
+       /**
+        * Our bit buckets
+        *
+        * @var array
+        * @access private
+        */
+       public $_buckets;
+
+       /**
+        * Total # of bit buckets we have
+        *
+        * @var int
+        * @access private
+        */
+       public $_bucketcount;
+
+       /**
+        * # of total servers we have
+        *
+        * @var int
+        * @access private
+        */
+       public $_active;
+
+       /**
+        * Stream timeout in seconds. Applies for example to fread()
+        *
+        * @var int
+        * @access private
+        */
+       public $_timeout_seconds;
+
+       /**
+        * Stream timeout in microseconds
+        *
+        * @var int
+        * @access private
+        */
+       public $_timeout_microseconds;
+
+       /**
+        * Connect timeout in seconds
+        */
+       public $_connect_timeout;
+
+       /**
+        * Number of connection attempts for each server
+        */
+       public $_connect_attempts;
+
+       /**
+        * @var LoggerInterface
+        */
+       private $_logger;
+
+       // }}}
+       // }}}
+       // {{{ methods
+       // {{{ public functions
+       // {{{ memcached()
+
+       /**
+        * Memcache initializer
+        *
+        * @param array $args Associative array of settings
+        */
+       public function __construct( $args ) {
+               $this->set_servers( $args['servers'] ?? array() );
+               $this->_debug = $args['debug'] ?? false;
+               $this->stats = array();
+               $this->_compress_threshold = $args['compress_threshold'] ?? 0;
+               $this->_persistent = $args['persistent'] ?? false;
+               $this->_compress_enable = true;
+               $this->_have_zlib = function_exists( 'gzcompress' );
+
+               $this->_cache_sock = array();
+               $this->_host_dead = array();
+
+               $this->_timeout_seconds = 0;
+               $this->_timeout_microseconds = $args['timeout'] ?? 500000;
+
+               $this->_connect_timeout = $args['connect_timeout'] ?? 0.1;
+               $this->_connect_attempts = 2;
+
+               $this->_logger = $args['logger'] ?? new NullLogger();
+       }
+
+       // }}}
+
+       /**
+        * @param mixed $value
+        * @return string|integer
+        */
+       public function serialize( $value ) {
+               return serialize( $value );
+       }
+
+       /**
+        * @param string $value
+        * @return mixed
+        */
+       public function unserialize( $value ) {
+               return unserialize( $value );
+       }
+
+       // {{{ add()
+
+       /**
+        * Adds a key/value to the memcache server if one isn't already set with
+        * that key
+        *
+        * @param string $key Key to set with data
+        * @param mixed $val Value to store
+        * @param int $exp (optional) Expiration time. This can be a number of seconds
+        * to cache for (up to 30 days inclusive).  Any timespans of 30 days + 1 second or
+        * longer must be the timestamp of the time at which the mapping should expire. It
+        * is safe to use timestamps in all cases, regardless of expiration
+        * eg: strtotime("+3 hour")
+        *
+        * @return bool
+        */
+       public function add( $key, $val, $exp = 0 ) {
+               return $this->_set( 'add', $key, $val, $exp );
+       }
+
+       // }}}
+       // {{{ decr()
+
+       /**
+        * Decrease a value stored on the memcache server
+        *
+        * @param string $key Key to decrease
+        * @param int $amt (optional) amount to decrease
+        *
+        * @return mixed False on failure, value on success
+        */
+       public function decr( $key, $amt = 1 ) {
+               return $this->_incrdecr( 'decr', $key, $amt );
+       }
+
+       // }}}
+       // {{{ delete()
+
+       /**
+        * Deletes a key from the server, optionally after $time
+        *
+        * @param string $key Key to delete
+        * @param int $time (optional) how long to wait before deleting
+        *
+        * @return bool True on success, false on failure
+        */
+       public function delete( $key, $time = 0 ) {
+               if ( !$this->_active ) {
+                       return false;
+               }
+
+               $sock = $this->get_sock( $key );
+               if ( !is_resource( $sock ) ) {
+                       return false;
+               }
+
+               $key = is_array( $key ) ? $key[1] : $key;
+
+               if ( isset( $this->stats['delete'] ) ) {
+                       $this->stats['delete']++;
+               } else {
+                       $this->stats['delete'] = 1;
+               }
+               $cmd = "delete $key $time\r\n";
+               if ( !$this->_fwrite( $sock, $cmd ) ) {
+                       return false;
+               }
+               $res = $this->_fgets( $sock );
+
+               if ( $this->_debug ) {
+                       $this->_debugprint( sprintf( "MemCache: delete %s (%s)", $key, $res ) );
+               }
+
+               if ( $res == "DELETED" || $res == "NOT_FOUND" ) {
+                       return true;
+               }
+
+               return false;
+       }
+
+       /**
+        * Changes the TTL on a key from the server to $time
+        *
+        * @param string $key
+        * @param int $time TTL in seconds
+        *
+        * @return bool True on success, false on failure
+        */
+       public function touch( $key, $time = 0 ) {
+               if ( !$this->_active ) {
+                       return false;
+               }
+
+               $sock = $this->get_sock( $key );
+               if ( !is_resource( $sock ) ) {
+                       return false;
+               }
+
+               $key = is_array( $key ) ? $key[1] : $key;
+
+               if ( isset( $this->stats['touch'] ) ) {
+                       $this->stats['touch']++;
+               } else {
+                       $this->stats['touch'] = 1;
+               }
+               $cmd = "touch $key $time\r\n";
+               if ( !$this->_fwrite( $sock, $cmd ) ) {
+                       return false;
+               }
+               $res = $this->_fgets( $sock );
+
+               if ( $this->_debug ) {
+                       $this->_debugprint( sprintf( "MemCache: touch %s (%s)", $key, $res ) );
+               }
+
+               if ( $res == "TOUCHED" ) {
+                       return true;
+               }
+
+               return false;
+       }
+
+       // }}}
+       // {{{ disconnect_all()
+
+       /**
+        * Disconnects all connected sockets
+        */
+       public function disconnect_all() {
+               foreach ( $this->_cache_sock as $sock ) {
+                       fclose( $sock );
+               }
+
+               $this->_cache_sock = array();
+       }
+
+       // }}}
+       // {{{ enable_compress()
+
+       /**
+        * Enable / Disable compression
+        *
+        * @param bool $enable True to enable, false to disable
+        */
+       public function enable_compress( $enable ) {
+               $this->_compress_enable = $enable;
+       }
+
+       // }}}
+       // {{{ forget_dead_hosts()
+
+       /**
+        * Forget about all of the dead hosts
+        */
+       public function forget_dead_hosts() {
+               $this->_host_dead = array();
+       }
+
+       // }}}
+       // {{{ get()
+
+       /**
+        * Retrieves the value associated with the key from the memcache server
+        *
+        * @param array|string $key key to retrieve
+        * @param float $casToken [optional]
+        *
+        * @return mixed
+        */
+       public function get( $key, &$casToken = null ) {
+               if ( $this->_debug ) {
+                       $this->_debugprint( "get($key)" );
+               }
+
+               if ( !is_array( $key ) && strval( $key ) === '' ) {
+                       $this->_debugprint( "Skipping key which equals to an empty string" );
+                       return false;
+               }
+
+               if ( !$this->_active ) {
+                       return false;
+               }
+
+               $sock = $this->get_sock( $key );
+
+               if ( !is_resource( $sock ) ) {
+                       return false;
+               }
+
+               $key = is_array( $key ) ? $key[1] : $key;
+               if ( isset( $this->stats['get'] ) ) {
+                       $this->stats['get']++;
+               } else {
+                       $this->stats['get'] = 1;
+               }
+
+               $cmd = "gets $key\r\n";
+               if ( !$this->_fwrite( $sock, $cmd ) ) {
+                       return false;
+               }
+
+               $val = array();
+               $this->_load_items( $sock, $val, $casToken );
+
+               if ( $this->_debug ) {
+                       foreach ( $val as $k => $v ) {
+                               $this->_debugprint(
+                                       sprintf( "MemCache: sock %s got %s", $this->serialize( $sock ), $k ) );
+                       }
+               }
+
+               $value = false;
+               if ( isset( $val[$key] ) ) {
+                       $value = $val[$key];
+               }
+               return $value;
+       }
+
+       // }}}
+       // {{{ get_multi()
+
+       /**
+        * Get multiple keys from the server(s)
+        *
+        * @param array $keys Keys to retrieve
+        *
+        * @return array
+        */
+       public function get_multi( $keys ) {
+               if ( !$this->_active ) {
+                       return array();
+               }
+
+               if ( isset( $this->stats['get_multi'] ) ) {
+                       $this->stats['get_multi']++;
+               } else {
+                       $this->stats['get_multi'] = 1;
+               }
+               $sock_keys = array();
+               $socks = array();
+               foreach ( $keys as $key ) {
+                       $sock = $this->get_sock( $key );
+                       if ( !is_resource( $sock ) ) {
+                               continue;
+                       }
+                       $key = is_array( $key ) ? $key[1] : $key;
+                       if ( !isset( $sock_keys[$sock] ) ) {
+                               $sock_keys[intval( $sock )] = array();
+                               $socks[] = $sock;
+                       }
+                       $sock_keys[intval( $sock )][] = $key;
+               }
+
+               $gather = array();
+               // Send out the requests
+               foreach ( $socks as $sock ) {
+                       $cmd = 'gets';
+                       foreach ( $sock_keys[intval( $sock )] as $key ) {
+                               $cmd .= ' ' . $key;
+                       }
+                       $cmd .= "\r\n";
+
+                       if ( $this->_fwrite( $sock, $cmd ) ) {
+                               $gather[] = $sock;
+                       }
+               }
+
+               // Parse responses
+               $val = array();
+               foreach ( $gather as $sock ) {
+                       $this->_load_items( $sock, $val, $casToken );
+               }
+
+               if ( $this->_debug ) {
+                       foreach ( $val as $k => $v ) {
+                               $this->_debugprint( sprintf( "MemCache: got %s", $k ) );
+                       }
+               }
+
+               return $val;
+       }
+
+       // }}}
+       // {{{ incr()
+
+       /**
+        * Increments $key (optionally) by $amt
+        *
+        * @param string $key Key to increment
+        * @param int $amt (optional) amount to increment
+        *
+        * @return int|null Null if the key does not exist yet (this does NOT
+        * create new mappings if the key does not exist). If the key does
+        * exist, this returns the new value for that key.
+        */
+       public function incr( $key, $amt = 1 ) {
+               return $this->_incrdecr( 'incr', $key, $amt );
+       }
+
+       // }}}
+       // {{{ replace()
+
+       /**
+        * Overwrites an existing value for key; only works if key is already set
+        *
+        * @param string $key Key to set value as
+        * @param mixed $value Value to store
+        * @param int $exp (optional) Expiration time. This can be a number of seconds
+        * to cache for (up to 30 days inclusive).  Any timespans of 30 days + 1 second or
+        * longer must be the timestamp of the time at which the mapping should expire. It
+        * is safe to use timestamps in all cases, regardless of exipration
+        * eg: strtotime("+3 hour")
+        *
+        * @return bool
+        */
+       public function replace( $key, $value, $exp = 0 ) {
+               return $this->_set( 'replace', $key, $value, $exp );
+       }
+
+       // }}}
+       // {{{ run_command()
+
+       /**
+        * Passes through $cmd to the memcache server connected by $sock; returns
+        * output as an array (null array if no output)
+        *
+        * @param Resource $sock Socket to send command on
+        * @param string $cmd Command to run
+        *
+        * @return array Output array
+        */
+       public function run_command( $sock, $cmd ) {
+               if ( !is_resource( $sock ) ) {
+                       return array();
+               }
+
+               if ( !$this->_fwrite( $sock, $cmd ) ) {
+                       return array();
+               }
+
+               $ret = array();
+               while ( true ) {
+                       $res = $this->_fgets( $sock );
+                       $ret[] = $res;
+                       if ( preg_match( '/^END/', $res ) ) {
+                               break;
+                       }
+                       if ( strlen( $res ) == 0 ) {
+                               break;
+                       }
+               }
+               return $ret;
+       }
+
+       // }}}
+       // {{{ set()
+
+       /**
+        * Unconditionally sets a key to a given value in the memcache.  Returns true
+        * if set successfully.
+        *
+        * @param string $key Key to set value as
+        * @param mixed $value Value to set
+        * @param int $exp (optional) Expiration time. This can be a number of seconds
+        * to cache for (up to 30 days inclusive).  Any timespans of 30 days + 1 second or
+        * longer must be the timestamp of the time at which the mapping should expire. It
+        * is safe to use timestamps in all cases, regardless of exipration
+        * eg: strtotime("+3 hour")
+        *
+        * @return bool True on success
+        */
+       public function set( $key, $value, $exp = 0 ) {
+               return $this->_set( 'set', $key, $value, $exp );
+       }
+
+       // }}}
+       // {{{ cas()
+
+       /**
+        * Sets a key to a given value in the memcache if the current value still corresponds
+        * to a known, given value.  Returns true if set successfully.
+        *
+        * @param float $casToken Current known value
+        * @param string $key Key to set value as
+        * @param mixed $value Value to set
+        * @param int $exp (optional) Expiration time. This can be a number of seconds
+        * to cache for (up to 30 days inclusive).  Any timespans of 30 days + 1 second or
+        * longer must be the timestamp of the time at which the mapping should expire. It
+        * is safe to use timestamps in all cases, regardless of exipration
+        * eg: strtotime("+3 hour")
+        *
+        * @return bool True on success
+        */
+       public function cas( $casToken, $key, $value, $exp = 0 ) {
+               return $this->_set( 'cas', $key, $value, $exp, $casToken );
+       }
+
+       // }}}
+       // {{{ set_compress_threshold()
+
+       /**
+        * Set the compression threshold
+        *
+        * @param int $thresh Threshold to compress if larger than
+        */
+       public function set_compress_threshold( $thresh ) {
+               $this->_compress_threshold = $thresh;
+       }
+
+       // }}}
+       // {{{ set_debug()
+
+       /**
+        * Set the debug flag
+        *
+        * @see __construct()
+        * @param bool $dbg True for debugging, false otherwise
+        */
+       public function set_debug( $dbg ) {
+               $this->_debug = $dbg;
+       }
+
+       // }}}
+       // {{{ set_servers()
+
+       /**
+        * Set the server list to distribute key gets and puts between
+        *
+        * @see __construct()
+        * @param array $list Array of servers to connect to
+        */
+       public function set_servers( $list ) {
+               $this->_servers = $list;
+               $this->_active = count( $list );
+               $this->_buckets = null;
+               $this->_bucketcount = 0;
+
+               $this->_single_sock = null;
+               if ( $this->_active == 1 ) {
+                       $this->_single_sock = $this->_servers[0];
+               }
+       }
+
+       /**
+        * Sets the timeout for new connections
+        *
+        * @param int $seconds Number of seconds
+        * @param int $microseconds Number of microseconds
+        */
+       public function set_timeout( $seconds, $microseconds ) {
+               $this->_timeout_seconds = $seconds;
+               $this->_timeout_microseconds = $microseconds;
+       }
+
+       // }}}
+       // }}}
+       // {{{ private methods
+       // {{{ _close_sock()
+
+       /**
+        * Close the specified socket
+        *
+        * @param string $sock Socket to close
+        *
+        * @access private
+        */
+       function _close_sock( $sock ) {
+               $host = array_search( $sock, $this->_cache_sock );
+               fclose( $this->_cache_sock[$host] );
+               unset( $this->_cache_sock[$host] );
+       }
+
+       // }}}
+       // {{{ _connect_sock()
+
+       /**
+        * Connects $sock to $host, timing out after $timeout
+        *
+        * @param int $sock Socket to connect
+        * @param string $host Host:IP to connect to
+        *
+        * @return bool
+        * @access private
+        */
+       function _connect_sock( &$sock, $host ) {
+               list( $ip, $port ) = preg_split( '/:(?=\d)/', $host );
+               $sock = false;
+               $timeout = $this->_connect_timeout;
+               $errno = $errstr = null;
+               for ( $i = 0; !$sock && $i < $this->_connect_attempts; $i++ ) {
+                       Wikimedia\suppressWarnings();
+                       if ( $this->_persistent == 1 ) {
+                               $sock = pfsockopen( $ip, $port, $errno, $errstr, $timeout );
+                       } else {
+                               $sock = fsockopen( $ip, $port, $errno, $errstr, $timeout );
+                       }
+                       Wikimedia\restoreWarnings();
+               }
+               if ( !$sock ) {
+                       $this->_error_log( "Error connecting to $host: $errstr" );
+                       $this->_dead_host( $host );
+                       return false;
+               }
+
+               // Initialise timeout
+               stream_set_timeout( $sock, $this->_timeout_seconds, $this->_timeout_microseconds );
+
+               // If the connection was persistent, flush the read buffer in case there
+               // was a previous incomplete request on this connection
+               if ( $this->_persistent ) {
+                       $this->_flush_read_buffer( $sock );
+               }
+               return true;
+       }
+
+       // }}}
+       // {{{ _dead_sock()
+
+       /**
+        * Marks a host as dead until 30-40 seconds in the future
+        *
+        * @param string $sock Socket to mark as dead
+        *
+        * @access private
+        */
+       function _dead_sock( $sock ) {
+               $host = array_search( $sock, $this->_cache_sock );
+               $this->_dead_host( $host );
+       }
+
+       /**
+        * @param string $host
+        */
+       function _dead_host( $host ) {
+               $ip = explode( ':', $host )[0];
+               $this->_host_dead[$ip] = time() + 30 + intval( rand( 0, 10 ) );
+               $this->_host_dead[$host] = $this->_host_dead[$ip];
+               unset( $this->_cache_sock[$host] );
+       }
+
+       // }}}
+       // {{{ get_sock()
+
+       /**
+        * get_sock
+        *
+        * @param string $key Key to retrieve value for;
+        *
+        * @return Resource|bool Resource on success, false on failure
+        * @access private
+        */
+       function get_sock( $key ) {
+               if ( !$this->_active ) {
+                       return false;
+               }
+
+               if ( $this->_single_sock !== null ) {
+                       return $this->sock_to_host( $this->_single_sock );
+               }
+
+               $hv = is_array( $key ) ? intval( $key[0] ) : $this->_hashfunc( $key );
+               if ( $this->_buckets === null ) {
+                       $bu = array();
+                       foreach ( $this->_servers as $v ) {
+                               if ( is_array( $v ) ) {
+                                       for ( $i = 0; $i < $v[1]; $i++ ) {
+                                               $bu[] = $v[0];
+                                       }
+                               } else {
+                                       $bu[] = $v;
+                               }
+                       }
+                       $this->_buckets = $bu;
+                       $this->_bucketcount = count( $bu );
+               }
+
+               $realkey = is_array( $key ) ? $key[1] : $key;
+               for ( $tries = 0; $tries < 20; $tries++ ) {
+                       $host = $this->_buckets[$hv % $this->_bucketcount];
+                       $sock = $this->sock_to_host( $host );
+                       if ( is_resource( $sock ) ) {
+                               return $sock;
+                       }
+                       $hv = $this->_hashfunc( $hv . $realkey );
+               }
+
+               return false;
+       }
+
+       // }}}
+       // {{{ _hashfunc()
+
+       /**
+        * Creates a hash integer based on the $key
+        *
+        * @param string $key Key to hash
+        *
+        * @return int Hash value
+        * @access private
+        */
+       function _hashfunc( $key ) {
+               # Hash function must be in [0,0x7ffffff]
+               # We take the first 31 bits of the MD5 hash, which unlike the hash
+               # function used in a previous version of this client, works
+               return hexdec( substr( md5( $key ), 0, 8 ) ) & 0x7fffffff;
+       }
+
+       // }}}
+       // {{{ _incrdecr()
+
+       /**
+        * Perform increment/decriment on $key
+        *
+        * @param string $cmd Command to perform
+        * @param string|array $key Key to perform it on
+        * @param int $amt Amount to adjust
+        *
+        * @return int New value of $key
+        * @access private
+        */
+       function _incrdecr( $cmd, $key, $amt = 1 ) {
+               if ( !$this->_active ) {
+                       return null;
+               }
+
+               $sock = $this->get_sock( $key );
+               if ( !is_resource( $sock ) ) {
+                       return null;
+               }
+
+               $key = is_array( $key ) ? $key[1] : $key;
+               if ( isset( $this->stats[$cmd] ) ) {
+                       $this->stats[$cmd]++;
+               } else {
+                       $this->stats[$cmd] = 1;
+               }
+               if ( !$this->_fwrite( $sock, "$cmd $key $amt\r\n" ) ) {
+                       return null;
+               }
+
+               $line = $this->_fgets( $sock );
+               $match = array();
+               if ( !preg_match( '/^(\d+)/', $line, $match ) ) {
+                       return null;
+               }
+               return $match[1];
+       }
+
+       // }}}
+       // {{{ _load_items()
+
+       /**
+        * Load items into $ret from $sock
+        *
+        * @param Resource $sock Socket to read from
+        * @param array $ret returned values
+        * @param float $casToken [optional]
+        * @return bool True for success, false for failure
+        *
+        * @access private
+        */
+       function _load_items( $sock, &$ret, &$casToken = null ) {
+               $results = array();
+
+               while ( 1 ) {
+                       $decl = $this->_fgets( $sock );
+
+                       if ( $decl === false ) {
+                               /*
+                                * If nothing can be read, something is wrong because we know exactly when
+                                * to stop reading (right after "END") and we return right after that.
+                                */
+                               return false;
+                       } elseif ( preg_match( '/^VALUE (\S+) (\d+) (\d+) (\d+)$/', $decl, $match ) ) {
+                               /*
+                                * Read all data returned. This can be either one or multiple values.
+                                * Save all that data (in an array) to be processed later: we'll first
+                                * want to continue reading until "END" before doing anything else,
+                                * to make sure that we don't leave our client in a state where it's
+                                * output is not yet fully read.
+                                */
+                               $results[] = array(
+                                       $match[1], // rkey
+                                       $match[2], // flags
+                                       $match[3], // len
+                                       $match[4], // casToken
+                                       $this->_fread( $sock, $match[3] + 2 ), // data
+                               );
+                       } elseif ( $decl == "END" ) {
+                               if ( count( $results ) == 0 ) {
+                                       return false;
+                               }
+
+                               /**
+                                * All data has been read, time to process the data and build
+                                * meaningful return values.
+                                */
+                               foreach ( $results as $vars ) {
+                                       list( $rkey, $flags, $len, $casToken, $data ) = $vars;
+
+                                       if ( $data === false || substr( $data, -2 ) !== "\r\n" ) {
+                                               $this->_handle_error( $sock,
+                                                       'line ending missing from data block from $1' );
+                                               return false;
+                                       }
+                                       $data = substr( $data, 0, -2 );
+                                       $ret[$rkey] = $data;
+
+                                       if ( $this->_have_zlib && $flags & self::COMPRESSED ) {
+                                               $ret[$rkey] = gzuncompress( $ret[$rkey] );
+                                       }
+
+                                       /*
+                                        * This unserialize is the exact reason that we only want to
+                                        * process data after having read until "END" (instead of doing
+                                        * this right away): "unserialize" can trigger outside code:
+                                        * in the event that $ret[$rkey] is a serialized object,
+                                        * unserializing it will trigger __wakeup() if present. If that
+                                        * function attempted to read from memcached (while we did not
+                                        * yet read "END"), these 2 calls would collide.
+                                        */
+                                       if ( $flags & self::SERIALIZED ) {
+                                               $ret[$rkey] = $this->unserialize( $ret[$rkey] );
+                                       } elseif ( $flags & self::INTVAL ) {
+                                               $ret[$rkey] = intval( $ret[$rkey] );
+                                       }
+                               }
+
+                               return true;
+                       } else {
+                               $this->_handle_error( $sock, 'Error parsing response from $1' );
+                               return false;
+                       }
+               }
+       }
+
+       // }}}
+       // {{{ _set()
+
+       /**
+        * Performs the requested storage operation to the memcache server
+        *
+        * @param string $cmd Command to perform
+        * @param string $key Key to act on
+        * @param mixed $val What we need to store
+        * @param int $exp (optional) Expiration time. This can be a number of seconds
+        * to cache for (up to 30 days inclusive).  Any timespans of 30 days + 1 second or
+        * longer must be the timestamp of the time at which the mapping should expire. It
+        * is safe to use timestamps in all cases, regardless of exipration
+        * eg: strtotime("+3 hour")
+        * @param float $casToken [optional]
+        *
+        * @return bool
+        * @access private
+        */
+       function _set( $cmd, $key, $val, $exp, $casToken = null ) {
+               if ( !$this->_active ) {
+                       return false;
+               }
+
+               $sock = $this->get_sock( $key );
+               if ( !is_resource( $sock ) ) {
+                       return false;
+               }
+
+               if ( isset( $this->stats[$cmd] ) ) {
+                       $this->stats[$cmd]++;
+               } else {
+                       $this->stats[$cmd] = 1;
+               }
+
+               $flags = 0;
+
+               if ( is_int( $val ) ) {
+                       $flags |= self::INTVAL;
+               } elseif ( !is_scalar( $val ) ) {
+                       $val = $this->serialize( $val );
+                       $flags |= self::SERIALIZED;
+                       if ( $this->_debug ) {
+                               $this->_debugprint( sprintf( "client: serializing data as it is not scalar" ) );
+                       }
+               }
+
+               $len = strlen( $val );
+
+               if ( $this->_have_zlib && $this->_compress_enable
+                       && $this->_compress_threshold && $len >= $this->_compress_threshold
+               ) {
+                       $c_val = gzcompress( $val, 9 );
+                       $c_len = strlen( $c_val );
+
+                       if ( $c_len < $len * ( 1 - self::COMPRESSION_SAVINGS ) ) {
+                               if ( $this->_debug ) {
+                                       $this->_debugprint( sprintf( "client: compressing data; was %d bytes is now %d bytes", $len, $c_len ) );
+                               }
+                               $val = $c_val;
+                               $len = $c_len;
+                               $flags |= self::COMPRESSED;
+                       }
+               }
+
+               $command = "$cmd $key $flags $exp $len";
+               if ( $casToken ) {
+                       $command .= " $casToken";
+               }
+
+               if ( !$this->_fwrite( $sock, "$command\r\n$val\r\n" ) ) {
+                       return false;
+               }
+
+               $line = $this->_fgets( $sock );
+
+               if ( $this->_debug ) {
+                       $this->_debugprint( sprintf( "%s %s (%s)", $cmd, $key, $line ) );
+               }
+               if ( $line === "STORED" ) {
+                       return true;
+               } elseif ( $line === "NOT_STORED" && $cmd === "set" ) {
+                       // "Not stored" is always used as the mcrouter response with AllAsyncRoute
+                       return true;
+               }
+
+               return false;
+       }
+
+       // }}}
+       // {{{ sock_to_host()
+
+       /**
+        * Returns the socket for the host
+        *
+        * @param string $host Host:IP to get socket for
+        *
+        * @return Resource|bool IO Stream or false
+        * @access private
+        */
+       function sock_to_host( $host ) {
+               if ( isset( $this->_cache_sock[$host] ) ) {
+                       return $this->_cache_sock[$host];
+               }
+
+               $sock = null;
+               $now = time();
+               list( $ip, /* $port */) = explode( ':', $host );
+               if ( isset( $this->_host_dead[$host] ) && $this->_host_dead[$host] > $now ||
+                       isset( $this->_host_dead[$ip] ) && $this->_host_dead[$ip] > $now
+               ) {
+                       return null;
+               }
+
+               if ( !$this->_connect_sock( $sock, $host ) ) {
+                       return null;
+               }
+
+               // Do not buffer writes
+               stream_set_write_buffer( $sock, 0 );
+
+               $this->_cache_sock[$host] = $sock;
+
+               return $this->_cache_sock[$host];
+       }
+
+       /**
+        * @param string $text
+        */
+       function _debugprint( $text ) {
+               $this->_logger->debug( $text );
+       }
+
+       /**
+        * @param string $text
+        */
+       function _error_log( $text ) {
+               $this->_logger->error( "Memcached error: $text" );
+       }
+
+       /**
+        * Write to a stream. If there is an error, mark the socket dead.
+        *
+        * @param Resource $sock The socket
+        * @param string $buf The string to write
+        * @return bool True on success, false on failure
+        */
+       function _fwrite( $sock, $buf ) {
+               $bytesWritten = 0;
+               $bufSize = strlen( $buf );
+               while ( $bytesWritten < $bufSize ) {
+                       $result = fwrite( $sock, $buf );
+                       $data = stream_get_meta_data( $sock );
+                       if ( $data['timed_out'] ) {
+                               $this->_handle_error( $sock, 'timeout writing to $1' );
+                               return false;
+                       }
+                       // Contrary to the documentation, fwrite() returns zero on error in PHP 5.3.
+                       if ( $result === false || $result === 0 ) {
+                               $this->_handle_error( $sock, 'error writing to $1' );
+                               return false;
+                       }
+                       $bytesWritten += $result;
+               }
+
+               return true;
+       }
+
+       /**
+        * Handle an I/O error. Mark the socket dead and log an error.
+        *
+        * @param Resource $sock
+        * @param string $msg
+        */
+       function _handle_error( $sock, $msg ) {
+               $peer = stream_socket_get_name( $sock, true /** remote **/ );
+               if ( strval( $peer ) === '' ) {
+                       $peer = array_search( $sock, $this->_cache_sock );
+                       if ( $peer === false ) {
+                               $peer = '[unknown host]';
+                       }
+               }
+               $msg = str_replace( '$1', $peer, $msg );
+               $this->_error_log( "$msg" );
+               $this->_dead_sock( $sock );
+       }
+
+       /**
+        * Read the specified number of bytes from a stream. If there is an error,
+        * mark the socket dead.
+        *
+        * @param Resource $sock The socket
+        * @param int $len The number of bytes to read
+        * @return string|bool The string on success, false on failure.
+        */
+       function _fread( $sock, $len ) {
+               $buf = '';
+               while ( $len > 0 ) {
+                       $result = fread( $sock, $len );
+                       $data = stream_get_meta_data( $sock );
+                       if ( $data['timed_out'] ) {
+                               $this->_handle_error( $sock, 'timeout reading from $1' );
+                               return false;
+                       }
+                       if ( $result === false ) {
+                               $this->_handle_error( $sock, 'error reading buffer from $1' );
+                               return false;
+                       }
+                       if ( $result === '' ) {
+                               // This will happen if the remote end of the socket is shut down
+                               $this->_handle_error( $sock, 'unexpected end of file reading from $1' );
+                               return false;
+                       }
+                       $len -= strlen( $result );
+                       $buf .= $result;
+               }
+               return $buf;
+       }
+
+       /**
+        * Read a line from a stream. If there is an error, mark the socket dead.
+        * The \r\n line ending is stripped from the response.
+        *
+        * @param Resource $sock The socket
+        * @return string|bool The string on success, false on failure
+        */
+       function _fgets( $sock ) {
+               $result = fgets( $sock );
+               // fgets() may return a partial line if there is a select timeout after
+               // a successful recv(), so we have to check for a timeout even if we
+               // got a string response.
+               $data = stream_get_meta_data( $sock );
+               if ( $data['timed_out'] ) {
+                       $this->_handle_error( $sock, 'timeout reading line from $1' );
+                       return false;
+               }
+               if ( $result === false ) {
+                       $this->_handle_error( $sock, 'error reading line from $1' );
+                       return false;
+               }
+               if ( substr( $result, -2 ) === "\r\n" ) {
+                       $result = substr( $result, 0, -2 );
+               } elseif ( substr( $result, -1 ) === "\n" ) {
+                       $result = substr( $result, 0, -1 );
+               } else {
+                       $this->_handle_error( $sock, 'line ending missing in response from $1' );
+                       return false;
+               }
+               return $result;
+       }
+
+       /**
+        * Flush the read buffer of a stream
+        * @param Resource $f
+        */
+       function _flush_read_buffer( $f ) {
+               if ( !is_resource( $f ) ) {
+                       return;
+               }
+               $r = array( $f );
+               $w = null;
+               $e = null;
+               $n = stream_select( $r, $w, $e, 0, 0 );
+               while ( $n == 1 && !feof( $f ) ) {
+                       fread( $f, 1024 );
+                       $r = array( $f );
+                       $w = null;
+                       $e = null;
+                       $n = stream_select( $r, $w, $e, 0, 0 );
+               }
+       }
+
+       // }}}
+       // }}}
+       // }}}
+}
+
+// }}}
index 1852685..b88b496 100644 (file)
@@ -506,6 +506,7 @@ class WANObjectCache implements IExpiringStore, IStoreKeyEncoder, LoggerAwareInt
                        }
                        $purgeValues[] = $purge;
                }
+
                return $purgeValues;
        }
 
@@ -2207,14 +2208,14 @@ class WANObjectCache implements IExpiringStore, IStoreKeyEncoder, LoggerAwareInt
                        // Wildcards select all matching routes, e.g. the WAN cluster on all DCs
                        $ok = $this->cache->set(
                                "/*/{$this->cluster}/{$key}",
-                               $this->makePurgeValue( $this->getCurrentTime(), self::HOLDOFF_TTL_NONE ),
+                               $this->makePurgeValue( $this->getCurrentTime(), $holdoff ),
                                $ttl
                        );
                } else {
-                       // This handles the mcrouter and the single-DC case
+                       // Some other proxy handles broadcasting or there is only one datacenter
                        $ok = $this->cache->set(
                                $key,
-                               $this->makePurgeValue( $this->getCurrentTime(), self::HOLDOFF_TTL_NONE ),
+                               $this->makePurgeValue( $this->getCurrentTime(), $holdoff ),
                                $ttl
                        );
                }
index 8615cfc..e1398b8 100644 (file)
@@ -224,10 +224,9 @@ class ChronologyProtector implements LoggerAwareInterface {
                        implode( ', ', array_keys( $this->shutdownPositions ) ) . "\n"
                );
 
-               // CP-protected writes should overwhelmingly go to the master datacenter, so use a
-               // DC-local lock to merge the values. Use a DC-local get() and a synchronous all-DC
-               // set(). This makes it possible for the BagOStuff class to write in parallel to all
-               // DCs with one RTT. The use of WRITE_SYNC avoids needing READ_LATEST for the get().
+               // CP-protected writes should overwhelmingly go to the master datacenter, so merge the
+               // positions with a DC-local lock, a DC-local get(), and an all-DC set() with WRITE_SYNC.
+               // If set() returns success, then any get() should be able to see the new positions.
                if ( $store->lock( $this->key, 3 ) ) {
                        if ( $workCallback ) {
                                // Let the store run the work before blocking on a replication sync barrier.
index 6029f77..084500a 100644 (file)
@@ -47,37 +47,6 @@ use Throwable;
  * @since 1.28
  */
 abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAwareInterface {
-       /** @var string Server that this instance is currently connected to */
-       protected $server;
-       /** @var string User that this instance is currently connected under the name of */
-       protected $user;
-       /** @var string Password used to establish the current connection */
-       protected $password;
-       /** @var array[] Map of (table => (dbname, schema, prefix) map) */
-       protected $tableAliases = [];
-       /** @var string[] Map of (index alias => index) */
-       protected $indexAliases = [];
-       /** @var bool Whether this PHP instance is for a CLI script */
-       protected $cliMode;
-       /** @var string Agent name for query profiling */
-       protected $agent;
-       /** @var int Bit field of class DBO_* constants */
-       protected $flags;
-       /** @var array LoadBalancer tracking information */
-       protected $lbInfo = [];
-       /** @var array|bool Variables use for schema element placeholders */
-       protected $schemaVars = false;
-       /** @var array Parameters used by initConnection() to establish a connection */
-       protected $connectionParams = [];
-       /** @var array SQL variables values to use for all new connections */
-       protected $connectionVariables = [];
-       /** @var string Current SQL query delimiter */
-       protected $delimiter = ';';
-       /** @var string|bool|null Stashed value of html_errors INI setting */
-       protected $htmlErrors;
-       /** @var int Row batch size to use for emulated INSERT SELECT queries */
-       protected $nonNativeInsertSelectBatchSize = 10000;
-
        /** @var BagOStuff APC cache */
        protected $srvCache;
        /** @var LoggerInterface */
@@ -92,25 +61,62 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
        protected $profiler;
        /** @var TransactionProfiler */
        protected $trxProfiler;
+
        /** @var DatabaseDomain */
        protected $currentDomain;
+
        /** @var object|resource|null Database connection */
        protected $conn;
 
        /** @var IDatabase|null Lazy handle to the master DB this server replicates from */
        private $lazyMasterHandle;
 
+       /** @var string Server that this instance is currently connected to */
+       protected $server;
+       /** @var string User that this instance is currently connected under the name of */
+       protected $user;
+       /** @var string Password used to establish the current connection */
+       protected $password;
+       /** @var bool Whether this PHP instance is for a CLI script */
+       protected $cliMode;
+       /** @var string Agent name for query profiling */
+       protected $agent;
+       /** @var array Parameters used by initConnection() to establish a connection */
+       protected $connectionParams;
+       /** @var string[]|int[]|float[] SQL variables values to use for all new connections */
+       protected $connectionVariables;
+       /** @var int Row batch size to use for emulated INSERT SELECT queries */
+       protected $nonNativeInsertSelectBatchSize;
+
+       /** @var int Current bit field of class DBO_* constants */
+       protected $flags;
+       /** @var array Current LoadBalancer tracking information */
+       protected $lbInfo = [];
+       /** @var string Current SQL query delimiter */
+       protected $delimiter = ';';
+       /** @var array[] Current map of (table => (dbname, schema, prefix) map) */
+       protected $tableAliases = [];
+       /** @var string[] Current map of (index alias => index) */
+       protected $indexAliases = [];
+       /** @var array|null Current variables use for schema element placeholders */
+       protected $schemaVars;
+
+       /** @var string|bool|null Stashed value of html_errors INI setting */
+       private $htmlErrors;
+       /** @var int[] Prior flags member variable values */
+       private $priorFlags = [];
+
        /** @var array Map of (name => 1) for locks obtained via lock() */
        protected $sessionNamedLocks = [];
        /** @var array Map of (table name => 1) for TEMPORARY tables */
        protected $sessionTempTables = [];
 
        /** @var string ID of the active transaction or the empty string otherwise */
-       protected $trxShortId = '';
+       private $trxShortId = '';
        /** @var int Transaction status */
-       protected $trxStatus = self::STATUS_TRX_NONE;
+       private $trxStatus = self::STATUS_TRX_NONE;
        /** @var Exception|null The last error that caused the status to become STATUS_TRX_ERROR */
-       protected $trxStatusCause;
+       private $trxStatusCause;
        /** @var array|null Error details of the last statement-only rollback */
        private $trxStatusIgnoredCause;
        /** @var float|null UNIX timestamp at the time of BEGIN for the last transaction */
@@ -154,9 +160,6 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
        /** @var bool Whether to suppress triggering of transaction end callbacks */
        private $trxEndCallbacksSuppressed = false;
 
-       /** @var int[] Prior flags member variable values */
-       private $priorFlags = [];
-
        /** @var integer|null Rows affected by the last query to query() or its CRUD wrappers */
        protected $affectedRowCount;
 
@@ -233,15 +236,14 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
         * @note exceptions for missing libraries/drivers should be thrown in initConnection()
         * @param array $params Parameters passed from Database::factory()
         */
-       protected function __construct( array $params ) {
+       public function __construct( array $params ) {
+               $this->connectionParams = [];
                foreach ( [ 'host', 'user', 'password', 'dbname', 'schema', 'tablePrefix' ] as $name ) {
                        $this->connectionParams[$name] = $params[$name];
                }
-
+               $this->connectionVariables = $params['variables'] ?? [];
                $this->cliMode = $params['cliMode'];
-               // Agent name is added to SQL queries in a comment, so make sure it can't break out
-               $this->agent = str_replace( '/', '-', $params['agent'] );
-
+               $this->agent = $params['agent'];
                $this->flags = $params['flags'];
                if ( $this->flags & self::DBO_DEFAULT ) {
                        if ( $this->cliMode ) {
@@ -250,11 +252,9 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                                $this->flags |= self::DBO_TRX;
                        }
                }
-
-               $this->connectionVariables = $params['variables'];
+               $this->nonNativeInsertSelectBatchSize = $params['nonNativeInsertSelectBatchSize'] ?? 10000;
 
                $this->srvCache = $params['srvCache'] ?? new HashBagOStuff();
-
                $this->profiler = is_callable( $params['profiler'] ) ? $params['profiler'] : null;
                $this->trxProfiler = $params['trxProfiler'];
                $this->connLogger = $params['connLogger'];
@@ -262,10 +262,6 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                $this->errorLogger = $params['errorLogger'];
                $this->deprecationLogger = $params['deprecationLogger'];
 
-               if ( isset( $params['nonNativeInsertSelectBatchSize'] ) ) {
-                       $this->nonNativeInsertSelectBatchSize = $params['nonNativeInsertSelectBatchSize'];
-               }
-
                // Set initial dummy domain until open() sets the final DB/prefix
                $this->currentDomain = new DatabaseDomain(
                        $params['dbname'] != '' ? $params['dbname'] : null,
@@ -395,6 +391,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                                'cliMode' => (bool)$params['cliMode'],
                                'agent' => (string)$params['agent'],
                                // Objects and callbacks
+                               'srvCache' => $params['srvCache'] ?? new HashBagOStuff(),
                                'profiler' => $params['profiler'] ?? null,
                                'trxProfiler' => $params['trxProfiler'] ?? new TransactionProfiler(),
                                'connLogger' => $params['connLogger'] ?? new NullLogger(),
@@ -491,7 +488,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
        }
 
        /**
-        * @return array Map of (Database::ATTR_* constant => value
+        * @return array Map of (Database::ATTR_* constant => value)
         * @since 1.31
         */
        protected static function getAttributes() {
@@ -965,7 +962,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
         * @throws DBReadOnlyError
         */
        protected function assertIsWritableMaster() {
-               if ( $this->getLBInfo( 'replica' ) === true ) {
+               if ( $this->getLBInfo( 'replica' ) ) {
                        throw new DBReadOnlyRoleError(
                                $this,
                                'Write operations are not allowed on replica database connections'
@@ -1148,7 +1145,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                // Send the query to the server and fetch any corresponding errors
                list( $ret, $err, $errno, $unignorable ) = $this->executeQuery( $sql, $fname, $flags );
                if ( $ret === false ) {
-                       $ignoreErrors = $this->hasFlags( $flags, self::QUERY_SILENCE_ERRORS );
+                       $ignoreErrors = $this->fieldHasBit( $flags, self::QUERY_SILENCE_ERRORS );
                        // Throw an error unless both the ignore flag was set and a rollback is not needed
                        $this->reportQueryError( $err, $errno, $sql, $fname, $ignoreErrors && !$unignorable );
                }
@@ -1188,11 +1185,11 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                        // Do not treat temporary table writes as "meaningful writes" since they are only
                        // visible to one session and are not permanent. Profile them as reads. Integration
                        // tests can override this behavior via $flags.
-                       $pseudoPermanent = $this->hasFlags( $flags, self::QUERY_PSEUDO_PERMANENT );
+                       $pseudoPermanent = $this->fieldHasBit( $flags, self::QUERY_PSEUDO_PERMANENT );
                        list( $tmpType, $tmpNew, $tmpDel ) = $this->getTempWrites( $sql, $pseudoPermanent );
                        $isPermWrite = ( $tmpType !== self::$TEMP_NORMAL );
                        // DBConnRef uses QUERY_REPLICA_ROLE to enforce the replica role for raw SQL queries
-                       if ( $isPermWrite && $this->hasFlags( $flags, self::QUERY_REPLICA_ROLE ) ) {
+                       if ( $isPermWrite && $this->fieldHasBit( $flags, self::QUERY_REPLICA_ROLE ) ) {
                                throw new DBReadOnlyRoleError( $this, "Cannot write; target role is DB_REPLICA" );
                        }
                } else {
@@ -1203,8 +1200,9 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                }
 
                // Add trace comment to the begin of the sql string, right after the operator.
-               // Or, for one-word queries (like "BEGIN" or COMMIT") add it to the end (T44598)
-               $commentedSql = preg_replace( '/\s|$/', " /* $fname {$this->agent} */ ", $sql, 1 );
+               // Or, for one-word queries (like "BEGIN" or COMMIT") add it to the end (T44598).
+               $encAgent = str_replace( '/', '-', $this->agent );
+               $commentedSql = preg_replace( '/\s|$/', " /* $fname $encAgent */ ", $sql, 1 );
 
                // Send the query to the server and fetch any corresponding errors.
                // This also doubles as a "ping" to see if the connection was dropped.
@@ -1212,7 +1210,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                        $this->executeQueryAttempt( $sql, $commentedSql, $isPermWrite, $fname, $flags );
 
                // Check if the query failed due to a recoverable connection loss
-               $allowRetry = !$this->hasFlags( $flags, self::QUERY_NO_RETRY );
+               $allowRetry = !$this->fieldHasBit( $flags, self::QUERY_NO_RETRY );
                if ( $ret === false && $recoverableCL && $reconnected && $allowRetry ) {
                        // Silently resend the query to the server since it is safe and possible
                        list( $ret, $err, $errno, $recoverableSR, $recoverableCL ) =
@@ -1283,7 +1281,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                        }
                }
 
-               $prefix = !is_null( $this->getLBInfo( 'master' ) ) ? 'query-m: ' : 'query: ';
+               $prefix = $this->getLBInfo( 'master' ) ? 'query-m: ' : 'query: ';
                $generalizedSql = new GeneralizedSql( $sql, $this->trxShortId, $prefix );
 
                $startTime = microtime( true );
@@ -1820,7 +1818,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                        $this->selectOptionsIncludeLocking( $options ) &&
                        $this->selectFieldsOrOptionsAggregate( $vars, $options )
                ) {
-                       // Some DB types (postgres/oracle) disallow FOR UPDATE with aggregate
+                       // Some DB types (e.g. postgres) disallow FOR UPDATE with aggregate
                        // functions. Discourage use of such queries to encourage compatibility.
                        call_user_func(
                                $this->deprecationLogger,
@@ -4279,10 +4277,8 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                }
 
                // This will reconnect if possible or return false if not
-               $this->clearFlag( self::DBO_TRX, self::REMEMBER_PRIOR );
-               $ok = ( $this->query( self::$PING_QUERY, __METHOD__, true ) !== false );
-               $this->restoreFlags( self::RESTORE_PRIOR );
-
+               $flags = self::QUERY_IGNORE_DBO_TRX | self::QUERY_SILENCE_ERRORS;
+               $ok = ( $this->query( self::$PING_QUERY, __METHOD__, $flags ) !== false );
                if ( $ok ) {
                        $rtt = $this->lastRoundTripEstimate;
                }
@@ -4472,7 +4468,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
        }
 
        public function setSchemaVars( $vars ) {
-               $this->schemaVars = $vars;
+               $this->schemaVars = is_array( $vars ) ? $vars : null;
        }
 
        public function sourceStream(
@@ -4624,11 +4620,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
         * @return array
         */
        protected function getSchemaVars() {
-               if ( $this->schemaVars ) {
-                       return $this->schemaVars;
-               } else {
-                       return $this->getDefaultSchemaVars();
-               }
+               return $this->schemaVars ?? $this->getDefaultSchemaVars();
        }
 
        /**
@@ -4818,8 +4810,9 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
         * @param int $field
         * @param int $flags
         * @return bool
+        * @since 1.34
         */
-       protected function hasFlags( $field, $flags ) {
+       final protected function fieldHasBit( $field, $flags ) {
                return ( ( $field & $flags ) === $flags );
        }
 
index a9223ac..851a178 100644 (file)
@@ -814,22 +814,16 @@ abstract class DatabaseMysqlBase extends Database {
        protected function getHeartbeatData( array $conds ) {
                // Query time and trip time are not counted
                $nowUnix = microtime( true );
-               // Do not bother starting implicit transactions here
-               $this->clearFlag( self::DBO_TRX, self::REMEMBER_PRIOR );
-               try {
-                       $whereSQL = $this->makeList( $conds, self::LIST_AND );
-                       // Use ORDER BY for channel based queries since that field might not be UNIQUE.
-                       // Note: this would use "TIMESTAMPDIFF(MICROSECOND,ts,UTC_TIMESTAMP(6))" but the
-                       // percision field is not supported in MySQL <= 5.5.
-                       $res = $this->query(
-                               "SELECT ts FROM heartbeat.heartbeat WHERE $whereSQL ORDER BY ts DESC LIMIT 1",
-                               __METHOD__,
-                               self::QUERY_SILENCE_ERRORS | self::QUERY_IGNORE_DBO_TRX
-                       );
-                       $row = $res ? $res->fetchObject() : false;
-               } finally {
-                       $this->restoreFlags();
-               }
+               $whereSQL = $this->makeList( $conds, self::LIST_AND );
+               // Use ORDER BY for channel based queries since that field might not be UNIQUE.
+               // Note: this would use "TIMESTAMPDIFF(MICROSECOND,ts,UTC_TIMESTAMP(6))" but the
+               // percision field is not supported in MySQL <= 5.5.
+               $res = $this->query(
+                       "SELECT ts FROM heartbeat.heartbeat WHERE $whereSQL ORDER BY ts DESC LIMIT 1",
+                       __METHOD__,
+                       self::QUERY_SILENCE_ERRORS | self::QUERY_IGNORE_DBO_TRX
+               );
+               $row = $res ? $res->fetchObject() : false;
 
                return [ $row ? $row->ts : null, $nowUnix ];
        }
@@ -1062,11 +1056,12 @@ abstract class DatabaseMysqlBase extends Database {
        }
 
        public function serverIsReadOnly() {
-               $flags = self::QUERY_IGNORE_DBO_TRX;
-               $res = $this->query( "SHOW GLOBAL VARIABLES LIKE 'read_only'", __METHOD__, $flags );
+               // Avoid SHOW to avoid internal temporary tables
+               $flags = self::QUERY_IGNORE_DBO_TRX | self::QUERY_SILENCE_ERRORS;
+               $res = $this->query( "SELECT @@GLOBAL.read_only AS Value", __METHOD__, $flags );
                $row = $this->fetchObject( $res );
 
-               return $row ? ( strtolower( $row->Value ) === 'on' ) : false;
+               return $row ? (bool)$row->Value : false;
        }
 
        /**
index 8768213..b4eb89a 100644 (file)
@@ -21,7 +21,7 @@ namespace Wikimedia\Rdbms;
 
 use InvalidArgumentException;
 use Wikimedia\ScopedCallback;
-use RuntimeException;
+use Exception;
 use stdClass;
 
 /**
@@ -99,9 +99,9 @@ interface IDatabase {
        const DBO_DEFAULT = 16;
        /** @var int Use DB persistent connections if possible */
        const DBO_PERSISTENT = 32;
-       /** @var int DBA session mode; mostly for Oracle */
+       /** @var int DBA session mode; was used by Oracle */
        const DBO_SYSDBA = 64;
-       /** @var int Schema file mode; mostly for Oracle */
+       /** @var int Schema file mode; was used by Oracle */
        const DBO_DDLMODE = 128;
        /** @var int Enable SSL/TLS in connection protocol */
        const DBO_SSL = 256;
@@ -130,8 +130,8 @@ interface IDatabase {
        const UNION_DISTINCT = false;
 
        /**
-        * A string describing the current software version, and possibly
-        * other details in a user-friendly way. Will be listed on Special:Version, etc.
+        * Get a human-readable string describing the current software version
+        *
         * Use getServerVersion() to get machine-friendly information.
         *
         * @return string Version information from the database server
@@ -168,34 +168,33 @@ interface IDatabase {
        public function explicitTrxActive();
 
        /**
-        * Assert that all explicit transactions or atomic sections have been closed.
+        * Assert that all explicit transactions or atomic sections have been closed
+        *
         * @throws DBTransactionError
         * @since 1.32
         */
        public function assertNoOpenTransactions();
 
        /**
-        * Get/set the table prefix.
-        * @param string|null $prefix The table prefix to set, or omitted to leave it unchanged.
+        * Get/set the table prefix
+        *
+        * @param string|null $prefix The table prefix to set, or omitted to leave it unchanged
         * @return string The previous table prefix
-        * @throws DBUnexpectedError
         */
        public function tablePrefix( $prefix = null );
 
        /**
-        * Get/set the db schema.
-        * @param string|null $schema The database schema to set, or omitted to leave it unchanged.
+        * Get/set the db schema
+        *
+        * @param string|null $schema The database schema to set, or omitted to leave it unchanged
         * @return string The previous db schema
         */
        public function dbSchema( $schema = null );
 
        /**
-        * Get properties passed down from the server info array of the load
-        * balancer.
-        *
-        * @param string|null $name The entry of the info array to get, or null to get the
-        *   whole array
+        * Get properties passed down from the server info array of the load balancer
         *
+        * @param string|null $name The entry of the info array to get, or null to get the whole array
         * @return array|mixed|null
         */
        public function getLBInfo( $name = null );
@@ -225,14 +224,14 @@ interface IDatabase {
        public function implicitOrderby();
 
        /**
-        * Return the last query that sent on account of IDatabase::query()
+        * Get the last query that sent on account of IDatabase::query()
+        *
         * @return string SQL text or empty string if there was no such query
         */
        public function lastQuery();
 
        /**
-        * Returns the last time the connection may have been used for write queries.
-        * Should return a timestamp if unsure.
+        * Get the last time the connection may have been used for a write query
         *
         * @return int|float UNIX timestamp or false
         * @since 1.24
@@ -264,7 +263,7 @@ interface IDatabase {
        /**
         * Get the time spend running write queries for this transaction
         *
-        * High times could be due to scanning, updates, locking, and such
+        * High values could be due to scanning, updates, locking, and such.
         *
         * @param string $type IDatabase::ESTIMATE_* constant [default: ESTIMATE_ALL]
         * @return float|bool Returns false if not transaction is active
@@ -289,8 +288,7 @@ interface IDatabase {
        public function pendingWriteRowsAffected();
 
        /**
-        * Is a connection to the database open?
-        * @return bool
+        * @return bool Whether a connection to the database open
         */
        public function isOpen();
 
@@ -336,38 +334,38 @@ interface IDatabase {
        public function getDomainID();
 
        /**
-        * Get the type of the DBMS, as it appears in $wgDBtype.
+        * Get the type of the DBMS (e.g. "mysql", "sqlite")
         *
         * @return string
         */
        public function getType();
 
        /**
-        * Fetch the next row from the given result object, in object form.
+        * Fetch the next row from the given result object, in object form
+        *
         * Fields can be retrieved with $row->fieldname, with fields acting like
-        * member variables.
-        * If no more rows are available, false is returned.
+        * member variables. If no more rows are available, false is returned.
         *
         * @param IResultWrapper|stdClass $res Object as returned from IDatabase::query(), etc.
         * @return stdClass|bool
-        * @throws DBUnexpectedError Thrown if the database returns an error
         */
        public function fetchObject( $res );
 
        /**
-        * Fetch the next row from the given result object, in associative array
-        * form. Fields are retrieved with $row['fieldname'].
+        * Fetch the next row from the given result object, in associative array form
+        *
+        * Fields are retrieved with $row['fieldname'].
         * If no more rows are available, false is returned.
         *
         * @param IResultWrapper $res Result object as returned from IDatabase::query(), etc.
         * @return array|bool
-        * @throws DBUnexpectedError Thrown if the database returns an error
         */
        public function fetchRow( $res );
 
        /**
-        * Get the number of rows in a query result. If the query did not return
-        * any rows (for example, if it was a write query), this returns zero.
+        * Get the number of rows in a query result
+        *
+        * Returns zero if the query did not return any rows or was a write query.
         *
         * @param mixed $res A SQL result
         * @return int
@@ -440,18 +438,16 @@ interface IDatabase {
        public function affectedRows();
 
        /**
-        * Returns a wikitext link to the DB's website, e.g.,
-        *   return "[https://www.mysql.com/ MySQL]";
-        * Should at least contain plain text, if for some reason
-        * your database has no website.
+        * Returns a wikitext style link to the DB's website (e.g. "[https://www.mysql.com/ MySQL]")
+        *
+        * Should at least contain plain text, if for some reason your database has no website.
         *
         * @return string Wikitext of a link to the server software's web site
         */
        public function getSoftwareLink();
 
        /**
-        * A string describing the current software version, like from
-        * mysql_get_server_info().
+        * A string describing the current software version, like from mysql_get_server_info()
         *
         * @return string Version information from the database server.
         */
@@ -464,14 +460,13 @@ interface IDatabase {
         * aside from read-only automatic transactions (assuming no callbacks are registered).
         * If a transaction is still open anyway, it will be rolled back.
         *
+        * @return bool Success
         * @throws DBError
-        * @return bool Operation success. true if already closed.
         */
        public function close();
 
        /**
-        * Run an SQL query and return the result. Normally throws a DBQueryError
-        * on failure. If errors are ignored, returns false instead.
+        * Run an SQL query and return the result
         *
         * If a connection loss is detected, then an attempt to reconnect will be made.
         * For queries that involve no larger transactions or locks, they will be re-issued
@@ -493,24 +488,24 @@ interface IDatabase {
         *     of errors is best handled by try/catch rather than using one of these flags.
         * @return bool|IResultWrapper True for a successful write query, IResultWrapper object
         *     for a successful read query, or false on failure if QUERY_SILENCE_ERRORS is set.
-        * @throws DBError
+        * @throws DBQueryError If the query is issued, fails, and QUERY_SILENCE_ERRORS is not set.
+        * @throws DBExpectedError If the query is not, and cannot, be issued yet (non-DBQueryError)
+        * @throws DBError If the query is inherently not allowed (non-DBExpectedError)
         */
        public function query( $sql, $fname = __METHOD__, $flags = 0 );
 
        /**
-        * Free a result object returned by query() or select(). It's usually not
-        * necessary to call this, just use unset() or let the variable holding
-        * the result object go out of scope.
+        * Free a result object returned by query() or select()
+        *
+        * It's usually not necessary to call this, just use unset() or let the variable
+        * holding the result object go out of scope.
         *
         * @param mixed $res A SQL result
         */
        public function freeResult( $res );
 
        /**
-        * A SELECT wrapper which returns a single field from a single result row.
-        *
-        * Usually throws a DBQueryError on failure. If errors are explicitly
-        * ignored, returns false on failure.
+        * A SELECT wrapper which returns a single field from a single result row
         *
         * If no result rows are returned from the query, false is returned.
         *
@@ -521,19 +516,15 @@ interface IDatabase {
         * @param string $fname The function name of the caller.
         * @param string|array $options The query options. See IDatabase::select() for details.
         * @param string|array $join_conds The query join conditions. See IDatabase::select() for details.
-        *
         * @return mixed The value from the field
-        * @throws DBError
+        * @throws DBError If an error occurs, see IDatabase::query()
         */
        public function selectField(
                $table, $var, $cond = '', $fname = __METHOD__, $options = [], $join_conds = []
        );
 
        /**
-        * A SELECT wrapper which returns a list of single field values from result rows.
-        *
-        * Usually throws a DBQueryError on failure. If errors are explicitly
-        * ignored, returns false on failure.
+        * A SELECT wrapper which returns a list of single field values from result rows
         *
         * If no result rows are returned from the query, false is returned.
         *
@@ -546,7 +537,7 @@ interface IDatabase {
         * @param string|array $join_conds The query join conditions. See IDatabase::select() for details.
         *
         * @return array The values from the field in the order they were returned from the DB
-        * @throws DBError
+        * @throws DBError If an error occurs, see IDatabase::query()
         * @since 1.25
         */
        public function selectFieldValues(
@@ -554,8 +545,7 @@ interface IDatabase {
        );
 
        /**
-        * Execute a SELECT query constructed using the various parameters provided.
-        * See below for full details of the parameters.
+        * Execute a SELECT query constructed using the various parameters provided
         *
         * @param string|array $table Table name(s)
         *
@@ -712,18 +702,23 @@ interface IDatabase {
         *    [ 'page' => [ 'LEFT JOIN', 'page_latest=rev_id' ] ]
         *
         * @return IResultWrapper Resulting rows
-        * @throws DBError
+        * @throws DBError If an error occurs, see IDatabase::query()
         */
        public function select(
-               $table, $vars, $conds = '', $fname = __METHOD__,
-               $options = [], $join_conds = []
+               $table,
+               $vars,
+               $conds = '',
+               $fname = __METHOD__,
+               $options = [],
+               $join_conds = []
        );
 
        /**
-        * The equivalent of IDatabase::select() except that the constructed SQL
-        * is returned, instead of being immediately executed. This can be useful for
-        * doing UNION queries, where the SQL text of each query is needed. In general,
-        * however, callers outside of Database classes should just use select().
+        * Take the same arguments as IDatabase::select() and return the SQL it would use
+        *
+        * This can be useful for making UNION queries, where the SQL text of each query
+        * is needed. In general, however, callers outside of Database classes should just
+        * use select().
         *
         * @see IDatabase::select()
         *
@@ -736,14 +731,20 @@ interface IDatabase {
         * @return string SQL query string
         */
        public function selectSQLText(
-               $table, $vars, $conds = '', $fname = __METHOD__,
-               $options = [], $join_conds = []
+               $table,
+               $vars,
+               $conds = '',
+               $fname = __METHOD__,
+               $options = [],
+               $join_conds = []
        );
 
        /**
-        * Single row SELECT wrapper. Equivalent to IDatabase::select(), except
-        * that a single row object is returned. If the query returns no rows,
-        * false is returned.
+        * Wrapper to IDatabase::select() that only fetches one row (via LIMIT)
+        *
+        * If the query returns no rows, false is returned.
+        *
+        * This method is convenient for fetching a row based on a unique key condition.
         *
         * @param string|array $table Table name
         * @param string|array $vars Field names
@@ -751,12 +752,16 @@ interface IDatabase {
         * @param string $fname Caller function name
         * @param string|array $options Query options
         * @param array|string $join_conds Join conditions
-        *
         * @return stdClass|bool
-        * @throws DBError
+        * @throws DBError If an error occurs, see IDatabase::query()
         */
-       public function selectRow( $table, $vars, $conds, $fname = __METHOD__,
-               $options = [], $join_conds = []
+       public function selectRow(
+               $table,
+               $vars,
+               $conds,
+               $fname = __METHOD__,
+               $options = [],
+               $join_conds = []
        );
 
        /**
@@ -779,7 +784,7 @@ interface IDatabase {
         * @param array $options Options for select
         * @param array|string $join_conds Join conditions
         * @return int Row count
-        * @throws DBError
+        * @throws DBError If an error occurs, see IDatabase::query()
         */
        public function estimateRowCount(
                $table, $var = '*', $conds = '', $fname = __METHOD__, $options = [], $join_conds = []
@@ -801,7 +806,7 @@ interface IDatabase {
         * @param array $options Options for select
         * @param array $join_conds Join conditions (since 1.27)
         * @return int Row count
-        * @throws DBError
+        * @throws DBError If an error occurs, see IDatabase::query()
         */
        public function selectRowCount(
                $tables, $var = '*', $conds = '', $fname = __METHOD__, $options = [], $join_conds = []
@@ -816,6 +821,7 @@ interface IDatabase {
         * @param array $options Options for select ("FOR UPDATE" is added automatically)
         * @param array $join_conds Join conditions
         * @return int Number of matching rows found (and locked)
+        * @throws DBError If an error occurs, see IDatabase::query()
         * @since 1.32
         */
        public function lockForUpdate(
@@ -829,20 +835,18 @@ interface IDatabase {
         * @param string $field Filed to check on that table
         * @param string $fname Calling function name (optional)
         * @return bool Whether $table has filed $field
-        * @throws DBError
+        * @throws DBError If an error occurs, see IDatabase::query()
         */
        public function fieldExists( $table, $field, $fname = __METHOD__ );
 
        /**
         * Determines whether an index exists
-        * Usually throws a DBQueryError on failure
-        * If errors are explicitly ignored, returns NULL on failure
         *
         * @param string $table
         * @param string $index
         * @param string $fname
         * @return bool|null
-        * @throws DBError
+        * @throws DBError If an error occurs, see IDatabase::query()
         */
        public function indexExists( $table, $index, $fname = __METHOD__ );
 
@@ -852,12 +856,12 @@ interface IDatabase {
         * @param string $table
         * @param string $fname
         * @return bool
-        * @throws DBError
+        * @throws DBError If an error occurs, see IDatabase::query()
         */
        public function tableExists( $table, $fname = __METHOD__ );
 
        /**
-        * INSERT wrapper, inserts an array into a table.
+        * INSERT wrapper, inserts an array into a table
         *
         * $a may be either:
         *
@@ -869,9 +873,6 @@ interface IDatabase {
         *     This causes a multi-row INSERT on DBMSs that support it. The keys in
         *     each subarray must be identical to each other, and in the same order.
         *
-        * Usually throws a DBQueryError on failure. If errors are explicitly ignored,
-        * returns success.
-        *
         * $options is an array of options, with boolean options encoded as values
         * with numeric keys, in the same style as $options in
         * IDatabase::select(). Supported options are:
@@ -887,7 +888,7 @@ interface IDatabase {
         * @param string $fname Calling function name (use __METHOD__) for logs/profiling
         * @param array $options Array of options
         * @return bool Return true if no exception was thrown (deprecated since 1.33)
-        * @throws DBError
+        * @throws DBError If an error occurs, see IDatabase::query()
         */
        public function insert( $table, $a, $fname = __METHOD__, $options = [] );
 
@@ -909,7 +910,7 @@ interface IDatabase {
         * @param array $options An array of UPDATE options, can be:
         *   - IGNORE: Ignore unique key conflicts
         * @return bool Return true if no exception was thrown (deprecated since 1.33)
-        * @throws DBError
+        * @throws DBError If an error occurs, see IDatabase::query()
         */
        public function update( $table, $values, $conds, $fname = __METHOD__, $options = [] );
 
@@ -935,7 +936,7 @@ interface IDatabase {
         *    - IDatabase::LIST_OR:    ORed WHERE clause (without the WHERE)
         *    - IDatabase::LIST_SET:   Comma separated with field names, like a SET clause
         *    - IDatabase::LIST_NAMES: Comma separated field names
-        * @throws DBError
+        * @throws DBError If an error occurs, see IDatabase::query()
         * @return string
         */
        public function makeList( $a, $mode = self::LIST_COMMA );
@@ -985,8 +986,7 @@ interface IDatabase {
 
        /**
         * Build a concatenation list to feed into a SQL query
-        * @param array $stringList List of raw SQL expressions; caller is
-        *   responsible for any quoting
+        * @param string[] $stringList Raw SQL expression list; caller is responsible for escaping
         * @return string
         */
        public function buildConcat( $stringList );
@@ -1012,7 +1012,7 @@ interface IDatabase {
        );
 
        /**
-        * Build a SUBSTRING function.
+        * Build a SUBSTRING function
         *
         * Behavior for non-ASCII values is undefined.
         *
@@ -1054,13 +1054,18 @@ interface IDatabase {
         * @since 1.31
         */
        public function buildSelectSubquery(
-               $table, $vars, $conds = '', $fname = __METHOD__,
-               $options = [], $join_conds = []
+               $table,
+               $vars,
+               $conds = '',
+               $fname = __METHOD__,
+               $options = [],
+               $join_conds = []
        );
 
        /**
-        * Construct a LIMIT query with optional offset. This is used for query
-        * pages. The SQL should be adjusted so that only the first $limit rows
+        * Construct a LIMIT query with optional offset
+        *
+        * The SQL should be adjusted so that only the first $limit rows
         * are returned. If $offset is provided as well, then the first $offset
         * rows should be discarded, and the next $limit rows should be returned.
         * If the result of the query is not ordered, then the rows to be returned
@@ -1071,7 +1076,6 @@ interface IDatabase {
         * @param string $sql SQL query we will append the limit too
         * @param int $limit The SQL limit
         * @param int|bool $offset The SQL offset (default false)
-        * @throws DBUnexpectedError
         * @return string
         * @since 1.34
         */
@@ -1130,7 +1134,7 @@ interface IDatabase {
        public function getServer();
 
        /**
-        * Adds quotes and backslashes.
+        * Escape and quote a raw value string for use in a SQL query
         *
         * @param string|int|null|bool|Blob $s
         * @return string|int
@@ -1138,7 +1142,7 @@ interface IDatabase {
        public function addQuotes( $s );
 
        /**
-        * Quotes an identifier, in order to make user controlled input safe
+        * Escape a SQL identifier (e.g. table, column, database) for use in a SQL query
         *
         * Depending on the database this will either be `backticks` or "double quotes"
         *
@@ -1149,11 +1153,12 @@ interface IDatabase {
        public function addIdentifierQuotes( $s );
 
        /**
-        * LIKE statement wrapper, receives a variable-length argument list with
-        * parts of pattern to match containing either string literals that will be
-        * escaped or tokens returned by anyChar() or anyString(). Alternatively,
-        * the function could be provided with an array of aforementioned
-        * parameters.
+        * LIKE statement wrapper
+        *
+        * This takes a variable-length argument list with parts of pattern to match
+        * containing either string literals that will be escaped or tokens returned by
+        * anyChar() or anyString(). Alternatively, the function could be provided with
+        * an array of aforementioned parameters.
         *
         * Example: $dbr->buildLike( 'My_page_title/', $dbr->anyString() ) returns
         * a LIKE clause that searches for subpages of 'My page title'.
@@ -1184,12 +1189,12 @@ interface IDatabase {
        public function anyString();
 
        /**
-        * Deprecated method, calls should be removed.
+        * Deprecated method, calls should be removed
         *
-        * This was formerly used for PostgreSQL and Oracle to handle
+        * This was formerly used for PostgreSQL to handle
         * self::insertId() auto-incrementing fields. It is no longer necessary
         * since DatabasePostgres::insertId() has been reimplemented using
-        * `lastval()` and Oracle has been reimplemented using triggers.
+        * `lastval()`
         *
         * Implementations should return null if inserting `NULL` into an
         * auto-incrementing field works, otherwise it should return an instance of
@@ -1202,7 +1207,7 @@ interface IDatabase {
        public function nextSequenceValue( $seqName );
 
        /**
-        * REPLACE query wrapper.
+        * REPLACE query wrapper
         *
         * REPLACE is a very handy MySQL extension, which functions like an INSERT
         * except that when there is a duplicate key error, the old row is deleted
@@ -1224,7 +1229,7 @@ interface IDatabase {
         * @param array $rows Can be either a single row to insert, or multiple rows,
         *   in the same format as for IDatabase::insert()
         * @param string $fname Calling function name (use __METHOD__) for logs/profiling
-        * @throws DBError
+        * @throws DBError If an error occurs, see IDatabase::query()
         */
        public function replace( $table, $uniqueIndexes, $rows, $fname = __METHOD__ );
 
@@ -1247,11 +1252,6 @@ interface IDatabase {
         * to collide. However if you do this, you run the risk of encountering
         * errors which wouldn't have occurred in MySQL.
         *
-        * Usually throws a DBQueryError on failure. If errors are explicitly ignored,
-        * returns success.
-        *
-        * @since 1.22
-        *
         * @param string $table Table name. This will be passed through Database::tableName().
         * @param array $rows A single row or list of rows to insert
         * @param array[]|string[]|string $uniqueIndexes All unique indexes. One of the following:
@@ -1264,8 +1264,9 @@ interface IDatabase {
         *   Values with integer keys form unquoted SET statements, which can be used for
         *   things like "field = field + 1" or similar computed values.
         * @param string $fname Calling function name (use __METHOD__) for logs/profiling
-        * @throws DBError
         * @return bool Return true if no exception was thrown (deprecated since 1.33)
+        * @throws DBError If an error occurs, see IDatabase::query()
+        * @since 1.22
         */
        public function upsert(
                $table, array $rows, $uniqueIndexes, array $set, $fname = __METHOD__
@@ -1289,28 +1290,31 @@ interface IDatabase {
         * @param array $conds Condition array of field names mapped to variables,
         *   ANDed together in the WHERE clause
         * @param string $fname Calling function name (use __METHOD__) for logs/profiling
-        * @throws DBError
-        */
-       public function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds,
+        * @throws DBError If an error occurs, see IDatabase::query()
+        */
+       public function deleteJoin(
+               $delTable,
+               $joinTable,
+               $delVar,
+               $joinVar,
+               $conds,
                $fname = __METHOD__
        );
 
        /**
-        * DELETE query wrapper.
+        * DELETE query wrapper
         *
         * @param string $table Table name
         * @param string|array $conds Array of conditions. See $conds in IDatabase::select()
         *   for the format. Use $conds == "*" to delete all rows
         * @param string $fname Name of the calling function
-        * @throws DBUnexpectedError
         * @return bool Return true if no exception was thrown (deprecated since 1.33)
-        * @throws DBError
+        * @throws DBError If an error occurs, see IDatabase::query()
         */
        public function delete( $table, $conds, $fname = __METHOD__ );
 
        /**
-        * INSERT SELECT wrapper. Takes data from a SELECT query and inserts it
-        * into another table.
+        * INSERT SELECT wrapper
         *
         * @warning If the insert will use an auto-increment or sequence to
         *  determine the value of a column, this may break replication on
@@ -1320,18 +1324,14 @@ interface IDatabase {
         * @param string $destTable The table name to insert into
         * @param string|array $srcTable May be either a table name, or an array of table names
         *    to include in a join.
-        *
         * @param array $varMap Must be an associative array of the form
         *    [ 'dest1' => 'source1', ... ]. Source items may be literals
         *    rather than field names, but strings should be quoted with
         *    IDatabase::addQuotes()
-        *
         * @param array $conds Condition array. See $conds in IDatabase::select() for
         *    the details of the format of condition arrays. May be "*" to copy the
         *    whole table.
-        *
         * @param string $fname The function name of the caller, from __METHOD__
-        *
         * @param array $insertOptions Options for the INSERT part of the query, see
         *    IDatabase::insert() for details. Also, one additional option is
         *    available: pass 'NO_AUTO_COLUMNS' to hint that the query does not use
@@ -1340,24 +1340,30 @@ interface IDatabase {
         *    IDatabase::select() for details.
         * @param array $selectJoinConds Join conditions for the SELECT part of the query, see
         *    IDatabase::select() for details.
-        *
         * @return bool Return true if no exception was thrown (deprecated since 1.33)
-        * @throws DBError
+        * @throws DBError If an error occurs, see IDatabase::query()
         */
-       public function insertSelect( $destTable, $srcTable, $varMap, $conds,
+       public function insertSelect(
+               $destTable,
+               $srcTable,
+               $varMap,
+               $conds,
                $fname = __METHOD__,
-               $insertOptions = [], $selectOptions = [], $selectJoinConds = []
+               $insertOptions = [],
+               $selectOptions = [],
+               $selectJoinConds = []
        );
 
        /**
-        * Returns true if current database backend supports ORDER BY or LIMIT for separate subqueries
-        * within the UNION construct.
+        * Determine if the RDBMS supports ORDER BY and LIMIT for separate subqueries within UNION
+        *
         * @return bool
         */
        public function unionSupportsOrderAndLimit();
 
        /**
         * Construct a UNION query
+        *
         * This is used for providing overload point for other DB abstractions
         * not compatible with the MySQL syntax.
         * @param array $sqls SQL statements to combine
@@ -1375,7 +1381,6 @@ interface IDatabase {
         * conditions and unions them all together.
         *
         * @see IDatabase::select()
-        * @since 1.30
         * @param string|array $table Table name
         * @param string|array $vars Field names
         * @param array $permute_conds Conditions for the Cartesian product. Keys
@@ -1391,15 +1396,22 @@ interface IDatabase {
         *     instead of ORDER BY.
         * @param string|array $join_conds Join conditions
         * @return string SQL query string.
+        * @since 1.30
         */
        public function unionConditionPermutations(
-               $table, $vars, array $permute_conds, $extra_conds = '', $fname = __METHOD__,
-               $options = [], $join_conds = []
+               $table,
+               $vars,
+               array $permute_conds,
+               $extra_conds = '',
+               $fname = __METHOD__,
+               $options = [],
+               $join_conds = []
        );
 
        /**
-        * Returns an SQL expression for a simple conditional. This doesn't need
-        * to be overridden unless CASE isn't supported in your DBMS.
+        * Returns an SQL expression for a simple conditional
+        *
+        * This doesn't need to be overridden unless CASE isn't supported in the RDBMS.
         *
         * @param string|array $cond SQL expression which will result in a boolean value
         * @param string $trueVal SQL expression to return if true
@@ -1409,13 +1421,11 @@ interface IDatabase {
        public function conditional( $cond, $trueVal, $falseVal );
 
        /**
-        * Returns a command for str_replace function in SQL query.
-        * Uses REPLACE() in MySQL
+        * Returns a SQL expression for simple string replacement (e.g. REPLACE() in mysql)
         *
         * @param string $orig Column to modify
         * @param string $old Column to seek
         * @param string $new Column to replace with
-        *
         * @return string
         */
        public function strreplace( $orig, $old, $new );
@@ -1457,7 +1467,7 @@ interface IDatabase {
        public function wasConnectionLoss();
 
        /**
-        * Determines if the last failure was due to the database being read-only.
+        * Determines if the last failure was due to the database being read-only
         *
         * @return bool
         */
@@ -1484,7 +1494,7 @@ interface IDatabase {
         * @return int|null Zero if the replica DB was past that position already,
         *   greater than zero if we waited for some period of time, less than
         *   zero if it timed out, and null on error
-        * @throws DBError
+        * @throws DBError If an error occurs, see IDatabase::query()
         */
        public function masterPosWait( DBMasterPos $pos, $timeout );
 
@@ -1492,7 +1502,7 @@ interface IDatabase {
         * Get the replication position of this replica DB
         *
         * @return DBMasterPos|bool False if this is not a replica DB
-        * @throws DBError
+        * @throws DBError If an error occurs, see IDatabase::query()
         */
        public function getReplicaPos();
 
@@ -1500,7 +1510,7 @@ interface IDatabase {
         * Get the position of this master
         *
         * @return DBMasterPos|bool False if this is not a master
-        * @throws DBError
+        * @throws DBError If an error occurs, see IDatabase::query()
         */
        public function getMasterPos();
 
@@ -1511,7 +1521,8 @@ interface IDatabase {
        public function serverIsReadOnly();
 
        /**
-        * Run a callback as soon as the current transaction commits or rolls back.
+        * Run a callback as soon as the current transaction commits or rolls back
+        *
         * An error is thrown if no transaction is pending. Queries in the function will run in
         * AUTOCOMMIT mode unless there are begin() calls. Callbacks must commit any transactions
         * that they begin.
@@ -1529,12 +1540,15 @@ interface IDatabase {
         *
         * @param callable $callback
         * @param string $fname Caller name
+        * @throws DBError If an error occurs, see IDatabase::query()
+        * @throws Exception If the callback runs immediately and an error occurs in it
         * @since 1.28
         */
        public function onTransactionResolution( callable $callback, $fname = __METHOD__ );
 
        /**
-        * Run a callback as soon as there is no transaction pending.
+        * Run a callback as soon as there is no transaction pending
+        *
         * If there is a transaction and it is rolled back, then the callback is cancelled.
         *
         * When transaction round mode (DBO_TRX) is set, the callback will run at the end
@@ -1563,6 +1577,8 @@ interface IDatabase {
         *
         * @param callable $callback
         * @param string $fname Caller name
+        * @throws DBError If an error occurs, see IDatabase::query()
+        * @throws Exception If the callback runs immediately and an error occurs in it
         * @since 1.32
         */
        public function onTransactionCommitOrIdle( callable $callback, $fname = __METHOD__ );
@@ -1578,7 +1594,8 @@ interface IDatabase {
        public function onTransactionIdle( callable $callback, $fname = __METHOD__ );
 
        /**
-        * Run a callback before the current transaction commits or now if there is none.
+        * Run a callback before the current transaction commits or now if there is none
+        *
         * If there is a transaction and it is rolled back, then the callback is cancelled.
         *
         * When transaction round mode (DBO_TRX) is set, the callback will run at the end
@@ -1598,12 +1615,14 @@ interface IDatabase {
         *
         * @param callable $callback
         * @param string $fname Caller name
+        * @throws DBError If an error occurs, see IDatabase::query()
+        * @throws Exception If the callback runs immediately and an error occurs in it
         * @since 1.22
         */
        public function onTransactionPreCommitOrIdle( callable $callback, $fname = __METHOD__ );
 
        /**
-        * Run a callback when the atomic section is cancelled.
+        * Run a callback when the atomic section is cancelled
         *
         * The callback is run just after the current atomic section, any outer
         * atomic section, or the whole transaction is rolled back.
@@ -1718,7 +1737,7 @@ interface IDatabase {
         * @param string $cancelable Pass self::ATOMIC_CANCELABLE to use a
         *  savepoint and enable self::cancelAtomic() for this section.
         * @return AtomicSectionIdentifier section ID token
-        * @throws DBError
+        * @throws DBError If an error occurs, see IDatabase::query()
         */
        public function startAtomic( $fname = __METHOD__, $cancelable = self::ATOMIC_NOT_CANCELABLE );
 
@@ -1731,7 +1750,7 @@ interface IDatabase {
         * @since 1.23
         * @see IDatabase::startAtomic
         * @param string $fname
-        * @throws DBError
+        * @throws DBError If an error occurs, see IDatabase::query()
         */
        public function endAtomic( $fname = __METHOD__ );
 
@@ -1758,7 +1777,7 @@ interface IDatabase {
         * @param string $fname
         * @param AtomicSectionIdentifier|null $sectionId Section ID from startAtomic();
         *   passing this enables cancellation of unclosed nested sections [optional]
-        * @throws DBError
+        * @throws DBError If an error occurs, see IDatabase::query()
         */
        public function cancelAtomic( $fname = __METHOD__, AtomicSectionIdentifier $sectionId = null );
 
@@ -1828,8 +1847,8 @@ interface IDatabase {
         * @param string $cancelable Pass self::ATOMIC_CANCELABLE to use a
         *  savepoint and enable self::cancelAtomic() for this section.
         * @return mixed $res Result of the callback (since 1.28)
-        * @throws DBError
-        * @throws RuntimeException
+        * @throws DBError If an error occurs, see IDatabase::query()
+        * @throws Exception If an error occurs in the callback
         * @since 1.27; prior to 1.31 this did a rollback() instead of
         *  cancelAtomic(), and assumed no callers up the stack would ever try to
         *  catch the exception.
@@ -1839,8 +1858,7 @@ interface IDatabase {
        );
 
        /**
-        * Begin a transaction. If a transaction is already in progress,
-        * that transaction will be committed before the new transaction is started.
+        * Begin a transaction
         *
         * Only call this from code with outer transcation scope.
         * See https://www.mediawiki.org/wiki/Database_transactions for details.
@@ -1856,12 +1874,13 @@ interface IDatabase {
         *
         * @param string $fname Calling function name
         * @param string $mode A situationally valid IDatabase::TRANSACTION_* constant [optional]
-        * @throws DBError
+        * @throws DBError If an error occurs, see IDatabase::query()
         */
        public function begin( $fname = __METHOD__, $mode = self::TRANSACTION_EXPLICIT );
 
        /**
-        * Commits a transaction previously started using begin().
+        * Commits a transaction previously started using begin()
+        *
         * If no transaction is in progress, a warning is issued.
         *
         * Only call this from code with outer transcation scope.
@@ -1872,19 +1891,15 @@ interface IDatabase {
         * @param string $flush Flush flag, set to situationally valid IDatabase::FLUSHING_*
         *   constant to disable warnings about explicitly committing implicit transactions,
         *   or calling commit when no transaction is in progress.
-        *
         *   This will trigger an exception if there is an ongoing explicit transaction.
-        *
         *   Only set the flush flag if you are sure that these warnings are not applicable,
         *   and no explicit transactions are open.
-        *
-        * @throws DBError
+        * @throws DBError If an error occurs, see IDatabase::query()
         */
        public function commit( $fname = __METHOD__, $flush = self::FLUSHING_ONE );
 
        /**
-        * Rollback a transaction previously started using begin().
-        * If no transaction is in progress, a warning is issued.
+        * Rollback a transaction previously started using begin()
         *
         * Only call this from code with outer transcation scope.
         * See https://www.mediawiki.org/wiki/Database_transactions for details.
@@ -1899,7 +1914,7 @@ interface IDatabase {
         *   constant to disable warnings about calling rollback when no transaction is in
         *   progress. This will silently break any ongoing explicit transaction. Only set the
         *   flush flag if you are sure that it is safe to ignore these warnings in your context.
-        * @throws DBError
+        * @throws DBError If an error occurs, see IDatabase::query()
         * @since 1.23 Added $flush parameter
         */
        public function rollback( $fname = __METHOD__, $flush = self::FLUSHING_ONE );
@@ -1916,21 +1931,18 @@ interface IDatabase {
         * @param string $flush Flush flag, set to situationally valid IDatabase::FLUSHING_*
         *   constant to disable warnings about explicitly committing implicit transactions,
         *   or calling commit when no transaction is in progress.
-        *
         *   This will trigger an exception if there is an ongoing explicit transaction.
-        *
         *   Only set the flush flag if you are sure that these warnings are not applicable,
         *   and no explicit transactions are open.
-        *
-        * @throws DBError
+        * @throws DBError If an error occurs, see IDatabase::query()
         * @since 1.28
         * @since 1.34 Added $flush parameter
         */
        public function flushSnapshot( $fname = __METHOD__, $flush = self::FLUSHING_ONE );
 
        /**
-        * Convert a timestamp in one of the formats accepted by wfTimestamp()
-        * to the format used for inserting into timestamp fields in this DBMS.
+        * Convert a timestamp in one of the formats accepted by ConvertibleTimestamp
+        * to the format used for inserting into timestamp fields in this DBMS
         *
         * The result is unquoted, and needs to be passed through addQuotes()
         * before it can be included in raw SQL.
@@ -1942,9 +1954,10 @@ interface IDatabase {
        public function timestamp( $ts = 0 );
 
        /**
-        * Convert a timestamp in one of the formats accepted by wfTimestamp()
-        * to the format used for inserting into timestamp fields in this DBMS. If
-        * NULL is input, it is passed through, allowing NULL values to be inserted
+        * Convert a timestamp in one of the formats accepted by ConvertibleTimestamp
+        * to the format used for inserting into timestamp fields in this DBMS
+        *
+        * If NULL is input, it is passed through, allowing NULL values to be inserted
         * into timestamp fields.
         *
         * The result is unquoted, and needs to be passed through addQuotes()
@@ -1970,7 +1983,7 @@ interface IDatabase {
         * Callers should avoid using this method while a transaction is active
         *
         * @return int|bool Database replication lag in seconds or false on error
-        * @throws DBError
+        * @throws DBError If an error occurs, see IDatabase::query()
         */
        public function getLag();
 
@@ -1985,13 +1998,13 @@ interface IDatabase {
         * indication of the staleness of subsequent reads.
         *
         * @return array ('lag': seconds or false on error, 'since': UNIX timestamp of BEGIN)
-        * @throws DBError
+        * @throws DBError If an error occurs, see IDatabase::query()
         * @since 1.27
         */
        public function getSessionLagStatus();
 
        /**
-        * Return the maximum number of items allowed in a list, or 0 for unlimited.
+        * Return the maximum number of items allowed in a list, or 0 for unlimited
         *
         * @return int
         */
@@ -2005,6 +2018,7 @@ interface IDatabase {
         *
         * @param string $b
         * @return string|Blob
+        * @throws DBError
         */
        public function encodeBlob( $b );
 
@@ -2015,6 +2029,7 @@ interface IDatabase {
         *
         * @param string|Blob $b
         * @return string
+        * @throws DBError
         */
        public function decodeBlob( $b );
 
@@ -2027,7 +2042,7 @@ interface IDatabase {
         *
         * @param array $options
         * @return void
-        * @throws DBError
+        * @throws DBError If an error occurs, see IDatabase::query()
         */
        public function setSessionOptions( array $options );
 
@@ -2046,7 +2061,7 @@ interface IDatabase {
         * @param string $lockName Name of lock to poll
         * @param string $method Name of method calling us
         * @return bool
-        * @throws DBError
+        * @throws DBError If an error occurs, see IDatabase::query()
         * @since 1.20
         */
        public function lockIsFree( $lockName, $method );
@@ -2059,8 +2074,8 @@ interface IDatabase {
         * @param string $lockName Name of lock to aquire
         * @param string $method Name of the calling method
         * @param int $timeout Acquisition timeout in seconds (0 means non-blocking)
-        * @return bool
-        * @throws DBError
+        * @return bool Success
+        * @throws DBError If an error occurs, see IDatabase::query()
         */
        public function lock( $lockName, $method, $timeout = 5 );
 
@@ -2071,12 +2086,8 @@ interface IDatabase {
         *
         * @param string $lockName Name of lock to release
         * @param string $method Name of the calling method
-        *
-        * @return int Returns 1 if the lock was released, 0 if the lock was not established
-        * by this thread (in which case the lock is not released), and NULL if the named lock
-        * did not exist
-        *
-        * @throws DBError
+        * @return bool Success
+        * @throws DBError If an error occurs, see IDatabase::query()
         */
        public function unlock( $lockName, $method );
 
@@ -2098,7 +2109,7 @@ interface IDatabase {
         * @param string $fname Name of the calling method
         * @param int $timeout Acquisition timeout in seconds
         * @return ScopedCallback|null
-        * @throws DBError
+        * @throws DBError If an error occurs, see IDatabase::query()
         * @since 1.27
         */
        public function getScopedLockAndFlush( $lockKey, $fname, $timeout );
index 5698cf8..3ceb339 100644 (file)
@@ -23,7 +23,19 @@ namespace Wikimedia\Rdbms;
 use InvalidArgumentException;
 
 /**
- * Class to handle database/prefix specification for IDatabase domains
+ * Class to handle database/schema/prefix specifications for IDatabase
+ *
+ * The components of a database domain are defined as follows:
+ *   - database: name of a server-side collection of schemas that is client-selectable
+ *   - schema: name of a server-side collection of tables within the given database
+ *   - prefix: table name prefix of an application-defined table collection
+ *
+ * If an RDBMS does not support server-side collections of table collections (schemas) then
+ * the schema component should be null and the "database" component treated as a collection
+ * of exactly one table collection (the implied schema for that "database").
+ *
+ * The above criteria should determine how components should map to RDBMS specific keywords
+ * rather than "database"/"schema" always mapping to "DATABASE"/"SCHEMA" as used by the RDBMS.
  */
 class DatabaseDomain {
        /** @var string|null */
diff --git a/includes/libs/rdbms/database/resultwrapper/MssqlResultWrapper.php b/includes/libs/rdbms/database/resultwrapper/MssqlResultWrapper.php
deleted file mode 100644 (file)
index ba79be1..0000000
+++ /dev/null
@@ -1,76 +0,0 @@
-<?php
-
-namespace Wikimedia\Rdbms;
-
-use stdClass;
-
-class MssqlResultWrapper extends ResultWrapper {
-       /** @var int|null */
-       private $seekTo = null;
-
-       /**
-        * @return stdClass|bool
-        */
-       public function fetchObject() {
-               $res = $this->result;
-
-               if ( $this->seekTo !== null ) {
-                       $result = sqlsrv_fetch_object( $res, stdClass::class, [],
-                               SQLSRV_SCROLL_ABSOLUTE, $this->seekTo );
-                       $this->seekTo = null;
-               } else {
-                       $result = sqlsrv_fetch_object( $res );
-               }
-
-               // Return boolean false when there are no more rows instead of null
-               if ( $result === null ) {
-                       return false;
-               }
-
-               return $result;
-       }
-
-       /**
-        * @return array|bool
-        */
-       public function fetchRow() {
-               $res = $this->result;
-
-               if ( $this->seekTo !== null ) {
-                       $result = sqlsrv_fetch_array( $res, SQLSRV_FETCH_BOTH,
-                               SQLSRV_SCROLL_ABSOLUTE, $this->seekTo );
-                       $this->seekTo = null;
-               } else {
-                       $result = sqlsrv_fetch_array( $res );
-               }
-
-               // Return boolean false when there are no more rows instead of null
-               if ( $result === null ) {
-                       return false;
-               }
-
-               return $result;
-       }
-
-       /**
-        * @param int $row
-        * @return bool
-        */
-       public function seek( $row ) {
-               $res = $this->result;
-
-               // check bounds
-               $numRows = $this->db->numRows( $res );
-               $row = intval( $row );
-
-               if ( $numRows === 0 ) {
-                       return false;
-               } elseif ( $row < 0 || $row > $numRows - 1 ) {
-                       return false;
-               }
-
-               // Unlike MySQL, the seek actually happens on the next access
-               $this->seekTo = $row;
-               return true;
-       }
-}
diff --git a/includes/libs/rdbms/encasing/MssqlBlob.php b/includes/libs/rdbms/encasing/MssqlBlob.php
deleted file mode 100644 (file)
index 1819a9a..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-<?php
-
-namespace Wikimedia\Rdbms;
-
-class MssqlBlob extends Blob {
-       /** @noinspection PhpMissingParentConstructorInspection */
-
-       /**
-        * @param Blob|string $data
-        */
-       public function __construct( $data ) {
-               if ( $data instanceof MssqlBlob ) {
-                       $this->data = $data->data;
-               } elseif ( $data instanceof Blob ) {
-                       $this->data = $data->fetch();
-               } else {
-                       $this->data = $data;
-               }
-       }
-
-       /**
-        * Returns an unquoted hex representation of a binary string
-        * for insertion into varbinary-type fields
-        * @return string
-        */
-       public function fetch() {
-               if ( $this->data === null ) {
-                       return 'null';
-               }
-
-               $ret = '0x';
-               $dataLength = strlen( $this->data );
-               for ( $i = 0; $i < $dataLength; $i++ ) {
-                       $ret .= bin2hex( pack( 'C', ord( $this->data[$i] ) ) );
-               }
-
-               return $ret;
-       }
-}
diff --git a/includes/libs/rdbms/field/MssqlField.php b/includes/libs/rdbms/field/MssqlField.php
deleted file mode 100644 (file)
index 98cc2b1..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-<?php
-
-namespace Wikimedia\Rdbms;
-
-class MssqlField implements Field {
-       private $name, $tableName, $default, $max_length, $nullable, $type;
-
-       function __construct( $info ) {
-               $this->name = $info['COLUMN_NAME'];
-               $this->tableName = $info['TABLE_NAME'];
-               $this->default = $info['COLUMN_DEFAULT'];
-               $this->max_length = $info['CHARACTER_MAXIMUM_LENGTH'];
-               $this->nullable = !( strtolower( $info['IS_NULLABLE'] ) == 'no' );
-               $this->type = $info['DATA_TYPE'];
-       }
-
-       function name() {
-               return $this->name;
-       }
-
-       function tableName() {
-               return $this->tableName;
-       }
-
-       function defaultValue() {
-               return $this->default;
-       }
-
-       function maxLength() {
-               return $this->max_length;
-       }
-
-       function isNullable() {
-               return $this->nullable;
-       }
-
-       function type() {
-               return $this->type;
-       }
-}
index 4426654..77467f0 100644 (file)
@@ -160,7 +160,6 @@ abstract class LBFactory implements ILBFactory {
        }
 
        public function destroy() {
-               $this->shutdown( self::SHUTDOWN_NO_CHRONPROT );
                $this->forEachLBCallMethod( 'disable' );
        }
 
index 990705c..160d501 100644 (file)
@@ -95,6 +95,8 @@ interface ILoadBalancer {
        const CONN_SILENCE_ERRORS = 2;
        /** @var int Caller is requesting the master DB server for possibly writes */
        const CONN_INTENT_WRITABLE = 4;
+       /** @var int Bypass and update any server-side read-only mode state cache */
+       const CONN_REFRESH_READ_ONLY = 8;
 
        /** @var string Manager of ILoadBalancer instances is running post-commit callbacks */
        const STAGE_POSTCOMMIT_CALLBACKS = 'stage-postcommit-callbacks';
@@ -155,6 +157,18 @@ interface ILoadBalancer {
         */
        public function redefineLocalDomain( $domain );
 
+       /**
+        * Indicate whether the tables on this domain are only temporary tables for testing
+        *
+        * In "temporary tables mode", the ILoadBalancer::CONN_TRX_AUTOCOMMIT flag is ignored
+        *
+        * @param bool $value
+        * @param string $domain
+        * @return bool Whether "temporary tables mode" was active
+        * @since 1.34
+        */
+       public function setTempTablesOnlyMode( $value, $domain );
+
        /**
         * Get the server index of the reader connection for a given group
         *
@@ -167,8 +181,7 @@ interface ILoadBalancer {
         *
         * @param string|bool $group Query group or false for the generic group
         * @param string|bool $domain DB domain ID or false for the local domain
-        * @throws DBError If no live handle can be obtained
-        * @return bool|int|string
+        * @return int|bool Returns false if no live handle can be obtained
         */
        public function getReaderIndex( $group = false, $domain = false );
 
@@ -650,10 +663,9 @@ interface ILoadBalancer {
        /**
         * @note This method may trigger a DB connection if not yet done
         * @param string|bool $domain DB domain ID or false for the local domain
-        * @param IDatabase|null $conn DB master connection; used to avoid loops [optional]
         * @return string|bool Reason the master is read-only or false if it is not
         */
-       public function getReadOnlyReason( $domain = false, IDatabase $conn = null );
+       public function getReadOnlyReason( $domain = false );
 
        /**
         * Disables/enables lag checks
index 98607ce..066d4b4 100644 (file)
@@ -101,6 +101,8 @@ class LoadBalancer implements ILoadBalancer {
        private $indexAliases = [];
        /** @var array[] Map of (name => callable) */
        private $trxRecurringCallbacks = [];
+       /** @var bool[] Map of (domain => whether to use "temp tables only" mode) */
+       private $tempTablesOnlyMode = [];
 
        /** @var Database Connection handle that caused a problem */
        private $errorConnection;
@@ -297,9 +299,10 @@ class LoadBalancer implements ILoadBalancer {
        /**
         * @param int $flags Bitfield of class CONN_* constants
         * @param int $i Specific server index or DB_MASTER/DB_REPLICA
+        * @param string $domain Database domain
         * @return int Sanitized bitfield
         */
-       private function sanitizeConnectionFlags( $flags, $i ) {
+       private function sanitizeConnectionFlags( $flags, $i, $domain ) {
                // Whether an outside caller is explicitly requesting the master database server
                if ( $i === self::DB_MASTER || $i === $this->getWriterIndex() ) {
                        $flags |= self::CONN_INTENT_WRITABLE;
@@ -320,6 +323,12 @@ class LoadBalancer implements ILoadBalancer {
                                $flags &= ~self::CONN_TRX_AUTOCOMMIT;
                                $type = $this->getServerType( $this->getWriterIndex() );
                                $this->connLogger->info( __METHOD__ . ": CONN_TRX_AUTOCOMMIT disallowed ($type)" );
+                       } elseif ( isset( $this->tempTablesOnlyMode[$domain] ) ) {
+                               // T202116: integration tests are active and queries should be all be using
+                               // temporary clone tables (via prefix). Such tables are not visible accross
+                               // different connections nor can there be REPEATABLE-READ snapshot staleness,
+                               // so use the same connection for everything.
+                               $flags &= ~self::CONN_TRX_AUTOCOMMIT;
                        }
                }
 
@@ -592,8 +601,7 @@ class LoadBalancer implements ILoadBalancer {
                        $this->connLogger->debug( __METHOD__ . ": Using reader #$i: $serverName..." );
 
                        // Get a connection to this server without triggering other server connections
-                       $flags = self::CONN_SILENCE_ERRORS;
-                       $conn = $this->getServerConnection( $i, $domain, $flags );
+                       $conn = $this->getServerConnection( $i, $domain, self::CONN_SILENCE_ERRORS );
                        if ( !$conn ) {
                                $this->connLogger->warning( __METHOD__ . ": Failed connecting to $i/$domain" );
                                unset( $currentLoads[$i] ); // avoid this server next iteration
@@ -875,7 +883,7 @@ class LoadBalancer implements ILoadBalancer {
        public function getConnection( $i, $groups = [], $domain = false, $flags = 0 ) {
                $domain = $this->resolveDomainID( $domain );
                $groups = $this->resolveGroups( $groups, $i );
-               $flags = $this->sanitizeConnectionFlags( $flags, $i );
+               $flags = $this->sanitizeConnectionFlags( $flags, $i, $domain );
                // If given DB_MASTER/DB_REPLICA, resolve it to a specific server index. Resolving
                // DB_REPLICA might trigger getServerConnection() calls due to the getReaderIndex()
                // connectivity checks or LoadMonitor::scaleLoads() server state cache regeneration.
@@ -884,7 +892,11 @@ class LoadBalancer implements ILoadBalancer {
                // Get an open connection to that server (might trigger a new connection)
                $conn = $this->getServerConnection( $serverIndex, $domain, $flags );
                // Set master DB handles as read-only if there is high replication lag
-               if ( $serverIndex === $this->getWriterIndex() && $this->getLaggedReplicaMode( $domain ) ) {
+               if (
+                       $serverIndex === $this->getWriterIndex() &&
+                       $this->getLaggedReplicaMode( $domain ) &&
+                       !is_string( $conn->getLBInfo( 'readOnlyReason' ) )
+               ) {
                        $reason = ( $this->getExistingReaderIndex( self::GROUP_GENERIC ) >= 0 )
                                ? 'The database is read-only until replication lag decreases.'
                                : 'The database is read-only until replica database servers becomes reachable.';
@@ -940,15 +952,15 @@ class LoadBalancer implements ILoadBalancer {
                // or the master database server is running in server-side read-only mode. Note that
                // replica DB handles are always read-only via Database::assertIsWritableMaster().
                // Read-only mode due to replication lag is *avoided* here to avoid recursion.
-               if ( $conn->getLBInfo( 'serverIndex' ) === $this->getWriterIndex() ) {
+               if ( $i === $this->getWriterIndex() ) {
                        if ( $this->readOnlyReason !== false ) {
-                               $conn->setLBInfo( 'readOnlyReason', $this->readOnlyReason );
-                       } elseif ( $this->masterRunningReadOnly( $domain, $conn ) ) {
-                               $conn->setLBInfo(
-                                       'readOnlyReason',
-                                       'The master database server is running in read-only mode.'
-                               );
+                               $readOnlyReason = $this->readOnlyReason;
+                       } elseif ( $this->isMasterConnectionReadOnly( $conn, $flags ) ) {
+                               $readOnlyReason = 'The master database server is running in read-only mode.';
+                       } else {
+                               $readOnlyReason = false;
                        }
+                       $conn->setLBInfo( 'readOnlyReason', $readOnlyReason );
                }
 
                return $conn;
@@ -958,17 +970,7 @@ class LoadBalancer implements ILoadBalancer {
                $serverIndex = $conn->getLBInfo( 'serverIndex' );
                $refCount = $conn->getLBInfo( 'foreignPoolRefCount' );
                if ( $serverIndex === null || $refCount === null ) {
-                       /**
-                        * This can happen in code like:
-                        *   foreach ( $dbs as $db ) {
-                        *     $conn = $lb->getConnection( $lb::DB_REPLICA, [], $db );
-                        *     ...
-                        *     $lb->reuseConnection( $conn );
-                        *   }
-                        * When a connection to the local DB is opened in this way, reuseConnection()
-                        * should be ignored
-                        */
-                       return;
+                       return; // non-foreign connection; no domain-use tracking to update
                } elseif ( $conn instanceof DBConnRef ) {
                        // DBConnRef already handles calling reuseConnection() and only passes the live
                        // Database instance to this method. Any caller passing in a DBConnRef is broken.
@@ -1300,7 +1302,7 @@ class LoadBalancer implements ILoadBalancer {
 
                // Create a live connection object
                try {
-                       $db = Database::factory( $server['type'], $server );
+                       $conn = Database::factory( $server['type'], $server );
                        // Log when many connection are made on requests
                        ++$this->connectionCounter;
                        $currentConnCount = $this->getCurrentConnectionCount();
@@ -1313,28 +1315,28 @@ class LoadBalancer implements ILoadBalancer {
                } catch ( DBConnectionError $e ) {
                        // FIXME: This is probably the ugliest thing I have ever done to
                        // PHP. I'm half-expecting it to segfault, just out of disgust. -- TS
-                       $db = $e->db;
+                       $conn = $e->db;
                }
 
-               $db->setLBInfo( $server );
-               $db->setLazyMasterHandle(
-                       $this->getLazyConnectionRef( self::DB_MASTER, [], $db->getDomainID() )
+               $conn->setLBInfo( $server );
+               $conn->setLazyMasterHandle(
+                       $this->getLazyConnectionRef( self::DB_MASTER, [], $conn->getDomainID() )
                );
-               $db->setTableAliases( $this->tableAliases );
-               $db->setIndexAliases( $this->indexAliases );
+               $conn->setTableAliases( $this->tableAliases );
+               $conn->setIndexAliases( $this->indexAliases );
 
                if ( $server['serverIndex'] === $this->getWriterIndex() ) {
                        if ( $this->trxRoundId !== false ) {
-                               $this->applyTransactionRoundFlags( $db );
+                               $this->applyTransactionRoundFlags( $conn );
                        }
                        foreach ( $this->trxRecurringCallbacks as $name => $callback ) {
-                               $db->setTransactionListener( $name, $callback );
+                               $conn->setTransactionListener( $name, $callback );
                        }
                }
 
                $this->lazyLoadReplicationPositions(); // session consistency
 
-               return $db;
+               return $conn;
        }
 
        /**
@@ -1919,13 +1921,8 @@ class LoadBalancer implements ILoadBalancer {
                }
 
                if ( $this->hasStreamingReplicaServers() ) {
-                       try {
-                               // Set "laggedReplicaMode"
-                               $this->getReaderIndex( self::GROUP_GENERIC, $domain );
-                       } catch ( DBConnectionError $e ) {
-                               // Sanity: avoid expensive re-connect attempts and failures
-                               $this->laggedReplicaMode = true;
-                       }
+                       // This will set "laggedReplicaMode" as needed
+                       $this->getReaderIndex( self::GROUP_GENERIC, $domain );
                }
 
                return $this->laggedReplicaMode;
@@ -1935,10 +1932,12 @@ class LoadBalancer implements ILoadBalancer {
                return $this->laggedReplicaMode;
        }
 
-       public function getReadOnlyReason( $domain = false, IDatabase $conn = null ) {
+       public function getReadOnlyReason( $domain = false ) {
+               $domainInstance = DatabaseDomain::newFromId( $this->resolveDomainID( $domain ) );
+
                if ( $this->readOnlyReason !== false ) {
                        return $this->readOnlyReason;
-               } elseif ( $this->masterRunningReadOnly( $domain, $conn ) ) {
+               } elseif ( $this->isMasterRunningReadOnly( $domainInstance ) ) {
                        return 'The master database server is running in read-only mode.';
                } elseif ( $this->getLaggedReplicaMode( $domain ) ) {
                        return ( $this->getExistingReaderIndex( self::GROUP_GENERIC ) >= 0 )
@@ -1950,26 +1949,68 @@ class LoadBalancer implements ILoadBalancer {
        }
 
        /**
-        * @param string $domain Domain ID, or false for the current domain
-        * @param IDatabase|null $conn DB master connectionl used to avoid loops [optional]
-        * @return bool
+        * @param IDatabase $conn Master connection
+        * @param int $flags Bitfield of class CONN_* constants
+        * @return bool Whether the entire server or currently selected DB/schema is read-only
         */
-       private function masterRunningReadOnly( $domain, IDatabase $conn = null ) {
-               $cache = $this->wanCache;
-               $masterServer = $this->getServerName( $this->getWriterIndex() );
+       private function isMasterConnectionReadOnly( IDatabase $conn, $flags = 0 ) {
+               // Note that table prefixes are not related to server-side read-only mode
+               $key = $this->srvCache->makeGlobalKey(
+                       'rdbms-server-readonly',
+                       $conn->getServer(),
+                       $conn->getDBname(),
+                       $conn->dbSchema()
+               );
 
-               return (bool)$cache->getWithSetCallback(
-                       $cache->makeGlobalKey( __CLASS__, 'server-read-only', $masterServer ),
+               if ( ( $flags & self::CONN_REFRESH_READ_ONLY ) == self::CONN_REFRESH_READ_ONLY ) {
+                       try {
+                               $readOnly = (int)$conn->serverIsReadOnly();
+                       } catch ( DBError $e ) {
+                               $readOnly = 0;
+                       }
+                       $this->srvCache->set( $key, $readOnly, BagOStuff::TTL_PROC_SHORT );
+               } else {
+                       $readOnly = $this->srvCache->getWithSetCallback(
+                               $key,
+                               BagOStuff::TTL_PROC_SHORT,
+                               function () use ( $conn ) {
+                                       try {
+                                               return (int)$conn->serverIsReadOnly();
+                                       } catch ( DBError $e ) {
+                                               return 0;
+                                       }
+                               }
+                       );
+               }
+
+               return (bool)$readOnly;
+       }
+
+       /**
+        * @param DatabaseDomain $domain
+        * @return bool Whether the entire master server or the local domain DB is read-only
+        */
+       private function isMasterRunningReadOnly( DatabaseDomain $domain ) {
+               // Context will often be HTTP GET/HEAD; heavily cache the results
+               return (bool)$this->wanCache->getWithSetCallback(
+                       // Note that table prefixes are not related to server-side read-only mode
+                       $this->wanCache->makeGlobalKey(
+                               'rdbms-server-readonly',
+                               $this->getMasterServerName(),
+                               $domain->getDatabase(),
+                               $domain->getSchema()
+                       ),
                        self::TTL_CACHE_READONLY,
-                       function () use ( $domain, $conn ) {
+                       function () use ( $domain ) {
                                $old = $this->trxProfiler->setSilenced( true );
                                try {
                                        $index = $this->getWriterIndex();
-                                       $dbw = $conn ?: $this->getServerConnection( $index, $domain );
-                                       $readOnly = (int)$dbw->serverIsReadOnly();
-                                       if ( !$conn ) {
-                                               $this->reuseConnection( $dbw );
-                                       }
+                                       // Reset the cache for isMasterConnectionReadOnly()
+                                       $flags = self::CONN_REFRESH_READ_ONLY;
+                                       $conn = $this->getServerConnection( $index, $domain->getId(), $flags );
+                                       // Reuse the process cache set above
+                                       $readOnly = (int)$this->isMasterConnectionReadOnly( $conn );
+                                       $this->reuseConnection( $conn );
                                } catch ( DBError $e ) {
                                        $readOnly = 0;
                                }
@@ -1977,7 +2018,7 @@ class LoadBalancer implements ILoadBalancer {
 
                                return $readOnly;
                        },
-                       [ 'pcTTL' => $cache::TTL_PROC_LONG, 'busyValue' => 0 ]
+                       [ 'pcTTL' => WANObjectCache::TTL_PROC_LONG, 'lockTSE' => 10, 'busyValue' => 0 ]
                );
        }
 
@@ -2232,9 +2273,9 @@ class LoadBalancer implements ILoadBalancer {
                ) );
 
                // Update the prefix for all local connections...
-               $this->forEachOpenConnection( function ( IDatabase $db ) use ( $prefix ) {
-                       if ( !$db->getLBInfo( 'foreign' ) ) {
-                               $db->tablePrefix( $prefix );
+               $this->forEachOpenConnection( function ( IDatabase $conn ) use ( $prefix ) {
+                       if ( !$conn->getLBInfo( 'foreign' ) ) {
+                               $conn->tablePrefix( $prefix );
                        }
                } );
        }
@@ -2245,6 +2286,17 @@ class LoadBalancer implements ILoadBalancer {
                $this->setLocalDomain( DatabaseDomain::newFromId( $domain ) );
        }
 
+       public function setTempTablesOnlyMode( $value, $domain ) {
+               $old = $this->tempTablesOnlyMode[$domain] ?? false;
+               if ( $value ) {
+                       $this->tempTablesOnlyMode[$domain] = true;
+               } else {
+                       unset( $this->tempTablesOnlyMode[$domain] );
+               }
+
+               return $old;
+       }
+
        /**
         * @param DatabaseDomain $domain
         */
@@ -2282,6 +2334,13 @@ class LoadBalancer implements ILoadBalancer {
                return $this->servers[$i];
        }
 
+       /**
+        * @return string
+        */
+       private function getMasterServerName() {
+               return $this->getServerName( $this->getWriterIndex() );
+       }
+
        function __destruct() {
                // Avoid connection leaks for sanity
                $this->disable();
index e66bd69..66be436 100644 (file)
@@ -565,7 +565,9 @@ class LogEventsList extends ContextSource {
                        }
                        $permissionlist = implode( ', ', $permissions );
                        wfDebug( "Checking for $permissionlist due to $field match on $bitfield\n" );
-                       return $user->isAllowedAny( ...$permissions );
+                       return MediaWikiServices::getInstance()
+                               ->getPermissionManager()
+                               ->userHasAnyRight( $user, ...$permissions );
                }
                return true;
        }
index 4ecc368..15b149e 100644 (file)
@@ -23,6 +23,8 @@
  * @file
  */
 
+use MediaWiki\MediaWikiServices;
+
 /**
  * @ingroup Pager
  */
@@ -462,7 +464,10 @@ class LogPager extends ReverseChronologicalPager {
                $user = $this->getUser();
                if ( !$user->isAllowed( 'deletedhistory' ) ) {
                        $this->mConds[] = $this->mDb->bitAnd( 'log_deleted', LogPage::DELETED_ACTION ) . ' = 0';
-               } elseif ( !$user->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
+               } elseif ( !MediaWikiServices::getInstance()
+                       ->getPermissionManager()
+                       ->userHasAnyRight( $user, 'suppressrevision', 'viewsuppressed' )
+               ) {
                        $this->mConds[] = $this->mDb->bitAnd( 'log_deleted', LogPage::SUPPRESSED_ACTION ) .
                                ' != ' . LogPage::SUPPRESSED_USER;
                }
@@ -480,7 +485,10 @@ class LogPager extends ReverseChronologicalPager {
                $user = $this->getUser();
                if ( !$user->isAllowed( 'deletedhistory' ) ) {
                        $this->mConds[] = $this->mDb->bitAnd( 'log_deleted', LogPage::DELETED_USER ) . ' = 0';
-               } elseif ( !$user->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
+               } elseif ( !MediaWikiServices::getInstance()
+                       ->getPermissionManager()
+                       ->userHasAnyRight( $user, 'suppressrevision', 'viewsuppressed' )
+               ) {
                        $this->mConds[] = $this->mDb->bitAnd( 'log_deleted', LogPage::SUPPRESSED_USER ) .
                                ' != ' . LogPage::SUPPRESSED_ACTION;
                }
index 7ee6dcb..6e4412c 100644 (file)
@@ -110,7 +110,7 @@ class ThumbnailImage extends MediaTransformOutput {
         * @return string
         */
        function toHtml( $options = [] ) {
-               global $wgPriorityHints, $wgPriorityHintsRatio, $wgElementTiming;
+               global $wgPriorityHints, $wgPriorityHintsRatio, $wgElementTiming, $wgNativeImageLazyLoading;
 
                if ( func_num_args() == 2 ) {
                        throw new MWException( __METHOD__ . ' called in the old style' );
@@ -126,6 +126,10 @@ class ThumbnailImage extends MediaTransformOutput {
                        'decoding' => 'async',
                ];
 
+               if ( $wgNativeImageLazyLoading ) {
+                       $attribs['loading'] = 'lazy';
+               }
+
                $elementTimingName = 'thumbnail';
 
                if ( $wgPriorityHints
index d9fe319..e634edc 100644 (file)
@@ -22,6 +22,7 @@
  */
 
 use MediaWiki\MediaWikiServices;
+use Wikimedia\AtEase\AtEase;
 use Wikimedia\Rdbms\Database;
 use Wikimedia\Rdbms\IDatabase;
 use Wikimedia\Rdbms\DBError;
@@ -30,6 +31,7 @@ use Wikimedia\Rdbms\DBConnectionError;
 use Wikimedia\Rdbms\IMaintainableDatabase;
 use Wikimedia\Rdbms\LoadBalancer;
 use Wikimedia\ScopedCallback;
+use Wikimedia\Timestamp\ConvertibleTimestamp;
 use Wikimedia\WaitConditionLoop;
 
 /**
@@ -43,7 +45,7 @@ class SqlBagOStuff extends MediumSpecificBagOStuff {
        /** @var string[] (server index => tag/host name) */
        protected $serverTags;
        /** @var int */
-       protected $numServers;
+       protected $numServerShards;
        /** @var int UNIX timestamp */
        protected $lastGarbageCollect = 0;
        /** @var int */
@@ -51,7 +53,7 @@ class SqlBagOStuff extends MediumSpecificBagOStuff {
        /** @var int */
        protected $purgeLimit = 100;
        /** @var int */
-       protected $shards = 1;
+       protected $numTableShards = 1;
        /** @var string */
        protected $tableName = 'objectcache';
        /** @var bool */
@@ -126,7 +128,7 @@ class SqlBagOStuff extends MediumSpecificBagOStuff {
                if ( isset( $params['servers'] ) ) {
                        $this->serverInfos = [];
                        $this->serverTags = [];
-                       $this->numServers = count( $params['servers'] );
+                       $this->numServerShards = count( $params['servers'] );
                        $index = 0;
                        foreach ( $params['servers'] as $tag => $info ) {
                                $this->serverInfos[$index] = $info;
@@ -139,11 +141,11 @@ class SqlBagOStuff extends MediumSpecificBagOStuff {
                        }
                } elseif ( isset( $params['server'] ) ) {
                        $this->serverInfos = [ $params['server'] ];
-                       $this->numServers = count( $this->serverInfos );
+                       $this->numServerShards = count( $this->serverInfos );
                } else {
                        // Default to using the main wiki's database servers
                        $this->serverInfos = false;
-                       $this->numServers = 1;
+                       $this->numServerShards = 1;
                        $this->attrMap[self::ATTR_SYNCWRITES] = self::QOS_SYNCWRITES_BE;
                }
                if ( isset( $params['purgePeriod'] ) ) {
@@ -156,7 +158,7 @@ class SqlBagOStuff extends MediumSpecificBagOStuff {
                        $this->tableName = $params['tableName'];
                }
                if ( isset( $params['shards'] ) ) {
-                       $this->shards = intval( $params['shards'] );
+                       $this->numTableShards = intval( $params['shards'] );
                }
                // Backwards-compatibility for < 1.34
                $this->replicaOnly = $params['replicaOnly'] ?? ( $params['slaveOnly'] ?? false );
@@ -165,52 +167,54 @@ class SqlBagOStuff extends MediumSpecificBagOStuff {
        /**
         * Get a connection to the specified database
         *
-        * @param int $serverIndex
+        * @param int $shardIndex
         * @return IMaintainableDatabase
         * @throws MWException
         */
-       protected function getDB( $serverIndex ) {
-               if ( $serverIndex >= $this->numServers ) {
-                       throw new MWException( __METHOD__ . ": Invalid server index \"$serverIndex\"" );
+       private function getConnection( $shardIndex ) {
+               if ( $shardIndex >= $this->numServerShards ) {
+                       throw new MWException( __METHOD__ . ": Invalid server index \"$shardIndex\"" );
                }
 
                # Don't keep timing out trying to connect for each call if the DB is down
                if (
-                       isset( $this->connFailureErrors[$serverIndex] ) &&
-                       ( $this->getCurrentTime() - $this->connFailureTimes[$serverIndex] ) < 60
+                       isset( $this->connFailureErrors[$shardIndex] ) &&
+                       ( $this->getCurrentTime() - $this->connFailureTimes[$shardIndex] ) < 60
                ) {
-                       throw $this->connFailureErrors[$serverIndex];
+                       throw $this->connFailureErrors[$shardIndex];
                }
 
                if ( $this->serverInfos ) {
-                       if ( !isset( $this->conns[$serverIndex] ) ) {
+                       if ( !isset( $this->conns[$shardIndex] ) ) {
                                // Use custom database defined by server connection info
-                               $info = $this->serverInfos[$serverIndex];
+                               $info = $this->serverInfos[$shardIndex];
                                $type = $info['type'] ?? 'mysql';
                                $host = $info['host'] ?? '[unknown]';
                                $this->logger->debug( __CLASS__ . ": connecting to $host" );
-                               $db = Database::factory( $type, $info );
-                               $db->clearFlag( DBO_TRX ); // auto-commit mode
-                               $this->conns[$serverIndex] = $db;
+                               $conn = Database::factory( $type, $info );
+                               $conn->clearFlag( DBO_TRX ); // auto-commit mode
+                               $this->conns[$shardIndex] = $conn;
                        }
-                       $db = $this->conns[$serverIndex];
+                       $conn = $this->conns[$shardIndex];
                } else {
                        // Use the main LB database
                        $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
                        $index = $this->replicaOnly ? DB_REPLICA : DB_MASTER;
-                       if ( $lb->getServerType( $lb->getWriterIndex() ) !== 'sqlite' ) {
-                               // Keep a separate connection to avoid contention and deadlocks
-                               $db = $lb->getConnectionRef( $index, [], false, $lb::CONN_TRX_AUTOCOMMIT );
-                       } else {
-                               // However, SQLite has the opposite behavior due to DB-level locking.
-                               // Stock sqlite MediaWiki installs use a separate sqlite cache DB instead.
-                               $db = $lb->getConnectionRef( $index );
+                       // If the RDBMS has row-level locking, use the autocommit connection to avoid
+                       // contention and deadlocks. Do not do this if it only has DB-level locking since
+                       // that would just cause deadlocks.
+                       $attribs = $lb->getServerAttributes( $lb->getWriterIndex() );
+                       $flags = $attribs[Database::ATTR_DB_LEVEL_LOCKING] ? 0 : $lb::CONN_TRX_AUTOCOMMIT;
+                       $conn = $lb->getMaintenanceConnectionRef( $index, [], false, $flags );
+                       // Automatically create the objectcache table for sqlite as needed
+                       if ( $conn->getType() === 'sqlite' ) {
+                               $this->initSqliteDatabase( $conn );
                        }
                }
 
-               $this->logger->debug( sprintf( "Connection %s will be used for SqlBagOStuff", $db ) );
+               $this->logger->debug( sprintf( "Connection %s will be used for SqlBagOStuff", $conn ) );
 
-               return $db;
+               return $conn;
        }
 
        /**
@@ -218,22 +222,22 @@ class SqlBagOStuff extends MediumSpecificBagOStuff {
         * @param string $key
         * @return array Server index and table name
         */
-       protected function getTableByKey( $key ) {
-               if ( $this->shards > 1 ) {
+       private function getTableByKey( $key ) {
+               if ( $this->numTableShards > 1 ) {
                        $hash = hexdec( substr( md5( $key ), 0, 8 ) ) & 0x7fffffff;
-                       $tableIndex = $hash % $this->shards;
+                       $tableIndex = $hash % $this->numTableShards;
                } else {
                        $tableIndex = 0;
                }
-               if ( $this->numServers > 1 ) {
+               if ( $this->numServerShards > 1 ) {
                        $sortedServers = $this->serverTags;
                        ArrayUtils::consistentHashSort( $sortedServers, $key );
                        reset( $sortedServers );
-                       $serverIndex = key( $sortedServers );
+                       $shardIndex = key( $sortedServers );
                } else {
-                       $serverIndex = 0;
+                       $shardIndex = 0;
                }
-               return [ $serverIndex, $this->getTableNameByShard( $tableIndex ) ];
+               return [ $shardIndex, $this->getTableNameByShard( $tableIndex ) ];
        }
 
        /**
@@ -241,9 +245,9 @@ class SqlBagOStuff extends MediumSpecificBagOStuff {
         * @param int $index
         * @return string
         */
-       protected function getTableNameByShard( $index ) {
-               if ( $this->shards > 1 ) {
-                       $decimals = strlen( $this->shards - 1 );
+       private function getTableNameByShard( $index ) {
+               if ( $this->numTableShards > 1 ) {
+                       $decimals = strlen( $this->numTableShards - 1 );
                        return $this->tableName .
                                sprintf( "%0{$decimals}d", $index );
                } else {
@@ -278,19 +282,19 @@ class SqlBagOStuff extends MediumSpecificBagOStuff {
                return $values;
        }
 
-       protected function fetchBlobMulti( array $keys, $flags = 0 ) {
+       private function fetchBlobMulti( array $keys, $flags = 0 ) {
                $values = []; // array of (key => value)
 
                $keysByTable = [];
                foreach ( $keys as $key ) {
-                       list( $serverIndex, $tableName ) = $this->getTableByKey( $key );
-                       $keysByTable[$serverIndex][$tableName][] = $key;
+                       list( $shardIndex, $tableName ) = $this->getTableByKey( $key );
+                       $keysByTable[$shardIndex][$tableName][] = $key;
                }
 
                $dataRows = [];
-               foreach ( $keysByTable as $serverIndex => $serverKeys ) {
+               foreach ( $keysByTable as $shardIndex => $serverKeys ) {
                        try {
-                               $db = $this->getDB( $serverIndex );
+                               $db = $this->getConnection( $shardIndex );
                                foreach ( $serverKeys as $tableName => $tableKeys ) {
                                        $res = $db->select( $tableName,
                                                [ 'keyname', 'value', 'exptime' ],
@@ -305,13 +309,13 @@ class SqlBagOStuff extends MediumSpecificBagOStuff {
                                                continue;
                                        }
                                        foreach ( $res as $row ) {
-                                               $row->serverIndex = $serverIndex;
+                                               $row->shardIndex = $shardIndex;
                                                $row->tableName = $tableName;
                                                $dataRows[$row->keyname] = $row;
                                        }
                                }
                        } catch ( DBError $e ) {
-                               $this->handleReadError( $e, $serverIndex );
+                               $this->handleReadError( $e, $shardIndex );
                        }
                }
 
@@ -321,14 +325,14 @@ class SqlBagOStuff extends MediumSpecificBagOStuff {
                                $this->debug( "get: retrieved data; expiry time is " . $row->exptime );
                                $db = null; // in case of connection failure
                                try {
-                                       $db = $this->getDB( $row->serverIndex );
+                                       $db = $this->getConnection( $row->shardIndex );
                                        if ( $this->isExpired( $db, $row->exptime ) ) { // MISS
                                                $this->debug( "get: key has expired" );
                                        } else { // HIT
                                                $values[$key] = $db->decodeBlob( $row->value );
                                        }
                                } catch ( DBQueryError $e ) {
-                                       $this->handleWriteError( $e, $db, $row->serverIndex );
+                                       $this->handleWriteError( $e, $db, $row->shardIndex );
                                }
                        } else { // MISS
                                $this->debug( 'get: no matching rows' );
@@ -352,8 +356,8 @@ class SqlBagOStuff extends MediumSpecificBagOStuff {
        private function modifyMulti( array $data, $exptime, $flags, $op ) {
                $keysByTable = [];
                foreach ( $data as $key => $value ) {
-                       list( $serverIndex, $tableName ) = $this->getTableByKey( $key );
-                       $keysByTable[$serverIndex][$tableName][] = $key;
+                       list( $shardIndex, $tableName ) = $this->getTableByKey( $key );
+                       $keysByTable[$shardIndex][$tableName][] = $key;
                }
 
                $exptime = $this->getExpirationAsTimestamp( $exptime );
@@ -361,14 +365,14 @@ class SqlBagOStuff extends MediumSpecificBagOStuff {
                $result = true;
                /** @noinspection PhpUnusedLocalVariableInspection */
                $silenceScope = $this->silenceTransactionProfiler();
-               foreach ( $keysByTable as $serverIndex => $serverKeys ) {
+               foreach ( $keysByTable as $shardIndex => $serverKeys ) {
                        $db = null; // in case of connection failure
                        try {
-                               $db = $this->getDB( $serverIndex );
+                               $db = $this->getConnection( $shardIndex );
                                $this->occasionallyGarbageCollect( $db ); // expire old entries if any
                                $dbExpiry = $exptime ? $db->timestamp( $exptime ) : $this->getMaxDateTime( $db );
                        } catch ( DBError $e ) {
-                               $this->handleWriteError( $e, $db, $serverIndex );
+                               $this->handleWriteError( $e, $db, $shardIndex );
                                $result = false;
                                continue;
                        }
@@ -384,14 +388,14 @@ class SqlBagOStuff extends MediumSpecificBagOStuff {
                                                $dbExpiry
                                        ) && $result;
                                } catch ( DBError $e ) {
-                                       $this->handleWriteError( $e, $db, $serverIndex );
+                                       $this->handleWriteError( $e, $db, $shardIndex );
                                        $result = false;
                                }
 
                        }
                }
 
-               if ( ( $flags & self::WRITE_SYNC ) == self::WRITE_SYNC ) {
+               if ( $this->fieldHasFlags( $flags, self::WRITE_SYNC ) ) {
                        $result = $this->waitForReplication() && $result;
                }
 
@@ -472,14 +476,14 @@ class SqlBagOStuff extends MediumSpecificBagOStuff {
        }
 
        protected function doCas( $casToken, $key, $value, $exptime = 0, $flags = 0 ) {
-               list( $serverIndex, $tableName ) = $this->getTableByKey( $key );
+               list( $shardIndex, $tableName ) = $this->getTableByKey( $key );
                $exptime = $this->getExpirationAsTimestamp( $exptime );
 
                /** @noinspection PhpUnusedLocalVariableInspection */
                $silenceScope = $this->silenceTransactionProfiler();
                $db = null; // in case of connection failure
                try {
-                       $db = $this->getDB( $serverIndex );
+                       $db = $this->getConnection( $shardIndex );
                        // (T26425) use a replace if the db supports it instead of
                        // delete/insert to avoid clashes with conflicting keynames
                        $db->update(
@@ -499,12 +503,17 @@ class SqlBagOStuff extends MediumSpecificBagOStuff {
                                __METHOD__
                        );
                } catch ( DBQueryError $e ) {
-                       $this->handleWriteError( $e, $db, $serverIndex );
+                       $this->handleWriteError( $e, $db, $shardIndex );
 
                        return false;
                }
 
-               return (bool)$db->affectedRows();
+               $success = (bool)$db->affectedRows();
+               if ( $this->fieldHasFlags( $flags, self::WRITE_SYNC ) ) {
+                       $success = $this->waitForReplication() && $success;
+               }
+
+               return $success;
        }
 
        protected function doDeleteMulti( array $keys, $flags = 0 ) {
@@ -520,15 +529,15 @@ class SqlBagOStuff extends MediumSpecificBagOStuff {
                return $this->modifyMulti( [ $key => null ], 0, $flags, self::$OP_DELETE );
        }
 
-       public function incr( $key, $step = 1 ) {
-               list( $serverIndex, $tableName ) = $this->getTableByKey( $key );
+       public function incr( $key, $step = 1, $flags = 0 ) {
+               list( $shardIndex, $tableName ) = $this->getTableByKey( $key );
 
                $newCount = false;
                /** @noinspection PhpUnusedLocalVariableInspection */
                $silenceScope = $this->silenceTransactionProfiler();
                $db = null; // in case of connection failure
                try {
-                       $db = $this->getDB( $serverIndex );
+                       $db = $this->getConnection( $shardIndex );
                        $encTimestamp = $db->addQuotes( $db->timestamp() );
                        $db->update(
                                $tableName,
@@ -548,19 +557,14 @@ class SqlBagOStuff extends MediumSpecificBagOStuff {
                                }
                        }
                } catch ( DBError $e ) {
-                       $this->handleWriteError( $e, $db, $serverIndex );
+                       $this->handleWriteError( $e, $db, $shardIndex );
                }
 
                return $newCount;
        }
 
-       public function merge( $key, callable $callback, $exptime = 0, $attempts = 10, $flags = 0 ) {
-               $ok = $this->mergeViaCas( $key, $callback, $exptime, $attempts, $flags );
-               if ( ( $flags & self::WRITE_SYNC ) == self::WRITE_SYNC ) {
-                       $ok = $this->waitForReplication() && $ok;
-               }
-
-               return $ok;
+       public function decr( $key, $value = 1, $flags = 0 ) {
+               return $this->incr( $key, -$value, $flags );
        }
 
        public function changeTTLMulti( array $keys, $exptime, $flags = 0 ) {
@@ -581,10 +585,10 @@ class SqlBagOStuff extends MediumSpecificBagOStuff {
         * @param string $exptime
         * @return bool
         */
-       protected function isExpired( $db, $exptime ) {
+       private function isExpired( IDatabase $db, $exptime ) {
                return (
                        $exptime != $this->getMaxDateTime( $db ) &&
-                       wfTimestamp( TS_UNIX, $exptime ) < $this->getCurrentTime()
+                       ConvertibleTimestamp::convert( TS_UNIX, $exptime ) < $this->getCurrentTime()
                );
        }
 
@@ -592,7 +596,7 @@ class SqlBagOStuff extends MediumSpecificBagOStuff {
         * @param IDatabase $db
         * @return string
         */
-       protected function getMaxDateTime( $db ) {
+       private function getMaxDateTime( $db ) {
                if ( (int)$this->getCurrentTime() > 0x7fffffff ) {
                        return $db->timestamp( 1 << 62 );
                } else {
@@ -604,7 +608,7 @@ class SqlBagOStuff extends MediumSpecificBagOStuff {
         * @param IDatabase $db
         * @throws DBError
         */
-       protected function occasionallyGarbageCollect( IDatabase $db ) {
+       private function occasionallyGarbageCollect( IDatabase $db ) {
                if (
                        // Random purging is enabled
                        $this->purgePeriod &&
@@ -642,16 +646,16 @@ class SqlBagOStuff extends MediumSpecificBagOStuff {
                /** @noinspection PhpUnusedLocalVariableInspection */
                $silenceScope = $this->silenceTransactionProfiler();
 
-               $serverIndexes = range( 0, $this->numServers - 1 );
-               shuffle( $serverIndexes );
+               $shardIndexes = range( 0, $this->numServerShards - 1 );
+               shuffle( $shardIndexes );
 
                $ok = true;
 
                $keysDeletedCount = 0;
-               foreach ( $serverIndexes as $numServersDone => $serverIndex ) {
+               foreach ( $shardIndexes as $numServersDone => $shardIndex ) {
                        $db = null; // in case of connection failure
                        try {
-                               $db = $this->getDB( $serverIndex );
+                               $db = $this->getConnection( $shardIndex );
                                $this->deleteServerObjectsExpiringBefore(
                                        $db,
                                        $timestamp,
@@ -661,7 +665,7 @@ class SqlBagOStuff extends MediumSpecificBagOStuff {
                                        $keysDeletedCount
                                );
                        } catch ( DBError $e ) {
-                               $this->handleWriteError( $e, $db, $serverIndex );
+                               $this->handleWriteError( $e, $db, $shardIndex );
                                $ok = false;
                        }
                }
@@ -686,8 +690,8 @@ class SqlBagOStuff extends MediumSpecificBagOStuff {
                $serversDoneCount = 0,
                &$keysDeletedCount = 0
        ) {
-               $cutoffUnix = wfTimestamp( TS_UNIX, $timestamp );
-               $shardIndexes = range( 0, $this->shards - 1 );
+               $cutoffUnix = ConvertibleTimestamp::convert( TS_UNIX, $timestamp );
+               $shardIndexes = range( 0, $this->numTableShards - 1 );
                shuffle( $shardIndexes );
 
                foreach ( $shardIndexes as $numShardsDone => $shardIndex ) {
@@ -708,7 +712,8 @@ class SqlBagOStuff extends MediumSpecificBagOStuff {
                                if ( $res->numRows() ) {
                                        $row = $res->current();
                                        if ( $lag === null ) {
-                                               $lag = max( $cutoffUnix - wfTimestamp( TS_UNIX, $row->exptime ), 1 );
+                                               $rowExpUnix = ConvertibleTimestamp::convert( TS_UNIX, $row->exptime );
+                                               $lag = max( $cutoffUnix - $rowExpUnix, 1 );
                                        }
 
                                        $keys = [];
@@ -730,15 +735,16 @@ class SqlBagOStuff extends MediumSpecificBagOStuff {
 
                                if ( is_callable( $progressCallback ) ) {
                                        if ( $lag ) {
-                                               $remainingLag = $cutoffUnix - wfTimestamp( TS_UNIX, $continue );
+                                               $continueUnix = ConvertibleTimestamp::convert( TS_UNIX, $continue );
+                                               $remainingLag = $cutoffUnix - $continueUnix;
                                                $processedLag = max( $lag - $remainingLag, 0 );
-                                               $doneRatio = ( $numShardsDone + $processedLag / $lag ) / $this->shards;
+                                               $doneRatio = ( $numShardsDone + $processedLag / $lag ) / $this->numTableShards;
                                        } else {
                                                $doneRatio = 1;
                                        }
 
-                                       $overallRatio = ( $doneRatio / $this->numServers )
-                                               + ( $serversDoneCount / $this->numServers );
+                                       $overallRatio = ( $doneRatio / $this->numServerShards )
+                                               + ( $serversDoneCount / $this->numServerShards );
                                        call_user_func( $progressCallback, $overallRatio * 100 );
                                }
                        } while ( $res->numRows() && $keysDeletedCount < $limit );
@@ -753,15 +759,15 @@ class SqlBagOStuff extends MediumSpecificBagOStuff {
        public function deleteAll() {
                /** @noinspection PhpUnusedLocalVariableInspection */
                $silenceScope = $this->silenceTransactionProfiler();
-               for ( $serverIndex = 0; $serverIndex < $this->numServers; $serverIndex++ ) {
+               for ( $shardIndex = 0; $shardIndex < $this->numServerShards; $shardIndex++ ) {
                        $db = null; // in case of connection failure
                        try {
-                               $db = $this->getDB( $serverIndex );
-                               for ( $i = 0; $i < $this->shards; $i++ ) {
+                               $db = $this->getConnection( $shardIndex );
+                               for ( $i = 0; $i < $this->numTableShards; $i++ ) {
                                        $db->delete( $this->getTableNameByShard( $i ), '*', __METHOD__ );
                                }
                        } catch ( DBError $e ) {
-                               $this->handleWriteError( $e, $db, $serverIndex );
+                               $this->handleWriteError( $e, $db, $shardIndex );
                                return false;
                        }
                }
@@ -779,11 +785,11 @@ class SqlBagOStuff extends MediumSpecificBagOStuff {
                        }
                }
 
-               list( $serverIndex ) = $this->getTableByKey( $key );
+               list( $shardIndex ) = $this->getTableByKey( $key );
 
                $db = null; // in case of connection failure
                try {
-                       $db = $this->getDB( $serverIndex );
+                       $db = $this->getConnection( $shardIndex );
                        $ok = $db->lock( $key, __METHOD__, $timeout );
                        if ( $ok ) {
                                $this->locks[$key] = [ 'class' => $rclass, 'depth' => 1 ];
@@ -796,7 +802,7 @@ class SqlBagOStuff extends MediumSpecificBagOStuff {
 
                        return $ok;
                } catch ( DBError $e ) {
-                       $this->handleWriteError( $e, $db, $serverIndex );
+                       $this->handleWriteError( $e, $db, $shardIndex );
                        $ok = false;
                }
 
@@ -811,11 +817,11 @@ class SqlBagOStuff extends MediumSpecificBagOStuff {
                if ( --$this->locks[$key]['depth'] <= 0 ) {
                        unset( $this->locks[$key] );
 
-                       list( $serverIndex ) = $this->getTableByKey( $key );
+                       list( $shardIndex ) = $this->getTableByKey( $key );
 
                        $db = null; // in case of connection failure
                        try {
-                               $db = $this->getDB( $serverIndex );
+                               $db = $this->getConnection( $shardIndex );
                                $ok = $db->unlock( $key, __METHOD__ );
                                if ( !$ok ) {
                                        $this->logger->warning(
@@ -824,7 +830,7 @@ class SqlBagOStuff extends MediumSpecificBagOStuff {
                                        );
                                }
                        } catch ( DBError $e ) {
-                               $this->handleWriteError( $e, $db, $serverIndex );
+                               $this->handleWriteError( $e, $db, $shardIndex );
                                $ok = false;
                        }
 
@@ -866,9 +872,9 @@ class SqlBagOStuff extends MediumSpecificBagOStuff {
                }
 
                if ( function_exists( 'gzinflate' ) ) {
-                       Wikimedia\suppressWarnings();
+                       AtEase::suppressWarnings();
                        $decomp = gzinflate( $serial );
-                       Wikimedia\restoreWarnings();
+                       AtEase::restoreWarnings();
 
                        if ( $decomp !== false ) {
                                $serial = $decomp;
@@ -882,11 +888,11 @@ class SqlBagOStuff extends MediumSpecificBagOStuff {
         * Handle a DBError which occurred during a read operation.
         *
         * @param DBError $exception
-        * @param int $serverIndex
+        * @param int $shardIndex
         */
-       protected function handleReadError( DBError $exception, $serverIndex ) {
+       private function handleReadError( DBError $exception, $shardIndex ) {
                if ( $exception instanceof DBConnectionError ) {
-                       $this->markServerDown( $exception, $serverIndex );
+                       $this->markServerDown( $exception, $shardIndex );
                }
 
                $this->setAndLogDBError( $exception );
@@ -897,12 +903,12 @@ class SqlBagOStuff extends MediumSpecificBagOStuff {
         *
         * @param DBError $exception
         * @param IDatabase|null $db DB handle or null if connection failed
-        * @param int $serverIndex
+        * @param int $shardIndex
         * @throws Exception
         */
-       protected function handleWriteError( DBError $exception, $db, $serverIndex ) {
+       private function handleWriteError( DBError $exception, $db, $shardIndex ) {
                if ( !( $db instanceof IDatabase ) ) {
-                       $this->markServerDown( $exception, $serverIndex );
+                       $this->markServerDown( $exception, $shardIndex );
                }
 
                $this->setAndLogDBError( $exception );
@@ -926,41 +932,68 @@ class SqlBagOStuff extends MediumSpecificBagOStuff {
         * Mark a server down due to a DBConnectionError exception
         *
         * @param DBError $exception
-        * @param int $serverIndex
+        * @param int $shardIndex
         */
-       protected function markServerDown( DBError $exception, $serverIndex ) {
-               unset( $this->conns[$serverIndex] ); // bug T103435
+       private function markServerDown( DBError $exception, $shardIndex ) {
+               unset( $this->conns[$shardIndex] ); // bug T103435
 
                $now = $this->getCurrentTime();
-               if ( isset( $this->connFailureTimes[$serverIndex] ) ) {
-                       if ( $now - $this->connFailureTimes[$serverIndex] >= 60 ) {
-                               unset( $this->connFailureTimes[$serverIndex] );
-                               unset( $this->connFailureErrors[$serverIndex] );
+               if ( isset( $this->connFailureTimes[$shardIndex] ) ) {
+                       if ( $now - $this->connFailureTimes[$shardIndex] >= 60 ) {
+                               unset( $this->connFailureTimes[$shardIndex] );
+                               unset( $this->connFailureErrors[$shardIndex] );
                        } else {
-                               $this->logger->debug( __METHOD__ . ": Server #$serverIndex already down" );
+                               $this->logger->debug( __METHOD__ . ": Server #$shardIndex already down" );
                                return;
                        }
                }
-               $this->logger->info( __METHOD__ . ": Server #$serverIndex down until " . ( $now + 60 ) );
-               $this->connFailureTimes[$serverIndex] = $now;
-               $this->connFailureErrors[$serverIndex] = $exception;
+               $this->logger->info( __METHOD__ . ": Server #$shardIndex down until " . ( $now + 60 ) );
+               $this->connFailureTimes[$shardIndex] = $now;
+               $this->connFailureErrors[$shardIndex] = $exception;
        }
 
        /**
-        * Create shard tables. For use from eval.php.
+        * @param IMaintainableDatabase $db
+        * @throws DBError
         */
-       public function createTables() {
-               for ( $serverIndex = 0; $serverIndex < $this->numServers; $serverIndex++ ) {
-                       $db = $this->getDB( $serverIndex );
-                       if ( $db->getType() !== 'mysql' ) {
-                               throw new MWException( __METHOD__ . ' is not supported on this DB server' );
-                       }
+       private function initSqliteDatabase( IMaintainableDatabase $db ) {
+               if ( $db->tableExists( 'objectcache' ) ) {
+                       return;
+               }
+               // Use one table for SQLite; sharding does not seem to have much benefit
+               $db->query( "PRAGMA journal_mode=WAL" ); // this is permanent
+               $db->startAtomic( __METHOD__ ); // atomic DDL
+               try {
+                       $encTable = $db->tableName( 'objectcache' );
+                       $encExptimeIndex = $db->addIdentifierQuotes( $db->tablePrefix() . 'exptime' );
+                       $db->query(
+                               "CREATE TABLE $encTable (\n" .
+                               "       keyname BLOB NOT NULL default '' PRIMARY KEY,\n" .
+                               "       value BLOB,\n" .
+                               "       exptime TEXT\n" .
+                               ")",
+                               __METHOD__
+                       );
+                       $db->query( "CREATE INDEX $encExptimeIndex ON $encTable (exptime)" );
+                       $db->endAtomic( __METHOD__ );
+               } catch ( DBError $e ) {
+                       $db->rollback( __METHOD__ );
+                       throw $e;
+               }
+       }
 
-                       for ( $i = 0; $i < $this->shards; $i++ ) {
-                               $db->query(
-                                       'CREATE TABLE ' . $db->tableName( $this->getTableNameByShard( $i ) ) .
-                                       ' LIKE ' . $db->tableName( 'objectcache' ),
-                                       __METHOD__ );
+       /**
+        * Create the shard tables on all databases (e.g. via eval.php/shell.php)
+        */
+       public function createTables() {
+               for ( $shardIndex = 0; $shardIndex < $this->numServerShards; $shardIndex++ ) {
+                       $db = $this->getConnection( $shardIndex );
+                       if ( in_array( $db->getType(), [ 'mysql', 'postgres' ], true ) ) {
+                               for ( $i = 0; $i < $this->numTableShards; $i++ ) {
+                                       $encBaseTable = $db->tableName( 'objectcache' );
+                                       $encShardTable = $db->tableName( $this->getTableNameByShard( $i ) );
+                                       $db->query( "CREATE TABLE $encShardTable LIKE $encBaseTable" );
+                               }
                        }
                }
        }
@@ -968,11 +1001,11 @@ class SqlBagOStuff extends MediumSpecificBagOStuff {
        /**
         * @return bool Whether the main DB is used, e.g. wfGetDB( DB_MASTER )
         */
-       protected function usesMainDB() {
+       private function usesMainDB() {
                return !$this->serverInfos;
        }
 
-       protected function waitForReplication() {
+       private function waitForReplication() {
                if ( !$this->usesMainDB() ) {
                        // Custom DB server list; probably doesn't use replication
                        return true;
@@ -1007,12 +1040,17 @@ class SqlBagOStuff extends MediumSpecificBagOStuff {
        }
 
        /**
-        * Returns a ScopedCallback which resets the silence flag in the transaction profiler when it is
-        * destroyed on the end of a scope, for example on return or throw
-        * @return ScopedCallback
-        * @since 1.32
+        * Silence the transaction profiler until the return value falls out of scope
+        *
+        * @return ScopedCallback|null
         */
-       protected function silenceTransactionProfiler() {
+       private function silenceTransactionProfiler() {
+               if ( !$this->usesMainDB() ) {
+                       // Custom DB is configured which either has no TransactionProfiler injected,
+                       // or has one specific for cache use, which we shouldn't silence
+                       return null;
+               }
+
                $trxProfiler = Profiler::instance()->getTransactionProfiler();
                $oldSilenced = $trxProfiler->setSilenced( true );
                return new ScopedCallback( function () use ( $trxProfiler, $oldSilenced ) {
index e488b6c..2de82bf 100644 (file)
@@ -91,7 +91,9 @@ class ImageHistoryList extends ContextSource {
                . Xml::openElement( 'table', [ 'class' => 'wikitable filehistory' ] ) . "\n"
                . '<tr><th></th>'
                . ( $this->current->isLocal()
-               && ( $this->getUser()->isAllowedAny( 'delete', 'deletedhistory' ) ) ? '<th></th>' : '' )
+               && ( MediaWikiServices::getInstance()
+                               ->getPermissionManager()
+                               ->userHasAnyRight( $this->getUser(), 'delete', 'deletedhistory' ) ) ? '<th></th>' : '' )
                . '<th>' . $this->msg( 'filehist-datetime' )->escaped() . '</th>'
                . ( $this->showThumb ? '<th>' . $this->msg( 'filehist-thumb' )->escaped() . '</th>' : '' )
                . '<th>' . $this->msg( 'filehist-dimensions' )->escaped() . '</th>'
@@ -126,7 +128,10 @@ class ImageHistoryList extends ContextSource {
                $row = $selected = '';
 
                // Deletion link
-               if ( $local && ( $user->isAllowedAny( 'delete', 'deletedhistory' ) ) ) {
+               if ( $local && ( MediaWikiServices::getInstance()
+                               ->getPermissionManager()
+                               ->userHasAnyRight( $user, 'delete', 'deletedhistory' ) )
+               ) {
                        $row .= '<td>';
                        # Link to remove from history
                        if ( $user->isAllowed( 'delete' ) ) {
index 8cc5a39..4607535 100644 (file)
@@ -3249,7 +3249,10 @@ class WikiPage implements Page, IDBAccessObject {
                        $flags |= EDIT_MINOR;
                }
 
-               if ( $bot && ( $guser->isAllowedAny( 'markbotedits', 'bot' ) ) ) {
+               if ( $bot && ( MediaWikiServices::getInstance()
+                               ->getPermissionManager()
+                               ->userHasAnyRight( $guser, 'markbotedits', 'bot' ) )
+               ) {
                        $flags |= EDIT_FORCE_BOT;
                }
 
index 04021cc..472bcdd 100644 (file)
  * @ingroup Pager
  */
 
-use Wikimedia\Rdbms\IResultWrapper;
-use Wikimedia\Rdbms\IDatabase;
+use MediaWiki\Linker\LinkRenderer;
 use MediaWiki\Linker\LinkTarget;
+use MediaWiki\MediaWikiServices;
 use MediaWiki\Navigation\PrevNextNavigationRenderer;
+use Wikimedia\Rdbms\IDatabase;
+use Wikimedia\Rdbms\IResultWrapper;
 
 /**
  * IndexPager is an efficient pager which uses a (roughly unique) index in the
@@ -157,7 +159,10 @@ abstract class IndexPager extends ContextSource implements Pager {
         */
        public $mResult;
 
-       public function __construct( IContextSource $context = null ) {
+       /** @var LinkRenderer */
+       private $linkRenderer;
+
+       public function __construct( IContextSource $context = null, LinkRenderer $linkRenderer = null ) {
                if ( $context ) {
                        $this->setContext( $context );
                }
@@ -209,6 +214,7 @@ abstract class IndexPager extends ContextSource implements Pager {
                                ? $dir[$this->mOrderType]
                                : $dir;
                }
+               $this->linkRenderer = $linkRenderer;
        }
 
        /**
@@ -526,9 +532,9 @@ abstract class IndexPager extends ContextSource implements Pager {
                        $attrs['class'] = "mw-{$type}link";
                }
 
-               return Linker::linkKnown(
+               return $this->getLinkRenderer()->makeKnownLink(
                        $this->getTitle(),
-                       $text,
+                       new HtmlArmor( $text ),
                        $attrs,
                        $query + $this->getDefaultQuery()
                );
@@ -804,4 +810,11 @@ abstract class IndexPager extends ContextSource implements Pager {
 
                return $prevNext->buildPrevNextNavigation( $title, $offset, $limit, $query,  $atend );
        }
+
+       protected function getLinkRenderer() {
+               if ( $this->linkRenderer === null ) {
+                        $this->linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
+               }
+               return $this->linkRenderer;
+       }
 }
index d94104b..7f54617 100644 (file)
@@ -21,6 +21,8 @@
  * @ingroup Pager
  */
 
+use MediaWiki\Linker\LinkRenderer;
+
 /**
  * Table-based display with a user-selectable sort order
  * @ingroup Pager
@@ -32,10 +34,8 @@ abstract class TablePager extends IndexPager {
        /** @var stdClass */
        protected $mCurrentRow;
 
-       public function __construct( IContextSource $context = null ) {
-               if ( $context ) {
-                       $this->setContext( $context );
-               }
+       public function __construct( IContextSource $context = null, LinkRenderer $linkRenderer = null ) {
+               parent::__construct( $context, $linkRenderer );
 
                $this->mSort = $this->getRequest()->getText( 'sort' );
                if ( !array_key_exists( $this->mSort, $this->getFieldNames() )
@@ -48,8 +48,6 @@ abstract class TablePager extends IndexPager {
                } elseif ( $this->getRequest()->getBool( 'desc' ) ) {
                        $this->mDefaultDirection = IndexPager::DIR_DESCENDING;
                } /* Else leave it at whatever the class default is */
-
-               parent::__construct();
        }
 
        /**
index b643c3f..a19f86c 100644 (file)
@@ -20,6 +20,7 @@
  * @file
  * @ingroup Parser
  */
+use MediaWiki\BadFileLookup;
 use MediaWiki\Config\ServiceOptions;
 use MediaWiki\Linker\LinkRenderer;
 use MediaWiki\Linker\LinkRendererFactory;
@@ -299,6 +300,9 @@ class Parser {
        /** @var LoggerInterface */
        private $logger;
 
+       /** @var BadFileLookup */
+       private $badFileLookup;
+
        /**
         * TODO Make this a const when HHVM support is dropped (T192166)
         *
@@ -339,6 +343,7 @@ class Parser {
         * @param LinkRendererFactory|null $linkRendererFactory
         * @param NamespaceInfo|null $nsInfo
         * @param LoggerInterface|null $logger
+        * @param BadFileLookup|null $badFileLookup
         */
        public function __construct(
                $svcOptions = null,
@@ -349,7 +354,8 @@ class Parser {
                SpecialPageFactory $spFactory = null,
                $linkRendererFactory = null,
                $nsInfo = null,
-               $logger = null
+               $logger = null,
+               BadFileLookup $badFileLookup = null
        ) {
                if ( !$svcOptions || is_array( $svcOptions ) ) {
                        // Pre-1.34 calling convention is the first parameter is just ParserConf, the seventh is
@@ -396,6 +402,8 @@ class Parser {
                        MediaWikiServices::getInstance()->getLinkRendererFactory();
                $this->nsInfo = $nsInfo ?? MediaWikiServices::getInstance()->getNamespaceInfo();
                $this->logger = $logger ?: new NullLogger();
+               $this->badFileLookup = $badFileLookup ??
+                       MediaWikiServices::getInstance()->getBadFileLookup();
        }
 
        /**
@@ -530,7 +538,10 @@ class Parser {
         * @param ParserOptions $options
         * @param bool $linestart
         * @param bool $clearState
-        * @param int|null $revid Number to pass in {{REVISIONID}}
+        * @param int|null $revid ID of the revision being rendered. This is used to render
+        *  REVISION* magic words. 0 means that any current revision will be used. Null means
+        *  that {{REVISIONID}}/{{REVISIONUSER}} will be empty and {{REVISIONTIMESTAMP}} will
+        *  use the current timestamp.
         * @return ParserOutput A ParserOutput
         * @return-taint escaped
         */
@@ -2498,7 +2509,7 @@ class Parser {
                                }
 
                                if ( $ns == NS_FILE ) {
-                                       if ( !wfIsBadImage( $nt->getDBkey(), $this->mTitle ) ) {
+                                       if ( !$this->badFileLookup->isBadFile( $nt->getDBkey(), $this->mTitle ) ) {
                                                if ( $wasblank ) {
                                                        # if no parameters were passed, $text
                                                        # becomes something like "File:Foo.png",
index 2585872..f871358 100644 (file)
@@ -49,7 +49,7 @@ class ParserCache {
        const USE_ANYTHING = 3;
 
        /** @var BagOStuff */
-       private $mMemc;
+       private $cache;
 
        /**
         * Anything cached prior to this is invalidated
@@ -79,7 +79,7 @@ class ParserCache {
         * @throws MWException
         */
        public function __construct( BagOStuff $cache, $cacheEpoch = '20030516000000' ) {
-               $this->mMemc = $cache;
+               $this->cache = $cache;
                $this->cacheEpoch = $cacheEpoch;
        }
 
@@ -95,7 +95,7 @@ class ParserCache {
                $pageid = $article->getId();
                $renderkey = (int)( $wgRequest->getVal( 'action' ) == 'render' );
 
-               $key = $this->mMemc->makeKey( 'pcache', 'idhash', "{$pageid}-{$renderkey}!{$hash}" );
+               $key = $this->cache->makeKey( 'pcache', 'idhash', "{$pageid}-{$renderkey}!{$hash}" );
                return $key;
        }
 
@@ -104,7 +104,7 @@ class ParserCache {
         * @return mixed|string
         */
        protected function getOptionsKey( $page ) {
-               return $this->mMemc->makeKey( 'pcache', 'idoptions', $page->getId() );
+               return $this->cache->makeKey( 'pcache', 'idoptions', $page->getId() );
        }
 
        /**
@@ -112,7 +112,7 @@ class ParserCache {
         * @since 1.28
         */
        public function deleteOptionsKey( $page ) {
-               $this->mMemc->delete( $this->getOptionsKey( $page ) );
+               $this->cache->delete( $this->getOptionsKey( $page ) );
        }
 
        /**
@@ -190,7 +190,7 @@ class ParserCache {
                }
 
                // Determine the options which affect this article
-               $optionsKey = $this->mMemc->get(
+               $optionsKey = $this->cache->get(
                        $this->getOptionsKey( $article ), BagOStuff::READ_VERIFIED );
                if ( $optionsKey instanceof CacheTime ) {
                        if ( $useOutdated < self::USE_EXPIRED && $optionsKey->expired( $article->getTouched() ) ) {
@@ -257,7 +257,7 @@ class ParserCache {
 
                $casToken = null;
                /** @var ParserOutput $value */
-               $value = $this->mMemc->get( $parserOutputKey, BagOStuff::READ_VERIFIED );
+               $value = $this->cache->get( $parserOutputKey, BagOStuff::READ_VERIFIED );
                if ( !$value ) {
                        wfDebug( "ParserOutput cache miss.\n" );
                        $this->incrementStats( $article, "miss.absent" );
@@ -319,7 +319,7 @@ class ParserCache {
                }
 
                $expire = $parserOutput->getCacheExpiry();
-               if ( $expire > 0 && !$this->mMemc instanceof EmptyBagOStuff ) {
+               if ( $expire > 0 && !$this->cache instanceof EmptyBagOStuff ) {
                        $cacheTime = $cacheTime ?: wfTimestampNow();
                        if ( !$revId ) {
                                $revision = $page->getRevision();
@@ -350,10 +350,15 @@ class ParserCache {
                        wfDebug( $msg );
 
                        // Save the parser output
-                       $this->mMemc->set( $parserOutputKey, $parserOutput, $expire );
+                       $this->cache->set(
+                               $parserOutputKey,
+                               $parserOutput,
+                               $expire,
+                               BagOStuff::WRITE_ALLOW_SEGMENTS
+                       );
 
                        // ...and its pointer
-                       $this->mMemc->set( $this->getOptionsKey( $page ), $optionsKey, $expire );
+                       $this->cache->set( $this->getOptionsKey( $page ), $optionsKey, $expire );
 
                        Hooks::run(
                                'ParserCacheSaveComplete',
@@ -372,6 +377,6 @@ class ParserCache {
         * @return BagOStuff
         */
        public function getCacheStorage() {
-               return $this->mMemc;
+               return $this->cache;
        }
 }
index 3d15e86..bab1f36 100644 (file)
@@ -19,6 +19,7 @@
  * @ingroup Parser
  */
 
+use MediaWiki\BadFileLookup;
 use MediaWiki\Config\ServiceOptions;
 use MediaWiki\Linker\LinkRendererFactory;
 use MediaWiki\MediaWikiServices;
@@ -54,6 +55,9 @@ class ParserFactory {
        /** @var LoggerInterface */
        private $logger;
 
+       /** @var BadFileLookup */
+       private $badFileLookup;
+
        /**
         * Old parameter list, which we support for backwards compatibility, were:
         *   array $parserConf See $wgParserConf documentation
@@ -77,6 +81,7 @@ class ParserFactory {
         * @param LinkRendererFactory $linkRendererFactory
         * @param NamespaceInfo|LinkRendererFactory|null $nsInfo
         * @param LoggerInterface|null $logger
+        * @param BadFileLookup|null $badFileLookup
         * @since 1.32
         */
        public function __construct(
@@ -87,7 +92,8 @@ class ParserFactory {
                SpecialPageFactory $spFactory,
                $linkRendererFactory,
                $nsInfo = null,
-               $logger = null
+               $logger = null,
+               BadFileLookup $badFileLookup = null
        ) {
                // @todo Do we need to retain compat for constructing this class directly?
                if ( !$nsInfo ) {
@@ -119,6 +125,8 @@ class ParserFactory {
                $this->linkRendererFactory = $linkRendererFactory;
                $this->nsInfo = $nsInfo;
                $this->logger = $logger ?: new NullLogger();
+               $this->badFileLookup = $badFileLookup ??
+                       MediaWikiServices::getInstance()->getBadFileLookup();
        }
 
        /**
@@ -135,7 +143,8 @@ class ParserFactory {
                        $this->specialPageFactory,
                        $this->linkRendererFactory,
                        $this->nsInfo,
-                       $this->logger
+                       $this->logger,
+                       $this->badFileLookup
                );
        }
 }
index 9892b15..0785225 100644 (file)
@@ -661,8 +661,9 @@ class ResourceLoader implements LoggerAwareInterface {
                                // Do not allow private modules to be loaded from the web.
                                // This is a security issue, see T36907.
                                if ( $module->getGroup() === 'private' ) {
+                                       // Not a serious error, just means something is trying to access it (T101806)
                                        $this->logger->debug( "Request for private module '$name' denied" );
-                                       $this->errors[] = "Cannot show private module \"$name\"";
+                                       $this->errors[] = "Cannot build private module \"$name\"";
                                        continue;
                                }
                                $modules[$name] = $module;
index d59e1c8..e17b393 100644 (file)
@@ -143,15 +143,15 @@ class ResourceLoaderClientHtml {
 
                        $group = $module->getGroup();
                        $context = $this->getContext( $group, ResourceLoaderModule::TYPE_COMBINED );
-                       if ( $module->isKnownEmpty( $context ) ) {
-                               // Avoid needless request or embed for empty module
-                               $data['states'][$name] = 'ready';
-                               continue;
-                       }
+                       $shouldEmbed = $module->shouldEmbedModule( $this->context );
 
-                       if ( $group === 'user' || $module->shouldEmbedModule( $this->context ) ) {
-                               // Call makeLoad() to decide how to load these, instead of
-                               // loading via mw.loader.load().
+                       if ( ( $group === 'user' || $shouldEmbed ) && $module->isKnownEmpty( $context ) ) {
+                               // This is a user-specific or embedded module, which means its output
+                               // can be specific to the current page or user. As such, we can optimise
+                               // the way we load it based on the current version of the module.
+                               // Avoid needless embed for empty module, preset ready state.
+                               $data['states'][$name] = 'ready';
+                       } elseif ( $group === 'user' || $shouldEmbed ) {
                                // - For group=user: We need to provide a pre-generated load.php
                                //   url to the client that has the 'user' and 'version' parameters
                                //   filled in. Without this, the client would wrongly use the static
@@ -187,15 +187,30 @@ class ResourceLoaderClientHtml {
 
                        $group = $module->getGroup();
                        $context = $this->getContext( $group, ResourceLoaderModule::TYPE_STYLES );
-                       // Avoid needless request for empty module
-                       if ( !$module->isKnownEmpty( $context ) ) {
-                               if ( $module->shouldEmbedModule( $this->context ) ) {
-                                       // Embed via style element
+                       if ( $module->shouldEmbedModule( $this->context ) ) {
+                               // Avoid needless embed for private embeds we know are empty.
+                               // (Set "ready" state directly instead, which we do a few lines above.)
+                               if ( !$module->isKnownEmpty( $context ) ) {
+                                       // Embed via <style> element
                                        $data['embed']['styles'][] = $name;
-                               } else {
-                                       // Load from load.php?only=styles via <link rel=stylesheet>
-                                       $data['styles'][] = $name;
                                }
+                       // For other style modules, always request them, regardless of whether they are
+                       // currently known to be empty. Because:
+                       // 1. Those modules are requested in batch, so there is no extra request overhead
+                       //    or extra HTML element to be avoided.
+                       // 2. Checking isKnownEmpty for those can be expensive and slow down page view
+                       //    generation (T230260).
+                       // 3. We don't want cached HTML to vary on the current state of a module.
+                       //    If the module becomes non-empty a few minutes later, it should start working
+                       //    on cached HTML without requiring a purge.
+                       //
+                       // But, user-specific modules:
+                       // * ... are used on page views not publicly cached.
+                       // * ... are in their own group and thus a require a request we can avoid
+                       // * ... have known-empty status preloaded by ResourceLoader.
+                       } elseif ( $group !== 'user' || !$module->isKnownEmpty( $context ) ) {
+                               // Load from load.php?only=styles via <link rel=stylesheet>
+                               $data['styles'][] = $name;
                        }
                        $deprecation = $module->getDeprecationInformation();
                        if ( $deprecation ) {
index 94e8a3e..c3948cb 100644 (file)
@@ -76,8 +76,8 @@ class ResourceLoaderContext implements MessageLocalizer {
                // Various parameters
                $this->user = $request->getRawVal( 'user' );
                $this->debug = $request->getRawVal( 'debug' ) === 'true';
-               $this->only = $request->getRawVal( 'only', null );
-               $this->version = $request->getRawVal( 'version', null );
+               $this->only = $request->getRawVal( 'only' );
+               $this->version = $request->getRawVal( 'version' );
                $this->raw = $request->getFuzzyBool( 'raw' );
 
                // Image requests
index 8f026dc..58c9ee5 100644 (file)
@@ -42,6 +42,15 @@ use MediaWiki\MediaWikiServices;
 class ResourceLoaderStartUpModule extends ResourceLoaderModule {
        protected $targets = [ 'desktop', 'mobile' ];
 
+       private $groupIds = [
+               // These reserved numbers MUST start at 0 and not skip any. These are preset
+               // for forward compatiblity so that they can be safely referenced by mediawiki.js,
+               // even when the code is cached and the order of registrations (and implicit
+               // group ids) changes between versions of the software.
+               'user' => 0,
+               'private' => 1,
+       ];
+
        /**
         * @param ResourceLoaderContext $context
         * @return array
@@ -304,7 +313,7 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
                        $registryData[$name] = [
                                'version' => $versionHash,
                                'dependencies' => $module->getDependencies( $context ),
-                               'group' => $module->getGroup(),
+                               'group' => $this->getGroupId( $module->getGroup() ),
                                'source' => $module->getSource(),
                                'skip' => $skipFunction,
                        ];
@@ -340,6 +349,18 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
                return $out;
        }
 
+       private function getGroupId( $groupName ) {
+               if ( $groupName === null ) {
+                       return null;
+               }
+
+               if ( !array_key_exists( $groupName, $this->groupIds ) ) {
+                       $this->groupIds[$groupName] = count( $this->groupIds );
+               }
+
+               return $this->groupIds[$groupName];
+       }
+
        /**
         * Base modules implicitly available to all modules.
         *
@@ -416,6 +437,8 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
                        ),
                        '$VARS.storeKey' => ResourceLoader::encodeJsonForScript( $this->getStoreKey() ),
                        '$VARS.storeVary' => ResourceLoader::encodeJsonForScript( $this->getStoreVary( $context ) ),
+                       '$VARS.groupUser' => ResourceLoader::encodeJsonForScript( $this->getGroupId( 'user' ) ),
+                       '$VARS.groupPrivate' => ResourceLoader::encodeJsonForScript( $this->getGroupId( 'private' ) ),
                ];
                $profilerStubs = [
                        '$CODE.profileExecuteStart();' => 'mw.loader.profiler.onExecuteStart( module );',
index bbad648..eeed05e 100644 (file)
@@ -238,7 +238,9 @@ abstract class Skin extends ContextSource {
 
                // Add various resources if required
                if ( $user->isLoggedIn()
-                       && $user->isAllowedAll( 'writeapi', 'viewmywatchlist', 'editmywatchlist' )
+                       && MediaWikiServices::getInstance()
+                                ->getPermissionManager()
+                                ->userHasAllRights( $user, 'writeapi', 'viewmywatchlist', 'editmywatchlist' )
                        && $this->getRelevantTitle()->canExist()
                ) {
                        $modules['watch'][] = 'mediawiki.page.watch.ajax';
@@ -306,6 +308,7 @@ abstract class Skin extends ContextSource {
        /**
         * Get the current revision ID
         *
+        * @deprecated since 1.34, use OutputPage::getRevisionId instead
         * @return int
         */
        public function getRevisionId() {
@@ -315,11 +318,11 @@ abstract class Skin extends ContextSource {
        /**
         * Whether the revision displayed is the latest revision of the page
         *
+        * @deprecated since 1.34, use OutputPage::isRevisionCurrent instead
         * @return bool
         */
        public function isRevisionCurrent() {
-               $revID = $this->getRevisionId();
-               return $revID == 0 || $revID == $this->getTitle()->getLatestRevID();
+               return $this->getOutput()->isRevisionCurrent();
        }
 
        /**
@@ -699,7 +702,7 @@ abstract class Skin extends ContextSource {
         * @return string HTML text with an URL
         */
        function printSource() {
-               $oldid = $this->getRevisionId();
+               $oldid = $this->getOutput()->getRevisionId();
                if ( $oldid ) {
                        $canonicalUrl = $this->getTitle()->getCanonicalURL( 'oldid=' . $oldid );
                        $url = htmlspecialchars( wfExpandIRI( $canonicalUrl ) );
@@ -733,11 +736,24 @@ abstract class Skin extends ContextSource {
                                        $msg = 'viewdeleted';
                                }
 
-                               return $this->msg( $msg )->rawParams(
+                               $subtitle = $this->msg( $msg )->rawParams(
                                        $linkRenderer->makeKnownLink(
                                                SpecialPage::getTitleFor( 'Undelete', $this->getTitle()->getPrefixedDBkey() ),
                                                $this->msg( 'restorelink' )->numParams( $n )->text() )
                                        )->escaped();
+
+                               // Allow extensions to add more links
+                               $links = [];
+                               Hooks::run( 'UndeletePageToolLinks', [ $this->getContext(), $linkRenderer, &$links ] );
+
+                               if ( $links ) {
+                                       $subtitle .= ''
+                                               . $this->msg( 'word-separator' )->escaped()
+                                               . $this->msg( 'parentheses' )
+                                                       ->rawParams( $this->getLanguage()->pipeList( $links ) )
+                                                       ->escaped();
+                               }
+                               return Html::rawElement( 'div', [ 'class' => 'mw-undelete-subtitle' ], $subtitle );
                        }
                }
 
@@ -828,7 +844,7 @@ abstract class Skin extends ContextSource {
        function getCopyright( $type = 'detect' ) {
                $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
                if ( $type == 'detect' ) {
-                       if ( !$this->isRevisionCurrent()
+                       if ( !$this->getOutput()->isRevisionCurrent()
                                && !$this->msg( 'history_copyright' )->inContentLanguage()->isDisabled()
                        ) {
                                $type = 'history';
@@ -932,7 +948,8 @@ abstract class Skin extends ContextSource {
 
                # No cached timestamp, load it from the database
                if ( $timestamp === null ) {
-                       $timestamp = Revision::getTimestampFromId( $this->getTitle(), $this->getRevisionId() );
+                       $timestamp = Revision::getTimestampFromId( $this->getTitle(),
+                               $this->getOutput()->getRevisionId() );
                }
 
                if ( $timestamp ) {
@@ -1086,8 +1103,8 @@ abstract class Skin extends ContextSource {
        function editUrlOptions() {
                $options = [ 'action' => 'edit' ];
 
-               if ( !$this->isRevisionCurrent() ) {
-                       $options['oldid'] = intval( $this->getRevisionId() );
+               if ( !$this->getOutput()->isRevisionCurrent() ) {
+                       $options['oldid'] = intval( $this->getOutput()->getRevisionId() );
                }
 
                return $options;
index af7ec29..f348135 100644 (file)
@@ -371,7 +371,7 @@ class SkinTemplate extends Skin {
                $tpl->set( 'credits', false );
                $tpl->set( 'numberofwatchingusers', false );
                if ( $title->exists() ) {
-                       if ( $out->isArticle() && $this->isRevisionCurrent() ) {
+                       if ( $out->isArticle() && $out->isRevisionCurrent() ) {
                                if ( $wgMaxCredits != 0 ) {
                                        /** @var CreditsAction $action */
                                        $action = Action::factory(
@@ -585,6 +585,7 @@ class SkinTemplate extends Skin {
                $request = $this->getRequest();
                $pageurl = $title->getLocalURL();
                $authManager = AuthManager::singleton();
+               $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
 
                /* set up the default links for the personal toolbar */
                $personal_urls = [];
@@ -704,7 +705,7 @@ class SkinTemplate extends Skin {
                        ];
 
                        // No need to show Talk and Contributions to anons if they can't contribute!
-                       if ( User::groupHasPermission( '*', 'edit' ) ) {
+                       if ( $permissionManager->groupHasPermission( '*', 'edit' ) ) {
                                // Because of caching, we can't link directly to the IP talk and
                                // contributions pages. Instead we use the special page shortcuts
                                // (which work correctly regardless of caching). This means we can't
@@ -732,7 +733,7 @@ class SkinTemplate extends Skin {
                        }
 
                        if ( $authManager->canAuthenticateNow() ) {
-                               $key = User::groupHasPermission( '*', 'read' )
+                               $key = $permissionManager->groupHasPermission( '*', 'read' )
                                        ? 'login'
                                        : 'login-private';
                                $personal_urls[$key] = $login_url;
@@ -974,7 +975,7 @@ class SkinTemplate extends Skin {
                                        // Whether to show the "Add a new section" tab
                                        // Checks if this is a current rev of talk page and is not forced to be hidden
                                        $showNewSection = !$out->forceHideNewSectionLink()
-                                               && ( ( $isTalk && $this->isRevisionCurrent() ) || $out->showNewSectionLink() );
+                                               && ( ( $isTalk && $out->isRevisionCurrent() ) || $out->showNewSectionLink() );
                                        $section = $request->getVal( 'section' );
 
                                        if ( $title->exists()
@@ -1068,8 +1069,8 @@ class SkinTemplate extends Skin {
                                }
 
                                if ( $title->quickUserCan( 'protect', $user ) && $title->getRestrictionTypes() &&
-                                       MediaWikiServices::getInstance()->getNamespaceInfo()->
-                                               getRestrictionLevels( $title->getNamespace(), $user ) !== [ '' ]
+                                       MediaWikiServices::getInstance()->getPermissionManager()
+                                               ->getNamespaceRestrictionLevels( $title->getNamespace(), $user ) !== [ '' ]
                                ) {
                                        $mode = $title->isProtected() ? 'unprotect' : 'protect';
                                        $content_navigation['actions'][$mode] = [
@@ -1081,7 +1082,10 @@ class SkinTemplate extends Skin {
                                }
 
                                // Checks if the user is logged in
-                               if ( $this->loggedin && $user->isAllowedAll( 'viewmywatchlist', 'editmywatchlist' ) ) {
+                               if ( $this->loggedin && MediaWikiServices::getInstance()
+                                               ->getPermissionManager()
+                                               ->userHasAllRights( $user, 'viewmywatchlist', 'editmywatchlist' )
+                               ) {
                                        /**
                                         * The following actions use messages which, if made particular to
                                         * the any specific skins, would break the Ajax code which makes this
@@ -1291,7 +1295,7 @@ class SkinTemplate extends Skin {
 
                if ( $out->isArticle() ) {
                        // Also add a "permalink" while we're at it
-                       $revid = $this->getRevisionId();
+                       $revid = $this->getOutput()->getRevisionId();
                        if ( $revid ) {
                                $nav_urls['permalink'] = [
                                        'text' => $this->msg( 'permalink' )->text(),
index 2fa8fab..3893e92 100644 (file)
@@ -766,6 +766,33 @@ abstract class ChangesListSpecialPage extends SpecialPage {
                }
        }
 
+       /**
+        * @see $wgRCLinkDays in DefaultSettings.php.
+        * @see $wgRCFilterByAge in DefaultSettings.php.
+        * @return int[]
+        */
+       protected function getLinkDays() {
+               $linkDays = $this->getConfig()->get( 'RCLinkDays' );
+               $filterByAge = $this->getConfig()->get( 'RCFilterByAge' );
+               $maxAge = $this->getConfig()->get( 'RCMaxAge' );
+               if ( $filterByAge ) {
+                       // Trim it to only links which are within $wgRCMaxAge.
+                       // Note that we allow one link higher than the max for things like
+                       // "age 56 days" being accessible through the "60 days" link.
+                       sort( $linkDays );
+
+                       $maxAgeDays = $maxAge / ( 3600 * 24 );
+                       foreach ( $linkDays as $i => $days ) {
+                               if ( $days >= $maxAgeDays ) {
+                                       array_splice( $linkDays, $i + 1 );
+                                       break;
+                               }
+                       }
+               }
+
+               return $linkDays;
+       }
+
        /**
         * Include the modules and configuration for the RCFilters app.
         * Conditional on the user having the feature enabled.
@@ -798,7 +825,7 @@ abstract class ChangesListSpecialPage extends SpecialPage {
                                        'maxDays' => (int)$this->getConfig()->get( 'RCMaxAge' ) / ( 24 * 3600 ), // Translate to days
                                        'limitArray' => $this->getConfig()->get( 'RCLinkLimits' ),
                                        'limitDefault' => $this->getDefaultLimit(),
-                                       'daysArray' => $this->getConfig()->get( 'RCLinkDays' ),
+                                       'daysArray' => $this->getLinkDays(),
                                        'daysDefault' => $this->getDefaultDays(),
                                ]
                        );
index 27cd2ab..0a1ce61 100644 (file)
@@ -116,7 +116,7 @@ abstract class RedirectSpecialArticle extends RedirectSpecialPage {
                // Avoid double redirect for action=edit&redlink=1 for existing pages
                // (compare to the check in EditPage::edit)
                if (
-                       $query &&
+                       $query && isset( $query['action'] ) && isset( $query['redlink'] ) &&
                        ( $query['action'] === 'edit' || $query['action'] === 'submit' ) &&
                        (bool)$query['redlink'] &&
                        $title instanceof Title &&
index d7e39d5..7d33035 100644 (file)
@@ -278,7 +278,9 @@ class SpecialPage implements MessageLocalizer {
         */
        public function isRestricted() {
                // DWIM: If anons can do something, then it is not restricted
-               return $this->mRestriction != '' && !User::groupHasPermission( '*', $this->mRestriction );
+               return $this->mRestriction != '' && !MediaWikiServices::getInstance()
+                               ->getPermissionManager()
+                               ->groupHasPermission( '*', $this->mRestriction );
        }
 
        /**
index 9b5dd3f..cc2fc80 100644 (file)
@@ -51,7 +51,9 @@ class SpecialCreateAccount extends LoginSignupSpecialPage {
        }
 
        public function isRestricted() {
-               return !User::groupHasPermission( '*', 'createaccount' );
+               return !MediaWikiServices::getInstance()
+                       ->getPermissionManager()
+                       ->groupHasPermission( '*', 'createaccount' );
        }
 
        public function userCanExecute( User $user ) {
index 6ef6cb3..70a1bd4 100644 (file)
@@ -261,8 +261,7 @@ class SpecialEditTags extends UnlistedSpecialPage {
                                                        // HTML maxlength uses "UTF-16 code units", which means that characters outside BMP
                                                        // (e.g. emojis) count for two each. This limit is overridden in JS to instead count
                                                        // Unicode codepoints.
-                                                       // "- 155" is to leave room for the auto-generated part of the log entry.
-                                                       'maxlength' => CommentStore::COMMENT_CHARACTER_LIMIT - 155,
+                                                       'maxlength' => CommentStore::COMMENT_CHARACTER_LIMIT,
                                                ] ) .
                                        '</td>' .
                                "</tr><tr>\n" .
index c3aec83..f21c206 100644 (file)
@@ -24,6 +24,7 @@
  * @ingroup SpecialPage
  */
 
+use MediaWiki\MediaWikiServices;
 use MediaWiki\Permissions\PermissionManager;
 
 /**
@@ -76,7 +77,10 @@ class SpecialImport extends SpecialPage {
                Hooks::run( 'ImportSources', [ &$this->importSources ] );
 
                $user = $this->getUser();
-               if ( !$user->isAllowedAny( 'import', 'importupload' ) ) {
+               if ( !MediaWikiServices::getInstance()
+                       ->getPermissionManager()
+                       ->userHasAnyRight( $user, 'import', 'importupload' )
+               ) {
                        throw new PermissionsError( 'import' );
                }
 
index 2f0c2ce..c6927c1 100644 (file)
@@ -21,6 +21,7 @@
  * @ingroup SpecialPage
  */
 
+use MediaWiki\MediaWikiServices;
 use Wikimedia\Timestamp\TimestampException;
 
 /**
@@ -264,7 +265,9 @@ class SpecialLog extends SpecialPage {
 
        private function getActionButtons( $formcontents ) {
                $user = $this->getUser();
-               $canRevDelete = $user->isAllowedAll( 'deletedhistory', 'deletelogentry' );
+               $canRevDelete = MediaWikiServices::getInstance()
+                       ->getPermissionManager()
+                       ->userHasAllRights( $user, 'deletedhistory', 'deletelogentry' );
                $showTagEditUI = ChangeTags::showTagEditingUI( $user );
                # If the user doesn't have the ability to delete log entries nor edit tags,
                # don't bother showing them the button(s).
index f3ae31a..77c0710 100644 (file)
@@ -99,14 +99,20 @@ class SpecialMute extends FormSpecialPage {
         * @return bool
         */
        public function onSubmit( array $data, HTMLForm $form = null ) {
+               $hookData = [];
                foreach ( $data as $userOption => $value ) {
+                       $hookData[$userOption]['before'] = $this->isTargetBlacklisted( $userOption );
                        if ( $value ) {
                                $this->muteTarget( $userOption );
                        } else {
                                $this->unmuteTarget( $userOption );
                        }
+                       $hookData[$userOption]['after'] = (bool)$value;
                }
 
+               // NOTE: this hook is temporary
+               Hooks::run( 'SpecialMuteSubmit', [ $hookData ] );
+
                return true;
        }
 
index c124c14..6bcca71 100644 (file)
@@ -24,7 +24,7 @@ class SpecialNewSection extends RedirectSpecialPage {
        public function __construct() {
                parent::__construct( 'NewSection' );
                $this->mAllowedRedirectParams = [ 'preloadtitle', 'nosummary', 'editintro',
-                       'preload', 'preloadparams[]', 'summary' ];
+                       'preload', 'preloadparams', 'summary' ];
        }
 
        /**
index 711d447..493f6db 100644 (file)
@@ -21,6 +21,8 @@
  * @ingroup SpecialPage
  */
 
+use MediaWiki\MediaWikiServices;
+
 /**
  * A special page that list newly created pages
  *
@@ -184,7 +186,9 @@ class SpecialNewpages extends IncludableSpecialPage {
                }
 
                // Disable some if needed
-               if ( !User::groupHasPermission( '*', 'createpage' ) ) {
+               if ( !MediaWikiServices::getInstance()->getPermissionManager()
+                               ->groupHasPermission( '*', 'createpage' )
+               ) {
                        unset( $filters['hideliu'] );
                }
                if ( !$this->getUser()->useNPPatrol() ) {
index 6949c61..30f4655 100644 (file)
@@ -847,7 +847,7 @@ class SpecialRecentChanges extends ChangesListSpecialPage {
                sort( $linkLimits );
                $linkLimits = array_unique( $linkLimits );
 
-               $linkDays = $config->get( 'RCLinkDays' );
+               $linkDays = $this->getLinkDays();
                $linkDays[] = $options['days'];
                sort( $linkDays );
                $linkDays = array_unique( $linkDays );
index 2443470..f5239b4 100644 (file)
@@ -382,7 +382,10 @@ class SpecialWatchlist extends ChangesListSpecialPage {
                // the necessary rights.
                if ( !$user->isAllowed( 'deletedhistory' ) ) {
                        $bitmask = LogPage::DELETED_ACTION;
-               } elseif ( !$user->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
+               } elseif ( !MediaWikiServices::getInstance()
+                       ->getPermissionManager()
+                       ->userHasAnyRight( $user, 'suppressrevision', 'viewsuppressed' )
+               ) {
                        $bitmask = LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED;
                } else {
                        $bitmask = 0;
index 5dae156..ea23973 100644 (file)
@@ -18,6 +18,8 @@
  * @file
  */
 
+use MediaWiki\MediaWikiServices;
+
 /**
  * Form to edit user preferences.
  *
@@ -71,7 +73,10 @@ class PreferencesFormOOUI extends OOUIHTMLForm {
         * @return string
         */
        function getButtons() {
-               if ( !$this->getModifiedUser()->isAllowedAny( 'editmyprivateinfo', 'editmyoptions' ) ) {
+               if ( !MediaWikiServices::getInstance()
+                       ->getPermissionManager()
+                       ->userHasAnyRight( $this->getModifiedUser(), 'editmyprivateinfo', 'editmyoptions' )
+               ) {
                        return '';
                }
 
index 76e2ab7..45d77ce 100644 (file)
@@ -235,7 +235,7 @@ class AllMessagesTablePager extends TablePager {
        }
 
        function formatValue( $field, $value ) {
-               $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
+               $linkRenderer = $this->getLinkRenderer();
                switch ( $field ) {
                        case 'am_title' :
                                $title = Title::makeTitle( NS_MEDIAWIKI, $value . $this->suffix );
@@ -256,8 +256,7 @@ class AllMessagesTablePager extends TablePager {
                                        $title = $linkRenderer->makeKnownLink( $title, $this->getLanguage()->lcfirst( $value ) );
                                } else {
                                        $title = $linkRenderer->makeBrokenLink(
-                                               $title,
-                                               $this->getLanguage()->lcfirst( $value )
+                                               $title, $this->getLanguage()->lcfirst( $value )
                                        );
                                }
                                if ( $this->mCurrentRow->am_talk_exists ) {
index 01aed22..77b7326 100644 (file)
@@ -45,9 +45,9 @@ class BlockListPager extends TablePager {
         * @param array $conds
         */
        public function __construct( $page, $conds ) {
+               parent::__construct( $page->getContext(), $page->getLinkRenderer() );
                $this->conds = $conds;
                $this->mDefaultDirection = IndexPager::DIR_DESCENDING;
-               parent::__construct( $page->getContext() );
        }
 
        function getFieldNames() {
@@ -97,7 +97,7 @@ class BlockListPager extends TablePager {
 
                $formatted = '';
 
-               $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
+               $linkRenderer = $this->getLinkRenderer();
 
                switch ( $name ) {
                        case 'ipb_timestamp':
@@ -250,7 +250,7 @@ class BlockListPager extends TablePager {
         */
        private function getRestrictionListHTML( stdClass $row ) {
                $items = [];
-               $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
+               $linkRenderer = $this->getLinkRenderer();
 
                foreach ( $this->restrictions as $restriction ) {
                        if ( $restriction->getBlockId() !== (int)$row->ipb_id ) {
index 7db90c1..db2ee38 100644 (file)
@@ -25,11 +25,6 @@ use MediaWiki\Linker\LinkRenderer;
  */
 class CategoryPager extends AlphabeticPager {
 
-       /**
-        * @var LinkRenderer
-        */
-       protected $linkRenderer;
-
        /**
         * @param IContextSource $context
         * @param string $from
@@ -37,15 +32,13 @@ class CategoryPager extends AlphabeticPager {
         */
        public function __construct( IContextSource $context, $from, LinkRenderer $linkRenderer
        ) {
-               parent::__construct( $context );
+               parent::__construct( $context, $linkRenderer );
                $from = str_replace( ' ', '_', $from );
                if ( $from !== '' ) {
                        $from = Title::capitalize( $from, NS_CATEGORY );
                        $this->setOffset( $from );
                        $this->setIncludeOffset( true );
                }
-
-               $this->linkRenderer = $linkRenderer;
        }
 
        function getQueryInfo() {
@@ -85,7 +78,7 @@ class CategoryPager extends AlphabeticPager {
        function formatRow( $result ) {
                $title = new TitleValue( NS_CATEGORY, $result->cat_title );
                $text = $title->getText();
-               $link = $this->linkRenderer->makeLink( $title, $text );
+               $link = $this->getLinkRenderer()->makeLink( $title, $text );
 
                $count = $this->msg( 'nmembers' )->numParams( $result->cat_pages )->escaped();
                return Html::rawElement( 'li', null, $this->getLanguage()->specialList( $link, $count ) ) . "\n";
index 1cb78b8..1b0c59a 100644 (file)
@@ -285,7 +285,9 @@ class ContribsPager extends RangeChronologicalPager {
                        $queryInfo['conds'][] = $revQuery['fields']['rev_user'] . ' >' . (int)( $max - $max / 100 );
                        # ignore local groups with the bot right
                        # @todo FIXME: Global groups may have 'bot' rights
-                       $groupsWithBotPermission = User::getGroupsWithPermission( 'bot' );
+                       $groupsWithBotPermission = MediaWikiServices::getInstance()
+                               ->getPermissionManager()
+                               ->getGroupsWithPermission( 'bot' );
                        if ( count( $groupsWithBotPermission ) ) {
                                $queryInfo['tables'][] = 'user_groups';
                                $queryInfo['conds'][] = 'ug_group IS NULL';
@@ -351,7 +353,10 @@ class ContribsPager extends RangeChronologicalPager {
                        $queryInfo['conds'][] = $this->mDb->bitAnd(
                                'rev_deleted', RevisionRecord::DELETED_USER
                                ) . ' = 0';
-               } elseif ( !$user->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
+               } elseif ( !MediaWikiServices::getInstance()
+                       ->getPermissionManager()
+                       ->userHasAnyRight( $user, 'suppressrevision', 'viewsuppressed' )
+               ) {
                        $queryInfo['conds'][] = $this->mDb->bitAnd(
                                'rev_deleted', RevisionRecord::SUPPRESSED_USER
                                ) . ' != ' . RevisionRecord::SUPPRESSED_USER;
@@ -616,7 +621,7 @@ class ContribsPager extends RangeChronologicalPager {
                $classes = [];
                $attribs = [];
 
-               $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
+               $linkRenderer = $this->getLinkRenderer();
 
                $page = null;
                // Create a title for the revision if possible
index 88e1ea8..7dbfae8 100644 (file)
@@ -90,7 +90,10 @@ class DeletedContribsPager extends IndexPager {
                // Paranoia: avoid brute force searches (T19792)
                if ( !$user->isAllowed( 'deletedhistory' ) ) {
                        $conds[] = $this->mDb->bitAnd( 'ar_deleted', RevisionRecord::DELETED_USER ) . ' = 0';
-               } elseif ( !$user->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
+               } elseif ( !MediaWikiServices::getInstance()
+                       ->getPermissionManager()
+                       ->userHasAnyRight( $user, 'suppressrevision', 'viewsuppressed' )
+               ) {
                        $conds[] = $this->mDb->bitAnd( 'ar_deleted', RevisionRecord::SUPPRESSED_USER ) .
                                ' != ' . RevisionRecord::SUPPRESSED_USER;
                }
@@ -286,7 +289,7 @@ class DeletedContribsPager extends IndexPager {
        function formatRevisionRow( $row ) {
                $page = Title::makeTitle( $row->ar_namespace, $row->ar_title );
 
-               $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
+               $linkRenderer = $this->getLinkRenderer();
 
                $rev = new Revision( [
                        'title' => $page,
index 2d3b6b2..6bdccd8 100644 (file)
@@ -53,7 +53,8 @@ class ImageListPager extends TablePager {
        public function __construct( IContextSource $context, $userName = null, $search = '',
                $including = false, $showAll = false
        ) {
-               $this->setContext( $context );
+               parent::__construct( $context );
+
                $this->mIncluding = $including;
                $this->mShowAll = $showAll;
 
@@ -94,8 +95,6 @@ class ImageListPager extends TablePager {
                } else {
                        $this->mDefaultDirection = IndexPager::DIR_DESCENDING;
                }
-
-               parent::__construct( $context );
        }
 
        /**
@@ -437,7 +436,7 @@ class ImageListPager extends TablePager {
         */
        function formatValue( $field, $value ) {
                $services = MediaWikiServices::getInstance();
-               $linkRenderer = $services->getLinkRenderer();
+               $linkRenderer = $this->getLinkRenderer();
                switch ( $field ) {
                        case 'thumb':
                                $opt = [ 'time' => wfTimestamp( TS_MW, $this->mCurrentRow->img_timestamp ) ];
@@ -478,7 +477,7 @@ class ImageListPager extends TablePager {
 
                                        // Add delete links if allowed
                                        // From https://github.com/Wikia/app/pull/3859
-                                       $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
+                                       $permissionManager = $services->getPermissionManager();
 
                                        if ( $permissionManager->userCan( 'delete', $this->getUser(), $filePage ) ) {
                                                $deleteMsg = $this->msg( 'listfiles-delete' )->text();
index 88dff6e..57db8b3 100644 (file)
@@ -87,7 +87,9 @@ class NewFilesPager extends RangeChronologicalPager {
                }
 
                if ( !$opts->getValue( 'showbots' ) ) {
-                       $groupsWithBotPermission = User::getGroupsWithPermission( 'bot' );
+                       $groupsWithBotPermission = MediaWikiServices::getInstance()
+                               ->getPermissionManager()
+                               ->getGroupsWithPermission( 'bot' );
 
                        if ( count( $groupsWithBotPermission ) ) {
                                $dbr = wfGetDB( DB_REPLICA );
@@ -192,7 +194,7 @@ class NewFilesPager extends RangeChronologicalPager {
                $user = User::newFromId( $row->img_user );
 
                $title = Title::makeTitle( NS_FILE, $name );
-               $ul = MediaWikiServices::getInstance()->getLinkRenderer()->makeLink(
+               $ul = $this->getLinkRenderer()->makeLink(
                        $user->getUserPage(),
                        $user->getName()
                );
index 8131671..c50563d 100644 (file)
@@ -68,7 +68,9 @@ class NewPagesPager extends ReverseChronologicalPager {
                        $conds[] = ActorMigration::newMigration()->getWhere(
                                $this->mDb, 'rc_user', User::newFromName( $user->getText(), false ), false
                        )['conds'];
-               } elseif ( User::groupHasPermission( '*', 'createpage' ) &&
+               } elseif ( MediaWikiServices::getInstance()
+                                       ->getPermissionManager()
+                                       ->groupHasPermission( '*', 'createpage' ) &&
                        $this->opts->getValue( 'hideliu' )
                ) {
                        # If anons cannot make new pages, don't "exclude logged in users"!
index 5583842..747dea2 100644 (file)
@@ -26,11 +26,6 @@ class ProtectedPagesPager extends TablePager {
        public $mConds;
        private $type, $level, $namespace, $sizetype, $size, $indefonly, $cascadeonly, $noredirect;
 
-       /**
-        * @var LinkRenderer
-        */
-       private $linkRenderer;
-
        /**
         * @param SpecialPage $form
         * @param array $conds
@@ -48,6 +43,7 @@ class ProtectedPagesPager extends TablePager {
                $sizetype, $size, $indefonly, $cascadeonly, $noredirect,
                LinkRenderer $linkRenderer
        ) {
+               parent::__construct( $form->getContext(), $linkRenderer );
                $this->mConds = $conds;
                $this->type = $type ?: 'edit';
                $this->level = $level;
@@ -57,8 +53,6 @@ class ProtectedPagesPager extends TablePager {
                $this->indefonly = (bool)$indefonly;
                $this->cascadeonly = (bool)$cascadeonly;
                $this->noredirect = (bool)$noredirect;
-               $this->linkRenderer = $linkRenderer;
-               parent::__construct( $form->getContext() );
        }
 
        function preprocessResults( $result ) {
@@ -119,6 +113,7 @@ class ProtectedPagesPager extends TablePager {
        function formatValue( $field, $value ) {
                /** @var object $row */
                $row = $this->mCurrentRow;
+               $linkRenderer = $this->getLinkRenderer();
 
                switch ( $field ) {
                        case 'log_timestamp':
@@ -148,7 +143,7 @@ class ProtectedPagesPager extends TablePager {
                                                )
                                        );
                                } else {
-                                       $formatted = $this->linkRenderer->makeLink( $title );
+                                       $formatted = $linkRenderer->makeLink( $title );
                                }
                                if ( !is_null( $row->page_len ) ) {
                                        $formatted .= $this->getLanguage()->getDirMark() .
@@ -165,7 +160,7 @@ class ProtectedPagesPager extends TablePager {
                                        $value, /* User preference timezone */true ) );
                                $title = Title::makeTitleSafe( $row->page_namespace, $row->page_title );
                                if ( $this->getUser()->isAllowed( 'protect' ) && $title ) {
-                                       $changeProtection = $this->linkRenderer->makeKnownLink(
+                                       $changeProtection = $linkRenderer->makeKnownLink(
                                                $title,
                                                $this->msg( 'protect_change' )->text(),
                                                [],
index 57b575b..e27fb58 100644 (file)
@@ -44,10 +44,6 @@ class UsersPager extends AlphabeticPager {
         * another page
         */
        public function __construct( IContextSource $context = null, $par = null, $including = null ) {
-               if ( $context ) {
-                       $this->setContext( $context );
-               }
-
                $request = $this->getRequest();
                $par = $par ?? '';
                $parms = explode( '/', $par );
@@ -87,7 +83,7 @@ class UsersPager extends AlphabeticPager {
                        }
                }
 
-               parent::__construct();
+               parent::__construct( $context );
        }
 
        /**
index 7307cc1..105eeaa 100644 (file)
@@ -22,6 +22,7 @@
 
 use MediaWiki\Config\ServiceOptions;
 use MediaWiki\Linker\LinkTarget;
+use MediaWiki\MediaWikiServices;
 
 /**
  * This is a utility class for dealing with namespaces that encodes all the "magic" behaviors of
@@ -93,10 +94,8 @@ class NamespaceInfo {
                'ExtraNamespaces',
                'ExtraSignatureNamespaces',
                'NamespaceContentModels',
-               'NamespaceProtection',
                'NamespacesWithSubpages',
                'NonincludableNamespaces',
-               'RestrictionLevels',
        ];
 
        /**
@@ -572,82 +571,18 @@ class NamespaceInfo {
         * Determine which restriction levels it makes sense to use in a namespace,
         * optionally filtered by a user's rights.
         *
-        * @todo Move this to PermissionManager and remove the dependency here on permissions-related
-        * config settings.
-        *
+        * @deprecated since 1.34 User PermissionManager::getNamespaceRestrictionLevels instead.
         * @param int $index Index to check
         * @param User|null $user User to check
         * @return array
         */
        public function getRestrictionLevels( $index, User $user = null ) {
-               if ( !isset( $this->options->get( 'NamespaceProtection' )[$index] ) ) {
-                       // All levels are valid if there's no namespace restriction.
-                       // But still filter by user, if necessary
-                       $levels = $this->options->get( 'RestrictionLevels' );
-                       if ( $user ) {
-                               $levels = array_values( array_filter( $levels, function ( $level ) use ( $user ) {
-                                       $right = $level;
-                                       if ( $right == 'sysop' ) {
-                                               $right = 'editprotected'; // BC
-                                       }
-                                       if ( $right == 'autoconfirmed' ) {
-                                               $right = 'editsemiprotected'; // BC
-                                       }
-                                       return ( $right == '' || $user->isAllowed( $right ) );
-                               } ) );
-                       }
-                       return $levels;
-               }
-
-               // $wgNamespaceProtection can require one or more rights to edit the namespace, which
-               // may be satisfied by membership in multiple groups each giving a subset of those rights.
-               // A restriction level is redundant if, for any one of the namespace rights, all groups
-               // giving that right also give the restriction level's right. Or, conversely, a
-               // restriction level is not redundant if, for every namespace right, there's at least one
-               // group giving that right without the restriction level's right.
-               //
-               // First, for each right, get a list of groups with that right.
-               $namespaceRightGroups = [];
-               foreach ( (array)$this->options->get( 'NamespaceProtection' )[$index] as $right ) {
-                       if ( $right == 'sysop' ) {
-                               $right = 'editprotected'; // BC
-                       }
-                       if ( $right == 'autoconfirmed' ) {
-                               $right = 'editsemiprotected'; // BC
-                       }
-                       if ( $right != '' ) {
-                               $namespaceRightGroups[$right] = User::getGroupsWithPermission( $right );
-                       }
-               }
-
-               // Now, go through the protection levels one by one.
-               $usableLevels = [ '' ];
-               foreach ( $this->options->get( 'RestrictionLevels' ) as $level ) {
-                       $right = $level;
-                       if ( $right == 'sysop' ) {
-                               $right = 'editprotected'; // BC
-                       }
-                       if ( $right == 'autoconfirmed' ) {
-                               $right = 'editsemiprotected'; // BC
-                       }
-
-                       if ( $right != '' &&
-                               !isset( $namespaceRightGroups[$right] ) &&
-                               ( !$user || $user->isAllowed( $right ) )
-                       ) {
-                               // Do any of the namespace rights imply the restriction right? (see explanation above)
-                               foreach ( $namespaceRightGroups as $groups ) {
-                                       if ( !array_diff( $groups, User::getGroupsWithPermission( $right ) ) ) {
-                                               // Yes, this one does.
-                                               continue 2;
-                                       }
-                               }
-                               // No, keep the restriction level
-                               $usableLevels[] = $level;
-                       }
-               }
-
-               return $usableLevels;
+               // PermissionManager is not injected because adding an explicit dependency
+               // breaks MW installer by adding a dependency chain on the database before
+               // it was set up. Also, the method is deprecated and will be soon removed.
+               return MediaWikiServices::getInstance()
+                       ->getPermissionManager()
+                       ->getNamespaceRestrictionLevels( $index, $user );
        }
 
        /**
index cc527e7..8c6b2f9 100644 (file)
@@ -1,4 +1,7 @@
 <?php
+
+use MediaWiki\MediaWikiServices;
+
 /**
  * Backend for uploading files from chunks.
  *
@@ -157,7 +160,8 @@ class UploadFromChunks extends UploadFromFile {
                // Get the file extension from the last chunk
                $ext = FileBackend::extensionFromPath( $this->mVirtualTempPath );
                // Get a 0-byte temp file to perform the concatenation at
-               $tmpFile = TempFSFile::factory( 'chunkedupload_', $ext, wfTempDir() );
+               $tmpFile = MediaWikiServices::getInstance()->getTempFSFileFactory()
+                       ->getTempFSFile( 'chunkedupload_', $ext );
                $tmpPath = false; // fail in concatenate()
                if ( $tmpFile ) {
                        // keep alive with $this
index b071774..b92fcc5 100644 (file)
@@ -1,4 +1,7 @@
 <?php
+
+use MediaWiki\MediaWikiServices;
+
 /**
  * Backend for uploading files from a HTTP resource.
  *
@@ -201,7 +204,8 @@ class UploadFromUrl extends UploadBase {
         * @return string Path to the file
         */
        protected function makeTemporaryFile() {
-               $tmpFile = TempFSFile::factory( 'URL', 'urlupload_', wfTempDir() );
+               $tmpFile = MediaWikiServices::getInstance()->getTempFSFileFactory()
+                       ->newTempFSFile( 'URL', 'urlupload_' );
                $tmpFile->bind( $this );
 
                return $tmpFile->getPath();
index 7c2f038..0553c92 100644 (file)
@@ -2042,14 +2042,10 @@ class User implements IDBAccessObject, UserIdentity {
                        $summary = "(limit $max in {$period}s)";
                        $count = $cache->get( $key );
                        // Already pinged?
-                       if ( $count ) {
-                               if ( $count >= $max ) {
-                                       wfDebugLog( 'ratelimit', "User '{$this->getName()}' " .
-                                               "(IP {$this->getRequest()->getIP()}) tripped $key at $count $summary" );
-                                       $triggered = true;
-                               } else {
-                                       wfDebug( __METHOD__ . ": ok. $key at $count $summary\n" );
-                               }
+                       if ( $count && $count >= $max ) {
+                               wfDebugLog( 'ratelimit', "User '{$this->getName()}' " .
+                                       "(IP {$this->getRequest()->getIP()}) tripped $key at $count $summary" );
+                               $triggered = true;
                        } else {
                                wfDebug( __METHOD__ . ": adding record for $key $summary\n" );
                                if ( $incrBy > 0 ) {
@@ -2057,7 +2053,7 @@ class User implements IDBAccessObject, UserIdentity {
                                }
                        }
                        if ( $incrBy > 0 ) {
-                               $cache->incr( $key, $incrBy );
+                               $cache->incrWithInit( $key, (int)$period, $incrBy, $incrBy );
                        }
                }
 
@@ -3601,32 +3597,28 @@ class User implements IDBAccessObject, UserIdentity {
        /**
         * Check if user is allowed to access a feature / make an action
         *
+        * @deprecated since 1.34, use MediaWikiServices::getInstance()
+        * ->getPermissionManager()->userHasAnyRights(...) instead
+        *
         * @param string $permissions,... Permissions to test
         * @return bool True if user is allowed to perform *any* of the given actions
         */
        public function isAllowedAny() {
-               $permissions = func_get_args();
-               foreach ( $permissions as $permission ) {
-                       if ( $this->isAllowed( $permission ) ) {
-                               return true;
-                       }
-               }
-               return false;
+               return MediaWikiServices::getInstance()
+                       ->getPermissionManager()
+                       ->userHasAnyRight( $this, ...func_get_args() );
        }
 
        /**
-        *
+        * @deprecated since 1.34, use MediaWikiServices::getInstance()
+        * ->getPermissionManager()->userHasAllRights(...) instead
         * @param string $permissions,... Permissions to test
         * @return bool True if the user is allowed to perform *all* of the given actions
         */
        public function isAllowedAll() {
-               $permissions = func_get_args();
-               foreach ( $permissions as $permission ) {
-                       if ( !$this->isAllowed( $permission ) ) {
-                               return false;
-                       }
-               }
-               return true;
+               return MediaWikiServices::getInstance()
+                       ->getPermissionManager()
+                       ->userHasAllRights( $this, ...func_get_args() );
        }
 
        /**
@@ -5351,7 +5343,9 @@ class User implements IDBAccessObject, UserIdentity {
                global $wgLang;
 
                $groups = [];
-               foreach ( self::getGroupsWithPermission( $permission ) as $group ) {
+               foreach ( MediaWikiServices::getInstance()
+                                         ->getPermissionManager()
+                                         ->getGroupsWithPermission( $permission ) as $group ) {
                        $groups[] = UserGroupMembership::getLink( $group, RequestContext::getMain(), 'wiki' );
                }
 
diff --git a/languages/ConverterRule.php b/languages/ConverterRule.php
deleted file mode 100644 (file)
index 4a330ad..0000000
+++ /dev/null
@@ -1,498 +0,0 @@
-<?php
-/**
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Language
- */
-
-/**
- * Parser for rules of language conversion, parse rules in -{ }- tag.
- * @ingroup Language
- * @author fdcn <fdcn64@gmail.com>, PhiLiP <philip.npc@gmail.com>
- */
-class ConverterRule {
-       public $mText; // original text in -{text}-
-       public $mConverter; // LanguageConverter object
-       public $mRuleDisplay = '';
-       public $mRuleTitle = false;
-       public $mRules = ''; // string : the text of the rules
-       public $mRulesAction = 'none';
-       public $mFlags = [];
-       public $mVariantFlags = [];
-       public $mConvTable = [];
-       public $mBidtable = []; // array of the translation in each variant
-       public $mUnidtable = []; // array of the translation in each variant
-
-       /**
-        * @param string $text The text between -{ and }-
-        * @param LanguageConverter $converter
-        */
-       public function __construct( $text, $converter ) {
-               $this->mText = $text;
-               $this->mConverter = $converter;
-       }
-
-       /**
-        * Check if variants array in convert array.
-        *
-        * @param array|string $variants Variant language code
-        * @return string Translated text
-        */
-       public function getTextInBidtable( $variants ) {
-               $variants = (array)$variants;
-               if ( !$variants ) {
-                       return false;
-               }
-               foreach ( $variants as $variant ) {
-                       if ( isset( $this->mBidtable[$variant] ) ) {
-                               return $this->mBidtable[$variant];
-                       }
-               }
-               return false;
-       }
-
-       /**
-        * Parse flags with syntax -{FLAG| ... }-
-        * @private
-        */
-       function parseFlags() {
-               $text = $this->mText;
-               $flags = [];
-               $variantFlags = [];
-
-               $sepPos = strpos( $text, '|' );
-               if ( $sepPos !== false ) {
-                       $validFlags = $this->mConverter->mFlags;
-                       $f = StringUtils::explode( ';', substr( $text, 0, $sepPos ) );
-                       foreach ( $f as $ff ) {
-                               $ff = trim( $ff );
-                               if ( isset( $validFlags[$ff] ) ) {
-                                       $flags[$validFlags[$ff]] = true;
-                               }
-                       }
-                       $text = strval( substr( $text, $sepPos + 1 ) );
-               }
-
-               if ( !$flags ) {
-                       $flags['S'] = true;
-               } elseif ( isset( $flags['R'] ) ) {
-                       $flags = [ 'R' => true ];// remove other flags
-               } elseif ( isset( $flags['N'] ) ) {
-                       $flags = [ 'N' => true ];// remove other flags
-               } elseif ( isset( $flags['-'] ) ) {
-                       $flags = [ '-' => true ];// remove other flags
-               } elseif ( count( $flags ) == 1 && isset( $flags['T'] ) ) {
-                       $flags['H'] = true;
-               } elseif ( isset( $flags['H'] ) ) {
-                       // replace A flag, and remove other flags except T
-                       $temp = [ '+' => true, 'H' => true ];
-                       if ( isset( $flags['T'] ) ) {
-                               $temp['T'] = true;
-                       }
-                       if ( isset( $flags['D'] ) ) {
-                               $temp['D'] = true;
-                       }
-                       $flags = $temp;
-               } else {
-                       if ( isset( $flags['A'] ) ) {
-                               $flags['+'] = true;
-                               $flags['S'] = true;
-                       }
-                       if ( isset( $flags['D'] ) ) {
-                               unset( $flags['S'] );
-                       }
-                       // try to find flags like "zh-hans", "zh-hant"
-                       // allow syntaxes like "-{zh-hans;zh-hant|XXXX}-"
-                       $variantFlags = array_intersect( array_keys( $flags ), $this->mConverter->mVariants );
-                       if ( $variantFlags ) {
-                               $variantFlags = array_flip( $variantFlags );
-                               $flags = [];
-                       }
-               }
-               $this->mVariantFlags = $variantFlags;
-               $this->mRules = $text;
-               $this->mFlags = $flags;
-       }
-
-       /**
-        * Generate conversion table.
-        * @private
-        */
-       function parseRules() {
-               $rules = $this->mRules;
-               $bidtable = [];
-               $unidtable = [];
-               $variants = $this->mConverter->mVariants;
-               $varsep_pattern = $this->mConverter->getVarSeparatorPattern();
-
-               // Split according to $varsep_pattern, but ignore semicolons from HTML entities
-               $rules = preg_replace( '/(&[#a-zA-Z0-9]+);/', "$1\x01", $rules );
-               $choice = preg_split( $varsep_pattern, $rules );
-               $choice = str_replace( "\x01", ';', $choice );
-
-               foreach ( $choice as $c ) {
-                       $v = explode( ':', $c, 2 );
-                       if ( count( $v ) != 2 ) {
-                               // syntax error, skip
-                               continue;
-                       }
-                       $to = trim( $v[1] );
-                       $v = trim( $v[0] );
-                       $u = explode( '=>', $v, 2 );
-                       $vv = $this->mConverter->validateVariant( $v );
-                       // if $to is empty (which is also used as $from in bidtable),
-                       // strtr() could return a wrong result.
-                       if ( count( $u ) == 1 && $to !== '' && $vv ) {
-                               $bidtable[$vv] = $to;
-                       } elseif ( count( $u ) == 2 ) {
-                               $from = trim( $u[0] );
-                               $v = trim( $u[1] );
-                               $vv = $this->mConverter->validateVariant( $v );
-                               // if $from is empty, strtr() could return a wrong result.
-                               if ( array_key_exists( $vv, $unidtable )
-                                       && !is_array( $unidtable[$vv] )
-                                       && $from !== ''
-                                       && $vv ) {
-                                       $unidtable[$vv] = [ $from => $to ];
-                               } elseif ( $from !== '' && $vv ) {
-                                       $unidtable[$vv][$from] = $to;
-                               }
-                       }
-                       // syntax error, pass
-                       if ( !isset( $this->mConverter->mVariantNames[$vv] ) ) {
-                               $bidtable = [];
-                               $unidtable = [];
-                               break;
-                       }
-               }
-               $this->mBidtable = $bidtable;
-               $this->mUnidtable = $unidtable;
-       }
-
-       /**
-        * @private
-        *
-        * @return string
-        */
-       function getRulesDesc() {
-               $codesep = $this->mConverter->mDescCodeSep;
-               $varsep = $this->mConverter->mDescVarSep;
-               $text = '';
-               foreach ( $this->mBidtable as $k => $v ) {
-                       $text .= $this->mConverter->mVariantNames[$k] . "$codesep$v$varsep";
-               }
-               foreach ( $this->mUnidtable as $k => $a ) {
-                       foreach ( $a as $from => $to ) {
-                               $text .= $from . '⇒' . $this->mConverter->mVariantNames[$k] .
-                                       "$codesep$to$varsep";
-                       }
-               }
-               return $text;
-       }
-
-       /**
-        * Parse rules conversion.
-        * @private
-        *
-        * @param string $variant
-        *
-        * @return string
-        */
-       function getRuleConvertedStr( $variant ) {
-               $bidtable = $this->mBidtable;
-               $unidtable = $this->mUnidtable;
-
-               if ( count( $bidtable ) + count( $unidtable ) == 0 ) {
-                       return $this->mRules;
-               } else {
-                       // display current variant in bidirectional array
-                       $disp = $this->getTextInBidtable( $variant );
-                       // or display current variant in fallbacks
-                       if ( $disp === false ) {
-                               $disp = $this->getTextInBidtable(
-                                       $this->mConverter->getVariantFallbacks( $variant ) );
-                       }
-                       // or display current variant in unidirectional array
-                       if ( $disp === false && array_key_exists( $variant, $unidtable ) ) {
-                               $disp = array_values( $unidtable[$variant] )[0];
-                       }
-                       // or display first text under disable manual convert
-                       if ( $disp === false && $this->mConverter->mManualLevel[$variant] == 'disable' ) {
-                               if ( count( $bidtable ) > 0 ) {
-                                       $disp = array_values( $bidtable )[0];
-                               } else {
-                                       $disp = array_values( array_values( $unidtable )[0] )[0];
-                               }
-                       }
-                       return $disp;
-               }
-       }
-
-       /**
-        * Similar to getRuleConvertedStr(), but this prefers to use original
-        * page title if $variant === $this->mConverter->mMainLanguageCode
-        * and may return false in this case (so this title conversion rule
-        * will be ignored and the original title is shown).
-        *
-        * @since 1.22
-        * @param string $variant The variant code to display page title in
-        * @return string|bool The converted title or false if just page name
-        */
-       function getRuleConvertedTitle( $variant ) {
-               if ( $variant === $this->mConverter->mMainLanguageCode ) {
-                       // If a string targeting exactly this variant is set,
-                       // use it. Otherwise, just return false, so the real
-                       // page name can be shown (and because variant === main,
-                       // there'll be no further automatic conversion).
-                       $disp = $this->getTextInBidtable( $variant );
-                       if ( $disp ) {
-                               return $disp;
-                       }
-                       if ( array_key_exists( $variant, $this->mUnidtable ) ) {
-                               $disp = array_values( $this->mUnidtable[$variant] )[0];
-                       }
-                       // Assigned above or still false.
-                       return $disp;
-               } else {
-                       return $this->getRuleConvertedStr( $variant );
-               }
-       }
-
-       /**
-        * Generate conversion table for all text.
-        * @private
-        */
-       function generateConvTable() {
-               // Special case optimisation
-               if ( !$this->mBidtable && !$this->mUnidtable ) {
-                       $this->mConvTable = [];
-                       return;
-               }
-
-               $bidtable = $this->mBidtable;
-               $unidtable = $this->mUnidtable;
-               $manLevel = $this->mConverter->mManualLevel;
-
-               $vmarked = [];
-               foreach ( $this->mConverter->mVariants as $v ) {
-                       /* for bidirectional array
-                               fill in the missing variants, if any,
-                               with fallbacks */
-                       if ( !isset( $bidtable[$v] ) ) {
-                               $variantFallbacks =
-                                       $this->mConverter->getVariantFallbacks( $v );
-                               $vf = $this->getTextInBidtable( $variantFallbacks );
-                               if ( $vf ) {
-                                       $bidtable[$v] = $vf;
-                               }
-                       }
-
-                       if ( isset( $bidtable[$v] ) ) {
-                               foreach ( $vmarked as $vo ) {
-                                       // use syntax: -{A|zh:WordZh;zh-tw:WordTw}-
-                                       // or -{H|zh:WordZh;zh-tw:WordTw}-
-                                       // or -{-|zh:WordZh;zh-tw:WordTw}-
-                                       // to introduce a custom mapping between
-                                       // words WordZh and WordTw in the whole text
-                                       if ( $manLevel[$v] == 'bidirectional' ) {
-                                               $this->mConvTable[$v][$bidtable[$vo]] = $bidtable[$v];
-                                       }
-                                       if ( $manLevel[$vo] == 'bidirectional' ) {
-                                               $this->mConvTable[$vo][$bidtable[$v]] = $bidtable[$vo];
-                                       }
-                               }
-                               $vmarked[] = $v;
-                       }
-                       /* for unidirectional array fill to convert tables */
-                       if ( ( $manLevel[$v] == 'bidirectional' || $manLevel[$v] == 'unidirectional' )
-                               && isset( $unidtable[$v] )
-                       ) {
-                               if ( isset( $this->mConvTable[$v] ) ) {
-                                       $this->mConvTable[$v] = $unidtable[$v] + $this->mConvTable[$v];
-                               } else {
-                                       $this->mConvTable[$v] = $unidtable[$v];
-                               }
-                       }
-               }
-       }
-
-       /**
-        * Parse rules and flags.
-        * @param string|null $variant Variant language code
-        */
-       public function parse( $variant = null ) {
-               if ( !$variant ) {
-                       $variant = $this->mConverter->getPreferredVariant();
-               }
-
-               $this->parseFlags();
-               $flags = $this->mFlags;
-
-               // convert to specified variant
-               // syntax: -{zh-hans;zh-hant[;...]|<text to convert>}-
-               if ( $this->mVariantFlags ) {
-                       // check if current variant in flags
-                       if ( isset( $this->mVariantFlags[$variant] ) ) {
-                               // then convert <text to convert> to current language
-                               $this->mRules = $this->mConverter->autoConvert( $this->mRules,
-                                       $variant );
-                       } else {
-                               // if current variant no in flags,
-                               // then we check its fallback variants.
-                               $variantFallbacks =
-                                       $this->mConverter->getVariantFallbacks( $variant );
-                               if ( is_array( $variantFallbacks ) ) {
-                                       foreach ( $variantFallbacks as $variantFallback ) {
-                                               // if current variant's fallback exist in flags
-                                               if ( isset( $this->mVariantFlags[$variantFallback] ) ) {
-                                                       // then convert <text to convert> to fallback language
-                                                       $this->mRules =
-                                                               $this->mConverter->autoConvert( $this->mRules,
-                                                                       $variantFallback );
-                                                       break;
-                                               }
-                                       }
-                               }
-                       }
-                       $this->mFlags = $flags = [ 'R' => true ];
-               }
-
-               if ( !isset( $flags['R'] ) && !isset( $flags['N'] ) ) {
-                       // decode => HTML entities modified by Sanitizer::removeHTMLtags
-                       $this->mRules = str_replace( '=&gt;', '=>', $this->mRules );
-                       $this->parseRules();
-               }
-               $rules = $this->mRules;
-
-               if ( !$this->mBidtable && !$this->mUnidtable ) {
-                       if ( isset( $flags['+'] ) || isset( $flags['-'] ) ) {
-                               // fill all variants if text in -{A/H/-|text}- is non-empty but without rules
-                               if ( $rules !== '' ) {
-                                       foreach ( $this->mConverter->mVariants as $v ) {
-                                               $this->mBidtable[$v] = $rules;
-                                       }
-                               }
-                       } elseif ( !isset( $flags['N'] ) && !isset( $flags['T'] ) ) {
-                               $this->mFlags = $flags = [ 'R' => true ];
-                       }
-               }
-
-               $this->mRuleDisplay = false;
-               foreach ( $flags as $flag => $unused ) {
-                       switch ( $flag ) {
-                               case 'R':
-                                       // if we don't do content convert, still strip the -{}- tags
-                                       $this->mRuleDisplay = $rules;
-                                       break;
-                               case 'N':
-                                       // process N flag: output current variant name
-                                       $ruleVar = trim( $rules );
-                                       $this->mRuleDisplay = $this->mConverter->mVariantNames[$ruleVar] ?? '';
-                                       break;
-                               case 'D':
-                                       // process D flag: output rules description
-                                       $this->mRuleDisplay = $this->getRulesDesc();
-                                       break;
-                               case 'H':
-                                       // process H,- flag or T only: output nothing
-                                       $this->mRuleDisplay = '';
-                                       break;
-                               case '-':
-                                       $this->mRulesAction = 'remove';
-                                       $this->mRuleDisplay = '';
-                                       break;
-                               case '+':
-                                       $this->mRulesAction = 'add';
-                                       $this->mRuleDisplay = '';
-                                       break;
-                               case 'S':
-                                       $this->mRuleDisplay = $this->getRuleConvertedStr( $variant );
-                                       break;
-                               case 'T':
-                                       $this->mRuleTitle = $this->getRuleConvertedTitle( $variant );
-                                       $this->mRuleDisplay = '';
-                                       break;
-                               default:
-                                       // ignore unknown flags (but see error case below)
-                       }
-               }
-               if ( $this->mRuleDisplay === false ) {
-                       $this->mRuleDisplay = '<span class="error">'
-                               . wfMessage( 'converter-manual-rule-error' )->inContentLanguage()->escaped()
-                               . '</span>';
-               }
-
-               $this->generateConvTable();
-       }
-
-       /**
-        * Checks if there are conversion rules.
-        * @return bool
-        */
-       public function hasRules() {
-               return $this->mRules !== '';
-       }
-
-       /**
-        * Get display text on markup -{...}-
-        * @return string
-        */
-       public function getDisplay() {
-               return $this->mRuleDisplay;
-       }
-
-       /**
-        * Get converted title.
-        * @return string
-        */
-       public function getTitle() {
-               return $this->mRuleTitle;
-       }
-
-       /**
-        * Return how deal with conversion rules.
-        * @return string
-        */
-       public function getRulesAction() {
-               return $this->mRulesAction;
-       }
-
-       /**
-        * Get conversion table. (bidirectional and unidirectional
-        * conversion table)
-        * @return array
-        */
-       public function getConvTable() {
-               return $this->mConvTable;
-       }
-
-       /**
-        * Get conversion rules string.
-        * @return string
-        */
-       public function getRules() {
-               return $this->mRules;
-       }
-
-       /**
-        * Get conversion flags.
-        * @return array
-        */
-       public function getFlags() {
-               return $this->mFlags;
-       }
-}
index 8a1d2a7..00f35b2 100644 (file)
@@ -299,7 +299,7 @@ class Names {
                'mk' => 'македонски', # Macedonian
                'ml' => 'മലയാളം', # Malayalam
                'mn' => 'монгол', # Halh Mongolian (Cyrillic) (ISO 639-3: khk)
-               'mni' => 'মেইতেই লোন্', # Manipuri/Meitei
+               'mni' => 'ꯃꯤꯇꯩ ꯂꯣꯟ', # Manipuri/Meitei
                'mnw' => 'ဘာသာ မန်', # Mon, T201583
                'mo' => 'молдовеняскэ', # Moldovan, deprecated (ISO 639-2: ro-Cyrl-MD)
                'mr' => 'मराठी', # Marathi
index f052ccf..23cdb22 100644 (file)
@@ -12,7 +12,8 @@
                        "Si Gam Acèh",
                        "아라",
                        "Macofe",
-                       "Rachmat04"
+                       "Rachmat04",
+                       "Martin Urbanec"
                ]
        },
        "tog-underline": "Bôh garéh yup peunawôt:",
        "booksources-search-legend": "Mita bak nè kitab",
        "booksources-search": "Mita",
        "specialloguserlabel": "Ureuëng ngui:",
-       "speciallogtitlelabel": "Sasaran (judu atawa {{ns:ureueng ngui}}:nan ureueng ngui keu ureueng ngui)",
+       "speciallogtitlelabel": "Sasaran (judu atawa {{ns:user}}:nan ureueng ngui keu ureueng ngui)",
        "log": "Log",
        "all-logs-page": "Ban dum log umom",
        "allpages": "Ban dum laman",
index 60f54e2..6ddc9fa 100644 (file)
        "rcfilters-filter-showlinkedto-label": "عرض التغييرات في الصفحات الموصولة بصفحة",
        "rcfilters-filter-showlinkedto-option-label": "<strong>الصفحات الموصولة إلى</strong> الصفحة المختارة",
        "rcfilters-target-page-placeholder": "أدخل اسم صفحة (أو تصنيف)",
+       "rcfilters-allcontents-label": "جميع المحتويات",
+       "rcfilters-alldiscussions-label": "جميع النقاشات",
        "rcnotefrom": "بالأسفل {{PLURAL:$5|التغيير|التغييرات}} منذ <strong>$3، $4</strong> (إلى <strong>$1</strong> معروضة).",
        "rclistfromreset": "إعادة ضبط خيار التاريخ",
        "rclistfrom": "أظهر التغييرات بدءًا من $3 $2",
index 75f17de..9648ee2 100644 (file)
        "rcfilters-filter-showlinkedto-label": "लिंक करने वाले पृष्ठों पर परिवर्तन दिखाएं",
        "rcfilters-filter-showlinkedto-option-label": "<strong>से जुड़ने वाले पृष्ठ</strong> चयनित पृष्ठ",
        "rcfilters-target-page-placeholder": "पृष्ठ(अथवा श्रेणी) का नाम भरें",
+       "rcfilters-allcontents-label": "सब सामग्री",
+       "rcfilters-alldiscussions-label": "सब चर्चा",
        "rcnotefrom": "नीचे <strong>$2</strong> के बाद से (<strong>$1</strong> तक) {{PLURAL:$5|हुआ बदलाव दर्शाया गया है|हुए बदलाव दर्शाए गये हैं}}।",
        "rclistfromreset": "चुने दिनांक पहले जैसा करें",
        "rclistfrom": "$3 $2 से नँवा बदलाव देखावा जाय",
        "move-subpages": "उप पन्ना घुस्कावा जाय ($1 तक)",
        "move-talk-subpages": "बातचीत पन्ना कय उप पन्ना भी लई जावा जाय ($1 तक)",
        "movepage-page-exists": "$1 पन्ना पहिलवे से है अव आप ओहपर फिरसे नाइ लिखि सका जात है ।",
+       "movepage-source-doesnt-exist": "पृष्ठ $1 मौजूद नाइ हय अउर एहका स्थानांतरित नही करा जाइ सकत।",
        "movepage-page-moved": "पन्ना $1 कय $2 पे घुस्काइ गय ।",
        "movepage-page-unmoved": "पन्ना $1 कय $2 पे नाइ घुस्काइ सका जात है ।",
        "movepage-max-pages": "$1 की अधिकतम सीमा तक पृष्ठ स्थानांतरित कर {{PLURAL:$1|दिया गया है|दिये गये हैं}}, अब और पृष्ठ अपने-आप स्थानांतरित नहीं होंगे।",
        "delete_and_move_reason": "\"[[$1]]\" से घुस्कावै खत्तीर जगह बनाई गा है",
        "selfmove": "स्रोत अव गन्तव्य पन्ना कय एक्कय शिर्षक है ;पन्ना कय उहिक उप्पर नाइ घुस्काय सका जात है ।",
        "immobile-source-namespace": "नामस्थान \"$1\" पे पन्ना नाइ घुस्काय सका जात है",
+       "immobile-source-namespace-iw": "अन्य विकियऽन् कय पृष्ठ एह विकी से स्थानांतरित नही करा जाइ सकत।",
        "immobile-target-namespace": "नामस्थान \"$1\" कय भित्तर पन्ना नाइ घुस्काय सका जात है",
        "immobile-target-namespace-iw": "अंतर विकि कड़ी पन्ना लई जाय खत्तीर उचित लक्ष्य नाइ है।",
        "immobile-source-page": "ई पन्ना नाइ घुस्की ।",
        "immobile-target-page": "इ गन्तव्य शिर्षक पय नाइ लैजाय सका जात अहै ।",
+       "movepage-invalid-target-title": "अनुरोध करा गवा नांव अवैध अहै।",
        "bad-target-model": "वाञ्छित स्थान भिन्न सामग्री नमूने का प्रयोग करता है। $1 को बदलकर $2 नहीं किया जा सकता है।",
        "imagenocrossnamespace": "फाइल कय बिना-फाइल नेमस्पेस मा नाइ घुस्काय सका जात है",
        "nonfile-cannot-move-to-file": "बिना-फाइल कय फाइल नेमस्पेस मा नाइ घुस्काय सका जात है ।",
index 7cdfa42..83cd424 100644 (file)
        "views": "Pakantenan",
        "toolbox": "Pekakas",
        "imagepage": "Cingak kaca berkas",
+       "mediawikipage": "Cingak kaca séwalapatra",
        "templatepage": "Cingak kaca cétakan",
        "viewhelppage": "Cingak kaca wantuan",
        "categorypage": "Cingak kaca kategori",
        "redirectedfrom": "(Kagingsirang saking $1)",
        "redirectpagesub": "Kaca gingsiran",
        "redirectto": "Magingsir ring:",
-       "lastmodifiedat": "Kaca puniki kaping untat kaubah rikala  $2, $1",
+       "lastmodifiedat": "Kaca puniki kaping untat kauah rikala  $2, $1",
        "protectedpage": "Kaca sané kasaibin",
        "jumpto": "Lanturang ka:",
        "jumptonavigation": "navigasi",
        "youhavenewmessages": "{{PLURAL:$3|Jero madué}} $1 ($2)",
        "youhavenewmessagesfromusers": "{{PLURAL:$4|You have}} $1 ring {{PLURAL:$3|another user|$3 users}} ($2).",
        "youhavenewmessagesmanyusers": "Jero madué $1 saking akéh sang anganggé ($2).",
+       "newmessageslinkplural": "{{PLURAL:$1|séwalapatra anyar abesik|999=séwalapatra anyar}}",
+       "youhavenewmessagesmulti": "Ida dané madué séwalapatra anyar ring $1",
        "editsection": "uah",
        "editold": "uah",
        "viewsourceold": "cingak wit",
        "editlink": "uah",
        "viewsourcelink": "cingak wit",
-       "editsectionhint": "Uah pahan: $1",
+       "editsectionhint": "Uah pah-pahan: $1",
        "toc": "Daging",
        "showtoc": "sinahang",
        "hidetoc": "engkebang",
        "nstab-special": "Kaca kusus",
        "nstab-project": "Kaca proyék",
        "nstab-image": "Depukan",
+       "nstab-mediawiki": "Séwalapatra",
        "nstab-template": "Cétakan",
        "nstab-help": "Kaca wantuan",
        "nstab-category": "Kategori",
        "pagehist": "Babad kaca",
        "deletedhist": "Babad sané kausapin",
        "mergehistory-from": "Kaca wit:",
+       "mergelog": "Gabung log",
        "revertmerge": "tansida nyarengin",
        "history-title": "Babad uahan saking \"$1\"",
        "difference-title": "$1: sane malianan ring revisi",
        "diff-empty": "(Nénten wénten sané malianan)",
        "diff-multi-sameuser": "({{PLURAL:$1|$1 revisi pantaraning}} olih pangawi sane pateh nenten kacumawisang)",
        "searchresults": "asil pangrereh",
-       "searchresults-title": "asil pangrereh anggen \"$1\"",
+       "searchresults-title": "Asil pangrereh anggén \"$1\"",
        "prevn": "{{PLURAL:$1|$1}} sadurungnyané",
        "nextn": "{{PLURAL:$1|$1}} salanturnyané",
        "prev-page": "kaca sadurungnyané",
        "allpages": "Makasami kaca",
        "allarticles": "Makasami kaca",
        "allinnamespace": "Makasami kaca (genah wastan $1)",
-       "allpagessubmit": "lanturang",
+       "allpagessubmit": "Lanturang",
        "allpages-bad-ns": "{{SITENAME}} nénten madué genah wastan \"$1\".",
        "allpages-hide-redirects": "Ngengkebang pagingsirian",
        "categories": "Golongan",
        "listusers-submit": "Sinahang",
        "listgrouprights-members": "kepahan krama",
        "emailuser": "email sane nganggo niki",
+       "emailmessage": "Séwalapatra:",
        "watchlist": "kepahan peninjoan",
        "mywatchlist": "kepahan peninjoan",
        "watchlistfor2": "Anggén $1 $2",
        "revertmove": "buwungang",
        "export": "ekspor lembar",
        "export-download": "Raksa pinaka berkas",
+       "allmessages": "Séwalapatra sistem",
        "allmessagesname": "pesengan",
        "allmessagesdefault": "teks lingga",
-       "thumbnail-more": "ngedenang",
+       "thumbnail-more": "Ngedénang",
        "thumbnail_error": "luput ngaryanin bentuk cenik $1",
        "import-interwiki-sourcepage": "Kaca wit:",
        "importlogpage": "Log impor",
        "tooltip-n-mainpage": "nuju lembar sane utama",
        "tooltip-n-mainpage-description": "Cingak kaca utama",
        "tooltip-n-portal": "Indik proyék, sané prasida kalaksanayang, genah ngrereh wantuan",
-       "tooltip-n-currentevents": "molihang warta indik kawentenan kawentenan sane pinih anyar",
+       "tooltip-n-currentevents": "Rereh pidarta indik kawéntenan sané pinih anyar",
        "tooltip-n-recentchanges": "Bacakan uahan sané mangkin ring wiki",
        "tooltip-n-randompage": "Cihnayang kaca napi kémanten",
        "tooltip-n-help": "Genah ngrereh wantuan",
        "tooltip-t-permalink": "Pranala ajeg kaanggen ngubah lembar puniki",
        "tooltip-ca-nstab-main": "Cingak kaca daging",
        "tooltip-ca-nstab-user": "Cingak kaca sang anganggé",
-       "tooltip-ca-nstab-special": "puniki lembar sane pinih utama sane nenten prasida kauwah",
+       "tooltip-ca-nstab-special": "Puniki kaca kusus tur nénten prasida kauwah",
        "tooltip-ca-nstab-project": "Cingak kaca proyek",
        "tooltip-ca-nstab-image": "Cingak kaca depukannyané",
+       "tooltip-ca-nstab-mediawiki": "Cingak séwalapatra sistem",
        "tooltip-ca-nstab-template": "Cingak citakan",
        "tooltip-ca-nstab-help": "Cingak kaca wantuan",
        "tooltip-ca-nstab-category": "Cingak kaca kategori",
        "tooltip-summary": "Dagingin ringkesan",
        "simpleantispam-label": "Pamariksa anti-spam.\nPuniki <strong>wenten</strong> kaisi!",
        "pageinfo-title": "Pidarta indik \"$1\"",
+       "pageinfo-header-basic": "Pidarta kaca",
        "pageinfo-header-edits": "Babad uahan",
        "pageinfo-header-restrictions": "Saiban kaca",
+       "pageinfo-header-properties": "Properti suratan",
        "pageinfo-display-title": "Edengang judul",
        "pageinfo-namespace": "Genah wastan",
        "pageinfo-article-id": "ID kaca",
        "show-big-image-size": "$1 × $2 piksel",
        "sunday-at": "Redite jam $1",
        "bad_image_list": "bentukne sekadi puniki:\n\nwantah kepahan daftar ( baris sane kakawitin anggen tanda *) sane kaitung pranala kapertama ring baris mangda pranala ring berkas sane kaon.\nPranala-Pranala sane selanturnyane ring baris sane pateh kamanahang antuk pinangging, inggih punika lembar sane prasida ngedengang berkas punika.",
-       "metadata": "metadata",
+       "metadata": "tadata",
        "metadata-help": "pupulan puniki madaging wacana imbuhan minab sane kaimbuhin olih kamera digital utawi scanner sane kaanggen antuk ngawi atawi \"mendigitalisasi\" pupulan. Yening pupulan niki sampun taen kautak-atik, rerincine sane wenten minab nenten samian nyiriang wacan saking gambar sane sampun kautak-atik niki.",
-       "metadata-fields": "bidang metadata gambar sane kacantumang ring pesen puniki jagi kalebuang ring tampilan lembar gambar rikala tabel metadata kacenikang.\n* make\n* model\n* datetimeoriginal\n* exposuretime\n* fnumber\n* isospeedratings\n* focallength\n* artist\n* copyright\n* imagedescription\n* gpslatitude\n* gpslongitude\n* gpsaltitude",
+       "metadata-fields": "Widang métadata gambar sané kacantumang ring séwalapatra puniki jagi kalebuang ring tampilan kaca gambar ri tatkala tabél métadata kacenikang.\nSané lianan jagi kasenetang.\n* make\n* model\n* datetimeoriginal\n* exposuretime\n* fnumber\n* isospeedratings\n* focallength\n* artist\n* copyright\n* imagedescription\n* gpslatitude\n* gpslongitude\n* gpsaltitude",
        "namespacesall": "samian",
        "monthsall": "samian",
        "imgmultipagenext": "kaca salanturnyané →",
        "logentry-protect-protect": "$1 {{GENDER:$2|nyaibin}} $3 $4",
        "logentry-upload-upload": "$1 {{GENDER:$2|ngunggahang}} $3",
        "logentry-upload-overwrite": "$1 {{GENDER:$2|ngunggahang}} vèrsi anyar saking $3",
+       "feedback-message": "Séwalapatra:",
        "searchsuggest-search": "Rereh ring {{SITENAME}}",
        "duration-days": "$1 {{PLURAL:$1|rahina}}",
        "pagelanguage": "Uah basa ring kaca",
        "pagelang-nonexistent-page": "Kaca $1 nénten wénten.",
+       "mw-widgets-abandonedit-keep": "Lanturang nguah",
        "log-action-filter-protect-protect": "Saiban",
        "log-action-filter-protect-move_prot": "Ngingsirang saiban"
 }
index bfaac9d..595ba7e 100644 (file)
@@ -47,7 +47,7 @@
        "tog-fancysig": "امضاءَ په داب ویکی متنی بزان(بی اتوماتیکی لینک)",
        "tog-uselivepreview": "پیش‌نمایش بدون نیاز به بروزرسانی صفحه",
        "tog-forceeditsummary": "من آ هال دی وهدی وارد کتن یک هالیکین خلاصه ی اصلاح",
-       "tog-watchlisthideown": "منی اصلاحات آ چه لیست چارگ پناه کن",
+       "tog-watchlisthideown": "منی ٹگلان چہ چارگء لیست‌ئا پناہ کن",
        "tog-watchlisthidebots": "اصلاحات بوت چه لیست چارگ پناه کن",
        "tog-watchlisthideminor": "هوردین اصلاحات چه لیست چارگ پناه کن",
        "tog-watchlisthideliu": "اصلاحات چه وارد بوتگین کاربران چه لیست چارگان پناه کن",
        "mimetype": "نوع مایم:",
        "download": "آیرگیزگ",
        "unwatchedpages": "نه چارتگین صفحات",
-       "listredirects": "Ù\84Û\8cست ØºÛ\8cر Ù\85ستÙ\82Û\8cÙ\85ان",
+       "listredirects": "Ù\86اتÙ\90Ú\86Ú©Ý\94Úº Ù\84Û\8cستان",
        "listduplicatedfiles": "فهرست همهٔ پرونده‌ها به‌همراه تکراری‌ها",
        "listduplicatedfiles-summary": "این فهرست پرونده‌هایی با نسخه‌های اخیر این پرونده تکراری است که نسخه‌های اخبر سایر پرونده‌ها است. فقط پرونده‌های محلی در نظر گرفته شده‌اند.",
        "listduplicatedfiles-entry": "[[:File:$1|$1]][[$3|{{PLURAL:$2|یک تکرار|$2 تکرار}}]] دارد.",
        "listusers-submit": "پیش دار",
        "listusers-noresult": "هچ کابری در گیزگ نه بوت.",
        "listusers-blocked": "(بند بیتگ)",
-       "activeusers": "لیست کاربران فعال",
+       "activeusers": "کنشدارݔں کارزورۏکانء لیست",
        "activeusers-count": "$1 {{PLURAL:$1|اصلاح|اصلاح}} نوکین",
        "activeusers-from": "پیشدار کاربرانی که شروع بنت گون :‌",
        "activeusers-noresult": "هچ کاربری درگیزگ نه بیت",
        "listgrouprights-group": "گروه",
        "listgrouprights-rights": "حقوق",
        "listgrouprights-helppage": "Help: حقوق گروه",
-       "listgrouprights-members": "(لیست اعضا)",
+       "listgrouprights-members": "(ھۏرݔنانء لیست)",
        "listgrouprights-addgroup": "تونیت اضافه کنت {{PLURAL:$2|گروه|گروهان}}: $1",
        "listgrouprights-removegroup": "تونیت بزوریت {{PLURAL:$2|گروهء|گروهانء}}: $1",
        "listgrouprights-addgroup-all": "تونیت کل گروهان اضافه کنت",
index d8e0cb1..831adfb 100644 (file)
@@ -75,7 +75,7 @@
        "sat": "Sap",
        "january": "Januari",
        "february": "Pibuari",
-       "march": "Marat",
+       "march": "Marit",
        "april": "April",
        "may_long": "Mai",
        "june": "Juni",
        "oct": "Ukt",
        "nov": "Nup",
        "dec": "Dis",
+       "january-date": "$1 Januari",
+       "february-date": "$1 Pibuari",
+       "march-date": "$1 Marat",
+       "april-date": "$1 April",
+       "may-date": "$1 Mai",
+       "june-date": "$1 Juni",
+       "july-date": "$1 Juli",
+       "august-date": "$1 Agustus",
+       "september-date": "$1 Siptimbir",
+       "october-date": "$1 Uktubir",
+       "november-date": "$1 Nupimbir",
+       "december-date": "$1 Disimbir",
+       "period-am": "AM",
+       "period-pm": "PM",
        "pagecategories": "{{PLURAL:$1|Tumbung}}",
-       "category_header": "Halaman dalam pilah \"$1\"",
+       "category_header": "Tungkaran dalam tumbung \"$1\"",
        "subcategories": "Sub-tumbung",
        "category-media-header": "Média dalam pilah \"$1\"",
        "category-empty": "\"Kada tahaga tulisan maupun média dalam pilah ngini.\"",
        "hidden-category-category": "Tumbung tasungkup",
        "category-subcat-count": "{{PLURAL:$2|Tumbung ngini baisi asa sub-tumbung nangkaya ngini.|Pilih ngini baisi {{PLURAL:$1|sub-tumbung|$1 sub-tumbung}}, matan sabarataan $2.}}",
        "category-subcat-count-limited": "Tumbung ini baisi {{PLURAL:$1|sub-tumbung|$1 sub-tutumbung}} barikut.",
-       "category-article-count": "{{PLURAL:$2|Pilah ngini baisi {{PLURAL:$1|$1 halaman}}, tumatan jumlah $2.}}",
+       "category-article-count": "{{PLURAL:$2|Tumbung ngini baisi {{PLURAL:$1|$1 tungkaran}}, tumatan jumlah $2.}}",
        "category-article-count-limited": "Tumbung ini baisi {{PLURAL:$1|asa tungkaran|$1 tutungkaran}} barikut.",
        "category-file-count": "{{PLURAL:$2|Pilah ngini baisi {{PLURAL:$1|$1 barakas}}, matan jumlah $2.}}",
        "category-file-count-limited": "Tumbung ngini baisi {{PLURAL:$1|barakas|$1 barakas}} barikut.",
        "returnto": "Bulik ka $1.",
        "tagline": "Matan {{SITENAME}}",
        "help": "Patulung",
-       "search": "Pangikihan",
-       "searchbutton": "Kikih",
+       "help-mediawiki": "Patulung parihal MediaWiki",
+       "search": "Panggagaian",
+       "searchbutton": "Gagai",
        "go": "Tulak",
        "searcharticle": "Tulak",
-       "history": "Riwayat halaman",
+       "history": "Sajarah tungkaran",
        "history_short": "Sajarah",
        "history_small": "riwayat",
        "updatedmarker": "dihanyari tumatan ilangan pauncitan pian",
        "protect": "Lindungi",
        "protect_change": "ubah",
        "unprotect": "Palindungan",
-       "newpage": "Halaman hanyar",
+       "newpage": "Tungkaran hanyar",
        "talkpagelinktext": "pandir",
        "specialpage": "Tungkaran istimiwa",
        "personaltools": "Pakakas saurang",
        "redirectedfrom": "(Diugahakan matan $1)",
        "redirectpagesub": "Tungkaran paugahan",
        "redirectto": "Maugahakan ka:",
-       "lastmodifiedat": "Halaman ngini pahabisan diubah wayah $1, pukul $2.",
+       "lastmodifiedat": "Tungkaran ngini pahabisan diubah wayah $1, pukul $2.",
        "viewcount": "Tungkaran ini sudah diungkai {{PLURAL:$1|kali|$1 kali}}.",
        "protectedpage": "Tungkaran nang dilindungi",
        "jumpto": "Malacung ka:",
        "edithelp": "Patulung mambabak",
        "helppage-top-gethelp": "Patulung",
        "mainpage": "Tungkaran Tatambaian",
-       "mainpage-description": "Halaman tatambaian",
+       "mainpage-description": "Tungkaran tatambaian",
        "policy-url": "Project:Kaaripan",
        "portal": "Lawang bubuhan",
        "portal-url": "Project:Lawang bubuhan",
        "hidetoc": "sungkupakan",
        "collapsible-collapse": "Siup",
        "collapsible-expand": "Kambangakan",
+       "confirmable-yes": "Inggih",
+       "confirmable-no": "Kada",
        "thisisdeleted": "Tiringi atawa mambulikakan $1?",
        "viewdeleted": "Tiringi $1?",
        "restorelink": "$1 {{PLURAL:$1|babakan|babakan}} nang sudah dihapus",
        "site-atom-feed": "Kitihan Atum $1",
        "page-rss-feed": "Kitihan RSS ''$1''",
        "page-atom-feed": "Kitihan Atum ''$1''",
-       "red-link-title": "$1 (halaman baluman ada)",
+       "red-link-title": "$1 (tungkaran baluman ada)",
        "sort-descending": "Surtir baturun",
        "sort-ascending": "Surtir banaik",
-       "nstab-main": "Halaman",
+       "nstab-main": "Tungkaran",
        "nstab-user": "Pamakai",
        "nstab-media": "Média",
-       "nstab-special": "Halaman istimiwa",
+       "nstab-special": "Tungkaran istimiwa",
        "nstab-project": "Halaman rangka gawian",
        "nstab-image": "Barakas",
        "nstab-mediawiki": "Pasan",
        "nstab-template": "Citakan",
        "nstab-help": "Patulung",
        "nstab-category": "Tumbung",
-       "mainpage-nstab": "Halaman tatambaian",
+       "mainpage-nstab": "Tungkaran tatambaian",
        "nosuchaction": "Kadada palakuan nangkaitu",
        "nosuchactiontext": "Tindakan nang diminta URL kada sah.\nPian tagasnya salah katik URL, atawa maumpati sabuting tautan nang kada bujur.\nNgini jua bisa ai ada bug di parangkat lunak nang dipuruk {{SITENAME}}.",
        "nosuchspecialpage": "Kadada halaman istimiwa nangitu",
        "cannotdelete-title": "Kada kawa mahapus tungkaran \"$1\"",
        "delete-hook-aborted": "Pahapusan diwalangakan ulih kait parser.\nKadada katarangan.",
        "badtitle": "Judul buruk",
-       "badtitletext": "Judul halaman nang diminta kada sah, puang, atawa judul antarbasa atawa antarwiki nang salah sambung.",
+       "badtitletext": "Judul tungkaran nang diminta kada sah, puang, atawa judul antarbasa atawa antarwiki nang salah sambung. Bisa baisi sabuting atawa labih karakter nang kada kawa dipakai di judul.",
        "perfcached": "Data barikut adalah timbuluk wan pina kada mutakhir. A maximum of {{PLURAL:$1|one result is|$1 results are}} available in the cache.",
        "perfcachedts": "Data nang dudi ni adalah timbuluk, wan tauncit dihahanyari pada $1. A maximum of {{PLURAL:$4|one result is|$4 results are}} available in the cache.",
        "querypage-no-updates": "Pamugaan matan tungkaran ngini rahat dipajahkan. Data nang ada di sia wayahini kada akan dimuat ulang.",
        "createacct-submit": "Ulah akun Pian",
        "createacct-benefit-heading": "{{SITENAME}} diulah ulih urang-urang nangkaya Pian.",
        "createacct-benefit-body1": "{{PLURAL:$1|babakan}}",
-       "createacct-benefit-body2": "{{PLURAL:$1|halaman}}",
+       "createacct-benefit-body2": "{{PLURAL:$1|tungkaran}}",
        "createacct-benefit-body3": "{{PLURAL:$1|sumbangan}} pahabisnya",
        "badretype": "Katasunduk nang Pian buati kada pas.",
        "userexists": "Ngaran pamakai nang dibuati hudah dipuruk urang lain.\nMuhun pilih sabuting ngaran lain.",
        "summary": "Kasimpulan:",
        "subject": "Parihal:",
        "minoredit": "Ngini adalah babakan sapalih",
-       "watchthis": "Itihi halaman ngini",
-       "savearticle": "Simpan halaman",
+       "watchthis": "Itihi tungkaran ini",
+       "savearticle": "Simpan tungkaran",
        "preview": "Tilik",
        "showpreview": "Tampaiakan titilikan",
        "showdiff": "Tampaiakan paubahan",
        "accmailtitle": "Katasunduk takirim.",
        "accmailtext": "Sabuting katasunduk babarang gasan [[User talk:$1|$1]] sudah dikirim ka $2.\n\nKatasunduk gasan pamakai hanyar nangini kawa diubah di halaman ''[[Special:ChangePassword|ubah katasunduk]]'' limbah babuat log.",
        "newarticle": "(Hanyar)",
-       "newarticletext": "Pian maumpati tautan ka halaman nang balum tasadia. Amun handak maulah halaman itu, katiklah isi halaman di kutak di bawah ngini (janaki [$1 halaman patulung] gasan maklumat labih lanjut). Amun pian kada bakurinah sampai ka halaman ngini, kalik picikan <strong>back</strong> di panjalajah wéb Pian.",
+       "newarticletext": "Pian maumpati tautan ka tungkaran nang balum tasadia. Amun handak maulah tungkaran itu, katiklah isi tungkaran di kutak di bawah ngini (itihi[$1 tungkaran patulung] gasan maklumat labih lanjut). Amun pian kada bakurinah sampai ka tungkaran ngini, kalik picikan <strong>back</strong> di panjalajah wéb Pian.",
        "anontalkpagetext": "----''Ngini adalah halaman pamandiran gasan pamakai kada bangaran nang baluman ma-ulah akun pulang, atawa  kada mamakainya. Kami tapaksa mamakai numurik alamat IP hagan maminanduinya.\nAlamat IP nangkaini kawaai dipuruk ulih babarapa pamakai.\nAmun Pian adalah pamakai kada bangaran wan marasa kumin nang kada pas ta ka Pian, muhun [[Special:CreateAccount|ulah sabuah akun]] atawa [[Special:UserLogin|babuat log]] gasan mahindari kabingungan awan pamakai kada bangaran lain kaina.",
-       "noarticletext": "Damini kadada naskah di halaman ngini.\nPian kawa [[Special:Search/{{PAGENAME}}|mangikihi gasan judul halaman ngini]] di halaman lain, <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} mancari log tarait], atawa [{{fullurl:{{FULLPAGENAME}}|action=edit}} maulah halaman ngini]</span>.",
-       "noarticletext-nopermission": "Wayahini kadada naskah di halaman ngini.\nPian kawa [[Special:Search/{{PAGENAME}}|manggagai gasan judul halaman ngini]] di halaman lain, atawa <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} manggagai log tarait]</span>, tagal Pian kada baisi ijin gasan maulah halaman ngini.",
+       "noarticletext": "Damini kadada naskah di tungkaran ngini.\nPian kawa [[Special:Search/{{PAGENAME}}|mangikihi gasan judul tungkaran ngini]] di tungkaran lain, <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} mancari log tarait], atawa [{{fullurl:{{FULLPAGENAME}}|action=edit}} maulah tungkaran ngini]</span>.",
+       "noarticletext-nopermission": "Wayahini kadada naskah di tungkaran ngini.\nPian kawa [[Special:Search/{{PAGENAME}}|manggagai gasan judul tungkaran ngini]] di tungkaran lain, atawa <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} manggagai log tarait]</span>, tagal Pian kada baisi ijin gasan maulah tungkaran ngini.",
        "userpage-userdoesnotexist": "Akun pamakai \"<nowiki>$1</nowiki>\" kada tadaptar.\nMuhun pariksa/ditukui amun Pian handak maulah/mambabak tungkaran ngini.",
        "userpage-userdoesnotexist-view": "Akun pamakai \"$1\" kada tadaptar.",
        "blocked-notice-logextract": "Pamakai nangini parhatan diblukir.\nLog blukir pahabisannya tasadia di bawah ngini gasan rujukan:",
        "semiprotectedpagewarning": "'''Catatan:''' Tungkaran ngini sudah dilindungi nang akibatnya pamakai tadaptar haja nang kawa mambabak.\nLog masuk pauncitnya disadiakan di bawah gasan rujukan:",
        "cascadeprotectedwarning": "<strong>Paringatan:</strong> Halaman ngini dilindungi jadinya pamakai lawan [[Special:ListGroupRights|hak aksis batantu]] wara nang kawa mambabaknya maraga ditransklusiakan dalam {{PLURAL:$1|halaman}} nang dilindungi barinting.",
        "titleprotectedwarning": "'''Paringatan: Tungkaran ngini sudah dilindungi nang akibatnya [[Special:ListGroupRights|hak khas]] diparluakan hagan maulah ngini.'''\nLog masuk pauncitnya disadiakan di bawah gasan rujukan:",
-       "templatesused": "{{PLURAL:$1|Citakan}} nang dipakai di halaman ngini:",
+       "templatesused": "{{PLURAL:$1|Citakan}} nang dipakai di tungkaran ngini:",
        "templatesusedpreview": "{{PLURAL:$1|Citakan|Cicitakan}} nang dipakai di titilikan ngini:",
        "templatesusedsection": "{{PLURAL:$1|Citakan|Cicitakan}} nang diguna'akan di hagian ini:",
        "template-protected": "(dilindungi)",
        "template-semiprotected": "(semi-dilindungi)",
-       "hiddencategories": "Halaman ngini adalah angguta matan {{PLURAL:$1|1 pilah tatukup|$1 pilah tatukup}}:",
+       "hiddencategories": "Tungkaran ngini adalah angguta matan {{PLURAL:$1|1 tumbung tatukup|$1 tumbung tatukup}}:",
        "nocreatetext": "{{SITENAME}} lagi mambatasi kakawaan maulah tungkaran hanyar.\nPian kawa babulik wan mambabak sabuah tungkaran nag ada, atawa [[Special:UserLogin|lbabuat log atawa baulah sabuah akun]]",
        "nocreate-loggedin": "Pian kada baisi ijin hagan maulah tungkaran-tungkaran hanyar.",
        "sectioneditnotsupported-title": "Pambabakan hagian kada didukung",
        "nextn": "{{PLURAL:$1|$1}} imbahnya",
        "prevn-title": "Tadahulu $1 {{PLURAL:$1|kulihan|kulihan-kulihan}}",
        "nextn-title": "$1 {{PLURAL:$1|kulihan|kulihan-kulihan}} imbahnya",
-       "shown-title": "Tampaiakan $1 {{PLURAL:$1|kulihan}} par halaman",
+       "shown-title": "Tampaiakan $1 {{PLURAL:$1|kulihan}} par tungkaran",
        "viewprevnext": "Tiringi ($1 {{int:pipe-separator}} $2) ($3)",
        "searchmenu-exists": "'''Ada tungkaran bangaran \"[[:$1]]\" dalam wiki ini.'''",
-       "searchmenu-new": "<strong>Ulah halaman \"[[:$1]]\" di wiki ngini!</strong> {{PLURAL:$2|0=|Itihi jua halaman nang dihagaakan matan pangikihan Pian.|Itihi jua kulihan pangikihan nang dihagaakan.}}",
-       "searchprofile-articles": "Halaman isi",
+       "searchmenu-new": "<strong>Ulah halaman \"[[:$1]]\" di wiki ngini!</strong> {{PLURAL:$2|0=|Itihi jua tungkaran nang dihagaakan matan panggagaian Pian.|Itihi jua kulihan panggagaian nang dihagaakan.}}",
+       "searchprofile-articles": "Tungkaran isi",
        "searchprofile-images": "Multimadia",
        "searchprofile-everything": "Samunyaan",
        "searchprofile-advanced": "Haratan",
        "enhancedrc-history": "sajarah",
        "recentchanges": "Paubahan pahanyarnya",
        "recentchanges-legend": "Pilihan paubahan pahanyarnya",
-       "recentchanges-summary": "Jajak paubahan wiki pahanyarnya pada halaman ngini",
+       "recentchanges-summary": "Jajak paubahan wiki pahanyarnya pada tungkaran ngini",
        "recentchanges-noresult": "Kadada paubahan dalam rantang waktu ngini nang rasuk lawan syarat.",
        "recentchanges-feed-description": "Susuri paubahan pahanyarnya dalam wiki di kitihan ini",
-       "recentchanges-label-newpage": "Babakan ngini maulah sabuting halaman hanyar",
+       "recentchanges-label-newpage": "Babakan ngini maulah sabuting tungkaran hanyar",
        "recentchanges-label-minor": "Ngini babakan sapalih",
        "recentchanges-label-bot": "Babakan ngini digawi ulih bot",
        "recentchanges-label-unpatrolled": "Babakan ngini baluman ta'awasi",
-       "recentchanges-label-plusminus": "Paubahan ukuran halaman dalam bita",
+       "recentchanges-label-plusminus": "Paubahan ukuran tungkaran dalam bita",
        "recentchanges-legend-heading": "<strong>Katarangan:</strong>",
-       "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (janaki jua [[Special:NewPages|daptar halaman hanyar]])",
+       "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (itihi jua [[Special:NewPages|daptar tungkaran hanyar]])",
        "rcnotefrom": "Di bawah ngini adalah {{PLURAL:$5|paubahan}} tumatan <strong>$3, $4</strong> (ditampaiakan sampai <strong>$1</strong> paubahan).",
        "rclistfrom": "Tampaiakan paubahan pahanyarnya matan $3 $2",
        "rcshowhideminor": "$1 pambabakan sapalih",
        "recentchangeslinked-feed": "Paubahan tarait",
        "recentchangeslinked-toolbox": "Paubahan tarait",
        "recentchangeslinked-title": "Paubahan nang tarait lawan \"$1\"",
-       "recentchangeslinked-summary": "Masukakan ngaran halaman gasan malihat paubahan pada halaman tarait matan atawa ka halaman ngintu (amun handak malihat angguta sabuting pilah, masukakan {{ns:category}}). Paubahan pada [[Special:Watchlist|daptar itihan Pian]] talihat <strong>dicitak kandal</strong>.",
-       "recentchangeslinked-page": "Ngaran halaman:",
-       "recentchangeslinked-to": "Tampaiakan paubahan matan halaman nang barait lawan halaman nang disurungakan",
+       "recentchangeslinked-summary": "Masukakan ngaran tungkaran gasan malihat paubahan pada tungkaran tarait matan atawa ka halaman ngintu (amun handak malihat angguta sabuting pilah, masukakan {{ns:category}}). Paubahan pada [[Special:Watchlist|daptar itihan Pian]] talihat <strong>dicitak kandal</strong>.",
+       "recentchangeslinked-page": "Ngaran tungkaran:",
+       "recentchangeslinked-to": "Tampaiakan paubahan matan tungkaran nang barait lawan tungkaran nang disurungakan",
        "upload": "Unggah barakas",
        "uploadbtn": "Hunggahakan barakas",
        "reuploaddesc": "Babulik ka furmulir paunggahan",
        "filehist-filesize": "Ukuran barakas",
        "filehist-comment": "Ulasan",
        "imagelinks": "Tautan barakas",
-       "linkstoimage": "{{PLURAL:$1|Halaman|$1 halaman}} nangini mamakai barakas ngini:",
+       "linkstoimage": "{{PLURAL:$1|Tungkaran|$1 tungkaran}} nangini mamakai barakas ngini:",
        "linkstoimage-more": "Labih daripada $1 {{PLURAL:$1|pamakaian halaman}} ka barakas ngini.\nDaptar barikut manampaiakan {{PLURAL:$1|halaman panambaian|$1 halaman panambaian}} nang mamakai barakas ngini haja.\nSabuting [[Special:WhatLinksHere/$2|daptar hibak]] tasadia.",
        "nolinkstoimage": "Kadada tutungkaran nang mamakai barakas ngini.",
        "morelinkstoimage": "Tiringi [[Special:WhatLinksHere/$1|tautan lagi]] ka barakas ngini.",
        "duplicatesoffile": "Barikut {{PLURAL:$1|barakas panggandaan|$1 babarakas panggandaan}} matan barakas ngini ([[Special:FileDuplicateSearch/$2|rarincian labih]]):",
        "sharedupload": "Barakas ini matan $1 wan mungkin dipuruk rangka-rangka gawian lain.",
        "sharedupload-desc-there": "Barakas ngini matan $1 wan pina dipuruk ulih rarangka-gawi lain.\nMuhun janaki [$2 tungkaran diskripsi barakas] gasan panjalasan labih.",
-       "sharedupload-desc-here": "Barakas ngini matan $1 wan pinanya dipakai ulih rangka-gawi lain.\nPamaparan ngini [$2 halaman diskripsi barakas] ditampaiakan di bawah.",
+       "sharedupload-desc-here": "Barakas ngini matan $1 wan pinanya dipakai ulih rangka-gawi lain.\nPamaparan ngini [$2 tungkaran panjalas barakas] ditampaiakan di bawah.",
        "filepage-nofile": "Kadada barakas bangaran ngini.",
        "filepage-nofile-link": "Kadada barakas bangaran ngini tasadia, tagal Pian kawa [$1 mahunggah ngini].",
        "uploadnewversion-linktext": "Buatakan bantuk nang labih hanyar matan barakas ini",
        "unusedtemplates": "Citakan nang kada dipuruk",
        "unusedtemplatestext": "Daptar barikut adalah samua tungkaran pada ngaran kamar {{ns:template}} nang kada dipuruk di tungkaran manapun.\nPariksa 'hulu tautan lain ka citakan itu sabalum mahapusnya.",
        "unusedtemplateswlh": "tautan lain",
-       "randompage": "Halaman babarang",
+       "randompage": "Tungkaran babarang",
        "randompage-nopages": "Kadada tungkaran pada {{PLURAL:$2||}}kamar ngaran ini: $1.",
        "randomredirect": "Paugahan babarang",
        "randomredirect-nopages": "Kada tadapat paugahan pada ngaran kamar \"$1\".",
        "listusers-creationsort": "Susun ulih tanggal paulahan",
        "usereditcount": "$1 {{PLURAL:$1|babakan|bababakan}}",
        "usercreated": "{{GENDER:$3|Diulah}} pada $1 pukul $2",
-       "newpages": "Halaman hanyar",
+       "newpages": "Tungkaran hanyar",
        "newpages-username": "Ngaran pamakai:",
        "ancientpages": "Tutungkaran panuhanya",
        "move": "Pindahakan",
        "prevpage": "Tungkaran sabalumnya ($1)",
        "allpagesfrom": "Manampaiakan tungkaran mulai matan:",
        "allpagesto": "Manampaiakan ujung pahabisan tungkaran:",
-       "allarticles": "Samunyaan halaman",
+       "allarticles": "Samunyaan tungkaran",
        "allinnamespace": "Sabarataan tutungkaran (ngaran-kamar $1)",
        "allpagessubmit": "Tulak",
        "allpagesprefix": "Tampilakan tutungkaran bamula lawan:",
        "sp-contributions-newonly": "Hanya tampaiakan babakan nang barupa paulahan halaman",
        "sp-contributions-submit": "Kikih",
        "whatlinkshere": "Tautan balik",
-       "whatlinkshere-title": "Halaman nang batautan ka ''$1''",
-       "whatlinkshere-page": "Halaman:",
-       "linkshere": "Halaman nangini batautan ka <strong>$2</strong>:",
+       "whatlinkshere-title": "Tungkaran nang batautan ka ''$1''",
+       "whatlinkshere-page": "Tungkaran:",
+       "linkshere": "Tungkaran nangini batautan ka <strong>$2</strong>:",
        "nolinkshere": "Kadada tutungkaran tataut ka '''$2'''.",
        "nolinkshere-ns": "Kadada tutungkaran tataut ka '''$2''' dalam ruang-ngaran nang dipilih.",
-       "isredirect": "halaman paugahan",
+       "isredirect": "tungkaran paugahan",
        "istemplate": "transklusi",
        "isimage": "tautan barakas",
        "whatlinkshere-prev": "$1 {{PLURAL:$1|sabalumnya|sabalumnya}}",
        "semiprotectedpagemovewarning": "'''Catatan:''' Tungkaran ngini sudah dilindungi laluai pamuruk tadaptar haja nang kawa mamindahakan ngini.\nLog masuk pauncitan disadiakan di bawah gasan rujukan:",
        "move-over-sharedrepo": "==Barakas ada==\n[[:$1]] ada pintangan panyimpanan babagi. Mamindahakan sabuah barakas ka judul ngini akan manulis-tindih barakas babagi.",
        "file-exists-sharedrepo": "Ngaran barakas nang dipilih sudah dipuruk pintangan panyimpanan babagi.\nMuhun pilih ngaran lain.",
-       "export": "Kirimi halaman ka luar",
+       "export": "Kirim tungkaran ka luar",
        "exporttext": "Pian kawa ma-ikspur naskah wan halam babakan matan sabuah tungkaran tartantu atawa sarangkai tutungkaran tabungkus dalam bantuk XML.\nNgini kawa di-impur dalam wiki lain mamuruk MediaWiki lung [[Special:Import|tungkaran impur]].\n\nHagan ma-ikspur tutungkaran, buati judul dalam kutak naskah di bawah, asa judul par garis, wan pilihi nang mana Pian handak ralatan tadamini nangkaitu jua samunyaan raralatan lawas, awan garis tungkaran halam, atawa ralatan tadamini awan panjalasan pasal babakan ta-uncit.\n\nDalam kasus pahanyarnya Pian kawa jua mamuruk sabuah tautanm gasan cuntuh [[{{#Special:Export}}/{{MediaWiki:Mainpage}}]] gasan tungkaran \"[[{{MediaWiki:Mainpage}}]]\".",
        "exportall": "Ekspor samunyaan tungkaran.",
        "exportcuronly": "Tamasuk ralatan tadamini haja, kada sahibakan halam",
        "import-logentry-upload-detail": "$1 {{PLURAL:$1|ralatan|raralatan}}",
        "import-logentry-interwiki-detail": "$1 {{PLURAL:$1|ralatan|raralatan}} matan $2",
        "javascripttest": "Mantis JavaScript",
-       "tooltip-pt-userpage": "Halaman {{GENDER:|pamakai Pian}}",
+       "tooltip-pt-userpage": "Tungkaran {{GENDER:|pamakai Pian}}",
        "tooltip-pt-anonuserpage": "Halaman pamakai IP Pian",
-       "tooltip-pt-mytalk": "Halaman {{GENDER:|pamandiran Pian}}",
+       "tooltip-pt-mytalk": "Tungkaran {{GENDER:|pamandiran Pian}}",
        "tooltip-pt-anontalk": "Pamandiran pasal bababakan matan alamat IP ngini",
        "tooltip-pt-preferences": "Kakatujuan {{GENDER:|Pian}}",
-       "tooltip-pt-watchlist": "Daptar halaman nang Pian itihi paubahannya",
+       "tooltip-pt-watchlist": "Daptar tungkaran nang Pian itihi paubahannya",
        "tooltip-pt-mycontris": "Daptar sumbangan {{GENDER:|Pian}}",
        "tooltip-pt-login": "Pian sabaiknya babuat ka dalam log; tagal ngini kada kawajiban pang",
        "tooltip-pt-logout": "Kaluar log",
        "tooltip-pt-createaccount": "Pian dianjurakan gasan maulah akun wan babuat log; tagal, hal ngintu kada wajib",
-       "tooltip-ca-talk": "Pamandiran pasal isi halaman",
-       "tooltip-ca-edit": "Babak halaman ngini",
+       "tooltip-ca-talk": "Pamandiran pasal isi tungkaran",
+       "tooltip-ca-edit": "Babak tungkaran ini",
        "tooltip-ca-addsection": "Mulai hagian hanyar",
-       "tooltip-ca-viewsource": "Halaman ngini dilindungi. Pian kawa manjanaki asal mulanya.",
-       "tooltip-ca-history": "Ralatan bahari halaman ngini",
+       "tooltip-ca-viewsource": "Tungkaran ngini dilindungi. Pian kawa maitihi asal mulanya.",
+       "tooltip-ca-history": "Ralatan bahari tungkaran ngini",
        "tooltip-ca-protect": "Lindungi tungkaran ini",
        "tooltip-ca-unprotect": "Ganti parlindungan tungkaran ngini",
        "tooltip-ca-delete": "Hapus tungkaran ini",
        "tooltip-ca-undelete": "Bulikakan babakan ka tungkaran ini sabalum tungkaran ini dihapus",
-       "tooltip-ca-move": "Pindahakan halaman ngini",
-       "tooltip-ca-watch": "Tambahi halaman ngini ka daptar itihan Pian",
+       "tooltip-ca-move": "Ugahakan tungkaran ngini",
+       "tooltip-ca-watch": "Tambahakan tungkaran ini ka daptar itihan Pian",
        "tooltip-ca-unwatch": "Buang tungkaran ngini matan daptar itihan Pian",
        "tooltip-search": "Gagai di {{SITENAME}}",
-       "tooltip-search-go": "Tulak ka sabuting halaman bangaran sama amun sudah ada",
-       "tooltip-search-fulltext": "Gagai halaman nang baisi naskah nang kaya ngini",
-       "tooltip-p-logo": "Ilangi halaman tatambaian",
-       "tooltip-n-mainpage": "Ilangi halaman tatambaian",
-       "tooltip-n-mainpage-description": "Ilangi halaman tatambaian",
+       "tooltip-search-go": "Tulak ka sabuting tungkaran bangaran sama amun sudah ada",
+       "tooltip-search-fulltext": "Gagai tungkaran nang baisi naskah nang kaya ngini",
+       "tooltip-p-logo": "Ilangi tungkaran tatambaian",
+       "tooltip-n-mainpage": "Ilangi tungkaran tatambaian",
+       "tooltip-n-mainpage-description": "Ilangi tungkaran tatambaian",
        "tooltip-n-portal": "Pasal rangka-gawian, apa nang kawa pian gawi, di mana gasan manggagai sasuatu",
        "tooltip-n-currentevents": "Gagai panjalasan pasal garamaan",
        "tooltip-n-recentchanges": "Daptar paubahan pahanyarnya dalam wiki",
-       "tooltip-n-randompage": "Tampaiakan babarang halaman",
+       "tooltip-n-randompage": "Tampaiakan sabuting tungkaran babarang",
        "tooltip-n-help": "Wadah manggagai patulung",
-       "tooltip-t-whatlinkshere": "Daptar samunyaan halaman wiki nang ada tautan ka sini",
-       "tooltip-t-recentchangeslinked": "Paubahan pahanyarnya dalam halaman nang baisi tautan tumatan halaman ngini",
+       "tooltip-t-whatlinkshere": "Daptar samunyaan tungkaran wiki nang ada tautan ka sini",
+       "tooltip-t-recentchangeslinked": "Paubahan pahanyarnya dalam tungkaran nang baisi tautan matan tungkaran ngini",
        "tooltip-feed-rss": "Kitihan RSS gasan tungkaran ini",
        "tooltip-feed-atom": "Kitihan Atum gasan tungkaran ngini",
        "tooltip-t-contributions": "Daptar sumbangan {{GENDER:$1|pamakai ngini}}",
        "tooltip-t-emailuser": "Kirimi suril ka {{GENDER:$1|pamakai ngini}}",
        "tooltip-t-upload": "Unggah barakas",
-       "tooltip-t-specialpages": "Daptar samunyaan halaman istimiwa",
-       "tooltip-t-print": "Vérsi citak halaman ngini",
-       "tooltip-t-permalink": "Tautan tatap ka ralatan halaman ngini",
-       "tooltip-ca-nstab-main": "Janaki halaman isi",
-       "tooltip-ca-nstab-user": "Janaki halaman pamakai",
+       "tooltip-t-specialpages": "Daptar samunyaan tungkaran istimiwa",
+       "tooltip-t-print": "Vérsi citak tungkaran ngini",
+       "tooltip-t-permalink": "Tautan tatap ka ralatan tungkaran ngini",
+       "tooltip-ca-nstab-main": "Tiringi isi tungkaran",
+       "tooltip-ca-nstab-user": "Tiringi tungkaran pamakai",
        "tooltip-ca-nstab-media": "Tiringi tungkaran media",
-       "tooltip-ca-nstab-special": "Ngini halaman istimiwa, kada kawa dibabak.",
+       "tooltip-ca-nstab-special": "Ngini tungkaran istimiwa, kada kawa dibabak.",
        "tooltip-ca-nstab-project": "Janaki halaman rangka gawian",
-       "tooltip-ca-nstab-image": "Janaki halaman barakas",
+       "tooltip-ca-nstab-image": "Tiringi tungkaran barakas",
        "tooltip-ca-nstab-mediawiki": "Janaki pasan sistem",
        "tooltip-ca-nstab-template": "Janaki citakan",
        "tooltip-ca-nstab-help": "Tiringi tungkaran patulung",
-       "tooltip-ca-nstab-category": "Janaki halaman pilah",
+       "tooltip-ca-nstab-category": "Tiringi tungkaran tumbung",
        "tooltip-minoredit": "Tandai ngini sabagai sabutik pambabakan sapalih",
        "tooltip-save": "Simpan paubahan Pian",
        "tooltip-preview": "Tilik paubahan Pian. Muhun pakai ngini sabalum manyimpan.",
        "tooltip-watchlistedit-raw-submit": "Hanyari daptar itihan",
        "tooltip-recreate": "Ulah pulang tungkaran biar gin suah dihapus",
        "tooltip-upload": "Mulai pangunggahan",
-       "tooltip-rollback": "\"Pambulik\" mamasahakan babakan-babakan di halaman ngini ka panyumbang pahabisan dalam satu kali kalik.",
+       "tooltip-rollback": "\"Pambulik\" mamasahakan babakan-babakan di tungkaran ngini ka panyumbang pahabisan dalam sakali kalik.",
        "tooltip-undo": "\"Bulikakan\" mawalangi ralatan ngini wan mambuka kutak pambabakan lawan mode tilik. Alasan kawa ditambahakan di kutak kasimpulan.",
        "tooltip-preferences-save": "Simpan kakatujuan",
        "tooltip-summary": "Buati sabuting kasimpulan handap",
        "pageinfo-hidden-categories": "{{PLURAL:$1|Tumbung}} tatukup ($1)",
        "pageinfo-templates": "{{PLURAL:$1|Citakan|Cicitakan}} nang ditransklusi ($1)",
        "pageinfo-transclusions": "{{PLURAL:$1|Tungkaran|Tutungkaran}} ditransklusikan pada ( $1 )",
-       "pageinfo-toolboxlink": "Panjalasan halaman",
+       "pageinfo-toolboxlink": "Panjalasan tungkaran",
        "pageinfo-redirectsto": "Ba-ugah ka",
        "pageinfo-redirectsto-info": "Maklumat",
        "pageinfo-contentpage": "Dirikin sabagai tungkaran isi",
        "metadata-help": "Barakas ngini mangandung panjalasan tambahan, mungkin ditambahakan ulih kudakan atawa paundai nang dipakai gasan maulah atawa digitalisasi barakas. Amun barakas ngini sudah diubah, parincian nang ada mungkin kada sapanuhnya sasuai lawan barakas nang diubah.",
        "metadata-expand": "Tampaiakan tambahan rincian",
        "metadata-collapse": "Sungkupakan tambahan rincian",
-       "metadata-fields": "Pancitraan metadata tadaptar dalam pasan ngini akan masuk dalam halaman pancitraan wayah tabel metadata tatukup. Nang lainnya cagaran babaku tatukup.\n* make\n* model\n* datetimeoriginal\n* exposuretime\n* fnumber\n* isospeedratings\n* focallength\n* artist\n* copyright\n* imagedescription\n* gpslatitude\n* gpslongitude\n* gpsaltitude",
+       "metadata-fields": "Pancitraan metadata tadaptar dalam pasan ngini akan masuk dalam tungkaran pancitraan wayah tabel metadata tatukup. Nang lainnya cagaran babaku tatukup.\n* make\n* model\n* datetimeoriginal\n* exposuretime\n* fnumber\n* isospeedratings\n* focallength\n* artist\n* copyright\n* imagedescription\n* gpslatitude\n* gpslongitude\n* gpsaltitude",
        "namespacesall": "samunyaan",
        "monthsall": "samunyaan",
        "confirmemail": "Yakinakan alamat suril",
        "fileduplicatesearch-result-1": "Barakas ''$1'' kada baisi panggandaan parsis.",
        "fileduplicatesearch-result-n": "Barakas ''$1'' baisi {{PLURAL:$2|1 panggandaan parsis|$2 papanggandaan parsis}}.",
        "fileduplicatesearch-noresults": "Kadada barakas bangaran ''$1'' taugai.",
-       "specialpages": "Halaman istimiwa",
+       "specialpages": "Tungkaran istimiwa",
        "specialpages-note-restricted": "* Tutungkaran istimiwa normal\n* <span class=\"mw-specialpagerestricted\">Tutungkaran istimiwa tabatas.</span>\n* <span class=\"mw-specialpagecached\">Tutungkaran istimiwa timbuluk (pinanya bakulat).</span>",
        "specialpages-group-maintenance": "Lapuran pamaliharaan",
        "specialpages-group-other": "Tungkaran istimiwa lainnya",
        "htmlform-submit": "Kirim",
        "htmlform-reset": "Bulikakan paubahan",
        "htmlform-selectorother-other": "Lain-lain",
-       "logentry-delete-delete": "$1 {{GENDER:$2|mahapus}} halaman $3",
+       "logentry-delete-delete": "$1 {{GENDER:$2|mahapus}} tungkaran $3",
        "logentry-delete-restore": "$1 {{GENDER:$2|mambulikakan}} halaman $3 ($4)",
        "logentry-delete-event": "$1 mangganti kakawaan dijanaki {{PLURAL:$5|sabuah log kajadian|$5 log kajadian}} pintangan $3: $4",
        "logentry-delete-revision": "$1 {{GENDER:$2|maubah}} tampaian {{PLURAL:$5|$5 ralatan}} di halaman $3: $4",
        "revdelete-uname-unhid": "ngaran pamakai kada disungkupakan",
        "revdelete-restricted": "Talamar pambatasan hagan pambakal-pambakal",
        "revdelete-unrestricted": "Buang pambatasan gasan pambakal-pambakal",
-       "logentry-move-move": "$1 {{GENDER:$2|mamindahakan}} halaman $3 ka $4",
+       "logentry-move-move": "$1 {{GENDER:$2|maugahakan}} tungkaran $3 ka $4",
        "logentry-move-move-noredirect": "$1 {{GENDER:$2|mamindahakan}} halaman $3 ka $4 kada pakai maulah paugahan",
        "logentry-move-move_redir": "$1 {{GENDER:$2|mamindahakan}} halaman $3 ka $4 manimpa paugahan lawas",
        "logentry-move-move_redir-noredirect": "$1 diugah tungkaran $3 ka $4 lung sabuah paugahan awan-kada maninggalakan sabuah paugahan",
index a315d3f..c7d5987 100644 (file)
        "watcherrortext": "\"$1\" এর নজরতালিকা পরিবর্তনের সময় একটি ত্রুটি হয়েছে।",
        "enotif_reset": "সমস্ত পাতা দেখা হয়েছে হিসেবে চিহ্নিত করুন",
        "enotif_impersonal_salutation": "{{SITENAME}} ব্যবহারকারী",
-       "enotif_subject_deleted": "{{SITENAME}} এর $1 পাতাটি {{gender:$2|$2}} অপসারণ করেছেন",
+       "enotif_subject_deleted": "{{SITENAME}} এর $1 পাতাটি $2 {{GENDER:$2|অপসারণ করেছেন}}",
        "enotif_subject_created": "{{SITENAME}} এর $1 পাতাটি {{gender:$2|$2}} তৈরী করেছেন",
        "enotif_subject_moved": "{{SITENAME}} এর $1 পাতাটি {{gender:$2|$2}} স্থানান্তর করেছেন",
        "enotif_subject_restored": "{{SITENAME}} এর $1 পাতাটি {{gender:$2|$2}} পুনরায় ফিরিয়ে এনেছেন",
        "enotif_subject_changed": "{{SITENAME}} এর $1 পাতাটি {{gender:$2|$2}} পরিবর্তন করেছেন",
-       "enotif_body_intro_deleted": "{{SITENAME}} এর $1 পাতাটি {{gender:$2|$2}} $PAGEEDITDATE তারিখে অপসারণ করেছেন, বিস্তারিত $3।",
+       "enotif_body_intro_deleted": "{{SITENAME}} এর $1 পাতাটি $2 $PAGEEDITDATE তারিখে {{GENDER:$2|অপসারণ করেছেন}}, বিস্তারিত $3।",
        "enotif_body_intro_created": "{{SITENAME}} এর $1 পাতাটি {{gender:$2|$2}} $PAGEEDITDATE তারিখে তৈরী করেছেন, বর্তমান সংস্করণ দেখুন এখানে $3।",
        "enotif_body_intro_moved": "{{SITENAME}} এর $1 পাতাটি {{gender:$2|$2}} $PAGEEDITDATE তারিখে স্থানান্তর করেছেন, বর্তমান সংস্করণ দেখুন এখানে $3।",
        "enotif_body_intro_restored": "{{SITENAME}} এর $1 পাতাটি {{gender:$2|$2}} $PAGEEDITDATE আগের অবস্থায় ফিরিয়ে এনেছেন, বর্তমান সংস্করণ দেখুন এখানে $3।",
index df0716f..c08494d 100644 (file)
        "recentchangeslinked-feed": "Srodne izmjene",
        "recentchangeslinked-toolbox": "Srodne izmjene",
        "recentchangeslinked-title": "Srodne promjene sa \"$1\"",
-       "recentchangeslinked-summary": "Upišite naziv stranice da biste vidjeli promjene koje vode na ili sa te stranice. (Da biste vidjeli članove neke kategorije, upišite Kategorija:Naziv kategorije). Promjene na stranicama na [[Special:Watchlist|spisku praćenja]] istaknute su <strong>podebljanim slovima</strong>.",
+       "recentchangeslinked-summary": "Upišite naziv stranice da biste vidjeli izmjene koje vode na ili sa te stranice. (Da biste vidjeli članove neke kategorije, upišite {{ns:category}}:Naziv kategorije). Izmjene na stranicama na [[Special:Watchlist|spisku praćenja]] istaknute su <strong>podebljanim slovima</strong>.",
        "recentchangeslinked-page": "Naslov stranice:",
        "recentchangeslinked-to": "Prikaži izmjene stranica koji su povezane s datom stranicom",
        "recentchanges-page-added-to-category": "Stranica [[:$1]] dodana je u kategoriju",
index 0fcfa49..04f2722 100644 (file)
        "rcfilters-filter-showlinkedto-label": "Zobrazit změny stránek, které sem odkazují",
        "rcfilters-filter-showlinkedto-option-label": "<strong>Stránky odkazující na</strong> vybranou stránku",
        "rcfilters-target-page-placeholder": "Zadejte název stránky (nebo kategorie)",
+       "rcfilters-allcontents-label": "Všechny obsahové",
+       "rcfilters-alldiscussions-label": "Všechny diskusní",
        "rcnotefrom": "Níže {{PLURAL:$5|je změna|jsou změny}} od <strong>$3, $4</strong> ({{PLURAL:$1|zobrazena|zobrazeny|zobrazeno}} nejvýše <strong>$1</strong>).",
        "rclistfromreset": "Obnovit výběr data",
        "rclistfrom": "Ukázat nové změny, počínaje od $2, $3",
        "block-log-flags-angry-autoblock": "rozšířené automatické blokování zapnuto",
        "block-log-flags-hiddenname": "uživatelské jméno skryto",
        "range_block_disabled": "Blokování rozsahů IP adres je zakázáno.",
+       "ipb-prevent-user-talk-edit": "U částečných bloků musí být editace vlastní uživatelské diskuse povolena, pokud blok nezahrnuje omezení jmenného prostoru {{ns:3}}.",
        "ipb_expiry_invalid": "Neplatný čas vypršení.",
        "ipb_expiry_old": "Čas vypršení je v minulosti.",
        "ipb_expiry_temp": "Blokování skrytých uživatelských jmen by měla být trvalá.",
        "move-subpages": "Přesunout i podstránky (maximálně $1)",
        "move-talk-subpages": "Přesunout i podstránky diskusní stránky (maximálně $1)",
        "movepage-page-exists": "Stránka $1 již existuje a nemůže být automaticky přepsána.",
+       "movepage-source-doesnt-exist": "Stránka $1 neexsituje a nelze ji přesunout.",
        "movepage-page-moved": "Stránka $1 byla přesunuta na $2.",
        "movepage-page-unmoved": "Stránka $1 nemůže být přesunuta na $2.",
        "movepage-max-pages": "{{PLURAL:$1|Byla přesunuta maximálně povolená jedna stránka|Byly přesunuty maximálně povolené $1 stránky|Bylo přesunuto maximálně povolených $1 stránek}}, více jich už automaticky přesunuto nebude.",
        "delete_and_move_reason": "Smazáno pro umožnění přesunu z „[[$1]]“",
        "selfmove": "Název je stejný; nelze stránku přesunout na sebe samu.",
        "immobile-source-namespace": "Stránky ve jmenném prostoru „$1“ nelze přesouvat",
+       "immobile-source-namespace-iw": "Z této wiki nelze přesouvat stránky na jiných wiki.",
        "immobile-target-namespace": "Stránky nelze přesouvat do jmenného prostoru „$1“",
        "immobile-target-namespace-iw": "Mezijazykový odkaz není validní cíl při přesouvání stránky.",
        "immobile-source-page": "Tuto stránku nelze přesouvat.",
        "immobile-target-page": "Stránku nelze přesunout na zadaný název.",
+       "movepage-invalid-target-title": "Požadovaný název není platný.",
        "bad-target-model": "Požadovaný cíl používá jiný model obsahu. Nelze převést $1 na $2.",
        "imagenocrossnamespace": "Nelze přesunout mimo jmenný prostor Soubor:",
        "nonfile-cannot-move-to-file": "Do jmenného prostoru {{ns:file}} nelze přesouvat stránky nepřináležející k souboru",
        "permanentlink": "Trvalý odkaz",
        "permanentlink-revid": "ID revize",
        "permanentlink-submit": "Přejít na revizi",
+       "newsection": "Nová sekce",
+       "newsection-page": "Cílová stránka",
+       "newsection-submit": "Jít na stránku",
        "dberr-problems": "Promiňte! Tento server má v tuto chvíli technické problémy.",
        "dberr-again": "Zkuste několik minut počkat a poté znovu načíst stránku.",
        "dberr-info": "(Nelze se připojit k databázi: $1)",
        "restrictionsfield-help": "Jedna IP adresa nebo CIDR rozsah na řádek. Všechno povolíte pomocí:<pre>0.0.0.0/0\n::/0</pre>",
        "edit-error-short": "Chyba: $1",
        "edit-error-long": "Chyby:\n\n$1",
+       "specialmute": "Ztlumení",
+       "specialmute-success": "Požadované ztlumení bylo upraveno. Všechny ztlumené uživatele najdete ve [[Special:Preferences|svém nastavení]].",
+       "specialmute-submit": "Potvrdit",
+       "specialmute-label-mute-email": "Ignorovat e-maily od tohoto uživatele",
+       "specialmute-header": "Vyberte si prosím požadované ztlumení uživatele <b>{{BIDI:[[User:$1|$1]]}}</b>.",
+       "specialmute-error-invalid-user": "Požadované uživatelské jméno nebylo nalezeno.",
+       "specialmute-error-no-options": "Funkce ztlumení uživatele není dostupná. Důvodem může být: neověřili jste svou e-mailovou adresu nebo administrátor wiki na této wiki vypnul e-mailové funkce nebo listinu zakázaných e-mailů.",
+       "specialmute-email-footer": "Spravovat nastavení e-mailů od uživatele {{BIDI:$2}} můžete na <$1>.",
+       "specialmute-login-required": "Pro změnu ztlumení se musíte přihlásit.",
+       "mute-preferences": "Nastavení ztlumení",
        "revid": "revize $1",
        "pageid": "Stránka s ID $1",
        "interfaceadmin-info": "$1\n\nOprávnění editovat celoprojektové soubory s CSS/JS/JSON bylo nedávno odděleno z oprávnění <code>editinterface</code>. Pokud nerozumíte, proč se vám zobrazuje tato chyba, vizte [[mw:MediaWiki_1.32/interface-admin]].",
index 0f8fa08..3bc553c 100644 (file)
        "systemblockedtext": "Dein Benutzername oder deine IP-Adresse wurde von MediaWiki automatisch gesperrt.\nDer angegebene Grund ist:\n\n:<em>$2</em>\n\n* Beginn der Sperre: $8\n* Ablauf der Sperre: $6\n* Sperre betrifft: $7\n\nDeine aktuelle IP-Adresse ist $3.\nBitte gib alle oben stehenden Details in jeder Anfrage an.",
        "blockednoreason": "keine Begründung angegeben",
        "blockedtext-composite": "<strong>Dein Benutzername oder deine IP-Adresse wurde gesperrt.</strong>\n\nDer Angegebene Grund ist:\n\n:<em>$2</em>\n\n* Beginn der Sperre: $8\n* Ablauf der längsten Sperre: $6\n\n* $5\n\nDeine aktuelle IP-Adresse ist $3.\nBitte gib alle oben stehenden Details in jeder Anfrage an.",
+       "blockedtext-composite-ids": "Relevante Sperr-IDs: $1 (deine IP-Adresse könnte ebenfalls von einem Blacklisten-Eintrag betroffen sein)",
        "blockedtext-composite-no-ids": "Deine IP-Adresse taucht in mehreren Sperrlisten auf",
        "blockedtext-composite-reason": "Es gibt mehrere Sperren gegen dein Benutzerkonto und/oder deine IP-Adresse",
        "whitelistedittext": "Du musst dich $1, um Seiten bearbeiten zu können.",
        "search-interwiki-more": "(weitere)",
        "search-interwiki-more-results": "Weitere Ergebnisse",
        "search-relatedarticle": "Verwandte",
+       "search-invalid-sort-order": "Die Sortierreihenfolge von $1 wurde nicht erkannt und deshalb wird die Standardsortierung angewendet. Gültige Sortierreihenfolgen sind: $2",
+       "search-unknown-profile": "Das Suchprofil von $1 wurde nicht erkannt und deshalb wird das Standardsuchprofil angewendet.",
        "searchrelated": "verwandt",
        "searchall": "alle",
        "showingresults": "Hier {{PLURAL:$1|ist '''1''' Ergebnis|sind '''$1''' Ergebnisse}}, beginnend mit Nummer '''$2.'''",
        "right-editmyusercss": "Eigene Benutzer-CSS-Dateien bearbeiten",
        "right-editmyuserjson": "Eigene Benutzer-JSON-Dateien bearbeiten",
        "right-editmyuserjs": "Eigene Benutzer-JavaScript-Dateien bearbeiten",
+       "right-editmyuserjsredirect": "Eigene Benutzer-JavaScript-Dateien bearbeiten, die Weiterleitungen sind",
        "right-viewmywatchlist": "Eigene Beobachtungsliste ansehen",
        "right-editmywatchlist": "Eigene Beobachtungsliste bearbeiten. Einige Aktionen ermöglichen das Hinzufügen von Seiten ohne dieses Recht.",
        "right-viewmyprivateinfo": "Eigene private Daten ansehen (beispielsweise E-Mail-Adresse, bürgerlicher Name)",
        "action-editmyusercss": "eigene Benutzer-CSS-Dateien zu bearbeiten",
        "action-editmyuserjson": "eigene Benutzer-JSON-Dateien zu bearbeiten",
        "action-editmyuserjs": "eigene Benutzer-JavaScript-Dateien zu bearbeiten",
+       "action-editmyuserjsredirect": "eigene Benutzer-JavaScript-Dateien, die Weiterleitungen sind, zu bearbeiten",
        "action-viewsuppressed": "vor jedem Benutzer versteckte Versionen anzusehen",
        "action-hideuser": "Benutzernamen zu sperren und zu verbergen",
        "action-ipblock-exempt": "IP-Sperren, automatische Sperren und Bereichssperren zu umgehen",
        "rcfilters-clear-all-filters": "Alle Filter löschen",
        "rcfilters-show-new-changes": "Neue Änderungen seit $1 ansehen",
        "rcfilters-search-placeholder": "Änderungen filtern (Menü oder Suche für den Filternamen verwenden)",
+       "rcfilters-search-placeholder-mobile": "Filter",
        "rcfilters-invalid-filter": "Ungültiger Filter",
        "rcfilters-empty-filter": "Keine aktiven Filter. Es werden alle Beiträge angezeigt.",
        "rcfilters-filterlist-title": "Filter",
        "changecontentmodel": "Inhaltsmodell einer Seite ändern",
        "changecontentmodel-legend": "Inhaltsmodell ändern",
        "changecontentmodel-title-label": "Seitentitel",
+       "changecontentmodel-current-label": "Aktuelles Inhaltsmodell:",
        "changecontentmodel-model-label": "Neues Inhaltsmodell",
        "changecontentmodel-reason-label": "Grund:",
        "changecontentmodel-submit": "Ändern",
        "move-subpages": "Unterseiten verschieben (bis zu $1)",
        "move-talk-subpages": "Unterseiten der Diskussionsseite verschieben (bis zu $1)",
        "movepage-page-exists": "Die Seite „$1“ ist bereits vorhanden und kann nicht automatisch überschrieben werden.",
+       "movepage-source-doesnt-exist": "Die Seite $1 existiert nicht und kann nicht verschoben werden.",
        "movepage-page-moved": "Die Seite „$1“ wurde nach „$2“ verschoben.",
        "movepage-page-unmoved": "Die Seite „$1“ konnte nicht nach „$2“ verschoben werden.",
        "movepage-max-pages": "Es wurde die Maximalanzahl von {{PLURAL:$1|einer Seite|$1 Seiten}} verschoben. Alle weiteren Seiten können nicht automatisch verschoben werden.",
        "delete_and_move_reason": "Gelöscht, um Platz für die Verschiebung von „[[$1]]“ zu machen",
        "selfmove": "Der Titel ist gleich.\nEine Seite kann nicht auf sich selbst verschoben werden.",
        "immobile-source-namespace": "Seiten des „$1“-Namensraums können nicht verschoben werden",
+       "immobile-source-namespace-iw": "Seiten auf anderen Wikis können nicht von diesem Wiki aus verschoben werden.",
        "immobile-target-namespace": "Seiten können nicht in den „$1“-Namensraum verschoben werden",
        "immobile-target-namespace-iw": "Interwiki-Link ist kein gültiges Ziel für Seitenverschiebungen.",
        "immobile-source-page": "Diese Seite ist nicht verschiebbar.",
        "immobile-target-page": "Es kann nicht auf diese Zielseite verschoben werden.",
+       "movepage-invalid-target-title": "Der gewünschte Seitenname ist ungültig.",
        "bad-target-model": "Die gewünschte Zielseite verwendet ein abweichendes Inhaltsmodell. Das Inhaltsmodell $1 kann nicht in das Inhaltsmodell $2 umgewandelt werden.",
        "imagenocrossnamespace": "Dateien können nicht aus dem {{ns:file}}-Namensraum heraus verschoben werden",
        "nonfile-cannot-move-to-file": "Nichtdateien können nicht in den {{ns:file}}-Namensraum hinein verschoben werden",
index 7125f9c..8a99dd9 100644 (file)
        "revertmerge": "Αναίρεση συγχώνευσης",
        "mergelogpagetext": "Παρακάτω είναι μια λίστα με τις πιο πρόσφατες συγχωνεύσεις ιστορικού μιας σελίδας σε άλλο.",
        "history-title": "Ιστορικό αναθεωρήσεων της σελίδας «$1»",
-       "difference-title": "Διαφορά μεταξύ των αναθεωρήσεων του \"$1\"",
+       "difference-title": "Διαφορά μεταξύ των αναθεωρήσεων του «$1»",
        "difference-title-multipage": "Διαφορά μεταξύ των σελίδων \"$1\" και \"$2\"",
        "difference-multipage": "(Διαφορές μεταξύ των σελίδων)",
        "lineno": "Γραμμή $1:",
index 814e6d7..f5af730 100644 (file)
        "rcfilters-filter-showlinkedto-label": "Mostrar cambios en páginas que enlazan a",
        "rcfilters-filter-showlinkedto-option-label": "<strong>Páginas que enlazan hacia</strong> la página seleccionada",
        "rcfilters-target-page-placeholder": "Escribe un nombre de página (o de categoría)",
+       "rcfilters-alldiscussions-label": "Todas las discusiones",
        "rcnotefrom": "Debajo {{PLURAL:$5|aparece el cambio|aparecen los cambios}} desde <strong>$3, $4</strong> (se muestran hasta <strong>$1</strong>).",
        "rclistfromreset": "Restablecer selección de fecha",
        "rclistfrom": "Mostrar cambios nuevos desde las $2 del $3",
        "move-subpages": "Intentar trasladar las subpáginas (hasta $1)",
        "move-talk-subpages": "Intentar trasladar las subpáginas de discusión (hasta $1)",
        "movepage-page-exists": "La página $1 ya existe, por lo que no se puede cambiarle el nombre automáticamente.",
+       "movepage-source-doesnt-exist": "La página $1 no existe por lo que no puede ser trasladada.",
        "movepage-page-moved": "La página $1 ha sido trasladada a $2.",
        "movepage-page-unmoved": "La página $1 no se ha podido trasladar a $2.",
        "movepage-max-pages": "Se {{PLURAL:$1|ha trasladado un máximo de una página|han trasladado un máximo de $1 páginas}}, y no van a trasladarse más automáticamente.",
index 821a48c..954ba46 100644 (file)
        "exif-photometricinterpretation-3": "Paletă",
        "exif-photometricinterpretation-4": "Mască de transparență",
        "exif-photometricinterpretation-5": "Separat (Probabil CMYK)",
+       "exif-photometricinterpretation-8": "CIE L*a*b*",
+       "exif-photometricinterpretation-9": "CIE L*a*b* (codare ICC)",
+       "exif-photometricinterpretation-10": "CIE L*a*b* (codare ITU)",
        "exif-unknowndate": "Dată necunoscută",
        "exif-orientation-1": "Normală",
        "exif-orientation-2": "Oglindită orizontal",
index e7608a6..473a03a 100644 (file)
        "exif-bitspersample": "Bitůw na průbka",
        "exif-compression": "Metoda kompresyji",
        "exif-photometricinterpretation": "Interpretacyjo fotůmetryčno",
-       "exif-orientation": "Uorjyntacyjo uobrozu",
+       "exif-orientation": "Ôriyntacyjŏ",
        "exif-samplesperpixel": "Průbek na piksel",
        "exif-planarconfiguration": "Rozkuod danych",
        "exif-ycbcrsubsampling": "Podprůbkowańe Y do C",
        "exif-ycbcrpositioning": "Rozmješčyńy Y i C",
-       "exif-xresolution": "Rozdźelčość w poźůmje",
-       "exif-yresolution": "Rozdźelčość w pjůńy",
+       "exif-xresolution": "Rozdzielczość we poziōmie",
+       "exif-yresolution": "Rodzielczość we piōnie",
        "exif-stripoffsets": "Přesůńjyńće pasůw uobrazu",
        "exif-rowsperstrip": "Ličba wjeršy na pas uobrazu",
        "exif-stripbytecounts": "Ličba bajtůw na pas uobrazu",
        "exif-primarychromaticities": "Kolory třech barw guůwnych",
        "exif-ycbcrcoefficients": "Maćeř wspůučynńikůw transformacyji barw ze RGB na YCbCr",
        "exif-referenceblackwhite": "Wartość půnktu uodńyśyńo čerńi i bjeli",
-       "exif-datetime": "Data i čas modyfikacyji plika",
+       "exif-datetime": "Data i czas modyfikacyje zbioru",
        "exif-imagedescription": "Titel uobrozka",
        "exif-make": "Producynt fotoaparatu",
        "exif-model": "Model fotoaparatu",
-       "exif-software": "Ůžyte uoprůgramowańy",
+       "exif-software": "Użyte ôprogramowanie",
        "exif-artist": "Autor",
        "exif-copyright": "Wuaśćićel praw autorskych",
-       "exif-exifversion": "Wersyja standardu Exif",
+       "exif-exifversion": "Wersyjŏ Exif",
        "exif-flashpixversion": "Uobsůgiwano wersyjo Flashpix",
-       "exif-colorspace": "Přestřyń kolorůw",
+       "exif-colorspace": "Przestrzyń farbōw",
        "exif-componentsconfiguration": "Značyńy skuadowych",
        "exif-compressedbitsperpixel": "Skůmpresowanych bitůw na piksel",
        "exif-pixelxdimension": "Prawidłowa szyrzka uobrozu",
        "exif-pixelydimension": "Prawidłowo wyżka uobrozu",
        "exif-usercomment": "Kůmyntoř užytkowńika",
        "exif-relatedsoundfile": "Powjůnzany plik audjo",
-       "exif-datetimeoriginal": "Data i čas utwořyńo uoryginouu",
-       "exif-datetimedigitized": "Data i čas zeskanowańo",
+       "exif-datetimeoriginal": "Data i czas stworzyniŏ ôryginału",
+       "exif-datetimedigitized": "Data i czas digitalizacyje",
        "exif-subsectime": "Data i čas modyfikacyji pliku – uuamki sekůnd",
        "exif-subsectimeoriginal": "Data i čas utwořyńo uoryginouu – uuamki sekůnd",
        "exif-subsectimedigitized": "Data i čas zeskanowańo – uuamki sekůnd",
        "exif-gpsdifferential": "Korekcyjo růžńicy GPS",
        "exif-compression-1": "ńyskůmpresowany",
        "exif-unknowndate": "ńyznano data",
-       "exif-orientation-1": "normalno",
+       "exif-orientation-1": "Normalno",
        "exif-orientation-2": "odbiće we źřadle w poźůmje",
        "exif-orientation-3": "uobroz uobrůcůny uo 180°",
        "exif-orientation-4": "uodbiće we źřadle w pjůńy",
index 2cc372a..5752bee 100644 (file)
@@ -16,6 +16,8 @@
        "exif-samplesperpixel": "Төс өлешләре саны",
        "exif-xresolution": "Ятма ачыклык",
        "exif-yresolution": "Асма ачыклык",
+       "exif-rowsperstrip": "Бер бүлемдә юллар саны",
+       "exif-stripbytecounts": "Кысылган бүлемдә байтлар саны",
        "exif-datetime": "Файл үзгәреше датасы һәм вакыты",
        "exif-imagedescription": "Сурәт атамасы",
        "exif-make": "Камера җитештерүчесе",
@@ -23,7 +25,7 @@
        "exif-software": "Кулланылган программа",
        "exif-artist": "Автор",
        "exif-copyright": "Авторлык хокукы иясе",
-       "exif-exifversion": "Exif Ñ\8eÑ\80амаÑ\81ы",
+       "exif-exifversion": "Exif Ñ\87Ñ\8bгаÑ\80Ñ\8bÑ\88ы",
        "exif-flashpixversion": "FlashPix ярашлы юрамасы",
        "exif-colorspace": "Төсләр киңлеге",
        "exif-componentsconfiguration": "Төсләр төзелешенең конфигурациясе",
        "exif-gpslongitude": "Озынлык",
        "exif-gpsaltituderef": "Югарылык индексы",
        "exif-gpsaltitude": "Югарылык",
-       "exif-gpstimestamp": "UTC буенча вакыт",
+       "exif-gpstimestamp": "GPS вакыты (атом сәгате)",
        "exif-gpssatellites": "Кулланылган иярченнәр тасвирламасы",
        "exif-gpsstatus": "Алгычның статусы һәм төшерү вакыты",
        "exif-gpsmeasuremode": "Урнашуны билгеләү ысулы",
        "exif-gpsdop": "Билгеләүнең дөреслеге",
        "exif-gpsspeedref": "Тизлекне исәпләү берәмлеге",
        "exif-gpsspeed": "Хәрәкәт тизлеге",
-       "exif-gpsdatestamp": "Дата",
+       "exif-gpsdatestamp": "GPS датасы",
        "exif-keywords": "Иң мөһиме",
+       "exif-headline": "Башисем",
        "exif-source": "Чыганак",
+       "exif-contact": "Элемтә өчен мәгълүмат",
        "exif-writer": "Язучы",
        "exif-languagecode": "Тел",
        "exif-iimversion": "IIM юрамасы",
        "exif-iimcategory": "Төркем",
        "exif-iimsupplementalcategory": "Өстәмә төркемнәр",
+       "exif-datetimereleased": "Чыгарылу вакыты",
        "exif-identifier": "Идентификатор",
        "exif-label": "Билгеләү",
        "exif-copyrighted": "Авторлык хокукы халәте",
        "exif-copyrightowner": "Авторлык хокукы иясе",
        "exif-usageterms": "Куллану шартлары",
+       "exif-photometricinterpretation-0": "Ак һәм кара (ак — 0)",
+       "exif-photometricinterpretation-1": "Ак һәм кара (кара — 0)",
+       "exif-unknowndate": "Билгесез вакыт",
        "exif-orientation-1": "Гадәти",
        "exif-orientation-3": "180° ка борылган",
+       "exif-planarconfiguration-1": "«chunky» форматы",
+       "exif-planarconfiguration-2": "«planar» форматы",
        "exif-componentsconfiguration-0": "барлыкта юк",
        "exif-exposureprogram-0": "Билгесез",
        "exif-exposureprogram-1": "Кулдан җайлау режимы",
        "exif-meteringmode-0": "Билгесез",
        "exif-meteringmode-1": "Уртача",
        "exif-meteringmode-3": "Нокталы",
-       "exif-meteringmode-4": "Ð\9cÑ\83лÑ\8cÑ\82инокталы",
+       "exif-meteringmode-4": "Ð\9aүп нокталы",
        "exif-meteringmode-5": "Паттернлы",
        "exif-meteringmode-6": "Өлешләтә",
        "exif-meteringmode-255": "Башка",
        "exif-gpsdop-moderate": "Уртача ($1)",
        "exif-gpsdop-fair": "Ярыйсы ($1)",
        "exif-gpsdop-poor": "Начар ($1)",
+       "exif-objectcycle-a": "Иртән генә",
+       "exif-objectcycle-p": "Кичен генә",
+       "exif-objectcycle-b": "Иртән һәм кичен",
        "exif-dc-date": "Дата(лар)",
        "exif-dc-publisher": "Нәшир",
        "exif-dc-relation": "Бәйле медиа",
        "exif-dc-type": "Медиа төре",
        "exif-rating-rejected": "Кире кагылды",
        "exif-isospeedratings-overflow": "65535 тән күбрәк",
+       "exif-iimcategory-fin": "Экономика һәм бизнес",
+       "exif-iimcategory-evn": "Әйләнә-тирәдәге мохит",
        "exif-iimcategory-hth": "Сәламәтлек",
        "exif-iimcategory-lab": "Хезмәт",
+       "exif-iimcategory-pol": "Сәясәт",
+       "exif-iimcategory-rel": "Дин һәм иман",
+       "exif-iimcategory-sci": "Фән һәм техника",
+       "exif-iimcategory-spo": "Спорт",
        "exif-iimcategory-wea": "Һава торышы",
        "exif-urgency-normal": "Гадәти ($1)",
        "exif-urgency-low": "Түбән ($1)",
index 0a03da6..b0aced5 100644 (file)
        "revdelete-unsuppress": "حذف محدودیت‌ها در بازبینی‌های ترمیم‌شده",
        "revdelete-log": "دلیل:",
        "revdelete-submit": "اعمال بر {{PLURAL:$1|نسخهٔ|نسخه‌های}} انتخاب شده",
-       "revdelete-success": "Ù¾Û\8cداÛ\8cÛ\8c Ù\86سخÙ\87 Ø¨Ù\87â\80\8cرÙ\88ز شد.",
+       "revdelete-success": "Ù¾Û\8cداÛ\8cÛ\8c Ù\86سخÙ\87 Ø±Ù\88زآÙ\85د شد.",
        "revdelete-failure": "'''پیدایی نسخه‌ها قابل به روز کردن نیست:'''\n$1",
        "logdelete-success": "تغییر پیدایی مورد انجام شد.",
        "logdelete-failure": "'''پیدایی سیاهه‌ها قابل تنظیم نیست:'''\n$1",
index 84e08b8..8299b19 100644 (file)
        "passwordreset-ignored": "Salasanan palauttamista ei käsitelty. Ehkä tarjoajaa ei ollut määritetty?",
        "passwordreset-invalidemail": "Virheellinen sähköpostiosoite",
        "passwordreset-nodata": "Käyttäjätunnusta ja salasanaa ei annettu",
-       "changeemail": "Muuta tai poista sähköpostiosoite",
+       "changeemail": "Muuta tai poista E-posti atressi",
        "changeemail-header": "Täydennä tämä lomake, jolla voit muuttaa sähköpostiosoitettasi. Jos haluat poistaa sähköpostiosoitteesi kokonaan tunnuksesi yhteydestä, älä kirjoita uudeksi osoitteeksi mitään vaan jätä se tyhjäksi.",
        "changeemail-no-info": "Tämän sivun käyttö edellyttää sisäänkirjautumista.",
        "changeemail-oldemail": "Nykyinen sähköpostiosoite:",
        "prefs-watchlist-managetokens": "Hallitse avaimia",
        "prefs-misc": "Muut",
        "prefs-resetpass": "Muuta salasana",
-       "prefs-changeemail": "Muuta tai poista sähköpostiosoite",
+       "prefs-changeemail": "Muuta tai poista E-posti atressi",
        "prefs-setemail": "Aseta sähköpostiosoite",
        "prefs-email": "Sähköpostiasetukset",
        "prefs-rendering": "Ulkoasu",
        "rcfilters-clear-all-filters": "Tyhjennä kaikki suodattimet",
        "rcfilters-show-new-changes": "Näytä uudet muutokset $1 alkaen",
        "rcfilters-search-placeholder": "Suodata muutoksia (käytä valikkoa tai etsi suodattimen nimeä)",
+       "rcfilters-search-placeholder-mobile": "Suodattimet",
        "rcfilters-invalid-filter": "Kelvoton suodatin",
        "rcfilters-empty-filter": "Ei aktiivisia suodattimia. Kaikki muutokset näytetään.",
        "rcfilters-filterlist-title": "Suodattimet",
        "rcfilters-filter-showlinkedto-label": "Näytä muutokset sivuilla, joista on linkki sivulle",
        "rcfilters-filter-showlinkedto-option-label": "<strong>Sivut, jotka linkittävät</strong> valitulle sivulle",
        "rcfilters-target-page-placeholder": "Anna sivun nimi (tai luokka)",
+       "rcfilters-alldiscussions-label": "Kaikki keskustelut",
        "rcnotefrom": "Alla ovat muutokset <strong>$3, $4</strong> lähtien. (Enintään <strong>$1</strong> näytetään.)",
        "rclistfromreset": "Tyhjennä ajankohdan valinta",
        "rclistfrom": "Näytä uudet muutokset $3 kello $2 alkaen",
        "permanentlink": "Pysyvä linkki",
        "permanentlink-revid": "Versiotunniste",
        "permanentlink-submit": "Mene sivuversioon",
+       "newsection-submit": "Siirry sivulle",
        "dberr-problems": "Tällä sivustolla on teknisiä ongelmia.",
        "dberr-again": "Odota hetki ja lataa sivu uudelleen.",
        "dberr-info": "(Tietokantaan ei saada yhteyttä: $1)",
index 27ac340..5cae831 100644 (file)
        "rcfilters-filter-showlinkedto-label": "Montrer les modifications des pages pointant vers",
        "rcfilters-filter-showlinkedto-option-label": "<strong>Pages pointant vers</strong> la page sélectionnée",
        "rcfilters-target-page-placeholder": "Entrer un nom de page (ou une catégorie)",
+       "rcfilters-allcontents-label": "Tous les contenus",
+       "rcfilters-alldiscussions-label": "Toutes les discussions",
        "rcnotefrom": "Ci-dessous {{PLURAL:$5|la modification effectuée|les modifications effectuées}} depuis le <strong>$3, $4</strong> (affichées jusqu’à <strong>$1</strong>).",
        "rclistfromreset": "Réinitialiser la sélection de la date",
        "rclistfrom": "Afficher les nouvelles modifications depuis le $3 à $2",
        "move-subpages": "Renommer les sous-pages (maximum $1)",
        "move-talk-subpages": "Renommer les sous-pages de la page de discussion (maximum $1)",
        "movepage-page-exists": "La page $1 existe déjà et ne peut pas être écrasée automatiquement.",
-       "movepage-source-doesnt-exist": "La page $1 n’existe pas et n’a pas pu être supprimée.",
+       "movepage-source-doesnt-exist": "La page $1 n’existe pas et n’a pas pu être renommée.",
        "movepage-page-moved": "La page $1 a été renommée en $2.",
        "movepage-page-unmoved": "La page $1 n'a pas pu être renommée en $2.",
        "movepage-max-pages": "Le maximum de $1 {{PLURAL:$1|page renommée|pages renommées}} a été atteint et aucune autre page ne sera renommée automatiquement.",
        "delete_and_move_reason": "Page supprimée pour permettre le renommage depuis « [[$1]] »",
        "selfmove": "Le titre est le même ;\nimpossible de renommer une page sur elle-même.",
        "immobile-source-namespace": "Vous ne pouvez pas renommer les pages dans l'espace de noms « $1 »",
-       "immobile-source-namespace-iw": "Les pages sur d’autres wikis ne peuvent être déplacées depuis ce wiki.",
+       "immobile-source-namespace-iw": "Il n'est pas possible de déplacer les pages depuis ce wiki vers les autres wikis.",
        "immobile-target-namespace": "Vous ne pouvez pas renommer des pages vers l’espace de noms « $1 ».",
        "immobile-target-namespace-iw": "Un lien interwiki n’est pas une cible valide pour un renommage de page.",
        "immobile-source-page": "Cette page n'est pas renommable.",
index a6eca3d..5d47b44 100644 (file)
        "rcfilters-clear-all-filters": "Borrar todos os filtros",
        "rcfilters-show-new-changes": "Amosar novos cambios dende $1",
        "rcfilters-search-placeholder": "Filtrar os cambios (use o menú ou procure o nome dun filtro)",
+       "rcfilters-search-placeholder-mobile": "Filtros",
        "rcfilters-invalid-filter": "Filtro no válido",
        "rcfilters-empty-filter": "Non hai filtros activos. Móstranse tódalas contribucións.",
        "rcfilters-filterlist-title": "Filtros",
        "rcfilters-filter-showlinkedto-label": "Amosar os cambios en páxinas que ligan con",
        "rcfilters-filter-showlinkedto-option-label": "<strong>Páxinas que ligan</strong> para a páxina seleccionada",
        "rcfilters-target-page-placeholder": "Insire un nome de páxina (ou categoría)",
+       "rcfilters-allcontents-label": "Tódolos contidos",
+       "rcfilters-alldiscussions-label": "Tódalas conversas",
        "rcnotefrom": "A continuación {{PLURAL:$5|móstrase o cambio feito|móstranse os cambios feitos}} desde o <strong>$3</strong> ás <strong>$4</strong> (móstranse <strong>$1</strong> como máximo).",
        "rclistfromreset": "Reinicializar a selección da data",
        "rclistfrom": "Amosar os cambios novos desde o $3 ás $2",
        "permanentlink": "Ligazón permanente",
        "permanentlink-revid": "ID da revisión",
        "permanentlink-submit": "Ir á revisión",
+       "newsection-submit": "Ir á páxina",
        "dberr-problems": "Sentímolo! Este sitio está experimentando dificultades técnicas.",
        "dberr-again": "Por favor, agarde uns minutos e logo probe a cargar de novo a páxina.",
        "dberr-info": "(Non se pode acceder ao servidor da base de datos: $1)",
index 510a3e4..890a16c 100644 (file)
@@ -7,7 +7,8 @@
                        "Macofe",
                        "V6rg",
                        "شیخ",
-                       "Fitoschido"
+                       "Fitoschido",
+                       "Mehtab ahmed"
                ]
        },
        "tog-underline": "خالؤنˇ جيره خط کشئن:",
        "search-suggest": "شيمي منظۊر بۊ: $1",
        "searchall": "همه",
        "search-nonefound": "نتيجه-اي ياته نۊبؤ.",
-       "mypreferences": "ترجيحات",
+       "mypreferences": "ترجيحون",
        "skin-preview": "پيشادئن",
        "prefs-user-pages": "کارگيري ولگؤن",
        "allowemail": "باخي کارگيرؤنˇ جي شأسته بۊن ايمىل هأىتن",
        "thumbnail-more": "پيلله گۊدن",
        "tooltip-pt-userpage": "{{جنس:|شيمي کارگير}} ولگ",
        "tooltip-pt-mytalk": "{{جنس:|شيمي}} گبˇ ولگ",
-       "tooltip-pt-preferences": "{{جنس:|شيمي}} ترجيحات",
+       "tooltip-pt-preferences": "{{GENDER:|اوھان جون}} ترجيحون",
        "tooltip-pt-watchlist": "ولگؤنˇ ليستي گه شۊمۊ ايشؤنˇ تغييرؤنه پى گينين",
        "tooltip-pt-mycontris": "{{GENDER:|شيمي}} مۊشارکتؤنˇ ليست",
        "tooltip-pt-login": "بئتره ديرين بشين؛ بسچی گه ايجباری نیه.",
index ebca6ef..f95fc94 100644 (file)
        "rcfilters-filter-showlinkedto-label": "הצגת שינויים בדפים שמקשרים אל",
        "rcfilters-filter-showlinkedto-option-label": "<strong>דפים שמקשרים אל</strong> הדף שנבחר",
        "rcfilters-target-page-placeholder": "יש להקליד שם דף (או קטגוריה)",
+       "rcfilters-allcontents-label": "כל התכנים",
+       "rcfilters-alldiscussions-label": "כל הדיונים",
        "rcnotefrom": "להלן {{PLURAL:$5|השינוי שבוצע|השינויים שבוצעו}} מאז <strong>$3, $4</strong> (מוצגים עד <strong>$1</strong>).",
        "rclistfromreset": "איפוס בחירת התאריך",
        "rclistfrom": "הצגת שינויים חדשים החל מ־$2, $3",
        "move-subpages": "העברת דפי המשנה (עד $1)",
        "move-talk-subpages": "העברת דפי המשנה של דף השיחה (עד $1)",
        "movepage-page-exists": "הדף $1 קיים כבר ולא ניתן לדרוס אותו אוטומטית.",
+       "movepage-source-doesnt-exist": "הדף $1 אינו קיים ולא ניתן להעבירו.",
        "movepage-page-moved": "הדף $1 הועבר לשם $2.",
        "movepage-page-unmoved": "לא ניתן להעביר את הדף $1 לשם $2.",
        "movepage-max-pages": "{{PLURAL:$1|דף אחד כבר הועבר|$1 דפים כבר הועברו}}. זה המספר המרבי ולא ניתן להעביר דפים נוספים אוטומטית.",
        "delete_and_move_reason": "מחיקה כדי לאפשר העברה מהשם \"[[$1]]\"",
        "selfmove": "הכותרת זהה;\nלא ניתן להעביר דף לעצמו.",
        "immobile-source-namespace": "לא ניתן להעביר דפים במרחב השם \"$1\".",
+       "immobile-source-namespace-iw": "לא ניתן להעביר דפים באתרי ויקי אחרים מתוך אתר הוויקי הזה.",
        "immobile-target-namespace": "לא ניתן להעביר דפים למרחב השם \"$1\".",
        "immobile-target-namespace-iw": "קישור בינוויקי אינו יעד תקין להעברת דף.",
        "immobile-source-page": "דף זה אינו ניתן להעברה.",
        "immobile-target-page": "לא ניתן להעביר אל כותרת יעד זו.",
+       "movepage-invalid-target-title": "השם המבוקש אינו תקין.",
        "bad-target-model": "היעד המבוקש משתמש במודל תוכן שונה. לא ניתן להמיר $1 ל{{grammar:תחילית|$2}}.",
        "imagenocrossnamespace": "לא ניתן להעביר קובץ למרחב שם אחר.",
        "nonfile-cannot-move-to-file": "לא ניתן להעביר דף שאינו קובץ למרחב קובץ.",
index 8e7f829..06284b0 100644 (file)
        "systemblockedtext": "Tu nomine de usator o adresse IP ha essite blocate automaticamente per MediaWiki.\nLe motivo presentate es:\n\n:<em>$2</em>\n\n* Initio del blocada: $8\n* Expiration del blocada: $6\n* Blocato intendite: $7\n\nTu adresse IP actual es $3.\nPer favor, include tote le detalios enumerate hic supra in omne questiones que tu pone.",
        "blockednoreason": "nulle motivo specificate",
        "blockedtext-composite": "<strong>Tu nomine de usator o adresse IP ha essite blocate.</strong>\n\nLe motivo presentate es:\n\n:<em>$2</em>.\n\n* Initio del blocada: $8\n* Expiration del blocada le plus longe: $6\n\n* $5\n\nTu adresse IP actual es $3.\nPer favor, include tote le detalios enumerate hic supra in omne questiones que tu pone.",
+       "blockedtext-composite-ids": "IDs de blocada relevante: $1 (tu adresse IP pote etiam esser in lista nigre)",
+       "blockedtext-composite-no-ids": "Tu adresse IP appare in plure listas nigre",
        "blockedtext-composite-reason": "Il ha plure blocadas contra tu conto e/o adresse IP",
        "whitelistedittext": "Tu debe $1 pro poter modificar paginas.",
        "confirmedittext": "Tu debe confirmar tu adresse de e-mail pro poter modificar paginas.\nPer favor entra e valida tu adresse de e-mail per medio de tu [[Special:Preferences|preferentias de usator]].",
        "search-interwiki-more": "(plus)",
        "search-interwiki-more-results": "plus resultatos",
        "search-relatedarticle": "Connexe",
+       "search-invalid-sort-order": "Le ordine de assortimento de $1 non es recognoscite; le ordine predefinite essera applicate. Le ordines de assortimento valide es: $2",
+       "search-unknown-profile": "Le profilo de recerca de $1 non es recognoscite. Le profilo de recerca predefinite essera applicate.",
        "searchrelated": "connexe",
        "searchall": "totes",
        "showingresults": "Infra se monstra non plus de {{PLURAL:$1|'''1''' resultato|'''$1''' resultatos}} a partir del numero '''$2'''.",
index 3b78ced..1b56359 100644 (file)
@@ -24,6 +24,7 @@
        "tog-watchmoves": "Tinye ihu akwụkwọ na failụ niile mụ bugara n'ihe m ga na-elebara anya",
        "tog-watchdeletion": "Tinye ihu akwụkwọ na failụ niile m hichara n'ebe m ga na-elebara anya",
        "tog-watchuploads": "Tinye failụ ohụụ m mere ọpụload n'ihe mụ ga na -elebara anya",
+       "tog-watchrollback": "Gbakwụnye ihu akwụkwọgasị ebe m mere ndezigharị n'ịhe m ga na-elebara anya",
        "tog-minordefault": "Me ka nhoro da na orü ntakịrị níle",
        "tog-previewontop": "Zitú ntàkịrị mgbe opuzọr zi igbe orü",
        "tog-previewonfirst": "Zitú nke takírí orü mbu",
        "tog-norollbackdiff": "egosila ndíiche ma í gosipútacha otu ebe a di na mbú",
        "tog-useeditwarning": "gwam mgbe m hapụrụ ihu akwụkwọ nhaziri na echekwaghị ihe ndị m gbamworo",
        "tog-prefershttps": "gbaa mbọ na eji njikọta doro anya mgbe ọbụla ị chọrọ ibanye n'ịntanetị",
+       "tog-showrollbackconfirmation": "zipụta lịnkị na-egosi mgbe a na-eme ndezighari",
        "underline-always": "M̀gbèọbụlà",
        "underline-never": "Emelaème",
-       "underline-default": "Ndatụ ihü njikota",
+       "underline-default": "difọọltụ bụrawuza",
        "editfont-style": "Rüwa ámá udị mkpúrù èdè:",
        "editfont-monospace": "Otụ ihe ná kechí mkpúrù èdè",
        "editfont-sansserif": "Mkpúrù èdè sans-serif",
        "october-date": "Ọnwaìri $1",
        "november-date": "Ọnwaìrinàotù $1",
        "december-date": "Ọnwa Iri na abụọ $1",
+       "period-am": "oge ụtụtụ",
        "period-pm": "oge mgbede",
        "pagecategories": "{{PLURAL:$1|Ụdàkọ}}",
        "category_header": "Ihu nà ime ụdàkọ \"$1\"",
        "history": "Ịta ihüá",
        "history_short": "Ịta",
        "history_small": "akụkọ ihe mere eme",
-       "updatedmarker": "ihe gáráníru ké mgbe m byàrà nga mbu",
+       "updatedmarker": "ndezi emere kemgbe ị gara na site a",
        "printableversion": "Ùdì ǹke mbipụ̀",
        "permalink": "Jikodo ekechịrị",
        "print": "Dotié",
        "pool-timeout": "Ógè e zuole Í ché ncedọ",
        "pool-queuefull": "Pool kyu zùrù",
        "pool-errorunknown": "Nsogbu nke námaghi",
-       "pool-servererror": "pulu kauta sava adịghị ugbu a",
+       "pool-servererror": "puulu kaụnta savis adịghị ugbu a",
        "poolcounter-usage-error": "e nwere nsogbu: $1",
        "aboutsite": "Màkà {{SITENAME}}",
        "aboutpage": "Project:Màkà",
        "right-move": "Papụ̀ ihuâ",
        "right-movefile": "Papụ̀ àfabà",
        "right-upload": "Tịnyé ihe na nsónùsòrò",
+       "right-writeapi": "Iji ede API",
        "right-delete": "Kàchafu ihü",
        "right-bigdelete": "Kàcha ihü nwéré ákíkó mbu dí ógólógó",
        "right-undelete": "Ágbakashia ótù ihü",
        "recentchanges-label-bot": "Bot deziri ihe a",
        "recentchanges-label-unpatrolled": "ebugharịbegi ndezi a",
        "recentchanges-label-plusminus": "Pegi a agbanwela na otu ọha site na ọnu ọgụgụ bayits",
+       "recentchanges-legend-heading": "<strong>Isi-okwu</strong>",
        "recentchanges-legend-newpage": "$1 - ihü ohúrù",
        "rcfilters-savedqueries-cancel-label": "Hapụ̀",
        "rclistfrom": "Zìrí ihe gbanwere ọhúrù shí $3 $2",
        "filehist-filesize": "Ívù usòrò",
        "filehist-comment": "Nkwute",
        "imagelinks": "Mgbanwe usòrò",
-       "linkstoimage": "{{PLURAL:$1|Ihü nká|Ihü nke $1}} na jikodo gá usòrò nká:",
+       "linkstoimage": "Ihe ndị na-eso {{PLURAL:$1|ihe eji Ihu akwụkwọ eme|$1 ihe eji Ihu akwụkwọ eme}} na faịlụ a:",
        "nolinkstoimage": "Ọdighi ihuakwụkwọ nwere failụ a.",
        "sharedupload": "Ákwúkwó runotu nke shì $1 na ó nwèríkí di na orürü nke ndi ozor.",
        "sharedupload-desc-here": "Failụ a si na $1,enwekwara ike iji ya eme ihe na arụmarụ ọzọ. Nkọwa na [$2 ihuakwukwọ nkọwa failụ] eziri na okpuru.",
        "undelete-show-file-submit": "Eeh",
        "namespace": "Ahàm̀bara:",
        "invert": "Tụgha ǹke ǹhọ̀rọ",
+       "tooltip-invert": "Kachie igbe a izocha mgbanwe Ihu-akwụkwọ ndị nnọ na aha-ebe ahọpụtara(yana aha-ebe jikọtara ya m'obụrụ na akachiri ya)",
+       "namespace_association": "Nyìrí aha-ebe",
+       "tooltip-namespace_association": "Kachie igbea itinye kwa okwu ma ọbụ isi-okwu aha-ebe jikọtara aha ahọpụtara",
        "blanknamespace": "(Ḿkpà)",
        "contributions": "atụmatụ metụrara Jenda.{{GENDER:$1|User}}",
        "contributions-title": "Orü ọ'bànifé nà $1",
        "svg-long-desc": "usòrò SVG, nà áhà pixel $1 × $2, ívụ usòrò: $3",
        "show-big-image": "Failụ si na nke mbu",
        "show-big-image-preview": "Otu nyochaa a ha:$1",
+       "show-big-image-other": "Ndị ọzọ {{PLURAL:$2|mkpebi|mkpebi}}:$1.",
        "show-big-image-size": "$1 × $2 piksels",
        "file-info-gif-looped": "etemte",
        "newimages-legend": "Nzàtà",
index 8b8a973..aca6e7c 100644 (file)
        "tag-mw-contentmodelchange": "Modifiko di la kontenajo di ula modelo",
        "tag-mw-contentmodelchange-description": "Redakturi qui [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:ChangeContentModel modifikas la modelo di kontenajo] di ula pagino",
        "tag-mw-new-redirect": "Nova ridirekto",
+       "tag-mw-new-redirect-description": "Redakturi qui kreas nova ridirekto, o chanjas kontenajo di pagino a ridirekto",
+       "tag-mw-removed-redirect": "Ridirekto efacita",
+       "tag-mw-changed-redirect-target": "Emo di ridirekto modifikata",
+       "tag-mw-changed-redirect-target-description": "Redakturi qui modifikas la skopo di ula ridirekto",
        "tag-mw-blank-description": "Redakturi qui efacas pagini",
        "tag-mw-replace": "Remplasita",
        "tag-mw-replace-description": "Redakturi qui removas plua kam 90% de la kontenajo di ula pagino",
index f797c29..7feae60 100644 (file)
                        "Senpremì",
                        "Ignazio Cannata",
                        "Frubino",
-                       "TheRukk"
+                       "TheRukk",
+                       "Titore"
                ]
        },
        "tog-underline": "Sottolinea i collegamenti:",
        "blockedtitle": "Utente bloccato.",
        "blocked-email-user": "<strong>Alla tua utenza è stato vietato l'invio di email. Puoi ancora modificare altre pagine di questa wiki.</strong> Puoi vedere tutti i dettagli del blocco su [[Special:MyContributions|contributi dell'utenza]].\n\nIl blocco è stato effettuato da $1.\n\nLa ragione data è <em>$2</em>.\n\n* Inizio del blocco: $8\n* Scadenza del blocco: $6\n* Destinatario del blocco: $7\n* ID Blocco #$5",
        "blockedtext-partial": "<strong>Alla tua utenza o indirizzo IP è stato vietato di apportare modifiche a questa pagina. Puoi ancora modificare altre pagine di questa wiki.</strong> Puoi vedere tutti i dettagli del blocco su [[Special:MyContributions|contributi dell'utenza]].\n\nIl blocco è stato effettuato da $1.\n\nLa ragione data è <em>$2</em>.\n\n* Inizio del blocco: $8\n* Scadenza del blocco: $6\n* Destinatario del blocco: $7\n* ID Blocco #$5",
-       "blockedtext": "<strong>Il tuo nome utente o indirizzo IP è stato bloccato.</strong>\n\nIl blocco è stato imposto da $1. La motivazione del blocco è la seguente: <em>$2</em>.\n\n* Inizio del blocco: $8\n* Scadenza del blocco: $6\n* Intervallo di blocco: $7\n\nSe lo si desidera, è possibile contattare $1 o un altro [[{{MediaWiki:Grouppage-sysop}}|amministratore]] per discutere del blocco.\n\nSi noti che la funzione \"{{int:emailuser}}\" non è attiva se non è stato registrato un indirizzo email valido nelle proprie [[Special:Preferences|preferenze]] o se l'utilizzo di tale funzione è stato bloccato.\n\nL'indirizzo IP attuale è $3, il numero ID del blocco è #$5.\nSi prega di specificare tutti i dettagli precedenti in qualsiasi richiesta di chiarimenti.",
+       "blockedtext": "<strong>Il tuo nome utente o indirizzo IP è stato bloccato.</strong>\n\nIl blocco è stato imposto da $1. La motivazione del blocco è la seguente: <em>$2</em>.\n\n* Inizio del blocco: $8\n* Scadenza del blocco: $6\n* Destinatario del blocco: $7\n\nSe lo si desidera, è possibile contattare $1 o un altro [[{{MediaWiki:Grouppage-sysop}}|amministratore]] per discutere del blocco.\n\nSi noti che la funzione \"{{int:emailuser}}\" non è attiva se non è stato registrato un indirizzo email valido nelle proprie [[Special:Preferences|preferenze]] o se l'utilizzo di tale funzione è stato bloccato.\n\nL'indirizzo IP attuale è $3, il numero ID del blocco è #$5.\nSi prega di specificare tutti i dettagli precedenti in qualsiasi richiesta di chiarimenti.",
        "autoblockedtext": "Questo indirizzo IP è stato bloccato automaticamente perché condiviso con un altro utente, a sua volta bloccato da $1.\nLa motivazione del blocco è la seguente:\n\n:<em>$2</em>\n\n* Inizio del blocco: $8\n* Scadenza del blocco: $6\n* Intervallo di blocco: $7\n\nÈ possibile contattare $1 o un altro [[{{MediaWiki:Grouppage-sysop}}|amministratore]] per richiedere eventuali chiarimenti circa il blocco.\n\nSi noti che la funzione \"{{int:emailuser}}\" non è attiva se non è stato registrato un indirizzo e-mail valido nelle proprie [[Special:Preferences|preferenze]] e, comunque, se nell'applicare il blocco, tale funzione è stata disabilitata (per la durata del blocco).\n\nL'indirizzo IP attuale è $3, il numero ID del blocco è #$5\nSi prega di specificare tutti i dettagli qui inclusi nel compilare qualsiasi richiesta di chiarimenti.",
        "systemblockedtext": "Il tuo nome utente o l'indirizzo IP è stato bloccato automaticamente da MediaWiki.\nLa motivazione del blocco è la seguente:\n\n:''$2''\n\n* Inizio del blocco: $8\n* Scadenza del blocco: $6\n* Intervallo di blocco: $7\n\nL'indirizzo IP attuale è $3.\nSi prega di specificare tutti i dettagli qui inclusi nel compilare qualsiasi richiesta di chiarimenti.",
        "blockednoreason": "nessuna motivazione indicata",
        "last": "prec",
        "page_first": "prima",
        "page_last": "ultima",
-       "histlegend": "Confronto tra versioni: selezionare le caselle corrispondenti alle versioni desiderate e premere Invio o il pulsante in basso.\n\nLegenda: '''({{int:cur}})''' = differenze con la versione attuale, '''({{int:last}})''' = differenze con la versione precedente, '''{{int:minoreditletter}}''' = modifica minore",
+       "histlegend": "Confronto tra versioni: selezionare le caselle corrispondenti alle versioni desiderate e premere Invio o il pulsante in basso.\n\nLegenda: '''({{int:cur}})''' = differenze con la versione corrente, '''({{int:last}})''' = differenze con la versione precedente, '''{{int:minoreditletter}}''' = modifica minore",
        "history-fieldset-title": "Filtra versioni",
        "history-show-deleted": "Solo versioni cancellate",
        "histfirst": "prima",
        "prefs-timeoffset": "Ore di differenza",
        "prefs-advancedediting": "Opzioni generali",
        "prefs-developertools": "Strumenti per gli sviluppatori",
-       "prefs-editor": "Editore",
+       "prefs-editor": "Editor",
        "prefs-preview": "Anteprima",
        "prefs-advancedrc": "Opzioni avanzate",
        "prefs-advancedrendering": "Opzioni avanzate",
        "rcfilters-filter-showlinkedto-label": "Mostra le modifiche alle pagine che collegano a",
        "rcfilters-filter-showlinkedto-option-label": "<strong>Pagine con collegamenti a</strong> la pagina selezionata",
        "rcfilters-target-page-placeholder": "Inserisci il nome di una pagina (o categoria)",
+       "rcfilters-alldiscussions-label": "Tutte le discussioni",
        "rcnotefrom": "Di seguito {{PLURAL:$5|è elencata la modifica apportata|sono elencate le modifiche apportate}} a partire da <strong>$3, $4</strong> (mostrate fino a <strong>$1</strong>).",
        "rclistfromreset": "Reimposta la selezione della data",
        "rclistfrom": "Mostra le nuove modifiche a partire daː $2, $3",
index 65f2f8e..d9c8e1a 100644 (file)
        "move-subpages": "下位ページも移動 ($1 件まで)",
        "move-talk-subpages": "トークページの下位ページも移動 ($1 件まで)",
        "movepage-page-exists": "ページ $1 は既に存在するため、自動的な上書きはできません。",
+       "movepage-source-doesnt-exist": "$1 というページは存在せず、移動できません。",
        "movepage-page-moved": "ページ $1 は $2 に移動しました。",
        "movepage-page-unmoved": "ページ $1 は $2 に移動できませんでした。",
        "movepage-max-pages": "自動的に移動できるのは $1 {{PLURAL:$1|ページ}}までで、それ以上は移動されません。",
        "delete_and_move_reason": "「[[$1]]」からの移動に伴う削除",
        "selfmove": "ページ名が同じです。\n自分自身には移動できません。",
        "immobile-source-namespace": "「$1」名前空間のページは移動できません",
+       "immobile-source-namespace-iw": "ほかのウィキのページをこのウィキへ移動させることはできません。",
        "immobile-target-namespace": "「$1」名前空間にはページを移動できません",
        "immobile-target-namespace-iw": "ウィキ間リンクは、ページの移動先には指定できません。",
        "immobile-source-page": "このページは移動できません。",
        "immobile-target-page": "指定したページ名には移動できません。",
+       "movepage-invalid-target-title": "要求された名前は無効です。",
        "bad-target-model": "指定した移動先では、異なるコンテンツ モデルを使用しています。$1から$2には変換できません。",
        "imagenocrossnamespace": "ファイルを、ファイル名前空間以外に移動させることはできません",
        "nonfile-cannot-move-to-file": "ファイル以外のものを、ファイル名前空間に移動させることはできません",
index 0f789a0..413c45a 100644 (file)
        "rcfilters-filter-showlinkedto-label": "다음 문서로 링크한 문서의 변경사항 보기",
        "rcfilters-filter-showlinkedto-option-label": "<strong>선택된 문서로 링크하는</strong> 문서들",
        "rcfilters-target-page-placeholder": "문서 이름(또는 분류)을 입력하세요",
+       "rcfilters-allcontents-label": "모든 내용",
+       "rcfilters-alldiscussions-label": "모든 토론",
        "rcnotefrom": "아래는 <strong>$3, $4</strong>부터 시작하는 {{PLURAL:$5|바뀜이 있습니다}}. (최대 <strong>$1</strong>개가 표시됨)",
        "rclistfromreset": "날짜 선택 초기화",
        "rclistfrom": "$3 $2부터 시작하는 새로 바뀐 문서 보기",
        "move-subpages": "하위 문서도 이동 ($1개까지)",
        "move-talk-subpages": "토론 문서의 하위 문서도 이동하기 ($1개까지)",
        "movepage-page-exists": "$1 문서가 이미 존재하므로 자동으로 덮어쓸 수 없습니다.",
+       "movepage-source-doesnt-exist": "$1 문서는 존재하지 않으며 이동할 수 없습니다.",
        "movepage-page-moved": "\"$1\" 문서를 \"$2\" 문서로 이동했습니다.",
        "movepage-page-unmoved": "$1 문서를 $2 문서로 이동할 수 없습니다.",
        "movepage-max-pages": "{{PLURAL:$1|문서}}를 최대 $1개 이동했으며 나머지 문서는 자동으로 이동하지 않습니다.",
        "delete_and_move_reason": "\"[[$1]]\"에서 문서를 이동하기 위해 삭제함",
        "selfmove": "제목이 동일합니다.\n같은 제목으로는 문서를 이동할 수 없습니다.",
        "immobile-source-namespace": "\"$1\" 이름공간에 속한 문서는 이동시킬 수 없습니다.",
+       "immobile-source-namespace-iw": "다른 위키의 문서는 이 위키로부터 이동할 수 없습니다.",
        "immobile-target-namespace": "\"$1\" 이름공간에 속한 문서는 이동시킬 수 없습니다.",
        "immobile-target-namespace-iw": "인터위키 링크를 넘어 문서를 이동할 수 없습니다.",
        "immobile-source-page": "이 문서는 이동할 수 없습니다.",
        "immobile-target-page": "목표 제목으로 이동할 수 없습니다.",
+       "movepage-invalid-target-title": "요청한 이름은 유효하지 않습니다.",
        "bad-target-model": "원하는 대상은 다른 내용 모델을 사용합니다. $1에서 $2로 변환할 수 없습니다.",
        "imagenocrossnamespace": "파일을 파일이 아닌 이름공간으로 이동할 수 없습니다.",
        "nonfile-cannot-move-to-file": "파일이 아닌 문서를 파일 이름공간으로 이동할 수 없습니다.",
index d990768..e816117 100644 (file)
        "rcfilters-preference-label": "Déi verbessert Versioun vun de rezenten Ännerunge verstoppen",
        "rcfilters-watchlist-preference-label": "Den Interface ouni JavaScript benotzen",
        "rcfilters-target-page-placeholder": "Gitt en Numm vun enger Säit (oder enger Kategorie) an",
+       "rcfilters-allcontents-label": "All Inhalter",
+       "rcfilters-alldiscussions-label": "All Diskussiounen",
        "rcnotefrom": "Hei drënner {{PLURAL:$5|gëtt d'Ännerung|ginn d'Ännerungen}} zanter <strong>$3, $4</strong> (maximal <strong>$1</strong> Ännerunge gi gewisen).",
        "rclistfromreset": "Eraussiche vum Datum zrécksetzen",
        "rclistfrom": "Nei Ännerunge vum $3 $2 u weisen",
index 110841f..e1b8d44 100644 (file)
        "continue-editing": "Pai ka kotak panyuntiangan",
        "previewconflict": "Pratayang iko mancaminan teks pado bagian ateh kotak suntiangan teks sabagaimano akan taliek bilo Sanak manyimpannyo.",
        "session_fail_preview": "Maaf, kami indak bisa mamproses suntiangan Sanak dek ilangnyo data sesi. \n\nSanak mungkin lah takalua dari log. <strong>Mohon pastikan baso Sanak masih masuak log. Cubo sajo sakali lai.</strong>.\nKok masih indak bisa, cubo [[Special:UserLogout|kalua]] dan masuak log sakali lai, dan pareso kok panjalajah web sanak mambuliahan panyimpanan ''cookies'' dari laman web ko.",
-       "session_fail_preview_html": "'''Kami indak dapek mamproses suntiangan Sanak karano hilangnyo sesi data.'''\n\n''Dek {{SITENAME}} mangizinan panggunoan HTML mantah, pratonton alah disuruakan sabagai pancagahan terhadok sarangan JavaScript.''\n\n'''Jikok iko marupoan suntiangan nan sah, silakan cubo lai.\nJikok masih jo indak barasil, cubolah [[Special:UserLogout|kalua log]] dan masuak baliak.'''",
+       "session_fail_preview_html": "Maaf, kami indak bisa mamproses suntiangan sanak dek ilangnyo data sesi. \n\n<em> Dek sebab teks HTML mantah pado {{SITENAME}} alah diaktifkan, pratinjau ko disuruakkan untuak mancagah sarangan JavaScript.</em>\n\n<strong>Kok iko satu pacuboan suntiangan nan sah, mohon cubo liak.</strong>\nKok indak juo bisa, cubo [[Special:UserLogout|kalua log]] dan masuak lai. Pareso kalau panjalajah web Sanak mampabuliahan cookie dari laman ko.",
        "token_suffix_mismatch": "'''Suntiangan Sanak ditolak karano aplikasi klien Sanak maubah karakter tando baco pado suntiangan.'''\nSuntiangan tasabuik ditolak untuak mancegah kasalahan pado teks laman.\nHal iko kadang tajadi jikok Sanak manggunokan layanan proxy anonim babasis web nan bamasalah.",
        "edit_form_incomplete": "'''Babarapo bagian dari formulir suntiangan indak mancapai server; pariso baliak apokah suntiangan Sanak tatap utuah dan cubo lai.'''",
        "editing": "Manyuntiang $1",
        "copyrightwarning2": "Parhatikan bahawa sadoalah kontribusi terhadap {{SITENAME}} dapek disuntiang, diubah, atau dihapuih oleh panyumbang lainnyo. Jikok Sanak indak ingin tulisan Sanak disuntiang urang lain, jan kiriman ka siko.<br />Sanak jua bajanji bahawa iko adolah hasil karyo Sanak surang, atau disalin dari sumber miliak umum atau sumber bebas nan lain (liek $1 untuak informasi labiah lanjuik). '''JAN KIRIMAN KARYO NAN DILINDUNGI HAK CIPTA TANPA IJIN!'''",
        "editpage-cannot-use-custom-model": "Model konten ko indak dapek diubah.",
        "longpageerror": "'''Kasalahan: Teks nan Sanak kiriman sagadang {{PLURAL:$1|$1 kilobita}}, barati labiah gadang dari jumlah maksimum {{PLURAL:$2|$2 kilobita}}. Teks indak dapek disimpan.'''",
-       "readonlywarning": "'''PARINGATAN: Basis data sadang dikunci untuak pamaliharaan, sahinggo saat iko Sanak indak dapek manyimpan hasil suntiangan.''' \nSanak mungkin paralu manyalin teks suntiangan Sanak ko dan simpankan ka sabuah berkas teks guno mamuekannyo baliak kundian.\n\nPanguruih nan mangunci basis data maagiahan panjalehan barikuik: $1",
+       "readonlywarning": "<strong>Paringatan: Basis data ko dikunci untuak karajo pambarasiahan. Sanak indak bisa manyimpan suntiangan kini ko.</strong>\nSanak disarankan untuak manyalin jo manyimpan suntiangan sanak ka file teks untuak diunggah kudian.\n\nManuruik panguruih sistem nan mangunci: $1",
        "protectedpagewarning": "'''Paringatan: Laman iko sadang dilinduangi sahinggo hanyo pangguno jo hak akses pangurus nan dapek manyuntiangnyo.'''\nEntri catatan tarakhir disadioan di bawah untuak referensi:",
-       "semiprotectedpagewarning": "'''Catatan:''' Laman ko sadang dilinduangi, jadi hanyo pangguno tadaftar nan dapek manyuntiangnyo.\nEntri log tarakhia disadioan di bawah untuak reperensi:",
-       "cascadeprotectedwarning": "'''Paringatan:''' Laman ko sadang dilinduangi jadi hanyo pangguno jo hak akses panguruih sajo nan dapek manyuntiangnyo karano disaratoan dalam {{PLURAL:$1|laman}} nan alah dilinduangi jo palinduangan batingkek:",
+       "semiprotectedpagewarning": "<strong>Catatan:</strong> Laman ko alah dilinduangi, hanyo pangguno nan alah takonfirmasi sacaro otomatis nan bisa manyuntiang.\nLog nan paliang akhia:",
+       "cascadeprotectedwarning": "<strong>Paringatan:</strong> Laman ko alah dilinduangi. Hanyo pangguno nan [[Special:ListGroupRights|punyo hak nan tatantu]] buliah untuak manyuntiang, dek karano laman ko ditransklusi pado {{PLURAL:$1|laman nan dilinduangi ko}}:",
        "titleprotectedwarning": "'''Paringatan: Laman iko alah dilinduangi sahinggo diparaluan [[Special:ListGroupRights|hak khusus]] untuak mambueknyo.'''\nEntri catatan tarakhir disadioan di bawah untuak referensi:",
        "templatesused": "{{PLURAL:$1|Templat}} nan digunoan di laman ko:",
        "templatesusedpreview": "{{PLURAL:$1|Templat}} nan digunoan dalam pratonton ko:",
        "defaultmessagetext": "Teks baku.",
        "content-failed-to-parse": "Gagal manjabarkan konten $2 untuak model $1: $3",
        "invalid-content-data": "Data kanduangan indak valid.",
-       "content-not-allowed-here": "Konten \"$1\" indak diizinan di laman [[:$2]]",
+       "content-not-allowed-here": "Isi \"$1\" indak diizinkan pado laman [[:$2]] di slot \"$3\"",
        "editwarning-warning": "Maninggakan laman ko dapek maakibaikan parubahan nan dibuek hilang. Jikok Sanak lah masuak log, dapek mamatian pasan ko malalui bagian \"Panyuntiangan\" pado laman pangaturan.",
        "slot-name-main": "Utamo",
        "content-model-wikitext": "Teks wiki",
index de98e58..0516e3e 100644 (file)
        "rcfilters-filter-showlinkedto-label": "Прикажи промени во страници кои водат кон",
        "rcfilters-filter-showlinkedto-option-label": "<strong>Страници кои води кон</strong> избраната страница",
        "rcfilters-target-page-placeholder": "Внесете име на страница (или категорија)",
+       "rcfilters-allcontents-label": "Сета содржина",
+       "rcfilters-alldiscussions-label": "Сите разговори",
        "rcnotefrom": "Подолу {{PLURAL:$5|е прикажана промената|се прикажани промените}} почнувајќи од <strong>$3, $4</strong>  (се прикажуваат до <b>$1</b>).",
        "rclistfromreset": "Нов избор на датуми",
        "rclistfrom": "Прикажи нови промени почнувајќи од $3 $2",
        "move-subpages": "Премести ги и потстраниците (највеќе до $1)",
        "move-talk-subpages": "Премести потстраници на разговорни страници (највеќе до $1)",
        "movepage-page-exists": "Страницата $1 веќе постои и не може автоматски да биде заменета.",
+       "movepage-source-doesnt-exist": "Страницата „$1“ не постои и затоа не може да се премести.",
        "movepage-page-moved": "Страницата $1 е преместена на $2.",
        "movepage-page-unmoved": "Страницата $1 не може да биде преместена во $2.",
        "movepage-max-pages": "{{PLURAL:$1|Преместен е највеќе $1 страница|Преместени се највеќе $1 страници}}. Повеќе од тоа не може да се преместува автоматски.",
        "delete_and_move_reason": "Избришано за да се ослободи место за преместувањето од „[[$1]]“",
        "selfmove": "Насловот е истоветен;\nне можам да го преместам на самиот себе.",
        "immobile-source-namespace": "Не може да се преместуваат страници во именскиот простор „$1“",
+       "immobile-source-namespace-iw": "Од ова вики не можат да се преместат страници на други викија.",
        "immobile-target-namespace": "Не може да се преместуваат страници во именскиот простор „$1“",
        "immobile-target-namespace-iw": "Меѓупроектна врска не може да се користи за преименување на страници.",
        "immobile-source-page": "Оваа страница не може да се преместува.",
        "immobile-target-page": "Не може да се премести под бараниот наслов.",
+       "movepage-invalid-target-title": "Побараното име е неважечко.",
        "bad-target-model": "Саканата одредница користи друг содржински модел. Не можам да претворам од $1 во $2.",
        "imagenocrossnamespace": "Не може да се премести податотека во неподатотечен именски простор",
        "nonfile-cannot-move-to-file": "Не можам да преместам неподатотека во податотечен именски простор",
index 59b7e93..e377971 100644 (file)
        "rcfilters-filter-showlinkedto-label": "കണ്ണി ചേർക്കപ്പെട്ട താളുകളിലെ മാറ്റങ്ങൾ കാണിക്കുക",
        "rcfilters-filter-showlinkedto-option-label": "തിരഞ്ഞെടുത്ത താളിലേക്ക് <strong>കണ്ണി ചേർക്കപ്പെട്ട താളുകൾ</strong>",
        "rcfilters-target-page-placeholder": "താളിന്റെ (അല്ലെങ്കിൽ വർഗ്ഗത്തിന്റെ) പേര് നൽകുക",
+       "rcfilters-allcontents-label": "എല്ലാ ഉള്ളടക്കവും",
+       "rcfilters-alldiscussions-label": "എല്ലാ സംവാദങ്ങളും",
        "rcnotefrom": "<strong>$3, $4</strong> മുതലുള്ള {{PLURAL:$5|മാറ്റം|മാറ്റങ്ങൾ}} ആണ് താഴെയുള്ളത്  (<strong>$1</strong> എണ്ണം വരെ കൊടുക്കുന്നതാണ്).",
        "rclistfromreset": "തീയതി എടുത്തത് പുനഃസജ്ജീകരിക്കുക",
        "rclistfrom": "$3 $2 മുതലുള്ള മാറ്റങ്ങൾ പ്രദർശിപ്പിക്കുക",
        "specialmute": "നിശബ്ദമാക്കുക",
        "specialmute-submit": "സ്ഥിരീകരിക്കുക",
        "specialmute-label-mute-email": "ഈ ഉപയോക്താവിൽ നിന്നുമുള്ള ഇമെയിലുകൾ നിശബ്ദമാക്കുക",
+       "specialmute-login-required": "താങ്കളുടെ നിശബ്ദമാക്കൽ ഐച്ഛികങ്ങൾ മാറ്റുന്നതിനായി ദയവായി പ്രവേശിക്കുക.",
+       "mute-preferences": "നിശബ്ദമാക്കൽ ഐച്ഛികങ്ങൾ",
        "revid": "നാൾപ്പതിപ്പ് $1",
        "pageid": "താൾ ഐ.ഡി. $1",
        "interfaceadmin-info": "$1\n\nസൈറ്റ്‌വ്യാപക സി.എസ്.എസ്./ജെ.എസ്./ജെസൺ പ്രമാണങ്ങൾ തിരുത്താനുള്ള അവകാശം സമീപകാലത്ത് <code>editinterface</code> അവകാശത്തിൽനിന്നും വേർപെടുത്തിയതാണ്. ഈ പിഴവ് എന്തുകൊണ്ടാണ് പ്രദർശിക്കപ്പെടുന്നതെന്ന് താങ്കൾക്ക് മനസ്സിലാകുന്നില്ലെങ്കിൽ [[mw:MediaWiki_1.32/interface-admin]] കാണുക.",
index c75710f..250dc65 100644 (file)
        "mainpage": "Paggena prencepale",
        "mainpage-description": "Paggena prencepale",
        "policy-url": "Project:Policy",
-       "portal": "Porta d'<nowiki/>'a commonetà",
+       "portal": "Porta d'a commonetà",
        "portal-url": "Project:Porta d''a commonetà",
        "privacy": "'Nformazzione ppe a privacy",
        "privacypage": "Project:'Nfrummazione ncopp'â privacy",
        "virus-scanfailed": "scanziona fallita (codece $1)",
        "virus-unknownscanner": "antivirus scanusciuto:",
        "logouttext": "'''Site asciùte.'''\n\nNota ca arcune paggene putessero cuntinuà ad cumparì comme se 'o logout nun fosse affettuato fin quanno nun sarrà pulezzata 'a cache d\"o proprio browser.",
+       "logging-out-notify": "Staje ascenno, aspietta.",
+       "logout-failed": "Nun se può ascì mo: $1",
        "cannotlogoutnow-title": "Mo nun se pò ascì",
        "cannotlogoutnow-text": "'A disconessione nun è possibbele quanno s'ausa $1.",
        "welcomeuser": "Bemmenuto, $1!",
        "badretype": "'E passwords ch'è mis nun songe eguale.",
        "usernameinprogress": "Na criazione 'e cunto pe' st'utente è già nprugresso. Pe' piacere aspettate.",
        "userexists": "'O nomme utente ch'avete miso è già ausàto.\nPe' piacere sciglite n'atu nomme.",
+       "createacct-normalization": "'O nomme tuio sarrà cagnato a \"$2\" pe raggioni tecniche.",
        "loginerror": "Probblema 'e accièsso",
        "createacct-error": "Errore 'e criazione 'e cunto",
        "createaccounterror": "Nun se può crià nu cunto: $1",
        "resetpass-abort-generic": "'O cagnamiento d' 'a password s'è spezzato 'a na stensione.",
        "resetpass-expired": "'A pasword è ammaturata. Avite 'e ffà na password nova pe putè trasì.",
        "resetpass-expired-soft": "'A pasword vuost è ammaturata e s'adda cagnà. Avite 'e scegliere na password nova mò, o ffà click ncopp'a \"{{int:authprovider-resetpass-skip-label}}\" p' 'a cagnà aroppo.",
+       "resetpass-validity": "'A pasword toia nun è bbona: $1",
        "resetpass-validity-soft": "'A password toja nun è bbona: $1\n\nAvite 'e scegliere na password nova mò, o ffà click ncopp'a \"{{int:authprovider-resetpass-skip-label}}\" p' 'a cagnà aròppo.",
        "passwordreset": "Riabbìa 'a password",
        "passwordreset-text-one": "Ghienche stu modulo pe' ricevere na mmasciata e-mail c' 'a password temporanea.",
        "histfirst": "primma",
        "histlast": "urdema",
        "historysize": "({{PLURAL:$1|1 byte|$1 byte}})",
-       "historyempty": "(abbacante)",
+       "historyempty": "abbacante",
        "history-feed-title": "Cronologgia",
        "history-feed-description": "Cronologgia d' 'a paggena ncopp'a stu sito",
        "history-feed-item-nocomment": "$1 'o $2",
        "rcfilters-savedqueries-apply-label": "Crea filtro",
        "rcfilters-savedqueries-cancel-label": "Scancella",
        "rcfilters-clear-all-filters": "Pulezza tutt' 'e filtre",
-       "rcfilters-show-new-changes": "Vide 'e cagnamiente cchiù nnove",
+       "rcfilters-show-new-changes": "Vide 'e cagnamiente cchiù nnove 'e $1",
        "rcfilters-invalid-filter": "Filtro invalido",
        "rcfilters-filterlist-title": "Filtre",
        "rcfilters-filterlist-whatsthis": "Cumme funzionano?",
        "rcfilters-highlightmenu-help": "Piglia nu culore p'evidenzià sta proprietà",
        "rcfilters-filterlist-noresults": "Nisciuno filtro truvato",
        "rcfilters-noresults-conflict": "Nun s'hanno truvato risultati pecché 'a cerca tene nu cunflitto",
-       "rcfilters-state-message-subset": "Sto filtro nun tene effetti pecché 'e risultati suoi traseno 'int' {{{{PLURAL:$2|'e cerca|cerche}} cchiù gruosse (pruova 'a evidenzià pe verè): $1",
+       "rcfilters-state-message-subset": "Sto filtro nun tene effetti pecché 'e risultati suoi traseno 'int' {{{{PLURAL:$2|'a cerca|'e ccerche}} cchiù gruosse (pruova 'a evidenzià pe verè): $1",
        "rcfilters-filtergroup-authorship": "Autore d' 'o cuntribbuto",
        "rcfilters-filter-editsbyself-label": "Cagnamiénte d'ê tuoie",
        "rcfilters-filter-editsbyself-description": "Contribbute d'ê tuoie",
        "rcfilters-filter-watchlistactivity-seen-description": "Càgni a paggene ch'hê visto 'a cuanno facettero ll'urdimo cagnamiénto.",
        "rcfilters-filtergroup-lastrevision": "Ùrdeme verziune",
        "rcfilters-filter-lastrevision-label": "Verzione 'e mmo",
+       "rcfilters-tag-prefix-namespace-inverted": "<strong>:no</strong> $1",
        "rcfilters-watchlist-markseen-button": "Segna tutt'ê cagni comme visti",
        "rcfilters-watchlist-edit-watchlist-button": "Càgna 'e lista tuia d'ê paggene cuntrullate",
        "rcfilters-watchlist-showupdated": "'E càgne 'e ppaggene ca nun hê visto so' 'e <strong>niro</strong> e ch'ê ppalluccelle chiene.",
        "deadendpages": "Paggene ca nun spòntano",
        "deadendpagestext": "'E paggene ccà abbascio nun spontano a n'ati paggene ncopp'a {{SITENAME}}.",
        "protectedpages": "Paggene prutette",
+       "protectedpages-filters": "Filtri:",
        "protectedpages-indef": "Sulamente prutezziune a tiempo nun definito",
        "protectedpages-summary": "Sta paggena elenca 'e paggene ch'esisteno e ca mo stanne prutette. P'avé n'elenco 'e titule prutette â criazione, vedite [[{{#special:ProtectedTitles}}|{{int:protectedtitles}}]].",
        "protectedpages-cascade": "Sulamente prutezziune ricurzive",
        "deleting-backlinks-warning": "<strong>Attenzione:</strong>\n[[Special:WhatLinksHere/{{FULLPAGENAME}}|ati paggene]] cunteneno cullegamiente o paggene appennute â n'ata paggena ca state pe' scancellà.",
        "deleting-subpages-warning": "<strong>Accuorto:</strong> 'A paggena ca staie pe scancellà tene  [[Special:PrefixIndex/{{FULLPAGENAME}}/|{{PLURAL:$1|na sottopaggena|$1 sottopaggene|51=cchiù 'e 50 sottopaggene}}]].",
        "rollback": "Ausa na revizione 'e primma",
+       "rollback-confirmation-yes": "Sfàjere",
+       "rollback-confirmation-no": "Scancella",
        "rollbacklink": "sfàjere",
        "rollbacklinkcount": "sfàje {{PLURAL:$1|nu cagnamiento|$1 cagnamiente}}",
        "rollbacklinkcount-morethan": "sfàje cchiù 'e {{PLURAL:$1|nu cagnamiento|$1 cagnamiente}}",
        "mycontris": "'E ffatiche d''e mmeje",
        "anoncontribs": "Cuntribbute",
        "contribsub2": "Ppe {{GENDER:$3|$1}} ($2)",
+       "contributions-subtitle": "Pe {{GENDER:$3|$1}}",
        "contributions-userdoesnotexist": "'O cunto utente \"$1\" nun è riggistrato.",
        "nocontribs": "Nisciunu cagnamiento è stato truvato cu sti criterie.",
        "uctop": "attuale",
        "ipb-disableusertalk": "Nun permettere a st'utente edità 'a paggena 'e chiacchiera d' 'a soja pe' tramente ch'e bloccato",
        "ipb-change-block": "Fremma n'ata vota ll'utente cu ste mpustaziune",
        "ipb-confirm": "Cunferma 'o blocco",
+       "ipb-sitewide": "Pe tutte parte",
        "ipb-pages-label": "Paggene",
        "badipaddress": "Indirizzo IP nun valido",
        "blockipsuccesssub": "Blocco aseguito",
index 6abed76..f64aee4 100644 (file)
        "search-category": "(kategori $1)",
        "search-file-match": "(matcher filinnhold)",
        "search-suggest": "Mente du: $1",
-       "search-rewritten": "Viser resultatet for $1. Søk i stedet for $2.",
+       "search-rewritten": "Viser resultater for $1. Søk etter $2 i stedet.",
        "search-interwiki-caption": "Resultater fra søsterprosjekter",
        "search-interwiki-default": "Resultater fra $1:",
        "search-interwiki-more": "(mer)",
index 0c02b1b..aad736d 100644 (file)
        "movethispage": "यो पृष्ठ सार्नुहोस्",
        "unusedimagestext": "निम्न फाइलहरू छन्, तर कुनै पनि पृष्ठमा प्रयोग गरिएको छैन। कृपया ध्यान दें कि अन्य वेबसाइट एउटा सिधै लिङ्कको फाइलसँग जोड्न सकिन्छ, र सक्रिय उपयोगमा हुँदा पनि यहाँ देखाउन सकिन्छ।",
        "unusedcategoriestext": "तल श्रेणीका पृष्ठहरू उपलब्ध भएता पनि उक्त पृष्ठहरूलाई अन्य पृष्ठहरू तथा श्रेणीले प्रयोग गर्न सक्दैनन् ।",
-       "notargettitle": "कुनैपनि निसाना(टारगेट) छैन",
+       "notargettitle": "कुनैपनि निसाना छैन",
        "notargettext": "यो कार्यको लागि तपाईँले कुनै लक्षित पृष्ठ वा प्रयोगकर्ता निर्दिष्ट गर्नु भएको छैन ।",
        "nopagetitle": "त्यस्तो गन्तव्या पृष्ठ भेटिएन",
        "nopagetext": "तपाईंले खुलाउनु भएको गन्तव्य पृष्ठ अस्तित्वमा  छैन।",
index 42aa999..a5ab1a3 100644 (file)
        "rcfilters-filter-showlinkedto-label": "Toon wijzigingen op pagina's gekoppeld naar",
        "rcfilters-filter-showlinkedto-option-label": "<strong>Pagina's gekoppeld naar</strong> de geselecteerde pagina",
        "rcfilters-target-page-placeholder": "Voer een paginanaam (of categorie) in",
+       "rcfilters-allcontents-label": "Alle inhoud",
+       "rcfilters-alldiscussions-label": "Al het overleg",
        "rcnotefrom": "Wijzigingen sinds <strong>$3 om $4</strong> (maximaal <strong>$1</strong> {{PLURAL:$1|wijziging|wijzigingen}}).",
        "rclistfromreset": "Datum selectie opnieuw instellen",
        "rclistfrom": "Wijzigingen bekijken vanaf $3 $2",
        "move-subpages": "Deelpagina's hernoemen (maximaal $1)",
        "move-talk-subpages": "Deelpagina's van overlegpagina's hernoemen (maximaal $1)",
        "movepage-page-exists": "De pagina $1 bestaat al en kan niet automatisch verwijderd worden.",
+       "movepage-source-doesnt-exist": "De pagina $1 bestaat niet en kan daarom niet worden hernoemd.",
        "movepage-page-moved": "De pagina $1 is hernoemd naar $2.",
        "movepage-page-unmoved": "De pagina $1 kon niet hernoemd worden naar $2.",
        "movepage-max-pages": "Het maximale aantal automatisch te hernoemen pagina's is bereikt ({{PLURAL:$1|$1|$1}}).\nDe overige pagina's worden niet automatisch hernoemd.",
        "delete_and_move_reason": "Verwijderd in verband met hernoeming van \"[[$1]]\"",
        "selfmove": "U kunt een pagina niet hernoemen naar dezelfde paginanaam.",
        "immobile-source-namespace": "Pagina's in de naamruimte \"$1\" kunnen niet hernoemd worden",
+       "immobile-source-namespace-iw": "Pagina's op andere wiki's kunnen niet naar deze wiki worden hernoemd.",
        "immobile-target-namespace": "Pagina's kunnen niet hernoemd worden naar de naamruimte \"$1\"",
        "immobile-target-namespace-iw": "Een interwikikoppeling is geen geldige bestemming voor het hernoemen van een pagina.",
        "immobile-source-page": "Deze pagina kan niet hernoemd worden.",
        "immobile-target-page": "Het is niet mogelijk te hernoemen naar die paginanaam.",
+       "movepage-invalid-target-title": "De opgevraagde naam is ongeldig.",
        "bad-target-model": "De gewenste bestemming gebruikt een ander inhoudsmodel. Het is niet mogelijk om te zetten van $1 naar $2.",
        "imagenocrossnamespace": "Een mediabestand kan niet naar een andere naamruimte verplaatst worden",
        "nonfile-cannot-move-to-file": "Het is niet mogelijk te hernoemen van en naar de bestandsnaamruimte",
index 92fb9bb..6d8d318 100644 (file)
        "period-pm": "ߖ.ߞ",
        "pagecategories": "{{PLURAL:$1|ߦߌߟߡߊ|ߦߌߟߡߊ ߟߎ߬}}",
        "category_header": "ߦߌߟߡߊ ߞߐߜߍ ߟߎ߬ $1",
-       "subcategories": "ß\9dß\8a߬ß\93ß\8f߲߬ ß\98ß\8b߬ߣß\8d߲ ߠߎ߬",
+       "subcategories": "ߦß\8cß\9fß¡ß\8aß\99ß\8b߲ ߠߎ߬",
        "category-media-header": "ߟߊߛߋߢߊ ߦߌߟߡߊ ߣߌ߲߬ ߘߐ߫ \"$1\"",
        "category-empty": "<em>ߞߐߜߍ߫ ߥߟߊ߫ ߟߊߛߋߢߊ߫ ߝߏߌ߫ ߕߍ߫ ߦߌߟߡߊ ߣߌ߲߬ ߞߣߐ߫ ߕߋ߲߬ߕߋ߲߬.</em>",
        "hidden-categories": "{{PLURAL:$1|ߦߌߟߡߊ߫ ߢߡߘߏ߲߰ߣߍ߲|ߦߌߟߡߊ߫ ߢߡߊߘߏ߲߰ߣߍ߲ ߠߎ߬}}",
        "collapsible-collapse": "ߞߏߟߊߔߑߛߌ߫",
        "collapsible-expand": "ߘߐ߬ߥߙߊ߬ߟߌ",
        "confirmable-confirm": "ߌ ߛߍ߬ߓߍ߫ ߓߊ߬ {{GENDER:$1|}}؟",
-       "confirmable-yes": "ß\90߲߬ߤߐ߲߫",
+       "confirmable-yes": "ß\90߲߬ß\90߲߬ߐ߲߫",
        "confirmable-no": "ߍ߲߬ߍ߲߫",
        "thisisdeleted": "ߦߊ߯ߟߊ߫ ߦߴߊ߬ ߝߍ߬ ߞߵߊ߬ ߦߌ߬ߘߊ߬ ߥߟߊ߫ ߞߵߊ߬ ߟߊߛߊߦߌ߲߬ ߞߎߘߊߞߍ߫ ߓߊ߬ $1؟",
        "viewdeleted": "ߦߌ߬ߘߊ߬ߟߌ ߓߊ߬ $1؟",
        "rcfilters-filter-bots-description": "ߡߊ߬ߦߟߍ߬ߡߊ߲߬ߠߌ߲ ߡߍ߲ ߠߎ߬ ߛߌ߲ߘߌߣߍ߲߫ ߞߍߒߖߘߍߦߋ߫ ߖߐ߯ߙߊ߲ ߠߎ߬ ߘߐ߫.",
        "rcfilters-filter-humans-label": "ߡߐ߱ (ߓߏߕ  ߕߍ߫)",
        "rcfilters-filter-humans-description": "ߡߐ߱ ߡߊ߬ߦߟߍ߬ߡߊ߲߬ߠߊ ߡߊ߬ߦߟߍ߬ߡߊ߲߬ ߣߐ.",
+       "rcfilters-filter-reviewstatus-unpatrolled-description": "ߡߊ߬ߦߟߍ߬ߡߊ߲߬ߠߌ߲ ߡߍ߲ ߠߎ߬ ߣߐ߬ߣߐ߬ߣߍ߲߫ ߓߍ߬ߙߍ߲߬ߓߍ߬ߙߍ߲߬ߣߍ߲ ߘߌ߫ ߣߴߊ߬ ߡߊ߫ ߞߍ߫ ߓߟߏߟߕߊ߫ ߘߌ߫ ߥߟߊ߫ ߞߍߒߖߘߍߦߋߕߊ",
        "rcfilters-filter-reviewstatus-unpatrolled-label": "ߓߍ߬ߙߍ߲߬ߓߍ߬ߙߍ߲߬ߓߊߟߌ",
+       "rcfilters-filter-reviewstatus-manual-description": "ߡߊ߬ߦߟߍ߬ߡߊ߲߬ߠߌ߲ ߡߍ߲ ߠߎ߬ ߣߐ߬ߣߐ߬ߣߍ߲߫ ߓߍ߬ߙߍ߲߬ߓߍ߬ߙߍ߲߬ߣߍ߲ ߘߌ߫ ߓߟߏ ߟߊ߫.",
+       "rcfilters-filter-reviewstatus-manual-label": "ߓߍ߬ߙߍ߲߬ߓߍ߬ߙߍ߲߬ߠߌ߲ ߓߟߏߟߕߊ",
+       "rcfilters-filter-reviewstatus-auto-description": "ߡߊ߬ߦߟߍ߬ߡߊ߲߬ߠߌ߲ ߡߍ߲ ߠߎ߬ ߞߍ߫ ߣߍ߲߫ ߟߊ߬ߓߊ߰ߙߊ߬ߟߊ߫ ߖߊ߲߬ߝߊ߬ߣߍ߲ ߓߟߏ߫ ߡߍ߲ ߓߊ߯ߙߊߣߐ ߦߋ߫ ߣߐ߬ߣߐ߬ ߟߊ߫ ߓߍ߬ߙߍ߲߬ߓߍ߬ߙߍ߲߬ߣߍ߲ ߘߌ߫ ߞߍߒߖߘߍߦߋߓߟߏߡߊ߬.",
        "rcfilters-filter-reviewstatus-auto-label": "ߞߍߒߖߘߍߦߋ߫ ߓߍ߬ߙߍ߲߬ߓߍ߬ߙߍ߲߬ߣߍ߲",
        "rcfilters-filter-minor-label": "ߡߊ߬ߦߟߍ߬ߡߊ߲߬ߠߌ߲߬ ߘߋ߬ߣߍ߲ ߠߎ߬",
+       "rcfilters-filter-minor-description": "ߛߓߍߦߟߊ ߟߊ߫ ߘߎ߲ߛߓߍ ߡߊߦߟߍ߬ߡߊ߲߫ ߢߟߋߢߟߋ ߘߌ߫.",
        "rcfilters-filter-major-label": "ߡߊ߬ߦߟߍ߬ߡߊ߲߬ߠߌ߲߬ ߘߋ߬ߣߍ߲ ߕߍ߫",
        "rcfilters-filter-major-description": "ߡߊ߬ߦߟߍ߬ߡߊ߲߬ߠߌ߲ ߕߍ߫ ߘߎ߲ߛߓߍߡߊ߫ ߘߋߣߍ߲ ߝߋ߲߫ ߘߌ߫.",
        "rcfilters-filtergroup-watchlist": "ߜߋ߬ߟߎ߲߬ߠߌ߲߬ ߛߙߍߘߍ ߞߐߜߍ ߟߎ߬",
        "rcfilters-watchlist-markseen-button": "ߡߊ߬ߦߟߍ߬ߡߊ߲߬ߠߌ߲ ߓߍ߯ ߣߐ߬ߣߐ߬ ߦߋߣߍ߲ ߘߌ߫",
        "rcfilters-watchlist-edit-watchlist-button": "ߌ ߟߊ߫ ߞߐߜߍ߫ ߡߊߝߟߍߣߍ߲ ߠߎ߬ ߛߙߍߘߍ ߡߊߦߟߍ߬ߡߊ߲߫",
        "rcfilters-target-page-placeholder": "ߞߐߜߍ ߕߐ߮ ߟߊߘߏ߲߬ (ߥߟߊ߫ ߦߌߟߡߊ)",
+       "rcfilters-allcontents-label": "ߞߣߐߘߐ ߟߎ߬ ߓߍ߯",
+       "rcfilters-alldiscussions-label": "ߘߊߘߐߖߊߥߏ ߟߎ߬ ߓߍ߯",
        "rcnotefrom": "ߘߎ߰ߟߊ ߘߐ߫ {{PLURAL:$5|is the change|are the changes}} ߞߊ߬ߦߌ߯ <strong>$3, $4</strong> (up to <strong>$1</strong> shown).",
        "rclistfromreset": "ߞߐߜߍ ߓߊߕߐߡߐ߲ߠߌ߲ ߡߊߦߟߍ߬ߡߊ߲߫",
        "rclistfrom": "ߡߊ߬ߦߟߍ߬ߡߊ߲߬ߠߌ߲߬ ߞߎߘߊ ߟߎ߫ ߦߌ߬ߘߊ ߘߊߡߌ߬ߣߊ߬ ߣߌ߲߭ ߡߊ߬ $2, $3",
        "rc-enhanced-hide": "ߝߊߙߊ߲ߝߊ߯ߛߌ ߟߎ߬ ߢߡߊߘߏ߲߰",
        "rc-old-title": "ߊ߬ ߓߊߞߘߐ ߟߊߘߊ߲߫ ߣߍ߲߫ ߦߋ߫ ߕߊ߲߬ ߠߋ߫ \"$1\"",
        "recentchangeslinked": "ߡߊ߬ߦߟߍ߬ߡߊ߲߬ߠߌ߲ ߜߋ߲߬ߞߘߎ߬ߢߐ߲߰ߡߊ ߟߎ߬",
+       "recentchangeslinked-feed": "ߡߊ߬ߦߟߍ߬ߡߊ߲߬ߠߌ߲ ߜߋ߲߬ߞߘߎ߬ߢߐ߲߰ߡߊ ߟߎ߬",
        "recentchangeslinked-toolbox": "ߢߟߊߞߎߘߦߊߟߌ߫ ߜߋ߲߬ߞߘߎ߬ߡߊ ߟߎ߬",
        "recentchangeslinked-title": "ߊ߬ ߟߌ߬ߤߟߊ ߡߊߦߟߍ߬ߡߊ߲߫ ߦߊ߲߬ \"$1\"",
        "recentchangeslinked-summary": "ߞߐߜߍ ߕߐ߮ ߟߊߘߏ߲߬߸ ߞߊ߬ ߞߐߜߍ ߛߘߌ߬ߜߋ߲ ߡߊ߬ߦߟߍ߬ߡߊ߲߬ߠߌ߲ ߦߋ߫߸ ߥߟߊ߫ \nߞߊ߬ ߝߘߊ߫ ߞߐߜߍ ߣߌ߲߬ ߠߊ߫. (ߖߐ߲߬ߛߊ߬ ߌ ߘߌ߫ ߦߌߟߡߊ ߛߌ߲߬ߝߏ߲ ߠߎ߬ ߦߋ߫߸ ߣߌ߲߬ ߠߊߘߏ߲߬ {{ns:category}}: ߦߌߟߡߊ ߕߐ߮). ߦߟߍ߬ߡߊ߲߬ ߡߍ߲ ߦߋ߫ ߞߐߜߍ ߣߌ߲߬ [[Special:Watchlist|your Watchlist]] ߘߐ߫߸ ߏ߬ ߦߋ߫ <strong>ߛߓߍߘߋ߲߫ ߞߎ߲ߓߊ</strong> ߟߋ߬ ߘߐ߫.",
        "recentchangeslinked-page": "ߞߐߜߍ ߕߐ߮:",
        "recentchangeslinked-to": "ߞߐߜߍ ߛߘߌ߬ߜߋ߲ ߠߎ߬ ߦߌ߬ߘߊ߬߸ ߞߊ߬ ߞߐߜߍ ߣߌ߬ ߞߋߟߋ߲ߘߌ߫",
        "recentchanges-page-added-to-category": "[[:$1]] ߓߘߊ߫ ߟߊߘߏ߲߬ ߦߌߟߡߊ ߘߐ߫",
+       "recentchanges-page-added-to-category-bundled": "[[:$1]] ߓߘߊ߫ ߟߊߘߏ߲߬ ߦߌߟߡߊ ߘߐ߫߸  [[Special:WhatLinksHere/$1|this page is included within other pages]]",
        "recentchanges-page-removed-from-category": "[[:$1]] ߛߋ߲߬ߓߐ߫ ߦߌߟߡߊ ߘߐ߫",
+       "recentchanges-page-removed-from-category-bundled": "[[:$1]] ߓߘߊ߫ ߛߋ߲߬ߓߐ߫ ߦߌߟߡߊ ߘߐ߫߸  [[Special:WhatLinksHere/$1|this page is included within other pages]]",
        "autochange-username": "ߡߋߘߌߦߊ߫-ߥߞߌ ߞߍߒߖߘߍߦߋ߫ ߡߊߦߟߍߡߊ߲ߠߌ߲",
        "upload": "ߞߐߕߐ߮ ߟߊߦߟߍ",
        "uploadbtn": "ߞߐߕߐ߮ ߟߊߦߟߍ߬",
        "uploadstash-bad-path-bad-format": "ߟߊߘߏ߲߬ߣߍ߲  \"$1\"  ߕߍ߫ ߖߙߎߡߎ߲߫ ߢߎߡߊ߫ ߘߌ߫.",
        "uploadstash-file-not-found": "ߟߊߘߏ߲߬ߣߍ߲  \"$1\" ߕߍ߫ ߥߊ߬ߣߊߙߌ ߟߎ߬ ߘߐ߫.",
        "uploadstash-file-not-found-no-thumb": "ߞߝߊ߬ߟߋ߲ߛߋ߲ ߕߍ߫ ߣߊ߬ ߟߊߛߐ߬ߘߐ߲߬ ߠߊ߫.",
+       "uploadstash-file-not-found-missing-content-type": "ߞߣߐߘߐ ߛߎ߯ߦߊ ߞߎ߲߬ߕߐ߮ ߞߐߢߌ߬ߣߊ߬ߣߍ߲߫.",
        "uploadstash-no-extension": "ߘߐ߬ߥߙߊ߬ߟߌ ߦߋ߫ ߝߏߦߊ߲ ߠߋ߬ ߘߌ߫.",
        "uploadstash-zero-length": "ߞߐߕߐ߮ ߦߋ߫ ߥߊ߲߬ߥߊ߲߬ ߘߐߞߏߟߏ߲ ߠߋ߬ ߘߌ߫.",
        "img-auth-nofile": "ߞߐߕߐ߮  \"$1\" ߕߍ߫ ߦߋ߲߬.",
        "metadata-fields": "ߟߐ߲ߕߊߞߐ߫ ߖߌ߬ߦߊ߬ߓߍ ߞߣߍ ߡߍ߲ ߦߋ߫ ߗߋߛߓߍ ߣߌ߲߬ ߘߐ߫߸ ߏ߬ ߘߌ߫ ߣߊ߬ ߥߟߏ߫ ߖߌ߬ߦߊ߬ߓߍ ߞߐߜߍ ߘߐ߫ ߣߌ߫ ߟߐ߲ߕߊߞߐ߫ ߥߟߊ߬ߟߋ߲ ߠߊߘߐ߯ߦߊ߫ ߘߊ߫. ߊ߬ ߕߐ߭ ߟߎ߬ ߢߡߊߘߏ߲߰ߣߍ߲ ߘߌ߫ ߕߏ߫ ߝߍ߭ ߞߏߛߐ߲߬.\n•ߊ߬ ߞߍ߫ \n•ߛߎ߯ߦߊ \n•ߕߎ߬ߡߊ߬ߘߊ ߣߌ߫ ߕߎ߬ߡߊ߬ߙߋ߲߫ ߓߐߛߎ߲ߡߊ \n•ߟߊ߬ߝߏߦߌ ߕߎ߬ߡߊ߬ߘߊ߬ ߖߐ߲ߖߐ߲ \n•ߞ ߝߙߍߕߍ \n•ߡ.ߛ.ߛ ߞߊߟߌߦߊ ߡߐ߬ߟߐ߲߬ߦߊ߬ߟߌ \n•ߕߊߞߎ߲ߡߊ ߥߊ߲߬ߥߊ߲ \n•ߞߎ߬ߛߊ߲ \n•ߓߊߦߟߍߡߊ߲ ߤߊߞߍ  ߘߞߖ \n•ߖߌ߬ߦߊ߬ߓߍ ߞߊ߲߬ߛߓߍ\n•ߘߟߊߕߍ߮ ߘߞߖ (ߘߊ߲߬ߠߊ߬ߕߍ߰ ߞߊ߲ߞߋ߫ ߖߊ߯ߓߡߊ)\n•ߘߎ߰ߕߍߟߍ߲ ߘߞߖ (ߘߊ߲߬ߠߊ߬ߕߍ߰ ߞߊ߲ߞߋ߫ ߖߊ߯ߓߡߊ)\n•ߞߐߓߋ ߘߞߖ (ߘߊ߲߬ߠߊ߬ߕߍ߰ ߞߊ߲ߞߋ߫ ߖߊ߯ߓߡߊ)",
        "namespacesall": "ߊ߬ ߓߍ߯",
        "monthsall": "ߡߎ߰ߡߍ",
+       "confirmemail": "ߢߎߡߍߙߋ߲߫ ߞߏ߲ߘߏ ߟߊߛߙߋߦߊ߫",
+       "confirmemail_noemail": "ߢߎߡߍߙߋ߲ߞߏ߲ߘߏ ߛߊ߲߬ߓߊ߬ߕߐ߰ ߖߐ߲ߖߐ߲߫ ߟߊߘߏ߲߬ߣߍ߲߬ ߕߴߌ ߓߟߏ߫ ߌ ߟߊ߫ \n[[Special:Preferences|user preferences]]",
+       "confirmemail_send": "ߟߊ߬ߛߙߋ߬ߦߊ߬ߟߌ߬ ߘߏߝߙߍߕߍ ߗߋ߫ ߒ ߡߊ߬",
+       "confirmemail_sent": "ߟߊ߬ߛߙߋ߬ߦߊ߬ߟߌ߬ ߢߎߡߍߙߋ߲ ߓߘߊ߫ ߗߋ߫",
+       "confirmemail_oncreate": "ߟߊ߬ߛߙߋ߬ߦߊ߬ߟߌ߬ ߘߏߝߙߍߕߍ ߓߘߊ߫ ߗߋ߫ ߌ ߟߊ߫ ߢߎߡߍߙߋ߲ߞߏ߲ߘߏ ߟߊ߫.\nߘߏߝߙߍߕߍ ߡߊߢߌ߬ߝߌ߲߬ߞߊ߬ߣߍ߲߬ ߕߍ߫ ߜߊ߲߬ߞߎ߲߬ߠߌ߲߬ߞߏ߫ ߘߐ߫߸ ߞߏ߬ߣߌ߲߬ ߌ ߞߊߞߊ߲߫ ߞߴߊ߬ ߡߊߛߐ߫ ߦߊ߬ߣߌ߲߫ ߢߎߡߍߙߋ߲ߞߏ߲ߘߏ ߓߐ߫ ߕߍ߫ ߥߞߌ ߟߊ߫.",
+       "confirmemail_sendfailed": "{{SITENAME}} ߕߴߛߋ߫ ߌ ߟߊ߫ ߟߊ߬ߛߙߋ߬ߦߊ߬ߟߌ߬ ߢߎߡߍߙߋ߲ߞߏ߲ߘߏ ߗߋ߫ ߟߊ߫.\nߌ ߟߊ߫ ߢߎߡߍߙߋ߲ߞߏ߲ߘߏ ߘߐߜߍ߫ ߛߕߍߘߋ߲߫ ߓߍ߲߬ߓߊ߬ߟߌ߬ߞߏ ߘߐ߫ ߖߊ߰ߣߌ߲߫.\n\nߗߋߛߓߍ߫ ߗߋߟߊ߫ ߟߊߛߊ߬ߦߌ߲߬ߣߍ߲: $1",
+       "confirmemail_invalid": "ߟߊ߬ߛߙߋ߬ߦߊ߬ߟߌ߬ ߘߏߝߙߍߕߍ ߓߍ߲߬ߓߊߟߌ.\nߘߏߝߙߍߕߍ ߛߕߊ ߝߊߣߍ߲߫ ߘߌ߫ ߞߍ߫ ߟߋ߬.",
+       "confirmemail_needlogin": "ߖߊ߰ߣߌ߲߫ $1 ߞߵߌ ߟߊ߫ ߢߎߡߍߙߋ߲ߞߏ߲ߘߏ ߟߊߛߙߋߦߊ߫.",
+       "confirmemail_success": "ߌ ߟߊ߫ ߢߎߡߍߙߋ߲ߞߏ߲ߘߏ ߓߘߊ߫ ߓߊ߲߫ ߟߊߛߙߋߦߊ߫ ߟߊ߫.\nߌ ߘߌ߫ ߛߴߌ  ߜߊ߲߬ߞߎ߲߬ ߠߊ߫ [[Special:UserLogin|log in]] ߡߎ߬ߕߎ߲߬ ߞߊ߬ ߛߍߥߊ߫ ߥߞߌ ߟߊ߫.",
+       "confirmemail_loggedin": "ߌ ߟߊ߫ ߢߎߡߍߙߋ߲ߞߏ߲ߘߏ ߓߘߊ߫ ߓߊ߲߫ ߟߊߛߙߋߦߊ߫ ߟߊ߫.",
+       "confirmemail_subject": "{{SITENAME}} ߢߎߡߍߙߋ߲ߞߏ߲ߘߏ ߛߊ߲߬ߓߊ߬ߕߐ߮ ߟߊ߬ߛߙߋ߬ߦߊ߬ߟߌ",
+       "confirmemail_invalidated": "ߢߎߡߍߙߋ߲ߞߏ߲ߘߏ ߛߊ߲߬ߓߊ߬ߕߐ߮ ߟߊ߬ߛߙߋ߬ߦߊ߬ߟߌ ߓߘߊ߫ ߘߐߛߊ߬",
+       "invalidateemail": "ߢߎߡߍߙߋ߲ߞߏ߲ߘߏ ߟߊ߬ߛߙߋ߬ߦߊ߬ߟߌ ߘߐߛߊ߬",
+       "notificationemail_subject_changed": "{{SITENAME}} ߢߎߡߍߙߋ߲ߞߏ߲ߘߏ߫ ߛߊ߲߬ߓߊ߬ߕߐ߰ ߟߊߞߎ߲߬ߘߎ߬ߣߍ߲ ߓߘߊ߫ ߡߊߝߊ߬ߟߋ߲߬.",
+       "notificationemail_subject_removed": "{{SITENAME}} ߢߎߡߍߙߋ߲ߞߏ߲ߘߏ ߛߊ߲߬ߓߊ߬ߕߐ߰ ߟߊߞߎ߲߬ߘߎ߬ߣߍ߲ ߓߘߊ߫ ߓߐ߫ ߊ߬ ߟߊ߫.",
        "scarytranscludetoolong": "[URL ߖߊ߰ߡߊ߲߬ ߞߏߖߎ߰]",
        "deletedwhileediting": "<strong>ߖߊ߲߬ߓߌ߬ߟߊ߬ߟߌ</strong> ߞߐߜߍ ߣߌ߲߬ ߕߎ߲߬ ߓߘߊ߫ ߖߏ߰ߛߌ߫ ߊ߬ ߡߊߦߟߍ߬ߡߊ߲ ߘߊߡߌ߬ߣߊ ߞߐ߫ ߌ ߓߟߏ߫.",
        "recreate": "ߊ߬ ߟߊߘߊ߲߫ ߕߎ߲߯",
        "confirm_purge_button": "ߏ߬ߞߍ߫",
+       "confirm-purge-top": "ߞߊ߬ ߢߡߊߘߏ߲߰ߣߍ߲ ߠߎ߬ ߖߏ߬ߛߌ߬ ߞߐߜߍ ߣߌ߲߬ ߘߐ߫؟",
+       "confirm-watch-button": "ߏ߬ߞߍ߫",
+       "confirm-watch-top": "ߞߐߜߍ ߣߌ߲߬ ߓߌ߬ߟߊ߬ ߌ ߟߊ߫ ߜߋ߬ߟߎ߬ߠߌ߲߬ ߛߙߍߘߍ ߘߐ߫",
+       "confirm-unwatch-button": "ߏ߬ߞߍ߫",
+       "confirm-unwatch-top": "ߞߊ߬ ߞߐߜߍ ߣߌ߲߬ ߛߋ߲߬ߓߐ߫ ߌ ߟߊ߫ ߟߊߞߙߐ߬ߛߌ߬ߕߊ߬ ߛߙߍߘߍ ߘߐ߫؟",
+       "confirm-rollback-button": "ߏ߬ߞߍ߫",
+       "confirm-rollback-top": "ߞߊ߬ ߡߊ߬ߦߟߍ߬ߡߊ߲߬ߠߌ߲ ߠߎ߬ ߟߊߞߐߛߊ߬ߦߌ߬ ߞߐߜߍ ߣߌ߲߬ ߘߐ߫؟",
+       "confirm-rollback-bottom": "ߞߍߟߌ ߣߌ߲߬ ߘߌ߫ ߣߊ߬ ߡߊ߬ߦߟߍ߬ߡߊ߲߬ߠߌ߲߬ ߓߊߕߐ߬ߡߐ߲߬ߣߍ߲ ߠߎ߬ ߟߊߞߐߛߊ߬ߦߌ߬ ߞߐߜߍ ߣߌ߲߬ ߘߐ߫ ߥߊ߯ߕߌ߫ ߞߎߘߎ߲ߣߍ߲߫ ߞߘߐ߫.",
+       "confirm-mcrrestore-title": "ߟߢߊ߬ߟߌ ߘߏ߫ ߟߊߞߎߣߎ߲߫",
+       "confirm-mcrundo-title": "ߡߊ߬ߦߟߍ߬ߡߊ߲߬ߠߌ߲ ߓߐ߫ ߊ߬ ߡߊ߬",
+       "mcrundofailed": "ߓߐߒߡߊߟߌ ߓߘߊ߫ ߗߌߙߏ߲߫",
+       "mcrundo-missingparam": "ߢߌ߬ߣߊ߬ ߓߘߊ߫ ߞߍ߫ ߟߊ߬ߓߍ߲߬ߢߐ߲߰ߡߊ ߡߊߢߌ߬ߣߌ߲߬ߞߊ߬ߣߍ߲ ߞߐ߫ ߡߞߊ߬ߛߌ߬ߟߌ ߘߐ߫",
        "parentheses-start": "⸜",
        "parentheses-end": "⸝",
+       "quotation-marks": "\"$1\"",
+       "imgmultipageprev": "ߞߐߜߍ ߢߍߕߊ",
        "imgmultipagenext": "ߞߐߜߍ ߣߊ߬ߕߐ ←",
        "imgmultigo": "ߥߊ߫߹",
        "imgmultigoto": "ߥߊ߫ ߞߐߜߍ ߣߌ߲߬ ߞߊ߲߬ $1",
+       "img-lang-go": "ߕߊ߯",
+       "table_pager_next": "ߞߐߜߍ߫ ߣߊ߬ߕߐ",
+       "table_pager_prev": "ߞߐߜߍ ߢߍߕߊ",
+       "table_pager_first": "ߞߐߜߍ ߝߟߐ",
+       "table_pager_last": "ߞߐߜߍ ߞߐ߯ߟߕߊ",
+       "table_pager_limit": "$1 ߞߣߐߘߐ ߟߎ߬ ߦߌ߬ߘߊ߬ ߞߐߜߍ ߡߊ߬",
+       "table_pager_limit_label": "ߞߎߡߘߊ ߟߎ߬ ߞߐߜߍ ߡߊ߬",
+       "table_pager_limit_submit": "ߕߊ߯",
+       "table_pager_empty": "ߞߐߝߟߌ߫ ߕߍ߫ ߦߋ߲߬",
+       "autosumm-blank": "ߞߐߜߍ ߖߏ߲߫",
+       "autosumm-replace": "ߞߣߐߘߐ ߣߐ߬ߘߐߓߌ߬ߟߊ߬  \"$1\" ߟߊ߫",
+       "autoredircomment": "ߞߐߜߍ ߓߘߊ߫ ߟߊߘߎ߲߬ߛߌ߲߫ ߦߊ߲߬ [[$1]]",
+       "autosumm-removed-redirect": "ߟߊ߬ߞߎ߲߬ߛߌ߲߬ߠߌ߲ ߓߘߊ߫ ߟߊߦߟߍ߬ߡߊ߲߫ ߦߊ߲߬ [[$1]]",
+       "autosumm-changed-redirect-target": "ߟߊ߬ߞߎ߲߬ߛߌ߲߬ߠߌ߲ ߞߎ߲߬ߕߋߟߋ߲ ߡߊߝߊ߬ߟߋ߲߫ ߞߊ߬ ߓߐ߫ [[$1]] ߞߊ߬ ߕߊ߯ [[$2]]",
+       "autosumm-new": "ߞߐߜߍ ߓߘߊ߫ ߛߌ߲ߘߌ߫ ߣߌ߲߬  \"$1\" ߡߊ߬",
+       "autosumm-newblank": "ߞߐߜߍ߫ ߘߐߞߏߟߏ߲ ߓߘߊ߫ ߛߌ߲ߘߌ߫",
+       "watchlistedit-normal-title": "ߜߋ߬ߟߎ߲߬ߠߌ߲߬ ߛߙߍߘߍ ߡߊߦߟߍ߬ߡߊ߲߫",
+       "watchlistedit-normal-legend": "ߞߎ߲߬ߕߐ߮ ߛߋ߲߬ߓߐ߫ ߜߋ߬ߟߎ߲߬ߠߌ߲߬ ߛߙߍߘߍ ߘߐ߫",
+       "watchlistedit-normal-submit": "ߞߎ߲߬ߕߐ߮ ߛߋ߲߬ߓߐ߫",
+       "watchlistedit-normal-done": "{{PLURAL:|ߞߎ߲߬ߕߐ߰ $1 ߞߋߟߋ߲ ߕߘߍ߬ ߦߋ߫|ߞߎ߲߬ߕߐ߮ $1 ߟߎ߬ ߕߎ߲߬ ߦߋ߫}} ߟߎ߫ ߛߋ߲߬ߓߐ߫ ߌ ߟߊ߫ ߜߋ߬ߟߎ߲߬ߠߌ߲߬ ߛߙߍߘߍ ߘߐ߫",
+       "watchlistedit-raw-title": "ߜߋ߬ߟߎ߲߬ߠߌ߲߬ ߛߙߍߘߍ ߡߎ߰ߡߍ",
+       "watchlistedit-raw-legend": "ߜߋ߬ߟߎ߲߬ߠߌ߲߬ ߛߙߍߘߍ ߡߎ߰ߡߍ",
+       "watchlistedit-raw-titles": "ߞߎ߲߬ߕߐ߮:",
+       "watchlistedit-raw-submit": "ߜߋ߬ߟߎ߲߬ߠߌ߲߬ ߛߙߍߘߍ ߟߏ߲ߘߐߦߊ߫",
+       "watchlistedit-raw-done": "ߌ ߟߊ߫ ߜߋ߬ߟߎ߬ߠߌ߲߬ ߛߙߍߘߍ ߓߘߊ߫ ߓߊ߲߫ ߟߏ߲ߘߐߦߊ߫ ߟߊ߫.",
+       "watchlistedit-raw-added": "{{PLURAL:$1|ߞߎ߲߬ߕߐ߮ ߁ ߕߘߍ߬ ߓߘߊ߫|ߞߎ߲߬ߕߐ߮ $1 ߟߎ߬ ߕߎ߲߬ ߓߘߊ߫ ߟߊߘߏ߲߬}} ߟߊߘߏ߲߬:",
+       "watchlistedit-raw-removed": "{{PLURAL:|ߞߎ߲߬ߕߐ߮ $1 ߕߘߍ߬ ߓߘߊ߫|ߞߎ߲߬ߕߐ߮ $1 ߟߎ߬ ߕߘߍ߬ ߓߘߊ߫}} ߛߋ߲߬ߓߐ߫:",
+       "watchlistedit-clear-title": "ߜߋ߬ߟߎ߲߬ߠߌ߲߬ ߛߙߍߘߍ ߖߏ߬ߛߌ߬",
+       "watchlistedit-clear-legend": "ߜߋ߬ߟߎ߲߬ߠߌ߲߬ ߛߙߍߘߍ ߖߏ߬ߛߌ߬",
+       "watchlistedit-clear-explain": "ߞߎ߲߬ߕߐ߮ ߟߎ߬ ߓߍ߯ ߘߌߣߊ߬ ߛߋ߲߬ߓߐ߫ ߌ ߟߊ߫ ߜߋ߬ߟߎ߬ߠߌ߲߬ ߛߙߍߘߍ ߘߐ߫",
+       "watchlistedit-clear-titles": "ߞߎ߲߬ߕߐ߮ ߟߎ߬:",
+       "watchlistedit-clear-submit": "ߜߋ߬ߟߎ߲߬ߠߌ߲߬ ߛߙߍߘߍ ߖߏ߬ߛߌ߬ (ߣߌ߲߬ ߦߋ߫ ߓߟߏߕߍ߰ߓߊߟߌ ߟߋ߬ ߘߌ߫)",
+       "watchlistedit-clear-done": "ߌ ߟߊ߫ ߜߋ߬ߟߎ߬ߠߌ߲߬ ߛߙߍߘߍ ߓߘߊ߫ ߓߊ߲߫ ߖߏ߬ߛߌ߬ ߟߊ߫.",
+       "watchlistedit-clear-jobqueue": "ߌ ߟߊ߫ ߜߋ߬ߟߎ߬ߠߌ߲߬ ߛߙߍߘߍ ߖߏ߬ߛߌ߬ߟߌ ߦߴߌ ߘߐ߫. ߊ߬ ߘߏ߲߬ ߘߌ߫ ߛߋ߫ ߥߊ߯ߕߌ߫ ߕߊ߬ ߟߊ߫߹",
+       "watchlistedit-clear-removed": "{{PLURAL:|ߞߎ߲߬ߕߐ߮ $1 ߁ ߕߘߍ߬ ߓߘߊ߫|ߞߎ߲߬ߕߐ߮ $1 ߟߎ߬ ߕߘߍ߬ ߓߘߊ߫}} ߛߋ߲߬ߓߐ߫:",
+       "watchlistedit-too-many": "ߞߐߜߍ߫ ߛߌߦߊߡߊ߲ߓߊ ߠߋ߬ ߦߌ߬ߘߊ߬ߣߍ߲߫ ߦߊ߲߬.",
        "watchlisttools-clear": "ߜߋ߬ߟߎ߲߬ߠߌ߲߬ ߞߐߜߍ ߖߏ߬ߛߌ߬",
        "watchlisttools-view": "ߡߊ߬ߦߟߍ߬ߡߊ߲߬ߠߌ߲߫ ߕߣߐ߬ߡߊ ߟߎ߫ ߦߌ߬ߘߊ߬ߟߌ",
        "watchlisttools-edit": "ߜߋ߬ߟߎ߲߬ߠߌ߲߬ ߞߐߜߍ ߦߋ߫ ߞߵߊ߬ ߡߊߦߟߍ߬ߡߊ߲߫",
        "watchlisttools-raw": "ߜߋ߬ߟߎ߲߬ߠߌ߲߬ ߞߐߜߍ ߡߎ߰ߡߍ ߡߊߦߟߍ߬ߡߊ߲߫",
        "signature": "[[{{ns:user}}:$1|$2]] ([[{{ns:user_talk}}:$1|ߓߊ߬ߘߏ߬ߟߌ]])",
+       "timezone-local": "ߕߌ߲߬ߞߎߘߎ߲",
+       "version-skins": "ߜߟߏ߬ ߡߊߞߍߣߍ߲ ߠߎ߬",
+       "version-specialpages": "ߞߐߜߍ߫ ߞߙߍߞߙߍߣߍ߲",
+       "version-parserhooks": "ߞߐ߬ߘߙߍ߬ ߞߎߙߎ߲ߞߎߙߎ߲ߠߊ",
+       "version-variables": "ߓߐߢߐ߲߯ߡߕߊ ߟߎ߬",
+       "version-editors": "ߛߓߍߦߟߊ",
+       "version-antispam": "ߞߏ߬ߘߏ (ߛߑߔߊߡ) ߢߍߓߍ߲ߠߌ߲",
+       "version-other": "ߘߏ߫ ߜߘߍ",
+       "version-hooks": "ߘߎ߲ߓߟߐ ߟߎ߬",
        "redirect": "ߟߊߞߎ߲߬ߛߌ߲߬ߣߍ߲߬ ߦߋ߫ ߞߐߕߐ߮ ߓߟߏ߫߸ ߟߊ߬ߓߊ߰ߙߊ߬ߟߊ߸ ߞߐߜߍ߸ ߡߛߊ߬ߦߌ߲߬ߠߌ߲߸ ߥߟߊ߫ ߜߊ߲߬ߞߎ߲߬ߠߌ߲ ID",
        "redirect-summary": "ߞߐߜߍ߫ ߓߟߏߡߊߞߊ߬ߣߍ߲ ߟߊߞߎ߲߬ߛߌ߲߬ߣߍ߲߬ ߦߋ߫ ߞߐߕߐ߮ (ߞߐߕߐ߮ ߕߐ߮ ߘߌ߫),ߞߐߜߍ (ߦߋ߫ ߡߛߊ߬ߦߌ߲߬ߠߌ߲ ID ߥߟߊ߫ ߞߐߜߍ ID ߘߌ ߞߊ߲߬), ߞߐߜߍ߫ ߟߊߓߊ߯ߙߕߊ ߦߋ߫ (ߟߊ߬ߓߊ߰ߙߟߊ߬ ߦߙߌߞߊ ID ߘߌ ߞߊ߲߬), ߥߟߊ߫ ߜߊ߲߬ߞߎ߲߬ߠߌ߲ ߘߏ߲߬ߕߐ߬ߟߊ ߦߋ߫ (ߜߊ߲߬ߞߎ߲߬ߠߌ߲ ID ߘߌ ߞߊ߲߬). ߟߊ߬ߓߊ߰ߙߊ߬ߟߌ:\n[[{{#Special:Redirect}}/file/Example.jpg]], \n[[{{#Special:Redirect}}/page/64308]],\n[[{{#Special:Redirect}}/revision/328429]], \n[[{{#Special:Redirect}}/user/101]], ߥߟߊ߫ \n[[{{#Special:Redirect}}/logid/186]].",
        "redirect-submit": "ߕߊ߯",
index 0b791d6..8a1f1fe 100644 (file)
        "rcfilters-filter-showlinkedto-label": "Pokaż zmiany na stronach linkujących do",
        "rcfilters-filter-showlinkedto-option-label": "<strong>Strony linkujące do</strong> zaznaczonej strony",
        "rcfilters-target-page-placeholder": "Wprowadź nazwę strony (lub kategorii)",
+       "rcfilters-alldiscussions-label": "Wszystkie dyskusje",
        "rcnotefrom": "Poniżej {{PLURAL:$5|pokazano zmianę|pokazano zmiany}} {{PLURAL:$5|wykonaną|wykonane}} po <strong>$3, $4</strong> (nie więcej niż '''$1''' pozycji).",
        "rclistfromreset": "Zresetuj wybór daty",
        "rclistfrom": "Pokaż nowe zmiany od $3 $2",
        "move-subpages": "Przenieś podstrony (nie więcej niż $1)",
        "move-talk-subpages": "Przenieś strony dyskusji podstron (nie więcej niż $1)",
        "movepage-page-exists": "Strona $1 istnieje. Automatyczne nadpisanie nie jest możliwe.",
+       "movepage-source-doesnt-exist": "Strona „$1” nie istnieje i nie może zostać przeniesiona.",
        "movepage-page-moved": "Strona $1 została przeniesiona do $2.",
        "movepage-page-unmoved": "Nazwa strony $1 nie może zostać zmieniona na $2.",
        "movepage-max-pages": "Przeniesionych zostało $1 {{PLURAL:$1|strona|strony|stron}}. Większa liczba nie może być przeniesiona automatycznie.",
        "delete_and_move_reason": "Usunięto, by zrobić miejsce dla przenoszonej strony „[[$1]]”",
        "selfmove": "Ta sama nazwa strony;\nstrony nie można przenieść na nią samą.",
        "immobile-source-namespace": "Nie można przenieść stron w przestrzeni nazw „$1”",
+       "immobile-source-namespace-iw": "Strony na innych wiki nie mogą zostać przeniesione z tej wiki.",
        "immobile-target-namespace": "Nie można przenieść stron do przestrzeni nazw „$1”",
        "immobile-target-namespace-iw": "Link interwiki jest nieprawidłowym tytułem, pod który miałaby być przeniesiona strona.",
        "immobile-source-page": "Tej strony nie można przenieść.",
        "immobile-target-page": "Nie można przenieść pod wskazany tytuł.",
+       "movepage-invalid-target-title": "Żądana nazwa strony jest nieprawidłowa.",
        "bad-target-model": "Strona docelowa używa innego modelu zawartości. Konwersja $1 → $2 nie jest możliwa.",
        "imagenocrossnamespace": "Nie można przenieść grafiki do przestrzeni nazw nie przeznaczonej dla grafik",
        "nonfile-cannot-move-to-file": "Nie można przenieść obiektu nie będącego plikiem do przestrzeni nazw „{{ns:file}}”",
index f6491eb..971568f 100644 (file)
        "rcfilters-filter-showlinkedto-label": "Mostrar alterações nas páginas que ligam para",
        "rcfilters-filter-showlinkedto-option-label": "<strong>Páginas que ligam para</strong> página selecionada",
        "rcfilters-target-page-placeholder": "Introduzir o nome de uma página (ou categoria)",
+       "rcfilters-allcontents-label": "Todo o conteúdo",
+       "rcfilters-alldiscussions-label": "Todas as discussões",
        "rcnotefrom": "Abaixo {{PLURAL:$5|é a mudança|são as mudanças}} desde <strong>$3, $4</strong> (up to <strong>$1</strong> shown).",
        "rclistfromreset": "Redefinir seleção da data",
        "rclistfrom": "Mostrar as novas alterações a partir das $2 de $3",
index 84e16ee..b3ee47b 100644 (file)
        "rcfilters-filter-showlinkedto-label": "Mostrar mudanças nas páginas que contêm hiperligações para",
        "rcfilters-filter-showlinkedto-option-label": "<strong>Páginas que contêm hiperligações</strong> para a página selecionada",
        "rcfilters-target-page-placeholder": "Introduzir o nome de uma página (ou categoria)",
+       "rcfilters-allcontents-label": "Todos os conteúdos",
+       "rcfilters-alldiscussions-label": "Todas as discussões",
        "rcnotefrom": "Abaixo {{PLURAL:$5|está a mudança|estão as mudanças}} desde <strong>$2</strong> (mostradas até <strong>$1</strong>).",
        "rclistfromreset": "Reiniciar a seleção da data",
        "rclistfrom": "Mostrar as novas mudanças a partir das $2 de $3",
        "move-subpages": "Mover subpáginas (até $1)",
        "move-talk-subpages": "Mover subpáginas da página de discussão (até $1)",
        "movepage-page-exists": "A página $1 já existe e não pode ser substituída.",
+       "movepage-source-doesnt-exist": "A página $1 não existe e não pode ser movida.",
        "movepage-page-moved": "A página $1 foi movida para $2.",
        "movepage-page-unmoved": "Não foi possível mover a página $1 para $2.",
        "movepage-max-pages": "O limite de $1 {{PLURAL:$1|página movida|páginas movidas}} foi atingido; não será possível mover mais páginas de forma automática.",
        "delete_and_move_reason": "Eliminada para poder mover \"[[$1]]\" para este título",
        "selfmove": "O título é o mesmo;\nnão é possível mover uma página para ela mesma.",
        "immobile-source-namespace": "Não é possível mover páginas no domínio \"$1\"",
+       "immobile-source-namespace-iw": "As páginas de outras wikis não podem ser movidas desta wiki.",
        "immobile-target-namespace": "Não é possível mover páginas para o domínio \"$1\"",
        "immobile-target-namespace-iw": "Uma hiperligação interwikis não é um destino válido para uma movimentação de página.",
        "immobile-source-page": "Esta página não pode ser movida.",
        "immobile-target-page": "Não é possível mover para esse título de destino.",
+       "movepage-invalid-target-title": "O nome pedido é inválido.",
        "bad-target-model": "O destino pretendido usa um modelo de conteúdo diferente. Não é possível converter de $1 para $2.",
        "imagenocrossnamespace": "Não é possível mover imagem para domínio que não de imagens",
        "nonfile-cannot-move-to-file": "Não é possível mover algo que não é um ficheiro para o domínio de ficheiros",
        "permanentlink": "Hiperligação permanente",
        "permanentlink-revid": "Identificador de revisão",
        "permanentlink-submit": "Ir para a revisão",
+       "newsection": "Secção nova",
+       "newsection-page": "Página de destino",
+       "newsection-submit": "Ir para a página",
        "dberr-problems": "Desculpe! Este sítio está com dificuldades técnicas.",
        "dberr-again": "Experimente esperar alguns minutos e atualizar.",
        "dberr-info": "(Não foi possível aceder ao servidor da base de dados: $1)",
index 86933e3..07a317e 100644 (file)
        "mytalk": "In the personal URLs page section - right upper corner.\n\nUsed as link title in your personal toolbox.\n\nSee also:\n* {{msg-mw|Mytalk}}\n* {{msg-mw|Accesskey-pt-mytalk}}\n* {{msg-mw|Tooltip-pt-mytalk}}\n{{Identical|Talk}}",
        "anontalk": "Same as {{msg-mw|mytalk}} but used for non-logged-in users.\n{{Identical|Talk}}\n\nSee also:\n* {{msg-mw|Accesskey-pt-anontalk}}\n* {{msg-mw|Tooltip-pt-anontalk}}",
        "navigation": "This is shown as a section header in the sidebar of most skins.\n\n{{Identical|Navigation}}",
-       "and": "The translation for \"and\" appears in the [[Special:Version]] page, between the last two items of a list. If a comma is needed, add it at the beginning without a gap between it and the \"&\". &amp;#32; is a blank space, one character long. Please leave it as it is.\n\nThis can also appear in the credits page if the credits feature is enabled,for example [{{canonicalurl:Support|action=credits}} the credits of the support page]. (To view any credits page type <nowiki>&action=credits</nowiki> at the end of any URL in the address bar.)\n{{Identical|And}}",
+       "and": "The translation for \"and\" appears in the [[Special:Version]] page, between the last two items of a list. If a comma is needed, add it at the beginning without a gap between it and the '''<code>&amp;#32;</code>''' is a blank space, one character long, as a numeric character entity reference (in order to avoid its automatic removal at start of the wikipage). Please leave it as it is (this does '''not''' imply any semicolon punctuation), or remove the whole sequence completely in languages that don't use a leading space.\n\nThis can also appear in the credits page if the credits feature is enabled,for example [{{canonicalurl:Support|action=credits}} the credits of the support page]. (To view any credits page type <nowiki>&action=credits</nowiki> at the end of any URL in the address bar.)\n{{Identical|And}}",
        "faq": "FAQ is short for ''frequently asked questions''.\n{{Identical|FAQ}}",
        "sitetitle": "{{Ignore}}",
        "sitesubtitle": "{{Ignore}}",
index c4fa153..f886c73 100644 (file)
        "rcfilters-watchlist-markseen-button": "Signe tutte le cangiaminde cumme 'ndrucate",
        "rcfilters-watchlist-edit-watchlist-button": "Cange l'elenghe de le pàggene condrollate",
        "rcfilters-preference-help": "Careche le urteme cangiaminde senze filtre de recerche o funziune de evidenziazione.",
+       "rcfilters-allcontents-label": "Tutte le condenute",
+       "rcfilters-alldiscussions-label": "Tutte le 'ngazzaminde",
        "rcnotefrom": "Sotte {{PLURAL:$5|ste 'u cangiamende|stonne le cangiaminde}} da <strong>$3, $4</strong> ('nzigne a <strong>$1</strong> fatte vedè).",
        "rclistfrom": "Fà vedè le urteme cangiaminde partenne da $3 $2",
        "rcshowhideminor": "$1 cangiaminde stuèdeche",
        "uploadstash-bad-path-unknown-type": "Tipe scanusciute \"$1\".",
        "uploadstash-bad-path-unrecognized-thumb-name": "Nome d'a miniature non acchiate.",
        "uploadstash-bad-path-bad-format": "'A chiave \"$1\" non ge ste jndr'à 'nu formate appropriate.",
+       "uploadstash-file-not-found": "Chiave \"$1\" non acchiate jndr'à scorte.",
        "uploadstash-file-not-found-no-thumb": "No ge se pò avè 'a miniature.",
        "uploadstash-file-not-found-no-local-path": "Nisciune percorse locale pa vôsce in scale.",
        "uploadstash-file-not-found-no-object": "Non ge pozze ccrejà 'nu oggette file locale pa miniature.",
        "pageswithprop-legend": "Pàggene cu 'na probbietà d'a pàgene",
        "pageswithprop-text": "Sta pàgene elenghe le pàggene ca ausane 'na particolare probbietà d'a pàgene.",
        "pageswithprop-prop": "Nome d'a probbietà:",
+       "pageswithprop-reverse": "Ordenamende a smerse",
+       "pageswithprop-sortbyvalue": "Ordene pe valore d'a probbietà",
        "pageswithprop-submit": "Véje",
        "pageswithprop-prophidden-long": "valore d'a probbietà d'u teste lunghe scunnute ($1)",
        "pageswithprop-prophidden-binary": "valore probbietà binarie scunnute ($1)",
        "speciallogtitlelabel": "Destinazione (titole o {{ns:user}}:nome de l'utende pe l'utende):",
        "log": "Archivije",
        "logeventslist-submit": "Fà 'ndrucà",
+       "logeventslist-more-filters": "'Ndruche le archivije aggiundive:",
+       "logeventslist-patrol-log": "Archivije de le condrolle",
+       "logeventslist-tag-log": "Archivije de le tag",
        "all-logs-page": "Tutte l'archivije pubbleche",
        "alllogstext": "Visualizzazione combinate de tutte le archivije disponibbele sus a {{SITENAME}}.\nTu puè restringere 'a viste selezionanne 'u tipe de archivije, 'u nome utende (senzibbile a le maiuscole), o le pàggene coinvolte (pure chiste senzibbile a le maiuscole).",
        "logempty": "Non ge stè 'n'anema de priatorie jndr'à l'archivije.",
        "dellogpage": "Archivie de le scangellaminde",
        "dellogpagetext": "Sotte ste 'na liste de le cchiù recende scangellaziune.",
        "deletionlog": "Archivije de le scangellaminde",
+       "log-name-create": "Archivije d'a ccrejazione de le pàggene",
+       "log-description-create": "Sotte ste 'n'elenghe de le urteme ccrejaziune de pàgene.",
+       "logentry-create-create": "$1 pàgena {{GENDER:$2|ccrejate}} $3",
        "reverted": "Turnà a 'a revisiona cchiù recende",
        "deletecomment": "Mutive:",
        "deleteotherreason": "Otre mutive de cchiù:",
        "deleting-backlinks-warning": "<strong>Attenziò:</strong> [[Special:WhatLinksHere/{{FULLPAGENAME}}|Otre pàggene]] appondene o vonne 'a pàgene ca tu vue ccù scangìlle.",
        "rollback": "Annulle le cangiaminde",
        "rollback-confirmation-confirm": "Pe piacere conferme:",
+       "rollback-confirmation-yes": "Annulle",
+       "rollback-confirmation-no": "Annulle",
        "rollbacklink": "annulle 'u cangiaminde",
        "rollbacklinkcount": "annulle $1 {{PLURAL:$1|cangiamende|cangiaminde}}",
        "rollbacklinkcount-morethan": "annulle cchiù de $1 {{PLURAL:$1|cangiamende|cangiaminde}}",
        "pageinfo-category-subcats": "Numere de sottocategorije",
        "pageinfo-category-files": "Numere de file",
        "pageinfo-user-id": "ID de l'utende",
+       "pageinfo-file-hash": "Valore hash",
        "pageinfo-view-protect-log": "'Ndruche l'archivije de le protezziune pe sta pàgene.",
        "markaspatrolleddiff": "Signe cumme condrollate",
        "markaspatrolledtext": "Signe sta pàgene cumme condrollate",
        "compare-revision-not-exists": "'A revisione ca è specificate non g'esiste.",
        "diff-form": "Differenze",
        "permanentlink-revid": "ID d'a revisione",
+       "newsection-submit": "Veje 'a pàgene",
        "dberr-problems": "Sime spiacende! Stu site stè 'ngondre de le difficoltà tecniche.",
        "dberr-again": "Aspitte quacche minute e pò recareche.",
        "dberr-info": "(Non ge riuscime a trasè sus a'u server d'u database: $1)",
        "htmlform-datetime-invalid": "'U valore specificate non jè 'na date. Pruéve ausanne 'u formate AAAA-MM-GG HH:MM:SS",
        "htmlform-date-toolow": "'U valore specificate avène apprime da date congesse de $1.",
        "htmlform-date-toohigh": "'U valore specificate avène apprisse da date congesse de $1.",
+       "htmlform-time-toolow": "'U valore specificate avène apprime de l'orarie congesse de $1.",
+       "htmlform-time-toohigh": "'U valore specificate avène apprisse de l'orarie congesse de $1.",
+       "htmlform-datetime-toolow": "'U valore specificate avène apprime da date e orarie congesse de $1.",
+       "htmlform-datetime-toohigh": "'U valore specificate avène apprisse da date e orarie congesse de $1.",
        "htmlform-title-badnamespace": "[[:$1]] non ge stè jndr'à 'u namespace \"{{ns:$2}}\".",
        "htmlform-title-not-creatable": "\"$1\" jè 'nu titole de 'na pàgene ca no se pò ccrejà",
        "htmlform-title-not-exists": "$1 non g'esiste.",
index d3f8b0a..e3dc8f5 100644 (file)
        "rcfilters-filter-showlinkedto-label": "Показать правки на ссылающихся страницах",
        "rcfilters-filter-showlinkedto-option-label": "<strong>Страницы, ссылающиеся</strong> на выбранную",
        "rcfilters-target-page-placeholder": "Введите имя страницы (или категории)",
+       "rcfilters-allcontents-label": "Все пространства имён",
+       "rcfilters-alldiscussions-label": "Все обсуждения",
        "rcnotefrom": "Ниже {{PLURAL:$5|указано изменение|перечислены изменения}} с <strong>$3, $4</strong> (показано не более <strong>$1</strong>).",
        "rclistfromreset": "Сбросить выбор даты",
        "rclistfrom": "Показать изменения с $3 $2.",
        "move-subpages": "Переименовать подстраницы (до $1)",
        "move-talk-subpages": "Переименовать подстраницы страницы обсуждения (до $1)",
        "movepage-page-exists": "Страница $1 уже существует и не может быть автоматически перезаписана.",
+       "movepage-source-doesnt-exist": "Страница $1 не существует, а потому не может быть переименована.",
        "movepage-page-moved": "Страница $1 была переименована в $2.",
        "movepage-page-unmoved": "Страница $1 не может быть переименована в $2.",
        "movepage-max-pages": "{{PLURAL:$1|Была переименована|Было переименовано|Были переименованы}} $1 {{PLURAL:$1|страница|страницы|страниц}} — это максимум; большее число страниц автоматически переименовать нельзя.",
        "delete_and_move_reason": "Удалено для возможности переименования «[[$1]]»",
        "selfmove": "Невозможно переименовать страницу: исходное и новое имя страницы совпадают.",
        "immobile-source-namespace": "Невозможно переименовывать страницы в пространстве имён «$1»",
+       "immobile-source-namespace-iw": "Страницы из других вики не могут быть переименованы в этой вики.",
        "immobile-target-namespace": "Невозможно переместить страницу в пространство имён «$1»",
        "immobile-target-namespace-iw": "Ссылка интервики не может быть использована для переименования.",
        "immobile-source-page": "Эту страницу нельзя переименовать.",
        "immobile-target-page": "Нельзя присвоить странице это имя.",
+       "movepage-invalid-target-title": "Запрошенное имя недопустимо.",
        "bad-target-model": "Невозможно преобразовать $1 в $2. У страниц несовместимые модели содержимого.",
        "imagenocrossnamespace": "Невозможно дать файлу имя из другого пространства имён",
        "nonfile-cannot-move-to-file": "Невозможно переименовывать не-файловые страницы в файлы",
        "specialmute-success": "Изменения по отключению уведомлений были сохранены. Просмотрите всех отключённых участников на [[Special:Preferences|ваших настройках]].",
        "specialmute-submit": "Подтвердить",
        "specialmute-label-mute-email": "Отключить эл. почту от этого участника",
-       "specialmute-header": "Пожалуйста, выберите настройки уведомлений для {{GENDER:$1|участника|участницы}} <b>{{BIDI:[[User:$1|$1]]}}</b>.",
+       "specialmute-header": "Пожалуйста, выберите настройки отключения уведомлений для {{GENDER:$1|участника|участницы}} <b>{{BIDI:[[User:$1|$1]]}}</b>.",
        "specialmute-error-invalid-user": "Указанное вами имя участника не может быть найдено.",
+       "specialmute-error-no-options": "Функции отключения уведомлений недоступны. Это вызвано либо тем, что вы не подтвердили электронную почту, либо тем, что администратор выключил в этой вики функции электронной почты и\\или функции чёрного списка.",
        "specialmute-email-footer": "Для управления настройками эл. почты {{GENDER:$2|участника|участницы}} {{BIDI:$2}}, пожалуйста, посетите <$1>.",
        "specialmute-login-required": "Пожалуйста авторизируйтесь, чтобы управлять отключением уведомлений.",
        "mute-preferences": "Настройки выключения",
index 0719083..fc6d25f 100644 (file)
@@ -33,7 +33,7 @@
        "tog-previewonfirst": "پھرين سنوار تي پيش-نگاھ ڏيکاريو",
        "tog-enotifwatchlistpages": "منهنجي نظر ۾ فھرست اندر شامل ڪنهن صفحي يا فائيل ۾ تبديل پيش اچي مون کي برقٽپال اماڻيو",
        "tog-enotifusertalkpages": "منهنجي مباحثي صفحي ۾ تبديليءَ جي صورت ۾ مون کي برقٽپال اماڻيو",
-       "tog-enotifminoredits": "صÙ\81Ø­Ù\86 Û¾ Ù\85عÙ\85Ù\88Ù\84Ù\8a ØªØ±Ù\85Ù\8aÙ\85Ù\86 Ø¬Ù\8a ØµÙ\88رت Û¾ بہ مون کي برقٽپال ڪريو",
+       "tog-enotifminoredits": "صÙ\81Ø­Ù\86 Û½ Ù\81ائÙ\8aÙ\84Ù\86 Ø¬Ù\8a Ù\85عÙ\85Ù\88Ù\84Ù\8a Ø³Ù\86Ù\88ارÙ\86 Ø¨Ø§Ø¨Øª بہ مون کي برقٽپال ڪريو",
        "tog-enotifrevealaddr": "پڌراين ۾ منهنجو برقٽپال پتو ظاهر ڪريو",
        "tog-shownumberswatching": "ڏسندڙ واپرائيندڙن جو انگ ڏيکاريو",
        "tog-oldsig": "توھان جو موجوده دستخط:",
        "returnto": "$1 ڏانھن وَرو.",
        "tagline": "{{SITENAME}} طرفان",
        "help": "مدد",
+       "help-mediawiki": "ميڊياوڪي بابت مدد",
        "search": "ڳولا",
        "searchbutton": "ڳوليو",
        "go": "هلو",
        "history": "صفحي جي سوانح",
        "history_short": "سوانح",
        "history_small": "سوانح",
+       "updatedmarker": "توھان جي آخري ڦيري کان جديديل",
        "printableversion": "ڇپائتو پرت",
        "permalink": "مسقتل ڳنڍڻو",
        "print": "ڇاپيو",
        "privacy": "ذاتيات پاليسي",
        "privacypage": "Project:ذاتيات پاليسي",
        "badaccess": "اجازتي چُڪَ",
-       "badaccess-groups": "هن عمل کي محدود ڪيو ويو آهي $1 {{PLURAL:$2|جو اختيار رکندڙ|جا اختيار رکندڙن}} لاءِ.",
+       "badaccess-groups": "توھان جنھن عمل لاءِ عرض ڪيو آھي اھو $1 {{PLURAL:$2|گروھ|گروھن}} جي واپرائيندڙن تائين محدود ٿيل آھي.",
        "versionrequired": "ذريعات‌وڪي جو ورزن $1 درڪار",
        "versionrequiredtext": "هيءُ صفحو استعمال ڪرڻ لاءِ ذريعات‌وڪي جو ورزن $1 درڪار آهي. وڌيڪ ڄاڻڻ لاءِ [[Special:Version|ورزن بابت صفحو]] ڏسو.",
        "ok": "ٺيڪ",
+       "backlinksubtitle": "→ $1",
        "retrievedfrom": "\"$1\" تان ورتل",
        "youhavenewmessages": "{{PLURAL:$3|توھان وٽ}} $1 ($2) آھن.",
+       "youhavenewmessagesfromusers": "{{PLURAL:$4|توھان کي}} {{PLURAL:$3|ٻي واپرائيندڙ|$3 واپرائيندڙن}} ($2) کان $1 آھن.",
        "youhavenewmessagesmanyusers": "توهان لاءِ ڪيترن ئي واپرائيندڙن ($2) طرفان $1 آهن.",
        "newmessageslinkplural": "{{PLURAL:$1|ھڪ نئون پيغام|999=نوان پيغام}}",
        "newmessagesdifflinkplural": "آخري {{PLURAL:$1|تبديلي|999=تبديليون}}",
        "site-atom-feed": "$1 اڻو روان رسد",
        "page-rss-feed": "\"$1\" RSS برق مواد",
        "page-atom-feed": "\"$1\" اڻو روان رسد",
+       "feed-atom": "ايٽم",
+       "feed-rss": "آر.ايس.ايس",
        "red-link-title": "$1 (صفحو وجود نٿو رکي)",
        "sort-descending": "لهندڙ ترتيب ڏيو",
        "sort-ascending": "چڙهندڙ ترتيب ڏيو",
        "badarticleerror": "هن صفحي تي اهڙو عمل ڪار نہ آهي.",
        "cannotdelete": "$1 نالي صفحو يا فائيل ڊهي نہ سگھيو. ٿي سگھي ٿو تہ ڪنهن ان کي اڳ ۾ ئي ڊاهي ڇڏيو هجي.",
        "cannotdelete-title": "$1 نالي صفحي کي ڊاهي نہ ٿا سگھون.",
+       "delete-scheduled": "صفحو \"$1\" ڊاھ لاءِ رٿيل آھي.\nمھرباني ڪري صابر رھو.",
        "badtitle": "خراب عنوان",
        "badtitletext": "صفحي جو گھربل عنوان ڪار ڪونهي، يا خالي آهي، يا وري غيردرست طريقي سان ڳنڍيل بين‌الزباني يا بين‌الوڪي عنوان آهي. \nان ۾ هڪ يا هڪ کان وڌيڪ اهڙا اکر موجود آهن، جيڪي عنوان ۾ استعمال ڪري نٿا سگھجن.",
        "title-invalid-utf8": "صفحي جي ڄاڻايل عنوان ۾ ناقابلِڪار يُو ٽِيئيف-8 ترتيب شامل آھي.",
        "title-invalid-interwiki": "ڄاڻايل عنوان ۾ اهڙو بين‌الوڪِي ڳنڍڻو شامل آهي، جيڪو عنوانن ۾ استعمال ڪري نٿو سگھجي.",
+       "title-invalid-talk-namespace": "گھربل پصفحي عنوان ڪنھن بحث صفحي ڏانھن اشارو ڪري ٿو جيڪو وجود نٿو رکي سگھي.",
        "title-invalid-characters": "صفحي جي ڄاڻايل عنوان ۾ ناقابلِڪار اکر شامل آهن: \"$1\".",
        "title-invalid-leading-colon": "صفحي جي ڄاڻايل عنوان جي ابتدا ۾ ناقابلِڪار ڪالن شامل آهي.",
        "viewsource": "ڪوڊ ڏسو",
        "viewsource-title": "$1 جو ڪوڊ ڏسو",
-       "protectedpagetext": "Ù\87Ù\8aØ¡Ù\8f ØµÙ\81Ø­Ù\88 ØªØ±Ù\85Ù\8aÙ\85Ù\86 Û½ Ù»Ù\8aÙ\86 Ø¹Ù\85Ù\84Ù\86 Ú©Ø§Ù\86 Ø¨Ú\86ائڻ Ù\84اءÙ\90 ØªØ­Ù\81ظÙ\8aÙ\84 آهي.",
+       "protectedpagetext": "Ù\87Ù\8aØ¡Ù\8f ØµÙ\81Ø­Ù\88 Ø³Ù\88ارڻ Û½ Ù»Ù\8aÙ\86 Ø¹Ù\85Ù\84Ù\86 Ú©Ø§Ù\86 Ø¨Ú\86ائڻ Ù\84اءÙ\90 ØªØ­Ù\81ظÙ\8aÙ\88 Ù\88Ù\8aÙ\88 آهي.",
        "viewsourcetext": "توهان هن صفحي جو ڪوڊ ڏسي ۽ نقل ڪري سگھو ٿا.",
+       "viewyourtext": "توھان ھاڻي ھن صفحي ۾ <strong>پنھنجي سنوارن</strong> جو ذريعو ڏسي ۽ نقل ڪري سگھو ٿا.",
        "protectedinterface": "هي صفحو سافٽ ويئر جو انٽرفيس متعين ڪري ٿو ۽ غلط استعال کان بچڻ لاءِ ان کي تحفظيو ويو آهي.\nتمام وڪي ۾ ترجمو شامل ڪرڻ لاءِ يا هن ۾ تبديلي ڪرڻ لاءِ ميڊياوڪي ترجمو [https://translatewiki.net/ translatewiki.net] استعمال ڪيو.",
        "namespaceprotected": "توهان کي نانءُپولار <strong>$1</strong> جا صفحا سنوارڻ جا اختيار ناهن.",
        "sitecssprotected": "اوهان وٽ ھن سيايسايس صفحي کي سنوارڻ جي اجازت ناھي، ڇو تہ ان سان سڀني گھمندڙ متاثر ٿي سگھن ٿا.",
        "mycustomcssprotected": "توهان کي هيءُ CSS صفحو سنوارڻ جي اجازت نہ آهي.",
-       "mycustomjsprotected": "توهان کي هيءُ جاوا اسڪرپٽ صفحو سنوارڻ جي اجازت حاصل ڪانهي.",
+       "mycustomjsprotected": "توهان کي هيءُ جاوا-اسڪرپٽ صفحو سنوارڻ جي اجازت ناھي.",
        "myprivateinfoprotected": "توهان کي پنهنجي ذاتي معلومات سنوارڻ جي اجازت حاصل نہ آهي.",
        "mypreferencesprotected": "توھان کي پنھنجون ترجيحون سنوارڻ جي اجات حاصل ڪانھي.",
        "ns-specialprotected": "خاص صفحا سنواري نٿا سگھجن.",
-       "titleprotected": "[[User:$1|$1]] اهڙي عنوان سان صفحو سرجڻ تي روڪ لڳائي ڇڏي آهي. سبب <em>$2</em> ڄاڻايو ويو آهي.",
-       "invalidtitle": "غلط عنوان",
+       "titleprotected": "[[User:$1|$1]] اهڙي عنوان سان صفحو سرجڻ تي روڪ لڳائي ڇڏي آهي.\nسبب <em>$2</em> ڄاڻايو ويو آهي.",
+       "invalidtitle": "ناقابلِڪار عنوان",
        "exception-nologin": "داخل ٿيل نہ آهيو",
-       "virus-unknownscanner": "اڻڄاتل اينٽي وائرس:",
+       "virus-unknownscanner": "اڻڄاتل اينٽي-وائرس:",
+       "logging-out-notify": "توھان کي خارج ڪيو پيو وڃي، مھرباني ڪري ترسو.",
+       "logout-failed": "ھاڻي خارج نٿو ٿي سگھجي: $1",
        "cannotlogoutnow-title": "ھاڻي خارج نٿو ٿي سگھجي",
        "cannotlogoutnow-text": "$1 استعمال ڪرڻ دوران خارج ٿيڻ ممڪن نہ آھي.",
        "welcomeuser": "ڀليڪار، $1!",
+       "welcomecreation-msg": "توھان جو کاتو کلي چڪو آھي.\nتوھان {{SITENAME}} لاءِ پنھنجون [[Special:Preferences|ترجيحون]] جيڪڏھن وڻي تہ بدلائي سگھو ٿا.",
        "yourname": "واپرائيندڙ-نانءُ:",
        "userlogin-yourname": "واپرائيندڙ-نانءُ",
        "userlogin-yourname-ph": "پنھنجو واپرائيندڙ-نانءُ ڄاڻايو",
        "createacct-realname": "اصل نالو (مرضيءَ موجب)",
        "createacct-reason": "سبب",
        "createacct-reason-ph": "توهان ٻيو کاتو ڇو کولي رهيا آهيو",
+       "createacct-reason-help": "کاتو سرجڻ لاگ ۾ ڏيکاريل پيغام",
        "createacct-submit": "پنھنجو کاتو کوليو",
        "createacct-another-submit": "کاتو کوليو",
        "createacct-continue-submit": "کاتو کولڻ جاري رکو",
        "changepassword-throttled": "توهان تازو ئي داخل ٿيڻ جون هيڪانديون گھڻيون ڪوششون ڪيون آهن. مهرباني ڪري $1 لاءِ ترسي پوءِ وري ڪوشش ڪريو.",
        "botpasswords": "بوٽ جو ڳجھولفظ",
        "botpasswords-disabled": "بوٽ ڳجھالفظ ناقابلِڪار ڪيل آھن.",
-       "botpasswords-existing": "باٽ جا هاڻوڪا پاسورڊَ",
-       "botpasswords-createnew": "باٽ جو نئون پاسورڊ ٺاهيو",
-       "botpasswords-editexisting": "باٽ جي هاڻوڪي پاسورڊ کي سنواريو",
+       "botpasswords-existing": "بوٽ جا موجودہ ڳجھالفظ",
+       "botpasswords-createnew": "بوٽ جو نئون ڳجھولفظ ٺاهيو",
+       "botpasswords-editexisting": "بوٽ جو موجودہ گجھولفظ سنواريو",
        "botpasswords-label-appid": "باٽ جو نالو:",
        "botpasswords-label-create": "سرجيو",
        "botpasswords-label-update": "تجديد",
        "botpasswords-label-resetpassword": "ڳجھولفظ ٻيھر مقرر ڪريو",
        "botpasswords-label-grants-column": "منظور",
        "botpasswords-bad-appid": "بوٽ نانءُ \"$1\" قابلِڪار ناھي.",
-       "botpasswords-created-title": "باٽ پاسورڊ ٺاهيو ويو آهي",
-       "botpasswords-deleted-title": "باٽ پاسورڊ ڊاٿو ويو",
+       "botpasswords-created-title": "بوٽ گجھولفظ ٺاھيو ويو",
+       "botpasswords-deleted-title": "بوٽ ڳجھولفظ ڊاٿو ويو",
        "resetpass_forbidden": "ڳجھالفظ بدلائي نٿا سگھجن",
        "resetpass_forbidden-reason": "ڳجھالفظ بدلائي نٿا سگھجن:$1",
        "resetpass-no-info": "هيءُ صفحو پڙهڻ لاءِ داخل ٿيڻ ضروري آهي.",
        "savechanges": "تبديليون سانڍيو",
        "publishpage": "صفحو ڇاپيو",
        "publishchanges": "تبديليون ڇاپيو",
-       "savearticle-start": "صفحو سانڍيو",
-       "savechanges-start": "تبديليون سانڍيو",
-       "publishpage-start": "صفحو ڇاپيو",
-       "publishchanges-start": "تبديليون ڇاپيو",
+       "savearticle-start": "صفحو سانڍيو",
+       "savechanges-start": "تبديليون سانڍيو",
+       "publishpage-start": "صفحو ڇاپيو",
+       "publishchanges-start": "تبديليون ڇاپيو",
        "preview": "پيش نگاھ",
        "showpreview": "پيش نگاھ",
        "showdiff": "تبديليون ڏيکاريو",
        "newarticletext": "توھان اھڙي صفحي جو ڳنڍڻو وٺي ھتي پھتا آھيو، جيڪو اڃا وجود نٿو رکي.\nاھڙو صفحو جوڙڻ لاءِ، ھيٺين دٻي ۾ لکڻ شروع ڪريو (وڌيڪ ڄاڻڻ لاءِ [$1 امدادي صفحو] ڏسندا).\nجي توھان ھتي غلطيءَ ۾ اچي ويا آهيو، تہ رڳو پنھنجي جھانگُوءَ جي <strong>back</strong> بٽڻ تي ٽڙڪ ڪريو.",
        "noarticletext": "في‌الوقت هن صفحي اندر ڪو بہ ٽيڪسٽ نہ آهي.\nتوهان ٻين صفحن ۾ [[Special:Search/{{PAGENAME}}|search ساڳي عنوان جي ڳولا]] ڪري سگھو ٿا،  \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": "صفحي \"{{FULLPAGENAME}}\" جو نمبر #$1 وجود نٿو رکي.\n\nاڪثر اهو تڏهن ٿيندو آهي، جڏهن اوهان ڪنهن پراڻي ڳنڍڻي تان اچو يا صفحو [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} ڊاٺو] ويو هجي.\n\n.",
+       "missing-revision": "صفحي \"{{FULLPAGENAME}}\" جو نمبر #$1 وجود نٿو رکي.\n\nاڪثر اهو تڏهن ٿيندو آهي، جڏهن اوهان ڪنهن پراڻي ڳنڍڻي تان اچو يا صفحو [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} ڊاٺو] ويو هجي.",
        "userpage-userdoesnotexist-view": "واپرائيندڙ کاتو $1 درج ٿيل نہ آهي.",
        "blocked-notice-logextract": "هيءُ واپرائيندڙ في‌الحال بندشيل آهي.\nتازو بندش لاگ حوالي طور پيش ڪجي ٿو:",
        "updated": "(تجديديل)",
        "yourtext": "توهان جو متن",
        "storedversion": "سانڍيل مسودو",
        "yourdiff": "تفاوت",
-       "copyrightwarning": "ياد رکندا تہ {{SITENAME}} لاءِ سموريون ڀاڱيداريون $2 تحت پڌريون ڪجن ٿيون (تفصيلن لاءِ $1 ڏسندا). اوهان جي تحرير کي {{SITENAME}} جي قائدن تحت سنواري سگهجي ٿو. جيڪڏهن اوهان نٿا چاهيو تہ اوهان جي لکڻين کي بي رحميءَ سان سنواريو وڃي يا ورهائي عام ڪيو وڃي تہ پوءِ پنهنجي لکڻي هتي جمع نہ ڪرايو. پنهنجو مواد هتي جمع ڪرڻ جو مطلب هوندو تہ توهان کي جمع ڪرايل مواد جي مفت فراهمي ۽ کُليل تبديليءَ تي ڪوبہ اعتراز ناهي.<br />\nتوهان اهڙي پڪ ڏيڻ جا پابند پڻ آهيو تہ توهان جو جمع ڪرايل مواد توهان جو پنهنجو لکيل آهي يا وري توهان ڪنهن مفت وسيلي تان ڪاپي ڪيو آهي.\n'''تحفظيل حق ۽ واسطا رکندڙ مواد واسطيدار مالڪ کان اڳواٽ اجازت وٺڻ کان سواءِ هتي جمع نہ ڪريو.'''",
+       "copyrightwarning": "ياد رکندا تہ {{SITENAME}} لاءِ سموريون ڀاڱيداريون $2 ھيٺ ڏنل ڄاتيون وڃن ٿيون (تفصيلن لاءِ $1 ڏسندا).\nجيڪڏهن اوهان نٿا چاهيو تہ اوهان جي لکڻيءَ کي بي رحميءَ سان سنواريو وڃي يا ورهائي عام ڪيو وڃي تہ پوءِ ان کي هتي اماڻيو.<br />\nتوهان اسان سان اھو بہ وچن ڪريو ٿا تہ ھي توهان پاڻ لکيو آھي يا وري ڪنھن مفت وسيلي يا عوامي ڊومين تان نقل ڪيو آهي.\n<strong>حق-۽-واسطا-رکندڙ ڪم کان اجازت سواءِ نہ اماڻيو.</strong>",
        "copyrightwarning2": "ياد رکندا تہ {{SITENAME}} لاءِ سموريون ڀاڱيدارين کي ٻيا ڀاڱيدار سنواري، بدلائي، يا ڊاهي سگھن ٿا. جيڪڏهن اوهان نہ ٿا چاهيو تہ اوهان جي لکڻين کي بي رحميءَ سان سنواريون وڃي يا ورهائي عام ڪيو وڃي تہ پوءِ پنهنجي لکڻي هتي جمع نہ ڪرايو.</br>\nتوهان اهڙي پڪ ڏيڻ جا پابند پڻ آهيو تہ توهان جو جمع ڪرايل مواد توهان جو پنهنجو لکيل آهي يا وري توهان ڪنهن اهڙي ئي مفت عوامي وسيلي تان ڪاپي ڪيو آهي. (تفصيلن لاءِ $1 ڏسندا).\n\n<strong>تحفظيل حق ۽ واسطا رکندڙ مواد واسطيدار مالڪ کان اڳواٽ اجازت وٺڻ بنان هتي جمع نہ ڪريو.</strong>",
-       "protectedpagewarning": "<strong>چتاءُ: هيءَ صفحو اهڙيءَ ريت تحفظيو ويو آهي جو فقط منتظمين ئي ان کي سنواري سگھن ٿا. </strong>\nتازه ترين لاگ حوالي طور پيش ڪجي ٿو:",
+       "protectedpagewarning": "<strong>چتاءُ: هيءَ صفحو اهڙيءَ ريت تحفظيو ويو آهي جو فقط منتظم ئي ان کي سنواري سگھن ٿا. </strong>\nتازي-ترين لاگ داخلا حوالي طور ھيٺ پيش ڪجي ٿي:",
        "semiprotectedpagewarning": "<strong>نوٽ:</strong> هيءَ صفحو اهڙيءَ ريت تحفظيو ويو آهي جو فقط خودڪار نموني پڪ ڪيل واپرائيندڙ ئي ان کي سنواري سگھن ٿا.\nتازه ترين لاگ حوالي طور پيش ڪجي ٿو:",
        "templatesused": "هن صفحي تي استعمال ٿيندڙ {{PLURAL:$1|سانچو|سانچا}}:",
        "templatesusedpreview": "هن پيش نگاھ ۾ استعمال ٿيل {{PLURAL:$1|سانچو|سانچا}}:",
        "templatesusedsection": "هن سيڪشن ۾ استعمال ٿيل {{PLURAL:$1|سانچو|سانچا}}:",
        "template-protected": "(تحفظيل)",
        "template-semiprotected": "(نيم-تحفظيل)",
-       "hiddencategories": "هيءُ صفحو  {{PLURAL:$1|1 لڪل زمري|$1 لڪل زمرن}}: جو رڪن آهي:",
+       "hiddencategories": "هيءُ صفحو {{PLURAL:$1|1 لڪل زمري|$1 لڪل زمرن}}: جو رڪن آهي:",
        "edittools-upload": "-",
        "nocreatetext": "{{SITENAME}} نوان صفحا سرجڻ جي روڪَ ڪئي آھي.\nتوھان اڳ ئي موجود صفحن کي سنواري سگھو ٿا، يا [[Special:UserLogin|داخل ٿي يا نئون کاتو کولي سگھو ٿا]].",
-       "nocreate-loggedin": "توهان کي نوان صفحا سرجڻ جي اجازت حاصل ڪانهي.",
-       "sectioneditnotsupported-title": "سيڪشن جي سنوار ممڪن نہ آهي",
-       "sectioneditnotsupported-text": "هن صفحي تي سيڪشن کي سنوارڻ ممڪن نہ آهي.",
-       "permissionserrors": "اجازتÙ\86اÙ\85Ù\8a Ø¬Ù\8a Ú\86Ù\8fÚªÙ\8e",
-       "permissionserrorstext": "هيٺين {{PLURAL:$1|سبب|سببن}} ڪري، توهان کي اهو ڪرڻ جي اجازت حاصل ڪانهي.",
+       "nocreate-loggedin": "توهان کي نوان صفحا سرجڻ جي اجازت ناھي.",
+       "sectioneditnotsupported-title": "ڀاڱي جي سنوار سپورٽڊ ناھي",
+       "sectioneditnotsupported-text": "هن صفحي تي ڀاڱي جي سنوار سپورٽڊ ناھي.",
+       "permissionserrors": "اجازتي چُڪَ",
+       "permissionserrorstext": "هيٺين {{PLURAL:$1|سبب|سببن}} ڪري، توهان کي اهو ڪرڻ جي اجازت ناھي.",
        "permissionserrorstext-withaction": "ھيٺين {{PLURAL:$1|سبب|سببن}} ڪري، توھان کي $2 جي اجازت ڪانھي.",
        "recreate-moveddeleted-warn": "'''خبردار: توھان اھڙو صفحو نئين سِر سرجي رھيا آھيو جيڪو اڳ ڊاٺو ويو آھي.'''\n\nبھتر ٿيندو تہ توھان سوچي وٺو تہ ڇا ان صفحي کي سنوارڻ چڱو ٿيندو.\nتوهان جي سھولت خاطر ھتي ان صفحي جو ڊاٺ لاگ ميسر ڪجي ٿو:",
        "moveddeleted-notice": "ھيءُ صفحو ڊھي چڪو آهي. \nحوالي طور صفحي جا ڊاھ، حفاظت ۽ چورڻ لاگ ھيٺ ڏنل آھن.",
        "moveddeleted-notice-recent": "معاف ڪجو، هيءُ صفحو تازو ئي ڊاٺو ويو ھو (پوين 24 ڪلاڪن اندر). حوالي طور صفحي جا ڊاھ، حفاظت ۽ چورڻ لاگ ھيٺ ڏنل آھن.",
        "log-fulllog": "پُورو لاگ ڏسو",
-       "edit-conflict": "سنوار تڪرار",
-       "postedit-confirmation-created": "هيءُ صفحو سرجي چڪو آهي.",
-       "postedit-confirmation-restored": "هيءُ صفحو بحالجي چڪو آهي.",
+       "edit-conflict": "سنوار تڪرار.",
+       "postedit-confirmation-created": "صفحو سرجي چڪو آهي.",
+       "postedit-confirmation-restored": "صفحو بحالجي چڪو آهي.",
        "postedit-confirmation-saved": "توھان جي سنوار سانڍجي وئي ھئي.",
-       "edit-already-exists": "نئون صفحو سرجي نہ سگھيو. اهو اڳ ۾ ئي وجود رکي ٿو.",
-       "invalid-content-data": "ناقابل ڪار موادي اعداد",
+       "postedit-confirmation-published": "توھان جي سنوار ڇاپجي وئي.",
+       "edit-already-exists": "نئون صفحو سرجي نہ سگھيو.\nاهو اڳ ئي وجود رکي ٿو.",
+       "defaultmessagetext": "پيغام جو ڏنل متن",
+       "invalid-content-data": "ناقابلِڪار موادي اعداد",
        "content-not-allowed-here": "\"$1\" مواد جي هن صفحي [[:$2]] جي جڳھ \"$3\" تي اجازت ناھي.",
+       "slot-name-main": "مُک",
        "content-model-wikitext": "وڪي‌ٽيڪسٽ",
-       "content-model-text": "سادو ٽيڪسٽ",
-       "content-model-javascript": "جاوا اسڪرپٽ",
+       "content-model-text": "سادو متن",
+       "content-model-javascript": "جاوا-اسڪرپٽ",
+       "content-model-css": "سي.ايس.ايس",
        "content-json-empty-object": "خالي آبجيڪٽ",
        "content-json-empty-array": "خالي اري",
        "duplicate-args-warning": "چتاءُ: [[:$2]] کي [[:$1]] ڪال ڪري رهيو آهي، جنهن منجھہ ’$3‘ نيم‌پيما لاءِ هڪ کان وڌيڪ قدر ڄاڻايل آهن. فقط آخري ڄاڻايل قدر استعمال ڪيو ويندو.",
        "parser-template-loop-warning": "سانچو چڪر لڌو ويو: [[$1]]",
+       "undo-nochange": "سنوار اڳ ئي اڻڪريل ظاھر پئي ٿي.",
+       "undo-summary": "$1 [[Special:Contributions/$2|$2]] ([[User talk:$2|بحث]]) پاران ورجاءُ اڻڪريو",
+       "undo-summary-username-hidden": "ڪنھن لڪيل واپرائيندڙ پاران $1 اڻڪريو",
        "cantcreateaccount-text": "هن آءِپي پتي تان کاتي جي کولڻ تي (<strong>$1</strong>)  [[User:$3|$3]] بندش وڌل آهي.\n\n$3 جو ڄاڻايل سبب ھي <em>$2</em> آهي.",
-       "cantcreateaccount-range-text": "آءÙ\90Ù¾Ù\8a Ù¾ØªÙ\86 Ø¬Ù\8a Ø­Ø¯ <strong>$1</strong> Û¾ [[User:$3|$3]] Ú©Ø§ØªÙ\88 Ú©Ù\88Ù\84Ú» ØªÙ\8a Ø±Ù\88Úª Ù\84ڳائÙ\8a Ù\88ئÙ\8a Ø¢Ù\87Ù\8aØ\8c$4 Ø¬Ù\86Ù\87Ù\86 Û¾ ØªÙ\88Ù\87اÙ\86 Ø¬Ù\88 Ø¢Ø¡Ù\90Ù¾Ù\8a Ù¾ØªÙ\88 Ø¨Û\81 (<strong>$4</strong>)Ø\8c  Ù¾Ú» Ø´Ø§Ù\85Ù\84 Ø¢Ù\87Ù\8a. \n\n$3 Ø§Ù\86 Ø±Ù\88ÚªÙ\8e Ø¬Ù\88 Ø³Ø¨Ø¨ \"$2\" ڄاڻايو آهي.",
+       "cantcreateaccount-range-text": "آئÙ\90Ù¾Ù\8a Ù¾ØªÙ\86 Ø¬Ù\8a Ø­Ø¯ <strong>$1</strong> Û¾ [[User:$3|$3]] Ú©Ø§ØªÙ\88 Ú©Ù\88Ù\84Ú» ØªÙ\8a Ø±Ù\88Úª Ù\84ڳائÙ\8a Ù\88ئÙ\8a Ø¢Ù\87Ù\8aØ\8c$4 Ø¬Ù\86Ú¾Ù\86 Û¾ ØªÙ\88Ù\87اÙ\86 Ø¬Ù\88 Ø¢Ø¦Ù\90Ù¾Ù\8a Ù¾ØªÙ\88 Ø¨Û\81 (<strong>$4</strong>)Ø\8c Ù¾Ú» Ø´Ø§Ù\85Ù\84 Ø¢Ù\87Ù\8a. \n\n$3 Ø§Ù\86 Ø±Ù\88ÚªÙ\8e Ø¬Ù\88 Ø³Ø¨Ø¨ <em>\"$2\"<em> ڄاڻايو آهي.",
        "viewpagelogs": "هن صفحي جا لاگس ڏسو",
        "nohistory": "هن صفحي جي ڪا بہ سوانح نہ آهي.",
-       "currentrev": "هاڻوڪو مسودو",
-       "currentrev-asof": "$1 جو تازو ترين مسودو",
+       "currentrev": "تازو-ترين ورجاءُ",
+       "currentrev-asof": "تازو-ترين ترين ورجاءُ بمطابق $1",
        "revisionasof": "$1 وارو پرت",
        "revision-info": "$1 جو {{GENDER:$6|$2}}$7 جي سنوار بعد مسودو",
        "previousrevision": "←اڃا پراڻو پرت",
        "cur": "ھاڻوڪو",
        "next": "اڳيون",
        "last": "پويون",
-       "page_first": "پهريون",
+       "page_first": "پھريون",
        "page_last": "آخري",
        "history-fieldset-title": "مسودا ڇاڻيو",
        "history-show-deleted": "رڳو ڊاٺل مسودا",
-       "histfirst": "اوائلي ترين",
-       "histlast": "تازه ترين",
+       "histfirst": "اوائلي-ترين",
+       "histlast": "نئون-ترين",
        "historysize": "({{PLURAL:$1|1 بائيٽ|$1 بائيٽون}})",
        "historyempty": "خالي",
        "history-feed-title": "ورجاءُ سوانح",
        "history-feed-description": "وڪي جي هن صفحي جي ورجاءُ سوانح",
        "history-feed-item-nocomment": "$2 تي $1",
+       "history-edit-tags": "چونڊيل ورجائن جا ٽيگز سنواريو",
        "rev-deleted-comment": "(سنوار جو تَتُ ھٽايل)",
        "rev-deleted-user": "(واپرائيندڙ-نانءُ ڊاٿو ويو)",
        "rev-deleted-event": "(لاگ تفصيل هٽايا ويا)",
        "rev-suppressed-no-diff": "توهان اهو تفاوت نٿا ڏسي سگھو، ڇاڪاڻ تہ مسودن مان ڪو ھڪ <strong>ڊاھيو ويو آھي</strong>.",
        "rev-delundel": "نمائش تبديل ڪريو",
        "rev-showdeleted": "ڏيکاريو",
-       "revisiondelete": "Ù\85سÙ\88ادا ڊاهيو/اڻ‌ڊاهيو",
-       "revdelete-no-file": "ڄاڻايل فائيل وجود نہ ٿو رکي.",
-       "revdelete-show-file-submit": "ها",
+       "revisiondelete": "Ù\88رجاءÙ\8e ڊاهيو/اڻ‌ڊاهيو",
+       "revdelete-no-file": "ڄاڻايل فائيل وجود نٿو رکي.",
+       "revdelete-show-file-submit": "ھا",
        "revdelete-legend": "نمائش جون پابنديون ترتيب ڪريو",
+       "revdelete-hide-text": "ورجاءَ جو متن",
        "revdelete-hide-image": "فائيل جو مواد لڪايو",
        "revdelete-hide-name": "هدف ۽ نيمپيما لڪايو",
        "revdelete-hide-comment": "سنوار جو تتُ",
        "revdelete-radio-set": "لڪل",
        "revdelete-radio-unset": "ظاهر",
        "revdelete-suppress": "منتظمن توڙي ٻين کان مليل اعداد دٻايو",
+       "revdelete-unsuppress": "ورايل ورجائن تان پابنديون ھٽايو",
        "revdelete-log": "سبب:",
+       "revdelete-submit": "چونڊيل {{PLURAL:$1|ورجاءُ|ورجاءَ}} لاڳو ڪريو",
+       "revdelete-success": "ورجاءُ ظاھريت جديدي وئي.",
+       "revdelete-failure": "ورجاءُ ظاھريت نہ جديدي سگھجي:\n$1",
+       "logdelete-success": "لاگ ظاھريت مرتب ٿي.",
+       "logdelete-failure": "لاگ ظاھريت مرتب نہ ٿي سگھجي:\n$1",
        "revdel-restore": "نمائش تبديل ڪريو",
        "pagehist": "صفحي جي سوانح",
-       "deletedhist": "Ú\8aاٺل سوانح",
+       "deletedhist": "Ú\8aاٿل سوانح",
        "revdelete-otherreason": "ٻيا/اضافي ڪارڻ:",
        "revdelete-reasonotherlist": "ٻيو ڪارڻ",
-       "revdelete-edit-reasonlist": "ڊاٺ جا سبب سنواريو",
-       "revdelete-offender": "ڀيري جو ليکڪ:",
+       "revdelete-edit-reasonlist": "ڊاھ جا سبب سنواريو",
+       "revdelete-offender": "ورجاءَ جو ليکڪ:",
        "mergehistory": "صفحن جون سوانح ضم ڪريو",
-       "mergehistory-box": "ٻن صفحن جي ڀيرن کي ضم ڪريو:",
-       "mergehistory-from": "ذريعہ صفحو:",
+       "mergehistory-box": "ٻن صفحن جي ورجائن کي ضم ڪريو:",
+       "mergehistory-from": "مصدر صفحو:",
        "mergehistory-into": "مقصود صفحو:",
        "mergehistory-list": "ضمائتي سنوار سوانح",
        "mergehistory-go": "ضم ڪرڻ جوڳيون سنوارون ڏيکاريو",
-       "mergehistory-submit": "ڀيرن کي ضم ڪريو",
-       "mergehistory-empty": "ڪي بہ ڀيرا ضم ڪري نہ ٿا سگھجن.",
+       "mergehistory-submit": "ورجاءَ ضم ڪريو",
+       "mergehistory-empty": "ڪي بہ ورجاءَ ضم نٿا ڪري سگھجن.",
+       "mergehistory-fail-bad-timestamp": "وقت-ٺپو ناقابلِڪار آھي.",
+       "mergehistory-fail-invalid-source": "ذريعو صفحو ناقابلِڪار آھي.",
+       "mergehistory-fail-invalid-dest": "منزل صفحو ناقابلِڪار آھي.",
+       "mergehistory-fail-permission": "سوانح ضم ڪرڻ لاءِ اڻپوريون اجازتون.",
+       "mergehistory-fail-self-merge": "ذريعو ۽ منزل صفحا ساڳيا آھن.",
        "mergehistory-no-source": "مصدر صفحو $1 وجود نٿو رکي.",
        "mergehistory-no-destination": "مقصود صفحو $1 وجود نہ ٿو رکي.",
        "mergehistory-invalid-source": "مصدر صفحي جو عنوان قابل‌ڪار هجڻ لازمي آهي.",
        "mergehistory-comment": "[[:$1]]، [[:$2]] ۾ ضم ٿي ويو: $3",
        "mergehistory-same-destination": "مصدر ۽ مقصود صفحا ساڳيا نٿا ٿي سگھن",
        "mergehistory-reason": "سبب:",
+       "mergehistory-revisionrow": "$1 ($2) $3 . . $4 $5 $6",
        "mergelog": "ضم لاگ",
-       "revertmerge": "اڻ ضم",
+       "revertmerge": "اڻ ضم ڪريو",
        "history-title": "\"$1\" جي ورجاءُ سوانح",
-       "difference-title": "\"$1\" Ø¬Ù\8a Ù\85سÙ\88دن ۾ تفاوت",
+       "difference-title": "\"$1\" Ø¬Ù\8a Ù\88رجائن ۾ تفاوت",
        "difference-title-multipage": "صفحن \"$1\" ۽ \"$2\" ۾ تفاوت",
-       "difference-multipage": "(صفحن درميان تفاوت)",
+       "difference-multipage": "(صفحن وچ ۾ تفاوت)",
        "lineno": "سِٽَ $1:",
-       "compareselectedversions": "چونڊيل پرت ڀيٽيو",
+       "compareselectedversions": "چونڊيل ورجاءَ ڀيٽيو",
+       "showhideselectedversions": "چونڊيل ورجائن جي ظاھريت بدلايو",
        "editundo": "اڻڪريو",
        "diff-empty": "(ڪو بہ تفاوت ڪونھي)",
-       "diff-multi-sameuser": "({{PLURAL:$1|هڪ تڪڙو مسودو|$1 تڪڙا مسودا}} ساڳي واپرائيندڙ طرفان ظهار نه ٿيندا)",
+       "diff-multi-sameuser": "({{PLURAL:$1|هڪ وچولو ورجاءُ|$1 وچولا ورجاءَ}} ساڳي واپرائيندڙ طرفان ظاھر نہ ٿيندا)",
        "searchresults": "ڳولا نتيجا",
+       "search-filter-title-prefix": "صرف انھن صفحن ۾ ڳوليندي جن جو عنوان \"$1\" سان شروع ٿي ٿو.",
+       "search-filter-title-prefix-reset": "سڀ صفحا ڳوليو",
        "searchresults-title": "”$1“ لاءِ ڳولا نتيجا",
        "titlematches": "صفحي جو عنوان مشابھت رکي ٿو",
        "textmatches": "صفحي جو متن مشابھت رکي ٿو",
        "prev-page": "اڳوڻو صفحو",
        "next-page": "اڳيون صفحو",
        "prevn-title": "{{PLURAL:$1|پويون|پويان}} $1 {{PLURAL:$1|نتيجو|نتيجا}}",
-       "nextn-title": "{{PLURAL:$1|ٻيو|ٻيا}} $1 {{PLURAL:$1|نتيجو|نتيجا}}",
+       "nextn-title": "اڳيان/اڳيان $1 {{PLURAL:$1|نتيجو|نتيجا}}",
        "shown-title": "$1 {{PLURAL:$1|نتيجو|نتيجا}} في صفحو ڏيکاريو",
        "viewprevnext": "ڏسو ($1 {{int:pipe-separator}} $2) ($3)",
        "searchmenu-exists": "<strong>ھن وڪيءَ تي \"[[:$1]]\" نالي ھڪ صفحو آھي.</strong> {{PLURAL:$2|0=|ٻيا لڌل ڳولا نتيجا پڻ ڏسو.}}",
        "search-result-size": "$1 ({{PLURAL:$2|لفظُ|$2 لفظَ}})",
        "search-result-category-size": "{{PLURAL:$1|1 رڪن|$1 رڪنَ}} ({{PLURAL:$2|1 ذيلي زمرو|$2 ذيلي زمرا}}, {{PLURAL:$3|1 فائيل|$3 فائيلَ}})",
        "search-redirect": "($1 کان چوريو)",
-       "search-section": "(سيڪشن $1)",
+       "search-section": "(ڀاڱو $1)",
        "search-category": "(زمرو $1)",
        "search-file-match": "(فائيل جي مواد سان ملي ٿو)",
        "search-suggest": "ڇا توهان جو مطلب ھيو: $1",
-       "search-rewritten": "نتيجا براءِ $1. يا $2 بابت نتيجا ڏسو.",
+       "search-rewritten": "$1 لاءِ نتيجا ڏيکاريندي. ان بجاءِ $2 ڳوليو.",
        "search-interwiki-caption": "برادر رٿائن مان نتيجا",
        "search-interwiki-default": "$1 مان نتيجا",
        "search-interwiki-more": "(وڌيڪ)",
        "powersearch-ns": "نانءُپولارن ۾ ڳوليو:",
        "powersearch-togglelabel": "چڪاسيو:",
        "powersearch-toggleall": "سڀ",
-       "powersearch-togglenone": "ڪو بہ نہ",
+       "powersearch-togglenone": "ڪوبہ نہ",
        "search-external": "خارجي ڳولا",
-       "search-error": "$1 ۾ ڳولا ڪندي چُڪَ ٿي.",
+       "search-error": "$1: ڳوليندي چُڪَ ظاھر ٿي آھي",
        "preferences": "ترجيحون",
        "mypreferences": "ترجيحون",
-       "prefs-edits": "ترÙ\85Ù\8aÙ\85Ù\86 Ø¬Ù\88 ØªØ¹Ø¯Ø§Ø¯:",
+       "prefs-edits": "سÙ\86Ù\88ارÙ\86 Ø¬Ù\88 Ø§Ù\86Ú¯:",
        "prefsnologintext2": "پنھنجون ترجيحون بدلائڻ لاءِ داخل ٿيو.",
        "prefs-skin": "چَمَ",
        "skin-preview": "پيش نگاھ",
        "prefs-email": "برقٽپال چارا",
        "prefs-rendering": "حليو",
        "saveprefs": "سانڍيو",
-       "restoreprefs": "شروعاتي ترتيبون واپس ڪيو (سمورن خانن ۾)",
+       "restoreprefs": "(سمورن خانن ۾) سڀ ڏنل ترتيبون ورايو",
        "prefs-editing": "سنوارڻ",
        "searchresultshead": "ڳولا",
        "stub-threshold-sample-link": "نمونو",
-       "stub-threshold-disabled": "غÙ\8aرÙ\81عال",
+       "stub-threshold-disabled": "اڻ-Ù\81عاÙ\8aل",
        "recentchangesdays": "تازين تبديلين ۾ ڏيکارڻ جي لاءِ ڏينهن:",
        "recentchangesdays-max": "وڌ ۾ وڌ $1 {{PLURAL:$1|ڏينهن}}",
        "recentchangescount": "تازين تبديلين، صفحن جي سوانح، ۽ لاگس ۾ ڏيکارڻ لاءِ سنوارن جو خودڪار ڏنل انگ:",
        "prefs-help-recentchangescount": "وڌ ۾ وڌ انگ: 1000",
        "savedprefs": "توھان جون ترجيحون سانڍجي چڪيون آھن.",
        "savedrights": "{{GENDER:$1|$1}} جا واپرائيندڙ گروھ سانڍجي چڪا آھن.",
-       "timezonelegend": "ٽائÙ\8aÙ\85 Ø²Ù\88Ù\86:",
+       "timezonelegend": "Ù\88Ù\82ت Ù¾Ù½Ù\88:",
        "localtime": "مقامي وقت:",
        "timezoneuseserverdefault": "وڪي عدم پيروي استعمال ڪريو ($1)",
        "timezoneuseoffset": "ٻيو (ھيٺ ڄاڻايو)",
-       "servertime": "سَروَر پٽاندر وقت:",
+       "servertime": "سَروَر جو وقت:",
        "guesstimezone": "جھانگُوءَ مان ڀريو",
        "timezoneregion-africa": "آفريڪا",
        "timezoneregion-america": "آمريڪا",
        "timezoneregion-indian": "سنڌي ساگر",
        "timezoneregion-pacific": "ماٺو ساگر",
        "allowemail": "ٻين واپرائيندڙن کي مون ڏانھن برقٽپال ڪرڻ جي اجازت ڏيو",
-       "email-allow-new-users-label": "نوان واپرائيندڙ برق ٽپال موڪلين",
-       "email-blacklist-label": "هنن واپرائندڙن کي مون ڏانهن برقٽپال ڪرڻ جي اجازت نه ڏيو:",
+       "email-allow-new-users-label": "بلڪل-نون واپرائيندڙن کان برقٽپالن جي اجازت ڏيو",
+       "email-blacklist-label": "هنن واپرائندڙن کي مون ڏانھن برقٽپال ڪرڻ کان منع ڪريو:",
        "prefs-searchoptions": "ڳولا",
        "prefs-namespaces": "نانءُپولار",
        "default": "ڏنل",
-       "prefs-files": "فائيلس",
-       "prefs-reset-intro": "اوهان هن صفحي جي مدد سان مرتب ڪيل ترجيحات کي اصل ترجيحات ۾ بدلائي سگھو ٿا.\nياد رکو، هي عمل واپس نٿو ٿي سگھي.",
+       "prefs-files": "فائيلَ",
+       "prefs-reset-intro": "اوهان هن صفحي کي ويب-سرزمين لاءِ ڏنل ترجيحن کي ٻيھر مرتب ڪرڻ لاءِ استعمال ڪري سگھو ٿا.\nهي عمل واپس نٿو ٿي سگھي.",
        "prefs-emailconfirm-label": "برقٽپال خاطري:",
        "youremail": "برقٽپال:",
        "username": "{{GENDER:$1|واپرائيندڙ-نانءُ}}",
        "prefs-registration-date-time": "$1",
        "yourrealname": "اصل نالو:",
        "yourlanguage": "ٻولي:",
-       "yournick": "Ù\86ئÙ\8aÙ\86 ØµØ­Ù\8aØ­:",
-       "prefs-help-signature": "بحث صفحي تي رايا ڏيڻ وقت هن نشانين ذريعي \"<nowiki>~~~~</nowiki>\" دستخط ڪيو، جيڪي پاڻ مرادو توهان جي دستخط ۽ وقت ۾ تبديل ٿي ويندا.",
-       "badsiglength": "اها صحيح هيڪاندي ڊگھي آهي.\nاها وڌ ۾ وڌ $1 {{PLURAL:$1|اکر|اکرن}} تي ٻڌل هوڻ گھرجي.",
-       "yourgender": "توهان ڪهڙو تعارف چاهيندا؟",
+       "yournick": "Ù\86ئÙ\88Ù\86 Ø¯Ø³ØªØ®Ø·",
+       "prefs-help-signature": "بحث صفحي تي رايا ڏيڻ وقت هن نشانين ذريعي \"<nowiki>~~~~</nowiki>\" دستخط ڪيو، جيڪي پاڻمرادو توهان جي دستخط ۽ وقت ۾ تبديل ٿي ويندا.",
+       "badsiglength": "اهو درتخط هيڪاندو ڊگھو آهي.\nاها وڌ ۾ وڌ $1 {{PLURAL:$1|اکر|اکرن}} تي ٻڌل هجڻ گھرجي.",
+       "yourgender": "توھان ڪيئن بيان ٿيڻ چاھيندا؟",
        "gender-unknown": "توهان جو ذڪر ڪندي، جيترو ٿي سگھيو، منطقگري بي جنس لفظن جو استعمال ڪندي.",
-       "gender-male": "هيءُ وڪي صفحا سنواريندو آهي",
-       "gender-female": "هيءَ وڪي صفحا سنواريندي آهي",
+       "gender-male": "هي وڪي صفحا سنواريندو آهي",
+       "gender-female": "ھوءَ وڪي صفحا سنواريندي آهي",
        "prefs-help-gender": "هن ترجيح جي تربيت اختياري آهي.\nاوهان يا ٻين واپرائيندڙن جو سافٽويئر ويليو ذريعي مناسب وياڪرڻي جنس مطابق ذڪر ڪندو.\nهي معلومات عام هوندي.",
        "email": "برقٽپال",
        "prefs-help-realname": "اصل نالو اختياري آهي.\nجيڪڏهن توهان اصل نالو ڄاڻائڻ جو فيصلو ٿا ڪريو، تہ اهو توهان کي توهان جي ڪم جي مڃتا ڏيڻ لاءِ ڪم آندو ويندو.",
        "prefs-help-email": "برقٽپال ڄاڻائڻ اختياري آهي، پر جڏهن توهان ڳجھولفظ وسري ويندا آهيو، تڏهن ان جو استعمال توهان کي نئون ڳجھولفظ ڏيڻ لاءِ استعمال ڪيو ويندو آهي.",
-       "prefs-help-email-others": "اوهان چونڊ ڪري سگھو ٿا ته اوهان جي ذاتي يا بحث صفحي تي موجود ڳنڍڻي ذريعي ٻيو ڪو واپرائيندڙ اوهان کي برقٽپال ڪري سگھي ٿو يا نه.\nاوهان جو برقٽپال پتو ٻين پاران رابطي ڪرڻ وقت ڳجهو رکيو ويندو.",
+       "prefs-help-email-others": "اوهان چونڊ ڪري سگھو ٿا تہ اوهان جي ذاتي يا بحث صفحي تي موجود ڳنڍڻي ذريعي ٻيو ڪو واپرائيندڙ اوهان کي برقٽپال ڪري سگھي ٿو يا نہ.\nاوهان جو برقٽپال پتو ٻين پاران رابطي ڪرڻ وقت ڳجھو رکيو ويندو.",
        "prefs-help-email-required": "برقٽپال پتو گھربل آهي.",
        "prefs-info": "بنيادي ڄاڻ",
        "prefs-i18n": "بين‌الاقوامڪاري",
-       "prefs-signature": "صحÙ\8aØ­",
+       "prefs-signature": "دستخط",
        "prefs-dateformat": "تاريخ جو طرز",
-       "prefs-advancedediting": "عمومي چارا",
-       "prefs-editor": "اÙ\8aÚ\8aÙ\8aٽر",
+       "prefs-advancedediting": "عام چارا",
+       "prefs-editor": "سÙ\86Ù\88ارگاھ",
        "prefs-preview": "پيش نگاھ",
        "prefs-advancedrc": "متقدم چارا",
        "prefs-advancedrendering": "متقدم چارا",
        "prefs-advancedwatchlist": "متقدم چارا",
        "prefs-displayrc": "نماڪار چارا",
        "prefs-displaywatchlist": "نماڪار چارا",
+       "prefs-changesrc": "تبديليون ڏيکاريل",
+       "prefs-changeswatchlist": "تبديليون ڏيکاريل",
+       "prefs-pageswatchlist": "نظر ۾ صفحا",
        "prefs-tokenwatchlist": "ٽوڪن",
        "prefs-diffs": "تفاوت",
        "prefs-help-prefershttps": "هيءَ ترجيح توهان جي ايند داخل ٿيڻ تي عمل ۾ ايندي.",
        "userrights-editusergroup": "{{GENDER:$1|واپرائيندڙ}} گروھ سنواريو",
        "userrights-viewusergroup": "{{GENDER:$1|واپرائيندڙ}} گروھ ڏيکاريو",
        "saveusergroups": "{{GENDER:$1|واپرائيندڙ}} گروھ سانڍيو",
-       "userrights-groupsmember": "برڪن:",
+       "userrights-groupsmember": "جÙ\88 رڪن:",
        "userrights-groupsmember-auto": "رڪن واجبي:",
        "userrights-groupsmember-type": "$1",
        "userrights-reason": "سبب:",
        "userrights-no-interwiki": "توهان کي ٻين وڪين تي واپرائيندڙ حق سنوارڻ جي اجازت ناھي.",
-       "userrights-nodatabase": "اعداخانو $1 يا تہ وجود نہ ٿو رکي يا تہ اهو مقامي اعدادخانو نہ آهي.",
+       "userrights-nodatabase": "اعداخانو $1 يا تہ وجود نٿو رکي يا تہ اهو مقامي اعدادخانو نہ آهي.",
        "userrights-changeable-col": "گروپَ جيڪي توهان تبديل ڪري سگھو ٿا",
        "userrights-unchangeable-col": "گروپَ جيڪي توهان تبديل نٿا ڪري سگھو",
        "userrights-irreversible-marker": "$1*",
        "userrights-no-shorten-expiry-marker": "$1#",
+       "userrights-expiry-current": "مدو پورو $1",
        "userrights-expiry-none": "مدي خارج نٿو ٿي",
+       "userrights-expiry": "مدو پورو:",
+       "userrights-expiry-existing": "موجودہ مدو پورو ٿيڻ جو وقت: $3، $2",
        "userrights-expiry-othertime": "ٻيو وقت:",
        "userrights-expiry-options": "1 ڏينھن:1 ڏينھن،1 ھفتو:1 ھفتا،1 مھينو:1 مھينو،3 مھينا:3 مھينا،6 مھينا:6 مھينا،1 سال:1 سال",
        "group": "گروھ:",
        "group-bureaucrat": "ڪامورا",
        "group-all": "(سڀ)",
        "group-user-member": "{{GENDER:$1|واپرائيندڙ}}",
+       "group-autoconfirmed-member": "{{GENDER:$1|پاڻمرادو-پڪ-ڪيل واپرائيندڙ}}",
        "group-bot-member": "{{GENDER:$1|بوٽ}}",
        "group-sysop-member": "{{GENDER:$1|منتظم}}",
        "group-interface-admin-member": "{{GENDER:$1|منتظم براءِ حليو}}",
        "grouppage-user": "{{ns:project}}:واپرائيندڙ",
        "grouppage-autoconfirmed": "{{ns:project}}:خودڪارنموني پڪ ڪيل رڪن",
        "grouppage-bot": "{{ns:project}}:بوٽس",
-       "grouppage-sysop": "{{ns:project}}:منتظمين",
+       "grouppage-sysop": "{{ns:project}}:منتظم",
        "grouppage-interface-admin": "{{ns:project}}:منتظم براءِ حليو",
        "grouppage-bureaucrat": "{{ns:project}}:ڪامورا",
        "grouppage-suppress": "{{ns:project}}:دٻايو",
        "right-read": "صفحا پڙهو",
        "right-edit": "صفحا سنواريو",
-       "right-createpage": "صفحا سنواريو (جيڪي مباحثي صفحا نہ آهن)",
-       "right-createtalk": "مباحثي صفحا سرجيو",
+       "right-createpage": "صفحا سرجيو (جيڪي گفتگوئي صفحا نہ آهن)",
+       "right-createtalk": "گفتگوئي صفحا سرجيو",
        "right-createaccount": "نوان واپرائيندڙ کاتا کوليو",
-       "right-minoredit": "ترÙ\85Ù\8aÙ\85Ù\8fÙ\86 Ú©Ù\8a Ù\85عÙ\85Ù\8fÙ\88Ù\84Ù\8a Ú\84اڻايو",
+       "right-minoredit": "سÙ\86Ù\88ارÙ\86 Ú©Ù\8a Ù\85عÙ\85Ù\88Ù\84Ù\8a Ø·Ù\88ر Ù\86شاÙ\86 Ù\84Ú³ايو",
        "right-move": "صفحا چوريو",
-       "right-move-subpages": "Ø°Ù\8aÙ\84Ù\8a ØµÙ\81Ø­Ù\86 Ø³Ù\85Ù\8aت ØµÙ\81حا چوريو",
+       "right-move-subpages": "صÙ\81Ø­Ù\86 Ú©Ù\8a Ø³Ù\86دÙ\86 Ø°Ù\8aÙ\84Ù\8a-صÙ\81Ø­Ù\86 Ø³Ù\85Ù\8aت چوريو",
        "right-move-categorypages": "زمراتي صفحا چوريو",
        "right-movefile": "فائيل چوريو",
        "right-upload": "فائيل چاڙهيو",
-       "right-reupload": "موجوده فائيلن مٿان",
+       "right-reupload": "موجوده فائيلن مٿان-لکو",
        "right-upload_by_url": "ڪنهن يُوآرايل تان فائيل چاڙهيو",
        "right-writeapi": "ايپيآءِ لکڻ جو استعمال",
        "right-delete": "صفحا ڊاهيو",
        "right-unblockself": "ڪنهن تان بندش ختم ڪريو",
        "right-editinterface": "واپرائيندڙ باهمرُو کي سنواريو",
        "right-viewmywatchlist": "پنهنجي نظر ۾ فھرست ڏسو",
-       "right-editmywatchlist": "پنهنجي نگھداشت واري فهرست کي سنواريو. ياد رکو ڪجهه ڪم هن اختيار کان سواءِ پڻ ممڪن آهن.",
-       "right-viewmyprivateinfo": "پنهنجي ذاتي معلومات ڏسو (جيئن: برق ٽپال پتو، اصل نالو وغيره)",
-       "right-editmyprivateinfo": "پنهنجي ذاتي معلومات سنواريو (جيئن برق ٽپال، اصل نالو)",
+       "right-editmywatchlist": "پنھنجي نظر ۾ فھرست کي سنواريو. ياد رکو ڪجھ ڪم هن اختيار کان سواءِ پڻ ممڪن آهن.",
+       "right-viewmyprivateinfo": "پنھنجي ذاتي ڊيٽا ڏسو (جيئن: برقٽپال پتو، اصل نالو وغيره)",
+       "right-editmyprivateinfo": "پنھنجي ذاتي ڊيٽا سنواريو (جيئن برقٽپال، اصل نالو)",
        "right-editmyoptions": "پنهنجون ترجيحون سنواريو",
-       "right-import": "ٻين وڪيز کان صفحا درآمديو",
+       "right-import": "ٻين وڪين کان صفحا درآمديو",
        "right-importupload": "ڪو فائيل چاڙهي صفحا درآمديو",
-       "right-patrol": "Ù»Ù\8aÙ\86 Ø¬Ù\8a ØªØ±Ù\85Ù\8aÙ\85Ù\86 Ú©Ù\8a گشت-ڪيل طور نشان لڳايو",
+       "right-patrol": "Ù»Ù\8aÙ\86 Ø¬Ù\88Ù\86 Ø³Ù\86Ù\88ارÙ\88Ù\86 گشت-ڪيل طور نشان لڳايو",
        "right-autopatrol": "سندس سنوارون پاڻمرادو گشت ڪيل طور نشان لڳل آھن",
-       "right-mergehistory": "صÙ\81Ø­Ù\86 Ø¬Ù\8a Ø³Ù\88اÙ\86Ø­ Ø³Ù\86Ù\88اريو",
+       "right-mergehistory": "صÙ\81Ø­Ù\86 Ø¬Ù\8a Ø³Ù\88اÙ\86Ø­ Ø¶Ù\85 Úªريو",
        "right-userrights": "واپرائيندڙ جا سڀ حق سنواريو",
        "right-userrights-interwiki": "ٻين وڪين تي واپرائيندڙن جا حق سنواريو",
        "right-siteadmin": "اعدادخانو بنديو ۽ کوليو",
        "right-managechangetags": "[[Special:Tags|ٽيگس]] سرجيو ۽ ڊاهيو.",
        "grant-group-file-interaction": "ميڊيا سان لھ وچڙ ۾ اچو",
        "grant-group-email": "برقٽپال اماڻيو",
-       "grant-group-other": "گاڏڙ ساڏڙ سرگرمي",
+       "grant-group-administration": "انتظامي عمل سرانجام ڏيو",
+       "grant-group-private-information": "اوھان بابت خانگي ڊيٽا تائين رسائي ڪريو",
+       "grant-group-other": "گاڏڙ-ساڏڙ سرگرمي",
        "grant-blockusers": "واپرائيندڙن کي بندشيو ۽ اڻبندشيو",
        "grant-createaccount": "کاتا کوليو",
        "grant-createeditmovepage": "صفحا سرجيو، سنواريو، ۽ چوريو",
+       "grant-delete": "صفحا، ورجاءَ، ۽ لاگ داخلائون ڊاھيو",
        "grant-editmywatchlist": "پنھنجي نظر ۾ فھرست سنواريو",
-       "grant-editpage": "Ù\87اڻÙ\88ÚªÙ\86 ØµÙ\81Ø­Ù\86 Ú©Ù\8a سنواريو",
+       "grant-editpage": "Ù\85Ù\88جÙ\88دÛ\81 ØµÙ\81حا سنواريو",
        "grant-editprotected": "تحفظيل صفحا سنواريو",
+       "grant-patrol": "صفحن ۾ تبديلين جو گشت ڪريو",
+       "grant-privateinfo": "خانگي معلومات تي رسائي ڪريو",
+       "grant-protect": "صفحا تحفظيو ۽ اڻتحفظيو",
        "grant-rollback": "صفحن ۾ ڪيل تبديليون واپس ورايو",
        "grant-sendemail": "ٻين واپرائيندڙن ڏانھن برقٽپال موڪليو",
-       "grant-uploadeditmovefile": "Ù\81ائÙ\8aÙ\84 Ú\86اÚ\99Ù\87Ù\8aÙ\88Ø\8c Ù\85Ù\8eٽاÙ\8aÙ\88Ø\8c Û½ Ú\8aاÙ\87يو",
+       "grant-uploadeditmovefile": "Ù\81ائÙ\8aÙ\84 Ú\86اÚ\99Ù\87Ù\8aÙ\88Ø\8c Ù\85Ù\8eٽاÙ\8aÙ\88Ø\8c Û½ Ú\86Ù\88ريو",
        "grant-uploadfile": "نئون فائيل چاڙهيو",
        "grant-basic": "بنيادي حقَ",
-       "grant-viewdeleted": "Ú\8aÙ\8eÙºÙ\8eÙ\84Ù\8e فائيلَ ۽ صفحا ڏسو",
+       "grant-viewdeleted": "Ú\8aÙ\8eÙ¿Ù\84 فائيلَ ۽ صفحا ڏسو",
        "grant-viewmywatchlist": "پنھنجي نظر ۾ فھرست ڏسو",
+       "grant-viewrestrictedlogs": "پابند-ٿيل لاگ داخلائون ڏسو",
        "newuserlogpage": "واپرائيندڙ جو سرجڻ لاگ",
+       "newuserlogpagetext": "ھي واپرائيندڙ سرجاين جو لاگ آھي.",
        "rightslog": "واپرائيندڙ حق لاگ",
        "action-read": "هي صفحو پڙهو",
        "action-edit": "هن صفحي کي سسنواريو",
        "action-minoredit": "هن سنوار کي معمولي طور نشان لڳايو",
        "action-move": "هيءَُ صفحو چوريو",
        "action-move-subpages": "هيءُ صفحو، ۽ ان جا ذيلي صفحا چوريو",
-       "action-move-categorypages": "زمرن جا صفحا چوريو",
+       "action-move-categorypages": "زمراتي صفحا چوريو",
        "action-movefile": "هيءُ فائيل چوريو",
        "action-upload": "هيءُ فائيل چاڙهيو",
        "action-delete": "هيءُ صفحو ڊاهيو",
        "action-deleterevision": "ڀيرا ڊاھيو",
+       "action-deletelogentry": "لاگ داخلائون ڊاھيو",
        "action-deletedhistory": "ڪنھن صفحي جي ڊاھ سوانح ڏسو",
-       "action-browsearchive": "ڊاٺل صفحن ۾ ڳوليو",
+       "action-deletedtext": "ڊاھيل ورجاءَ جو متن ڏسو",
+       "action-browsearchive": "ڊاٿل صفحن ۾ ڳوليو",
        "action-undelete": "صفحا اڻڊاھيو",
        "action-suppressrevision": "لڪيل ڀيرن تي نظرثاني ڪريو ۽ بحاليو",
        "action-suppressionlog": "هيءُ ذاتي لاگ ڏسو",
        "action-rollback": "ڪنھن مخصوص صفحي تي آخري سنوار ڪندڙ واپرائيندڙ جي سمورين سنوارن کي ترت واپس-ورايو",
        "action-import": "ٻي ڪنهن وڪي کان صفحا درآمد ڪريو",
        "action-importupload": "ڪو فائيل چاڙهي صفحا درآمديو",
-       "action-patrol": "ٻين جي ترميمن کي گشت-ڪيل طور نشان لڳايو",
+       "action-patrol": "ٻين جون سنوارون گشت-ڪيل طور نشان لڳايو",
+       "action-autopatrol": "پنھنجي سنوار گشت-ڪيل طور نشان لڳرايو",
        "action-unwatchedpages": "اڻ ڏٺل صفحن جي فھرست ڏسو",
        "action-mergehistory": "هن صفحي جي سوانح ضم ڪريو",
        "action-userrights": "واپرائيندڙ جا سڀ حق سنواريو",
        "action-userrights-interwiki": "ٻين وڪين جي واپرائيندڙن جا حق سنواريو",
        "action-siteadmin": "اعدادخاني کي بند ڪريو يا کوليو",
        "action-sendemail": "برقٽپال اماڻيو",
+       "action-editmyoptions": "پنھنجون ترجيحون سنواريو",
        "action-editmywatchlist": "پنھنجي نظر ۾ فھرست سنواريو",
        "action-viewmywatchlist": "پنهنجي نظر ۾ فھرست ڏسو",
        "action-viewmyprivateinfo": "پنهنجي ذاتي معلومات ڏسو",
        "action-editmyprivateinfo": "پنهنجي ذاتي معلومات سنواريو",
        "action-purge": "هن صفحي جي صفائي ڪيو",
+       "action-editprotected": "\"{{int:protect-level-sysop}}\" طور تحفظيل صفحا سنواريو",
+       "action-editsemiprotected": "\"{{int:protect-level-autoconfirmed}}\" طور تحفظيل صفحا سنواريو",
+       "action-editinterface": "واپرائيندڙ انٽرفيس سنواريو",
+       "action-editusercss": "واپرائيندڙن جا سي.ايس.ايس فائيل سنواريو",
+       "action-unblockself": "ڪنھن جي بندش ختم ڪريو",
        "nchanges": "$1 {{PLURAL:$1|تبديلي|تبديليون}}",
+       "ntimes": "$1ڀيرا",
+       "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|آخري ڦيري کان}}",
        "enhancedrc-history": "سوانح",
        "recentchanges": "تازيون تبديليون",
        "recentchanges-legend": "تازين تبديلين جا چارا",
        "recentchanges-summary": "ھن صفحي تي وڪيءَ ۾ ڪيل تازيون ترين سنوارون ڏيکاريو.",
        "recentchanges-noresult": "ڏنل عرصي ۾ ڪي بہ تبديليون ھنن ڪسوٽين سان نٿيون ملن.",
-       "recentchanges-feed-description": "ۡهن روان رسد ۾ آيل تازيون تبديليون لهو",
+       "recentchanges-feed-description": "هن روان رسد ۾ آيل تازيون تبديليون لھو.",
        "recentchanges-label-newpage": "هن سنوار ھڪ نئون صفحو سرجيو",
        "recentchanges-label-minor": "ھيءَ ھڪ معمولي سنوار آھي",
        "recentchanges-label-bot": "ھيءَ سنوار بوٽ عمل ۾ آندي",
        "recentchanges-legend-heading": "<strong>ڪنجي:</strong>",
        "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (پڻ ڏسو [[Special:NewPages|نون صفحن جي فھرست]])",
        "recentchanges-submit": "ڏيکاريو",
+       "rcfilters-tag-remove": "'$1' ھٽايو",
        "rcfilters-legend-heading": "<strong>مخففن جي فھرست:</strong>",
        "rcfilters-other-review-tools": "نظرثانيءَ جا ٻيا اوزار",
        "rcfilters-group-results-by-page": "صفحي جي لحاظ سان گروھي نتيجا",
        "rcfilters-activefilters": "سرگرم ڇاڻيون",
        "rcfilters-activefilters-hide": "لڪايو",
        "rcfilters-activefilters-show": "ڏيکاريو",
+       "rcfilters-activefilters-hide-tooltip": "سرگرم ڇاڻين جي ايراضي لڪايو",
+       "rcfilters-activefilters-show-tooltip": "سرگرم ڇاڻين جي ايراضي ڏيکاريو",
        "rcfilters-advancedfilters": "متقدم ڇاڻيون",
        "rcfilters-limit-title": "ڏيکارڻ لاءِ نتيجا",
        "rcfilters-limit-and-date-label": "$1 {{PLURAL:$1|تبديلي|$1 تبديليون}}، $2",
        "rcfilters-days-title": "ھاڻوڪا ڏينھن",
        "rcfilters-hours-title": "ھاڻوڪا ڪلاڪَ",
        "rcfilters-days-show-days": "$1 {{PLURAL:$1|ڏينھُن|ڏينھَن}}",
+       "rcfilters-days-show-hours": "$1 {{PLURAL:$1|ڪلاڪ}}",
        "rcfilters-highlighted-filters-list": "نمايان-ٿيل:$1",
        "rcfilters-quickfilters": "سانڍيل ڇاڻيون",
-       "rcfilters-quickfilters-placeholder-title": "اڃان ڪا به ڇاڻي سانڍيل ناهي",
+       "rcfilters-quickfilters-placeholder-title": "اڃا ڪابہ ڇاڻي سانڍيل ناهي",
        "rcfilters-savedqueries-defaultlabel": "سانڍيل ڇاڻيون",
        "rcfilters-savedqueries-rename": "ٻيھر نالو ڏيو",
-       "rcfilters-savedqueries-setdefault": "Ú\8aÙ\8aÙ\81اÙ\84Ù½ Ø¬Ù\8a Ø·Ù\88ر ØªÙ\8a Ú\8fÙ\8aکارÙ\8aو",
+       "rcfilters-savedqueries-setdefault": "Ú\8fÙ\86Ù\84 Ø·Ù\88ر ØªÙ\8a Ø±Ú©و",
        "rcfilters-savedqueries-remove": "ڊاھيو",
        "rcfilters-savedqueries-new-name-label": "نالو",
        "rcfilters-savedqueries-apply-label": "ڇاڻي سرجيو",
        "rcfilters-savedqueries-cancel-label": "رد",
        "rcfilters-savedqueries-add-new-title": "ھاڻوڪيون ڇاڻين جون ترتيبون سانڍيو",
        "rcfilters-restore-default-filters": "ڏنل ڇاڻيون ريسٽور ڪريو",
-       "rcfilters-clear-all-filters": "سڀئي لڳل ڇاڻيو هٽايو",
+       "rcfilters-clear-all-filters": "سڀئي لڳل ڇاڻيون هٽايو",
        "rcfilters-show-new-changes": "$1 کان نيون تبديليون ڏسو",
        "rcfilters-search-placeholder": "تبديليون ڇاڻيو (مينيو استعمال ڪريو يا ڇاڻيءَ جي ڳولا ڪريو)",
        "rcfilters-invalid-filter": "ناقابلِڪار ڇاڻي",
        "rcfilters-filter-editsbyself-label": "مون پاران تبديليون",
        "rcfilters-filter-editsbyself-description": "توھان جون پنھنجون ڀاڱيداريون.",
        "rcfilters-filter-editsbyother-label": "ٻين پاران تبديليون",
-       "rcfilters-filtergroup-user-experience-level": "Ù\88اپرائÙ\8aÙ\86دÚ\99Ù\86 Ø¬Ù\8a Ø¯Ø§Ø®Ù\84ا ۽ تجربو",
+       "rcfilters-filtergroup-user-experience-level": "Ù\88اپرائÙ\8aÙ\86دÚ\99Ù\86 Ø¬Ù\8a Ø±Ø¬Ø³Ù½Ø±Ù\8aØ´Ù\86 ۽ تجربو",
        "rcfilters-filter-user-experience-level-registered-label": "رجسٽر ٿيل",
        "rcfilters-filter-user-experience-level-registered-description": "داخل ٿيل ايڊيٽر.",
        "rcfilters-filter-user-experience-level-unregistered-label": "اڻرجسٽر ٿيل",
        "rcfilters-filter-minor-label": "معمولي سنوارون",
        "rcfilters-filter-major-label": "غير-معمولي سنوارون",
        "rcfilters-filter-major-description": "معمولي طور نشان نہ لڳل سنوارون.",
+       "rcfilters-filtergroup-watchlist": "نظر ۾ فھرستيل صفحا",
        "rcfilters-filter-watchlist-watched-label": "نظر ۾ فھڙست تي",
        "rcfilters-filter-watchlist-watched-description": "توھان جي نظر ۾ فھرست ۾ صفحن ۾ تبديليون.",
        "rcfilters-filter-watchlist-watchednew-label": "نيون نظر ۾ فھرست ۾ تبديليون",
        "rcfilters-filter-newpages-description": "نوان صفحا ٺاھيندڙ سنوارون.",
        "rcfilters-filter-categorization-label": "زمري ۾ تبديليون",
        "rcfilters-filter-logactions-label": "لاگڊ عمل",
+       "rcfilters-filtergroup-lastrevision": "تازا-ترين ورجاءَ",
+       "rcfilters-filter-lastrevision-label": "تازو-ترين ورجاءُ",
+       "rcfilters-filter-lastrevision-description": "ڪنھن صفحي ۾ صرف تازي ترين تبديلي.",
+       "rcfilters-filter-previousrevision-label": "تازو-ترين ورجاءُ نہ",
+       "rcfilters-filter-previousrevision-description": "سڀ تبديليون جيڪي \"تازو-ترين ورجاءُ\" ناھن.",
+       "rcfilters-tag-prefix-namespace-inverted": "<strong>:نہ</strong> $1",
        "rcfilters-view-tags": "ٽيگ-ٿيل سنوارون",
        "rcfilters-liveupdates-button": "سڌي-سنئين تجديد",
+       "rcfilters-liveupdates-button-title-on": "سڌيون-سنيون جدتون بند ڪريو",
+       "rcfilters-liveupdates-button-title-off": "نئون تبديليون جيئن ئي ٿين ڏيکاريو",
+       "rcfilters-watchlist-markseen-button": "سڀ تبديلين کي ڏٺل طور نشان لڳايو",
+       "rcfilters-watchlist-edit-watchlist-button": "پنھنجي نظر ۾ صفحن جي فھرست سنواريو",
+       "rcfilters-alldiscussions-label": "سڀ گفتگوئون",
        "rcnotefrom": "هيٺ {{PLURAL:$5|تبديلي آهي|تبديليون آهن}} کان <strong>$3, $4</strong> (تائين <strong>$1</strong> ) ڏيکاريل آهن.",
+       "rclistfromreset": "تاريخ چونڊڻ ٻيھر مرتب ڪريو",
        "rclistfrom": "$2، $3 کان شروع ٿيندڙ نيون تبديليون ڏيکاريو",
        "rcshowhideminor": "$1 معمولي سنوارون",
        "rcshowhideminor-show": "ڏيکاريو",
        "newpageletter": "نئون",
        "boteditletter": "گ",
        "unpatrolledletter": "!",
+       "rc-change-size": "$1",
        "rc-change-size-new": "$1 {{PLURAL:$1|بائيٽ|بائيٽون}} تبديليءَ کانپوءِ",
-       "newsectionsummary": "/* $1 */ نئون سيڪشن",
+       "newsectionsummary": "/* $1 */ نئون ڀاڱو",
        "rc-enhanced-expand": "تفصيل ڏيکاريو",
        "rc-enhanced-hide": "تفصيل لڪايو",
        "rc-old-title": "اصل ۾ \"$1\" طور سرجيل",
        "recentchangeslinked-summary": "تبديليون ڏسڻ لاءِ صفحي جو نالو هڻو پوءِ اها هن صفحي تي هجن يا ڳنڍيل صفحي تي. (زمري جارُڪن ڏسڻ لاءِ، {{ns:زمرو}}:زمري جو نالو)هڻو. [[Special:Watchlist|your Watchlist]] صفحي تي تبديليون <strong>bold</strong> ۾ آهن.",
        "recentchangeslinked-page": "صفحي جو نالو:",
        "recentchangeslinked-to": "رڳو ڄاڻايل صفحي سان ڳانڍيل صفحن ۾ ٿيل تبديليون نمايو",
+       "recentchanges-page-added-to-category": "[[:$1]] کي زمري ۾ وڌو ويو",
+       "recentchanges-page-removed-from-category": "[[:$1]] کي زمري مان ھٽايو ويو",
        "upload": "فائيل چاڙھيو",
        "uploadbtn": "فائيل چاڙهيو",
        "uploadnologin": "داخل ٿيل ناھيو",
        "uploadnologintext": "فائيل چاڙهڻ لاءِ $1.",
        "uploaderror": "چاڙھ چُڪَ",
-       "uploadtext": "Ù\81ائÙ\84 Ú\86اÚ\99Ù\87Ú» Ù\84اءÙ\90 Ù\87Ù\8aÙºÙ\8aÙ\88Ù\86 Ù\81ارÙ\85 Ø§Ø³ØªØ¹Ù\85اÙ\84 ÚªÙ\8aÙ\88.\nپراڻا Ú\86اÚ\99Ù\87Ù\8aÙ\84 Ù\81ائÙ\84 Ú\8fسڻ Ù\8aا Ú³Ù\88Ù\84Ú» Ù\84اءÙ\90 [[Special:FileList|Ú\86اÚ\99Ù\87Ù\8aÙ\84 Ù\81ائÙ\84Ù\86 Ø¬Ù\8a Ù\81Ù\87رست]] ØªÙ\8a Ù\88Ú\83Ù\88Ø\8c Ù»Ù\87Ù\8aر Ú\86اÚ\99Ù\87Ù\8aÙ\84 Ù\81ائÙ\84 [[Special:Log/upload|Ú\86اÚ\99Ù\87Ù\8aÙ\84 Ù\84اگ]] Û½ Ø®ØªÙ\85 ÚªÙ\8aÙ\84 [[Special:Log/delete|Ú\8aاٺ Ù\84اگ]] ØªÙ\8a Ú\8fسÙ\8a Ø³Ú¯Ú¾Ø¬Ù\86 Ù¿Ø§.\n\nÙ\81ائÙ\84 Ø¬Ù\8a Ø§Ø³ØªØ¹Ù\85اÙ\84 Ù\84اءÙ\90 Ù\87Ù\8aÙº Ú\8fÙ\8aکارÙ\8aÙ\84 Ø·Ø±Ù\8aÙ\82Ù\88 Ø§Ø³ØªØ¹Ù\85اÙ\84 ÚªØ±Ù\8a Ø³Ú¯Ú¾Ø¬Ù\8a Ù¿Ù\88:\n* <strong><code><nowiki>[[</nowiki>{{ns:file}}<nowiki>:Ù\81ائÙ\84 Ø¬Ù\88 Ù\86اÙ\84Ù\88.jpg]]</nowiki></code></strong> Ù\81ائÙ\84 Ø¬Ù\8a Ù\85ÚªÙ\85Ù\84 Ø§Ø³ØªØ¹Ù\85اÙ\84 Ù\84اءÙ\90\n* <strong><code><nowiki>[[</nowiki>{{ns:file}}<nowiki>:Ù\81ائل جو نالو.png|200px|thumb|left|متبادل اکر]]</nowiki></code></strong> هن جي مدد سان تصوير جي سائيز ڏئي سگھجي ٿي جيئن 200 پگزل\n* <strong><code><nowiki>[[</nowiki>{{ns:media}}<nowiki>:File.ogg]]</nowiki></code></strong> فائل کي ڏيکارڻ کان بغير شامل ڪرڻ",
+       "uploadtext": "Ù\81ائÙ\8aÙ\84 Ú\86اÚ\99Ù\87Ú» Ù\84اءÙ\90 Ù\87Ù\8aÙºÙ\8aÙ\88Ù\86 Ù\81ارÙ\85 Ø§Ø³ØªØ¹Ù\85اÙ\84 ÚªÙ\8aÙ\88.\nپراڻا Ú\86اÚ\99Ù\87Ù\8aÙ\84 Ù\81ائÙ\8aÙ\84 Ú\8fسڻ Ù\8aا Ú³Ù\88Ù\84Ú» Ù\84اءÙ\90 [[Special:FileList|Ú\86اÚ\99Ù\87Ù\8aÙ\84 Ù\81ائÙ\8aÙ\84Ù\86 Ø¬Ù\8a Ù\81ھرست]] ØªÙ\8a Ù\88Ú\83Ù\88Ø\8c Ù»Ù\8aھر Ú\86اÚ\99Ù\87Ù\8aÙ\84 Ù\81ائÙ\8aÙ\84 [[Special:Log/upload|Ú\86اÚ\99Ù\87Ù\8aÙ\84 Ù\84اگ]] Û½ Ø®ØªÙ\85 ÚªÙ\8aÙ\84 [[Special:Log/delete|Ú\8aاٺ Ù\84اگ]] ØªÙ\8a Ú\8fسÙ\8a Ø³Ú¯Ú¾Ø¬Ù\86 Ù¿Ø§.\n\nÙ\81ائÙ\8aÙ\84 Ø¬Ù\8a Ø§Ø³ØªØ¹Ù\85اÙ\84 Ù\84اءÙ\90 Ù\87Ù\8aÙº Ú\8fÙ\8aکارÙ\8aÙ\84 Ø·Ø±Ù\8aÙ\82Ù\88 Ø§Ø³ØªØ¹Ù\85اÙ\84 ÚªØ±Ù\8a Ø³Ú¯Ú¾Ø¬Ù\8a Ù¿Ù\88:\n* <strong><code><nowiki>[[</nowiki>{{ns:file}}<nowiki>:Ù\81ائÙ\84 Ø¬Ù\88 Ù\86اÙ\84Ù\88.jpg]]</nowiki></code></strong> Ù\81ائÙ\8aÙ\84 Ø¬Ù\8a Ù\85ÚªÙ\85Ù\84 Ø§Ø³ØªØ¹Ù\85اÙ\84 Ù\84اءÙ\90\n* <strong><code><nowiki>[[</nowiki>{{ns:file}}<nowiki>:Ù\81ائÙ\8aل جو نالو.png|200px|thumb|left|متبادل اکر]]</nowiki></code></strong> هن جي مدد سان تصوير جي سائيز ڏئي سگھجي ٿي جيئن 200 پگزل\n* <strong><code><nowiki>[[</nowiki>{{ns:media}}<nowiki>:File.ogg]]</nowiki></code></strong> فائل کي ڏيکارڻ کان بغير شامل ڪرڻ",
        "uploadlogpage": "چاڙھ لاگ",
-       "filename": "فائيل نانءُ",
+       "filename": "فائيل-نانءُ",
        "filedesc": "تَتُ",
        "fileuploadsummary": "تَتُ:",
        "filereuploadsummary": "فائيل تبديليون:",
        "filesource": "ذريعو:",
        "ignorewarnings": "چتائن کي نظرانداز ڪريو",
-       "badfilename": "فائيل‌نانءُ بدلائي \"$1\" رکيو ويو آهي.",
+       "badfilename": "فائيل‌-نانءُ بدلائي \"$1\" رکيو ويو آهي.",
        "empty-file": "توهان جو جمع ڪرايل فائيل خالي آهي.",
-       "filename-tooshort": "فائيل نانءَُ هيڪاندو ننڍو آهي.",
+       "filename-tooshort": "فائيل-نانءُ هيڪاندو ننڍو آهي.",
        "filetype-banned": "فائيل جو هيءُ قسم بندشيل آهي.",
-       "verification-error": "Ù\87Ù\86 Ù\81ائÙ\8aÙ\84 Ø¬Ù\8a ØªØµØ¯Ù\8aÙ\82 Ù¿Ù\8a Ù\86Û\81 سگھي.",
-       "illegal-filename": "اهو فائيل‌نانءُ ناقابل قبول آهي.",
-       "unknown-error": "ڪا اڻجاتل چُڪَ ٿي.",
+       "verification-error": "Ù\87Ù\86 Ù\81ائÙ\8aÙ\84 ØªØµØ¯Ù\8aÙ\82 Ù¾Ø§Ø³ Ù\86Û\81 ÚªØ±Ù\8a سگھي.",
+       "illegal-filename": "اھو فائيل‌-نانءُ ڪار ناھي آهي.",
+       "unknown-error": "ڪا اڻڄاتل چُڪَ پيش آئي.",
        "tmp-create-error": "عارضي فائيل سرجي نہ سگھيو.",
        "uploadwarning": "چاڙھ جو چتاءُ",
        "savefile": "فائيل سانڍيو",
        "uploaddisabled": "چاڙھ ناقابلِ ڪار بڻيل.",
-       "uploaddisabledtext": "فائيل چاڙهڻ بند ڪيل آهن.",
+       "uploaddisabledtext": "فائيل چاڙهڻ بند ناقابلِڪار بڻيل آهن.",
        "upload-scripted-pi-callback": "ن فائيل کي اپلوڊ نه ٿو ڪري سگهي جنهن ۾ ايڪس ايم ايل اسٽائيل شيٽ جون پراسيسنگ هدايتون شامل هجن.",
-       "uploaded-script-svg": "اسڪرپٽ جوڳو ايليمينٽ ”$1” مليو آهي، اپلوڊ ٿيل ايس وي جي فائيل ۾.",
-       "uploaded-hostile-svg": "اپلوڊ ٿيل ايس وي جي فائيل جو غير محفوظ سي ايس ايس ۾ اسٽائيل ايلمينٽ مليو",
+       "uploaded-script-svg": "چاڙھيل ايس.وي.جي فائيل ۾ اسڪرپٽ-جوڳو ايليمينٽ ”$1” مليو آهي.",
+       "uploaded-hostile-svg": "چاڙھيل ايس.وي.جي فائيل جو غير محفوظ سي.ايس.ايس اسٽائيل ايلمينٽ ۾ مليو.",
        "uploaded-event-handler-on-svg": "ايس وي جي فائيل ۾ ايوينٽ هينڊلر خصوصيتون <code>$1=\"$2\"</code> مقرر ڪرڻ جي اجازت نہ آهي.",
        "uploaded-href-unsafe-target-svg": "href جو غير محفوظ ڊيٽا: يوآرآءِ نشانو مليو آهي <code>&lt;$1 $2=\"$3\"&gt;</code> چاڙھيل اَيسوِيجِي فائيل ۾",
-       "uploaded-animate-svg": "”اينيميٽ“ ٽيگ ڳوليو  جيڪا ٿي سگهي ٿو href کي تبديل ڪري رهي هجي. \"form\" وصف استعمال ڪندي <code>&lt;$1 $2=\"$3\"&gt;</code> اپلوڊ ٿيل ايس وي جي فائيل ۾",
-       "uploaded-setting-event-handler-svg": "Ù\88اÙ\82عÙ\8a Ú©Ù\8a Ù\87Ù\8aÙ\86Ú\8aÙ\84 ÚªÙ\86دÚ\99 Ø¬Ù\8a Ø³Ù\8aÙ½Ù\86Ú¯ Ø¬Ù\88Ù\86 Ù\88صÙ\81Ù\88Ù\86 Ø¨Ù\84اڪ Ù¿Ù\8aÙ\84 Ø¢Ù\87Ù\86. \n<code>&lt;$1 $2=\"$3\"&gt;</code> Ø§Ù¾Ù\84Ù\88Ú\8a Ù¿Ù\8aÙ\84 Ø§Ù\8aس Ù\88Ù\8a جي فائيل ۾ مليو",
-       "uploaded-setting-href-svg": "\"set\"  Ù½Ù\8aÚ¯ Ú©Ù\8a \"href\" Ù\88صÙ\81 Ø§Ø³ØªØ¹Ù\85اÙ\84 ÚªÙ\86دÙ\8a Ø¨Ù\86Ù\8aادÙ\8a Ø¹Ù\86صر Ú©Ù\8a Ø¨Ù\84اڪ ÚªÙ\8aÙ\88 Ù\88Ù\8aÙ\88 Ø¢Ù\87Ù\8a",
-       "uploaded-wrong-setting-svg": "\"set\" ٽيگ کي استعمال ڪندي رموٽ/ڊيٽا/اسڪرپٽ ٽارگيٽ کي ڪنهن وصف سان جوڙڻ کي بلاڪ ڪيو ويو آهي. \n<code>&lt;set to=\"$1\"&gt;</code>اپلوڊ ٿيل ايس وي جي فائيل ۾ مليو آهي.",
-       "uploaded-setting-handler-svg": "اÙ\87Ù\8a Ø§Ù\8aس Ù\88Ù\8a Ø¬Ù\8a Ø¬Ù\8aÚªÙ\8a â\80\9dÙ\87Ù\8aÙ\86Ú\8aÙ\84 ÚªÙ\86دÚ\99â\80\9c Ù\88صÙ\81Ù\86 Ú©Ù\8a Ø±Ù\85Ù\88Ù½/Ú\8aÙ\8aٽا/اسڪرپٽ Ú©Ù\8a Ø³Ù\8aÙ½ Ù¿Ø§ ÚªÙ\86Ø\8c Ú©Ù\8a Ø¨Ù\84اڪ ÚªÙ\8aÙ\88 Ù\88Ù\8aÙ\88 Ø¢Ù\87Ù\8a.<code>$1=\"$2\"</code> Ù\85Ù\84Ù\8aÙ\88 Ø¢Ù\87Ù\8a Ø§Ù¾Ù\84Ù\88Ú\8a Ù¿Ù\8aÙ\84 Ø§Ù\8aس Ù\88Ù\8a Ø¬Ù\8a Ù\81ائÙ\8aÙ\84 Û¾.",
-       "uploaded-remote-url-svg": "ايس وي جي جيڪا سيٽ ڪري ٿي ڪنهن اسٽائيل وصف  رموٽ يو آر ايل سان  بلاڪ ٿيل آهي.\n <code>$1=\"$2\"</code> اپلوڊ ٿيل ايس وي جي فائيل ۾ مليو",
-       "uploaded-image-filter-svg": "هن يو آر ايل سان <code>&lt;$1 $2=\"$3\"&gt;</code> اميج فلٽر مليو آهي، اپلوڊ ٿيل ايس وي جي فائيل ۾،",
+       "uploaded-animate-svg": "”اينيميٽ“ ٽيگ ڳوليو جيڪو ٿي سگھي ٿو href کي تبديل ڪري رهي هجي، چاڙھيل ايس.وي.جي فائيل ۾ \"form\" وصف استعمال ڪندي <code>&lt;$1 $2=\"$3\"&gt;</code>",
+       "uploaded-setting-event-handler-svg": "Ù\85Ù\88Ù\82عÙ\88-سÙ\86Ú\80اÙ\84Ù\8aÙ\86دÚ\99 Ø¬Ø§ Ø§Ù\86تساب ØªØ±ØªÙ\8aبڻ Ø¨Ù\86دشÙ\8aÙ\84 Ø¢Ù\87Ù\86Ø\8c <code>&lt;$1 $2=\"$3\"&gt;</code> Ú\86اÚ\99Ú¾Ù\8aÙ\84 Ø§Ù\8aس.Ù\88Ù\8a.جي فائيل ۾ مليو",
+       "uploaded-setting-href-svg": "\"set\"  Ù½Ù\8aÚ¯ Ú©Ù\8a \"href\" Ù\88صÙ\81 Ø§Ø³ØªØ¹Ù\85اÙ\84 ÚªÙ\86دÙ\8a Ø¨Ù\86Ù\8aادÙ\8a Ø¹Ù\86صر Ú©Ù\8a Ø¨Ù\86دشÙ\8aÙ\88 Ù\88Ù\8aÙ\88 Ø¢Ú¾Ù\8a.",
+       "uploaded-wrong-setting-svg": "\"set\" ٽيگ کي استعمال ڪندي رموٽ/ڊيٽا/اسڪرپٽ ٽارگيٽ کي ڪنھڻ وصف سان جوڙڻ کي بلاڪ ڪيو ويو آهي. \n<code>&lt;set to=\"$1\"&gt;</code> چاڙھيل ايس.وي.جي فائيل ۾ مليو آهي.",
+       "uploaded-setting-handler-svg": "اÙ\8aس.Ù\88Ù\8a.جÙ\8a Ø¬Ù\8aÚªÙ\8a \"سÙ\86Ú\80اÙ\84Ù\8aÙ\86دÚ\99\" Ù\88صÙ\81Ù\86 Ú©Ù\8a Ø±Ù\85Ù\88Ù½/Ú\8aÙ\8aٽا/اسڪرپٽ Ú©Ù\8a Ø³Ù\8aÙ½ ÚªØ±Ù\8a Ù¿Ù\88Ø\8c Ú©Ù\8a Ø¨Ù\84اڪ ÚªÙ\8aÙ\88 Ù\88Ù\8aÙ\88 Ø¢Ù\87Ù\8a.<code>$1=\"$2\"</code> Ú\86اÚ\99Ú¾Ù\8aÙ\84 Ø§Ù\8aس.Ù\88Ù\8a.جÙ\8a Ù\81ائÙ\8aÙ\84 Û¾ Ù\85Ù\84Ù\8aÙ\88 Ø¢Ú¾Ù\8a.",
+       "uploaded-remote-url-svg": "ايس.وي.جي جيڪو سيٽ ڪري ٿو ڪنهن اسٽائيل وصف رموٽ يوآرايل سان بندشيل آهي. <code>$1=\"$2\"</code> چاڙھيل ايس.وي.جي فائيل ۾ مليو.",
+       "uploaded-image-filter-svg": "چاڙھيل ايس.وي.جي فائيل ۾ يوآرايل:<code>&lt;$1 $2=\"$3\"&gt;</code> سان عڪس ڇاڻي ملي آهي.",
        "uploadvirus": "هن فائيل ۾ وائرس آهي! \nتفصيل: $1",
        "upload-source": "ذريعي جو فائيل",
        "sourcefilename": "ذريعي جي فائيل جو نالو:",
        "upload-options": "چاڙھ جا چارا",
        "watchthisupload": "هيءُ فائيل نظر ۾ رکو",
        "upload-file-error": "اندروني چُڪَ",
-       "upload-misc-error": "چارهڻ مهل اَڻڄاتل چُڪ ٿي آهي",
-       "upload-http-error": "ايڇ ٽي ٽي پي جي چُڪَ ٿي آهي: $1",
+       "upload-misc-error": "چاڙھ جي اَڻڄاتل چُڪَ",
+       "upload-http-error": "ڪا ايڇ.ٽي.ٽي.پي چُڪَ پيش آئي آهي: $1",
        "upload-dialog-title": "فائيل چاڙهيو",
        "upload-dialog-button-cancel": "رد",
        "upload-dialog-button-back": "واپس",
        "upload-form-label-infoform-description": "تشريح",
        "upload-form-label-usage-title": "استعمال",
        "upload-form-label-usage-filename": "فائيل نانءُ",
-       "upload-form-label-own-work": "هيءُ منهنجو پنهنجو ڪم آهي.",
+       "upload-form-label-own-work": "هيءُ منھنجو پنھنجو ڪم آهي.",
        "upload-form-label-infoform-categories": "زمرا",
        "upload-form-label-infoform-date": "تاريخ",
        "backend-fail-notexists": "فائيل ''$1'' وجود نٿو رکي.",
        "img-auth-accessdenied": "دسترس کان جواب",
        "license": "لائيسنسڪاري:",
        "license-header": "لائيسنسڪاري",
-       "nolicense": "Ú\86Ù\88Ù\86Ú\8a Ø§Ú»Ù\85Ù\88جÙ\88د",
+       "nolicense": "ÚªÙ\88بÛ\81 Ù\86Û\81 Ú\86Ù\88Ù\86Ú\8aÙ\8aÙ\84",
        "listfiles-delete": "ڊاهيو",
        "imgfile": "فائيل",
-       "listfiles": "فائيل فهرست",
+       "listfiles": "فائيل فھرست",
        "listfiles_thumb": "ٽِڪِلِي",
        "listfiles_date": "تاريخ",
        "listfiles_name": "نالو",
        "filehist-datetime": "تاريخ/وقت",
        "filehist-thumb": "آڱوٺي ننھن",
        "filehist-thumbtext": "$1 جي نظرثاني لاءِ تصويري نشان",
-       "filehist-nothumb": "ٽِڪِلِي اڻموجود",
+       "filehist-nothumb": "ٽِڪِلِي ڪونھي",
        "filehist-user": "واپرائيندڙ",
        "filehist-dimensions": "ماپَ",
        "filehist-filesize": "فائيل ماپ",
        "sharedupload": "هيءَ فائيل $1 کان آهي ۽ ان کي ٻيون رٿائون به استعمال ڪري سگھن ٿيون.",
        "sharedupload-desc-here": "ھي فائيل $1 مان آھي ۽ ٻين رٿائن پاران پڻ استعمال ٿي سگھي ٿو. تشريح انجي [[$2 جو تشريحي صفحو]] ھيٺان ڏنل آھي.",
        "filepage-nofile": "ھن نالي سان ڪوبہ  فائيل وجود نٿو رکي.",
-       "uploadnewversion-linktext": "Ù\87Ù\86 Ù\81ائÙ\8aÙ\84 Ø¬Ù\88 Ù\86ئÙ\88Ù\86 Ù¾Ø±Øª چاڙهيو",
+       "uploadnewversion-linktext": "Ù\87Ù\86 Ù\81ائÙ\8aÙ\84 Ø¬Ù\88 Ù\86ئÙ\88Ù\86 Ù\88رجاءÙ\8f چاڙهيو",
        "shared-repo-from": "$1 کان",
        "shared-repo-name-wikimediacommons": "وڪيميڊيا ڪامنز",
        "upload-disallowed-here": "توھان ھن فائيل مٿان لکي نہ ٿا سگھو.",
        "filerevert-comment": "سبب:",
-       "filerevert-submit": "Ù\88اپس Ù\88راÙ\8aÙ\88",
+       "filerevert-submit": "ورايو",
        "filedelete": "$1 کي ڊاهيو",
        "filedelete-legend": "فائيل ڊاهيو",
        "filedelete-comment": "سبب:",
        "filedelete-submit": "ڊاهيو",
        "filedelete-reason-otherlist": "ٻيو سبب",
-       "filedelete-edit-reasonlist": "ڊاٺ جا سبب سنواريو",
-       "filedelete-maintenance-title": "فائيل ڊهي نہ سگھيو",
+       "filedelete-edit-reasonlist": "ڊاھ جا سبب سنواريو",
+       "filedelete-maintenance-title": "فائيل ڊاھجي نہ سگھيو",
        "mimesearch": "مائيم ڳولا",
        "download": "اتاريو",
        "unwatchedpages": "اڻ ڏٺل صفحا",
-       "listredirects": "چورڻن جي فهرست",
-       "unusedtemplates": "اڻ استعماليل سانچا",
+       "listredirects": "چورڻن جي فھرست",
+       "unusedtemplates": "اڻ-استعماليل سانچا",
        "unusedtemplateswlh": "ٻيا ڳنڍڻا",
        "randompage": "بلاترتيب صفحو",
        "randomincategory": "زمري مان ڪو بلاترتيب صفحو",
        "randomincategory-category": "زمرو:",
        "randomincategory-legend": "زمري مان ڪو بلاترتيب صفحو",
        "randomincategory-submit": "هلو",
-       "randomredirect": "بلا ترتيب چورڻو",
-       "statistics": "انگ اکر",
+       "randomredirect": "بلاترتيب چورڻو",
+       "statistics": "انگ-اکر",
        "statistics-header-pages": "صفحي انگ اکر",
        "statistics-header-edits": "سنوار جا انگ-اکر",
        "statistics-header-users": "واپرائيندڙن جا انگ اکر",
-       "statistics-header-hooks": "ٻيا انگ اکر",
+       "statistics-header-hooks": "ٻيا انگ-اکر",
        "statistics-articles": "موادي صفحا",
        "statistics-pages": "صفحا",
        "statistics-pages-desc": "وڪيءَ ۾ سڀ صفحا ٻشمول بحث صفحا، ڇوريل، وغيره.",
        "pageswithprop-prop": "خصوصيت نانءُ:",
        "pageswithprop-submit": "ھلو",
        "doubleredirects": "ٻٽا چورڻا",
-       "double-redirect-fixed-move": "[[$1]] چورجي چڪو آهي. ان کي خودڪاراً تجديديو ويو ۽ هاڻي اهو [[$2]] ڏانهن وٺي وڃي ٿو.",
+       "double-redirect-fixed-move": "[[$1]] چورجي چڪو آهي.\nان کي خودڪاراً تجديديو ويو ۽ هاڻي اهو [[$2]] ڏانھن وٺي وڃي ٿو.",
        "double-redirect-fixer": "ريڊائرڪٽ فڪس-ڪندڙ",
        "brokenredirects": "ٽٽل چورڻا",
        "brokenredirects-edit": "سنواريو",
        "brokenredirects-delete": "ڊاهيو",
-       "withoutinterwiki": "ڪنهن بہ ٻي ٻوليءَ سان نہ ڳنڍيل صفحا",
-       "withoutinterwiki-summary": "هيٺيان صفحا ڪنهن بہ ٻي ٻوليءَ ۾ ساڳي صفحي سان ڳنڍيل نہ آهن.",
+       "withoutinterwiki": "ٻولين جي ڳنڍڻن سواءِ صفحا",
+       "withoutinterwiki-summary": "ھيٺيان صفحا ڪنھن بہ ٻي ٻوليءَ ۾ ساڳي صفحي سان ڳنڍيل نہ آھن.",
        "withoutinterwiki-legend": "اڳياڙي",
        "withoutinterwiki-submit": "ڏيکاريو",
-       "fewestrevisions": "گھٽانگھٽ ترميميل صفحا",
+       "fewestrevisions": "گھٽ-ترين ورجاءَ رکندڙ صفحا",
        "nbytes": "$1 {{PLURAL:$1|بائيٽ|بائيٽون}}",
        "ncategories": "$1 {{PLURAL:$1|زمرو|زمرا}}",
        "ninterwikis": "$1 {{PLURAL:$1|بين‌الوڪي}}",
        "nimagelinks": "$1 {{PLURAL:$1|صفحي|صفحن}} ۾ استعمال ٿيل",
        "ntransclusions": "$1 {{PLURAL:$1|صفحي|صفحن}} ۾ استعمال ٿيل",
        "specialpage-empty": "ھن رپورٽ لاءِ ڪي بہ نتيجا ناھن.",
-       "lonelypages": "يتيم صفحا",
-       "uncategorizedpages": "اڻ زمريل صفحا",
+       "lonelypages": "يتيم-ٿيل صفحا",
+       "uncategorizedpages": "اڻزمرايل صفحا",
        "uncategorizedcategories": "اڻزمرايل زمرا",
        "uncategorizedimages": "اڻزمرايل فائيل",
        "uncategorizedtemplates": "اڻزمرايل سانچا",
-       "unusedcategories": "اڻ استعماليل زمرا",
-       "unusedimages": "اڻ استعماليل فائيلس",
+       "unusedcategories": "اڻ-استعماليل زمرا",
+       "unusedimages": "اڻ-استعماليل فائيلَ",
        "wantedcategories": "گھربل زمرا",
        "wantedpages": "گھربل صفحا",
        "wantedtemplates": "گھربل سانچا",
        "mostlinkedtemplates": "گھڻي کان گھڻا سانچا رکندڙ صفحا",
        "mostcategories": "گھڻي کان گھڻا زمرا رکندڙ صفحا",
        "mostimages": "وڌانوڌ ڳنڍيندڙ فائيل",
-       "mostrevisions": "وڌانوڌ ترميميل صفحا",
+       "mostrevisions": "وڌانوڌ ورجاءَ رکندڙ صفحا",
        "prefixindex": "هيءَ اڳياڙي رکندڙ سمورا صفحا",
        "prefixindex-namespace": "سمورا صفحا جن کي هيءَ اڳياڙي آهي ($1 نانءُپولار)",
        "prefixindex-submit": "ڏيکاريو",
        "protectedpages": "تحفظيل صفحا",
        "protectedpages-filters": "ڇاڻيون:",
        "protectedpages-noredirect": "چورڻا لڪايو",
-       "protectedpages-timestamp": "اوقاتي مُهُرَ",
+       "protectedpages-timestamp": "وقت-ٺپو",
        "protectedpages-page": "صفحو",
-       "protectedpages-params": "تحÙ\81ظ Ø¬Ø§ Ù\86Ù\85Ù\8aپيما",
+       "protectedpages-params": "تحÙ\81ظ Ø¬Ø§ Ù\86Ù\8aÙ\85پيما",
        "protectedpages-reason": "سبب",
        "protectedpages-submit": "صفحا ڏيکاريو",
        "protectedpages-unknown-timestamp": "اڻڄاتل",
        "protectedtitles": "تحفظيل عنوان",
        "protectedtitles-submit": "عنوان ڏيکاريو",
        "listusers": "واپرائيندڙن جي فهرست",
-       "listusers-editsonly": "صرÙ\81 ØªØ±Ù\85Ù\8aÙ\85ن وارا واپرائيندڙ ڏيکاريو",
+       "listusers-editsonly": "صرÙ\81 Ø³Ù\86Ù\88ارن وارا واپرائيندڙ ڏيکاريو",
        "listusers-temporarygroupsonly": "صرف عارضي واپرائيندڙ گروھن ۾ واپرائيندڙ ڏيکاريو",
        "listusers-creationsort": "سرجڻ جي تاريخ سان مرتب ڪريو",
        "listusers-desc": "گھٽجندڙ ترتيب ۾ مرتب ڪريو",
        "newpages": "نوان صفحا",
        "newpages-submit": "ڏيکاريو",
        "newpages-username": "واپرائيندڙ-نانءُ:",
-       "ancientpages": "قديم ترين صفحا",
+       "ancientpages": "قديم-ترين صفحا",
        "move": "چوريو",
        "movethispage": "هيءُ صفحو چوريو",
        "notargettitle": "بنان هدف",
        "nopagetitle": "اهدافي صفحو اڻموجود",
        "pager-newer-n": "{{PLURAL:$1|نئون تر 1|نوان تر $1}}",
        "pager-older-n": "{{PLURAL:$1|پراڻو 1|پراڻا $1}}",
-       "apisandbox-retry": "ٻيهر ڪوشش ڪريو",
-       "apisandbox-helpurls": "امدادي ڳنڍڻا",
+       "apisandbox-retry": "ٻيھر ڪوشش ڪريو",
+       "apisandbox-helpurls": "مددي ڳنڍڻا",
        "apisandbox-examples": "مثال",
-       "apisandbox-dynamic-parameters-add-label": "نيمپيما شامل ڪريو",
+       "apisandbox-dynamic-parameters-add-label": "نيمپيما وجھو:",
        "apisandbox-dynamic-parameters-add-placeholder": "نيمپيما نانءُ",
-       "apisandbox-add-multi": "شامل ڪيو",
+       "apisandbox-add-multi": "وجھو",
        "apisandbox-results": "نتيجا",
        "apisandbox-continue": "جاري رکو",
        "booksources": "ڪتابي وسيلا",
        "emailuser": "هن واپرائيندڙ کي برقٽپال اماڻيو",
        "emailuser-title-target": "ھن {{GENDER:$1|واپرائيندڙ}} ڏانھن برقٽپال موڪليو",
        "emailuser-title-notarget": "واپرائيندڙ ڏانھن برقٽپال اماڻيو",
-       "emailpagetext": "Ù\87Ù\8aÙº Ú\8fÙ\86Ù\84 Ù\81ارÙ\85 Ø¬Ù\8a Ø°Ø±Ù\8aعÙ\8a Ø§Ù\88Ù\87اÙ\86 Ù\87Ù\86 {{GENDER:$1|Ù\88اپرائÙ\8aÙ\86دÚ\99}} Ú©Ù\8a Ø¨Ø±Ù\82Ù\8a Ù½Ù¾Ø§Ù\84 Ù¾Ù\8aغاÙ\85 Ù\85Ù\88ÚªÙ\84Ù\8a Ø³Ú¯Ú¾Ù\88 Ù¿Ø§. Ø¬Ù\8aÚªÙ\88 Ø¨Ø±Ù\82 Ù½Ù¾Ø§Ù\84 Ù¾ØªÙ\88 Ø§Ù\88Ù\87اÙ\86 [[Special:Preferences|Ù¾Ù\86Ù\87Ù\86جÙ\8a ØªØ±Ø¬Ù\8aحات]] ۾ ڏنو آهي اهو هتي \"کان\" جي طور نظر ايندو، جيئن وصول ڪندڙ اوهان کي سڌو جواب ڏئي سگھي.",
+       "emailpagetext": "Ù\87Ù\8aÙº Ú\8fÙ\86Ù\84 Ù\81ارÙ\85 Ø¬Ù\8a Ø°Ø±Ù\8aعÙ\8a Ø§Ù\88Ù\87اÙ\86 Ù\87Ù\86 {{GENDER:$1|Ù\88اپرائÙ\8aÙ\86دÚ\99}} Ú©Ù\8a Ø¨Ø±Ù\82ٽپاÙ\84 Ù¾Ù\8aغاÙ\85 Ù\85Ù\88ÚªÙ\84Ù\8a Ø³Ú¯Ú¾Ù\88 Ù¿Ø§. Ø¬Ù\8aÚªÙ\88 Ø¨Ø±Ù\82ٽپاÙ\84 Ù¾ØªÙ\88 Ø§Ù\88Ù\87اÙ\86 [[Special:Preferences|Ù¾Ù\86Ù\87Ù\86جÙ\8a ØªØ±Ø¬Ù\8aØ­Ù\86]] ۾ ڏنو آهي اهو هتي \"کان\" جي طور نظر ايندو، جيئن وصول ڪندڙ اوهان کي سڌو جواب ڏئي سگھي.",
        "usermaildisabled": "واپرائيندڙ برقٽپال ناقابلِڪار بڻيل",
        "usermaildisabledtext": "توهان هن وڪي تي ٻين واپرائيندڙن ڏانهن برقٽپال نٿا موڪلي سگھو",
        "noemailtitle": "برقٽپال پتو ناھي",
        "actioncomplete": "ڪم پُورو",
        "actionfailed": "عمل ناڪام",
        "deletedtext": "\"$1\" ڊهي چڪو آهي.\nتازو ڊاٺل صفحن جي فهرست لاءِ $2 ڏسندا.",
-       "dellogpage": "ڊاٺ لاگ",
+       "dellogpage": "ڊاھ لاگ",
        "deletionlog": "ڊاٺ لاگ",
        "deletecomment": "سبب:",
        "deleteotherreason": "اڃا ڪو ٻيو سبب:",
        "deletereasonotherlist": "ٻيو سبب",
        "delete-edit-reasonlist": "ڊاٺ جا سبب سنواريو",
-       "rollback": "ترÙ\85Ù\8aÙ\85Ù\86 Ú©Ù\8a واپس ورايو",
+       "rollback": "سÙ\86Ù\88ارÙ\88Ù\86 واپس ورايو",
        "rollbacklink": "واپس ورايو",
        "rollbacklinkcount": "$1 {{PLURAL:$1|سنوار|سنوارون}} واپس-ورايو",
        "revertpage": "[[Special:Contributions/$2|$2]] ([[User talk:$2|بحث]]) پاران سنوارون واپس [[User:$1|$1]] جي آخري مسودي ڏانھن ڪيون ويون",
        "changecontentmodel-title-label": "صفحي جو عنوان",
        "changecontentmodel-reason-label": "سبب:",
+       "changecontentmodel-submit": "بدلايو",
        "logentry-contentmodel-change-revertlink": "واپس ورايو",
        "logentry-contentmodel-change-revert": "واپس ورايو",
        "protectlogpage": "تحفظ لاگ",
        "whatlinkshere-title": "\"$1\" سان ڳنڍيندڙ صفحا",
        "whatlinkshere-page": "صفحو:",
        "linkshere": "هيٺيان صفحا <strong>$2</strong> سان ڳنڍيل آهن:",
-       "nolinkshere": "'''$2''' سان ڪو بہ صفحو ڳنڍيل ناهي.",
+       "nolinkshere": "<strong>$2</strong> سان ڪو بہ صفحو ڳنڍيل ناهي.",
        "isredirect": "چورڻو صفحو",
        "istemplate": "شموليت",
        "isimage": "فائيل جو ڳنڍڻو",
        "whatlinkshere-prev": "{{PLURAL:$1|پويون|پويون $1}}",
        "whatlinkshere-next": "{{PLURAL:$1|اڳيون|اڳيان $1}}",
-       "whatlinkshere-links": "â\86\90 ڳنڍڻا",
+       "whatlinkshere-links": "â\86\92 ڳنڍڻا",
        "whatlinkshere-hideredirs": "$1 چوري ٿو",
        "whatlinkshere-hidetrans": "$1 شموليت",
        "whatlinkshere-hidelinks": "$1 ڳنڍڻا",
        "contribslink": "ڀاڱيداريون",
        "emaillink": "برقٽپال اماڻيو",
        "blocklogpage": "بندش لاگ",
-       "blocklogentry": "\"[[$1]]\" کي بندشيو ويو $2 $3 جي عرصي لاء",
+       "blocklogentry": "$2 $3 جي عرصي لاءِ [[$1]] کي بندشيو وي",
        "unblocklogentry": "$1 تان بندش هٽائي وئي",
        "block-log-flags-anononly": "فقط نامعلوم واپرائيندڙَ",
        "block-log-flags-nocreate": "کاتو کولڻ کان روڪ ٿيل",
        "tooltip-publish": "پنهنجيون تبديليون ڇاپيو",
        "tooltip-preview": "پنھنجي تبديلين تي نگاھ وجھو. براءِ مھرباني اھو سانڍڻ کان اڳ ڪندا.",
        "tooltip-diff": "لکت ۾ ڪيل پنھنجون تبديليون ڏسو",
-       "tooltip-compareselectedversions": "Ù\87Ù\86 ØµÙ\81Ø­Ù\8a Ø¬Ù\86 Ù»Ù\86 Ú\86Ù\88Ù\86Ú\8aÙ\8aÙ\84 Ù¾Ø±ØªÙ\86 Ø¯Ø±Ù\85Ù\8aاÙ\86 ØªÙ\81اÙ\88ت Ú\8fسÙ\88.",
+       "tooltip-compareselectedversions": "Ù\87Ù\86 ØµÙ\81Ø­Ù\8a Ø¬Ù\86 Ù\88Ú\86 Û¾ Ú\86Ù\88Ù\86Ú\8aÙ\8aÙ\84 Ù\88رجائÙ\86 Ù\88Ú\86 Û¾ ØªÙ\81اÙ\88ت Ú\8fسÙ\88",
        "tooltip-watch": "هيءُ صفحو پنهنجي نظر ۾ فھرست ۾ شامل ڪريو",
        "tooltip-watchlistedit-normal-submit": "فائيل ھٽايو",
        "tooltip-watchlistedit-raw-submit": "واچ لسٽ کي اَپڊيٽ ڪيو",
        "pageinfo-language": "صفحي جي مواد جي ٻولي",
        "pageinfo-content-model": "صفحي جي مواد جو ماڊل",
        "pageinfo-robot-index": "اجازت ڏنل",
-       "pageinfo-robot-noindex": "اجازت ناهي",
+       "pageinfo-robot-noindex": "اجازت-ناهي",
        "pageinfo-watchers": "صفحا ڏسندڙن جو انگ",
        "pageinfo-few-watchers": "$1 کان گھٽ {{PLURAL:$1|ڏسندڙ}}",
        "pageinfo-redirects-name": "ھن صفحي ڏانھن ڇوريل صفحن جو انگ",
        "pageinfo-firsttime": "صفحي سرجڻ جي تاريخ",
        "pageinfo-lastuser": "تازو ترين سنواريندڙ",
        "pageinfo-lasttime": "تازي ترين سنوار جي تاريخ",
-       "pageinfo-edits": "سÚ\80Ù\86Ù\8a ØªØ±Ù\85Ù\8aÙ\85ن جو انگ",
+       "pageinfo-edits": "سÚ\80Ù\86Ù\8a Ø³Ù\86Ù\88ارن جو انگ",
        "pageinfo-authors": "چٽن ليکڪن جو مڪمل انگ",
-       "pageinfo-recent-edits": "(گذرÙ\8aÙ\84 $1 Û¾) ØªØ§Ø²Ù\8aÙ\86 ØªØ±Ù\85Ù\8aÙ\85ن جو انگ",
+       "pageinfo-recent-edits": "(گذرÙ\8aÙ\84 $1 Û¾) ØªØ§Ø²Ù\8aÙ\86 Ø³Ù\86Ù\88ارن جو انگ",
        "pageinfo-recent-authors": "چٽن ليکڪن جو تازو انگ",
        "pageinfo-magic-words": "جادوئي {{PLURAL:$1|لفظُ|لفظَ}} ($1)",
        "pageinfo-hidden-categories": "لڪيل {{PLURAL:$1|زمرو|زمرا}} ($1)",
        "confirm-unwatch-button": "ٺيڪ",
        "confirm-unwatch-top": "هيءُ صفحو پنهنجي نظر ۾ فهرست مان هٽائيندا؟",
        "confirm-rollback-top": "ھن صفحي ۾ ڪيل سنوارون واپس ورايون؟",
+       "semicolon-separator": "؛&#32;",
+       "comma-separator": "،&#32;",
        "quotation-marks": "\"$1\"",
        "imgmultipageprev": "← اڳوڻو صفحو",
-       "imgmultipagenext": "ايندڙ صفحو →",
+       "imgmultipagenext": "اڳيون صفحو ←",
        "imgmultigo": "هلو!",
-       "imgmultigoto": "$1 صفحي تي هلو",
+       "imgmultigoto": "$1 صفحي ڏانھن هلو",
        "img-lang-go": "ھلو",
        "table_pager_next": "مٿيون صفحو",
        "table_pager_prev": "پويون صفحو",
        "table_pager_limit_submit": "ھلو",
        "table_pager_empty": "ڪو بہ نتيجو نہ مليو",
        "autoredircomment": "صفحي کي [[$1]] ڏانھن چوريو",
+       "autosumm-removed-redirect": "[[$1]] ڏانھن چورڻو ھٽايو",
        "autosumm-newblank": "خالي صفحو سرجيو ويو",
        "watchlistedit-normal-title": "نظر ۾ فھرست کي سنواريو",
        "watchlistedit-raw-titles": "عنوانَ:",
        "version-specialpages": "خاص صفحا",
        "version-variables": "ڦِرڻا",
        "version-other": "ٻيو",
-       "version-license": "ذريعات‌وڪي لائيسنس",
-       "version-ext-license": "لائيسنس",
+       "version-license": "ذريعات‌وڪي اجازتنامو",
+       "version-ext-license": "اجازتنامو",
        "version-ext-colheader-name": "توسيع",
        "version-skin-colheader-name": "چَمَ",
        "version-ext-colheader-version": "ڀيرو",
-       "version-ext-colheader-license": "لائيسنس",
+       "version-ext-colheader-license": "اجازتنامو",
        "version-ext-colheader-description": "تشريح",
        "version-ext-colheader-credits": "ليکڪ",
-       "version-license-title": "لائيسنس براءِ $1",
+       "version-license-title": "$1 لاءِ اجازتنامو",
        "version-poweredby-others": "ٻيا",
        "version-poweredby-translators": "translatewiki.net جا ترجميڪار",
        "version-software": "تنصيب شده منطقگري",
        "version-software-version": "ڀيرو",
        "version-libraries-library": "لائبريري",
        "version-libraries-version": "ڀيرو",
-       "version-libraries-license": "لائيسنس",
+       "version-libraries-license": "اجازتنامو",
        "version-libraries-description": "تشريح",
        "version-libraries-authors": "ليکڪ",
        "redirect-submit": "ھلو",
        "tag-filter-submit": "ڇاڻي",
        "tag-list-wrapper": "[[Special:Tags|{{PLURAL:$1|ٽيگ|ٽيگز}}]]: $2",
        "tag-mw-new-redirect": "نئون چوريل",
+       "tag-mw-removed-redirect": "چورڻو ھٽايو",
        "tag-mw-blank": "خالي",
+       "tag-mw-rollback": "واپس-ورايو",
        "tag-mw-rollback-description": "واپس-ورايو ڳنڍڻي کي استعمال ڪندي پوين سنوارن کي واپس ورائيندڙ سنوارون",
        "tags-title": "ٽيگس",
        "tags-tag": "ٽيگ نانءُ",
index e0f4f5c..049bb3b 100644 (file)
        "rcfilters-filter-showlinkedto-label": "Prikaži promjene na stranicama ka kojima vode veze",
        "rcfilters-filter-showlinkedto-option-label": "<strong>Stranice ka kojima vode veze sa</strong> izabrane stranice",
        "rcfilters-target-page-placeholder": "Unesite ime stranice (ili kategorije)",
+       "rcfilters-allcontents-label": "Cijeli sadržaj",
+       "rcfilters-alldiscussions-label": "Svi razgovori",
        "rcnotefrom": "Ispod {{PLURAL:$5|je izmjena|su izmjene}} od <strong>$3, $4</strong> (do <strong>$1</strong> prikazano).",
        "rclistfromreset": "Resetiraj izbor datuma",
        "rclistfrom": "Prikaži nove poruke od / Прикажи нове поруке од $3 $2",
        "move-subpages": "Premjesti podstranice (sve do $1)",
        "move-talk-subpages": "Premjesti podstranice stranice za razgovor (sve do $1)",
        "movepage-page-exists": "Stranica $1 već postoji i ne može biti automatski zamijenjena.",
+       "movepage-source-doesnt-exist": "Stranica $1 ne postoji i zbog toga ne može se premjestiti.",
        "movepage-page-moved": "Stranica $1 je premještena na $2.",
        "movepage-page-unmoved": "Stranica $1 ne može biti premještena u $2.",
        "movepage-max-pages": "Maksimum od $1 {{PLURAL:$1|stranice|stranice|stranica}} je premješteno i više nije moguće premjestiti automatski.",
        "delete_and_move_reason": "Obrisano da se oslobodi mjesto za premještanje iz „[[$1]]“",
        "selfmove": "Naslov je istovetan;\nne mogu ga premjestiti preko same sebe.",
        "immobile-source-namespace": "Ne mogu premjestiti stranice u imenski prostor \"$1\"",
+       "immobile-source-namespace-iw": "S ovog wikija ne mogu se premjestiti stranice na drugim wikijima.",
        "immobile-target-namespace": "Ne mogu se premjestiti stranice u imenski prostor \"$1\"",
        "immobile-target-namespace-iw": "Međuwiki link nije valjano odredište premještanja stranice.",
        "immobile-source-page": "Ova stranica se ne može premještati.",
        "immobile-target-page": "Ne može se preusmjeriti na taj odredišni naslov.",
+       "movepage-invalid-target-title": "Zatraženo ime nije valjano.",
        "bad-target-model": "Željeno odredište koristi drugačiji model sadržaja. Ne mogu da pretvorim iz $1 u $2.",
        "imagenocrossnamespace": "Ne može se premjestiti datoteka u nedatotečni imenski prostor",
        "nonfile-cannot-move-to-file": "Ne mogu se premjestiti podaci u datotečni imenski prostor",
index 9d3b381..4ef77de 100644 (file)
        "rev-showdeleted": "ݙیکھاؤ",
        "revdelete-show-file-submit": "ڄیا",
        "revdelete-hide-text": "دہرائی دی عبارت",
+       "revdelete-hide-image": "فائل دا مواد لکاؤ",
+       "revdelete-hide-name": "پیرامیٹر تے ٹارگٹ لکاؤ",
        "revdelete-hide-comment": "تبدیلی دا خلاصہ",
        "revdelete-radio-same": "(تبدیل نہ کرو)",
        "revdelete-radio-set": "پوشیدہ",
        "yourrealname": "اصلی ناں:",
        "yourlanguage": "زبان",
        "yournick": "نویں دستخط:",
+       "gender-male": "او وکی ورقیاں وچ تبدیلی کریندا ہے",
+       "gender-female": "او وکی ورقیاں وچ تبدیلی کریندی ہے",
        "email": "ای میل",
        "prefs-help-email-required": "ای میل پتے دی لوڑ ہے۔",
        "prefs-info": "بنیادی معلومات",
index a65feb1..253b272 100644 (file)
        "rcfilters-filter-showlinkedto-label": "Pokaži spremembe na straneh, ki kažejo na",
        "rcfilters-filter-showlinkedto-option-label": "<strong>Strani, ki kažejo na</strong> izbrano stran",
        "rcfilters-target-page-placeholder": "Vnesite ime strani (ali kategorije)",
+       "rcfilters-allcontents-label": "Vse vsebine",
+       "rcfilters-alldiscussions-label": "Vse razprave",
        "rcnotefrom": "{{PLURAL:$5|Navedena je sprememba|Navedeni sta spremembi|Navedene so spremembe}} od <strong>$3 $4</strong> dalje (prikazujem jih do <strong>$1</strong>).",
        "rclistfromreset": "Ponastavi izbiro datuma",
        "rclistfrom": "Prikaži spremembe od $3 $2 naprej",
index 5129716..fa0a999 100644 (file)
        "rcfilters-filter-showlinkedto-label": "Visa ändringar på sidor som länkar till",
        "rcfilters-filter-showlinkedto-option-label": "<strong>Sidor som länkar till</strong> den valda sidan",
        "rcfilters-target-page-placeholder": "Ange namnet på en sida (eller kategori)",
+       "rcfilters-allcontents-label": "Allt innehåll",
+       "rcfilters-alldiscussions-label": "Alla diskussioner",
        "rcnotefrom": "Nedan visas {{PLURAL:$5|ändringen|ändringar}} sedan <strong>$3, $4</strong> (upp till <strong>$1</strong> ändringar visas).",
        "rclistfromreset": "Återställ datumval",
        "rclistfrom": "Visa nya ändringar från och med $2 $3",
        "move-subpages": "Flytta undersidor (upp till $1)",
        "move-talk-subpages": "Flytta undersidor av diskussionssidan (upp till $1)",
        "movepage-page-exists": "Sidan $1 finns redan och kan inte skrivas över automatiskt.",
+       "movepage-source-doesnt-exist": "Sidan $1 finns inte och kan inte flyttas.",
        "movepage-page-moved": "Sidan $1 har flyttats till $2.",
        "movepage-page-unmoved": "Sidan $1 kunde inte flyttas till $2.",
        "movepage-max-pages": "Gränsen på $1 {{PLURAL:$1|flyttad sida|flyttade sidor}} har uppnåtts och inga fler sidor kommer att flyttas automatiskt.",
        "delete_and_move_reason": "Raderad för att göra plats till flyttning av \"[[$1]]\"",
        "selfmove": "Titeln är densamma;\nkan inte flytta en sida till sig själv.",
        "immobile-source-namespace": "Kan inte flytta sidor i namnrymden \"$1\"",
+       "immobile-source-namespace-iw": "Sidor på andra wikis kan inte flyttas från denna wiki.",
        "immobile-target-namespace": "Kan inte flytta sidor till namnrymden \"$1\"",
        "immobile-target-namespace-iw": "Interwikilänk är inte ett giltigt mål för sidflyttar.",
        "immobile-source-page": "Denna sida är inte flyttbar.",
        "immobile-target-page": "Kan inte flytta till det målnamnet.",
+       "movepage-invalid-target-title": "Det begärda namnet är inte giltigt.",
        "bad-target-model": "Den önskade destinationen använder en annan innehållsmodell. Kan inte konvertera från $1 till $2.",
        "imagenocrossnamespace": "Kan inte flytta filer till andra namnrymder än filnamnrymden.",
        "nonfile-cannot-move-to-file": "Kan inte flytta icke-fil till filnamnrymden.",
index 0fe4b17..765dcd4 100644 (file)
        "editfont-monospace": "Monotypowe krojło",
        "editfont-sansserif": "Bezszeryfowe krojło",
        "editfont-serif": "Szeryfowe krojło",
-       "sunday": "Ńydźela",
-       "monday": "Pyńdźołek",
+       "sunday": "Niydziela",
+       "monday": "Pyńdziałek",
        "tuesday": "Wtorek",
        "wednesday": "Strzoda",
-       "thursday": "Sztwortek",
-       "friday": "Pntek",
+       "thursday": "Sztwŏrtek",
+       "friday": "Pntek",
        "saturday": "Sobota",
-       "sun": "Ńyd",
+       "sun": "Niy",
        "mon": "Pyń",
        "tue": "Wto",
        "wed": "Str",
        "thu": "Szt",
-       "fri": "P",
+       "fri": "P",
        "sat": "Sob",
        "january": "styczyń",
        "february": "luty",
        "march": "marzec",
-       "april": "kwiyciyń",
+       "april": "kwieciyń",
        "may_long": "mŏj",
-       "june": "czyrwiyc",
-       "july": "lipiyc",
+       "june": "czyrwiec",
+       "july": "lipiec",
        "august": "siyrpiyń",
        "september": "wrzesiyń",
-       "october": "paździyrnik",
+       "october": "październik",
        "november": "listopad",
        "december": "grudziyń",
-       "january-gen": "styczńa",
-       "february-gen": "lutygo",
+       "january-gen": "stycznia",
+       "february-gen": "lutego",
        "march-gen": "marca",
-       "april-gen": "kwjetńa",
-       "may-gen": "moja",
+       "april-gen": "kwietnia",
+       "may-gen": "mŏja",
        "june-gen": "czyrwca",
        "july-gen": "lipca",
-       "august-gen": "śyrpńa",
-       "september-gen": "wrzyśńa",
-       "october-gen": "paźdźerńika",
+       "august-gen": "siyrpnia",
+       "september-gen": "września",
+       "october-gen": "października",
        "november-gen": "listopada",
-       "december-gen": "grudńa",
+       "december-gen": "grudnia",
        "jan": "sty",
        "feb": "lut",
        "mar": "mar",
        "september-date": "$1 wrzyśńa",
        "october-date": "$1 paźdźyrńika",
        "december-date": "$1 grudńa",
-       "pagecategories": "{{PLURAL:$1|Kategoryjo|Kategoryje|Kategoryji}}",
-       "category_header": "Zajty we katygoryji \"$1\"",
-       "subcategories": "Podkatygoryje",
-       "category-media-header": "Pliki we katygoryji \"$1\"",
-       "category-empty": "''Terozki w tyj katygoryji sům żodne artikle a pliki''",
-       "hidden-categories": "{{PLURAL:$1|Schowano katygoryjo|Schowane katygoryje|Schowanych katygoryj}}",
+       "pagecategories": "{{PLURAL:$1|Kategoryjŏ|Kategoryje}}",
+       "category_header": "Strōny we kategoryji \"$1\"",
+       "subcategories": "Podkategoryje",
+       "category-media-header": "Zbiory we kategoryji „$1”",
+       "category-empty": "<em>We tyj kategoryji niy ma terŏz żŏdnych strōn ani mediōw.</em>",
+       "hidden-categories": "{{PLURAL:$1|Skrytŏ kategoryjŏ|Skryte kategoryje|Skrytych kategoryji}}",
        "hidden-category-category": "Schowane katygoryje",
-       "category-subcat-count": "{{PLURAL:$2|Ta katygoryjo mo jyno jedno podkatygoryjo.|Ta katygoryjo mo {{PLURAL:$1|tako podkatygoryjo|$1 podkatygoryje|$1 podkatygoryj}} ze wjelośći wszyjskich katygoryj: $2.}}",
+       "category-subcat-count": "{{PLURAL:$2|Ta kategoryjŏ mŏ ino jednã podkategoryjõ.|Ta kategoryjõ mŏ {{PLURAL:$1|takõ podkategoryjõ|$1 podkategoryje|$1 podkategoryji}} ze wszyjskich $2 kategoryji.}}",
        "category-subcat-count-limited": "Ta katygoryjo mo {{PLURAL:$1|tako podkatygoryjo|$1 podkatygoryje|$1 podkatygoryji}}.",
-       "category-article-count": "{{PLURAL:$2|W tyj katygoryji je jyno jydno zajta.|W katygoryji {{PLURAL:$1|je ukozano $1 zajta|sům ukozane $1 zajty|je ukozanych $1 zajtůw}} ze cołkij wjelośći $2 zajtůw.}}",
+       "category-article-count": "{{PLURAL:$2|We tyj kategoryji je ino jedna strōna.|We kategoryji {{PLURAL:$1|je ta strōna|sōm te $1 strōny|je te $1 strōn}} ze wszyjskich $2 strōn.}}",
        "category-article-count-limited": "W katygoryji {{PLURAL:$1|je pokozano $1 zajta|sům pokozane $1 zajty|je pokazanych $1 zajtůw}}.",
-       "category-file-count": "{{PLURAL:$2|W katygoryji znojduje śe jydyn plik.|W katygoryji {{PLURAL:$1|je pokozany $1 plik|sům pokozane $1 pliki|je pokozanych $1 plikůw}} ze cołkyj liczby $2 plikůw.}}",
+       "category-file-count": "{{PLURAL:$2|We tyj kategoryji je ino jedyn zbiōr.|We tyj kategoryji {{PLURAL:$1|je tyn $1 zbiōr|sōm te $1 zbiory|je te $1 zbiorōw}} ze wszyjskich $2 zbiorōw.}}",
        "category-file-count-limited": "W katygoryji {{PLURAL:$1|je pokozany $1 plik|sům pokozane $1 pliki|je pokozanych $1 plikůw}}.",
-       "listingcontinuesabbrev": "ć.d.",
+       "listingcontinuesabbrev": "cd.",
        "index-category": "Indeksowane zajty",
-       "noindex-category": "Ńyindeksowane zajty",
-       "broken-file-category": "Zajty z linkami do niyôbecnych zbiorōw",
-       "about": "Uo serwiśe",
+       "noindex-category": "Niyindeksowane strōny",
+       "broken-file-category": "Strōny ze zepsutymi linkami do zbiorōw",
+       "about": "Ô serwisie",
        "article": "zajta",
-       "newwindow": "(uodwjyro śe we nowym uokńe)",
-       "cancel": "Uodćepej",
+       "newwindow": "(ôtwiyrŏ we nowym ôknie)",
+       "cancel": "Ôdciep",
        "moredotdotdot": "Wjyncyj...",
        "morenotlisted": "Ńy je to kůmplytno lista",
        "mypage": "Zajta",
-       "mytalk": "Dyskusyjo",
+       "mytalk": "Dyskusyjŏ",
        "anontalk": "Godka tygo IP",
-       "navigation": "Nawigacyjo",
-       "and": "&#32;a",
+       "navigation": "Nawigacyjŏ",
+       "and": "&#32;i",
        "faq": "FAQ",
        "actions": "Akcyje",
        "namespaces": "Przestrzynie mian",
-       "variants": "Ôpcyje",
-       "navigation-heading": "Menu nawigacyje",
+       "variants": "Warianty",
+       "navigation-heading": "Myni nawigacyje",
        "errorpagetitle": "Feler",
-       "returnto": "Nazod do zajty $1.",
+       "returnto": "Wrōć do $1.",
        "tagline": "Ze {{GRAMMAR:D.lp|{{SITENAME}}}}",
-       "help": "Půmoc",
+       "help": "PÅ\8dmoc",
        "search": "Szukej",
        "searchbutton": "Szukej",
        "go": "Przyńdź",
-       "searcharticle": "dź",
-       "history": "Gyszichta zajty",
-       "history_short": "Gyszichta",
+       "searcharticle": "Idź",
+       "history": "Historyjŏ strōny",
+       "history_short": "Historyjŏ",
        "updatedmarker": "pomjyńane uod uostatńij wizyty",
        "printableversion": "Wersyjŏ do durku",
-       "permalink": "Link do tyj wersyje zajty",
+       "permalink": "Link trwały",
        "print": "Drukuj",
-       "view": "Podglůnd",
-       "view-foreign": "Uobejrzij we {{grammar:MS.lp|$1}}",
+       "view": "Pokŏż",
+       "view-foreign": "Ôbejzdrzij we {{grammar:MS.lp|$1}}",
        "edit": "Edytuj",
-       "create": "Stwůrz",
-       "create-local": "Wkludź lokalny uopis",
-       "delete": "Wyćep",
+       "create": "StwÅ\8drz",
+       "create-local": "Wkludź lokalny ôpis",
+       "delete": "Skasuj",
        "undelete_short": "Wćep nazod {{PLURAL:$1|jedna wersyjo|$1 wersyje|$1 wersyji}}",
        "viewdeleted_short": "{{PLURAL:$1|jedna wyćepano wersyjo|$1 wyćepane wersyje|$1 wyćepanych wersyjůw}}",
        "protect": "Zawrzij",
        "protect_change": "půmjyń",
        "unprotect": "Uodymkńij",
-       "newpage": "Nowy artikel",
+       "newpage": "Nowŏ strōna",
        "talkpagelinktext": "dyskusyjŏ",
-       "specialpage": "Szpecyjolno zajta",
-       "personaltools": "Perzōnŏlne",
-       "talk": "Dyskusyjo",
-       "views": "Ôbŏcz",
-       "toolbox": "Nŏczynia",
+       "specialpage": "Specjalnŏ strōna",
+       "personaltools": "Włŏsne nŏrzyńdzia",
+       "talk": "Dyskusyjŏ",
+       "views": "Widoki",
+       "toolbox": "Nŏrzyńdzia",
        "imagepage": "Uobejrz zajta pliku",
        "mediawikipage": "Zajta komuńikata",
        "templatepage": "Zajta mustra",
        "viewhelppage": "Zajta půmocy",
        "categorypage": "Zajta katygoryji",
        "viewtalkpage": "Zajta godki",
-       "otherlanguages": "We inkszych godkach",
-       "redirectedfrom": "(Punkńyńto ze $1)",
-       "redirectpagesub": "Zajta przekerowujůnco",
-       "redirectto": "Przekerowańy do:",
-       "lastmodifiedat": "Ta zajta bōła ôstatnio edytowanŏ $2, $1.",
+       "otherlanguages": "We inkszych jynzykach",
+       "redirectedfrom": "(Pōnkniyntŏ ze $1)",
+       "redirectpagesub": "Strōna przekerowaniŏ",
+       "redirectto": "Przekerowanie do:",
+       "lastmodifiedat": "Ta strōna była ôstatni rŏz edytowanŏ $2, $1.",
        "viewcount": "W ta zajta filowano {{PLURAL:$1|tylko roz|$1 rozůw}}.",
        "protectedpage": "Zajta zawarto",
-       "jumpto": "dź do:",
+       "jumpto": "Idź do:",
        "jumptonavigation": "nawigacyjŏ",
        "jumptosearch": "szukej",
        "view-pool-error": "Felerńe, syrwyry sům przećůnżone.\n\n$1",
        "aboutpage": "Project:Ô serwisie",
        "copyright": "Tekst udostympńany na licencyji $1, eli inakszyj ńy podano.",
        "copyrightpage": "{{ns:project}}:Autorske prawa",
-       "currentevents": "Aktualne przitrefjyńa",
-       "currentevents-url": "Project:Aktualne przitrefjyńa",
+       "currentevents": "Terŏźne wydarzynia",
+       "currentevents-url": "Project:Terŏźne wydarzynia",
        "disclaimers": "Prawne informacyje",
        "disclaimerpage": "Project:Prawne informacyje",
-       "edithelp": "Půmoc we půmjyÅ\84\84y",
+       "edithelp": "PÅ\8dmoc we edycyji",
        "mainpage": "Przodniŏ zajta",
-       "mainpage-description": "Przodńo zajta",
+       "mainpage-description": "Przodniŏ strōna",
        "policy-url": "Project:Prawidła",
-       "portal": "Portal używoczůw",
-       "portal-url": "Project:Portal używoczůw",
-       "privacy": "Prawidła chrōniyniŏ prywŏtności",
-       "privacypage": "Project:Prawidła chrōniyniŏ prywŏtności",
+       "portal": "Portal społeczności",
+       "portal-url": "Project:Portal społeczności",
+       "privacy": "Prawidła chrōniyniŏ prywatności",
+       "privacypage": "Project:Prawidła chrōniyniŏ prywatności",
        "badaccess": "Felerne uprawńyńo",
        "badaccess-group0": "Ńy mosz uprawńyń coby wykůnać ta uoperacyjo.",
        "badaccess-groups": "Ta uoperacyjo mogům wykůnać ino użytkownicy ze keryjś z {{PLURAL:$2|grupy|grup}}: $1.",
        "versionrequiredtext": "Wymagano jest MediaWiki we wersji $1 coby skorzistać zr tyj zajty. Uobocz [[Special:Version]]",
        "ok": "OK",
        "retrievedfrom": "Zdrzōdło \"$1\"",
-       "youhavenewmessages": "Mosz $1 ($2).",
-       "youhavenewmessagesfromusers": "Mosz $1 uod {{PLURAL:$3|inszygo używocza|$3 używoczy}} ($2).",
+       "youhavenewmessages": "Mŏsz $1 ($2).",
+       "youhavenewmessagesfromusers": "Mŏsz $1 ôd {{PLURAL:$3|inszego używŏcza|$3 używŏczy}} ($2).",
        "youhavenewmessagesmanyusers": "Mosz $1 uod wjelu używoczy ($2).",
-       "newmessageslinkplural": "{{PLURAL:$1|jedno nowina|999=nowiny}}",
-       "newmessagesdifflinkplural": "{{PLURAL:$1|ôstatniŏ pōmiana|999=ôstatnie pōmiany}}",
+       "newmessageslinkplural": "{{PLURAL:$1|jedna nowina|999=nowiny}}",
+       "newmessagesdifflinkplural": "{{PLURAL:$1|ôstatniŏ zmiana|999=ôstatnie zmiany}}",
        "youhavenewmessagesmulti": "Mosz nowe powjadůmjyńa: $1",
        "editsection": "edytuj",
        "editold": "edytuj",
-       "viewsourceold": "pokoż zdrzůdło",
+       "viewsourceold": "pokŏż zdrzōdło",
        "editlink": "edytuj",
-       "viewsourcelink": "zdrzůdłowy tekst",
-       "editsectionhint": "Edytuj tajlã: $1",
-       "toc": "Treść",
+       "viewsourcelink": "pokŏż zdrzōdło",
+       "editsectionhint": "Edytuj sekcyjõ: $1",
+       "toc": "Wykŏz treści",
        "showtoc": "uobejrzij",
        "hidetoc": "schrůń",
        "collapsible-collapse": "Zwjyń",
        "site-atom-feed": "Kanoł Atom {{GRAMMAR:D.lp|$1}}",
        "page-rss-feed": "Kanoł RSS \"$1\"",
        "page-atom-feed": "Kanoł Atom \"$1\"",
-       "red-link-title": "$1 (niy ma zajty)",
+       "red-link-title": "$1 (niy ma strōny)",
        "sort-descending": "Sortuj pomńijszajůnco",
        "sort-ascending": "Sortuj rosnůnco",
-       "nstab-main": "Zajta",
-       "nstab-user": "{{GENDER:{{BASEPAGENAME}}|Zajta używocza|Zajta używoczki}}",
+       "nstab-main": "Strōna",
+       "nstab-user": "{{GENDER:{{BASEPAGENAME}}|Strōna ôd używŏcza|Strōna ôd używŏczki}}",
        "nstab-media": "Pliki",
-       "nstab-special": "Ekstra zajta",
-       "nstab-project": "Zajta projektu",
-       "nstab-image": "Plik",
-       "nstab-mediawiki": "Komuńikat",
+       "nstab-special": "Specjalnŏ strōna",
+       "nstab-project": "Strōna projektu",
+       "nstab-image": "Zbiōr",
+       "nstab-mediawiki": "Kōmunikat",
        "nstab-template": "Muster",
        "nstab-help": "Zajta půmocy",
-       "nstab-category": "Kategoryjo",
-       "mainpage-nstab": "Przodniŏ zajta",
+       "nstab-category": "Kategoryjŏ",
+       "mainpage-nstab": "Przodniŏ strōna",
        "nosuchaction": "Ńy mo takij uoperacyji",
        "nosuchactiontext": "Uoprogramowańy ńy rozpoznowo uoperacyji takij kej podano w URL.",
-       "nosuchspecialpage": "Ńy mo takij szpecyjolnyj zajty",
-       "nospecialpagetext": "<strong>Uoprogramowańy ńy rozpoznowo takij szpecyjalnyj zajty.</strong>\n\nLista szpecyjalnych zajtůw znojdźesz na [[Special:SpecialPages|{{int:specialpages}}]].",
+       "nosuchspecialpage": "Niy ma takij specjalnyj strōny",
+       "nospecialpagetext": "<strong>Ôbranŏ była niynŏleżnŏ specjalnŏ strōna.</strong>\n\nListã specjalnych strōn idzie znojś na [[Special:SpecialPages|{{int:specialpages}}]].",
        "error": "Feler",
        "databaseerror": "Feler bazy danych",
        "databaseerror-text": "Pojawjůł śe feler przi wysyłańu zapytańa do bazy danych. Mogebność je, aże je to feler we uoprogramowańu.",
        "cannotdelete-title": "Ńy idźie wyćepać zajty \"$1\".",
        "delete-hook-aborted": "Wyćepywańe sztopńynte bez hak. Przyczyna ńyuokreślůno.",
        "no-null-revision": "Ńy je mogebne stworzyńe zerowyj wersyji zajty \"$1\"",
-       "badtitle": "Felerny titel",
-       "badtitletext": "Podano felerny titel zajty. Prawdopodańy sům w ńim znoki, kerych ńy wolno używać we titlach abo je pusty.",
+       "badtitle": "Niynŏleżny tytuł",
+       "badtitletext": "Podany tytuł strōny to je niynŏleżny, prōzny, abo źle zalinkowany tytuł metajynzykowy abo interwiki.\nMoże w nim być jedyn abo wiyncyj znakōw, co niy mogōm być używane we tytułach.",
        "perfcached": "To co sam je naszkryflane, to ino kopja ze pamjyńći podryncznyj a może ńy być aktualne. Nojwjyncyj {{PLURAL:$1|jydyn wynik je|$1 wyniki sům}} we tyj pamjyńći.",
        "perfcachedts": "To co sam je naszkryflane, to ino kopja s pamjyńći podryncznyj a bůło uaktualńůne $1. Nojwjyncyj {{PLURAL:$4|jeden wynik je|$4 wyniki sům}} dostympne.",
        "querypage-no-updates": "Uaktualńyńo lo tyj zajty sům terozki zawarte. Dane, kere sam sům, ńy zostouy uodśwjyżůne.",
-       "viewsource": "Zdrzůdłowy tekst",
-       "viewsource-title": "Uobocz zdrzůdło lo $1",
+       "viewsource": "ZdrzÅ\8ddłowy tekst",
+       "viewsource-title": "Pokŏż zdrzōdło $1",
        "actionthrottled": "Akcyjo wstrzimano",
        "actionthrottledtext": "Mechańizm uobrůny przed spamym uograńiczo liczba wykonań tyj czynnośći we jednostce czasu. Průbowołżeś go uocygańić. Prosza, sprůbuj na nowo za pora minut.",
        "protectedpagetext": "Ta zajta je zawarto przed sprowjańym.",
-       "viewsourcetext": "We tekst zdrzůduowy tyj zajty możno dali filować, idźe go tyż kopjyrować.",
+       "viewsourcetext": "Możesz ôglōndać i kopiować zdrzōdło tyj strōny.",
        "viewyourtext": "We tekst zdrzůduowy tyj zajty możno dali filować, idźe go tyż kopjować.",
        "protectedinterface": "Na tyj zajće znojduje śe tekst interfejsu uoprogramowańo, bestůż uůna je zawarto uod sprowjańo. Coby doćepnůńć abo sprowjić tůmaczyńa wszyskich serwerůw, użyj [https://translatewiki.net/ translatewiki.net], průjyktu lokalizacyji MediaWiki.",
        "editinginterface": "''''Dej pozůr:''' Sprowjosz zajta, na keryj je tekst interfejsu uoprogramowańo. Pomjyńyńa na tyj zajće zmjyńům wyglůnd interfejsu lo inkszych użytkowńikůw. Coby doćepnůńć abo sprowjić tůmaczyńa, użyj [https://translatewiki.net/wiki/Main_Page?setlang=szl translatewiki.net].",
        "welcomeuser": "Witej, $1",
        "welcomecreation-msg": "Uotwarli my sam lo Ćebje kůnto.\nPamjyntej coby posztalować [[Special:Preferences|preferencyji]]",
        "yourname": "Mjano użytkowńika:",
-       "userlogin-yourname": "Mjano używocza",
-       "userlogin-yourname-ph": "Wkludź swoje miano używacza",
+       "userlogin-yourname": "Miano używŏcza",
+       "userlogin-yourname-ph": "Wkludź swoje miano używŏcza",
        "createacct-another-username-ph": "Wszkryflej mjano użytkowńika",
        "yourpassword": "Hasło:",
        "userlogin-yourpassword": "Hasło",
        "userlogin-yourpassword-ph": "Wkludź swoje hasło",
        "createacct-yourpassword-ph": "Wkludź hasło",
        "yourpasswordagain": "Naszkryflej ausdruk zaś",
-       "createacct-yourpasswordagain": "Potwjyrdź hasło",
+       "createacct-yourpasswordagain": "Potwiyrdź hasło",
        "createacct-yourpasswordagain-ph": "Wkludź hasło jeszcze rŏz",
-       "userlogin-remembermypassword": "Ńy wylogůwywuj mje",
+       "userlogin-remembermypassword": "Niy ôdlogowuj mie",
        "userlogin-signwithsecure": "Użyj bezpjecznygo połůnczyńa",
        "yourdomainname": "Twoja domyna",
        "password-change-forbidden": "Ńy można půmjyńać haseł na tyj wiki.",
        "externaldberror": "Je jaki feler we zewnyntrznyj baźe autentyfikacyjnyj, abo ńy mosz uprawńyń potrzebnych do aktualizacyji zewnyntrznego kůnta.",
-       "login": "Zaloguj śe",
+       "login": "Wloguj sie",
        "nav-login-createaccount": "Logowańy / Tworzyńy kůnta",
        "logout": "Wyloguj",
        "userlogout": "Uodloguj śe",
        "notloggedin": "Ńy jeżeś zalogowany",
-       "userlogin-noaccount": "Ńy mosz kůnta?",
-       "userlogin-joinproject": "Doćep śe do {{SITENAME}}",
-       "createaccount": "Twůrz nowe kůnto",
-       "userlogin-resetpassword-link": "Ńy pamjyntosz hasła?",
-       "userlogin-helplink2": "Hilfa przi logůwańu",
+       "userlogin-noaccount": "Niy mŏsz kōnta?",
+       "userlogin-joinproject": "Dołōncz do {{GRAMMAR:D.lp|{{SITENAME}}}}",
+       "createaccount": "TwÅ\8drz nowe kÅ\8dnto",
+       "userlogin-resetpassword-link": "Niy pamiyntŏsz hasła?",
+       "userlogin-helplink2": "Pōmoc przi logowaniu",
        "userlogin-loggedin": "Zalogowano kej {{GENDER:$1|$1}}. Użyj formulara půńiżyj, coby zalogować śe kej inkszy używocz.",
        "userlogin-createanother": "Twůrz inksze kůnto",
        "createacct-emailrequired": "E-brif",
-       "createacct-emailoptional": "E-brif (uopcjůnalne)",
-       "createacct-email-ph": "Wkludź swojã adresã e-brifa",
+       "createacct-emailoptional": "Adresa e-mail (niymusowo)",
+       "createacct-email-ph": "Wkludź swojã adresã e-mail",
        "createacct-another-email-ph": "Nastow e-brif",
        "createaccountmail": "Użyj chwilowygo hasła losowo genyrowanygo a wyślij je na wrychtowany adres e-brifa.",
        "createacct-realname": "Prawdźiwe imje a nazwisko (uopcjůnalńe)",
        "createacct-reason": "Powůd:",
        "createacct-reason-ph": "Pojakymu tworzisz nowe kůnta",
-       "createacct-submit": "Twůrz kůnto",
+       "createacct-submit": "Stwōrz kōnto",
        "createacct-another-submit": "Twůrz inksze kůnto",
-       "createacct-benefit-heading": "{{grammar:B.lp|{{SITENAME}}}} tworzům perzůny take kej Ty.",
+       "createacct-benefit-heading": "{{grammar:B.lp|{{SITENAME}}}} tworzÅ\8dm ludzie jak Ty.",
        "createacct-benefit-body1": "{{PLURAL:$1|edycyjo|edycyje|edycyji}}",
-       "createacct-benefit-body2": "{{PLURAL:$1|zajta|zajty|zajt}}",
-       "createacct-benefit-body3": "{{PLURAL:$1|używocz|używoczůw}} we uostatńim czaśe",
+       "createacct-benefit-body2": "{{PLURAL:$1|strōna|strōny|strōn}}",
+       "createacct-benefit-body3": "{{PLURAL:$1|nojnowszy używŏcz|nojnowsi używŏcze|nojnowszych używŏczōw}}",
        "badretype": "Hasła kere żeś naszkryfloł ńy zgodzajům śe jydne ze drugim.",
        "userexists": "Mjano użytkowńika, kere żeś wybroł, je zajynte. Wybjer, prosza, inksze mjano.",
        "loginerror": "Feler przi logowańu",
        "loginlanguagelabel": "Godka: $1",
        "suspicious-userlogout": "Polecyńe wylogowańo uostoło uodćepńynte skiż tygo co wyglůnda, aże uostoło posłane bez uszkodzůna przeglůndarka abo buforujůncy serwer proxy.",
        "createacct-another-realname-tip": "Wszkryflańy twojigo mjana a nazwiska ńy je końyczne.\nKej bydźesz chćoł je podoć, bydům użyte, coby dokůmyntowoć Twoje autorstwo.",
-       "pt-login": "Zaloguj śe",
-       "pt-login-button": "Zaloguj śe",
+       "pt-login": "Wloguj sie",
+       "pt-login-button": "Wloguj sie",
        "pt-createaccount": "Twōrz nowe kōnto",
-       "pt-userlogout": "Uodloguj śe",
+       "pt-userlogout": "Ôdloguj sie",
        "php-mail-error-unknown": "Ńyznany feler we funkcyji mail()",
        "user-mail-no-addy": "Průba posłańo e‐brifa bez adresu uodbjorcy",
        "user-mail-no-body": "Bůła průba posłańo e-brifa uo blank abo krůtkim tekśće.",
        "resetpass-wrong-oldpass": "Felerne tymczasowe abo aktualne hasło.\nMożliwe co właśńy zmjyńiłżeś swoje hasło abo poprosiłżeś uo nowe tymczasowe hasło.",
        "resetpass-temp-password": "Tymczasowe hasło:",
        "resetpass-abort-generic": "Půmjyńańe hasła uostoła zatrzimane bez rozszyrzyńe.",
-       "passwordreset": "Wyczyść hasło",
+       "passwordreset": "Wysnŏż hasło",
        "passwordreset-disabled": "No tyj wiki zamkńynto resytowańy hasył.",
        "passwordreset-username": "Miano ôd używŏcza:",
        "passwordreset-domain": "Domyna:",
        "passwordreset-emailtext-ip": "Ftoś (cheba Ty, s IP $1)\npado, aże chce informacyji lo konta do {{GRAMMAR:MS.lp{{SITENAME}}}} ($4).\nZe tym ausdrukym sům powjůnzane kůnta:\n$2\n\n{{PLURAL:$3|Tymczasowygo hasła|Tymczasowych hasył}} możno użyć we {{PLURAL:$5|jedyn dźyń|$5 dńi}}.\n\nJak chćołżeś gynał to zrobjyć, to zaloguj śe terozki a podej swoje hasło.\n\nJak ftoś inkszy chćoł nowe hasło abo jak Ci śe przipůmńoło stare a ńy chcysz nowygo, to zignoruj to a używej starygo hasła.",
        "passwordreset-emailelement": "Mjano sprowjorza: \n$1\n\nTymczasowe hasło: \n$2",
        "passwordreset-emailsentemail": "E-brif posłany.",
-       "changeemail": "Pomjyno ausdruka e-mail",
+       "changeemail": "Zmiyń abo skasuj adresã e-mail",
        "changeemail-header": "Pomjyno ausduku e-mail",
        "changeemail-no-info": "Muśisz być zalogowany, coby uzyskać bezpostrzedńi dostymp do tyj zajty.",
        "changeemail-oldemail": "Uobecny ausdruk:",
        "resettokens": "Resetuj tokeny",
        "bold_sample": "Ruby tekst",
        "bold_tip": "Ruby tekst",
-       "italic_sample": "Przechylůny tekst",
-       "italic_tip": "Przechylůny tekst",
-       "link_sample": "Titel linka",
+       "italic_sample": "Kursywa",
+       "italic_tip": "Kursywa",
+       "link_sample": "Tytuł linku",
        "link_tip": "Wewnytrzny link",
-       "extlink_sample": "http://www.example.com titla linku",
-       "extlink_tip": "Eksterny link (pamjyntej uo prefikśe http:// )",
-       "headline_sample": "Tekst iberszryftu",
-       "headline_tip": "Iberszryft 2. stůpńo",
-       "nowiki_sample": "Wćepej sam tekst bez formatowańo",
-       "nowiki_tip": "Zignoruj formatowańy wiki",
-       "image_tip": "Plik uosadzůny we zajće",
-       "media_tip": "Link do plika",
-       "sig_tip": "Twojo szrajbka ze datum a czasym",
-       "hr_tip": "Poźůmo lińijo (używej mjyrńy)",
-       "summary": "Popis půmjyńań:",
+       "extlink_sample": "http://www.example.com tytuł linku",
+       "extlink_tip": "Zewnyntrzny link (pamiyntej ô prefiksie http:// )",
+       "headline_sample": "Tekst nŏgōwka",
+       "headline_tip": "Nŏgōwek 2. poziōmu",
+       "nowiki_sample": "Wraź sam niysformatowany tekst",
+       "nowiki_tip": "Ignoruj formatowanie wiki",
+       "image_tip": "Wrażōny zbiōr",
+       "media_tip": "Link do zbioru",
+       "sig_tip": "Twōj podpis ze datōm i czasym",
+       "hr_tip": "Poziōmŏ linijŏ (niy nadużywej)",
+       "summary": "Ôpis zmian:",
        "subject": "Tyjma/iberszryft:",
-       "minoredit": "To je niywielgŏ pōmiana",
-       "watchthis": "Dej pozůr",
-       "savearticle": "Spamjyntej",
-       "preview": "Uobźyrańy",
-       "showpreview": "Uobźyrej",
-       "showdiff": "Pozdrzyj na půmjyńańy",
-       "anoneditwarning": "<strong>Dej pozůr:</strong> Ńy jeżeś zalogůwany. Twůj IP ausdruk bydźe bez wszyjskich widoczny eli zrobisz egal jako půmjana. Eli <strong>[$1 zalogůjesz śe]</strong> abo <strong>[$2 stworzisz kůnto]</strong>, Twoje půmjany bydům przipisane do kůnta, wroz ze inkszymi korzyśćůma.",
+       "minoredit": "To je małŏ zmiana",
+       "watchthis": "Ôbserwuj tã strōnã",
+       "savearticle": "Spamiyntej",
+       "preview": "Podglōnd",
+       "showpreview": "Pokŏż podglōnd",
+       "showdiff": "Pokŏż zmiany",
+       "anoneditwarning": "<strong>Pozōr:</strong> Niy je żeś wlogowany(ŏ). Jak zrobisz jakeś zmiany, to Twoja adresa IP bydzie publicznie widać. Jeźli <strong>[$1 sie wlogujesz]</strong> abo <strong>[$2 stworzisz kōnto]</strong>, to Twoje zmiany bydōm przipisane do kōnta społym ze inkszymi profitami.",
        "anonpreviewwarning": "Ńy jeżeś zalogowany. Twój IP ausdruk uostańy spamjyntany, eli ty bydźesz sprowjać zajte.",
        "missingsummary": "'''Pozůr:''' Ńy wprowadźůł żeś uopisu pomjyńań. Kej go ńy chcesz wprowadzać, naćiś knefel Spamjyntej jeszcze roz.",
        "missingcommenttext": "Wćepej kůmyntorz půńiżyj.",
        "summary-preview": "Podglůnd uopisu:",
        "subject-preview": "Podglůnd tyjmy/nagłůwka:",
        "blockedtitle": "Użytkowńik je zawarty uod sprowjyń",
-       "blockedtext": "'''Twoje kůnto abo IP ausdruk sům zawarte.'''\n\nUo zawarću zdecydowoł $1. Pado, aże skuli: ''$2''.\n\n* Zawarte uod: $8\n* Uodymkńe śe: $6\n* Zawarće skiż: $7\n\nCoby wyjaśńić sprawa zawarćo, naszkryflej do $1 abo inkszygo [[{{MediaWiki:Grouppage-sysop}}|admińistratora]].\nŃy możesz posłać e-brifa bez \"poślij e-brifa tymu użytkowńikowi\", jak żeś ńy podoł dobrygo ausdruku e-brifa we [[Special:Preferences|preferencyjach kůnta]], abo jak e-brify mosz tyż zawarte. Terozki mosz ausdruk IP $3 a nůmera zawarćo to #$5. Proszymy podać jedyn abo uoba jak chcysz połosprawjać uo zawarću.",
+       "blockedtext": "<strong>Twoje kōnto abo adresa IP sōm zablokowane.</strong>\n\nBlokada była nałożōnŏ ôd $1.\nPodany powōd to: <em>$2</em>.\n\n* Poczōntek blokady: $8\n* Kōniec blokady: $6\n* Zablokowany używŏcz: $7\n\nŻeby wyklarować prziczyny blokady, możesz sie skōntaktować ze $1 abo inkszym [[{{MediaWiki:Grouppage-sysop}}|administratorym]].\nNiy możesz użyć funkcyje „{{int:emailuser}}”, jeźli niy mŏsz nŏleżnyj adresy e-mail we swojich [[Special:Preferences|preferyncyjach]] abo jeźli takŏ możliwość była Ci ôdkŏzanŏ.\nTwoja terŏźnŏ adresa IP to $3, a numer idyntyfikacyjny blokady to #$5.\nProszymy ô podanie ôbōch tych informacyji przi klarowaniu blokady.",
        "autoblockedtext": "Tyn adres IP zostou zawarty automatyčńy, gdyž kořisto s ńygo inkšy užytkowńik, zawarty uod sprowjyń bez administratora $1.\nPowůd zawarćo:\n\n:''$2''\n\n* Počůntek zawarćo: $8\n* Zawarće wygaso: $6\n* Zawarće je skiž: $7\n\n* Zawarte uod: $8 * Uodymkńe śe: $6 * Zawarće skiż: $7 Coby wyjaśńić sprawa zawarćo, naszkryflej do $1 abo inkszygo [[{{MediaWiki:Grouppage-sysop}}|admińistratora]]. Ńy możesz posłać e-brifa bez \"poślij e-brifa tymu użytkowńikowi\", jak żeś ńy podoł dobrygo ausdruku e-brifa we [[Special:Preferences|preferencyjach kůnta]], abo jak e-brify mosz tyż zawarte. Terozki mosz ausdruk IP $3 a nůmera zawarćo to #$5. Proszymy podać jedyn abo uoba jak chcysz połosprawjać uo zawarću.",
        "blockednoreason": "ńy podano skuli czygo",
        "whitelistedittext": "Muśisz $1 coby můc sprowjać artikle.",
        "nosuchsectiontitle": "Ńy mo takij tajli",
        "nosuchsectiontext": "Průbowołżeś sprowjać tajla kero ńy istńeje.",
        "loginreqtitle": "Muśisz śe zalogować",
-       "loginreqlink": "zaloguj śe",
+       "loginreqlink": "Wloguj sie",
        "loginreqpagetext": "Muśisz $1 coby můc przeglůndać inksze zajty.",
        "accmailtitle": "Hasło posłane.",
        "accmailtext": "Cufalńe hasło lo [[User talk:$1|$1]] uostoło posłane do $2. Hasło lo tygo nowygo kůnta po zalogowańu je mogebność pomjyńić na zajće ''[[Special:ChangePassword|pomjyńańe hasła]]''.",
        "newarticle": "(Nowy)",
-       "newarticletext": "Niy ma artikla ze takim titlym. Eli chcesz go sprŏwić, napisz niżyj jego tekst (wiyncyj informacyji znojdziesz [$1 na zajcie pōmocy]). Eli jeżeś sam felernie, naciś ino knefel \"Nazŏd\" we swojij przeziyrŏczce.",
-       "anontalkpagetext": "---- ''To je zajta godki lo anůnimowych używoczy  - takich, kerzi ńy majům jeszcze swojigo kůnta abo ńy chcům go terozki używać.\nBy jejich idyntyfikować, używomy numerůw IP.\nEli jeżeś anůnimowym używoczym a wydowo Ći śe, aże zamjyszczůne sam kůmyntorze ńy sům skjyrowane do Ćebje, [[Special:CreateAccount|utwůrz kůnto]] abo [[Special:UserLogin|zaloguj śe]] - beztůż uńikńesz potym podobnych ńyporozumjyń.''",
-       "noarticletext": "Niy mōmy zajty ze takim titlym. Możesz [{{fullurl:{{FULLPAGENAME}}|action=edit}} wciepać artikel {{FULLPAGENAME}}] abo [[Special:Search/{{PAGENAME}}|szukać {{PAGENAME}} we inkszych]].",
-       "noarticletext-nopermission": "Ta zajta terozki je pusto.\nMogesz [[Special:Search/{{PAGENAME}}|wysznupać ta titla]] we treśćach inkszych zajtůw, abo <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} przesznupać powjůnzane rejery]</span>, nale ńy mosz uprowńyń coby ta zajta wćepać",
+       "newarticletext": "Prōbujesz ôtworzić link do strōny, co jeszcze niy istniyje.\nŻeby stworzić strōnã, weź wkludzać we polu niżyj (wejzdrzij na [$1 strōnã pōmocy]). Jeźliś je sam bez cufal, to kliknij knefel <strong>nazŏd</strong> we przeglōndarce.",
+       "anontalkpagetext": "----\n<em>To je strōna dyskusyje anōnimowego używŏcza – takigo, co niy mŏ jeszcze swojigo kōnta abo niy chce go terŏz używać.</em>\nŻeby go idyntyfikować, używōmy adresōw IP.\nAle adresa IP może być używanŏ ôd wielu używŏczōw.\nJeźli je żeś anōnimowy używŏcz i uwŏżŏsz, iże wkludzōne sam kōmyntŏrze niy sōm do Ciebie, to [[Special:CreateAccount|stwōrz kōnto]] abo [[Special:UserLogin|wloguj sie]], żeby żŏdyn Cie niy mylōł z inkszymi anōnimowymi używŏczami.",
+       "noarticletext": "Niy ma terŏz żŏdnego tekstu.\nMożesz [[Special:Search/{{PAGENAME}}|szukać tego tytułu na inkszych strōnach]],\n<span class=\"plainlinks\">[{{fullurl:{{#special:Log}}|page={{urlencode:{{FULLPAGENAMEE}}}}}} przeszukać regest] \nabo [{{fullurl:{{FULLPAGENAME}}|action=edit}} stworzić tã strōnã]</span>.",
+       "noarticletext-nopermission": "Ta strōna je terŏz prōznŏ.\nMożesz [[Special:Search/{{PAGENAME}}|szukać tego tytułu]] we treściach inkszych strōn abo <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} przeszukać powiōnzane regesty]</span>, ale niy mŏsz praw do stworzyniŏ tyj strōny.",
        "userpage-userdoesnotexist": "Użytkowńik \"<nowiki>$1</nowiki>\" ńy je zarejesztrowany. Sprowdź eli na pewno chćołżeś stworzyć/pomjynić gynał ta zajta.",
        "userpage-userdoesnotexist-view": "Kōnto używŏcza''$1'' niy je zaregistrowane.",
        "blocked-notice-logextract": "{{GENDER:$1|Tyn sprowjorz|Ta sprowjorka}} mo zawrzite sprowjyńa.",
-       "clearyourcache": "'''Dej pozůr:''' Coby uobejrzeć pomjyńańo pů naszkryflańu nowych sztalowań poleć przeglůndorce wyczyśćić zawartość pamjyńći podryncznyj (cache). '''Mozilla / Firefox / Safari:''' przitrzimej ''Shift'' klikajůnc na ''Uodśwjyž'' abo wciś ''Ctrl-Shift-R'' (''Cmd-Shift-R'' na Macu), '''IE :''' przitrzimej ''Ctrl'' klikajůnc na ''Uodśwjyž'' abo wciś ''Ctrl-F5''; '''Konqueror:''': kliknij knefel ''Uodśwjyž'' abo wciś ''F5''; użytkowńicy '''Opery''' mogům być zmuszeńi coby cołkym wyczyśćić jejich pamjyńć podrynczno we menu ''Werkcojgi→Preferencyje''.; '''Internet Explorer:''' trzim ''Ctrl'' a wćiś ''Uodśwjyż'', abo wćiś ''Ctrl-F5''.",
+       "clearyourcache": "<strong>Pozōr:</strong> żeby ôbejzdrzeć zmiany po spamiyntaniu, może być potrzebne wysnŏżynie pamiyńci podryncznyj przeglōndarki.\n* <strong>Firefox / Safari:</strong> Przitrzim <em>Shift</em> przi klikaniu <em>Ôdświyż terŏźnõ strōnã</em>, abo naciś knefle <em>Ctrl+F5</em> abo <em>Ctrl+R</em> (<em>⌘-R</em> na kōmputrze Mac)\n* <strong>Google Chrome:</strong> Naciś <em>Ctrl-Shift-R</em> (<em>⌘-Shift-R</em> na kōmputrze Mac)\n* <strong>Internet Explorer:</strong> Przitrzim <em>Ctrl</em> przi klikaniu <em>Ôdświyż</em>, abo naciś knefle <em>Ctrl+F5</em>\n* <strong>Opera:</strong> Przejdź do <em>Myni → Sztelōnki</em> (<em>Opera → Preferyncyje</em> w Mac), a potym <em>Prywatność i bezpieczyństwo → Wysnŏż dane przeglōndaniŏ → Ôprōznij pamiyńć podryncznõ</em>.",
        "usercssyoucanpreview": "!'''Podpowjydź:''' Użyj knefla \"Podglůnd\", coby przetestować Twůj nowy arkusz stylůw CSS abo kod JavaScript przed jego zaszrajbowańym.",
        "userjsyoucanpreview": "!'''Podpowjydź:''' Użyj knefla \"Podglůnd\", coby przetestować Twůj nowy arkusz stylůw CSS abo kod JavaScript przed jego zaszrajbowańym.",
        "usercsspreview": "'''Pamjyntej, aże to je no raźe ino podglůnd Twojego arkusza stylůw CSS.'''\n'''Ńic jeszcze ńy zostoło naszkryflane!'''",
        "userinvalidconfigtitle": "<strong>Pozůr:</strong> Ńy mo skůrki uo mjańe \"$1\". Pamjyntej, aże zajty użytkowńika zawjyrajůnce CSS, JSON i JavaScript powinny zaczynać śe małům buchsztabům, lb. {{ns:user}}:Foo/vector.css.",
        "updated": "(Pomjyńano)",
        "note": "'''Pozůr:'''",
-       "previewnote": "'''To je ino podglůnd - artikel jeszcze ńy je spamjyntany!'''",
-       "continue-editing": "Pōdź do placu edycyje",
+       "previewnote": "<strong>Pamiyntej, iże to je ino podglōnd.</strong>\nZmiany jeszcze niy sōm spamiyntane!",
+       "continue-editing": "Idź do pola edycyje",
        "previewconflict": "Wersyjo podglůndano uodnośi śe do tekstu ze pola edycyje na wjyrchu. Tak bydźe wyglůndać zajta jeli zdecydujesz śe jům naszkryflać.",
        "session_fail_preview": "'''Pozůr! Serwer ńy może przetworzić tyj edycyji, beztuż co dane sesyji uostoły utracůne.\nPoprůbuj jeszcze roz.\nEli to tyż ńy do podpory – [[Special:UserLogout|wyloguj śe]] a zaloguj jeszcze roz.'''",
        "session_fail_preview_html": "'''Przepraszomy! Serwer ńy może przetworzić tygo sprowjyńo skuli utraty danych ze sesyji.'''\n\n''Jako iże na {{GRAMMAR:MS.lp|{{SITENAME}}}} włůnczono zostoła uopcyjo \"raw HTML\", podglůnd zostoł schrůńony coby zabezpjeczyć przed atakami JavaScript.''\n\n'''Jeli to je prawiduowo průba sprowjańo, sprůbuj ješče roz. Kejby to ńy pomoguo - wylůguj śe a zalůguj na nowo.'''",
        "token_suffix_mismatch": "'''Twoje sprowjyńy zostoło uodćepane skuli tego, co twůj klijynt pomjyszoł znaki uod interpůnkcyji we żetůńe sprowjyń. Twoje sprowjyńy zostoło uodćepane coby zapobjec zńyszczyńu tekstu zajty. Take felery zdorzajům śe w roźe korzistańo ze felernych anůnimowych śećowych usłůg proxy.'''",
        "editing": "Edytujesz $1",
-       "creating": "Tworzyńy $1",
-       "editingsection": "Edytujesz $1 (sekcyjo)",
+       "creating": "Tworzynie $1",
+       "editingsection": "Edytujesz $1 (sekcyjŏ)",
        "editingcomment": "Sprowjosz \"$1\" (nowy kůmyntorz)",
        "editconflict": "Kůnflikt sprowjyń: $1",
        "explainconflict": "Ftoś zdůnżůł wćepać swoja wersyjo artikla ńim żeś naszkryflou sprowjyńy.\nWe polu edycyji na wjyrchu mosz tekst zajty aktuelńy naszkryflany we baźe danych.\nTwoje pomjyńańo sům we polu edycyji půńiżyj.\nBy wćepać swoje pomjyńańo muśisz pomjyńać tekst we polu na wjyrchu.\n'''Ino''' tekst ze pola na wjyrchu bydźe naszkryflany we baźe jak \nwciśńesz knefel \"$1\".",
        "semiprotectedpagewarning": "'''Pozůr:''' Ta zajta zostoła zawarto a ino zaregiszterowani użytkownicy mogům jům sprowjać.\nUostotńy wpis w rejerze je ńyżej.",
        "cascadeprotectedwarning": "'''Dej pozůr:''' Ta zajta zostoła zawarto a ino użytkowńicy ze uprawńyńami admińistratora mogům jům sprowjać. Zajta ta je podpjynto pod {{PLURAL:$1|nastympujůnco zajta, kero zostoła zawarto|nastympujůncych zajtach, kere zostouy zawarte}} ze załůnczonům uopcjům dźedźiczyńo:",
        "titleprotectedwarning": "'''Dej pozůr: Zajta uo tym titlu zostoła zawarto a ino [[Special:ListGroupRights|ńykerzi użytkowńicy]] mogům jům wćepać.'''\nUostatńy wpis z rejera je ńyżej.",
-       "templatesused": "{{PLURAL:$1|Muster|Mustry}} użyte na tyj zajće:",
-       "templatesusedpreview": "{{PLURAL:$1|Muster|Mustry}} użyte na tyj zajće:",
+       "templatesused": "{{PLURAL:$1|Muster użyty|Mustry użyte}} na tyj strōnie:",
+       "templatesusedpreview": "{{PLURAL:$1|Muster użyty|Mustry użyte}} na tyj podglōńdzie:",
        "templatesusedsection": "{{PLURAL:$1|Szablon|Szablůny}} użyte we tyj tajli:",
        "template-protected": "(chrōniōny)",
-       "template-semiprotected": "(tajlowo zawarte)",
-       "hiddencategories": "Ta zajta je {{PLURAL:$1|we jednyj schrůńunyj katygoryji|we $1 schrůńunych katygoryjach}}:",
+       "template-semiprotected": "(pōłzawarte)",
+       "hiddencategories": "Ta strōna je we {{PLURAL:$1|jednyj skrytyj kategoryji|$1 skrytych kategoryjach}}:",
        "nocreatetext": "Na {{GRAMMAR:MS.lp|{{SITENAME}}}} tworzyńy nowych zajtůw uograńiczůno.\nMoges sprowjać te co już sům, abo [[Special:UserLogin|zalogować śe, abo śa zaregisztrować]].",
        "nocreate-loggedin": "Ńy mosz uprowńyń do tworzyńo nowych zajtůw.",
        "sectioneditnotsupported-title": "Sprowjańy tajli ńymogebne",
        "sectioneditnotsupported-text": "Sprowjańy tajli ńymogebne na tyj zajće.",
-       "permissionserrors": "Felerne uprawńyńo",
+       "permissionserrors": "Feler uprawniyń",
        "permissionserrorstext": "Ńy mosz uprowńyń do takij akcyje {{PLURAL:$1|skuli tego, co:|bestůż, co:}}",
-       "permissionserrorstext-withaction": "Ńy mogesz $2, ze {{PLURAL:$1|takigo powodu|takich powodůw}}:",
-       "recreate-moveddeleted-warn": "'''Uostrzeżyńy: Wćepujesz ta samo zajta, kero bůła poprzedńo wyćepano.'''\n\nZastanůw śe, czy noleżoło by śe go sam wćepywać.\nRejer wyćepań tyj zajty je podany půńiżej, cobyś mjoł wygoda:",
-       "moveddeleted-notice": "Ta zajta zostoła wyćepńynto. Rejer wyćepań tyj zajty je pokozany půńiżyj.",
+       "permissionserrorstext-withaction": "Niy mŏsz przizwolyniŏ na $2, skuli {{PLURAL:$1|takigo powodu|takich powodōw}}:",
+       "recreate-moveddeleted-warn": "<strong>Pozōr: Prziwrŏcŏsz strōnã, co była przōdzij skasowanŏ.</strong>\n\nDej pozōr, czy prziwrōcynie tyj strōny je nŏleżne.\nRegesty kasowań i pōnkniyńć tyj strōny idzie ôbejzdrzeć niżyj.",
+       "moveddeleted-notice": "Ta strōna była skasowanŏ.\nRegest skasowań, zabezpieczyń i pōnkniyńć tyj strōny je pokŏzany niżyj.",
        "log-fulllog": "Ukoż rejer",
        "edit-hook-aborted": "Sprowjyńy sztopńynte skiż hoka.\nŃy je wjadůme pů jakymu.",
        "edit-gone-missing": "Ńy idźe zaktualizować zajty.\nZdowo śe, co zostoła wyćepano.",
        "postedit-confirmation-saved": "Spamjyntano twoje sprowjyńe.",
        "edit-already-exists": "Ńy idźe utworzić nowyj zajty.\nTako zajta już sam je.",
        "defaultmessagetext": "Tekst důmyślny",
+       "content-model-wikitext": "wikitekst",
        "expensive-parserfunction-warning": "Dej pozůr: ta zajta mo za dużo uodwouań do funkcyji parsera, kere mocno uobćůnżajům systym.\n\nPowinno być myńi jak $2 {{PLURAL:$2|wywołańy|wywołańo|wywołań}}, a terozki {{PLURAL:$1|je $1 wywołańy|sům $1 wywołańo|je $1 wywołań}}.",
        "expensive-parserfunction-category": "Zajty kere majům za dużo uodwołań do funkcyji parsera, kere mocno uobćůnżajům systym.",
        "post-expand-template-inclusion-warning": "Dej pozůr: Dokuplowane mustry sům moc wjelge.\nŃykere mustry ńy bydům dokuplowane.",
        "parser-template-loop-warning": "Wykryto muster zapyntlyńo: [[$1]]",
        "parser-template-recursion-depth-warning": "Przekroczůno limit głymbokośći rekurencyji mustru ($1)",
        "undo-success": "Sprowjyńy zostoło wycofane. Prosza pomjarkować ukozane půniżyj dyferencyje mjyndzy wersyjůma, coby zweryfikować jejich poprawność, potym zaś naszkryflać pomjyńańo coby zakończyć uoperacyjo.",
-       "undo-failure": "Edycyjŏ niy może być cofniyntŏ skuli ôstudy ze wersyjōma postrzednimi.",
+       "undo-failure": "Ta edycyjŏ niy może być cŏfniyntŏ skuli kōnfliktu ze wersyjami postrzednimi.",
        "undo-norev": "Sprowjyńo ńy idźe cofnůńć skuli tego, co ńy istńije abo uostoło wyćepane.",
        "undo-summary": "Wycůfańy wersyji $1 naszkryflanej bez [[Special:Contributions/$2|$2]] ([[User talk:$2|godka]])",
        "cantcreateaccount-text": "Tworzyńy kůnta s tygo adresu IP ('''$1''') uostoło zawarte bez użytkowńika [[User:$3|$3]].\n\nSkuli: ''$2''",
-       "viewpagelogs": "Uobocz rejery uoperacyji lo tyj zajty",
+       "viewpagelogs": "Ôbejzdrz regesty dlŏ tyj strōny",
        "nohistory": "Ta zajta ńy mo swojij historyje sprowjyń.",
        "currentrev": "Aktuelno wersyjo",
-       "currentrev-asof": "Aktuelno wersyjo na dźyń $1",
-       "revisionasof": "Wersyjo ze dńa $1",
-       "revision-info": "Wersyjo ze dńo $1 autorstwa {{GENDER:$6|$2}}$7",
-       "previousrevision": "← starszo wersyjo",
-       "nextrevision": "Nostympno wersyjo→",
-       "currentrevisionlink": "Aktualno wersyjo",
-       "cur": "akt.",
+       "currentrev-asof": "Teroźnŏ wersyjŏ na dziyń $1",
+       "revisionasof": "Wersyjŏ ze dnia $1",
+       "revision-info": "Wersyjo ze dnia $1 autorstwa {{GENDER:$6|$2}}$7",
+       "previousrevision": "← starszŏ wersyjŏ",
+       "nextrevision": "Nastympnŏ wersyjŏ →",
+       "currentrevisionlink": "Terŏźnŏ wersyjŏ",
+       "cur": "ter.",
        "next": "nastympno",
        "last": "poprz.",
        "page_first": "poczůnek",
        "page_last": "kůńec",
-       "histlegend": "Wybůr růżńic do porůwnańo: postow kropki we boksach a naćiś enter abo knefel na dole.<br />\nLegynda: (akt.) - růżńice s wersyjům bjeżůncům, (poprz.) - růżńice s wersyjům poprzedzajůncům, d - drobne zmjany",
-       "history-fieldset-title": "Przeglůndej gyszichta",
+       "histlegend": "Ôbranie rōżnic: Ôznŏcz szaltry przi wersyjach do porōwnaniŏ i wziś enter abo knefel na spodku.<br />\nLegynda: <strong>({{int:cur}})</strong> = rōżnica ze ôstatniōm wersyjōm, <strong>({{int:last}})</strong> = rōżnica ze poprzedniōm wersyjōm, <strong>{{int:minoreditletter}}</strong> = małŏ edycyjŏ.",
+       "history-fieldset-title": "Filtruj wersyje",
        "history-show-deleted": "Jyno wyćepane",
        "histfirst": "nojstarsze",
        "histlast": "nojnowsze",
        "historysize": "({{PLURAL:$1|1 bajt|$1 bajty|$1 bajtůw}})",
        "historyempty": "(blank)",
-       "history-feed-title": "Gyszichta wersyjůw",
-       "history-feed-description": "Historyjo wersyje tyj zajty wiki",
+       "history-feed-title": "Historyjŏ wersyji",
+       "history-feed-description": "Historyjo wersyji tyj strōny wiki",
        "history-feed-item-nocomment": "$1 uo $2",
        "history-feed-empty": "Wybrano zajta ńy istńije.\nMůgła uostać wyćepano abo przećepano pod inksze mjano.\nMożesz tyż [[Special:Search|sznupać]] za tům zajtům.",
        "rev-deleted-comment": "(kůmyntorz wyćepany)",
        "rev-deleted-event": "(szkryflańy wyćepane)",
        "rev-deleted-text-permission": "Wersyjo tyj zajty uostoua wyćepano a ńy je dostympna publičńy. Ščygůuy idźe znejść we [{{fullurl:{{#Special:Log}}/suppress|page={{PAGENAMEE}}}} rejeře wyćepań].",
        "rev-deleted-text-view": "Ta wersyjo zajty uostoua wyćepano a ńy je dostympna publičńy.\nAtoli kej admińistrator {{GRAMMAR:MS.lp|{{SITENAME}}}} možeš jům uobejřeć.\nPowody wyćepańo idźe znejść we [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} rejeře wyćepań]",
-       "rev-delundel": "ukoż/schrůń",
+       "rev-delundel": "pokŏż/skryj",
        "rev-showdeleted": "ukoż",
        "revisiondelete": "Wyćep/wćep nazod wersyje",
        "revdelete-nooldid-title": "Ńy wybrano wersyji",
        "mergehistory-comment": "Historyjo [[:$1]] skuplowano ze [[:$2]]: $3",
        "mergehistory-same-destination": "Zajta zdrzůdłowo a docelowo ńy mogům być te same.",
        "mergehistory-reason": "Kůmyntorz:",
-       "mergelog": "Skuplowane",
+       "mergelog": "Regest scalyń",
        "revertmerge": "Uodkupluj",
        "mergelogpagetext": "Půńiżyj je lista uostatńich kuplowań historyji půmjyńań zajtůw.",
-       "history-title": "Historyjŏ pōmian zajty \"$1\"",
-       "difference-title": "$1: Růżńice mjyndzy wersyjůma",
+       "history-title": "Historyjŏ wersyji strōny „$1”",
+       "difference-title": "$1: Porōwnanie wersyji",
        "difference-multipage": "(Porůwnańy zajt)",
-       "lineno": "Lińijo $1:",
-       "compareselectedversions": "zrůwnej uobrane wersyje",
+       "lineno": "Linijŏ $1:",
+       "compareselectedversions": "Porōwnej ôbrane wersyje",
        "showhideselectedversions": "Ukoż/ukryj uobrane wersyje",
-       "editundo": "uodćepej",
+       "editundo": "cŏfnij",
+       "diff-empty": "(Brak rōżnic)",
+       "diff-multi-sameuser": "({{PLURAL:$1|Niyma pokŏzanŏ jedna postrzedniŏ wersyjŏ|Niy sōm pokŏzane $1 postrzednie wersyje|Niy je pokŏzane $1 postrzednich wersyji}} ôd tego samego używŏcza)",
+       "diff-multi-otherusers": "({{PLURAL:$1|Niyma pokŏzanŏ jedna postrzedniŏ wersyjŏ|Niy sōm pokŏzane $1 postrzednie wersyje|Niy je pokŏzane $1 postrzednich wersyji}} ôd {{PLURAL:$2|jednego inkszego używŏcza|$2 inkszych używŏczōw}})",
        "diff-multi-manyusers": "(Ńy pokozano {{PLURAL:$1|jydnyj wersyji postrzedńij|$1 wersyji postrzedńich}}, sprowjanej bez {{PLURAL:$2|jydnygo sprowjorza|$2 sprowjorzow}} .)",
        "difference-missing-revision": "{{PLURAL:$2|Wersyjo|$2 wersyje|$2 wersyji}} #$1 zajty \"{{PAGENAME}}\" ńy {{PLURAL:$2|uostoła znaleźůno|uostoły znaleźůne|uostoło znaleźůnych}}. Zauobycz je to skiż starygo linky do wyćępanyj zajty. Powůd wyćepańa nojdźesz we [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} rejerze].",
-       "searchresults": "Efekty podszukōnkōw",
-       "searchresults-title": "Efekty podszukōnkōw dlŏ „$1”",
+       "searchresults": "Efekty szukaniŏ",
+       "searchresults-title": "Efekty szukaniŏ dlŏ „$1”",
        "titlematches": "Znolyźono we titlach:",
        "textmatches": "Znejdźono na zajtach:",
        "notextmatches": "Ńy znejdźono we tekście zajtůw",
-       "prevn": "poprzedńe {{PLURAL:$1|$1}}",
-       "nextn": "nostympne {{PLURAL:$1|$1}}",
-       "prevn-title": "{{PLURAL:$1|Poprzedńi|Poprzedńe}} $1 {{PLURAL:$1|wyńik|wyńiki|wyńikůw}}",
-       "nextn-title": "{{PLURAL:$1|Dalszy|Dalsze|Dalszych}} $1 {{PLURAL:$1|wyńik|wyńiki|wyńikůw}}",
-       "shown-title": "Ukoż $1 {{PLURAL:$1|wynik|wyniki|wynikůw}} lo zajta",
-       "viewprevnext": "Uobźyrej ($1 {{int:pipe-separator}} $2) ($3)",
-       "searchmenu-exists": "'''Ńy ma zajty uo mjańy \"[[:$1]]\" na tyj wiki'''",
-       "searchmenu-new": "<strong>Sprŏw zajtã „[[:$1]]” na tyj wiki!</strong> {{PLURAL:$2|0=|Ôbezdrzij tyż zajtã ze efektami podszukōnkōw.|Ôbezdrzij tyż efekty podszukōnkōw.}}",
-       "searchprofile-articles": "Zajty",
-       "searchprofile-images": "Multimedyja",
+       "prevn": "{{PLURAL:$1|poprzedni|poprzednie $1}}",
+       "nextn": "{{PLURAL:$1|nastympny|nastympne $1}}",
+       "prevn-title": "{{PLURAL:$1|Poprzedni|Poprzedie|Poprzednich}} $1 {{PLURAL:$1|wynik|wyniki|wynikōw}}",
+       "nextn-title": "{{PLURAL:$1|Dalszy|Dalsze|Dalszych}} $1 {{PLURAL:$1|wynik|wyniki|wynikōw}}",
+       "shown-title": "Pokŏż $1 {{PLURAL:$1|wynik|wyniki|wynikōw}} na strōnã",
+       "viewprevnext": "Pokŏż ($1 {{int:pipe-separator}} $2) ($3)",
+       "searchmenu-exists": "<strong>Na tyj wiki je strōna ze mianym „[[:$1]]”.</strong>",
+       "searchmenu-new": "<strong>Stwōrz strōnã „[[:$1]]” na tyj wiki!</strong> {{PLURAL:$2|0=|Ôbejzdrzij tyż strōnã ze wynikami szukaniŏ.|Ôbejzdrzij tyż wyniki szukaniŏ.}}",
+       "searchprofile-articles": "Strōny",
+       "searchprofile-images": "Multimedia",
        "searchprofile-everything": "Wszyjsko",
-       "searchprofile-advanced": "Rozszerzůne",
-       "searchprofile-articles-tooltip": "Podszukowaniy we przestrzyni mian $1",
-       "searchprofile-images-tooltip": "Szukej plikōw",
-       "searchprofile-everything-tooltip": "Podszukowaniy cołkij zawartości (a tyż zajtōw dyskusyje)",
-       "searchprofile-advanced-tooltip": "Podszukowaniy we ôbranych zortach mian",
-       "search-result-size": "$1 ({{PLURAL:$2|1 słowo|$2 słowa|$2 słůw}})",
-       "search-result-category-size": "{{PLURAL:$1|1 element|$1 elementy|$1 elementów}} ({{PLURAL:$2|1 kategoryjo|$2 kategoryje|$2 kategoryje}}, {{PLURAL:$3|1 uobrozek|$3 uobrozki|$3 uobrozkow}})",
-       "search-redirect": "(půnkńyńćy $1)",
-       "search-section": "(tajla $1)",
-       "search-suggest": "Myśloł żeś: $1 ?",
+       "searchprofile-advanced": "Rozszyrzōne",
+       "searchprofile-articles-tooltip": "Szukanie we $1",
+       "searchprofile-images-tooltip": "Szukej zbiorōw",
+       "searchprofile-everything-tooltip": "Szukanie we cołkij zawartości (społym ze strōnami dyskusyje)",
+       "searchprofile-advanced-tooltip": "Szukanie we ôbranych zortach mian",
+       "search-result-size": "$1 ({{PLURAL:$2|1 słowo|$2 słowa|$2 słōw}})",
+       "search-result-category-size": "{{PLURAL:$1|1 elymynt|$1 elymynta|$1 elymyntōw}} ({{PLURAL:$2|1 podkategoryjŏ|$2 podkategoryje|$2 podkategoryji}}, {{PLURAL:$3|1 zbiōr|$3 zbiory|$3 zbiorōw}})",
+       "search-redirect": "(pōnkniyńcie ze $1)",
+       "search-section": "(sekcyjŏ $1)",
+       "search-file-match": "(ôdpowiadŏ zawartości zbioru)",
+       "search-suggest": "Niy rozchodzi sie ô: $1",
        "search-interwiki-caption": "Śostrzane projekty",
        "search-interwiki-default": "$1 wyńiki:",
        "search-interwiki-more": "(wjyncyj)",
        "searchrelated": "podane",
        "searchall": "wszyjske",
        "showingresults": "To lista na keryj je {{PLURAL:$1|'''1''' wyńik|'''$1''' wyńikůw}}, sztartujůnc uod nůmery '''$2'''.",
-       "search-showingresults": "{{PLURAL:$4|Rezultat <strong>$1</strong> ze <strong>$3</strong>|Rezultaty <strong>$1 - $2</strong> ze <strong>$3</strong>}}",
-       "search-nonefound": "Å\83y mo wynikůw, kere uodpadajům kryterjům zapytaÅ\84o.",
+       "search-showingresults": "{{PLURAL:$4|Rezultat <strong>$1</strong> ze <strong>$3</strong>|Rezultaty <strong>$1  $2</strong> ze <strong>$3</strong>}}",
+       "search-nonefound": "Å»Å\8fdne wyniki niy Ã´dpowiadajÅ\8dm tymu zapytaniu.",
        "powersearch-legend": "Sznupańy zaawansowane",
        "powersearch-ns": "Sznupej we przestrzyńach mjan:",
        "powersearch-togglelabel": "Uoznocz:",
        "prefs-labs": "Funkcyje \"labs\"",
        "prefs-user-pages": "Zajty ôd używŏczōw",
        "prefs-personal": "Dane używocza",
-       "prefs-rc": "Ńydowno pomjyńane",
+       "prefs-rc": "Ôstatnie zmiany",
        "prefs-watchlist": "Pozůrlista",
        "prefs-watchlist-days": "Liczba dńůw widocznych na liśće artikli, na kere dowosz pozůr:",
        "prefs-watchlist-days-max": "Max $1 {{PLURAL:$1|dźyń|dńi}}",
        "group-user": "Używŏcze",
        "group-autoconfirmed": "Autōmatycznie przituplowani używŏcze",
        "group-bot": "Boty",
-       "group-sysop": "Admińi",
+       "group-sysop": "Administratorzi",
        "group-bureaucrat": "Bjurokraty",
        "group-suppress": "Rewizorze",
        "group-all": "(wszyjscy)",
        "grouppage-user": "{{ns:project}}:Używŏcze",
        "grouppage-autoconfirmed": "{{ns:project}}:Autōmatycznie przituplowani używŏcze",
        "grouppage-bot": "{{ns:project}}:Boty",
-       "grouppage-sysop": "{{ns:project}}:Admińistratory",
+       "grouppage-sysop": "{{ns:project}}:Administratorzi",
        "grouppage-bureaucrat": "{{ns:project}}:Bjurokraty",
        "grouppage-suppress": "{{ns:project}}:Rewizorze",
        "right-read": "Czytej zajty",
        "right-userrights": "Sprowjej wšyjske uprawńyńo užytkowńikůw",
        "right-userrights-interwiki": "Sprowjej uprawńyńo užytkowńikůw na zajtach inkšych Wiki",
        "right-siteadmin": "Zawjerańy i uodmykańy bazy danych",
-       "newuserlogpage": "Nowe użytkowniki",
+       "newuserlogpage": "Ksiōnżka nowych używŏczōw",
        "newuserlogpagetext": "To je rejer uostatńo utworzůnych kůnt użytkowńikůw",
-       "rightslog": "Uprawńyńo",
+       "rightslog": "Regest uprawniyń używŏczōw",
        "rightslogtext": "Rejer půmjyńań uprawńyń užytkowńikůw.",
        "action-read": "přeglůndańo tyj zajty",
-       "action-edit": "edycyje tyj zajty",
+       "action-edit": "edycyje tyj strōny",
        "action-createpage": "tworzyńo zajtůw",
        "action-createtalk": "tworzyńo zajtůw godki",
-       "action-createaccount": "utwořyńo tygo kůnta užytkowńika",
+       "action-createaccount": "stworzynie tego kōnta używŏcza",
        "action-minoredit": "do uoznačyńo tygo sprowjyńo kej drobne půmjyńańe",
        "action-move": "přećepańe tyj zajty",
        "action-move-subpages": "přećepańo tyj zajty uoroz s jeij podzajtůma",
        "action-userrights-interwiki": "sprowjańo uprowńyń sprowjořy na inkšych witrynach wiki",
        "action-siteadmin": "zawarćo a uodymkńyńćo bazy danych",
        "nchanges": "$1 {{PLURAL:$1|pomjyńańe|pomjyńańa|pomjyńań}}",
-       "enhancedrc-history": "gyszichta",
-       "recentchanges": "Ńydowno půmjyńane",
-       "recentchanges-legend": "Uopcyje ńydowno půmjyńanych",
-       "recentchanges-summary": "Ta zajta ukozuje gyszichta uostatńich půmjyńań na tyj wiki.",
+       "enhancedrc-history": "historyjŏ",
+       "recentchanges": "Ôstatnie zmiany",
+       "recentchanges-legend": "Ôpcyje ôstatnich zmian",
+       "recentchanges-summary": "Na tyj strōnie idzie śledzić ôstatnie zmiany na wiki.",
+       "recentchanges-noresult": "Żŏdne zmiany we podanym ôkresie niy pasujōm tym kryteriōm.",
        "recentchanges-feed-description": "Dowej pozůr na půmjyńane na uostatku na tyj wiki.",
-       "recentchanges-label-newpage": "Ta edycyjŏ sprŏwiła nowõ zajtã",
-       "recentchanges-label-minor": "To je niywielgŏ pōmiana",
-       "recentchanges-label-bot": "Ta pōmiana sprŏwił bot",
-       "recentchanges-label-unpatrolled": "Ta edycyjŏ niy ôstała jeszcze przichwŏlōnŏ",
-       "recentchanges-label-plusminus": "Půmjyńono mjara zajty we bajtach",
+       "recentchanges-label-newpage": "Ta edycyjŏ stworziła nowõ strōnã",
+       "recentchanges-label-minor": "To je małŏ zmiana",
+       "recentchanges-label-bot": "To je zmiana zrobiōnŏ ôd bota",
+       "recentchanges-label-unpatrolled": "Ta edycyjŏ niy była jeszcze sprawdzōnŏ",
+       "recentchanges-label-plusminus": "Strōna zmiyniyła srogość ô tela bajtōw",
        "recentchanges-legend-heading": "<strong>Legynda:</strong>",
-       "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (uobejrzij tyż [[Special:NewPages|lista nowych zajt]])",
-       "rcnotefrom": "Půńiżej pokazano půmjyńańo zrobjůne pů <b>$2</b> (ńy wjyncyj kej <b>$1</b> pozycji).",
-       "rclistfrom": "Ukoż půmjyńańa uod $3 $2",
-       "rcshowhideminor": "$1 drobne půmjyńańa",
-       "rcshowhideminor-show": "Pokoż",
-       "rcshowhideminor-hide": "Schrůń",
+       "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (ôbejzdrzij tyż [[Special:NewPages|listã nowych strōn]])",
+       "rcnotefrom": "Niżyj {{PLURAL:$5|je zmiana|sōm zmiany}} ôd <strong>$3, $4</strong> ({{PLURAL:$5|je pokŏzanŏ|sōm pokŏzane}} nojwyżyj <strong>$1</strong>).",
+       "rclistfrom": "Pokŏż zmiany ôd $3 $2",
+       "rcshowhideminor": "$1 małe zmiany",
+       "rcshowhideminor-show": "Pokŏż",
+       "rcshowhideminor-hide": "Skryj",
        "rcshowhidebots": "$1 boty",
-       "rcshowhidebots-show": "Pokoż",
-       "rcshowhidebots-hide": "Schrůń",
-       "rcshowhideliu": "$1 zaregisztrowanych",
-       "rcshowhideliu-hide": "Schrůń",
-       "rcshowhideanons": "$1 anůńimowych",
-       "rcshowhideanons-show": "Pokoż",
-       "rcshowhideanons-hide": "Schrůń",
-       "rcshowhidepatr": "$1 uowjerzůne",
+       "rcshowhidebots-show": "Pokŏż",
+       "rcshowhidebots-hide": "Skryj",
+       "rcshowhideliu": "$1 zaregistrowanych",
+       "rcshowhideliu-show": "Pokŏż",
+       "rcshowhideliu-hide": "Skryj",
+       "rcshowhideanons": "$1 anōnimowych używŏczōw",
+       "rcshowhideanons-show": "Pokŏż",
+       "rcshowhideanons-hide": "Skryj",
+       "rcshowhidepatr": "$1 zweryfikowane edycyje",
        "rcshowhidemine": "$1 moje edycyje",
-       "rcshowhidemine-show": "Pokoż",
-       "rcshowhidemine-hide": "Schrůń",
-       "rclinks": "Ukŏż ôstatnie $1 mian bez ôstatnie $2 dni.",
-       "diff": "zmj.",
-       "hist": "gysz.",
-       "hide": "Schrůń",
-       "show": "Ukoż",
+       "rcshowhidemine-show": "Pokŏż",
+       "rcshowhidemine-hide": "Skryj",
+       "rclinks": "Ukŏż ôstatnie $1 zmian bez ôstatnie $2 dni.",
+       "diff": "rōżn.",
+       "hist": "hist.",
+       "hide": "Skryj",
+       "show": "Pokŏż",
        "minoreditletter": "d",
        "newpageletter": "N",
        "boteditletter": "b",
-       "rc-change-size-new": "$1 {{PLURAL:$1|bajt|bajty|bajtōw}} po mianie",
+       "rc-change-size-new": "$1 {{PLURAL:$1|bajt|bajty|bajtōw}} po zmianie",
        "newsectionsummary": "/* $1 */ nowo tajla",
        "rc-enhanced-expand": "Pokoż szczygůły",
        "rc-enhanced-hide": "Schrůń detajle",
-       "recentchangeslinked": "Půmjyńańa we nalinkowanych",
+       "rc-old-title": "ôryginalnie stworzōne za „$1”",
+       "recentchangeslinked": "Zmiany we linkowanych",
        "recentchangeslinked-feed": "Pomjyńańa we adresowanych",
-       "recentchangeslinked-toolbox": "Půmjyńańa we nalinkowanych",
-       "recentchangeslinked-title": "Pomjyńyńo w adrésowanych s \"$1\"",
-       "recentchangeslinked-summary": "Ńiżyj je lista ńydowno půmjyńanych na zajtach, na kere uobrano zajta linkuje (abo wszyjskich zajtach patrzůncych do uobranyj kategoryje).\nZajty z [[Special:Watchlist|pozůrlisty]] sům '''rube'''",
-       "recentchangeslinked-page": "Mjano zajty",
-       "recentchangeslinked-to": "Ukoż půmjyńańa na zajtach, kere linkujům na uobrano zajta",
-       "upload": "Wćepej plik",
+       "recentchangeslinked-toolbox": "Zmiany we linkowanych",
+       "recentchangeslinked-title": "Zmiany we linkowanych z „$1”",
+       "recentchangeslinked-summary": "Wkludź miano strōny, żeby ôbejzdrzeć zmiany na strōnach linkowanych do nij abo co dō nij linkujōm. (Żeby ôbejzdrzeć strōny z kategoryje, wkludź {{ns:category}}:Miano kategoryje). Strōny ze [[Special:Watchlist|Ôbserwowanych]] sōm <strong>porubiōne</strong>.",
+       "recentchangeslinked-page": "Miano strōny:",
+       "recentchangeslinked-to": "Pokŏż zmiany na strōnach, co linkujōm do podanyj strōny",
+       "upload": "Zaladuj zbiōr",
        "uploadbtn": "Wćepej sam plik",
        "reuploaddesc": "Nazod do formulařa uod wćepywańo.",
        "uploadnologin": "Ńy jest žeś zalogůwany",
        "upload-permitted": "Dopuščalne formaty plikůw: $1.",
        "upload-preferred": "Zalecane formaty plikůw: $1.",
        "upload-prohibited": "Zakozane formaty plikůw: $1.",
-       "uploadlogpage": "Wćepane sam",
+       "uploadlogpage": "Regest przisłań",
        "uploadlogpagetext": "Půńiżyj jee lista plikůw wćepanych na uostatku.\nPrzelyź na zajta [[Special:NewFiles|galeryje nowych plikůw]], coby uobejzdrzeć pliki kej mińatůrki.",
        "filename": "Mjano pliku",
-       "filedesc": "Popis",
+       "filedesc": "Ôpis",
        "fileuploadsummary": "Uopis:",
        "filestatus": "Status prawny:",
        "filesource": "Kod zdřůduowy:",
        "upload-curl-error6-text": "Podany URL je ńyosiůngalny. Proša, sprowdź dokuadńy čy podany URL je prawidouwy i čy dano zajta dźauo.",
        "upload-curl-error28": "Překročůny čas kery bůu na wćepywańe",
        "upload-curl-error28-text": "Zajta uodpowjado za powoli. Proša, sprawdź čy zajta dźauo, uodčekej pora minut i sprůbuj zaś. Možeš tyž sprůbować wončas kej zajta bydźe mńij uobćůnžůno.",
-       "license": "Licencyjo:",
-       "license-header": "Licencyjo",
+       "license": "Licyncyjŏ:",
+       "license-header": "Licyncyjŏ",
        "nolicense": "Ńy wybrano (naškryflej rynčńy!)",
        "license-nopreview": "(Podglůnd ńydostympny)",
        "upload_source_url": " (poprowny, publičńy dostympny URL)",
        "upload_source_file": "(plik na twojym kůmputrze)",
        "listfiles-summary": "To je ekstra zajta na kery sům pokazywane wšyske pliki wćepane na serwer. Důmyślńy na wiyrchu listy wyśwjetlajům śe pliki wćepane na uostatku. Coby půmjyńić sposůb sortowańo, klikńij na naguůwek kolůmny.",
        "listfiles_search_for": "Šnupej za grafikům uo mjańe:",
-       "imgfile": "plik",
-       "listfiles": "Lista plikůw",
+       "imgfile": "zbiōr",
+       "listfiles": "Lista zbiorōw",
        "listfiles_date": "Data",
        "listfiles_name": "Mjano",
        "listfiles_user": "Užytkowńik",
        "listfiles_size": "Rozmior (bajty)",
        "listfiles_description": "Uopis",
-       "file-anchor-link": "Plik",
-       "filehist": "Gyszichta pliku",
-       "filehist-help": "Klikńij na datum/cas, coby uwidzieć, jak plik w tyn czas wypadoł.",
+       "file-anchor-link": "Zbiōr",
+       "filehist": "Historyjŏ zbioru",
+       "filehist-help": "Kliknij w datã/czas, żeby ôbejzdrzeć zbiōr, jak wtynczŏs wyglōndoł.",
        "filehist-deleteall": "wyćep wszyske",
        "filehist-deleteone": "Wyćep",
-       "filehist-revert": "cofej",
-       "filehist-current": "aktualny",
-       "filehist-datetime": "Datum a czas",
-       "filehist-thumb": "Mińiwersyjo",
-       "filehist-thumbtext": "Mińiwersyje $1",
-       "filehist-nothumb": "Ńy ma mińjaturki",
+       "filehist-revert": "cŏfnij",
+       "filehist-current": "terŏźnŏ",
+       "filehist-datetime": "Data i czas",
+       "filehist-thumb": "Miniatura",
+       "filehist-thumbtext": "Miniatura wersyje $1",
+       "filehist-nothumb": "Bez miniatury",
        "filehist-user": "Używŏcz",
-       "filehist-dimensions": "Wymjyry",
+       "filehist-dimensions": "Wymiary",
        "filehist-filesize": "Rozmior plika",
-       "filehist-comment": "Komyntorz",
-       "imagelinks": "Używańy pliku",
-       "linkstoimage": "{{PLURAL:$1|Tako zajta linkuje|Take zajty linkujům}} do tygo plika:",
-       "linkstoimage-more": "Wjyncyj jak $1 {{PLURAL:$1|zajta je adresowano|zajty sům adresowane|zajtůw je adresowanych}} do tygo plika.\nPůńiższo lista pokozuje ino {{PLURAL:$1|pjyrszy link|pjyrsze $1 linki|pjyrszych $1 linkůw}} do tygo plika.\nDostympno je tyż [[Special:WhatLinksHere/$2|połno lista]].",
-       "nolinkstoimage": "Žodno zajta Å\84y je adrésowano do tygo plika.",
+       "filehist-comment": "Kōmyntŏrz",
+       "imagelinks": "Użycie zbioru",
+       "linkstoimage": "{{PLURAL:$1|Ta strōna używŏ|Te strōny używajōm}} tego zbioru:",
+       "linkstoimage-more": "Tyn zbiōr {{PLURAL:$1|używŏ wiyncyj niż jedna strōna|używajōm wiyncyj niż $1 strōny|używŏ wiyncyj niż $1 strōn}}.\nTa lista pokazuje ino {{PLURAL:$1|piyrszõ|piyrsze $1}}.\n[[Special:WhatLinksHere/$2|Połnŏ lista]] je tyż dostympnŏ.",
+       "nolinkstoimage": "Å»Å\8fdnÅ\8f strÅ\8dna niy używÅ\8f tego zbioru.",
        "morelinkstoimage": "Pokož [[Special:WhatLinksHere/$1|wjyncy uodnośnikůw]] do tygo plika.",
+       "linkstoimage-redirect": "$1 (przekerowanie do zbioru) $2",
        "duplicatesoffile": "{{PLURAL:$1|Nastympujůncy plik je kopjům|Nastympujůnce pliki sům kopjůma}} tygo plika:",
        "sharedupload": "Tyn plik je wćepńynty na $1 a inksze projekty tyż go mogům używać.",
-       "sharedupload-desc-here": "Tyn plik śe nałoźi na $1 a idzie go użyć we inkszych projektach.\nNiżyj sům informacyje ze [$2 zajty popisu] tygo pliku.",
+       "sharedupload-desc-here": "Tyn zbiōr je ze $1 i może być używany we inkszych projektach.\nÔpis na jego [$2 strōnie ôpisu zbioru] je pokŏzany niżyj.",
+       "filepage-nofile": "Niy ma zbioru ze tym mianym.",
        "uploadnewversion-linktext": "Wćepńij nowšo wersyjo tygo plika",
-       "upload-disallowed-here": "Ńy moges nadpisać tygo plika.",
+       "upload-disallowed-here": "Niy możesz podmiynić tego zbioru.",
        "filerevert": "Přiwracańy $1",
        "filerevert-legend": "Přiwracańy poprzedńy wersje plika",
        "filerevert-intro": "Zamjeřoš přiwrůćić '''[[Media:$1|$1]]''' do wersje z [$4 $3, $2].",
        "unusedtemplates": "Ńyužywane šablôny",
        "unusedtemplatestext": "Půńižej znojdowo śe lista wšyjstkich zajtůw s přestřyńi mjan {{ns:template}}, kere ńy sům užywane bez inkše zajty. Sprowdź inkše adresowańa ku šablůnům, ńim wyćepńeš ta zajta.",
        "unusedtemplateswlh": "ku adresatu",
-       "randompage": "Cufalno zajta",
+       "randompage": "Losowŏ strōna",
        "randompage-nopages": "We przestrzyńi mjan \"$1\" ńy ma żodnych zajtůw.",
        "randomredirect": "Losowe překerowańy",
        "randomredirect-nopages": "We przestrzyńi mjan \"$1\" ńy ma przekerowań.",
-       "statistics": "Sztatystyka",
+       "statistics": "Statystyka",
        "statistics-header-pages": "Statystyka zajtůw",
        "statistics-header-edits": "Statystyka sprowjyń",
        "statistics-header-users": "Statystyka užytkowńikůw",
        "doubleredirects": "Podwůjne překierowańa",
        "doubleredirectstext": "Na tyi liśće mogům znojdować śe překerowańo pozorne. Uoznača to, aže půńižej pjyrwšej lińii artikla, zawjerajůncyj \"#REDIRECT ...\", može znojdować śe dodotkowy tekst. Koždy wjerš listy zawjero uodwouańo do pjyrwšygo i drůgygo překerowańo a pjyrwšom lińjům tekstu drůgygo překerowańo. Uůmožliwjo to na ogůu uodnaleźyńy wuaśćiwygo artikla, do kerygo powinno śe překerowywać.",
        "double-redirect-fixed-move": "zajta [[$1]] zostoła zastůmpjůno bez przekerowańy, skiż jeij przekludzyńo ku [[$2]]",
-       "double-redirect-fixer": "Korektor przekerowań",
+       "double-redirect-fixer": "Korektōr przekerowań",
        "brokenredirects": "Zuomane překerowańa",
        "brokenredirectstext": "Překerowańo půńižej wskazujům na artikle kerych sam ńy ma.",
        "brokenredirects-edit": "sprowjéj",
        "withoutinterwiki-legend": "Prefiks",
        "withoutinterwiki-submit": "Pokož",
        "fewestrevisions": "Zajty z nojmńijšom ilośćům wersyji",
-       "nbytes": "$1 {{PLURAL:$1|bajty|bajtůw}}",
+       "nbytes": "$1 {{PLURAL:$1|bajt|bajty|bajtōw}}",
        "ncategories": "$1 {{PLURAL:$1|kategoryja|kategorje|kategorjůw}}",
        "nlinks": "$1 {{PLURAL:$1|link|linki|linkůw}}",
-       "nmembers": "$1 {{PLURAL:$1|elyment|elymenty|elymentůw}}",
+       "nmembers": "$1 {{PLURAL:$1|elymynt|elymynta|elymyntōw}}",
        "nrevisions": "$1 {{PLURAL:$1|wersja|wersje|wersjůw}}",
        "specialpage-empty": "Ta zajta je pusto.",
        "lonelypages": "Poćepńynte zajty",
        "mostcategories": "Zajty kere majům nojwiyncyj kategoryjůw",
        "mostimages": "Nojczyńśćij adresowane pliki",
        "mostrevisions": "Nojczyńśćij sprowjane artikle",
-       "prefixindex": "Wszyskie zajty wedle prefiksa",
+       "prefixindex": "Wszyjske strōny ze prefiksym",
        "shortpages": "Nojkrůtsze zajty",
        "longpages": "Duge artikle",
        "deadendpages": "Artikle bez linkůw",
        "protectedpagesempty": "Żodno zajta ńy je terozki zawarto ze podanymi parametrami.",
        "protectedtitles": "Zawarte mjana artikli",
        "protectedtitlesempty": "Do tych štalowań utwořyńy artikla uo dowolnym mjańy ńy je zawarte",
-       "listusers": "Lista užytkowÅ\84ikůw",
+       "listusers": "Lista używÅ\8fczÅ\8dw",
        "listusers-editsonly": "Pokoż yno użytkowńikůw kere majům sprowjyńa",
        "usereditcount": "$1 {{PLURAL:$1|sprowjyńe|sprowjyńa|sprowjyń}}",
        "usercreated": "{{GENDER:$3|Utworzono}} $1 uo $2",
-       "newpages": "Nowe zajty",
+       "newpages": "Nowe strōny",
        "newpages-username": "Mjano użytkowńika:",
        "ancientpages": "Nojstarše artikle",
-       "move": "Przećep",
+       "move": "Przeniyś",
        "movethispage": "Přećepej ta zajta",
        "unusedimagestext": "Pamjyntej, proša, aže inkše witryny, np. projekty Wikimedja w inkšych godkach, můgům adresować do tych plikůw užywajůnc bezpośredńo URL. Bez tůž ńykere ze plikůw můgům sam być na tej liśće pokozane mimo, aže žodna zajta ńy adresuje do ńich.",
        "unusedcategoriestext": "Katygorje pokazane půńižej istńejům, choć ńy kořisto s ńich žadyn artikel ańi katygorja.",
        "notargettext": "Ńy podano zajty abo užytkowńika, do kerych ta uoperacyjo mo być wykůnano.",
        "nopagetitle": "Ńy ma sam zajty docelowyj",
        "nopagetext": "Wybrano zajta docelowo ńy istńeje.",
-       "pager-newer-n": "{{PLURAL:$1|1 nowšy|$1 nowše|$1 nowšych}}",
+       "pager-newer-n": "{{PLURAL:$1|1 nowszy|$1 nowsze|$1 nowszych}}",
        "pager-older-n": "{{PLURAL:$1|1 starszy|$1 starsze|$1 starszych}}",
        "suppress": "Oversight",
-       "booksources": "Kśůnžki",
+       "booksources": "Zdrzōdła ksiōnżek",
        "booksources-search-legend": "Szukej informacyji ô ksiōnżkach",
        "booksources-search": "Szukej",
        "booksources-text": "Půńiżyj je lista uodnośńikůw do inkszych witryn, kere pośredńiczům we sprzedaży nowych a używanych buchůw, a tyż můgům mjeć dolsze informacyje uo poszukiwanym bez ćebje buchu.",
        "booksources-invalid-isbn": "Podany numer ISBN zostoł rozpoznany kej felerny. Sprowdź aże podany numer je zgodny s numerym kery je we zdrzůdle.",
-       "specialloguserlabel": "Užytkowńik:",
-       "speciallogtitlelabel": "Titel:",
-       "log": "Register dźołano",
-       "all-logs-page": "Wszyjstke uoperacyje",
-       "alllogstext": "Wspůlny rejer wszyjstkych typůw uoperacyji do {{SITENAME}}.\nMożesz zawyńźić liczba wyńikůw wybjerajůnc typ rejeru, mjano użytkowńika abo titel zajty (wjelge a mołe buchsztaby majům znoczyńy).",
-       "logempty": "Ńy ma wpisůw we rejeře",
+       "specialloguserlabel": "Fto:",
+       "speciallogtitlelabel": "Cyl (nazwa abo {{ns:user}}:miano ôd używŏcza):",
+       "log": "Regest ôperacyji",
+       "all-logs-page": "Wszyjske óperacyje",
+       "alllogstext": "Spōlne pokŏzanie wszyjskich dostympnych regestōw {{SITENAME}}.\nMożesz uakuratnić widok bez ôbranie zorty regestu, miana ôd używŏcza, abo tykanyj strōny (dŏwŏ pozōr na małe i sroge litery).",
+       "logempty": "Niy ma we regeście zgodliwych elymyntōw.",
        "log-title-wildcard": "Šnupej za titlami kere začynojům śe uod tygo tekstu",
-       "allpages": "Wšyskie zajty",
+       "allpages": "Wszyjske strōny",
        "nextpage": "Nostympno zajta ($1)",
        "prevpage": "Popředńo zajta ($1)",
        "allpagesfrom": "Zajty začynojůnce śe na:",
        "allpagesto": "Zajty uo titlach kere na zadku majům:",
-       "allarticles": "Wszyske zajty",
+       "allarticles": "Wszyjske strōny",
        "allinnamespace": "Wszyjstke zajty (we przestrzyńi mjan $1)",
-       "allpagessubmit": "Ukoż",
+       "allpagessubmit": "Idź",
        "allpagesprefix": "Ukoż artikle s prefiksym:",
        "allpagesbadtitle": "Podane mjano je felerne, zawjyro prefiks mjyndzyprojektowy abo mjyndzygodkow. Może uůne tyż zawjerać jako buchsztaba abo inksze znaki, kerych ńy wolno używać we mjanach.",
        "allpages-bad-ns": "{{GRAMMAR:MS.lp|{{SITENAME}}}} ńy mo przestrzyńi mjan „$1”.",
-       "allpages-hide-redirects": "Ukoż pukńyńća",
+       "allpages-hide-redirects": "Pokŏż przekerowania",
        "categories": "Kategoryje",
        "categoriespagetext": "Zajta przedstowjo lista katygoryji s zajtůma a plikůma.\n[[Special:UnusedCategories|Ńyużywane kategoryj]] ńy zostoły tukej pokozane.\nKukńij tyż [[Special:WantedCategories|ńyistńyjůnce kategoryje]].",
        "categoriesfrom": "Pokož kategoryje začynajůnc uod:",
        "deletedcontributions": "Wyćepane sprowjyńa użytkowńika",
        "deletedcontributions-title": "Wyćepane sprowjyńa użytkowńika",
+       "sp-deletedcontributions-contribs": "wkłŏd",
        "linksearch": "Necowe uodwołańa",
        "linksearch-pat": "Wzorzec sznupańo",
        "linksearch-ns": "Przestrzyń mjan",
        "listgrouprights-group": "Grupa",
        "listgrouprights-rights": "Uprawńyńo",
        "listgrouprights-helppage": "Help:Uprawńyńo grup użytkowńikůw",
-       "listgrouprights-members": "(listo człůnkůw grupy)",
+       "listgrouprights-members": "(lista czōnkōw grupy)",
        "listgrouprights-addgroup": "Idźe dodać do {{PLURAL:$2|grupy|grup}}: $1",
        "listgrouprights-removegroup": "Idźe wyćepać s {{PLURAL:$2|grupy|grup}}: $1",
        "listgrouprights-addgroup-all": "Idźe dodać do kożdyj grupy",
        "listgrouprights-addgroup-self": "Je mogebny dać swe konto do {{PLURAL:$2|grupy|grup:}} $1",
        "mailnologin": "Brak adresu",
        "mailnologintext": "Muśyš śe [[Special:UserLogin|zalůgować]] i mjeć wpisany aktualny adres e-brif w swojich [[Special:Preferences|preferyncyjach]], coby můc wysuać e-brif do inkšygo užytkowńika.",
-       "emailuser": "Poślij tymu używoczowi e-brif",
+       "emailuser": "Poślij tymu używŏczowi e-mail",
        "emailpagetext": "Możesz użyć půńiższygo formularza, coby wysłać wjadůmość e-brif do tygo użytkowńika.\nAdres e-brifa, kery zostoł bez Ćebje wkludzůny we [[Special:Preferences|Twojich sztalowańach]], pojawi śe we polu „Uod”, bez cůż uodbjorca bydźe můg Ći uodpedźeć.",
        "defemailsubject": "{{SITENAME}} - e-mail ôd używŏcza \"$1\"",
        "usermaildisabled": "E-mail ôd używŏcza je zastŏwiōny",
        "emailsent": "Wjadůmość zostoua wysuano",
        "emailsenttext": "Twoja wjadůmość zostoua wysuano.",
        "emailuserfooter": "Wjadůmość e-brif zostoła wysłano s {{GRAMMAR:D.lp|{{SITENAME}}}} ku $2 bez $1 s użyćym „Wyślij e-brif ku tym użytkowńikowi”.",
-       "watchlist": "Pozůrlista",
-       "mywatchlist": "Pozůrlista",
-       "watchlistfor2": "Lo $1 ($2)",
+       "usermessage-editor": "Nadŏwca systymowych kōmunikatōw",
+       "watchlist": "Ôbserwowane",
+       "mywatchlist": "Ôbserwowane",
+       "watchlistfor2": "{{GENDER:$1|Używŏcza|Używŏczki}} $1 $2",
        "nowatchlist": "Ńy ma žodnych pozycyji na liśće zajtůw, na kere dowoš pozůr.",
        "watchlistanontext": "$1 coby uobejřeć abo sprowjać elymynty listy zajtůw, na kere dowoš pozůr",
        "watchnologin": "Ńy jest žeś zalůgowany",
        "addedwatchtext": "Zajta \"[[:$1]]\" zostoua dodano do Twojij [[Special:Watchlist|listy artiklůw, na kere dowoš pozůr]].\nNa tyi liśće bydźeš mjou rejer přišuych sprowjyń tyi zajty i jeji zajty godki, a mjano zajty bydźeš mjou škryflane '''tustym''' na [[Special:RecentChanges|liśće půmjyńanych na ůostatku]], cobyś mjou wygoda w jei pomjyńańa filować.",
        "removedwatchtext": "Artikel \"[[:$1]]\" zostou wyćepńjynty s [[Special:Watchlist|Twojij pozorlisty]].",
-       "watch": "Dej pozůr",
+       "watch": "Ôbserwuj",
        "watchthispage": "Dej pozůr",
-       "unwatch": "Ńy dowej pozoru",
+       "unwatch": "Niy ôbserwuj",
        "unwatchthispage": "Přestoń dować pozůr",
        "notanarticle": "To ńy je artikel",
        "notvisiblerev": "Wersyja zostoua wyćepano",
-       "watchlist-details": "Na pozorliśće {{PLURAL:$1|je 1 artikel|sům $1 artikle|je $1 artikli}} ńy rachujůnc zajtůw godek.",
+       "watchlist-details": "Na Twojij liście ôbserwowanych {{PLURAL:$1|je $1 strōna|sōm $1 strōny|je $1 strōn}} (plus strōny dyskusyje).",
        "wlheader-enotif": "Wysůuańy powjadůmjyń na adres e-brif je zouůnčůne",
-       "wlheader-showupdated": "Zajty, kere były pōmiyniane ôd twojij ôstatnij nŏwiydzki na nich ôstały ukŏzane '''na rubo'''",
-       "wlnote": "Půńižy pokazano {{PLURAL:$1|ostatńy sprawjyńy dokůnane|ostatńy '''$1''' sprawjyńe dokůnane|ostatńych '''$1''' sprawjyń dokůnanych}} bez {{PLURAL:$2|uostatńo godźina|uostatńich '''$2''' godźin}}.",
-       "wlshowlast": "Pokož uostatńy $1 godźin $2 dńi ()",
-       "watchlist-options": "Uopcyje artikli na kere dowosz pozůr",
+       "wlheader-showupdated": "Zajty, co były zmiyniane ôd twojij ôstatnij nŏwiydzki na nich ôstały ukŏzane <strong>na rubo</strong>.",
+       "wlnote": "Niżyj {{PLURAL:$1|je ôstaniŏ zmiana|sōm ôstatnie <strong>$1</strong> zmiany|je ôstatnie <strong>$1</strong> zmian}} ze {{PLURAL:$2|ôstatnij godziny|ôstatnich <strong>$2</strong> godzin}}, na $3, $4.",
+       "wlshowlast": "Pokŏż ôstatnie $1 godzin $2 dni",
+       "watchlist-options": "Ôpcyje ôbserwowanych",
        "watching": "Dowom pozor...",
        "unwatching": "Ńy dowům pozůr.",
-       "enotif_reset": "Uoznoč wšyjstke zajty kej uodwjydzůne",
+       "enotif_reset": "Ôznŏcz wszyjske strōny za nawiydzōne",
        "enotif_impersonal_salutation": "užytkowńik {{GRAMMAR:D.lp|{{SITENAME}}}}",
        "enotif_lastvisited": "Uobejřij na zajće $1 wšyjstke půmjyńańo uod Twojej uostatńij wizyty.",
        "enotif_lastdiff": "Uobejřij na zajće $1 te pomjyńeńe.",
        "actioncomplete": "Fertig",
        "actionfailed": "Ńy udało śe.",
        "deletedtext": "Wyćepano \"$1\". Rejer uostatnio zrobiůnych wyćepań možeš uobejžyć tukej: $2.",
-       "dellogpage": "Wyćepane",
+       "dellogpage": "Regest kasowań",
        "dellogpagetext": "To je lista uostatńo wykůnanych wyćepań.",
        "deletionlog": "rejer wyćepań",
        "reverted": "Přiwrůcůno popředńo wersyja",
        "delete-toobig": "Ta zajta mo fest dugo historyja sprowjyń, wjyncyj jak $1 {{PLURAL:$1|půmjyńańy|půmjyńańo|půmjyńań}}.\nJeij wyćepańy mogło by spowodować zakłucyńo we dźołańu {{GRAMMAR:D.lp|{{SITENAME}}}} a bez tůż zostało uograńiczůne.",
        "delete-warning-toobig": "Ta zajta mo fest dugo historia sprowjyń, wjyncy kej $1 {{PLURAL:$1|půmjyńeńe|půmjyńańo|půmjyńań}}.\nDej pozůr, bo jei wyćepańe może spowodować zakłůcyńo w pracy {{GRAMMAR:D.lp|{{SITENAME}}}}.",
        "rollback": "Wycofej sprowjyńe",
-       "rollbacklink": "cofej",
+       "rollbacklink": "cŏfej",
+       "rollbacklinkcount": "cŏfnij $1 {{PLURAL:$1|edycyjõ|edycyje|edycyji}}",
        "rollbackfailed": "Ńy idźe wycofać sprowjyńo",
        "cantrollback": "Ńy idże cofnůńć pomjyńyńo, sam je ino jedna wersyja tyi zajty.",
        "alreadyrolled": "Ńy idźe lů zajty [[:$1|$1]] cofnůńć uostatńygo pomjyńeńa, kere wykonoł [[User:$2|$2]] ([[User talk:$2|godka]]{{int:pipe-separator}}[[Special:Contributions/$2|{{int:contribslink}}]]).\nKto inkszy zdůnżůł już to zrobić abo wprowadźił własne poprowki do treśći zajty.\n\nAutorym ostatńygo pomjyńyńo je terozki [[User:$3|$3]] ([[User talk:$3|godka]]{{int:pipe-separator}}[[Special:Contributions/$3|{{int:contribslink}}]]).",
        "revertpage": "Wycofano sprowjyńe użytkowńika [[Special:Contributions/$2|$2]] ([[User talk:$2|godka]]). Autor prziwrůcůnej wersyji to [[User:$1|$1]].",
        "rollback-success": "Wycofano sprowjyńa użytkowńika $1.\nPrziwrůcůno uostatńo wersyja autorstwa  $2.",
        "sessionfailure": "Feler weryfikacyji zalůgowańo.\nPolecyńy zostoło anulowane, coby ůńiknůńć przechwycyńo sesyji.\n\nNaćiś knefel „cofej”, przeładuj zajta, a potym zaś wydej polecyńy",
-       "protectlogpage": "Zawarte",
+       "protectlogpage": "Regest zawarć",
        "protectlogtext": "Půńižej znojdowo śe lista zawarć i uodymkńjyńć pojydynčych zajtůw.\nCoby přejřeć lista uobecńy zawartych zajtůw, přeńdź na zajta wykazu [[Special:ProtectedPages|zawartych zajtůw]].",
-       "protectedarticle": "zawar [[$1]]",
-       "modifiedarticleprotection": "pomjyńiu poźům zawarćo [[$1]]",
+       "protectedarticle": "zawar „[[$1]]”",
+       "modifiedarticleprotection": "zmiyniōł(yła) poziōm zawarciŏ „[[$1]]”",
        "unprotectedarticle": "uodymknyu [[$1]]",
        "movedarticleprotection": "przekludzůno sztalowańa zabezpjeczyńo s „[[$2]]” ku „[[$1]]”",
        "protect-title": "Pomjyńeńe poźomu zawarćo „$1”",
        "protect-locked-dblock": "Ńy idźe půmjyńić poźůmu zawarća s kuli tygo co baza danych tyž je zawarto. Uobecne štalowańa dla zajty '''$1''' to:",
        "protect-locked-access": "Ńy moš uprowńyń coby pomjyńyć poziům zawarcia zajty. Uobecne ustawjyńo dlo zajty '''$1''' to:",
        "protect-cascadeon": "Ta zajta je zawarto od pomjyńań, po takjymu, co jei užywo {{PLURAL:$1|ta zajta, kero je zawarto|nastympůjůnce zajty, kere zostauy zawarte}} a opcyjo dźedźičyńo je zaůončono. Možeš pomjyńyć poziům zawarcia tyi zajty, ale dlo dźedźičyńo zawarcia to ńy mo wpuywu.",
-       "protect-default": "Dozwolōne do wszyjskich używaczy.",
+       "protect-default": "Przizwolōne wszyjskim",
        "protect-fallback": "Wymago pozwolynjo \"$1\"",
        "protect-level-autoconfirmed": "Blokuj nowe a ńyregistrowane używocze",
        "protect-level-sysop": "Ino admini",
        "maximum-size": "Maksymalno wjelgość",
        "pagesize": "(bajtůw)",
        "restriction-edit": "Edytuj",
-       "restriction-move": "PÅ\99\87epÅ\84jyÅ\84Ä\87e",
+       "restriction-move": "PÅ\8dnknij",
        "restriction-create": "Stwůř",
        "restriction-upload": "Wćep",
        "restriction-level-sysop": "poune zawarće",
        "undelete-show-file-confirm": "Jeżeś echt pewny co chcesz uobejzdrzeć wyćepano wersyjo plika „<nowiki>$1</nowiki>” s $2 $3?",
        "undelete-show-file-submit": "Ja",
        "namespace": "Przestrzyń mian:",
-       "invert": "Wybjer na uopy",
-       "tooltip-invert": "Ôznŏcz tyn kastlik, coby skryć pōmiany na zajtach we ôbranych przestrzyniach mian (i swiōnzanych ze nimi inkszymi przestrzyniami mian, eli ôznŏczōno)",
-       "namespace_association": "powiōnzanŏ przestrzyń mian",
-       "tooltip-namespace_association": "Ôznŏcz tyn kastlik, coby zawrzić zajty dyskusyje i tyjmy swiōnzane ze ôbranymi przestrzyniami mian",
+       "invert": "Ôdwrōć zaznaczynie",
+       "tooltip-invert": "Ôznŏcz te pole, coby skryć zmiany na strōnach we ôbranyj przestrzyni mian (i swiōnzanōm z niōm inkszōm przestrzyniōm mian, jeźli je ôznaczōnŏ)",
+       "namespace_association": "Swiōnzanŏ przestrzyń mian",
+       "tooltip-namespace_association": "Ôznŏcz te pole, coby przidać strōnã dyskusyje i tymat swiōnzane ze ôbranōm przestrzyniōm mian",
        "blanknamespace": "(przodńo)",
-       "contributions": "Ajnzac {{GENDER:$1|używocza|używoczki}}",
-       "contributions-title": "Wkłod użytkowńika $1",
+       "contributions": "Wkłŏd ôd {{GENDER:$1|używŏcza|używŏczki}}",
+       "contributions-title": "Wkłŏd {{GENDER:$1|używŏcza|używŏczki}} $1",
        "mycontris": "Edycyje",
-       "contribsub2": "Lo {{GENDER:$3|używocza|używoczki}} $1 ($2)",
-       "nocontribs": "Brak pomjyńań uodpowjadajůncych tym kryterjům.",
-       "uctop": "teroźńo",
+       "anoncontribs": "Edycyje",
+       "contribsub2": "{{GENDER:$3|używŏcza|używŏczki}} $1 ($2)",
+       "nocontribs": "Brak zmian, co ôdpowiadajōm tym kryteriōm.",
+       "uctop": "terŏźnŏ",
        "month": "Do miesiōnca:",
        "year": "Do roku:",
-       "sp-contributions-newbies": "Pokoż ajnzac ino uod nowych użytkowńikůw",
+       "sp-contributions-newbies": "Pokŏż ino wkłŏd ôd nowych kōnt",
        "sp-contributions-newbies-sub": "Dlo nowych užytkowńikůw",
        "sp-contributions-newbies-title": "Wkłod nowych użytkowńików",
-       "sp-contributions-blocklog": "zawarća",
-       "sp-contributions-deleted": "Wyćepane sprowjyńa użytkowńika",
-       "sp-contributions-uploads": "wćepane uobrozki",
-       "sp-contributions-logs": "rejer dźołońo",
-       "sp-contributions-talk": "↓ dyskusyjo",
-       "sp-contributions-userrights": "Zařůndzańy prowami užytkowńikůw",
+       "sp-contributions-blocklog": "zawarcia",
+       "sp-contributions-deleted": "skasowany wkłŏd ôd {{GENDER:$1|używŏcza|używŏczki}}",
+       "sp-contributions-uploads": "zaladowane zbiory",
+       "sp-contributions-logs": "regest",
+       "sp-contributions-talk": "dyskusyjŏ",
+       "sp-contributions-userrights": "zarzōndzanie prawami ôd {{GENDER:$1|używŏcza|używŏczki}}",
        "sp-contributions-search": "Szukej wkładu",
-       "sp-contributions-username": "Adres IP abo mjano użytkowńika",
-       "sp-contributions-toponly": "Ukoż jyno ůostanie wersyje",
+       "sp-contributions-username": "Adresa IP abo miano używŏcza",
+       "sp-contributions-toponly": "Pokŏż ino edycyje, co sōm ôstatnimi wersyjami",
+       "sp-contributions-newonly": "Pokŏż ino edycyje, co stworziły strōny",
        "sp-contributions-submit": "Szukej",
        "whatlinkshere": "Co sam linkuje",
-       "whatlinkshere-title": "Zajty, kere linkujům na \"$1\"",
-       "whatlinkshere-page": "Zajta:",
-       "linkshere": "Nastympůjůnce zajty sóm adrésůwane do '''$1''':",
-       "nolinkshere": "Żodno zajta ńy je adrésowana do '''$2'''.",
+       "whatlinkshere-title": "Strōny, co linkujōm do „$1”",
+       "whatlinkshere-page": "Strōna:",
+       "linkshere": "Te strōny linkujōm do <strong>$2</strong>:",
+       "nolinkshere": "Żŏdnŏ strōna niy linkuje do <strong>$2</strong>.",
        "nolinkshere-ns": "Žodno zajta ńy je adresowano do '''$2''' we wybrany přestřyni mjan.",
-       "isredirect": "překerowujůnca zajta",
-       "istemplate": "doÅ\82ůnczony muster",
-       "isimage": "Link do plika",
-       "whatlinkshere-prev": "{{PLURAL:$1|popředńe|popředńe $1}}",
+       "isredirect": "strōna przekerowaniŏ",
+       "istemplate": "doÅ\82Å\8dnczynie",
+       "isimage": "link do zbioru",
+       "whatlinkshere-prev": "{{PLURAL:$1|poprzednie|poprzednie $1}}",
        "whatlinkshere-next": "{{PLURAL:$1|nastympne|nastympne $1}}",
-       "whatlinkshere-links": "← do adrésata",
-       "whatlinkshere-hideredirs": "$1 {{PLURAL:$1|punkńyńćy|punkńyńćo|puńkńyńć}}",
-       "whatlinkshere-hidetrans": "$1 {{PLURAL:$1|dokuplowańy|dokuplowańo|dokuplowań}}",
-       "whatlinkshere-hidelinks": "$1 {{PLURAL:$1|link|linki|linkůw}}",
-       "whatlinkshere-hideimages": "$1 linki ze plikůw",
-       "whatlinkshere-filters": "Filtery",
+       "whatlinkshere-links": "← linki",
+       "whatlinkshere-hideredirs": "$1 pōnkniyńcia",
+       "whatlinkshere-hidetrans": "$1 dołōnczynia",
+       "whatlinkshere-hidelinks": "$1 linki",
+       "whatlinkshere-hideimages": "$1 linki zbiorōw",
+       "whatlinkshere-filters": "Filtry",
        "blockip": "Zawrzij sprowjorza",
        "blockiptext": "Tyn formularz służy do zawjerańo sprowjyń spod uokreślůnygo adresu IP abo kůnkretnymu użytkowńikowi.\nZawjerać noleży jydyńy po to, by zapobjec wandalizmům, zgodńy ze [[{{MediaWiki:Policy-url}}|przijyntymi reglůma]].\nPodej powůd (np. umjeszczajůnc mjana zajtůw, na kerych dopuszczůno śe wandalizmu).",
        "ipaddressorusername": "Adres IP abo mjano użytkowńika",
        "ipbenableautoblock": "Zawřij uostatńi adres IP tygo užytkowńika i autůmatyčńy wšyjstke kolejne, s kerych bydźe průbowou sprowjać zajty",
        "ipbsubmit": "Zawřij uod sprowjyń tygo užytkowńika",
        "ipbother": "Ikszy czas",
-       "ipboptions": "2 godźiny:2 hours,1 dźyń:1 day,3 dńi:3 days,1 tydźyń:1 week,2 tydńe:2 weeks,1 mjeśůnc:1 month,3 mjeśůnce:3 months,6 mjeśůncůw:6 months,1 rok:1 year,nawdy:infinite",
+       "ipboptions": "2 godziny:2 hours,1 dziyń:1 day,3 dni:3 days,1 tydziyń:1 week,2 tydnie:2 weeks,1 miesiōnc:1 month,3 miesiōnce:3 months,6 miesiyncy:6 months,1 rok:1 year,na dycki:infinite",
        "ipbhidename": "Schrůń mjano użytkowńika/adres IP w rejerze zawarć, na liśće aktywnych zawarć i liśće użytkowńikůw",
        "ipbwatchuser": "Dowej pozůr na zajta uosobisto i zajta godki tygo užytkowńika",
        "ipb-change-block": "Zmjyń sztalowańa zawarća uod sprowjyń",
        "ipblocklist": "Zawarte używocze",
        "ipblocklist-legend": "Znejdź zawartygo uod sprawjyń užytkowńika",
        "ipblocklist-submit": "Šnupej",
-       "infiniteblock": "na zawše",
+       "infiniteblock": "na dycki",
        "expiringblock": "wygaso $1",
        "anononlyblock": "ino ńyzalůgowańy",
        "noautoblockblock": "autůmatyčne zawjyrańy uod sprowjyń wůuůnčůne",
        "ipblocklist-empty": "Lista zawarć je pusto.",
        "ipblocklist-no-results": "Podany adres IP abo užytkowńik ńy je zawarty uod sprowjyń.",
        "blocklink": "blokuj",
-       "unblocklink": "uodymknij",
-       "change-blocklink": "půmjyń zawarće uod sprowjyń",
+       "unblocklink": "ôdblokuj",
+       "change-blocklink": "zmiyń blokadã",
        "contribslink": "wkłŏd",
        "autoblocker": "Zawarto Ci sprowjyńo autůmatyczńy, bez tůż co używosz tygo samygo adresu IP, co używocz „[[User:$1|$1]]”.\nPowůd zawarća $1 to: „$2”",
-       "blocklogpage": "Gyszichta zawjyrańo",
-       "blocklogentry": "zawarto [[$1]], bydźe uodymkńynty: $2 $3",
-       "reblock-logentry": "{{GENDER:$2|pōmiynił|pōmiyniła}} nasztalowania zawarciŏ dlŏ [[$1]], czas zawarciŏ: $2 $3",
+       "blocklogpage": "Regest blokad",
+       "blocklogentry": "zawartŏ [[$1]], bydzie ôtwartŏ: $2 $3",
+       "reblock-logentry": "{{GENDER:$2|zmiynił|zmiyniyła}} sztelōnki zawarciŏ dlŏ [[$1]], kōniec zawarciŏ: $2 $3",
        "blocklogtext": "Půńižej znojdowo śe lista zawarć zouožůnych i zdjyntych s poščygůlnych adresůw IP.\nNa li'śće ńy mo adresůw IP, kere zawarto w sposůb autůmatyčny.\nCoby přejřeć lista uobecńy aktywnych zawarć, přyńdź na zajta [[Special:BlockList|zawartych adresůw i užytkowńikůw]].",
        "unblocklogentry": "uodymknyu $1",
        "block-log-flags-anononly": "ino anůnimowi",
-       "block-log-flags-nocreate": "tworzińy kůnta je zawarte",
+       "block-log-flags-nocreate": "tworzynie kōnta je zastawiōne",
        "block-log-flags-noautoblock": "autůmatyczne zawjerańy uod sprawjyń wyłůnczůne",
        "block-log-flags-noemail": "e-brif zawarty",
        "block-log-flags-nousertalk": "ńy może sprowjać włosnyj zajty godki",
        "ipb_cant_unblock": "Feler: Zawarće uo ID $1 ńy zostouo znejdźone. Moguo uone zostać oudymkńynte wčeśnij.",
        "ipb_blocked_as_range": "Feler: Adres IP $1 ńy zostou zawarty bezpośredńo i ńy može zostać uodymkńjynty.\nNoležy uůn do zawartygo zakresu adresůw $2. Uodymknůńć možno ino couki zakres.",
        "ip_range_invalid": "Ńypoprowny zakres adresów IP.",
-       "proxyblocker": "Zawjyrańe proxy",
+       "proxyblocker": "Blokowanie proxy",
        "proxyblockreason": "Twůj adres IP zostou zawarty, bo je to adres uotwartygo proxy.\nSprawa noležy wyjaśńić s dostawcům Internetu abo půmocům techńičnům informujůnc uo tym powažnym problymje s bezpječyństwym.",
        "sorbsreason": "Twůj adres IP znojdowo śe na liśće serwerůw open proxy w DNSBL, užywanej bez {{GRAMMAR:B.lp|{{SITENAME}}}}.",
        "sorbs_create_account_reason": "Twůj adres IP znojdowo śe na liśće serwerůw open proxy w DNSBL, užywanej bez {{GRAMMAR:B.lp|{{SITENAME}}}}.\nŃy možeš utwořić kůnta",
        "movepage-page-moved": "Zajta $1 uostoła przekludzůno ku $2.",
        "movepage-page-unmoved": "Mjana zajty $1 ńy idźe půmjyńić na $2.",
        "movepage-max-pages": "Przekludzůnych uostało $1 {{PLURAL:$1|zajta|zajty|zajtůw}}. Wjynkszyj liczby ńy idźe przekludźić automatyczńy.",
-       "movelogpage": "Przećepńynte",
+       "movelogpage": "Regest przeniysiōnych",
        "movelogpagetext": "Uoto lista zajtůw, kere uostatńo zostouy přećepane.",
        "movereason": "Czymu:",
        "revertmove": "cofej",
        "imageinvalidfilename": "Mjano plika docelowygo je felerne",
        "fix-double-redirects": "Poprow przekerowańa kere adresujům ku uoryginalnymu titlowi zajty",
        "move-leave-redirect": "Uostow przekerowańy pode dotychczasowym titlem",
-       "export": "Eksport zajtůw",
+       "export": "Eksport strōn",
        "exporttext": "Možeš wyeksportować treść i historja sprowjyń jednyj zajty abo zestawu zajtůw we formaće XML.\nWyeksportowane informacyje možna půźńij zaimportować do inkšej wiki, dźouajůncyj na uoprůgramowańu MediaWiki, kořistajůnc ze [[Special:Import|zajty importu]].\n\nWyeksportowańy wjelu zajtůw wymogo wpisańo půńižej titli zajtůw, po jednym titlu we wjeršu a uokreślyńo čy mo zostać wyeksportowano bježůnco čy wšyjstke wersyje zajty s uopisůma sprawjyń abo tyž ino bježůnca wersyjo s uopisym uostatńygo sprawjyńo.\n\nMožeš tyž užyć linku, np.[[{{#Special:Export}}/{{MediaWiki:Mainpage}}]] do zajty „[[{{MediaWiki:Mainpage}}]]”.",
        "exportcuronly": "Ino bježůnco wersyjo, bes historji",
        "exportnohistory": "----\n'''Pozůr:''' Wůuůnčůno možliwość eksportowańo peunej historii zajtůw s užyćym tygo nařyńdźa s kuli kuopotůw s wydajnośćůn",
        "allmessagescurrent": "Tekst uobecny",
        "allmessagestext": "Uoto lista wšyjstkych kůmůńikatůw systymowych dostympnych w přestřyńi mjan MedjaWiki.\nUodwjydź [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation Tuůmačyńy MediaWiki] a tyž [https://translatewiki.net translatewiki.net] kejbyś chćou učestńičyć w tuůmačyńu uoprůgramowańo MediaWiki.",
        "allmessages-not-supported-database": "Ta zajta ńy može być užyta, bez tůž co zmjynna '''$wgUseDatabaseMessages''' je wůuůnčůno.",
-       "thumbnail-more": "Zwjynksz",
+       "thumbnail-more": "Powiynksz",
        "filemissing": "Ńyma pliku.",
        "thumbnail_error": "Feler při gynerowańu mińatury: $1",
        "djvu_page_error": "Zajta DjVu poza zakresym",
        "import-upload": "Wćepej dane XML",
        "import-token-mismatch": "Straćiły śe dane ze sesyje. Prosza spróbować zaś.",
        "import-invalid-interwiki": "Ńy idźe importować s podanyj wiki.",
-       "importlogpage": "Rejer importa",
+       "importlogpage": "Regest importōw",
        "importlogpagetext": "Rejer přeprowadzůnych importůw zajtůw s inkšych serwisůw wiki.",
        "import-logentry-upload-detail": "$1 {{PLURAL:$1|wersyja|wersyje|wersyji}}",
        "import-logentry-interwiki-detail": "$1 {{PLURAL:$1|wersyja|wersyje|wersyji}} ze $2",
-       "tooltip-pt-userpage": "Mojo perzůnalno zajta",
+       "tooltip-pt-userpage": "{{GENDER:|Moja}} strōna",
        "tooltip-pt-anonuserpage": "Zajta użytkowńika do adresu IP spod kerygo sprowjosz",
-       "tooltip-pt-mytalk": "Mojo zajta dyskusyje",
+       "tooltip-pt-mytalk": "{{GENDER:|Moja}} strōna dyskusyje",
        "tooltip-pt-anontalk": "Godka użytkowńika do adresu IP spod kerygo sprowjosz",
-       "tooltip-pt-preferences": "Moje preferyncyje",
-       "tooltip-pt-watchlist": "Lista artiklůw, na kere dowosz pozůr",
-       "tooltip-pt-mycontris": "Lista {{GENDER:|moich}} edycyji",
-       "tooltip-pt-login": "Chćeli by my, cobyś śe nalogowoł, nale to ńy je powinne",
-       "tooltip-pt-logout": "Uodloguj śe ze wiki",
-       "tooltip-pt-createaccount": "Namowjůmy do stworzyńo kůnta a zalogůwańo śe, atoli ńy je to uobowjůnzkowe",
-       "tooltip-ca-talk": "Dyskusyjo uo tym artiklu",
-       "tooltip-ca-edit": "Edytuj tã zajtã",
-       "tooltip-ca-addsection": "Przidej nowy temat",
-       "tooltip-ca-viewsource": "Ta zajta je zawrzito. Mogesz uobźyrać zdrzůdłowy tekst.",
-       "tooltip-ca-history": "Storsze wersyje tyj zajty",
-       "tooltip-ca-protect": "Zawřij ta zajta",
-       "tooltip-ca-delete": "Wyćep ta zajta",
+       "tooltip-pt-preferences": "{{GENDER:|Moje}} preferyncyje",
+       "tooltip-pt-watchlist": "Lista strōn, co je ôbserwujesz",
+       "tooltip-pt-mycontris": "Lista {{GENDER:|mojich}} edycyji",
+       "tooltip-pt-login": "Rekōmyndujymy wlogowanie, ale ône niyma musowe.",
+       "tooltip-pt-logout": "Ôdloguj sie",
+       "tooltip-pt-createaccount": "Rekōmyndujymy stworzynie kōnta i wlogowanie sie, ale to niyma musowe.",
+       "tooltip-ca-talk": "Dyskusyjŏ ô strōnie",
+       "tooltip-ca-edit": "Edytuj tã strōnã",
+       "tooltip-ca-addsection": "Przidej nowõ sekcyjõ",
+       "tooltip-ca-viewsource": "Ta strōna je zawartŏ. Możesz ôglōndać jeji zdrzōdło.",
+       "tooltip-ca-history": "Starsze wersyje tyj strōny",
+       "tooltip-ca-protect": "Zawrzij tã strōnã",
+       "tooltip-ca-delete": "Skasuj tã strōnã",
        "tooltip-ca-undelete": "Prziwrůć wersyjo tyj zajty sprzed wyćepańo",
-       "tooltip-ca-move": "Przećep ta zajta kaj indzij.",
-       "tooltip-ca-watch": "Przidej artikel na pozůrlista",
-       "tooltip-ca-unwatch": "Wyciep tyn artikel ze pozůrlisty",
+       "tooltip-ca-move": "Przeniyś tã strōnã",
+       "tooltip-ca-watch": "Przidej tã strōnã do ôbserwowanych",
+       "tooltip-ca-unwatch": "Skasuj tyn artykuł ze ôbserwowanych",
        "tooltip-search": "Szukej we {{SITENAME}}",
-       "tooltip-search-go": "Pōdź na zajtã ô gynau takij titli, eli sam je",
-       "tooltip-search-fulltext": "Szukej wkludzōnygo tekstu we zajtach",
-       "tooltip-p-logo": "Przodniŏ zajta",
-       "tooltip-n-mainpage": "Przelyź na przodńo zajta",
-       "tooltip-n-mainpage-description": "Przelyź na przodńo zajta",
-       "tooltip-n-portal": "Uo projekće, co mogesz robić, kaj mogesz nolyźć informacyje",
-       "tooltip-n-currentevents": "Informacyje uo aktualnych przitrefjyńach",
-       "tooltip-n-recentchanges": "Spisek niydŏwnych pōmian we wiki",
-       "tooltip-n-randompage": "Ukoż cufalno zajta",
-       "tooltip-n-help": "Sam śe mogesz moc przewjedźeć",
-       "tooltip-t-whatlinkshere": "Ukoż zajty, kere sam linkujům",
-       "tooltip-t-recentchangeslinked": "Ńydowno půmjyńane na zajtach, do kerych ta zajta linkuje",
+       "tooltip-search-go": "Ôtwōrz strōnã ze prawie takim tytułym, jeźli ôna istniyje",
+       "tooltip-search-fulltext": "Szukej na strōnach wkludzōnego tekstu",
+       "tooltip-p-logo": "Nawiydź przodniõ strōnã",
+       "tooltip-n-mainpage": "Nawiydź przodniõ strōnã",
+       "tooltip-n-mainpage-description": "Nawiydź przodniõ strōnã",
+       "tooltip-n-portal": "Ô projekcie, co możesz zrobić, kaj szukać",
+       "tooltip-n-currentevents": "Informacyje ô terŏźnych wydarzyniach",
+       "tooltip-n-recentchanges": "Lista niydŏwnych zmian na wiki",
+       "tooltip-n-randompage": "Zaladuj losowõ strōnã",
+       "tooltip-n-help": "Miyjsce, kaj znojdziesz pōmoc",
+       "tooltip-t-whatlinkshere": "Lista wszyjskich strōn wiki, co sam linkujōm",
+       "tooltip-t-recentchangeslinked": "Ôstatnie zmiany na strōnach, co na nie ta strōna linkuje",
        "tooltip-feed-rss": "Kanau RSS do tyj zajty",
        "tooltip-feed-atom": "Kanoł Atom lo tyj zajty",
-       "tooltip-t-contributions": "Ukoż ajnzace tygo używocza",
-       "tooltip-t-emailuser": "Wyślij e-brif do tygo użytkowńika",
-       "tooltip-t-upload": "Wćepej plik na serwer",
-       "tooltip-t-specialpages": "Spisek wszyjskich szpecyjalnych zajt",
-       "tooltip-t-print": "Wersyjo do durku",
-       "tooltip-t-permalink": "Pewny link do tyj wersyje zajty",
-       "tooltip-ca-nstab-main": "Uobźyrej zajta artikla",
-       "tooltip-ca-nstab-user": "Ukoż perzůnalno zajta używocza",
+       "tooltip-t-contributions": "Pokŏż wkłŏd ôd {{GENDER:$1|tego używŏcza|tyj używŏczki}}",
+       "tooltip-t-emailuser": "Wyślij e-mail do {{GENDER:$1|tego używŏcza|tyj używŏczki}}",
+       "tooltip-t-upload": "Zaladuj zbiory",
+       "tooltip-t-specialpages": "Lista wszyjskich ekstra strōn",
+       "tooltip-t-print": "Wersyjŏ do durku",
+       "tooltip-t-permalink": "Trwały link do tyj wersyje strōny",
+       "tooltip-ca-nstab-main": "Pokŏż strōnã treści",
+       "tooltip-ca-nstab-user": "Wejzdrzij na strōnã ôd użwŏcza",
        "tooltip-ca-nstab-media": "Uobejřij zajta artikla",
-       "tooltip-ca-nstab-special": "To je ekstra zajta i niy idzie jij edytować",
-       "tooltip-ca-nstab-project": "Uobejřij zajta projektu",
-       "tooltip-ca-nstab-image": "Ukoż zajta grafiki",
-       "tooltip-ca-nstab-mediawiki": "Zoboč komunikat systymowy",
-       "tooltip-ca-nstab-template": "Uobźyrej muster",
+       "tooltip-ca-nstab-special": "To je specjalnŏ strōna i niy idzie jij edytować",
+       "tooltip-ca-nstab-project": "Pokŏż strōnã projektu",
+       "tooltip-ca-nstab-image": "Pokŏż strōnã grafiki",
+       "tooltip-ca-nstab-mediawiki": "Pokŏż kōmunikat systymowy",
+       "tooltip-ca-nstab-template": "Ôbejzdrzij muster",
        "tooltip-ca-nstab-help": "Pokŏż zajtã pōmocy",
-       "tooltip-ca-nstab-category": "Ukoż zajta kategoryje",
-       "tooltip-minoredit": "Uoznacz ta zmjana za drobno",
-       "tooltip-save": "Naszkryflej půmjyńańa",
-       "tooltip-preview": "Niźli spamiyntŏsz pōmiany pozdrzij na efekt swojij edycyje.",
-       "tooltip-diff": "Ukozuje twoje půmjyńańa we tekśće",
-       "tooltip-compareselectedversions": "Uobźyrej zmjyny mjyndzy dwůma uobranymi wersyjůma tyj zajty",
-       "tooltip-watch": "Dodej tyn artikel do pozorlisty",
+       "tooltip-ca-nstab-category": "Pokŏż strōnã kategoryje",
+       "tooltip-minoredit": "Ôznŏcz tã zmianã za małõ edycyjõ",
+       "tooltip-save": "Spamiyntej swoje zmiany",
+       "tooltip-preview": "Podyjzdrzij swoje zmiany; użyj tego przed spamiyntowaniym.",
+       "tooltip-diff": "Pokŏż zmiany zrobiōne we tekście.",
+       "tooltip-compareselectedversions": "Ôbejzdrzij rōżnice miyndzy dwōma ôbranymi wersyjami tyj strōny",
+       "tooltip-watch": "Przidej tyn artykuł do ôbserwowanych",
        "tooltip-recreate": "Wćepej nazod zajta mimo aže bůua wčeśńij wyćepano.",
        "tooltip-upload": "Rozpočyńće wćepywańa",
-       "tooltip-rollback": "\"cofej\" jednym klikniynciym rewertuje pōmianã ôd ôstatnigo używŏcza.",
-       "tooltip-undo": "\"anuluj pōmianã\" cofŏ tã edycyjõ i ôtwiyrŏ ôkno edycyje we trybie ôbziyraniŏ.\nDozwolŏ na wkludzyniy szticha we popisie pōmian.",
-       "tooltip-summary": "Krůtko popisz",
+       "tooltip-rollback": "\"Cŏfej\" jednym klikniyńciym cŏfie wszyjske zmiany ôd ôstatnigo używŏcza.",
+       "tooltip-undo": "\"Cŏfnij\" cŏfie tã edycyjõ i ôtwiyrŏ ôkno edycyje we trybie podglōndu.\nDozwolŏ na wkludzynie powodu we ôpisie.",
+       "tooltip-summary": "Wkludź krōtki ôpis",
        "anonymous": "{{PLURAL:$1|Anůńimowy użytkowńik|Anůńimowe użytkowńiki}} {{SITENAME}}",
        "siteuser": "Užytkowńik {{GRAMMAR:D.lp|{{SITENAME}}}} – $1",
        "lastmodifiedatby": "Uostatńy sprowjyńy tej zajty: $2, $1 (autor půmjyńań: $3)",
        "spambot_username": "MediaWiki – wyćepywańe spamu",
        "spam_reverting": "Přiwracańy uostatńij wersyji we kerej ńy bůuo linkůw do $1",
        "spam_blanking": "Wšyjstke wersyje zawjerouy uodnośńiki do $1. Čyščyńy zajty.",
-       "simpleantispam-label": "Filter antyspamowy.\n'''ŃY''' szrajbůj sam ńic!",
-       "pageinfo-toolboxlink": "Informacyjo uo tyj zajće",
+       "simpleantispam-label": "Kōntrola antyspamowŏ.\n<strong>NIY</strong> wpisuj sam nic!",
+       "pageinfo-title": "Informacyje ô strōnie „$1”",
+       "pageinfo-header-basic": "Podstawowe informacyje",
+       "pageinfo-header-edits": "Historyjŏ edycyji",
+       "pageinfo-header-restrictions": "Zabezpieczynie strōny",
+       "pageinfo-header-properties": "Włŏsności strōny",
+       "pageinfo-display-title": "Pokazowany tytuł",
+       "pageinfo-default-sort": "Wychodny klucz zortowaniŏ",
+       "pageinfo-length": "Dugość strōny (we bajtach)",
+       "pageinfo-article-id": "Idyntyfikatōr strōny",
+       "pageinfo-language": "Jynzyk zawartości strōny",
+       "pageinfo-content-model": "Model zawartości strōny",
+       "pageinfo-robot-policy": "Indeksowane ôd robotōw",
+       "pageinfo-robot-index": "Przizwolōne",
+       "pageinfo-robot-noindex": "Niyprzizwolōne",
+       "pageinfo-watchers": "Liczba ôbserwatorōw",
+       "pageinfo-few-watchers": "Mynij jak $1 {{PLURAL:$1|ôbserwatōr|ôbserwatorōw}}",
+       "pageinfo-redirects-name": "Liczba przekerowań do tyj strōny",
+       "pageinfo-subpages-name": "Liczba podstrōn ôd tyj strōny",
+       "pageinfo-subpages-value": "$1 ($2 {{PLURAL:$2|przekerowanie|przekerowania|przekerowań}}; $3 {{PLURAL:$3|bez przekerowaniŏ|bez przekerowań}})",
+       "pageinfo-firstuser": "Kreatōr strōny",
+       "pageinfo-firsttime": "Data stworzyniŏ strōny",
+       "pageinfo-lastuser": "Ôstatni edytōr",
+       "pageinfo-lasttime": "Data ôstatnij edycyje",
+       "pageinfo-edits": "Połnŏ liczba edycyji",
+       "pageinfo-authors": "Połnŏ liczba autorōw",
+       "pageinfo-recent-edits": "Liczba ôstatnich edycyji (we ôstatnich $1)",
+       "pageinfo-recent-authors": "Liczba ôstatnich autorōw",
+       "pageinfo-magic-words": "Magiczne {{PLURAL:$1|słowo|słowa}} ($1)",
+       "pageinfo-hidden-categories": "{{PLURAL:$1|Skrytŏ kategoryjŏ|Skryte kategoryje}} ($1)",
+       "pageinfo-templates": "Używan{{PLURAL:$1|y szymel|e szymle}} ($1)",
+       "pageinfo-toolboxlink": "Informacyjŏ ô strōnie",
+       "pageinfo-contentpage": "Rachowanŏ za strōna zawartości",
+       "pageinfo-contentpage-yes": "Ja",
        "markaspatrolleddiff": "uoznoč sprawjyńy kej „sprawdzůne”",
        "markaspatrolledtext": "Uoznoč tyn artikel kej „sprawdzůny”",
        "markedaspatrolled": "Sprawdzůne",
        "markedaspatrollederror": "Ńy idźe uoznačyć kej „sprawdzůne”",
        "markedaspatrollederrortext": "Muśyš wybrać wersyja coby uoznačyć jům kej „sprawdzůna”.",
        "markedaspatrollederror-noautopatrol": "Ńy moš uprawńyń wymaganych do uoznačańo swojich sprawjyń kej „sprawdzůne”.",
-       "patrol-log-page": "Dźynńik patrolowańo",
+       "patrol-log-page": "Regest patrolowaniŏ",
        "patrol-log-header": "Půniżej je dźeńńik patrolowańo zajtůw.",
        "deletedrevision": "Wyćepano popředńy wersyje $1",
        "filedeleteerror-short": "Feler při wyćepywańu plika $1",
        "mediawarning": "'''Pozůr!''' Tyn plik može zawjerać zuośliwy kod. Jak go uodymkńyš možeš zaraźić swůj systym.",
        "imagemaxsize": "Na zajtach uopisu plikůw uůgrańič rozmjar uobrazkůw do:",
        "thumbsize": "Rozmjar mińjatůrki",
-       "widthheightpage": "$1 × $2, $3 {{PLURAL:$3|zajta|zajty|zajtůw}}",
+       "widthheightpage": "$1 × $2, $3 {{PLURAL:$3|strōna|strōny|strōn}}",
        "file-info": "rozmjor plika: $1, typ MIME: $2",
-       "file-info-size": "$1 × $2 pikselůw, wjelgość plika: $3, zorta MIME: $4",
-       "file-nohires": "Wjynksze wymjyry ńy sům dostympne",
-       "svg-long-desc": "Plik SVG, nůminalńe $1 × $2 pixelůw, rozmior plika: $3",
-       "show-big-image": "Pjyrwy wymjor",
-       "show-big-image-preview": "Mjara podglůndu – $1.",
-       "show-big-image-other": "{{PLURAL:$2|Inkszo rozdźelczość|Inksze rozdźelczośći}}: $1.",
-       "show-big-image-size": "$1 x $2 pikselůw",
+       "file-info-size": "$1 × $2 pikselōw, srogość zbioru: $3, zorta MIME: $4",
+       "file-info-size-pages": "$1 × $2 pikselōw, srogość zbioru: $3, typ MIME: $4, $5 {{PLURAL:$5|strōna|strōny|strōn}}",
+       "file-nohires": "Niy ma dostympnyj srogszyj rodzielczości.",
+       "svg-long-desc": "Zbiōr SVG, nōminalnie $1 × $2 pikselōw, srogość zbioru: $3",
+       "show-big-image": "Ôryginalny zbiōr",
+       "show-big-image-preview": "Srogość tego podglōndu: $1.",
+       "show-big-image-other": "{{PLURAL:$2|Inkszŏ rozdzielczość|Inksze rozdzielczości}}: $1.",
+       "show-big-image-size": "$1 x $2 pikselōw",
        "newimages": "Galerjo nowych uobrozkůw",
        "imagelisttext": "Půnižyj na {{PLURAL:$1||posortowanyj $2}} liśće {{PLURAL:$1|znojdowo|znojdujům|znojdowo}} śe '''$1''' {{PLURAL:$1|plik|pliki|plikůw}}.",
        "newimages-summary": "Na tyj ekstra zajće prezyntowane sům uostatńo wćepńynte pliki.",
        "sp-newimages-showfrom": "pokož nowe pliki začynajůnc uod $2, $1",
        "bad_image_list": "Dane trza wćepać we formaće:\n\nJyno tajle listy (lińije, kere śe napoczynajům uod *) absztychujemy.\nPjyrszy link we lińiji muśi być linkym do zakozanygo pliku.\nDolsze linki we lińiji sům uwożane za wyjimki  – sům to mjana zajtůw, na kerych idzie użyć plik ze zakozanym mjanym.",
        "metadata": "Metadane",
-       "metadata-help": "Tyn plik mo ekstra informacyje na isto przidane uod fotoaparata abo skanera, kere bůły użite lo powstańo tygo pliku.\nEli plik był modyfikowany, dane mogům w tajli ńy być we zgodźe ze parametrůma modyfikowanego pliku.",
+       "metadata-help": "We tym zbiorze sōm ekstra informacyje pewnikym przidane ôd fotoaparatu abo skanera użytego do zrobiyniŏ abo zdigitalizowaniŏ go.\nJeźli zbiōr bōł modyfikowany, niykere informacyje mogōm niy cołkym ôdpowiadać zmodyfikowanymu zbiorowi.",
        "metadata-expand": "Pokož ščygůuy",
        "metadata-collapse": "Schowej ščygůuy",
-       "metadata-fields": "Wyszkryflůne niżyj pola EXIF bydům wyszkryflůne na zajcie plika. Inksze pola bydům mjarkowańy schrůńůne.\n* make\n* model\n* datetimeoriginal\n* exposuretime\n* fnumber\n* isospeedratings\n* focallength\n* artist\n* copyright\n* imagedescription\n* gpslatitude\n* gpslongitude\n* gpsaltitude",
+       "metadata-fields": "Metadane ôbrazōw wymianowane we tyj wiadōmości bydōm pokazowane na strōnie grafiki po zwiniyńciu tabule metadanych.\nInksze pola bydōm wychodnie skryte.\n* make\n* model\n* datetimeoriginal\n* exposuretime\n* fnumber\n* isospeedratings\n* focallength\n* artist\n* copyright\n* imagedescription\n* gpslatitude\n* gpslongitude\n* gpsaltitude",
        "namespacesall": "wszyjske",
        "monthsall": "wszyjske",
        "confirmemail": "Potwjerdź adres e-brif",
        "confirmemail_noemail": "Ńy podoužeś prawiduowygo adresa e-brifa we [[Special:Preferences|preferencyjach]].",
        "confirmemail_text": "Projekt {{SITENAME}} wymago weryfikacyji adresa e-brif před užyćym fůnkcyji kořistajůncych s počty.\nWćiś knefel půńižy coby wysúać na swůj adres list s linkym do zajty WWW.\nList bydźe zawjeroú link do zajty, w kerym zakodowany bydźe idyntyfikator.\nUodymkńij tyn link we přyglůndarce, čym potwjerdźiš, co ježeś užytkowńikym tygo adresa e-brif.",
-       "confirmemail_pending": "Kod potwjerdzyńo zostou wuaśńy do Ćebje wysúany. Jak žeś śe rejerowou ńydowno, počekej pora minut na dostarčyńy wjadůmośći ńim zaś wyśleš prośba uo wysuańy kodu.",
+       "confirmemail_pending": "Kod potwierdzyniŏ bōł prawie do Ciebie wysłany. Jak Twoje kōnto było niydŏwno zaregistrowane, to poczekej pŏrã minut na jego dotarcie, podwiela wyślesz prośbã ô nowy.",
        "confirmemail_send": "Wyślij kod potwjerdzyńo",
        "confirmemail_sent": "Wjadůmość e-brif s kodym uwjeřitelńajůncym zostoua wysuano.",
        "confirmemail_oncreate": "Link s kodym potwjerdzyńo zostou wysuany na Twůj adres e-brif.\nKod tyn ńy je wymagany coby śe sam lůgować, ale bydźeš muśou go aktywować uodmykajůnc uotřimany link we přyglůndarce ńim zouůnčyš ńykere uopcyje e-brif na wiki.",
        "confirm-purge-top": "Wyčyśćić pamjyńć podrynčnům do tyi zajty?",
        "confirm-purge-bottom": "Uodśwjyżeńy zajty wyczyśći pamjyńć podrynczno a wymuśi pokozańy jeij aktualnyj wersyji.",
        "imgmultipageprev": "← popředńo zajta",
-       "imgmultipagenext": "nostympno zajta →",
-       "imgmultigo": "Přyńdź",
-       "imgmultigoto": "Přyńdź do zajty $1",
+       "imgmultipagenext": "nastympnŏ strōna →",
+       "imgmultigo": "Idź!",
+       "imgmultigoto": "Idź do strōny $1",
        "ascending_abbrev": "rosn.",
        "descending_abbrev": "mal.",
        "table_pager_next": "Nostympno zajta",
        "watchlistedit-raw-done": "Lista zajtůw na kere dowoš pozůr zostoua uaktualńůna",
        "watchlistedit-raw-added": "Dodano {{PLURAL:$1|1 pozycyja|$1 pozycyje|$1 pozycyji}} do listy artikli na kere dowoš pozůr:",
        "watchlistedit-raw-removed": "Wyćepano {{PLURAL:$1|1 pozycyja|$1 pozycyje|$1 pozycyji}} z listy zajtůw na kere dowoš pozůr:",
-       "watchlisttools-view": "Pokož wažńijše pomjyńańo",
-       "watchlisttools-edit": "Pokož i zmjyńoj pozorliste",
-       "watchlisttools-raw": "Zmjyńoj surowo pozorlista",
-       "signature": "[[{{ns:user}}:$1|$2]] ([[{{ns:user_talk}}:$1|dyskusyjo]])",
+       "watchlisttools-clear": "Wysnŏż ôbserwowane",
+       "watchlisttools-view": "Pokŏż zmiany we ôbserwowanych",
+       "watchlisttools-edit": "Pokŏż i edytuj ôbserwowane",
+       "watchlisttools-raw": "tekstowy edytōr listy",
+       "signature": "[[{{ns:user}}:$1|$2]] ([[{{ns:user_talk}}:$1|dyskusyjŏ]])",
        "duplicate-defaultsort": "Pozůr: Zmjarkowanym kluczym sortowańo bydźe \"$2\" a zastůmpi uůn zawczasu używany klucz \"$1\".",
        "version": "Wersjo",
        "version-extensions": "Zainstalowane rozšeřyńa",
        "version-hook-name": "Mjano haka (ang. hook name)",
        "version-hook-subscribedby": "Zapotřebowany bez",
        "version-version": "($1)",
-       "version-license": "Licencjo",
+       "version-license": "Licyncyjŏ MediaWiki",
        "version-software": "Zainstalowane uoprůgramowańy",
        "version-software-product": "Mjano",
        "version-software-version": "Wersjo",
+       "redirect": "Przekerowanie podle zbioru, używŏcza, strōny, wersyje, abo idyntyfikatora regestu.",
+       "redirect-summary": "Ta specjalnŏ strōna przekerowuje do: zbioru (ze podanym mianym), strōny (ze podanym numerym wersyje abo idyntyfikatorym strōny), strōny używŏcza (ze podanym idyntyfikatorym numerycznym) abo do regestu (ze podanym numerym akcyje). Spusōb użyciŏ:\n[[{{#Special:Redirect}}/file/Example.jpg]], [[{{#Special:Redirect}}/revision/328429]], [[{{#Special:Redirect}}/user/101]] abo [[{{#Special:Redirect}}/logid/186]].",
+       "redirect-submit": "Idź",
+       "redirect-lookup": "Znojdź:",
+       "redirect-value": "Werta:",
+       "redirect-user": "ID używŏcza",
+       "redirect-page": "Idyntyfikatōr strōny",
+       "redirect-revision": "Wersyjŏ strōny",
+       "redirect-file": "Miano zbioru",
        "fileduplicatesearch": "Šnupej za duplikatym plika",
        "fileduplicatesearch-summary": "Šnupej za duplikatůma plika na podstawje wartośći fůnkcyji skrůtu.",
        "fileduplicatesearch-filename": "Mjano pliku:",
        "fileduplicatesearch-info": "$1 × $2 pikseli<br />Wjelgość plika: $3<br />Typ MIME: $4",
        "fileduplicatesearch-result-1": "Ńy ma duplikatu pliku „$1”.",
        "fileduplicatesearch-result-n": "We {{GRAMMAR:MS.lp|{{SITENAME}}}} {{PLURAL:$2|je dodatkowo kopia|sům $2 dodatkowe kopje|je $2 dodatkowych kopii}} plika „$1”.",
-       "specialpages": "Szpecjalne zajty",
+       "specialpages": "Ekstra strōny",
        "specialpages-note-restricted": "* Ekstra zajty uogůlńy dostympne.\n* <strong class=\"mw-specialpagerestricted\">Ekstra zajty do kerych dostymp je uograńiczůny.</strong>",
        "specialpages-group-maintenance": "Raporty kůnserwacyjne",
        "specialpages-group-other": "Inkše ekstra zajty",
        "blankpage": "Pusto zajta",
        "intentionallyblankpage": "Ta zajta nauůmyślńy uostoua śe pusto",
        "external_image_whitelist": "#Leave this line exactly as it is<pre>\n#Wstow půńiżyj tajle wyrażyń regularnych (jyno to, co znojduje śe mjyndzy //)\n#Wyrażyńa te uostanům przipasowane ku URL-ům zewnyntrznym (bezpostrzredńo linkowanych) grafik\n#Dopasowane URL-e zostanům wyśwjetlůne kej grafiki, w przećiwnym raźe bydźe pokozany yno link ku grafice\n#Lińje kere s anfanga majům # sům traktowane kej kůmyntorze\n\n#Put all regex fragments above this line. Leave this line exactly as it is</pre>",
-       "tag-filter": "Filter [[Special:Tags|tagůw]]",
-       "tag-list-wrapper": "[[Special:Tags|{{PLURAL:$1|Uoznaczyńe|Uoznaczyńo}}]]: $2",
-       "diff-form": "'''formulař'''",
-       "logentry-delete-delete": "$1 {{GENDER:$2|wyćepoł|wyćepała}} zajta $3",
+       "tag-filter": "Filter [[Special:Tags|tagōw]]",
+       "tag-list-wrapper": "[[Special:Tags|{{PLURAL:$1|Ôznaczynie|Ôznaczynia}}]]: $2",
+       "tags-active-yes": "Ja",
+       "tags-active-no": "Niy",
+       "tags-hitcount": "$1 {{PLURAL:$1|zmiana|zmiany|zmian}}",
+       "diff-form": "Rōżnice",
+       "logentry-delete-delete": "$1 {{GENDER:$2|skasowoł|skasowała}} strōnã $3",
+       "logentry-delete-restore": "$1 {{GENDER:$2|prziwrōciōł|prziwrōciyła}} strōnã $3",
+       "logentry-delete-revision": "$1 {{GENDER:$2|zmiyniōł|zmiyniyła}} widoczność {{PLURAL:$5|wersyje|$5 wersyji}} strōny $3: $4",
+       "revdelete-content-hid": "zawartość skrytŏ",
        "revdelete-restricted": "naštaluj uograničyńo do administratorůw",
        "revdelete-unrestricted": "wycofej uograničyńo do administratorůw",
-       "logentry-move-move": "$1 {{GENDER:$2|przećep|przećepła}} zajta $3 do $4",
-       "logentry-newusers-create": "Kůnto {{GENDER:$2|używocza}} $1 uostało stworzůne",
-       "logentry-upload-upload": "$1 {{GENDER:$2|posłoł|posłała}} $3",
+       "logentry-move-move": "$1 {{GENDER:$2|przeniōs|przeniosła}} strōnã $3 do $4",
+       "logentry-move-move-noredirect": "$1 {{GENDER:$2|pōnknōł|pōnkła}} strōnã $3 do $4 bez ôstawianiŏ przekerowaniŏ",
+       "logentry-move-move_redir": "$1 {{GENDER:$2|pōnknōł|pōnkła}} strōnã $3 do $4 na przekerowanie",
+       "logentry-patrol-patrol-auto": "$1 autōmatycznie {{GENDER:$2|ôznaczōł|ôznaczyła}} wersyjõ $4 strōny $3 za sprawdzonõ",
+       "logentry-newusers-create": "Kōnto ôd {{GENDER:$2|używŏcza|używŏczki}} $1 ôstało stworzōne",
+       "logentry-newusers-autocreate": "Kōnto $1 było stworzōne autōmatycznie",
+       "logentry-upload-upload": "$1 {{GENDER:$2|przisłoł|przisłała}} $3",
+       "logentry-upload-overwrite": "$1 {{GENDER:$2|zaladowoł|zaladowała}} nowõ wersyjõ $3",
        "rightsnone": "podstawowo",
        "searchsuggest-search": "Szukej we {{SITENAME}}",
-       "expand_templates_ok": "OK"
+       "duration-days": "$1 {{PLURAL:$1|dziyń|dni}}",
+       "expand_templates_ok": "OK",
+       "randomrootpage": "Losowŏ strōna (bez podstrōn)"
 }
index f50c18c..c639a58 100644 (file)
@@ -73,6 +73,7 @@
        "tog-norollbackdiff": "రోల్‌బ్యాక్ చేసాక తేడాలు చూపించవద్దు",
        "tog-useeditwarning": "ఏదైనా పేజీని నేను వదిలివెళ్తున్నప్పుడు దానిలో భద్రపరచని మార్పులు ఉంటే నన్ను హెచ్చరించు",
        "tog-prefershttps": "లాగిన్ అయి ఉన్నప్పుడెల్లా భద్ర కనెక్షనునే వాడు",
+       "tog-showrollbackconfirmation": "రోల్‌బ్యాక్ లింకును నొక్కినపుడు నిర్ధారించుకునే సందేశాన్ని చూపించు",
        "underline-always": "ఎల్లప్పుడూ",
        "underline-never": "ఎప్పటికీ వద్దు",
        "underline-default": "అలంకారపు లేదా విహారిణి అప్రమేయం",
        "index-category": "సూచీకరించిన పేజీలు",
        "noindex-category": "సూచీకరించని పేజీలు",
        "broken-file-category": "తెగిపోయిన ఫైలులింకులు గల పేజీలు",
+       "categoryviewer-pagedlinks": "($1) ($2)",
+       "category-header-numerals": "$1–$2",
        "about": "గురించి",
        "article": "విషయపు పేజీ",
        "newwindow": "(కొత్త విండోలో వస్తుంది)",
        "versionrequired": "మీడియావికీ సాఫ్టువేరు వెర్షను $1 కావాలి",
        "versionrequiredtext": "ఈ పేజీని వాడటానికి మీకు మీడియావికీ సాఫ్టువేరు వెర్షను $1 కావాలి. [[Special:Version|వెర్షను పేజీ]]ని చూడండి.",
        "ok": "సరే",
+       "pagetitle": "$1 - {{SITENAME}}",
+       "pagetitle-view-mainpage": "{{SITENAME}}",
+       "backlinksubtitle": "← $1",
        "retrievedfrom": "\"$1\" నుండి వెలికితీశారు",
        "youhavenewmessages": "మీకు $1 ఉన్నాయి ($2).",
        "youhavenewmessagesfromusers": "{{PLURAL:$4|మీకు}} {{PLURAL:$3|మరో వాడుకరి|$3 వాడుకరుల}} నుండి  $1 ($2).",
        "page-rss-feed": "\"$1\" RSS ఫీడు",
        "page-atom-feed": "\"$1\" ఆటమ్ ఫీడు",
        "feed-atom": "యాటమ్",
+       "feed-rss": "RSS",
        "red-link-title": "$1 (పుట లేదు)",
        "sort-descending": "అవరోహణ క్రమంలో అమర్చు",
        "sort-ascending": "ఆరోహణ క్రమంలో అమర్చు",
        "virus-scanfailed": "స్కాన్ విఫలమైంది (సంకేతం $1)",
        "virus-unknownscanner": "అజ్ఞాత యాంటీవైరస్:",
        "logouttext": "<strong>ఇప్పుడు మీరు లాగౌటయ్యారు.</strong>\n\nఅయితే, ఓ గమనిక.. మీ విహారిణిలోని కోశాన్ని ఖాళీ చేసేవరకూ కొన్ని పేజీలు మీరింకా లాగినై ఉన్నట్లుగానే చూపించవచ్చు.",
+       "logging-out-notify": "మిమ్మల్ని లాగౌటు చేస్తున్నాం, ఆగండి.",
        "logout-failed": "ఇప్పుడు లాగౌట్ అవలేరు: $1",
        "cannotlogoutnow-title": "ఇప్పుడు లాగౌట్ అవలేరు",
        "cannotlogoutnow-text": "$1 ను వాడుతూండగా లాగౌట్ అవలేరు.",
        "nocookiesnew": "ఖాతాని సృష్టించాం, కానీ మీరు ఇంకా లోనికి ప్రవేశించలేదు.\nవాడుకరుల ప్రవేశానికి {{SITENAME}} కూకీలను వాడుతుంది.\nమీరు కూకీలని అచేతనం చేసివున్నారు.\nదయచేసి వాటిని చేతనంచేసి, మీ కొత్త వాడుకరి పేరు, సంకేతపదాలతో లోనికి ప్రవేశించండి.",
        "nocookieslogin": "వాడుకరుల ప్రవేశానికై {{SITENAME}} కూకీలను వాడుతుంది.\nమీరు కుకీలని అచేతనం చేసివున్నారు.\nవాటిని చేతనంచేసి ప్రయత్నించండి.",
        "nocookiesfornew": "మూలాన్ని కనుక్కోలేకపోయాం కాబట్టి, ఈ వాడుకరి ఖాతాను సృష్టించలేకపోయాం.\nమీ కంప్యూటర్లో కూకీలు చేతనమై ఉన్నాయని నిశ్చయించుకొని, ఈ పేజీని తిరిగి లోడు చేసి, మళ్ళీ ప్రయత్నించండి.",
+       "nocookiesforlogin": "{{int:nocookieslogin}}",
        "createacct-loginerror": "ఖాతా విజయవంతంగా సృష్టించబడింది, కానీ ఆటోమాటిగ్గా లాగిన్ అవలేరు.  స్వయంగా మీరే [[Special:UserLogin|లాగినవండి]].",
        "noname": "మీరు సరైన వాడుకరి పేరు ఇవ్వలేదు.",
        "loginsuccesstitle": "లాగినయ్యారు",
        "user-mail-no-body": "ఈమెయిలును ఖాళీగానో, మరీ తక్కువ విషయంతోనో పంపేందుకు ప్రయత్నించారు.",
        "changepassword": "సంకేతపదాన్ని మార్చండి",
        "resetpass_announce": "లాగిన్ను పూర్తిచేసేందుకు, తప్పనిసరిగా కొత్త సంకేతపదాన్ని ఇవ్వాలి:",
+       "resetpass_text": "<!-- ఇక్కడ పాఠ్యం చేర్చండి  -->",
        "resetpass_header": "ఖాతా సంకేతపదం మార్పు",
        "oldpassword": "పాత సంకేతపదం:",
        "newpassword": "కొత్త సంకేతపదం:",
        "headline_tip": "2వ స్థాయి శీర్షిక",
        "nowiki_sample": "ఫార్మాటు చేయని పాఠ్యాన్ని ఇక్కడ చేర్చండి",
        "nowiki_tip": "వికీ ఫార్మాటును పట్టించుకోవద్దు",
+       "image_sample": "Example.jpg",
        "image_tip": "ఇమిడ్చిన ఫైలు",
+       "media_sample": "Example.ogg",
        "media_tip": "దస్త్రపు లంకె",
        "sig_tip": "సమయంతో సహా మీ సంతకం",
        "hr_tip": "అడ్డగీత (అరుదుగా వాడండి)",
        "template-protected": "(సంరక్షితం)",
        "template-semiprotected": "(సెమీ-రక్షణలో ఉంది)",
        "hiddencategories": "ఈ పేజీ {{PLURAL:$1|ఒక దాచిన వర్గంలో|$1 దాచిన వర్గాల్లో}} ఉంది:",
+       "edittools-upload": "-",
        "nocreatetext": "{{SITENAME}}లో కొత్త పేజీలు సృష్టించడాన్ని నియంత్రించారు.\nమీరు వెనక్కి వెళ్ళి వేరే పేజీలు మార్చవచ్చు, లేదా [[Special:UserLogin|లోనికి ప్రవేశించండి లేదా ఖాతా సృష్టించుకోండి]].",
        "nocreate-loggedin": "కొత్త పేజీలను సృష్టించేందుకు మీకు అనుమతి లేదు.",
        "sectioneditnotsupported-title": "విభాగపు దిద్దుబాట్లకు తోడ్పాటు లేదు",
        "content-not-allowed-here": "స్లాట్ \"$3\" లో [[:$2]] పేజీలో పాఠ్యం \"$1\" కి అనుమతి లేదు",
        "editwarning-warning": "ఈ పేజీని వదిలివెళ్ళడం వల్ల మీరు చేసిన మార్పులను కోల్పోయే అవకాశం ఉంది.\nమీరు లాగిన్ అయివుంటే, ఈ హెచ్చరికని మీ అభిరుచులలోని \"{{int:prefs-editing}}\"  విభాగంలో అచేతనం చేసుకోవచ్చు.",
        "editpage-invalidcontentmodel-title": "ఈ కంటెంటు మోడలుకు మద్దతు లేదు",
+       "editpage-invalidcontentmodel-text": "\"$1\" అనే కంటెంటు మోడలుకు మద్దతు లేదు.",
        "editpage-notsupportedcontentformat-title": "పాఠ్యపు ఆకృతికి మద్దతు లేదు",
        "editpage-notsupportedcontentformat-text": "$2 పాఠ్యపు మోడల్, పాఠ్యపు ఆకృతి $1 కి మద్దతు ఇవ్వదు",
        "slot-name-main": "ప్రధాన",
        "content-model-text": "సాదా పాఠ్యం",
        "content-model-javascript": "జావాస్క్రిప్ట్",
        "content-model-css": "CSS",
+       "content-model-json": "JSON",
        "content-json-empty-object": "ఖాళీ అంశం",
        "content-json-empty-array": "ఖాళీ అరే",
        "duplicate-args-warning": "<strong>హెచ్చరిక:</strong> [[:$1]], \"$3\" పరామితికి ఒకటి కంటే ఎక్కువ విలువలు ఇచ్చి [[:$2]] ను పిలుస్తోంది. చిట్టచివరిగా ఇచ్చిన విలువను మాత్రమే వాడుతాం.",
        "mergehistory-comment": "[[:$1]]ని [[:$2]] లోనికి విలీనం చేసారు: $3",
        "mergehistory-same-destination": "మూల, గమ్యస్థాన పేజీలు ఒకటే కాకూడదు",
        "mergehistory-reason": "కారణం:",
+       "mergehistory-revisionrow": "$1 ($2) $3 . . $4 $5 $6",
        "mergelog": "విలీనాల చిట్టా",
        "revertmerge": "విలీనాన్ని రద్దుచెయ్యి",
        "mergelogpagetext": "ఒక పేజీ చరితాన్ని మరో పేజీ చరితం లోకి ఇటీవల చేసిన విలీనాల జాబితా ఇది.",
        "youremail": "ఈమెయిలు:",
        "username": "{{GENDER:$1|వాడుకరి పేరు}}:",
        "prefs-memberingroups": "ఈ {{PLURAL:$1|గుంపులో|గుంపులలో}} {{GENDER:$2|సభ్యుడు|సభ్యురాలు}}:",
+       "prefs-memberingroups-type": "$1",
        "group-membership-link-with-expiry": "$1 ($2 వరకు)",
        "prefs-registration": "నమోదైన సమయం:",
+       "prefs-registration-date-time": "$1",
        "yourrealname": "అసలు పేరు:",
        "yourlanguage": "భాష:",
        "yourvariant": "విషయపు భాషా వైవిధ్యం:",
        "prefs-advancedwatchlist": "ఉన్నత ఎంపికలు",
        "prefs-displayrc": "ప్రదర్శన ఎంపికలు",
        "prefs-displaywatchlist": "ప్రదర్శన ఎంపికలు",
+       "prefs-changesrc": "చూపించే మార్పులు",
+       "prefs-changeswatchlist": "చూపించే మార్పులు",
+       "prefs-pageswatchlist": "వీక్షించే పేజీలు",
        "prefs-tokenwatchlist": "టోకెన్",
        "prefs-diffs": "తేడాలు",
        "prefs-help-prefershttps": "ఈ అభిరుచి మీరు పైసారి లాగినైనపుడు అమలౌతుంది.",
        "saveusergroups": "{{GENDER:$1|వాడుకరి}} గుంపులను భద్రపరచు",
        "userrights-groupsmember": "సభ్యులు:",
        "userrights-groupsmember-auto": "సంభావిత సభ్యులు:",
+       "userrights-groupsmember-type": "$1",
        "userrights-groups-help": "ఈ వాడుకరి ఏయే గుంపులలో ఉండాలో మీరు మార్చవచ్చు.\n* టిక్కు పెట్టివుంటే సదరు గుంపులో ఈ వాడుకరి ఉన్నట్టు.\n* టిక్కు లేకుంటే సదరు గుంపులో ఈ వాడుకరి లేనట్టు.\n* * గుర్తు ఉంటే ఒకసారి ఆ గుంపుకు చేర్చాక మీరు తీసివేయలేరు, లేదా తీసివేసాక తిరిగి చేర్చలేరు.\n* ఈ # గుర్తు ఉంటే ఆ గుంపు కాలం తీరిపోయే సమయాన్ని పెంచగలరు; దాన్ని తగ్గించలేరు.",
        "userrights-reason": "కారణం:",
        "userrights-no-interwiki": "ఇతర వికీలలో వాడుకరి హక్కులను మార్చడానికి మీకు అనుమతి లేదు.",
        "userrights-nodatabase": "$1 అనే డేటాబేసు లేదు లేదా అది స్థానికం కాదు.",
        "userrights-changeable-col": "మీరు మార్చదగిన గుంపులు",
        "userrights-unchangeable-col": "మీరు మార్చలేని గుంపులు",
+       "userrights-irreversible-marker": "$1*",
+       "userrights-no-shorten-expiry-marker": "$1#",
        "userrights-expiry-current": "కాలంతీరే వ్యవధి $1",
        "userrights-expiry-none": "ఎన్నటికీ కాలం తీరిపోదు",
        "userrights-expiry": "కాలం తీరిపోయే వ్యవధి",
        "action-applychangetags": "మీ మార్పులతో ట్యాగులను ఆపాదించే",
        "action-deletechangetags": "డేటాబేసు నుండి ట్యాగులను తొలగించే",
        "action-purge": "ఈ పేజీని పర్జ్ చేసే",
+       "action-bigdelete": "పెద్ద చరితం ఉన్న పేజీలను తొలగించు",
        "action-blockemail": "ఈమెయిలు పంపకుండా వాడుకరిని నిరోధించే",
        "action-bot": "ఆటోమాటిక్ ప్రాసెస్ లాగా భావించే",
        "action-editsemiprotected": "\"{{int:protect-level-autoconfirmed}}\" గా సంరక్షించబడ్డ పేజీలను మార్చే",
        "action-override-export-depth": "5 లింకుల లోతు వరకు ఉన్న పేజీలతో సహా, పేజీలను ఎగుమతి చేసే",
        "action-suppressredirect": "పేజీని తరలించేటపుడు పాత పేరు నుండి దారిమార్పును సృష్టించకుండా చేసే",
        "nchanges": "{{PLURAL:$1|ఒక మార్పు|$1 మార్పులు}}",
+       "ntimes": "$1×",
        "enhancedrc-since-last-visit": "{{PLURAL:$1|చివరి సందర్శన తరువాత}}, $1",
        "enhancedrc-history": "చరిత్ర",
        "recentchanges": "ఇటీవలి మార్పులు",
        "recentchanges-label-plusminus": "ఈ పేజి పరిమాణంలో  జరిగిన మార్పుల  బైట్ల సంఖ్య",
        "recentchanges-legend-heading": "<strong>సూచిక :</strong>",
        "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} ([[Special:NewPages|కొత్త పేజీల జాబితా]]ను కూడా చూడండి)",
+       "recentchanges-legend-plusminus": "(<em>±123</em>)",
        "recentchanges-submit": "చూపించు",
        "rcfilters-tag-remove": "'$1'ను తీసివెయ్యి",
        "rcfilters-legend-heading": "<strong>సంక్షేపాల (ఎబ్రీవియేషన్లు) జాబితా:</strong>",
        "rcfilters-clear-all-filters": "వడపోతకాలన్నింటినీ తుడిచెయ్యి",
        "rcfilters-show-new-changes": "$1 నుండి జరిగిన సరికొత్త మార్పులను చూడండి",
        "rcfilters-search-placeholder": "మార్పులను వడకట్టండి (మెనూను వాడండి లేదా వడపోత పేరు కోసం వెతకండి)",
+       "rcfilters-search-placeholder-mobile": "వడపోతలు",
        "rcfilters-invalid-filter": "తప్పు వడపోతకం",
        "rcfilters-empty-filter": "చేతనంగా ఉన్న వడపోతకాలేమీ లేవు. మార్పుచేర్పు లన్నిటినీ చూపించాం.",
        "rcfilters-filterlist-title": "వడపోతలు",
        "rcfilters-filter-logactions-label": "చిట్టాల్లోకి చేరిన కార్యకలాపాలు",
        "rcfilters-filter-logactions-description": "నిర్వాహక పనులు, ఖాతాల సృష్టి, పేజీ తొలగింపులు, ఎక్కింపులు...",
        "rcfilters-hideminor-conflicts-typeofchange": "కొన్ని రకాల మార్పులను \"చిన్న\" మార్పులుగా సూచించ జాలరు. అంచేత ఈ వడపోత కింది మార్పు రకాల వడపోతలతో ఘర్షిస్తోంది: $1",
+       "rcfilters-typeofchange-conflicts-hideminor": "ఈ రకపు వడపోత \"చిన్న మార్పుల\" వడపోతతో ఘర్షణ పడుతుంది. కొన్ని రకాల మార్పులను \"చిన్న\" అని సూచించలేం.",
        "rcfilters-filtergroup-lastrevision": "ఇటీవలి కూర్పులు",
        "rcfilters-filter-lastrevision-label": "ఇటీవలి కూర్పు",
        "rcfilters-filter-lastrevision-description": "పేజీలో ఇటీవల జరిగిన చిట్టచివరి మార్పు.",
        "rcfilters-filter-showlinkedto-label": "ఓ పేజీ నుండి లింకై ఉన్న పేజీల్లో జరిగిన మార్పులను చూపించు",
        "rcfilters-filter-showlinkedto-option-label": "ఎంచుకున్న పేజీకి <strong>లింకైన పేజీలు</strong>",
        "rcfilters-target-page-placeholder": "పేజీ (లేదా వర్గం) పేరు ఇవ్వండి",
+       "rcfilters-allcontents-label": "కంటెంటులన్నీ",
+       "rcfilters-alldiscussions-label": "చర్చలన్నీ",
        "rcnotefrom": "<strong>$3, $4</strong> తరువాత జరిగిన {{PLURAL:$5|మార్పు|మార్పులు}} కింద ఇచ్చాం (<strong>$1</strong> దాకా చూపించాం).",
        "rclistfromreset": "తేదీ ఎంపికను రీసెట్ చెయ్యి",
        "rclistfrom": "$3, $2 తో మొదలుపెట్టి ఆ తరువాత జరిగిన మార్పులను చూపించు",
        "minoreditletter": "చి",
        "newpageletter": "కొ",
        "boteditletter": "బా",
+       "unpatrolledletter": "!",
+       "rc-change-size": "$1",
        "rc-change-size-new": "మార్పు తర్వాత $1 {{PLURAL:$1|బైటు|బైట్లు}}",
        "newsectionsummary": "/* $1 */ కొత్త విభాగం",
        "rc-enhanced-expand": "వివరాలను చూపించు",
        "recentchangeslinked-page": "పేజీ పేరు:",
        "recentchangeslinked-to": "లేదంటే, ఇచ్చిన పేజీకి లింకయివున్న పేజీలలో జరిగిన మార్పులను చూపించు",
        "recentchanges-page-added-to-category": "[[:$1]] ను వర్గానికి చేర్చాం",
-       "recentchanges-page-added-to-category-bundled": "[[:$1]] à°µà°°à±\8dà°\97ానిà°\95à°¿ à°\9aà±\87à°°à±\8dà°\9aబడిà°\82ది, [[Special:WhatLinksHere/$1|à°\88 à°ªà±\87à°\9cà±\80 à°\87తర à°ªà±\87à°\9cà±\80à°²à±\8dà°²à±\8b à°\9aà±\87à°°à±\8dà°\9aబడింది]]",
+       "recentchanges-page-added-to-category-bundled": "[[:$1]] à°µà°°à±\8dà°\97ానిà°\95à°¿ à°\9aà±\87à°°à±\8dà°\9aారà±\81, [[Special:WhatLinksHere/$1|à°\88 à°ªà±\87à°\9cà±\80 à°\87తర à°ªà±\87à°\9cà±\80à°²à±\8dà°²à±\8b à°\9aà±\87à°°à°¿ à°\89ంది]]",
        "recentchanges-page-removed-from-category": "[[:$1]] వర్గం నుండి తీసివేయబడింది",
-       "recentchanges-page-removed-from-category-bundled": "[[:$1]] à°µà°°à±\8dà°\97à°\82 à°¨à±\81à°\82à°¡à°¿ à°¤à±\80సివà±\87యబడిà°\82ది, [[Special:WhatLinksHere/$1|ఈ పేజీ ఇతర పేజీల్లో చేర్చబడింది]]",
+       "recentchanges-page-removed-from-category-bundled": "[[:$1]] à°µà°°à±\8dà°\97à°\82 à°¨à±\81à°\82à°¡à°¿ à°¤à±\80సివà±\87సారà±\81, [[Special:WhatLinksHere/$1|ఈ పేజీ ఇతర పేజీల్లో చేర్చబడింది]]",
        "autochange-username": "MediaWiki ఆటోమాటిక్ మార్పు",
        "upload": "దస్త్రపు ఎక్కింపు",
        "uploadbtn": "దస్త్రాన్ని ఎక్కించు",
        "uploaddisabledtext": "ఫైళ్ళ ఎక్కింపులను అచేతనం చేసారు.",
        "php-uploaddisabledtext": "PHPలో ఫైలు ఎక్కింపులు అచేతనమై ఉన్నాయి.\nదయచేసి file_uploads అమరికని చూడండి.",
        "uploadscripted": "ఈ ఫైల్లో HTML కోడు గానీ స్క్రిప్టు కోడు గానీ ఉంది. వెబ్ బ్రౌజరు దాన్ని పొరపాటుగా అనువదించే అవకాశం ఉంది.",
+       "upload-scripted-dtd": "అప్రామాణిక DTD డిక్లరేషన్ను కలిగి ఉన్న SVG ఫైళ్ళను అప్‌లోడు చెయ్యలేరు.",
        "uploadscriptednamespace": "ఈ SVG ఫైలులోని పేరుబరి \"<nowiki>$1</nowiki>\" చెల్లనిది",
        "uploadinvalidxml": "ఎక్కించిన ఫైలులోని XML ను పార్సు చెయ్యలేకపోయాం.",
        "uploadvirus": "ఈ ఫైలులో వైరస్‌ ఉంది! వివరాలు: $1",
        "apisandbox-add-multi": "చేర్చు",
        "apisandbox-results": "ఫలితాలు",
        "apisandbox-request-url-label": "అభ్యర్థన URL:",
+       "apisandbox-request-format-json-label": "JSON",
        "apisandbox-request-time": "అభ్యర్ధన సమయం: {{PLURAL:$1|$1 మి.సె.}}",
        "apisandbox-continue": "కొనసాగించు",
        "apisandbox-continue-clear": "తుడిచివేయి",
        "apisandbox-multivalue-all-values": "$1 (అన్ని విలువలు)",
        "booksources": "పుస్తక మూలాలు",
        "booksources-search-legend": "పుస్తక మూలాల కోసం వెతుకు",
+       "booksources-isbn": "ISBN:",
        "booksources-search": "వెతుకు",
        "booksources-text": "కొత్త, పాత పుస్తకాలు అమ్మే ఇతర సైట్లకు లింకులు కింద ఇచ్చాం. మీరు వెతికే పుస్తకాలకు సంబంధించిన మరింత సమాచారం కూడా అక్కడ దొరకొచ్చు:",
        "booksources-invalid-isbn": "మీరిచ్చిన ISBN సరైనదిగా అనిపించుటలేదు; అసలు మూలాన్నుండి కాపీ చేయడంలో పొరపాట్లున్నాయేమో చూసుకోండి.",
        "listgrouprights-rights": "హక్కులు",
        "listgrouprights-helppage": "Help:గుంపు హక్కులు",
        "listgrouprights-members": "(సభ్యుల జాబితా)",
+       "listgrouprights-right-display": "<span class=\"listgrouprights-granted\">$1 <code>($2)</code></span>",
+       "listgrouprights-right-revoked": "<span class=\"listgrouprights-revoked\">$1 <code>($2)</code></span>",
        "listgrouprights-addgroup": "{{PLURAL:$2|గుంపుని|గుంపులను}} చేర్చగలరు: $1",
        "listgrouprights-removegroup": "{{PLURAL:$2|గుంపుని|గుంపులను}} తొలగించగలరు: $1",
        "listgrouprights-addgroup-all": "అన్ని గుంపులను చేర్చగలరు",
        "listgrants": "గ్రాంట్లు",
        "listgrants-grant": "గ్రాంటు",
        "listgrants-rights": "హక్కులు",
+       "listgrants-grant-display": "$1 <code>($2)</code>",
        "trackingcategories": "పహారా కాయు వర్గాలు",
        "trackingcategories-msg": "పహారా కాయు వర్గము",
        "trackingcategories-name": "సందేశం పేరు",
        "emailuserfooter": "ఈ ఈమెయిలును  $1, {{GENDER:$2|$2}} కు {{SITENAME}} లోని \"{{int:emailuser}}\" ఫంక్షను ద్వారా {{GENDER:$1|పంపించారు}}. {{GENDER:$2|మీరు}} ఈ ఈమెయిలుకు జవాబు పంపిస్తే, {{GENDER:$2|మీ}} మెయిలును నేరుగా {{GENDER:$1|ఒరిజినల్ సెండరుకు}} పంపిస్తాం. దీనితో, {{GENDER:$2|మీ}} ఈమెయిలు అడ్రసు {{GENDER:$1|వారికి}} తెలిసిపోతుంది.",
        "usermessage-summary": "వ్యవస్థ సందేశాన్ని వదిలివేస్తున్నాం.",
        "usermessage-editor": "వ్యవస్థ సందేశకులు",
+       "usermessage-template": "MediaWiki:UserMessage",
        "watchlist": "వీక్షణ జాబితా",
        "mywatchlist": "వీక్షణ జాబితా",
        "watchlistfor2": "$1 కొరకు $2",
        "deleting-backlinks-warning": "<strong>హెచ్చరిక:</strong> మీరు తొలగించబోతున్న పేజీకి [[Special:WhatLinksHere/{{FULLPAGENAME}}|ఇతర పేజీల]] నుండి లింకులు ఉన్నాయి. లేదా ఇతర పేజీల్లో అది ట్రాన్స్‍క్లూడు అవుతోంది.",
        "deleting-subpages-warning": "<strong>హెచ్చరిక:</strong> మీరు తొలగించబోతున్న పేజీకి [[Special:PrefixIndex/{{FULLPAGENAME}}/|{{PLURAL:$1|ఒక ఉపపేజీ ఉంది|$1 ఉపపేజీలున్నాయి|51=50 కి పైగా ఉపపేజీలున్నాయి}}]].",
        "rollback": "దిద్దుబాట్లను రద్దుచేయి",
+       "rollback-confirmation-confirm": "నిర్ధారించండి:",
+       "rollback-confirmation-yes": "రోల్‌బ్యాక్ చెయ్యి",
        "rollback-confirmation-no": "రద్దుచేయి",
        "rollbacklink": "రద్దుచేయి",
        "rollbacklinkcount": "$1 {{PLURAL:$1|మార్పును|మార్పులను}} రద్దుచేయి",
        "protect-fallback": "\"$1\" అనుమతి ఉన్న వాడుకరులను మాత్రమే అనుమతించు",
        "protect-level-autoconfirmed": "స్వయన్నిర్ధారిత వాడుకరులను మాత్రమే అనుమతించు",
        "protect-level-sysop": "నిర్వాహకులను మాత్రమే అనుమతించు",
+       "protect-summary-desc": "[$1=$2] ($3)",
        "protect-summary-cascade": "కాస్కేడింగు",
        "protect-expiring": "$1 (UTC)న కాలం చెల్లుతుంది",
        "protect-expiring-local": "$1న కాలం చెల్లుతుంది",
        "undelete-error-long": "ఫైలు $1 తొలగింపును రద్దు పరచడంలో లోపాలు దొర్లాయి",
        "undelete-show-file-confirm": "$2 నాడు $3 సమయాన ఉన్న \"<nowiki>$1</nowiki>\" ఫైలు యొక్క తొలగించిన కూర్పుని మీరు నిజంగానే చూడాలనుకుంటున్నారా?",
        "undelete-show-file-submit": "అవును",
+       "undelete-revision-row2": "$1 ($2) $3 . . $4 $5 $6 $7 $8",
        "namespace": "పేరుబరి:",
        "invert": "ఎంపికను తిరగవెయ్యి",
        "tooltip-invert": "ఎంచుకున్న పేరుబరి (చెక్ చేసి ఉంటే అనుబంధ పేరుబరి కూడా) లోని పేజీల్లో జరిగిన మార్పులను దాచేందుకు ఈ పెట్టెను చెక్ చెయ్యండి",
        "mycontris": "నా మార్పులు",
        "anoncontribs": "మార్పుచేర్పులు",
        "contribsub2": "{{GENDER:$3|$1}} ($2) కొరకు",
+       "contributions-subtitle": "{{GENDER:$3|$1}} కొరకు",
        "contributions-userdoesnotexist": "వాడుకరి ఖాతా \"$1\" నమోదుకాలేదు.",
+       "negative-namespace-not-supported": "నెగటివ్ విలువలున్న పేరుబరులకు మద్దతు లేదు.",
        "nocontribs": "ఈ విధమైన మార్పులేమీ దొరకలేదు.",
        "uctop": "ప్రస్తుత",
        "month": "ఈ నెల నుండి (అంతకు ముందువి):",
        "ipb-confirm": "నిరోధాన్ని ధృవపరచండి",
        "ipb-sitewide": "సైట్ వ్యాప్తంగా",
        "ipb-partial": "పాక్షికం",
+       "ipb-partial-help": "ప్రత్యేకించిన పేజీలు లేదా పేరుబరులు.",
        "ipb-pages-label": "పేజీలు",
+       "ipb-namespaces-label": "పేరుబరులు",
        "badipaddress": "సరైన ఐ.పి. అడ్రసు కాదు",
        "blockipsuccesssub": "నిరోధం విజయవంతం అయింది",
        "blockipsuccesstext": "[[Special:Contributions/$1|$1]] నిరోధించబడింది.<br />\nనిరోధాల సమీక్ష కొరకు [[Special:BlockList|నిరోధాల జాబితా]] చూడండి.",
index b83bc05..251b62c 100644 (file)
        "changeemail-submit": "Baguhin ang e-liham",
        "changeemail-throttled": "Masyadong madami ang kamakailan lamang mong pagsubok sa pag-login.\nMaghintay po muna ng $1 bago subukan uli.",
        "resettokens": "I-reset ang mga token o susi",
+       "resettokens-token-label": "$1 (kasalukuyang halaga: $2)",
        "bold_sample": "Makapal na panitik",
        "bold_tip": "Makapal na panitik",
        "italic_sample": "Nakahilig na panitik",
        "listfiles_size": "Sukat",
        "listfiles_description": "Paglalarawan",
        "listfiles_count": "Mga bersiyon",
+       "listfiles-latestversion": "Kasalukuyang bersiyon",
        "listfiles-latestversion-yes": "Oo",
        "listfiles-latestversion-no": "Hindi",
        "file-anchor-link": "File",
        "apisandbox-request-time": "Oras ng paghiling: $1",
        "apisandbox-continue": "Ipagpatuloy",
        "apisandbox-continue-clear": "Burado",
+       "apisandbox-multivalue-all-namespaces": "$1 (Lahat ng ngalan-espasyo)",
        "booksources": "Mga mapagkukunang aklat",
        "booksources-search-legend": "Maghanap ng mapagkukunang aklat",
        "booksources-isbn": "ISBN:",
        "protect-default": "Pahintulutan ang lahat ng mga tagagamit",
        "protect-fallback": "Pahintulutan ang mga tagagamit lamang na may pahintulot na \"$1\"",
        "protect-level-autoconfirmed": "Hadlangan ang bago at hindi nagpapatalang mga tagagamit",
-       "protect-level-sysop": "Mga tagapangasiwa (''sysop'') lamang",
+       "protect-level-sysop": "Pahintulutan lamang ang mga tagapangasiwa (''sysop'')",
        "protect-summary-cascade": "baita-baitang",
        "protect-expiring": "mawawalan ng bisa sa $1 (UTC)",
        "protect-expiring-local": "magtatapos sa $1",
        "logentry-newusers-autocreate": "Automatikong {{GENDER:$2|inilikha}} ang account ng tagagamit na $1",
        "logentry-upload-upload": "{{GENDER:$2|Ikinarga}} ni $1 ang $3",
        "rightsnone": "(wala)",
+       "rightslogentry-temporary-group": "$1 (pansamantala, hanggang $2)",
        "feedback-adding": "Idinaragdag ang pakaing-tugon sa pahina...",
        "feedback-back": "Magbalik",
        "feedback-bugcheck": "Mahusay! Suriin lang na hindi pa ito isa sa [$1 nalalamang mga depekto].",
index c9db9bc..4ada642 100644 (file)
                        "Fitoschido",
                        "TmY e12",
                        "Dual",
-                       "ToprakM"
+                       "ToprakM",
+                       "Suvarioglu"
                ]
        },
        "tog-underline": "Bağlantıların altını çizme:",
        "rcfilters-watchlist-preference-label": "JavaScript olmayan bir arayüz kullanın",
        "rcfilters-watchlist-preference-help": "Filtre Listesini arama olmadan veya işlevselliği vurgulayarak İzleme Listesi'ni yükler.",
        "rcfilters-target-page-placeholder": "Bir sayfa (ya da kategori) adı girin",
+       "rcfilters-allcontents-label": "Tüm içerikler",
        "rcnotefrom": "<strong>$3, $4</strong> tarihinden itibaren yapılan {{PLURAL:$5|değişiklik|değişiklik}} aşağıdadır (<strong>$1</strong> tarhine kadar olanlar gösterilmektedir).",
        "rclistfromreset": "Tarih seçimini sıfırla",
        "rclistfrom": "$3 $2 tarihinden itibaren yeni değişiklikleri göster",
index 9fd4840..5a73826 100644 (file)
        "rcfilters-filter-showlinkedto-label": "Показати зміни на сторінках, що посилаються сюди",
        "rcfilters-filter-showlinkedto-option-label": "<strong>Сторінки, що посилаються на</strong> обрану сторінку",
        "rcfilters-target-page-placeholder": "Уведіть назву сторінки (чи категорії)",
+       "rcfilters-allcontents-label": "Весь вміст",
+       "rcfilters-alldiscussions-label": "Всі обговорення",
        "rcnotefrom": "Нижче знаходяться {{PLURAL:$5|редагування}} з <strong>$3, $4</strong> (відображено до <strong>$1</strong>).",
        "rclistfromreset": "Скинути вибір дати",
        "rclistfrom": "Показати редагування починаючи з $3 $2.",
        "move-subpages": "Перейменувати підсторінки (до $1)",
        "move-talk-subpages": "Перейменувати підсторінки сторінки обговорення (до $1)",
        "movepage-page-exists": "Сторінка $1 вже існує і не може бути автоматично перезаписана.",
+       "movepage-source-doesnt-exist": "Сторінка $1 не існує та не може бути перейменована.",
        "movepage-page-moved": "Сторінка $1 перейменована на $2.",
        "movepage-page-unmoved": "Сторінка $1 не може бути перейменована на $2.",
        "movepage-max-pages": "$1 {{PLURAL:$1|сторінка була перейменована|сторінки були перейменовані|сторінок були перейменовані}} — це максимум, більше сторінок не можна перейменувати автоматично.",
        "delete_and_move_reason": "Вилучена для можливості перейменування сторінки «[[$1]]»",
        "selfmove": "Ця назва є ідентичною з поточною;\nнеможливо перейменувати сторінку на поточну назву.",
        "immobile-source-namespace": "Не можна перейменовувати сторінки з простору назв «$1»",
+       "immobile-source-namespace-iw": "Сторінки з інших вікі не можуть бути перейменовані у цій вікі.",
        "immobile-target-namespace": "Не можна перейменовувати сторінки до простору назв «$1»",
        "immobile-target-namespace-iw": "Інтервікі-посилання не підходить для перейменування сторінки.",
        "immobile-source-page": "Цю сторінку не можна перейменувати.",
        "immobile-target-page": "Не можна присвоїти сторінці цю назву.",
+       "movepage-invalid-target-title": "Запитуване ім'я недопустиме.",
        "bad-target-model": "Неможливо перетворити $1 на $2: несумісні моделі даних.",
        "imagenocrossnamespace": "Неможливо дати файлові назву з іншого простору назв",
        "nonfile-cannot-move-to-file": "Не можна перейменовувати сторінки з інших просторів назв на файли",
index 8c1df68..361f463 100644 (file)
        "redirectedfrom": "(重定向自$1)",
        "redirectpagesub": "重定向页面",
        "redirectto": "重定向至:",
-       "lastmodifiedat": "在$2,此页面最后编辑于$1。",
+       "lastmodifiedat": "此页面最后编辑于$1 $2。",
        "viewcount": "此页面已经被访问过$1次。",
        "protectedpage": "受保护页面",
        "jumpto": "跳转至:",
        "group-sysop": "管理员",
        "group-interface-admin": "界面管理员",
        "group-bureaucrat": "行政员",
-       "group-suppress": "Flow监督员",
+       "group-suppress": "结构式讨论监督员",
        "group-all": "(所有)",
        "group-user-member": "{{GENDER:$1|用户}}",
        "group-autoconfirmed-member": "{{GENDER:$1|自动确认用户}}",
        "rcfilters-filter-showlinkedto-label": "显示链接到该页面的页面上的更改",
        "rcfilters-filter-showlinkedto-option-label": "<strong>链接到</strong>选定页面的页面",
        "rcfilters-target-page-placeholder": "输入页面(或分类)名称",
+       "rcfilters-allcontents-label": "所有内容",
+       "rcfilters-alldiscussions-label": "所有讨论",
        "rcnotefrom": "下面{{PLURAL:$5|是}}<strong>$3 $4</strong>之后的更改(最多显示<strong>$1</strong>个)。",
        "rclistfromreset": "重置时间选择",
        "rclistfrom": "显示$3 $2之后的新更改",
        "move-page-legend": "移动页面",
        "movepagetext": "您可以使用下面的表单来重命名一个页面,同时将其所有版本历史移动到新页面。旧标题将会被重定向到新标题。您可以自动更新链接至原标题的重定向。如果您不选择这样做的话,请检查[[Special:DoubleRedirects|双重]]或[[Special:BrokenRedirects|损坏重定向]]链接。您有责任确保链接会被正确指向他们应该被指向的地方。\n\n注意:如果已存在使用新标题的页面,此页面将<strong>不会</strong>被移动,除非新页面是重定向,并且没有过去的编辑历史。这意味着您可在误操作后将页面移回原处,同时,您无法覆盖现有页面。\n\n<strong>注意:</strong>对这样一个经常被访问的页面而言这可能是一个重大且唐突的更改;请在行动前先了解您的修改可能带来的一切后果。",
        "movepagetext-noredirectfixer": "用下面的表单来重命名一个页面,并将其版本历史同时移动到新页面。老的页面将成为新页面的重定向页。请检查[[Special:DoubleRedirects|双重重定向]]或[[Special:BrokenRedirects|损坏重定向]]链接。您应当负责确定所有链接依然会链到指定的页面。\n\n注意如果新页面已经有内容的话,页面将<strong>不会</strong>被移动,除非新页面无内容或是重定向页,而且没有版本历史。这意味着您可在误操作后将页面移回原处,同时,您无法覆盖现有页面。\n\n<strong>注意:</strong>对一个经常被访问的页面而言这可能是一个重大且唐突的更改;请在行动前先确定您了解其所可能带来的后果。",
+       "movepagetext-noredirectsupport": "使用下方表单来重命名页面,其所有历史记录也会被移动到新名称下。\n你需要确保相关链接指向正确。\n\n注意,如果新名称的页面已经存在,则此页面<strong>不会</strong>被移动。\n这意味着,如果重命名出错,你可以将页面重命名回原名称,但不能覆盖已存在页面。\n\n<strong>注意:</strong>\n对于人气较高的页面而已,此操作可能导致剧烈和意想不到的变化;\n请确保你了解此行为可能导致的后果,然后再执行操作。",
        "movepagetalktext": "如果您勾选此框,相关联的讨论页将被自动移动到新的标题,除非这里已经有了一个非空讨论页。\n\n在这种情况下,如有需要,您将不得不手动移动或合并页面。",
        "moveuserpage-warning": "'''警告:'''你将移动一个用户页面。请注意,只有该页面会被移动,该用户'''不会'''被更名。",
        "movecategorypage-warning": "<strong>警告:</strong>您将移动分类页面。请注意只有此页面将会移动,旧有分类的任何页面将<em>不会</em>同步移动。",
        "move-subpages": "移动子页面(最多$1页)",
        "move-talk-subpages": "如果可能,移动子对话页面(上至$1页)",
        "movepage-page-exists": "页面$1已存在,无法自动覆盖。",
+       "movepage-source-doesnt-exist": "页面 $1 不存在且无法被移动。",
        "movepage-page-moved": "页面$1已经移动到$2。",
        "movepage-page-unmoved": "页面$1无法移动到$2。",
        "movepage-max-pages": "所移动$1个页面的数量已达最大限额,无法同时自动移动更多页面。",
        "delete_and_move_reason": "删除以便移动[[$1]]",
        "selfmove": "标题相同;无法对页面进行自我移动。",
        "immobile-source-namespace": "无法移动名字空间为“$1”的页面",
+       "immobile-source-namespace-iw": "无法从此维基项目将页面移动至其他维基项目。",
        "immobile-target-namespace": "无法将页面移动到“$1”名字空间",
        "immobile-target-namespace-iw": "在移动页面时,跨wiki链接不是有效的目标。",
        "immobile-source-page": "此页面不能移动。",
        "immobile-target-page": "无法移动至该目标标题。",
+       "movepage-invalid-target-title": "请求的名称无效。",
        "bad-target-model": "要求的目标使用不同的内容模式。无法从$1转换到$2。",
        "imagenocrossnamespace": "无法将文件移动到非文件名字空间",
        "nonfile-cannot-move-to-file": "无法将非文件移动到文件名字空间",
index c5641c9..3b6f771 100644 (file)
        "group-sysop": "管理員",
        "group-interface-admin": "介面管理員",
        "group-bureaucrat": "行政員",
-       "group-suppress": "監督員",
+       "group-suppress": "çµ\90æ§\8bå¼\8fè¨\8eè«\96ç\9b£ç\9d£å\93¡",
        "group-all": "(全部)",
        "group-user-member": "{{GENDER:$1|使用者}}",
        "group-autoconfirmed-member": "自動確認使用者",
        "rcfilters-filter-showlinkedto-label": "顯示連結到該頁面的頁面上的更改",
        "rcfilters-filter-showlinkedto-option-label": "<strong>連結到</strong>指定頁面的頁面",
        "rcfilters-target-page-placeholder": "輸入頁面名稱(或分類)",
+       "rcfilters-allcontents-label": "所有內容",
+       "rcfilters-alldiscussions-label": "所有討論",
        "rcnotefrom": "以下{{PLURAL:$5|為}}自 <strong>$3 $4</strong> 以來的變更 (最多顯示 <strong>$1</strong> 筆)。",
        "rclistfromreset": "重設日期選擇",
        "rclistfrom": "顯示自 $3 $2 以來的新變更",
index fcf42ea..cd77468 100644 (file)
        "newpages-username": "用戶名稱:",
        "speciallogtitlelabel": "目標 (標題或用戶):",
        "checkbox-select": "選擇: $1",
+       "emailuser": "Email 聯絡此用戶",
        "emailusername": "用戶名稱:",
        "wlshowhidebots": "機械人",
        "blanknamespace": "(主要)",
index 1641c2a..4b682f8 100644 (file)
@@ -1,5 +1,5 @@
 <?php
-/** Manipuri/Meitei (মেইতেই লোন্)
+/** Manipuri/Meitei (ꯃꯤꯇꯩ ꯂꯣꯟ)
  *
  * To improve a translation please visit https://translatewiki.net
  *
@@ -7,3 +7,16 @@
  * @file
  *
  */
+
+$digitTransformTable = [
+       '0' => '꯰', # U+ABF0
+       '1' => '꯱', # U+ABF1
+       '2' => '꯲', # U+ABF2
+       '3' => '꯳', # U+ABF3
+       '4' => '꯴', # U+ABF4
+       '5' => '꯵', # U+ABF5
+       '6' => '꯶', # U+ABF6
+       '7' => '꯷', # U+ABF7
+       '8' => '꯸', # U+ABF8
+       '9' => '꯹', # U+ABF9
+];
index 08eade9..358dc21 100644 (file)
@@ -316,7 +316,7 @@ abstract class BackupDumper extends Maintenance {
 
                $dbr = $this->forcedDb;
                if ( $this->forcedDb === null ) {
-                       $dbr = wfGetDB( DB_REPLICA );
+                       $dbr = $this->getDB( DB_REPLICA );
                }
                $this->maxCount = $dbr->selectField( $table, "MAX($field)", '', __METHOD__ );
                $this->startTime = microtime( true );
index 5024395..ea12e42 100644 (file)
@@ -28,7 +28,6 @@ require_once __DIR__ . '/Maintenance.php';
 
 use MediaWiki\Linker\LinkTarget;
 use MediaWiki\MediaWikiServices;
-use MediaWiki\Storage\RevisionRecord;
 use Wikimedia\Rdbms\IResultWrapper;
 use Wikimedia\Rdbms\IMaintainableDatabase;
 
@@ -73,8 +72,6 @@ class NamespaceDupes extends Maintenance {
        }
 
        public function execute() {
-               $this->db = $this->getDB( DB_MASTER );
-
                $options = [
                        'fix' => $this->hasOption( 'fix' ),
                        'merge' => $this->hasOption( 'merge' ),
@@ -254,8 +251,8 @@ class NamespaceDupes extends Maintenance {
                foreach ( $targets as $row ) {
                        // Find the new title and determine the action to take
 
-                       $newTitle = $this->getDestinationTitle( $ns, $name,
-                               $row->page_namespace, $row->page_title, $options );
+                       $newTitle = $this->getDestinationTitle(
+                               $ns, $name, $row->page_namespace, $row->page_title );
                        $logStatus = false;
                        if ( !$newTitle ) {
                                $logStatus = 'invalid title';
@@ -338,18 +335,20 @@ class NamespaceDupes extends Maintenance {
        private function checkLinkTable( $table, $fieldPrefix, $ns, $name, $options,
                $extraConds = []
        ) {
+               $dbw = $this->getDB( DB_MASTER );
+
                $batchConds = [];
                $fromField = "{$fieldPrefix}_from";
                $namespaceField = "{$fieldPrefix}_namespace";
                $titleField = "{$fieldPrefix}_title";
                $batchSize = 500;
                while ( true ) {
-                       $res = $this->db->select(
+                       $res = $dbw->select(
                                $table,
                                [ $fromField, $namespaceField, $titleField ],
                                array_merge( $batchConds, $extraConds, [
                                        $namespaceField => 0,
-                                       $titleField . $this->db->buildLike( "$name:", $this->db->anyString() )
+                                       $titleField . $dbw->buildLike( "$name:", $dbw->anyString() )
                                ] ),
                                __METHOD__,
                                [
@@ -364,8 +363,8 @@ class NamespaceDupes extends Maintenance {
                        foreach ( $res as $row ) {
                                $logTitle = "from={$row->$fromField} ns={$row->$namespaceField} " .
                                        "dbk={$row->$titleField}";
-                               $destTitle = $this->getDestinationTitle( $ns, $name,
-                                       $row->$namespaceField, $row->$titleField, $options );
+                               $destTitle = $this->getDestinationTitle(
+                                       $ns, $name, $row->$namespaceField, $row->$titleField );
                                $this->totalLinks++;
                                if ( !$destTitle ) {
                                        $this->output( "$table $logTitle *** INVALID\n" );
@@ -378,7 +377,7 @@ class NamespaceDupes extends Maintenance {
                                        continue;
                                }
 
-                               $this->db->update( $table,
+                               $dbw->update( $table,
                                        // SET
                                        [
                                                $namespaceField => $destTitle->getNamespace(),
@@ -396,8 +395,8 @@ class NamespaceDupes extends Maintenance {
                                $this->output( "$table $logTitle -> " .
                                        $destTitle->getPrefixedDBkey() . "\n" );
                        }
-                       $encLastTitle = $this->db->addQuotes( $row->$titleField );
-                       $encLastFrom = $this->db->addQuotes( $row->$fromField );
+                       $encLastTitle = $dbw->addQuotes( $row->$titleField );
+                       $encLastFrom = $dbw->addQuotes( $row->$fromField );
 
                        $batchConds = [
                                "$titleField > $encLastTitle " .
@@ -433,6 +432,8 @@ class NamespaceDupes extends Maintenance {
         * @return IResultWrapper
         */
        private function getTargetList( $ns, $name, $options ) {
+               $dbw = $this->getDB( DB_MASTER );
+
                if (
                        $options['move-talk'] &&
                        MediaWikiServices::getInstance()->getNamespaceInfo()->isSubject( $ns )
@@ -442,7 +443,7 @@ class NamespaceDupes extends Maintenance {
                        $checkNamespaces = NS_MAIN;
                }
 
-               return $this->db->select( 'page',
+               return $dbw->select( 'page',
                        [
                                'page_id',
                                'page_title',
@@ -450,7 +451,7 @@ class NamespaceDupes extends Maintenance {
                        ],
                        [
                                'page_namespace' => $checkNamespaces,
-                               'page_title' . $this->db->buildLike( "$name:", $this->db->anyString() ),
+                               'page_title' . $dbw->buildLike( "$name:", $dbw->anyString() ),
                        ],
                        __METHOD__
                );
@@ -462,10 +463,9 @@ class NamespaceDupes extends Maintenance {
         * @param string $name The conflicting prefix
         * @param int $sourceNs The source namespace
         * @param int $sourceDbk The source DB key (i.e. page_title)
-        * @param array $options Associative array of validated command-line options
         * @return Title|false
         */
-       private function getDestinationTitle( $ns, $name, $sourceNs, $sourceDbk, $options ) {
+       private function getDestinationTitle( $ns, $name, $sourceNs, $sourceDbk ) {
                $dbk = substr( $sourceDbk, strlen( "$name:" ) );
                if ( $ns == 0 ) {
                        // An interwiki; try an alternate encoding with '-' for ':'
@@ -518,7 +518,9 @@ class NamespaceDupes extends Maintenance {
         * @return bool
         */
        private function movePage( $id, LinkTarget $newLinkTarget ) {
-               $this->db->update( 'page',
+               $dbw = $this->getDB( DB_MASTER );
+
+               $dbw->update( 'page',
                        [
                                "page_namespace" => $newLinkTarget->getNamespace(),
                                "page_title" => $newLinkTarget->getDBkey(),
@@ -535,7 +537,7 @@ class NamespaceDupes extends Maintenance {
                        [ 'imagelinks', 'il' ] ];
                foreach ( $fromNamespaceTables as $tableInfo ) {
                        list( $table, $fieldPrefix ) = $tableInfo;
-                       $this->db->update( $table,
+                       $dbw->update( $table,
                                // SET
                                [ "{$fieldPrefix}_from_namespace" => $newLinkTarget->getNamespace() ],
                                // WHERE
@@ -559,12 +561,8 @@ class NamespaceDupes extends Maintenance {
         * @return bool
         */
        private function canMerge( $id, LinkTarget $linkTarget, &$logStatus ) {
-               $latestDest = Revision::newFromTitle(
-                       $linkTarget, 0, RevisionRecord::READ_LATEST
-               );
-               $latestSource = Revision::newFromPageId(
-                       $id, 0, RevisionRecord::READ_LATEST
-               );
+               $latestDest = Revision::newFromTitle( $linkTarget, 0, Revision::READ_LATEST );
+               $latestSource = Revision::newFromPageId( $id, 0, Revision::READ_LATEST );
                if ( $latestSource->getTimestamp() > $latestDest->getTimestamp() ) {
                        $logStatus = 'cannot merge since source is later';
                        return false;
@@ -581,6 +579,8 @@ class NamespaceDupes extends Maintenance {
         * @return bool
         */
        private function mergePage( $row, Title $newTitle ) {
+               $dbw = $this->getDB( DB_MASTER );
+
                $id = $row->page_id;
 
                // Construct the WikiPage object we will need later, while the
@@ -592,17 +592,17 @@ class NamespaceDupes extends Maintenance {
                $wikiPage->loadPageData( 'fromdbmaster' );
 
                $destId = $newTitle->getArticleID();
-               $this->beginTransaction( $this->db, __METHOD__ );
-               $this->db->update( 'revision',
+               $this->beginTransaction( $dbw, __METHOD__ );
+               $dbw->update( 'revision',
                        // SET
                        [ 'rev_page' => $destId ],
                        // WHERE
                        [ 'rev_page' => $id ],
                        __METHOD__ );
 
-               $this->db->delete( 'page', [ 'page_id' => $id ], __METHOD__ );
+               $dbw->delete( 'page', [ 'page_id' => $id ], __METHOD__ );
 
-               $this->commitTransaction( $this->db, __METHOD__ );
+               $this->commitTransaction( $dbw, __METHOD__ );
 
                /* Call LinksDeletionUpdate to delete outgoing links from the old title,
                 * and update category counts.
index 35af15c..7267b2c 100644 (file)
@@ -367,7 +367,9 @@ class RebuildRecentchanges extends Maintenance {
                # @NOTE: users with 'bot' rights choose when edits are bot edits or not. That information
                # may be lost at this point (aside from joining on the patrol log table entries).
                $botgroups = [ 'bot' ];
-               $autopatrolgroups = $wgUseRCPatrol ? User::getGroupsWithPermission( 'autopatrol' ) : [];
+               $autopatrolgroups = $wgUseRCPatrol ? MediaWikiServices::getInstance()
+                       ->getPermissionManager()
+                       ->getGroupsWithPermission( 'autopatrol' ) : [];
 
                # Flag our recent bot edits
                if ( $botgroups ) {
index 2e4cc88..7cc7575 100644 (file)
@@ -27,7 +27,6 @@
 
 require_once __DIR__ . '/Maintenance.php';
 
-use Wikimedia\Rdbms\IMaintainableDatabase;
 use Wikimedia\Rdbms\DatabaseSqlite;
 
 /**
@@ -38,11 +37,6 @@ use Wikimedia\Rdbms\DatabaseSqlite;
 class RebuildTextIndex extends Maintenance {
        const RTI_CHUNK_SIZE = 500;
 
-       /**
-        * @var IMaintainableDatabase
-        */
-       private $db;
-
        public function __construct() {
                parent::__construct();
                $this->addDescription( 'Rebuild search index table from scratch' );
@@ -54,23 +48,19 @@ class RebuildTextIndex extends Maintenance {
 
        public function execute() {
                // Shouldn't be needed for Postgres
-               $this->db = $this->getDB( DB_MASTER );
-               if ( $this->db->getType() == 'postgres' ) {
+               $dbw = $this->getDB( DB_MASTER );
+               if ( $dbw->getType() == 'postgres' ) {
                        $this->fatalError( "This script is not needed when using Postgres.\n" );
                }
 
-               if ( $this->db->getType() == 'sqlite' ) {
+               if ( $dbw->getType() == 'sqlite' ) {
                        if ( !DatabaseSqlite::getFulltextSearchModule() ) {
                                $this->fatalError( "Your version of SQLite module for PHP doesn't "
                                        . "support full-text search (FTS3).\n" );
                        }
-                       if ( !$this->db->checkForEnabledSearch() ) {
-                               $this->fatalError( "Your database schema is not configured for "
-                                       . "full-text search support. Run update.php.\n" );
-                       }
                }
 
-               if ( $this->db->getType() == 'mysql' ) {
+               if ( $dbw->getType() == 'mysql' ) {
                        $this->dropMysqlTextIndex();
                        $this->clearSearchIndex();
                        $this->populateSearchIndex();
@@ -87,8 +77,9 @@ class RebuildTextIndex extends Maintenance {
         * Populates the search index with content from all pages
         */
        protected function populateSearchIndex() {
-               $res = $this->db->select( 'page', 'MAX(page_id) AS count' );
-               $s = $this->db->fetchObject( $res );
+               $dbw = $this->getDB( DB_MASTER );
+               $res = $dbw->select( 'page', 'MAX(page_id) AS count' );
+               $s = $dbw->fetchObject( $res );
                $count = $s->count;
                $this->output( "Rebuilding index fields for {$count} pages...\n" );
                $n = 0;
@@ -101,7 +92,7 @@ class RebuildTextIndex extends Maintenance {
                        }
                        $end = $n + self::RTI_CHUNK_SIZE - 1;
 
-                       $res = $this->db->select(
+                       $res = $dbw->select(
                                $revQuery['tables'],
                                $revQuery['fields'],
                                [ "page_id BETWEEN $n AND $end", 'page_latest = rev_id' ],
@@ -131,11 +122,12 @@ class RebuildTextIndex extends Maintenance {
         * (MySQL only) Drops fulltext index before populating the table.
         */
        private function dropMysqlTextIndex() {
-               $searchindex = $this->db->tableName( 'searchindex' );
-               if ( $this->db->indexExists( 'searchindex', 'si_title', __METHOD__ ) ) {
+               $dbw = $this->getDB( DB_MASTER );
+               $searchindex = $dbw->tableName( 'searchindex' );
+               if ( $dbw->indexExists( 'searchindex', 'si_title', __METHOD__ ) ) {
                        $this->output( "Dropping index...\n" );
                        $sql = "ALTER TABLE $searchindex DROP INDEX si_title, DROP INDEX si_text";
-                       $this->db->query( $sql, __METHOD__ );
+                       $dbw->query( $sql, __METHOD__ );
                }
        }
 
@@ -143,11 +135,12 @@ class RebuildTextIndex extends Maintenance {
         * (MySQL only) Adds back fulltext index after populating the table.
         */
        private function createMysqlTextIndex() {
-               $searchindex = $this->db->tableName( 'searchindex' );
+               $dbw = $this->getDB( DB_MASTER );
+               $searchindex = $dbw->tableName( 'searchindex' );
                $this->output( "\nRebuild the index...\n" );
                foreach ( [ 'si_title', 'si_text' ] as $field ) {
                        $sql = "ALTER TABLE $searchindex ADD FULLTEXT $field ($field)";
-                       $this->db->query( $sql, __METHOD__ );
+                       $dbw->query( $sql, __METHOD__ );
                }
        }
 
@@ -155,8 +148,9 @@ class RebuildTextIndex extends Maintenance {
         * Deletes everything from search index.
         */
        private function clearSearchIndex() {
+               $dbw = $this->getDB( DB_MASTER );
                $this->output( 'Clearing searchindex table...' );
-               $this->db->delete( 'searchindex', '*', __METHOD__ );
+               $dbw->delete( 'searchindex', '*', __METHOD__ );
                $this->output( "Done\n" );
        }
 }
index 21d8b2d..612c092 100644 (file)
@@ -67,7 +67,7 @@ class MwSql extends Maintenance {
                $replicaDB = $this->getOption( 'replicadb', $this->getOption( 'slave', '' ) );
                if ( $replicaDB === 'any' ) {
                        $index = DB_REPLICA;
-               } elseif ( $replicaDB != '' ) {
+               } elseif ( $replicaDB !== '' ) {
                        $index = null;
                        $serverCount = $lb->getServerCount();
                        for ( $i = 0; $i < $serverCount; ++$i ) {
@@ -76,7 +76,7 @@ class MwSql extends Maintenance {
                                        break;
                                }
                        }
-                       if ( $index === null ) {
+                       if ( $index === null || $index === $lb->getWriterIndex() ) {
                                $this->fatalError( "No replica DB server configured with the name '$replicaDB'." );
                        }
                } else {
index bfd4d97..b9d5792 100644 (file)
  * @ingroup Maintenance
  */
 
+use MediaWiki\MediaWikiServices;
+
+use Wikimedia\Rdbms\DatabaseSqlite;
+
 require_once __DIR__ . '/Maintenance.php';
 
 /**
@@ -59,37 +63,37 @@ class SqliteMaintenance extends Maintenance {
                        return;
                }
 
-               $this->db = $this->getDB( DB_MASTER );
-
-               if ( $this->db->getType() != 'sqlite' ) {
+               $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
+               $dbw = $lb->getConnection( DB_MASTER );
+               if ( !( $dbw instanceof DatabaseSqlite ) ) {
                        $this->error( "This maintenance script requires a SQLite database.\n" );
 
                        return;
                }
 
                if ( $this->hasOption( 'vacuum' ) ) {
-                       $this->vacuum();
+                       $this->vacuum( $dbw );
                }
 
                if ( $this->hasOption( 'integrity' ) ) {
-                       $this->integrityCheck();
+                       $this->integrityCheck( $dbw );
                }
 
                if ( $this->hasOption( 'backup-to' ) ) {
-                       $this->backup( $this->getOption( 'backup-to' ) );
+                       $this->backup( $dbw, $this->getOption( 'backup-to' ) );
                }
        }
 
-       private function vacuum() {
-               $prevSize = filesize( $this->db->getDbFilePath() );
+       private function vacuum( DatabaseSqlite $dbw ) {
+               $prevSize = filesize( $dbw->getDbFilePath() );
                if ( $prevSize == 0 ) {
                        $this->fatalError( "Can't vacuum an empty database.\n" );
                }
 
                $this->output( 'VACUUM: ' );
-               if ( $this->db->query( 'VACUUM' ) ) {
+               if ( $dbw->query( 'VACUUM' ) ) {
                        clearstatcache();
-                       $newSize = filesize( $this->db->getDbFilePath() );
+                       $newSize = filesize( $dbw->getDbFilePath() );
                        $this->output( sprintf( "Database size was %d, now %d (%.1f%% reduction).\n",
                                $prevSize, $newSize, ( $prevSize - $newSize ) * 100.0 / $prevSize ) );
                } else {
@@ -97,9 +101,9 @@ class SqliteMaintenance extends Maintenance {
                }
        }
 
-       private function integrityCheck() {
+       private function integrityCheck( DatabaseSqlite $dbw ) {
                $this->output( "Performing database integrity checks:\n" );
-               $res = $this->db->query( 'PRAGMA integrity_check' );
+               $res = $dbw->query( 'PRAGMA integrity_check' );
 
                if ( !$res || $res->numRows() == 0 ) {
                        $this->error( "Error: integrity check query returned nothing.\n" );
@@ -112,10 +116,10 @@ class SqliteMaintenance extends Maintenance {
                }
        }
 
-       private function backup( $fileName ) {
+       private function backup( DatabaseSqlite $dbw, $fileName ) {
                $this->output( "Backing up database:\n   Locking..." );
-               $this->db->query( 'BEGIN IMMEDIATE TRANSACTION', __METHOD__ );
-               $ourFile = $this->db->getDbFilePath();
+               $dbw->query( 'BEGIN IMMEDIATE TRANSACTION', __METHOD__ );
+               $ourFile = $dbw->getDbFilePath();
                $this->output( "   Copying database file $ourFile to $fileName... " );
                Wikimedia\suppressWarnings();
                if ( !copy( $ourFile, $fileName ) ) {
@@ -124,7 +128,7 @@ class SqliteMaintenance extends Maintenance {
                }
                Wikimedia\restoreWarnings();
                $this->output( "   Releasing lock...\n" );
-               $this->db->query( 'COMMIT TRANSACTION', __METHOD__ );
+               $dbw->query( 'COMMIT TRANSACTION', __METHOD__ );
        }
 
        private function checkSyntax() {
index 94aa3b9..997110c 100644 (file)
                switch ( this.displayLayer ) {
                        case 'month':
                                this.labelButton.setLabel( this.moment.format( 'MMMM YYYY' ) );
+                               this.labelButton.toggle( true );
                                this.upButton.toggle( true );
 
                                // First week displayed is the first week spanned by the month, unless it begins on Monday, in
 
                        case 'year':
                                this.labelButton.setLabel( this.moment.format( 'YYYY' ) );
+                               this.labelButton.toggle( true );
                                this.upButton.toggle( true );
 
                                currentMonth = moment( this.moment ).startOf( 'year' );
 
                        case 'duodecade':
                                this.labelButton.setLabel( null );
+                               this.labelButton.toggle( false );
                                this.upButton.toggle( false );
 
                                currentYear = moment( { year: Math.floor( this.moment.year() / 20 ) * 20 } );
                        framed: false,
                        classes: [ 'mw-widget-calendarWidget-labelButton' ]
                } );
+               // FIXME This button is actually not clickable because labelButton covers it,
+               // should it just be a plain icon?
                this.upButton = new OO.ui.ButtonWidget( {
                        tabIndex: -1,
                        framed: false,
                this.$header.append(
                        this.prevButton.$element,
                        this.nextButton.$element,
-                       this.upButton.$element,
-                       this.labelButton.$element
+                       this.labelButton.$element,
+                       this.upButton.$element
                );
        };
 
index 7932f73..ba5ae33 100644 (file)
@@ -39,7 +39,9 @@
 
 .mw-widget-calendarWidget-upButton {
        position: absolute;
+       top: 0;
        right: 3em;
+       pointer-events: none;
 }
 
 .mw-widget-calendarWidget-prevButton {
index ad05c6f..a3249de 100644 (file)
                         *             // From mw.loader.register()
                         *             'version': '########' (hash)
                         *             'dependencies': ['required.foo', 'bar.also', ...]
-                        *             'group': 'somegroup', (or) null
+                        *             'group': string, integer, (or) null
                         *             'source': 'local', (or) 'anotherwiki'
                         *             'skip': 'return !!window.Example', (or) null, (or) boolean result of skip
                         *             'module': export Object
 
                                dependencies.forEach( function ( module ) {
                                        // Only queue modules that are still in the initial 'registered' state
-                                       // (not ones already loading, ready or error).
+                                       // (e.g. not ones already loading or loaded etc.).
                                        if ( registry[ module ].state === 'registered' && queue.indexOf( module ) === -1 ) {
-                                               // Private modules must be embedded in the page. Don't bother queuing
-                                               // these as the server will deny them anyway (T101806).
-                                               if ( registry[ module ].group === 'private' ) {
-                                                       setAndPropagate( module, 'error' );
-                                               } else {
-                                                       queue.push( module );
-                                               }
+                                               queue.push( module );
                                        }
                                } );
 
                                                // Optimisation: Inherit (Object.create), not copy ($.extend)
                                                currReqBase = Object.create( reqBase );
                                                // User modules require a user name in the query string.
-                                               if ( group === 'user' && mw.config.get( 'wgUserName' ) !== null ) {
+                                               if ( group === $VARS.groupUser && mw.config.get( 'wgUserName' ) !== null ) {
                                                        currReqBase.user = mw.config.get( 'wgUserName' );
                                                }
 
                                        packageExports: {},
                                        version: String( version || '' ),
                                        dependencies: dependencies || [],
-                                       group: typeof group === 'string' ? group : null,
+                                       group: typeof group === 'undefined' ? null : group,
                                        source: typeof source === 'string' ? source : 'local',
                                        state: 'registered',
                                        skip: typeof skip === 'string' ? skip : null
                                                        descriptor.state !== 'ready' ||
                                                        // Unversioned, private, or site-/user-specific
                                                        !descriptor.version ||
-                                                       descriptor.group === 'private' ||
-                                                       descriptor.group === 'user' ||
+                                                       descriptor.group === $VARS.groupPrivate ||
+                                                       descriptor.group === $VARS.groupUser ||
                                                        // Partial descriptor
                                                        // (e.g. skipped module, or style module with state=ready)
                                                        [ descriptor.script, descriptor.style, descriptor.messages,
index e71cc88..7c8df1a 100644 (file)
@@ -61,6 +61,7 @@ $wgAutoloadClasses += [
        'MediaWikiPHPUnitResultPrinter' => "$testDir/phpunit/MediaWikiPHPUnitResultPrinter.php",
        'MediaWikiPHPUnitTestListener' => "$testDir/phpunit/MediaWikiPHPUnitTestListener.php",
        'MediaWikiTestCase' => "$testDir/phpunit/MediaWikiIntegrationTestCase.php",
+       'MediaWikiTestCaseTrait' => "$testDir/phpunit/MediaWikiTestCaseTrait.php",
        'MediaWikiUnitTestCase' => "$testDir/phpunit/MediaWikiUnitTestCase.php",
        'MediaWikiIntegrationTestCase' => "$testDir/phpunit/MediaWikiIntegrationTestCase.php",
        'MediaWikiTestResult' => "$testDir/phpunit/MediaWikiTestResult.php",
@@ -217,6 +218,9 @@ $wgAutoloadClasses += [
        'MockSearchResultSet' => "$testDir/phpunit/mocks/search/MockSearchResultSet.php",
        'MockSearchResult' => "$testDir/phpunit/mocks/search/MockSearchResult.php",
 
+       # tests/phpunit/unit/includes
+       'BadFileLookupTest' => "$testDir/phpunit/unit/includes/BadFileLookupTest.php",
+
        # tests/phpunit/unit/includes/libs/filebackend/fsfile
        'TempFSFileTestTrait' => "$testDir/phpunit/unit/includes/libs/filebackend/fsfile/TempFSFileTestTrait.php",
 
index 496f265..33518ef 100644 (file)
@@ -23,8 +23,9 @@ use Wikimedia\TestingAccessWrapper;
 abstract class MediaWikiIntegrationTestCase extends PHPUnit\Framework\TestCase {
 
        use MediaWikiCoversValidator;
-       use PHPUnit4And6Compat;
        use MediaWikiGroupValidator;
+       use MediaWikiTestCaseTrait;
+       use PHPUnit4And6Compat;
 
        /**
         * The original service locator. This is overridden during setUp().
@@ -1501,7 +1502,6 @@ abstract class MediaWikiIntegrationTestCase extends PHPUnit\Framework\TestCase {
 
                if ( !isset( $db->_originalTablePrefix ) ) {
                        $oldPrefix = $db->tablePrefix();
-
                        if ( $oldPrefix === $prefix ) {
                                // table already has the correct prefix, but presumably no cloned tables
                                $oldPrefix = self::$oldTablePrefix;
@@ -1511,11 +1511,13 @@ abstract class MediaWikiIntegrationTestCase extends PHPUnit\Framework\TestCase {
                        $tablesCloned = self::listTables( $db );
                        $dbClone = new CloneDatabase( $db, $tablesCloned, $prefix, $oldPrefix );
                        $dbClone->useTemporaryTables( self::$useTemporaryTables );
-
                        $dbClone->cloneTableStructure();
 
                        $db->tablePrefix( $prefix );
                        $db->_originalTablePrefix = $oldPrefix;
+
+                       $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
+                       $lb->setTempTablesOnlyMode( self::$useTemporaryTables, $lb->getLocalDomainID() );
                }
 
                return true;
@@ -1862,8 +1864,10 @@ abstract class MediaWikiIntegrationTestCase extends PHPUnit\Framework\TestCase {
 
                $dbClone = new CloneDatabase( $db, $tables, $db->tablePrefix(), $db->_originalTablePrefix );
                $dbClone->useTemporaryTables( self::$useTemporaryTables );
-
                $dbClone->cloneTableStructure();
+
+               $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
+               $lb->setTempTablesOnlyMode( self::$useTemporaryTables, $lb->getLocalDomainID() );
        }
 
        /**
@@ -2510,20 +2514,6 @@ abstract class MediaWikiIntegrationTestCase extends PHPUnit\Framework\TestCase {
                        'comment' => $comment,
                ] );
        }
-
-       /**
-        * Returns a PHPUnit constraint that matches anything other than a fixed set of values. This can
-        * be used to whitelist values, e.g.
-        *   $mock->expects( $this->never() )->method( $this->anythingBut( 'foo', 'bar' ) );
-        * which will throw if any unexpected method is called.
-        *
-        * @param mixed ...$values Values that are not matched
-        */
-       protected function anythingBut( ...$values ) {
-               return $this->logicalNot( $this->logicalOr(
-                       ...array_map( [ $this, 'matches' ], $values )
-               ) );
-       }
 }
 
 class_alias( 'MediaWikiIntegrationTestCase', 'MediaWikiTestCase' );
diff --git a/tests/phpunit/MediaWikiTestCaseTrait.php b/tests/phpunit/MediaWikiTestCaseTrait.php
new file mode 100644 (file)
index 0000000..77d7c04
--- /dev/null
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * For code common to both MediaWikiUnitTestCase and MediaWikiIntegrationTestCase.
+ */
+trait MediaWikiTestCaseTrait {
+       /**
+        * Returns a PHPUnit constraint that matches anything other than a fixed set of values. This can
+        * be used to whitelist values, e.g.
+        *   $mock->expects( $this->never() )->method( $this->anythingBut( 'foo', 'bar' ) );
+        * which will throw if any unexpected method is called.
+        *
+        * @param mixed ...$values Values that are not matched
+        */
+       protected function anythingBut( ...$values ) {
+               return $this->logicalNot( $this->logicalOr(
+                       ...array_map( [ $this, 'matches' ], $values )
+               ) );
+       }
+}
index 5f7746b..ccf3357 100644 (file)
@@ -26,10 +26,13 @@ use PHPUnit\Framework\TestCase;
  *
  * Extend this class if you are testing classes which use dependency injection and do not access
  * global functions, variables, services or a storage backend.
+ *
+ * @since 1.34
  */
 abstract class MediaWikiUnitTestCase extends TestCase {
        use PHPUnit4And6Compat;
        use MediaWikiCoversValidator;
+       use MediaWikiTestCaseTrait;
 
        private $unitGlobals = [];
 
@@ -38,7 +41,7 @@ abstract class MediaWikiUnitTestCase extends TestCase {
                $reflection = new ReflectionClass( $this );
                $dirSeparator = DIRECTORY_SEPARATOR;
                if ( strpos( $reflection->getFilename(), "${dirSeparator}unit${dirSeparator}" ) === false ) {
-                       $this->fail( 'This unit test needs to be in "tests/phpunit/unit" !' );
+                       $this->fail( 'This unit test needs to be in "tests/phpunit/unit"!' );
                }
                $this->unitGlobals = $GLOBALS;
                unset( $GLOBALS );
@@ -54,4 +57,19 @@ abstract class MediaWikiUnitTestCase extends TestCase {
                $GLOBALS = $this->unitGlobals;
                parent::tearDown();
        }
+
+       /**
+        * Create a temporary hook handler which will be reset by tearDown.
+        * This replaces other handlers for the same hook.
+        * @param string $hookName Hook name
+        * @param mixed $handler Value suitable for a hook handler
+        * @since 1.34
+        */
+       protected function setTemporaryHook( $hookName, $handler ) {
+               // This will be reset by tearDown() when it restores globals. We don't want to use
+               // Hooks::register()/clear() because they won't replace other handlers for the same hook,
+               // which doesn't match behavior of MediaWikiIntegrationTestCase.
+               global $wgHooks;
+               $wgHooks[$hookName] = [ $handler ];
+       }
 }
index 0765ab8..95571f2 100644 (file)
@@ -5,28 +5,55 @@
  * @group Database
  */
 class GlobalWithDBTest extends MediaWikiTestCase {
+       private function setUpBadImageTests( $name ) {
+               if ( in_array( $name, [
+                       'Hook bad.jpg',
+                       'Redirect to bad.jpg',
+                       'Redirect_to_good.jpg',
+                       'Redirect to hook bad.jpg',
+                       'Redirect to hook good.jpg',
+               ] ) ) {
+                       $this->markTestSkipped( "Didn't get RepoGroup working properly yet" );
+               }
+
+               // Don't try to fetch the files from Commons or anything, please
+               $this->setMwGlobals( 'wgForeignFileRepos', [] );
+               // We need to reset services immediately so that editPage() doesn't use the old RepoGroup
+               // and hit the network
+               $this->resetServices();
+
+               // XXX How do we get file redirects to work?
+               $this->editPage( 'File:Redirect to bad.jpg', '#REDIRECT [[Bad.jpg]]' );
+               $this->editPage( 'File:Redirect to good.jpg', '#REDIRECT [[Good.jpg]]' );
+               $this->editPage( 'File:Redirect to hook bad.jpg', '#REDIRECT [[Hook bad.jpg]]' );
+               $this->editPage( 'File:Redirect to hook good.jpg', '#REDIRECT [[Hook good.jpg]]' );
+
+               $this->setTemporaryHook( 'BadImage', 'BadFileLookupTest::badImageHook' );
+       }
+
        /**
-        * @dataProvider provideWfIsBadImageList
+        * @dataProvider BadFileLookupTest::provideIsBadFile
         * @covers ::wfIsBadImage
         */
-       public function testWfIsBadImage( $name, $title, $blacklist, $expected, $desc ) {
-               $this->assertEquals( $expected, wfIsBadImage( $name, $title, $blacklist ), $desc );
+       public function testWfIsBadImage( $name, $title, $expected ) {
+               $this->setUpBadImageTests( $name );
+
+               $this->editPage( 'MediaWiki:Bad image list', BadFileLookupTest::BLACKLIST );
+               $this->resetServices();
+               // Enable messages from MediaWiki namespace
+               MessageCache::singleton()->enable();
+
+               $this->assertEquals( $expected, wfIsBadImage( $name, $title ) );
        }
 
-       public static function provideWfIsBadImageList() {
-               $blacklist = '* [[File:Bad.jpg]] except [[Nasty page]]';
-
-               return [
-                       [ 'Bad.jpg', false, $blacklist, true,
-                               'Called on a bad image' ],
-                       [ 'Bad.jpg', Title::makeTitle( NS_MAIN, 'A page' ), $blacklist, true,
-                               'Called on a bad image' ],
-                       [ 'NotBad.jpg', false, $blacklist, false,
-                               'Called on a non-bad image' ],
-                       [ 'Bad.jpg', Title::makeTitle( NS_MAIN, 'Nasty page' ), $blacklist, false,
-                               'Called on a bad image but is on a whitelisted page' ],
-                       [ 'File:Bad.jpg', false, $blacklist, false,
-                               'Called on a bad image with File:' ],
-               ];
+       /**
+        * @dataProvider BadFileLookupTest::provideIsBadFile
+        * @covers ::wfIsBadImage
+        */
+       public function testWfIsBadImage_blacklistParam( $name, $title, $expected ) {
+               $this->setUpBadImageTests( $name );
+
+               $this->hideDeprecated( 'wfIsBadImage with $blacklist parameter' );
+               $this->assertSame( $expected, wfIsBadImage( $name, $title, BadFileLookupTest::BLACKLIST ) );
        }
 }
index 00b8d18..6520fc5 100644 (file)
@@ -3029,6 +3029,35 @@ class OutputPageTest extends MediaWikiTestCase {
                ];
        }
 
+       /**
+        * @param int $titleLastRevision Last Title revision to set
+        * @param int $outputRevision Revision stored in OutputPage
+        * @param bool $expectedResult Expected result of $output->isRevisionCurrent call
+        * @covers OutputPage::isRevisionCurrent
+        * @dataProvider provideIsRevisionCurrent
+        */
+       public function testIsRevisionCurrent( $titleLastRevision, $outputRevision, $expectedResult ) {
+               $titleMock = $this->getMock( Title::class, [], [], '', false );
+               $titleMock->expects( $this->any() )
+                       ->method( 'getLatestRevID' )
+                       ->willReturn( $titleLastRevision );
+
+               $output = $this->newInstance( [], null, [ 'notitle' => true ] );
+               $output->setTitle( $titleMock );
+               $output->setRevisionId( $outputRevision );
+               $this->assertEquals( $expectedResult, $output->isRevisionCurrent() );
+       }
+
+       public function provideIsRevisionCurrent() {
+               return [
+                       [ 10, null, true ],
+                       [ 42, 42, true ],
+                       [ null, 0, true ],
+                       [ 42, 47, false ],
+                       [ 47, 42, false ]
+               ];
+       }
+
        /**
         * @return OutputPage
         */
index 8108639..88847e2 100644 (file)
@@ -5,6 +5,7 @@ namespace MediaWiki\Tests\Permissions;
 use Action;
 use ContentHandler;
 use FauxRequest;
+use LoggedServiceOptions;
 use MediaWiki\Block\DatabaseBlock;
 use MediaWiki\Block\Restriction\NamespaceRestriction;
 use MediaWiki\Block\Restriction\PageRestriction;
@@ -14,6 +15,8 @@ use MediaWiki\MediaWikiServices;
 use MediaWiki\Permissions\PermissionManager;
 use MediaWiki\Revision\MutableRevisionRecord;
 use MediaWiki\Revision\RevisionLookup;
+use MWException;
+use TestAllServiceOptionsUsed;
 use Wikimedia\ScopedCallback;
 use MediaWiki\Session\SessionId;
 use MediaWiki\Session\TestUtils;
@@ -30,6 +33,7 @@ use Wikimedia\TestingAccessWrapper;
  * @covers \MediaWiki\Permissions\PermissionManager
  */
 class PermissionManagerTest extends MediaWikiLangTestCase {
+       use TestAllServiceOptionsUsed;
 
        /**
         * @var string
@@ -725,15 +729,23 @@ class PermissionManagerTest extends MediaWikiLangTestCase {
                                }
                        } );
                $permissionManager = new PermissionManager(
+                       new LoggedServiceOptions(
+                               self::$serviceOptionsAccessLog,
+                               PermissionManager::$constructorOptions,
+                               [
+                                       'WhitelistRead' => [],
+                                       'WhitelistReadRegexp' => [],
+                                       'EmailConfirmToEdit' => false,
+                                       'BlockDisablesLogin' => false,
+                                       'GroupPermissions' => [],
+                                       'RevokePermissions' => [],
+                                       'AvailableRights' => [],
+                                       'NamespaceProtection' => [],
+                                       'RestrictionLevels' => []
+                               ]
+                       ),
                        $services->getSpecialPageFactory(),
                        $revisionLookup,
-                       [],
-                       [],
-                       false,
-                       false,
-                       [],
-                       [],
-                       [],
                        MediaWikiServices::getInstance()->getNamespaceInfo()
                );
                $this->setService( 'PermissionManager', $permissionManager );
@@ -1548,7 +1560,8 @@ class PermissionManagerTest extends MediaWikiLangTestCase {
                $user = $this->getTestUser( [ 'unittesters', 'testwriters' ] )->getUser();
                $userWrapper = TestingAccessWrapper::newFromObject( $user );
 
-               $rights = MediaWikiServices::getInstance()->getPermissionManager()
+               $rights = MediaWikiServices::getInstance()
+                       ->getPermissionManager()
                        ->getUserPermissions( $user );
                $this->assertContains( 'test', $rights, 'sanity check' );
                $this->assertContains( 'runtest', $rights, 'sanity check' );
@@ -1556,13 +1569,14 @@ class PermissionManagerTest extends MediaWikiLangTestCase {
                $this->assertNotContains( 'nukeworld', $rights, 'sanity check' );
 
                // Add a hook manipluating the rights
-               $this->mergeMwGlobalArrayValue( 'wgHooks', [ 'UserGetRights' => [ function ( $user, &$rights ) {
+               $this->setTemporaryHook( 'UserGetRights', function ( $user, &$rights ) {
                        $rights[] = 'nukeworld';
                        $rights = array_diff( $rights, [ 'writetest' ] );
-               } ] ] );
+               } );
 
                $this->resetServices();
-               $rights = MediaWikiServices::getInstance()->getPermissionManager()
+               $rights = MediaWikiServices::getInstance()
+                       ->getPermissionManager()
                        ->getUserPermissions( $user );
                $this->assertContains( 'test', $rights );
                $this->assertContains( 'runtest', $rights );
@@ -1585,7 +1599,8 @@ class PermissionManagerTest extends MediaWikiLangTestCase {
                $userWrapper->mRequest = $mockRequest;
 
                $this->resetServices();
-               $rights = MediaWikiServices::getInstance()->getPermissionManager()
+               $rights = MediaWikiServices::getInstance()
+                       ->getPermissionManager()
                        ->getUserPermissions( $user );
                $this->assertContains( 'test', $rights );
                $this->assertNotContains( 'runtest', $rights );
@@ -1776,4 +1791,75 @@ class PermissionManagerTest extends MediaWikiLangTestCase {
                return $revision;
        }
 
+       public function provideGetRestrictionLevels() {
+               return [
+                       'No namespace restriction' => [ [ '', 'autoconfirmed', 'sysop' ], NS_TALK ],
+                       'Restricted to autoconfirmed' => [ [ '', 'sysop' ], NS_MAIN ],
+                       'Restricted to sysop' => [ [ '' ], NS_USER ],
+                       'Restricted to someone in two groups' => [ [ '', 'sysop' ], 101 ],
+                       'No special permissions' => [
+                               [ '' ],
+                               NS_TALK,
+                               []
+                       ],
+                       'autoconfirmed' => [
+                               [ '', 'autoconfirmed' ],
+                               NS_TALK,
+                               [ 'autoconfirmed' ]
+                       ],
+                       'autoconfirmed revoked' => [
+                               [ '' ],
+                               NS_TALK,
+                               [ 'autoconfirmed', 'noeditsemiprotected' ]
+                       ],
+                       'sysop' => [
+                               [ '', 'autoconfirmed', 'sysop' ],
+                               NS_TALK,
+                               [ 'sysop' ]
+                       ],
+                       'sysop with autoconfirmed revoked (a bit silly)' => [
+                               [ '', 'sysop' ],
+                               NS_TALK,
+                               [ 'sysop', 'noeditsemiprotected' ]
+                       ],
+               ];
+       }
+
+       /**
+        * @dataProvider provideGetRestrictionLevels
+        * @covers       \MediaWiki\Permissions\PermissionManager::getNamespaceRestrictionLevels
+        *
+        * @param array $expected
+        * @param int $ns
+        * @param array|null $userGroups
+        * @throws MWException
+        */
+       public function testGetRestrictionLevels( array $expected, $ns, array $userGroups = null ) {
+               $this->setMwGlobals( [
+                       'wgGroupPermissions' => [
+                               '*' => [ 'edit' => true ],
+                               'autoconfirmed' => [ 'editsemiprotected' => true ],
+                               'sysop' => [
+                                       'editsemiprotected' => true,
+                                       'editprotected' => true,
+                               ],
+                               'privileged' => [ 'privileged' => true ],
+                       ],
+                       'wgRevokePermissions' => [
+                               'noeditsemiprotected' => [ 'editsemiprotected' => true ],
+                       ],
+                       'wgNamespaceProtection' => [
+                               NS_MAIN => 'autoconfirmed',
+                               NS_USER => 'sysop',
+                               101 => [ 'editsemiprotected', 'privileged' ],
+                       ],
+                       'wgRestrictionLevels' => [ '', 'autoconfirmed', 'sysop' ],
+                       'wgAutopromote' => []
+               ] );
+               $this->resetServices();
+               $user = is_null( $userGroups ) ? null : $this->getTestUser( $userGroups )->getUser();
+               $this->assertSame( $expected, MediaWikiServices::getInstance()
+                       ->getPermissionManager()
+                       ->getNamespaceRestrictionLevels( $ns, $user ) );
+       }
 }
index 076ff36..49dcf07 100644 (file)
@@ -3,13 +3,12 @@
 namespace MediaWiki\Tests\Rest\BasicAccess;
 
 use GuzzleHttp\Psr7\Uri;
-use MediaWiki\Permissions\PermissionManager;
+use MediaWiki\MediaWikiServices;
 use MediaWiki\Rest\BasicAccess\MWBasicAuthorizer;
 use MediaWiki\Rest\Handler;
 use MediaWiki\Rest\RequestData;
 use MediaWiki\Rest\ResponseFactory;
 use MediaWiki\Rest\Router;
-use MediaWiki\User\UserIdentity;
 use MediaWikiTestCase;
 use User;
 
@@ -24,23 +23,15 @@ use User;
 class MWBasicRequestAuthorizerTest extends MediaWikiTestCase {
        private function createRouter( $userRights ) {
                $user = User::newFromName( 'Test user' );
-
-               $pm = new class( $user, $userRights ) extends PermissionManager {
-                       private $testUser;
-                       private $testUserRights;
-
-                       public function __construct( $user, $userRights ) {
-                               $this->testUser = $user;
-                               $this->testUserRights = $userRights;
-                       }
-
-                       public function userHasRight( UserIdentity $user, $action = '' ) {
-                               if ( $user === $this->testUser ) {
-                                       return $this->testUserRights[$action] ?? false;
-                               }
-                               return parent::userHasRight( $user, $action );
-                       }
-               };
+               // Don't allow the rights to everybody so that user rights kick in.
+               $this->mergeMwGlobalArrayValue( 'wgGroupPermissions', [ '*' => $userRights ] );
+               $this->resetServices();
+               $this->overrideUserPermissions(
+                       $user,
+                       array_keys( array_filter( $userRights ), function ( $value ) {
+                               return $value === true;
+                       } )
+               );
 
                global $IP;
 
@@ -50,7 +41,7 @@ class MWBasicRequestAuthorizerTest extends MediaWikiTestCase {
                        '/rest',
                        new \EmptyBagOStuff(),
                        new ResponseFactory(),
-                       new MWBasicAuthorizer( $user, $pm ) );
+                       new MWBasicAuthorizer( $user, MediaWikiServices::getInstance()->getPermissionManager() ) );
        }
 
        public function testReadDenied() {
diff --git a/tests/phpunit/includes/Rest/EntryPointTest.php b/tests/phpunit/includes/Rest/EntryPointTest.php
new file mode 100644 (file)
index 0000000..b599e9d
--- /dev/null
@@ -0,0 +1,96 @@
+<?php
+
+namespace MediaWiki\Tests\Rest;
+
+use EmptyBagOStuff;
+use GuzzleHttp\Psr7\Uri;
+use GuzzleHttp\Psr7\Stream;
+use MediaWiki\Rest\BasicAccess\StaticBasicAuthorizer;
+use MediaWiki\Rest\Handler;
+use MediaWiki\Rest\EntryPoint;
+use MediaWiki\Rest\RequestData;
+use MediaWiki\Rest\ResponseFactory;
+use MediaWiki\Rest\Router;
+use RequestContext;
+use WebResponse;
+
+/**
+ * @covers \MediaWiki\Rest\EntryPoint
+ * @covers \MediaWiki\Rest\Router
+ */
+class EntryPointTest extends \MediaWikiTestCase {
+       private static $mockHandler;
+
+       private function createRouter() {
+               global $IP;
+
+               return new Router(
+                       [ "$IP/tests/phpunit/unit/includes/Rest/testRoutes.json" ],
+                       [],
+                       '/rest',
+                       new EmptyBagOStuff(),
+                       new ResponseFactory(),
+                       new StaticBasicAuthorizer() );
+       }
+
+       private function createWebResponse() {
+               return $this->getMockBuilder( WebResponse::class )
+                       ->setMethods( [ 'header' ] )
+                       ->getMock();
+       }
+
+       public static function mockHandlerHeader() {
+               return new class extends Handler {
+                       public function execute() {
+                               $response = $this->getResponseFactory()->create();
+                               $response->setHeader( 'Foo', 'Bar' );
+                               return $response;
+                       }
+               };
+       }
+
+       public function testHeader() {
+               $webResponse = $this->createWebResponse();
+               $webResponse->expects( $this->any() )
+                       ->method( 'header' )
+                       ->withConsecutive(
+                               [ 'HTTP/1.1 200 OK', true, null ],
+                               [ 'Foo: Bar', true, null ]
+                       );
+
+               $entryPoint = new EntryPoint(
+                       RequestContext::getMain(),
+                       new RequestData( [ 'uri' => new Uri( '/rest/mock/EntryPoint/header' ) ] ),
+                       $webResponse,
+                       $this->createRouter() );
+               $entryPoint->execute();
+               $this->assertTrue( true );
+       }
+
+       public static function mockHandlerBodyRewind() {
+               return new class extends Handler {
+                       public function execute() {
+                               $response = $this->getResponseFactory()->create();
+                               $stream = new Stream( fopen( 'php://memory', 'w+' ) );
+                               $stream->write( 'hello' );
+                               $response->setBody( $stream );
+                               return $response;
+                       }
+               };
+       }
+
+       /**
+        * Make sure EntryPoint rewinds a seekable body stream before reading.
+        */
+       public function testBodyRewind() {
+               $entryPoint = new EntryPoint(
+                       RequestContext::getMain(),
+                       new RequestData( [ 'uri' => new Uri( '/rest/mock/EntryPoint/bodyRewind' ) ] ),
+                       $this->createWebResponse(),
+                       $this->createRouter() );
+               ob_start();
+               $entryPoint->execute();
+               $this->assertSame( 'hello', ob_get_clean() );
+       }
+
+}
index 47d3b92..7a4ea2d 100644 (file)
@@ -12,6 +12,7 @@ use Psr\Log\NullLogger;
 use WANObjectCache;
 use Wikimedia\Rdbms\IDatabase;
 use Wikimedia\Rdbms\LoadBalancer;
+use Wikimedia\Rdbms\MaintainableDBConnRef;
 use Wikimedia\TestingAccessWrapper;
 
 /**
@@ -51,8 +52,10 @@ class NameTableStoreTest extends MediaWikiTestCase {
                        ->disableOriginalConstructor()
                        ->getMock();
                $mock->expects( $this->any() )
-                       ->method( 'getConnection' )
-                       ->willReturn( $db );
+                       ->method( 'getConnectionRef' )
+                       ->willReturnCallback( function ( $i ) use ( $mock, $db ) {
+                               return new MaintainableDBConnRef( $mock, $db, $i );
+                       } );
                return $mock;
        }
 
index e6cf8c8..208b955 100644 (file)
@@ -942,11 +942,10 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
                        'address' => '127.0.8.1',
                        'by' => $this->user->getId(),
                        'reason' => 'no reason given',
-                       'timestamp' => $prev + 3600,
+                       'timestamp' => $prev,
                        'auto' => true,
                        'expiry' => 0
                ] );
-               $this->user->mBlock->setTimestamp( 0 );
                $this->assertEquals( [ [ 'autoblockedtext',
                                "[[User:Useruser|\u{202A}Useruser\u{202C}]]", 'no reason given', '127.0.0.1',
                                "\u{202A}Useruser\u{202C}", null, 'infinite', '127.0.8.1',
index cdd7576..6244ed6 100644 (file)
@@ -166,34 +166,30 @@ class WatchActionTest extends MediaWikiTestCase {
 
        /**
         * @covers WatchAction::doWatch()
+        * @throws Exception
         */
        public function testDoWatchNoCheckRights() {
-               $notPermittedUser = $this->getMock( User::class );
-               $notPermittedUser->method( 'isAllowed' )->willReturn( false );
-
+               $notPermittedUser = $this->getUser( null, null, [] );
                $actual = WatchAction::doWatch( $this->testWikiPage->getTitle(), $notPermittedUser, false );
-
                $this->assertTrue( $actual->isGood() );
        }
 
        /**
         * @covers WatchAction::doWatch()
+        * @throws Exception
         */
        public function testDoWatchUserNotPermittedStatusNotGood() {
-               $notPermittedUser = $this->getMock( User::class );
-               $notPermittedUser->method( 'isAllowed' )->willReturn( false );
-
+               $notPermittedUser = $this->getUser( null, null, [] );
                $actual = WatchAction::doWatch( $this->testWikiPage->getTitle(), $notPermittedUser, true );
-
                $this->assertFalse( $actual->isGood() );
        }
 
        /**
         * @covers WatchAction::doWatch()
+        * @throws Exception
         */
        public function testDoWatchCallsUserAddWatch() {
-               $permittedUser = $this->getMock( User::class );
-               $permittedUser->method( 'isAllowed' )->willReturn( true );
+               $permittedUser = $this->getUser( null, null, [ 'editmywatchlist' ] );
                $permittedUser->expects( $this->once() )
                        ->method( 'addWatch' )
                        ->with( $this->equalTo( $this->testWikiPage->getTitle() ), $this->equalTo( true ) );
@@ -205,11 +201,10 @@ class WatchActionTest extends MediaWikiTestCase {
 
        /**
         * @covers WatchAction::doUnWatch()
+        * @throws Exception
         */
        public function testDoUnWatchWithoutRights() {
-               $notPermittedUser = $this->getMock( User::class );
-               $notPermittedUser->method( 'isAllowed' )->willReturn( false );
-
+               $notPermittedUser = $this->getUser( null, null, [] );
                $actual = WatchAction::doUnWatch( $this->testWikiPage->getTitle(), $notPermittedUser );
 
                $this->assertFalse( $actual->isGood() );
@@ -219,8 +214,7 @@ class WatchActionTest extends MediaWikiTestCase {
         * @covers WatchAction::doUnWatch()
         */
        public function testDoUnWatchUserHookAborted() {
-               $permittedUser = $this->getMock( User::class );
-               $permittedUser->method( 'isAllowed' )->willReturn( true );
+               $permittedUser = $this->getUser( null, null, [ 'editmywatchlist' ] );
                Hooks::register( 'UnwatchArticle', function () {
                        return false;
                } );
@@ -235,10 +229,10 @@ class WatchActionTest extends MediaWikiTestCase {
 
        /**
         * @covers WatchAction::doUnWatch()
+        * @throws Exception
         */
        public function testDoUnWatchCallsUserRemoveWatch() {
-               $permittedUser = $this->getMock( User::class );
-               $permittedUser->method( 'isAllowed' )->willReturn( true );
+               $permittedUser = $this->getUser( null, null,  [ 'editmywatchlist' ] );
                $permittedUser->expects( $this->once() )
                        ->method( 'removeWatch' )
                        ->with( $this->equalTo( $this->testWikiPage->getTitle() ) );
@@ -250,9 +244,10 @@ class WatchActionTest extends MediaWikiTestCase {
 
        /**
         * @covers WatchAction::getWatchToken()
+        * @throws Exception
         */
        public function testGetWatchTokenNormalizesToWatch() {
-               $user = $this->getMock( User::class );
+               $user = $this->getUser( null, null );
                $user->expects( $this->once() )
                        ->method( 'getEditToken' )
                        ->with( $this->equalTo( 'watch' ) );
@@ -262,9 +257,10 @@ class WatchActionTest extends MediaWikiTestCase {
 
        /**
         * @covers WatchAction::getWatchToken()
+        * @throws Exception
         */
        public function testGetWatchTokenProxiesUserGetEditToken() {
-               $user = $this->getMock( User::class );
+               $user = $this->getUser( null, null );
                $user->expects( $this->once() )->method( 'getEditToken' );
 
                WatchAction::getWatchToken( $this->watchAction->getTitle(), $user );
@@ -272,9 +268,10 @@ class WatchActionTest extends MediaWikiTestCase {
 
        /**
         * @covers WatchAction::doWatchOrUnwatch()
+        * @throws Exception
         */
        public function testDoWatchOrUnwatchUserNotLoggedIn() {
-               $user = $this->getLoggedInIsWatchedUser( false );
+               $user = $this->getUser( false );
                $user->expects( $this->never() )->method( 'removeWatch' );
                $user->expects( $this->never() )->method( 'addWatch' );
 
@@ -285,9 +282,10 @@ class WatchActionTest extends MediaWikiTestCase {
 
        /**
         * @covers WatchAction::doWatchOrUnwatch()
+        * @throws Exception
         */
        public function testDoWatchOrUnwatchSkipsIfAlreadyWatched() {
-               $user = $this->getLoggedInIsWatchedUser();
+               $user = $this->getUser();
                $user->expects( $this->never() )->method( 'removeWatch' );
                $user->expects( $this->never() )->method( 'addWatch' );
 
@@ -298,9 +296,10 @@ class WatchActionTest extends MediaWikiTestCase {
 
        /**
         * @covers WatchAction::doWatchOrUnwatch()
+        * @throws Exception
         */
        public function testDoWatchOrUnwatchSkipsIfAlreadyUnWatched() {
-               $user = $this->getLoggedInIsWatchedUser( true, false );
+               $user = $this->getUser( true, false );
                $user->expects( $this->never() )->method( 'removeWatch' );
                $user->expects( $this->never() )->method( 'addWatch' );
 
@@ -311,9 +310,10 @@ class WatchActionTest extends MediaWikiTestCase {
 
        /**
         * @covers WatchAction::doWatchOrUnwatch()
+        * @throws Exception
         */
        public function testDoWatchOrUnwatchWatchesIfWatch() {
-               $user = $this->getLoggedInIsWatchedUser( true, false );
+               $user = $this->getUser( true, false );
                $user->expects( $this->never() )->method( 'removeWatch' );
                $user->expects( $this->once() )
                        ->method( 'addWatch' )
@@ -326,10 +326,10 @@ class WatchActionTest extends MediaWikiTestCase {
 
        /**
         * @covers WatchAction::doWatchOrUnwatch()
+        * @throws Exception
         */
        public function testDoWatchOrUnwatchUnwatchesIfUnwatch() {
-               $user = $this->getLoggedInIsWatchedUser();
-               $user->method( 'isAllowed' )->willReturn( true );
+               $user = $this->getUser( true, true, [ 'editmywatchlist' ] );
                $user->expects( $this->never() )->method( 'addWatch' );
                $user->expects( $this->once() )
                        ->method( 'removeWatch' )
@@ -343,13 +343,20 @@ class WatchActionTest extends MediaWikiTestCase {
        /**
         * @param bool $isLoggedIn Whether the user should be "marked" as logged in
         * @param bool $isWatched The value any call to isWatched should return
+        * @param array $permissions The permissions of the user
         * @return PHPUnit_Framework_MockObject_MockObject
+        * @throws Exception
         */
-       private function getLoggedInIsWatchedUser( $isLoggedIn = true, $isWatched = true ) {
+       private function getUser(
+               $isLoggedIn = true,
+               $isWatched = true,
+               $permissions = []
+       ) {
                $user = $this->getMock( User::class );
+               $user->method( 'getId' )->willReturn( 42 );
                $user->method( 'isLoggedIn' )->willReturn( $isLoggedIn );
                $user->method( 'isWatched' )->willReturn( $isWatched );
-
+               $this->overrideUserPermissions( $user, $permissions );
                return $user;
        }
 
index 30ba1c1..bdce70c 100644 (file)
@@ -29,8 +29,6 @@ class ApiOptionsTest extends MediaWikiLangTestCase {
                // Set up groups and rights
                $this->mUserMock->expects( $this->any() )
                        ->method( 'getEffectiveGroups' )->will( $this->returnValue( [ '*', 'user' ] ) );
-               $this->mUserMock->expects( $this->any() )
-                       ->method( 'isAllowedAny' )->will( $this->returnValue( true ) );
 
                // Set up callback for User::getOptionKinds
                $this->mUserMock->expects( $this->any() )
@@ -49,6 +47,7 @@ class ApiOptionsTest extends MediaWikiLangTestCase {
                $this->mContext->getContext()->setTitle( Title::newFromText( 'Test' ) );
                $this->mContext->setUser( $this->mUserMock );
 
+               $this->overrideUserPermissions( $this->mUserMock, [ 'editmyoptions' ] );
                $main = new ApiMain( $this->mContext );
 
                // Empty session
index 1f7c00b..b4144fd 100644 (file)
@@ -1,5 +1,7 @@
 <?php
 
+use MediaWiki\MediaWikiServices;
+
 /**
  * @group API
  * @group Database
@@ -18,7 +20,9 @@ class ApiTokensTest extends ApiTestCase {
        protected function runTokenTest( TestUser $user ) {
                $tokens = $this->getTokenList( $user );
 
-               $rights = $user->getUser()->getRights();
+               $rights = MediaWikiServices::getInstance()
+                       ->getPermissionManager()
+                       ->getUserPermissions( $user->getUser() );
 
                $this->assertArrayHasKey( 'edittoken', $tokens );
                $this->assertArrayHasKey( 'movetoken', $tokens );
index 71f76e7..e085d88 100644 (file)
@@ -56,7 +56,8 @@ class BlockManagerTest extends MediaWikiTestCase {
                                MediaWikiServices::getInstance()->getMainConfig()
                        ),
                        $this->user,
-                       $this->user->getRequest()
+                       $this->user->getRequest(),
+                       MediaWikiServices::getInstance()->getPermissionManager()
                ];
        }
 
index 43e7075..f037a8c 100644 (file)
@@ -1,5 +1,6 @@
 <?php
 
+use Psr\Log\NullLogger;
 use Wikimedia\Rdbms\TransactionProfiler;
 use Wikimedia\Rdbms\DatabaseDomain;
 use Wikimedia\Rdbms\Database;
@@ -43,19 +44,31 @@ class DatabaseTestHelper extends Database {
        protected $unionSupportsOrderAndLimit = true;
 
        public function __construct( $testName, array $opts = [] ) {
+               parent::__construct( $opts + [
+                       'host' => null,
+                       'user' => null,
+                       'password' => null,
+                       'dbname' => null,
+                       'schema' => null,
+                       'tablePrefix' => '',
+                       'flags' => 0,
+                       'cliMode' => $opts['cliMode'] ?? true,
+                       'agent' => '',
+                       'srvCache' => new HashBagOStuff(),
+                       'profiler' => null,
+                       'trxProfiler' => new TransactionProfiler(),
+                       'connLogger' => new NullLogger(),
+                       'queryLogger' => new NullLogger(),
+                       'errorLogger' => function ( Exception $e ) {
+                               wfWarn( get_class( $e ) . ": {$e->getMessage()}" );
+                       },
+                       'deprecationLogger' => function ( $msg ) {
+                               wfWarn( $msg );
+                       }
+               ] );
+
                $this->testName = $testName;
 
-               $this->profiler = null;
-               $this->trxProfiler = new TransactionProfiler();
-               $this->cliMode = $opts['cliMode'] ?? true;
-               $this->connLogger = new \Psr\Log\NullLogger();
-               $this->queryLogger = new \Psr\Log\NullLogger();
-               $this->errorLogger = function ( Exception $e ) {
-                       wfWarn( get_class( $e ) . ": {$e->getMessage()}" );
-               };
-               $this->deprecationLogger = function ( $msg ) {
-                       wfWarn( $msg );
-               };
                $this->currentDomain = DatabaseDomain::newUnspecified();
                $this->open( 'localhost', 'testuser', 'password', 'testdb', null, '' );
        }
index b377c63..7e5ff84 100644 (file)
@@ -373,4 +373,27 @@ class DeferredUpdatesTest extends MediaWikiTestCase {
                DeferredUpdates::tryOpportunisticExecute( 'run' );
                $this->assertEquals( [ 'oti', 1, 2 ], $calls );
        }
+
+       /**
+        * @covers DeferredUpdates::attemptUpdate
+        */
+       public function testCallbackUpdateRounds() {
+               $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
+
+               $fname = __METHOD__;
+               $called = false;
+               DeferredUpdates::attemptUpdate(
+                       new MWCallableUpdate(
+                               function () use ( $lbFactory, $fname, &$called ) {
+                                       $lbFactory->flushReplicaSnapshots( $fname );
+                                       $lbFactory->commitMasterChanges( $fname );
+                                       $called = true;
+                               },
+                               $fname
+                       ),
+                       $lbFactory
+               );
+
+               $this->assertTrue( $called, "Callback ran" );
+       }
 }
diff --git a/tests/phpunit/includes/filebackend/lockmanager/LockManagerGroupIntegrationTest.php b/tests/phpunit/includes/filebackend/lockmanager/LockManagerGroupIntegrationTest.php
new file mode 100644 (file)
index 0000000..0615e95
--- /dev/null
@@ -0,0 +1,84 @@
+<?php
+
+use Wikimedia\Rdbms\ILoadBalancer;
+use Wikimedia\Rdbms\LBFactory;
+
+/**
+ * Most of the file is covered by the unit test and/or FileBackendTest. Here we fill in the missing
+ * bits that don't work with unit tests yet.
+ *
+ * @covers LockManagerGroup
+ */
+class LockManagerGroupIntegrationTest extends MediaWikiIntegrationTestCase {
+       public function testWgLockManagers() {
+               $this->setMwGlobals( 'wgLockManagers',
+                       [ [ 'name' => 'a', 'class' => 'b' ], [ 'name' => 'c', 'class' => 'd' ] ] );
+               LockManagerGroup::destroySingletons();
+
+               $lmg = LockManagerGroup::singleton();
+               $domain = WikiMap::getCurrentWikiDbDomain()->getId();
+
+               $this->assertSame(
+                       [ 'class' => 'b', 'name' => 'a', 'domain' => $domain ],
+                       $lmg->config( 'a' ) );
+               $this->assertSame(
+                       [ 'class' => 'd', 'name' => 'c', 'domain' => $domain ],
+                       $lmg->config( 'c' ) );
+       }
+
+       public function testSingletonFalse() {
+               $this->setMwGlobals( 'wgLockManagers', [ [ 'name' => 'a', 'class' => 'b' ] ] );
+               LockManagerGroup::destroySingletons();
+
+               $this->assertSame(
+                       WikiMap::getCurrentWikiDbDomain()->getId(),
+                       LockManagerGroup::singleton( false )->config( 'a' )['domain']
+               );
+       }
+
+       public function testSingletonNull() {
+               $this->setMwGlobals( 'wgLockManagers', [ [ 'name' => 'a', 'class' => 'b' ] ] );
+               LockManagerGroup::destroySingletons();
+
+               $this->assertSame(
+                       null,
+                       LockManagerGroup::singleton( null )->config( 'a' )['domain']
+               );
+       }
+
+       public function testDestroySingletons() {
+               $instance = LockManagerGroup::singleton();
+               $this->assertSame( $instance, LockManagerGroup::singleton() );
+               LockManagerGroup::destroySingletons();
+               $this->assertNotSame( $instance, LockManagerGroup::singleton() );
+       }
+
+       public function testDestroySingletonsNamedDomain() {
+               $instance = LockManagerGroup::singleton( 'domain' );
+               $this->assertSame( $instance, LockManagerGroup::singleton( 'domain' ) );
+               LockManagerGroup::destroySingletons();
+               $this->assertNotSame( $instance, LockManagerGroup::singleton( 'domain' ) );
+       }
+
+       public function testGetDBLockManager() {
+               $this->markTestSkipped( 'DBLockManager case in LockManagerGroup::get appears to be ' .
+                       'broken, tries to instantiate an abstract class' );
+
+               $mockLB = $this->createMock( ILoadBalancer::class );
+               $mockLB->expects( $this->never() )
+                       ->method( $this->anythingBut( '__destruct', 'getLazyConnectionRef' ) );
+               $mockLB->expects( $this->once() )->method( 'getLazyConnectionRef' )
+                       ->with( DB_MASTER, [], 'domain', $mockLB::CONN_TRX_AUTOCOMMIT )
+                       ->willReturn( 'bogus value' );
+
+               $mockLBFactory = $this->createMock( LBFactory::class );
+               $mockLBFactory->expects( $this->never() )
+                       ->method( $this->anythingBut( '__destruct', 'getMainLB' ) );
+               $mockLBFactory->expects( $this->once() )->method( 'getMainLB' )->with( 'domain' )
+                       ->willReturn( $mockLB );
+
+               $lmg = new LockManagerGroup( 'domain',
+                       [ [ 'name' => 'a', 'class' => DBLockManager::class ] ], $mockLBFactory );
+               $this->assertSame( [], $lmg->get( 'a' ) );
+       }
+}
diff --git a/tests/phpunit/includes/language/ConverterRuleTest.php b/tests/phpunit/includes/language/ConverterRuleTest.php
new file mode 100644 (file)
index 0000000..1e06142
--- /dev/null
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * @covers ConverterRule
+ */
+class ConverterRuleTest extends MediaWikiTestCase {
+
+       public function setUp() {
+               parent::setUp();
+               $this->setMwGlobals( 'wgUser', new User );
+       }
+
+       public function testParseEmpty() {
+               $converter = new LanguageConverter( new Language(), 'en' );
+               $rule = new ConverterRule( '', $converter );
+               $rule->parse();
+
+               $this->assertSame( false, $rule->getTitle(), 'title' );
+               $this->assertSame( [], $rule->getConvTable(), 'conversion table' );
+               $this->assertSame( 'none', $rule->getRulesAction(), 'rules action' );
+       }
+
+}
index 42805b2..325babc 100644 (file)
@@ -1,6 +1,22 @@
 <?php
 
+use MediaWiki\MediaWikiServices;
+use Wikimedia\TestingAccessWrapper;
+
+/**
+ * Just to test one deprecated method and one line of ServiceWiring code.
+ */
 class TempFSFileIntegrationTest extends MediaWikiIntegrationTestCase {
+       /**
+        * @coversNothing
+        */
+       public function testServiceWiring() {
+               $this->setMwGlobals( 'wgTmpDirectory', '/hopefully invalid' );
+               $factory = MediaWikiServices::getInstance()->getTempFSFileFactory();
+               $this->assertSame( '/hopefully invalid',
+                       ( TestingAccessWrapper::newFromObject( $factory ) )->tmpDirectory );
+       }
+
        use TempFSFileTestTrait;
 
        private function newFile() {
index f496fa3..d239ac1 100644 (file)
@@ -290,6 +290,10 @@ class BagOStuffTest extends MediaWikiTestCase {
 
                $val = $this->cache->incrWithInit( $key, 0, 1, 3 );
                $this->assertEquals( 4, $val, "Correct init value" );
+               $this->cache->delete( $key );
+
+               $val = $this->cache->incrWithInit( $key, 0, 5 );
+               $this->assertEquals( 5, $val, "Correct init value" );
        }
 
        /**
index 329c642..ac988e6 100644 (file)
@@ -1348,6 +1348,35 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase {
                }
        }
 
+       /**
+        * @covers WANObjectCache::get()
+        * @covers WANObjectCache::processCheckKeys()
+        */
+       public function testCheckKeyHoldoff() {
+               $cache = $this->cache;
+               $key = wfRandomString();
+               $checkKey = wfRandomString();
+
+               $mockWallClock = 1549343530.2053;
+               $cache->setMockTime( $mockWallClock );
+               $cache->touchCheckKey( $checkKey, 8 );
+
+               $mockWallClock += 1;
+               $cache->set( $key, 1, 60 );
+               $this->assertEquals( 1, $cache->get( $key, $curTTL, [ $checkKey ] ) );
+               $this->assertLessThan( 0, $curTTL, "Key in hold-off due to check key" );
+
+               $mockWallClock += 3;
+               $cache->set( $key, 1, 60 );
+               $this->assertEquals( 1, $cache->get( $key, $curTTL, [ $checkKey ] ) );
+               $this->assertLessThan( 0, $curTTL, "Key in hold-off due to check key" );
+
+               $mockWallClock += 10;
+               $cache->set( $key, 1, 60 );
+               $this->assertEquals( 1, $cache->get( $key, $curTTL, [ $checkKey ] ) );
+               $this->assertGreaterThan( 0, $curTTL, "Key not in hold-off due to check key" );
+       }
+
        /**
         * @covers WANObjectCache::delete
         * @covers WANObjectCache::relayDelete
index cbafbe9..30973c8 100644 (file)
@@ -1434,7 +1434,13 @@ more stuff
                                                        . " nonumy eirmod tempor invidunt ut labore et dolore magna "
                                                        . "aliquyam erat, sed diam voluptua. At vero eos et accusam "
                                                        . "et justo duo dolores et ea rebum. Stet clita kasd gubergren, "
-                                                       . "no sea  takimata sanctus est Lorem ipsum dolor sit amet.'",
+                                                       . "no sea  takimata sanctus est Lorem ipsum dolor sit amet. "
+                                                       . " this here is some more filler content added to try and "
+                                                       . "reach the maximum automatic summary length so that this is"
+                                                       . " truncated ipot sodit colrad ut ad olve amit basul dat"
+                                                       . "Dorbet romt crobit trop bri. DannyS712 put me here lor pe"
+                                                       . " ode quob zot bozro see also T22281 for background pol sup"
+                                                       . "Lorem ipsum dolor sit amet'",
                                                null
                                        ],
                                ],
index 651c871..4175ead 100644 (file)
@@ -234,6 +234,7 @@ class ParserMethodsTest extends MediaWikiLangTestCase {
                $po = new ParserOptions( $frank );
 
                yield 'current' => [ $text, $po, 0, 'user:CurrentAuthor;id:200;time:20160606000000;' ];
+               yield 'current' => [ $text, $po, null, 'user:;id:;time:' ];
                yield 'current with ID' => [ $text, $po, 200, 'user:CurrentAuthor;id:200;time:20160606000000;' ];
 
                $text = '* user:{{REVISIONUSER}};id:{{REVISIONID}};time:{{REVISIONTIMESTAMP}};';
index f6fd824..c748e2c 100644 (file)
@@ -35,6 +35,7 @@ class ResourceLoaderContextTest extends PHPUnit\Framework\TestCase {
                // Misc
                $this->assertEquals( 'ltr', $ctx->getDirection() );
                $this->assertEquals( 'qqx|fallback||||||||', $ctx->getHash() );
+               $this->assertSame( [], $ctx->getReqBase() );
                $this->assertInstanceOf( User::class, $ctx->getUserObj() );
        }
 
@@ -75,6 +76,7 @@ class ResourceLoaderContextTest extends PHPUnit\Framework\TestCase {
                // Misc
                $this->assertEquals( 'ltr', $ctx->getDirection() );
                $this->assertEquals( 'zh|fallback|||styles|||||', $ctx->getHash() );
+               $this->assertSame( [ 'lang' => 'zh' ], $ctx->getReqBase() );
        }
 
        public static function provideDirection() {
index 213eed2..d4462e9 100644 (file)
@@ -326,13 +326,13 @@ mw.loader.register([
         "test.group.foo",
         "{blankVer}",
         [],
-        "x-foo"
+        2
     ],
     [
         "test.group.bar",
         "{blankVer}",
         [],
-        "x-bar"
+        3
     ]
 ]);'
                        ] ],
@@ -640,25 +640,25 @@ mw.loader.register([
         "test.group.foo.1",
         "{blankVer}",
         [],
-        "x-foo"
+        2
     ],
     [
         "test.group.foo.2",
         "{blankVer}",
         [],
-        "x-foo"
+        2
     ],
     [
         "test.group.bar.1",
         "{blankVer}",
         [],
-        "x-bar"
+        3
     ],
     [
         "test.group.bar.2",
         "{blankVer}",
         [],
-        "x-bar",
+        3,
         "example"
     ]
 ]);'
index 86c2e9f..ac4a1ca 100644 (file)
@@ -1095,6 +1095,32 @@ END
                $rl->respond( $context );
        }
 
+       /**
+        * Refuse requests for private modules.
+        *
+        * @covers ResourceLoader::respond
+        */
+       public function testRespondErrorPrivate() {
+               $rl = $this->getMockBuilder( EmptyResourceLoader::class )
+                       ->setMethods( [
+                               'measureResponseTime',
+                               'tryRespondNotModified',
+                               'sendResponseHeaders',
+                       ] )
+                       ->getMock();
+               $rl->register( [
+                       'foo' => [ 'class' => ResourceLoaderTestModule::class ],
+                       'bar' => [ 'class' => ResourceLoaderTestModule::class, 'group' => 'private' ],
+               ] );
+               $context = $this->getResourceLoaderContext(
+                       [ 'modules' => 'foo|bar', 'only' => null ],
+                       $rl
+               );
+
+               $this->expectOutputRegex( '/^\/\*.+Cannot build private module/s' );
+               $rl->respond( $context );
+       }
+
        /**
         * @covers ResourceLoader::respond
         */
index 90f6ad9..510a2f2 100644 (file)
@@ -24,7 +24,6 @@ class SpecialPreferencesTest extends MediaWikiTestCase {
        public function testT43337() {
                // Set a low limit
                $this->setMwGlobals( 'wgMaxSigChars', 2 );
-
                $user = $this->createMock( User::class );
                $user->expects( $this->any() )
                        ->method( 'isAnon' )
@@ -47,6 +46,10 @@ class SpecialPreferencesTest extends MediaWikiTestCase {
                $user->method( 'getOptions' )
                        ->willReturn( [] );
 
+               // isAnyAllowed used to return null from the mock,
+               // thus revoke it's permissions.
+               $this->overrideUserPermissions( $user, [] );
+
                # Forge a request to call the special page
                $context = new RequestContext();
                $context->setRequest( new FauxRequest() );
index 028c438..7f97a16 100644 (file)
@@ -54,14 +54,12 @@ class NamespaceInfoTest extends MediaWikiTestCase {
                'ExtraNamespaces' => [],
                'ExtraSignatureNamespaces' => [],
                'NamespaceContentModels' => [],
-               'NamespaceProtection' => [],
                'NamespacesWithSubpages' => [
                        NS_TALK => true,
                        NS_USER => true,
                        NS_USER_TALK => true,
                ],
                'NonincludableNamespaces' => [],
-               'RestrictionLevels' => [ '', 'autoconfirmed', 'sysop' ],
        ];
 
        private function newObj( array $options = [] ) : NamespaceInfo {
@@ -1245,53 +1243,17 @@ class NamespaceInfoTest extends MediaWikiTestCase {
         */
 
        /**
-        * This mock user can only have isAllowed() called on it.
-        *
-        * @param array $groups Groups for the mock user to have
-        * @return User
-        */
-       private function getMockUser( array $groups = [] ) : User {
-               $groups[] = '*';
-
-               $mock = $this->createMock( User::class );
-               $mock->method( 'isAllowed' )->will( $this->returnCallback(
-                       function ( $action ) use ( $groups ) {
-                               global $wgGroupPermissions, $wgRevokePermissions;
-                               if ( $action == '' ) {
-                                       return true;
-                               }
-                               foreach ( $wgRevokePermissions as $group => $rights ) {
-                                       if ( !in_array( $group, $groups ) ) {
-                                               continue;
-                                       }
-                                       if ( isset( $rights[$action] ) && $rights[$action] ) {
-                                               return false;
-                                       }
-                               }
-                               foreach ( $wgGroupPermissions as $group => $rights ) {
-                                       if ( !in_array( $group, $groups ) ) {
-                                               continue;
-                                       }
-                                       if ( isset( $rights[$action] ) && $rights[$action] ) {
-                                               return true;
-                                       }
-                               }
-                               return false;
-                       }
-               ) );
-               $mock->expects( $this->never() )->method( $this->anythingBut( 'isAllowed' ) );
-               return $mock;
-       }
-
-       /**
+        * TODO: This is superceeded by PermissionManagerTest::testGetNamespaceRestrictionLevels
+        * Remove when deprecated method is removed.
         * @dataProvider provideGetRestrictionLevels
-        * @covers NamespaceInfo::getRestrictionLevels
+        * @covers       NamespaceInfo::getRestrictionLevels
         *
         * @param array $expected
         * @param int $ns
-        * @param User|null $user
+        * @param array|null $groups
+        * @throws MWException
         */
-       public function testGetRestrictionLevels( array $expected, $ns, User $user = null ) {
+       public function testGetRestrictionLevels( array $expected, $ns, array $groups = null ) {
                $this->setMwGlobals( [
                        'wgGroupPermissions' => [
                                '*' => [ 'edit' => true ],
@@ -1305,14 +1267,17 @@ class NamespaceInfoTest extends MediaWikiTestCase {
                        'wgRevokePermissions' => [
                                'noeditsemiprotected' => [ 'editsemiprotected' => true ],
                        ],
-               ] );
-               $obj = $this->newObj( [
-                       'NamespaceProtection' => [
+                       'wgNamespaceProtection' => [
                                NS_MAIN => 'autoconfirmed',
                                NS_USER => 'sysop',
                                101 => [ 'editsemiprotected', 'privileged' ],
                        ],
+                       'wgRestrictionLevels' => [ '', 'autoconfirmed', 'sysop' ],
+                       'wgAutopromote' => []
                ] );
+               $this->resetServices();
+               $obj = $this->newObj();
+               $user = is_null( $groups ) ? null : $this->getTestUser( $groups )->getUser();
                $this->assertSame( $expected, $obj->getRestrictionLevels( $ns, $user ) );
        }
 
@@ -1322,26 +1287,26 @@ class NamespaceInfoTest extends MediaWikiTestCase {
                        'Restricted to autoconfirmed' => [ [ '', 'sysop' ], NS_MAIN ],
                        'Restricted to sysop' => [ [ '' ], NS_USER ],
                        'Restricted to someone in two groups' => [ [ '', 'sysop' ], 101 ],
-                       'No special permissions' => [ [ '' ], NS_TALK, $this->getMockUser() ],
+                       'No special permissions' => [ [ '' ], NS_TALK, [] ],
                        'autoconfirmed' => [
                                [ '', 'autoconfirmed' ],
                                NS_TALK,
-                               $this->getMockUser( [ 'autoconfirmed' ] )
+                               [ 'autoconfirmed' ]
                        ],
                        'autoconfirmed revoked' => [
                                [ '' ],
                                NS_TALK,
-                               $this->getMockUser( [ 'autoconfirmed', 'noeditsemiprotected' ] )
+                               [ 'autoconfirmed', 'noeditsemiprotected' ]
                        ],
                        'sysop' => [
                                [ '', 'autoconfirmed', 'sysop' ],
                                NS_TALK,
-                               $this->getMockUser( [ 'sysop' ] )
+                               [ 'sysop' ]
                        ],
                        'sysop with autoconfirmed revoked (a bit silly)' => [
                                [ '', 'sysop' ],
                                NS_TALK,
-                               $this->getMockUser( [ 'sysop', 'noeditsemiprotected' ] )
+                               [ 'sysop', 'noeditsemiprotected' ]
                        ],
                ];
        }
index 62e8e23..f48385d 100644 (file)
@@ -105,6 +105,7 @@ class UserTest extends MediaWikiTestCase {
        }
 
        /**
+        * TODO: Remove. This is the same as PermissionManagerTest::testGetUserPermissions
         * @covers User::getRights
         */
        public function testUserPermissions() {
@@ -116,6 +117,7 @@ class UserTest extends MediaWikiTestCase {
        }
 
        /**
+        * TODO: Remove. This is the same as PermissionManagerTest::testGetUserPermissionsHooks
         * @covers User::getRights
         */
        public function testUserGetRightsHooks() {
index 6fa911b..53edbf2 100644 (file)
@@ -26,6 +26,7 @@ class DatabaseSqliteTest extends \MediaWikiIntegrationTestCase {
                $this->db = $this->getMockBuilder( DatabaseSqlite::class )
                        ->setConstructorArgs( [ [
                                'dbFilePath' => ':memory:',
+                               'dbname' => 'Foo',
                                'schema' => false,
                                'host' => false,
                                'user' => false,
diff --git a/tests/phpunit/unit/includes/BadFileLookupTest.php b/tests/phpunit/unit/includes/BadFileLookupTest.php
new file mode 100644 (file)
index 0000000..6ecfe37
--- /dev/null
@@ -0,0 +1,185 @@
+<?php
+
+use MediaWiki\BadFileLookup;
+
+/**
+ * @coversDefaultClass MediaWiki\BadFileLookup
+ */
+class BadFileLookupTest extends MediaWikiUnitTestCase {
+       /** Shared with GlobalWithDBTest */
+       const BLACKLIST = <<<WIKITEXT
+Comment line, no effect [[File:Good.jpg]]
+ * Indented list is also a comment [[File:Good.jpg]]
+* [[File:Bad.jpg]] except [[Nasty page]]
+*[[Image:Bad2.jpg]] also works
+* So does [[Bad3.jpg]]
+* [[User:Bad4.jpg]] works although it is silly
+* [[File:Redirect to good.jpg]] doesn't do anything if RepoGroup is working, because we only look at
+  the final name, but will work if RepoGroup returns null
+* List line with no link
+* [[Malformed title<>]] doesn't break anything, the line is ignored [[File:Good.jpg]]
+* [[File:Bad5.jpg]] before [[malformed title<>]] doesn't ignore the line
+WIKITEXT;
+
+       /** Shared with GlobalWithDBTest */
+       public static function badImageHook( $name, &$bad ) {
+               switch ( $name ) {
+               case 'Hook_bad.jpg':
+               case 'Redirect_to_hook_good.jpg':
+                       $bad = true;
+                       return false;
+
+               case 'Hook_good.jpg':
+               case 'Redirect_to_hook_bad.jpg':
+                       $bad = false;
+                       return false;
+               }
+
+               return true;
+       }
+
+       private function getMockRepoGroup() {
+               $mock = $this->createMock( RepoGroup::class );
+               $mock->expects( $this->once() )->method( 'findFile' )
+                       ->will( $this->returnCallback( function ( $name ) {
+                               $mockFile = $this->createMock( File::class );
+                               $mockFile->expects( $this->once() )->method( 'getTitle' )
+                                       ->will( $this->returnCallback( function () use ( $name ) {
+                                               switch ( $name ) {
+                                               case 'Redirect to bad.jpg':
+                                                       return new TitleValue( NS_FILE, 'Bad.jpg' );
+                                               case 'Redirect_to_good.jpg':
+                                                       return new TitleValue( NS_FILE, 'Good.jpg' );
+                                               case 'Redirect to hook bad.jpg':
+                                                       return new TitleValue( NS_FILE, 'Hook_bad.jpg' );
+                                               case 'Redirect to hook good.jpg':
+                                                       return new TitleValue( NS_FILE, 'Hook_good.jpg' );
+                                               default:
+                                                       return new TitleValue( NS_FILE, $name );
+                                               }
+                                       } ) );
+                               $mockFile->expects( $this->never() )->method( $this->anythingBut( 'getTitle' ) );
+                               return $mockFile;
+                       } ) );
+               $mock->expects( $this->never() )->method( $this->anythingBut( 'findFile' ) );
+
+               return $mock;
+       }
+
+       /**
+        * Just returns null for every findFile().
+        */
+       private function getMockRepoGroupNull() {
+               $mock = $this->createMock( RepoGroup::class );
+               $mock->expects( $this->once() )->method( 'findFile' )->willReturn( null );
+               $mock->expects( $this->never() )->method( $this->anythingBut( 'findFile' ) );
+
+               return $mock;
+       }
+
+       private function getMockTitleParser() {
+               $mock = $this->createMock( TitleParser::class );
+               $mock->method( 'parseTitle' )->will( $this->returnCallback( function ( $text ) {
+                       if ( strpos( $text, '<' ) !== false ) {
+                               throw $this->createMock( MalformedTitleException::class );
+                       }
+                       if ( strpos( $text, ':' ) === false ) {
+                               return new TitleValue( NS_MAIN, $text );
+                       }
+                       list( $ns, $text ) = explode( ':', $text );
+                       switch ( $ns ) {
+                       case 'Image':
+                       case 'File':
+                               $ns = NS_FILE;
+                               break;
+
+                       case 'User':
+                               $ns = NS_USER;
+                               break;
+                       }
+                       return new TitleValue( $ns, $text );
+               } ) );
+               $mock->expects( $this->never() )->method( $this->anythingBut( 'parseTitle' ) );
+
+               return $mock;
+       }
+
+       public function setUp() {
+               parent::setUp();
+
+               $this->setTemporaryHook( 'BadImage', __CLASS__ . '::badImageHook' );
+       }
+
+       /**
+        * @dataProvider provideIsBadFile
+        * @covers ::__construct
+        * @covers ::isBadFile
+        */
+       public function testIsBadFile( $name, $title, $expected ) {
+               $bfl = new BadFileLookup(
+                       function () {
+                               return self::BLACKLIST;
+                       },
+                       new EmptyBagOStuff,
+                       $this->getMockRepoGroup(),
+                       $this->getMockTitleParser()
+               );
+
+               $this->assertSame( $expected, $bfl->isBadFile( $name, $title ) );
+       }
+
+       /**
+        * @dataProvider provideIsBadFile
+        * @covers ::__construct
+        * @covers ::isBadFile
+        */
+       public function testIsBadFile_nullRepoGroup( $name, $title, $expected ) {
+               $bfl = new BadFileLookup(
+                       function () {
+                               return self::BLACKLIST;
+                       },
+                       new EmptyBagOStuff,
+                       $this->getMockRepoGroupNull(),
+                       $this->getMockTitleParser()
+               );
+
+               // Hack -- these expectations are reversed if the repo group returns null. In that case 1)
+               // we don't honor redirects, and 2) we don't replace spaces by underscores (which makes the
+               // hook not see 'Hook bad.jpg').
+               if ( in_array( $name, [
+                       'Redirect to bad.jpg',
+                       'Redirect_to_good.jpg',
+                       'Hook bad.jpg',
+                       'Redirect to hook bad.jpg',
+               ] ) ) {
+                       $expected = !$expected;
+               }
+
+               $this->assertSame( $expected, $bfl->isBadFile( $name, $title ) );
+       }
+
+       /** Shared with GlobalWithDBTest */
+       public static function provideIsBadFile() {
+               return [
+                       'No context page' => [ 'Bad.jpg', null, true ],
+                       'Context page not whitelisted' =>
+                               [ 'Bad.jpg', new TitleValue( NS_MAIN, 'A page' ), true ],
+                       'Good image' => [ 'Good.jpg', null, false ],
+                       'Whitelisted context page' =>
+                               [ 'Bad.jpg', new TitleValue( NS_MAIN, 'Nasty page' ), false ],
+                       'Bad image with Image:' => [ 'Image:Bad.jpg', null, false ],
+                       'Bad image with File:' => [ 'File:Bad.jpg', null, false ],
+                       'Bad image with Image: in blacklist' => [ 'Bad2.jpg', null, true ],
+                       'Bad image without prefix in blacklist' => [ 'Bad3.jpg', null, true ],
+                       'Bad image with different namespace in blacklist' => [ 'Bad4.jpg', null, true ],
+                       'Redirect to bad image' => [ 'Redirect to bad.jpg', null, true ],
+                       'Redirect to good image' => [ 'Redirect_to_good.jpg', null, false ],
+                       'Hook says bad (with space)' => [ 'Hook bad.jpg', null, true ],
+                       'Hook says bad (with underscore)' => [ 'Hook_bad.jpg', null, true ],
+                       'Hook says good' => [ 'Hook good.jpg', null, false ],
+                       'Redirect to hook bad image' => [ 'Redirect to hook bad.jpg', null, true ],
+                       'Redirect to hook good image' => [ 'Redirect to hook good.jpg', null, false ],
+                       'Malformed title doesn\'t break the line' => [ 'Bad5.jpg', null, true ],
+               ];
+       }
+}
diff --git a/tests/phpunit/unit/includes/Rest/EntryPointTest.php b/tests/phpunit/unit/includes/Rest/EntryPointTest.php
deleted file mode 100644 (file)
index a74c0cb..0000000
+++ /dev/null
@@ -1,91 +0,0 @@
-<?php
-
-namespace MediaWiki\Tests\Rest;
-
-use EmptyBagOStuff;
-use GuzzleHttp\Psr7\Uri;
-use GuzzleHttp\Psr7\Stream;
-use MediaWiki\Rest\BasicAccess\StaticBasicAuthorizer;
-use MediaWiki\Rest\Handler;
-use MediaWiki\Rest\EntryPoint;
-use MediaWiki\Rest\RequestData;
-use MediaWiki\Rest\ResponseFactory;
-use MediaWiki\Rest\Router;
-use WebResponse;
-
-/**
- * @covers \MediaWiki\Rest\EntryPoint
- * @covers \MediaWiki\Rest\Router
- */
-class EntryPointTest extends \MediaWikiUnitTestCase {
-       private static $mockHandler;
-
-       private function createRouter() {
-               return new Router(
-                       [ __DIR__ . '/testRoutes.json' ],
-                       [],
-                       '/rest',
-                       new EmptyBagOStuff(),
-                       new ResponseFactory(),
-                       new StaticBasicAuthorizer() );
-       }
-
-       private function createWebResponse() {
-               return $this->getMockBuilder( WebResponse::class )
-                       ->setMethods( [ 'header' ] )
-                       ->getMock();
-       }
-
-       public static function mockHandlerHeader() {
-               return new class extends Handler {
-                       public function execute() {
-                               $response = $this->getResponseFactory()->create();
-                               $response->setHeader( 'Foo', 'Bar' );
-                               return $response;
-                       }
-               };
-       }
-
-       public function testHeader() {
-               $webResponse = $this->createWebResponse();
-               $webResponse->expects( $this->any() )
-                       ->method( 'header' )
-                       ->withConsecutive(
-                               [ 'HTTP/1.1 200 OK', true, null ],
-                               [ 'Foo: Bar', true, null ]
-                       );
-
-               $entryPoint = new EntryPoint(
-                       new RequestData( [ 'uri' => new Uri( '/rest/mock/EntryPoint/header' ) ] ),
-                       $webResponse,
-                       $this->createRouter() );
-               $entryPoint->execute();
-               $this->assertTrue( true );
-       }
-
-       public static function mockHandlerBodyRewind() {
-               return new class extends Handler {
-                       public function execute() {
-                               $response = $this->getResponseFactory()->create();
-                               $stream = new Stream( fopen( 'php://memory', 'w+' ) );
-                               $stream->write( 'hello' );
-                               $response->setBody( $stream );
-                               return $response;
-                       }
-               };
-       }
-
-       /**
-        * Make sure EntryPoint rewinds a seekable body stream before reading.
-        */
-       public function testBodyRewind() {
-               $entryPoint = new EntryPoint(
-                       new RequestData( [ 'uri' => new Uri( '/rest/mock/EntryPoint/bodyRewind' ) ] ),
-                       $this->createWebResponse(),
-                       $this->createRouter() );
-               ob_start();
-               $entryPoint->execute();
-               $this->assertSame( 'hello', ob_get_clean() );
-       }
-
-}
diff --git a/tests/phpunit/unit/includes/libs/filebackend/fsfile/TempFSFileTest.php b/tests/phpunit/unit/includes/libs/filebackend/fsfile/TempFSFileTest.php
new file mode 100644 (file)
index 0000000..743ac63
--- /dev/null
@@ -0,0 +1,16 @@
+<?php
+
+use MediaWiki\FileBackend\FSFile\TempFSFileFactory;
+
+/**
+ * @coversDefaultClass \MediaWiki\FileBackend\FSFile\TempFSFileFactory
+ * @covers ::__construct
+ * @covers ::newTempFSFile
+ */
+class TempFSFileTest extends MediaWikiUnitTestCase {
+       use TempFSFileTestTrait;
+
+       private function newFile() {
+               return ( new TempFSFileFactory() )->newTempFSFile( 'tmp' );
+       }
+}
index ed1288b..894dd19 100644 (file)
                        } );
        } );
 
+       QUnit.test( 'No storing of group=private responses', function ( assert ) {
+               var name = 'test.group.priv';
+
+               // Enable store and stub timeout/idle scheduling
+               this.sandbox.stub( mw.loader.store, 'enabled', true );
+               this.sandbox.stub( window, 'setTimeout', function ( fn ) {
+                       fn();
+               } );
+               this.sandbox.stub( mw, 'requestIdleCallback', function ( fn ) {
+                       fn();
+               } );
+
+               // See ResourceLoaderStartUpModule::$groupIds
+               mw.loader.register( name, 'x', [], 1 );
+               assert.strictEqual( mw.loader.store.get( name ), false, 'Not in store' );
+
+               mw.loader.implement( name, function () {} );
+               return mw.loader.using( name ).then( function () {
+                       assert.strictEqual( mw.loader.getState( name ), 'ready' );
+                       assert.strictEqual( mw.loader.store.get( name ), false, 'Still not in store' );
+               } );
+       } );
+
+       QUnit.test( 'No storing of group=user responses', function ( assert ) {
+               var name = 'test.group.user';
+
+               // Enable store and stub timeout/idle scheduling
+               this.sandbox.stub( mw.loader.store, 'enabled', true );
+               this.sandbox.stub( window, 'setTimeout', function ( fn ) {
+                       fn();
+               } );
+               this.sandbox.stub( mw, 'requestIdleCallback', function ( fn ) {
+                       fn();
+               } );
+
+               // See ResourceLoaderStartUpModule::$groupIds
+               mw.loader.register( name, 'y', [], 0 );
+               assert.strictEqual( mw.loader.store.get( name ), false, 'Not in store' );
+
+               mw.loader.implement( name, function () {} );
+               return mw.loader.using( name ).then( function () {
+                       assert.strictEqual( mw.loader.getState( name ), 'ready' );
+                       assert.strictEqual( mw.loader.store.get( name ), false, 'Still not in store' );
+               } );
+       } );
+
        QUnit.test( 'require()', function ( assert ) {
                mw.loader.register( [
                        [ 'test.require1', '0' ],
index 4e5c213..13dbc0e 100644 (file)
--- a/thumb.php
+++ b/thumb.php
@@ -91,6 +91,7 @@ function wfThumbHandle404() {
  */
 function wfStreamThumb( array $params ) {
        global $wgVaryOnXFP;
+       $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
 
        $headers = []; // HTTP headers to send
 
@@ -154,9 +155,8 @@ function wfStreamThumb( array $params ) {
 
        // Check permissions if there are read restrictions
        $varyHeader = [];
-       if ( !in_array( 'read', User::getGroupPermissions( [ '*' ] ), true ) ) {
+       if ( !in_array( 'read', $permissionManager->getGroupPermissions( [ '*' ] ), true ) ) {
                $user = RequestContext::getMain()->getUser();
-               $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
                $imgTitle = $img->getTitle();
 
                if ( !$imgTitle || !$permissionManager->userCan( 'read', $user, $imgTitle ) ) {