Merge "Update MediaWikiTitleCodec to use NamespaceInfo"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Mon, 15 Apr 2019 21:40:16 +0000 (21:40 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Mon, 15 Apr 2019 21:40:16 +0000 (21:40 +0000)
480 files changed:
.phpcs.xml
.stylelintrc.json
HISTORY
RELEASE-NOTES-1.33
RELEASE-NOTES-1.34 [new file with mode: 0644]
autoload.php
docs/extension.schema.v1.json
docs/extension.schema.v2.json
docs/hooks.txt
img_auth.php
includes/AjaxResponse.php
includes/Category.php
includes/ConfiguredReadOnlyMode.php
includes/DefaultSettings.php
includes/DevelopmentSettings.php
includes/Feed.php [deleted file]
includes/FormOptions.php
includes/GlobalFunctions.php
includes/MWNamespace.php
includes/MediaWiki.php
includes/MediaWikiServices.php
includes/MergeHistory.php
includes/OutputPage.php
includes/PHPVersionCheck.php
includes/Permissions/PermissionManager.php
includes/Preferences.php [deleted file]
includes/ReadOnlyMode.php
includes/Revision/RenderedRevision.php
includes/ServiceWiring.php
includes/SiteConfiguration.php
includes/Storage/DerivedPageDataUpdater.php
includes/StubObject.php
includes/StubUserLang.php [new file with mode: 0644]
includes/TemplateParser.php
includes/Title.php
includes/WebRequest.php
includes/WebRequestUpload.php
includes/actions/HistoryAction.php
includes/actions/pagers/HistoryPager.php
includes/api/ApiAuthManagerHelper.php
includes/api/ApiFeedRecentChanges.php
includes/api/ApiFormatXmlRsd.php [new file with mode: 0644]
includes/api/ApiImport.php
includes/api/ApiImportReporter.php [new file with mode: 0644]
includes/api/ApiOpenSearch.php
includes/api/ApiOpenSearchFormatJson.php [new file with mode: 0644]
includes/api/ApiRsd.php
includes/api/i18n/he.json
includes/api/i18n/ja.json
includes/api/i18n/zh-hant.json
includes/block/Restriction/AbstractRestriction.php
includes/block/Restriction/Restriction.php
includes/changes/AtomFeed.php [new file with mode: 0644]
includes/changes/ChangesFeed.php
includes/changes/ChannelFeed.php [new file with mode: 0644]
includes/changes/FeedItem.php [new file with mode: 0644]
includes/changes/RSSFeed.php [new file with mode: 0644]
includes/compat/ObjectFactory.php [deleted file]
includes/compat/XMPReader.php [deleted file]
includes/content/JsonContent.php
includes/dao/DBAccessBase.php
includes/db/DatabaseOracle.php
includes/db/MWLBFactory.php
includes/deferred/CdnCacheUpdate.php
includes/diff/DairikiDiff.php [deleted file]
includes/diff/Diff.php [new file with mode: 0644]
includes/diff/DiffOp.php [new file with mode: 0644]
includes/diff/DiffOpAdd.php [new file with mode: 0644]
includes/diff/DiffOpChange.php [new file with mode: 0644]
includes/diff/DiffOpCopy.php [new file with mode: 0644]
includes/diff/DiffOpDelete.php [new file with mode: 0644]
includes/diff/DifferenceEngine.php
includes/diff/TextSlotDiffRenderer.php
includes/edit/PreparedEdit.php
includes/exception/MWException.php
includes/exception/MWExceptionRenderer.php
includes/filerepo/file/LocalFile.php
includes/filerepo/file/LocalFileDeleteBatch.php [new file with mode: 0644]
includes/filerepo/file/LocalFileLockError.php [new file with mode: 0644]
includes/filerepo/file/LocalFileMoveBatch.php [new file with mode: 0644]
includes/filerepo/file/LocalFileRestoreBatch.php [new file with mode: 0644]
includes/htmlform/HTMLFormActionFieldLayout.php [new file with mode: 0644]
includes/htmlform/HTMLFormElement.php
includes/htmlform/HTMLFormFieldLayout.php [new file with mode: 0644]
includes/htmlform/fields/HTMLDateTimeField.php
includes/htmlform/fields/HTMLFormFieldWithButton.php
includes/htmlform/fields/HTMLTextField.php
includes/http/HttpRequestFactory.php
includes/import/WikiImporter.php
includes/installer/CliInstaller.php
includes/installer/DatabaseInstaller.php
includes/installer/DatabaseUpdater.php
includes/installer/Installer.php
includes/installer/MssqlInstaller.php
includes/installer/MysqlInstaller.php
includes/installer/PhpBugTests.php [deleted file]
includes/installer/PhpXmlBugTester.php [new file with mode: 0644]
includes/installer/PostgresInstaller.php
includes/installer/WebInstaller.php
includes/installer/i18n/ar.json
includes/installer/i18n/ast.json
includes/installer/i18n/ba.json
includes/installer/i18n/be-tarask.json
includes/installer/i18n/bg.json
includes/installer/i18n/bn.json
includes/installer/i18n/br.json
includes/installer/i18n/bs.json
includes/installer/i18n/ca.json
includes/installer/i18n/ckb.json
includes/installer/i18n/cs.json
includes/installer/i18n/csb.json
includes/installer/i18n/da.json
includes/installer/i18n/de.json
includes/installer/i18n/el.json
includes/installer/i18n/en.json
includes/installer/i18n/eo.json
includes/installer/i18n/es.json
includes/installer/i18n/eu.json
includes/installer/i18n/fa.json
includes/installer/i18n/fi.json
includes/installer/i18n/fr.json
includes/installer/i18n/frp.json
includes/installer/i18n/gl.json
includes/installer/i18n/gsw.json
includes/installer/i18n/he.json
includes/installer/i18n/hi.json
includes/installer/i18n/hrx.json
includes/installer/i18n/hsb.json
includes/installer/i18n/hu.json
includes/installer/i18n/ia.json
includes/installer/i18n/id.json
includes/installer/i18n/io.json
includes/installer/i18n/is.json
includes/installer/i18n/it.json
includes/installer/i18n/ja.json
includes/installer/i18n/ka.json
includes/installer/i18n/ko.json
includes/installer/i18n/ksh.json
includes/installer/i18n/ku-latn.json
includes/installer/i18n/lb.json
includes/installer/i18n/lij.json
includes/installer/i18n/lt.json
includes/installer/i18n/lv.json
includes/installer/i18n/mk.json
includes/installer/i18n/mr.json
includes/installer/i18n/ms.json
includes/installer/i18n/mzn.json
includes/installer/i18n/nap.json
includes/installer/i18n/nb.json
includes/installer/i18n/nl.json
includes/installer/i18n/oc.json
includes/installer/i18n/pl.json
includes/installer/i18n/pms.json
includes/installer/i18n/ps.json
includes/installer/i18n/pt-br.json
includes/installer/i18n/pt.json
includes/installer/i18n/ro.json
includes/installer/i18n/ru.json
includes/installer/i18n/sco.json
includes/installer/i18n/sh.json
includes/installer/i18n/sl.json
includes/installer/i18n/sr-ec.json
includes/installer/i18n/sr-el.json
includes/installer/i18n/sv.json
includes/installer/i18n/te.json
includes/installer/i18n/th.json
includes/installer/i18n/tl.json
includes/installer/i18n/tr.json
includes/installer/i18n/tt-cyrl.json
includes/installer/i18n/uk.json
includes/installer/i18n/vi.json
includes/installer/i18n/yi.json
includes/installer/i18n/zh-hans.json
includes/installer/i18n/zh-hant.json
includes/jobqueue/GenericParameterJob.php [new file with mode: 0644]
includes/jobqueue/IJobSpecification.php
includes/jobqueue/Job.php
includes/jobqueue/JobQueue.php
includes/jobqueue/JobQueueDB.php
includes/jobqueue/JobQueueFederated.php
includes/jobqueue/JobQueueGroup.php
includes/jobqueue/JobQueueMemory.php
includes/jobqueue/JobQueueRedis.php
includes/jobqueue/JobSpecification.php
includes/jobqueue/RunnableJob.php [new file with mode: 0644]
includes/jobqueue/jobs/CdnPurgeJob.php
includes/jobqueue/jobs/ClearUserWatchlistJob.php
includes/jobqueue/jobs/ClearWatchlistNotificationsJob.php
includes/jobqueue/jobs/DeletePageJob.php
includes/jobqueue/jobs/DuplicateJob.php
includes/jobqueue/jobs/EnqueueJob.php
includes/jobqueue/jobs/NullJob.php
includes/jobqueue/jobs/UserGroupExpiryJob.php
includes/libs/ArrayUtils.php
includes/libs/CSSMin.php
includes/libs/CryptRand.php [deleted file]
includes/libs/DeferredStringifier.php
includes/libs/MemoizedCallable.php
includes/libs/filebackend/FSFileBackend.php
includes/libs/filebackend/FileBackend.php
includes/libs/filebackend/FileBackendError.php [deleted file]
includes/libs/filebackend/FileBackendStore.php
includes/libs/filebackend/SwiftFileBackend.php
includes/libs/filebackend/exception/FileBackendError.php [new file with mode: 0644]
includes/libs/filebackend/fileiteration/FSFileBackendDirList.php [new file with mode: 0644]
includes/libs/filebackend/fileiteration/FSFileBackendFileList.php [new file with mode: 0644]
includes/libs/filebackend/fileiteration/FSFileBackendList.php [new file with mode: 0644]
includes/libs/filebackend/fileiteration/FileBackendStoreShardDirIterator.php [new file with mode: 0644]
includes/libs/filebackend/fileiteration/FileBackendStoreShardFileIterator.php [new file with mode: 0644]
includes/libs/filebackend/fileiteration/FileBackendStoreShardListIterator.php [new file with mode: 0644]
includes/libs/filebackend/fileiteration/SwiftFileBackendDirList.php [new file with mode: 0644]
includes/libs/filebackend/fileiteration/SwiftFileBackendFileList.php [new file with mode: 0644]
includes/libs/filebackend/fileiteration/SwiftFileBackendList.php [new file with mode: 0644]
includes/libs/filebackend/fileophandle/FSFileOpHandle.php [new file with mode: 0644]
includes/libs/filebackend/fileophandle/FileBackendStoreOpHandle.php [new file with mode: 0644]
includes/libs/filebackend/fileophandle/SwiftFileOpHandle.php [new file with mode: 0644]
includes/libs/jsminplus.php
includes/libs/mime/MimeAnalyzer.php
includes/libs/objectcache/APCUBagOStuff.php
includes/libs/rdbms/database/DBConnRef.php
includes/libs/rdbms/database/Database.php
includes/libs/rdbms/database/DatabasePostgres.php
includes/libs/rdbms/database/DatabaseSqlite.php
includes/libs/rdbms/database/IDatabase.php
includes/logging/DatabaseLogEntry.php [new file with mode: 0644]
includes/logging/LegacyLogFormatter.php [new file with mode: 0644]
includes/logging/LogEntry.php
includes/logging/LogEntryBase.php [new file with mode: 0644]
includes/logging/LogFormatter.php
includes/logging/LogPage.php
includes/logging/ManualLogEntry.php [new file with mode: 0644]
includes/logging/RCDatabaseLogEntry.php [new file with mode: 0644]
includes/media/DjVuImage.php
includes/media/MediaHandler.php
includes/media/MediaTransformError.php
includes/media/SVGMetadataExtractor.php
includes/media/SVGReader.php [new file with mode: 0644]
includes/media/ThumbnailImage.php
includes/media/XCFHandler.php
includes/page/ImagePage.php
includes/page/WikiPage.php
includes/parser/CoreParserFunctions.php
includes/parser/DateFormatter.php
includes/parser/DateFormatterFactory.php [new file with mode: 0644]
includes/parser/Parser.php
includes/parser/ParserFactory.php
includes/parser/ParserOptions.php
includes/parser/Preprocessor.php
includes/parser/Preprocessor_DOM.php
includes/parser/Preprocessor_Hash.php
includes/preferences/DefaultPreferencesFactory.php
includes/preferences/PreferencesFactory.php
includes/profiler/SectionProfileCallback.php [new file with mode: 0644]
includes/profiler/SectionProfiler.php
includes/registration/ExtensionDependencyError.php
includes/registration/ExtensionRegistry.php
includes/registration/VersionChecker.php
includes/resourceloader/ResourceLoader.php
includes/resourceloader/ResourceLoaderContext.php
includes/resourceloader/ResourceLoaderImage.php
includes/resourceloader/ResourceLoaderStartUpModule.php
includes/resourceloader/ResourceLoaderWikiModule.php
includes/search/SearchEngine.php
includes/search/SearchMySQL.php
includes/search/SearchOracle.php
includes/search/SearchResultSet.php
includes/search/SearchSqlite.php
includes/shell/Command.php
includes/specialpage/RedirectSpecialPage.php
includes/specialpage/SpecialPageFactory.php
includes/specials/SpecialActiveUsers.php [new file with mode: 0644]
includes/specials/SpecialActiveusers.php [deleted file]
includes/specials/SpecialBlock.php
includes/specials/SpecialBookSources.php [new file with mode: 0644]
includes/specials/SpecialBooksources.php [deleted file]
includes/specials/SpecialEmailUser.php [new file with mode: 0644]
includes/specials/SpecialEmailuser.php [deleted file]
includes/specials/SpecialImport.php
includes/specials/SpecialListFiles.php [new file with mode: 0644]
includes/specials/SpecialListGrants.php [new file with mode: 0644]
includes/specials/SpecialListGroupRights.php [new file with mode: 0644]
includes/specials/SpecialListUsers.php [new file with mode: 0644]
includes/specials/SpecialListfiles.php [deleted file]
includes/specials/SpecialListgrants.php [deleted file]
includes/specials/SpecialListgrouprights.php [deleted file]
includes/specials/SpecialListusers.php [deleted file]
includes/specials/SpecialPreferences.php
includes/specials/SpecialRecentChanges.php [new file with mode: 0644]
includes/specials/SpecialRecentChangesLinked.php [new file with mode: 0644]
includes/specials/SpecialRecentchanges.php [deleted file]
includes/specials/SpecialRecentchangeslinked.php [deleted file]
includes/specials/SpecialRevisionDelete.php [new file with mode: 0644]
includes/specials/SpecialRevisiondelete.php [deleted file]
includes/specials/SpecialVersion.php
includes/specials/SpecialWatchlist.php
includes/specials/SpecialWhatLinksHere.php [new file with mode: 0644]
includes/specials/SpecialWhatlinkshere.php [deleted file]
includes/specials/forms/PreferencesFormLegacy.php [deleted file]
includes/specials/helpers/ImportReporter.php
includes/specials/pagers/ContribsPager.php
includes/title/NamespaceInfo.php
includes/upload/UploadStash.php
includes/upload/UploadStashFile.php [new file with mode: 0644]
includes/user/User.php
includes/utils/AutoloadGenerator.php
includes/utils/ClassCollector.php [new file with mode: 0644]
includes/utils/MWCryptRand.php
includes/utils/UIDGenerator.php
includes/watcheditem/NoWriteWatchedItemStore.php
includes/watcheditem/WatchedItemStore.php
includes/widget/search/InterwikiSearchResultSetWidget.php
languages/Language.php
languages/i18n/ar.json
languages/i18n/az.json
languages/i18n/be-tarask.json
languages/i18n/br.json
languages/i18n/ca.json
languages/i18n/cs.json
languages/i18n/de.json
languages/i18n/diq.json
languages/i18n/el.json
languages/i18n/en.json
languages/i18n/exif/ast.json
languages/i18n/fi.json
languages/i18n/fr.json
languages/i18n/fy.json
languages/i18n/he.json
languages/i18n/hr.json
languages/i18n/hy.json
languages/i18n/hyw.json
languages/i18n/ia.json
languages/i18n/id.json
languages/i18n/io.json
languages/i18n/it.json
languages/i18n/ja.json
languages/i18n/ko.json
languages/i18n/kum.json
languages/i18n/lrc.json
languages/i18n/lv.json
languages/i18n/mk.json
languages/i18n/my.json
languages/i18n/nb.json
languages/i18n/nds-nl.json
languages/i18n/nl.json
languages/i18n/nn.json
languages/i18n/pl.json
languages/i18n/pt-br.json
languages/i18n/qqq.json
languages/i18n/ro.json
languages/i18n/roa-tara.json
languages/i18n/sh.json
languages/i18n/sl.json
languages/i18n/sr-ec.json
languages/i18n/uk.json
languages/i18n/yue.json
languages/i18n/zh-hans.json
languages/i18n/zh-hant.json
maintenance/Maintenance.php
maintenance/cleanupSpam.php
maintenance/dev/includes/router.php
maintenance/dev/installphp.sh
maintenance/generateSitemap.php
maintenance/language/generateCollationData.php
maintenance/language/generateNormalizerDataAr.php
maintenance/language/generateNormalizerDataMl.php
maintenance/storage/checkStorage.php
maintenance/storage/compressOld.php
maintenance/storage/recompressTracked.php
mw-config/config-cc.css
mw-config/config.css
resources/Resources.php
resources/lib/foreign-resources.yaml
resources/lib/mustache/LICENSE
resources/lib/mustache/mustache.js
resources/src/jquery.tablesorter.styles/jquery.tablesorter.styles.less
resources/src/jquery.tipsy/jquery.tipsy.css
resources/src/jquery/jquery.confirmable.css
resources/src/jquery/jquery.suggestions.css
resources/src/mediawiki.action/mediawiki.action.edit.collapsibleFooter.css
resources/src/mediawiki.action/mediawiki.action.edit.styles.less
resources/src/mediawiki.action/mediawiki.action.history.css
resources/src/mediawiki.action/mediawiki.action.history.styles.less
resources/src/mediawiki.action/mediawiki.action.view.categoryPage.less
resources/src/mediawiki.action/mediawiki.action.view.filepage.css
resources/src/mediawiki.action/mediawiki.action.view.metadata.css
resources/src/mediawiki.action/mediawiki.action.view.postEdit.less
resources/src/mediawiki.action/mediawiki.action.view.postEdit.monobook.css
resources/src/mediawiki.action/mediawiki.action.view.redirectPage.css
resources/src/mediawiki.apihelp.css
resources/src/mediawiki.apipretty.css
resources/src/mediawiki.content.json.less
resources/src/mediawiki.debug/debug.less
resources/src/mediawiki.diff.styles/diff.css
resources/src/mediawiki.diff.styles/print.css
resources/src/mediawiki.feedlink/feedlink.css
resources/src/mediawiki.filewarning/filewarning.less
resources/src/mediawiki.hlist/default.css
resources/src/mediawiki.hlist/hlist.less
resources/src/mediawiki.htmlform.ooui.styles.less
resources/src/mediawiki.interface.helpers.styles.less
resources/src/mediawiki.legacy/commonPrint.css
resources/src/mediawiki.legacy/oldshared.css
resources/src/mediawiki.legacy/shared.css
resources/src/mediawiki.page.gallery.styles/gallery.css
resources/src/mediawiki.page.gallery.styles/print.css
resources/src/mediawiki.pager.tablePager/TablePager.less
resources/src/mediawiki.rcfilters/Controller.js
resources/src/mediawiki.rcfilters/dm/FilterGroup.js
resources/src/mediawiki.rcfilters/dm/FilterItem.js
resources/src/mediawiki.rcfilters/dm/FiltersViewModel.js
resources/src/mediawiki.rcfilters/mw.rcfilters.init.js
resources/src/mediawiki.rcfilters/styles/mw.rcfilters.less
resources/src/mediawiki.rcfilters/ui/ChangesLimitAndDateButtonWidget.js
resources/src/mediawiki.rcfilters/ui/ChangesListWrapperWidget.js
resources/src/mediawiki.rcfilters/ui/GroupWidget.js
resources/src/mediawiki.rcfilters/ui/SavedLinksListItemWidget.js
resources/src/mediawiki.searchSuggest/searchSuggest.css
resources/src/mediawiki.skinning/content.css
resources/src/mediawiki.skinning/content.externallinks.less
resources/src/mediawiki.skinning/content.parsoid.less
resources/src/mediawiki.skinning/elements.css
resources/src/mediawiki.skinning/interface.css
resources/src/mediawiki.special.apisandbox/apisandbox.css
resources/src/mediawiki.special.block.js
resources/src/mediawiki.special.changeslist.enhanced.less
resources/src/mediawiki.special.search.interwikiwidget.styles.less
resources/src/mediawiki.special.search.styles.css
resources/src/mediawiki.special.userlogin.common.styles/userlogin.css
resources/src/mediawiki.special.userlogin.signup.styles/signup.css
resources/src/mediawiki.special/movePage.css
resources/src/mediawiki.special/pagesWithProp.css
resources/src/mediawiki.special/special.less
resources/src/mediawiki.toc.styles/common.css
resources/src/mediawiki.toc.styles/print.css
resources/src/mediawiki.toc.styles/screen.less
resources/src/mediawiki.ui/components/buttons.less
resources/src/mediawiki.ui/components/forms.less
resources/src/startup/mediawiki.js
tests/parser/parserTests.txt
tests/phpunit/ResourceLoaderTestCase.php
tests/phpunit/includes/GlobalFunctions/GlobalTest.php
tests/phpunit/includes/MediaWikiServicesTest.php
tests/phpunit/includes/Permissions/PermissionManagerTest.php
tests/phpunit/includes/ReadOnlyModeTest.php
tests/phpunit/includes/ServiceWiringTest.php
tests/phpunit/includes/SiteStatsTest.php
tests/phpunit/includes/Storage/DerivedPageDataUpdaterTest.php
tests/phpunit/includes/Storage/NameTableStoreTest.php
tests/phpunit/includes/Storage/PreparedEditTest.php [new file with mode: 0644]
tests/phpunit/includes/TitlePermissionTest.php
tests/phpunit/includes/TitleTest.php
tests/phpunit/includes/api/query/ApiQueryTestBase.php
tests/phpunit/includes/auth/UserDataAuthenticationRequestTest.php
tests/phpunit/includes/changes/CategoryMembershipChangeTest.php
tests/phpunit/includes/content/ContentHandlerTest.php
tests/phpunit/includes/content/JsonContentTest.php
tests/phpunit/includes/content/TextContentHandlerTest.php
tests/phpunit/includes/diff/DifferenceEngineTest.php
tests/phpunit/includes/http/HttpTest.php
tests/phpunit/includes/jobqueue/JobQueueTest.php
tests/phpunit/includes/jobqueue/JobTest.php
tests/phpunit/includes/jobqueue/jobs/ClearUserWatchlistJobTest.php
tests/phpunit/includes/libs/ArrayUtilsTest.php
tests/phpunit/includes/page/WikiPageDbTestBase.php
tests/phpunit/includes/parser/ParserOptionsTest.php
tests/phpunit/includes/poolcounter/PoolCounterTest.php
tests/phpunit/includes/preferences/DefaultPreferencesFactoryTest.php
tests/phpunit/includes/registration/VersionCheckerTest.php
tests/phpunit/includes/resourceloader/ResourceLoaderContextTest.php
tests/phpunit/includes/resourceloader/ResourceLoaderOOUIImageModuleTest.php
tests/phpunit/includes/resourceloader/ResourceLoaderTest.php
tests/phpunit/includes/search/SearchEngineTest.php
tests/phpunit/includes/watcheditem/WatchedItemQueryServiceUnitTest.php
tests/phpunit/includes/watcheditem/WatchedItemStoreUnitTest.php
tests/phpunit/structure/AvailableRightsTest.php
tests/phpunit/suites/ParserTestTopLevelSuite.php
tests/qunit/data/load.mock.php
tests/qunit/suites/resources/mediawiki/mediawiki.loader.test.js
tests/selenium/specs/page.js
tests/selenium/specs/rollback.js

index 170e16d..cc9e53c 100644 (file)
                        Whitelist existing violations, but enable the sniff to prevent
                        any new occurrences.
                -->
-               <exclude-pattern>*/includes/Feed\.php</exclude-pattern>
-               <exclude-pattern>*/includes/installer/PhpBugTests\.php</exclude-pattern>
                <exclude-pattern>*/includes/specials/SpecialMostinterwikis\.php</exclude-pattern>
-               <exclude-pattern>*/includes/compat/XMPReader\.php</exclude-pattern>
-               <exclude-pattern>*/includes/diff/DairikiDiff\.php</exclude-pattern>
                <exclude-pattern>*/includes/specials/SpecialAncientpages\.php</exclude-pattern>
                <exclude-pattern>*/includes/specials/SpecialBrokenRedirects\.php</exclude-pattern>
                <exclude-pattern>*/includes/specials/SpecialConfirmemail\.php</exclude-pattern>
                        Whitelist existing violations, but enable the sniff to prevent
                        any new occurrences.
                -->
-               <exclude-pattern>*/includes/specials/SpecialActiveusers\.php</exclude-pattern>
-               <exclude-pattern>*/includes/specials/SpecialBooksources\.php</exclude-pattern>
-               <exclude-pattern>*/includes/specials/SpecialEmailuser\.php</exclude-pattern>
-               <exclude-pattern>*/includes/specials/SpecialListfiles\.php</exclude-pattern>
-               <exclude-pattern>*/includes/specials/SpecialListgrants\.php</exclude-pattern>
-               <exclude-pattern>*/includes/specials/SpecialListgrouprights\.php</exclude-pattern>
-               <exclude-pattern>*/includes/specials/SpecialListusers.php</exclude-pattern>
-               <exclude-pattern>*/includes/specials/SpecialRecentchanges\.php</exclude-pattern>
-               <exclude-pattern>*/includes/specials/SpecialRecentchangeslinked\.php</exclude-pattern>
-               <exclude-pattern>*/includes/specials/SpecialRevisiondelete\.php</exclude-pattern>
-               <exclude-pattern>*/includes/specials/SpecialWhatlinkshere\.php</exclude-pattern>
                <exclude-pattern>*/maintenance/language/alltrans\.php</exclude-pattern>
                <exclude-pattern>*/maintenance/language/digit2html\.php</exclude-pattern>
                <exclude-pattern>*/maintenance/language/langmemusage\.php</exclude-pattern>
                        any new occurrences.
                -->
                <exclude-pattern>*/includes/api/ApiErrorFormatter\.php</exclude-pattern>
-               <exclude-pattern>*/includes/api/ApiImport\.php</exclude-pattern>
-               <exclude-pattern>*/includes/api/ApiMessage\.php</exclude-pattern>
-               <exclude-pattern>*/includes/api/ApiOpenSearch\.php</exclude-pattern>
-               <exclude-pattern>*/includes/api/ApiRsd\.php</exclude-pattern>
-               <exclude-pattern>*/includes/compat/XMPReader\.php</exclude-pattern>
-               <exclude-pattern>*/includes/diff/DairikiDiff\.php</exclude-pattern>
-               <exclude-pattern>*/includes/Feed\.php</exclude-pattern>
-               <exclude-pattern>*/includes/filerepo/file/LocalFile\.php</exclude-pattern>
-               <exclude-pattern>*/includes/htmlform/HTMLFormElement\.php</exclude-pattern>
-               <exclude-pattern>*/includes/libs/filebackend/FileBackendStore\.php</exclude-pattern>
-               <exclude-pattern>*/includes/libs/filebackend/FSFileBackend\.php</exclude-pattern>
-               <exclude-pattern>*/includes/libs/filebackend/SwiftFileBackend\.php</exclude-pattern>
-               <exclude-pattern>*/includes/logging/LogEntry\.php</exclude-pattern>
-               <exclude-pattern>*/includes/logging/LogFormatter\.php</exclude-pattern>
-               <exclude-pattern>*/includes/media/SVGMetadataExtractor\.php</exclude-pattern>
                <exclude-pattern>*/includes/parser/Preprocessor_DOM\.php</exclude-pattern>
                <exclude-pattern>*/includes/parser/Preprocessor_Hash\.php</exclude-pattern>
                <exclude-pattern>*/includes/parser/Preprocessor\.php</exclude-pattern>
-               <exclude-pattern>*/includes/PathRouter\.php</exclude-pattern>
-               <exclude-pattern>*/includes/profiler/SectionProfiler\.php</exclude-pattern>
-               <exclude-pattern>*/includes/specialpage/LoginSignupSpecialPage\.php</exclude-pattern>
-               <exclude-pattern>*/includes/specials/forms/PreferencesFormLegacy\.php</exclude-pattern>
-               <exclude-pattern>*/includes/StubObject\.php</exclude-pattern>
-               <exclude-pattern>*/includes/upload/UploadStash\.php</exclude-pattern>
-               <exclude-pattern>*/includes/utils/AutoloadGenerator\.php</exclude-pattern>
                <exclude-pattern>*/maintenance/dumpIterator\.php</exclude-pattern>
                <exclude-pattern>*/maintenance/Maintenance\.php</exclude-pattern>
                <exclude-pattern>*/maintenance/findDeprecated\.php</exclude-pattern>
        </rule>
        <rule ref="PSR2.Methods.MethodDeclaration.Underscore">
                <exclude-pattern>*/includes/StubObject\.php</exclude-pattern>
+               <exclude-pattern>*/includes/StubUserLang\.php</exclude-pattern>
        </rule>
        <rule ref="MediaWiki.Usage.AssignmentInReturn.AssignmentInReturn">
                <exclude-pattern>*/tests/phpunit/*\.php</exclude-pattern>
index 60c8f36..43f499b 100644 (file)
@@ -1,8 +1,8 @@
 {
        "extends": "stylelint-config-wikimedia",
        "rules": {
+               "selector-class-pattern": "^((mw|oo-ui)-|(wikitable|(toc(|toggle|hidden))|client-(no)?js)$)",
                "no-descending-specificity": null,
-
                "selector-max-id": null
        }
 }
diff --git a/HISTORY b/HISTORY
index a38e215..426eb17 100644 (file)
--- a/HISTORY
+++ b/HISTORY
@@ -1,4 +1,4 @@
-Change notes from older releases. For current info see RELEASE-NOTES-1.33.
+Change notes from older releases. For current info see RELEASE-NOTES-1.34.
 
 = MediaWiki 1.32 =
 
index fd316c4..db5fea0 100644 (file)
@@ -4,8 +4,8 @@
 
 THIS IS NOT A RELEASE YET
 
-MediaWiki 1.33 is an alpha-quality branch and is not recommended for use in
-production.
+MediaWiki 1.33 is a pre-release testing branch, and is not recommended for use
+in production.
 
 == Upgrading notes for 1.33 ==
 1.33 has several database changes since 1.32, and will not work without schema
@@ -359,13 +359,8 @@ because of Phabricator reports.
 * MessageBlobStore::getBlob(), deprecated in 1.27, has been removed.
   Use ::getBlobs() instead.
 * The .background-size() LESS mixin, deprecated in 1.27, has been removed.
-* MWNamespace::clearCaches() has been removed.  So has the $rebuild parameter
-  to MWNamespace::getCanonicalNamespaces(), which was deprecated since 1.31.
-  Instead, create a new NamespaceInfo, such as by calling
-  resetServiceForTesting( 'NamespaceInfo' ) on a MediaWikiServices.
-  For classes that inherit from MediaWikiTestCase and used setMwGlobals() to
-  modify a variable that affects namespaces, caches will automatically be
-  reset and any calls to MWNamespace::clearCaches() can be removed entirely.
+* ReadOnlyMode::clearCache() and ConfiguredReadOnlyMode::clearCache() have been
+  removed. Use MediaWikiTestCase::overrideMwServices() instead.
 
 === Deprecations in 1.33 ===
 * The configuration option $wgUseESI has been deprecated, and is expected
@@ -434,7 +429,8 @@ because of Phabricator reports.
 * Block::isValid is deprecated, since it is no longer needed in core.
 * Calling Maintenance::hasArg() as well as Maintenance::getArg() with no
   parameter has been deprecated. Please pass the argument number 0.
-* The MWNamespace class is deprecated.  Use MediaWikiServices::getNamespaceInfo.
+* ResourceLoaderContext::expandModuleNames has been deprecated.
+  Use ResourceLoader::expandModuleNames instead.
 
 === Other changes in 1.33 ===
 * (T201747) Html::openElement() warns if given an element name with a space
diff --git a/RELEASE-NOTES-1.34 b/RELEASE-NOTES-1.34
new file mode 100644 (file)
index 0000000..219cba4
--- /dev/null
@@ -0,0 +1,141 @@
+= MediaWiki 1.34 =
+
+== MediaWiki 1.34.0-PRERELEASE ==
+
+THIS IS NOT A RELEASE YET
+
+MediaWiki 1.34 is an alpha-quality development branch, and is not recommended
+for use in production.
+
+== Upgrading notes for 1.34 ==
+1.34 has several database changes since 1.33, and will not work without schema
+updates. Note that due to changes to some very large tables like the revision
+table, the schema update may take quite long (minutes on a medium sized site,
+many hours on a large site).
+
+Don't forget to always back up your database before upgrading!
+
+See the file UPGRADE for more detailed upgrade instructions, including
+important information when upgrading from versions prior to 1.11.
+
+Some specific notes for MediaWiki 1.34 upgrades are below:
+
+* …
+
+For notes on 1.33.x and older releases, see HISTORY.
+
+=== Configuration changes for system administrators in 1.34 ===
+==== New configuration ====
+* …
+
+==== Changed configuration ====
+* …
+
+==== Removed configuration ====
+* …
+
+=== New user-facing features in 1.34 ===
+* …
+
+=== New developer features in 1.34 ===
+* …
+
+=== External library changes in 1.34 ===
+==== New external libraries ====
+* …
+
+==== Changed external libraries ====
+* Updated Mustache from 1.0.0 to v3.0.1.
+* …
+
+==== Removed external libraries ====
+* …
+
+=== Bug fixes in 1.34 ===
+* …
+
+=== Action API changes in 1.34 ===
+* …
+
+=== Action API internal changes in 1.34 ===
+* …
+
+=== Languages updated in 1.34 ===
+MediaWiki supports over 350 languages. Many localisations are updated regularly.
+Below only new and removed languages are listed, as well as changes to languages
+because of Phabricator reports.
+
+* …
+
+=== Breaking changes in 1.34 ===
+* Preferences class, deprecated in 1.31, has been removed.
+* The following parts of code, deprecated in 1.32, were removed in favor of
+  built-in PHP functions:
+  * CryptRand class
+  * CryptRand service
+  * Functions of the MWCryptRand class: singleton(), wasStrong() and generate().
+* Language::setCode, deprecated in 1.32, was removed. Use Language::factory to
+  create a new Language object with a different language code.
+* MWNamespace::clearCaches() has been removed.  So has the $rebuild parameter
+  to MWNamespace::getCanonicalNamespaces(), which was deprecated since 1.31.
+  Instead, reset services, such as by calling $this->overrideMwServices() (if
+  your test extends MediaWikiTestCase). Services will generally not pick up
+  configuration changes from after they were created, so you must reset
+  services after any configuration change. Even if your code works now, it is
+  likely to break in future versions as more code is moved to services.
+* The ill-defined "DatabaseOraclePostInit" hook has been removed.
+* PreferencesFormLegacy and PreferencesForm classes, deprecated in 1.32, have
+  been removed.
+* ObjectFactory class, deprecated in 1.31, has been removed.
+* HWLDFWordAccumudlator class, deprecated in 1.28, has been removed.
+* XMPInfo, XMPReader and XMPValidate, deprecated in 1.32, have been removed.
+* The RedirectSpecialPage::execute method could sometimes return a Title object.
+  This behavior was removed, and the method now matches the parent signature
+  (SpecialPage::execute) which is to return HTML string or void.
+  To obtain the destination title, use RedirectSpecialPage::getRedirect.
+
+=== Deprecations in 1.34 ===
+* The MWNamespace class is deprecated. Use MediaWikiServices::getNamespaceInfo.
+* …
+
+=== Other changes in 1.34 ===
+* …
+
+== Compatibility ==
+MediaWiki 1.34 requires PHP 7.0.13 or later. Although HHVM 3.18.5 or later is
+supported, it is generally advised to use PHP 7.0.13 or later for long term
+support.
+
+MySQL/MariaDB is the recommended DBMS. PostgreSQL or SQLite can also be used,
+but support for them is somewhat less mature. There is experimental support for
+Oracle and Microsoft SQL Server.
+
+The supported versions are:
+
+* MySQL 5.5.8 or later
+* PostgreSQL 9.2 or later
+* SQLite 3.8.0 or later
+* Oracle 9.0.1 or later
+* Microsoft SQL Server 2005 (9.00.1399)
+
+== Online documentation ==
+Documentation for both end-users and site administrators is available on
+MediaWiki.org, and is covered under the GNU Free Documentation License (except
+for pages that explicitly state that their contents are in the public domain):
+
+       https://www.mediawiki.org/wiki/Special:MyLanguage/Documentation
+
+== Mailing list ==
+A mailing list is available for MediaWiki user support and discussion:
+
+       https://lists.wikimedia.org/mailman/listinfo/mediawiki-l
+
+A low-traffic announcements-only list is also available:
+
+       https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce
+
+It's highly recommended that you sign up for one of these lists if you're
+going to run a public MediaWiki, so you can be notified of security fixes.
+
+== IRC help ==
+There's usually someone online in #mediawiki on irc.freenode.net.
index 5fda217..ab36d84 100644 (file)
@@ -52,12 +52,12 @@ $wgAutoloadLocalClasses = [
        'ApiFormatPhp' => __DIR__ . '/includes/api/ApiFormatPhp.php',
        'ApiFormatRaw' => __DIR__ . '/includes/api/ApiFormatRaw.php',
        'ApiFormatXml' => __DIR__ . '/includes/api/ApiFormatXml.php',
-       'ApiFormatXmlRsd' => __DIR__ . '/includes/api/ApiRsd.php',
+       'ApiFormatXmlRsd' => __DIR__ . '/includes/api/ApiFormatXmlRsd.php',
        'ApiHelp' => __DIR__ . '/includes/api/ApiHelp.php',
        'ApiHelpParamValueMessage' => __DIR__ . '/includes/api/ApiHelpParamValueMessage.php',
        'ApiImageRotate' => __DIR__ . '/includes/api/ApiImageRotate.php',
        'ApiImport' => __DIR__ . '/includes/api/ApiImport.php',
-       'ApiImportReporter' => __DIR__ . '/includes/api/ApiImport.php',
+       'ApiImportReporter' => __DIR__ . '/includes/api/ApiImportReporter.php',
        'ApiLinkAccount' => __DIR__ . '/includes/api/ApiLinkAccount.php',
        'ApiLogin' => __DIR__ . '/includes/api/ApiLogin.php',
        'ApiLogout' => __DIR__ . '/includes/api/ApiLogout.php',
@@ -69,7 +69,7 @@ $wgAutoloadLocalClasses = [
        'ApiModuleManager' => __DIR__ . '/includes/api/ApiModuleManager.php',
        'ApiMove' => __DIR__ . '/includes/api/ApiMove.php',
        'ApiOpenSearch' => __DIR__ . '/includes/api/ApiOpenSearch.php',
-       'ApiOpenSearchFormatJson' => __DIR__ . '/includes/api/ApiOpenSearch.php',
+       'ApiOpenSearchFormatJson' => __DIR__ . '/includes/api/ApiOpenSearchFormatJson.php',
        'ApiOptions' => __DIR__ . '/includes/api/ApiOptions.php',
        'ApiPageSet' => __DIR__ . '/includes/api/ApiPageSet.php',
        'ApiParamInfo' => __DIR__ . '/includes/api/ApiParamInfo.php',
@@ -161,7 +161,7 @@ $wgAutoloadLocalClasses = [
        'ArrayUtils' => __DIR__ . '/includes/libs/ArrayUtils.php',
        'Article' => __DIR__ . '/includes/page/Article.php',
        'AssembleUploadChunksJob' => __DIR__ . '/includes/jobqueue/jobs/AssembleUploadChunksJob.php',
-       'AtomFeed' => __DIR__ . '/includes/Feed.php',
+       'AtomFeed' => __DIR__ . '/includes/changes/AtomFeed.php',
        'AtomicSectionUpdate' => __DIR__ . '/includes/deferred/AtomicSectionUpdate.php',
        'AttachLatest' => __DIR__ . '/maintenance/attachLatest.php',
        'AugmentPageProps' => __DIR__ . '/includes/search/AugmentPageProps.php',
@@ -253,7 +253,7 @@ $wgAutoloadLocalClasses = [
        'ChangesListSpecialPage' => __DIR__ . '/includes/specialpage/ChangesListSpecialPage.php',
        'ChangesListStringOptionsFilter' => __DIR__ . '/includes/changes/ChangesListStringOptionsFilter.php',
        'ChangesListStringOptionsFilterGroup' => __DIR__ . '/includes/changes/ChangesListStringOptionsFilterGroup.php',
-       'ChannelFeed' => __DIR__ . '/includes/Feed.php',
+       'ChannelFeed' => __DIR__ . '/includes/changes/ChannelFeed.php',
        'CheckBadRedirects' => __DIR__ . '/maintenance/checkBadRedirects.php',
        'CheckComposerLockUpToDate' => __DIR__ . '/maintenance/checkComposerLockUpToDate.php',
        'CheckExtensionsCLI' => __DIR__ . '/maintenance/language/checkLanguage.inc',
@@ -262,7 +262,7 @@ $wgAutoloadLocalClasses = [
        'CheckLess' => __DIR__ . '/maintenance/checkLess.php',
        'CheckStorage' => __DIR__ . '/maintenance/storage/checkStorage.php',
        'CheckUsernames' => __DIR__ . '/maintenance/checkUsernames.php',
-       'ClassCollector' => __DIR__ . '/includes/utils/AutoloadGenerator.php',
+       'ClassCollector' => __DIR__ . '/includes/utils/ClassCollector.php',
        'CleanupAncientTables' => __DIR__ . '/maintenance/cleanupAncientTables.php',
        'CleanupBlocks' => __DIR__ . '/maintenance/cleanupBlocks.php',
        'CleanupCaps' => __DIR__ . '/maintenance/cleanupCaps.php',
@@ -327,7 +327,6 @@ $wgAutoloadLocalClasses = [
        'CreditsAction' => __DIR__ . '/includes/actions/CreditsAction.php',
        'CrhConverter' => __DIR__ . '/languages/classes/LanguageCrh.php',
        'CryptHKDF' => __DIR__ . '/includes/libs/CryptHKDF.php',
-       'CryptRand' => __DIR__ . '/includes/libs/CryptRand.php',
        'CssContent' => __DIR__ . '/includes/content/CssContent.php',
        'CssContentHandler' => __DIR__ . '/includes/content/CssContentHandler.php',
        'CsvStatsOutput' => __DIR__ . '/maintenance/language/StatOutputs.php',
@@ -354,7 +353,7 @@ $wgAutoloadLocalClasses = [
        'DatabaseBase' => __DIR__ . '/includes/libs/rdbms/database/Database.php',
        'DatabaseInstaller' => __DIR__ . '/includes/installer/DatabaseInstaller.php',
        'DatabaseLag' => __DIR__ . '/maintenance/lag.php',
-       'DatabaseLogEntry' => __DIR__ . '/includes/logging/LogEntry.php',
+       'DatabaseLogEntry' => __DIR__ . '/includes/logging/DatabaseLogEntry.php',
        'DatabaseMssql' => __DIR__ . '/includes/libs/rdbms/database/DatabaseMssql.php',
        'DatabaseMysqlBase' => __DIR__ . '/includes/libs/rdbms/database/DatabaseMysqlBase.php',
        'DatabaseMysqli' => __DIR__ . '/includes/libs/rdbms/database/DatabaseMysqli.php',
@@ -364,6 +363,7 @@ $wgAutoloadLocalClasses = [
        'DatabaseUpdater' => __DIR__ . '/includes/installer/DatabaseUpdater.php',
        'DateFormats' => __DIR__ . '/maintenance/language/date-formats.php',
        'DateFormatter' => __DIR__ . '/includes/parser/DateFormatter.php',
+       'DateFormatterFactory' => __DIR__ . '/includes/parser/DateFormatterFactory.php',
        'DeadendPagesPage' => __DIR__ . '/includes/specials/SpecialDeadendpages.php',
        'DeduplicateArchiveRevId' => __DIR__ . '/maintenance/deduplicateArchiveRevId.php',
        'DeferrableCallback' => __DIR__ . '/includes/deferred/DeferrableCallback.php',
@@ -395,15 +395,15 @@ $wgAutoloadLocalClasses = [
        'DerivativeRequest' => __DIR__ . '/includes/DerivativeRequest.php',
        'DerivativeResourceLoaderContext' => __DIR__ . '/includes/resourceloader/DerivativeResourceLoaderContext.php',
        'DescribeFileOp' => __DIR__ . '/includes/libs/filebackend/fileop/DescribeFileOp.php',
-       'Diff' => __DIR__ . '/includes/diff/DairikiDiff.php',
+       'Diff' => __DIR__ . '/includes/diff/Diff.php',
        'DiffEngine' => __DIR__ . '/includes/diff/DiffEngine.php',
        'DiffFormatter' => __DIR__ . '/includes/diff/DiffFormatter.php',
        'DiffHistoryBlob' => __DIR__ . '/includes/historyblob/DiffHistoryBlob.php',
-       'DiffOp' => __DIR__ . '/includes/diff/DairikiDiff.php',
-       'DiffOpAdd' => __DIR__ . '/includes/diff/DairikiDiff.php',
-       'DiffOpChange' => __DIR__ . '/includes/diff/DairikiDiff.php',
-       'DiffOpCopy' => __DIR__ . '/includes/diff/DairikiDiff.php',
-       'DiffOpDelete' => __DIR__ . '/includes/diff/DairikiDiff.php',
+       'DiffOp' => __DIR__ . '/includes/diff/DiffOp.php',
+       'DiffOpAdd' => __DIR__ . '/includes/diff/DiffOpAdd.php',
+       'DiffOpChange' => __DIR__ . '/includes/diff/DiffOpChange.php',
+       'DiffOpCopy' => __DIR__ . '/includes/diff/DiffOpCopy.php',
+       'DiffOpDelete' => __DIR__ . '/includes/diff/DiffOpDelete.php',
        'DifferenceEngine' => __DIR__ . '/includes/diff/DifferenceEngine.php',
        'DifferenceEngineSlotDiffRenderer' => __DIR__ . '/includes/diff/DifferenceEngineSlotDiffRenderer.php',
        'Digit2Html' => __DIR__ . '/maintenance/language/digit2html.php',
@@ -487,10 +487,10 @@ $wgAutoloadLocalClasses = [
        'ExternalUserNames' => __DIR__ . '/includes/user/ExternalUserNames.php',
        'FSFile' => __DIR__ . '/includes/libs/filebackend/fsfile/FSFile.php',
        'FSFileBackend' => __DIR__ . '/includes/libs/filebackend/FSFileBackend.php',
-       'FSFileBackendDirList' => __DIR__ . '/includes/libs/filebackend/FSFileBackend.php',
-       'FSFileBackendFileList' => __DIR__ . '/includes/libs/filebackend/FSFileBackend.php',
-       'FSFileBackendList' => __DIR__ . '/includes/libs/filebackend/FSFileBackend.php',
-       'FSFileOpHandle' => __DIR__ . '/includes/libs/filebackend/FSFileBackend.php',
+       'FSFileBackendDirList' => __DIR__ . '/includes/libs/filebackend/fileiteration/FSFileBackendDirList.php',
+       'FSFileBackendFileList' => __DIR__ . '/includes/libs/filebackend/fileiteration/FSFileBackendFileList.php',
+       'FSFileBackendList' => __DIR__ . '/includes/libs/filebackend/fileiteration/FSFileBackendList.php',
+       'FSFileOpHandle' => __DIR__ . '/includes/libs/filebackend/fileophandle/FSFileOpHandle.php',
        'FSLockManager' => __DIR__ . '/includes/libs/lockmanager/FSLockManager.php',
        'FakeConverter' => __DIR__ . '/languages/FakeConverter.php',
        'FakeMaintenance' => __DIR__ . '/maintenance/Maintenance.php',
@@ -498,7 +498,7 @@ $wgAutoloadLocalClasses = [
        'FatalError' => __DIR__ . '/includes/exception/FatalError.php',
        'FauxRequest' => __DIR__ . '/includes/FauxRequest.php',
        'FauxResponse' => __DIR__ . '/includes/FauxResponse.php',
-       'FeedItem' => __DIR__ . '/includes/Feed.php',
+       'FeedItem' => __DIR__ . '/includes/changes/FeedItem.php',
        'FeedUtils' => __DIR__ . '/includes/FeedUtils.php',
        'FetchText' => __DIR__ . '/maintenance/fetchText.php',
        'FewestrevisionsPage' => __DIR__ . '/includes/specials/SpecialFewestrevisions.php',
@@ -507,14 +507,14 @@ $wgAutoloadLocalClasses = [
        'FileAwareNodeVisitor' => __DIR__ . '/maintenance/findDeprecated.php',
        'FileBackend' => __DIR__ . '/includes/libs/filebackend/FileBackend.php',
        'FileBackendDBRepoWrapper' => __DIR__ . '/includes/filerepo/FileBackendDBRepoWrapper.php',
-       'FileBackendError' => __DIR__ . '/includes/libs/filebackend/FileBackendError.php',
+       'FileBackendError' => __DIR__ . '/includes/libs/filebackend/exception/FileBackendError.php',
        'FileBackendGroup' => __DIR__ . '/includes/filebackend/FileBackendGroup.php',
        'FileBackendMultiWrite' => __DIR__ . '/includes/libs/filebackend/FileBackendMultiWrite.php',
        'FileBackendStore' => __DIR__ . '/includes/libs/filebackend/FileBackendStore.php',
-       'FileBackendStoreOpHandle' => __DIR__ . '/includes/libs/filebackend/FileBackendStore.php',
-       'FileBackendStoreShardDirIterator' => __DIR__ . '/includes/libs/filebackend/FileBackendStore.php',
-       'FileBackendStoreShardFileIterator' => __DIR__ . '/includes/libs/filebackend/FileBackendStore.php',
-       'FileBackendStoreShardListIterator' => __DIR__ . '/includes/libs/filebackend/FileBackendStore.php',
+       'FileBackendStoreOpHandle' => __DIR__ . '/includes/libs/filebackend/fileophandle/FileBackendStoreOpHandle.php',
+       'FileBackendStoreShardDirIterator' => __DIR__ . '/includes/libs/filebackend/fileiteration/FileBackendStoreShardDirIterator.php',
+       'FileBackendStoreShardFileIterator' => __DIR__ . '/includes/libs/filebackend/fileiteration/FileBackendStoreShardFileIterator.php',
+       'FileBackendStoreShardListIterator' => __DIR__ . '/includes/libs/filebackend/fileiteration/FileBackendStoreShardListIterator.php',
        'FileBasedSiteLookup' => __DIR__ . '/includes/site/FileBasedSiteLookup.php',
        'FileCacheBase' => __DIR__ . '/includes/cache/FileCacheBase.php',
        'FileContentHandler' => __DIR__ . '/includes/content/FileContentHandler.php',
@@ -565,6 +565,7 @@ $wgAutoloadLocalClasses = [
        'GenerateNormalizerDataMl' => __DIR__ . '/maintenance/language/generateNormalizerDataMl.php',
        'GenerateSitemap' => __DIR__ . '/maintenance/generateSitemap.php',
        'GenericArrayObject' => __DIR__ . '/includes/libs/GenericArrayObject.php',
+       'GenericParameterJob' => __DIR__ . '/includes/jobqueue/GenericParameterJob.php',
        'GetConfiguration' => __DIR__ . '/maintenance/getConfiguration.php',
        'GetLagTimes' => __DIR__ . '/maintenance/getLagTimes.php',
        'GetReplicaServer' => __DIR__ . '/maintenance/getReplicaServer.php',
@@ -588,11 +589,11 @@ $wgAutoloadLocalClasses = [
        'HTMLFileCache' => __DIR__ . '/includes/cache/HTMLFileCache.php',
        'HTMLFloatField' => __DIR__ . '/includes/htmlform/fields/HTMLFloatField.php',
        'HTMLForm' => __DIR__ . '/includes/htmlform/HTMLForm.php',
-       'HTMLFormActionFieldLayout' => __DIR__ . '/includes/htmlform/HTMLFormElement.php',
+       'HTMLFormActionFieldLayout' => __DIR__ . '/includes/htmlform/HTMLFormActionFieldLayout.php',
        'HTMLFormElement' => __DIR__ . '/includes/htmlform/HTMLFormElement.php',
        'HTMLFormField' => __DIR__ . '/includes/htmlform/HTMLFormField.php',
        'HTMLFormFieldCloner' => __DIR__ . '/includes/htmlform/fields/HTMLFormFieldCloner.php',
-       'HTMLFormFieldLayout' => __DIR__ . '/includes/htmlform/HTMLFormElement.php',
+       'HTMLFormFieldLayout' => __DIR__ . '/includes/htmlform/HTMLFormFieldLayout.php',
        'HTMLFormFieldRequiredOptionsException' => __DIR__ . '/includes/htmlform/HTMLFormFieldRequiredOptionsException.php',
        'HTMLFormFieldWithButton' => __DIR__ . '/includes/htmlform/fields/HTMLFormFieldWithButton.php',
        'HTMLHiddenField' => __DIR__ . '/includes/htmlform/fields/HTMLHiddenField.php',
@@ -621,7 +622,6 @@ $wgAutoloadLocalClasses = [
        'HTMLUserTextField' => __DIR__ . '/includes/htmlform/fields/HTMLUserTextField.php',
        'HTMLUsersMultiselectField' => __DIR__ . '/includes/htmlform/fields/HTMLUsersMultiselectField.php',
        'HTTPFileStreamer' => __DIR__ . '/includes/libs/filebackend/HTTPFileStreamer.php',
-       'HWLDFWordAccumulator' => __DIR__ . '/includes/diff/DairikiDiff.php',
        'HashBagOStuff' => __DIR__ . '/includes/libs/objectcache/HashBagOStuff.php',
        'HashConfig' => __DIR__ . '/includes/config/HashConfig.php',
        'HashRing' => __DIR__ . '/includes/libs/HashRing.php',
@@ -771,7 +771,7 @@ $wgAutoloadLocalClasses = [
        'LanguageZh_hans' => __DIR__ . '/languages/classes/LanguageZh_hans.php',
        'Languages' => __DIR__ . '/maintenance/language/languages.inc',
        'LayeredParameterizedPassword' => __DIR__ . '/includes/password/LayeredParameterizedPassword.php',
-       'LegacyLogFormatter' => __DIR__ . '/includes/logging/LogFormatter.php',
+       'LegacyLogFormatter' => __DIR__ . '/includes/logging/LegacyLogFormatter.php',
        'License' => __DIR__ . '/includes/specials/helpers/License.php',
        'Licenses' => __DIR__ . '/includes/specials/formfields/Licenses.php',
        'LinkBatch' => __DIR__ . '/includes/cache/LinkBatch.php',
@@ -789,10 +789,10 @@ $wgAutoloadLocalClasses = [
        'LoadBalancer' => __DIR__ . '/includes/libs/rdbms/loadbalancer/LoadBalancer.php',
        'LoadBalancerSingle' => __DIR__ . '/includes/libs/rdbms/loadbalancer/LoadBalancerSingle.php',
        'LocalFile' => __DIR__ . '/includes/filerepo/file/LocalFile.php',
-       'LocalFileDeleteBatch' => __DIR__ . '/includes/filerepo/file/LocalFile.php',
-       'LocalFileLockError' => __DIR__ . '/includes/filerepo/file/LocalFile.php',
-       'LocalFileMoveBatch' => __DIR__ . '/includes/filerepo/file/LocalFile.php',
-       'LocalFileRestoreBatch' => __DIR__ . '/includes/filerepo/file/LocalFile.php',
+       'LocalFileDeleteBatch' => __DIR__ . '/includes/filerepo/file/LocalFileDeleteBatch.php',
+       'LocalFileLockError' => __DIR__ . '/includes/filerepo/file/LocalFileLockError.php',
+       'LocalFileMoveBatch' => __DIR__ . '/includes/filerepo/file/LocalFileMoveBatch.php',
+       'LocalFileRestoreBatch' => __DIR__ . '/includes/filerepo/file/LocalFileRestoreBatch.php',
        'LocalIdLookup' => __DIR__ . '/includes/user/LocalIdLookup.php',
        'LocalRepo' => __DIR__ . '/includes/filerepo/LocalRepo.php',
        'LocalSettingsGenerator' => __DIR__ . '/includes/installer/LocalSettingsGenerator.php',
@@ -802,7 +802,7 @@ $wgAutoloadLocalClasses = [
        'LockManager' => __DIR__ . '/includes/libs/lockmanager/LockManager.php',
        'LockManagerGroup' => __DIR__ . '/includes/filebackend/lockmanager/LockManagerGroup.php',
        'LogEntry' => __DIR__ . '/includes/logging/LogEntry.php',
-       'LogEntryBase' => __DIR__ . '/includes/logging/LogEntry.php',
+       'LogEntryBase' => __DIR__ . '/includes/logging/LogEntryBase.php',
        'LogEventsList' => __DIR__ . '/includes/logging/LogEventsList.php',
        'LogFormatter' => __DIR__ . '/includes/logging/LogFormatter.php',
        'LogPage' => __DIR__ . '/includes/logging/LogPage.php',
@@ -850,7 +850,7 @@ $wgAutoloadLocalClasses = [
        'MalformedTitleException' => __DIR__ . '/includes/title/MalformedTitleException.php',
        'ManageForeignResources' => __DIR__ . '/maintenance/manageForeignResources.php',
        'ManageJobs' => __DIR__ . '/maintenance/manageJobs.php',
-       'ManualLogEntry' => __DIR__ . '/includes/logging/LogEntry.php',
+       'ManualLogEntry' => __DIR__ . '/includes/logging/ManualLogEntry.php',
        'MapCacheLRU' => __DIR__ . '/includes/libs/MapCacheLRU.php',
        'MappedIterator' => __DIR__ . '/includes/libs/MappedIterator.php',
        'MarkpatrolledAction' => __DIR__ . '/includes/actions/MarkpatrolledAction.php',
@@ -1036,7 +1036,6 @@ $wgAutoloadLocalClasses = [
        'ORAField' => __DIR__ . '/includes/db/ORAField.php',
        'ORAResult' => __DIR__ . '/includes/db/ORAResult.php',
        'ObjectCache' => __DIR__ . '/includes/objectcache/ObjectCache.php',
-       'ObjectFactory' => __DIR__ . '/includes/compat/ObjectFactory.php',
        'OldChangesList' => __DIR__ . '/includes/changes/OldChangesList.php',
        'OldLocalFile' => __DIR__ . '/includes/filerepo/file/OldLocalFile.php',
        'OldRevisionImporter' => __DIR__ . '/includes/import/OldRevisionImporter.php',
@@ -1104,7 +1103,7 @@ $wgAutoloadLocalClasses = [
        'PerRowAugmentor' => __DIR__ . '/includes/search/PerRowAugmentor.php',
        'PermissionsError' => __DIR__ . '/includes/exception/PermissionsError.php',
        'PhpHttpRequest' => __DIR__ . '/includes/http/PhpHttpRequest.php',
-       'PhpXmlBugTester' => __DIR__ . '/includes/installer/PhpBugTests.php',
+       'PhpXmlBugTester' => __DIR__ . '/includes/installer/PhpXmlBugTester.php',
        'Pingback' => __DIR__ . '/includes/Pingback.php',
        'PoolCounter' => __DIR__ . '/includes/poolcounter/PoolCounter.php',
        'PoolCounterNull' => __DIR__ . '/includes/poolcounter/PoolCounterNull.php',
@@ -1133,9 +1132,6 @@ $wgAutoloadLocalClasses = [
        'PostgreSqlLockManager' => __DIR__ . '/includes/libs/lockmanager/PostgreSqlLockManager.php',
        'PostgresInstaller' => __DIR__ . '/includes/installer/PostgresInstaller.php',
        'PostgresUpdater' => __DIR__ . '/includes/installer/PostgresUpdater.php',
-       'Preferences' => __DIR__ . '/includes/Preferences.php',
-       'PreferencesForm' => __DIR__ . '/includes/specials/forms/PreferencesFormLegacy.php',
-       'PreferencesFormLegacy' => __DIR__ . '/includes/specials/forms/PreferencesFormLegacy.php',
        'PreferencesFormOOUI' => __DIR__ . '/includes/specials/forms/PreferencesFormOOUI.php',
        'PrefixSearch' => __DIR__ . '/includes/search/PrefixSearch.php',
        'PrefixingStatsdDataFactoryProxy' => __DIR__ . '/includes/libs/stats/PrefixingStatsdDataFactoryProxy.php',
@@ -1179,12 +1175,12 @@ $wgAutoloadLocalClasses = [
        'QuorumLockManager' => __DIR__ . '/includes/libs/lockmanager/QuorumLockManager.php',
        'RCCacheEntry' => __DIR__ . '/includes/changes/RCCacheEntry.php',
        'RCCacheEntryFactory' => __DIR__ . '/includes/changes/RCCacheEntryFactory.php',
-       'RCDatabaseLogEntry' => __DIR__ . '/includes/logging/LogEntry.php',
+       'RCDatabaseLogEntry' => __DIR__ . '/includes/logging/RCDatabaseLogEntry.php',
        'RCFeed' => __DIR__ . '/includes/rcfeed/RCFeed.php',
        'RCFeedEngine' => __DIR__ . '/includes/rcfeed/RCFeedEngine.php',
        'RCFeedFormatter' => __DIR__ . '/includes/rcfeed/RCFeedFormatter.php',
        'RESTBagOStuff' => __DIR__ . '/includes/libs/objectcache/RESTBagOStuff.php',
-       'RSSFeed' => __DIR__ . '/includes/Feed.php',
+       'RSSFeed' => __DIR__ . '/includes/changes/RSSFeed.php',
        'RandomPage' => __DIR__ . '/includes/specials/SpecialRandompage.php',
        'RangeChronologicalPager' => __DIR__ . '/includes/pager/RangeChronologicalPager.php',
        'RangeDifference' => __DIR__ . '/includes/diff/RangeDifference.php',
@@ -1289,8 +1285,9 @@ $wgAutoloadLocalClasses = [
        'RowUpdateGenerator' => __DIR__ . '/includes/utils/RowUpdateGenerator.php',
        'RunBatchedQuery' => __DIR__ . '/maintenance/runBatchedQuery.php',
        'RunJobs' => __DIR__ . '/maintenance/runJobs.php',
+       'RunnableJob' => __DIR__ . '/includes/jobqueue/RunnableJob.php',
        'SVGMetadataExtractor' => __DIR__ . '/includes/media/SVGMetadataExtractor.php',
-       'SVGReader' => __DIR__ . '/includes/media/SVGMetadataExtractor.php',
+       'SVGReader' => __DIR__ . '/includes/media/SVGReader.php',
        'SamplingStatsdClient' => __DIR__ . '/includes/libs/stats/SamplingStatsdClient.php',
        'Sanitizer' => __DIR__ . '/includes/parser/Sanitizer.php',
        'ScopedLock' => __DIR__ . '/includes/libs/lockmanager/ScopedLock.php',
@@ -1317,7 +1314,7 @@ $wgAutoloadLocalClasses = [
        'SearchSuggestion' => __DIR__ . '/includes/search/SearchSuggestion.php',
        'SearchSuggestionSet' => __DIR__ . '/includes/search/SearchSuggestionSet.php',
        'SearchUpdate' => __DIR__ . '/includes/deferred/SearchUpdate.php',
-       'SectionProfileCallback' => __DIR__ . '/includes/profiler/SectionProfiler.php',
+       'SectionProfileCallback' => __DIR__ . '/includes/profiler/SectionProfileCallback.php',
        'SectionProfiler' => __DIR__ . '/includes/profiler/SectionProfiler.php',
        'SevenZipStream' => __DIR__ . '/maintenance/7zip.inc',
        'ShiConverter' => __DIR__ . '/languages/classes/LanguageShi.php',
@@ -1344,7 +1341,7 @@ $wgAutoloadLocalClasses = [
        'SkinTemplate' => __DIR__ . '/includes/skins/SkinTemplate.php',
        'SlideshowImageGallery' => __DIR__ . '/includes/gallery/SlideshowImageGallery.php',
        'SlotDiffRenderer' => __DIR__ . '/includes/diff/SlotDiffRenderer.php',
-       'SpecialActiveUsers' => __DIR__ . '/includes/specials/SpecialActiveusers.php',
+       'SpecialActiveUsers' => __DIR__ . '/includes/specials/SpecialActiveUsers.php',
        'SpecialAllMessages' => __DIR__ . '/includes/specials/SpecialAllMessages.php',
        'SpecialAllMyUploads' => __DIR__ . '/includes/specials/redirects/SpecialAllMyUploads.php',
        'SpecialAllPages' => __DIR__ . '/includes/specials/SpecialAllPages.php',
@@ -1354,7 +1351,7 @@ $wgAutoloadLocalClasses = [
        'SpecialBlankpage' => __DIR__ . '/includes/specials/SpecialBlankpage.php',
        'SpecialBlock' => __DIR__ . '/includes/specials/SpecialBlock.php',
        'SpecialBlockList' => __DIR__ . '/includes/specials/SpecialBlockList.php',
-       'SpecialBookSources' => __DIR__ . '/includes/specials/SpecialBooksources.php',
+       'SpecialBookSources' => __DIR__ . '/includes/specials/SpecialBookSources.php',
        'SpecialBotPasswords' => __DIR__ . '/includes/specials/SpecialBotPasswords.php',
        'SpecialCachedPage' => __DIR__ . '/includes/specials/SpecialCachedPage.php',
        'SpecialCategories' => __DIR__ . '/includes/specials/SpecialCategories.php',
@@ -1368,7 +1365,7 @@ $wgAutoloadLocalClasses = [
        'SpecialDiff' => __DIR__ . '/includes/specials/SpecialDiff.php',
        'SpecialEditTags' => __DIR__ . '/includes/specials/SpecialEditTags.php',
        'SpecialEditWatchlist' => __DIR__ . '/includes/specials/SpecialEditWatchlist.php',
-       'SpecialEmailUser' => __DIR__ . '/includes/specials/SpecialEmailuser.php',
+       'SpecialEmailUser' => __DIR__ . '/includes/specials/SpecialEmailUser.php',
        'SpecialExpandTemplates' => __DIR__ . '/includes/specials/SpecialExpandTemplates.php',
        'SpecialExport' => __DIR__ . '/includes/specials/SpecialExport.php',
        'SpecialFilepath' => __DIR__ . '/includes/specials/SpecialFilepath.php',
@@ -1378,10 +1375,10 @@ $wgAutoloadLocalClasses = [
        'SpecialLinkAccounts' => __DIR__ . '/includes/specials/SpecialLinkAccounts.php',
        'SpecialListAdmins' => __DIR__ . '/includes/specials/redirects/SpecialListAdmins.php',
        'SpecialListBots' => __DIR__ . '/includes/specials/redirects/SpecialListBots.php',
-       'SpecialListFiles' => __DIR__ . '/includes/specials/SpecialListfiles.php',
-       'SpecialListGrants' => __DIR__ . '/includes/specials/SpecialListgrants.php',
-       'SpecialListGroupRights' => __DIR__ . '/includes/specials/SpecialListgrouprights.php',
-       'SpecialListUsers' => __DIR__ . '/includes/specials/SpecialListusers.php',
+       'SpecialListFiles' => __DIR__ . '/includes/specials/SpecialListFiles.php',
+       'SpecialListGrants' => __DIR__ . '/includes/specials/SpecialListGrants.php',
+       'SpecialListGroupRights' => __DIR__ . '/includes/specials/SpecialListGroupRights.php',
+       'SpecialListUsers' => __DIR__ . '/includes/specials/SpecialListUsers.php',
        'SpecialLockdb' => __DIR__ . '/includes/specials/SpecialLockdb.php',
        'SpecialLog' => __DIR__ . '/includes/specials/SpecialLog.php',
        'SpecialMergeHistory' => __DIR__ . '/includes/specials/SpecialMergeHistory.php',
@@ -1408,13 +1405,13 @@ $wgAutoloadLocalClasses = [
        'SpecialRandomInCategory' => __DIR__ . '/includes/specials/SpecialRandomInCategory.php',
        'SpecialRandomredirect' => __DIR__ . '/includes/specials/SpecialRandomredirect.php',
        'SpecialRandomrootpage' => __DIR__ . '/includes/specials/SpecialRandomrootpage.php',
-       'SpecialRecentChanges' => __DIR__ . '/includes/specials/SpecialRecentchanges.php',
-       'SpecialRecentChangesLinked' => __DIR__ . '/includes/specials/SpecialRecentchangeslinked.php',
+       'SpecialRecentChanges' => __DIR__ . '/includes/specials/SpecialRecentChanges.php',
+       'SpecialRecentChangesLinked' => __DIR__ . '/includes/specials/SpecialRecentChangesLinked.php',
        'SpecialRedirect' => __DIR__ . '/includes/specials/SpecialRedirect.php',
        'SpecialRedirectToSpecial' => __DIR__ . '/includes/specialpage/SpecialRedirectToSpecial.php',
        'SpecialRemoveCredentials' => __DIR__ . '/includes/specials/SpecialRemoveCredentials.php',
        'SpecialResetTokens' => __DIR__ . '/includes/specials/SpecialResetTokens.php',
-       'SpecialRevisionDelete' => __DIR__ . '/includes/specials/SpecialRevisiondelete.php',
+       'SpecialRevisionDelete' => __DIR__ . '/includes/specials/SpecialRevisionDelete.php',
        'SpecialRunJobs' => __DIR__ . '/includes/specials/SpecialRunJobs.php',
        'SpecialSearch' => __DIR__ . '/includes/specials/SpecialSearch.php',
        'SpecialSpecialpages' => __DIR__ . '/includes/specials/SpecialSpecialpages.php',
@@ -1432,7 +1429,7 @@ $wgAutoloadLocalClasses = [
        'SpecialUserLogout' => __DIR__ . '/includes/specials/SpecialUserLogout.php',
        'SpecialVersion' => __DIR__ . '/includes/specials/SpecialVersion.php',
        'SpecialWatchlist' => __DIR__ . '/includes/specials/SpecialWatchlist.php',
-       'SpecialWhatLinksHere' => __DIR__ . '/includes/specials/SpecialWhatlinkshere.php',
+       'SpecialWhatLinksHere' => __DIR__ . '/includes/specials/SpecialWhatLinksHere.php',
        'SqlBagOStuff' => __DIR__ . '/includes/objectcache/SqlBagOStuff.php',
        'SqlSearchResultSet' => __DIR__ . '/includes/search/SqlSearchResultSet.php',
        'Sqlite' => __DIR__ . '/maintenance/sqlite.inc',
@@ -1453,15 +1450,15 @@ $wgAutoloadLocalClasses = [
        'StringUtils' => __DIR__ . '/includes/libs/StringUtils.php',
        'StripState' => __DIR__ . '/includes/parser/StripState.php',
        'StubObject' => __DIR__ . '/includes/StubObject.php',
-       'StubUserLang' => __DIR__ . '/includes/StubObject.php',
+       'StubUserLang' => __DIR__ . '/includes/StubUserLang.php',
        'SubmitAction' => __DIR__ . '/includes/actions/SubmitAction.php',
        'SubpageImportTitleFactory' => __DIR__ . '/includes/title/SubpageImportTitleFactory.php',
        'SvgHandler' => __DIR__ . '/includes/media/SvgHandler.php',
        'SwiftFileBackend' => __DIR__ . '/includes/libs/filebackend/SwiftFileBackend.php',
-       'SwiftFileBackendDirList' => __DIR__ . '/includes/libs/filebackend/SwiftFileBackend.php',
-       'SwiftFileBackendFileList' => __DIR__ . '/includes/libs/filebackend/SwiftFileBackend.php',
-       'SwiftFileBackendList' => __DIR__ . '/includes/libs/filebackend/SwiftFileBackend.php',
-       'SwiftFileOpHandle' => __DIR__ . '/includes/libs/filebackend/SwiftFileBackend.php',
+       'SwiftFileBackendDirList' => __DIR__ . '/includes/libs/filebackend/fileiteration/SwiftFileBackendDirList.php',
+       'SwiftFileBackendFileList' => __DIR__ . '/includes/libs/filebackend/fileiteration/SwiftFileBackendFileList.php',
+       'SwiftFileBackendList' => __DIR__ . '/includes/libs/filebackend/fileiteration/SwiftFileBackendList.php',
+       'SwiftFileOpHandle' => __DIR__ . '/includes/libs/filebackend/fileophandle/SwiftFileOpHandle.php',
        'SwiftVirtualRESTService' => __DIR__ . '/includes/libs/virtualrest/SwiftVirtualRESTService.php',
        'SyncFileBackend' => __DIR__ . '/maintenance/syncFileBackend.php',
        'TableCleanup' => __DIR__ . '/maintenance/cleanupTable.inc',
@@ -1542,7 +1539,7 @@ $wgAutoloadLocalClasses = [
        'UploadStash' => __DIR__ . '/includes/upload/UploadStash.php',
        'UploadStashBadPathException' => __DIR__ . '/includes/upload/exception/UploadStashBadPathException.php',
        'UploadStashException' => __DIR__ . '/includes/upload/exception/UploadStashException.php',
-       'UploadStashFile' => __DIR__ . '/includes/upload/UploadStash.php',
+       'UploadStashFile' => __DIR__ . '/includes/upload/UploadStashFile.php',
        'UploadStashFileException' => __DIR__ . '/includes/upload/exception/UploadStashFileException.php',
        'UploadStashFileNotFoundException' => __DIR__ . '/includes/upload/exception/UploadStashFileNotFoundException.php',
        'UploadStashNoSuchKeyException' => __DIR__ . '/includes/upload/exception/UploadStashNoSuchKeyException.php',
@@ -1694,9 +1691,6 @@ $wgAutoloadLocalClasses = [
        'WrapOldPasswords' => __DIR__ . '/maintenance/wrapOldPasswords.php',
        'XCFHandler' => __DIR__ . '/includes/media/XCFHandler.php',
        'XMLRCFeedFormatter' => __DIR__ . '/includes/rcfeed/XMLRCFeedFormatter.php',
-       'XMPInfo' => __DIR__ . '/includes/compat/XMPReader.php',
-       'XMPReader' => __DIR__ . '/includes/compat/XMPReader.php',
-       'XMPValidate' => __DIR__ . '/includes/compat/XMPReader.php',
        'Xhprof' => __DIR__ . '/includes/libs/Xhprof.php',
        'XhprofData' => __DIR__ . '/includes/libs/XhprofData.php',
        'Xml' => __DIR__ . '/includes/Xml.php',
index 8cd4e71..36e2fe2 100644 (file)
                                                "php": {
                                                        "type": "string",
                                                        "description": "Version constraint string against PHP."
+                                               },
+                                               "ability-shell": {
+                                                       "type": "boolean",
+                                                       "default": false,
+                                                       "description": "Whether this extension requires shell access."
                                                }
                                        },
                                        "patternProperties": {
index 1d64095..ed903f8 100644 (file)
                                                "php": {
                                                        "type": "string",
                                                        "description": "Version constraint string against PHP."
+                                               },
+                                               "ability-shell": {
+                                                       "type": "boolean",
+                                                       "default": false,
+                                                       "description": "Whether this extension requires shell access."
                                                }
                                        },
                                        "patternProperties": {
index d3d04ba..b2f1e81 100644 (file)
@@ -1193,9 +1193,6 @@ a custom editor, e.g. for a special namespace, etc.
 $article: Article being edited
 $user: User performing the edit
 
-'DatabaseOraclePostInit': Called after initialising an Oracle database
-$db: the DatabaseOracle object
-
 'DeletedContribsPager::reallyDoQuery': Called before really executing the query
 for Special:DeletedContributions
 Similar to ContribsPager::reallyDoQuery
index ca69d31..0a209e9 100644 (file)
@@ -148,7 +148,7 @@ function wfImageAuthMain() {
                }
 
                // Run hook for extension authorization plugins
-               /** @var $result array */
+               /** @var array $result */
                $result = null;
                if ( !Hooks::run( 'ImgAuthBeforeStream', [ &$title, &$path, &$name, &$result ] ) ) {
                        wfForbidden( $result[0], $result[1], array_slice( $result, 2 ) );
@@ -186,13 +186,12 @@ function wfImageAuthMain() {
  * subsequent arguments to $msg2 will be passed as parameters only for replacing in $msg2
  * @param string $msg1
  * @param string $msg2
+ * @param mixed ...$args To pass as params to wfMessage() with $msg2. Either variadic, or a single
+ *   array argument.
  */
-function wfForbidden( $msg1, $msg2 ) {
+function wfForbidden( $msg1, $msg2, ...$args ) {
        global $wgImgAuthDetails;
 
-       $args = func_get_args();
-       array_shift( $args );
-       array_shift( $args );
        $args = ( isset( $args[0] ) && is_array( $args[0] ) ) ? $args[0] : $args;
 
        $msgHdr = wfMessage( $msg1 )->escaped();
index 8f4ed40..5f889ad 100644 (file)
@@ -162,7 +162,7 @@ class AjaxResponse {
                        // For back-compat, it is supported that mResponseCode be a string like " 200 OK"
                        // (with leading space and the status message after). Cast response code to an integer
                        // to take advantage of PHP's conversion rules which will turn "  200 OK" into 200.
-                       // https://secure.php.net/manual/en/language.types.string.php#language.types.string.conversion
+                       // https://www.php.net/manual/en/language.types.string.php#language.types.string.conversion
                        $n = intval( trim( $this->mResponseCode ) );
                        HttpStatus::header( $n );
                }
index ca16536..77f1212 100644 (file)
@@ -25,8 +25,6 @@
  * Category objects are immutable, strictly speaking. If you call methods that change the database,
  * like to refresh link counts, the objects will be appropriately reinitialized.
  * Member variables are lazy-initialized.
- *
- * @todo Move some stuff from CategoryPage.php to here, and use that.
  */
 class Category {
        /** Name of the category, normalized to DB-key form */
index 17c28ec..7df2aed 100644 (file)
@@ -63,11 +63,4 @@ class ConfiguredReadOnlyMode {
        public function setReason( $msg ) {
                $this->overrideReason = $msg;
        }
-
-       /**
-        * Clear the cache of the read only file
-        */
-       public function clearCache() {
-               $this->fileReason = null;
-       }
 }
index 44e0310..4547009 100644 (file)
@@ -72,7 +72,7 @@ $wgConfigRegistry = [
  * MediaWiki version number
  * @since 1.2
  */
-$wgVersion = '1.33.0-alpha';
+$wgVersion = '1.34.0-alpha';
 
 /**
  * Name of the site. It must be changed in LocalSettings.php
@@ -782,7 +782,7 @@ $wgLockManagers = [];
 
 /**
  * Show Exif data, on by default if available.
- * Requires PHP's Exif extension: https://secure.php.net/manual/en/ref.exif.php
+ * Requires PHP's Exif extension: https://www.php.net/manual/en/ref.exif.php
  *
  * @note FOR WINDOWS USERS:
  * To enable Exif functions, add the following line to the "Windows
@@ -2100,7 +2100,7 @@ $wgDBerrorLog = false;
  * Defaults to the wiki timezone ($wgLocaltimezone).
  *
  * A list of usable timezones can found at:
- * https://secure.php.net/manual/en/timezones.php
+ * https://www.php.net/manual/en/timezones.php
  *
  * @par Examples:
  * @code
@@ -3170,7 +3170,7 @@ $wgForceUIMsgAsContentMsg = [];
  * timezone-nameinlowercase like timezone-utc.
  *
  * A list of usable timezones can found at:
- * https://secure.php.net/manual/en/timezones.php
+ * https://www.php.net/manual/en/timezones.php
  *
  * @par Examples:
  * @code
@@ -7640,7 +7640,7 @@ $wgCategoryPagingLimit = 200;
  *     all languages in a mediocre way. However, it is better than "uppercase".
  *
  * To use the uca-default collation, you must have PHP's intl extension
- * installed. See https://secure.php.net/manual/en/intl.setup.php . The details of the
+ * installed. See https://www.php.net/manual/en/intl.setup.php . The details of the
  * resulting collation will depend on the version of ICU installed on the
  * server.
  *
@@ -8996,7 +8996,7 @@ $wgEnableBlockNoticeStats = false;
 /**
  * Origin Trials tokens.
  *
- * @since 1.34
+ * @since 1.33
  * @var array
  */
 $wgOriginTrials = [];
@@ -9006,7 +9006,7 @@ $wgOriginTrials = [];
  *
  * @warning EXPERIMENTAL!
  *
- * @since 1.34
+ * @since 1.33
  * @var bool
  */
 $wgPriorityHints = false;
@@ -9016,11 +9016,42 @@ $wgPriorityHints = false;
  *
  * @warning EXPERIMENTAL!
  *
- * @since 1.34
+ * @since 1.33
  * @var bool
  */
 $wgElementTiming = false;
 
+/**
+ * Expiry of the endpoint definition for the Reporting API.
+ *
+ * @warning EXPERIMENTAL!
+ *
+ * @since 1.34
+ * @var int
+ */
+$wgReportToExpiry = 86400;
+
+/**
+ * List of endpoints for the Reporting API.
+ *
+ * @warning EXPERIMENTAL!
+ *
+ * @since 1.34
+ * @var array
+ */
+$wgReportToEndpoints = [];
+
+/**
+ * List of Feature Policy Reporting types to enable.
+ * Each entry is turned into a Feature-Policy-Report-Only header.
+ *
+ * @warning EXPERIMENTAL!
+ *
+ * @since 1.34
+ * @var array
+ */
+$wgFeaturePolicyReportOnly = [];
+
 /**
  * For really cool vim folding this needs to be at the end:
  * vim: foldmarker=@{,@} foldmethod=marker
index 7f00c6c..4bf00d0 100644 (file)
@@ -25,8 +25,8 @@ ini_set( 'display_errors', 1 );
  * Debugging: MediaWiki
  */
 global $wgDevelopmentWarnings, $wgShowExceptionDetails, $wgShowHostnames,
-       $wgDebugRawPage, $wgDebugComments, $wgDebugDumpSql, $wgDebugTimestamps,
-       $wgCommandLineMode, $wgDebugLogFile, $wgDBerrorLog, $wgDebugLogGroups;
+       $wgDebugRawPage, $wgSQLMode, $wgCommandLineMode, $wgDebugLogFile,
+       $wgDBerrorLog, $wgDebugLogGroups;
 
 // Use of wfWarn() should cause tests to fail
 $wgDevelopmentWarnings = true;
diff --git a/includes/Feed.php b/includes/Feed.php
deleted file mode 100644 (file)
index 86e9bee..0000000
+++ /dev/null
@@ -1,494 +0,0 @@
-<?php
-/**
- * Basic support for outputting syndication feeds in RSS, other formats.
- *
- * Contain a feed class as well as classes to build rss / atom ... feeds
- * Available feeds are defined in Defines.php
- *
- * Copyright © 2004 Brion Vibber <brion@pobox.com>
- * https://www.mediawiki.org/
- *
- * 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
- */
-
-/**
- * @defgroup Feed Feed
- */
-
-/**
- * A base class for basic support for outputting syndication feeds in RSS and other formats.
- *
- * @ingroup Feed
- */
-class FeedItem {
-       /** @var Title */
-       public $title;
-
-       public $description;
-
-       public $url;
-
-       public $date;
-
-       public $author;
-
-       public $uniqueId;
-
-       public $comments;
-
-       public $rssIsPermalink = false;
-
-       /**
-        * @param string|Title $title Item's title
-        * @param string $description
-        * @param string $url URL uniquely designating the item.
-        * @param string $date Item's date
-        * @param string $author Author's user name
-        * @param string $comments
-        */
-       function __construct( $title, $description, $url, $date = '', $author = '', $comments = '' ) {
-               $this->title = $title;
-               $this->description = $description;
-               $this->url = $url;
-               $this->uniqueId = $url;
-               $this->date = $date;
-               $this->author = $author;
-               $this->comments = $comments;
-       }
-
-       /**
-        * Encode $string so that it can be safely embedded in a XML document
-        *
-        * @param string $string String to encode
-        * @return string
-        */
-       public function xmlEncode( $string ) {
-               $string = str_replace( "\r\n", "\n", $string );
-               $string = preg_replace( '/[\x00-\x08\x0b\x0c\x0e-\x1f]/', '', $string );
-               return htmlspecialchars( $string );
-       }
-
-       /**
-        * Get the unique id of this item; already xml-encoded
-        * @return string
-        */
-       public function getUniqueID() {
-               $id = $this->getUniqueIdUnescaped();
-               if ( $id ) {
-                       return $this->xmlEncode( $id );
-               }
-       }
-
-       /**
-        * Get the unique id of this item, without any escaping
-        * @return string
-        */
-       public function getUniqueIdUnescaped() {
-               if ( $this->uniqueId ) {
-                       return wfExpandUrl( $this->uniqueId, PROTO_CURRENT );
-               }
-       }
-
-       /**
-        * Set the unique id of an item
-        *
-        * @param string $uniqueId Unique id for the item
-        * @param bool $rssIsPermalink Set to true if the guid (unique id) is a permalink (RSS feeds only)
-        */
-       public function setUniqueId( $uniqueId, $rssIsPermalink = false ) {
-               $this->uniqueId = $uniqueId;
-               $this->rssIsPermalink = $rssIsPermalink;
-       }
-
-       /**
-        * Get the title of this item; already xml-encoded
-        *
-        * @return string
-        */
-       public function getTitle() {
-               return $this->xmlEncode( $this->title );
-       }
-
-       /**
-        * Get the URL of this item; already xml-encoded
-        *
-        * @return string
-        */
-       public function getUrl() {
-               return $this->xmlEncode( $this->url );
-       }
-
-       /** Get the URL of this item without any escaping
-        *
-        * @return string
-        */
-       public function getUrlUnescaped() {
-               return $this->url;
-       }
-
-       /**
-        * Get the description of this item; already xml-encoded
-        *
-        * @return string
-        */
-       public function getDescription() {
-               return $this->xmlEncode( $this->description );
-       }
-
-       /**
-        * Get the description of this item without any escaping
-        *
-        * @return string
-        */
-       public function getDescriptionUnescaped() {
-               return $this->description;
-       }
-
-       /**
-        * Get the language of this item
-        *
-        * @return string
-        */
-       public function getLanguage() {
-               global $wgLanguageCode;
-               return LanguageCode::bcp47( $wgLanguageCode );
-       }
-
-       /**
-        * Get the date of this item
-        *
-        * @return string
-        */
-       public function getDate() {
-               return $this->date;
-       }
-
-       /**
-        * Get the author of this item; already xml-encoded
-        *
-        * @return string
-        */
-       public function getAuthor() {
-               return $this->xmlEncode( $this->author );
-       }
-
-       /**
-        * Get the author of this item without any escaping
-        *
-        * @return string
-        */
-       public function getAuthorUnescaped() {
-               return $this->author;
-       }
-
-       /**
-        * Get the comment of this item; already xml-encoded
-        *
-        * @return string
-        */
-       public function getComments() {
-               return $this->xmlEncode( $this->comments );
-       }
-
-       /**
-        * Get the comment of this item without any escaping
-        *
-        * @return string
-        */
-       public function getCommentsUnescaped() {
-               return $this->comments;
-       }
-
-       /**
-        * Quickie hack... strip out wikilinks to more legible form from the comment.
-        *
-        * @param string $text Wikitext
-        * @return string
-        */
-       public static function stripComment( $text ) {
-               return preg_replace( '/\[\[([^]]*\|)?([^]]+)\]\]/', '\2', $text );
-       }
-       /**#@-*/
-}
-
-/**
- * Class to support the outputting of syndication feeds in Atom and RSS format.
- *
- * @ingroup Feed
- */
-abstract class ChannelFeed extends FeedItem {
-
-       /** @var TemplateParser */
-       protected $templateParser;
-
-       /**
-        * @param string|Title $title Feed's title
-        * @param string $description
-        * @param string $url URL uniquely designating the feed.
-        * @param string $date Feed's date
-        * @param string $author Author's user name
-        * @param string $comments
-        */
-       function __construct( $title, $description, $url, $date = '', $author = '', $comments = '' ) {
-               parent::__construct( $title, $description, $url, $date, $author, $comments );
-               $this->templateParser = new TemplateParser();
-       }
-
-       /**
-        * Generate Header of the feed
-        * @par Example:
-        * @code
-        * print "<feed>";
-        * @endcode
-        */
-       abstract public function outHeader();
-
-       /**
-        * Generate an item
-        * @par Example:
-        * @code
-        * print "<item>...</item>";
-        * @endcode
-        * @param FeedItem $item
-        */
-       abstract public function outItem( $item );
-
-       /**
-        * Generate Footer of the feed
-        * @par Example:
-        * @code
-        * print "</feed>";
-        * @endcode
-        */
-       abstract public function outFooter();
-
-       /**
-        * Setup and send HTTP headers. Don't send any content;
-        * content might end up being cached and re-sent with
-        * these same headers later.
-        *
-        * This should be called from the outHeader() method,
-        * but can also be called separately.
-        */
-       public function httpHeaders() {
-               global $wgOut, $wgVaryOnXFP;
-
-               # We take over from $wgOut, excepting its cache header info
-               $wgOut->disable();
-               $mimetype = $this->contentType();
-               header( "Content-type: $mimetype; charset=UTF-8" );
-
-               // Set a sane filename
-               $exts = MediaWiki\MediaWikiServices::getInstance()->getMimeAnalyzer()
-                       ->getExtensionsForType( $mimetype );
-               $ext = $exts ? strtok( $exts, ' ' ) : 'xml';
-               header( "Content-Disposition: inline; filename=\"feed.{$ext}\"" );
-
-               if ( $wgVaryOnXFP ) {
-                       $wgOut->addVaryHeader( 'X-Forwarded-Proto' );
-               }
-               $wgOut->sendCacheControl();
-       }
-
-       /**
-        * Return an internet media type to be sent in the headers.
-        *
-        * @return string
-        */
-       private function contentType() {
-               global $wgRequest;
-
-               $ctype = $wgRequest->getVal( 'ctype', 'application/xml' );
-               $allowedctypes = [
-                       'application/xml',
-                       'text/xml',
-                       'application/rss+xml',
-                       'application/atom+xml'
-               ];
-
-               return ( in_array( $ctype, $allowedctypes ) ? $ctype : 'application/xml' );
-       }
-
-       /**
-        * Output the initial XML headers.
-        */
-       protected function outXmlHeader() {
-               $this->httpHeaders();
-               echo '<?xml version="1.0"?>' . "\n";
-       }
-}
-
-/**
- * Generate a RSS feed
- *
- * @ingroup Feed
- */
-class RSSFeed extends ChannelFeed {
-
-       /**
-        * Format a date given a timestamp. If a timestamp is not given, nothing is returned
-        *
-        * @param int|null $ts Timestamp
-        * @return string|null Date string
-        */
-       function formatTime( $ts ) {
-               if ( $ts ) {
-                       return gmdate( 'D, d M Y H:i:s \G\M\T', wfTimestamp( TS_UNIX, $ts ) );
-               }
-       }
-
-       /**
-        * Output an RSS 2.0 header
-        */
-       function outHeader() {
-               global $wgVersion;
-
-               $this->outXmlHeader();
-               // Manually escaping rather than letting Mustache do it because Mustache
-               // uses htmlentities, which does not work with XML
-               $templateParams = [
-                       'title' => $this->getTitle(),
-                       'url' => $this->xmlEncode( wfExpandUrl( $this->getUrlUnescaped(), PROTO_CURRENT ) ),
-                       'description' => $this->getDescription(),
-                       'language' => $this->xmlEncode( $this->getLanguage() ),
-                       'version' => $this->xmlEncode( $wgVersion ),
-                       'timestamp' => $this->xmlEncode( $this->formatTime( wfTimestampNow() ) )
-               ];
-               print $this->templateParser->processTemplate( 'RSSHeader', $templateParams );
-       }
-
-       /**
-        * Output an RSS 2.0 item
-        * @param FeedItem $item Item to be output
-        */
-       function outItem( $item ) {
-               // Manually escaping rather than letting Mustache do it because Mustache
-               // uses htmlentities, which does not work with XML
-               $templateParams = [
-                       "title" => $item->getTitle(),
-                       "url" => $this->xmlEncode( wfExpandUrl( $item->getUrlUnescaped(), PROTO_CURRENT ) ),
-                       "permalink" => $item->rssIsPermalink,
-                       "uniqueID" => $item->getUniqueID(),
-                       "description" => $item->getDescription(),
-                       "date" => $this->xmlEncode( $this->formatTime( $item->getDate() ) ),
-                       "author" => $item->getAuthor()
-               ];
-               $comments = $item->getCommentsUnescaped();
-               if ( $comments ) {
-                       $commentsEscaped = $this->xmlEncode( wfExpandUrl( $comments, PROTO_CURRENT ) );
-                       $templateParams["comments"] = $commentsEscaped;
-               }
-               print $this->templateParser->processTemplate( 'RSSItem', $templateParams );
-       }
-
-       /**
-        * Output an RSS 2.0 footer
-        */
-       function outFooter() {
-               print "</channel></rss>";
-       }
-}
-
-/**
- * Generate an Atom feed
- *
- * @ingroup Feed
- */
-class AtomFeed extends ChannelFeed {
-       /**
-        * Format a date given timestamp, if one is given.
-        *
-        * @param string|int|null $timestamp
-        * @return string|null
-        */
-       function formatTime( $timestamp ) {
-               if ( $timestamp ) {
-                       // need to use RFC 822 time format at least for rss2.0
-                       return gmdate( 'Y-m-d\TH:i:s', wfTimestamp( TS_UNIX, $timestamp ) );
-               }
-       }
-
-       /**
-        * Outputs a basic header for Atom 1.0 feeds.
-        */
-       function outHeader() {
-               global $wgVersion;
-               $this->outXmlHeader();
-               // Manually escaping rather than letting Mustache do it because Mustache
-               // uses htmlentities, which does not work with XML
-               $templateParams = [
-                       'language' => $this->xmlEncode( $this->getLanguage() ),
-                       'feedID' => $this->getFeedId(),
-                       'title' => $this->getTitle(),
-                       'url' => $this->xmlEncode( wfExpandUrl( $this->getUrlUnescaped(), PROTO_CURRENT ) ),
-                       'selfUrl' => $this->getSelfUrl(),
-                       'timestamp' => $this->xmlEncode( $this->formatTime( wfTimestampNow() ) ),
-                       'description' => $this->getDescription(),
-                       'version' => $this->xmlEncode( $wgVersion ),
-               ];
-               print $this->templateParser->processTemplate( 'AtomHeader', $templateParams );
-       }
-
-       /**
-        * Atom 1.0 requires a unique, opaque IRI as a unique identifier
-        * for every feed we create. For now just use the URL, but who
-        * can tell if that's right? If we put options on the feed, do we
-        * have to change the id? Maybe? Maybe not.
-        *
-        * @return string
-        */
-       private function getFeedId() {
-               return $this->getSelfUrl();
-       }
-
-       /**
-        * Atom 1.0 requests a self-reference to the feed.
-        * @return string
-        */
-       private function getSelfUrl() {
-               global $wgRequest;
-               return htmlspecialchars( $wgRequest->getFullRequestURL() );
-       }
-
-       /**
-        * Output a given item.
-        * @param FeedItem $item
-        */
-       function outItem( $item ) {
-               global $wgMimeType;
-               // Manually escaping rather than letting Mustache do it because Mustache
-               // uses htmlentities, which does not work with XML
-               $templateParams = [
-                       "uniqueID" => $item->getUniqueID(),
-                       "title" => $item->getTitle(),
-                       "mimeType" => $this->xmlEncode( $wgMimeType ),
-                       "url" => $this->xmlEncode( wfExpandUrl( $item->getUrlUnescaped(), PROTO_CURRENT ) ),
-                       "date" => $this->xmlEncode( $this->formatTime( $item->getDate() ) ),
-                       "description" => $item->getDescription(),
-                       "author" => $item->getAuthor()
-               ];
-               print $this->templateParser->processTemplate( 'AtomItem', $templateParams );
-       }
-
-       /**
-        * Outputs the footer for Atom 1.0 feed (basically '\</feed\>').
-        */
-       function outFooter() {
-               print "</feed>";
-       }
-}
index 53ddac9..a6e01cc 100644 (file)
@@ -381,7 +381,7 @@ class FormOptions implements ArrayAccess {
 
        /** @name ArrayAccess functions
         * These functions implement the ArrayAccess PHP interface.
-        * @see https://secure.php.net/manual/en/class.arrayaccess.php
+        * @see https://www.php.net/manual/en/class.arrayaccess.php
         */
        /* @{ */
        /**
index cdbc27a..b6a1470 100644 (file)
@@ -2050,7 +2050,7 @@ function wfMkdirParents( $dir, $mode = null, $caller = null ) {
  */
 function wfRecursiveRemoveDir( $dir ) {
        wfDebug( __FUNCTION__ . "( $dir )\n" );
-       // taken from https://secure.php.net/manual/en/function.rmdir.php#98622
+       // taken from https://www.php.net/manual/en/function.rmdir.php#98622
        if ( is_dir( $dir ) ) {
                $objects = scandir( $dir );
                foreach ( $objects as $object ) {
index a36a12f..1529473 100644 (file)
@@ -22,7 +22,7 @@
 use MediaWiki\MediaWikiServices;
 
 /**
- * @deprecated since 1.33, use NamespaceInfo instead
+ * @deprecated since 1.34, use NamespaceInfo instead
  */
 class MWNamespace {
        /**
index 990ed4e..69bafaf 100644 (file)
@@ -262,7 +262,7 @@ class MediaWiki {
                                                $target = $specialPage->getRedirect( $subpage );
                                                // target can also be true. We let that case fall through to normal processing.
                                                if ( $target instanceof Title ) {
-                                                       $query = $specialPage->getRedirectQuery() ?: [];
+                                                       $query = $specialPage->getRedirectQuery( $subpage ) ?: [];
                                                        $request = new DerivativeRequest( $this->context->getRequest(), $query );
                                                        $request->setRequestURL( $this->context->getRequest()->getRequestURL() );
                                                        $this->context->setRequest( $request );
index 473cbe5..655946f 100644 (file)
@@ -6,7 +6,7 @@ use CommentStore;
 use Config;
 use ConfigFactory;
 use CryptHKDF;
-use CryptRand;
+use DateFormatterFactory;
 use EventRelayerGroup;
 use GenderCache;
 use GlobalVarConfig;
@@ -517,13 +517,11 @@ class MediaWikiServices extends ServiceContainer {
        }
 
        /**
-        * @since 1.28
-        * @deprecated since 1.32, use random_bytes()/random_int()
-        * @return CryptRand
+        * @since 1.33
+        * @return DateFormatterFactory
         */
-       public function getCryptRand() {
-               wfDeprecated( __METHOD__, '1.32' );
-               return $this->getService( 'CryptRand' );
+       public function getDateFormatterFactory() {
+               return $this->getService( 'DateFormatterFactory' );
        }
 
        /**
@@ -669,19 +667,19 @@ class MediaWikiServices extends ServiceContainer {
        }
 
        /**
-        * @since 1.32
-        * @return NameTableStoreFactory
+        * @since 1.34
+        * @return NamespaceInfo
         */
-       public function getNameTableStoreFactory() {
-               return $this->getService( 'NameTableStoreFactory' );
+       public function getNamespaceInfo() : NamespaceInfo {
+               return $this->getService( 'NamespaceInfo' );
        }
 
        /**
-        * @since 1.33
-        * @return NamespaceInfo
+        * @since 1.32
+        * @return NameTableStoreFactory
         */
-       public function getNamespaceInfo() : NamespaceInfo {
-               return $this->getService( 'NamespaceInfo' );
+       public function getNameTableStoreFactory() {
+               return $this->getService( 'NameTableStoreFactory' );
        }
 
        /**
index 0914a9b..6bd4471 100644 (file)
@@ -32,7 +32,7 @@ use Wikimedia\Rdbms\IDatabase;
  */
 class MergeHistory {
 
-       /** @const int Maximum number of revisions that can be merged at once */
+       /** Maximum number of revisions that can be merged at once */
        const REVISION_LIMIT = 5000;
 
        /** @var Title Page from which history will be merged */
index b0000ab..859593b 100644 (file)
@@ -2527,6 +2527,37 @@ class OutputPage extends ContextSource {
                return $config->get( 'OriginTrials' );
        }
 
+       private function getReportTo() {
+               $config = $this->getConfig();
+
+               $expiry = $config->get( 'ReportToExpiry' );
+
+               if ( !$expiry ) {
+                       return false;
+               }
+
+               $endpoints = $config->get( 'ReportToEndpoints' );
+
+               if ( !$endpoints ) {
+                       return false;
+               }
+
+               $output = [ 'max_age' => $expiry, 'endpoints' => [] ];
+
+               foreach ( $endpoints as $endpoint ) {
+                       $output['endpoints'][] = [ 'url' => $endpoint ];
+               }
+
+               return json_encode( $output, JSON_UNESCAPED_SLASHES );
+       }
+
+       private function getFeaturePolicyReportOnly() {
+               $config = $this->getConfig();
+
+               $features = $config->get( 'FeaturePolicyReportOnly' );
+               return implode( ';', $features );
+       }
+
        /**
         * Send cache control HTTP headers
         */
@@ -2678,10 +2709,6 @@ class OutputPage extends ContextSource {
                $response->header( 'Content-language: ' .
                        MediaWikiServices::getInstance()->getContentLanguage()->getHtmlCode() );
 
-               if ( !$this->mArticleBodyOnly ) {
-                       $sk = $this->getSkin();
-               }
-
                $linkHeader = $this->getLinkHeader();
                if ( $linkHeader ) {
                        $response->header( $linkHeader );
@@ -2698,6 +2725,16 @@ class OutputPage extends ContextSource {
                        $response->header( "Origin-Trial: $originTrial", false );
                }
 
+               $reportTo = $this->getReportTo();
+               if ( $reportTo ) {
+                       $response->header( "Report-To: $reportTo" );
+               }
+
+               $featurePolicyReportOnly = $this->getFeaturePolicyReportOnly();
+               if ( $featurePolicyReportOnly ) {
+                       $response->header( "Feature-Policy-Report-Only: $featurePolicyReportOnly" );
+               }
+
                ContentSecurityPolicy::sendHeaders( $this );
 
                if ( $this->mArticleBodyOnly ) {
index 01d5f9d..2d9216d 100644 (file)
@@ -36,7 +36,7 @@
  */
 class PHPVersionCheck {
        /* @var string The number of the MediaWiki version used. */
-       var $mwVersion = '1.33';
+       var $mwVersion = '1.34';
 
        /* @var array A mapping of PHP functions to PHP extensions. */
        var $functionsExtensionsMapping = array(
@@ -112,7 +112,7 @@ class PHPVersionCheck {
                        'vendor' => 'the PHP Group',
                        'upstreamSupported' => '5.6.0',
                        'minSupported' => '7.0.13',
-                       'upgradeURL' => 'https://secure.php.net/downloads.php',
+                       'upgradeURL' => 'https://www.php.net/downloads.php',
                );
        }
 
@@ -206,7 +206,7 @@ HTML;
 
                        $missingExtText = '';
                        $missingExtHtml = '';
-                       $baseUrl = 'https://secure.php.net';
+                       $baseUrl = 'https://www.php.net';
                        foreach ( $missingExtensions as $ext ) {
                                $missingExtText .= " * $ext <$baseUrl/$ext>\n";
                                $missingExtHtml .= "<li><b>$ext</b> "
index 1d94e0e..549b7ba 100644 (file)
@@ -27,7 +27,7 @@ use MediaWiki\Linker\LinkTarget;
 use MediaWiki\Special\SpecialPageFactory;
 use MessageSpecifier;
 use MWException;
-use MWNamespace;
+use NamespaceInfo;
 use RequestContext;
 use SpecialPage;
 use Title;
@@ -78,13 +78,15 @@ class PermissionManager {
                $whitelistRead,
                $whitelistReadRegexp,
                $emailConfirmToEdit,
-               $blockDisablesLogin
+               $blockDisablesLogin,
+               NamespaceInfo $nsInfo
        ) {
                $this->specialPageFactory = $specialPageFactory;
                $this->whitelistRead = $whitelistRead;
                $this->whitelistReadRegexp = $whitelistReadRegexp;
                $this->emailConfirmToEdit = $emailConfirmToEdit;
                $this->blockDisablesLogin = $blockDisablesLogin;
+               $this->nsInfo = $nsInfo;
        }
 
        /**
@@ -597,13 +599,15 @@ class PermissionManager {
                        return $errors;
                }
 
-               $isSubPage = MWNamespace::hasSubpages( $page->getNamespace() ) ?
+               $isSubPage = $this->nsInfo->hasSubpages( $page->getNamespace() ) ?
                        strpos( $page->getText(), '/' ) !== false : false;
 
                if ( $action == 'create' ) {
                        if (
-                               ( MWNamespace::isTalk( $page->getNamespace() ) && !$user->isAllowed( 'createtalk' ) ) ||
-                               ( !MWNamespace::isTalk( $page->getNamespace() ) && !$user->isAllowed( 'createpage' ) )
+                               ( $this->nsInfo->isTalk( $page->getNamespace() ) &&
+                                       !$user->isAllowed( 'createtalk' ) ) ||
+                               ( !$this->nsInfo->isTalk( $page->getNamespace() ) &&
+                                       !$user->isAllowed( 'createpage' ) )
                        ) {
                                $errors[] = $user->isAnon() ? [ 'nocreatetext' ] : [ 'nocreate-loggedin' ];
                        }
@@ -817,7 +821,7 @@ class PermissionManager {
                        }
                } elseif ( $action == 'move' ) {
                        // Check for immobile pages
-                       if ( !MWNamespace::isMovable( $page->getNamespace() ) ) {
+                       if ( !$this->nsInfo->isMovable( $page->getNamespace() ) ) {
                                // Specific message for this case
                                $errors[] = [ 'immobile-source-namespace', $page->getNsText() ];
                        } elseif ( !$page->isMovable() ) {
@@ -825,7 +829,7 @@ class PermissionManager {
                                $errors[] = [ 'immobile-source-page' ];
                        }
                } elseif ( $action == 'move-target' ) {
-                       if ( !MWNamespace::isMovable( $page->getNamespace() ) ) {
+                       if ( !$this->nsInfo->isMovable( $page->getNamespace() ) ) {
                                $errors[] = [ 'immobile-target-namespace', $page->getNsText() ];
                        } elseif ( !$page->isMovable() ) {
                                $errors[] = [ 'immobile-target-page' ];
diff --git a/includes/Preferences.php b/includes/Preferences.php
deleted file mode 100644 (file)
index 70f7060..0000000
+++ /dev/null
@@ -1,273 +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
- */
-
-use MediaWiki\Auth\AuthManager;
-use MediaWiki\MediaWikiServices;
-use MediaWiki\Preferences\DefaultPreferencesFactory;
-
-/**
- * This class has been replaced by the PreferencesFactory service.
- *
- * @deprecated since 1.31 use the PreferencesFactory service instead.
- */
-class Preferences {
-
-       /**
-        * A shim to maintain backwards-compatibility of this class, basically replicating the
-        * default behaviour of the PreferencesFactory service but not permitting overriding.
-        * @return DefaultPreferencesFactory
-        */
-       protected static function getDefaultPreferencesFactory() {
-               $services = MediaWikiServices::getInstance();
-               $authManager = AuthManager::singleton();
-               $linkRenderer = $services->getLinkRenderer();
-               $config = $services->getMainConfig();
-               $preferencesFactory = new DefaultPreferencesFactory(
-                       $config, $services->getContentLanguage(), $authManager,
-                       $linkRenderer
-               );
-               return $preferencesFactory;
-       }
-
-       /**
-        * @throws MWException
-        * @param User $user
-        * @param IContextSource $context
-        * @return array|null
-        */
-       public static function getPreferences( $user, IContextSource $context ) {
-               wfDeprecated( __METHOD__, '1.31' );
-               $preferencesFactory = self::getDefaultPreferencesFactory();
-               return $preferencesFactory->getFormDescriptor( $user, $context );
-       }
-
-       /**
-        * Loads existing values for a given array of preferences
-        * @throws MWException
-        * @param User $user
-        * @param IContextSource $context
-        * @param array &$defaultPreferences Array to load values for
-        * @return array|null
-        */
-       public static function loadPreferenceValues( $user, $context, &$defaultPreferences ) {
-               throw new Exception( __METHOD__ . '() is deprecated and does nothing' );
-       }
-
-       /**
-        * Pull option from a user account. Handles stuff like array-type preferences.
-        *
-        * @param string $name
-        * @param array $info
-        * @param User $user
-        * @return array|string
-        */
-       public static function getOptionFromUser( $name, $info, $user ) {
-               throw new Exception( __METHOD__ . '() is deprecated and does nothing' );
-       }
-
-       /**
-        * @param User $user
-        * @param IContextSource $context
-        * @param array &$defaultPreferences
-        * @return void
-        */
-       public static function profilePreferences(
-               $user, IContextSource $context, &$defaultPreferences
-       ) {
-               wfDeprecated( __METHOD__, '1.31' );
-               $defaultPreferences = self::getPreferences( $user, $context );
-       }
-
-       /**
-        * @param User $user
-        * @param IContextSource $context
-        * @param array &$defaultPreferences
-        * @return void
-        */
-       public static function skinPreferences( $user, IContextSource $context, &$defaultPreferences ) {
-               wfDeprecated( __METHOD__, '1.31' );
-               $defaultPreferences = self::getPreferences( $user, $context );
-       }
-
-       /**
-        * @param User $user
-        * @param IContextSource $context
-        * @param array &$defaultPreferences
-        */
-       public static function filesPreferences(
-               $user, IContextSource $context, &$defaultPreferences
-       ) {
-               wfDeprecated( __METHOD__, '1.31' );
-               $defaultPreferences = self::getPreferences( $user, $context );
-       }
-
-       /**
-        * @param User $user
-        * @param IContextSource $context
-        * @param array &$defaultPreferences
-        * @return void
-        */
-       public static function datetimePreferences(
-               $user, IContextSource $context, &$defaultPreferences
-       ) {
-               wfDeprecated( __METHOD__, '1.31' );
-               $defaultPreferences = self::getPreferences( $user, $context );
-       }
-
-       /**
-        * @param User $user
-        * @param IContextSource $context
-        * @param array &$defaultPreferences
-        */
-       public static function renderingPreferences(
-               $user, IContextSource $context, &$defaultPreferences
-       ) {
-               wfDeprecated( __METHOD__, '1.31' );
-               $defaultPreferences = self::getPreferences( $user, $context );
-       }
-
-       /**
-        * @param User $user
-        * @param IContextSource $context
-        * @param array &$defaultPreferences
-        */
-       public static function editingPreferences(
-               $user, IContextSource $context, &$defaultPreferences
-       ) {
-               wfDeprecated( __METHOD__, '1.31' );
-               $defaultPreferences = self::getPreferences( $user, $context );
-       }
-
-       /**
-        * @param User $user
-        * @param IContextSource $context
-        * @param array &$defaultPreferences
-        */
-       public static function rcPreferences( $user, IContextSource $context, &$defaultPreferences ) {
-               wfDeprecated( __METHOD__, '1.31' );
-               $defaultPreferences = self::getPreferences( $user, $context );
-       }
-
-       /**
-        * @param User $user
-        * @param IContextSource $context
-        * @param array &$defaultPreferences
-        */
-       public static function watchlistPreferences(
-               $user, IContextSource $context, &$defaultPreferences
-       ) {
-               wfDeprecated( __METHOD__, '1.31' );
-               $defaultPreferences = self::getPreferences( $user, $context );
-       }
-
-       /**
-        * @param User $user
-        * @param IContextSource $context
-        * @param array &$defaultPreferences
-        */
-       public static function searchPreferences(
-               $user, IContextSource $context, &$defaultPreferences
-       ) {
-               wfDeprecated( __METHOD__, '1.31' );
-               $defaultPreferences = self::getPreferences( $user, $context );
-       }
-
-       /**
-        * Dummy, kept for backwards-compatibility.
-        * @param User $user
-        * @param IContextSource $context
-        * @param array &$defaultPreferences
-        */
-       public static function miscPreferences( $user, IContextSource $context, &$defaultPreferences ) {
-               wfDeprecated( __METHOD__, '1.31' );
-       }
-
-       /**
-        * @param User $user
-        * @param IContextSource $context
-        * @return array Text/links to display as key; $skinkey as value
-        */
-       public static function generateSkinOptions( $user, IContextSource $context ) {
-               wfDeprecated( __METHOD__, '1.31' );
-               return self::getPreferences( $user, $context );
-       }
-
-       /**
-        * @param IContextSource $context
-        * @return array
-        */
-       static function getDateOptions( IContextSource $context ) {
-               throw new Exception( __METHOD__ . '() is deprecated and does nothing' );
-       }
-
-       /**
-        * @param IContextSource $context
-        * @return array
-        */
-       public static function getImageSizes( IContextSource $context ) {
-               throw new Exception( __METHOD__ . '() is deprecated and does nothing' );
-       }
-
-       /**
-        * @param IContextSource $context
-        * @return array
-        */
-       public static function getThumbSizes( IContextSource $context ) {
-               throw new Exception( __METHOD__ . '() is deprecated and does nothing' );
-       }
-
-       /**
-        * @param string $signature
-        * @param array $alldata
-        * @param HTMLForm $form
-        * @return bool|string
-        */
-       public static function validateSignature( $signature, $alldata, $form ) {
-               throw new Exception( __METHOD__ . '() is deprecated and does nothing' );
-       }
-
-       /**
-        * @param string $signature
-        * @param array $alldata
-        * @param HTMLForm $form
-        * @return string
-        */
-       public static function cleanSignature( $signature, $alldata, $form ) {
-               throw new Exception( __METHOD__ . '() is deprecated and does nothing now' );
-       }
-
-       /**
-        * @param User $user
-        * @param IContextSource $context
-        * @param string $formClass
-        * @param array $remove Array of items to remove
-        * @return PreferencesFormLegacy|HTMLForm
-        */
-       public static function getFormObject(
-               $user,
-               IContextSource $context,
-               $formClass = PreferencesFormLegacy::class,
-               array $remove = []
-       ) {
-               wfDeprecated( __METHOD__, '1.31' );
-               $preferencesFactory = self::getDefaultPreferencesFactory();
-               return $preferencesFactory->getForm( $user, $context, $formClass, $remove );
-       }
-}
index e767359..1a09290 100644 (file)
@@ -58,11 +58,4 @@ class ReadOnlyMode {
        public function setReason( $msg ) {
                $this->configuredReadOnly->setReason( $msg );
        }
-
-       /**
-        * Clear the cache of the read only file
-        */
-       public function clearCache() {
-               $this->configuredReadOnly->clearCache();
-       }
 }
index 094105a..c4a0054 100644 (file)
@@ -380,9 +380,9 @@ class RenderedRevision implements SlotRenderingProvider {
                $method = __METHOD__;
 
                if ( $out->getFlag( 'vary-revision' ) ) {
-                       // XXX: Would be just keep the output if the speculative revision ID was correct,
-                       // but that can go wrong for some edge cases, like {{PAGEID}} during page creation.
-                       // For that specific case, it would perhaps nice to have a vary-page flag.
+                       // If {{PAGEID}} resolved to 0 or {{REVISIONTIMESTAMP}} used the current
+                       // timestamp rather than that of an actual revision, then those words need
+                       // to resolve to the actual page ID or revision timestamp, respectively.
                        $this->saveParseLogger->info(
                                "$method: Prepared output has vary-revision...\n"
                        );
@@ -395,6 +395,14 @@ class RenderedRevision implements SlotRenderingProvider {
                                "$method: Prepared output has vary-revision-id with wrong ID...\n"
                        );
                        return true;
+               } elseif ( $out->getFlag( 'vary-revision-exists' ) ) {
+                       // If {{REVISIONID}} resolved to '', it now needs to resolve to '-'.
+                       // Note that edit stashing always uses '-', which can be used for both
+                       // edit filter checks and canonical parser cache.
+                       $this->saveParseLogger->info(
+                               "$method: Prepared output has vary-revision-exists...\n"
+                       );
+                       return true;
                } else {
                        // NOTE: In the original fix for T135261, the output was discarded if 'vary-user' was
                        // set for a null-edit. The reason was that the original rendering in that case was
index e141087..d39a0c7 100644 (file)
@@ -134,8 +134,8 @@ return [
                return new CryptHKDF( $secret, $config->get( 'HKDFAlgorithm' ), $cache, $context );
        },
 
-       'CryptRand' => function () : CryptRand {
-               return new CryptRand();
+       'DateFormatterFactory' => function () : DateFormatterFactory {
+               return new DateFormatterFactory;
        },
 
        'DBLoadBalancer' => function ( MediaWikiServices $services ) : Wikimedia\Rdbms\LoadBalancer {
@@ -330,6 +330,10 @@ return [
                return new MimeAnalyzer( $params );
        },
 
+       'NamespaceInfo' => function ( MediaWikiServices $services ) : NamespaceInfo {
+               return new NamespaceInfo( $services->getMainConfig() );
+       },
+
        'NameTableStoreFactory' => function ( MediaWikiServices $services ) : NameTableStoreFactory {
                return new NameTableStoreFactory(
                        $services->getDBLoadBalancerFactory(),
@@ -338,10 +342,6 @@ return [
                );
        },
 
-       'NamespaceInfo' => function ( MediaWikiServices $services ) : NamespaceInfo {
-               return new NamespaceInfo( $services->getMainConfig() );
-       },
-
        'OldRevisionImporter' => function ( MediaWikiServices $services ) : OldRevisionImporter {
                return new ImportableOldRevisionImporter(
                        true,
@@ -373,7 +373,8 @@ return [
                        wfUrlProtocols(),
                        $services->getSpecialPageFactory(),
                        $services->getMainConfig(),
-                       $services->getLinkRendererFactory()
+                       $services->getLinkRendererFactory(),
+                       $services->getNamespaceInfo()
                );
        },
 
@@ -402,7 +403,9 @@ return [
                        $config->get( 'WhitelistRead' ),
                        $config->get( 'WhitelistReadRegexp' ),
                        $config->get( 'EmailConfirmToEdit' ),
-                       $config->get( 'BlockDisablesLogin' ) );
+                       $config->get( 'BlockDisablesLogin' ),
+                       $services->getNamespaceInfo()
+               );
        },
 
        'PreferencesFactory' => function ( MediaWikiServices $services ) : PreferencesFactory {
@@ -571,8 +574,13 @@ return [
        },
 
        'SpecialPageFactory' => function ( MediaWikiServices $services ) : SpecialPageFactory {
+               $config = $services->getMainConfig();
+               $options = [];
+               foreach ( SpecialPageFactory::$constructorOptions as $key ) {
+                       $options[$key] = $config->get( $key );
+               }
                return new SpecialPageFactory(
-                       $services->getMainConfig(),
+                       $options,
                        $services->getContentLanguage()
                );
        },
index b400797..cab98a7 100644 (file)
@@ -582,14 +582,14 @@ class SiteConfiguration {
         * which is not fun
         *
         * @param array $array1
+        * @param array ...$arrays
         *
         * @return array
         */
-       static function arrayMerge( $array1/* ... */ ) {
+       static function arrayMerge( array $array1, ...$arrays ) {
                $out = $array1;
-               $argsCount = func_num_args();
-               for ( $i = 1; $i < $argsCount; $i++ ) {
-                       foreach ( func_get_arg( $i ) as $key => $value ) {
+               foreach ( $arrays as $array ) {
+                       foreach ( $array as $key => $value ) {
                                if ( isset( $out[$key] ) && is_array( $out[$key] ) && is_array( $value ) ) {
                                        $out[$key] = self::arrayMerge( $out[$key], $value );
                                } elseif ( !isset( $out[$key] ) || !$out[$key] && !is_numeric( $key ) ) {
index d5c1656..3dbe0a8 100644 (file)
@@ -750,9 +750,6 @@ class DerivedPageDataUpdater implements IDBAccessObject {
 
                $parentRevision = $this->grabCurrentRevision();
 
-               $this->slotsOutput = [];
-               $this->canonicalParserOutput = null;
-
                // The edit may have already been prepared via api.php?action=stashedit
                $stashedEdit = false;
 
@@ -1242,7 +1239,7 @@ class DerivedPageDataUpdater implements IDBAccessObject {
                $preparedEdit = new PreparedEdit();
 
                $preparedEdit->popts = $this->getCanonicalParserOptions();
-               $preparedEdit->output = $this->getCanonicalParserOutput();
+               $preparedEdit->parserOutputCallback = [ $this, 'getCanonicalParserOutput' ];
                $preparedEdit->pstContent = $this->revision->getContent( SlotRecord::MAIN );
                $preparedEdit->newContent =
                        $slotsUpdate->isModifiedSlot( SlotRecord::MAIN )
@@ -1404,13 +1401,31 @@ class DerivedPageDataUpdater implements IDBAccessObject {
                $legacyUser = User::newFromIdentity( $this->user );
                $legacyRevision = new Revision( $this->revision );
 
-               $this->doParserCacheUpdate();
+               $userParserOptions = ParserOptions::newFromUser( $legacyUser );
+               // Decide whether to save the final canonical parser ouput based on the fact that
+               // users are typically redirected to viewing pages right after they edit those pages.
+               // Due to vary-revision-id, getting/saving that output here might require a reparse.
+               if ( $userParserOptions->matchesForCacheKey( $this->getCanonicalParserOptions() ) ) {
+                       // Whether getting the final output requires a reparse or not, the user will
+                       // need canonical output anyway, since that is what their parser options use.
+                       // A reparse now at least has the benefit of various warm process caches.
+                       $this->doParserCacheUpdate();
+               } else {
+                       // If the user does not have canonical parse options, then don't risk another parse
+                       // to make output they cannot use on the page refresh that typically occurs after
+                       // editing. Doing the parser output save post-send will still benefit *other* users.
+                       DeferredUpdates::addCallableUpdate( function () {
+                               $this->doParserCacheUpdate();
+                       } );
+               }
 
-               $this->doSecondaryDataUpdates( [
-                       // T52785 do not update any other pages on a null edit
-                       'recursive' => $this->options['changed'],
-                       'defer' => DeferredUpdates::POSTSEND,
-               ] );
+               // Defer the getCannonicalParserOutput() call triggered by getSecondaryDataUpdates()
+               DeferredUpdates::addCallableUpdate( function () {
+                       $this->doSecondaryDataUpdates( [
+                               // T52785 do not update any other pages on a null edit
+                               'recursive' => $this->options['changed']
+                       ] );
+               } );
 
                // TODO: MCR: check if *any* changed slot supports categories!
                if ( $this->rcWatchCategoryMembership
@@ -1430,8 +1445,11 @@ class DerivedPageDataUpdater implements IDBAccessObject {
                }
 
                // TODO: replace legacy hook! Use a listener on PageEventEmitter instead!
+               // @note: Extensions should *avoid* calling getCannonicalParserOutput() when using
+               // this hook whenever possible in order to avoid unnecessary additional parses.
                $editInfo = $this->getPreparedEdit();
-               Hooks::run( 'ArticleEditUpdates', [ &$wikiPage, &$editInfo, $this->options['changed'] ] );
+               Hooks::run( 'ArticleEditUpdates',
+                       [ &$wikiPage, &$editInfo, $this->options['changed'] ] );
 
                // TODO: replace legacy hook! Use a listener on PageEventEmitter instead!
                if ( Hooks::run( 'ArticleEditUpdatesDeleteFromRecentchanges', [ &$wikiPage ] ) ) {
index 9213076..1583dbf 100644 (file)
@@ -171,38 +171,3 @@ class StubObject {
                }
        }
 }
-
-/**
- * Stub object for the user language. Assigned to the $wgLang global.
- */
-class StubUserLang extends StubObject {
-
-       public function __construct() {
-               parent::__construct( 'wgLang' );
-       }
-
-       /**
-        * Call Language::findVariantLink after unstubbing $wgLang.
-        *
-        * This method is implemented with a full signature rather than relying on
-        * __call so that the pass-by-reference signature of the proxied method is
-        * honored.
-        *
-        * @param string &$link The name of the link
-        * @param Title &$nt The title object of the link
-        * @param bool $ignoreOtherCond To disable other conditions when
-        *   we need to transclude a template or update a category's link
-        */
-       public function findVariantLink( &$link, &$nt, $ignoreOtherCond = false ) {
-               global $wgLang;
-               $this->_unstub( 'findVariantLink', 3 );
-               $wgLang->findVariantLink( $link, $nt, $ignoreOtherCond );
-       }
-
-       /**
-        * @return Language
-        */
-       public function _newObject() {
-               return RequestContext::getMain()->getLanguage();
-       }
-}
diff --git a/includes/StubUserLang.php b/includes/StubUserLang.php
new file mode 100644 (file)
index 0000000..6f7bed6
--- /dev/null
@@ -0,0 +1,54 @@
+<?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
+ */
+
+/**
+ * Stub object for the user language. Assigned to the $wgLang global.
+ */
+class StubUserLang extends StubObject {
+
+       public function __construct() {
+               parent::__construct( 'wgLang' );
+       }
+
+       /**
+        * Call Language::findVariantLink after unstubbing $wgLang.
+        *
+        * This method is implemented with a full signature rather than relying on
+        * __call so that the pass-by-reference signature of the proxied method is
+        * honored.
+        *
+        * @param string &$link The name of the link
+        * @param Title &$nt The title object of the link
+        * @param bool $ignoreOtherCond To disable other conditions when
+        *   we need to transclude a template or update a category's link
+        */
+       public function findVariantLink( &$link, &$nt, $ignoreOtherCond = false ) {
+               global $wgLang;
+               $this->_unstub( 'findVariantLink', 3 );
+               $wgLang->findVariantLink( $link, $nt, $ignoreOtherCond );
+       }
+
+       /**
+        * @return Language
+        */
+       public function _newObject() {
+               return RequestContext::getMain()->getLanguage();
+       }
+}
index 11a8e85..fd856be 100644 (file)
@@ -62,9 +62,9 @@ class TemplateParser {
         */
        public function enableRecursivePartials( $enable ) {
                if ( $enable ) {
-                       $this->compileFlags = $this->compileFlags | LightnCandy::FLAG_RUNTIMEPARTIAL;
+                       $this->compileFlags |= LightnCandy::FLAG_RUNTIMEPARTIAL;
                } else {
-                       $this->compileFlags = $this->compileFlags & ~LightnCandy::FLAG_RUNTIMEPARTIAL;
+                       $this->compileFlags &= ~LightnCandy::FLAG_RUNTIMEPARTIAL;
                }
        }
 
index 3891c82..12ab532 100644 (file)
@@ -623,15 +623,33 @@ class Title implements LinkTarget, IDBAccessObject {
        /**
         * Create a new Title for the Main Page
         *
+        * This uses the 'mainpage' interface message, which could be specified in
+        * `$wgForceUIMsgAsContentMsg`. If that is the case, then calling this method
+        * will use the user language, which would involve initialising the session
+        * via `RequestContext::getMain()->getLanguage()`. For session-less endpoints,
+        * be sure to pass in a MessageLocalizer (such as your own RequestContext,
+        * or ResourceloaderContext) to prevent an error.
+        *
         * @note The Title instance returned by this method is not guaranteed to be a fresh instance.
         * It may instead be a cached instance created previously, with references to it remaining
         * elsewhere.
         *
-        * @return Title The new object
+        * @param MessageLocalizer|null $localizer An optional context to use (since 1.34)
+        * @return Title
         */
-       public static function newMainPage() {
-               $title = self::newFromText( wfMessage( 'mainpage' )->inContentLanguage()->text() );
-               // Don't give fatal errors if the message is broken
+       public static function newMainPage( MessageLocalizer $localizer = null ) {
+               if ( $localizer ) {
+                       $msg = $localizer->msg( 'mainpage' );
+               } else {
+                       $msg = wfMessage( 'mainpage' );
+               }
+
+               $title = self::newFromText( $msg->inContentLanguage()->text() );
+
+               // Every page renders at least one link to the Main Page (e.g. sidebar).
+               // If the localised value is invalid, don't produce fatal errors that
+               // would make the wiki inaccessible (and hard to fix the invalid message).
+               // Gracefully fallback...
                if ( !$title ) {
                        $title = self::newFromText( 'Main Page' );
                }
index d5b081e..2a03d2d 100644 (file)
@@ -382,7 +382,7 @@ class WebRequest {
         */
        private function getGPCVal( $arr, $name, $default ) {
                # PHP is so nice to not touch input data, except sometimes:
-               # https://secure.php.net/variables.external#language.variables.external.dot-in-names
+               # 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] ) ) {
index 34c6062..c2d08b8 100644 (file)
@@ -103,7 +103,7 @@ class WebRequestUpload {
 
        /**
         * Return the upload error. See link for explanation
-        * https://secure.php.net/manual/en/features.file-upload.errors.php
+        * https://www.php.net/manual/en/features.file-upload.errors.php
         *
         * @return int One of the UPLOAD_ constants, 0 if non-existent
         */
index bc1d351..fc42be4 100644 (file)
@@ -181,10 +181,9 @@ class HistoryAction extends FormlessAction {
                }
 
                // Handle atom/RSS feeds.
-               $feedType = $request->getVal( 'feed' );
-               if ( $feedType ) {
+               $feedType = $request->getRawVal( 'feed' );
+               if ( $feedType !== null ) {
                        $this->feed( $feedType );
-
                        return;
                }
 
index 4e9d8e9..c9c1b51 100644 (file)
@@ -602,6 +602,15 @@ class HistoryPager extends ReverseChronologicalPager {
                }
        }
 
+       /**
+        * @inheritDoc
+        */
+       function getDefaultQuery() {
+               parent::getDefaultQuery();
+               unset( $this->mDefaultQuery['date-range-to'] );
+               return $this->mDefaultQuery;
+       }
+
        /**
         * This is called if a write operation is possible from the generated HTML
         * @param bool $enable
index e37b4d4..2f66277 100644 (file)
@@ -345,10 +345,10 @@ class ApiAuthManagerHelper {
        /**
         * Fetch the standard parameters this helper recognizes
         * @param string $action AuthManager action
-        * @param string $param,... Parameters to use
+        * @param string ...$wantedParams Parameters to use
         * @return array
         */
-       public static function getStandardParams( $action, $param /* ... */ ) {
+       public static function getStandardParams( $action, ...$wantedParams ) {
                $params = [
                        'requests' => [
                                ApiBase::PARAM_TYPE => 'string',
@@ -384,8 +384,6 @@ class ApiAuthManagerHelper {
                ];
 
                $ret = [];
-               $wantedParams = func_get_args();
-               array_shift( $wantedParams );
                foreach ( $wantedParams as $name ) {
                        if ( isset( $params[$name] ) ) {
                                $ret[$name] = $params[$name];
index e5dba8f..678b97b 100644 (file)
@@ -101,7 +101,7 @@ class ApiFeedRecentChanges extends ApiBase {
                                $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $this->params['target'] ) ] );
                        }
 
-                       $feed = new ChangesFeed( $feedFormat, false );
+                       $feed = new ChangesFeed( $feedFormat );
                        $feedObj = $feed->getFeedObject(
                                $this->msg( 'recentchangeslinked-title', $title->getPrefixedText() )
                                        ->inContentLanguage()->text(),
@@ -109,7 +109,7 @@ class ApiFeedRecentChanges extends ApiBase {
                                SpecialPage::getTitleFor( 'Recentchangeslinked' )->getFullURL()
                        );
                } else {
-                       $feed = new ChangesFeed( $feedFormat, 'rcfeed' );
+                       $feed = new ChangesFeed( $feedFormat );
                        $feedObj = $feed->getFeedObject(
                                $this->msg( 'recentchanges' )->inContentLanguage()->text(),
                                $this->msg( 'recentchanges-feed-description' )->inContentLanguage()->text(),
diff --git a/includes/api/ApiFormatXmlRsd.php b/includes/api/ApiFormatXmlRsd.php
new file mode 100644 (file)
index 0000000..6b892fa
--- /dev/null
@@ -0,0 +1,38 @@
+<?php
+
+/**
+ * Copyright © 2010 Bryan Tong Minh and Brion Vibber
+ *
+ * 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
+ */
+
+class ApiFormatXmlRsd extends ApiFormatXml {
+       public function __construct( ApiMain $main, $format ) {
+               parent::__construct( $main, $format );
+               $this->setRootElement( 'rsd' );
+       }
+
+       public function getMimeType() {
+               return 'application/rsd+xml';
+       }
+
+       public static function recXmlPrint( $name, $value, $indent, $attributes = [] ) {
+               unset( $attributes['_idx'] );
+               return parent::recXmlPrint( $name, $value, $indent, $attributes );
+       }
+}
index 596ab75..b36045e 100644 (file)
@@ -181,42 +181,3 @@ class ApiImport extends ApiBase {
                return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Import';
        }
 }
-
-/**
- * Import reporter for the API
- * @ingroup API
- */
-class ApiImportReporter extends ImportReporter {
-       private $mResultArr = [];
-
-       /**
-        * @param Title $title
-        * @param Title $origTitle
-        * @param int $revisionCount
-        * @param int $successCount
-        * @param array $pageInfo
-        * @return void
-        */
-       public function reportPage( $title, $origTitle, $revisionCount, $successCount, $pageInfo ) {
-               // Add a result entry
-               $r = [];
-
-               if ( $title === null ) {
-                       # Invalid or non-importable title
-                       $r['title'] = $pageInfo['title'];
-                       $r['invalid'] = true;
-               } else {
-                       ApiQueryBase::addTitleInfo( $r, $title );
-                       $r['revisions'] = (int)$successCount;
-               }
-
-               $this->mResultArr[] = $r;
-
-               // Piggyback on the parent to do the logging
-               parent::reportPage( $title, $origTitle, $revisionCount, $successCount, $pageInfo );
-       }
-
-       public function getData() {
-               return $this->mResultArr;
-       }
-}
diff --git a/includes/api/ApiImportReporter.php b/includes/api/ApiImportReporter.php
new file mode 100644 (file)
index 0000000..21d9d23
--- /dev/null
@@ -0,0 +1,60 @@
+<?php
+/**
+ * Copyright © 2009 Roan Kattouw "<Firstname>.<Lastname>@gmail.com"
+ *
+ * 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
+ */
+
+/**
+ * Import reporter for the API
+ * @ingroup API
+ */
+class ApiImportReporter extends ImportReporter {
+       private $mResultArr = [];
+
+       /**
+        * @param Title $title
+        * @param Title $origTitle
+        * @param int $revisionCount
+        * @param int $successCount
+        * @param array $pageInfo
+        * @return void
+        */
+       public function reportPage( $title, $origTitle, $revisionCount, $successCount, $pageInfo ) {
+               // Add a result entry
+               $r = [];
+
+               if ( $title === null ) {
+                       # Invalid or non-importable title
+                       $r['title'] = $pageInfo['title'];
+                       $r['invalid'] = true;
+               } else {
+                       ApiQueryBase::addTitleInfo( $r, $title );
+                       $r['revisions'] = (int)$successCount;
+               }
+
+               $this->mResultArr[] = $r;
+
+               // Piggyback on the parent to do the logging
+               parent::reportPage( $title, $origTitle, $revisionCount, $successCount, $pageInfo );
+       }
+
+       public function getData() {
+               return $this->mResultArr;
+       }
+}
index 416fc7f..8e2837b 100644 (file)
@@ -379,41 +379,3 @@ class ApiOpenSearch extends ApiBase {
                }
        }
 }
-
-/**
- * @ingroup API
- */
-class ApiOpenSearchFormatJson extends ApiFormatJson {
-       private $warningsAsError = false;
-
-       public function __construct( ApiMain $main, $fm, $warningsAsError ) {
-               parent::__construct( $main, "json$fm" );
-               $this->warningsAsError = $warningsAsError;
-       }
-
-       public function execute() {
-               $result = $this->getResult();
-               if ( !$result->getResultData( 'error' ) && !$result->getResultData( 'errors' ) ) {
-                       // Ignore warnings or treat as errors, as requested
-                       $warnings = $result->removeValue( 'warnings', null );
-                       if ( $this->warningsAsError && $warnings ) {
-                               $this->dieWithError(
-                                       'apierror-opensearch-json-warnings',
-                                       'warnings',
-                                       [ 'warnings' => $warnings ]
-                               );
-                       }
-
-                       // Ignore any other unexpected keys (e.g. from $wgDebugToolbar)
-                       $remove = array_keys( array_diff_key(
-                               $result->getResultData(),
-                               [ 0 => 'search', 1 => 'terms', 2 => 'descriptions', 3 => 'urls' ]
-                       ) );
-                       foreach ( $remove as $key ) {
-                               $result->removeValue( $key, null );
-                       }
-               }
-
-               parent::execute();
-       }
-}
diff --git a/includes/api/ApiOpenSearchFormatJson.php b/includes/api/ApiOpenSearchFormatJson.php
new file mode 100644 (file)
index 0000000..b1903f2
--- /dev/null
@@ -0,0 +1,61 @@
+<?php
+/**
+ * Copyright © 2006 Yuri Astrakhan "<Firstname><Lastname>@gmail.com"
+ * Copyright © 2008 Brion Vibber <brion@wikimedia.org>
+ * Copyright © 2014 Wikimedia Foundation and contributors
+ *
+ * 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 API
+ */
+class ApiOpenSearchFormatJson extends ApiFormatJson {
+       private $warningsAsError = false;
+
+       public function __construct( ApiMain $main, $fm, $warningsAsError ) {
+               parent::__construct( $main, "json$fm" );
+               $this->warningsAsError = $warningsAsError;
+       }
+
+       public function execute() {
+               $result = $this->getResult();
+               if ( !$result->getResultData( 'error' ) && !$result->getResultData( 'errors' ) ) {
+                       // Ignore warnings or treat as errors, as requested
+                       $warnings = $result->removeValue( 'warnings', null );
+                       if ( $this->warningsAsError && $warnings ) {
+                               $this->dieWithError(
+                                       'apierror-opensearch-json-warnings',
+                                       'warnings',
+                                       [ 'warnings' => $warnings ]
+                               );
+                       }
+
+                       // Ignore any other unexpected keys (e.g. from $wgDebugToolbar)
+                       $remove = array_keys( array_diff_key(
+                               $result->getResultData(),
+                               [ 0 => 'search', 1 => 'terms', 2 => 'descriptions', 3 => 'urls' ]
+                       ) );
+                       foreach ( $remove as $key ) {
+                               $result->removeValue( $key, null );
+                       }
+               }
+
+               parent::execute();
+       }
+}
index 71bab36..00ab77b 100644 (file)
@@ -149,19 +149,3 @@ class ApiRsd extends ApiBase {
                return $outputData;
        }
 }
-
-class ApiFormatXmlRsd extends ApiFormatXml {
-       public function __construct( ApiMain $main, $format ) {
-               parent::__construct( $main, $format );
-               $this->setRootElement( 'rsd' );
-       }
-
-       public function getMimeType() {
-               return 'application/rsd+xml';
-       }
-
-       public static function recXmlPrint( $name, $value, $indent, $attributes = [] ) {
-               unset( $attributes['_idx'] );
-               return parent::recXmlPrint( $name, $value, $indent, $attributes );
-       }
-}
index 3b13904..5992590 100644 (file)
        "apihelp-query+userinfo-paramvalue-prop-registrationdate": "הוספת תאריך הרישום של המשתמש.",
        "apihelp-query+userinfo-paramvalue-prop-unreadcount": "הוספת מניין הדפים שלא נקראו ברשימת המעקב של המשתמש (לכל היותר $1; מחזיר <samp>$2</samp> אם יש יותר).",
        "apihelp-query+userinfo-paramvalue-prop-centralids": "הוספת המזהה המרכזי ומצב השיוך למשתמש.",
+       "apihelp-query+userinfo-paramvalue-prop-latestcontrib": "הוספת התאריך של התרומה האחרונה של המשתמש.",
        "apihelp-query+userinfo-param-attachedwiki": "עם <kbd>$1prop=centralids</kbd>, לציין האם המשתמש משויך לוויקי עם המזהה הזה.",
        "apihelp-query+userinfo-example-simple": "קבלת מידע על המשתמש הנוכחי.",
        "apihelp-query+userinfo-example-data": "קבלת מידע נוסף על המשתמש הנוכחי.",
index 9e28c39..6ce892b 100644 (file)
@@ -23,7 +23,7 @@
        "apihelp-main-param-smaxage": "<code>s-maxage</code> HTTP キャッシュ コントロール ヘッダー に、この秒数を設定します。エラーがキャッシュされることはありません。",
        "apihelp-main-param-maxage": "<code>max-age</code> HTTP キャッシュ コントロール ヘッダー に、この秒数を設定します。エラーがキャッシュされることはありません。",
        "apihelp-main-param-assert": "<kbd>user</kbd> を設定した場合は利用者がログイン済みかどうかを、<kbd>bot</kbd> を指定した場合はボット権限があるかどうかを、それぞれ検証します。",
-       "apihelp-main-param-assertuser": "現在のユーザーが指定されたユーザーであることを確認します。",
+       "apihelp-main-param-assertuser": "現在の利用者が指定された利用者であることを確認します。",
        "apihelp-main-param-requestid": "任意の値を指定でき、その値が結果に含められます。リクエストを識別するために使用できます。",
        "apihelp-main-param-servedby": "リクエストを処理したホスト名を結果に含めます。",
        "apihelp-main-param-curtimestamp": "現在のタイムスタンプを結果に含めます。",
        "apihelp-import-extended-description": "<var>xml</var> パラメーターでファイルを送信する場合、ファイルのアップロードとしてHTTP POSTされなければならない (例えば、multipart/form-dataを使用する) 点に注意してください。",
        "apihelp-import-param-summary": "記録されるページ取り込みの要約。",
        "apihelp-import-param-xml": "XMLファイルをアップロード",
-       "apihelp-import-param-assignknownusers": "指定されたユーザーがこのウィキに存在する場合そのユーザーに編集を割り当てる",
+       "apihelp-import-param-assignknownusers": "指定された利用者がこのウィキに存在する場合その利用者に編集を割り当てる。",
        "apihelp-import-param-interwikisource": "ウィキ間の取り込みの場合: 取り込み元のウィキ。",
        "apihelp-import-param-interwikipage": "ウィキ間の取り込みの場合: 取り込むページ。",
        "apihelp-import-param-fullhistory": "ウィキ間の取り込みの場合: 現在の版のみではなく完全な履歴を取り込む。",
        "apierror-botsnotsupported": "この API インターフェースはボットをサポートしていません。",
        "apierror-emptypage": "内容がないページの新規作成は許可されていません。",
        "apierror-filedoesnotexist": "ファイルが存在しません。",
-       "apierror-invaliduser": "無効なユーザー名「$1」。",
+       "apierror-invaliduser": "無効な利用者名「$1」。",
        "apierror-missingparam": "パラメーター <var>$1</var> を設定してください。",
        "apierror-mustbeloggedin": "$1にログインしている必要があります。",
        "apierror-noimageredirect": "画像のリダイレクトを作成する権限がありません。",
index 225db87..09b92d4 100644 (file)
        "apihelp-json-summary": "使用 JSON 格式輸出資料。",
        "apihelp-json-param-callback": "若有指定,將輸出包在指定的函式呼叫。出於安全考量,會限制所有使用者特定資料。",
        "apihelp-json-param-utf8": "若有指定的話,將多數(並非全部)非 ASCII 字元編碼成 UTF-8,而不是以十六進位轉義序列來取代掉。預設是當 <var>formatversion</var> 不是 <kbd>1</kbd> 時。",
-       "apihelp-json-param-ascii": "若有指定,編碼所有使用十六進位跳脫序列的非 ASCII。預設當 <var>formatversion</var> 為 <kbd>1</kbd> 時。",
+       "apihelp-json-param-ascii": "若有指定,編碼所有使用十六進位跳脫序列的非 ASCII 字碼。預設當 <var>formatversion</var> 為 <kbd>1</kbd> 時。",
        "apihelp-json-param-formatversion": "輸出格式:\n;1:向下相容格式(XML 式布林值,用於內容節點的 <samp>*</samp> 鍵、其它)。\n;2:現代格式。\n;latest:使用最新格式(目前為 <kbd>2</kbd>)可能會不帶警告作更改。",
        "apihelp-jsonfm-summary": "使用 JSON 格式輸出資料 (使用 HTML 格式顯示)。",
        "apihelp-none-summary": "不輸出。",
index 7970266..a010e83 100644 (file)
@@ -25,12 +25,14 @@ namespace MediaWiki\Block\Restriction;
 abstract class AbstractRestriction implements Restriction {
 
        /**
-        * @var string
+        * String constant identifying the type of restriction. Expected to be overriden in subclasses
+        * with a non-empty string value.
         */
        const TYPE = '';
 
        /**
-        * @var int
+        * Numeric type identifier. Expected to be overriden in subclasses with a non-zero integer
+        * number. Must not exceed 127 to fit into a TINYINT database field.
         */
        const TYPE_ID = 0;
 
index fdf223a..d717fe7 100644 (file)
@@ -61,7 +61,7 @@ interface Restriction {
         * Gets the id of the type of restriction. This id is used in the database.
         *
         * @since 1.33
-        * @return string
+        * @return int
         */
        public static function getTypeId();
 
diff --git a/includes/changes/AtomFeed.php b/includes/changes/AtomFeed.php
new file mode 100644 (file)
index 0000000..a4ce0c1
--- /dev/null
@@ -0,0 +1,111 @@
+<?php
+/**
+ * Copyright © 2004 Brion Vibber <brion@pobox.com>
+ * https://www.mediawiki.org/
+ *
+ * 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
+ */
+
+/**
+ * Generate an Atom feed.
+ *
+ * @ingroup Feed
+ */
+class AtomFeed extends ChannelFeed {
+       /**
+        * Format a date given timestamp, if one is given.
+        *
+        * @param string|int|null $timestamp
+        * @return string|null
+        */
+       function formatTime( $timestamp ) {
+               if ( $timestamp ) {
+                       // need to use RFC 822 time format at least for rss2.0
+                       return gmdate( 'Y-m-d\TH:i:s', wfTimestamp( TS_UNIX, $timestamp ) );
+               }
+       }
+
+       /**
+        * Outputs a basic header for Atom 1.0 feeds.
+        */
+       function outHeader() {
+               global $wgVersion;
+               $this->outXmlHeader();
+               // Manually escaping rather than letting Mustache do it because Mustache
+               // uses htmlentities, which does not work with XML
+               $templateParams = [
+                       'language' => $this->xmlEncode( $this->getLanguage() ),
+                       'feedID' => $this->getFeedId(),
+                       'title' => $this->getTitle(),
+                       'url' => $this->xmlEncode( wfExpandUrl( $this->getUrlUnescaped(), PROTO_CURRENT ) ),
+                       'selfUrl' => $this->getSelfUrl(),
+                       'timestamp' => $this->xmlEncode( $this->formatTime( wfTimestampNow() ) ),
+                       'description' => $this->getDescription(),
+                       'version' => $this->xmlEncode( $wgVersion ),
+               ];
+               print $this->templateParser->processTemplate( 'AtomHeader', $templateParams );
+       }
+
+       /**
+        * Atom 1.0 requires a unique, opaque IRI as a unique identifier
+        * for every feed we create. For now just use the URL, but who
+        * can tell if that's right? If we put options on the feed, do we
+        * have to change the id? Maybe? Maybe not.
+        *
+        * @return string
+        */
+       private function getFeedId() {
+               return $this->getSelfUrl();
+       }
+
+       /**
+        * Atom 1.0 requests a self-reference to the feed.
+        * @return string
+        */
+       private function getSelfUrl() {
+               global $wgRequest;
+               return htmlspecialchars( $wgRequest->getFullRequestURL() );
+       }
+
+       /**
+        * Output a given item.
+        * @param FeedItem $item
+        */
+       function outItem( $item ) {
+               global $wgMimeType;
+               // Manually escaping rather than letting Mustache do it because Mustache
+               // uses htmlentities, which does not work with XML
+               $templateParams = [
+                       "uniqueID" => $item->getUniqueID(),
+                       "title" => $item->getTitle(),
+                       "mimeType" => $this->xmlEncode( $wgMimeType ),
+                       "url" => $this->xmlEncode( wfExpandUrl( $item->getUrlUnescaped(), PROTO_CURRENT ) ),
+                       "date" => $this->xmlEncode( $this->formatTime( $item->getDate() ) ),
+                       "description" => $item->getDescription(),
+                       "author" => $item->getAuthor()
+               ];
+               print $this->templateParser->processTemplate( 'AtomItem', $templateParams );
+       }
+
+       /**
+        * Outputs the footer for Atom 1.0 feed (basically '\</feed\>').
+        */
+       function outFooter() {
+               print "</feed>";
+       }
+}
index 50c6826..4d00fbc 100644 (file)
  * @file
  */
 
-use Wikimedia\Rdbms\ResultWrapper;
-use MediaWiki\MediaWikiServices;
-
 /**
- * Feed to Special:RecentChanges and Special:RecentChangesLiked
+ * Feed to Special:RecentChanges and Special:RecentChangesLinked.
  *
  * @ingroup Feed
  */
 class ChangesFeed {
-       public $format, $type, $titleMsg, $descMsg;
+       private $format;
 
        /**
         * @param string $format Feed's format (either 'rss' or 'atom')
-        * @param string $type Type of feed (for cache keys)
         */
-       public function __construct( $format, $type ) {
+       public function __construct( $format ) {
                $this->format = $format;
-               $this->type = $type;
        }
 
        /**
@@ -65,119 +60,6 @@ class ChangesFeed {
                        $feedTitle, htmlspecialchars( $description ), $url );
        }
 
-       /**
-        * Generates feed's content
-        *
-        * @param ChannelFeed $feed ChannelFeed subclass object (generally the one returned
-        *   by getFeedObject())
-        * @param ResultWrapper $rows ResultWrapper object with rows in recentchanges table
-        * @param int $lastmod Timestamp of the last item in the recentchanges table (only
-        *   used for the cache key)
-        * @param FormOptions $opts As in SpecialRecentChanges::getDefaultOptions()
-        * @return null|bool True or null
-        */
-       public function execute( $feed, $rows, $lastmod, $opts ) {
-               global $wgLang, $wgRenderHashAppend;
-
-               if ( !FeedUtils::checkFeedOutput( $this->format ) ) {
-                       return null;
-               }
-
-               $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
-               $optionsHash = md5( serialize( $opts->getAllValues() ) ) . $wgRenderHashAppend;
-               $timekey = $cache->makeKey(
-                       $this->type, $this->format, $wgLang->getCode(), $optionsHash, 'timestamp' );
-               $key = $cache->makeKey( $this->type, $this->format, $wgLang->getCode(), $optionsHash );
-
-               FeedUtils::checkPurge( $timekey, $key );
-
-               /**
-                * Bumping around loading up diffs can be pretty slow, so where
-                * possible we want to cache the feed output so the next visitor
-                * gets it quick too.
-                */
-               $cachedFeed = $this->loadFromCache( $lastmod, $timekey, $key );
-               if ( is_string( $cachedFeed ) ) {
-                       wfDebug( "RC: Outputting cached feed\n" );
-                       $feed->httpHeaders();
-                       echo $cachedFeed;
-               } else {
-                       wfDebug( "RC: rendering new feed and caching it\n" );
-                       ob_start();
-                       self::generateFeed( $rows, $feed );
-                       $cachedFeed = ob_get_contents();
-                       ob_end_flush();
-                       $this->saveToCache( $cachedFeed, $timekey, $key );
-               }
-               return true;
-       }
-
-       /**
-        * Save to feed result to cache
-        *
-        * @param string $feed Feed's content
-        * @param string $timekey Memcached key of the last modification
-        * @param string $key Memcached key of the content
-        */
-       public function saveToCache( $feed, $timekey, $key ) {
-               $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
-               $cache->set( $key, $feed, $cache::TTL_DAY );
-               $cache->set( $timekey, wfTimestamp( TS_MW ), $cache::TTL_DAY );
-       }
-
-       /**
-        * Try to load the feed result from cache
-        *
-        * @param int $lastmod Timestamp of the last item in the recentchanges table
-        * @param string $timekey Memcached key of the last modification
-        * @param string $key Memcached key of the content
-        * @return string|bool Feed's content on cache hit or false on cache miss
-        */
-       public function loadFromCache( $lastmod, $timekey, $key ) {
-               global $wgFeedCacheTimeout, $wgOut;
-
-               $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
-               $feedLastmod = $cache->get( $timekey );
-
-               if ( ( $wgFeedCacheTimeout > 0 ) && $feedLastmod ) {
-                       /**
-                        * If the cached feed was rendered very recently, we may
-                        * go ahead and use it even if there have been edits made
-                        * since it was rendered. This keeps a swarm of requests
-                        * from being too bad on a super-frequently edited wiki.
-                        */
-
-                       $feedAge = time() - wfTimestamp( TS_UNIX, $feedLastmod );
-                       $feedLastmodUnix = wfTimestamp( TS_UNIX, $feedLastmod );
-                       $lastmodUnix = wfTimestamp( TS_UNIX, $lastmod );
-
-                       if ( $feedAge < $wgFeedCacheTimeout || $feedLastmodUnix > $lastmodUnix ) {
-                               wfDebug( "RC: loading feed from cache ($key; $feedLastmod; $lastmod)...\n" );
-                               if ( $feedLastmodUnix < $lastmodUnix ) {
-                                       $wgOut->setLastModified( $feedLastmod ); // T23916
-                               }
-                               return $cache->get( $key );
-                       } else {
-                               wfDebug( "RC: cached feed timestamp check failed ($feedLastmod; $lastmod)\n" );
-                       }
-               }
-               return false;
-       }
-
-       /**
-        * Generate the feed items given a row from the database, printing the feed.
-        * @param object $rows IDatabase resource with recentchanges rows
-        * @param ChannelFeed &$feed
-        */
-       public static function generateFeed( $rows, &$feed ) {
-               $items = self::buildItems( $rows );
-               $feed->outHeader();
-               foreach ( $items as $item ) {
-                       $feed->outItem( $item );
-               }
-               $feed->outFooter();
-       }
-
        /**
         * Generate the feed items given a row from the database.
         * @param object $rows IDatabase resource with recentchanges rows
diff --git a/includes/changes/ChannelFeed.php b/includes/changes/ChannelFeed.php
new file mode 100644 (file)
index 0000000..a1b832e
--- /dev/null
@@ -0,0 +1,129 @@
+<?php
+/**
+ * Copyright © 2004 Brion Vibber <brion@pobox.com>
+ * https://www.mediawiki.org/
+ *
+ * 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
+ */
+
+/**
+ * Class to support the outputting of syndication feeds in Atom and RSS format.
+ *
+ * @ingroup Feed
+ */
+abstract class ChannelFeed extends FeedItem {
+
+       /** @var TemplateParser */
+       protected $templateParser;
+
+       /**
+        * @param string|Title $title Feed's title
+        * @param string $description
+        * @param string $url URL uniquely designating the feed.
+        * @param string $date Feed's date
+        * @param string $author Author's user name
+        * @param string $comments
+        */
+       function __construct( $title, $description, $url, $date = '', $author = '', $comments = '' ) {
+               parent::__construct( $title, $description, $url, $date, $author, $comments );
+               $this->templateParser = new TemplateParser();
+       }
+
+       /**
+        * Generate Header of the feed
+        * @par Example:
+        * @code
+        * print "<feed>";
+        * @endcode
+        */
+       abstract public function outHeader();
+
+       /**
+        * Generate an item
+        * @par Example:
+        * @code
+        * print "<item>...</item>";
+        * @endcode
+        * @param FeedItem $item
+        */
+       abstract public function outItem( $item );
+
+       /**
+        * Generate Footer of the feed
+        * @par Example:
+        * @code
+        * print "</feed>";
+        * @endcode
+        */
+       abstract public function outFooter();
+
+       /**
+        * Setup and send HTTP headers. Don't send any content;
+        * content might end up being cached and re-sent with
+        * these same headers later.
+        *
+        * This should be called from the outHeader() method,
+        * but can also be called separately.
+        */
+       public function httpHeaders() {
+               global $wgOut, $wgVaryOnXFP;
+
+               # We take over from $wgOut, excepting its cache header info
+               $wgOut->disable();
+               $mimetype = $this->contentType();
+               header( "Content-type: $mimetype; charset=UTF-8" );
+
+               // Set a sane filename
+               $exts = MediaWiki\MediaWikiServices::getInstance()->getMimeAnalyzer()
+                       ->getExtensionsForType( $mimetype );
+               $ext = $exts ? strtok( $exts, ' ' ) : 'xml';
+               header( "Content-Disposition: inline; filename=\"feed.{$ext}\"" );
+
+               if ( $wgVaryOnXFP ) {
+                       $wgOut->addVaryHeader( 'X-Forwarded-Proto' );
+               }
+               $wgOut->sendCacheControl();
+       }
+
+       /**
+        * Return an internet media type to be sent in the headers.
+        *
+        * @return string
+        */
+       private function contentType() {
+               global $wgRequest;
+
+               $ctype = $wgRequest->getVal( 'ctype', 'application/xml' );
+               $allowedctypes = [
+                       'application/xml',
+                       'text/xml',
+                       'application/rss+xml',
+                       'application/atom+xml'
+               ];
+
+               return ( in_array( $ctype, $allowedctypes ) ? $ctype : 'application/xml' );
+       }
+
+       /**
+        * Output the initial XML headers.
+        */
+       protected function outXmlHeader() {
+               $this->httpHeaders();
+               echo '<?xml version="1.0"?>' . "\n";
+       }
+}
diff --git a/includes/changes/FeedItem.php b/includes/changes/FeedItem.php
new file mode 100644 (file)
index 0000000..a6a2615
--- /dev/null
@@ -0,0 +1,222 @@
+<?php
+/**
+ * Copyright © 2004 Brion Vibber <brion@pobox.com>
+ * https://www.mediawiki.org/
+ *
+ * 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
+ */
+
+/**
+ * @defgroup Feed Feed
+ */
+
+/**
+ * A base class for outputting syndication feeds (e.g. RSS and other formats).
+ *
+ * @ingroup Feed
+ */
+class FeedItem {
+       /** @var Title */
+       public $title;
+
+       public $description;
+
+       public $url;
+
+       public $date;
+
+       public $author;
+
+       public $uniqueId;
+
+       public $comments;
+
+       public $rssIsPermalink = false;
+
+       /**
+        * @param string|Title $title Item's title
+        * @param string $description
+        * @param string $url URL uniquely designating the item.
+        * @param string $date Item's date
+        * @param string $author Author's user name
+        * @param string $comments
+        */
+       function __construct( $title, $description, $url, $date = '', $author = '', $comments = '' ) {
+               $this->title = $title;
+               $this->description = $description;
+               $this->url = $url;
+               $this->uniqueId = $url;
+               $this->date = $date;
+               $this->author = $author;
+               $this->comments = $comments;
+       }
+
+       /**
+        * Encode $string so that it can be safely embedded in a XML document
+        *
+        * @param string $string String to encode
+        * @return string
+        */
+       public function xmlEncode( $string ) {
+               $string = str_replace( "\r\n", "\n", $string );
+               $string = preg_replace( '/[\x00-\x08\x0b\x0c\x0e-\x1f]/', '', $string );
+               return htmlspecialchars( $string );
+       }
+
+       /**
+        * Get the unique id of this item; already xml-encoded
+        * @return string
+        */
+       public function getUniqueID() {
+               $id = $this->getUniqueIdUnescaped();
+               if ( $id ) {
+                       return $this->xmlEncode( $id );
+               }
+       }
+
+       /**
+        * Get the unique id of this item, without any escaping
+        * @return string
+        */
+       public function getUniqueIdUnescaped() {
+               if ( $this->uniqueId ) {
+                       return wfExpandUrl( $this->uniqueId, PROTO_CURRENT );
+               }
+       }
+
+       /**
+        * Set the unique id of an item
+        *
+        * @param string $uniqueId Unique id for the item
+        * @param bool $rssIsPermalink Set to true if the guid (unique id) is a permalink (RSS feeds only)
+        */
+       public function setUniqueId( $uniqueId, $rssIsPermalink = false ) {
+               $this->uniqueId = $uniqueId;
+               $this->rssIsPermalink = $rssIsPermalink;
+       }
+
+       /**
+        * Get the title of this item; already xml-encoded
+        *
+        * @return string
+        */
+       public function getTitle() {
+               return $this->xmlEncode( $this->title );
+       }
+
+       /**
+        * Get the URL of this item; already xml-encoded
+        *
+        * @return string
+        */
+       public function getUrl() {
+               return $this->xmlEncode( $this->url );
+       }
+
+       /** Get the URL of this item without any escaping
+        *
+        * @return string
+        */
+       public function getUrlUnescaped() {
+               return $this->url;
+       }
+
+       /**
+        * Get the description of this item; already xml-encoded
+        *
+        * @return string
+        */
+       public function getDescription() {
+               return $this->xmlEncode( $this->description );
+       }
+
+       /**
+        * Get the description of this item without any escaping
+        *
+        * @return string
+        */
+       public function getDescriptionUnescaped() {
+               return $this->description;
+       }
+
+       /**
+        * Get the language of this item
+        *
+        * @return string
+        */
+       public function getLanguage() {
+               global $wgLanguageCode;
+               return LanguageCode::bcp47( $wgLanguageCode );
+       }
+
+       /**
+        * Get the date of this item
+        *
+        * @return string
+        */
+       public function getDate() {
+               return $this->date;
+       }
+
+       /**
+        * Get the author of this item; already xml-encoded
+        *
+        * @return string
+        */
+       public function getAuthor() {
+               return $this->xmlEncode( $this->author );
+       }
+
+       /**
+        * Get the author of this item without any escaping
+        *
+        * @return string
+        */
+       public function getAuthorUnescaped() {
+               return $this->author;
+       }
+
+       /**
+        * Get the comment of this item; already xml-encoded
+        *
+        * @return string
+        */
+       public function getComments() {
+               return $this->xmlEncode( $this->comments );
+       }
+
+       /**
+        * Get the comment of this item without any escaping
+        *
+        * @return string
+        */
+       public function getCommentsUnescaped() {
+               return $this->comments;
+       }
+
+       /**
+        * Quickie hack... strip out wikilinks to more legible form from the comment.
+        *
+        * @param string $text Wikitext
+        * @return string
+        */
+       public static function stripComment( $text ) {
+               return preg_replace( '/\[\[([^]]*\|)?([^]]+)\]\]/', '\2', $text );
+       }
+       /**#@-*/
+}
diff --git a/includes/changes/RSSFeed.php b/includes/changes/RSSFeed.php
new file mode 100644 (file)
index 0000000..3b34500
--- /dev/null
@@ -0,0 +1,93 @@
+<?php
+/**
+ * Copyright © 2004 Brion Vibber <brion@pobox.com>
+ * https://www.mediawiki.org/
+ *
+ * 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
+ */
+
+/**
+ * Generate an RSS feed.
+ *
+ * @ingroup Feed
+ */
+class RSSFeed extends ChannelFeed {
+
+       /**
+        * Format a date given a timestamp. If a timestamp is not given, nothing is returned
+        *
+        * @param int|null $ts Timestamp
+        * @return string|null Date string
+        */
+       function formatTime( $ts ) {
+               if ( $ts ) {
+                       return gmdate( 'D, d M Y H:i:s \G\M\T', wfTimestamp( TS_UNIX, $ts ) );
+               }
+       }
+
+       /**
+        * Output an RSS 2.0 header
+        */
+       function outHeader() {
+               global $wgVersion;
+
+               $this->outXmlHeader();
+               // Manually escaping rather than letting Mustache do it because Mustache
+               // uses htmlentities, which does not work with XML
+               $templateParams = [
+                       'title' => $this->getTitle(),
+                       'url' => $this->xmlEncode( wfExpandUrl( $this->getUrlUnescaped(), PROTO_CURRENT ) ),
+                       'description' => $this->getDescription(),
+                       'language' => $this->xmlEncode( $this->getLanguage() ),
+                       'version' => $this->xmlEncode( $wgVersion ),
+                       'timestamp' => $this->xmlEncode( $this->formatTime( wfTimestampNow() ) )
+               ];
+               print $this->templateParser->processTemplate( 'RSSHeader', $templateParams );
+       }
+
+       /**
+        * Output an RSS 2.0 item
+        * @param FeedItem $item Item to be output
+        */
+       function outItem( $item ) {
+               // Manually escaping rather than letting Mustache do it because Mustache
+               // uses htmlentities, which does not work with XML
+               $templateParams = [
+                       "title" => $item->getTitle(),
+                       "url" => $this->xmlEncode( wfExpandUrl( $item->getUrlUnescaped(), PROTO_CURRENT ) ),
+                       "permalink" => $item->rssIsPermalink,
+                       "uniqueID" => $item->getUniqueID(),
+                       "description" => $item->getDescription(),
+                       "date" => $this->xmlEncode( $this->formatTime( $item->getDate() ) ),
+                       "author" => $item->getAuthor()
+               ];
+               $comments = $item->getCommentsUnescaped();
+               if ( $comments ) {
+                       $commentsEscaped = $this->xmlEncode( wfExpandUrl( $comments, PROTO_CURRENT ) );
+                       $templateParams["comments"] = $commentsEscaped;
+               }
+               print $this->templateParser->processTemplate( 'RSSItem', $templateParams );
+       }
+
+       /**
+        * Output an RSS 2.0 footer
+        */
+       function outFooter() {
+               print "</channel></rss>";
+       }
+}
diff --git a/includes/compat/ObjectFactory.php b/includes/compat/ObjectFactory.php
deleted file mode 100644 (file)
index 7646238..0000000
+++ /dev/null
@@ -1,27 +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
- */
-
-/**
- * Construct objects from configuration instructions.
- *
- * @deprecated since 1.31, use \Wikimedia\ObjectFactory instead
- */
-class ObjectFactory extends \Wikimedia\ObjectFactory {
-}
diff --git a/includes/compat/XMPReader.php b/includes/compat/XMPReader.php
deleted file mode 100644 (file)
index 5854842..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-<?php
-/**
- * Back-compat for pre-librarized XMP classes
- *
- * 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.
- */
-
-use Wikimedia\XMPReader\Info;
-use Wikimedia\XMPReader\Reader;
-use Wikimedia\XMPReader\Validate;
-
-/**
- * @deprecated since 1.32
- */
-class XMPInfo extends Info {
-}
-
-/**
- * @deprecated since 1.32
- */
-class XMPReader extends Reader {
-}
-
-/**
- * @deprecated since 1.32
- */
-class XMPValidate extends Validate {
-}
index 2cd1fb3..a1f199d 100644 (file)
@@ -220,7 +220,7 @@ class JsonContent extends TextContent {
                        return Html::rawElement( 'td', [], $this->arrayTable( $val ) );
                }
 
-               return Html::element( 'td', [ 'class' => 'value' ], $this->primitiveValue( $val ) );
+               return Html::element( 'td', [ 'class' => 'mw-json-value' ], $this->primitiveValue( $val ) );
        }
 
        /**
index 8900962..e099b38 100644 (file)
@@ -2,7 +2,7 @@
 
 use MediaWiki\MediaWikiServices;
 use Wikimedia\Rdbms\IDatabase;
-use Wikimedia\Rdbms\LoadBalancer;
+use Wikimedia\Rdbms\ILoadBalancer;
 
 /**
  * Base class for objects that allow access to other wiki's databases using
@@ -88,7 +88,7 @@ abstract class DBAccessBase implements IDBAccessObject {
         *
         * @since 1.21
         *
-        * @return LoadBalancer The database load balancer object
+        * @return ILoadBalancer The database load balancer object
         */
        public function getLoadBalancer() {
                $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
index 3d80bbd..f4753d6 100644 (file)
@@ -21,7 +21,7 @@
  * @ingroup Database
  */
 
-use MediaWiki\MediaWikiServices;
+use Wikimedia\Timestamp\ConvertibleTimestamp;
 use Wikimedia\Rdbms\Database;
 use Wikimedia\Rdbms\DatabaseDomain;
 use Wikimedia\Rdbms\Blob;
@@ -52,10 +52,18 @@ class DatabaseOracle extends Database {
        /** @var array */
        private $mFieldInfoCache = [];
 
-       function __construct( array $p ) {
-               $p['tablePrefix'] = strtoupper( $p['tablePrefix'] );
-               parent::__construct( $p );
-               Hooks::run( 'DatabaseOraclePostInit', [ $this ] );
+       /** @var string[] Map of (reserved table name => alternate table name) */
+       private $keywordTableMap = [];
+
+       /**
+        * @see Database::__construct()
+        * @param array $params Additional parameters include:
+        *   - keywordTableMap : Map of reserved table names to alternative table names to use
+        */
+       function __construct( array $params ) {
+               $this->keywordTableMap = $params['keywordTableMap'] ?? [];
+               $params['tablePrefix'] = strtoupper( $params['tablePrefix'] );
+               parent::__construct( $params );
        }
 
        function __destruct() {
@@ -79,8 +87,6 @@ class DatabaseOracle extends Database {
        }
 
        protected function open( $server, $user, $password, $dbName, $schema, $tablePrefix ) {
-               global $wgDBOracleDRCP;
-
                if ( !function_exists( 'oci_connect' ) ) {
                        throw new DBConnectionError(
                                $this,
@@ -107,10 +113,6 @@ class DatabaseOracle extends Database {
                        return null;
                }
 
-               if ( $wgDBOracleDRCP ) {
-                       $this->setFlag( DBO_PERSISTENT );
-               }
-
                $session_mode = ( $this->flags & DBO_SYSDBA ) ? OCI_SYSDBA : OCI_DEFAULT;
 
                Wikimedia\suppressWarnings();
@@ -184,9 +186,8 @@ class DatabaseOracle extends Database {
         * @return bool|mixed|ORAResult
         */
        protected function doQuery( $sql ) {
-               wfDebug( "SQL: [$sql]\n" );
-               if ( !StringUtils::isUtf8( $sql ) ) {
-                       throw new InvalidArgumentException( "SQL encoding is invalid\n$sql" );
+               if ( !mb_check_encoding( (string)$sql, 'UTF-8' ) ) {
+                       throw new DBUnexpectedError( $this, "SQL encoding is invalid\n$sql" );
                }
 
                // handle some oracle specifics
@@ -420,7 +421,11 @@ class DatabaseOracle extends Database {
                }
 
                if ( $val === null ) {
-                       if ( $col_info != false && $col_info->isNullable() == 0 && $col_info->defaultValue() != null ) {
+                       if (
+                               $col_info != false &&
+                               $col_info->isNullable() == 0 &&
+                               $col_info->defaultValue() != null
+                       ) {
                                $bind .= 'DEFAULT';
                        } else {
                                $bind .= 'NULL';
@@ -465,7 +470,7 @@ class DatabaseOracle extends Database {
                $this->mLastResult = $stmt = oci_parse( $this->conn, $sql );
                if ( $stmt === false ) {
                        $e = oci_error( $this->conn );
-                       $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
+                       $this->reportQueryError( $e['message'], $e['code'], $sql, $fname );
 
                        return false;
                }
@@ -481,15 +486,17 @@ class DatabaseOracle extends Database {
                                }
 
                                // backward compatibility
-                               if ( preg_match( '/^timestamp.*/i', $col_type ) == 1 && strtolower( $val ) == 'infinity' ) {
+                               if (
+                                       preg_match( '/^timestamp.*/i', $col_type ) == 1 &&
+                                       strtolower( $val ) == 'infinity'
+                               ) {
                                        $val = $this->getInfinity();
                                }
 
-                               $val = MediaWikiServices::getInstance()->getContentLanguage()->
-                                       checkTitleEncoding( $val );
+                               $val = $this->getVerifiedUTF8( $val );
                                if ( oci_bind_by_name( $stmt, ":$col", $val, -1, SQLT_CHR ) === false ) {
                                        $e = oci_error( $stmt );
-                                       $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
+                                       $this->reportQueryError( $e['message'], $e['code'], $sql, $fname );
 
                                        return false;
                                }
@@ -498,7 +505,10 @@ class DatabaseOracle extends Database {
                                $lob[$col] = oci_new_descriptor( $this->conn, OCI_D_LOB );
                                if ( $lob[$col] === false ) {
                                        $e = oci_error( $stmt );
-                                       throw new DBUnexpectedError( $this, "Cannot create LOB descriptor: " . $e['message'] );
+                                       throw new DBUnexpectedError(
+                                               $this,
+                                               "Cannot create LOB descriptor: " . $e['message']
+                                       );
                                }
 
                                if ( is_object( $val ) ) {
@@ -520,7 +530,7 @@ class DatabaseOracle extends Database {
                if ( oci_execute( $stmt, $this->execFlags() ) === false ) {
                        $e = oci_error( $stmt );
                        if ( !$this->ignoreDupValOnIndex || $e['code'] != '1' ) {
-                               $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
+                               $this->reportQueryError( $e['message'], $e['code'], $sql, $fname );
 
                                return false;
                        } else {
@@ -554,7 +564,8 @@ class DatabaseOracle extends Database {
                if ( $sequenceData !== false &&
                        !isset( $varMap[$sequenceData['column']] )
                ) {
-                       $varMap[$sequenceData['column']] = 'GET_SEQUENCE_VALUE(\'' . $sequenceData['sequence'] . '\')';
+                       $varMap[$sequenceData['column']] =
+                               'GET_SEQUENCE_VALUE(\'' . $sequenceData['sequence'] . '\')';
                }
 
                // count-alias subselect fields to avoid abigious definition errors
@@ -573,7 +584,8 @@ class DatabaseOracle extends Database {
                        $selectJoinConds
                );
 
-               $sql = "INSERT INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ') ' . $selectSql;
+               $sql = "INSERT INTO $destTable (" .
+                       implode( ',', array_keys( $varMap ) ) . ') ' . $selectSql;
 
                if ( in_array( 'IGNORE', $insertOptions ) ) {
                        $this->ignoreDupValOnIndex = true;
@@ -612,24 +624,21 @@ class DatabaseOracle extends Database {
                return parent::upsert( $table, $rows, $uniqueIndexes, $set, $fname );
        }
 
-       function tableName( $name, $format = 'quoted' ) {
-               /*
-               Replace reserved words with better ones
-               Using uppercase because that's the only way Oracle can handle
-               quoted tablenames
-               */
-               switch ( $name ) {
-                       case 'user':
-                               $name = 'MWUSER';
-                               break;
-                       case 'text':
-                               $name = 'PAGECONTENT';
-                               break;
-               }
+       public function tableName( $name, $format = 'quoted' ) {
+               // Replace reserved words with better ones
+               $name = $this->remappedTableName( $name );
 
                return strtoupper( parent::tableName( $name, $format ) );
        }
 
+       /**
+        * @param string $name
+        * @return string Value of $name or remapped name if $name is a reserved keyword
+        */
+       public function remappedTableName( $name ) {
+               return $this->keywordTableMap[$name] ?? $name;
+       }
+
        function tableNameInternal( $name ) {
                $name = $this->tableName( $name );
 
@@ -640,23 +649,31 @@ class DatabaseOracle extends Database {
         * Return sequence_name if table has a sequence
         *
         * @param string $table
-        * @return bool
+        * @return string[]|bool
         */
        private function getSequenceData( $table ) {
                if ( $this->sequenceData == null ) {
-                       $result = $this->doQuery( "SELECT lower(asq.sequence_name),
-                               lower(atc.table_name),
-                               lower(atc.column_name)
-                       FROM all_sequences asq, all_tab_columns atc
-                       WHERE decode(
-                                       atc.table_name,
-                                       '{$this->tablePrefix}MWUSER',
-                                       '{$this->tablePrefix}USER',
-                                       atc.table_name
-                               ) || '_' ||
-                               atc.column_name || '_SEQ' = '{$this->tablePrefix}' || asq.sequence_name
-                               AND asq.sequence_owner = upper('{$this->getDBname()}')
-                               AND atc.owner = upper('{$this->getDBname()}')" );
+                       $dbname = $this->currentDomain->getDatabase();
+                       $prefix = $this->currentDomain->getTablePrefix();
+                       // See https://docs.oracle.com/cd/B19306_01/server.102/b14200/functions040.htm
+                       $decodeArgs = [ 'atc.table_name' ]; // the switch
+                       foreach ( $this->keywordTableMap as $reserved => $alternative ) {
+                               $search = strtoupper( $prefix . $alternative ); // case
+                               $replace = strtoupper( $prefix . $reserved ); // result
+                               $decodeArgs[] = $this->addQuotes( $search );
+                               $decodeArgs[] = $this->addQuotes( $replace );
+                       }
+                       $decodeArgs[] = [ 'atc.table_name' ]; // default
+                       $decodeArgs = implode( ', ', $decodeArgs );
+
+                       $result = $this->doQuery(
+                               "SELECT lower(asq.sequence_name), lower(atc.table_name), lower(atc.column_name)
+                               FROM all_sequences asq, all_tab_columns atc
+                               WHERE decode({$decodeArgs}) || '_' ||
+                               atc.column_name || '_SEQ' = '{$prefix}' || asq.sequence_name
+                               AND asq.sequence_owner = upper('{$dbname}')
+                               AND atc.owner = upper('{$dbname}')"
+                       );
 
                        while ( ( $row = $result->fetchRow() ) !== false ) {
                                $this->sequenceData[$row[1]] = [
@@ -710,13 +727,14 @@ class DatabaseOracle extends Database {
                $fname = __METHOD__
        ) {
                $temporary = $temporary ? 'TRUE' : 'FALSE';
+               $tablePrefix = $this->currentDomain->getTablePrefix();
 
                $newName = strtoupper( $newName );
                $oldName = strtoupper( $oldName );
 
-               $tabName = substr( $newName, strlen( $this->tablePrefix ) );
+               $tabName = substr( $newName, strlen( $tablePrefix ) );
                $oldPrefix = substr( $oldName, 0, strlen( $oldName ) - strlen( $tabName ) );
-               $newPrefix = strtoupper( $this->tablePrefix );
+               $newPrefix = strtoupper( $tablePrefix );
 
                return $this->doQuery( "BEGIN DUPLICATE_TABLE( '$tabName', " .
                        "'$oldPrefix', '$newPrefix', $temporary ); END;" );
@@ -756,8 +774,10 @@ class DatabaseOracle extends Database {
                return $this->doQuery( "DROP TABLE $tableName CASCADE CONSTRAINTS PURGE" );
        }
 
-       function timestamp( $ts = 0 ) {
-               return wfTimestamp( TS_ORACLE, $ts );
+       public function timestamp( $ts = 0 ) {
+               $t = new ConvertibleTimestamp( $ts );
+               // Let errors bubble up to avoid putting garbage in the DB
+               return $t->getTimestamp( TS_ORACLE );
        }
 
        /**
@@ -912,7 +932,10 @@ class DatabaseOracle extends Database {
         */
        function fieldInfo( $table, $field ) {
                if ( is_array( $table ) ) {
-                       throw new DBUnexpectedError( $this, 'DatabaseOracle::fieldInfo called with table array!' );
+                       throw new DBUnexpectedError(
+                               $this,
+                               'DatabaseOracle::fieldInfo called with table array!'
+                       );
                }
 
                return $this->fieldInfoMulti( $table, $field );
@@ -1061,12 +1084,7 @@ class DatabaseOracle extends Database {
        }
 
        function addQuotes( $s ) {
-               $contLang = MediaWikiServices::getInstance()->getContentLanguage();
-               if ( isset( $contLang->mLoaded ) && $contLang->mLoaded ) {
-                       $s = $contLang->checkTitleEncoding( $s );
-               }
-
-               return "'" . $this->strencode( $s ) . "'";
+               return "'" . $this->strencode( $this->getVerifiedUTF8( $s ) ) . "'";
        }
 
        public function addIdentifierQuotes( $s ) {
@@ -1090,11 +1108,9 @@ class DatabaseOracle extends Database {
                $col_type = $col_info != false ? $col_info->type() : 'CONSTANT';
                if ( $col_type == 'CLOB' ) {
                        $col = 'TO_CHAR(' . $col . ')';
-                       $val =
-                               MediaWikiServices::getInstance()->getContentLanguage()->checkTitleEncoding( $val );
+                       $val = $this->getVerifiedUTF8( $val );
                } elseif ( $col_type == 'VARCHAR2' ) {
-                       $val =
-                               MediaWikiServices::getInstance()->getContentLanguage()->checkTitleEncoding( $val );
+                       $val = $this->getVerifiedUTF8( $val );
                }
        }
 
@@ -1260,12 +1276,14 @@ class DatabaseOracle extends Database {
                                        $val = $val->getData();
                                }
 
-                               if ( preg_match( '/^timestamp.*/i', $col_type ) == 1 && strtolower( $val ) == 'infinity' ) {
+                               if (
+                                       preg_match( '/^timestamp.*/i', $col_type ) == 1 &&
+                                       strtolower( $val ) == 'infinity'
+                               ) {
                                        $val = '31-12-2030 12:00:00.000000';
                                }
 
-                               $val = MediaWikiServices::getInstance()->getContentLanguage()->
-                                       checkTitleEncoding( $val );
+                               $val = $this->getVerifiedUTF8( $val );
                                if ( oci_bind_by_name( $stmt, ":$col", $val ) === false ) {
                                        $e = oci_error( $stmt );
                                        $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
@@ -1277,7 +1295,10 @@ class DatabaseOracle extends Database {
                                $lob[$col] = oci_new_descriptor( $this->conn, OCI_D_LOB );
                                if ( $lob[$col] === false ) {
                                        $e = oci_error( $stmt );
-                                       throw new DBUnexpectedError( $this, "Cannot create LOB descriptor: " . $e['message'] );
+                                       throw new DBUnexpectedError(
+                                               $this,
+                                               "Cannot create LOB descriptor: " . $e['message']
+                                       );
                                }
 
                                if ( is_object( $val ) ) {
@@ -1366,4 +1387,16 @@ class DatabaseOracle extends Database {
        public function getInfinity() {
                return '31-12-2030 12:00:00.000000';
        }
+
+       /**
+        * @param string $s
+        * @return string
+        */
+       private function getVerifiedUTF8( $s ) {
+               if ( mb_check_encoding( (string)$s, 'UTF-8' ) ) {
+                       return $s; // valid
+               }
+
+               throw new DBUnexpectedError( $this, "Non BLOB/CLOB field must be UTF-8." );
+       }
 }
index bbb3d2f..6633fba 100644 (file)
@@ -53,7 +53,7 @@ abstract class MWLBFactory {
        ) {
                global $wgCommandLineMode;
 
-               static $typesWithSchema = [ 'postgres', 'msssql' ];
+               $typesWithSchema = self::getDbTypesWithSchemas();
 
                $lbConf += [
                        'localDomain' => new DatabaseDomain(
@@ -82,77 +82,29 @@ abstract class MWLBFactory {
                // for Database classes in the relevant Installer subclass.
                // Such as MysqlInstaller::openConnection and PostgresInstaller::openConnectionWithParams.
                if ( $lbConf['class'] === Wikimedia\Rdbms\LBFactorySimple::class ) {
-                       $httpMethod = $_SERVER['REQUEST_METHOD'] ?? null;
-                       // T93097: hint for how file-based databases (e.g. sqlite) should go about locking.
-                       // See https://www.sqlite.org/lang_transaction.html
-                       // See https://www.sqlite.org/lockingv3.html#shared_lock
-                       $isReadRequest = in_array( $httpMethod, [ 'GET', 'HEAD', 'OPTIONS', 'TRACE' ] );
-
                        if ( isset( $lbConf['servers'] ) ) {
-                               // Server array is already explicitly configured; leave alone
+                               // Server array is already explicitly configured
                        } elseif ( is_array( $mainConfig->get( 'DBservers' ) ) ) {
                                $lbConf['servers'] = [];
                                foreach ( $mainConfig->get( 'DBservers' ) as $i => $server ) {
-                                       if ( $server['type'] === 'sqlite' ) {
-                                               $server += [
-                                                       'dbDirectory' => $mainConfig->get( 'SQLiteDataDir' ),
-                                                       'trxMode' => $isReadRequest ? 'DEFERRED' : 'IMMEDIATE'
-                                               ];
-                                       } elseif ( $server['type'] === 'postgres' ) {
-                                               $server += [
-                                                       'port' => $mainConfig->get( 'DBport' ),
-                                                       // Work around the reserved word usage in MediaWiki schema
-                                                       'keywordTableMap' => [ 'user' => 'mwuser', 'text' => 'pagecontent' ]
-                                               ];
-                                       } elseif ( $server['type'] === 'mssql' ) {
-                                               $server += [
-                                                       'port' => $mainConfig->get( 'DBport' ),
-                                                       'useWindowsAuth' => $mainConfig->get( 'DBWindowsAuthentication' )
-                                               ];
-                                       }
-
-                                       if ( in_array( $server['type'], $typesWithSchema, true ) ) {
-                                               $server += [ 'schema' => $mainConfig->get( 'DBmwschema' ) ];
-                                       }
-
-                                       $server += [
-                                               'tablePrefix' => $mainConfig->get( 'DBprefix' ),
-                                               'flags' => DBO_DEFAULT,
-                                               'sqlMode' => $mainConfig->get( 'SQLMode' ),
-                                       ];
-
-                                       $lbConf['servers'][$i] = $server;
+                                       $lbConf['servers'][$i] = self::initServerInfo( $server, $mainConfig );
                                }
                        } else {
-                               $flags = DBO_DEFAULT;
-                               $flags |= $mainConfig->get( 'DebugDumpSql' ) ? DBO_DEBUG : 0;
-                               $flags |= $mainConfig->get( 'DBssl' ) ? DBO_SSL : 0;
-                               $flags |= $mainConfig->get( 'DBcompress' ) ? DBO_COMPRESS : 0;
-                               $server = [
-                                       'host' => $mainConfig->get( 'DBserver' ),
-                                       'user' => $mainConfig->get( 'DBuser' ),
-                                       'password' => $mainConfig->get( 'DBpassword' ),
-                                       'dbname' => $mainConfig->get( 'DBname' ),
-                                       'tablePrefix' => $mainConfig->get( 'DBprefix' ),
-                                       'type' => $mainConfig->get( 'DBtype' ),
-                                       'load' => 1,
-                                       'flags' => $flags,
-                                       'sqlMode' => $mainConfig->get( 'SQLMode' ),
-                                       'trxMode' => $isReadRequest ? 'DEFERRED' : 'IMMEDIATE'
-                               ];
-                               if ( in_array( $server['type'], $typesWithSchema, true ) ) {
-                                       $server += [ 'schema' => $mainConfig->get( 'DBmwschema' ) ];
-                               }
-                               if ( $server['type'] === 'sqlite' ) {
-                                       $server[ 'dbDirectory'] = $mainConfig->get( 'SQLiteDataDir' );
-                               } elseif ( $server['type'] === 'postgres' ) {
-                                       $server['port'] = $mainConfig->get( 'DBport' );
-                                       // Work around the reserved word usage in MediaWiki schema
-                                       $server['keywordTableMap'] = [ 'user' => 'mwuser', 'text' => 'pagecontent' ];
-                               } elseif ( $server['type'] === 'mssql' ) {
-                                       $server['port'] = $mainConfig->get( 'DBport' );
-                                       $server['useWindowsAuth'] = $mainConfig->get( 'DBWindowsAuthentication' );
-                               }
+                               $server = self::initServerInfo(
+                                       [
+                                               'host' => $mainConfig->get( 'DBserver' ),
+                                               'user' => $mainConfig->get( 'DBuser' ),
+                                               'password' => $mainConfig->get( 'DBpassword' ),
+                                               'dbname' => $mainConfig->get( 'DBname' ),
+                                               'type' => $mainConfig->get( 'DBtype' ),
+                                               'load' => 1
+                                       ],
+                                       $mainConfig
+                               );
+
+                               $server['flags'] |= $mainConfig->get( 'DBssl' ) ? DBO_SSL : 0;
+                               $server['flags'] |= $mainConfig->get( 'DBcompress' ) ? DBO_COMPRESS : 0;
+
                                $lbConf['servers'] = [ $server ];
                        }
                        if ( !isset( $lbConf['externalClusters'] ) ) {
@@ -167,15 +119,76 @@ abstract class MWLBFactory {
                                }
                                $lbConf['serverTemplate']['sqlMode'] = $mainConfig->get( 'SQLMode' );
                        }
-                       $serversCheck = $lbConf['serverTemplate'] ?? [];
+                       $serversCheck = [ $lbConf['serverTemplate'] ] ?? [];
                }
 
-               self::sanityCheckServerConfig( $serversCheck, $mainConfig );
-               $lbConf = self::applyDefaultCaching( $lbConf, $srvCace, $mainStash, $wanCache );
+               self::assertValidServerConfigs( $serversCheck, $mainConfig );
+
+               $lbConf = self::injectObjectCaches( $lbConf, $srvCace, $mainStash, $wanCache );
 
                return $lbConf;
        }
 
+       /**
+        * @return array
+        */
+       private static function getDbTypesWithSchemas() {
+               return [ 'postgres', 'msssql' ];
+       }
+
+       /**
+        * @param array $server
+        * @param Config $mainConfig
+        * @return array
+        */
+       private static function initServerInfo( array $server, Config $mainConfig ) {
+               if ( $server['type'] === 'sqlite' ) {
+                       $httpMethod = $_SERVER['REQUEST_METHOD'] ?? null;
+                       // T93097: hint for how file-based databases (e.g. sqlite) should go about locking.
+                       // See https://www.sqlite.org/lang_transaction.html
+                       // See https://www.sqlite.org/lockingv3.html#shared_lock
+                       $isHttpRead = in_array( $httpMethod, [ 'GET', 'HEAD', 'OPTIONS', 'TRACE' ] );
+                       $server += [
+                               'dbDirectory' => $mainConfig->get( 'SQLiteDataDir' ),
+                               'trxMode' => $isHttpRead ? 'DEFERRED' : 'IMMEDIATE'
+                       ];
+               } elseif ( $server['type'] === 'postgres' ) {
+                       $server += [
+                               'port' => $mainConfig->get( 'DBport' ),
+                               // 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' => $mainConfig->get( 'DBport' ),
+                               'useWindowsAuth' => $mainConfig->get( 'DBWindowsAuthentication' )
+                       ];
+               }
+
+               if ( in_array( $server['type'], self::getDbTypesWithSchemas(), true ) ) {
+                       $server += [ 'schema' => $mainConfig->get( 'DBmwschema' ) ];
+               }
+
+               $flags = DBO_DEFAULT;
+               $flags |= $mainConfig->get( 'DebugDumpSql' ) ? DBO_DEBUG : 0;
+               if ( $server['type'] === 'oracle' ) {
+                       $flags |= $mainConfig->get( 'DBOracleDRCP' ) ? DBO_PERSISTENT : 0;
+               }
+
+               $server += [
+                       'tablePrefix' => $mainConfig->get( 'DBprefix' ),
+                       'flags' => $flags,
+                       'sqlMode' => $mainConfig->get( 'SQLMode' ),
+               ];
+
+               return $server;
+       }
+
        /**
         * @param array $lbConf
         * @param BagOStuff $sCache
@@ -183,7 +196,7 @@ abstract class MWLBFactory {
         * @param WANObjectCache $wCache
         * @return array
         */
-       private static function applyDefaultCaching(
+       private static function injectObjectCaches(
                array $lbConf, BagOStuff $sCache, BagOStuff $mStash, WANObjectCache $wCache
        ) {
                // Use APC/memcached style caching, but avoids loops with CACHE_DB (T141804)
@@ -204,7 +217,7 @@ abstract class MWLBFactory {
         * @param array $servers
         * @param Config $mainConfig
         */
-       private static function sanityCheckServerConfig( array $servers, Config $mainConfig ) {
+       private static function assertValidServerConfigs( array $servers, Config $mainConfig ) {
                $ldDB = $mainConfig->get( 'DBname' ); // local domain DB
                $ldTP = $mainConfig->get( 'DBprefix' ); // local domain prefix
 
index 6f961e8..2d07f75 100644 (file)
@@ -71,13 +71,10 @@ class CdnCacheUpdate implements DeferrableUpdate, MergeableUpdate {
                self::purge( $this->urls );
 
                if ( $wgCdnReboundPurgeDelay > 0 ) {
-                       JobQueueGroup::singleton()->lazyPush( new CdnPurgeJob(
-                               Title::makeTitle( NS_SPECIAL, 'Badtitle/' . __CLASS__ ),
-                               [
-                                       'urls' => $this->urls,
-                                       'jobReleaseTimestamp' => time() + $wgCdnReboundPurgeDelay
-                               ]
-                       ) );
+                       JobQueueGroup::singleton()->lazyPush( new CdnPurgeJob( [
+                               'urls' => $this->urls,
+                               'jobReleaseTimestamp' => time() + $wgCdnReboundPurgeDelay
+                       ] ) );
                }
        }
 
diff --git a/includes/diff/DairikiDiff.php b/includes/diff/DairikiDiff.php
deleted file mode 100644 (file)
index 063f826..0000000
+++ /dev/null
@@ -1,334 +0,0 @@
-<?php
-/**
- * A PHP diff engine for phpwiki. (Taken from phpwiki-1.3.3)
- *
- * Copyright © 2000, 2001 Geoffrey T. Dairiki <dairiki@dairiki.org>
- * You may copy this code freely under the conditions of the GPL.
- *
- * 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 DifferenceEngine
- * @defgroup DifferenceEngine DifferenceEngine
- */
-
-/**
- * The base class for all other DiffOp classes.
- *
- * The classes that extend DiffOp are: DiffOpCopy, DiffOpDelete, DiffOpAdd and
- * DiffOpChange. FakeDiffOp also extends DiffOp, but it is not located in this file.
- *
- * @private
- * @ingroup DifferenceEngine
- */
-abstract class DiffOp {
-
-       /**
-        * @var string
-        */
-       public $type;
-
-       /**
-        * @var string[]
-        */
-       public $orig;
-
-       /**
-        * @var string[]
-        */
-       public $closing;
-
-       /**
-        * @return string
-        */
-       public function getType() {
-               return $this->type;
-       }
-
-       /**
-        * @return string[]
-        */
-       public function getOrig() {
-               return $this->orig;
-       }
-
-       /**
-        * @param int|null $i
-        * @return string[]|string|null
-        */
-       public function getClosing( $i = null ) {
-               if ( $i === null ) {
-                       return $this->closing;
-               }
-               if ( array_key_exists( $i, $this->closing ) ) {
-                       return $this->closing[$i];
-               }
-               return null;
-       }
-
-       abstract public function reverse();
-
-       /**
-        * @return int
-        */
-       public function norig() {
-               return $this->orig ? count( $this->orig ) : 0;
-       }
-
-       /**
-        * @return int
-        */
-       public function nclosing() {
-               return $this->closing ? count( $this->closing ) : 0;
-       }
-}
-
-/**
- * Extends DiffOp. Used to mark strings that have been
- * copied from one string array to the other.
- *
- * @private
- * @ingroup DifferenceEngine
- */
-class DiffOpCopy extends DiffOp {
-       public $type = 'copy';
-
-       public function __construct( $orig, $closing = false ) {
-               if ( !is_array( $closing ) ) {
-                       $closing = $orig;
-               }
-               $this->orig = $orig;
-               $this->closing = $closing;
-       }
-
-       /**
-        * @return DiffOpCopy
-        */
-       public function reverse() {
-               return new DiffOpCopy( $this->closing, $this->orig );
-       }
-}
-
-/**
- * Extends DiffOp. Used to mark strings that have been
- * deleted from the first string array.
- *
- * @private
- * @ingroup DifferenceEngine
- */
-class DiffOpDelete extends DiffOp {
-       public $type = 'delete';
-
-       public function __construct( $lines ) {
-               $this->orig = $lines;
-               $this->closing = false;
-       }
-
-       /**
-        * @return DiffOpAdd
-        */
-       public function reverse() {
-               return new DiffOpAdd( $this->orig );
-       }
-}
-
-/**
- * Extends DiffOp. Used to mark strings that have been
- * added from the first string array.
- *
- * @private
- * @ingroup DifferenceEngine
- */
-class DiffOpAdd extends DiffOp {
-       public $type = 'add';
-
-       public function __construct( $lines ) {
-               $this->closing = $lines;
-               $this->orig = false;
-       }
-
-       /**
-        * @return DiffOpDelete
-        */
-       public function reverse() {
-               return new DiffOpDelete( $this->closing );
-       }
-}
-
-/**
- * Extends DiffOp. Used to mark strings that have been
- * changed from the first string array (both added and subtracted).
- *
- * @private
- * @ingroup DifferenceEngine
- */
-class DiffOpChange extends DiffOp {
-       public $type = 'change';
-
-       public function __construct( $orig, $closing ) {
-               $this->orig = $orig;
-               $this->closing = $closing;
-       }
-
-       /**
-        * @return DiffOpChange
-        */
-       public function reverse() {
-               return new DiffOpChange( $this->closing, $this->orig );
-       }
-}
-
-/**
- * Class representing a 'diff' between two sequences of strings.
- * @todo document
- * @private
- * @ingroup DifferenceEngine
- */
-class Diff {
-
-       /**
-        * @var DiffOp[]
-        */
-       public $edits;
-
-       /**
-        * @var int If this diff complexity is exceeded, a ComplexityException is thrown
-        *          0 means no limit.
-        */
-       protected $bailoutComplexity = 0;
-
-       /**
-        * Computes diff between sequences of strings.
-        *
-        * @param string[] $from_lines An array of strings.
-        *   Typically these are lines from a file.
-        * @param string[] $to_lines An array of strings.
-        * @throws \MediaWiki\Diff\ComplexityException
-        */
-       public function __construct( $from_lines, $to_lines ) {
-               $eng = new DiffEngine;
-               $eng->setBailoutComplexity( $this->bailoutComplexity );
-               $this->edits = $eng->diff( $from_lines, $to_lines );
-       }
-
-       /**
-        * @return DiffOp[]
-        */
-       public function getEdits() {
-               return $this->edits;
-       }
-
-       /**
-        * Compute reversed Diff.
-        *
-        * SYNOPSIS:
-        *
-        *    $diff = new Diff($lines1, $lines2);
-        *    $rev = $diff->reverse();
-        *
-        * @return Object A Diff object representing the inverse of the
-        *   original diff.
-        */
-       public function reverse() {
-               $rev = $this;
-               $rev->edits = [];
-               /** @var DiffOp $edit */
-               foreach ( $this->edits as $edit ) {
-                       $rev->edits[] = $edit->reverse();
-               }
-
-               return $rev;
-       }
-
-       /**
-        * Check for empty diff.
-        *
-        * @return bool True if two sequences were identical.
-        */
-       public function isEmpty() {
-               foreach ( $this->edits as $edit ) {
-                       if ( $edit->type != 'copy' ) {
-                               return false;
-                       }
-               }
-
-               return true;
-       }
-
-       /**
-        * Compute the length of the Longest Common Subsequence (LCS).
-        *
-        * This is mostly for diagnostic purposed.
-        *
-        * @return int The length of the LCS.
-        */
-       public function lcs() {
-               $lcs = 0;
-               foreach ( $this->edits as $edit ) {
-                       if ( $edit->type == 'copy' ) {
-                               $lcs += count( $edit->orig );
-                       }
-               }
-
-               return $lcs;
-       }
-
-       /**
-        * Get the original set of lines.
-        *
-        * This reconstructs the $from_lines parameter passed to the
-        * constructor.
-        *
-        * @return string[] The original sequence of strings.
-        */
-       public function orig() {
-               $lines = [];
-
-               foreach ( $this->edits as $edit ) {
-                       if ( $edit->orig ) {
-                               array_splice( $lines, count( $lines ), 0, $edit->orig );
-                       }
-               }
-
-               return $lines;
-       }
-
-       /**
-        * Get the closing set of lines.
-        *
-        * This reconstructs the $to_lines parameter passed to the
-        * constructor.
-        *
-        * @return string[] The sequence of strings.
-        */
-       public function closing() {
-               $lines = [];
-
-               foreach ( $this->edits as $edit ) {
-                       if ( $edit->closing ) {
-                               array_splice( $lines, count( $lines ), 0, $edit->closing );
-                       }
-               }
-
-               return $lines;
-       }
-}
-
-/**
- * @deprecated Alias for WordAccumulator, to be soon removed
- */
-class HWLDFWordAccumulator extends MediaWiki\Diff\WordAccumulator {
-}
diff --git a/includes/diff/Diff.php b/includes/diff/Diff.php
new file mode 100644 (file)
index 0000000..2dab88b
--- /dev/null
@@ -0,0 +1,160 @@
+<?php
+/**
+ * A PHP diff engine for phpwiki. (Taken from phpwiki-1.3.3)
+ *
+ * Copyright © 2000, 2001 Geoffrey T. Dairiki <dairiki@dairiki.org>
+ * You may copy this code freely under the conditions of the GPL.
+ *
+ * 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 DifferenceEngine
+ */
+
+/**
+ * Class representing a 'diff' between two sequences of strings.
+ * @private
+ * @ingroup DifferenceEngine
+ */
+class Diff {
+
+       /**
+        * @var DiffOp[]
+        */
+       public $edits;
+
+       /**
+        * @var int If this diff complexity is exceeded, a ComplexityException is thrown
+        *          0 means no limit.
+        */
+       protected $bailoutComplexity = 0;
+
+       /**
+        * Computes diff between sequences of strings.
+        *
+        * @param string[] $from_lines An array of strings.
+        *   Typically these are lines from a file.
+        * @param string[] $to_lines An array of strings.
+        * @throws \MediaWiki\Diff\ComplexityException
+        */
+       public function __construct( $from_lines, $to_lines ) {
+               $eng = new DiffEngine;
+               $eng->setBailoutComplexity( $this->bailoutComplexity );
+               $this->edits = $eng->diff( $from_lines, $to_lines );
+       }
+
+       /**
+        * @return DiffOp[]
+        */
+       public function getEdits() {
+               return $this->edits;
+       }
+
+       /**
+        * Compute reversed Diff.
+        *
+        * SYNOPSIS:
+        *
+        *    $diff = new Diff($lines1, $lines2);
+        *    $rev = $diff->reverse();
+        *
+        * @return Object A Diff object representing the inverse of the
+        *   original diff.
+        */
+       public function reverse() {
+               $rev = $this;
+               $rev->edits = [];
+               /** @var DiffOp $edit */
+               foreach ( $this->edits as $edit ) {
+                       $rev->edits[] = $edit->reverse();
+               }
+
+               return $rev;
+       }
+
+       /**
+        * Check for empty diff.
+        *
+        * @return bool True if two sequences were identical.
+        */
+       public function isEmpty() {
+               foreach ( $this->edits as $edit ) {
+                       if ( $edit->type != 'copy' ) {
+                               return false;
+                       }
+               }
+
+               return true;
+       }
+
+       /**
+        * Compute the length of the Longest Common Subsequence (LCS).
+        *
+        * This is mostly for diagnostic purposed.
+        *
+        * @return int The length of the LCS.
+        */
+       public function lcs() {
+               $lcs = 0;
+               foreach ( $this->edits as $edit ) {
+                       if ( $edit->type == 'copy' ) {
+                               $lcs += count( $edit->orig );
+                       }
+               }
+
+               return $lcs;
+       }
+
+       /**
+        * Get the original set of lines.
+        *
+        * This reconstructs the $from_lines parameter passed to the
+        * constructor.
+        *
+        * @return string[] The original sequence of strings.
+        */
+       public function orig() {
+               $lines = [];
+
+               foreach ( $this->edits as $edit ) {
+                       if ( $edit->orig ) {
+                               array_splice( $lines, count( $lines ), 0, $edit->orig );
+                       }
+               }
+
+               return $lines;
+       }
+
+       /**
+        * Get the closing set of lines.
+        *
+        * This reconstructs the $to_lines parameter passed to the
+        * constructor.
+        *
+        * @return string[] The sequence of strings.
+        */
+       public function closing() {
+               $lines = [];
+
+               foreach ( $this->edits as $edit ) {
+                       if ( $edit->closing ) {
+                               array_splice( $lines, count( $lines ), 0, $edit->closing );
+                       }
+               }
+
+               return $lines;
+       }
+}
diff --git a/includes/diff/DiffOp.php b/includes/diff/DiffOp.php
new file mode 100644 (file)
index 0000000..2a1f3e1
--- /dev/null
@@ -0,0 +1,97 @@
+<?php
+/**
+ * A PHP diff engine for phpwiki. (Taken from phpwiki-1.3.3)
+ *
+ * Copyright © 2000, 2001 Geoffrey T. Dairiki <dairiki@dairiki.org>
+ * You may copy this code freely under the conditions of the GPL.
+ *
+ * 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 DifferenceEngine
+ * @defgroup DifferenceEngine DifferenceEngine
+ */
+
+/**
+ * The base class for all other DiffOp classes.
+ *
+ * The classes that extend DiffOp are: DiffOpCopy, DiffOpDelete, DiffOpAdd and
+ * DiffOpChange. FakeDiffOp also extends DiffOp, but it is not located in this file.
+ *
+ * @private
+ * @ingroup DifferenceEngine
+ */
+abstract class DiffOp {
+
+       /**
+        * @var string
+        */
+       public $type;
+
+       /**
+        * @var string[]
+        */
+       public $orig;
+
+       /**
+        * @var string[]
+        */
+       public $closing;
+
+       /**
+        * @return string
+        */
+       public function getType() {
+               return $this->type;
+       }
+
+       /**
+        * @return string[]
+        */
+       public function getOrig() {
+               return $this->orig;
+       }
+
+       /**
+        * @param int|null $i
+        * @return string[]|string|null
+        */
+       public function getClosing( $i = null ) {
+               if ( $i === null ) {
+                       return $this->closing;
+               }
+               if ( array_key_exists( $i, $this->closing ) ) {
+                       return $this->closing[$i];
+               }
+               return null;
+       }
+
+       abstract public function reverse();
+
+       /**
+        * @return int
+        */
+       public function norig() {
+               return $this->orig ? count( $this->orig ) : 0;
+       }
+
+       /**
+        * @return int
+        */
+       public function nclosing() {
+               return $this->closing ? count( $this->closing ) : 0;
+       }
+}
diff --git a/includes/diff/DiffOpAdd.php b/includes/diff/DiffOpAdd.php
new file mode 100644 (file)
index 0000000..3f95e8d
--- /dev/null
@@ -0,0 +1,49 @@
+<?php
+
+/**
+ * A PHP diff engine for phpwiki. (Taken from phpwiki-1.3.3)
+ *
+ * Copyright © 2000, 2001 Geoffrey T. Dairiki <dairiki@dairiki.org>
+ * You may copy this code freely under the conditions of the GPL.
+ *
+ * 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 DifferenceEngine
+ */
+
+/**
+ * Extends DiffOp. Used to mark strings that have been
+ * added from the first string array.
+ *
+ * @private
+ * @ingroup DifferenceEngine
+ */
+class DiffOpAdd extends DiffOp {
+       public $type = 'add';
+
+       public function __construct( $lines ) {
+               $this->closing = $lines;
+               $this->orig = false;
+       }
+
+       /**
+        * @return DiffOpDelete
+        */
+       public function reverse() {
+               return new DiffOpDelete( $this->closing );
+       }
+}
diff --git a/includes/diff/DiffOpChange.php b/includes/diff/DiffOpChange.php
new file mode 100644 (file)
index 0000000..bde9287
--- /dev/null
@@ -0,0 +1,49 @@
+<?php
+
+/**
+ * A PHP diff engine for phpwiki. (Taken from phpwiki-1.3.3)
+ *
+ * Copyright © 2000, 2001 Geoffrey T. Dairiki <dairiki@dairiki.org>
+ * You may copy this code freely under the conditions of the GPL.
+ *
+ * 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 DifferenceEngine
+ */
+
+/**
+ * Extends DiffOp. Used to mark strings that have been
+ * changed from the first string array (both added and subtracted).
+ *
+ * @private
+ * @ingroup DifferenceEngine
+ */
+class DiffOpChange extends DiffOp {
+       public $type = 'change';
+
+       public function __construct( $orig, $closing ) {
+               $this->orig = $orig;
+               $this->closing = $closing;
+       }
+
+       /**
+        * @return DiffOpChange
+        */
+       public function reverse() {
+               return new DiffOpChange( $this->closing, $this->orig );
+       }
+}
diff --git a/includes/diff/DiffOpCopy.php b/includes/diff/DiffOpCopy.php
new file mode 100644 (file)
index 0000000..904ee58
--- /dev/null
@@ -0,0 +1,52 @@
+<?php
+
+/**
+ * A PHP diff engine for phpwiki. (Taken from phpwiki-1.3.3)
+ *
+ * Copyright © 2000, 2001 Geoffrey T. Dairiki <dairiki@dairiki.org>
+ * You may copy this code freely under the conditions of the GPL.
+ *
+ * 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 DifferenceEngine
+ */
+
+/**
+ * Extends DiffOp. Used to mark strings that have been
+ * copied from one string array to the other.
+ *
+ * @private
+ * @ingroup DifferenceEngine
+ */
+class DiffOpCopy extends DiffOp {
+       public $type = 'copy';
+
+       public function __construct( $orig, $closing = false ) {
+               if ( !is_array( $closing ) ) {
+                       $closing = $orig;
+               }
+               $this->orig = $orig;
+               $this->closing = $closing;
+       }
+
+       /**
+        * @return DiffOpCopy
+        */
+       public function reverse() {
+               return new DiffOpCopy( $this->closing, $this->orig );
+       }
+}
diff --git a/includes/diff/DiffOpDelete.php b/includes/diff/DiffOpDelete.php
new file mode 100644 (file)
index 0000000..22b65ab
--- /dev/null
@@ -0,0 +1,49 @@
+<?php
+
+/**
+ * A PHP diff engine for phpwiki. (Taken from phpwiki-1.3.3)
+ *
+ * Copyright © 2000, 2001 Geoffrey T. Dairiki <dairiki@dairiki.org>
+ * You may copy this code freely under the conditions of the GPL.
+ *
+ * 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 DifferenceEngine
+ */
+
+/**
+ * Extends DiffOp. Used to mark strings that have been
+ * deleted from the first string array.
+ *
+ * @private
+ * @ingroup DifferenceEngine
+ */
+class DiffOpDelete extends DiffOp {
+       public $type = 'delete';
+
+       public function __construct( $lines ) {
+               $this->orig = $lines;
+               $this->closing = false;
+       }
+
+       /**
+        * @return DiffOpAdd
+        */
+       public function reverse() {
+               return new DiffOpAdd( $this->orig );
+       }
+}
index e39334f..833bc69 100644 (file)
@@ -250,7 +250,7 @@ class DifferenceEngine extends ContextSource {
 
                        $slotContents = $this->getSlotContents();
                        $this->slotDiffRenderers = array_map( function ( $contents ) {
-                               /** @var $content Content */
+                               /** @var Content $content */
                                $content = $contents['new'] ?: $contents['old'];
                                return $content->getContentHandler()->getSlotDiffRenderer( $this->getContext() );
                        }, $slotContents );
index 001944c..bb965e1 100644 (file)
@@ -72,7 +72,7 @@ class TextSlotDiffRenderer extends SlotDiffRenderer {
         * @return string
         */
        public static function diff( $oldText, $newText ) {
-               /** @var $slotDiffRenderer TextSlotDiffRenderer */
+               /** @var TextSlotDiffRenderer $slotDiffRenderer */
                $slotDiffRenderer = ContentHandler::getForModelID( CONTENT_MODEL_TEXT )
                        ->getSlotDiffRenderer( RequestContext::getMain() );
                return $slotDiffRenderer->getTextDiff( $oldText, $newText );
index 7007316..88eae36 100644 (file)
@@ -22,6 +22,7 @@ namespace MediaWiki\Edit;
 
 use Content;
 use ParserOptions;
+use RuntimeException;
 use ParserOutput;
 
 /**
@@ -32,7 +33,6 @@ use ParserOutput;
  * @since 1.30
  */
 class PreparedEdit {
-
        /**
         * Time this prepared edit was made
         *
@@ -73,7 +73,7 @@ class PreparedEdit {
         *
         * @var ParserOutput|null
         */
-       public $output;
+       private $canonicalOutput;
 
        /**
         * Content that is being saved (before PST)
@@ -89,4 +89,36 @@ class PreparedEdit {
         */
        public $oldContent;
 
+       /**
+        * Lazy-loading callback to get canonical ParserOutput object
+        *
+        * @var callable
+        */
+       public $parserOutputCallback;
+
+       /**
+        * @return ParserOutput Canonical parser output
+        */
+       public function getOutput() {
+               if ( !$this->canonicalOutput ) {
+                       $this->canonicalOutput = call_user_func( $this->parserOutputCallback );
+               }
+
+               return $this->canonicalOutput;
+       }
+
+       /**
+        * Fetch the ParserOutput via a lazy-loaded callback (for backwards compatibility).
+        *
+        * @deprecated since 1.33
+        * @param string $name
+        * @return mixed
+        */
+       function __get( $name ) {
+               if ( $name === 'output' ) {
+                       return $this->getOutput();
+               }
+
+               throw new RuntimeException( "Undefined field $name." );
+       }
 }
index 502cee8..cb7ff19 100644 (file)
@@ -69,23 +69,22 @@ class MWException extends Exception {
         * @param string $key Message name
         * @param string $fallback Default message if the message cache can't be
         *                  called by the exception
-        * The function also has other parameters that are arguments for the message
+        * @param mixed ...$params To pass to wfMessage()
         * @return string Message with arguments replaced
         */
-       public function msg( $key, $fallback /*[, params...] */ ) {
+       public function msg( $key, $fallback, ...$params ) {
                global $wgSitename;
-               $args = array_slice( func_get_args(), 2 );
 
                // FIXME: Keep logic in sync with MWExceptionRenderer::msg.
                $res = false;
                if ( $this->useMessageCache() ) {
                        try {
-                               $res = wfMessage( $key, $args )->text();
+                               $res = wfMessage( $key, $params )->text();
                        } catch ( Exception $e ) {
                        }
                }
                if ( $res === false ) {
-                       $res = wfMsgReplaceArgs( $fallback, $args );
+                       $res = wfMsgReplaceArgs( $fallback, $params );
                        // If an exception happens inside message rendering,
                        // {{SITENAME}} sometimes won't be replaced.
                        $res = strtr( $res, [
index 66775bf..c52a867 100644 (file)
@@ -191,18 +191,17 @@ class MWExceptionRenderer {
         * @param string $key Message name
         * @param string $fallback Default message if the message cache can't be
         *                  called by the exception
-        * The function also has other parameters that are arguments for the message
+        * @param mixed ...$params To pass to wfMessage()
         * @return string Message with arguments replaced
         */
-       private static function msg( $key, $fallback /*[, params...] */ ) {
+       private static function msg( $key, $fallback, ...$params ) {
                global $wgSitename;
-               $args = array_slice( func_get_args(), 2 );
 
                // FIXME: Keep logic in sync with MWException::msg.
                try {
-                       $res = wfMessage( $key, $args )->text();
+                       $res = wfMessage( $key, $params )->text();
                } catch ( Exception $e ) {
-                       $res = wfMsgReplaceArgs( $fallback, $args );
+                       $res = wfMsgReplaceArgs( $fallback, $params );
                        // If an exception happens inside message rendering,
                        // {{SITENAME}} sometimes won't be replaced.
                        $res = strtr( $res, [
index aa04fae..86b8bbb 100644 (file)
@@ -2295,1148 +2295,4 @@ class LocalFile extends File {
        function __destruct() {
                $this->unlock();
        }
-} // LocalFile class
-
-# ------------------------------------------------------------------------------
-
-/**
- * Helper class for file deletion
- * @ingroup FileAbstraction
- */
-class LocalFileDeleteBatch {
-       /** @var LocalFile */
-       private $file;
-
-       /** @var string */
-       private $reason;
-
-       /** @var array */
-       private $srcRels = [];
-
-       /** @var array */
-       private $archiveUrls = [];
-
-       /** @var array Items to be processed in the deletion batch */
-       private $deletionBatch;
-
-       /** @var bool Whether to suppress all suppressable fields when deleting */
-       private $suppress;
-
-       /** @var Status */
-       private $status;
-
-       /** @var User */
-       private $user;
-
-       /**
-        * @param File $file
-        * @param string $reason
-        * @param bool $suppress
-        * @param User|null $user
-        */
-       function __construct( File $file, $reason = '', $suppress = false, $user = null ) {
-               $this->file = $file;
-               $this->reason = $reason;
-               $this->suppress = $suppress;
-               if ( $user ) {
-                       $this->user = $user;
-               } else {
-                       global $wgUser;
-                       $this->user = $wgUser;
-               }
-               $this->status = $file->repo->newGood();
-       }
-
-       public function addCurrent() {
-               $this->srcRels['.'] = $this->file->getRel();
-       }
-
-       /**
-        * @param string $oldName
-        */
-       public function addOld( $oldName ) {
-               $this->srcRels[$oldName] = $this->file->getArchiveRel( $oldName );
-               $this->archiveUrls[] = $this->file->getArchiveUrl( $oldName );
-       }
-
-       /**
-        * Add the old versions of the image to the batch
-        * @return string[] List of archive names from old versions
-        */
-       public function addOlds() {
-               $archiveNames = [];
-
-               $dbw = $this->file->repo->getMasterDB();
-               $result = $dbw->select( 'oldimage',
-                       [ 'oi_archive_name' ],
-                       [ 'oi_name' => $this->file->getName() ],
-                       __METHOD__
-               );
-
-               foreach ( $result as $row ) {
-                       $this->addOld( $row->oi_archive_name );
-                       $archiveNames[] = $row->oi_archive_name;
-               }
-
-               return $archiveNames;
-       }
-
-       /**
-        * @return array
-        */
-       protected function getOldRels() {
-               if ( !isset( $this->srcRels['.'] ) ) {
-                       $oldRels =& $this->srcRels;
-                       $deleteCurrent = false;
-               } else {
-                       $oldRels = $this->srcRels;
-                       unset( $oldRels['.'] );
-                       $deleteCurrent = true;
-               }
-
-               return [ $oldRels, $deleteCurrent ];
-       }
-
-       /**
-        * @return array
-        */
-       protected function getHashes() {
-               $hashes = [];
-               list( $oldRels, $deleteCurrent ) = $this->getOldRels();
-
-               if ( $deleteCurrent ) {
-                       $hashes['.'] = $this->file->getSha1();
-               }
-
-               if ( count( $oldRels ) ) {
-                       $dbw = $this->file->repo->getMasterDB();
-                       $res = $dbw->select(
-                               'oldimage',
-                               [ 'oi_archive_name', 'oi_sha1' ],
-                               [ 'oi_archive_name' => array_keys( $oldRels ),
-                                       'oi_name' => $this->file->getName() ], // performance
-                               __METHOD__
-                       );
-
-                       foreach ( $res as $row ) {
-                               if ( rtrim( $row->oi_sha1, "\0" ) === '' ) {
-                                       // Get the hash from the file
-                                       $oldUrl = $this->file->getArchiveVirtualUrl( $row->oi_archive_name );
-                                       $props = $this->file->repo->getFileProps( $oldUrl );
-
-                                       if ( $props['fileExists'] ) {
-                                               // Upgrade the oldimage row
-                                               $dbw->update( 'oldimage',
-                                                       [ 'oi_sha1' => $props['sha1'] ],
-                                                       [ 'oi_name' => $this->file->getName(), 'oi_archive_name' => $row->oi_archive_name ],
-                                                       __METHOD__ );
-                                               $hashes[$row->oi_archive_name] = $props['sha1'];
-                                       } else {
-                                               $hashes[$row->oi_archive_name] = false;
-                                       }
-                               } else {
-                                       $hashes[$row->oi_archive_name] = $row->oi_sha1;
-                               }
-                       }
-               }
-
-               $missing = array_diff_key( $this->srcRels, $hashes );
-
-               foreach ( $missing as $name => $rel ) {
-                       $this->status->error( 'filedelete-old-unregistered', $name );
-               }
-
-               foreach ( $hashes as $name => $hash ) {
-                       if ( !$hash ) {
-                               $this->status->error( 'filedelete-missing', $this->srcRels[$name] );
-                               unset( $hashes[$name] );
-                       }
-               }
-
-               return $hashes;
-       }
-
-       protected function doDBInserts() {
-               global $wgActorTableSchemaMigrationStage;
-
-               $now = time();
-               $dbw = $this->file->repo->getMasterDB();
-
-               $commentStore = MediaWikiServices::getInstance()->getCommentStore();
-               $actorMigration = ActorMigration::newMigration();
-
-               $encTimestamp = $dbw->addQuotes( $dbw->timestamp( $now ) );
-               $encUserId = $dbw->addQuotes( $this->user->getId() );
-               $encGroup = $dbw->addQuotes( 'deleted' );
-               $ext = $this->file->getExtension();
-               $dotExt = $ext === '' ? '' : ".$ext";
-               $encExt = $dbw->addQuotes( $dotExt );
-               list( $oldRels, $deleteCurrent ) = $this->getOldRels();
-
-               // Bitfields to further suppress the content
-               if ( $this->suppress ) {
-                       $bitfield = Revision::SUPPRESSED_ALL;
-               } else {
-                       $bitfield = 'oi_deleted';
-               }
-
-               if ( $deleteCurrent ) {
-                       $tables = [ 'image' ];
-                       $fields = [
-                               'fa_storage_group' => $encGroup,
-                               'fa_storage_key' => $dbw->conditional(
-                                       [ 'img_sha1' => '' ],
-                                       $dbw->addQuotes( '' ),
-                                       $dbw->buildConcat( [ "img_sha1", $encExt ] )
-                               ),
-                               'fa_deleted_user' => $encUserId,
-                               'fa_deleted_timestamp' => $encTimestamp,
-                               'fa_deleted' => $this->suppress ? $bitfield : 0,
-                               'fa_name' => 'img_name',
-                               'fa_archive_name' => 'NULL',
-                               'fa_size' => 'img_size',
-                               'fa_width' => 'img_width',
-                               'fa_height' => 'img_height',
-                               'fa_metadata' => 'img_metadata',
-                               'fa_bits' => 'img_bits',
-                               'fa_media_type' => 'img_media_type',
-                               'fa_major_mime' => 'img_major_mime',
-                               'fa_minor_mime' => 'img_minor_mime',
-                               'fa_description_id' => 'img_description_id',
-                               'fa_timestamp' => 'img_timestamp',
-                               'fa_sha1' => 'img_sha1'
-                       ];
-                       $joins = [];
-
-                       $fields += array_map(
-                               [ $dbw, 'addQuotes' ],
-                               $commentStore->insert( $dbw, 'fa_deleted_reason', $this->reason )
-                       );
-
-                       if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
-                               $fields['fa_user'] = 'img_user';
-                               $fields['fa_user_text'] = 'img_user_text';
-                       }
-                       if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
-                               $fields['fa_actor'] = 'img_actor';
-                       }
-
-                       if (
-                               ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_BOTH ) === SCHEMA_COMPAT_WRITE_BOTH
-                       ) {
-                               // Upgrade any rows that are still old-style. Otherwise an upgrade
-                               // might be missed if a deletion happens while the migration script
-                               // is running.
-                               $res = $dbw->select(
-                                       [ 'image' ],
-                                       [ 'img_name', 'img_user', 'img_user_text' ],
-                                       [ 'img_name' => $this->file->getName(), 'img_actor' => 0 ],
-                                       __METHOD__
-                               );
-                               foreach ( $res as $row ) {
-                                       $actorId = User::newFromAnyId( $row->img_user, $row->img_user_text, null )->getActorId( $dbw );
-                                       $dbw->update(
-                                               'image',
-                                               [ 'img_actor' => $actorId ],
-                                               [ 'img_name' => $row->img_name, 'img_actor' => 0 ],
-                                               __METHOD__
-                                       );
-                               }
-                       }
-
-                       $dbw->insertSelect( 'filearchive', $tables, $fields,
-                               [ 'img_name' => $this->file->getName() ], __METHOD__, [], [], $joins );
-               }
-
-               if ( count( $oldRels ) ) {
-                       $fileQuery = OldLocalFile::getQueryInfo();
-                       $res = $dbw->select(
-                               $fileQuery['tables'],
-                               $fileQuery['fields'],
-                               [
-                                       'oi_name' => $this->file->getName(),
-                                       'oi_archive_name' => array_keys( $oldRels )
-                               ],
-                               __METHOD__,
-                               [ 'FOR UPDATE' ],
-                               $fileQuery['joins']
-                       );
-                       $rowsInsert = [];
-                       if ( $res->numRows() ) {
-                               $reason = $commentStore->createComment( $dbw, $this->reason );
-                               foreach ( $res as $row ) {
-                                       $comment = $commentStore->getComment( 'oi_description', $row );
-                                       $user = User::newFromAnyId( $row->oi_user, $row->oi_user_text, $row->oi_actor );
-                                       $rowsInsert[] = [
-                                               // Deletion-specific fields
-                                               'fa_storage_group' => 'deleted',
-                                               'fa_storage_key' => ( $row->oi_sha1 === '' )
-                                               ? ''
-                                               : "{$row->oi_sha1}{$dotExt}",
-                                               'fa_deleted_user' => $this->user->getId(),
-                                               'fa_deleted_timestamp' => $dbw->timestamp( $now ),
-                                               // Counterpart fields
-                                               'fa_deleted' => $this->suppress ? $bitfield : $row->oi_deleted,
-                                               'fa_name' => $row->oi_name,
-                                               'fa_archive_name' => $row->oi_archive_name,
-                                               'fa_size' => $row->oi_size,
-                                               'fa_width' => $row->oi_width,
-                                               'fa_height' => $row->oi_height,
-                                               'fa_metadata' => $row->oi_metadata,
-                                               'fa_bits' => $row->oi_bits,
-                                               'fa_media_type' => $row->oi_media_type,
-                                               'fa_major_mime' => $row->oi_major_mime,
-                                               'fa_minor_mime' => $row->oi_minor_mime,
-                                               'fa_timestamp' => $row->oi_timestamp,
-                                               'fa_sha1' => $row->oi_sha1
-                                       ] + $commentStore->insert( $dbw, 'fa_deleted_reason', $reason )
-                                       + $commentStore->insert( $dbw, 'fa_description', $comment )
-                                       + $actorMigration->getInsertValues( $dbw, 'fa_user', $user );
-                               }
-                       }
-
-                       $dbw->insert( 'filearchive', $rowsInsert, __METHOD__ );
-               }
-       }
-
-       function doDBDeletes() {
-               $dbw = $this->file->repo->getMasterDB();
-               list( $oldRels, $deleteCurrent ) = $this->getOldRels();
-
-               if ( count( $oldRels ) ) {
-                       $dbw->delete( 'oldimage',
-                               [
-                                       'oi_name' => $this->file->getName(),
-                                       'oi_archive_name' => array_keys( $oldRels )
-                               ], __METHOD__ );
-               }
-
-               if ( $deleteCurrent ) {
-                       $dbw->delete( 'image', [ 'img_name' => $this->file->getName() ], __METHOD__ );
-               }
-       }
-
-       /**
-        * Run the transaction
-        * @return Status
-        */
-       public function execute() {
-               $repo = $this->file->getRepo();
-               $this->file->lock();
-
-               // Prepare deletion batch
-               $hashes = $this->getHashes();
-               $this->deletionBatch = [];
-               $ext = $this->file->getExtension();
-               $dotExt = $ext === '' ? '' : ".$ext";
-
-               foreach ( $this->srcRels as $name => $srcRel ) {
-                       // Skip files that have no hash (e.g. missing DB record, or sha1 field and file source)
-                       if ( isset( $hashes[$name] ) ) {
-                               $hash = $hashes[$name];
-                               $key = $hash . $dotExt;
-                               $dstRel = $repo->getDeletedHashPath( $key ) . $key;
-                               $this->deletionBatch[$name] = [ $srcRel, $dstRel ];
-                       }
-               }
-
-               if ( !$repo->hasSha1Storage() ) {
-                       // Removes non-existent file from the batch, so we don't get errors.
-                       // This also handles files in the 'deleted' zone deleted via revision deletion.
-                       $checkStatus = $this->removeNonexistentFiles( $this->deletionBatch );
-                       if ( !$checkStatus->isGood() ) {
-                               $this->status->merge( $checkStatus );
-                               return $this->status;
-                       }
-                       $this->deletionBatch = $checkStatus->value;
-
-                       // Execute the file deletion batch
-                       $status = $this->file->repo->deleteBatch( $this->deletionBatch );
-                       if ( !$status->isGood() ) {
-                               $this->status->merge( $status );
-                       }
-               }
-
-               if ( !$this->status->isOK() ) {
-                       // Critical file deletion error; abort
-                       $this->file->unlock();
-
-                       return $this->status;
-               }
-
-               // Copy the image/oldimage rows to filearchive
-               $this->doDBInserts();
-               // Delete image/oldimage rows
-               $this->doDBDeletes();
-
-               // Commit and return
-               $this->file->unlock();
-
-               return $this->status;
-       }
-
-       /**
-        * Removes non-existent files from a deletion batch.
-        * @param array $batch
-        * @return Status
-        */
-       protected function removeNonexistentFiles( $batch ) {
-               $files = $newBatch = [];
-
-               foreach ( $batch as $batchItem ) {
-                       list( $src, ) = $batchItem;
-                       $files[$src] = $this->file->repo->getVirtualUrl( 'public' ) . '/' . rawurlencode( $src );
-               }
-
-               $result = $this->file->repo->fileExistsBatch( $files );
-               if ( in_array( null, $result, true ) ) {
-                       return Status::newFatal( 'backend-fail-internal',
-                               $this->file->repo->getBackend()->getName() );
-               }
-
-               foreach ( $batch as $batchItem ) {
-                       if ( $result[$batchItem[0]] ) {
-                               $newBatch[] = $batchItem;
-                       }
-               }
-
-               return Status::newGood( $newBatch );
-       }
-}
-
-# ------------------------------------------------------------------------------
-
-/**
- * Helper class for file undeletion
- * @ingroup FileAbstraction
- */
-class LocalFileRestoreBatch {
-       /** @var LocalFile */
-       private $file;
-
-       /** @var string[] List of file IDs to restore */
-       private $cleanupBatch;
-
-       /** @var string[] List of file IDs to restore */
-       private $ids;
-
-       /** @var bool Add all revisions of the file */
-       private $all;
-
-       /** @var bool Whether to remove all settings for suppressed fields */
-       private $unsuppress = false;
-
-       /**
-        * @param File $file
-        * @param bool $unsuppress
-        */
-       function __construct( File $file, $unsuppress = false ) {
-               $this->file = $file;
-               $this->cleanupBatch = [];
-               $this->ids = [];
-               $this->unsuppress = $unsuppress;
-       }
-
-       /**
-        * Add a file by ID
-        * @param int $fa_id
-        */
-       public function addId( $fa_id ) {
-               $this->ids[] = $fa_id;
-       }
-
-       /**
-        * Add a whole lot of files by ID
-        * @param int[] $ids
-        */
-       public function addIds( $ids ) {
-               $this->ids = array_merge( $this->ids, $ids );
-       }
-
-       /**
-        * Add all revisions of the file
-        */
-       public function addAll() {
-               $this->all = true;
-       }
-
-       /**
-        * Run the transaction, except the cleanup batch.
-        * The cleanup batch should be run in a separate transaction, because it locks different
-        * rows and there's no need to keep the image row locked while it's acquiring those locks
-        * The caller may have its own transaction open.
-        * So we save the batch and let the caller call cleanup()
-        * @return Status
-        */
-       public function execute() {
-               /** @var Language */
-               global $wgLang;
-
-               $repo = $this->file->getRepo();
-               if ( !$this->all && !$this->ids ) {
-                       // Do nothing
-                       return $repo->newGood();
-               }
-
-               $lockOwnsTrx = $this->file->lock();
-
-               $dbw = $this->file->repo->getMasterDB();
-
-               $commentStore = MediaWikiServices::getInstance()->getCommentStore();
-               $actorMigration = ActorMigration::newMigration();
-
-               $status = $this->file->repo->newGood();
-
-               $exists = (bool)$dbw->selectField( 'image', '1',
-                       [ 'img_name' => $this->file->getName() ],
-                       __METHOD__,
-                       // The lock() should already prevents changes, but this still may need
-                       // to bypass any transaction snapshot. However, if lock() started the
-                       // trx (which it probably did) then snapshot is post-lock and up-to-date.
-                       $lockOwnsTrx ? [] : [ 'LOCK IN SHARE MODE' ]
-               );
-
-               // Fetch all or selected archived revisions for the file,
-               // sorted from the most recent to the oldest.
-               $conditions = [ 'fa_name' => $this->file->getName() ];
-
-               if ( !$this->all ) {
-                       $conditions['fa_id'] = $this->ids;
-               }
-
-               $arFileQuery = ArchivedFile::getQueryInfo();
-               $result = $dbw->select(
-                       $arFileQuery['tables'],
-                       $arFileQuery['fields'],
-                       $conditions,
-                       __METHOD__,
-                       [ 'ORDER BY' => 'fa_timestamp DESC' ],
-                       $arFileQuery['joins']
-               );
-
-               $idsPresent = [];
-               $storeBatch = [];
-               $insertBatch = [];
-               $insertCurrent = false;
-               $deleteIds = [];
-               $first = true;
-               $archiveNames = [];
-
-               foreach ( $result as $row ) {
-                       $idsPresent[] = $row->fa_id;
-
-                       if ( $row->fa_name != $this->file->getName() ) {
-                               $status->error( 'undelete-filename-mismatch', $wgLang->timeanddate( $row->fa_timestamp ) );
-                               $status->failCount++;
-                               continue;
-                       }
-
-                       if ( $row->fa_storage_key == '' ) {
-                               // Revision was missing pre-deletion
-                               $status->error( 'undelete-bad-store-key', $wgLang->timeanddate( $row->fa_timestamp ) );
-                               $status->failCount++;
-                               continue;
-                       }
-
-                       $deletedRel = $repo->getDeletedHashPath( $row->fa_storage_key ) .
-                               $row->fa_storage_key;
-                       $deletedUrl = $repo->getVirtualUrl() . '/deleted/' . $deletedRel;
-
-                       if ( isset( $row->fa_sha1 ) ) {
-                               $sha1 = $row->fa_sha1;
-                       } else {
-                               // old row, populate from key
-                               $sha1 = LocalRepo::getHashFromKey( $row->fa_storage_key );
-                       }
-
-                       # Fix leading zero
-                       if ( strlen( $sha1 ) == 32 && $sha1[0] == '0' ) {
-                               $sha1 = substr( $sha1, 1 );
-                       }
-
-                       if ( is_null( $row->fa_major_mime ) || $row->fa_major_mime == 'unknown'
-                               || is_null( $row->fa_minor_mime ) || $row->fa_minor_mime == 'unknown'
-                               || is_null( $row->fa_media_type ) || $row->fa_media_type == 'UNKNOWN'
-                               || is_null( $row->fa_metadata )
-                       ) {
-                               // Refresh our metadata
-                               // Required for a new current revision; nice for older ones too. :)
-                               $props = RepoGroup::singleton()->getFileProps( $deletedUrl );
-                       } else {
-                               $props = [
-                                       'minor_mime' => $row->fa_minor_mime,
-                                       'major_mime' => $row->fa_major_mime,
-                                       'media_type' => $row->fa_media_type,
-                                       'metadata' => $row->fa_metadata
-                               ];
-                       }
-
-                       $comment = $commentStore->getComment( 'fa_description', $row );
-                       $user = User::newFromAnyId( $row->fa_user, $row->fa_user_text, $row->fa_actor );
-                       if ( $first && !$exists ) {
-                               // This revision will be published as the new current version
-                               $destRel = $this->file->getRel();
-                               $commentFields = $commentStore->insert( $dbw, 'img_description', $comment );
-                               $actorFields = $actorMigration->getInsertValues( $dbw, 'img_user', $user );
-                               $insertCurrent = [
-                                       'img_name' => $row->fa_name,
-                                       'img_size' => $row->fa_size,
-                                       'img_width' => $row->fa_width,
-                                       'img_height' => $row->fa_height,
-                                       'img_metadata' => $props['metadata'],
-                                       'img_bits' => $row->fa_bits,
-                                       'img_media_type' => $props['media_type'],
-                                       'img_major_mime' => $props['major_mime'],
-                                       'img_minor_mime' => $props['minor_mime'],
-                                       'img_timestamp' => $row->fa_timestamp,
-                                       'img_sha1' => $sha1
-                               ] + $commentFields + $actorFields;
-
-                               // The live (current) version cannot be hidden!
-                               if ( !$this->unsuppress && $row->fa_deleted ) {
-                                       $status->fatal( 'undeleterevdel' );
-                                       $this->file->unlock();
-                                       return $status;
-                               }
-                       } else {
-                               $archiveName = $row->fa_archive_name;
-
-                               if ( $archiveName == '' ) {
-                                       // This was originally a current version; we
-                                       // have to devise a new archive name for it.
-                                       // Format is <timestamp of archiving>!<name>
-                                       $timestamp = wfTimestamp( TS_UNIX, $row->fa_deleted_timestamp );
-
-                                       do {
-                                               $archiveName = wfTimestamp( TS_MW, $timestamp ) . '!' . $row->fa_name;
-                                               $timestamp++;
-                                       } while ( isset( $archiveNames[$archiveName] ) );
-                               }
-
-                               $archiveNames[$archiveName] = true;
-                               $destRel = $this->file->getArchiveRel( $archiveName );
-                               $insertBatch[] = [
-                                       'oi_name' => $row->fa_name,
-                                       'oi_archive_name' => $archiveName,
-                                       'oi_size' => $row->fa_size,
-                                       'oi_width' => $row->fa_width,
-                                       'oi_height' => $row->fa_height,
-                                       'oi_bits' => $row->fa_bits,
-                                       'oi_timestamp' => $row->fa_timestamp,
-                                       'oi_metadata' => $props['metadata'],
-                                       'oi_media_type' => $props['media_type'],
-                                       'oi_major_mime' => $props['major_mime'],
-                                       'oi_minor_mime' => $props['minor_mime'],
-                                       'oi_deleted' => $this->unsuppress ? 0 : $row->fa_deleted,
-                                       'oi_sha1' => $sha1
-                               ] + $commentStore->insert( $dbw, 'oi_description', $comment )
-                               + $actorMigration->getInsertValues( $dbw, 'oi_user', $user );
-                       }
-
-                       $deleteIds[] = $row->fa_id;
-
-                       if ( !$this->unsuppress && $row->fa_deleted & File::DELETED_FILE ) {
-                               // private files can stay where they are
-                               $status->successCount++;
-                       } else {
-                               $storeBatch[] = [ $deletedUrl, 'public', $destRel ];
-                               $this->cleanupBatch[] = $row->fa_storage_key;
-                       }
-
-                       $first = false;
-               }
-
-               unset( $result );
-
-               // Add a warning to the status object for missing IDs
-               $missingIds = array_diff( $this->ids, $idsPresent );
-
-               foreach ( $missingIds as $id ) {
-                       $status->error( 'undelete-missing-filearchive', $id );
-               }
-
-               if ( !$repo->hasSha1Storage() ) {
-                       // Remove missing files from batch, so we don't get errors when undeleting them
-                       $checkStatus = $this->removeNonexistentFiles( $storeBatch );
-                       if ( !$checkStatus->isGood() ) {
-                               $status->merge( $checkStatus );
-                               return $status;
-                       }
-                       $storeBatch = $checkStatus->value;
-
-                       // Run the store batch
-                       // Use the OVERWRITE_SAME flag to smooth over a common error
-                       $storeStatus = $this->file->repo->storeBatch( $storeBatch, FileRepo::OVERWRITE_SAME );
-                       $status->merge( $storeStatus );
-
-                       if ( !$status->isGood() ) {
-                               // Even if some files could be copied, fail entirely as that is the
-                               // easiest thing to do without data loss
-                               $this->cleanupFailedBatch( $storeStatus, $storeBatch );
-                               $status->setOK( false );
-                               $this->file->unlock();
-
-                               return $status;
-                       }
-               }
-
-               // Run the DB updates
-               // Because we have locked the image row, key conflicts should be rare.
-               // If they do occur, we can roll back the transaction at this time with
-               // no data loss, but leaving unregistered files scattered throughout the
-               // public zone.
-               // This is not ideal, which is why it's important to lock the image row.
-               if ( $insertCurrent ) {
-                       $dbw->insert( 'image', $insertCurrent, __METHOD__ );
-               }
-
-               if ( $insertBatch ) {
-                       $dbw->insert( 'oldimage', $insertBatch, __METHOD__ );
-               }
-
-               if ( $deleteIds ) {
-                       $dbw->delete( 'filearchive',
-                               [ 'fa_id' => $deleteIds ],
-                               __METHOD__ );
-               }
-
-               // If store batch is empty (all files are missing), deletion is to be considered successful
-               if ( $status->successCount > 0 || !$storeBatch || $repo->hasSha1Storage() ) {
-                       if ( !$exists ) {
-                               wfDebug( __METHOD__ . " restored {$status->successCount} items, creating a new current\n" );
-
-                               DeferredUpdates::addUpdate( SiteStatsUpdate::factory( [ 'images' => 1 ] ) );
-
-                               $this->file->purgeEverything();
-                       } else {
-                               wfDebug( __METHOD__ . " restored {$status->successCount} as archived versions\n" );
-                               $this->file->purgeDescription();
-                       }
-               }
-
-               $this->file->unlock();
-
-               return $status;
-       }
-
-       /**
-        * Removes non-existent files from a store batch.
-        * @param array $triplets
-        * @return Status
-        */
-       protected function removeNonexistentFiles( $triplets ) {
-               $files = $filteredTriplets = [];
-               foreach ( $triplets as $file ) {
-                       $files[$file[0]] = $file[0];
-               }
-
-               $result = $this->file->repo->fileExistsBatch( $files );
-               if ( in_array( null, $result, true ) ) {
-                       return Status::newFatal( 'backend-fail-internal',
-                               $this->file->repo->getBackend()->getName() );
-               }
-
-               foreach ( $triplets as $file ) {
-                       if ( $result[$file[0]] ) {
-                               $filteredTriplets[] = $file;
-                       }
-               }
-
-               return Status::newGood( $filteredTriplets );
-       }
-
-       /**
-        * Removes non-existent files from a cleanup batch.
-        * @param string[] $batch
-        * @return string[]
-        */
-       protected function removeNonexistentFromCleanup( $batch ) {
-               $files = $newBatch = [];
-               $repo = $this->file->repo;
-
-               foreach ( $batch as $file ) {
-                       $files[$file] = $repo->getVirtualUrl( 'deleted' ) . '/' .
-                               rawurlencode( $repo->getDeletedHashPath( $file ) . $file );
-               }
-
-               $result = $repo->fileExistsBatch( $files );
-
-               foreach ( $batch as $file ) {
-                       if ( $result[$file] ) {
-                               $newBatch[] = $file;
-                       }
-               }
-
-               return $newBatch;
-       }
-
-       /**
-        * Delete unused files in the deleted zone.
-        * This should be called from outside the transaction in which execute() was called.
-        * @return Status
-        */
-       public function cleanup() {
-               if ( !$this->cleanupBatch ) {
-                       return $this->file->repo->newGood();
-               }
-
-               $this->cleanupBatch = $this->removeNonexistentFromCleanup( $this->cleanupBatch );
-
-               $status = $this->file->repo->cleanupDeletedBatch( $this->cleanupBatch );
-
-               return $status;
-       }
-
-       /**
-        * Cleanup a failed batch. The batch was only partially successful, so
-        * rollback by removing all items that were successfully copied.
-        *
-        * @param Status $storeStatus
-        * @param array[] $storeBatch
-        */
-       protected function cleanupFailedBatch( $storeStatus, $storeBatch ) {
-               $cleanupBatch = [];
-
-               foreach ( $storeStatus->success as $i => $success ) {
-                       // Check if this item of the batch was successfully copied
-                       if ( $success ) {
-                               // Item was successfully copied and needs to be removed again
-                               // Extract ($dstZone, $dstRel) from the batch
-                               $cleanupBatch[] = [ $storeBatch[$i][1], $storeBatch[$i][2] ];
-                       }
-               }
-               $this->file->repo->cleanupBatch( $cleanupBatch );
-       }
-}
-
-# ------------------------------------------------------------------------------
-
-/**
- * Helper class for file movement
- * @ingroup FileAbstraction
- */
-class LocalFileMoveBatch {
-       /** @var LocalFile */
-       protected $file;
-
-       /** @var Title */
-       protected $target;
-
-       protected $cur;
-
-       protected $olds;
-
-       protected $oldCount;
-
-       protected $archive;
-
-       /** @var IDatabase */
-       protected $db;
-
-       /**
-        * @param File $file
-        * @param Title $target
-        */
-       function __construct( File $file, Title $target ) {
-               $this->file = $file;
-               $this->target = $target;
-               $this->oldHash = $this->file->repo->getHashPath( $this->file->getName() );
-               $this->newHash = $this->file->repo->getHashPath( $this->target->getDBkey() );
-               $this->oldName = $this->file->getName();
-               $this->newName = $this->file->repo->getNameFromTitle( $this->target );
-               $this->oldRel = $this->oldHash . $this->oldName;
-               $this->newRel = $this->newHash . $this->newName;
-               $this->db = $file->getRepo()->getMasterDB();
-       }
-
-       /**
-        * Add the current image to the batch
-        */
-       public function addCurrent() {
-               $this->cur = [ $this->oldRel, $this->newRel ];
-       }
-
-       /**
-        * Add the old versions of the image to the batch
-        * @return string[] List of archive names from old versions
-        */
-       public function addOlds() {
-               $archiveBase = 'archive';
-               $this->olds = [];
-               $this->oldCount = 0;
-               $archiveNames = [];
-
-               $result = $this->db->select( 'oldimage',
-                       [ 'oi_archive_name', 'oi_deleted' ],
-                       [ 'oi_name' => $this->oldName ],
-                       __METHOD__,
-                       [ 'LOCK IN SHARE MODE' ] // ignore snapshot
-               );
-
-               foreach ( $result as $row ) {
-                       $archiveNames[] = $row->oi_archive_name;
-                       $oldName = $row->oi_archive_name;
-                       $bits = explode( '!', $oldName, 2 );
-
-                       if ( count( $bits ) != 2 ) {
-                               wfDebug( "Old file name missing !: '$oldName' \n" );
-                               continue;
-                       }
-
-                       list( $timestamp, $filename ) = $bits;
-
-                       if ( $this->oldName != $filename ) {
-                               wfDebug( "Old file name doesn't match: '$oldName' \n" );
-                               continue;
-                       }
-
-                       $this->oldCount++;
-
-                       // Do we want to add those to oldCount?
-                       if ( $row->oi_deleted & File::DELETED_FILE ) {
-                               continue;
-                       }
-
-                       $this->olds[] = [
-                               "{$archiveBase}/{$this->oldHash}{$oldName}",
-                               "{$archiveBase}/{$this->newHash}{$timestamp}!{$this->newName}"
-                       ];
-               }
-
-               return $archiveNames;
-       }
-
-       /**
-        * Perform the move.
-        * @return Status
-        */
-       public function execute() {
-               $repo = $this->file->repo;
-               $status = $repo->newGood();
-               $destFile = wfLocalFile( $this->target );
-
-               $this->file->lock();
-               $destFile->lock(); // quickly fail if destination is not available
-
-               $triplets = $this->getMoveTriplets();
-               $checkStatus = $this->removeNonexistentFiles( $triplets );
-               if ( !$checkStatus->isGood() ) {
-                       $destFile->unlock();
-                       $this->file->unlock();
-                       $status->merge( $checkStatus ); // couldn't talk to file backend
-                       return $status;
-               }
-               $triplets = $checkStatus->value;
-
-               // Verify the file versions metadata in the DB.
-               $statusDb = $this->verifyDBUpdates();
-               if ( !$statusDb->isGood() ) {
-                       $destFile->unlock();
-                       $this->file->unlock();
-                       $statusDb->setOK( false );
-
-                       return $statusDb;
-               }
-
-               if ( !$repo->hasSha1Storage() ) {
-                       // Copy the files into their new location.
-                       // If a prior process fataled copying or cleaning up files we tolerate any
-                       // of the existing files if they are identical to the ones being stored.
-                       $statusMove = $repo->storeBatch( $triplets, FileRepo::OVERWRITE_SAME );
-                       wfDebugLog( 'imagemove', "Moved files for {$this->file->getName()}: " .
-                               "{$statusMove->successCount} successes, {$statusMove->failCount} failures" );
-                       if ( !$statusMove->isGood() ) {
-                               // Delete any files copied over (while the destination is still locked)
-                               $this->cleanupTarget( $triplets );
-                               $destFile->unlock();
-                               $this->file->unlock();
-                               wfDebugLog( 'imagemove', "Error in moving files: "
-                                       . $statusMove->getWikiText( false, false, 'en' ) );
-                               $statusMove->setOK( false );
-
-                               return $statusMove;
-                       }
-                       $status->merge( $statusMove );
-               }
-
-               // Rename the file versions metadata in the DB.
-               $this->doDBUpdates();
-
-               wfDebugLog( 'imagemove', "Renamed {$this->file->getName()} in database: " .
-                       "{$statusDb->successCount} successes, {$statusDb->failCount} failures" );
-
-               $destFile->unlock();
-               $this->file->unlock();
-
-               // Everything went ok, remove the source files
-               $this->cleanupSource( $triplets );
-
-               $status->merge( $statusDb );
-
-               return $status;
-       }
-
-       /**
-        * Verify the database updates and return a new Status indicating how
-        * many rows would be updated.
-        *
-        * @return Status
-        */
-       protected function verifyDBUpdates() {
-               $repo = $this->file->repo;
-               $status = $repo->newGood();
-               $dbw = $this->db;
-
-               $hasCurrent = $dbw->lockForUpdate(
-                       'image',
-                       [ 'img_name' => $this->oldName ],
-                       __METHOD__
-               );
-               $oldRowCount = $dbw->lockForUpdate(
-                       'oldimage',
-                       [ 'oi_name' => $this->oldName ],
-                       __METHOD__
-               );
-
-               if ( $hasCurrent ) {
-                       $status->successCount++;
-               } else {
-                       $status->failCount++;
-               }
-               $status->successCount += $oldRowCount;
-               // T36934: oldCount is based on files that actually exist.
-               // There may be more DB rows than such files, in which case $affected
-               // can be greater than $total. We use max() to avoid negatives here.
-               $status->failCount += max( 0, $this->oldCount - $oldRowCount );
-               if ( $status->failCount ) {
-                       $status->error( 'imageinvalidfilename' );
-               }
-
-               return $status;
-       }
-
-       /**
-        * Do the database updates and return a new Status indicating how
-        * many rows where updated.
-        */
-       protected function doDBUpdates() {
-               $dbw = $this->db;
-
-               // Update current image
-               $dbw->update(
-                       'image',
-                       [ 'img_name' => $this->newName ],
-                       [ 'img_name' => $this->oldName ],
-                       __METHOD__
-               );
-
-               // Update old images
-               $dbw->update(
-                       'oldimage',
-                       [
-                               'oi_name' => $this->newName,
-                               'oi_archive_name = ' . $dbw->strreplace( 'oi_archive_name',
-                                       $dbw->addQuotes( $this->oldName ), $dbw->addQuotes( $this->newName ) ),
-                       ],
-                       [ 'oi_name' => $this->oldName ],
-                       __METHOD__
-               );
-       }
-
-       /**
-        * Generate triplets for FileRepo::storeBatch().
-        * @return array[]
-        */
-       protected function getMoveTriplets() {
-               $moves = array_merge( [ $this->cur ], $this->olds );
-               $triplets = []; // The format is: (srcUrl, destZone, destUrl)
-
-               foreach ( $moves as $move ) {
-                       // $move: (oldRelativePath, newRelativePath)
-                       $srcUrl = $this->file->repo->getVirtualUrl() . '/public/' . rawurlencode( $move[0] );
-                       $triplets[] = [ $srcUrl, 'public', $move[1] ];
-                       wfDebugLog(
-                               'imagemove',
-                               "Generated move triplet for {$this->file->getName()}: {$srcUrl} :: public :: {$move[1]}"
-                       );
-               }
-
-               return $triplets;
-       }
-
-       /**
-        * Removes non-existent files from move batch.
-        * @param array $triplets
-        * @return Status
-        */
-       protected function removeNonexistentFiles( $triplets ) {
-               $files = [];
-
-               foreach ( $triplets as $file ) {
-                       $files[$file[0]] = $file[0];
-               }
-
-               $result = $this->file->repo->fileExistsBatch( $files );
-               if ( in_array( null, $result, true ) ) {
-                       return Status::newFatal( 'backend-fail-internal',
-                               $this->file->repo->getBackend()->getName() );
-               }
-
-               $filteredTriplets = [];
-               foreach ( $triplets as $file ) {
-                       if ( $result[$file[0]] ) {
-                               $filteredTriplets[] = $file;
-                       } else {
-                               wfDebugLog( 'imagemove', "File {$file[0]} does not exist" );
-                       }
-               }
-
-               return Status::newGood( $filteredTriplets );
-       }
-
-       /**
-        * Cleanup a partially moved array of triplets by deleting the target
-        * files. Called if something went wrong half way.
-        * @param array[] $triplets
-        */
-       protected function cleanupTarget( $triplets ) {
-               // Create dest pairs from the triplets
-               $pairs = [];
-               foreach ( $triplets as $triplet ) {
-                       // $triplet: (old source virtual URL, dst zone, dest rel)
-                       $pairs[] = [ $triplet[1], $triplet[2] ];
-               }
-
-               $this->file->repo->cleanupBatch( $pairs );
-       }
-
-       /**
-        * Cleanup a fully moved array of triplets by deleting the source files.
-        * Called at the end of the move process if everything else went ok.
-        * @param array[] $triplets
-        */
-       protected function cleanupSource( $triplets ) {
-               // Create source file names from the triplets
-               $files = [];
-               foreach ( $triplets as $triplet ) {
-                       $files[] = $triplet[0];
-               }
-
-               $this->file->repo->cleanupBatch( $files );
-       }
-}
-
-class LocalFileLockError extends ErrorPageError {
-       public function __construct( Status $status ) {
-               parent::__construct(
-                       'actionfailed',
-                       $status->getMessage()
-               );
-       }
-
-       public function report() {
-               global $wgOut;
-               $wgOut->setStatusCode( 429 );
-               parent::report();
-       }
 }
diff --git a/includes/filerepo/file/LocalFileDeleteBatch.php b/includes/filerepo/file/LocalFileDeleteBatch.php
new file mode 100644 (file)
index 0000000..ecd63e7
--- /dev/null
@@ -0,0 +1,429 @@
+<?php
+/**
+ * Local file in the wiki's own database.
+ *
+ * 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 FileAbstraction
+ */
+
+use MediaWiki\MediaWikiServices;
+
+/**
+ * Helper class for file deletion
+ * @ingroup FileAbstraction
+ */
+class LocalFileDeleteBatch {
+       /** @var LocalFile */
+       private $file;
+
+       /** @var string */
+       private $reason;
+
+       /** @var array */
+       private $srcRels = [];
+
+       /** @var array */
+       private $archiveUrls = [];
+
+       /** @var array Items to be processed in the deletion batch */
+       private $deletionBatch;
+
+       /** @var bool Whether to suppress all suppressable fields when deleting */
+       private $suppress;
+
+       /** @var Status */
+       private $status;
+
+       /** @var User */
+       private $user;
+
+       /**
+        * @param File $file
+        * @param string $reason
+        * @param bool $suppress
+        * @param User|null $user
+        */
+       function __construct( File $file, $reason = '', $suppress = false, $user = null ) {
+               $this->file = $file;
+               $this->reason = $reason;
+               $this->suppress = $suppress;
+               if ( $user ) {
+                       $this->user = $user;
+               } else {
+                       global $wgUser;
+                       $this->user = $wgUser;
+               }
+               $this->status = $file->repo->newGood();
+       }
+
+       public function addCurrent() {
+               $this->srcRels['.'] = $this->file->getRel();
+       }
+
+       /**
+        * @param string $oldName
+        */
+       public function addOld( $oldName ) {
+               $this->srcRels[$oldName] = $this->file->getArchiveRel( $oldName );
+               $this->archiveUrls[] = $this->file->getArchiveUrl( $oldName );
+       }
+
+       /**
+        * Add the old versions of the image to the batch
+        * @return string[] List of archive names from old versions
+        */
+       public function addOlds() {
+               $archiveNames = [];
+
+               $dbw = $this->file->repo->getMasterDB();
+               $result = $dbw->select( 'oldimage',
+                       [ 'oi_archive_name' ],
+                       [ 'oi_name' => $this->file->getName() ],
+                       __METHOD__
+               );
+
+               foreach ( $result as $row ) {
+                       $this->addOld( $row->oi_archive_name );
+                       $archiveNames[] = $row->oi_archive_name;
+               }
+
+               return $archiveNames;
+       }
+
+       /**
+        * @return array
+        */
+       protected function getOldRels() {
+               if ( !isset( $this->srcRels['.'] ) ) {
+                       $oldRels =& $this->srcRels;
+                       $deleteCurrent = false;
+               } else {
+                       $oldRels = $this->srcRels;
+                       unset( $oldRels['.'] );
+                       $deleteCurrent = true;
+               }
+
+               return [ $oldRels, $deleteCurrent ];
+       }
+
+       /**
+        * @return array
+        */
+       protected function getHashes() {
+               $hashes = [];
+               list( $oldRels, $deleteCurrent ) = $this->getOldRels();
+
+               if ( $deleteCurrent ) {
+                       $hashes['.'] = $this->file->getSha1();
+               }
+
+               if ( count( $oldRels ) ) {
+                       $dbw = $this->file->repo->getMasterDB();
+                       $res = $dbw->select(
+                               'oldimage',
+                               [ 'oi_archive_name', 'oi_sha1' ],
+                               [ 'oi_archive_name' => array_keys( $oldRels ),
+                                       'oi_name' => $this->file->getName() ], // performance
+                               __METHOD__
+                       );
+
+                       foreach ( $res as $row ) {
+                               if ( rtrim( $row->oi_sha1, "\0" ) === '' ) {
+                                       // Get the hash from the file
+                                       $oldUrl = $this->file->getArchiveVirtualUrl( $row->oi_archive_name );
+                                       $props = $this->file->repo->getFileProps( $oldUrl );
+
+                                       if ( $props['fileExists'] ) {
+                                               // Upgrade the oldimage row
+                                               $dbw->update( 'oldimage',
+                                                       [ 'oi_sha1' => $props['sha1'] ],
+                                                       [ 'oi_name' => $this->file->getName(), 'oi_archive_name' => $row->oi_archive_name ],
+                                                       __METHOD__ );
+                                               $hashes[$row->oi_archive_name] = $props['sha1'];
+                                       } else {
+                                               $hashes[$row->oi_archive_name] = false;
+                                       }
+                               } else {
+                                       $hashes[$row->oi_archive_name] = $row->oi_sha1;
+                               }
+                       }
+               }
+
+               $missing = array_diff_key( $this->srcRels, $hashes );
+
+               foreach ( $missing as $name => $rel ) {
+                       $this->status->error( 'filedelete-old-unregistered', $name );
+               }
+
+               foreach ( $hashes as $name => $hash ) {
+                       if ( !$hash ) {
+                               $this->status->error( 'filedelete-missing', $this->srcRels[$name] );
+                               unset( $hashes[$name] );
+                       }
+               }
+
+               return $hashes;
+       }
+
+       protected function doDBInserts() {
+               global $wgActorTableSchemaMigrationStage;
+
+               $now = time();
+               $dbw = $this->file->repo->getMasterDB();
+
+               $commentStore = MediaWikiServices::getInstance()->getCommentStore();
+               $actorMigration = ActorMigration::newMigration();
+
+               $encTimestamp = $dbw->addQuotes( $dbw->timestamp( $now ) );
+               $encUserId = $dbw->addQuotes( $this->user->getId() );
+               $encGroup = $dbw->addQuotes( 'deleted' );
+               $ext = $this->file->getExtension();
+               $dotExt = $ext === '' ? '' : ".$ext";
+               $encExt = $dbw->addQuotes( $dotExt );
+               list( $oldRels, $deleteCurrent ) = $this->getOldRels();
+
+               // Bitfields to further suppress the content
+               if ( $this->suppress ) {
+                       $bitfield = Revision::SUPPRESSED_ALL;
+               } else {
+                       $bitfield = 'oi_deleted';
+               }
+
+               if ( $deleteCurrent ) {
+                       $tables = [ 'image' ];
+                       $fields = [
+                               'fa_storage_group' => $encGroup,
+                               'fa_storage_key' => $dbw->conditional(
+                                       [ 'img_sha1' => '' ],
+                                       $dbw->addQuotes( '' ),
+                                       $dbw->buildConcat( [ "img_sha1", $encExt ] )
+                               ),
+                               'fa_deleted_user' => $encUserId,
+                               'fa_deleted_timestamp' => $encTimestamp,
+                               'fa_deleted' => $this->suppress ? $bitfield : 0,
+                               'fa_name' => 'img_name',
+                               'fa_archive_name' => 'NULL',
+                               'fa_size' => 'img_size',
+                               'fa_width' => 'img_width',
+                               'fa_height' => 'img_height',
+                               'fa_metadata' => 'img_metadata',
+                               'fa_bits' => 'img_bits',
+                               'fa_media_type' => 'img_media_type',
+                               'fa_major_mime' => 'img_major_mime',
+                               'fa_minor_mime' => 'img_minor_mime',
+                               'fa_description_id' => 'img_description_id',
+                               'fa_timestamp' => 'img_timestamp',
+                               'fa_sha1' => 'img_sha1'
+                       ];
+                       $joins = [];
+
+                       $fields += array_map(
+                               [ $dbw, 'addQuotes' ],
+                               $commentStore->insert( $dbw, 'fa_deleted_reason', $this->reason )
+                       );
+
+                       if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
+                               $fields['fa_user'] = 'img_user';
+                               $fields['fa_user_text'] = 'img_user_text';
+                       }
+                       if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
+                               $fields['fa_actor'] = 'img_actor';
+                       }
+
+                       if (
+                               ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_BOTH ) === SCHEMA_COMPAT_WRITE_BOTH
+                       ) {
+                               // Upgrade any rows that are still old-style. Otherwise an upgrade
+                               // might be missed if a deletion happens while the migration script
+                               // is running.
+                               $res = $dbw->select(
+                                       [ 'image' ],
+                                       [ 'img_name', 'img_user', 'img_user_text' ],
+                                       [ 'img_name' => $this->file->getName(), 'img_actor' => 0 ],
+                                       __METHOD__
+                               );
+                               foreach ( $res as $row ) {
+                                       $actorId = User::newFromAnyId( $row->img_user, $row->img_user_text, null )->getActorId( $dbw );
+                                       $dbw->update(
+                                               'image',
+                                               [ 'img_actor' => $actorId ],
+                                               [ 'img_name' => $row->img_name, 'img_actor' => 0 ],
+                                               __METHOD__
+                                       );
+                               }
+                       }
+
+                       $dbw->insertSelect( 'filearchive', $tables, $fields,
+                               [ 'img_name' => $this->file->getName() ], __METHOD__, [], [], $joins );
+               }
+
+               if ( count( $oldRels ) ) {
+                       $fileQuery = OldLocalFile::getQueryInfo();
+                       $res = $dbw->select(
+                               $fileQuery['tables'],
+                               $fileQuery['fields'],
+                               [
+                                       'oi_name' => $this->file->getName(),
+                                       'oi_archive_name' => array_keys( $oldRels )
+                               ],
+                               __METHOD__,
+                               [ 'FOR UPDATE' ],
+                               $fileQuery['joins']
+                       );
+                       $rowsInsert = [];
+                       if ( $res->numRows() ) {
+                               $reason = $commentStore->createComment( $dbw, $this->reason );
+                               foreach ( $res as $row ) {
+                                       $comment = $commentStore->getComment( 'oi_description', $row );
+                                       $user = User::newFromAnyId( $row->oi_user, $row->oi_user_text, $row->oi_actor );
+                                       $rowsInsert[] = [
+                                               // Deletion-specific fields
+                                               'fa_storage_group' => 'deleted',
+                                               'fa_storage_key' => ( $row->oi_sha1 === '' )
+                                               ? ''
+                                               : "{$row->oi_sha1}{$dotExt}",
+                                               'fa_deleted_user' => $this->user->getId(),
+                                               'fa_deleted_timestamp' => $dbw->timestamp( $now ),
+                                               // Counterpart fields
+                                               'fa_deleted' => $this->suppress ? $bitfield : $row->oi_deleted,
+                                               'fa_name' => $row->oi_name,
+                                               'fa_archive_name' => $row->oi_archive_name,
+                                               'fa_size' => $row->oi_size,
+                                               'fa_width' => $row->oi_width,
+                                               'fa_height' => $row->oi_height,
+                                               'fa_metadata' => $row->oi_metadata,
+                                               'fa_bits' => $row->oi_bits,
+                                               'fa_media_type' => $row->oi_media_type,
+                                               'fa_major_mime' => $row->oi_major_mime,
+                                               'fa_minor_mime' => $row->oi_minor_mime,
+                                               'fa_timestamp' => $row->oi_timestamp,
+                                               'fa_sha1' => $row->oi_sha1
+                                       ] + $commentStore->insert( $dbw, 'fa_deleted_reason', $reason )
+                                       + $commentStore->insert( $dbw, 'fa_description', $comment )
+                                       + $actorMigration->getInsertValues( $dbw, 'fa_user', $user );
+                               }
+                       }
+
+                       $dbw->insert( 'filearchive', $rowsInsert, __METHOD__ );
+               }
+       }
+
+       function doDBDeletes() {
+               $dbw = $this->file->repo->getMasterDB();
+               list( $oldRels, $deleteCurrent ) = $this->getOldRels();
+
+               if ( count( $oldRels ) ) {
+                       $dbw->delete( 'oldimage',
+                               [
+                                       'oi_name' => $this->file->getName(),
+                                       'oi_archive_name' => array_keys( $oldRels )
+                               ], __METHOD__ );
+               }
+
+               if ( $deleteCurrent ) {
+                       $dbw->delete( 'image', [ 'img_name' => $this->file->getName() ], __METHOD__ );
+               }
+       }
+
+       /**
+        * Run the transaction
+        * @return Status
+        */
+       public function execute() {
+               $repo = $this->file->getRepo();
+               $this->file->lock();
+
+               // Prepare deletion batch
+               $hashes = $this->getHashes();
+               $this->deletionBatch = [];
+               $ext = $this->file->getExtension();
+               $dotExt = $ext === '' ? '' : ".$ext";
+
+               foreach ( $this->srcRels as $name => $srcRel ) {
+                       // Skip files that have no hash (e.g. missing DB record, or sha1 field and file source)
+                       if ( isset( $hashes[$name] ) ) {
+                               $hash = $hashes[$name];
+                               $key = $hash . $dotExt;
+                               $dstRel = $repo->getDeletedHashPath( $key ) . $key;
+                               $this->deletionBatch[$name] = [ $srcRel, $dstRel ];
+                       }
+               }
+
+               if ( !$repo->hasSha1Storage() ) {
+                       // Removes non-existent file from the batch, so we don't get errors.
+                       // This also handles files in the 'deleted' zone deleted via revision deletion.
+                       $checkStatus = $this->removeNonexistentFiles( $this->deletionBatch );
+                       if ( !$checkStatus->isGood() ) {
+                               $this->status->merge( $checkStatus );
+                               return $this->status;
+                       }
+                       $this->deletionBatch = $checkStatus->value;
+
+                       // Execute the file deletion batch
+                       $status = $this->file->repo->deleteBatch( $this->deletionBatch );
+                       if ( !$status->isGood() ) {
+                               $this->status->merge( $status );
+                       }
+               }
+
+               if ( !$this->status->isOK() ) {
+                       // Critical file deletion error; abort
+                       $this->file->unlock();
+
+                       return $this->status;
+               }
+
+               // Copy the image/oldimage rows to filearchive
+               $this->doDBInserts();
+               // Delete image/oldimage rows
+               $this->doDBDeletes();
+
+               // Commit and return
+               $this->file->unlock();
+
+               return $this->status;
+       }
+
+       /**
+        * Removes non-existent files from a deletion batch.
+        * @param array $batch
+        * @return Status
+        */
+       protected function removeNonexistentFiles( $batch ) {
+               $files = $newBatch = [];
+
+               foreach ( $batch as $batchItem ) {
+                       list( $src, ) = $batchItem;
+                       $files[$src] = $this->file->repo->getVirtualUrl( 'public' ) . '/' . rawurlencode( $src );
+               }
+
+               $result = $this->file->repo->fileExistsBatch( $files );
+               if ( in_array( null, $result, true ) ) {
+                       return Status::newFatal( 'backend-fail-internal',
+                               $this->file->repo->getBackend()->getName() );
+               }
+
+               foreach ( $batch as $batchItem ) {
+                       if ( $result[$batchItem[0]] ) {
+                               $newBatch[] = $batchItem;
+                       }
+               }
+
+               return Status::newGood( $newBatch );
+       }
+}
diff --git a/includes/filerepo/file/LocalFileLockError.php b/includes/filerepo/file/LocalFileLockError.php
new file mode 100644 (file)
index 0000000..7cfc8c2
--- /dev/null
@@ -0,0 +1,37 @@
+<?php
+/**
+ * Local file in the wiki's own database.
+ *
+ * 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 FileAbstraction
+ */
+
+class LocalFileLockError extends ErrorPageError {
+       public function __construct( Status $status ) {
+               parent::__construct(
+                       'actionfailed',
+                       $status->getMessage()
+               );
+       }
+
+       public function report() {
+               global $wgOut;
+               $wgOut->setStatusCode( 429 );
+               parent::report();
+       }
+}
diff --git a/includes/filerepo/file/LocalFileMoveBatch.php b/includes/filerepo/file/LocalFileMoveBatch.php
new file mode 100644 (file)
index 0000000..5594004
--- /dev/null
@@ -0,0 +1,339 @@
+<?php
+/**
+ * Local file in the wiki's own database.
+ *
+ * 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 FileAbstraction
+ */
+
+use Wikimedia\Rdbms\IDatabase;
+
+/**
+ * Helper class for file movement
+ * @ingroup FileAbstraction
+ */
+class LocalFileMoveBatch {
+       /** @var LocalFile */
+       protected $file;
+
+       /** @var Title */
+       protected $target;
+
+       protected $cur;
+
+       protected $olds;
+
+       protected $oldCount;
+
+       protected $archive;
+
+       /** @var IDatabase */
+       protected $db;
+
+       /**
+        * @param File $file
+        * @param Title $target
+        */
+       function __construct( File $file, Title $target ) {
+               $this->file = $file;
+               $this->target = $target;
+               $this->oldHash = $this->file->repo->getHashPath( $this->file->getName() );
+               $this->newHash = $this->file->repo->getHashPath( $this->target->getDBkey() );
+               $this->oldName = $this->file->getName();
+               $this->newName = $this->file->repo->getNameFromTitle( $this->target );
+               $this->oldRel = $this->oldHash . $this->oldName;
+               $this->newRel = $this->newHash . $this->newName;
+               $this->db = $file->getRepo()->getMasterDB();
+       }
+
+       /**
+        * Add the current image to the batch
+        */
+       public function addCurrent() {
+               $this->cur = [ $this->oldRel, $this->newRel ];
+       }
+
+       /**
+        * Add the old versions of the image to the batch
+        * @return string[] List of archive names from old versions
+        */
+       public function addOlds() {
+               $archiveBase = 'archive';
+               $this->olds = [];
+               $this->oldCount = 0;
+               $archiveNames = [];
+
+               $result = $this->db->select( 'oldimage',
+                       [ 'oi_archive_name', 'oi_deleted' ],
+                       [ 'oi_name' => $this->oldName ],
+                       __METHOD__,
+                       [ 'LOCK IN SHARE MODE' ] // ignore snapshot
+               );
+
+               foreach ( $result as $row ) {
+                       $archiveNames[] = $row->oi_archive_name;
+                       $oldName = $row->oi_archive_name;
+                       $bits = explode( '!', $oldName, 2 );
+
+                       if ( count( $bits ) != 2 ) {
+                               wfDebug( "Old file name missing !: '$oldName' \n" );
+                               continue;
+                       }
+
+                       list( $timestamp, $filename ) = $bits;
+
+                       if ( $this->oldName != $filename ) {
+                               wfDebug( "Old file name doesn't match: '$oldName' \n" );
+                               continue;
+                       }
+
+                       $this->oldCount++;
+
+                       // Do we want to add those to oldCount?
+                       if ( $row->oi_deleted & File::DELETED_FILE ) {
+                               continue;
+                       }
+
+                       $this->olds[] = [
+                               "{$archiveBase}/{$this->oldHash}{$oldName}",
+                               "{$archiveBase}/{$this->newHash}{$timestamp}!{$this->newName}"
+                       ];
+               }
+
+               return $archiveNames;
+       }
+
+       /**
+        * Perform the move.
+        * @return Status
+        */
+       public function execute() {
+               $repo = $this->file->repo;
+               $status = $repo->newGood();
+               $destFile = wfLocalFile( $this->target );
+
+               $this->file->lock();
+               $destFile->lock(); // quickly fail if destination is not available
+
+               $triplets = $this->getMoveTriplets();
+               $checkStatus = $this->removeNonexistentFiles( $triplets );
+               if ( !$checkStatus->isGood() ) {
+                       $destFile->unlock();
+                       $this->file->unlock();
+                       $status->merge( $checkStatus ); // couldn't talk to file backend
+                       return $status;
+               }
+               $triplets = $checkStatus->value;
+
+               // Verify the file versions metadata in the DB.
+               $statusDb = $this->verifyDBUpdates();
+               if ( !$statusDb->isGood() ) {
+                       $destFile->unlock();
+                       $this->file->unlock();
+                       $statusDb->setOK( false );
+
+                       return $statusDb;
+               }
+
+               if ( !$repo->hasSha1Storage() ) {
+                       // Copy the files into their new location.
+                       // If a prior process fataled copying or cleaning up files we tolerate any
+                       // of the existing files if they are identical to the ones being stored.
+                       $statusMove = $repo->storeBatch( $triplets, FileRepo::OVERWRITE_SAME );
+                       wfDebugLog( 'imagemove', "Moved files for {$this->file->getName()}: " .
+                               "{$statusMove->successCount} successes, {$statusMove->failCount} failures" );
+                       if ( !$statusMove->isGood() ) {
+                               // Delete any files copied over (while the destination is still locked)
+                               $this->cleanupTarget( $triplets );
+                               $destFile->unlock();
+                               $this->file->unlock();
+                               wfDebugLog( 'imagemove', "Error in moving files: "
+                                       . $statusMove->getWikiText( false, false, 'en' ) );
+                               $statusMove->setOK( false );
+
+                               return $statusMove;
+                       }
+                       $status->merge( $statusMove );
+               }
+
+               // Rename the file versions metadata in the DB.
+               $this->doDBUpdates();
+
+               wfDebugLog( 'imagemove', "Renamed {$this->file->getName()} in database: " .
+                       "{$statusDb->successCount} successes, {$statusDb->failCount} failures" );
+
+               $destFile->unlock();
+               $this->file->unlock();
+
+               // Everything went ok, remove the source files
+               $this->cleanupSource( $triplets );
+
+               $status->merge( $statusDb );
+
+               return $status;
+       }
+
+       /**
+        * Verify the database updates and return a new Status indicating how
+        * many rows would be updated.
+        *
+        * @return Status
+        */
+       protected function verifyDBUpdates() {
+               $repo = $this->file->repo;
+               $status = $repo->newGood();
+               $dbw = $this->db;
+
+               $hasCurrent = $dbw->lockForUpdate(
+                       'image',
+                       [ 'img_name' => $this->oldName ],
+                       __METHOD__
+               );
+               $oldRowCount = $dbw->lockForUpdate(
+                       'oldimage',
+                       [ 'oi_name' => $this->oldName ],
+                       __METHOD__
+               );
+
+               if ( $hasCurrent ) {
+                       $status->successCount++;
+               } else {
+                       $status->failCount++;
+               }
+               $status->successCount += $oldRowCount;
+               // T36934: oldCount is based on files that actually exist.
+               // There may be more DB rows than such files, in which case $affected
+               // can be greater than $total. We use max() to avoid negatives here.
+               $status->failCount += max( 0, $this->oldCount - $oldRowCount );
+               if ( $status->failCount ) {
+                       $status->error( 'imageinvalidfilename' );
+               }
+
+               return $status;
+       }
+
+       /**
+        * Do the database updates and return a new Status indicating how
+        * many rows where updated.
+        */
+       protected function doDBUpdates() {
+               $dbw = $this->db;
+
+               // Update current image
+               $dbw->update(
+                       'image',
+                       [ 'img_name' => $this->newName ],
+                       [ 'img_name' => $this->oldName ],
+                       __METHOD__
+               );
+
+               // Update old images
+               $dbw->update(
+                       'oldimage',
+                       [
+                               'oi_name' => $this->newName,
+                               'oi_archive_name = ' . $dbw->strreplace( 'oi_archive_name',
+                                       $dbw->addQuotes( $this->oldName ), $dbw->addQuotes( $this->newName ) ),
+                       ],
+                       [ 'oi_name' => $this->oldName ],
+                       __METHOD__
+               );
+       }
+
+       /**
+        * Generate triplets for FileRepo::storeBatch().
+        * @return array[]
+        */
+       protected function getMoveTriplets() {
+               $moves = array_merge( [ $this->cur ], $this->olds );
+               $triplets = []; // The format is: (srcUrl, destZone, destUrl)
+
+               foreach ( $moves as $move ) {
+                       // $move: (oldRelativePath, newRelativePath)
+                       $srcUrl = $this->file->repo->getVirtualUrl() . '/public/' . rawurlencode( $move[0] );
+                       $triplets[] = [ $srcUrl, 'public', $move[1] ];
+                       wfDebugLog(
+                               'imagemove',
+                               "Generated move triplet for {$this->file->getName()}: {$srcUrl} :: public :: {$move[1]}"
+                       );
+               }
+
+               return $triplets;
+       }
+
+       /**
+        * Removes non-existent files from move batch.
+        * @param array $triplets
+        * @return Status
+        */
+       protected function removeNonexistentFiles( $triplets ) {
+               $files = [];
+
+               foreach ( $triplets as $file ) {
+                       $files[$file[0]] = $file[0];
+               }
+
+               $result = $this->file->repo->fileExistsBatch( $files );
+               if ( in_array( null, $result, true ) ) {
+                       return Status::newFatal( 'backend-fail-internal',
+                               $this->file->repo->getBackend()->getName() );
+               }
+
+               $filteredTriplets = [];
+               foreach ( $triplets as $file ) {
+                       if ( $result[$file[0]] ) {
+                               $filteredTriplets[] = $file;
+                       } else {
+                               wfDebugLog( 'imagemove', "File {$file[0]} does not exist" );
+                       }
+               }
+
+               return Status::newGood( $filteredTriplets );
+       }
+
+       /**
+        * Cleanup a partially moved array of triplets by deleting the target
+        * files. Called if something went wrong half way.
+        * @param array[] $triplets
+        */
+       protected function cleanupTarget( $triplets ) {
+               // Create dest pairs from the triplets
+               $pairs = [];
+               foreach ( $triplets as $triplet ) {
+                       // $triplet: (old source virtual URL, dst zone, dest rel)
+                       $pairs[] = [ $triplet[1], $triplet[2] ];
+               }
+
+               $this->file->repo->cleanupBatch( $pairs );
+       }
+
+       /**
+        * Cleanup a fully moved array of triplets by deleting the source files.
+        * Called at the end of the move process if everything else went ok.
+        * @param array[] $triplets
+        */
+       protected function cleanupSource( $triplets ) {
+               // Create source file names from the triplets
+               $files = [];
+               foreach ( $triplets as $triplet ) {
+                       $files[] = $triplet[0];
+               }
+
+               $this->file->repo->cleanupBatch( $files );
+       }
+}
diff --git a/includes/filerepo/file/LocalFileRestoreBatch.php b/includes/filerepo/file/LocalFileRestoreBatch.php
new file mode 100644 (file)
index 0000000..8dedbec
--- /dev/null
@@ -0,0 +1,427 @@
+<?php
+/**
+ * Local file in the wiki's own database.
+ *
+ * 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 FileAbstraction
+ */
+
+use MediaWiki\MediaWikiServices;
+
+/**
+ * Helper class for file undeletion
+ * @ingroup FileAbstraction
+ */
+class LocalFileRestoreBatch {
+       /** @var LocalFile */
+       private $file;
+
+       /** @var string[] List of file IDs to restore */
+       private $cleanupBatch;
+
+       /** @var string[] List of file IDs to restore */
+       private $ids;
+
+       /** @var bool Add all revisions of the file */
+       private $all;
+
+       /** @var bool Whether to remove all settings for suppressed fields */
+       private $unsuppress = false;
+
+       /**
+        * @param File $file
+        * @param bool $unsuppress
+        */
+       function __construct( File $file, $unsuppress = false ) {
+               $this->file = $file;
+               $this->cleanupBatch = [];
+               $this->ids = [];
+               $this->unsuppress = $unsuppress;
+       }
+
+       /**
+        * Add a file by ID
+        * @param int $fa_id
+        */
+       public function addId( $fa_id ) {
+               $this->ids[] = $fa_id;
+       }
+
+       /**
+        * Add a whole lot of files by ID
+        * @param int[] $ids
+        */
+       public function addIds( $ids ) {
+               $this->ids = array_merge( $this->ids, $ids );
+       }
+
+       /**
+        * Add all revisions of the file
+        */
+       public function addAll() {
+               $this->all = true;
+       }
+
+       /**
+        * Run the transaction, except the cleanup batch.
+        * The cleanup batch should be run in a separate transaction, because it locks different
+        * rows and there's no need to keep the image row locked while it's acquiring those locks
+        * The caller may have its own transaction open.
+        * So we save the batch and let the caller call cleanup()
+        * @return Status
+        */
+       public function execute() {
+               /** @var Language */
+               global $wgLang;
+
+               $repo = $this->file->getRepo();
+               if ( !$this->all && !$this->ids ) {
+                       // Do nothing
+                       return $repo->newGood();
+               }
+
+               $lockOwnsTrx = $this->file->lock();
+
+               $dbw = $this->file->repo->getMasterDB();
+
+               $commentStore = MediaWikiServices::getInstance()->getCommentStore();
+               $actorMigration = ActorMigration::newMigration();
+
+               $status = $this->file->repo->newGood();
+
+               $exists = (bool)$dbw->selectField( 'image', '1',
+                       [ 'img_name' => $this->file->getName() ],
+                       __METHOD__,
+                       // The lock() should already prevents changes, but this still may need
+                       // to bypass any transaction snapshot. However, if lock() started the
+                       // trx (which it probably did) then snapshot is post-lock and up-to-date.
+                       $lockOwnsTrx ? [] : [ 'LOCK IN SHARE MODE' ]
+               );
+
+               // Fetch all or selected archived revisions for the file,
+               // sorted from the most recent to the oldest.
+               $conditions = [ 'fa_name' => $this->file->getName() ];
+
+               if ( !$this->all ) {
+                       $conditions['fa_id'] = $this->ids;
+               }
+
+               $arFileQuery = ArchivedFile::getQueryInfo();
+               $result = $dbw->select(
+                       $arFileQuery['tables'],
+                       $arFileQuery['fields'],
+                       $conditions,
+                       __METHOD__,
+                       [ 'ORDER BY' => 'fa_timestamp DESC' ],
+                       $arFileQuery['joins']
+               );
+
+               $idsPresent = [];
+               $storeBatch = [];
+               $insertBatch = [];
+               $insertCurrent = false;
+               $deleteIds = [];
+               $first = true;
+               $archiveNames = [];
+
+               foreach ( $result as $row ) {
+                       $idsPresent[] = $row->fa_id;
+
+                       if ( $row->fa_name != $this->file->getName() ) {
+                               $status->error( 'undelete-filename-mismatch', $wgLang->timeanddate( $row->fa_timestamp ) );
+                               $status->failCount++;
+                               continue;
+                       }
+
+                       if ( $row->fa_storage_key == '' ) {
+                               // Revision was missing pre-deletion
+                               $status->error( 'undelete-bad-store-key', $wgLang->timeanddate( $row->fa_timestamp ) );
+                               $status->failCount++;
+                               continue;
+                       }
+
+                       $deletedRel = $repo->getDeletedHashPath( $row->fa_storage_key ) .
+                               $row->fa_storage_key;
+                       $deletedUrl = $repo->getVirtualUrl() . '/deleted/' . $deletedRel;
+
+                       if ( isset( $row->fa_sha1 ) ) {
+                               $sha1 = $row->fa_sha1;
+                       } else {
+                               // old row, populate from key
+                               $sha1 = LocalRepo::getHashFromKey( $row->fa_storage_key );
+                       }
+
+                       # Fix leading zero
+                       if ( strlen( $sha1 ) == 32 && $sha1[0] == '0' ) {
+                               $sha1 = substr( $sha1, 1 );
+                       }
+
+                       if ( is_null( $row->fa_major_mime ) || $row->fa_major_mime == 'unknown'
+                               || is_null( $row->fa_minor_mime ) || $row->fa_minor_mime == 'unknown'
+                               || is_null( $row->fa_media_type ) || $row->fa_media_type == 'UNKNOWN'
+                               || is_null( $row->fa_metadata )
+                       ) {
+                               // Refresh our metadata
+                               // Required for a new current revision; nice for older ones too. :)
+                               $props = RepoGroup::singleton()->getFileProps( $deletedUrl );
+                       } else {
+                               $props = [
+                                       'minor_mime' => $row->fa_minor_mime,
+                                       'major_mime' => $row->fa_major_mime,
+                                       'media_type' => $row->fa_media_type,
+                                       'metadata' => $row->fa_metadata
+                               ];
+                       }
+
+                       $comment = $commentStore->getComment( 'fa_description', $row );
+                       $user = User::newFromAnyId( $row->fa_user, $row->fa_user_text, $row->fa_actor );
+                       if ( $first && !$exists ) {
+                               // This revision will be published as the new current version
+                               $destRel = $this->file->getRel();
+                               $commentFields = $commentStore->insert( $dbw, 'img_description', $comment );
+                               $actorFields = $actorMigration->getInsertValues( $dbw, 'img_user', $user );
+                               $insertCurrent = [
+                                       'img_name' => $row->fa_name,
+                                       'img_size' => $row->fa_size,
+                                       'img_width' => $row->fa_width,
+                                       'img_height' => $row->fa_height,
+                                       'img_metadata' => $props['metadata'],
+                                       'img_bits' => $row->fa_bits,
+                                       'img_media_type' => $props['media_type'],
+                                       'img_major_mime' => $props['major_mime'],
+                                       'img_minor_mime' => $props['minor_mime'],
+                                       'img_timestamp' => $row->fa_timestamp,
+                                       'img_sha1' => $sha1
+                               ] + $commentFields + $actorFields;
+
+                               // The live (current) version cannot be hidden!
+                               if ( !$this->unsuppress && $row->fa_deleted ) {
+                                       $status->fatal( 'undeleterevdel' );
+                                       $this->file->unlock();
+                                       return $status;
+                               }
+                       } else {
+                               $archiveName = $row->fa_archive_name;
+
+                               if ( $archiveName == '' ) {
+                                       // This was originally a current version; we
+                                       // have to devise a new archive name for it.
+                                       // Format is <timestamp of archiving>!<name>
+                                       $timestamp = wfTimestamp( TS_UNIX, $row->fa_deleted_timestamp );
+
+                                       do {
+                                               $archiveName = wfTimestamp( TS_MW, $timestamp ) . '!' . $row->fa_name;
+                                               $timestamp++;
+                                       } while ( isset( $archiveNames[$archiveName] ) );
+                               }
+
+                               $archiveNames[$archiveName] = true;
+                               $destRel = $this->file->getArchiveRel( $archiveName );
+                               $insertBatch[] = [
+                                       'oi_name' => $row->fa_name,
+                                       'oi_archive_name' => $archiveName,
+                                       'oi_size' => $row->fa_size,
+                                       'oi_width' => $row->fa_width,
+                                       'oi_height' => $row->fa_height,
+                                       'oi_bits' => $row->fa_bits,
+                                       'oi_timestamp' => $row->fa_timestamp,
+                                       'oi_metadata' => $props['metadata'],
+                                       'oi_media_type' => $props['media_type'],
+                                       'oi_major_mime' => $props['major_mime'],
+                                       'oi_minor_mime' => $props['minor_mime'],
+                                       'oi_deleted' => $this->unsuppress ? 0 : $row->fa_deleted,
+                                       'oi_sha1' => $sha1
+                               ] + $commentStore->insert( $dbw, 'oi_description', $comment )
+                               + $actorMigration->getInsertValues( $dbw, 'oi_user', $user );
+                       }
+
+                       $deleteIds[] = $row->fa_id;
+
+                       if ( !$this->unsuppress && $row->fa_deleted & File::DELETED_FILE ) {
+                               // private files can stay where they are
+                               $status->successCount++;
+                       } else {
+                               $storeBatch[] = [ $deletedUrl, 'public', $destRel ];
+                               $this->cleanupBatch[] = $row->fa_storage_key;
+                       }
+
+                       $first = false;
+               }
+
+               unset( $result );
+
+               // Add a warning to the status object for missing IDs
+               $missingIds = array_diff( $this->ids, $idsPresent );
+
+               foreach ( $missingIds as $id ) {
+                       $status->error( 'undelete-missing-filearchive', $id );
+               }
+
+               if ( !$repo->hasSha1Storage() ) {
+                       // Remove missing files from batch, so we don't get errors when undeleting them
+                       $checkStatus = $this->removeNonexistentFiles( $storeBatch );
+                       if ( !$checkStatus->isGood() ) {
+                               $status->merge( $checkStatus );
+                               return $status;
+                       }
+                       $storeBatch = $checkStatus->value;
+
+                       // Run the store batch
+                       // Use the OVERWRITE_SAME flag to smooth over a common error
+                       $storeStatus = $this->file->repo->storeBatch( $storeBatch, FileRepo::OVERWRITE_SAME );
+                       $status->merge( $storeStatus );
+
+                       if ( !$status->isGood() ) {
+                               // Even if some files could be copied, fail entirely as that is the
+                               // easiest thing to do without data loss
+                               $this->cleanupFailedBatch( $storeStatus, $storeBatch );
+                               $status->setOK( false );
+                               $this->file->unlock();
+
+                               return $status;
+                       }
+               }
+
+               // Run the DB updates
+               // Because we have locked the image row, key conflicts should be rare.
+               // If they do occur, we can roll back the transaction at this time with
+               // no data loss, but leaving unregistered files scattered throughout the
+               // public zone.
+               // This is not ideal, which is why it's important to lock the image row.
+               if ( $insertCurrent ) {
+                       $dbw->insert( 'image', $insertCurrent, __METHOD__ );
+               }
+
+               if ( $insertBatch ) {
+                       $dbw->insert( 'oldimage', $insertBatch, __METHOD__ );
+               }
+
+               if ( $deleteIds ) {
+                       $dbw->delete( 'filearchive',
+                               [ 'fa_id' => $deleteIds ],
+                               __METHOD__ );
+               }
+
+               // If store batch is empty (all files are missing), deletion is to be considered successful
+               if ( $status->successCount > 0 || !$storeBatch || $repo->hasSha1Storage() ) {
+                       if ( !$exists ) {
+                               wfDebug( __METHOD__ . " restored {$status->successCount} items, creating a new current\n" );
+
+                               DeferredUpdates::addUpdate( SiteStatsUpdate::factory( [ 'images' => 1 ] ) );
+
+                               $this->file->purgeEverything();
+                       } else {
+                               wfDebug( __METHOD__ . " restored {$status->successCount} as archived versions\n" );
+                               $this->file->purgeDescription();
+                       }
+               }
+
+               $this->file->unlock();
+
+               return $status;
+       }
+
+       /**
+        * Removes non-existent files from a store batch.
+        * @param array $triplets
+        * @return Status
+        */
+       protected function removeNonexistentFiles( $triplets ) {
+               $files = $filteredTriplets = [];
+               foreach ( $triplets as $file ) {
+                       $files[$file[0]] = $file[0];
+               }
+
+               $result = $this->file->repo->fileExistsBatch( $files );
+               if ( in_array( null, $result, true ) ) {
+                       return Status::newFatal( 'backend-fail-internal',
+                               $this->file->repo->getBackend()->getName() );
+               }
+
+               foreach ( $triplets as $file ) {
+                       if ( $result[$file[0]] ) {
+                               $filteredTriplets[] = $file;
+                       }
+               }
+
+               return Status::newGood( $filteredTriplets );
+       }
+
+       /**
+        * Removes non-existent files from a cleanup batch.
+        * @param string[] $batch
+        * @return string[]
+        */
+       protected function removeNonexistentFromCleanup( $batch ) {
+               $files = $newBatch = [];
+               $repo = $this->file->repo;
+
+               foreach ( $batch as $file ) {
+                       $files[$file] = $repo->getVirtualUrl( 'deleted' ) . '/' .
+                               rawurlencode( $repo->getDeletedHashPath( $file ) . $file );
+               }
+
+               $result = $repo->fileExistsBatch( $files );
+
+               foreach ( $batch as $file ) {
+                       if ( $result[$file] ) {
+                               $newBatch[] = $file;
+                       }
+               }
+
+               return $newBatch;
+       }
+
+       /**
+        * Delete unused files in the deleted zone.
+        * This should be called from outside the transaction in which execute() was called.
+        * @return Status
+        */
+       public function cleanup() {
+               if ( !$this->cleanupBatch ) {
+                       return $this->file->repo->newGood();
+               }
+
+               $this->cleanupBatch = $this->removeNonexistentFromCleanup( $this->cleanupBatch );
+
+               $status = $this->file->repo->cleanupDeletedBatch( $this->cleanupBatch );
+
+               return $status;
+       }
+
+       /**
+        * Cleanup a failed batch. The batch was only partially successful, so
+        * rollback by removing all items that were successfully copied.
+        *
+        * @param Status $storeStatus
+        * @param array[] $storeBatch
+        */
+       protected function cleanupFailedBatch( $storeStatus, $storeBatch ) {
+               $cleanupBatch = [];
+
+               foreach ( $storeStatus->success as $i => $success ) {
+                       // Check if this item of the batch was successfully copied
+                       if ( $success ) {
+                               // Item was successfully copied and needs to be removed again
+                               // Extract ($dstZone, $dstRel) from the batch
+                               $cleanupBatch[] = [ $storeBatch[$i][1], $storeBatch[$i][2] ];
+                       }
+               }
+               $this->file->repo->cleanupBatch( $cleanupBatch );
+       }
+}
diff --git a/includes/htmlform/HTMLFormActionFieldLayout.php b/includes/htmlform/HTMLFormActionFieldLayout.php
new file mode 100644 (file)
index 0000000..2c08224
--- /dev/null
@@ -0,0 +1,16 @@
+<?php
+
+class HTMLFormActionFieldLayout extends OOUI\ActionFieldLayout {
+       use HTMLFormElement;
+
+       public function __construct( $fieldWidget, $buttonWidget = false, array $config = [] ) {
+               parent::__construct( $fieldWidget, $buttonWidget, $config );
+
+               // Traits
+               $this->initializeHTMLFormElement( $config );
+       }
+
+       protected function getJavaScriptClassName() {
+               return 'mw.htmlform.ActionFieldLayout';
+       }
+}
index fbef265..b77c17e 100644 (file)
@@ -33,33 +33,3 @@ trait HTMLFormElement {
                } );
        }
 }
-
-class HTMLFormFieldLayout extends OOUI\FieldLayout {
-       use HTMLFormElement;
-
-       public function __construct( $fieldWidget, array $config = [] ) {
-               parent::__construct( $fieldWidget, $config );
-
-               // Traits
-               $this->initializeHTMLFormElement( $config );
-       }
-
-       protected function getJavaScriptClassName() {
-               return 'mw.htmlform.FieldLayout';
-       }
-}
-
-class HTMLFormActionFieldLayout extends OOUI\ActionFieldLayout {
-       use HTMLFormElement;
-
-       public function __construct( $fieldWidget, $buttonWidget = false, array $config = [] ) {
-               parent::__construct( $fieldWidget, $buttonWidget, $config );
-
-               // Traits
-               $this->initializeHTMLFormElement( $config );
-       }
-
-       protected function getJavaScriptClassName() {
-               return 'mw.htmlform.ActionFieldLayout';
-       }
-}
diff --git a/includes/htmlform/HTMLFormFieldLayout.php b/includes/htmlform/HTMLFormFieldLayout.php
new file mode 100644 (file)
index 0000000..ce93db2
--- /dev/null
@@ -0,0 +1,16 @@
+<?php
+
+class HTMLFormFieldLayout extends OOUI\FieldLayout {
+       use HTMLFormElement;
+
+       public function __construct( $fieldWidget, array $config = [] ) {
+               parent::__construct( $fieldWidget, $config );
+
+               // Traits
+               $this->initializeHTMLFormElement( $config );
+       }
+
+       protected function getJavaScriptClassName() {
+               return 'mw.htmlform.FieldLayout';
+       }
+}
index ffdf5f8..d1f3c44 100644 (file)
@@ -172,6 +172,7 @@ class HTMLDateTimeField extends HTMLTextField {
                }
 
                if ( $this->mType === 'date' ) {
+                       $this->mParent->getOutput()->addModuleStyles( 'mediawiki.widgets.DateInputWidget.styles' );
                        return new MediaWiki\Widget\DateInputWidget( $params );
                } else {
                        return new MediaWiki\Widget\DateTimeInputWidget( $params );
index 03e479b..93f5363 100644 (file)
@@ -59,6 +59,7 @@ class HTMLFormFieldWithButton extends HTMLFormField {
                        'type' => $this->mButtonType,
                        'label' => $this->mButtonValue,
                        'flags' => $this->mButtonFlags,
+                       'id' => $this->mButtonId,
                ] + OOUI\Element::configFromHtmlAttributes(
                        $this->getAttributes( [ 'disabled', 'tabindex' ] )
                ) );
index 60c63d6..56589b0 100644 (file)
@@ -131,6 +131,9 @@ class HTMLTextField extends HTMLFormField {
                                case 'url':
                                        $type = $this->mParams['type'];
                                        break;
+                               case 'textwithbutton':
+                                       $type = $this->mParams['inputtype'] ?? 'text';
+                                       break;
                        }
                }
 
index a3a14d0..f155348 100644 (file)
@@ -46,7 +46,7 @@ class HttpRequestFactory {
                if ( !Http::$httpEngine ) {
                        Http::$httpEngine = 'guzzle';
                } elseif ( Http::$httpEngine == 'curl' && !function_exists( 'curl_init' ) ) {
-                       throw new DomainException( __METHOD__ . ': curl (https://secure.php.net/curl) is not ' .
+                       throw new DomainException( __METHOD__ . ': curl (https://www.php.net/curl) is not ' .
                           'installed, but Http::$httpEngine is set to "curl"' );
                }
 
@@ -64,7 +64,7 @@ class HttpRequestFactory {
                                        throw new DomainException( __METHOD__ . ': allow_url_fopen ' .
                                           'needs to be enabled for pure PHP http requests to ' .
                                           'work. If possible, curl should be used instead. See ' .
-                                          'https://secure.php.net/curl.'
+                                          'https://www.php.net/curl.'
                                        );
                                }
                                return new PhpHttpRequest( $url, $options, $caller, Profiler::instance() );
index 41ec673..466e3d8 100644 (file)
@@ -120,10 +120,7 @@ class WikiImporter {
                wfDebug( "IMPORT: $data\n" );
        }
 
-       public function notice( $msg /*, $param, ...*/ ) {
-               $params = func_get_args();
-               array_shift( $params );
-
+       public function notice( $msg, ...$params ) {
                if ( is_callable( $this->mNoticeCallback ) ) {
                        call_user_func( $this->mNoticeCallback, $msg, $params );
                } else { # No ImportReporter -> CLI
@@ -430,8 +427,7 @@ class WikiImporter {
                        }
                }
 
-               $args = func_get_args();
-               return Hooks::run( 'AfterImportPage', $args );
+               return Hooks::run( 'AfterImportPage', func_get_args() );
        }
 
        /**
@@ -486,8 +482,7 @@ class WikiImporter {
        private function pageOutCallback( $title, $foreignTitle, $revCount,
                        $sucCount, $pageInfo ) {
                if ( isset( $this->mPageOutCallback ) ) {
-                       $args = func_get_args();
-                       call_user_func_array( $this->mPageOutCallback, $args );
+                       call_user_func_array( $this->mPageOutCallback, func_get_args() );
                }
        }
 
index 7267ddd..c008333 100644 (file)
@@ -200,24 +200,23 @@ class CliInstaller extends Installer {
                $this->showMessage( 'config-install-step-done' );
        }
 
-       public function showMessage( $msg /*, ... */ ) {
-               echo $this->getMessageText( func_get_args() ) . "\n";
+       public function showMessage( $msg, ...$params ) {
+               echo $this->getMessageText( $msg, $params ) . "\n";
                flush();
        }
 
-       public function showError( $msg /*, ... */ ) {
-               echo "***{$this->getMessageText( func_get_args() )}***\n";
+       public function showError( $msg, ...$params ) {
+               echo "***{$this->getMessageText( $msg, $params )}***\n";
                flush();
        }
 
        /**
+        * @param string $msg
         * @param array $params
         *
         * @return string
         */
-       protected function getMessageText( $params ) {
-               $msg = array_shift( $params );
-
+       protected function getMessageText( $msg, $params ) {
                $text = wfMessage( $msg, $params )->parse();
 
                $text = preg_replace( '/<a href="(.*?)".*?>(.*?)<\/a>/', '$2 &lt;$1&gt;', $text );
index 6315de4..a219452 100644 (file)
@@ -37,8 +37,6 @@ abstract class DatabaseInstaller {
        /**
         * The Installer object.
         *
-        * @todo Naming this parent is confusing, 'installer' would be clearer.
-        *
         * @var WebInstaller
         */
        public $parent;
index 54ffa5a..b32be39 100644 (file)
@@ -1227,7 +1227,7 @@ abstract class DatabaseUpdater {
         */
        protected function rebuildLocalisationCache() {
                /**
-                * @var $cl RebuildLocalisationCache
+                * @var RebuildLocalisationCache $cl
                 */
                $cl = $this->maintenance->runChild(
                        RebuildLocalisationCache::class, 'rebuildLocalisationCache.php'
index ea022bb..9053f8d 100644 (file)
@@ -336,14 +336,16 @@ abstract class Installer {
         * The messages will be in wikitext format, which will be converted to an
         * output format such as HTML or text before being sent to the user.
         * @param string $msg
+        * @param mixed ...$params
         */
-       abstract public function showMessage( $msg /*, ... */ );
+       abstract public function showMessage( $msg, ...$params );
 
        /**
         * Same as showMessage(), but for displaying errors
         * @param string $msg
+        * @param mixed ...$params
         */
-       abstract public function showError( $msg /*, ... */ );
+       abstract public function showError( $msg, ...$params );
 
        /**
         * Show a message to the installing user by using a Status object
index e462220..6d62c43 100644 (file)
@@ -187,7 +187,7 @@ class MssqlInstaller extends DatabaseInstaller {
                        return $status;
                }
                /**
-                * @var $conn Database
+                * @var Database $conn
                 */
                $conn = $status->value;
 
@@ -241,7 +241,7 @@ class MssqlInstaller extends DatabaseInstaller {
                        return;
                }
                /**
-                * @var $conn Database
+                * @var Database $conn
                 */
                $conn = $status->value;
                $conn->selectDB( $this->getVar( 'wgDBname' ) );
index e9a2d03..74924d0 100644 (file)
@@ -128,7 +128,7 @@ class MysqlInstaller extends DatabaseInstaller {
                        return $status;
                }
                /**
-                * @var $conn Database
+                * @var Database $conn
                 */
                $conn = $status->value;
 
@@ -167,7 +167,7 @@ class MysqlInstaller extends DatabaseInstaller {
                        return;
                }
                /**
-                * @var $conn Database
+                * @var Database $conn
                 */
                $conn = $status->value;
                $conn->selectDB( $this->getVar( 'wgDBname' ) );
@@ -232,7 +232,7 @@ class MysqlInstaller extends DatabaseInstaller {
                $status = $this->getConnection();
 
                /**
-                * @var $conn Database
+                * @var Database $conn
                 */
                $conn = $status->value;
 
diff --git a/includes/installer/PhpBugTests.php b/includes/installer/PhpBugTests.php
deleted file mode 100644 (file)
index 4e1e365..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-<?php
-/**
- * Classes for self-contained tests for known bugs in 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
- */
-
-/**
- * @defgroup PHPBugTests PHP known bugs tests
- */
-/**
- * Test for PHP+libxml2 bug which breaks XML input subtly with certain versions.
- * Known fixed with PHP 5.2.9 + libxml2-2.7.3
- * @see https://bugs.php.net/bug.php?id=45996
- * @ingroup PHPBugTests
- */
-class PhpXmlBugTester {
-       private $parsedData = '';
-       public $ok = false;
-
-       public function __construct() {
-               $charData = '<b>c</b>';
-               $xml = '<a>' . htmlspecialchars( $charData ) . '</a>';
-
-               $parser = xml_parser_create();
-               xml_set_character_data_handler( $parser, [ $this, 'chardata' ] );
-               $parsedOk = xml_parse( $parser, $xml, true );
-               $this->ok = $parsedOk && ( $this->parsedData == $charData );
-       }
-
-       public function chardata( $parser, $data ) {
-               $this->parsedData .= $data;
-       }
-}
diff --git a/includes/installer/PhpXmlBugTester.php b/includes/installer/PhpXmlBugTester.php
new file mode 100644 (file)
index 0000000..4e1e365
--- /dev/null
@@ -0,0 +1,49 @@
+<?php
+/**
+ * Classes for self-contained tests for known bugs in 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
+ */
+
+/**
+ * @defgroup PHPBugTests PHP known bugs tests
+ */
+/**
+ * Test for PHP+libxml2 bug which breaks XML input subtly with certain versions.
+ * Known fixed with PHP 5.2.9 + libxml2-2.7.3
+ * @see https://bugs.php.net/bug.php?id=45996
+ * @ingroup PHPBugTests
+ */
+class PhpXmlBugTester {
+       private $parsedData = '';
+       public $ok = false;
+
+       public function __construct() {
+               $charData = '<b>c</b>';
+               $xml = '<a>' . htmlspecialchars( $charData ) . '</a>';
+
+               $parser = xml_parser_create();
+               xml_set_character_data_handler( $parser, [ $this, 'chardata' ] );
+               $parsedOk = xml_parse( $parser, $xml, true );
+               $this->ok = $parsedOk && ( $this->parsedData == $charData );
+       }
+
+       public function chardata( $parser, $data ) {
+               $this->parsedData .= $data;
+       }
+}
index f9ee319..21b6f39 100644 (file)
@@ -119,7 +119,7 @@ class PostgresInstaller extends DatabaseInstaller {
                        return $status;
                }
                /**
-                * @var $conn Database
+                * @var Database $conn
                 */
                $conn = $status->value;
 
@@ -190,7 +190,7 @@ class PostgresInstaller extends DatabaseInstaller {
 
                if ( $status->isOK() ) {
                        /**
-                        * @var $conn Database
+                        * @var Database $conn
                         */
                        $conn = $status->value;
                        $conn->clearFlag( DBO_TRX );
@@ -242,7 +242,7 @@ class PostgresInstaller extends DatabaseInstaller {
                                $status = $this->openPgConnection( 'create-schema' );
                                if ( $status->isOK() ) {
                                        /**
-                                        * @var $conn Database
+                                        * @var Database $conn
                                         */
                                        $conn = $status->value;
                                        $safeRole = $conn->addIdentifierQuotes( $this->getVar( 'wgDBuser' ) );
@@ -296,7 +296,7 @@ class PostgresInstaller extends DatabaseInstaller {
                        return false;
                }
                /**
-                * @var $conn Database
+                * @var Database $conn
                 */
                $conn = $status->value;
                $superuser = $this->getVar( '_InstallUser' );
@@ -645,7 +645,7 @@ class PostgresInstaller extends DatabaseInstaller {
                        return $status;
                }
                /**
-                * @var $conn Database
+                * @var Database $conn
                 */
                $conn = $status->value;
 
index 2d12e62..0a6be86 100644 (file)
@@ -374,13 +374,14 @@ class WebInstaller extends Installer {
         * Show an error message in a box. Parameters are like wfMessage(), or
         * alternatively, pass a Message object in.
         * @param string|Message $msg
+        * @param mixed ...$params
         */
-       public function showError( $msg /*...*/ ) {
+       public function showError( $msg, ...$params ) {
                if ( !( $msg instanceof Message ) ) {
-                       $args = func_get_args();
-                       array_shift( $args );
-                       $args = array_map( 'htmlspecialchars', $args );
-                       $msg = wfMessage( $msg, $args );
+                       $msg = wfMessage(
+                               $msg,
+                               array_map( 'htmlspecialchars', $params )
+                       );
                }
                $text = $msg->useDatabase( false )->plain();
                $this->output->addHTML( $this->getErrorBox( $text ) );
@@ -675,9 +676,7 @@ class WebInstaller extends Installer {
         * @param string $msg
         * @return string
         */
-       public function getHelpBox( $msg /*, ... */ ) {
-               $args = func_get_args();
-               array_shift( $args );
+       public function getHelpBox( $msg, ...$args ) {
                $args = array_map( 'htmlspecialchars', $args );
                $text = wfMessage( $msg, $args )->useDatabase( false )->plain();
                $html = $this->parse( $text, true );
@@ -693,10 +692,10 @@ class WebInstaller extends Installer {
        /**
         * Output a help box.
         * @param string $msg Key for wfMessage()
+        * @param mixed ...$params
         */
-       public function showHelpBox( $msg /*, ... */ ) {
-               $args = func_get_args();
-               $html = $this->getHelpBox( ...$args );
+       public function showHelpBox( $msg, ...$params ) {
+               $html = $this->getHelpBox( $msg, ...$params );
                $this->output->addHTML( $html );
        }
 
@@ -705,12 +704,11 @@ class WebInstaller extends Installer {
         * Output looks like a list.
         *
         * @param string $msg
+        * @param mixed ...$params
         */
-       public function showMessage( $msg /*, ... */ ) {
-               $args = func_get_args();
-               array_shift( $args );
+       public function showMessage( $msg, ...$params ) {
                $html = '<div class="config-message">' .
-                       $this->parse( wfMessage( $msg, $args )->useDatabase( false )->plain() ) .
+                       $this->parse( wfMessage( $msg, $params )->useDatabase( false )->plain() ) .
                        "</div>\n";
                $this->output->addHTML( $html );
        }
index 989d4ba..d04b0f2 100644 (file)
        "config-pcre-no-utf8": "<strong>فادح:</strong> يبدو أن وحدة PCRE في PHP يتم تجميعها بدون دعم PCRE_UTF8، \nيتطلب ميدياويكي دعم UTF-8 ليعمل بشكل صحيح.",
        "config-memory-raised": "<code>memory_limit</code> في PHP $1 ومرتفعة إلى $2.",
        "config-memory-bad": "<strong>تحذير:</strong> في PHP <code>memory_limit</code> $1،\nهذا على الارجح منخفض جدا;\nقد يفشل التثبيت!",
-       "config-apc": "تثبيت [https://secure.php.net/apc APC]",
-       "config-apcu": "[https://secure.php.net/apcu APCu] مثبت",
+       "config-apc": "تثبيت [https://www.php.net/apc APC]",
+       "config-apcu": "[https://www.php.net/apcu APCu] مثبت",
        "config-wincache": "تثبيت [https://www.iis.net/downloads/microsoft/wincache-extension WinCache]",
-       "config-no-cache-apcu": "<strong>تحذير:</strong> تعذر العثور على [https://secure.php.net/apcu APCu] أو [https://www.iis.net/downloads/microsoft/wincache-extension WinCache];\nكائن التخزين المؤقت غير ممكّّن.",
+       "config-no-cache-apcu": "<strong>تحذير:</strong> تعذر العثور على [https://www.php.net/apcu APCu] أو [https://www.iis.net/downloads/microsoft/wincache-extension WinCache];\nكائن التخزين المؤقت غير ممكّّن.",
        "config-mod-security": "<strong>تحذير:</strong> تم تمكين خادم الويب الخاص بك [https://modsecurity.org/ mod_security]/mod_security2، العديد من التكوينات الشائعة له سوف تتسبب في مشاكل لميدياويكي والبرامج الأخرى التي تسمح للمستخدمين بنشر محتوى عشوائي; \nإذا كان ذلك ممكنا، يجب تعطيل هذا، أو يمكنك الرجوع إلى [https://modsecurity.org/documentation/ توثيق mod_security] أو الاتصال بدعم مضيفك إذا واجهتك أخطاء عشوائية.",
        "config-diff3-bad": "أداة جنو diff3 لمقارنة النصوص غير موجودة. يمكنك تجاهل هذا الآن، لكن ربما تصادف تضاربات تحريرية أكثر من المعتاد.",
        "config-git": "العثور على برنامج التحكم في إصدار جت <code>$1</code>.",
        "config-type-oracle": "أوراكل",
        "config-type-mssql": "خادم SQL لميكروسوفت",
        "config-support-info": "ميدياويكي يدعم نظم قواعد البيانات التالية: $1 إذا كنت لا ترى نظام قاعدة البيانات الذي تحاول استخدامه مدرجًا أدناه، اتبع الإرشادات المرتبطة فوق لتمكين الدعم.",
-       "config-dbsupport-mysql": "* [{{int:version-db-mysql-url}} MySQL] هو الهدف الأساسي لميدياويكي وأفضل دعم له، يعمل ميدياويكي أيضا مع [{{int:version-db-mariadb-url}} MariaDB] و[{{int:version-db-percona-url}} Percona Server]، وهما متوافقان مع MySQL، ([https://secure.php.net/manual/en/mysqli.installation.php كيفية تجميع PHP مع دعم MySQL])",
-       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] هو نظام قاعدة بيانات مفتوح المصدر مشهور كبديل لـMySQL ([https://secure.php.net/manual/en/pgsql.installation.php كيفية تجميع PHP مع دعم PostgreSQL])",
-       "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite] هو نظام قاعدة بيانات خفيف مدعوم بشكل جيد. ([https://secure.php.net/manual/en/pdo.installation.php كيفية ترجمة PHP باستخدام دعم SQLite]، يستخدم PDO)",
-       "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] هي قاعدة بيانات مؤسسة تجارية. ([https://secure.php.net/manual/en/oci8.installation.php كيفية تجميع PHP مع دعم OCI8])",
-       "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] هي قاعدة بيانات مؤسسة تجارية لويندوز. ([https://secure.php.net/manual/en/sqlsrv.installation.php كيفية تجميع PHP مع دعم SQLSRV])",
+       "config-dbsupport-mysql": "* [{{int:version-db-mysql-url}} MySQL] هو الهدف الأساسي لميدياويكي وأفضل دعم له، يعمل ميدياويكي أيضا مع [{{int:version-db-mariadb-url}} MariaDB] و[{{int:version-db-percona-url}} Percona Server]، وهما متوافقان مع MySQL، ([https://www.php.net/manual/en/mysqli.installation.php كيفية تجميع PHP مع دعم MySQL])",
+       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] هو نظام قاعدة بيانات مفتوح المصدر مشهور كبديل لـMySQL ([https://www.php.net/manual/en/pgsql.installation.php كيفية تجميع PHP مع دعم PostgreSQL])",
+       "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite] هو نظام قاعدة بيانات خفيف مدعوم بشكل جيد. ([https://www.php.net/manual/en/pdo.installation.php كيفية ترجمة PHP باستخدام دعم SQLite]، يستخدم PDO)",
+       "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] هي قاعدة بيانات مؤسسة تجارية. ([https://www.php.net/manual/en/oci8.installation.php كيفية تجميع PHP مع دعم OCI8])",
+       "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] هي قاعدة بيانات مؤسسة تجارية لويندوز. ([https://www.php.net/manual/en/sqlsrv.installation.php كيفية تجميع PHP مع دعم SQLSRV])",
        "config-header-mysql": "إعدادات MariaDB/MySQL",
        "config-header-postgres": "إعدادات PostgreSQL",
        "config-header-sqlite": "إعدادات SQLite",
        "config-license-help": "يضع العديد من الويكيات العامة جميع المساهمات تحت [https://freedomdefined.org/Definition خصة حرة]، \nوهذا يساعد على خلق شعور بالملكية المجتمعية ويشجع على المساهمة طويلة الأجل، \nليس من الضروري عموما استخدام ويكي خاص أو شركة. \n\nإذا كنت تريد أن تكون قادرا على استخدام النص من ويكيبيديا، وتريد أن تكون ويكيبيديا قادرة على قبول النص الذي تم نسخه من الويكي الخاص بك، فيجب عليك اختيار <strong>{{int:config-license-cc-by-sa}}</strong>. \n\nاستخدمت ويكيبيديا في السابق رخصة جنو للوثائق الحرة، \nGFDL هو ترخيص صالح، ولكن من الصعب فهمه، \nمن الصعب أيضا إعادة استخدام المحتوى المرخص بموجب GFDL.",
        "config-email-settings": "إعدادات البريد الإلكتروني",
        "config-enable-email": "تمكين البريد الإلكتروني الصادرة",
-       "config-enable-email-help": "إذا كنت تريد إرسال بريد إلكتروني إلى العمل، [Config-dbsupport-oracle/manual/en/mail.configuration.php إعدات بريد PHP's] تحتاج لأن يتم تكوينها بشكل صحيح. إذا كنت لا تريد أيا من ميزات البريد الإلكتروني، يمكنك تعطيلها هنا.",
+       "config-enable-email-help": "إذا كنت تريد إرسال بريد إلكتروني إلى العمل، [https://www.php.net/manual/en/mail.configuration.php إعدات بريد PHP] تحتاج لأن يتم تكوينها بشكل صحيح. إذا كنت لا تريد أيا من ميزات البريد الإلكتروني، يمكنك تعطيلها هنا.",
        "config-email-user": "تفعيل البريد الإلكتروني من المستخدم إلى مستخدم آخر",
        "config-email-user-help": "يتيح لكل المستخدمين إرسال رسائل بريد إلكتروني إلى بعضهم البعض إذا فعَّلوا هذا الخيار في تفضيلاتهم.",
        "config-email-usertalk": "فعل إخطارات صفحات نقاش المستخدمين",
index 73929dd..9432999 100644 (file)
@@ -7,47 +7,47 @@
                        "Crucifunked"
                ]
        },
-       "config-desc": "L'instalador pa MediaWiki",
+       "config-desc": "L'instalador de MediaWiki",
        "config-title": "Instalación de MediaWiki $1",
        "config-information": "Información",
-       "config-localsettings-upgrade": "Detectose un ficheru <code>LocalSettings.php</code>.\nP'anovar esta instalación, escriba'l valor de\n<code>$wgUpgradeKey</code> nel cuadru d'abaxo.\nAlcontraralu en <code>LocalSettings.php</code>.",
-       "config-localsettings-cli-upgrade": "Deteutose un ficheru <code>LocalSettings.php</code>.\nP'anovar esta instalación, execute <code>update.php</code>",
+       "config-localsettings-upgrade": "Deteutóse un ficheru <code>LocalSettings.php</code>.\nP'anovar esta instalación, introduz el valor de <code>$wgUpgradeKey</code> na caxa d'embaxo.\nVas alcontralu en <code>LocalSettings.php</code>.",
+       "config-localsettings-cli-upgrade": "Deteutóse un ficheru <code>LocalSettings.php</code>.\nP'anovar esta instalación, executa <code>update.php</code>",
        "config-localsettings-key": "Clave d'anovamientu:",
        "config-localsettings-badkey": "La clave d'anovamientu que disti ye incorreuta.",
-       "config-upgrade-key-missing": "Deteutose una instalación esistente de MediaWiki.\nP'anovar esta instalación, ponga la llinia siguiente al final del ficheru <code>LocalSettings.php</code>:\n\n$1",
+       "config-upgrade-key-missing": "Deteutóse una instalación esistente de MediaWiki.\nP'anovar esta instalación, pon la llinia de darréu no baxero del ficheru <code>LocalSettings.php</code>:\n\n$1",
        "config-localsettings-incomplete": "Paez que'l ficheru <code>LocalSettings.php</code> esistente ta incompletu.\nLa variable $1 nun ta definida.\nCamude'l ficheru <code>LocalSettings.php</code> pa qu'esta variable quede definida y calque \"{{int:Config-continue}}\".",
        "config-localsettings-connection-error": "Alcontróse un error al conectar cola base de datos usando la configuración especificada en <code>LocalSettings.php</code>. Corrixa esta configuración y vuelva a intentalo.\n\n$1",
-       "config-session-error": "Error al aniciar sesión: $1",
+       "config-session-error": "Fallu al aniciar sesión: $1",
        "config-session-expired": "Paez que caducaron los sos datos de sesión.\nLes sesiones tan configuraes pa tener una duración de $1.\nPue incrementar esto configurando <code>session.gc_maxlifetime</code> en php.ini.\nReanicie'l procesu d'instalación.",
        "config-no-session": "¡Perdiéronse los sos datos de sesión!\nCompruebe php.ini y asegúrese de qu'en <code>session.save_path</code> ta definíu un direutoriu correutu.",
        "config-your-language": "La so llingua:",
-       "config-your-language-help": "Seleicione la llingua a emplegar nel procesu d'instalación.",
+       "config-your-language-help": "Esbilla la llingua a usar nel procesu d'instalación.",
        "config-wiki-language": "Llingua de la wiki:",
        "config-wiki-language-help": "Seleicione la llingua que s'usará preferentemente na wiki.",
        "config-back": "← Atrás",
        "config-continue": "Siguir →",
        "config-page-language": "Llingua",
-       "config-page-welcome": "¡Bienveníu a MediaWiki!",
+       "config-page-welcome": "¡Afáyate en MediaWiki!",
        "config-page-dbconnect": "Conectar cola base de datos",
        "config-page-upgrade": "Anovar instalación esistente",
        "config-page-dbsettings": "Configuración de la base de datos",
        "config-page-name": "Nome",
        "config-page-options": "Opciones",
        "config-page-install": "Instalar",
-       "config-page-complete": "¡Completo!",
+       "config-page-complete": "¡Completóse!",
        "config-page-restart": "Reaniciar la instalación",
        "config-page-readme": "Llei-me",
        "config-page-releasenotes": "Notes de la versión",
        "config-page-copying": "Copiar",
        "config-page-upgradedoc": "Anovando",
        "config-page-existingwiki": "Wiki esistente",
-       "config-help-restart": "¿Quier llimpiar tolos datos guardaos qu'escribió y reaniciar el procesu d'instalación?",
+       "config-help-restart": "¿Quies llimpiar tolos datos guardaos qu'introduxesti y reaniciar el procesu d'instalación?",
        "config-restart": "Sí, reanicialu",
        "config-welcome": "=== Comprobaciones del entornu ===\nAgora van facese unes comprobaciones básiques para ver si l'entornu ye afayadizu pa la instalación de MediaWiki.\nAlcuérdese d'incluir esta información si necesita encontu pa completar la instalación.",
        "config-copyright": "=== Drechos d'autor y condiciones d'usu ===\n\n$1\n\nEsti programa ye software llibre; puedes redistribuilu y/o camudalu baxo les condiciones de la llicencia pública xeneral GNU tal como la publica la Free Software Foundation; versión 2 o (como prefieras) cualquier versión posterior.\n\nEsti programa distribúese cola esperanza de que pueda ser útil, pero <strong>ensin garantía denguna</strong>; nin siquiera la garantía implícita de <strong>comercialidá</strong> o \n<strong>adautación a un fin determináu</strong>.\nVer la Llicencia pública xeneral GNU pa más detalles.\n\nHabríes de tener recibío <doclink href=Copying>una copia de la llicencia pública xeneral GNU</doclink> xunto con esti programa; sinón, escribi a la Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA, o [https://www.gnu.org/copyleft/gpl.html lléila en llinia].",
        "config-sidebar": "* [https://www.mediawiki.org/wiki/MediaWiki/gl Páxina principal de MediaWiki]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents Guía del usuariu]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents Guía del alministrador]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Entrugues frecuentes]\n----\n* <doclink href=Readme>Lléame</doclink>\n* <doclink href=ReleaseNotes>Notes de llanzamientu</doclink>\n* <doclink href=Copying>Copia</doclink>\n* <doclink href=UpgradeDoc>Anovamientu</doclink>",
-       "config-env-good": "Comprobóse l'entornu.\nPue instalar MediaWiki.",
-       "config-env-bad": "Comprobóse l'entornu.\nNun pue instalar MediaWiki.",
+       "config-env-good": "Comprobóse l'entornu.\nPues instalar MediaWiki.",
+       "config-env-bad": "Comprobóse l'entornu.\nNun pues instalar MediaWiki.",
        "config-env-php": "PHP $1 ta instaláu.",
        "config-env-hhvm": "HHVM $1 ta instaláu.",
        "config-unicode-using-intl": "Usando la [https://pecl.php.net/intl estensión intl PECL] pa la normalización Unicode.",
        "config-pcre-no-utf8": "<strong>Erru fatal:</strong> Paez que'l módulu PCRE de PHP foi compiláu ensin el soporte PCRE_UTF8.\nMediaWiki requier compatibilidá con UTF_8 pa furrular correutamente.",
        "config-memory-raised": "El parámetru <code>memory_limit</code> de PHP ye $1. Auméntase a $2.",
        "config-memory-bad": "<strong>Alvertencia:</strong>: el parámetru <code>memory_limit</code> de PHP ye $1.\nProbablemente sía demasiáu baxu.\n¡La instalación puede fallar!",
-       "config-apc": "[https://secure.php.net/apc APC] ta instaláu",
-       "config-apcu": "[https://secure.php.net/apcu APCu] ta instaláu",
+       "config-apc": "[https://www.php.net/apc APC] ta instaláu",
+       "config-apcu": "[https://www.php.net/apcu APCu] ta instaláu",
        "config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension WinCache] ta instaláu",
-       "config-no-cache-apcu": "<strong>Atención:</strong> Nun pudo alcontrase [https://secure.php.net/apcu APCu] o [https://www.iis.net/downloads/microsoft/wincache-extension WinCache].\nLa caché d'oxetos nun ta activada.",
+       "config-no-cache-apcu": "<strong>Atención:</strong> Nun pudo alcontrase [https://www.php.net/apcu APCu] o [https://www.iis.net/downloads/microsoft/wincache-extension WinCache].\nLa caché d'oxetos nun ta activada.",
        "config-mod-security": "<strong>Alvertencia:</strong> El to servidor web tien activáu [https://modsecurity.org/mod_security]/mod_security2 .Munches de les sos configuraciones comunes pueden causar problemes a MediaWiki o otru software que dexe a los usuarios publicar conteníu arbitrario. De ser posible, tendríes de desactivalo. Si non, consulta la  [https://modsecurity.org/documentation/ mod_security documentation] o contacta col alministrador del to servidor si atopes erros aleatorios.",
-       "config-diff3-bad": "Nun s'alcontró GNU diff3.",
+       "config-diff3-bad": "Nun s'alcontró la utilidá de comparanza de testos GNU diff3. Pues inorar esto pel momentu pero podríes tener problemes d'edición davezu.",
        "config-git": "Alcontróse'l software de control de versiones Git: <code>$1</code>.",
-       "config-git-bad": "Nun s'alcontró el software de control de versiones Git.",
+       "config-git-bad": "Nun s'alcontró el software de control de versiones Git. Pues inorar esto pel momentu. Decátate que Special:Version nun va amosase nos hashes de los unvios.",
        "config-imagemagick": "ImageMagick atopáu: <code>$1</code>.\nLa miniaturización d'imaxes habilitaráse si habilites les cargues.",
        "config-gd": "Atopóse una biblioteca de gráficos GD integrada.\nLa miniaturización d'imaxes habilitaráse si habilites les xubíes.",
        "config-no-scaling": "Nun s'atopó la biblioteca GD o ImageMagik.\nVa desactivase la miniaturización d'imaxes.",
        "config-using-32bit": "<strong>Atención:</strong> paez que'l sistema funciona con enteros de 32 bits. Esto ta [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:32-bit desaconseyáu].",
        "config-db-type": "Tipu de base de datos:",
        "config-db-host": "Servidor de la base de datos:",
-       "config-db-host-help": "Si'l to servidor de base de datos ta n'otru servidor, escribe'l nome del equipu o la so dirección IP equí.\n\nSi tas utilizando alojamiento web compartíu, el to provisor tendría de date'l nome correctu del servidor na so documentación.\n\nSi vas instalar nun servidor Windows y a utilizar MySQL, l'usu de \"localhost\" como nome del servidor puede nun #funcionar. Si ye asina, intenta poner \"127.0.0.1\" como dirección IP local.\n\nSi utilices PostgreSQL, dexa esti campu vacío pa conectase al traviés d'un socket de Unix.",
+       "config-db-host-help": "Si'l sirvidor de bases de datos ta nun sirvidor diferente, introduz equí la IP o'l nome del agospiu.\n\nSi tas usando un agospiu web compartíu, el so fornidor debió especificar el nome del agospiu na so documentación.\n\nSi tas usando MySQL, usar «localhost» pue nun funcionar pal nome del sirvidor. Si non, prueba «120.0.0.1» pa la direición IP llocal.\n\nSi tas usando PostgreSQL, dexa esti campu baleru pa coneutate per una ralura d'Unix.",
        "config-db-host-oracle": "TNS de la base de datos:",
        "config-db-host-oracle-help": "Escribe un [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm nome de conexón local] válidu; un archivu tnsnames.ora ten de ser visible pa esta instalación.<br />Si tas utilizando biblioteques de veceru 10g o más recién tamién puedes utilizar el métodu de asignación de nomes [http://download.oracle.com/docs/cd/Y11882_01/network.112/y10836/naming.htm Easy Connect].",
        "config-db-wiki-settings": "Identifica esta wiki",
-       "config-db-name": "Nome de base de datos:",
+       "config-db-name": "Nome de la base de datos (ensin guiones):",
        "config-db-name-help": "Escueye un nome qu'identifique la to wiki. Nun tien de contener espacios. \nSi tas utilizando agospiamientu web compartíu, el to provisor va date un nome específicu de base de datos por que lu utilices, o bien va dexate crear bases de datos al traviés d'un panel de control.",
        "config-db-name-oracle": "Esquema de la base de datos:",
        "config-db-account-oracle-warn": "Hai tres escenarios compatibles pa la instalación de Oracle como motor de base de datos:\n\nSi desees crear una cuenta de base de datos como parte del procesu d'instalación, por favor apurre una cuenta con rol SYSDBA como cuenta de base de datos pa la instalación y especifica les credenciales que quies tener pal accesu a la web a la cuenta; d'otra miente, puedes crear manualmente la cuenta d'accesu a la web y suministrar namái esa cuenta (si tien los permisos necesarios pa crear los oxetos d'esquema) o dar dos cuentes distintos, una con privilexos de creación y otra con accesu acutáu a la web\n\nLa secuencia de comandos (script) pa crear una cuenta colos privilexos necesarios puede atopase nel direutoriu \"maintenance/oracle/\" d'esta instalación. Ten en cuenta qu'utilizar una cuenta acutada va desactivar toles capacidaes de caltenimientu cola cuenta predeterminada.",
@@ -99,7 +99,7 @@
        "config-db-account-lock": "Usar el mesmu nome d'usuariu y contraseña demientres la operación normal",
        "config-db-wiki-account": "Cuenta d'usuariu pa operar normalmente",
        "config-db-wiki-help": "Escribe'l nome d'usuariu y la contraseña que se van utilizar p'aportar a la base de datos mientres la operación normal de la wiki.\nSi esta cuenta nun esiste y la cuenta d'instalación tien permisos bastante, va crease esta cuenta d'usuariu colos mínimos permisos necesarios pa operar normalmente la wiki.",
-       "config-db-prefix": "Prefixu de tables de la base de datos:",
+       "config-db-prefix": "Prefixu de tables de la base de datos (ensin guiones):",
        "config-db-prefix-help": "Si precises compartir una base de datos ente múltiples wikis, o ente MediaWiki y otra aplicación web, puedes optar por amestar un prefixu a tolos nomes de tabla pa evitar conflictos.\nNun utilices espacios.\n\nDe normal déxase esti campu vacío.",
        "config-mysql-old": "Precísase MySQL $1 o posterior. Tienes $2.",
        "config-db-port": "Puertu de la base de datos:",
        "config-oracle-def-ts": "Espaciu de tables predetermináu:",
        "config-oracle-temp-ts": "Espaciu de tables temporal:",
        "config-type-mysql": "MariaDB, MySQL o compatible",
+       "config-type-postgres": "PostgreSQL",
+       "config-type-sqlite": "SQLite",
+       "config-type-oracle": "Oracle",
        "config-type-mssql": "Microsoft SQL Server",
        "config-support-info": "MediaWiki ye compatible colos siguientes sistemes de bases de datos:\n\n$1\n\nSi nun atopes na llista el sistema de base de datos que tas intentando utilizar, sigue les instrucciones enllazaes enriba p'activar la compatibilidá.",
-       "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] ye un sistema comercial de base de datos empresariales pa Windows. ([https://secure.php.net/manual/en/sqlsrv.installation.php Cómo compilar PHP con compatibilidá pa SQLSRV])",
-       "config-header-mysql": "Configuración de MariaDB/MySQL",
-       "config-header-postgres": "Configuración de PostgreSQL",
-       "config-header-sqlite": "Configuración de SQLite",
-       "config-header-oracle": "Configuración d'Oracle",
-       "config-header-mssql": "Configuración de Microsoft SQL Server",
+       "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] ye un sistema comercial de base de datos empresariales pa Windows. ([https://www.php.net/manual/en/sqlsrv.installation.php Cómo compilar PHP con compatibilidá pa SQLSRV])",
+       "config-header-mysql": "Axustes de MariaDB/MySQL",
+       "config-header-postgres": "Axustes de PostgreSQL",
+       "config-header-sqlite": "Axustes de SQLite",
+       "config-header-oracle": "Axustes d'Oracle",
+       "config-header-mssql": "Axustes de Microsoft SQL Server",
        "config-invalid-db-type": "Triba non válida de base de datos.",
-       "config-missing-db-name": "Tienes d'introducir un valor pa «{{int:config-db-name}}».",
-       "config-missing-db-host": "Tienes d'escribir un valor pa «{{int:config-db-host}}».",
-       "config-missing-db-server-oracle": "Tienes d'escribir un valor pa «{{int:config-db-host-oracle}}».",
+       "config-missing-db-name": "Has introducir un valor pa «{{int:config-db-name}}».",
+       "config-missing-db-host": "Has introducir un valor pa «{{int:config-db-host}}».",
+       "config-missing-db-server-oracle": "Has introducir un valor pa «{{int:config-db-host-oracle}}».",
        "config-invalid-db-server-oracle": "TNS inválidu pa la base de datos «$1».\nUsa una cadena «TNS Name» o «Easy Connect» ([http://docs.oracle.com/cd/E11882_01/network.112/e10836/naming.htm Métodos de nomenclatura d'Oracle]).",
        "config-invalid-db-name": "Nome inválidu de la base de datos «$1».\nUsa sólo lletres ASCII (a-z, A-Z), númberos (0-9), guiones baxos (_) y guiones (-).",
        "config-invalid-db-prefix": "Prefixu inválidu pa la base de datos «$1».\nUsa sólo lletres ASCII (a-z, A-Z), númberos (0-9), guiones baxos (_) y guiones (-).",
        "config-connection-error": "$1.\n\nComprueba'l sirvidor, el nome d'usuariu y la contraseña, y tenta nuevamente.",
        "config-invalid-schema": "Esquema inválidu «$1» pa MediaWiki.\nUsa sólo lletres ASCII (a-z, A-Z), númberos (0-9) y guiones baxos (_).",
-       "config-db-sys-create-oracle": "L'instalador sólo almite l'emplegu d'una cuenta SYSDBA pa crear una cuenta nueva.",
+       "config-db-sys-create-oracle": "L'instalador namái sofita l'usu d'una cuenta SYSDBA pa la creación d'otra cuenta nueva.",
        "config-db-sys-user-exists-oracle": "La cuenta d'usuariu «$1» yá esiste. ¡SYSDBA sólo puede utilizase pa crear una nueva cuenta!",
        "config-postgres-old": "Ríquese PostgreSQL $1 o posterior. Tienes la versión $2.",
        "config-mssql-old": "Ríquese Microsoft SQL Server $1 o posterior. Tienes la versión $2.",
        "config-db-web-create": "Crear la cuenta si nun esiste yá",
        "config-db-web-no-create-privs": "La cuenta qu'especificasti pa la instalación nun tien permisos abondo pa crear una cuenta.\nLa cuenta qu'especifiques equí yá tien d'esistir.",
        "config-mysql-engine": "Motor d'almacenamientu:",
-       "config-mysql-innodb": "InnoDB (recomendao)",
+       "config-mysql-innodb": "InnoDB (aconséyase)",
        "config-mysql-myisam": "MyISAM",
        "config-mssql-auth": "Triba d'autenticación:",
        "config-mssql-sqlauth": "Autenticación de SQL Server",
        "config-admin-password-confirm": "Repiti la contraseña:",
        "config-admin-help": "Escribe equí'l nome d'usuariu que desees, como por casu \"Nel Bloggs\".\nEsti ye'l nome que vas usar pa entrar na wiki.",
        "config-admin-name-blank": "Escribe'l nome d'usuariu d'un alministrador.",
+       "config-admin-password-mismatch": "Les dos contraseñes qu'introduxesti nun concasen.",
        "config-optional-skip": "Yá toi aburríu, namái instala la wiki.",
        "config-profile-wiki": "Wiki públicu",
        "config-profile-no-anon": "Ríquese crear una cuenta",
        "config-license-cc-choose": "Escoyer una llicencia Creative Commons  personalizada",
        "config-email-settings": "Configuración de corréu electrónicu",
        "config-enable-email": "Activar el corréu electrónicu de salida",
-       "config-enable-email-help": "Si quies que'l corréu electrónicu funcione, les [https://secure.php.net/manual/en/mail.configuration.php preferencies de corréu de PHP] tienen de tar configuraes correutamente.\nSi nun quies les funciones de corréu electrónicu, puedes desactivales equí.",
+       "config-enable-email-help": "Si quies que'l corréu electrónicu funcione, les [https://www.php.net/manual/en/mail.configuration.php preferencies de corréu de PHP] tienen de tar configuraes correutamente.\nSi nun quies les funciones de corréu electrónicu, puedes desactivales equí.",
        "config-email-user": "Activar el corréu electrónicu ente usuarios",
        "config-logo": "URL del logo:",
        "config-instantcommons": "Activar Instant Commons",
+       "config-advanced-settings": "Configuración avanzada",
        "config-extensions": "Estensiones",
-       "config-skins": "Apariencies",
+       "config-skins": "Estilos",
        "config-skins-help": "Deteutáronse les apariencies de la llista anterior nel direutoriu <code>./skins</code>. Tienes d'activar siquier una, y escoyer la predeterminada.",
        "config-skins-use-as-default": "Utilizar esta apariencia como predeterminada",
        "config-skins-missing": "Nun s'atopó nenguna apariencia; MediaWiki utilizará una apariencia de respaldu hasta qu'instales delles apariencies afayadices.",
-       "config-skins-must-enable-some": "Tienes d'escoyer polo meno una apariencia p'activar.",
+       "config-skins-must-enable-some": "Has escoyer polo menos un estilu p'activar.",
        "config-skins-must-enable-default": "L'apariencia escoyida como predeterminada tien de tar activada.",
        "config-install-step-done": "fecho",
        "config-install-step-failed": "falló",
        "config-install-interwiki": "Enllenando la tabla d'interwiki predeterminada",
        "config-install-interwiki-list": "Nun pudo lleese'l ficheru <code>interwiki.list</code>.",
        "config-download-localsettings": "Descargar <code>LocalSettings.php</code>",
-       "config-help": "Ayuda",
+       "config-help": "ayuda",
        "config-nofile": "Nun pudo atopase'l ficheru \"$1\". ¿Desaniciose?",
        "config-skins-screenshots": "$1 (imaxes de pantalla: $2)",
+       "config-skins-screenshot": "$1 ($2)",
+       "config-extensions-requires": "$1 (rique $2)",
        "config-screenshot": "imaxe de pantalla",
        "mainpagetext": "<strong>Instalóse MediaWiki.</strong>",
        "mainpagedocfooter": "Consulta la [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents Guía del usuariu] pa saber cómo usar el software wiki.\n\n== Primeros pasos ==\n\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Llista de les opciones de configuración]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ EMF de MediaWiki]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Llista de corréu de llanzamientos de MediaWiki]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Llocaliza MediaWiki na to llingua]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam Depriende como combatir la puxarra na to wiki]"
index bfe2ca4..e0d2bc9 100644 (file)
@@ -67,9 +67,9 @@
        "config-pcre-no-utf8": "'''Фаталь хата'''. PHP өсөн PCRE модуле  PCRE_UTF8 менән яраҡлыштырылмаған.\nMediaWiki дөрөҫ эшләһен өсөн UTF-8 талап ителә.",
        "config-memory-raised": "Хәтер сикләнгән PHP  (<code>memory_limit</code>)  $1  $2 тиклем арттырылған.",
        "config-memory-bad": "'''Иғтибар:''' PHP күләме <code>memory_limit</code> $1 тәшкил итә.\nБәлки, был саманан тыш аҙҙыр. \nҠуйылыштың уңышһыҙлыҡҡа осрауы бар!",
-       "config-apc": "[https://secure.php.net/apc APC] урынлаштырылды",
+       "config-apc": "[https://www.php.net/apc APC] урынлаштырылды",
        "config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension WinCache] урынлыштырылды",
-       "config-no-cache-apcu": "'''Иғтибар:'''  [https://secure.php.net/apcu APCu], [http://xcache.lighttpd.net/ XCache] табылманы йәки [https://www.iis.net/downloads/microsoft/wincache-extension WinCache].\nОбъекттарҙы кэшлау һүндереләсәк..",
+       "config-no-cache-apcu": "'''Иғтибар:'''  [https://www.php.net/apcu APCu], [http://xcache.lighttpd.net/ XCache] табылманы йәки [https://www.iis.net/downloads/microsoft/wincache-extension WinCache].\nОбъекттарҙы кэшлау һүндереләсәк..",
        "config-mod-security": "<strong>Иғтибар</strong>: һеҙҙең веб-серверығыҙҙа [https://modsecurity.org/ mod_security]/mod_security2 ҡабыҙылған. Уның күп кенә стандарт көйләүҙәре MediaWiki йәки бүтән ПО ҡулланыусыларға серверға ирекле контент ебәрегрә мөмкинлек буйынса проблемалар тыуҙырыуы мөмкин.\nКөтөлмәгән хаталарға тап булһағыҙ, ошонда [https://modsecurity.org/documentation/ документации mod_security]йәки үҙегеҙҙең хостинг-провайдерығыҙға мөрәжәғәт итегеҙ.",
        "config-diff3-bad": "GNU diff3 табылманы.",
        "config-git": "Git өлгөләрҙе контролләү системаһы табылды: <code>$1</code>.",
        "config-type-mysql": "MySQL (йәки тура килгән)",
        "config-type-mssql": "Microsoft SQL Server",
        "config-support-info": "MediaWiki -ла түбәндәге СУБД бар:\n\n$1\n\nӘгәр мәғлүмәт һаҡлау системаһын исемлектә күрмәһәгеҙ, рөхсәт алыу өсөн өҫтәге һылтанмалағы инструкция буйынса эш итегеҙ.",
-       "config-dbsupport-mysql": "* [{{int:version-db-mysql-url}} MySQL] — MediaWiki-ҙың иҫ яҡшы эшләгән төп мәғлүмәттәр базаһы.  MediaWiki шулай уҡ MySQL-тап килгән [{{int:version-db-mariadb-url}} MariaDB] һәм [{{int:version-db-percona-url}} Percona Server] менән эшләй. ([https://secure.php.net/manual/ru/mysql.installation.php MySQL-ярҙамында PHP туплау инструкцияһы])",
-       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] —  СУБД-ның популяр открыткаһы, MySQL өсөн альтернатива.\nТөҙәтелмәгән хаталар булыуы мөмкин, эш схемаһында ҡулланыу тәҡдим ителмәй. ([https://secure.php.net/manual/en/pgsql.installation.php  PostgreSQL рөхсәт ителгән РНР йыйыу инструкцияһы]).",
+       "config-dbsupport-mysql": "* [{{int:version-db-mysql-url}} MySQL] — MediaWiki-ҙың иҫ яҡшы эшләгән төп мәғлүмәттәр базаһы.  MediaWiki шулай уҡ MySQL-тап килгән [{{int:version-db-mariadb-url}} MariaDB] һәм [{{int:version-db-percona-url}} Percona Server] менән эшләй. ([https://www.php.net/manual/ru/mysql.installation.php MySQL-ярҙамында PHP туплау инструкцияһы])",
+       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] —  СУБД-ның популяр открыткаһы, MySQL өсөн альтернатива.\nТөҙәтелмәгән хаталар булыуы мөмкин, эш схемаһында ҡулланыу тәҡдим ителмәй. ([https://www.php.net/manual/en/pgsql.installation.php  PostgreSQL рөхсәт ителгән РНР йыйыу инструкцияһы]).",
        "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite] — яҡшы һәм еңел мәғлүмәт базаһы системаһы. ([http://www.php.net/manual/ru/pdo.installation.php  собрать PHP  SQLite]  PDO менән эшләй торған инструкция)",
        "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] — предприятие масштабындаға коммерция базыһы. ([http://www.php.net/manual/ru/oci8.installation.php OCI8 ярҙамындағы РНР нисек йыйырға])",
-       "config-dbsupport-mssql": "* [{{int:version-db-oracle-url}} Oracle] — предприятие масштабындаға Windows өсөн коммерция базыһы. ([https://secure.php.net/manual/en/sqlsrv.installation.php OCI8 ярҙамындағы РНР нисек йыйырға])",
+       "config-dbsupport-mssql": "* [{{int:version-db-oracle-url}} Oracle] — предприятие масштабындаға Windows өсөн коммерция базыһы. ([https://www.php.net/manual/en/sqlsrv.installation.php OCI8 ярҙамындағы РНР нисек йыйырға])",
        "config-header-mysql": "MySQL көйләү",
        "config-header-postgres": "PostgreSQL көйләү",
        "config-header-sqlite": "SQLite көйләү",
index ecc7211..65743df 100644 (file)
        "config-pcre-no-utf8": "'''Фатальная памылка''': модуль PCRE для PHP скампіляваны без падтрымкі PCRE_UTF8.\nMediaWiki патрабуе падтрымкі UTF-8 для слушнай працы.",
        "config-memory-raised": "Абмежаваньне на даступную для PHP памяць <code>memory_limit</code> было падвышанае з $1 да $2.",
        "config-memory-bad": "'''Папярэджаньне:''' памер PHP <code>memory_limit</code> складае $1.\nВерагодна, гэта вельмі мала.\nУсталяваньне можа быць няўдалым!",
-       "config-apc": "[https://secure.php.net/apc APC] усталяваны",
-       "config-apcu": "[https://secure.php.net/apcu APCu] ўсталяваны",
+       "config-apc": "[https://www.php.net/apc APC] усталяваны",
+       "config-apcu": "[https://www.php.net/apcu APCu] ўсталяваны",
        "config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension WinCache] усталяваны",
-       "config-no-cache-apcu": "<strong>Папярэджаньне:</strong> ня знойдзеныя [https://secure.php.net/apcu APCu] ці [https://www.iis.net/downloads/microsoft/wincache-extension WinCache]. Кэшаваньне аб’ектаў адключанае.",
+       "config-no-cache-apcu": "<strong>Папярэджаньне:</strong> ня знойдзеныя [https://www.php.net/apcu APCu] ці [https://www.iis.net/downloads/microsoft/wincache-extension WinCache]. Кэшаваньне аб’ектаў адключанае.",
        "config-mod-security": "'''Папярэджаньне''': на Вашым ўэб-сэрверы ўключаны [https://modsecurity.org/ mod_security]. У выпадку няслушнай наладцы, ён можа стаць прычынай праблемаў для MediaWiki ці іншага праграмнага забесьпячэньня, якое дазваляе ўдзельнікам дасылаць на сэрвэр любы зьмест.\nГлядзіце [https://modsecurity.org/documentation/ дакумэнтацыю mod_security] ці зьвярніцеся ў падтрымку Вашага хосту, калі ў Вас узьнікаюць выпадковыя праблемы.",
        "config-diff3-bad": "Праграма параўнаньня тэксту GNU diff3 ня знойдзеная. Вы можаце праігнараваць гэта, але можаце сутыкнуцца з канфліктамі рэдагаваньня больш часта.",
        "config-git": "Знойдзеная сыстэма канстролю вэрсіяў Git: <code>$1</code>",
        "config-type-oracle": "Oracle",
        "config-type-mssql": "Microsoft SQL Server",
        "config-support-info": "MediaWiki падтрымлівае наступныя сыстэмы базаў зьвестак:\n\n$1\n\nКалі Вы ня бачыце сыстэму базаў зьвестак, якую Вы спрабуеце выкарыстоўваць ў сьпісе ніжэй, перайдзіце па спасылцы інструкцыі, якая знаходзіцца ніжэй, каб уключыць падтрымку.",
-       "config-dbsupport-mysql": "* [{{int:version-db-mariadb-url}} MariaDB] зьяўляецца галоўнай мэтай MediaWiki і падтрымліваецца найлепш. MediaWiki таксама працуе з [{{int:version-db-mysql-url}} MySQL] і [{{int:version-db-percona-url}} Percona Server], якія сумяшчальныя з MariaDB. ([https://secure.php.net/manual/en/mysqli.installation.php Як скампіляваць PHP з падтрымкай MySQL])",
-       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] — папулярная сыстэма базы зьвестак з адкрытым кодам, якая зьяўляецца альтэрнатывай MySQL. ([https://secure.php.net/manual/en/pgsql.installation.php Як кампіляваць PHP з падтрымкай PostgreSQL])",
-       "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite] — невялікая сыстэма базы зьвестак, якая мае вельмі добрую падтрымку. ([https://secure.php.net/manual/en/pdo.installation.php Як кампіляваць PHP з падтрымкай SQLite], выкарыстоўвае PDO)",
-       "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] зьяўляецца камэрцыйнай прафэсійнай базай зьвестак. ([https://secure.php.net/manual/en/oci8.installation.php Як скампіляваць PHP з падтрымкай OCI8])",
-       "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] — камэрцыйная база зьвестак для Windows. ([https://secure.php.net/manual/en/sqlsrv.installation.php Як скампіляваць PHP з падтрымкай SQLSRV])",
+       "config-dbsupport-mysql": "* [{{int:version-db-mariadb-url}} MariaDB] зьяўляецца галоўнай мэтай MediaWiki і падтрымліваецца найлепш. MediaWiki таксама працуе з [{{int:version-db-mysql-url}} MySQL] і [{{int:version-db-percona-url}} Percona Server], якія сумяшчальныя з MariaDB. ([https://www.php.net/manual/en/mysqli.installation.php Як скампіляваць PHP з падтрымкай MySQL])",
+       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] — папулярная сыстэма базы зьвестак з адкрытым кодам, якая зьяўляецца альтэрнатывай MySQL. ([https://www.php.net/manual/en/pgsql.installation.php Як кампіляваць PHP з падтрымкай PostgreSQL])",
+       "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite] — невялікая сыстэма базы зьвестак, якая мае вельмі добрую падтрымку. ([https://www.php.net/manual/en/pdo.installation.php Як кампіляваць PHP з падтрымкай SQLite], выкарыстоўвае PDO)",
+       "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] зьяўляецца камэрцыйнай прафэсійнай базай зьвестак. ([https://www.php.net/manual/en/oci8.installation.php Як скампіляваць PHP з падтрымкай OCI8])",
+       "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] — камэрцыйная база зьвестак для Windows. ([https://www.php.net/manual/en/sqlsrv.installation.php Як скампіляваць PHP з падтрымкай SQLSRV])",
        "config-header-mysql": "Налады MariaDB/MySQL",
        "config-header-postgres": "Налады PostgreSQL",
        "config-header-sqlite": "Налады SQLite",
        "config-license-help": "Шматлікія адкрытыя вікі публікуюць увесь унёсак у праект на ўмовах [https://freedomdefined.org/Definition вольнай ліцэнзіі].\nГэта дазваляе ствараць эфэкт супольнай уласнасьці і садзейнічае доўгатэрміноваму ўнёску.\nДля прыватных і карпаратыўных вікі гэта не зьяўляецца неабходнасьцю.\n\nКалі Вы жадаеце выкарыстоўваць тэкст зь Вікіпэдыі, і жадаеце, каб Вікіпэдыя магла прымаць тэксты, скапіяваныя з Вашай вікі, Вам неабходна выбраць ліцэнзію <strong>{{int:config-license-cc-by-sa}}</strong>.\n\nРаней Вікіпэдыя выкарыстоўвала ліцэнзію GNU Free Documentation.\nЯна ўсё яшчэ дзейнічае, але яна ўтрымлівае некаторыя моманты, якія ўскладняюць паўторнае выкарыстаньне і інтэрпрэтацыю матэрыялаў.",
        "config-email-settings": "Налады электроннай пошты",
        "config-enable-email": "Дазволіць выходзячыя электронныя лісты",
-       "config-enable-email-help": "Калі вы жадаеце, каб працавала электронная пошта, неабходна сканфігураваць PHP [https://secure.php.net/manual/en/mail.configuration.php адпаведным чынам].\nКалі вы не жадаеце выкарыстоўваць магчымасьці электроннай пошты, вы можаце іх адключыць тут.",
+       "config-enable-email-help": "Калі вы жадаеце, каб працавала электронная пошта, неабходна сканфігураваць PHP [https://www.php.net/manual/en/mail.configuration.php адпаведным чынам].\nКалі вы не жадаеце выкарыстоўваць магчымасьці электроннай пошты, вы можаце іх адключыць тут.",
        "config-email-user": "Дазволіць электронную пошту для сувязі паміж удзельнікамі",
        "config-email-user-help": "Дазволіць усім удзельнікам дасылаць адзін аднаму электронныя лісты, калі ўключаная адпаведная магчымасьць ў іх наладах.",
        "config-email-usertalk": "Уключыць абвяшчэньні пра паведамленьні на старонцы абмеркаваньня",
index 21d7cbb..8074a14 100644 (file)
        "config-pcre-no-utf8": "<strong>Фатално:</strong> Модулът PCRE на PHP изглежда е компилиран без поддръжка на PCRE_UTF8.\nЗа да функционира правилно, МедияУики изисква поддръжка на UTF-8.",
        "config-memory-raised": "<code>memory_limit</code> на PHP е $1, увеличаване до $2.",
        "config-memory-bad": "<strong>Внимание:</strong> <code>memory_limit</code> на PHP е $1.\nСтойността вероятно е твърде ниска.\nВъзможно е инсталацията да се провали!",
-       "config-apc": "[https://secure.php.net/apc APC] е инсталиран",
-       "config-apcu": "[https://secure.php.net/apcu APCu] е инсталиран",
+       "config-apc": "[https://www.php.net/apc APC] е инсталиран",
+       "config-apcu": "[https://www.php.net/apcu APCu] е инсталиран",
        "config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension WinCache] е инсталиран",
-       "config-no-cache-apcu": "<strong>Внимание:</strong> [https://secure.php.net/apcu APCu] и [https://www.iis.net/downloads/microsoft/wincache-extension WinCache] не могат да бъдат открити.\nКеширането на обекти не е активирано.",
+       "config-no-cache-apcu": "<strong>Внимание:</strong> [https://www.php.net/apcu APCu] и [https://www.iis.net/downloads/microsoft/wincache-extension WinCache] не могат да бъдат открити.\nКеширането на обекти не е активирано.",
        "config-mod-security": "<strong>Предупреждение:</strong> [https://modsecurity.org/ mod_security]/mod_security2 е включено на вашия уеб сървър. Много от обичайните му конфигурации пораждат проблеми с МедияУики и друг софтуер, който позволява публикуване на произволно съдържание.\nАко е възможно, моля изключете го. В противен случай се обърнете към [https://modsecurity.org/documentation/ документацията на mod_security] или се свържете с поддръжката на хостинга си, ако се сблъскате със случайни грешки.",
        "config-diff3-bad": "Инструментът за сравняване на текст GNU diff3 не беше намерен. Можете да игнорирате това за сега, но конфликтите при редактиране може да бъдат по-чести.",
        "config-git": "Налична е системата за контрол на версиите Git: <code>$1</code>.",
        "config-type-mysql": "MariaDB, MySQL (или съвместима)",
        "config-type-mssql": "Microsoft SQL сървър",
        "config-support-info": "МедияУики поддържа следните системи за бази от данни:\n\n$1\n\nАко не виждате желаната за използване система в списъка по-долу, следвайте инструкциите за активиране на поддръжка по-горе.",
-       "config-dbsupport-mysql": "* [{{int:version-db-mariadb-url}} MariaDB] е най-важна за МедияУики и се поддържа най-добре. МедияУики работи също така с [{{int:version-db-mysql-url}} MySQL] и [{{int:version-db-percona-url}} Percona Server], които са съвместими с MariaDB.\n([https://secure.php.net/manual/en/mysqli.installation.php Как се компилира PHP с поддръжка на MySQL])",
-       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] е популярна система за управление на бази от данни, алтернатива на MySQL. ([https://secure.php.net/manual/bg/pgsql.installation.php Как се компилира PHP с поддръжка на PostgreSQL])",
-       "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite] е олекотена система за бази от данни, която е много добре поддържана. ([https://secure.php.net/manual/en/pdo.installation.php Как се компилира PHP с поддръжка на SQLite], използва PDO)",
-       "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] е комерсиална корпоративна база от данни. ([https://secure.php.net/manual/en/oci8.installation.php Как се компилира PHP с поддръжка на OCI8])",
-       "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] е комерсиална корпоративна база от данни за Windows. ([https://secure.php.net/manual/en/sqlsrv.installation.php Как да се компилира PHP с поддръжка на SQLSRV])",
+       "config-dbsupport-mysql": "* [{{int:version-db-mariadb-url}} MariaDB] е най-важна за МедияУики и се поддържа най-добре. МедияУики работи също така с [{{int:version-db-mysql-url}} MySQL] и [{{int:version-db-percona-url}} Percona Server], които са съвместими с MariaDB.\n([https://www.php.net/manual/en/mysqli.installation.php Как се компилира PHP с поддръжка на MySQL])",
+       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] е популярна система за управление на бази от данни, алтернатива на MySQL. ([https://www.php.net/manual/bg/pgsql.installation.php Как се компилира PHP с поддръжка на PostgreSQL])",
+       "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite] е олекотена система за бази от данни, която е много добре поддържана. ([https://www.php.net/manual/en/pdo.installation.php Как се компилира PHP с поддръжка на SQLite], използва PDO)",
+       "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] е комерсиална корпоративна база от данни. ([https://www.php.net/manual/en/oci8.installation.php Как се компилира PHP с поддръжка на OCI8])",
+       "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] е комерсиална корпоративна база от данни за Windows. ([https://www.php.net/manual/en/sqlsrv.installation.php Как да се компилира PHP с поддръжка на SQLSRV])",
        "config-header-mysql": "Настройки на MariaDB/MySQL",
        "config-header-postgres": "Настройки за PostgreSQL",
        "config-header-sqlite": "Настройки за SQLite",
        "config-license-help": "Много публични уикита поставят всички приноси под [https://freedomdefined.org/Definition/Bg свободен лиценз].\nТова помага за създаването на усещане за общност и насърчава дългосрочните приноси. \nТова не е необходимо като цяло за частно или корпоративно уики.\n\nАко искате да използвате текстове от Уикипедия, и искате Уикипедия да може да приема текстове, копирани от вашето уики, трябва да изберете лиценз <strong>{{int:config-license-cc-by-sa}}</strong>.\n\nЛицензът за свободна документация на GNU е старият лиценз на съдържанието на Уикипедия.\nТой все още е валиден лиценз, но някои негови условия са трудни за разбиране и правят по-сложни повторното използване и интерпретацията.",
        "config-email-settings": "Настройки за е-поща",
        "config-enable-email": "Разрешаване на изходящи е-писма",
-       "config-enable-email-help": "За да работят възможностите за използване на е-поща, необходимо е [https://secure.php.net/manual/en/mail.configuration.ph настройките за поща на PHP] да бъдат конфигурирани правилно.\nАко няма да се използват услугите за е-поща в уикито, те могат да бъдат изключени тук.",
+       "config-enable-email-help": "За да работят възможностите за използване на е-поща, необходимо е [https://www.php.net/manual/en/mail.configuration.ph настройките за поща на PHP] да бъдат конфигурирани правилно.\nАко няма да се използват услугите за е-поща в уикито, те могат да бъдат изключени тук.",
        "config-email-user": "Позволяване на потребителите да си изпращат е-писма през уикито",
        "config-email-user-help": "Позволяване на потребителите да си изпращат е-писма ако са разрешили това в настройките си.",
        "config-email-usertalk": "Оповестяване при промяна на потребителската беседа",
index 9df3c8b..2392f73 100644 (file)
@@ -45,8 +45,8 @@
        "config-env-php": "পিএইচপি $1 ইন্সটল করা হয়েছে।",
        "config-env-hhvm": "HHVM $1 ইনস্টল করা হয়েছে।",
        "config-memory-raised": "পিএইচপির <code>memory_limit</code> হচ্ছে $1, বৃদ্ধি পেয়ে $2 হয়েছে।",
-       "config-apc": "[https://secure.php.net/apc এপিসি] ইনস্টল করা হয়েছে",
-       "config-apcu": "[https://secure.php.net/apcu এপিসিu] ইনস্টল করা হয়েছে",
+       "config-apc": "[https://www.php.net/apc এপিসি] ইনস্টল করা হয়েছে",
+       "config-apcu": "[https://www.php.net/apcu এপিসিu] ইনস্টল করা হয়েছে",
        "config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension WinCache] ইনস্টল করা হয়েছে",
        "config-diff3-bad": "GNU diff3 লেখা তুলনা করার কার্যকারিতা পাওয়া যায়নি। আপনি এখন এটিকে উপেক্ষা করতে পারেন, তবে এতে আপনি আরো ঘন ঘন সম্পাদনা সংঘাতের মধ্যে পড়তে পারেন।",
        "config-git": "Git সংস্করণের নিয়ন্ত্রণ সফটওয়্যার পাওয়া গেছে: <code>$1</code>।",
@@ -72,7 +72,7 @@
        "config-oracle-temp-ts": "সাময়কি টেবিলস্পেস:",
        "config-type-mysql": "MariaDB, MySQL, বা উপযুক্তগুলি",
        "config-type-mssql": "মাইক্রোসফট SQL সার্ভার",
-       "config-dbsupport-postgres": "* MySQL-এর বিকল্প হিসেবে [{{int:version-db-postgres-url}} PostgreSQL] হচ্ছে একটি জনপ্রিয় মুক্ত উৎসের ডাটাবেস ব্যবস্থা। ([https://secure.php.net/manual/en/pgsql.installation.php PostgreSQL সমর্থনসহ কিভাবে PHP সঙ্কলন করবেন])",
+       "config-dbsupport-postgres": "* MySQL-এর বিকল্প হিসেবে [{{int:version-db-postgres-url}} PostgreSQL] হচ্ছে একটি জনপ্রিয় মুক্ত উৎসের ডাটাবেস ব্যবস্থা। ([https://www.php.net/manual/en/pgsql.installation.php PostgreSQL সমর্থনসহ কিভাবে PHP সঙ্কলন করবেন])",
        "config-header-mysql": "MariaDB/MySQL সেটিং",
        "config-header-postgres": "PostgreSQL সেটিংস",
        "config-header-sqlite": "SQLite সেটিংস",
index 876ed53..91f9bdb 100644 (file)
        "config-pcre-no-utf8": "'''Fazi groñs ''': evit doare eo bet kempunet modulenn PCRE PHP hep ar skor PCRE_UTF8.\nEzhomm en deus MediaWiki eus UTF-8 evit mont plaen en-dro.",
        "config-memory-raised": "<code>memory_limit</code> ar PHP zo $1, kemmet e $2.",
        "config-memory-bad": "'''Diwallit :''' Da $1 emañ arventenn <code>memory_limit</code> PHP.\nRe izel eo moarvat.\nMarteze e c'hwito ar staliadenn !",
-       "config-apc": "Staliet eo [https://secure.php.net/apc APC]",
-       "config-apcu": "Staliet eo [https://secure.php.net/apcu APCu]",
+       "config-apc": "Staliet eo [https://www.php.net/apc APC]",
+       "config-apcu": "Staliet eo [https://www.php.net/apcu APCu]",
        "config-wincache": "Staliet eo [https://www.iis.net/downloads/microsoft/wincache-extension WinCache]",
-       "config-no-cache-apcu": "<strong>Taolit pled :</strong> N'eus ket bet gallet kavout [https://secure.php.net/apcu APCu], [http://xcache.lighttpd.net/ XCache] pe [https://www.iis.net/downloads/microsoft/wincache-extension WinCache].\nN'eo ket gweredekaet ar c'hrubuilhañ traezoù.",
+       "config-no-cache-apcu": "<strong>Taolit pled :</strong> N'eus ket bet gallet kavout [https://www.php.net/apcu APCu], [http://xcache.lighttpd.net/ XCache] pe [https://www.iis.net/downloads/microsoft/wincache-extension WinCache].\nN'eo ket gweredekaet ar c'hrubuilhañ traezoù.",
        "config-mod-security": "<strong>Taolit pled :</strong> Gweredekaet eo [https://modsecurity.org/ mod_security]/mod_security2 gant ho servijer web. Ma n'eo ket kfluniet mat e c'hall tegas trubuilhoù da MediaWiki ha meziantoù all a aotre implijerien da ouzhpennañ danvez evel ma karont.\nE kement ha m'eo posupl e tlefe bezañ diweredekaet. A-hend-all, sellit ouzh [https://modsecurity.org/documentation/ mod_security an teuliadur] pe kit e darempred gant skoazell ho herberc'hier m'en em gavit gant fazioù dargouezhek.",
        "config-diff3-bad": "N'eo ket bet kavet GNU diff3.",
        "config-git": "Kavet eo bet ar meziant kontrolliñ adstummoù Git : <code>$1</code>.",
        "config-type-oracle": "Oracle",
        "config-type-mssql": "Microsoft SQL Server",
        "config-support-info": "Skoret eo ar reizhiadoù diaz titouroù da-heul gant MediaWiki :\n\n$1\n\nMa ne welit ket amañ dindan ar reizhiad diaz titouroù a fell deoc'h ober ganti, heuilhit an titouroù a-us (s.o. al liammoù) evit gweredekaat ar skorañ.",
-       "config-dbsupport-mysql": "* [{{int:version-db-mysql-url}} MySQL] eo an dibab kentañ evit MediaWiki hag an hini skoret ar gwellañ. Mont a ra MediaWiki en-dro gant [{{int:version-db-mariadb-url}} MariaDB] ha [{{int:version-db-percona-url}} Percona Server] ivez, kenglotus o-daou gant MySQL. ([https://secure.php.net/manual/en/mysqli.installation.php Penaos kempunañ PHP gant skor MySQL])",
-       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] zo anezhi ur reizhiad diaz roadennoù frank a wirioù brudet-mat a c'haller ober gantañ e plas MySQL. ([https://secure.php.net/manual/en/pgsql.installation.php Penaos kempunañ PHP gant skor PostgreSQL])",
+       "config-dbsupport-mysql": "* [{{int:version-db-mysql-url}} MySQL] eo an dibab kentañ evit MediaWiki hag an hini skoret ar gwellañ. Mont a ra MediaWiki en-dro gant [{{int:version-db-mariadb-url}} MariaDB] ha [{{int:version-db-percona-url}} Percona Server] ivez, kenglotus o-daou gant MySQL. ([https://www.php.net/manual/en/mysqli.installation.php Penaos kempunañ PHP gant skor MySQL])",
+       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] zo anezhi ur reizhiad diaz roadennoù frank a wirioù brudet-mat a c'haller ober gantañ e plas MySQL. ([https://www.php.net/manual/en/pgsql.installation.php Penaos kempunañ PHP gant skor PostgreSQL])",
        "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite] zo anezhi ur reizhiad diaz roadennoù skañv skoret eus ar c'hentañ. ([http://www.php.net/manual/en/pdo.installation.php Penaos kempunañ PHP gant skor SQLite], implijout a ra PDO)",
        "config-dbsupport-oracle": "* Un embregerezh kenwerzhel diaz roadennoù eo [{{int:version-db-oracle-url}} Oracle]. ([http://www.php.net/manual/en/oci8.installation.php Penaos kempunañ PHP gant skor OCI8])",
-       "config-dbsupport-mssql": "* Un embregerezh kenwerzhel diaz roadennoù evit Windows eo [{{int:version-db-mssql-url}} Microsoft SQL Server]. ([https://secure.php.net/manual/en/sqlsrv.installation.php Penaos kempunañ PHP gant skor SQLSRV])",
+       "config-dbsupport-mssql": "* Un embregerezh kenwerzhel diaz roadennoù evit Windows eo [{{int:version-db-mssql-url}} Microsoft SQL Server]. ([https://www.php.net/manual/en/sqlsrv.installation.php Penaos kempunañ PHP gant skor SQLSRV])",
        "config-header-mysql": "Arventennoù MySQL",
        "config-header-postgres": "Arventennoù PostgreSQL",
        "config-header-sqlite": "Arventennoù SQLite",
index 58931b8..e6118f8 100644 (file)
@@ -50,8 +50,8 @@
        "config-no-db": "Ne mogu pronaći pogodan upravljački program za bazu podataka! Morate ga instalirati za PHP-bazu.\n{{PLURAL:$2|Podržana je sljedeća vrsta|Podržane su sljedeće vrste}} baze podataka: $1.\n\nAko se sami kompajlirali PHP, omogućite klijent baze podataka u postavkama koristeći, naprimjer, <code>./configure --with-mysqli</code>.\nAko ste instalirali PHP iz paketa za Debian ili Ubuntu, onda također morate instalirati, naprimjer, paket <code>php-mysql</code>.",
        "config-memory-raised": "<code>memory_limit</code> za PHP iznosi $1, povišen na $2.",
        "config-memory-bad": "<strong>Upozorenje:</strong> <code>memory_limit</code> za PHP iznosi $1.\nOvo je vjerovatno premalo.\nInstalacija možda neće uspjeti!",
-       "config-apc": "[https://secure.php.net/apc APC] je instaliran",
-       "config-apcu": "[https://secure.php.net/apcu APCu] je instaliran",
+       "config-apc": "[https://www.php.net/apc APC] je instaliran",
+       "config-apcu": "[https://www.php.net/apcu APCu] je instaliran",
        "config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension WinCache] je instaliran",
        "config-diff3-bad": "GNU diff3 nije pronađen.",
        "config-git": "Pronađen je Git program za kontrolu verzija: <code>$1</code>.",
index c856203..15f98e6 100644 (file)
@@ -66,8 +66,8 @@
        "config-pcre-old": "<strong>Error fatal:</strong> Cal el PCRE $1 o superior.\nEl binari PHP que utilitzeu està enllaçat amb el PCRE $2.\n[https://www.mediawiki.org/wiki/Manual:Errors_and_symptoms/PCRE Més informació].",
        "config-memory-raised": "El <code>memory_limit</code> del PHP és $1 i s'ha aixecat a $2.",
        "config-memory-bad": "<strong>Avís:</strong> El <code>memory_limit</code> del PHP és $1.\nAixò és probablement massa baix.\nLa instal·lació pot fallar!",
-       "config-apc": "L'[https://secure.php.net/apc APC] està instal·lat",
-       "config-apcu": "L'[https://secure.php.net/apcu APCu] està instal·lat",
+       "config-apc": "L'[https://www.php.net/apc APC] està instal·lat",
+       "config-apcu": "L'[https://www.php.net/apcu APCu] està instal·lat",
        "config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension WinCache] està instal·lat",
        "config-diff3-bad": "No s'ha trobat el GNU diff3. Podeu ignorar-ho per ara, però us podeu trobar amb conflictes d'edició més habitualment.",
        "config-git": "S'ha trobat el programari de control de versions Git: <code>$1</code>.",
        "config-license-cc-choose": "Selecció d'una llicència personalitzada de Creative Commons",
        "config-email-settings": "Paràmetres del correu electrònic",
        "config-enable-email": "Habilita el correu sortint",
-       "config-enable-email-help": "Si voleu que el correu electrònic funcioni, cal configurar [https://secure.php.net/manual/en/mail.configuration.php PHP's els paràmetres de correu] correctament.\nSi no voleu cap funcionalitat de correu, podeu inhabilitar-ho.",
+       "config-enable-email-help": "Si voleu que el correu electrònic funcioni, cal configurar [https://www.php.net/manual/en/mail.configuration.php PHP's els paràmetres de correu] correctament.\nSi no voleu cap funcionalitat de correu, podeu inhabilitar-ho.",
        "config-email-user": "Habilita el correu electrònic usuari-a-usuari",
        "config-email-user-help": "Permet que tots els usuaris puguin enviar-se correu si ho han habilitat a les preferències.",
        "config-email-usertalk": "Habilita la notificació a la pàgina de discussió de l'usuari",
index 33bce2f..1b814a2 100644 (file)
@@ -31,7 +31,7 @@
        "config-page-existingwiki": "ویکی پێشوو",
        "config-restart": "بەڵێ، دەستی پێ بکەرەوە",
        "config-env-php": "PHP $1 دامەزراوە.",
-       "config-apc": "[https://secure.php.net/apc APC] دامەزراوە",
+       "config-apc": "[https://www.php.net/apc APC] دامەزراوە",
        "config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension WinCache] دامەزراوە",
        "config-db-type": "جۆری داتابەیس:",
        "config-db-host": "خانەخوێی داتابەیس:",
index f1d06ce..944ecbe 100644 (file)
        "config-pcre-no-utf8": "<strong>Kritická chyba:</strong> PHP modul PCRE byl zřejmě přeložen bez podpory PCRE_UTF8.\nMediaWiki vyžaduje ke správné funkci podporu UTF-8.",
        "config-memory-raised": "<code>memory_limit</code> v PHP byl nastaven na $1, zvýšen na $2.",
        "config-memory-bad": "<strong>Upozornění:</strong> <code>memory_limit</code> je v PHP nastaven na $1.\nTo je pravděpodobně příliš málo.\nInstalace může selhat!",
-       "config-apc": "Je nainstalováno [https://secure.php.net/apc APC]",
-       "config-apcu": "Je nainstalováno [https://secure.php.net/apcu APCu]",
+       "config-apc": "Je nainstalováno [https://www.php.net/apc APC]",
+       "config-apcu": "Je nainstalováno [https://www.php.net/apcu APCu]",
        "config-wincache": "Je nainstalováno [https://www.iis.net/downloads/microsoft/wincache-extension WinCache]",
-       "config-no-cache-apcu": "<strong>Upozornění:</strong> Nebylo nalezeno [https://secure.php.net/apcu APCu], \nani [https://www.iis.net/downloads/microsoft/wincache-extension WinCache].\nCachování objektů není povoleno.",
+       "config-no-cache-apcu": "<strong>Upozornění:</strong> Nebylo nalezeno [https://www.php.net/apcu APCu], \nani [https://www.iis.net/downloads/microsoft/wincache-extension WinCache].\nCachování objektů není povoleno.",
        "config-mod-security": "<strong>Upozornění:</strong> váš webový server má zapnuto [https://modsecurity.org/ mod_security]/mod_security2. Mnoho běžných konfigurací bude způsobovat potíže MediaWiki a dalším programům, které umožňují ukládat libovolný obsah.\nPokud je to možné, mělo by se to vypnout. Jinak se v případě, že narazíte na náhodné chyby, podívejte do [https://modsecurity.org/documentation/ dokumentace mod_security] nebo kontaktujte technickou podporu vašeho poskytovatele.",
        "config-diff3-bad": "Nebyl nalezen nástroj pro porovnávání textu GNU diff3. Můžete to zatím ignorovat, ale je možné, že budete častěji narážet na editační konflikty.",
        "config-git": "Nalezen software pro správu verzí Git: <code>$1</code>.",
        "config-type-oracle": "Oracle",
        "config-type-mssql": "Microsoft SQL Server",
        "config-support-info": "MediaWiki podporuje následující databázové systémy:\n\n$1\n\nPokud v nabídce níže nevidíte databázový systém, který chcete použít, musíte pro zapnutí podpory následovat instrukce odkázané výše.",
-       "config-dbsupport-mysql": "* [{{int:version-db-mariadb-url}} MariaDB] je pro MediaWiki hlavní platformou a je podporováno nejlépe. MediaWiki pracuje také s [{{int:version-db-mysql-url}} MySQL] a [{{int:version-db-percona-url}} Percona Server], které jsou s MariaDB kompatibilní. ([https://secure.php.net/manual/en/mysqli.installation.php Jak zkompilovat PHP s podporou MySQL])",
-       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] je populární otevřený databázový systém používaný jako alternativa k MySQL. ([https://secure.php.net/manual/en/pgsql.installation.php Jak přeložit PHP s podporou PostgreSQL])",
-       "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite] je velmi dobře podporovaný odlehčený databázový systém. ([https://secure.php.net/manual/en/pdo.installation.php Jak přeložit PHP s podporou SQLite], používá PDO)",
-       "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] je komerční podniková databáze. ([https://secure.php.net/manual/en/oci8.installation.php Jak přeložit PHP s podporou OCI8])",
-       "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] je komerční podniková databáze pro Windows. ([https://secure.php.net/manual/en/sqlsrv.installation.php Jak přeložit PHP s podporou SQLSRV])",
+       "config-dbsupport-mysql": "* [{{int:version-db-mariadb-url}} MariaDB] je pro MediaWiki hlavní platformou a je podporováno nejlépe. MediaWiki pracuje také s [{{int:version-db-mysql-url}} MySQL] a [{{int:version-db-percona-url}} Percona Server], které jsou s MariaDB kompatibilní. ([https://www.php.net/manual/en/mysqli.installation.php Jak zkompilovat PHP s podporou MySQL])",
+       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] je populární otevřený databázový systém používaný jako alternativa k MySQL. ([https://www.php.net/manual/en/pgsql.installation.php Jak přeložit PHP s podporou PostgreSQL])",
+       "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite] je velmi dobře podporovaný odlehčený databázový systém. ([https://www.php.net/manual/en/pdo.installation.php Jak přeložit PHP s podporou SQLite], používá PDO)",
+       "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] je komerční podniková databáze. ([https://www.php.net/manual/en/oci8.installation.php Jak přeložit PHP s podporou OCI8])",
+       "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] je komerční podniková databáze pro Windows. ([https://www.php.net/manual/en/sqlsrv.installation.php Jak přeložit PHP s podporou SQLSRV])",
        "config-header-mysql": "Nastavení MariaDB/MySQL",
        "config-header-postgres": "Nastavení PostgreSQL",
        "config-header-sqlite": "Nastavení SQLite",
        "config-license-help": "Mnoho veřejných wiki všechny příspěvky zveřejňuje pod některou [https://freedomdefined.org/Definition/Cs svobodnou licencí].\nTo pomáhá vytvořit duch komunitního vlastnictví a povzbuzuje dlouhodobé přispívání.\nTo obecně není potřeba u soukromé nebo firemní wiki.\n\nPokud chcete být schopni používat text z Wikipedie a chcete, aby Wikipedie byla schopna přijímat text okopírovaný z vaší wiki, měli byste zvolit <strong>{{int:config-license-cc-by-sa}}</strong>.\n\nDříve Wikipedie používala GNU Free Documentation License.\nGFDL je platná licence, ale složité jí porozumět.\nTaké je komplikované používat obsah licencovaný pod GFDL.",
        "config-email-settings": "Nastavení e-mailu",
        "config-enable-email": "Zapnout odchozí e-mail",
-       "config-enable-email-help": "Pokud chcete, aby e-mail fungoval, je potřeba správně nakonfigurovat [https://secure.php.net/manual/en/mail.configuration.php e-mailová nastavení PHP].\nPokud nechcete žádné e-mailové funkce, můžete je zde vypnout.",
+       "config-enable-email-help": "Pokud chcete, aby e-mail fungoval, je potřeba správně nakonfigurovat [https://www.php.net/manual/en/mail.configuration.php e-mailová nastavení PHP].\nPokud nechcete žádné e-mailové funkce, můžete je zde vypnout.",
        "config-email-user": "Umožnit vzájemné e-maily mezi uživateli",
        "config-email-user-help": "Umožní všem uživatelům posílat si navzájem e-maily, pokud si to zapnout v uživatelském nastavení.",
        "config-email-usertalk": "Umožnit notifikace k uživatelským diskusím",
index 6c8e9b8..f7df024 100644 (file)
@@ -35,7 +35,7 @@
        "config-env-hhvm": "HHVM $1 je wjinastalowóné",
        "config-memory-raised": "Paraméter PHP <code>memory_limit</code> $1 òstôł zwikszony do $2.",
        "config-apc": "[Http://www.php.net/apc APC] je wjinstalowóny",
-       "config-apcu": "[https://secure.php.net/apcu APCu] je wjinstalowóny",
+       "config-apcu": "[https://www.php.net/apcu APCu] je wjinstalowóny",
        "config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension WinCache] je wjinstalowóny",
        "config-diff3-bad": "Felënk GNU diff3.",
        "config-mysql-innodb": "InnoDB",
index 943b8fc..946955e 100644 (file)
@@ -46,8 +46,8 @@
        "config-restart": "Ja, genstart den",
        "config-env-php": "PHP $1 er installeret.",
        "config-env-hhvm": "HHVM $1 er installeret.",
-       "config-apc": "[https://secure.php.net/apc APC] er installeret",
-       "config-apcu": "[https://secure.php.net/apcu APCu] er installeret",
+       "config-apc": "[https://www.php.net/apc APC] er installeret",
+       "config-apcu": "[https://www.php.net/apcu APCu] er installeret",
        "config-db-type": "Databasetype:",
        "config-db-host": "Databasevært:",
        "config-db-name": "Databasenavn (ingen bindestreg):",
index c144ce7..5fcc4c9 100644 (file)
        "config-pcre-no-utf8": "'''Fataler Fehler:''' Das PHP-Modul PCRE scheint ohne PCRE_UTF8-Unterstützung kompiliert worden zu sein.\nMediaWiki benötigt die UTF-8-Unterstützung, um fehlerfrei lauffähig zu sein.",
        "config-memory-raised": "Der PHP-Parameter <code>memory_limit</code> betrug $1 und wurde auf $2 erhöht.",
        "config-memory-bad": "'''Warnung:''' Der PHP-Parameter <code>memory_limit</code> beträgt $1.\nDieser Wert ist wahrscheinlich zu niedrig.\nDer Installationsvorgang könnte eventuell scheitern!",
-       "config-apc": "[https://secure.php.net/apc APC] ist installiert",
-       "config-apcu": "[https://secure.php.net/apcu APCu] ist installiert",
+       "config-apc": "[https://www.php.net/apc APC] ist installiert",
+       "config-apcu": "[https://www.php.net/apcu APCu] ist installiert",
        "config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension WinCache] ist installiert",
-       "config-no-cache-apcu": "<strong>Warnung:</strong> [https://secure.php.net/apcu APCu] oder [https://www.iis.net/downloads/microsoft/wincache-extension WinCache] konnten nicht gefunden werden.\nDer Objektcache ist nicht aktiviert.",
+       "config-no-cache-apcu": "<strong>Warnung:</strong> [https://www.php.net/apcu APCu] oder [https://www.iis.net/downloads/microsoft/wincache-extension WinCache] konnten nicht gefunden werden.\nDer Objektcache ist nicht aktiviert.",
        "config-mod-security": "'''Warnung:''' Auf dem Webserver wurde [https://modsecurity.org/ ModSecurity] aktiviert. Sofern falsch konfiguriert, kann dies zu Problemen mit MediaWiki sowie anderer Software auf dem Server führen und es Benutzern ermöglichen, beliebige Inhalte im Wiki einzustellen.\nFür weitere Informationen empfehlen wir die [https://modsecurity.org/documentation/ Dokumentation zu ModSecurity] oder den Kontakt zum Hoster, sofern Fehler auftreten.",
        "config-diff3-bad": "Das Textvergleichswerkzeug GNU diff3 wurde nicht gefunden. Du kannst diese Meldung vorerst ignorieren, jedoch kann es vermehrt zu Bearbeitungskonflikten kommen.",
        "config-git": "Die Versionsverwaltungssoftware „Git“ wurde gefunden: <code>$1</code>.",
        "config-type-oracle": "Oracle",
        "config-type-mssql": "Microsoft SQL Server",
        "config-support-info": "MediaWiki unterstützt die folgenden Datenbanksysteme:\n\n$1\n\nSofern unterhalb nicht das Datenbanksystem angezeigt wird, das verwendet werden soll, muss dieses noch verfügbar gemacht werden. Oben ist zu jedem unterstützten Datenbanksystem ein Link zur entsprechenden Anleitung vorhanden.",
-       "config-dbsupport-mysql": "* [{{int:version-db-mariadb-url}} MariaDB] ist das von MediaWiki primär unterstützte Datenbanksystem. MediaWiki funktioniert auch mit [{{int:version-db-mysql-url}} MySQL] und [{{int:version-db-percona-url}} Percona Server], die MariaDB-kompatibel sind. ([https://secure.php.net/manual/en/mysqli.installation.php Anleitung zur Kompilierung von PHP mit MySQL-Unterstützung])",
-       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] ist ein beliebtes Open-Source-Datenbanksystem und eine Alternative zu MySQL. ([https://secure.php.net/manual/en/pgsql.installation.php Anleitung zur Kompilierung von PHP mit PostgreSQL-Unterstützung])",
+       "config-dbsupport-mysql": "* [{{int:version-db-mariadb-url}} MariaDB] ist das von MediaWiki primär unterstützte Datenbanksystem. MediaWiki funktioniert auch mit [{{int:version-db-mysql-url}} MySQL] und [{{int:version-db-percona-url}} Percona Server], die MariaDB-kompatibel sind. ([https://www.php.net/manual/en/mysqli.installation.php Anleitung zur Kompilierung von PHP mit MySQL-Unterstützung])",
+       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] ist ein beliebtes Open-Source-Datenbanksystem und eine Alternative zu MySQL. ([https://www.php.net/manual/en/pgsql.installation.php Anleitung zur Kompilierung von PHP mit PostgreSQL-Unterstützung])",
        "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite] ist ein verschlanktes Datenbanksystem, das auch gut unterstützt wird ([https://www.php.net/manual/de/pdo.installation.php Anleitung zur Kompilierung von PHP mit SQLite-Unterstützung], verwendet PHP Data Objects (PDO))",
        "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] ist eine kommerzielle Unternehmensdatenbank ([https://www.php.net/manual/en/oci8.installation.php Anleitung zur Kompilierung von PHP mit OCI8-Unterstützung])",
        "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] ist eine gewerbliche Unternehmensdatenbank für Windows. ([https://www.php.net/manual/de/sqlsrv.installation.php Anleitung zur Kompilierung von PHP mit SQLSRV-Unterstützung])",
        "config-license-help": "Viele öffentliche Wikis publizieren alle Beiträge unter einer [https://freedomdefined.org/Definition/De freien Lizenz.]\nDies trägt dazu bei, ein Gefühl von Gemeinschaft zu schaffen, und ermutigt zu längerfristiger Mitarbeit.\nHingegen ist im Allgemeinen eine freie Lizenz auf geschlossenen Wikis nicht notwendig.\n\nSofern man Texte aus der Wikipedia verwenden möchte und umgekehrt, sollte die Lizenz {{int:config-license-cc-by-sa}} gewählt werden.\n\nDie Wikipedia nutzte vormals die GNU-Lizenz für freie Dokumentation (GFDL).\nDie GFDL ist eine gültige Lizenz, die allerdings schwer zu verstehen ist.\nEs ist zudem schwierig, gemäß dieser Lizenz lizenzierte Inhalte wiederzuverwenden.",
        "config-email-settings": "E-Mail-Einstellungen",
        "config-enable-email": "Ausgehende E-Mails ermöglichen",
-       "config-enable-email-help": "Sofern die E-Mail-Funktionen genutzt werden sollen, müssen die entsprechenden [https://secure.php.net/manual/en/mail.configuration.php PHP-E-Mail-Einstellungen] richtig konfiguriert werden.\nFür den Fall, dass die E-Mail-Funktionen nicht benötigt werden, können sie hier deaktiviert werden.",
+       "config-enable-email-help": "Sofern die E-Mail-Funktionen genutzt werden sollen, müssen die entsprechenden [https://www.php.net/manual/en/mail.configuration.php PHP-E-Mail-Einstellungen] richtig konfiguriert werden.\nFür den Fall, dass die E-Mail-Funktionen nicht benötigt werden, können sie hier deaktiviert werden.",
        "config-email-user": "E-Mail-Versand von Benutzer zu Benutzer aktivieren",
        "config-email-user-help": "Allen Benutzern ermöglichen, sich gegenseitig E-Mails zu schicken, sofern sie es in ihren Einstellungen aktiviert haben.",
        "config-email-usertalk": "Benachrichtigungen zu Änderungen an Benutzerdiskussionsseiten ermöglichen",
index 55b7799..c3b3181 100644 (file)
        "config-pcre-no-utf8": "<strong>Κρίσιμο:</strong> Το PCRE module της PHP  φαίνεται να έχει μεταγλωττιστεί χωρίς υποστήριξη  PCRE_UTF8.\nΓια τη σωστή λειτουργία του MediaWiki απαιτείται υποστήριξη UTF-8.",
        "config-memory-raised": "Το  <code>memory_limit</code> της PHP είναι  $1 και αυξήθηκε σε  $2.",
        "config-memory-bad": "<strong>Προειδοποίηση:</strong> το <code>memory_limit</code> της PHP είναι $1.\nΑυτή η τιμή είναι πιθανώς πολύ χαμηλή.\n\nΗ εγκατάσταση ενδέχεται να αποτύχει!",
-       "config-apc": "Το [https://secure.php.net/apc APC] είναι εγκατεστημένο",
-       "config-apcu": "Το [https://secure.php.net/apcu APCu] είναι εγκατεστημένο",
+       "config-apc": "Το [https://www.php.net/apc APC] είναι εγκατεστημένο",
+       "config-apcu": "Το [https://www.php.net/apcu APCu] είναι εγκατεστημένο",
        "config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension Το WinCache] είναι εγκατεστημένο",
-       "config-no-cache-apcu": "<strong>Προειδοποίηση:</strong> Αποτυχία εύρεσης του [https://secure.php.net/apcu APCu] ή του [https://www.iis.net/downloads/microsoft/wincache-extension WinCache].\nΗ αρχειοθέτηση αντικειμένων δεν έχει ενεργοποιηθεί.",
+       "config-no-cache-apcu": "<strong>Προειδοποίηση:</strong> Αποτυχία εύρεσης του [https://www.php.net/apcu APCu] ή του [https://www.iis.net/downloads/microsoft/wincache-extension WinCache].\nΗ αρχειοθέτηση αντικειμένων δεν έχει ενεργοποιηθεί.",
        "config-diff3-bad": "Το GNU diff3 δεν βρέθηκε.",
        "config-git": "Βρέθηκε το λογισμικό ελέγχου εκδόσεων Git: <code>$1</code>.",
        "config-git-bad": "Το λογισμικό ελέγχου εκδόσεων Git δεν βρέθηκε.",
        "config-type-oracle": "Oracle",
        "config-type-mssql": "Microsoft SQL Server",
        "config-support-info": "To MediaWiki υποστηρίζει τα ακόλουθα συστήματα βάσεων δεδομένων:\n\n$1\n\nΑν δεν εμφανίζεται παρακάτω το σύστημα βάσης δεδομένων που θέλετε να χρησιμοποιήσετε, τότε ακολουθήστε τις οδηγίες στον παραπάνω σύνδεσμο για να ενεργοποιήσετε την υποστήριξη.",
-       "config-dbsupport-mysql": "* Η [{{int:version-db-mysql-url}} MySQL] είναι ο πρωταρχικός στόχος για το MediaWiki και υποστηρίζεται καλύτερα. Το MediaWiki συνεργάζεται επίσης με τη [{{int:version-db-mariadb-url}} MariaDB] και το [{{int:version-db-percona-url}} διακομιστή Percona], που είναι όλα συμβατά με MySQL. ([https://secure.php.net/manual/en/mysqli.installation.php Πώς να μεταγλωττίσετε την PHP με υποστήριξη MySQL])",
-       "config-dbsupport-postgres": "* Η [{{int:version-db-postgres-url}} PostgreSQL] είναι δημοφιλές σύστημα βάσης δεδομένων ανοικτού κώδικα ως εναλλακτική της MySQL. ([https://secure.php.net/manual/en/pgsql.installation.php Πώς να μεταγλωττίσετε την PHP με υποστήριξη PostgreSQL])",
+       "config-dbsupport-mysql": "* Η [{{int:version-db-mysql-url}} MySQL] είναι ο πρωταρχικός στόχος για το MediaWiki και υποστηρίζεται καλύτερα. Το MediaWiki συνεργάζεται επίσης με τη [{{int:version-db-mariadb-url}} MariaDB] και το [{{int:version-db-percona-url}} διακομιστή Percona], που είναι όλα συμβατά με MySQL. ([https://www.php.net/manual/en/mysqli.installation.php Πώς να μεταγλωττίσετε την PHP με υποστήριξη MySQL])",
+       "config-dbsupport-postgres": "* Η [{{int:version-db-postgres-url}} PostgreSQL] είναι δημοφιλές σύστημα βάσης δεδομένων ανοικτού κώδικα ως εναλλακτική της MySQL. ([https://www.php.net/manual/en/pgsql.installation.php Πώς να μεταγλωττίσετε την PHP με υποστήριξη PostgreSQL])",
        "config-dbsupport-sqlite": "* Η [{{int:version-db-sqlite-url}} SQLite] είναι ένα ελαφρύ σύστημα βάσης δεδομένων που υποστηρίζεται πολύ καλά. ([http://www.php.net/manual/en/pdo.installation.php Πώς να μεταγλωττίσετε την PHP με υποστήριξη SQLite], χρησιμοποιεί PDO)",
        "config-dbsupport-oracle": "* Η [{{int:version-db-oracle-url}} Oracle] είναι εμπορική βάση δεδομένων για επιχειρήσεις. ([http://www.php.net/manual/en/oci8.installation.php Πώς να μεταγλωττίσετε την PHP με υποστήριξη OCI8])",
-       "config-dbsupport-mssql": "* Ο [{{int:version-db-mssql-url}} Microsoft SQL Server] είναι εμπορική βάση δεδομένων για επιχειρήσεις που λειτουργεί σε Windows. ([https://secure.php.net/manual/en/sqlsrv.installation.php Πώς να μεταγλωττίσετε την PHP με υποστήριξη SQLSRV])",
+       "config-dbsupport-mssql": "* Ο [{{int:version-db-mssql-url}} Microsoft SQL Server] είναι εμπορική βάση δεδομένων για επιχειρήσεις που λειτουργεί σε Windows. ([https://www.php.net/manual/en/sqlsrv.installation.php Πώς να μεταγλωττίσετε την PHP με υποστήριξη SQLSRV])",
        "config-header-mysql": "Ρυθμίσεις MySQL",
        "config-header-postgres": "Ρυθμίσεις PostgreSQL",
        "config-header-sqlite": "Ρυθμίσεις SQLite",
        "config-project-namespace-help": "Ακολουθώντας το παράδειγμα της Βικιπαίδειας, πολλά wiki διατηρούν τις σελίδες πολιτικής τους χωριστά από τις σελίδες περιεχομένου τους, σε έναν '''ονοματοχώρο έργου'''.\nΌλοι οι τίτλοι σελίδων σε αυτόν τον ονοματοχώρο ξεκινούν με συγκεκριμένο πρόθεμα, το οποίο μπορείτε να ορίσετε εδώ.\nΣυνήθως, αυτό το πρόθεμα παράγεται από το όνομα του wiki, αλλά δεν μπορεί να περιέχει χαρακτήρες στίξης όπως «#» ή «:».",
        "config-admin-box": "Λογαριασμός διαχειριστή",
        "config-admin-name": "Το όνομα χρήστη σας:",
-       "config-admin-password": "Î\9aÏ\89δικÏ\8cÏ\82 Ï\80Ï\81Ï\8cÏ\83βαÏ\83ηÏ\82:",
+       "config-admin-password": "ΣÏ\85νθημαÏ\84ικÏ\8c:",
        "config-admin-password-confirm": "Επανάληψη κωδικού πρόσβασης:",
        "config-admin-help": "Εισαγάγετε το προτιμώμενο όνομα χρήστη εδώ, για παράδειγμα «Γιάννης Ιστολόγιος». \nΑυτό είναι το όνομα που θα χρησιμοποιείτε για να συνδέεστε στο wiki.",
        "config-admin-name-blank": "Εισαγάγετε όνομα χρήστη διαχειριστή.",
index 66b657b..4852380 100644 (file)
        "config-pcre-no-utf8": "<strong>Fatal:</strong> PHP's PCRE module seems to be compiled without PCRE_UTF8 support.\nMediaWiki requires UTF-8 support to function correctly.",
        "config-memory-raised": "PHP's <code>memory_limit</code> is $1, raised to $2.",
        "config-memory-bad": "<strong>Warning:</strong> PHP's <code>memory_limit</code> is $1.\nThis is probably too low.\nThe installation may fail!",
-       "config-apc": "[https://secure.php.net/apc APC] is installed",
-       "config-apcu": "[https://secure.php.net/apcu APCu] is installed",
+       "config-apc": "[https://www.php.net/apc APC] is installed",
+       "config-apcu": "[https://www.php.net/apcu APCu] is installed",
        "config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension WinCache] is installed",
-       "config-no-cache-apcu": "<strong>Warning:</strong> Could not find [https://secure.php.net/apcu APCu] or [https://www.iis.net/downloads/microsoft/wincache-extension WinCache].\nObject caching is not enabled.",
+       "config-no-cache-apcu": "<strong>Warning:</strong> Could not find [https://www.php.net/apcu APCu] or [https://www.iis.net/downloads/microsoft/wincache-extension WinCache].\nObject caching is not enabled.",
        "config-mod-security": "<strong>Warning:</strong> Your web server has [https://modsecurity.org/ mod_security]/mod_security2 enabled. Many common configurations of this will cause problems for MediaWiki and other software that allows users to post arbitrary content.\nIf possible, this should be disabled. Otherwise, refer to [https://modsecurity.org/documentation/ mod_security documentation] or contact your host's support if you encounter random errors.",
 
        "config-diff3-bad": "GNU diff3 text comparison utility not found. You can ignore this for now, but might run into edit conflicts more frequently.",
        "config-type-oracle": "Oracle",
        "config-type-mssql": "Microsoft SQL Server",
        "config-support-info": "MediaWiki supports the following database systems:\n\n$1\n\nIf you do not see the database system you are trying to use listed below, then follow the instructions linked above to enable support.",
-       "config-dbsupport-mysql": "* [{{int:version-db-mariadb-url}} MariaDB] is the primary target for MediaWiki and is best supported. MediaWiki also works with [{{int:version-db-mysql-url}} MySQL] and [{{int:version-db-percona-url}} Percona Server], which are MariaDB compatible. ([https://secure.php.net/manual/en/mysqli.installation.php How to compile PHP with MySQL support])",
-       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] is a popular open source database system as an alternative to MySQL. ([https://secure.php.net/manual/en/pgsql.installation.php How to compile PHP with PostgreSQL support])",
-       "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite] is a lightweight database system that is very well supported. ([https://secure.php.net/manual/en/pdo.installation.php How to compile PHP with SQLite support], uses PDO)",
-       "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] is a commercial enterprise database. ([https://secure.php.net/manual/en/oci8.installation.php How to compile PHP with OCI8 support])",
-       "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] is a commercial enterprise database for Windows. ([https://secure.php.net/manual/en/sqlsrv.installation.php How to compile PHP with SQLSRV support])",
+       "config-dbsupport-mysql": "* [{{int:version-db-mariadb-url}} MariaDB] is the primary target for MediaWiki and is best supported. MediaWiki also works with [{{int:version-db-mysql-url}} MySQL] and [{{int:version-db-percona-url}} Percona Server], which are MariaDB compatible. ([https://www.php.net/manual/en/mysqli.installation.php How to compile PHP with MySQL support])",
+       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] is a popular open source database system as an alternative to MySQL. ([https://www.php.net/manual/en/pgsql.installation.php How to compile PHP with PostgreSQL support])",
+       "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite] is a lightweight database system that is very well supported. ([https://www.php.net/manual/en/pdo.installation.php How to compile PHP with SQLite support], uses PDO)",
+       "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] is a commercial enterprise database. ([https://www.php.net/manual/en/oci8.installation.php How to compile PHP with OCI8 support])",
+       "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] is a commercial enterprise database for Windows. ([https://www.php.net/manual/en/sqlsrv.installation.php How to compile PHP with SQLSRV support])",
        "config-header-mysql": "MariaDB/MySQL settings",
        "config-header-postgres": "PostgreSQL settings",
        "config-header-sqlite": "SQLite settings",
        "config-license-help": "Many public wikis put all contributions under a [https://freedomdefined.org/Definition free license].\nThis helps to create a sense of community ownership and encourages long-term contribution.\nIt is not generally necessary for a private or corporate wiki.\n\nIf you want to be able to use text from Wikipedia, and you want Wikipedia to be able to accept text copied from your wiki, you should choose <strong>{{int:config-license-cc-by-sa}}</strong>.\n\nWikipedia previously used the GNU Free Documentation License.\nThe GFDL is a valid license, but it is difficult to understand.\nIt is also difficult to reuse content licensed under the GFDL.",
        "config-email-settings": "Email settings",
        "config-enable-email": "Enable outbound email",
-       "config-enable-email-help": "If you want email to work, [https://secure.php.net/manual/en/mail.configuration.php PHP's mail settings] need to be configured correctly.\nIf you do not want any email features, you can disable them here.",
+       "config-enable-email-help": "If you want email to work, [https://www.php.net/manual/en/mail.configuration.php PHP's mail settings] need to be configured correctly.\nIf you do not want any email features, you can disable them here.",
        "config-email-user": "Enable user-to-user email",
        "config-email-user-help": "Allow all users to send each other email if they have enabled it in their preferences.",
        "config-email-usertalk": "Enable user talk page notification",
index 924bc2b..364209c 100644 (file)
@@ -41,8 +41,8 @@
        "config-env-bad": "La medio estas kontrolita.\nNe eblas instali MediaWiki.",
        "config-env-php": "PHP $1 estas instalita.",
        "config-env-hhvm": "HHVM $1 estas instalita.",
-       "config-apc": "[https://secure.php.net/apc APC] estas instalita",
-       "config-apcu": "[https://secure.php.net/apcu APCu] estas instalita",
+       "config-apc": "[https://www.php.net/apc APC] estas instalita",
+       "config-apcu": "[https://www.php.net/apcu APCu] estas instalita",
        "config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension WinCache] estas instalita",
        "config-diff3-bad": "GNU diff3 ne estis trovita.",
        "config-db-type": "Tipo de datumbazo:",
index 1942957..ab0b549 100644 (file)
        "config-pcre-no-utf8": "'''Error fatal ''': Parece que el módulo PCRE de PHP fue compilado sin el soporte PCRE_UTF8.\nMediaWiki requiere compatibilidad con UTF-8 para funcionar correctamente.",
        "config-memory-raised": "El parámetro <code>memory_limit</code> de PHP es $1. Se aumenta a $2.",
        "config-memory-bad": "<strong>Advertencia:</strong> el parámetro <code>memory_limit</code> de PHP es $1.\nProbablemente sea demasiado bajo.\n¡La instalación puede fallar!",
-       "config-apc": "[https://secure.php.net/apc APC] está instalado",
-       "config-apcu": "[https://secure.php.net/apcu APCu] está instalado",
+       "config-apc": "[https://www.php.net/apc APC] está instalado",
+       "config-apcu": "[https://www.php.net/apcu APCu] está instalado",
        "config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension WinCache] está instalado",
-       "config-no-cache-apcu": "<strong>Atención:</strong> no se pudo encontrar [https://secure.php.net/apcu APCu] o [https://www.iis.net/downloads/microsoft/wincache-extension WinCache].\nEl almacenamiento en antememoria de objetos no está activado.",
+       "config-no-cache-apcu": "<strong>Atención:</strong> no se pudo encontrar [https://www.php.net/apcu APCu] o [https://www.iis.net/downloads/microsoft/wincache-extension WinCache].\nEl almacenamiento en antememoria de objetos no está activado.",
        "config-mod-security": "<strong>Advertencia:</strong> tu servidor web tiene activado [https://modsecurity.org/ mod_security]/mod_security2. Muchas de sus configuraciones comunes pueden causar problemas a MediaWiki u otro software que permita a los usuarios publicar contenido arbitrario. De ser posible, deberías desactivarlo. Si no, consulta la [https://modsecurity.org/documentation/ documentación de mod_security] o contacta con el administrador de tu servidor si encuentras errores aleatorios.",
        "config-diff3-bad": "GNU diff3 no se encuentra.",
        "config-git": "Se encontró el software de control de versiones Git: <code>$1</code>.",
        "config-type-oracle": "Oracle",
        "config-type-mssql": "Microsoft SQL Server",
        "config-support-info": "MediaWiki es compatible con los siguientes sistemas de bases de datos:\n\n$1\n\nSi no encuentras en el listado el sistema de base de datos que estás intentando utilizar, sigue las instrucciones enlazadas arriba para activar la compatibilidad.",
-       "config-dbsupport-mysql": "* [{{int:version-db-mariadb-url}} MariaDB] es la base de datos mayoritaria para MediaWiki y la que goza de mayor compatibilidad. MediaWiki también funciona con [{{int:version-db-myslql-url}} MySQL] y [{{int:version-db-percona-url}} Percona Server], que son compatibles con MariaDB. ([https://secure.php.net/manual/es/mysql.installation.php Cómo compilar PHP con compatibilidad MySQL])",
-       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] es un sistema de base de datos popular de código abierto, alternativa a MySQL. ([https://secure.php.net/manual/es/pgsql.installation.php Cómo compilar PHP con compatibilidad PostgreSQL]).",
-       "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite] es un sistema de base de datos ligero con gran compatibilidad con MediaWiki. ([https://secure.php.net/manual/en/pdo.installation.php Cómo compilar PHP con compatibilidad SQLite], usando PDO)",
-       "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] es una base de datos comercial a nivel empresarial. ([https://secure.php.net/manual/en/oci8.installation.php Cómo compilar PHP con compatibilidad con OCI8])",
-       "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] es un sistema comercial de base de datos empresariales para Windows. ([https://secure.php.net/manual/en/sqlsrv.installation.php Cómo compilar PHP con compatibilidad con SQLSRV])",
+       "config-dbsupport-mysql": "* [{{int:version-db-mariadb-url}} MariaDB] es la base de datos mayoritaria para MediaWiki y la que goza de mayor compatibilidad. MediaWiki también funciona con [{{int:version-db-myslql-url}} MySQL] y [{{int:version-db-percona-url}} Percona Server], que son compatibles con MariaDB. ([https://www.php.net/manual/es/mysql.installation.php Cómo compilar PHP con compatibilidad MySQL])",
+       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] es un sistema de base de datos popular de código abierto, alternativa a MySQL. ([https://www.php.net/manual/es/pgsql.installation.php Cómo compilar PHP con compatibilidad PostgreSQL]).",
+       "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite] es un sistema de base de datos ligero con gran compatibilidad con MediaWiki. ([https://www.php.net/manual/en/pdo.installation.php Cómo compilar PHP con compatibilidad SQLite], usando PDO)",
+       "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] es una base de datos comercial a nivel empresarial. ([https://www.php.net/manual/en/oci8.installation.php Cómo compilar PHP con compatibilidad con OCI8])",
+       "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] es un sistema comercial de base de datos empresariales para Windows. ([https://www.php.net/manual/en/sqlsrv.installation.php Cómo compilar PHP con compatibilidad con SQLSRV])",
        "config-header-mysql": "Configuración de MariaDB/MySQL",
        "config-header-postgres": "Configuración de PostgreSQL",
        "config-header-sqlite": "Configuración de SQLite",
        "config-license-help": "Muchos wikis públicos ponen todas las contribuciones bajo una [https://freedomdefined.org/Definition licencia libre].\nEsto ayuda a crear un sentido de propiedad comunitaria y alienta la contribución a largo plazo.\nEsto no es generalmente necesario para un wiki privado o corporativo.\n\nSi deseas poder utilizar texto de Wikipedia, y deseas que Wikipedia pueda aceptar el texto copiado de tu wiki, debes elegir <strong>{{int:config-license-cc-by-sa}}</strong>.\n\nWikipedia utilizaba anteriormente la licencia de documentación libre de GNU (GFDL).\nLa GFDL es una licencia válida, pero es difícil de entender.\nTambién es difícil reutilizar el contenido licenciado bajo la GFDL.",
        "config-email-settings": "Configuración de correo electrónico",
        "config-enable-email": "Activar el envío de correos electrónicos",
-       "config-enable-email-help": "Si quieres que el correo electrónico funcione, la [https://secure.php.net/manual/en/mail.configuration.php configuración PHP de correo electrónico] debe ser la correcta.\nSi no quieres ninguna funcionalidad de correo electrónico, puedes desactivarlas aquí.",
+       "config-enable-email-help": "Si quieres que el correo electrónico funcione, la [https://www.php.net/manual/en/mail.configuration.php configuración PHP de correo electrónico] debe ser la correcta.\nSi no quieres ninguna funcionalidad de correo electrónico, puedes desactivarlas aquí.",
        "config-email-user": "Activar correo electrónico entre usuarios",
        "config-email-user-help": "Permitir que todos los usuarios intercambien correos electrónicos si lo han activado en sus preferencias.",
        "config-email-usertalk": "Activar notificaciones de páginas de discusión de usuarios",
index d681fe3..61a1d2f 100644 (file)
        "config-pcre-no-utf8": "<strong>Fatal:</strong> PHREko PCRE modulua PCRE_UTF8 ko laguntza gabe bildu da.\nMediaWiki-k UTF-8 euskarria behar du behar bezala funtziona dezan.",
        "config-memory-raised": "PHP-ko <code>memory_limit</code> $1 da, $2-ra igota.",
        "config-memory-bad": "<strong>Warning:</strong> PHPko <code>memory_limit</code> $1 da.\nZiurrenik hau oso baxua da.\nInstalazioa huts egin dezake!",
-       "config-apc": "[https://secure.php.net/apc APC] instalatuta dago",
-       "config-apcu": "[https://secure.php.net/apcu APCu] instalatuta dago",
+       "config-apc": "[https://www.php.net/apc APC] instalatuta dago",
+       "config-apcu": "[https://www.php.net/apcu APCu] instalatuta dago",
        "config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension WinCache] instalatuta dago",
-       "config-no-cache-apcu": "<strong>Warning:</strong> Ezin izan da [https://secure.php.net/apcu APCu] edo [https://www.iis.net/downloads/microsoft/wincache-extension WinCache] aurkitu.\nObjektu katxea ez dago aktibatuta.",
+       "config-no-cache-apcu": "<strong>Warning:</strong> Ezin izan da [https://www.php.net/apcu APCu] edo [https://www.iis.net/downloads/microsoft/wincache-extension WinCache] aurkitu.\nObjektu katxea ez dago aktibatuta.",
        "config-mod-security": "<strong>Warning:</strong> Zure web zerbitzariak [https://modsecurity.org/mod_security] / mod_security2 aktibatu du. Honen konfigurazio komun asko sortu ahal dituzte arazoak MediaWikin  eta beste software batzuetan, hautazko edukia argitaratzeko aukera ematen dutenei erabiltzaileei.\nAhal izanez gero, desgaitu egin beharko litzateke. Bestela, kontsultatu [https://modsecurity.org/documentation/ mod_security documentation] edo jarri harremanetan zure ostalariarekin ausazko akatsak aurkitzen badituzu.",
        "config-diff3-bad": "GNU diff3 ez da aurkitu.",
        "config-git": "Git bertsio-kontrol software aurkitu da: <code>$1</code>",
        "config-type-oracle": "Oracle",
        "config-type-mssql": "Microsoft SQL Server",
        "config-support-info": "MediaWiki-k onartzen du hurrengo datu-base sistemak:\n\n$1\n\nListan ez baduzu ikusten erabili nahi duzun sistema, jarraitu goiko argibideak aktibatzeko.",
-       "config-dbsupport-mysql": "* [{{int:version-db-mysql-url}} MySQL] MediaWikiren lehenengoko helburua da eta primeran babesturik dago. MediaWikik ere [{{int:version-db-mariadb-url}} MariaDB]-rekin egiten du lan baita [{{int:version-db-percona-url}} Percona Server]-kin, MySQL-rekin balio dutenak. ([https://secure.php.net/manual/en/mysqli.installation.php How to compile PHP with MySQL support])",
-       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] iturburu irekiko datu basea sistema famatua da MySQL-rako alternatiba bezala. ([https://secure.php.net/manual/en/pgsql.installation.php How to compile PHP with PostgreSQL support])",
+       "config-dbsupport-mysql": "* [{{int:version-db-mysql-url}} MySQL] MediaWikiren lehenengoko helburua da eta primeran babesturik dago. MediaWikik ere [{{int:version-db-mariadb-url}} MariaDB]-rekin egiten du lan baita [{{int:version-db-percona-url}} Percona Server]-kin, MySQL-rekin balio dutenak. ([https://www.php.net/manual/en/mysqli.installation.php How to compile PHP with MySQL support])",
+       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] iturburu irekiko datu basea sistema famatua da MySQL-rako alternatiba bezala. ([https://www.php.net/manual/en/pgsql.installation.php How to compile PHP with PostgreSQL support])",
        "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite] oso ondo onartzen duen datu-basearen sistema arina da.\n ([http://www.php.net/manual/en/pdo.installation.php How to compile PHP with SQLite support], uses PDO)",
        "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] enpresa komertzial baten datu-basea da. ([http://www.php.net/manual/en/oci8.installation.php How to compile PHP with OCI8 support])",
-       "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] Windows-entzako enpresa komertzial baten datu-basea da.  ([https://secure.php.net/manual/en/sqlsrv.installation.php How to compile PHP with SQLSRV support])",
+       "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] Windows-entzako enpresa komertzial baten datu-basea da.  ([https://www.php.net/manual/en/sqlsrv.installation.php How to compile PHP with SQLSRV support])",
        "config-header-mysql": "MySQL hobespenak",
        "config-header-postgres": "PostgreSQL hobespenak",
        "config-header-sqlite": "SQLite hobespenak",
index bef363d..b17b093 100644 (file)
        "config-pcre-no-utf8": "<strong>مخرب:</strong> به‌ نظر می‌رسد پودمان پی‌سی‌آراییِ پی‌اچ‌پی بدون پشتیبانی پی‌سی‌آرایی_یو‌تی‌اف۸ تهیه شده‌است.\nمدیاویکی برای درست عمل کردن نیازمند پشتیبانی یوتی‌اف-۸ است.",
        "config-memory-raised": "PHP's <code>memory_limit</code>, نسخهٔ $1 است، به نسخهٔ $2 ارتقاء داده شده‌است.",
        "config-memory-bad": "'''هشدار:''' PHP's <code>memory_limit</code> نسخهٔ $1 است.\nاین ممکن است خیلی پایین باشد.\nممکن است نصب با مشکل رو‌به‌رو شود.",
-       "config-apc": "[https://secure.php.net/apc APC] نصب شده‌است.",
-       "config-apcu": "[https://secure.php.net/apcu APCu] نصب شده‌است",
+       "config-apc": "[https://www.php.net/apc APC] نصب شده‌است.",
+       "config-apcu": "[https://www.php.net/apcu APCu] نصب شده‌است",
        "config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension WinCache] نصب شده‌است.",
-       "config-no-cache-apcu": "<strong>هشدار:</strong> پیوند [https://secure.php.net/apcu APCu] یا [https://www.iis.net/downloads/microsoft/wincache-extension WinCache] یافت نشد. ذخیره شی فعال نیست.",
+       "config-no-cache-apcu": "<strong>هشدار:</strong> پیوند [https://www.php.net/apcu APCu] یا [https://www.iis.net/downloads/microsoft/wincache-extension WinCache] یافت نشد. ذخیره شی فعال نیست.",
        "config-mod-security": "'''هشدار:''' وب سرور شما [https://modsecurity.org/ mod_security] فعال است.اگر اشتباه پیکربندی شده‌‌ باشد،می تواند باعث ایجاد مشکلاتی برای مدیاویکی یا دیگر نرم‌افزاری شود که به کاربران اجازه می‌دهد پیام دلخواه ارسال کنند.\nبه [https://modsecurity.org/documentation/ mod_security documentation] مراجعه کنید یا اگر با خطاهای اتفاقی مواجه شدید با پشتیبانی میزبان خود در تماس باشید.",
        "config-diff3-bad": "ابزار مقایسه متن تفاوت۳ گنو پیدا نشد. می‌توانید فعلاً این پیام را نادیده بگیرید، اما ممکن است به دفعات بیشتری دچار تعارض ویرایشی شوید.",
        "config-git": "کنترل نسخهٔ نرم‌افزار گیت پیدا شد: <code>$1</code>.",
        "config-type-mysql": "MariaDB، مای‌اس‌کیو‌ال (یا سازگار)",
        "config-type-mssql": "سرور مایکروسافت اس‌کیو‌ال",
        "config-support-info": "مدیاویکی سامانه‌های پایگاه اطلاعاتی زیر را حمایت می‌کند:\n$1\nاگر متوجه سامانه پایگاه اطلاعاتی که سعی دارید از فهرست زیر استفاده کنید، نمی‌شوید، بنابراین دستورالعمل‌های مرتبط در بالا را برای فعال کردن پشتیبانی دنبال کنید.",
-       "config-dbsupport-mysql": "*[{{int:version-db-mariadb-url}} MariaDB] مهم‌ترین هدف برای مدیاویکی است و بهترین پشتیبانی. مدیاویکی همچنین کار می‌کند با [{{int:version-db-mysql-url}} MariaDB] و [{{int:version-db-percona-url}} Percona Server] که با MariaDB سازگار هستند.([https://secure.php.net/manual/en/mysqli.installation.php چگونه php را با MariaDB کامپایل کنیم])",
-       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} پستگرس‌کیوال] یک سامانه پایگاه اطلاعات متن‌باز پر‌طرفدار است که جایگزینی برای مای‌اس‌کیوال است. ([https://secure.php.net/manual/en/pgsql.installation.php راهنمای تنظیم کردن پی‌اچ‌پی به همراه پستگرس‌کیوال])",
-       "config-dbsupport-sqlite": "*[{{int:version-db-sqlite-url}} اس‌کیولایت] یک سامانه پایگاه اطلاعاتی کم حجمی است که بسیار خوب پشتیبانی شده‌است.\n([https://secure.php.net/manual/en/pdo.installation.php چگونگی کامپایل پی‌اچ‌پی با اس‌کیولایت]، از PDO استفاده می‌کند)",
-       "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] یک پایگاه اطلاعاتی کار تبلیغاتی است.\n([https://secure.php.net/manual/en/oci8.installation.php How to compile PHP with OCI8 support])",
-       "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] یک پایگاه اطلاعاتی موسسهٔ تبلیغاتی برای وینذوز است. ([https://secure.php.net/manual/en/sqlsrv.installation.php How to compile PHP with SQLSRV support])",
+       "config-dbsupport-mysql": "*[{{int:version-db-mariadb-url}} MariaDB] مهم‌ترین هدف برای مدیاویکی است و بهترین پشتیبانی. مدیاویکی همچنین کار می‌کند با [{{int:version-db-mysql-url}} MariaDB] و [{{int:version-db-percona-url}} Percona Server] که با MariaDB سازگار هستند.([https://www.php.net/manual/en/mysqli.installation.php چگونه php را با MariaDB کامپایل کنیم])",
+       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} پستگرس‌کیوال] یک سامانه پایگاه اطلاعات متن‌باز پر‌طرفدار است که جایگزینی برای مای‌اس‌کیوال است. ([https://www.php.net/manual/en/pgsql.installation.php راهنمای تنظیم کردن پی‌اچ‌پی به همراه پستگرس‌کیوال])",
+       "config-dbsupport-sqlite": "*[{{int:version-db-sqlite-url}} اس‌کیولایت] یک سامانه پایگاه اطلاعاتی کم حجمی است که بسیار خوب پشتیبانی شده‌است.\n([https://www.php.net/manual/en/pdo.installation.php چگونگی کامپایل پی‌اچ‌پی با اس‌کیولایت]، از PDO استفاده می‌کند)",
+       "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] یک پایگاه اطلاعاتی کار تبلیغاتی است.\n([https://www.php.net/manual/en/oci8.installation.php How to compile PHP with OCI8 support])",
+       "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] یک پایگاه اطلاعاتی موسسهٔ تبلیغاتی برای وینذوز است. ([https://www.php.net/manual/en/sqlsrv.installation.php How to compile PHP with SQLSRV support])",
        "config-header-mysql": "تنظیمات MariaDB/مای‌اس‌کیو‌ال",
        "config-header-postgres": "تنظیمات پست‌گر‌اس‌کیو‌ال",
        "config-header-sqlite": "تنظیمات اس‌کیو‌لایت",
        "config-license-help": "بسیاری از وبگاه‌ها ویرایش‌های ها را با  [https://freedomdefined.org/Definition اجازه‌نامهٔ آزاد] منتشر می‌کنند.\nاین کار به داشتن حس مالکیت جمعی کمک می‌کند و ویرایش‌های طولانی مدت را اشاعه می‌دهد.\nاین برای ویکی‌های خصوصی یا سازمانی الزامی نیست.\n\nاگر شما می‌خواهید از متون ویکی‌پدیا استفاده کنید، یا اینکه به ویکی‌پدیا اجازه دهید از متون شما استفاده کند باید متون خود را با <strong>{{int:config-license-cc-by-sa}}</strong> منتشر کنید.\n\nویکی‌پدیا در گذشته از اجازه‌نامهٔ داده‌های آزاد گنو استفاده می‌کرد.\nاین اجازه‌نامه مورد قبول است، ولی فهم آن آسان نیست.\nهمچنین استفادهٔ دوباره از متون تحت اجازه‌نامهٔ داده‌های آزاد گنو به سختی انجام می‌گیرد.",
        "config-email-settings": "تنظیمات ایمیل",
        "config-enable-email": "فعال‌سازی ایمیل خروجی",
-       "config-enable-email-help": "اگر می‌خواهید ارسال ایمیل کار کند، [https://secure.php.net/manual/en/mail.configuration.php PHP's mail settings] نیازمند پیکربندی صحیح است.\nاگر هیچ قابلیت ایمیلی نمی‌خواهید، می‌توانید آنها را اینجا غیر‌فعال کنید.",
+       "config-enable-email-help": "اگر می‌خواهید ارسال ایمیل کار کند، [https://www.php.net/manual/en/mail.configuration.php PHP's mail settings] نیازمند پیکربندی صحیح است.\nاگر هیچ قابلیت ایمیلی نمی‌خواهید، می‌توانید آنها را اینجا غیر‌فعال کنید.",
        "config-email-user": "فعال کردن ایمیل کاربر به کاربر",
        "config-email-user-help": "به همهٔ کاربرانی که ارسال ایمیل را در ترجیحات خود فعال کرده‌اند، اجازه داده خواهد شد که به یکدیگر ایمیل ارسال کنند.",
        "config-email-usertalk": "فعال کردن اطلاع‌رسانی صفحهٔ بحث کاربر",
index a134db8..fbc80f8 100644 (file)
        "config-outdated-sqlite": "<strong>Varoitus:</strong> sinulla on käytössä SQLite $1, joke on vanhempi kuin vähintään vaadittava versio $2. SQLite ei ole saatavilla.",
        "config-no-fts3": "<strong>Varoitus:</strong> SQLite on koostettu ilman [//sqlite.org/fts3.html FTS3-moduulia], hakuominaisuudet eivät ole käytössä tässä taustajärjestelmässä.",
        "config-pcre-old": "<strong>Tärkeää:</strong> PCRE $1 tai uudempi versio tarvitaan.\nPHP-binäärisi on linkitetty versiolla PCRE $2.\n[https://www.mediawiki.org/wiki/Manual:Errors_and_symptoms/PCRE Lisätietoja].",
+       "config-pcre-no-utf8": "<strong>Vakava virhe:</strong> PHP:n PCRE-moduuli näyttää olevan koottu ilman PCRE_UTF8 tukea.\nMediaWiki edellyttää UTF-8 tukea toimiakseen oikein.",
        "config-memory-raised": "PHP:n <code>memory_limit</code> on $1, nostetaan arvoon $2.",
        "config-memory-bad": "'''Varoitus:''' PHP:n <code>memory_limit</code> on $1.\nTämä on luultavasti liian alhainen.\nAsennus saattaa epäonnistua!",
-       "config-apc": "[https://secure.php.net/apc APC] on asennettu",
-       "config-apcu": "[https://secure.php.net/apcu APCu] on asennettu",
+       "config-apc": "[https://www.php.net/apc APC] on asennettu",
+       "config-apcu": "[https://www.php.net/apcu APCu] on asennettu",
        "config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension WinCache] on asennettu",
        "config-diff3-bad": "GNU diff3:a ei löytynyt.",
        "config-git": "Löydetty Git-versionhallintaohjelmisto: <code>$1</code>",
        "config-type-oracle": "Oracle",
        "config-type-mssql": "Microsoft SQL Server",
        "config-support-info": "MediaWiki tukee seuraavia tietokantajärjestelmiä:\n\n$1\n\nJos et näe tietokantajärjestelmää, jota yrität käyttää, listattuna alhaalla, seuraa yläpuolella olevia ohjeita tuen aktivoimiseksi.",
-       "config-dbsupport-mysql": "* [{{int:version-db-mariadb-url}} MariaDB] on MediaWikin ensisijainen kohde ja se on myös parhaiten tuettu. MediaWiki voi myös käyttää [{{int:version-db-mysql-url}} MySQL]- sekä [{{int:version-db-percona-url}} Percona Server]-järjestelmiä, jotka ovat MariaDB-yhteensopivia. ([https://secure.php.net/manual/en/mysqli.installation.php Miten käännetään PHP MySQL-tuella])",
-       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] on suosittu avoimen lähdekoodin tietokantajärjestelmä vaihtoehtona MySQL:lle. ([https://secure.php.net/manual/en/pgsql.installation.php Kuinka käännetään PHP PostgreSQL-tuella])",
-       "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite] on kevyt tietokantajärjestelmä, jota tuetaan hyvin. ([https://secure.php.net/manual/en/pdo.installation.php Miten käännetään PHP SQLite-tuella], käyttää PDO:ta)",
-       "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] on kaupallinen yritystietokanta. ([https://secure.php.net/manual/en/oci8.installation.php Kuinka käännetään PHP OCI8-tuella])",
-       "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] on kaupallinen yritystietokanta Windowsille. ([https://secure.php.net/manual/en/sqlsrv.installation.php Miten käännetään PHP SQLSRV-tuella])",
+       "config-dbsupport-mysql": "* [{{int:version-db-mariadb-url}} MariaDB] on MediaWikin ensisijainen kohde ja se on myös parhaiten tuettu. MediaWiki voi myös käyttää [{{int:version-db-mysql-url}} MySQL]- sekä [{{int:version-db-percona-url}} Percona Server]-järjestelmiä, jotka ovat MariaDB-yhteensopivia. ([https://www.php.net/manual/en/mysqli.installation.php Miten käännetään PHP MySQL-tuella])",
+       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] on suosittu avoimen lähdekoodin tietokantajärjestelmä vaihtoehtona MySQL:lle. ([https://www.php.net/manual/en/pgsql.installation.php Kuinka käännetään PHP PostgreSQL-tuella])",
+       "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite] on kevyt tietokantajärjestelmä, jota tuetaan hyvin. ([https://www.php.net/manual/en/pdo.installation.php Miten käännetään PHP SQLite-tuella], käyttää PDO:ta)",
+       "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] on kaupallinen yritystietokanta. ([https://www.php.net/manual/en/oci8.installation.php Kuinka käännetään PHP OCI8-tuella])",
+       "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] on kaupallinen yritystietokanta Windowsille. ([https://www.php.net/manual/en/sqlsrv.installation.php Miten käännetään PHP SQLSRV-tuella])",
        "config-header-mysql": "MariaDB/MySQL-asetukset",
        "config-header-postgres": "PostgreSQL-asetukset",
        "config-header-sqlite": "SQLite-asetukset",
        "config-license-help": "Monet julkiset wikit käyttävät muokkauksiin [https://freedomdefined.org/Definition vapaata lisenssiä].\nTämä auttaa luomaan yhteisöllisen omistajuuden tunteen ja kannustaa pitkäkestoiseen muokkaamiseen.\nSe ei ole yleensä tarpeen yksityiselle tai yrityksen wikille.\n\nJos haluat pystyä käyttämään tekstiä Wikipediasta, ja haluat Wikipedian pystyvän hyväksymään wikistäsi kopioitua tekstiä, sinun tulisi valita <strong>{{int:config-license-cc-by-sa}}</strong>.\n\nWikipedia käytti aiemmin GNU Free Documentation Licenseä.\nGFDL on kelvollinen lisenssi, mutta vaikea ymmärtää.\nOn myös vaikeaa käyttää uudelleen GFDL-lisensöityä sisältöä.",
        "config-email-settings": "Sähköpostiasetukset",
        "config-enable-email": "Ota käyttöön sähköpostien lähetys",
-       "config-enable-email-help": "Jotta sähköposti toimii, [https://secure.php.net/manual/en/mail.configuration.php PHP:n sähköpostiasetukset] täytyy asettaa oikein.\nJos et halua käyttää sähköpostiominaisuuksia, ne voi kytkeä pois päältä tästä.",
+       "config-enable-email-help": "Jotta sähköposti toimii, [https://www.php.net/manual/en/mail.configuration.php PHP:n sähköpostiasetukset] täytyy asettaa oikein.\nJos et halua käyttää sähköpostiominaisuuksia, ne voi kytkeä pois päältä tästä.",
        "config-email-user": "Ota käyttöön käyttäjältä käyttäjälle sähköpostit",
        "config-email-user-help": "Salli käyttäjien lähettää sähköpostia toisilleen jos he ovat ottaneet käyttöön toiminnon asetuksissaan.",
        "config-email-usertalk": "Ota käyttöön käyttäjien keskustelusivusta ilmoittaminen",
        "config-install-done": "<strong>Onnittelut!</strong>\nOlet asentanut MediaWikin.\n\nAsennusohjelma on luonut <code>LocalSettings.php</code> -tiedoston.\nSiinä on kaikki MediaWikin asetukset.\n\nLataa tiedosto ja laita se MediaWikin asennushakemistoon (sama kuin missä on index.php). Lataamisen olisi pitänyt alkaa automaattisesti.\n\nMikäli latausta ei tarjottu tai keskeytit latauksen, käynnistä se uudestaan tästä linkistä:\n\n$3\n\n<strong>Huom:</strong> Mikäli et nyt lataa tiedostoa, luotu tiedosto ei ole saatavissa myöhemmin, jos poistut asennuksesta lataamatta sitä.\n\nKun olet laittanut tiedoston oikeaan paikkaan, voit <strong>[$2 mennä wikiisi]</strong>.",
        "config-install-done-path": "<strong>Onnittelut!</strong>\nOlet asentanut MediaWikin.\n\nAsennusohjelma on luonut <code>LocalSettings.php</code> -tiedoston.\nSiinä on kaikki MediaWikin asetukset.\n\nLataa tiedosto ja laita se sijaintiin <code>$4</code>. Lataamisen olisi pitänyt alkaa automaattisesti.\n\nMikäli latausta ei tarjottu tai keskeytit latauksen, käynnistä se uudestaan tästä linkistä:\n\n$3\n\n<strong>Huom:</strong> Mikäli et nyt lataa tiedostoa, luotu tiedosto ei ole saatavissa myöhemmin, jos poistut asennuksesta lataamatta sitä.\n\nKun olet laittanut tiedoston oikeaan paikkaan, voit <strong>[$2 mennä wikiisi]</strong>.",
        "config-install-success": "MediaWiki on asennettu onnistuneesti. Voit nyt vierailla <$1$2> katsellaksesi wikiäsi. Jos sinulla on kysyttävää, tutustu usein kysyttyjen kysymysten luetteloon: <https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ> tai käytä jotakin sivulla linkitettyä tukifoorumia.",
+       "config-install-db-success": "Tietokanta asennettiin onnistuneesti",
        "config-download-localsettings": "Lataa <code>LocalSettings.php</code>",
        "config-help": "ohje",
        "config-help-tooltip": "Klikkaa laajentaaksesi",
index 2c154e7..eb1805c 100644 (file)
        "config-pcre-no-utf8": "<strong>Erreur fatale :</strong> le module PCRE de PHP semble être compilé sans la prise en charge de PCRE_UTF8.\nMediaWiki a besoin de la gestion d’UTF-8 pour fonctionner correctement.",
        "config-memory-raised": "Le paramètre <code>memory_limit</code> de PHP était à $1, porté à $2.",
        "config-memory-bad": "<strong>Attention :</strong> Le paramètre <code>memory_limit</code> de PHP est à $1.\nCette valeur est probablement trop faible.\nIl est possible que l’installation échoue !",
-       "config-apc": "[https://secure.php.net/apc APC] est installé",
-       "config-apcu": "[https://secure.php.net/apcu APCu] est installé",
+       "config-apc": "[https://www.php.net/apc APC] est installé",
+       "config-apcu": "[https://www.php.net/apcu APCu] est installé",
        "config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension WinCache] est installé",
-       "config-no-cache-apcu": "<strong>Attention :</strong> impossible de trouver [https://secure.php.net/apcu APCu] ou [https://www.iis.net/downloads/microsoft/wincache-extension WinCache].\nLa mise en cache d’objets n’est pas activée.",
+       "config-no-cache-apcu": "<strong>Attention :</strong> impossible de trouver [https://www.php.net/apcu APCu] ou [https://www.iis.net/downloads/microsoft/wincache-extension WinCache].\nLa mise en cache d’objets n’est pas activée.",
        "config-mod-security": "<strong>Attention :</strong> votre serveur web a activé [https://modsecurity.org/ mod_security]/mod_security2 . Dans plusieurs configurations communes cela pose des problèmes à MediaWiki ou à d’autres applications qui permettent aux utilisateurs de publier un contenu quelconque. \nSi possible, ceci devrait être désactivé. Sinon, reportez-vous à [https://modsecurity.org/documentation/ la documentation de mod_security] ou contactez l’assistance de votre hébergeur si vous rencontrez des erreurs aléatoires.",
        "config-diff3-bad": "L’utilitaire de comparaison de texte GNU diff3 est introuvable. Vous pouvez l’ignorer pour le moment, mais cela peut provoquer des conflits de modification plus souvent.",
        "config-git": "Logiciel de contrôle de version Git trouvé : <code>$1</code>.",
        "config-type-oracle": "Oracle",
        "config-type-mssql": "Microsoft SQL Server",
        "config-support-info": "MediaWiki prend en charge ces systèmes de bases de données :\n\n$1\n\nSi vous ne voyez pas le système de base de données que vous essayez d’utiliser ci-dessous, alors suivez les instructions ci-dessus (voir liens) pour activer la prise en charge.",
-       "config-dbsupport-mysql": "* [{{int:version-db-mariadb-url}} MariaDB] est le premier choix pour MediaWiki et est le mieux pris en charge. MediaWiki fonctionne aussi avec [{{int:version-db-mysql-url}} MySQL] et [{{int:version-db-percona-url}} Percona Server], qui sont compatibles avec MariaDB. ([https://secure.php.net/manual/en/mysqli.installation.php Comment compiler PHP avec la prise en charge de MySQL])",
-       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] est un système de base de données populaire en ''source ouverte'' qui peut être une alternative à MySQL ([https://secure.php.net/manual/en/pgsql.installation.php Comment compiler PHP avec la prise en charge de PostgreSQL]).",
-       "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite] est un système de base de données léger bien pris en charge ([https://secure.php.net/manual/en/pdo.installation.php Comment compiler PHP avec la prise en charge de SQLite], en utilisant PDO).",
-       "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] est un système commercial de gestion de base de données d’entreprise. ([https://secure.php.net/manual/en/oci8.installation.php Comment compiler PHP avec la prise en charge d’OCI8])",
-       "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] est une base de données commerciale d’entreprise pour Windows. ([https://secure.php.net/manual/en/sqlsrv.installation.php Comment compiler PHP avec la prise en charge de SQLSRV])",
+       "config-dbsupport-mysql": "* [{{int:version-db-mariadb-url}} MariaDB] est le premier choix pour MediaWiki et est le mieux pris en charge. MediaWiki fonctionne aussi avec [{{int:version-db-mysql-url}} MySQL] et [{{int:version-db-percona-url}} Percona Server], qui sont compatibles avec MariaDB. ([https://www.php.net/manual/en/mysqli.installation.php Comment compiler PHP avec la prise en charge de MySQL])",
+       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] est un système de base de données populaire en ''source ouverte'' qui peut être une alternative à MySQL ([https://www.php.net/manual/en/pgsql.installation.php Comment compiler PHP avec la prise en charge de PostgreSQL]).",
+       "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite] est un système de base de données léger bien pris en charge ([https://www.php.net/manual/en/pdo.installation.php Comment compiler PHP avec la prise en charge de SQLite], en utilisant PDO).",
+       "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] est un système commercial de gestion de base de données d’entreprise. ([https://www.php.net/manual/en/oci8.installation.php Comment compiler PHP avec la prise en charge d’OCI8])",
+       "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] est une base de données commerciale d’entreprise pour Windows. ([https://www.php.net/manual/en/sqlsrv.installation.php Comment compiler PHP avec la prise en charge de SQLSRV])",
        "config-header-mysql": "Paramètres de MariaDB/MySQL",
        "config-header-postgres": "Paramètres de PostgreSQL",
        "config-header-sqlite": "Paramètres de SQLite",
        "config-license-help": "Beaucoup de wikis publics mettent l’ensemble des contributions sous une [https://freedomdefined.org/Definition/Fr licence libre].\nCela contribue à créer un sentiment d’appartenance à une communauté et encourage les contributions sur le long terme.\nCe n’est généralement pas nécessaire pour un wiki privé ou d’entreprise.\n\nSi vous souhaitez utiliser des textes de Wikipédia, et souhaitez que Wikipédia puisse réutiliser des textes copiés depuis votre wiki, vous devriez choisir <strong>{{int:config-license-cc-by-sa}}</strong>.\n\nWikipédia utilisait auparavant la Licence de Documentation Libre GNU (GFDL).\nC’est une licence valide, mais difficile à comprendre. \nIl est aussi difficile de réutiliser du contenu sous la licence GFDL.",
        "config-email-settings": "Paramètres de courriel",
        "config-enable-email": "Activer les courriels sortants",
-       "config-enable-email-help": "Si vous souhaitez utiliser le courriel, vous devez avoir les [https://secure.php.net/manual/en/mail.configuration.php paramètres courriel de PHP] configurés correctement (texte en anglais).\nSi vous ne voulez pas du service de courriel, vous pouvez le désactiver ici.",
+       "config-enable-email-help": "Si vous souhaitez utiliser le courriel, vous devez avoir les [https://www.php.net/manual/en/mail.configuration.php paramètres courriel de PHP] configurés correctement (texte en anglais).\nSi vous ne voulez pas du service de courriel, vous pouvez le désactiver ici.",
        "config-email-user": "Activer les courriers électroniques d'utilisateur à utilisateur",
        "config-email-user-help": "Permet à tous les utilisateurs d'envoyer des courriels à d'autres utilisateurs si cela est activé dans leurs préférences.",
        "config-email-usertalk": "Activer la notification des pages de discussion des utilisateurs",
index fa84d5f..033692a 100644 (file)
@@ -30,7 +30,7 @@
        "config-page-existingwiki": "Vouiqui ègzistent",
        "config-env-php": "PHP $1 est enstalâ.",
        "config-memory-raised": "Lo paramètre <code>memory_limit</code> de PHP ére a $1, portâ a $2.",
-       "config-apc": "[https://secure.php.net/apc APC] est enstalâ",
+       "config-apc": "[https://www.php.net/apc APC] est enstalâ",
        "config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension WinCache] est enstalâ",
        "config-diff3-bad": "GNU diff3 entrovâblo.",
        "config-db-type": "Tipo de bâsa de balyês :",
index c5d34f3..86c0c57 100644 (file)
        "config-pcre-no-utf8": "<strong>Erro fatal:</strong> Semella que o módulo PCRE do PHP foi compilado sen o soporte PCRE_UTF8.\nMediaWiki necesita soporte UTF-8 para funcionar correctamente.",
        "config-memory-raised": "O parámetro <code>memory_limit</code> do PHP é $1. Aumentado a $2.",
        "config-memory-bad": "<strong>Atención:<strong> O parámetro <code>memory_limit</code> do PHP é $1.\nProbablemente é un valor baixo de máis.\nA instalación pode fallar!",
-       "config-apc": "[https://secure.php.net/apc APC] está instalado",
-       "config-apcu": "[https://secure.php.net/apcu APCu] está instalado",
+       "config-apc": "[https://www.php.net/apc APC] está instalado",
+       "config-apcu": "[https://www.php.net/apcu APCu] está instalado",
        "config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension WinCache] está instalado",
-       "config-no-cache-apcu": "<strong>Advertencia:</strong> Non se puido atopar [https://secure.php.net/apcu APCu], [http://xcache.lighttpd.net/ XCache] ou [https://www.iis.net/downloads/microsoft/wincache-extension WinCache].\nA caché de obxectos non está activada.",
+       "config-no-cache-apcu": "<strong>Advertencia:</strong> Non se puido atopar [https://www.php.net/apcu APCu], [http://xcache.lighttpd.net/ XCache] ou [https://www.iis.net/downloads/microsoft/wincache-extension WinCache].\nA caché de obxectos non está activada.",
        "config-mod-security": "<strong>Atención:</strong> O seu servidor web ten o [https://modsecurity.org/ mod_security] activado. Se estivese mal configurado, pode causar problemas a MediaWiki ou calquera outro software que permita aos usuarios publicar contidos arbitrarios.\nOlle a [https://modsecurity.org/documentation/ documentación do mod_security] ou póñase en contacto co soporte do seu servidor se atopa erros aleatorios.",
        "config-diff3-bad": "GNU diff3 non se atopou.",
        "config-git": "Atopouse o software de control da versión de Git: <code>$1</code>.",
        "config-type-oracle": "Oracle",
        "config-type-mssql": "Microsoft SQL Server",
        "config-support-info": "MediaWiki soporta os seguintes sistemas de bases de datos:\n\n$1\n\nSe non ve listado a continuación o sistema de base de datos que intenta usar, siga as instrucións ligadas enriba para activar o soporte.",
-       "config-dbsupport-mysql": "* [{{int:version-db-mariadb-url}} MariaDB] é o obxectivo principal para MediaWiki e é o que mellor soportado está. MediaWiki tamén funciona con [{{int:version-db-mysql-url}} MySQL] e [{{int:version-db-percona-url}} Percona Server], que son compatibles con MariaDB. ([https://secure.php.net/manual/en/mysqli.installation.php Como compilar PHP con compatibilidade MySQL])",
-       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] é un sistema de base de datos popular e de código aberto como alternativa a MySQL. ([https://secure.php.net/manual/en/pgsql.installation.php Como compilar PHP con compatibilidade PostgreSQL])",
-       "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite] é un sistema de base de datos lixeiro moi ben soportado. ([https://secure.php.net/manual/en/pdo.installation.php Como compilar o PHP con soporte SQLite], emprega PDO)",
-       "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] é un sistema comercial de xestión de base de datos a nivel empresarial. ([https://secure.php.net/manual/en/oci8.installation.php Como compilar PHP con soporte OCI8])",
-       "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] é un sistema comercial de xestión de base de datos de nivel empresarial para Windows. ([https://secure.php.net/manual/en/sqlsrv.installation.php Como compilar o PHP con compatibilidade SQLSRV])",
+       "config-dbsupport-mysql": "* [{{int:version-db-mariadb-url}} MariaDB] é o obxectivo principal para MediaWiki e é o que mellor soportado está. MediaWiki tamén funciona con [{{int:version-db-mysql-url}} MySQL] e [{{int:version-db-percona-url}} Percona Server], que son compatibles con MariaDB. ([https://www.php.net/manual/en/mysqli.installation.php Como compilar PHP con compatibilidade MySQL])",
+       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] é un sistema de base de datos popular e de código aberto como alternativa a MySQL. ([https://www.php.net/manual/en/pgsql.installation.php Como compilar PHP con compatibilidade PostgreSQL])",
+       "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite] é un sistema de base de datos lixeiro moi ben soportado. ([https://www.php.net/manual/en/pdo.installation.php Como compilar o PHP con soporte SQLite], emprega PDO)",
+       "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] é un sistema comercial de xestión de base de datos a nivel empresarial. ([https://www.php.net/manual/en/oci8.installation.php Como compilar PHP con soporte OCI8])",
+       "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] é un sistema comercial de xestión de base de datos de nivel empresarial para Windows. ([https://www.php.net/manual/en/sqlsrv.installation.php Como compilar o PHP con compatibilidade SQLSRV])",
        "config-header-mysql": "Configuración MariaDB/MySQL",
        "config-header-postgres": "Configuración do PostgreSQL",
        "config-header-sqlite": "Configuración do SQLite",
        "config-license-help": "Moitos wikis públicos liberan todas as súas contribucións baixo unha [https://freedomdefined.org/Definition/Gl licenza libre].\nIsto axuda a crear un sentido de propiedade comunitaria e anima a seguir contribuíndo durante moito tempo.\nXeralmente, non é necesario nos wikis privados ou de empresas.\n\nSe quere poder empregar textos da Wikipedia, así como que a Wikipedia poida aceptar textos copiados do seu wiki, escolla a licenza <strong>{{int:config-license-cc-by-sa}}</strong>.\n\nA licenza de documentación libre de GNU era a licenza anterior da Wikipedia.\nMalia aínda ser unha licenza válida, é difícil de entender.\nTamén é difícil reusar contidos baixo esta licenza.",
        "config-email-settings": "Configuración do correo electrónico",
        "config-enable-email": "Activar os correos electrónicos de saída",
-       "config-enable-email-help": "Se quere que o correo electrónico funcione, cómpre configurar os [https://secure.php.net/manual/en/mail.configuration.php parámetros PHP de correo] correctamente.\nSe non quere ningunha característica de correo electrónico, pode desactivalas aquí.",
+       "config-enable-email-help": "Se quere que o correo electrónico funcione, cómpre configurar os [https://www.php.net/manual/en/mail.configuration.php parámetros PHP de correo] correctamente.\nSe non quere ningunha característica de correo electrónico, pode desactivalas aquí.",
        "config-email-user": "Activar o intercambio de correos electrónicos entre usuarios",
        "config-email-user-help": "Permitir que todos os usuarios intercambien correos electrónicos, se o teñen activado nas súas preferencias.",
        "config-email-usertalk": "Activar a notificación da páxina de conversa de usuario",
index 48dfbe3..337a179 100644 (file)
@@ -49,7 +49,7 @@
        "config-pcre-no-utf8": "'''Fatale Fähler: S PHP-Modul PCRE isch schyns ohni PCRE_UTF8-Unterstitzig kompiliert wore.'''\nMediaWiki brucht d UTF-8-Unterstitzi zum fählerfrej lauffähig syy.",
        "config-memory-raised": "Dr PHP-Parameter <code>memory_limit</code> lyt bi $1 un isch uf $2 uffegsetzt wore.",
        "config-memory-bad": "'''Warnig:''' Dr PHP-Parameter <code>memory_limit</code> lyt bi $1.\nDää Wärt isch wahrschyns z nider.\nDr Inschtallationsvorgang chennt wäge däm fählschlaa!",
-       "config-apc": "[https://secure.php.net/apc APC] isch inschtalliert",
+       "config-apc": "[https://www.php.net/apc APC] isch inschtalliert",
        "config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension WinCache] isch inschtalliert",
        "config-diff3-bad": "GNU diff3 isch nit gfunde wore.",
        "config-imagemagick": "ImageMagick isch gfunde wore: <code>$1</code>.\nMiniaturaasichte vu Bilder sin megli, sobald s Uffelade vu Dateie aktiviert isch.",
index 756c87c..08e56e0 100644 (file)
        "config-pcre-no-utf8": "<strong>שגיאה סופנית</strong>: נראה שמודול PCRE של PHP מהודר ללא תמיכה ב־PCRE_UTF8.\nמדיה־ויקי דורשת תמיכה ב־UTF-8 לפעילות נכונה.",
        "config-memory-raised": "ערך האפשרות <code>memory_limit</code> של PHP הוא $1, הועלה ל־$2.",
        "config-memory-bad": "'''אזהרה:''' ערך האפשרות <code>memory_limit</code> של PHP הוא $1.\nזה כנראה נמוך מדי.\nההתקנה עשויה להיכשל!",
-       "config-apc": "[https://secure.php.net/apc APC] מותקן",
-       "config-apcu": "[https://secure.php.net/apcu APCu] מותקן",
+       "config-apc": "[https://www.php.net/apc APC] מותקן",
+       "config-apcu": "[https://www.php.net/apcu APCu] מותקן",
        "config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension WinCache] מותקן",
-       "config-no-cache-apcu": "<strong>אזהרה:</strong> לא נמצא [https://secure.php.net/apcu APCu]‏ או [https://www.iis.net/downloads/microsoft/wincache-extension WinCache].\nמטמון עצמים לא מופעל.",
+       "config-no-cache-apcu": "<strong>אזהרה:</strong> לא נמצא [https://www.php.net/apcu APCu]‏ או [https://www.iis.net/downloads/microsoft/wincache-extension WinCache].\nמטמון עצמים לא מופעל.",
        "config-mod-security": "'''אזהרה''': בשרת הווב שלך מופעל [https://modsecurity.org/ mod_security]. אם הוא לא מוגדר טוב, זה יכול לגרום לבעיות במדיה־ויקי ובתכנה אחרת שמאפשרת למשתמשים לשלוח תוכן שרירותי.\nיש לקרוא את [https://modsecurity.org/documentation/ התיעוד של mod_security] או ליצור קשר עם אנשי התמיכה של שירותי האירוח שלכם אם מופיעות לך שגיאות אקראיות.",
        "config-diff3-bad": "GNU diff3 לא נמצא.",
        "config-git": "נמצאה Git, תכנת בקרת התצורה: <code dir=\"ltr\">$1</code>.",
        "config-type-mysql": "MariaDB‏, MySQL, או תואם",
        "config-type-mssql": "Microsoft SQL Server",
        "config-support-info": "מדיה־ויקי תומכת במערכות מסדי הנתונים הבאות:\n\n$1\n\nאם אינך רואה את מסד הנתונים שלך ברשימה, יש לעקוב אחר ההוראות המקושרות לעיל כדי להפעיל את התמיכה.",
-       "config-dbsupport-mysql": "* [{{int:version-db-mariadb-url}} MariaDB] הוא היעד העיקרי עבור מדיה־ויקי ולו התמיכה הטובה ביותר. מדיה־ויקי עובדת גם עם [{{int:version-db-mysql-url}} MySQL] ועם [{{int:version-db-percona-url}} Percona Server], שתואמים ל־MariaDB. (ר׳ [https://secure.php.net/manual/en/mysql.installation.php how to compile PHP with MySQL support])",
-       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] הוא מסד נתונים נפוץ בקוד פתוח והוא נפוץ בתור חלופה ל־MySQL. (ר׳ [https://secure.php.net/manual/en/pgsql.installation.php how to compile PHP with PostgreSQL support]).",
-       "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite] הוא מסד נתונים קליל עם תמיכה טובה מאוד. (ר׳ [https://secure.php.net/manual/en/pdo.installation.php How to compile PHP with SQLite support], משתמש ב־PDO)",
-       "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] הוא מסד נתונים עסקי מסחרי. (ר׳ [https://secure.php.net/manual/en/oci8.installation.php How to compile PHP with OCI8 support])",
-       "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] הוא מסד נתונים עסקי מסחרי לחלונות. ([https://secure.php.net/manual/en/sqlsrv.installation.php How to compile PHP with SQLSRV support])",
+       "config-dbsupport-mysql": "* [{{int:version-db-mariadb-url}} MariaDB] הוא היעד העיקרי עבור מדיה־ויקי ולו התמיכה הטובה ביותר. מדיה־ויקי עובדת גם עם [{{int:version-db-mysql-url}} MySQL] ועם [{{int:version-db-percona-url}} Percona Server], שתואמים ל־MariaDB. (ר׳ [https://www.php.net/manual/en/mysql.installation.php how to compile PHP with MySQL support])",
+       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] הוא מסד נתונים נפוץ בקוד פתוח והוא נפוץ בתור חלופה ל־MySQL. (ר׳ [https://www.php.net/manual/en/pgsql.installation.php how to compile PHP with PostgreSQL support]).",
+       "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite] הוא מסד נתונים קליל עם תמיכה טובה מאוד. (ר׳ [https://www.php.net/manual/en/pdo.installation.php How to compile PHP with SQLite support], משתמש ב־PDO)",
+       "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] הוא מסד נתונים עסקי מסחרי. (ר׳ [https://www.php.net/manual/en/oci8.installation.php How to compile PHP with OCI8 support])",
+       "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] הוא מסד נתונים עסקי מסחרי לחלונות. ([https://www.php.net/manual/en/sqlsrv.installation.php How to compile PHP with SQLSRV support])",
        "config-header-mysql": "הגדרות MariaDB/MySQL",
        "config-header-postgres": "הגדרות PostgreSQL",
        "config-header-sqlite": "הגדרות SQLite",
        "config-license-help": "אתרי ויקי ציבוריים רבים מפרסמים את כל התרומות [https://freedomdefined.org/Definition ברישיון חופשי].\nזה עוזר ליצור תחושה של בעלות קהילתית ומעודד תרומה לאורך זמן.\nזה בדרך כלל לא נחוץ לאתר ויקי פרטי או אתר של חברה מסחרית.\n\nאם האפשרות להשתמש בטקסט מוויקיפדיה והאפשרות שוויקיפדיה תוכל תקבל עותקים של טקסטים מהוויקי שלך חשובות לך, כדאי לבחור ב<strong>{{int:config-license-cc-by-sa}}</strong>.\n\nויקיפדיה השתמשה בעבר ברישיון החופשי למסמכים של גנו (GNU FDL או GFDL).\nהוא עדיין רישיון תקין, אבל קשה להבנה.\nכמו־כן, קשה לעשות שימוש חוזר ביצירות שפורסמו לפי GFDL.",
        "config-email-settings": "הגדרות דוא״ל",
        "config-enable-email": "להפעיל דוא״ל יוצא",
-       "config-enable-email-help": "אם אתם רוצים שדוא״ל יעבוד, [https://secure.php.net/manual/en/mail.configuration.php אפשרויות הדוא״ל של PHP] צריכות להיות מוגדרות נכון.\nאם אינכם רוצים להפעיל שום אפשרויות דוא״ל, כבו אותן כאן ועכשיו.",
+       "config-enable-email-help": "אם אתם רוצים שדוא״ל יעבוד, [https://www.php.net/manual/en/mail.configuration.php אפשרויות הדוא״ל של PHP] צריכות להיות מוגדרות נכון.\nאם אינכם רוצים להפעיל שום אפשרויות דוא״ל, כבו אותן כאן ועכשיו.",
        "config-email-user": "להפעיל שליחת דוא״ל ממשתמש למשתמש",
        "config-email-user-help": "לאפשר לכל המשתמשים לשלוח אחד לשני דוא״ל אם הם הפעילו את זה בהעדפות שלהם.",
        "config-email-usertalk": "להפעיל הודעות על דף שיחת משתמש",
index ca3d1df..90b11a9 100644 (file)
@@ -47,8 +47,8 @@
        "config-env-php": "PHP $1 स्थापित किया गया है।",
        "config-env-hhvm": "एचएचवीएम $1 स्थापित किया गया है।",
        "config-memory-raised": "पीएचपी की <code>memory_limit</code> सीमा $1 है, जो $2 तक बढ़ गई है।",
-       "config-apc": "[https://secure.php.net/apc एपीसी] स्थापित है।",
-       "config-apcu": "[https://secure.php.net/apcu एपीसीयू] स्थापित है।",
+       "config-apc": "[https://www.php.net/apc एपीसी] स्थापित है।",
+       "config-apcu": "[https://www.php.net/apcu एपीसीयू] स्थापित है।",
        "config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension विनकैश] स्थापित है।",
        "config-using-32bit": "<विशेष>चेतावनी:</विशेष> आपका सिस्टम 32-बिट पूर्णांक के साथ चल रहा है यह [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:32-bit विवेचित नहीं है]।",
        "config-db-type": "डेटाबेस प्रकार:",
index b358c4d..75a76b1 100644 (file)
@@ -57,7 +57,7 @@
        "config-pcre-no-utf8": "'''Fataler Fehler:''' Das PHP-Modul PCRE scheint ohne PCRE_UTF8-Unterstützung kompiliert worre sin.\nMediaWiki benöticht die UTF-8-Unnerstützung, um fehlerfrei looffähich zu sin.",
        "config-memory-raised": "Der PHP-Parameter <code>memory_limit</code> betruch $1 und woard uff $2 erhöcht.",
        "config-memory-bad": "'''Warnung:''' Der PHP-Parameter <code>memory_limit</code> beträcht $1.\nDer Weart ist wahrscheinlich zu niedrich.\nDer Installationsvoargang könnt doher scheitre!",
-       "config-apc": "[https://secure.php.net/apc APC] ist installiert",
+       "config-apc": "[https://www.php.net/apc APC] ist installiert",
        "config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension WinCache] ist installiert",
        "config-mod-security": "'''Warnung:''' Uff dem Webserver woard [https://modsecurity.org/ ModSecurity] aktiviert. Sofern falsch konfiguriert, kann das zu Probleme mit MediaWiki sowie annrer Software uff dem Server führe und es Benutzer ermöchliche beliebiche Inhalte im Wiki Renzustelle.\nFür weitre Informatione empfehle mir die [https://modsecurity.org/documentation/ Dokumentation zu ModSecurity] orrer den Kontakt zum Hoster, sofern Fehler ufftrete.",
        "config-diff3-bad": "GNU diff3 woard net gefund.",
        "config-type-mysql": "MySQL (orrer kompatible Datebanksysteme)",
        "config-type-mssql": "Microsoft SQL Server",
        "config-support-info": "MediaWiki unnerstützt die follichenne Datebanksysteme:\n\n$1\n\nSoweit net das Datebanksystem oongezeicht weard, das verwennt werre soll, gebt das uwe en Link zu der Oonleitung mit Informatione, wie das aktiviert sin kann.",
-       "config-dbsupport-mysql": "* [{{int:version-db-mysql-url}} MySQL] ist das von MediaWiki primär unterstützte Datebanksystem. MediaWiki funktioniert ooch mit [{{int:version-db-mariadb-url}} MariaDB] und [{{int:version-db-percona-url}} Percona Server], die MySQL-kompatibel sind. ([https://secure.php.net/manual/en/mysqli.installation.php Oonleitung zur Kompilierung von PHP mit MySQL-Unnerstützung] [englisch Sproch])",
-       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] ist en beliebtes Open-Source-Datebanksystem und ein Alternativ zu MySQL. Es gibt awer enche klenre Implementierungsfehler, so dass von der Nutzung in ener Produktivumgebung abgerat weard. ([https://secure.php.net/manual/en/pgsql.installation.php Oonnleitung zur Kompilierung von PHP mit PostgreSQL-Unterstützung])",
+       "config-dbsupport-mysql": "* [{{int:version-db-mysql-url}} MySQL] ist das von MediaWiki primär unterstützte Datebanksystem. MediaWiki funktioniert ooch mit [{{int:version-db-mariadb-url}} MariaDB] und [{{int:version-db-percona-url}} Percona Server], die MySQL-kompatibel sind. ([https://www.php.net/manual/en/mysqli.installation.php Oonleitung zur Kompilierung von PHP mit MySQL-Unnerstützung] [englisch Sproch])",
+       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] ist en beliebtes Open-Source-Datebanksystem und ein Alternativ zu MySQL. Es gibt awer enche klenre Implementierungsfehler, so dass von der Nutzung in ener Produktivumgebung abgerat weard. ([https://www.php.net/manual/en/pgsql.installation.php Oonnleitung zur Kompilierung von PHP mit PostgreSQL-Unterstützung])",
        "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite] ist en verschlanktes Datebanksystem, das ooch gut unnerstützt weard ([http://www.php.net/manual/de/pdo.installation.php Oonleitung zur Kompilierung von PHP mit SQLite-Unterstützung], verwennt PHP Data Objects (PDO))",
        "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] ist en kommerzielle Unnernehmensdatebank ([http://www.php.net/manual/en/oci8.installation.php Oonleitung zur Kompilierung von PHP mit OCI8-Unnerstützung (en)])",
        "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] ist en gewerbliche Unnernehmensdatebank für Windows. ([Config-dbsupport-oracle/manual/de/sqlsrv.installation.php Oonleitung zur Kompilierung von PHP mithilfe SQLSRV-Unnerstützung])",
index 10c6159..f2b2a7f 100644 (file)
@@ -53,7 +53,7 @@
        "config-pcre-no-utf8": "'''Ćežki zmylk''': Zda so, zo PCRE-modul za PHP ma so bjez PCRE_UTF8-podpěry kompilować.\nMediaWiki trjeba UTF-8-podpěru, zo by korektnje fungował.",
        "config-memory-raised": "PHP-parameter <code>memory_limit</code> je $1, je so na hódnotu $2 zwyšił.",
        "config-memory-bad": "'''Warnowanje:''' PHP-parameter <code>memory_limit</code> ma hódnotu $1,\nTo je najskerje přeniske.\nInstalacija móhła so njeporadźić!",
-       "config-apc": "[https://secure.php.net/apc APC] je instalowany",
+       "config-apc": "[https://www.php.net/apc APC] je instalowany",
        "config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension WinCache] je instalowany",
        "config-diff3-bad": "GNU diff3 njenamakany.",
        "config-no-uri": "'''Zmylk:''' Aktualny URI njeda so postajić.\nInstalacija bu přetorhnjena.",
@@ -87,8 +87,8 @@
        "config-type-postgres": "PostgreSQL",
        "config-type-sqlite": "SQLite",
        "config-type-oracle": "Oracle",
-       "config-dbsupport-mysql": "* [{{int:version-db-mysql-url}} MySQL] je primarny cil za MediaWiki a podpěruje so najlěpje. MediaWiki funguje tež z [{{int:version-db-mariadb-url}} MariaDB] a [{{int:version-db-percona-url}} Percona Server], kotrejž stej kompatibelnej z MySQL. ([https://secure.php.net/manual/en/mysqli.installation.php Nawod ke kompilowanju  PHP z  MySQL-podpěru])",
-       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] je popularny system datoweje banki zjawneho žórła jako alternatiwa k MySQL. Móhło hišće někotre zmylki eksistować, a njeporuča so jón w produktiwnej wokolinje wužiwać. ([https://secure.php.net/manual/en/pgsql.installation.php Nawod za kompilowanje PHP z podpěru PostgreSQL])",
+       "config-dbsupport-mysql": "* [{{int:version-db-mysql-url}} MySQL] je primarny cil za MediaWiki a podpěruje so najlěpje. MediaWiki funguje tež z [{{int:version-db-mariadb-url}} MariaDB] a [{{int:version-db-percona-url}} Percona Server], kotrejž stej kompatibelnej z MySQL. ([https://www.php.net/manual/en/mysqli.installation.php Nawod ke kompilowanju  PHP z  MySQL-podpěru])",
+       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] je popularny system datoweje banki zjawneho žórła jako alternatiwa k MySQL. Móhło hišće někotre zmylki eksistować, a njeporuča so jón w produktiwnej wokolinje wužiwać. ([https://www.php.net/manual/en/pgsql.installation.php Nawod za kompilowanje PHP z podpěru PostgreSQL])",
        "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] je komercielna předewzaćelska datowa banka. ([http://www.php.net/manual/en/oci8.installation.php Nawod za kompilowanje PHP z OCI8-podpěru])",
        "config-header-mysql": "Nastajenja MySQL",
        "config-header-postgres": "Nastajenja PostgreSQL",
index 25c6cae..a2a4e3b 100644 (file)
        "config-pcre-no-utf8": "<strong>Kritikus hiba:</strong> Úgy tűnik, hogy a PHP PRCE modulja PRCE_UTF8 támogatás nélkül lett fordítva.\nA MediaWikinek UTF-8-támogatásra van szüksége a helyes működéshez.",
        "config-memory-raised": "A PHP <code>memory_limit</code> beállításának értéke: $1. Meg lett növelve a következő értékre: $2.",
        "config-memory-bad": "<strong>Figyelmeztetés:</strong> A PHP <code>memory_limit</code> beállításának értéke $1.\nEz az érték valószínűleg túl kevés, a telepítés sikertelen lehet.",
-       "config-apc": "Az [https://secure.php.net/apc APC] telepítve van",
-       "config-apcu": "Az [https://secure.php.net/apcu APCu] telepítve van",
+       "config-apc": "Az [https://www.php.net/apc APC] telepítve van",
+       "config-apcu": "Az [https://www.php.net/apcu APCu] telepítve van",
        "config-wincache": "A [https://www.iis.net/downloads/microsoft/wincache-extension WinCache] telepítve van",
-       "config-no-cache-apcu": "<strong>Figyelmeztetés:</strong> nem találhatók a következők: [https://secure.php.net/apcu APCu] vagy [https://www.iis.net/downloads/microsoft/wincache-extension WinCache].\nAz objektum gyorsítótárazása nincs engedélyezve.",
+       "config-no-cache-apcu": "<strong>Figyelmeztetés:</strong> nem találhatók a következők: [https://www.php.net/apcu APCu] vagy [https://www.iis.net/downloads/microsoft/wincache-extension WinCache].\nAz objektum gyorsítótárazása nincs engedélyezve.",
        "config-diff3-bad": "GNU diff3 nem található.",
        "config-git": "Megtaláltam a Git verziókezelő szoftvert: <code>$1</code>.",
        "config-git-bad": "A Git verziókezelő rendszer nem található.",
        "config-type-mysql": "MySQL (vagy kompatibilis)",
        "config-type-mssql": "Microsoft SQL Szerver",
        "config-support-info": "A MediaWiki a következő adatbázisrendszereket támogatja:\n\n$1\n\nHa az alábbi listán nem találod azt a rendszert, melyet használni szeretnél, a fenti linken található instrukciókat követve engedélyezheted a támogatását.",
-       "config-dbsupport-mysql": "* A [{{int:version-db-mysql-url}} MySQL] a MediaWiki elsődleges célpontja, így a legjobban támogatott. A MediaWiki elfut [{{int:version-db-mariadb-url}} MariaDB-n] és [{{int:version-db-percona-url}} Percona Serveren] is, mivel ezek MySQL-kompatibilisek. ([https://secure.php.net/manual/en/mysql.installation.php Hogyan fordítható a PHP MySQL-támogatással])",
-       "config-dbsupport-postgres": "* A [{{int:version-db-postgres-url}} PostgreSQL] népszerű, nyílt forráskódú adatbázisrendszer, a MySQL alternatívája. ([https://secure.php.net/manual/en/pgsql.installation.php Hogyan fordítható a PHP PostgreSQL-támogatással])",
+       "config-dbsupport-mysql": "* A [{{int:version-db-mysql-url}} MySQL] a MediaWiki elsődleges célpontja, így a legjobban támogatott. A MediaWiki elfut [{{int:version-db-mariadb-url}} MariaDB-n] és [{{int:version-db-percona-url}} Percona Serveren] is, mivel ezek MySQL-kompatibilisek. ([https://www.php.net/manual/en/mysql.installation.php Hogyan fordítható a PHP MySQL-támogatással])",
+       "config-dbsupport-postgres": "* A [{{int:version-db-postgres-url}} PostgreSQL] népszerű, nyílt forráskódú adatbázisrendszer, a MySQL alternatívája. ([https://www.php.net/manual/en/pgsql.installation.php Hogyan fordítható a PHP PostgreSQL-támogatással])",
        "config-dbsupport-sqlite": "* Az [{{int:version-db-sqlite-url}} SQLite] egy könnyű, nagyon jól támogatott adatbázisrendszer. ([http://www.php.net/manual/en/pdo.installation.php Hogyan fordítható a PHP SQLite-támogatással], PDO-t használ)",
        "config-dbsupport-oracle": "* Az [{{int:version-db-oracle-url}} Oracle] kereskedelmi, vállalati adatbázisrendszer. ([http://www.php.net/manual/en/oci8.installation.php Hogyan fordítható a PHP OCI8-támogatással])",
-       "config-dbsupport-mssql": "* A [{{int:version-db-mssql-url}} Microsoft SQL Server] kereskedelmi, vállalati adatbázisrendszer. ([https://secure.php.net/manual/en/sqlsrv.installation.php Hogyan fordítható a PHP SQLSRV-támogatással])",
+       "config-dbsupport-mssql": "* A [{{int:version-db-mssql-url}} Microsoft SQL Server] kereskedelmi, vállalati adatbázisrendszer. ([https://www.php.net/manual/en/sqlsrv.installation.php Hogyan fordítható a PHP SQLSRV-támogatással])",
        "config-header-mysql": "MySQL-beállítások",
        "config-header-postgres": "PostgreSQL-beállítások",
        "config-header-sqlite": "SQLite-beállítások",
index 86cddd6..e6936f6 100644 (file)
        "config-pcre-no-utf8": "'''Fatal''': Le modulo PCRE de PHP pare haber essite compilate sin supporto de PCRE_UTF8.\nMediaWiki require supporto de UTF-8 pro functionar correctemente.",
        "config-memory-raised": "Le <code>memory_limit</code> de PHP es $1, elevate a $2.",
        "config-memory-bad": "'''Aviso:''' Le <code>memory_limit</code> de PHP es $1.\nIsto es probabilemente troppo basse.\nLe installation pote faller!",
-       "config-apc": "[https://secure.php.net/apc APC] es installate",
-       "config-apcu": "[https://secure.php.net/apcu APCu] es installate",
+       "config-apc": "[https://www.php.net/apc APC] es installate",
+       "config-apcu": "[https://www.php.net/apcu APCu] es installate",
        "config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension WinCache] es installate",
-       "config-no-cache-apcu": "<strong>Attention:</strong> Impossibile trovar [https://secure.php.net/apcu APCu] o [https://www.iis.net/downloads/microsoft/wincache-extension WinCache].\nLe cache de objectos non es activate.",
+       "config-no-cache-apcu": "<strong>Attention:</strong> Impossibile trovar [https://www.php.net/apcu APCu] o [https://www.iis.net/downloads/microsoft/wincache-extension WinCache].\nLe cache de objectos non es activate.",
        "config-mod-security": "<strong>Attention</strong>: [https://modsecurity.org/ mod_security]/mod_security2 es active in tu servitor web. Multe configurationes commun de isto causa problemas pro MediaWiki o altere software que permitte al usatores de publicar contento arbitrari. Si possibile, isto deberea esser disactivate.\nAlteremente, consulta le [https://modsecurity.org/documentation/ documentation de mod_security] o contacta le servicio de adjuta de tu servitor si tu incontra estranie errores.",
        "config-diff3-bad": "Le utilitate de comparation de texto GNU diff3 non ha essite trovate. Es possibile ignorar isto pro le momento, ma tu poterea incontrar conflictos de modification plus frequentemente.",
        "config-git": "Systema de controlo de version Git trovate: <code>$1</code>",
        "config-type-oracle": "Oracle",
        "config-type-mssql": "Microsoft SQL Server",
        "config-support-info": "MediaWiki supporta le sequente systemas de base de datos:\n\n$1\n\nSi tu non vide hic infra le systema de base de datos que tu tenta usar, alora seque le instructiones ligate hic supra pro activar le supporto.",
-       "config-dbsupport-mysql": "* [{{int:version-db-mariadb-url}} MariaDB] es le systema primari pro MediaWiki e le melio supportate. MediaWiki functiona anque con [{{int:version-db-mysql-url}} MySQL] e con [{{int:version-db-percona-url}} Percona Server], le quales es compatibile con MariaDB. ([https://secure.php.net/manual/en/mysqli.installation.php Como compilar PHP con supporto de MySQL])",
-       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] es un systema de base de datos popular e open source, alternativa a MySQL. ([https://secure.php.net/manual/en/pgsql.installation.php Como compilar PHP con supporto de PostgreSQL])",
-       "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite] es un systema de base de datos legier que es multo ben supportate. ([https://secure.php.net/manual/en/pdo.installation.php Como compilar PHP con supporto de SQLite], usa PDO)",
-       "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] es un banca de datos commercial pro interprisas. ([https://secure.php.net/manual/en/oci8.installation.php Como compilar PHP con supporto de OCI8])",
-       "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] es un base de datos de interprisa commercial pro Windows. ([https://secure.php.net/manual/en/sqlsrv.installation.php Como compilar PHP con supporto de SQLSRV])",
+       "config-dbsupport-mysql": "* [{{int:version-db-mariadb-url}} MariaDB] es le systema primari pro MediaWiki e le melio supportate. MediaWiki functiona anque con [{{int:version-db-mysql-url}} MySQL] e con [{{int:version-db-percona-url}} Percona Server], le quales es compatibile con MariaDB. ([https://www.php.net/manual/en/mysqli.installation.php Como compilar PHP con supporto de MySQL])",
+       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] es un systema de base de datos popular e open source, alternativa a MySQL. ([https://www.php.net/manual/en/pgsql.installation.php Como compilar PHP con supporto de PostgreSQL])",
+       "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite] es un systema de base de datos legier que es multo ben supportate. ([https://www.php.net/manual/en/pdo.installation.php Como compilar PHP con supporto de SQLite], usa PDO)",
+       "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] es un banca de datos commercial pro interprisas. ([https://www.php.net/manual/en/oci8.installation.php Como compilar PHP con supporto de OCI8])",
+       "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] es un base de datos de interprisa commercial pro Windows. ([https://www.php.net/manual/en/sqlsrv.installation.php Como compilar PHP con supporto de SQLSRV])",
        "config-header-mysql": "Configuration de MariaDB/MySQL",
        "config-header-postgres": "Configuration de PostgreSQL",
        "config-header-sqlite": "Configuration de SQLite",
index 7d8ce34..0071634 100644 (file)
        "config-pcre-no-utf8": "'''Fatal''': Modul PCRE PHP tampaknya dikompilasi tanpa dukungan PCRE_UTF8.\nMediaWiki memerlukan dukungan UTF-8 untuk berfungsi dengan benar.",
        "config-memory-raised": "<code>memory_limit</code> PHP adalah $1, dinaikkan ke $2.",
        "config-memory-bad": "'''Peringatan:''' <code>memory_limit</code> PHP adalah $1.\nIni terlalu rendah.\nInstalasi terancam gagal!",
-       "config-apc": "[https://secure.php.net/apc APC] telah dipasang",
-       "config-apcu": "[https://secure.php.net/apcu APCu] telah dipasang",
+       "config-apc": "[https://www.php.net/apc APC] telah dipasang",
+       "config-apcu": "[https://www.php.net/apcu APCu] telah dipasang",
        "config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension WinCache] telah diinstal",
-       "config-no-cache-apcu": "<strong>Peringatan:</strong> Tidak dapat menemukan [https://secure.php.net/apcu APCu], [http://xcache.lighttpd.net/ XCache] atau [https://www.iis.net/downloads/microsoft/wincache-extension WinCache]. Singgahan obyek tidak diaktifkan.",
+       "config-no-cache-apcu": "<strong>Peringatan:</strong> Tidak dapat menemukan [https://www.php.net/apcu APCu], [http://xcache.lighttpd.net/ XCache] atau [https://www.iis.net/downloads/microsoft/wincache-extension WinCache]. Singgahan obyek tidak diaktifkan.",
        "config-mod-security": "<strong>Peringatan:</strong> Server web Anda memiliki [https://modsecurity.org/ mod_security] yang diaktifkan. Jika salah dalam mengkonfigurasi, ini dapat menyebabkan masalah untuk MediaWiki atau perangkat lunak lain yang memungkinkan pengguna untuk mengirim sembarang konten.\nLihat [https://modsecurity.org/documentation/ dokumentasi mod_security] atau hubungi layanan host Anda jika Anda mengalami kesalahan acak.",
        "config-diff3-bad": "GNU diff3 tidak ditemukan.",
        "config-git": "Menemukan perangkat lunak kontrol versi Git: <code>$1</code>.",
        "config-type-oracle": "Oracle",
        "config-type-mssql": "Microsoft SQL Server",
        "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://secure.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://secure.php.net/manual/en/pgsql.installation.php Bagaimana mengompilasikan PHP dengan dukungan PostgreSQL])",
+       "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-oracle": "* [{{int:version-db-oracle-url}} Oracle] adalah basis data komersial untuk perusahaan. ([http://www.php.net/manual/en/oci8.installation.php cara mengompilasi PHP dengan dukungan OCI8])",
-       "config-dbsupport-mssql": "[{{int:version-db-mssql-url}} Microsoft SQL Server] adalah database perusahaan komersial untuk Windows. ([https://secure.php.net/manual/en/sqlsrv.installation.php Bagaimana cara mengkompilasi PHP dengan dukungan SQLSRV])",
+       "config-dbsupport-mssql": "[{{int:version-db-mssql-url}} Microsoft SQL Server] adalah database perusahaan komersial untuk Windows. ([https://www.php.net/manual/en/sqlsrv.installation.php Bagaimana cara mengkompilasi PHP dengan dukungan SQLSRV])",
        "config-header-mysql": "Pengaturan MariaDB/MySQL",
        "config-header-postgres": "Pengaturan PostgreSQL",
        "config-header-sqlite": "Pengaturan SQLite",
index 530fd0e..4e9781b 100644 (file)
@@ -38,8 +38,8 @@
        "config-env-bad": "Omno verifikesis.\nVu NE POVAS intalar MediaWiki.",
        "config-env-php": "PHP $1 instalesis.",
        "config-env-hhvm": "HHVM $1 instalesis.",
-       "config-apc": "[https://secure.php.net/apc APC] instalesis",
-       "config-apcu": "[https://secure.php.net/apcu APCu] instalesis",
+       "config-apc": "[https://www.php.net/apc APC] instalesis",
+       "config-apcu": "[https://www.php.net/apcu APCu] instalesis",
        "config-profile-private": "Privata wiki",
        "config-license": "Autoroyuro e permiso:",
        "config-license-cc-0": "Creative Commons Zero (Publika domeno)",
index 707fc24..3af2fc0 100644 (file)
@@ -34,7 +34,7 @@
        "config-copyright": "=== Höfundarréttur og skilmálar ===\n\n$1\n\nÞetta er frjáls hugbúnaður; þú mátt dreifa honum og/eða breyta samkvæmt skilmálum í almenna GNU GPL notkunarleyfinu eins og það er gefið út af Frjálsu hugbúnaðarstofnuninni; annaðhvort útgáfu 2 af GPL-leyfinu, eða (ef þér sýnist svo) einhverri nýrri útgáfu leyfisins.\n\nHugbúnaði þessum er dreift í þeirri von að hann geti verið gagnlegur, en <strong>ÁN ALLRAR ÁBYRGÐAR</strong>; einnig án þeirrar ábyrgðar sem gefin er í skyn með <strong>SELJANLEIKA</strong> eða <strong>EIGINLEIKUM TIL TILTEKINNA NOTA</strong>. Sjá almenna GNU GPL notkunarleyfið fyrir nánari upplýsingar.\n\nÞað ætti að hafa fylgt afrit af almenna <doclink href=Copying>GNU GPL notkunarleyfinu</doclink> með forritinu; ef ekki skrifið þá Fjálsu hugbúnarstofnuninni: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA, eða [https://www.gnu.org/copyleft/gpl.html lestu það á netinu].",
        "config-env-php": "PHP $1 er uppsett.",
        "config-env-hhvm": "HHVM $1 er uppsett.",
-       "config-apc": "[https://secure.php.net/apc APC] er uppsett",
+       "config-apc": "[https://www.php.net/apc APC] er uppsett",
        "config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension WinCache] er uppsett",
        "config-diff3-bad": "GNU diff3 textasamanburðartólið fannst ekki. Þú getur hunsað þetta núna, en þú gætir lent oftar í breytingaárekstrum.",
        "config-using-server": "Nota \"<nowiki>$1</nowiki>\" sem heiti á þjóni.",
index c1eabf2..cc34a31 100644 (file)
        "config-pcre-no-utf8": "'''Errore''': Il modulo PCRE di PHP sembra essere stato compilato senza il supporto PCRE_UTF8, ma MediaWiki lo richiede per funzionare correttamente.",
        "config-memory-raised": "Il valore <code>memory_limit</code> di PHP è $1, aumentato a $2.",
        "config-memory-bad": "''Attenzione:''' Il valore di <code>memory_limit</code> di PHP è $1.\nProbabilmente è troppo basso.\nL'installazione potrebbe non riuscire!",
-       "config-apc": "[https://secure.php.net/apc APC] è installato",
-       "config-apcu": "[https://secure.php.net/apcu APCu] è installato",
+       "config-apc": "[https://www.php.net/apc APC] è installato",
+       "config-apcu": "[https://www.php.net/apcu APCu] è installato",
        "config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension WinCache] è installato",
-       "config-no-cache-apcu": "'''Attenzione:''' [https://secure.php.net/apcu APCu], [http://xcache.lighttpd.net/ XCache] o [https://www.iis.net/downloads/microsoft/wincache-extension WinCache] non sono stati trovati.\nLa caching degli oggetti non è attivata.",
+       "config-no-cache-apcu": "'''Attenzione:''' [https://www.php.net/apcu APCu], [http://xcache.lighttpd.net/ XCache] o [https://www.iis.net/downloads/microsoft/wincache-extension WinCache] non sono stati trovati.\nLa caching degli oggetti non è attivata.",
        "config-mod-security": "<strong>Attenzione:</strong> Il tuo server web ha il [https://modsecurity.org/ mod_security] abilitato. Se non correttamente configurato, può creare problemi a MediaWiki o ad altro software che permette agli utenti di pubblicare contenuto.\nFai riferimento alla [https://modsecurity.org/documentation/ documentazione sul mod_security] o contatta il supporto tecnico del tuo provider di hosting se si verificano errori.",
        "config-diff3-bad": "Utilità di confronto del testo GNU diff3 non trovata. È possibile ignorare per ora, ma potresti incorrere in conflitti di modifica più frequentemente.",
        "config-git": "Trovato software di controllo della versione Git: <code>$1</code>.",
        "config-type-mysql": "MariaDB, MySQL o compatibile",
        "config-type-mssql": "Microsoft SQL Server",
        "config-support-info": "MediaWiki supporta i seguenti sistemi di database:\n\n$1\n\nSe fra quelli elencati qui sotto non vedi il sistema di database che vorresti utilizzare, seguire le istruzioni linkate sopra per abilitare il supporto.",
-       "config-dbsupport-mysql": "* [{{int:version-db-mariadb-url}} MariaDB] è la configurazione preferibile per MediaWiki ed è quella meglio supportata. MediaWiki funziona anche con [{{int:version-db-mysql-url}} MySQL] e [{{int:version-db-percona-url}} Percona Server], che sono compatibili con MariaDB.([https://secure.php.net/manual/en/mysqli.installation.php Come compilare PHP con supporto MySQL])",
-       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] è un popolare sistema di database open source come alternativa a MySQL. ([https://secure.php.net/manual/en/pgsql.installation.php Come compilare PHP con supporto PostgreSQL])",
-       "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite]  è un sistema di database leggero, che è supportato molto bene. ([https://secure.php.net/manual/en/pdo.installation.php Come compilare PHP con supporto SQLite], utilizza PDO)",
-       "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] è un database di un'impresa commerciale. ([https://secure.php.net/manual/en/oci8.installation.php Come compilare PHP con supporto OCI8])",
-       "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] è un database di un'impresa commerciale per Windows. ([https://secure.php.net/manual/en/sqlsrv.installation.php Come compilare PHP con supporto SQLSRV])",
+       "config-dbsupport-mysql": "* [{{int:version-db-mariadb-url}} MariaDB] è la configurazione preferibile per MediaWiki ed è quella meglio supportata. MediaWiki funziona anche con [{{int:version-db-mysql-url}} MySQL] e [{{int:version-db-percona-url}} Percona Server], che sono compatibili con MariaDB.([https://www.php.net/manual/en/mysqli.installation.php Come compilare PHP con supporto MySQL])",
+       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] è un popolare sistema di database open source come alternativa a MySQL. ([https://www.php.net/manual/en/pgsql.installation.php Come compilare PHP con supporto PostgreSQL])",
+       "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite]  è un sistema di database leggero, che è supportato molto bene. ([https://www.php.net/manual/en/pdo.installation.php Come compilare PHP con supporto SQLite], utilizza PDO)",
+       "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] è un database di un'impresa commerciale. ([https://www.php.net/manual/en/oci8.installation.php Come compilare PHP con supporto OCI8])",
+       "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] è un database di un'impresa commerciale per Windows. ([https://www.php.net/manual/en/sqlsrv.installation.php Come compilare PHP con supporto SQLSRV])",
        "config-header-mysql": "Impostazioni MySQL",
        "config-header-postgres": "Impostazioni PostgreSQL",
        "config-header-sqlite": "Impostazioni SQLite",
        "config-license-help": "Molti wiki pubblici rilasciano i loro contributi con una [https://freedomdefined.org/Definition licenza libera]. Questo aiuta a creare un senso di proprietà condivisa nella comunità e incoraggia a contribuire a lungo termine. Non è generalmente necessario per un wiki privato o aziendale.\n\nSe vuoi usare testi da Wikipedia, o desideri che Wikipedia possa essere in grado di accettare testi copiati dal tuo wiki, dovresti scegliere <strong>{{int:config-license-cc-by-sa}}</strong>.\n\nIn precedenza Wikipedia ha utilizzato la GNU Free Documentation License. La GFDL è una licenza valida, ma è di difficile comprensione e complica il riutilizzo dei contenuti.",
        "config-email-settings": "Impostazioni email",
        "config-enable-email": "Abilita la posta elettronica in uscita",
-       "config-enable-email-help": "Se vuoi che funzionino le email, le [https://secure.php.net/manual/en/mail.configuration.php impostazioni della posta] devono essere configurate correttamente.\nSe non si desidera alcuna funzionalità di posta elettronica, puoi disabilitarla qui.",
+       "config-enable-email-help": "Se vuoi che funzionino le email, le [https://www.php.net/manual/en/mail.configuration.php impostazioni della posta] devono essere configurate correttamente.\nSe non si desidera alcuna funzionalità di posta elettronica, puoi disabilitarla qui.",
        "config-email-user": "Abilita invio email fra utenti",
        "config-email-user-help": "Consente a tutti gli utenti di inviarsi a vicenda email, se lo hanno abilitato nelle loro preferenze.",
        "config-email-usertalk": "Abilita le notifiche per le pagine di discussione utente",
index 70462a1..f8318f0 100644 (file)
        "config-pcre-no-utf8": "<strong>致命的エラー:</strong> PHP の PCRE が PCRE_UTF8 対応なしでコンパイルされているようです。\nMediaWiki を正しく動作させるには、UTF-8 対応が必要です。",
        "config-memory-raised": "PHPの<code>memory_limit</code>は$1で、$2に引き上げられました。",
        "config-memory-bad": "<strong>警告:</strong> PHPの<code>memory_limit</code>に$1に設定されています。\nこの値はおそらく小さすぎます。\nインストールが失敗するおそれがあります!",
-       "config-apc": "[https://secure.php.net/apc APC] がインストール済み",
-       "config-apcu": "[https://secure.php.net/apcu APCu] がインストール済み",
+       "config-apc": "[https://www.php.net/apc APC] がインストール済み",
+       "config-apcu": "[https://www.php.net/apcu APCu] がインストール済み",
        "config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension WinCache] がインストール済み",
-       "config-no-cache-apcu": "<strong>警告:</strong> [https://secure.php.net/apcu APCu]、 [https://www.iis.net/downloads/microsoft/wincache-extension WinCache] のいずれも見つかりませんでした。\nオブジェクトのキャッシュは有効化されません。",
+       "config-no-cache-apcu": "<strong>警告:</strong> [https://www.php.net/apcu APCu]、 [https://www.iis.net/downloads/microsoft/wincache-extension WinCache] のいずれも見つかりませんでした。\nオブジェクトのキャッシュは有効化されません。",
        "config-mod-security": "<strong>警告:</strong> あなたのウェブサーバーでは [https://modsecurity.org/ mod_security] が有効になっています。正しく構成されていない場合は、MediaWiki や利用者にコンテンツの投稿を許可するその他のソフトウェアに問題が発生する場合があります。\n[https://modsecurity.org/documentation/ mod_security の説明文書]を確認するか、ランダムなエラーが発生した場合はあなたのホストのサポートにお問い合わせください。",
        "config-diff3-bad": "GNU diff3 が見つかりません。",
        "config-git": "バージョン管理ソフトウェア Git が見つかりました: <code>$1</code>",
        "config-type-oracle": "Oracle",
        "config-type-mssql": "マイクロソフト SQL Server",
        "config-support-info": "MediaWiki は以下のデータベース システムに対応しています:\n\n$1\n\n使用しようとしているデータベース システムが下記の一覧にない場合は、上記リンク先の手順に従ってインストールしてください。",
-       "config-dbsupport-mysql": "* [{{int:version-db-mariadb-url}} MariaDB]はMediaWikiの主要な対象であり、最もよくサポートされています。MediaWikiはMariaDBと互換性のある[{{int:version-db-mysql-url}} MySQL]、[{{int:version-db-percona-url}} Percona Server]でも動きます。 ([https://secure.php.net/manual/ja/mysqli.installation.php PHPをMySQLサポート付きでコンパイルする方法])",
-       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] は、MySQLの代替として人気がある公開のデータベースシステムです。([https://secure.php.net/manual/en/pgsql.installation.php PHPをPostgreSQLサポート付きでコンパイルする方法])",
-       "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite]は、良くサポートされている、軽量データベースシステムです。([https://secure.php.net/manual/ja/pdo.installation.php SQLiteに対応したPHPをコンパイルする方法]、PDOを使用)",
-       "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle]は商業企業のデータベースです。([https://secure.php.net/manual/en/oci8.installation.php OCI8サポートなPHPをコンパイルする方法])",
-       "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server]は商業企業のWindows用データベースです。([https://secure.php.net/manual/en/sqlsrv.installation.php SQLSRVサポートなPHPをコンパイルする方法])",
+       "config-dbsupport-mysql": "* [{{int:version-db-mariadb-url}} MariaDB]はMediaWikiの主要な対象であり、最もよくサポートされています。MediaWikiはMariaDBと互換性のある[{{int:version-db-mysql-url}} MySQL]、[{{int:version-db-percona-url}} Percona Server]でも動きます。 ([https://www.php.net/manual/ja/mysqli.installation.php PHPをMySQLサポート付きでコンパイルする方法])",
+       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] は、MySQLの代替として人気がある公開のデータベースシステムです。([https://www.php.net/manual/en/pgsql.installation.php PHPをPostgreSQLサポート付きでコンパイルする方法])",
+       "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite]は、良くサポートされている、軽量データベースシステムです。([https://www.php.net/manual/ja/pdo.installation.php SQLiteに対応したPHPをコンパイルする方法]、PDOを使用)",
+       "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle]は商業企業のデータベースです。([https://www.php.net/manual/en/oci8.installation.php OCI8サポートなPHPをコンパイルする方法])",
+       "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server]は商業企業のWindows用データベースです。([https://www.php.net/manual/en/sqlsrv.installation.php SQLSRVサポートなPHPをコンパイルする方法])",
        "config-header-mysql": "MariaDB/MySQL の設定",
        "config-header-postgres": "PostgreSQL の設定",
        "config-header-sqlite": "SQLite の設定",
        "config-enable-email-help": "メールを使用したい場合は、[Config-dbsupport-oracle/manual/en/mail.configuration.php PHP のメール設定]が正しく設定されている必要があります。\nメールの機能を使用しない場合は、ここで無効にすることができます。",
        "config-email-user": "利用者間のメールを有効にする",
        "config-email-user-help": "設定で有効になっている場合、すべてのユーザーがお互いにメールのやりとりを行うことを許可する。",
-       "config-email-usertalk": "ユーザーのトークページでの通知を有効にする",
-       "config-email-usertalk-help": "設定で有効にしている場合は、ユーザーのトークページの変更の通知を受けることをユーザーに許可する。",
+       "config-email-usertalk": "利用者のトークページでの通知を有効にする",
+       "config-email-usertalk-help": "設定で有効にしている場合は、利用者のトークページの変更の通知を受けることを利用者に許可する。",
        "config-email-watchlist": "ウォッチリストの通知を有効にする",
        "config-email-watchlist-help": "利用者が設定で有効にしている場合、閲覧されたページに関する通知を受け取ることを許可する。",
        "config-email-auth": "メールの認証を有効にする",
index 90967d1..856fa6f 100644 (file)
@@ -27,7 +27,7 @@
        "config-sidebar": "* [https://www.mediawiki.org მედიავიკის ვებ-გვერდი]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents/ka მომხმარებლების დახმარება]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents/ka ადმინისტრატორების დახმარება]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ/ka FAQ]\n----\n* <doclink href=Readme>წამიკითხე</doclink>\n* <doclink href=ReleaseNotes>ინფორმაცია გამოშვებაზე</doclink>\n* <doclink href=Copying>ლიცენზია</doclink>\n* <doclink href=UpgradeDoc>განახლება</doclink>",
        "config-env-php": "PHP $1 დაინსტალირებულია",
        "config-env-hhvm": "HHVM $1 დაინსტალირებულია.",
-       "config-apc": "[https://secure.php.net/apc APC] დაყენდა",
+       "config-apc": "[https://www.php.net/apc APC] დაყენდა",
        "config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension WinCache] დაყენდა",
        "config-diff3-bad": "GNU diff3 ვერ მოიძებნა.",
        "config-db-type": "მონაცემთა ბაზის ტიპი:",
index cc2efd4..dca5ef8 100644 (file)
        "config-pcre-no-utf8": "<strong>치명:</strong> PHP의 PCRE 모듈은 RCRE_UTF8 지원 없이 컴파일된 것 같습니다.\n미디어위키가 올바르게 작동하려면 UTF-8을 지원해야 합니다.",
        "config-memory-raised": "PHP의 <code>memory_limit</code>는 $1이며 $2(으)로 늘렸습니다.",
        "config-memory-bad": "<strong>경고:</strong> PHP의 <code>memory_limit</code>는 $1입니다.\n아마도 너무 낮은 것 같습니다.\n설치가 실패할 수 있습니다!",
-       "config-apc": "[https://secure.php.net/apc APC]가 설치되었습니다",
-       "config-apcu": "[https://secure.php.net/apcu APCu]가 설치되었습니다",
+       "config-apc": "[https://www.php.net/apc APC]가 설치되었습니다",
+       "config-apcu": "[https://www.php.net/apcu APCu]가 설치되었습니다",
        "config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension WinCache]가 설치되었습니다",
-       "config-no-cache-apcu": "<strong>경고:</strong> [https://secure.php.net/apcu APCu] 또는 [https://www.iis.net/downloads/microsoft/wincache-extension WinCache]를 찾을 수 없습니다.",
+       "config-no-cache-apcu": "<strong>경고:</strong> [https://www.php.net/apcu APCu] 또는 [https://www.iis.net/downloads/microsoft/wincache-extension WinCache]를 찾을 수 없습니다. 오브젝트 캐시를 사용할 수 없습니다.",
        "config-mod-security": "<strong>경고:</strong> 웹 서버에 [https://modsecurity.org/ mod_security]가 허용되었습니다. 잘못 설정된 경우 미디어위키나 사용자가 임의의 내용을 게시할 수 있는 다른 소프트웨어에 대한 문제를 일으킬 수 있습니다.\n[https://modsecurity.org/documentation/ mod_security] 문서를 참고하거나 임의의 오류가 발생할 경우 호스트의 지원 요청에 문의하십시오.",
        "config-diff3-bad": "GNU diff3를 찾을 수 없습니다.",
        "config-git": "Git 버전 관리 소프트웨어를 찾았습니다: <code>$1</code>.",
        "config-type-oracle": "Oracle",
        "config-type-mssql": "Microsoft SQL 서버",
        "config-support-info": "미디어위키는 다음의 데이터베이스 시스템을 지원합니다:\n\n$1\n\n데이터베이스 시스템이 표시되지 않을 때 아래에 나열된 다음 지원을 활성화하려면 위의 링크된 지시에 따라 설치해볼 수 있습니다.",
-       "config-dbsupport-mysql": "* [{{int:version-db-mariadb-url}} MariaDB]는 미디어위키의 기본 대상이며 가장 잘 지원됩니다. 미디어위키는 또한 MariaDB와 호환되는 [{{int:version-db-mysql-url}} MySQL]과 [{{int:version-db-percona-url}} Percona 서버]에서도 작동합니다. ([https://secure.php.net/manual/en/mysql.installation.php MySQL 지원으로 PHP를 컴파일하는 방법])",
-       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL]은 MySQL의 대안으로서 인기 있는 오픈 소스 데이터베이스 시스템입니다. ([https://secure.php.net/manual/en/pgsql.installation.php PostgreSQL 지원으로 PHP를 컴파일하는 방법])",
-       "config-dbsupport-sqlite": "*  [{{int:version-db-sqlite-url}} SQLite]는 매우 잘 지원되고 가벼운 데이터베이스 시스템입니다. ([https://secure.php.net/manual/en/pdo.installation.php SQLite 지원으로 PHP를 컴파일하는 방법], PDO 사용)",
-       "config-dbsupport-oracle": "*  [{{int:version-db-oracle-url}} Oracle]은 상용 기업 데이터베이스입니다. ([https://secure.php.net/manual/en/oci8.installation.php OCI8 지원으로 PHP를 컴파일하는 방법])",
-       "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL 서버]는 Windows용 상용 기업 데이터베이스입니다. ([https://secure.php.net/manual/en/sqlsrv.installation.php SQLSRV 지원으로 PHP를 컴파일하는 방법])",
+       "config-dbsupport-mysql": "* [{{int:version-db-mariadb-url}} MariaDB]는 미디어위키의 기본 대상이며 가장 잘 지원됩니다. 미디어위키는 또한 MariaDB와 호환되는 [{{int:version-db-mysql-url}} MySQL]과 [{{int:version-db-percona-url}} Percona 서버]에서도 작동합니다. ([https://www.php.net/manual/en/mysql.installation.php MySQL 지원으로 PHP를 컴파일하는 방법])",
+       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL]은 MySQL의 대안으로서 인기 있는 오픈 소스 데이터베이스 시스템입니다. ([https://www.php.net/manual/en/pgsql.installation.php PostgreSQL 지원으로 PHP를 컴파일하는 방법])",
+       "config-dbsupport-sqlite": "*  [{{int:version-db-sqlite-url}} SQLite]는 매우 잘 지원되고 가벼운 데이터베이스 시스템입니다. ([https://www.php.net/manual/en/pdo.installation.php SQLite 지원으로 PHP를 컴파일하는 방법], PDO 사용)",
+       "config-dbsupport-oracle": "*  [{{int:version-db-oracle-url}} Oracle]은 상용 기업 데이터베이스입니다. ([https://www.php.net/manual/en/oci8.installation.php OCI8 지원으로 PHP를 컴파일하는 방법])",
+       "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL 서버]는 Windows용 상용 기업 데이터베이스입니다. ([https://www.php.net/manual/en/sqlsrv.installation.php SQLSRV 지원으로 PHP를 컴파일하는 방법])",
        "config-header-mysql": "MariaDB/MySQL 설정",
        "config-header-postgres": "PostgreSQL 설정",
        "config-header-sqlite": "SQLite 설정",
index 5ac7ec6..50cf468 100644 (file)
@@ -60,9 +60,9 @@
        "config-pcre-no-utf8": "'''Dä:''' Et PHP-Modul <i lang=\"en\">PCRE</i> schingk ohne de <i lang=\"en\">PCRE_UTF8</i>-Aandeile övversaz ze sin.\nMediaWiki bruch dä UTF-8-Krohm ävver, öm ohne Fähler loufe ze künne.",
        "config-memory-raised": "Der jrühzte zohjelasse Shpeisherbedarf vum PHP, et <code lang=\"en\">memory_limit</code>, shtund op $1 un es op $2 erop jesaz woode.",
        "config-memory-bad": "'''Opjepaß:''' Dem PHP singe Parameeter <code lang=\"en\">memory_limit</code> es $1.\nDat es wall ze winnisch.\nEt Enreeschte kunnt doh draan kappott jon!",
-       "config-apc": "Dä <code lang=\"en\">[https://secure.php.net/apc APC]</code> es ennjeresht.",
+       "config-apc": "Dä <code lang=\"en\">[https://www.php.net/apc APC]</code> es ennjeresht.",
        "config-wincache": "Dä <code lang=\"en\">[https://www.iis.net/downloads/microsoft/wincache-extension WinCache]</code> es ennjeresht.",
-       "config-no-cache-apcu": "'''Opjepaß:''' Mer kunnte dä <code lang=\"en\" xml:lang=\"en\" dir=\"rtl\">[https://secure.php.net/apcu APCu]</code>, dä <code lang=\"en\" xml:lang=\"en\" dir=\"rtl\">[http://xcache.lighttpd.net/ XCache]</code> udder dä <code lang=\"en\" xml:lang=\"en\" dir=\"rtl\">[https://www.iis.net/downloads/microsoft/wincache-extension WinCache]</code> nit fenge.\nEt <i lang=\"en\" xml:lang=\"en\" dir=\"rtl\">object caching</i> es nit müjjelesch un es ußjeschalldt.",
+       "config-no-cache-apcu": "'''Opjepaß:''' Mer kunnte dä <code lang=\"en\" xml:lang=\"en\" dir=\"rtl\">[https://www.php.net/apcu APCu]</code>, dä <code lang=\"en\" xml:lang=\"en\" dir=\"rtl\">[http://xcache.lighttpd.net/ XCache]</code> udder dä <code lang=\"en\" xml:lang=\"en\" dir=\"rtl\">[https://www.iis.net/downloads/microsoft/wincache-extension WinCache]</code> nit fenge.\nEt <i lang=\"en\" xml:lang=\"en\" dir=\"rtl\">object caching</i> es nit müjjelesch un es ußjeschalldt.",
        "config-mod-security": "<strong>Opjepaß</strong>: Dinge Wäbßööver hät <code  lang=\"en\" xml:lang=\"en\" dir=\"ltr\">[https://modsecurity.org/ mod_security]</code> enjeschalldt. Jenohch schtandattmähßejje Enschtällonge heh em Wikki künne Problehme met MehdijaWikki un och met ander Projramme aanschtivvelle, di zohlohße, dat vun ußerhallef öhndsene Krohm op dä Webßööver jebraat wähde künnt.\nWann müjjelesch sullt mer dat affschallde. Söns beloor Der di Sigg <code  lang=\"en\" xml:lang=\"en\" dir=\"ltr\">[https://modsecurity.org/documentation/ mod_security documentation]</code> udder donn met dä Fachlück för Dinge Webßööver kalle, wann zohfälleje un koomijje Fähler bemärke deihß.",
        "config-diff3-bad": "Mer han <i lang=\"en\">GNU</i> <code lang=\"en\">diff3</code> nit jefonge.",
        "config-git": "Mer han de Väsjohn <code>$1</code> vun däm Väsjohnsverwalldongsprojamm <i lang=\"en\">Git</i> jefonge.",
        "config-type-oracle": "<i lang=\"en\">Oracle</i>",
        "config-type-mssql": "Dä <i lang=\"en\" xml:lang=\"en\">SQL</i>-ẞööver vun <i lang=\"en\" xml:lang=\"en\">Microsoft</i>",
        "config-support-info": "MediaWiki kann met heh dä Daatebangk_Süßteeme zosamme jonn:\n\n$1\n\nWann dat Daatebangk_Süßteem, wat De nämme wells, onge nit dobei es, dann donn desch aan di Aanleidonge hallde, di bovve verlengk sen, öm et op Dingem ẞööver singem Süßteem müjjelesh ze maache, se aan et Loufe ze krijje.",
-       "config-dbsupport-mysql": "* <i lang=\"en\" xml:lang=\"en\">[{{int:version-db-mysql-url}} MySQL]</i> es dat vum MediaWiki et eets un et bäß ongerschtöz Daatebangksüßtehm. Et leuf ävver och met <i lang=\"en\" xml:lang=\"en\">[{{int:version-db-mariadb-url}} MariaDB]</i> un <i lang=\"en\" xml:lang=\"en\">[{{int:version-db-percona-url}} Percona Server]</i>. Di sin kumpatihbel mem <i lang=\"en\" xml:lang=\"en\">MySQL</i>. ([https://secure.php.net/manual/de/mysql.installation.php Aanleidung för et Övversäze un Enreeschte von <i lang=\"en\" xml:lang=\"en\" dir=\"ltr\" title=\"PHP Hypertext Preprocessor\">PHP</i> met <i lang=\"en\">MySQL</i> dobei, op Deutsch])",
-       "config-dbsupport-postgres": "* <i lang=\"en\">[{{int:version-db-postgres-url}} PostgreSQL]</i> es e bikannt Daatebangksüßtehm met offe Quälltäxde, un ed es och en Wahl nävve <i lang=\"en\">MySQL</i>. Et sinn_er ävver paa klein Fählersche bekannt, um mer künne et em Momang för et reschtijje Werke nit ämfähle. ([https://secure.php.net/manual/de/pgsql.installation.php Aanleidung för et Övversäze un Enreeschte von PHP met <i lang=\"en\">PostgreSQL</i> dobei, op Deutsch])",
+       "config-dbsupport-mysql": "* <i lang=\"en\" xml:lang=\"en\">[{{int:version-db-mysql-url}} MySQL]</i> es dat vum MediaWiki et eets un et bäß ongerschtöz Daatebangksüßtehm. Et leuf ävver och met <i lang=\"en\" xml:lang=\"en\">[{{int:version-db-mariadb-url}} MariaDB]</i> un <i lang=\"en\" xml:lang=\"en\">[{{int:version-db-percona-url}} Percona Server]</i>. Di sin kumpatihbel mem <i lang=\"en\" xml:lang=\"en\">MySQL</i>. ([https://www.php.net/manual/de/mysql.installation.php Aanleidung för et Övversäze un Enreeschte von <i lang=\"en\" xml:lang=\"en\" dir=\"ltr\" title=\"PHP Hypertext Preprocessor\">PHP</i> met <i lang=\"en\">MySQL</i> dobei, op Deutsch])",
+       "config-dbsupport-postgres": "* <i lang=\"en\">[{{int:version-db-postgres-url}} PostgreSQL]</i> es e bikannt Daatebangksüßtehm met offe Quälltäxde, un ed es och en Wahl nävve <i lang=\"en\">MySQL</i>. Et sinn_er ävver paa klein Fählersche bekannt, um mer künne et em Momang för et reschtijje Werke nit ämfähle. ([https://www.php.net/manual/de/pgsql.installation.php Aanleidung för et Övversäze un Enreeschte von PHP met <i lang=\"en\">PostgreSQL</i> dobei, op Deutsch])",
        "config-dbsupport-sqlite": "* <i lang=\"en\">[{{int:version-db-sqlite-url}} SQLite]</i> es e eijfach Daatebangksüßtehm, wat joot en Schoß jehallde weed. ([http://www.php.net/manual/de/pdo.installation.php Aanleidong för et Övversäze un Enreeschte von PHP met <i lang=\"en\">SQLite</i> dobei, op Deutsch])",
        "config-dbsupport-oracle": "* <i lang=\"en\">[{{int:version-db-oracle-url}} Oracle]</i> es e jeschäfflesch Daatebangksüßtehm för Ferme. ([http://www.php.net/manual/de/oci8.installation.php Aanleidong för et Övversäze un Enreeschte von PHP met <i lang=\"en\" xml:lang=\"en\">OCI8</i> dobei, op Deutsch])",
-       "config-dbsupport-mssql": "* Dä <i lang=\"en\" xml:lang=\"en\">[{{int:version-db-mssql-url}} Microsoft SQL Server]</i> es e jeschäfflesch Dahtebangksüßtehm för Rääschner met <i lang=\"en\" xml:lang=\"en\">Windows</i>. ([https://secure.php.net/manual/en/sqlsrv.installation.php Aanleidong för et Övversäze un Enreeschte von <i lang=\"en\" xml:lang=\"en\" dir=\"ltr\" title=\"PHP Hypertext Preprocessor\">PHP</i> met <i lang=\"en\" xml:lang=\"en\">SQLSRV </i> dobei, op Deutsch])",
+       "config-dbsupport-mssql": "* Dä <i lang=\"en\" xml:lang=\"en\">[{{int:version-db-mssql-url}} Microsoft SQL Server]</i> es e jeschäfflesch Dahtebangksüßtehm för Rääschner met <i lang=\"en\" xml:lang=\"en\">Windows</i>. ([https://www.php.net/manual/en/sqlsrv.installation.php Aanleidong för et Övversäze un Enreeschte von <i lang=\"en\" xml:lang=\"en\" dir=\"ltr\" title=\"PHP Hypertext Preprocessor\">PHP</i> met <i lang=\"en\" xml:lang=\"en\">SQLSRV </i> dobei, op Deutsch])",
        "config-header-mysql": "De Enshtällunge för de <i lang=\"en\">MySQL</i> Daatebangk",
        "config-header-postgres": "De Enshtällunge för de <i lang=\"en\">PostgreSQL</i> Daatebangk",
        "config-header-sqlite": "De Enshtällunge för de <i lang=\"en\">SQLite</i> Daatebangk",
index a6d5fda..11b662e 100644 (file)
@@ -26,7 +26,7 @@
        "config-page-upgradedoc": "Bilindkirin",
        "config-page-existingwiki": "Wîkiya heye",
        "config-restart": "Erê, jinûve bide destpêkirin",
-       "config-apc": "[https://secure.php.net/apc APC] hate avakirin",
+       "config-apc": "[https://www.php.net/apc APC] hate avakirin",
        "config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension WinCache] hate avakirin",
        "config-diff3-bad": "GNU diff3 nehate dîtin.",
        "config-db-type": "Cureya danegehê:",
index 9c550df..bbe1a1d 100644 (file)
@@ -49,8 +49,8 @@
        "config-no-db": "Et konnt kee passenden Datebank-Driver fonnt ginn! Dir musst een Datebank-Driver fir PHP installéieren.\n{{PLURAL:$2|Dësn Datebank-Typ gëtt|Dës Datebank-Type ginn}} ënnerstëtzt: $1.\n\nWann Dir PHP selwer compiléiert hutt, da rekonfiguréiert en mat dem ageschalten Datebank-Client, zum Beispill an deem Dir <code>./configure --with-mysqli</code> benotzt.\nWann Dir PHP vun engem Debian oder Ubuntu Package aus installéiert hutt, da musst Dir och den php5-mysql Modul installéieren.",
        "config-outdated-sqlite": "'''Warnung:''' SQLite $1 ass installéiert. Allerdengs brauch MediaWiki SQLite $2 oder méi nei. SQLite ass dofir net disponibel.",
        "config-memory-bad": "'''Opgepasst:''' De Parameter <code>memory_limit</code> vu PHP ass $1.\nDat ass wahrscheinlech ze niddreg.\nD'Installatioun kéint net funktionéieren.",
-       "config-apc": "[https://secure.php.net/apc APC] ass installéiert",
-       "config-apcu": "[https://secure.php.net/apcu APCu] ass installéiert.",
+       "config-apc": "[https://www.php.net/apc APC] ass installéiert",
+       "config-apcu": "[https://www.php.net/apcu APCu] ass installéiert.",
        "config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension WinCache] ass installéiert",
        "config-diff3-bad": "GNU diff3 gouf net fonnt.",
        "config-git": "D'Software Git fir d'Kontroll vu Versioune gouf fonnt: <code>$1</code>.",
@@ -84,9 +84,9 @@
        "config-type-sqlite": "SQLite",
        "config-type-oracle": "Oracle",
        "config-type-mssql": "Microsoft SQL Server",
-       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] ass e beléiften Open-Source-Datebanksystem an eng Alternativ zu MySQL. ([https://secure.php.net/manual/en/pgsql.installation.php Uleedung fir d'Kompilatoun vu PHP mat PostgreSQL-Ënnerstëtzung])",
+       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] ass e beléiften Open-Source-Datebanksystem an eng Alternativ zu MySQL. ([https://www.php.net/manual/en/pgsql.installation.php Uleedung fir d'Kompilatoun vu PHP mat PostgreSQL-Ënnerstëtzung])",
        "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] ass eng kommerziell Datebank-Software. ([http://www.php.net/manual/en/oci8.installation.php How to compile PHP mat OCI8 Ënnerstëtzung])",
-       "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] ass eng kommerziell Datebank-Software fir Windows. ([https://secure.php.net/manual/en/sqlsrv.installation.php Wéi PHP mat SQLSRV Ënnerstëtzung kompiléieren])",
+       "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] ass eng kommerziell Datebank-Software fir Windows. ([https://www.php.net/manual/en/sqlsrv.installation.php Wéi PHP mat SQLSRV Ënnerstëtzung kompiléieren])",
        "config-header-mysql": "MariaDB/MySQL-Astellungen",
        "config-header-postgres": "PostgreSQL-Astellungen",
        "config-header-sqlite": "SQLite-Astellungen",
index 0f004c1..b686444 100644 (file)
        "config-pcre-no-utf8": "'''Fatale''': o modulo PCRE de PHP pâ ch'o segge stæto compilou sença o supporto PCRE_UTF8. A MediaWiki a-o richiede pe fonçionâ corettamente.",
        "config-memory-raised": "O valô <code>memory_limit</code> de PHP o l'è $1, aomentou a $2.",
        "config-memory-bad": "''Atençion:''' O valô de <code>memory_limit</code> do PHP o l'è $1.\nFoscia o l'è troppo basso.\nL'installaçion a porriæ fallî!",
-       "config-apc": "[https://secure.php.net/apc APC] o l'è installou",
-       "config-apcu": "[https://secure.php.net/apc APC] o l'è installou",
+       "config-apc": "[https://www.php.net/apc APC] o l'è installou",
+       "config-apcu": "[https://www.php.net/apc APC] o l'è installou",
        "config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension WinCache] o l'è installou",
-       "config-no-cache-apcu": "'''Atençion:''' [https://secure.php.net/apcu APCu], [http://xcache.lighttpd.net/ XCache] ò [https://www.iis.net/downloads/microsoft/wincache-extension WinCache] no son stæti trovæ.\nA caching di ogetti a no l'è attivâ.",
+       "config-no-cache-apcu": "'''Atençion:''' [https://www.php.net/apcu APCu], [http://xcache.lighttpd.net/ XCache] ò [https://www.iis.net/downloads/microsoft/wincache-extension WinCache] no son stæti trovæ.\nA caching di ogetti a no l'è attivâ.",
        "config-mod-security": "<strong>Atençion:</strong> O to serviou web o g'ha o [https://modsecurity.org/ mod_security] abilitou. Gh'è tante configuaçioin che crean di problemi a-a MediaWiki ò a atro software ch'o permette a-i utenti de pubbricâ quâ-se-segge contegnuo. Se poscibbile o doviæ ese disabilitou.\nFanni rifeimento a-a [https://modsecurity.org/documentation/ documentaçion insce-o mod_security] ò contatta o supporto tecnico do to provider de hosting se se veifica di erroî.",
        "config-diff3-bad": "GNU diff3 non trovou.",
        "config-git": "Trovou software de controllo da verscion Git: <code>$1</code>.",
        "config-type-mysql": "MariaDB, MySQL ò compatibbile",
        "config-type-mssql": "Microsoft SQL Server",
        "config-support-info": "MediaWiki o supporta i seguenti scistemi de database:\n\n$1\n\nSe fra quelli elencæ chì de sotta no ti veddi o scistema de database che ti voriesci doeuviâ, segui e instruçioin inganciæ de d'ato pe abilitâ o supporto.",
-       "config-dbsupport-mysql": "* [{{int:version-db-mysql-url}} MySQL] a l'è a primma scelta pe MediaWiki e a l'è quella megio suportâ. MediaWiki a fonçion-a ascì con [{{int:version-db-mariadb-url}} MariaDB] e [{{int:version-db-percona-url}} Percona Server], che son compatibbili con MySQL.([https://secure.php.net/manual/en/mysqli.installation.php Comme compilâ PHP con suporto MySQL])",
-       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] o l'è un popolare scistema de database open source comme alternativa a MySQL. ([https://secure.php.net/manual/en/pgsql.installation.php Comme compilâ PHP con suporto PostgreSQL])",
+       "config-dbsupport-mysql": "* [{{int:version-db-mysql-url}} MySQL] a l'è a primma scelta pe MediaWiki e a l'è quella megio suportâ. MediaWiki a fonçion-a ascì con [{{int:version-db-mariadb-url}} MariaDB] e [{{int:version-db-percona-url}} Percona Server], che son compatibbili con MySQL.([https://www.php.net/manual/en/mysqli.installation.php Comme compilâ PHP con suporto MySQL])",
+       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] o l'è un popolare scistema de database open source comme alternativa a MySQL. ([https://www.php.net/manual/en/pgsql.installation.php Comme compilâ PHP con suporto PostgreSQL])",
        "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite] o l'è un scistema de database leggio, ch'o l'è suportou molto ben. ([http://www.php.net/manual/en/pdo.installation.php Comme compilâ PHP con suporto SQLite], o l'utilizza PDO)",
        "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] o l'è un database de un'impreiza comerciâ. ([http://www.php.net/manual/en/oci8.installation.php Comme compilâ PHP con suporto OCI8])",
-       "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] o l'è un database de un'impreiza commerciâ per Windows. ([https://secure.php.net/manual/en/sqlsrv.installation.php Comme compilâ PHP con supporto SQLSRV])",
+       "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] o l'è un database de un'impreiza commerciâ per Windows. ([https://www.php.net/manual/en/sqlsrv.installation.php Comme compilâ PHP con supporto SQLSRV])",
        "config-header-mysql": "Impostaçioin MySQL",
        "config-header-postgres": "Impostaçioin PostgreSQL",
        "config-header-sqlite": "Impostaçioin SQLite",
index fe1888d..fcb39df 100644 (file)
        "config-env-hhvm": "HHVM $1 yra įdiegtas.",
        "config-outdated-sqlite": "<strong>Įspėjimas:</strong> jūs turite SQLite $1, kuri yra mažesnė nei minimali reikalinga versija $2. SQLite nebus prieinama.",
        "config-memory-raised": "PHP <code>memory_limit</code> yra $1, padidintas iki $2.",
-       "config-apc": "[https://secure.php.net/apc APC] yra įdiegtas",
-       "config-apcu": "[https://secure.php.net/apcu APCu] yra įdiegtas",
+       "config-apc": "[https://www.php.net/apc APC] yra įdiegtas",
+       "config-apcu": "[https://www.php.net/apcu APCu] yra įdiegtas",
        "config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension WinCache] yra įdiegtas",
-       "config-no-cache-apcu": "<strong>Įspėjimas:</strong> Nepavyko rasti [https://secure.php.net/apcu APCu], [http://xcache.lighttpd.net/ XCache] ar [https://www.iis.net/downloads/microsoft/wincache-extension WinCache].\nObjekto spartinimas neįjungtas.",
+       "config-no-cache-apcu": "<strong>Įspėjimas:</strong> Nepavyko rasti [https://www.php.net/apcu APCu], [http://xcache.lighttpd.net/ XCache] ar [https://www.iis.net/downloads/microsoft/wincache-extension WinCache].\nObjekto spartinimas neįjungtas.",
        "config-diff3-bad": "GNU diff3 nerastas.",
        "config-git": "Rasta Git versijų kontrolės sistema: <code>$1</code>.",
        "config-imagemagick": "Rastas „ImageMagick“: <code>$1</code>.\nPaveikslėlių miniatiūrizavimas bus įjungtas, jeigu įgalinsite vaizdų įkėlimą.",
index baa6e49..e744495 100644 (file)
@@ -27,7 +27,7 @@
        "config-restart": "Jā, restartēt",
        "config-env-php": "PHP $1 ir uzstādīts.",
        "config-env-hhvm": "HHVM $1 ir uzstādīts.",
-       "config-apcu": "[https://secure.php.net/apcu APCu] ir uzstādīts",
+       "config-apcu": "[https://www.php.net/apcu APCu] ir uzstādīts",
        "config-diff3-bad": "GNU diff3 nav atrasts.",
        "config-db-host-oracle": "Datubāzes TNS:",
        "config-db-name": "Datubāzes nosaukums:",
index 3df5d6b..450fa8b 100644 (file)
        "config-pcre-no-utf8": "<strong>Кобно</strong>: PCRE-модулот на PHP е срочен без поддршка за PCRE_UTF8.\nМедијаВики бара поддршка за UTF-8 за да може да работи правилно.",
        "config-memory-raised": "<code>memory_limit</code> за PHP изнесува $1, зголемен на $2.",
        "config-memory-bad": "<strong>Предупредување:</strong> <code>memory_limit</code> за PHP изнесува $1.\nОва е веројатно премалку.\nВоспоставката може да не успее!",
-       "config-apc": "[https://secure.php.net/apc APC] е воспоставен",
-       "config-apcu": "[https://secure.php.net/apcu APCu] е воспоставен",
+       "config-apc": "[https://www.php.net/apc APC] е воспоставен",
+       "config-apcu": "[https://www.php.net/apcu APCu] е воспоставен",
        "config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension WinCache] е воспоставен",
-       "config-no-cache-apcu": "<strong>Предупредување:</strong> Не можев да го најдам [https://secure.php.net/apcu APCu] или [https://www.iis.net/downloads/microsoft/wincache-extension WinCache].",
+       "config-no-cache-apcu": "<strong>Предупредување:</strong> Не можев да го најдам [https://www.php.net/apcu APCu] или [https://www.iis.net/downloads/microsoft/wincache-extension WinCache].",
        "config-mod-security": "'''Предупредување''': на вашиот опслужувач има овозможено [https://modsecurity.org/ mod_security]. Ако не е поставено како што треба, ова може да предизвика проблеми кај МедијаВики и други програми што им овозможуваат на корисниците да објавуваат произволни содржини.\nПогледнете ја [https://modsecurity.org/documentation/ mod_security документацијата] или обратете се кај домаќинот ако наидете на случајни грешки.",
        "config-diff3-bad": "Не го пронајдов споредникот за текст ГНУ разл3 (GNU diff3). Засега ова можете да го занемарите, но затоа пак спротиставеностите во уредувањата може да зачестат.",
        "config-git": "Го пронајдов Git програмот за контрола на верзии: <code>$1</code>.",
        "config-type-oracle": "Oracle",
        "config-type-mssql": "Microsoft SQL Server",
        "config-support-info": "МедијаВики ги поддржува следниве системи на бази на податоци:\n\n$1\n\nАко системот што сакате да го користите не е наведен подолу, тогаш проследете ја горенаведената врска со инструкции за да овозможите поддршка за тој систем.",
-       "config-dbsupport-mysql": "* [{{int:version-db-mariadb-url}} MariaDB] е главната цел на МедијаВики и најдобро е поддржан. МедијаВики работи и со  [{{int:version-db-mysql-url}} MySQL] и [{{int:version-db-percona-url}} Percona], кои се складни со MariaDB. ([https://secure.php.net/manual/en/mysqli.installation.php Како да срочите PHP со поддршка за MySQL])",
-       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] е популарен систем на бази на податоци со отворен код кој претставува алтернатива на MySQL ([https://secure.php.net/manual/en/pgsql.installation.php како да составите PHP со поддршка за PostgreSQL]). ([https://secure.php.net/manual/en/pgsql.installation.php Како да срочите PHP со поддршка за PostgreSQL])",
-       "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite] е лесен систем за бази на податоци кој е многу добро поддржан. ([https://secure.php.net/manual/en/pdo.installation.php Како да составите PHP со поддршка за SQLite], користи PDO)",
-       "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] е база на податоци на комерцијално претпријатие. ([https://secure.php.net/manual/en/oci8.installation.php Како да составите PHP со поддршка за OCI8])",
-       "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server]  е база на податоци на комерцијално претпријатиe за Windows ([https://secure.php.net/manual/en/sqlsrv.installation.php How to compile PHP with SQLSRV поддршка])",
+       "config-dbsupport-mysql": "* [{{int:version-db-mariadb-url}} MariaDB] е главната цел на МедијаВики и најдобро е поддржан. МедијаВики работи и со  [{{int:version-db-mysql-url}} MySQL] и [{{int:version-db-percona-url}} Percona], кои се складни со MariaDB. ([https://www.php.net/manual/en/mysqli.installation.php Како да срочите PHP со поддршка за MySQL])",
+       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] е популарен систем на бази на податоци со отворен код кој претставува алтернатива на MySQL ([https://www.php.net/manual/en/pgsql.installation.php како да составите PHP со поддршка за PostgreSQL]). ([https://www.php.net/manual/en/pgsql.installation.php Како да срочите PHP со поддршка за PostgreSQL])",
+       "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite] е лесен систем за бази на податоци кој е многу добро поддржан. ([https://www.php.net/manual/en/pdo.installation.php Како да составите PHP со поддршка за SQLite], користи PDO)",
+       "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] е база на податоци на комерцијално претпријатие. ([https://www.php.net/manual/en/oci8.installation.php Како да составите PHP со поддршка за OCI8])",
+       "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server]  е база на податоци на комерцијално претпријатиe за Windows ([https://www.php.net/manual/en/sqlsrv.installation.php How to compile PHP with SQLSRV поддршка])",
        "config-header-mysql": "Нагодувања на MariaDB/MySQL",
        "config-header-postgres": "Нагодувања на PostgreSQL",
        "config-header-sqlite": "Нагодувања на SQLite",
index c00382d..67d9f54 100644 (file)
@@ -52,7 +52,7 @@
        "config-outdated-sqlite": "<strong>इशारा:</strong> आपणापाशी SQLite $1 आहे, जी किमान आवश्यक आवृत्ती $2 पेक्षा, निम्न आहे. SQLite अनुपलब्ध राहील.",
        "config-memory-raised": "पीएचपीची <code>memory_limit</code> ही $1 आहे, त्यास $2 ला वाढविली.",
        "config-memory-bad": "पीएचपीची <code>memory_limit</code> ही $1 आहे.\nही बरीच खालच्या स्तरावरची आहे.\nउभारणी अयशस्वी होऊ शकते!",
-       "config-apc": "[https://secure.php.net/apc APC] उभारली आहे",
+       "config-apc": "[https://www.php.net/apc APC] उभारली आहे",
        "config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension WinCache] उभारली आहे",
        "config-diff3-bad": "GNU diff3 सापडली नाही.",
        "config-git-bad": "गीट आवृत्ती नियमन संचेतन सापडली नाही.",
index 334d3a0..0318f64 100644 (file)
@@ -58,7 +58,7 @@
        "config-no-fts3": "<strong>Amaran:</strong> SQLite disusun tanpa [//sqlite.org/fts3.html modil FTS3], maka ciri-ciri pencarian tidak akan disediakan pada backend ini.",
        "config-pcre-old": "<strong>Amaran keras:</strong> PCRE $1 ke atas diperlukan.\nBinari PHP anda berpaut dengan PCRE $2.\n[https://www.mediawiki.org/wiki/Manual:Errors_and_symptoms/PCRE Keterangan lanjut].",
        "config-memory-bad": "<strong>Amaran:</strong> <code>memory_limit</code> (Had memori) PHP ialah $1.\nIni mungkin terlalu rendah.\nPemasangan mungkin akan gagal!",
-       "config-apc": "[https://secure.php.net/apc APC] dipasang",
+       "config-apc": "[https://www.php.net/apc APC] dipasang",
        "config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension WinCache] dipasang",
        "config-mod-security": "<strong>Amaran:</strong> Pelayan web anda dihidupkan [https://modsecurity.org/ mod_security]/mod_security2. Kebanyakan konfigurasinya yang umum boleh menimbulkan kesulitan untuk MediaWiki dan perisian-perisian lain yang membolehkan pengguna untuk mengeposkan kandungan yang sewenang-wenang.\nJika boleh, ciri-ciri ini harus dimatikan. Jika tidak, rujuki [https://modsecurity.org/documentation/ dokumentasi mod_security] atau hubungi bantuan hos anda jika anda menghadapi ralat sembarangan.",
        "config-diff3-bad": "GNU diff3 tidak dijumpai.",
index c64a2cd..5662f0a 100644 (file)
@@ -31,8 +31,8 @@
        "config-env-hhvm": "اچ‌اچ‌وی‌ام $1 نصب بیه.",
        "config-unicode-using-intl": "عادی یونیکد وسه [https://pecl.php.net/intl افزونهٔ intl برای PECL] جه استفاده هاکن.",
        "config-memory-raised": "PHP's <code>memory_limit</code>, نسخهٔ $1 هسته، ونه نسخهٔ $2 ره بَیری آپگریت هاکنی.",
-       "config-apc": "[https://secure.php.net/apc APC] نصب بیه.",
-       "config-apcu": "[https://secure.php.net/apcu APCu] نصب بیه.",
+       "config-apc": "[https://www.php.net/apc APC] نصب بیه.",
+       "config-apcu": "[https://www.php.net/apcu APCu] نصب بیه.",
        "config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension WinCache] نصب بیه.",
        "config-diff3-bad": "GNU diff3 پیدا نیه.",
        "config-mssql-auth": "نوع تأیید:",
index 8183ed7..d4dc4b6 100644 (file)
        "config-pcre-no-utf8": "<strong>Fatale:</strong> 'E module PCRE d' 'o PHP pare ca se so' compilate senza PCRE_UTF8 supporto.\nA MediaWiki serve nu supporto UTF-8 pe' putè funziunà apposto.",
        "config-memory-raised": "'O valore 'e PHP <code>memory_limit</code> è $1, aumentato a $2.",
        "config-memory-bad": "<strong>Attenziò:</strong> 'o valore 'e PHP <code>memory_limit</code> è $1.\nProbabbilmente troppo basso.\n'A installazione se putesse scassà!",
-       "config-apc": "[https://secure.php.net/apc APC] è installato",
-       "config-apcu": "[https://secure.php.net/apcu APCu] è installato",
+       "config-apc": "[https://www.php.net/apc APC] è installato",
+       "config-apcu": "[https://www.php.net/apcu APCu] è installato",
        "config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension WinCache] è installato",
-       "config-no-cache-apcu": "<strong>Attenziò:</strong> [https://secure.php.net/apcu APCu], [http://xcache.lighttpd.net/ XCache] o [https://www.iis.net/downloads/microsoft/wincache-extension WinCache] nun so' state truvate.\n'A funziona caching 'e ll'oggette non è apicciata.",
+       "config-no-cache-apcu": "<strong>Attenziò:</strong> [https://www.php.net/apcu APCu], [http://xcache.lighttpd.net/ XCache] o [https://www.iis.net/downloads/microsoft/wincache-extension WinCache] nun so' state truvate.\n'A funziona caching 'e ll'oggette non è apicciata.",
        "config-mod-security": "<strong>Attenziò:</strong> 'O servitore web vuosto téne [https://modsecurity.org/ mod_security]/mod_security2 appicciato. Ce stanno tante mpustaziune commune ca 'o facessero causà prubbleme a MediaWiki e ll'ati software ca permettessero ll'utente 'e pubbrecà cuntenute.\nSi putite, stutate sta funziona. Sinò, riferite 'a [https://modsecurity.org/documentation/ documentaziona ncopp' 'o mod_security] o cuntattate 'o host vuosto pe' ve dà supporto quanno se scummogliasse cocch'errore.",
        "config-diff3-bad": "GNU diff3 nun truvato.",
        "config-git": "Truvato software 'e cuntrollo d' 'a verziona Git: <code>$1</code>.",
        "config-type-mysql": "MariaDB, MySQL o compatibbele",
        "config-type-mssql": "Microsoft SQL Server",
        "config-support-info": "MediaWiki supporta 'e sisteme 'e database ccà abbascio:\n\n$1\n\nSi nfra chiste ccà nun vedite 'o sistema 'e database ca vulite ausà, allora avite liegge 'e instruziune ccà ncoppa pe' ne dà supporto.",
-       "config-dbsupport-mysql": "* [{{int:version-db-mariadb-url}} MariaDB] è 'a configurazione cchiù mmeglio p' 'o MediaWiki e è chilla meglio suppurtata. MediaWiki può faticà pure cu' [{{int:version-db-mariadb-url}} MariaDB] e [{{int:version-db-percona-url}} Percona Server], ca fossero MariaDB cumpatibbele. ([https://secure.php.net/manual/en/mysqli.installation.php Comme s'adda fà pe' cumpilà PHP cu suppuorto MySQL])",
-       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] è nu sistema canusciuto 'e database open source ca fosse n'alternativa a MySQL.\n([https://secure.php.net/manual/en/pgsql.installation.php Comme s'avess'a cumpilà PHP cu suppuorto PostgreSQL])",
-       "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite]  è nu sistema 'e database leggero, ca fosse assaje buono suppurtato.\n([https://secure.php.net/manual/en/pdo.installation.php Comme cumpilà PHP cu suppuorto SQLite], aùsa PDO)",
-       "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] è nu database 'e na fraveca commerciale. ([https://secure.php.net/manual/en/oci8.installation.php Comme cumpilà PHP cu suppuorto OCI8])",
-       "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] è nu database 'e na fraveca commerciale p' 'o Windows. ([https://secure.php.net/manual/en/sqlsrv.installation.php Comme cumpilà PHP cu suppuorto SQLSRV])",
+       "config-dbsupport-mysql": "* [{{int:version-db-mariadb-url}} MariaDB] è 'a configurazione cchiù mmeglio p' 'o MediaWiki e è chilla meglio suppurtata. MediaWiki può faticà pure cu' [{{int:version-db-mariadb-url}} MariaDB] e [{{int:version-db-percona-url}} Percona Server], ca fossero MariaDB cumpatibbele. ([https://www.php.net/manual/en/mysqli.installation.php Comme s'adda fà pe' cumpilà PHP cu suppuorto MySQL])",
+       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] è nu sistema canusciuto 'e database open source ca fosse n'alternativa a MySQL.\n([https://www.php.net/manual/en/pgsql.installation.php Comme s'avess'a cumpilà PHP cu suppuorto PostgreSQL])",
+       "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite]  è nu sistema 'e database leggero, ca fosse assaje buono suppurtato.\n([https://www.php.net/manual/en/pdo.installation.php Comme cumpilà PHP cu suppuorto SQLite], aùsa PDO)",
+       "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] è nu database 'e na fraveca commerciale. ([https://www.php.net/manual/en/oci8.installation.php Comme cumpilà PHP cu suppuorto OCI8])",
+       "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] è nu database 'e na fraveca commerciale p' 'o Windows. ([https://www.php.net/manual/en/sqlsrv.installation.php Comme cumpilà PHP cu suppuorto SQLSRV])",
        "config-header-mysql": "Mpustaziune MariaDB/MySQL",
        "config-header-postgres": "Mpustaziune PostgreSQL",
        "config-header-sqlite": "Mpustaziune SQLite",
index fef5391..8dec566 100644 (file)
        "config-pcre-no-utf8": "'''Fatal''': PHPs PCRE modul ser ut til å være kompilert uten PCRE_UTF8-støtte.\nMediaWiki krever UTF-8-støtte for å fungere riktig.",
        "config-memory-raised": "PHPs <code>memory_limit</code> er $1, økt til $2.",
        "config-memory-bad": "'''Advarsel:''' PHPs <code>memory_limit</code> er $1.\nDette er sannsynligvis for lavt.\nInstallasjonen kan mislykkes!",
-       "config-apc": "[https://secure.php.net/apc APC] er installert",
-       "config-apcu": "[https://secure.php.net/apcu APCu] er installert",
+       "config-apc": "[https://www.php.net/apc APC] er installert",
+       "config-apcu": "[https://www.php.net/apcu APCu] er installert",
        "config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension WinCache] er installert",
-       "config-no-cache-apcu": "<strong>Advarsel:</strong> Kunne ikke finne [https://secure.php.net/apcu APCu] eller [https://www.iis.net/downloads/microsoft/wincache-extension WinCache].\nObjekthurtiglagring er ikke aktivert.",
+       "config-no-cache-apcu": "<strong>Advarsel:</strong> Kunne ikke finne [https://www.php.net/apcu APCu] eller [https://www.iis.net/downloads/microsoft/wincache-extension WinCache].\nObjekthurtiglagring er ikke aktivert.",
        "config-mod-security": "'''Advarsel''': Din web-tjener har [https://modsecurity.org/ mod_security] påslått. Hvis denne er feilinnstilt, kan det gi problemer for MediaWiki eller annen programvare som tillater brukere å poste vilkårlig innhold.\nSjekk [https://modsecurity.org/documentation/ mod_security-dokumentasjonen] eller ta kontakt med din nettleverandør hvis du opplever tilfeldige feil.",
        "config-diff3-bad": "GNU diff3 ikke funnet.",
        "config-git": "Har funnet Git version control software: <code>$1</code>.",
        "config-type-oracle": "Oracle",
        "config-type-mssql": "Microsoft SQLServer",
        "config-support-info": "MediaWiki støtter følgende databasesystem:\n\n$1\n\nHvis du ikke ser databasesystemet du prøver å bruke i listen nedenfor, følg instruksjonene det er lenket til over for å aktivere støtte.",
-       "config-dbsupport-mysql": "* [{{int:version-db-mariadb-url}} MariaDB] er den foretrukne databasetypen for MediaWiki og har best støtte.  MediaWiki fungerer også med [{{int:version-db-mysql-url}} MySQL] og [{{int:version-db-percona-url}} Percona Server], som begge er MariaDB-kompatible. ([https://secure.php.net/manual/en/mysqli.installation.php Hvordan kompilere PHP med MySQL-støtte])",
-       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] er et populært åpen kildekode-databasesystem og et alternativ til MySQL.  ([https://secure.php.net/manual/en/pgsql.installation.php Hvordan kompilere PHP med PostgreSQL-støtte])",
+       "config-dbsupport-mysql": "* [{{int:version-db-mariadb-url}} MariaDB] er den foretrukne databasetypen for MediaWiki og har best støtte.  MediaWiki fungerer også med [{{int:version-db-mysql-url}} MySQL] og [{{int:version-db-percona-url}} Percona Server], som begge er MariaDB-kompatible. ([https://www.php.net/manual/en/mysqli.installation.php Hvordan kompilere PHP med MySQL-støtte])",
+       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] er et populært åpen kildekode-databasesystem og et alternativ til MySQL.  ([https://www.php.net/manual/en/pgsql.installation.php Hvordan kompilere PHP med PostgreSQL-støtte])",
        "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite] er et lettvekts-databasesystem som har veldig god støtte. ([http://www.php.net/manual/en/pdo.installation.php Hvordan kompilere PHP med SQLite-støtte], bruker PDO)",
-       "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] er en kommersiell database for bedrifter. ([https://secure.php.net/manual/en/oci8.installation.php Hvordan kompilere PHP med OCI8-støtte])",
-       "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] er et kommersielt databasesystem under Windows for bedrifter. ([https://secure.php.net/manual/en/sqlsrv.installation.php Hvordan kompilere PHP med SQLSRV-støtte])",
+       "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] er en kommersiell database for bedrifter. ([https://www.php.net/manual/en/oci8.installation.php Hvordan kompilere PHP med OCI8-støtte])",
+       "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] er et kommersielt databasesystem under Windows for bedrifter. ([https://www.php.net/manual/en/sqlsrv.installation.php Hvordan kompilere PHP med SQLSRV-støtte])",
        "config-header-mysql": "MariadB/MySQL-innstillinger",
        "config-header-postgres": "PostgreSQL-innstillinger",
        "config-header-sqlite": "SQLite-innstillinger",
        "config-license-help": "Mange åpne wikier legger alle bidrag under en [https://freedomdefined.org/Definition gratislisens].\nDette gir en følelse av felleseie og stimulerer til langvarige bidrag.\nDette er normalt unødvendig for en privat eller virksomhetsbegrenset wiki.\n\nHvis du ønsker å kunne bruke tekst fra Wikipedia, og at Wikipedia skal kunne ta i mot tekst kopiert fra din wiki, bør du velge <strong>{{int:config-license-cc-by-sa}}</strong>.\n\nWikipedia brukte tidligere GNU Free Documentation License.\nGFDL er en grei lisens, med vanskelig å forstå.\nDet er også vanskelig å gjenbruke innhold lisensiert under GFDL.",
        "config-email-settings": "E-postinnstillinger",
        "config-enable-email": "Aktiver utgående e-post",
-       "config-enable-email-help": "Hvis du vil at e-post skal virke, må [https://secure.php.net/manual/en/mail.configuration.php PHPs epostinnstillinger] konfigureres riktig.\nHvis du ikke ønsker noen epostfunksjoner, kan du deaktivere dem her.",
+       "config-enable-email-help": "Hvis du vil at e-post skal virke, må [https://www.php.net/manual/en/mail.configuration.php PHPs epostinnstillinger] konfigureres riktig.\nHvis du ikke ønsker noen epostfunksjoner, kan du deaktivere dem her.",
        "config-email-user": "Aktiver e-post mellom brukere",
        "config-email-user-help": "Tillat alle brukere å sende hverandre e-post hvis de har aktivert det i deres innstillinger.",
        "config-email-usertalk": "Aktiver brukerdiskusjonssidevarsler",
index c79aa63..a12445c 100644 (file)
        "config-pcre-no-utf8": "<strong>Onherstelbare fout:</strong> de module PRCE van PHP lijkt te zijn gecompileerd zonder ondersteuning voor PCRE_UTF8.\nMediaWiki heeft ondersteuning voor UTF-8 nodig om correct te kunnen werken.",
        "config-memory-raised": "PHP's <code>memory_limit</code> is $1 en is verhoogd tot $2.",
        "config-memory-bad": "'''Waarschuwing:''' PHP's <code>memory_limit</code> is $1.\nDit is waarschijnlijk te laag.\nDe installatie kan mislukken!",
-       "config-apc": "[https://secure.php.net/apc APC] is op dit moment geïnstalleerd",
-       "config-apcu": "[https://secure.php.net/apcu APCu] is geïnstalleerd",
+       "config-apc": "[https://www.php.net/apc APC] is op dit moment geïnstalleerd",
+       "config-apcu": "[https://www.php.net/apcu APCu] is geïnstalleerd",
        "config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension WinCache] is op dit moment geïnstalleerd",
-       "config-no-cache-apcu": "<strong>Waarschuwing:</strong> [https://secure.php.net/apcu APCu], [http://xcache.lighttpd.net/ XCache] of [https://www.iis.net/downloads/microsoft/wincache-extension WinCache] is niet aangetroffen.\nHet cachen van objecten is niet ingeschakeld.",
+       "config-no-cache-apcu": "<strong>Waarschuwing:</strong> [https://www.php.net/apcu APCu], [http://xcache.lighttpd.net/ XCache] of [https://www.iis.net/downloads/microsoft/wincache-extension WinCache] is niet aangetroffen.\nHet cachen van objecten is niet ingeschakeld.",
        "config-mod-security": "<strong>Waarschuwing:</strong> Uw webserver heeft de module [https://modsecurity.org/ mod_security]/mod_security2 ingeschakeld. Veel standaard instellingen hiervan zorgen voor problemen in combinatie met MediaWiki en andere software die gebruikers in staat stelt willekeurige inhoud te posten.\nIndien mogelijk, zou deze moeten worden uitgeschakeld. Lees anders de [https://modsecurity.org/documentation/ documentatie over mod_security] of neem contact op met de helpdesk van uw provider als u tegen problemen aanloopt.",
        "config-diff3-bad": "GNU diff3 niet aangetroffen. U kunt dit voorlopig negeren, maar bewerkingsconflicten kunnen vaker voorkomen.",
        "config-git": "Versiecontrolesoftware git is aangetroffen: <code>$1</code>",
        "config-type-oracle": "Oracle",
        "config-type-mssql": "Microsoft SQL Server",
        "config-support-info": "MediaWiki ondersteunt de volgende databasesystemen:\n\n$1\n\nAls u het databasesysteem dat u wilt gebruiken niet in de lijst terugvindt, volg dan de handleiding waarnaar hierboven wordt verwezen om ondersteuning toe te voegen.",
-       "config-dbsupport-mysql": "* [{{int:version-db-mariadb-url}} MariaDB] is de primaire database voor MediaWiki en wordt het best ondersteund. MediaWiki werkt ook met [{{int:version-db-mysql-url}} MySQL] en [{{int:version-db-percona-url}} Percona Server], die MariaDB-compatibel zijn ([https://secure.php.net/manual/en/mysqli.installation.php hoe PHP te compileren met MySQL-ondersteuning]).",
-       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] is een populair open source databasesysteem als alternatief voor MySQL.([https://secure.php.net/manual/en/pgsql.installation.php Hoe u PHP kunt compileren met ondersteuning voor PostgreSQL])",
-       "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite] is een zeer goed ondersteund lichtgewicht databasesysteem ([https://secure.php.net/manual/en/pdo.installation.php hoe PHP gecompileerd moet zijn met ondersteuning voor SQLite]; gebruikt PDO).",
-       "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] is een commerciële database voor grote bedrijven ([https://secure.php.net/manual/en/oci8.installation.php PHP compileren met ondersteuning voor OCI8]).",
-       "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] is een commerciële enterprisedatabase voor Windows ([https://secure.php.net/manual/en/sqlsrv.installation.php PHP compileren met ondersteuning voor SQLSRV]).",
+       "config-dbsupport-mysql": "* [{{int:version-db-mariadb-url}} MariaDB] is de primaire database voor MediaWiki en wordt het best ondersteund. MediaWiki werkt ook met [{{int:version-db-mysql-url}} MySQL] en [{{int:version-db-percona-url}} Percona Server], die MariaDB-compatibel zijn ([https://www.php.net/manual/en/mysqli.installation.php hoe PHP te compileren met MySQL-ondersteuning]).",
+       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] is een populair open source databasesysteem als alternatief voor MySQL.([https://www.php.net/manual/en/pgsql.installation.php Hoe u PHP kunt compileren met ondersteuning voor PostgreSQL])",
+       "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite] is een zeer goed ondersteund lichtgewicht databasesysteem ([https://www.php.net/manual/en/pdo.installation.php hoe PHP gecompileerd moet zijn met ondersteuning voor SQLite]; gebruikt PDO).",
+       "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] is een commerciële database voor grote bedrijven ([https://www.php.net/manual/en/oci8.installation.php PHP compileren met ondersteuning voor OCI8]).",
+       "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] is een commerciële enterprisedatabase voor Windows ([https://www.php.net/manual/en/sqlsrv.installation.php PHP compileren met ondersteuning voor SQLSRV]).",
        "config-header-mysql": "MariaDB/MySQL-instellingen",
        "config-header-postgres": "PostgreSQL-instellingen",
        "config-header-sqlite": "SQLite-instellingen",
        "config-license-help": "In veel openbare wiki's zijn alle bijdragen beschikbaar onder een [https://freedomdefined.org/Definition vrije licentie].\nDit helpt bij het creëren van een gevoel van gemeenschappelijk eigendom en stimuleert bijdragen op lange termijn.\nDit is over het algemeen niet nodig is voor een particuliere of zakelijke wiki.\n\nAls u teksten uit Wikipedia wilt kunnen gebruiken en u wilt het mogelijk maken teksten uit uw wiki naar Wikipedia te kopiëren, kies dan de licentie <strong>{{int:config-license-cc-by-sa}}</strong>.\n\nDe GNU Free Documentation License is de oude licentie voor inhoud uit Wikipedia.\nDit is nog steeds een geldige licentie, maar deze licentie is lastig te begrijpen.\nHet is ook lastig inhoud te hergebruiken onder de GFDL.",
        "config-email-settings": "E-mailinstellingen",
        "config-enable-email": "Uitgaande e-mail inschakelen",
-       "config-enable-email-help": "Als u wilt dat e-mailen mogelijk is, dan moeten de [https://secure.php.net/manual/en/mail.configuration.php e-mailinstellingen van PHP] correct zijn.\nAls u niet wilt dat e-mailen mogelijk is, dan kunt u de instellingen hier uitschakelen.",
+       "config-enable-email-help": "Als u wilt dat e-mailen mogelijk is, dan moeten de [https://www.php.net/manual/en/mail.configuration.php e-mailinstellingen van PHP] correct zijn.\nAls u niet wilt dat e-mailen mogelijk is, dan kunt u de instellingen hier uitschakelen.",
        "config-email-user": "E-mail tussen gebruikers inschakelen",
        "config-email-user-help": "Gebruikers toestaan e-mail aan elkaar te verzenden als dit in de voorkeuren is ingesteld.",
        "config-email-usertalk": "Gebruikersoverlegmeldingen inschakelen",
index e88aec2..7a99bb8 100644 (file)
@@ -42,7 +42,7 @@
        "config-env-hhvm": "HHVM $1 es installat.",
        "config-unicode-using-intl": "Utilizacion de [https://pecl.php.net/intl l'extension PECL intl] per la normalizacion Unicode.",
        "config-memory-raised": "Lo paramètre <code>memory_limit</code> de PHP èra a $1, portat a $2.",
-       "config-apc": "[https://secure.php.net/apc APC] es installat",
+       "config-apc": "[https://www.php.net/apc APC] es installat",
        "config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension WinCache] es installat",
        "config-diff3-bad": "GNU diff3 pas trobat.",
        "config-git": "Logicial de contraròtle de version Git trobat : <code>$1</code>.",
index 0efcc11..41da9dc 100644 (file)
        "config-pcre-no-utf8": "'''Błąd krytyczny''' – wydaje się, że moduł PCRE w PHP został skompilowany bez wsparcia dla UTF‐8.\nMediaWiki wymaga wsparcia dla UTF‐8 do prawidłowego działania.",
        "config-memory-raised": "PHP <code>memory_limit</code> było ustawione na $1, zostanie zwiększone do $2.",
        "config-memory-bad": "'''Uwaga:''' PHP <code>memory_limit</code> jest ustawione na $1.\nTo jest prawdopodobnie zbyt mało.\nInstalacja może się nie udać!",
-       "config-apc": "[https://secure.php.net/apc APC] jest zainstalowany",
-       "config-apcu": "[https://secure.php.net/apcu APCu] jest zainstalowany",
+       "config-apc": "[https://www.php.net/apc APC] jest zainstalowany",
+       "config-apcu": "[https://www.php.net/apcu APCu] jest zainstalowany",
        "config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension WinCache] jest zainstalowany",
-       "config-no-cache-apcu": "<strong>Ostrzeżenie:</strong> Nie można było znaleźć [https://secure.php.net/apcu APCu] ani [https://www.iis.net/downloads/microsoft/wincache-extension WinCache].\nPamięć podręczna obiektów nie została włączona.",
+       "config-no-cache-apcu": "<strong>Ostrzeżenie:</strong> Nie można było znaleźć [https://www.php.net/apcu APCu] ani [https://www.iis.net/downloads/microsoft/wincache-extension WinCache].\nPamięć podręczna obiektów nie została włączona.",
        "config-mod-security": "''' Ostrzeżenie ''': Serwer sieci web ma włączone [https://modsecurity.org/ mod_security]. Jeśli jest niepoprawnie skonfigurowane, może być przyczyną problemów MediaWiki lub innego oprogramowania, które pozwala użytkownikom na wysyłanie dowolnej zawartości.\nSprawdź w [https://modsecurity.org/documentation/ dokumentacji mod_security] lub skontaktuj się z obsługa hosta, jeśli wystąpią losowe błędy.",
        "config-diff3-bad": "Nie znaleziono funkcjonalności porównywania tekstu GNU diff3. Możesz zignorować ten komunikat, jednak konflikty edycji będą wówczas częstsze.",
        "config-git": "Znaleziono oprogramowanie kontroli wersji Git: <code>$1</code>.",
        "config-type-mysql": "MariaDB, MySQL lub kompatybilna",
        "config-type-mssql": "Microsoft SQL Server",
        "config-support-info": "MediaWiki może współpracować z następującymi systemami baz danych:\n\n$1\n\nPoniżej wyświetlone są systemy baz danych gotowe do użycia. Jeżeli poniżej brakuje bazy danych, z której chcesz skorzystać, oznacza to, że brakuje odpowiedniego oprogramowania lub zostało ono niepoprawnie skonfigurowane. Powyżej znajdziesz odnośniki do dokumentacji, która pomoże w konfiguracji odpowiednich komponentów.",
-       "config-dbsupport-mysql": "* [{{int:version-db-mariadb-url}} MariaDB] jest bazą danych, na której rozwijane jest oprogramowanie MediaWiki. MediaWiki działa również z [{{int:version-db-mysql-url}} MySQL] i [{{int:version-db-percona-url}} Percona Server], które są zgodne z MariaDB. ([https://secure.php.net/manual/en/mysqli.installation.php Zobacz, jak skompilować PHP ze wsparciem dla MySQL])",
-       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] jest popularnym, otawrtym systemem baz danych, często stosowanym jako alternatywa dla MySQL. ([https://secure.php.net/manual/en/pgsql.installation.php Zobacz, jak skompilować PHP z obsługą PostgreSQL])",
-       "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite] jest niewielkim systemem bazy danych, z którym MediaWiki bardzo dobrze współpracuje. ([https://secure.php.net/manual/pl/pdo.installation.php Zobacz, jak skompilować PHP ze wsparciem dla SQLite], korzystając z PDO)",
-       "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] jest komercyjną profesjonalną bazą danych. ([https://secure.php.net/manual/pl/oci8.installation.php Jak skompilować PHP ze wsparciem dla OCI8])",
-       "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] jest komercyjną profesjonalną bazą danych. ([https://secure.php.net/manual/pl/sqlsrv.installation.php Jak skompilować PHP ze wsparciem dla SQLSRV])",
+       "config-dbsupport-mysql": "* [{{int:version-db-mariadb-url}} MariaDB] jest bazą danych, na której rozwijane jest oprogramowanie MediaWiki. MediaWiki działa również z [{{int:version-db-mysql-url}} MySQL] i [{{int:version-db-percona-url}} Percona Server], które są zgodne z MariaDB. ([https://www.php.net/manual/en/mysqli.installation.php Zobacz, jak skompilować PHP ze wsparciem dla MySQL])",
+       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] jest popularnym, otawrtym systemem baz danych, często stosowanym jako alternatywa dla MySQL. ([https://www.php.net/manual/en/pgsql.installation.php Zobacz, jak skompilować PHP z obsługą PostgreSQL])",
+       "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite] jest niewielkim systemem bazy danych, z którym MediaWiki bardzo dobrze współpracuje. ([https://www.php.net/manual/pl/pdo.installation.php Zobacz, jak skompilować PHP ze wsparciem dla SQLite], korzystając z PDO)",
+       "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] jest komercyjną profesjonalną bazą danych. ([https://www.php.net/manual/pl/oci8.installation.php Jak skompilować PHP ze wsparciem dla OCI8])",
+       "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] jest komercyjną profesjonalną bazą danych. ([https://www.php.net/manual/pl/sqlsrv.installation.php Jak skompilować PHP ze wsparciem dla SQLSRV])",
        "config-header-mysql": "Ustawienia MariaDB/MySQL",
        "config-header-postgres": "Ustawienia PostgreSQL",
        "config-header-sqlite": "Ustawienia SQLite",
        "config-license-help": "Wiele publicznych wiki umieszcza wszystkie dopisane treści na [https://freedomdefined.org/Definition wolnej licencji].\nPomaga to tworzyć poczucie wspólnoty i zachęca do długoterminowego wkładu.\nNie jest to zazwyczaj konieczne w prywatnych lub firmowych wiki.\n\nJeśli chcesz móc użyć tekstu z Wikipedii i chcesz Wikipedia mogła zaakceptować tekst skopiowany z twojej wiki, należy wybrać <strong>{{int:config-license-cc-by-sa}}</strong>.\n\nWikipedia używała poprzednio GNU Free Documentation License.\nGFDL jest poprawną licencję, ale trudno ją zrozumieć.\nTrudno także ponowne użyć zawartości na licencji GFDL.",
        "config-email-settings": "Ustawienia e-maili",
        "config-enable-email": "Włącz wychodzące wiadomości e–mail",
-       "config-enable-email-help": "Jeśli chcesz, aby działał e-mail, [https://secure.php.net/manual/pl/mail.configuration.php Ustawienia poczty PHP] muszą być poprawnie wprowadzone.\nJeśli nie chcesz jakichś funkcji poczty e-mail, można je wyłączyć tutaj.",
+       "config-enable-email-help": "Jeśli chcesz, aby działał e-mail, [https://www.php.net/manual/pl/mail.configuration.php Ustawienia poczty PHP] muszą być poprawnie wprowadzone.\nJeśli nie chcesz jakichś funkcji poczty e-mail, można je wyłączyć tutaj.",
        "config-email-user": "Włącz możliwość przesyłania e‐maili pomiędzy użytkownikami",
        "config-email-user-help": "Zezwalaj użytkownikom na wysyłanie wzajemnie e‐maili, jeśli będą mieć włączoną tę funkcję w swoich preferencjach.",
        "config-email-usertalk": "Włącz powiadamianie o zmianach na stronie dyskusji użytkownika",
        "config-install-done": "<strong>'''Gratulacje!</strong>\nUdało Ci się zainstalować MediaWiki.\n\nInstalator wygenerował plik konfiguracyjny <code>LocalSettings.php</code>.\n\nMusisz go pobrać i umieścić w katalogu głównym Twojej instalacji wiki (tym samym katalogu co index.php). Pobieranie powinno zacząć się automatycznie.\n\nJeżeli pobieranie nie zostało zaproponowane lub jeśli użytkownik je anulował, można ponownie uruchomić pobranie klikając poniższe łącze:\n\n$3\n\n<strong>Uwaga</strong>: Jeśli nie zrobisz tego teraz, wygenerowany plik konfiguracyjny nie będzie już dostępny po zakończeniu instalacji.\n\nPo załadowaniu pliku konfiguracyjnego możesz <strong>[$2 wejść na wiki]</strong>.",
        "config-install-done-path": "<strong>Gratulacje!</strong>\nZainstalowałeś właśnie MediaWiki.\n\nInstalator wygenerował plik <code>LocalSettings.php</code>.\nZawiera całą Twoją konfigurację.\n\nMusisz go pobrać i umieścić w <code>$4</code>. Pobieranie powinno rozpocząć się automatycznie.\n\nJeżeli nie pojawiła się informacja o pobieraniu lub jeżeli ja anulowałeś, kliknij poniższy link:\n\n$3\n\n<strong>Uwaga:</strong> Jeżeli nie zrobisz tego teraz, wygenerowany plik konfiguracyjny nie będzie potem dostępny, jeżeli wyjdziesz z instalacji bez jego pobrania.\n\nGdy to będzie zrobione, możesz <strong>[$2 wejść na swoją wiki]</strong>.",
        "config-install-success": "MediaWiki została pomyślnie zainstalowana. Możesz teraz\nodwiedzić <$1$2>, aby zobaczyć swoją wiki.\nJeśli masz pytania, sprawdź naszą listę najczęściej zadawanych pytań:\n<https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ> lub użyj jednej z\nform wsparcia odsyłanej z tej strony.",
+       "config-install-db-success": "Baza danych została pomyślnie utworzona",
        "config-download-localsettings": "Pobierz <code>LocalSettings.php</code>",
        "config-help": "pomoc",
        "config-help-tooltip": "kliknij, aby rozwinąć",
index 9d41768..130d507 100644 (file)
@@ -63,7 +63,7 @@
        "config-pcre-no-utf8": "'''Fatal''': ël mòdul PCRE ëd PHP a smija esse compilà sensa l'apògg PCRE_UTF8.\nMediaWiki a ciama l'apògg d'UTF8 për marcé për da bin.",
        "config-memory-raised": "<code>memory_limit</code> ëd PHP a l'é $1, aussà a $2.",
        "config-memory-bad": "'''Avis:''' <code>memory_limit</code> ëd PHP a l'é $1.\nSossì a l'é probabilment tròp bass.\nL'instalassion a peul falì!",
-       "config-apc": "[https://secure.php.net/apc APC] a l'é instalà",
+       "config-apc": "[https://www.php.net/apc APC] a l'é instalà",
        "config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension WinCache]  a l'é instalà",
        "config-mod-security": "'''Avis''': Sò servent për l'aragnà a l'ha [https://modsecurity.org/ mod_security] abilità. Se mal configurà, a peul causé dij problema për MediaWiki o d'àutri programa ch'a përmëtto a j'utent dë spedì un contnù qualsëssìa.\nCh'a fasa arferiment a la [https://modsecurity.org/documentation/ mod_security documentassion] o ch'a contata l'echip ëd sò servissi s'a-j rivo dj'eror casuaj.",
        "config-diff3-bad": "GNU diff3 pa trovà.",
        "config-oracle-def-ts": "Spassi dla tàula dë stàndard:",
        "config-oracle-temp-ts": "Spassi dla tàula temporani:",
        "config-support-info": "MediaWiki a manten ij sistema ëd base ëd dàit sì-dapress:\n\n$1\n\nS'a vëd pa listà sì-sota ël sistema ëd base ëd dàit ch'a preuva a dovré, antlora va andaré a j'istrussion dl'anliura sì-dzora për abilité ël manteniment.",
-       "config-dbsupport-mysql": "* $1 e l'é l'obietiv primari për MediaWiki e a l'é mej mantnù ([https://secure.php.net/manual/en/mysql.installation.php com compilé PHP con ël manteniment MySQL])",
-       "config-dbsupport-postgres": "* $1 e l'é un sistema ëd base ëd dàit popolar a sorgiss duverta com alternativa a MySQL ([https://secure.php.net/manual/en/pgsql.installation.php com compilé PHP con ël manteniment ëd PostgreSQL]). A peulo ess-ie chèich cit bigat, e a l'é nen arcomandà ëd dovrelo an n'ambient ëd produssion.",
+       "config-dbsupport-mysql": "* $1 e l'é l'obietiv primari për MediaWiki e a l'é mej mantnù ([https://www.php.net/manual/en/mysql.installation.php com compilé PHP con ël manteniment MySQL])",
+       "config-dbsupport-postgres": "* $1 e l'é un sistema ëd base ëd dàit popolar a sorgiss duverta com alternativa a MySQL ([https://www.php.net/manual/en/pgsql.installation.php com compilé PHP con ël manteniment ëd PostgreSQL]). A peulo ess-ie chèich cit bigat, e a l'é nen arcomandà ëd dovrelo an n'ambient ëd produssion.",
        "config-dbsupport-sqlite": "* $1 e l'é un sistema ëd base ëd dàit leger che a l'é motobin bin mantnù ([http://www.php.net/manual/en/pdo.installation.php com compilé PHP con ël manteniment ëd SQLite], a deuvra PDO)",
        "config-dbsupport-oracle": "* $1 a l'é na base ëd dàit comersial për j'amprèise. ([http://www.php.net/manual/en/oci8.installation.php Com compilé PHP con ël manteniment OCI8])",
        "config-header-mysql": "Ampostassion MySQL",
index 840c449..632813c 100644 (file)
@@ -30,7 +30,7 @@
        "config-restart": "هو، سر له نوي يې پيل کړه",
        "config-env-php": "د $1 PHP نصب شو.",
        "config-env-hhvm": "HHVM $1 نصب شو.",
-       "config-apc": "[https://secure.php.net/apc APC] نصب شو",
+       "config-apc": "[https://www.php.net/apc APC] نصب شو",
        "config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension WinCache] نصب شو",
        "config-diff3-bad": "جي ان يو ډيف3 و نه موندل شو.",
        "config-using-server": "د پالنگر نوم \"<nowiki>$1</nowiki>\" کارېږي.",
index 872e67b..2bae20f 100644 (file)
        "config-pcre-no-utf8": "<strong>Erro fatal:</strong> O módulo PCRE do PHP parece ser compilado sem suporte a PCRE_UTF8.\nO MediaWiki requer suporte a UTF-8 para funcionar corretamente.",
        "config-memory-raised": "A configuração <code>memory_limit</code> do PHP era $1; foi aumentada para $2.",
        "config-memory-bad": "<strong>Aviso:</strong> A configuração <code>memory_limit</code> do PHP é $1.\nIsso provavelmente é muito baixo.\nA instalação pode falhar!",
-       "config-apc": "[https://secure.php.net/apc APC] está instalado",
-       "config-apcu": "[https://secure.php.net/apcu APCu] está instalado",
+       "config-apc": "[https://www.php.net/apc APC] está instalado",
+       "config-apcu": "[https://www.php.net/apcu APCu] está instalado",
        "config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension WinCache] está instalado",
-       "config-no-cache-apcu": "<strong>Aviso:</strong> Não foram encontrados o [https://secure.php.net/apcu APCu], ou o [https://www.iis.net/downloads/microsoft/wincache-extension WinCache].\nA cache de objetos não está ativa.",
+       "config-no-cache-apcu": "<strong>Aviso:</strong> Não foram encontrados o [https://www.php.net/manual/pt_BR/book.apcu.php APCu], ou o [https://www.iis.net/downloads/microsoft/wincache-extension WinCache].\nA cache de objetos não está ativa.",
        "config-mod-security": "<strong>Aviso:</strong> Seu servidor web tem [https://modsecurity.org/ mod_security2] habilitado. Muitas configurações comuns de módulo podem causar problemas para o MediaWiki ou outro software que permite aos usuários postar conteúdo arbitrário.\nSe possível, ele dever ser desativad. Consulte a [https://modsecurity.org/documentation/ documentação do mod_security] ou entre em contato com o suporte do seu host se você encontrar erros aleatórios.",
        "config-diff3-bad": "Utilitário de comparação de texto do GNU diff3 não encontrado. Você pode ignorar isso por enquanto, mas pode se deparar com conflitos de edição com mais frequência.",
        "config-git": "Foi encontrado o software de controle de versão Git: <code>$1</code>.",
        "config-type-oracle": "Oracle",
        "config-type-mssql": "Microsoft SQL Server",
        "config-support-info": "O MediaWiki suporta os sistemas de banco de dados a seguir:\n\n$1\n\nSe você não vê o sistema de banco de dados que você está tentando usar listados abaixo, siga as instruções relacionadas acima, para ativar o suporte.",
-       "config-dbsupport-mysql": "* [{{int:version-db-mariadb-url}} MariaDB] é a base de dados preferida para o MediaWiki e a melhor suportada. O MediaWiki também trabalha com [{{int:version-db-mysql-url}} MySQL] e [{{int:version-db-percona-url}} Percona Server], que são compatíveis com MariaDB. ([https://secure.php.net/manual/pt_BR/mysqli.installation.php Como compilar PHP com suporte para MySQL].)",
-       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] é um popular sistema de banco de dados de código aberto como uma alternativa para o MySQL. ([https://secure.php.net/manual/en/pgsql.installation.php Como compilar o PHP com suporte PostgreSQL])",
-       "config-dbsupport-sqlite": "* O [{{int:version-db-sqlite-url}} SQLite] é uma plataforma de base de dados ligeira muito bem suportada. ([https://secure.php.net/manual/en/pdo.installation.php Como compilar PHP com suporte para SQLite], usa PDO.)",
-       "config-dbsupport-oracle": "* A [{{int:version-db-oracle-url}} Oracle] é uma base de dados comercial para empresas. ([https://secure.php.net/manual/en/oci8.installation.php Como compilar PHP com suporte para OCI8].)",
-       "config-dbsupport-mssql": "* O [{{int:version-db-mssql-url}} Microsoft SQL Server] é uma base de dados comercial do Windows para empresas. ([https://secure.php.net/manual/en/sqlsrv.installation.php Como compilar PHP com suporte para SQLSRV].)",
+       "config-dbsupport-mysql": "* [{{int:version-db-mariadb-url}} MariaDB] é a base de dados preferida para o MediaWiki e a melhor suportada. O MediaWiki também trabalha com [{{int:version-db-mysql-url}} MySQL] e [{{int:version-db-percona-url}} Percona Server], que são compatíveis com MariaDB. ([https://www.php.net/manual/pt_BR/mysqli.installation.php Como compilar PHP com suporte para MySQL].)",
+       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] é um popular sistema de banco de dados de código aberto como uma alternativa para o MySQL. ([https://www.php.net/manual/en/pgsql.installation.php Como compilar o PHP com suporte PostgreSQL])",
+       "config-dbsupport-sqlite": "* O [{{int:version-db-sqlite-url}} SQLite] é uma plataforma de base de dados ligeira muito bem suportada. ([https://www.php.net/manual/en/pdo.installation.php Como compilar PHP com suporte para SQLite], usa PDO.)",
+       "config-dbsupport-oracle": "* A [{{int:version-db-oracle-url}} Oracle] é uma base de dados comercial para empresas. ([https://www.php.net/manual/pt_BR/oci8.installation.php Como compilar PHP com suporte para OCI8].)",
+       "config-dbsupport-mssql": "* O [{{int:version-db-mssql-url}} Microsoft SQL Server] é uma base de dados comercial do Windows para empresas. ([https://www.php.net/manual/en/sqlsrv.installation.php Como compilar PHP com suporte para SQLSRV].)",
        "config-header-mysql": "Definições MariaDB/MySQL",
        "config-header-postgres": "Configurações PostgreSQL",
        "config-header-sqlite": "Configurações SQLite",
        "config-license-help": "Muitas wikis públicas colocam todas as contribuições sob uma [https://freedomdefined.org/Definition licença livre].\nIsso ajuda a criar um senso de propriedade da comunidade e incentiva a contribuição de longo prazo.\nGeralmente não é necessário para uma empresa privada ou wiki corporativa.\nSe você quiser poder usar o texto da Wikipédia e quiser que a Wikipédia possa aceitar o texto copiado da sua wiki, você deve escolher <strong>{{int:config-license-cc-by-sa}}</strong>.\n\nA Wikipédia usou anteriormente a Licença de Documentação Livre GNU.\n A GFDL é uma licença válida, mas é difícil de entender.\nTambém é difícil reutilizar conteúdo licenciado sob o GFDL.",
        "config-email-settings": "Configurações de e-mail",
        "config-enable-email": "Ativar envio de e-mail",
-       "config-enable-email-help": "Se você quer que o e-mail funcione, estas [Config-dbsupport-oracle/manual/en/mail.configuration.php configurações de e-mail PHP] precisam ser configuradas corretamente.\nSe você não quiser usar nenhuma das funcionalidades, você pode desabilitá-las aqui.",
+       "config-enable-email-help": "Se você quer que o e-mail funcione, estas [https://www.php.net/manual/pt_BR/mail.configuration.php configurações de e-mail PHP] precisam ser configuradas corretamente.\nSe você não quiser usar nenhuma das funcionalidades, você pode desabilitá-las aqui.",
        "config-email-user": "Ativar e-mails entre usuários",
        "config-email-user-help": "Permitir que todos os usuários enviem e-mail entre si se eles tiverem habilitado este recurso em suas preferências.",
        "config-email-usertalk": "Ativar notificação de alterações em páginas de discussão de usuário",
index f493ef3..8768afc 100644 (file)
        "config-pcre-no-utf8": "'''Erro fatal''': O módulo PCRE do PHP parece ter sido compilado sem suporte PCRE_UTF8.\nO MediaWiki necessita do suporte UTF-8 para funcionar corretamente.",
        "config-memory-raised": "A configuração <code>memory_limit</code> do PHP era $1; foi aumentada para $2.",
        "config-memory-bad": "<strong>Aviso:</strong> A configuração <code>memory_limit</code> do PHP é $1.\nIsto é provavelmente demasiado baixo.\nA instalação poderá falhar!",
-       "config-apc": "[https://secure.php.net/apc APC] instalada",
-       "config-apcu": "[https://secure.php.net/apcu APCu] instalado",
+       "config-apc": "[https://www.php.net/apc APC] instalada",
+       "config-apcu": "[https://www.php.net/apcu APCu] instalado",
        "config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension WinCache] instalada",
-       "config-no-cache-apcu": "<strong>Aviso:</strong> Não foi encontrado o [https://secure.php.net/apcu APCu] nem o [https://www.iis.net/downloads/microsoft/wincache-extension WinCache].\nA cache de objetos não está ativa.",
+       "config-no-cache-apcu": "<strong>Aviso:</strong> Não foi encontrado o [https://www.php.net/apcu APCu] nem o [https://www.iis.net/downloads/microsoft/wincache-extension WinCache].\nA cache de objetos não está ativa.",
        "config-mod-security": "<strong>Aviso:</strong> O seu servidor de Internet tem o [https://modsecurity.org/ mod_security]/mod_security2 ativado. Muitas das suas configurações normais podem causar problemas ao MediaWiki e a outros programas, permitindo que os utilizadores publiquem conteúdos arbitrários.\nSe possível, isto deve ser desativado. Se não, consulte a [https://modsecurity.org/documentation/ mod_security documentação] ou peça apoio ao fornecedor do alojamento do seu servidor se encontrar erros aleatórios.",
        "config-diff3-bad": "O utilitário de comparação de texto GNU diff3 não foi encontrado. Pode ignorar esta situação por agora, mas poderá encontrar conflitos entre edições mais frequentemente.",
        "config-git": "Foi encontrado o software de controlo de versões Git: <code>$1</code>.",
        "config-type-oracle": "Oracle",
        "config-type-mssql": "Microsoft SQL Server",
        "config-support-info": "O MediaWiki suporta as seguintes plataformas de base de dados:\n\n$1\n\nSe a plataforma que pretende usar não está listada abaixo, siga as instruções nas hiperligações acima para ativar o suporte.",
-       "config-dbsupport-mysql": "* [{{int:version-db-mariadb-url}} MariaDB] é a base de dados preferida para o MediaWiki e a melhor suportada. O MediaWiki também trabalha com [{{int:version-db-mysql-url}} MySQL] e [{{int:version-db-percona-url}} Percona Server], que são compatíveis com MariaDB. ([https://secure.php.net/manual/pt_BR/mysqli.installation.php Como compilar PHP com suporte para MySQL].)",
-       "config-dbsupport-postgres": "* O [{{int:version-db-postgres-url}} PostgreSQL] é uma plataforma popular de base de dados de código aberto, alternativa ao MySQL. ([https://secure.php.net/manual/en/pgsql.installation.php Como compilar PHP com suporte para PostgreSQL].)",
-       "config-dbsupport-sqlite": "* O [{{int:version-db-sqlite-url}} SQLite] é uma plataforma de base de dados ligeira muito bem suportada. ([https://secure.php.net/manual/en/pdo.installation.php Como compilar PHP com suporte para SQLite], usa PDO.)",
-       "config-dbsupport-oracle": "* A [{{int:version-db-oracle-url}} Oracle] é uma base de dados comercial para empresas. ([https://secure.php.net/manual/en/oci8.installation.php Como compilar PHP com suporte para OCI8].)",
-       "config-dbsupport-mssql": "* O [{{int:version-db-mssql-url}} Microsoft SQL Server] é uma base de dados comercial do Windows para empresas. ([https://secure.php.net/manual/en/sqlsrv.installation.php Como compilar PHP com suporte para SQLSRV].)",
+       "config-dbsupport-mysql": "* [{{int:version-db-mariadb-url}} MariaDB] é a base de dados preferida para o MediaWiki e a melhor suportada. O MediaWiki também trabalha com [{{int:version-db-mysql-url}} MySQL] e [{{int:version-db-percona-url}} Percona Server], que são compatíveis com MariaDB. ([https://www.php.net/manual/pt_BR/mysqli.installation.php Como compilar PHP com suporte para MySQL].)",
+       "config-dbsupport-postgres": "* O [{{int:version-db-postgres-url}} PostgreSQL] é uma plataforma popular de base de dados de código aberto, alternativa ao MySQL. ([https://www.php.net/manual/en/pgsql.installation.php Como compilar PHP com suporte para PostgreSQL].)",
+       "config-dbsupport-sqlite": "* O [{{int:version-db-sqlite-url}} SQLite] é uma plataforma de base de dados ligeira muito bem suportada. ([https://www.php.net/manual/en/pdo.installation.php Como compilar PHP com suporte para SQLite], usa PDO.)",
+       "config-dbsupport-oracle": "* A [{{int:version-db-oracle-url}} Oracle] é uma base de dados comercial para empresas. ([https://www.php.net/manual/en/oci8.installation.php Como compilar PHP com suporte para OCI8].)",
+       "config-dbsupport-mssql": "* O [{{int:version-db-mssql-url}} Microsoft SQL Server] é uma base de dados comercial do Windows para empresas. ([https://www.php.net/manual/en/sqlsrv.installation.php Como compilar PHP com suporte para SQLSRV].)",
        "config-header-mysql": "Definições MariaDB/MySQL",
        "config-header-postgres": "Definições PostgreSQL",
        "config-header-sqlite": "Definições SQLite",
        "config-license-help": "Muitas wikis de acesso público licenciam todas as colaborações com uma [https://freedomdefined.org/Definition licença livre].\nIsto ajuda a criar um sentido de propriedade da comunidade e encoraja as colaborações a longo prazo.\nTal não é geralmente necessário nas wikis privadas ou corporativas.\n\nSe pretende que seja possível usar textos da Wikipédia na sua wiki e que seja possível a Wikipédia aceitar textos copiados da sua wiki, deve escolher a licença <strong>{{int:config-license-cc-by-sa}}</strong>..\n\nA licença anterior da Wikipédia era a licença GNU Free Documentation License.\nA GFDL é uma licença válida, mas de difícil compreensão.\nTambém é difícil reutilizar conteúdos licenciados com a GFDL.",
        "config-email-settings": "Definições do correio eletrónico",
        "config-enable-email": "Ativar mensagens eletrónicas de saída",
-       "config-enable-email-help": "Se quer que o correio eletrónico funcione, as [https://secure.php.net/manual/en/mail.configuration.php definições de correio eletrónico do PHP] têm de estar configuradas corretamente.\nSe não pretende viabilizar qualquer funcionalidade de correio eletrónico, pode desativá-lo aqui.",
+       "config-enable-email-help": "Se quer que o correio eletrónico funcione, as [https://www.php.net/manual/en/mail.configuration.php definições de correio eletrónico do PHP] têm de estar configuradas corretamente.\nSe não pretende viabilizar qualquer funcionalidade de correio eletrónico, pode desativá-lo aqui.",
        "config-email-user": "Ativar mensagens eletrónicas entre utilizadores",
        "config-email-user-help": "Permitir que todos os utilizadores troquem entre si mensagens de correio eletrónico, se tiverem ativado esta funcionalidade nas suas preferências.",
        "config-email-usertalk": "Ativar notificações de alterações à página de discussão dos utilizadores",
index f6c63e0..dca1df5 100644 (file)
        "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-memory-raised": "PHP<code>memory_limit</code> este $1, mărit cu $2.",
        "config-memory-bad": "<strong>Atenție:</strong> PHP<code>memory_limit</code> este $1.\nAcesta este probabil mai jos.\nAceastă instalare eșuează!",
-       "config-apc": "[https://secure.php.net/apc APC] este instalat",
-       "config-apcu": "[https://secure.php.net/apcu APCu] este instalat",
+       "config-apc": "[https://www.php.net/apc APC] este instalat",
+       "config-apcu": "[https://www.php.net/apcu APCu] este instalat",
        "config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension WinCache] este instalat",
-       "config-no-cache-apcu": "<strong>Atenție:</strong> Nu am putut găsi [https://secure.php.net/apcu APCu] sau [https://www.iis.net/downloads/microsoft/wincache-extension WinCache].\nObiect cache nu este activat.",
+       "config-no-cache-apcu": "<strong>Atenție:</strong> Nu am putut găsi [https://www.php.net/apcu APCu] sau [https://www.iis.net/downloads/microsoft/wincache-extension WinCache].\nObiect cache nu este activat.",
        "config-mod-security": "<strong>Avertisment:</strong> Serverul dvs. web are activat [https://modsecurity.org/ mod_security] / mod_security2. Multe configurații comune vor provoca probleme pentru MediaWiki și alte programe software care permit utilizatorilor să posteze conținuturi arbitrare.\nDacă este posibil, aceasta ar trebui dezactivată. În caz contrar, consultați documentația [https://modsecurity.org/documentation/ mod_security] sau contactați asistența gazdei dvs. dacă întâmpinați erori aleatorii.",
        "config-diff3-bad": "Utilitarul de comparare a textului GNU diff3 nu a fost găsit. Puteți ignora acest lucru pentru moment, dar ar putea apărea mai frecvent conflicte de editare.",
        "config-git": "Am găsit versiunea software de control Git:<code>$1</code>.",
index ccadc13..58cfe86 100644 (file)
        "config-pcre-no-utf8": "'''Фатальная ошибка'''. Модуль PCRE для PHP, похоже, собран без поддержки PCRE_UTF8.\nMediaWiki требует поддержки UTF-8 для корректной работы.",
        "config-memory-raised": "Ограничение на доступную PHP память (<code>memory_limit</code>) поднято с $1 до $2.",
        "config-memory-bad": "'''Внимание:''' размер PHP <code>memory_limit</code> составляет $1.\nВероятно, этого слишком мало.\nУстановка может потерпеть неудачу!",
-       "config-apc": "[https://secure.php.net/apc APC] установлен",
-       "config-apcu": "[https://secure.php.net/apcu APCu] установлен",
+       "config-apc": "[https://www.php.net/apc APC] установлен",
+       "config-apcu": "[https://www.php.net/apcu APCu] установлен",
        "config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension WinCache] установлен",
-       "config-no-cache-apcu": "<strong>Внимание:</strong> Не найдены [https://secure.php.net/apcu APCu] или [https://www.iis.net/downloads/microsoft/wincache-extension WinCache].\nКэширование объектов будет отключено.",
+       "config-no-cache-apcu": "<strong>Внимание:</strong> Не найдены [https://www.php.net/apcu APCu] или [https://www.iis.net/downloads/microsoft/wincache-extension WinCache].\nКэширование объектов будет отключено.",
        "config-mod-security": "<strong>Внимание</strong>: На вашем веб-сервере включён [https://modsecurity.org/ mod_security]/mod_security2. Многие его стандартные настройки могут вызывать проблемы для MediaWiki или другого ПО, позволяющего пользователям отправлять на сервер произвольный контент.\nПо возможности он должен быть отключён. Обратитесь к [https://modsecurity.org/documentation/ документации mod_security] или в службу поддержки вашего хостинг-провайдера, если вы сталкиваетесь со случайными ошибками.",
        "config-diff3-bad": "Утилита для сравнения GNU diff3 не найдена. Вы можете пока это проигнорировать, но, скорее всего, вы будете видеть эту ошибку вновь и вновь при конфликтах редактирования.",
        "config-git": "Найдена система контроля версий Git: <code>$1</code>.",
        "config-type-oracle": "Oracle",
        "config-type-mssql": "Microsoft SQL Server",
        "config-support-info": "MediaWiki поддерживает следующие СУБД:\n\n$1\n\nЕсли вы не видите своей системы хранения данных в этом списке, следуйте инструкциям, на которые есть ссылка выше, чтобы получить поддержку.",
-       "config-dbsupport-mysql": "* [{{int:version-db-mysql-url}} MySQL] — основная база данных для MediaWiki, которая поддерживается лучше всего. MediaWiki также работает с [{{int:version-db-mariadb-url}} MariaDB] и [{{int:version-db-percona-url}} Percona Server], которые являются MariaDB-совместимыми. (См.[https://secure.php.net/manual/ru/mysql.installation.php Как собрать PHP с поддержкой MySQL])",
-       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] — популярная СУБД с открытым исходным кодом, альтернатива MySQL. ([https://secure.php.net/manual/ru/pgsql.installation.php Как собрать PHP с поддержкой PostgreSQL]).",
-       "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite] — это легковесная система баз данных, имеющая очень хорошую поддержку. ([https://secure.php.net/manual/ru/pdo.installation.php Как собрать PHP с поддержкой SQLite], работающей посредством PDO)",
-       "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] — это коммерческая корпоративная база данных. ([https://secure.php.net/manual/ru/oci8.installation.php Как собрать PHP с поддержкой OCI8])",
-       "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] — это коммерческая корпоративная база данных для Windows. ([https://secure.php.net/manual/ru/sqlsrv.installation.php Как собрать PHP с поддержкой SQLSRV])",
+       "config-dbsupport-mysql": "* [{{int:version-db-mysql-url}} MySQL] — основная база данных для MediaWiki, которая поддерживается лучше всего. MediaWiki также работает с [{{int:version-db-mariadb-url}} MariaDB] и [{{int:version-db-percona-url}} Percona Server], которые являются MariaDB-совместимыми. (См.[https://www.php.net/manual/ru/mysql.installation.php Как собрать PHP с поддержкой MySQL])",
+       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] — популярная СУБД с открытым исходным кодом, альтернатива MySQL. ([https://www.php.net/manual/ru/pgsql.installation.php Как собрать PHP с поддержкой PostgreSQL]).",
+       "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite] — это легковесная система баз данных, имеющая очень хорошую поддержку. ([https://www.php.net/manual/ru/pdo.installation.php Как собрать PHP с поддержкой SQLite], работающей посредством PDO)",
+       "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] — это коммерческая корпоративная база данных. ([https://www.php.net/manual/ru/oci8.installation.php Как собрать PHP с поддержкой OCI8])",
+       "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] — это коммерческая корпоративная база данных для Windows. ([https://www.php.net/manual/ru/sqlsrv.installation.php Как собрать PHP с поддержкой SQLSRV])",
        "config-header-mysql": "Настройки MariaDB/MySQL",
        "config-header-postgres": "Настройки PostgreSQL",
        "config-header-sqlite": "Настройки SQLite",
        "config-license-help": "Многие общедоступные вики разрешают использовать свои материалы на условиях [https://freedomdefined.org/Definition/Ru свободных лицензий].\nЭто помогает созданию чувства общности, стимулирует долгосрочное участие.\nНо в этом нет необходимости для частных или корпоративных вики.\n\nЕсли вы хотите использовать тексты из Википедии или хотите, что в Википедию можно было копировать тексты из вашей вики, вам следует выбрать <strong>{{int:config-license-cc-by-sa}}</strong>.\n\nВикипедия ранее использовала лицензию GNU Free Documentation License.\nGFDL может быть использована, но она сложна для понимания и осложняет повторное использование материалов.",
        "config-email-settings": "Настройки электронной почты",
        "config-enable-email": "Включить исходящие e-mail",
-       "config-enable-email-help": "Если вы хотите, чтобы электронная почта работала, необходимо правильно настроить [https://secure.php.net/manual/ru/mail.configuration.php работу почты в PHP].\nЕсли вы не хотите использовать возможности электронной почты, вы можете её здесь отключить.",
+       "config-enable-email-help": "Если вы хотите, чтобы электронная почта работала, необходимо правильно настроить [https://www.php.net/manual/ru/mail.configuration.php работу почты в PHP].\nЕсли вы не хотите использовать возможности электронной почты, вы можете её здесь отключить.",
        "config-email-user": "Включить электронную почту от участника к участнику",
        "config-email-user-help": "Разрешить всем пользователям отправлять друг другу электронные письма, если выставлена соответствующая настройка в профиле.",
        "config-email-usertalk": "Включить уведомления пользователей о сообщениях на их странице обсуждения",
index 68d40d5..a8bada1 100644 (file)
@@ -61,9 +61,9 @@
        "config-pcre-no-utf8": "<strong>Fatal:</strong> PHP's PCRE module seems tae be compiled wioot PCRE_UTF8 support.\nMediaWiki requires UTF-8 support tae function correctly.",
        "config-memory-raised": "PHP's <code>memerie_limit</code> is $1, raised til $2.",
        "config-memory-bad": "<strong>Warnishment:</strong> PHP's <code>memerie_limit</code> is $1.\nThis is proably ower low.\nThe installation micht fail!",
-       "config-apc": "[https://secure.php.net/apc APC] is installed.",
+       "config-apc": "[https://www.php.net/apc APC] is installed.",
        "config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension WinCache] is instawed.",
-       "config-no-cache-apcu": "<strong>Wairnin:</strong> Could nae find [https://secure.php.net/apcu APCu], [http://xcache.lighttpd.net/ XCache] or [https://www.iis.net/downloads/microsoft/wincache-extension WinCache].\nObject cachin isna enabled.",
+       "config-no-cache-apcu": "<strong>Wairnin:</strong> Could nae find [https://www.php.net/apcu APCu], [http://xcache.lighttpd.net/ XCache] or [https://www.iis.net/downloads/microsoft/wincache-extension WinCache].\nObject cachin isna enabled.",
        "config-mod-security": "<strong>Warnishment:</strong> Yer wab server haes [https://modsecurity.org/ mod_security] enabled. Gif misconfeegured, it can cause problems fer MediaWiki or ither saffware that allous uisers tae post arbitrie content.\nRefer til [https://modsecurity.org/documentation/ mod_security documentation] or contact yer host's support gif ye encounter random mistaks.",
        "config-diff3-bad": "GNU diff3 naw foond.",
        "config-git": "Foond the Git version control saffware: <code>$1</code>.",
        "config-type-mysql": "MaSQL (or compâtible)",
        "config-type-mssql": "Micræsaff SQL Server",
        "config-support-info": "MediaWiki supports the follaein database systems:\n\n$1\n\nGif ye dinna see the database system ye'r tryin tae uise listed ablow, than follae the instructions linked abuin tae enable support.",
-       "config-dbsupport-mysql": "* [{{int:version-db-mysql-url}} MySQL] is the primarie tairget fer MediaWiki n is best supported. MediaWiki warks forby wi [{{int:version-db-mariadb-url}} MariaDB] n [{{int:version-db-percona-url}} Percona Server], thir ar MySQL compatible. ([https://secure.php.net/manual/en/mysqli.installation.php Hou tae compile PHP wi MySQL support])",
-       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] is ae popular apen soorce database system aes aen alternative til MySQL. Thaur micht be some wee bugs still hingin roond, n it's na recommendit fer uiss in ae production environment. ([https://secure.php.net/manual/en/pgsql.installation.php Hou tae compile PHP wi PostgreSQL support])",
+       "config-dbsupport-mysql": "* [{{int:version-db-mysql-url}} MySQL] is the primarie tairget fer MediaWiki n is best supported. MediaWiki warks forby wi [{{int:version-db-mariadb-url}} MariaDB] n [{{int:version-db-percona-url}} Percona Server], thir ar MySQL compatible. ([https://www.php.net/manual/en/mysqli.installation.php Hou tae compile PHP wi MySQL support])",
+       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] is ae popular apen soorce database system aes aen alternative til MySQL. Thaur micht be some wee bugs still hingin roond, n it's na recommendit fer uiss in ae production environment. ([https://www.php.net/manual/en/pgsql.installation.php Hou tae compile PHP wi PostgreSQL support])",
        "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite] is ae lichtweicht database system that is ver weel supportit. ([http://www.php.net/manual/en/pdo.installation.php Hou tae compile PHP wi SQLite support], uises PDO)",
        "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] is ae commercial enterprise database. ([http://www.php.net/manual/en/oci8.installation.php Hou tae compile PHP wi OCI8 support])",
-       "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] is ae commercial enterprise database fer Windows. ([https://secure.php.net/manual/en/sqlsrv.installation.php Hou tae compile PHP wi SQLSRV support])",
+       "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] is ae commercial enterprise database fer Windows. ([https://www.php.net/manual/en/sqlsrv.installation.php Hou tae compile PHP wi SQLSRV support])",
        "config-header-mysql": "MaSQL settins",
        "config-header-postgres": "PostgreSQL settins",
        "config-header-sqlite": "SQLite settins",
index 37ea7bb..e884770 100644 (file)
        "config-pcre-no-utf8": "<strong>Kobno</strong>: PCRE modul PHP-a je hitan bez podrške za PCRE_UTF8.\nMediaWiki zahtijeva podršku za UTF-8 kako bi ispravno funkcionirao.",
        "config-memory-raised": "<code>memory_limit</code> za PHP iznosi $1, povišen na $2.",
        "config-memory-bad": "<strong>Upozorenje:</strong> <code>memory_limit</code> za PHP iznosi $1.\nOvo je vjerovatno premalo.\nUspostava možda neće uspjeti!",
-       "config-apc": "[https://secure.php.net/apc APC] je uspostavljen",
-       "config-apcu": "[https://secure.php.net/apcu APCu] je uspostavljen",
+       "config-apc": "[https://www.php.net/apc APC] je uspostavljen",
+       "config-apcu": "[https://www.php.net/apcu APCu] je uspostavljen",
        "config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension WinCache] je uspostavljen",
-       "config-no-cache-apcu": "<strong>Upozorenje:</strong> Nisam mogao naći [https://secure.php.net/apcu APCu] ili [https://www.iis.net/downloads/microsoft/wincache-extension WinCache].",
+       "config-no-cache-apcu": "<strong>Upozorenje:</strong> Nisam mogao naći [https://www.php.net/apcu APCu] ili [https://www.iis.net/downloads/microsoft/wincache-extension WinCache].",
        "config-mod-security": "'''Upozorenje''': na vašem poslužitelju ima omogućeno [https://modsecurity.org/ mod_security]. Ako nije postavljeno kako treba, to može uzrokovati probleme s programom MediaWikija i drugim programima koji korisnicima omogućuju objavljivanje proizvoljnog sadržaja.\nPogledajte [https://modsecurity.org/documentation/ mod_security dokumentaciju] ili kontaktirajte domaćin ako naiđete na slučajne greške.",
        "config-diff3-bad": "Nisam pronašao usporednik za tekst GNU razl3 (GNU diff3). Za sada ovo možete zanemariti, ali kontradikcije u uređivanju mogu postati češće.",
        "config-git": "Pronašao sam Git program za kontrolu verzija: <code>$1</code>.",
        "config-type-mysql": "MariaDB, MySQL ili kompatibilan",
        "config-type-mssql": "Microsoft SQL Server",
        "config-support-info": "MediaWiki podržava sljedeće sustave baza podataka:\n\n$1\n\nAko sustav koji želite koristiti nije naveden u nastavku, slijedite vezu gore navedenih uputa kako biste omogućili podršku za taj sustav.",
-       "config-dbsupport-mysql": "* [{{int:version-db-mariadb-url}} MariaDB] je glavna meta MediaWikija i najbolje je podržan. MediaWiki također radi sa [{{int:version-db-mysql-url}} MySQL-om] i [{{int:version-db-percona-url}} Percona], koji su skladni sa MariaDB-om. ([https://secure.php.net/manual/en/mysqli.installation.php Kako kompajlirati PHP sa podrškom MySQL-a])",
-       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] je popularan sistem baza podataka otvorenog koda koji predstavlja alternativu MySQL-u. ([https://secure.php.net/manual/en/pgsql.installation.php Kako kompajlirati PHP sa podrškom PostgreSQL-a])",
-       "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite] je lagan sistem baze podataka koji je veoma dobro podržan. ([https://secure.php.net/manual/en/pdo.installation.php Kako kompajlirati PHP sa podrškom SQLite-a], koristi PDO)",
-       "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] je baza podataka komercijalnih preduzeća. ([https://secure.php.net/manual/en/oci8.installation.php Kako kompajlirati PHP sa podrškom OCI8-a])",
-       "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] je baza podataka komercijalnih preduzeća za Windows. ([https://secure.php.net/manual/en/sqlsrv.installation.php Kako kompajlirati PHP sa podrškom SQLSRV-a])",
+       "config-dbsupport-mysql": "* [{{int:version-db-mariadb-url}} MariaDB] je glavna meta MediaWikija i najbolje je podržan. MediaWiki također radi sa [{{int:version-db-mysql-url}} MySQL-om] i [{{int:version-db-percona-url}} Percona], koji su skladni sa MariaDB-om. ([https://www.php.net/manual/en/mysqli.installation.php Kako kompajlirati PHP sa podrškom MySQL-a])",
+       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] je popularan sistem baza podataka otvorenog koda koji predstavlja alternativu MySQL-u. ([https://www.php.net/manual/en/pgsql.installation.php Kako kompajlirati PHP sa podrškom PostgreSQL-a])",
+       "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite] je lagan sistem baze podataka koji je veoma dobro podržan. ([https://www.php.net/manual/en/pdo.installation.php Kako kompajlirati PHP sa podrškom SQLite-a], koristi PDO)",
+       "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] je baza podataka komercijalnih preduzeća. ([https://www.php.net/manual/en/oci8.installation.php Kako kompajlirati PHP sa podrškom OCI8-a])",
+       "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] je baza podataka komercijalnih preduzeća za Windows. ([https://www.php.net/manual/en/sqlsrv.installation.php Kako kompajlirati PHP sa podrškom SQLSRV-a])",
        "config-header-mysql": "Podešavanja MariaDB/MySQL-a",
        "config-header-postgres": "Podešavanja PostgreSQL-a",
        "config-header-sqlite": "Podešavanja SQLite-a",
index 97288bb..5471fdb 100644 (file)
@@ -52,7 +52,7 @@
        "config-env-hhvm": "HHVM $1 je nameščen.",
        "config-unicode-using-intl": "Uporaba [https://pecl.php.net/intl razširitve PECL intl] za normalizacijo unikoda.",
        "config-memory-raised": "PHP-jev <code>memory_limit</code> je $1, dvignjen na $2.",
-       "config-apc": "[https://secure.php.net/apc APC] je nameščen",
+       "config-apc": "[https://www.php.net/apc APC] je nameščen",
        "config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension WinCache] je nameščen",
        "config-diff3-bad": "Pripomočka za primerjavo besedila GNU diff3 nismo našli. To lahko zaenkrat prezrete, ampak morda boste pogosteje naleteli na spore pri urejanju.",
        "config-using-server": "Uporabljam ime strežnika \"<nowiki>$1</nowiki>\".",
index 6cac5a6..89c4780 100644 (file)
        "config-pcre-no-utf8": "<strong>Неотклоњива грешка:</strong> Изгледа да је PCRE модул PHP-а  компајлиран без PCRE_UTF8 подршке.\nMediaWiki захтева UTF-8 подршку за исправно функционисање.",
        "config-memory-raised": "Вредност параметра <code>memory_limit</code> PHP-а износи $1, подигнута на $2.",
        "config-memory-bad": "<strong>Упозорење:</strong> Вредност параметра <code>memory_limit</code> PHP-а износи $1.\nОво је вероватно прениско.\nИнсталација можда неће успети!",
-       "config-apc": "[https://secure.php.net/apc APC] је инсталиран",
-       "config-apcu": "[https://secure.php.net/apcu APCu] је инсталиран",
+       "config-apc": "[https://www.php.net/apc APC] је инсталиран",
+       "config-apcu": "[https://www.php.net/apcu APCu] је инсталиран",
        "config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension WinCache] је инсталиран",
-       "config-no-cache-apcu": "<strong>Упозорење:</strong> Није могуће пронаћи [https://secure.php.net/apcu APCu] или [https://www.iis.net/downloads/microsoft/wincache-extension WinCache].\nКеширање објеката није омогућено.",
+       "config-no-cache-apcu": "<strong>Упозорење:</strong> Није могуће пронаћи [https://www.php.net/apcu APCu] или [https://www.iis.net/downloads/microsoft/wincache-extension WinCache].\nКеширање објеката није омогућено.",
        "config-diff3-bad": "GNU diff3 није пронађен.",
        "config-git": "Пронађен је Git софтвер за контролу верзија: <code>$1</code>",
        "config-git-bad": "Није пронађен Git софтвер за контролу верзија.",
        "config-type-oracle": "Oracle",
        "config-type-mssql": "Microsoft SQL Server",
        "config-support-info": "MediaWiki подржава следеће системе база података:\n\n$1\n\nАко не видите систем који покушавате да користите на листи испод, онда пратите повезана упутства изнад како бисте омогућили подршку.",
-       "config-dbsupport-mysql": "* [{{int:version-db-mariadb-url}} MariaDB] је примарна мета за MediaWiki и најбоље је подржана. MediaWiki такође ради са [{{int:version-db-mysql-url}} MySQL-ом] и [{{int:version-db-percona-url}} Percona Server-ом], који су компатибилни са MariaDB-ом. ([https://secure.php.net/manual/en/mysqli.installation.php Како компајлирати PHP са подршком MySQL-а])",
-       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] је популаран систем база података отвореног кода кaо алтернатива MySQL-у. ([https://secure.php.net/manual/en/pgsql.installation.php Како компајлирати PHP са подршком PostgreSQL-а])",
-       "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite] је лаган систем базе података који је веома добро подржан. ([https://secure.php.net/manual/en/pdo.installation.php Како компајлирати PHP са подршком SQLite-а], користи PDO)",
-       "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] је база података комерцијалних предузећа. ([https://secure.php.net/manual/en/oci8.installation.php Како компајлирати PHP са подршком OCI8-а])",
-       "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] је база података комерцијалних предузећа за Windows. ([https://secure.php.net/manual/en/sqlsrv.installation.php Како компајлирати PHP са подршком SQLSRV-а])",
+       "config-dbsupport-mysql": "* [{{int:version-db-mariadb-url}} MariaDB] је примарна мета за MediaWiki и најбоље је подржана. MediaWiki такође ради са [{{int:version-db-mysql-url}} MySQL-ом] и [{{int:version-db-percona-url}} Percona Server-ом], који су компатибилни са MariaDB-ом. ([https://www.php.net/manual/en/mysqli.installation.php Како компајлирати PHP са подршком MySQL-а])",
+       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] је популаран систем база података отвореног кода кaо алтернатива MySQL-у. ([https://www.php.net/manual/en/pgsql.installation.php Како компајлирати PHP са подршком PostgreSQL-а])",
+       "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite] је лаган систем базе података који је веома добро подржан. ([https://www.php.net/manual/en/pdo.installation.php Како компајлирати PHP са подршком SQLite-а], користи PDO)",
+       "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] је база података комерцијалних предузећа. ([https://www.php.net/manual/en/oci8.installation.php Како компајлирати PHP са подршком OCI8-а])",
+       "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] је база података комерцијалних предузећа за Windows. ([https://www.php.net/manual/en/sqlsrv.installation.php Како компајлирати PHP са подршком SQLSRV-а])",
        "config-header-mysql": "Подешавања MariaDB/MySQL-а",
        "config-header-postgres": "Подешавања PostgreSQL-а",
        "config-header-sqlite": "Подешавања SQLite-а",
index 3415608..0b31bca 100644 (file)
        "config-pcre-no-utf8": "<strong>Neotklonjiva greška:</strong> Izgleda da je PCRE modul PHP-a  kompajliran bez PCRE_UTF8 podrške.\nMediaWiki zahteva UTF-8 podršku za ispravno funkcionisanje.",
        "config-memory-raised": "Vrednost parametra <code>memory_limit</code> PHP-a iznosi $1, podignuta na $2.",
        "config-memory-bad": "<strong>Upozorenje:</strong> Vrednost parametra <code>memory_limit</code> PHP-a iznosi $1.\nOvo je verovatno prenisko.\nInstalacija možda neće uspeti!",
-       "config-apc": "[https://secure.php.net/apc APC] je instaliran",
-       "config-apcu": "[https://secure.php.net/apcu APCu] je instaliran",
+       "config-apc": "[https://www.php.net/apc APC] je instaliran",
+       "config-apcu": "[https://www.php.net/apcu APCu] je instaliran",
        "config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension WinCache] je instaliran",
-       "config-no-cache-apcu": "<strong>Upozorenje:</strong> Nije moguće pronaći [https://secure.php.net/apcu APCu] ili [https://www.iis.net/downloads/microsoft/wincache-extension WinCache].\nKeširanje objekata nije omogućeno.",
+       "config-no-cache-apcu": "<strong>Upozorenje:</strong> Nije moguće pronaći [https://www.php.net/apcu APCu] ili [https://www.iis.net/downloads/microsoft/wincache-extension WinCache].\nKeširanje objekata nije omogućeno.",
        "config-diff3-bad": "GNU diff3 nije pronađen.",
        "config-git": "Pronađen je Git softver za kontrolu verzija: <code>$1</code>",
        "config-git-bad": "Nije pronađen Git softver za kontrolu verzija.",
        "config-type-oracle": "Oracle",
        "config-type-mssql": "Microsoft SQL Server",
        "config-support-info": "MediaWiki podržava sledeće sisteme baza podataka:\n\n$1\n\nAko ne vidite sistem koji pokušavate da koristite na listi ispod, onda pratite povezana uputstva iznad kako biste omogućili podršku.",
-       "config-dbsupport-mysql": "* [{{int:version-db-mariadb-url}} MariaDB] je primarna meta za MediaWiki i najbolje je podržana. MediaWiki takođe radi sa [{{int:version-db-mysql-url}} MySQL-om] i [{{int:version-db-percona-url}} Percona Server-om], koji su kompatibilni sa MariaDB-om. ([https://secure.php.net/manual/en/mysqli.installation.php Kako kompajlirati PHP sa podrškom MySQL-a])",
-       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] je popularan sistem baza podataka otvorenog koda kao alternativa MySQL-u. ([https://secure.php.net/manual/en/pgsql.installation.php Kako kompajlirati PHP sa podrškom PostgreSQL-a])",
-       "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite] je lagan sistem baze podataka koji je veoma dobro podržan. ([https://secure.php.net/manual/en/pdo.installation.php Kako kompajlirati PHP sa podrškom SQLite-a], koristi PDO)",
-       "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] je baza podataka komercijalnih preduzeća. ([https://secure.php.net/manual/en/oci8.installation.php Kako kompajlirati PHP sa podrškom OCI8-a])",
-       "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] je baza podataka komercijalnih preduzeća za Windows. ([https://secure.php.net/manual/en/sqlsrv.installation.php Kako kompajlirati PHP sa podrškom SQLSRV-a])",
+       "config-dbsupport-mysql": "* [{{int:version-db-mariadb-url}} MariaDB] je primarna meta za MediaWiki i najbolje je podržana. MediaWiki takođe radi sa [{{int:version-db-mysql-url}} MySQL-om] i [{{int:version-db-percona-url}} Percona Server-om], koji su kompatibilni sa MariaDB-om. ([https://www.php.net/manual/en/mysqli.installation.php Kako kompajlirati PHP sa podrškom MySQL-a])",
+       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] je popularan sistem baza podataka otvorenog koda kao alternativa MySQL-u. ([https://www.php.net/manual/en/pgsql.installation.php Kako kompajlirati PHP sa podrškom PostgreSQL-a])",
+       "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite] je lagan sistem baze podataka koji je veoma dobro podržan. ([https://www.php.net/manual/en/pdo.installation.php Kako kompajlirati PHP sa podrškom SQLite-a], koristi PDO)",
+       "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] je baza podataka komercijalnih preduzeća. ([https://www.php.net/manual/en/oci8.installation.php Kako kompajlirati PHP sa podrškom OCI8-a])",
+       "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] je baza podataka komercijalnih preduzeća za Windows. ([https://www.php.net/manual/en/sqlsrv.installation.php Kako kompajlirati PHP sa podrškom SQLSRV-a])",
        "config-header-mysql": "Podešavanja MariaDB/MySQL-a",
        "config-header-postgres": "Podešavanja PostgreSQL-a",
        "config-header-sqlite": "Podešavanja SQLite-a",
index d0b5e14..e2cb99e 100644 (file)
        "config-pcre-no-utf8": "'''Kritiskt:''' PHP:s PCRE-modul verkar vara kompilerat utan PCRE_UTF8-stöd.\nMediaWiki kräver stöd för UTF-8 för att fungera korrekt.",
        "config-memory-raised": "PHPs <code>memory_limit</code> är $1, ökad till $2.",
        "config-memory-bad": "''' Varning:''' PHP:s <code>memory_limit</code> är $1.\nDetta är förmodligen för lågt.\nInstallationen kan misslyckas!",
-       "config-apc": "[https://secure.php.net/apc APC] är installerat",
-       "config-apcu": "[https://secure.php.net/apcu APCu] är installerat",
+       "config-apc": "[https://www.php.net/apc APC] är installerat",
+       "config-apcu": "[https://www.php.net/apcu APCu] är installerat",
        "config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension WinCache] är installerat",
-       "config-no-cache-apcu": "<strong>Varning:</strong> Kunde inte hitta [https://secure.php.net/apcu APCu] eller [https://www.iis.net/downloads/microsoft/wincache-extension WinCache].\nCachelagring av objekt är inte aktiverat.",
+       "config-no-cache-apcu": "<strong>Varning:</strong> Kunde inte hitta [https://www.php.net/apcu APCu] eller [https://www.iis.net/downloads/microsoft/wincache-extension WinCache].\nCachelagring av objekt är inte aktiverat.",
        "config-mod-security": "'''Varning:''' Din webbserver har [https://modsecurity.org/ mod_security] aktiverat. Om felaktigt konfigurerat kan den skapa problem för MediaWiki eller annan programvara som tillåter användaren att posta godtyckligt innehåll.\nTitta på [https://modsecurity.org/documentation/ mod_security-dokumentationen] eller kontakta din värd om du påträffar slumpmässiga fel.",
        "config-diff3-bad": "Inget verktyg för att jämföra GNU diff3-text hittades. Du kan ignorera detta för tillfället, men då kan du stöta på redigeringskonflikter mer frekvent.",
        "config-git": "Hittade Git-mjukvara för versionskontroll: <code>$1</code>.",
        "config-type-mysql": "MariaDB, MySQL eller kompatibelt",
        "config-type-mssql": "Microsoft SQL Server",
        "config-support-info": "MediaWiki stöder följande databassystem:\n\n$1\n\nOm du inte ser det databassystem som du försöker använda nedanstående, följ då instruktionerna länkade ovan för aktivera stöd för det.",
-       "config-dbsupport-mysql": "* [{{int:version-db-mariadb-url}} MariaDB] är det primära målet för MediaWiki och stöds bäst. MediaWiki fungerar även med [{{int:version-db-mysql-url}} MySQL] och [{{int:version-db-percona-url}} Percona Server], som är kompatibla med MariaDB. ([https://secure.php.net/manual/en/mysqli.installation.php Hur man kompilerar PHP med stöd för MySQL])",
-       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] är ett populärt databassystem med öppen källkod som ett alternativ till MySQL. ([https://secure.php.net/manual/en/pgsql.installation.php Hur man kompilerar PHP med PostgreSQL-stöd])",
-       "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite] är en lättviktsdatabassystem med väldigt bra stöd. ([https://secure.php.net/manual/en/pdo.installation.php Hur man kompilerar PHP med SQLite stöd], använder PDO)",
-       "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] är en kommersiellt databas för företag. ([https://secure.php.net/manual/en/oci8.installation.php Hur man kompilerar PHP med OCI8 stöd])",
-       "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] är en kommersiellt databas för företag för Windows. ([https://secure.php.net/manual/en/sqlsrv.installation.php Hur man kompilerar PHP med SQLSRV stöd])",
+       "config-dbsupport-mysql": "* [{{int:version-db-mariadb-url}} MariaDB] är det primära målet för MediaWiki och stöds bäst. MediaWiki fungerar även med [{{int:version-db-mysql-url}} MySQL] och [{{int:version-db-percona-url}} Percona Server], som är kompatibla med MariaDB. ([https://www.php.net/manual/en/mysqli.installation.php Hur man kompilerar PHP med stöd för MySQL])",
+       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] är ett populärt databassystem med öppen källkod som ett alternativ till MySQL. ([https://www.php.net/manual/en/pgsql.installation.php Hur man kompilerar PHP med PostgreSQL-stöd])",
+       "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite] är en lättviktsdatabassystem med väldigt bra stöd. ([https://www.php.net/manual/en/pdo.installation.php Hur man kompilerar PHP med SQLite stöd], använder PDO)",
+       "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] är en kommersiellt databas för företag. ([https://www.php.net/manual/en/oci8.installation.php Hur man kompilerar PHP med OCI8 stöd])",
+       "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] är en kommersiellt databas för företag för Windows. ([https://www.php.net/manual/en/sqlsrv.installation.php Hur man kompilerar PHP med SQLSRV stöd])",
        "config-header-mysql": "MariaDB/MySQL-inställningar",
        "config-header-postgres": "PostgreSQL-inställningar",
        "config-header-sqlite": "SQLite-inställningar",
index dd4bd0c..2fce89e 100644 (file)
@@ -53,7 +53,7 @@
        "config-outdated-sqlite": "<strong>హెచ్చరిక:</strong> మీ వద్ద SQLite $1 ఉంది. అదికావలసిన వెర్షను $2 కంటే దిగువది. SQLite అందుబాటులో ఉండదు.",
        "config-memory-raised": "PHP యొక్క <code>memory_limit</code> $1, దాన్ని $2 కి పెంచాం.",
        "config-memory-bad": "<strong>హెచ్చరిక:</strong> PHP యొక్క <code>memory_limit</code> $1.\nబహుశా ఇది మరీ తక్కువ.\nస్థాపన విఫలం కావచ్చు!",
-       "config-apc": "[https://secure.php.net/apc APC] స్థాపించబడింది",
+       "config-apc": "[https://www.php.net/apc APC] స్థాపించబడింది",
        "config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension WinCache] స్థాపించబడింది",
        "config-diff3-bad": "GNU diff3 కనబడలేదు.",
        "config-no-uri": "<strong>లోపం:</strong> ప్రస్తుత URI ఏమిటో నిర్ధారించలేకపోయాం.\nస్థాపన ఆగిపోయింది.",
@@ -89,7 +89,7 @@
        "config-type-mysql": "MySQL (లేదా సరిపోయేది)",
        "config-type-mssql": "Microsoft SQL Server",
        "config-support-info": "MediaWiki కింది డేటాబేసు వ్యవస్థలకు అనుకూలిస్తుంది:\n\n$1\n\nమీరు వాడదలచిన డేటాబేసు వ్యవస్ కింది జాబితాలో లేకపోతే, పైన లింకు ద్వారా ఇచ్చిన సూచనలను పాటించి, అనుకూలతలను సాధించండి.",
-       "config-dbsupport-postgres": "* MySQL కు ప్రత్యామ్నాయంగా [{{int:version-db-postgres-url}} PostgreSQL] ప్రజామోదం పొందిన ఓపెన్‍సోర్సు డేటాబేసు వ్యవస్థ. దానిలో చిన్న చితకా లోపాలుండే అవకాశం ఉంది. అందుచేత దాన్ని ఉత్పాదక రంగంలో వాడవచ్చని చెప్పలేం.  ([https://secure.php.net/manual/en/pgsql.installation.php How to compile PHP with PostgreSQL support])",
+       "config-dbsupport-postgres": "* MySQL కు ప్రత్యామ్నాయంగా [{{int:version-db-postgres-url}} PostgreSQL] ప్రజామోదం పొందిన ఓపెన్‍సోర్సు డేటాబేసు వ్యవస్థ. దానిలో చిన్న చితకా లోపాలుండే అవకాశం ఉంది. అందుచేత దాన్ని ఉత్పాదక రంగంలో వాడవచ్చని చెప్పలేం.  ([https://www.php.net/manual/en/pgsql.installation.php How to compile PHP with PostgreSQL support])",
        "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite] ఓ తేలికైన డేటాబేసు వ్యవస్థ. దానికి చక్కటి అనుకూలతలున్నాయి. ([http://www.php.net/manual/en/pdo.installation.php How to compile PHP with SQLite support], uses PDO)",
        "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] ఒక వాణిజ్యపరంగా సంస్థాగతంగా వాడదగ్గ డేటాబేసు. ([http://www.php.net/manual/en/oci8.installation.php How to compile PHP with OCI8 support])",
        "config-header-mysql": "MySQL అమరికలు",
index 115f4e6..a4e2ac0 100644 (file)
        "config-pcre-no-utf8": "<strong>ข้อผิดพลาดร้ายแรง:</strong> โมดูล PCRE ของ PHP ดูเหมือนจะถูกคอมไพล์โดยไม่มีการรองรับ PCRE_UTF8\nMediaWiki ต้องการการรองรับ UTF-8 เพื่อให้ทำงานได้อย่างถูกต้อง",
        "config-memory-raised": "<code>memory_limit</code> ของ PHP คือ $1 ได้เพิ่มเป็น $2",
        "config-memory-bad": "<strong>คำเตือน:</strong> <code>memory_limit</code> ของ PHP คือ $1.\nเป็นไปได้ว่ามันอาจต่ำเกินไป\nการติดตั้งอาจล้มเหลวได้!",
-       "config-apc": "มี [https://secure.php.net/apc APC] ติดตั้งอยู่",
-       "config-apcu": "มี [https://secure.php.net/apcu APCu] ติดตั้งอยู่",
+       "config-apc": "มี [https://www.php.net/apc APC] ติดตั้งอยู่",
+       "config-apcu": "มี [https://www.php.net/apcu APCu] ติดตั้งอยู่",
        "config-wincache": "มี [https://www.iis.net/downloads/microsoft/wincache-extension WinCache] ติดตั้งอยู่",
-       "config-no-cache-apcu": "<strong>คำเตือน:</strong> ไม่พบ [https://secure.php.net/apcu APCu] [http://xcache.lighttpd.net/ XCache] หรือ [https://www.iis.net/downloads/microsoft/wincache-extension WinCache]\nการแคชวัตถุไม่ได้ถูกเปิดใช้งาน",
+       "config-no-cache-apcu": "<strong>คำเตือน:</strong> ไม่พบ [https://www.php.net/apcu APCu] [http://xcache.lighttpd.net/ XCache] หรือ [https://www.iis.net/downloads/microsoft/wincache-extension WinCache]\nการแคชวัตถุไม่ได้ถูกเปิดใช้งาน",
        "config-mod-security": "<strong>คำเตือน:</strong> เว็บเซิร์ฟเวอร์ของคุณมี [https://modsecurity.org/ mod_security]/mod_security2 เปิดใช้งานอยู่ การตั้งค่าทั่วไปหลายอย่างของสิ่งนี้จะก่อให้เกิดปัญหาสำหรับ MediaWiki และซอฟต์แวร์อื่นที่อนุญาตให้ผู้ใช้สามารถโพสต์เนื้อหาได้ตามที่ผู้ใช้\nหากเป็นไปได้ ควรปิดใช้งานคุณลักษณะนี้ หรือมิฉะนั้นก็ อ้างไปยัง[https://modsecurity.org/documentation/ เอกสารกำกับการใช้งาน mod_security] หรือติดต่อการสนับสนุนจากโฮสต์ของคุณ ถ้าคุณพบข้อผิดพลาดโดยสุ่ม",
        "config-diff3-bad": "ไม่พบโปรแกรมเปรียบเทียบข้อความ GNU diff3 คุณสามารถละเว้นสิ่งนี้ได้ในตอนนี้ แต่อาจพบข้อขัดแย้งในการแก้ไขบ่อยครั้งกว่า",
        "config-git": "พบซอฟต์แวร์ควบคุมรุ่น Git: <code>$1</code>",
        "config-type-mysql": "MySQL (หรือที่เข้ากันได้)",
        "config-type-mssql": "Microsoft SQL Server",
        "config-support-info": "MediaWiki สนับสนุนระบบฐานข้อมูลต่อไปนี้:\n\n$1\n\nถ้าคุณไม่พบระบบฐานข้อมูลที่คุณกำลังพยายามใช้ในรายการด้านล่างนี้ ให้ทำตามคำแนะนำที่เชื่อมโยงด้านบนเพื่อเปิดใช้งานการสนับสนุน",
-       "config-dbsupport-mysql": "* [{{int:version-db-mysql-url}} MySQL] คือเป้าหมายหลักสำหรับ MediaWiki และได้รับการสนับสนุนดีที่สุด MediaWiki ยังคงสามารถใช้ได้ร่วมกับ [{{int:version-db-mariadb-url}} MariaDB] และ [{{int:version-db-percona-url}} Percona Server] ซึ่งเข้ากันได้กับ MySQL ([https://secure.php.net/manual/en/mysqli.installation.php วิธีการคอมไพล์ PHP ด้วยการสนับสนุน MySQL])",
-       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] คือระบบฐานข้อมูลแบบโอเพนซอร์สที่ได้รับความนิยมสูงที่สามารถใช้แทน MySQL ได้ ([https://secure.php.net/manual/en/pgsql.installation.php วิธีการคอมไพล์ PHP ด้วยการสนับสนุน PostgreSQL])",
+       "config-dbsupport-mysql": "* [{{int:version-db-mysql-url}} MySQL] คือเป้าหมายหลักสำหรับ MediaWiki และได้รับการสนับสนุนดีที่สุด MediaWiki ยังคงสามารถใช้ได้ร่วมกับ [{{int:version-db-mariadb-url}} MariaDB] และ [{{int:version-db-percona-url}} Percona Server] ซึ่งเข้ากันได้กับ MySQL ([https://www.php.net/manual/en/mysqli.installation.php วิธีการคอมไพล์ PHP ด้วยการสนับสนุน MySQL])",
+       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] คือระบบฐานข้อมูลแบบโอเพนซอร์สที่ได้รับความนิยมสูงที่สามารถใช้แทน MySQL ได้ ([https://www.php.net/manual/en/pgsql.installation.php วิธีการคอมไพล์ PHP ด้วยการสนับสนุน PostgreSQL])",
        "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite] คือระบบฐานข้อมูลขนาดเล็กที่ได้รับการสนับสนุนดีมาก ([http://www.php.net/manual/en/pdo.installation.php วิธีการคอมไพล์ PHP ด้วยการสนับสนุน SQLite], ใช้ PDO)",
        "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] คือฐานข้อมูลสำหรับองค์กรพาณิชย์ ([http://www.php.net/manual/en/oci8.installation.php วิธีการคอมไพล์ PHP ด้วยการสนับสนุน OCI8])",
-       "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] คือฐานข้อมูลสำหรับองค์กรพาณิชย์สำหรับ Windows. ([https://secure.php.net/manual/en/sqlsrv.installation.php วิธีการคอมไพล์ PHP ด้วยการสนับสนุน SQLSRV])",
+       "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] คือฐานข้อมูลสำหรับองค์กรพาณิชย์สำหรับ Windows. ([https://www.php.net/manual/en/sqlsrv.installation.php วิธีการคอมไพล์ PHP ด้วยการสนับสนุน SQLSRV])",
        "config-header-mysql": "การตั้งค่า MySQL",
        "config-header-postgres": "การตั้งค่า PostgreSQL",
        "config-header-sqlite": "การตั้งค่า SQLite",
index 3614333..fe53fa1 100644 (file)
@@ -63,7 +63,7 @@
        "config-pcre-no-utf8": "'''Malubha''': Tila tinipon ang modyul na PCRE ng PHP na wala ang suporta ng PCRE_UTF8.\nNangangailangan ang MediaWiki ng suporta ng UTF-8 upang maging tama ang pag-andar.",
        "config-memory-raised": "Ang <code>hangganan_ng_alaala</code> ng PHP ay $1, itinaas sa $2.",
        "config-memory-bad": "'''Babala:''' Ang <code>hangganan_ng_alaala</code> ng PHP ay $1.\nIto ay maaaring napakababa.\nMaaaring mabigo ang pagluluklok!",
-       "config-apc": "Ininstala na ang [https://secure.php.net/apc APC]",
+       "config-apc": "Ininstala na ang [https://www.php.net/apc APC]",
        "config-wincache": "Ininstala na ang [https://www.iis.net/downloads/microsoft/wincache-extension WinCache]",
        "config-mod-security": "'''Babala''': Ang tagapaghain mo ng sangkasaputan ay pinagana na mayroong [https://modsecurity.org/ mod_security]. Kung mali ang kaayusan, makapagdurulot ito ng mga suliranin para sa MediaWiki o ibang mga sopwer na nagpapahintulot sa mga tagagamit na magpaskil ng hindi makatwirang nilalaman.\nSumangguni sa [https://modsecurity.org/documentation/ mod_security kasulatan] o makipag-ugnayan sa suporta ng iyong tagapagpasinaya kapag nakatagpo ng alin mang mga kamalian.",
        "config-diff3-bad": "Hindi natagpuan ang GNU diff3.",
        "config-type-sqlite": "SQLite",
        "config-type-oracle": "Oracle",
        "config-support-info": "Sinusuportahan ng MediaWiki ang sumusunod na mga sistema ng kalipunan ng dato:\n\n$1\n\nKung hindi mo makita ang sistema ng kalipunan ng dato na sinusubukan mong gamitin na nakatala sa ibaba, kung gayon ay sundi ang mga tagubilin na nakakawing sa itaas upang mapagana ang suporta,",
-       "config-dbsupport-mysql": "* [{{int:version-db-mysql-url}} MySQL] ang pangunahing puntirya para sa MediaWiki at ang pinaka sinusuportahan. Gumagana rin ang MediaWiki [{{int:version-db-mariadb-url}} MariaDB] at sa [{{int:version-db-percona-url}} Percona Server], na tugma sa MySQL.  ([https://secure.php.net/manual/en/mysql.installation.php Paano magtipon ng PHP na mayroong suporta ng MySQL])",
-       "config-dbsupport-postgres": "* Ang [{{int:version-db-postgres-url}} PostgreSQL] ay isang bantog na sistema ng kalipunan ng dato na bukas ang pinagmulan na panghalili sa MySQL. ([https://secure.php.net/manual/en/pgsql.installation.php Paano magtipon ng PHP na mayroong suporta ng PostgreSQL]).",
+       "config-dbsupport-mysql": "* [{{int:version-db-mysql-url}} MySQL] ang pangunahing puntirya para sa MediaWiki at ang pinaka sinusuportahan. Gumagana rin ang MediaWiki [{{int:version-db-mariadb-url}} MariaDB] at sa [{{int:version-db-percona-url}} Percona Server], na tugma sa MySQL.  ([https://www.php.net/manual/en/mysql.installation.php Paano magtipon ng PHP na mayroong suporta ng MySQL])",
+       "config-dbsupport-postgres": "* Ang [{{int:version-db-postgres-url}} PostgreSQL] ay isang bantog na sistema ng kalipunan ng dato na bukas ang pinagmulan na panghalili sa MySQL. ([https://www.php.net/manual/en/pgsql.installation.php Paano magtipon ng PHP na mayroong suporta ng PostgreSQL]).",
        "config-dbsupport-sqlite": "* Ang [{{int:version-db-sqlite-url}} SQLite] ay isang magaan ang timbang na sistema ng kalipunan ng dato na sinusuportahan nang napaka mainam. ([http://www.php.net/manual/en/pdo.installation.php Paano magtipon ng PHP na mayroong suporta ng SQLite], gumagamit ng PDO)",
        "config-dbsupport-oracle": "* Ang [{{int:version-db-oracle-url}} Oracle] ay isang kalipunan ng dato ng kasigasigang pangkalakal. ([http://www.php.net/manual/en/oci8.installation.php Paano magtipunan ng PHP na mayroong suporta ng OCI8])",
        "config-header-mysql": "Mga katakdaan ng MariaDB/MySQL",
index c8732e2..feb682a 100644 (file)
        "config-pcre-no-utf8": "<strong>Önemli hata:</strong> PHP'nin PCRE modülü PCRE_UTF8 desteği olmadan derlenmiş gözüküyor.\nMediaWiki'nin doğru çalışabilmesi için UTF-8 desteği gereklidir.",
        "config-memory-raised": "PHP'nin <code>memory_limit</code> (hafıza sınırı) değeri $1, $2'ye yükseltildi.",
        "config-memory-bad": "<strong>Uyarı:</strong> PHP'nin <code>memory_limit</code> (hafıza sınırı) değeri $1.\nBu büyük ihtimalle çok düşük.\nKurulum başarısız olabilir!",
-       "config-apc": "[https://secure.php.net/apc APC] kuruldu",
-       "config-apcu": "[https://secure.php.net/apcu APCu] kuruldu",
+       "config-apc": "[https://www.php.net/apc APC] kuruldu",
+       "config-apcu": "[https://www.php.net/apcu APCu] kuruldu",
        "config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension WinCache] kuruldu",
-       "config-no-cache-apcu": "<strong>Uyarı:</strong> [https://secure.php.net/apcu APCu] ya da [https://www.iis.net/downloads/microsoft/wincache-extension WinCache] kurulumu bulunamadı.\nNesne önbellekleme etkin değil.",
+       "config-no-cache-apcu": "<strong>Uyarı:</strong> [https://www.php.net/apcu APCu] ya da [https://www.iis.net/downloads/microsoft/wincache-extension WinCache] kurulumu bulunamadı.\nNesne önbellekleme etkin değil.",
        "config-mod-security": "<strong>'''Uyarı:'''</strong> Web sunucunuz [https://modsecurity.org/mod_security2 mod_security] etkin. Bunun birçok yaygın yapılandırması bulunur ve eğer yanlış yapılandırılmış ise, bu MediaWiki ve kullanıcılara isteğe bağlı içerik göndermesine izin veren diğer yazılımlar için sorun oluşturabilir.\nMümkünse bu devre dışı bırakılmalıdır. Aksi takdirde rastgele hatalar alırsanız [https://modsecurity.org/documentation/ mod_security belgelemesine] bakın ya da sunucunuzun desteğine başvurun.",
        "config-diff3-bad": "GNU diff3 bulunamadı.",
        "config-git": "Sürüm kontrol yazılımı Git bulundu: <code>$1</code>.",
index 5565354..bc222a5 100644 (file)
@@ -29,7 +29,7 @@
        "config-restart": "Әйе, яңадан башларга",
        "config-env-php": "PHP $1 куелды.",
        "config-env-hhvm": "HHVM $1 куелды.",
-       "config-apc": "[https://secure.php.net/apc APC] куелды",
+       "config-apc": "[https://www.php.net/apc APC] куелды",
        "config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension WinCache] куелды",
        "config-diff3-bad": "GNU diff3 табылмады.",
        "config-git": "Git юрамалар идарә итү системасы табылды: <code>$1</code>.",
index 54bae6e..5a88deb 100644 (file)
        "config-pcre-no-utf8": "'''Помилка''': PCRE-модуть PHP, вочевидь, було зібрано без підтримки PCRE_UTF8.\nMediaWiki вимагає підтримку UTF-8 для коректної роботи.",
        "config-memory-raised": "Обмеження пам'яті PHP (<code>memory_limit</code>) $1, піднято до $2.",
        "config-memory-bad": "'''Увага:''' Розмір пам'яті PHP (<code>memory_limit</code>) становить $1.\nІмовірно, це замало.\nВстановлення може не вдатись!",
-       "config-apc": "[https://secure.php.net/apc APC] встановлено",
-       "config-apcu": "[https://secure.php.net/apcu APCu] встановлено",
+       "config-apc": "[https://www.php.net/apc APC] встановлено",
+       "config-apcu": "[https://www.php.net/apcu APCu] встановлено",
        "config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension WinCache] встановлено",
-       "config-no-cache-apcu": "<strong>Увага:</strong> Не вдалося знайти [https://secure.php.net/apcu APCu] чи [https://www.iis.net/downloads/microsoft/wincache-extension WinCache].\nКешування об'єктів не ввімкнено.",
+       "config-no-cache-apcu": "<strong>Увага:</strong> Не вдалося знайти [https://www.php.net/apcu APCu] чи [https://www.iis.net/downloads/microsoft/wincache-extension WinCache].\nКешування об'єктів не ввімкнено.",
        "config-mod-security": "'''Увага''': на Вашому веб-сервері увімкнено [https://modsecurity.org/ mod_security]. У разі неправильних налаштувать, він може викликати проблеми MediaWiki або іншого ПЗ, яке дозволяє користувачам надсилати довільний вміст.\nЗверніться до [https://modsecurity.org/documentation/ документації mod_security] або підтримки Вашого хостера, якщо під час роботи виникають незрозумілі помилки.",
        "config-diff3-bad": "УтилітуGNU diff3, призначену для порівняння текстів, не знайдено. Можете поки що це проігнорувати, але згодом, ймовірно, будете частіше натрапляти на конфлікти редагувань.",
        "config-git": "Знайшов програму управління версіями Git: <code>$1</code>.",
        "config-type-mysql": "\nMariaDB, MySQL (або сумісні)",
        "config-type-mssql": "Microsoft SQL Server",
        "config-support-info": "MediaWiki підтримує такі системи баз даних:\n\n$1\n\nЯкщо Ви не бачите серед перерахованих систему баз даних, яку використовуєте, виконайте вказівки, вказані вище, щоб увімкнути підтримку.",
-       "config-dbsupport-mysql": "* [{{int:version-db-mariadb-url}} MariaDB] є основною ціллю для MediaWiki і найкраще підтримується.  MediaWiki також працює з [{{int:version-db-mysql-url}} MySQL] та [{{int:version-db-percona-url}} Percona Server], які сумісні з MariaDB.  ([https://secure.php.net/manual/en/mysqli.installation.php Як зібрати PHP з підтримкою MySQL])",
-       "config-dbsupport-postgres": "*  [{{int:version-db-postgres-url}} PostgreSQL] — популярна відкрита СУБД, альтернатива MySQL. ([https://secure.php.net/manual/en/pgsql.installation.php як зібрати PHP з допомогою PostgreSQL]).",
+       "config-dbsupport-mysql": "* [{{int:version-db-mariadb-url}} MariaDB] є основною ціллю для MediaWiki і найкраще підтримується.  MediaWiki також працює з [{{int:version-db-mysql-url}} MySQL] та [{{int:version-db-percona-url}} Percona Server], які сумісні з MariaDB.  ([https://www.php.net/manual/en/mysqli.installation.php Як зібрати PHP з підтримкою MySQL])",
+       "config-dbsupport-postgres": "*  [{{int:version-db-postgres-url}} PostgreSQL] — популярна відкрита СУБД, альтернатива MySQL. ([https://www.php.net/manual/en/pgsql.installation.php як зібрати PHP з допомогою PostgreSQL]).",
        "config-dbsupport-sqlite": "*  [{{int:version-db-sqlite-url}} SQLite] — легка система баз даних, яка дуже добре підтримується. ([http://www.php.net/manual/en/pdo.installation.php Як зібрати PHP з допомогою SQLite], що використовує PDO)",
        "config-dbsupport-oracle": "*  [{{int:version-db-oracle-url}} Oracle] — комерційна база даних масштабу підприємства. ([http://www.php.net/manual/en/oci8.installation.php Як зібрати PHP з підтримкою OCI8])",
-       "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] — це комерційна база даних для Windows масштабу підприємства. ([https://secure.php.net/manual/en/sqlsrv.installation.php Як зібрати PHP з підтримкою SQLSRV])",
+       "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] — це комерційна база даних для Windows масштабу підприємства. ([https://www.php.net/manual/en/sqlsrv.installation.php Як зібрати PHP з підтримкою SQLSRV])",
        "config-header-mysql": "Налаштування MariaDB/MySQL",
        "config-header-postgres": "Налаштування PostgreSQL",
        "config-header-sqlite": "Налаштування SQLite",
index 4ee268d..890f996 100644 (file)
        "config-pcre-no-utf8": "<strong>Lỗi chí tử:</strong> Mô đun PCRE của PHP dường như được biên dịch mà không có hỗ trợ PCRE_UTF8.\nMediaWiki yêu cầu phải có hỗ trợ UTF-8 để hoạt động chính xác.",
        "config-memory-raised": "<code>memory_limit</code> của PHP là $1, tăng lên $2.",
        "config-memory-bad": "<strong>Cảnh báo:</strong> <code>memory_limit</code> của PHP là $1.\nGiá trị này có lẽ quá thấp.\nCài đặt có thể bị thất bại!",
-       "config-apc": "[https://secure.php.net/apc APC] đã được cài đặt",
-       "config-apcu": "[https://secure.php.net/apcu APCu] đã được cài đặt",
+       "config-apc": "[https://www.php.net/apc APC] đã được cài đặt",
+       "config-apcu": "[https://www.php.net/apcu APCu] đã được cài đặt",
        "config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension WinCache] đã được cài đặt",
-       "config-no-cache-apcu": "<strong>Cảnh báo:</strong> Không tìm thấy [https://secure.php.net/apcu APCu] hoặc [https://www.iis.net/downloads/microsoft/wincache-extension WinCache].\nVùng nhớ đệm đối tượng không được kích hoạt.",
+       "config-no-cache-apcu": "<strong>Cảnh báo:</strong> Không tìm thấy [https://www.php.net/apcu APCu] hoặc [https://www.iis.net/downloads/microsoft/wincache-extension WinCache].\nVùng nhớ đệm đối tượng không được kích hoạt.",
        "config-mod-security": "<strong>Cảnh báo:</strong> [https://modsecurity.org/ mod_security]/mod_security2 đã được kích hoạt trên máy chủ Web của bạn. Nhiều cấu hình phổ biến của phần mềm này sẽ gây vấn đề cho MediaWiki và những phần mềm khác cho phép người dùng đăng các nội dung tùy tiện.\nNếu có thể, bạn nên vô hiệu nó. Còn không, tra cứu [https://modsecurity.org/documentation/ tài liệu mod_security] hoặc liên hệ với nhà cung cấp hỗ trợ cho máy chủ nếu bạn gặp những lỗi ngẫu nhiên nào đó.",
        "config-diff3-bad": "Không tìm thấy GNU diff3.",
        "config-git": "Đã tìm thấy phần mềm điều khiển phiên bản Git: <code>$1</code>.",
        "config-type-mysql": "MariaDB, MySQL, hoặc tương hợp",
        "config-type-mssql": "Microsoft SQL Server",
        "config-support-info": "MediaWiki hỗ trợ các hệ thống cơ sở dữ liệu sau đây:\n\n$1\n\nNếu bạn không thấy hệ thống cơ sở dữ liệu mà bạn đang muốn sử dụng được liệt kê dưới đây, thì hãy theo chỉ dẫn được liên kết ở trên để kích hoạt tính năng hỗ trợ.",
-       "config-dbsupport-mysql": "* [{{int:version-db-mariadb-url}} MariaDB] là mục tiêu chính cho MediaWiki và được hỗ trợ tốt nhất. MediaWiki cũng làm việc với [{{int:version-db-mysql-url}} MySQL] và [{{int:version-db-percona-url}} Percona Server], là những cơ sở dữ liệu tương thích với MariaDB. ([https://secure.php.net/manual/en/mysqli.installation.php Làm thế nào để biên dịch PHP với sự hỗ trợ của MySQL])",
-       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] là một hệ thống cơ sở dữ liệu mã nguồn mở phổ biến như là một thay thế cho MySQL. ([https://secure.php.net/manual/en/pgsql.installation.php Làm thế nào để biên dịch PHP với sự hỗ trợ của PostgreSQL])",
-       "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite] là một hệ thống cơ sở dữ liệu dung lượng nhẹ được hỗ trợ rất tốt. ([https://secure.php.net/manual/en/pdo.installation.php Làm thế nào để biên dịch PHP với sự hỗ trợ của SQLite], sử dụng PDO)",
-       "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] là một cơ sở dữ liệu doanh nghiệp thương mại. ([https://secure.php.net/manual/en/oci8.installation.php Làm thế nào để biên dịch PHP với sự hỗ trợ của OCI8])",
-       "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] là một cơ sở dữ liệu doanh nghiệp thương mại cho Windows. ([https://secure.php.net/manual/en/sqlsrv.installation.php Làm thế nào để biên dịch PHP với sự hỗ trợ của SQLSRV])",
+       "config-dbsupport-mysql": "* [{{int:version-db-mariadb-url}} MariaDB] là mục tiêu chính cho MediaWiki và được hỗ trợ tốt nhất. MediaWiki cũng làm việc với [{{int:version-db-mysql-url}} MySQL] và [{{int:version-db-percona-url}} Percona Server], là những cơ sở dữ liệu tương thích với MariaDB. ([https://www.php.net/manual/en/mysqli.installation.php Làm thế nào để biên dịch PHP với sự hỗ trợ của MySQL])",
+       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] là một hệ thống cơ sở dữ liệu mã nguồn mở phổ biến như là một thay thế cho MySQL. ([https://www.php.net/manual/en/pgsql.installation.php Làm thế nào để biên dịch PHP với sự hỗ trợ của PostgreSQL])",
+       "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite] là một hệ thống cơ sở dữ liệu dung lượng nhẹ được hỗ trợ rất tốt. ([https://www.php.net/manual/en/pdo.installation.php Làm thế nào để biên dịch PHP với sự hỗ trợ của SQLite], sử dụng PDO)",
+       "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] là một cơ sở dữ liệu doanh nghiệp thương mại. ([https://www.php.net/manual/en/oci8.installation.php Làm thế nào để biên dịch PHP với sự hỗ trợ của OCI8])",
+       "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] là một cơ sở dữ liệu doanh nghiệp thương mại cho Windows. ([https://www.php.net/manual/en/sqlsrv.installation.php Làm thế nào để biên dịch PHP với sự hỗ trợ của SQLSRV])",
        "config-header-mysql": "Thiết lập MariaDB/MySQL",
        "config-header-postgres": "Thiết lập PostgreSQL",
        "config-header-sqlite": "Thiết lập SQLite",
        "config-license-help": "Nhiều wiki công khai phát hành tất cả các đóng góp theo một [https://freedomdefined.org/Definition/Vi?uselang=vi giấy phép tự do].\nĐiều này giúp tạo nên thái độ cộng đồng sở hữu và ủng hộ sự đóng góp lâu dài.\nNói chung, một wiki riêng tư hoặc của công ty không nhất thiết phải sử dụng một giấy phép tự do.\n\nNếu bạn muốn được phép sử dụng văn bản từ Wikipedia và muốn Wikipedia nhận được những văn bản được sao chép từ wiki của bạn, bạn nên chọn <strong>{{int:config-license-cc-by-sa}}</strong>.\n\nWikipedia từng sử dụng Giấy phép Tài liệu Tự do GNU.\nGFDL là một giấy phép hợp lệ nhưng khó hiểu trên thực tế.\nNội dung được phát hành theo GFDL cũng khó tái sử dụng.",
        "config-email-settings": "Thiết lập thư điện tử",
        "config-enable-email": "Cho phép gửi thư điện tử đi",
-       "config-enable-email-help": "Nếu bạn muốn khả năng gửi thư điện tử, [https://secure.php.net/manual/en/mail.configuration.php thiết lập mail của PHP] cần phải được cấu hình đúng.\nNếu bạn không muốn sử dụng bất kỳ tính năng thư điện tử nào, bạn có thể vô hiệu chúng ở đây.",
+       "config-enable-email-help": "Nếu bạn muốn khả năng gửi thư điện tử, [https://www.php.net/manual/en/mail.configuration.php thiết lập mail của PHP] cần phải được cấu hình đúng.\nNếu bạn không muốn sử dụng bất kỳ tính năng thư điện tử nào, bạn có thể vô hiệu chúng ở đây.",
        "config-email-user": "Cho phép người dùng gửi thư điện tử cho người dùng khác",
        "config-email-user-help": "Cho phép tất cả người dùng gửi thư điện tử cho nhau, nếu họ đã kích hoạt nó trong cài đặt tùy chọn của họ.",
        "config-email-usertalk": "Gửi thư thông báo về tin nhắn mới",
index 2496153..8877f2d 100644 (file)
@@ -39,7 +39,7 @@
        "config-env-bad": "מ'האט קאנטראלירט די סביבה.\nאיר קענט נישט אינסטאלירן מעדיעוויקי.",
        "config-env-php": "PHP $1 איז אינצטאלירט.",
        "config-env-hhvm": "HHVM $1 איז אינסטאלירט.",
-       "config-apc": "[https://secure.php.net/apc APC] איז אינסטאַלירט",
+       "config-apc": "[https://www.php.net/apc APC] איז אינסטאַלירט",
        "config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension WinCache] איז אינסטאַלירט",
        "config-diff3-bad": "GNU diff3 נישט געטראפן.",
        "config-using-server": "באניצן סארווער־נאמען \"<nowiki>$1</nowiki>\".",
index 4f4a6a6..6035c61 100644 (file)
        "config-pcre-no-utf8": "<strong>致命错误:</strong>PHP的PCRE模块在编译时可能没有包含PCRE_UTF8支持。\nMediaWiki需要UTF-8支持才能正常工作。",
        "config-memory-raised": "PHP的内存使用上限<code>memory_limit</code>为$1,自动提升到$2。",
        "config-memory-bad": "<strong>警告:</strong>PHP的内存使用上限<code>memory_limit</code>为$1。\n该设定可能过低,并导致安装失败!",
-       "config-apc": "[https://secure.php.net/apc APC]已安装",
-       "config-apcu": "已安装[https://secure.php.net/apcu APCu]",
+       "config-apc": "[https://www.php.net/apc APC]已安装",
+       "config-apcu": "已安装[https://www.php.net/apcu APCu]",
        "config-wincache": "已安装[https://www.iis.net/downloads/microsoft/wincache-extension WinCache]",
-       "config-no-cache-apcu": "<strong>警告:</strong>找不到[https://secure.php.net/apcu APCu]或[https://www.iis.net/downloads/microsoft/wincache-extension WinCache]。对象缓存未启用。",
+       "config-no-cache-apcu": "<strong>警告:</strong>找不到[https://www.php.net/apcu APCu]或[https://www.iis.net/downloads/microsoft/wincache-extension WinCache]。对象缓存未启用。",
        "config-mod-security": "<strong>警告:</strong>您的web服务器已启用[https://modsecurity.org/ mod_security]/mod_security2。它的很多常见配置可能导致MediaWiki及其他软件允许用户发布任意内容的问题。如果可能,这应当被禁用。否则,当您遭遇随机错误时,请参考[https://modsecurity.org/documentation/ mod_security 文档]或联络您的主机支持。",
        "config-diff3-bad": "找不到GNU diff3文字对比工具程序,您可以暂时忽略它,但可能会更频繁遇到编辑冲突。",
        "config-git": "发现Git版本控制软件:<code>$1</code>",
        "config-type-mysql": "MariaDB、MySQL或兼容程序",
        "config-type-mssql": "微软SQL服务器",
        "config-support-info": "MediaWiki支持以下数据库系统:\n\n$1\n\n如果您在下面列出的数据库系统中没有找到您希望使用的系统,请根据上方链向的指引启用支持。",
-       "config-dbsupport-mysql": "* [{{int:version-db-mariadb-url}} MariaDB]是用于MediaWiki的主要数据库,对它的支持最为完备。MediaWiki也可以在[{{int:version-db-mysql-url}} MySQL]和[{{int:version-db-percona-url}} Percona Server]下工作,它们与MariaDB兼容。([https://secure.php.net/manual/en/mysqli.installation.php 如何将对MySQL的支持编译进PHP中])",
-       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL]是一种流行的开源数据库系统,可作为MySQL的替代。([https://secure.php.net/manual/en/pgsql.installation.php 如何将对PostgreSQL的支持编译进PHP中])",
-       "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite]是一种轻量级的数据库系统,能被良好地支持。([https://secure.php.net/manual/en/pdo.installation.php 如何将对SQLite的支持编译进PHP中],须使用PDO)",
-       "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle]是一种商用企业级的数据库。([https://secure.php.net/manual/en/oci8.installation.php 如何将对OCI8的支持编译进PHP中])",
-       "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server]是一个适用于Windows的商业性企业数据库。([https://secure.php.net/manual/en/sqlsrv.installation.php 如何编译带有SQLSRV支持的PHP])",
+       "config-dbsupport-mysql": "* [{{int:version-db-mariadb-url}} MariaDB]是用于MediaWiki的主要数据库,对它的支持最为完备。MediaWiki也可以在[{{int:version-db-mysql-url}} MySQL]和[{{int:version-db-percona-url}} Percona Server]下工作,它们与MariaDB兼容。([https://www.php.net/manual/en/mysqli.installation.php 如何将对MySQL的支持编译进PHP中])",
+       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL]是一种流行的开源数据库系统,可作为MySQL的替代。([https://www.php.net/manual/en/pgsql.installation.php 如何将对PostgreSQL的支持编译进PHP中])",
+       "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite]是一种轻量级的数据库系统,能被良好地支持。([https://www.php.net/manual/en/pdo.installation.php 如何将对SQLite的支持编译进PHP中],须使用PDO)",
+       "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle]是一种商用企业级的数据库。([https://www.php.net/manual/en/oci8.installation.php 如何将对OCI8的支持编译进PHP中])",
+       "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server]是一个适用于Windows的商业性企业数据库。([https://www.php.net/manual/en/sqlsrv.installation.php 如何编译带有SQLSRV支持的PHP])",
        "config-header-mysql": "MariaDB/MySQL设置",
        "config-header-postgres": "PostgreSQL设置",
        "config-header-sqlite": "SQLite设置",
        "config-license-help": "许多公共wiki将所有用户贡献置于[https://freedomdefined.org/Definition 自由许可证]之下。这有助于构建社区的主人翁意识,并鼓励长期贡献。对于非公共wiki或公司wiki,这并非必要条件。\n\n如果您希望使用来自维基百科的内容,并希望维基百科能接受复制自您的wiki的内容,您应当选择<strong>{{int:config-license-cc-by-sa}}</strong>。\n\nGNU自由文档许可证是维基百科曾经使用过的许可证,并迄今有效。然而,该许可证难以理解,并会增加重用内容的难度。",
        "config-email-settings": "电子邮件设置",
        "config-enable-email": "启用出站电子邮件",
-       "config-enable-email-help": "如果您希望使用电子邮件功能,请正确配置[https://secure.php.net/manual/en/mail.configuration.php PHP的邮件设定]。如果您不需要任何电子邮件功能,请在此处禁用它。",
+       "config-enable-email-help": "如果您希望使用电子邮件功能,请正确配置[https://www.php.net/manual/en/mail.configuration.php PHP的邮件设定]。如果您不需要任何电子邮件功能,请在此处禁用它。",
        "config-email-user": "启用用户到用户的电子邮件",
        "config-email-user-help": "允许所有用户互发邮件,假若他们启用了该功能。",
        "config-email-usertalk": "启用用户讨论页面通知",
index 5bf0020..7a30492 100644 (file)
        "config-pcre-no-utf8": "<strong>嚴重:</strong> PHP 的 PCRE 模組在編譯時未包含 PCRE_UTF8 支援。\nMediaWiki 需要支援 UTF-8 才可正常運作。",
        "config-memory-raised": "PHP 的記憶體使用上限 <code>memory_limit</code> 目前為 $1,自動提高到 $2。",
        "config-memory-bad": "<strong>警告:</strong>PHP 的記憶體使用上限 <code>memory_limit</code> 為 $1。\n該設定值可能過低。\n這可能導致後續的安裝失敗!",
-       "config-apc": "[https://secure.php.net/apc APC] 已安裝",
-       "config-apcu": "已安裝[https://secure.php.net/apcu APCu]",
+       "config-apc": "[https://www.php.net/apc APC] 已安裝",
+       "config-apcu": "已安裝[https://www.php.net/apcu APCu]",
        "config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension WinCache] 已安裝",
-       "config-no-cache-apcu": "<strong>警告:</strong>找不到[https://secure.php.net/apcu APCu]或[https://www.iis.net/downloads/microsoft/wincache-extension WinCache]。未開啟物件快取。",
+       "config-no-cache-apcu": "<strong>警告:</strong>找不到[https://www.php.net/apcu APCu]或[https://www.iis.net/downloads/microsoft/wincache-extension WinCache]。未開啟物件快取。",
        "config-mod-security": "<strong>警告:</strong>您的網頁伺服器已開啟 [https://modsecurity.org/ mod_security] 模組,如果設定不恰當會導致使用者可在 MediaWiki 或其他應用程式發佈任意的內容。\n若您遇到任何問題,請參考 [https://modsecurity.org/documentation/ mod_security 文件] 或聯繫您的伺服器技術支援人員。",
        "config-diff3-bad": "找不到 GNU diff3 文字比對工具程式,而您現在可忽略它,但可能會更頻繁地遇到編輯衝突。",
        "config-git": "找到 Git 版本控制軟體:<code>$1</code>。",
        "config-type-mysql": "MariaDB、MySQL、或與其相容的套件",
        "config-type-mssql": "Microsoft SQL Server",
        "config-support-info": "MediaWiki 支援以下資料庫系統:\n\n$1\n\n如果您下方沒有看到您要使用的資料庫系統,請根據上方連結指示開啟資料庫的支援。",
-       "config-dbsupport-mysql": "* [{{int:version-db-mariadb-url}} MariaDB] 是 MediaWiki 主要支援的資料庫系統。MediaWiki 也同時可運作與於 [{{int:version-db-mysql-url}} MySQL] 和 [{{int:version-db-percona-url}} Percona 伺服器],上述這些與 MariaDB 相容的資料庫系統。([https://secure.php.net/manual/en/mysqli.installation.php 如何編譯支援 MySQL 的 PHP])",
-       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] 是一套受歡迎的開源資料庫系統,可用來替代 MySQL。([https://secure.php.net/manual/en/pgsql.installation.php 如何編譯支援 PostgreSQL 的 PHP])。",
-       "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite] 是一套輕量級的資料庫系統,MediaWiki 可在此資料庫系統上良好的運作。([https://secure.php.net/manual/en/pdo.installation.php 如何編譯支援 SQLite 的 PHP],須透過 PDO)",
-       "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] 是一套商用企業級的資料庫。([https://secure.php.net/manual/en/oci8.installation.php 如何編譯支援 OCI8 的 PHP])",
-       "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] 是一套 Windows 專用的商用企業級的資料庫。 ([https://secure.php.net/manual/en/sqlsrv.installation.php 如何編譯支援 SQLSRV 的 PHP])",
+       "config-dbsupport-mysql": "* [{{int:version-db-mariadb-url}} MariaDB] 是 MediaWiki 主要支援的資料庫系統。MediaWiki 也同時可運作與於 [{{int:version-db-mysql-url}} MySQL] 和 [{{int:version-db-percona-url}} Percona 伺服器],上述這些與 MariaDB 相容的資料庫系統。([https://www.php.net/manual/en/mysqli.installation.php 如何編譯支援 MySQL 的 PHP])",
+       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] 是一套受歡迎的開源資料庫系統,可用來替代 MySQL。([https://www.php.net/manual/en/pgsql.installation.php 如何編譯支援 PostgreSQL 的 PHP])。",
+       "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite] 是一套輕量級的資料庫系統,MediaWiki 可在此資料庫系統上良好的運作。([https://www.php.net/manual/en/pdo.installation.php 如何編譯支援 SQLite 的 PHP],須透過 PDO)",
+       "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] 是一套商用企業級的資料庫。([https://www.php.net/manual/en/oci8.installation.php 如何編譯支援 OCI8 的 PHP])",
+       "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] 是一套 Windows 專用的商用企業級的資料庫。 ([https://www.php.net/manual/en/sqlsrv.installation.php 如何編譯支援 SQLSRV 的 PHP])",
        "config-header-mysql": "MariaDB/MySQL 設定",
        "config-header-postgres": "PostgreSQL 設定",
        "config-header-sqlite": "SQLite 設定",
        "config-license-help": "許多開放式 Wiki 會以 [https://freedomdefined.org/Definition 自由授權條款] 的方式釋放出編者的所有貢獻,這有助於構建社群的所有權,並且能鼓勵長期貢獻。對於封閉式的 Wiki 或公司 Wiki 則是非必要的。\n\n如果您希望使用來自維基百科(Wikipedia)的內容,並希望維基百科能接受您的 Wiki 內容,請應選擇 <strong>{{int:config-license-cc-by-sa}}</strong> 授權條款。\n\n維基百科(Wikipedia)先前是使用 GNU 自由文件授權條款,\n但該授權條款的內容較難理解,因此較難再利用在該條款底下的內容。",
        "config-email-settings": "E-mail 設定",
        "config-enable-email": "開啟外寄電子郵件",
-       "config-enable-email-help": "如果您要使用電子郵件功能,請正確設定 [https://secure.php.net/manual/en/mail.configuration.php PHP 的郵件設定]。\n如果您不需要使用電子郵件功能,請在此處關閉。",
+       "config-enable-email-help": "如果您要使用電子郵件功能,請正確設定 [https://www.php.net/manual/en/mail.configuration.php PHP 的郵件設定]。\n如果您不需要使用電子郵件功能,請在此處關閉。",
        "config-email-user": "開啟使用者對使用者間的電子郵件互通",
        "config-email-user-help": "若使用者在個人偏好設定開啟了此功能,則可允許使用者間相互傳送郵件。",
        "config-email-usertalk": "開啟使用者討論頁面通知",
diff --git a/includes/jobqueue/GenericParameterJob.php b/includes/jobqueue/GenericParameterJob.php
new file mode 100644 (file)
index 0000000..f7da42b
--- /dev/null
@@ -0,0 +1,34 @@
+<?php
+/**
+ * Interface for generic jobs only uses the parameters field.
+ *
+ * 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
+ */
+
+/**
+ * Interface for generic jobs only uses the parameters field and are JSON serializable
+ *
+ * @ingroup JobQueue
+ * @since 1.33
+ */
+interface GenericParameterJob extends IJobSpecification {
+       /**
+        * @param array $params JSON-serializable map of parameters
+        */
+       public function __construct( array $params );
+}
index 8bc1bc3..2b3caa2 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /**
- * Job queue task description base code.
+ * Job queue task description interface
  *
  * 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
  */
 
 /**
- * Job queue task description interface
+ * Interface for serializable objects that describe a job queue task
+ *
+ * A job specification can be inserted into a queue via JobQueue::push().
+ * The specification parameters should be JSON serializable (e.g. no PHP classes).
+ * Whatever queue the job specification is pushed into is assumed to have job runners
+ * that will eventually pop the job specification from the queue, construct a RunnableJob
+ * instance from the specification, and then execute that instance via RunnableJob::run().
  *
  * @ingroup JobQueue
  * @since 1.23
  */
 interface IJobSpecification {
        /**
-        * @return string Job type
+        * @return string Job type that defines what sort of changes this job makes
         */
        public function getType();
 
        /**
-        * @return array
+        * @return array Parameters that specify sources, targets, and options for execution
         */
        public function getParams();
 
@@ -76,9 +82,4 @@ interface IJobSpecification {
         * @return bool Whether this is job is a root job
         */
        public function isRootJob();
-
-       /**
-        * @return Title Descriptive title (this can simply be informative)
-        */
-       public function getTitle();
 }
index 060003b..6054e35 100644 (file)
@@ -27,7 +27,7 @@
  *
  * @ingroup JobQueue
  */
-abstract class Job implements IJobSpecification {
+abstract class Job implements RunnableJob {
        /** @var string */
        public $command;
 
@@ -55,12 +55,6 @@ abstract class Job implements IJobSpecification {
        /** @var int Job must not be wrapped in the usual explicit LBFactory transaction round */
        const JOB_NO_EXPLICIT_TRX_ROUND = 1;
 
-       /**
-        * Run the job
-        * @return bool Success
-        */
-       abstract public function run();
-
        /**
         * Create the appropriate object to handle a specific job
         *
@@ -77,17 +71,24 @@ abstract class Job implements IJobSpecification {
                        $title = $params;
                        $params = func_num_args() >= 3 ? func_get_arg( 2 ) : [];
                } else {
-                       // Subclasses can override getTitle() to return something more meaningful
-                       $title = Title::makeTitle( NS_SPECIAL, 'Blankpage' );
+                       $title = ( isset( $params['namespace'] ) && isset( $params['title'] ) )
+                               ? Title::makeTitle( $params['namespace'], $params['title'] )
+                               : Title::makeTitle( NS_SPECIAL, '' );
                }
 
+               $params = is_array( $params ) ? $params : []; // sanity
+
                if ( isset( $wgJobClasses[$command] ) ) {
                        $handler = $wgJobClasses[$command];
 
                        if ( is_callable( $handler ) ) {
                                $job = call_user_func( $handler, $title, $params );
                        } elseif ( class_exists( $handler ) ) {
-                               $job = new $handler( $title, $params );
+                               if ( is_subclass_of( $handler, GenericParameterJob::class ) ) {
+                                       $job = new $handler( $params );
+                               } else {
+                                       $job = new $handler( $title, $params );
+                               }
                        } else {
                                $job = null;
                        }
@@ -112,17 +113,27 @@ abstract class Job implements IJobSpecification {
                if ( $params instanceof Title ) {
                        // Backwards compatibility for old signature ($command, $title, $params)
                        $title = $params;
-                       $params = func_get_arg( 2 );
-               } else {
-                       // Subclasses can override getTitle() to return something more meaningful
-                       $title = Title::makeTitle( NS_SPECIAL, 'Blankpage' );
+                       $params = func_num_args() >= 3 ? func_get_arg( 2 ) : [];
+                       $params = is_array( $params ) ? $params : []; // sanity
+                       // Set namespace/title params if both are missing and this is not a dummy title
+                       if (
+                               $title->getDBkey() !== '' &&
+                               !isset( $params['namespace'] ) &&
+                               !isset( $params['title'] )
+                       ) {
+                               $params['namespace'] = $title->getNamespace();
+                               $params['title'] = $title->getDBKey();
+                               // Note that JobQueue classes will prefer the parameters over getTitle()
+                               $this->title = $title;
+                       }
                }
 
                $this->command = $command;
-               $this->title = $title;
-               $this->params = is_array( $params ) ? $params : [];
-               if ( !isset( $this->params['requestId'] ) ) {
-                       $this->params['requestId'] = WebRequest::getRequestId();
+               $this->params = $params + [ 'requestId' => WebRequest::getRequestId() ];
+               if ( $this->title === null ) {
+                       $this->title = ( isset( $params['namespace'] ) && isset( $params['title'] ) )
+                               ? Title::makeTitle( $params['namespace'], $params['title'] )
+                               : Title::makeTitle( NS_SPECIAL, '' );
                }
        }
 
@@ -145,7 +156,7 @@ abstract class Job implements IJobSpecification {
        /**
         * @return Title
         */
-       public function getTitle() {
+       final public function getTitle() {
                return $this->title;
        }
 
@@ -268,8 +279,6 @@ abstract class Job implements IJobSpecification {
        public function getDeduplicationInfo() {
                $info = [
                        'type' => $this->getType(),
-                       'namespace' => $this->getTitle()->getNamespace(),
-                       'title' => $this->getTitle()->getDBkey(),
                        'params' => $this->getParams()
                ];
                if ( is_array( $info['params'] ) ) {
index 0644002..f5ed7b9 100644 (file)
@@ -361,7 +361,7 @@ abstract class JobQueue {
         * Outside callers should use JobQueueGroup::pop() instead of this function.
         *
         * @throws JobQueueError
-        * @return Job|bool Returns false if there are no jobs
+        * @return RunnableJob|bool Returns false if there are no jobs
         */
        final public function pop() {
                $this->assertNotReadOnly();
@@ -383,7 +383,7 @@ abstract class JobQueue {
 
        /**
         * @see JobQueue::pop()
-        * @return Job|bool
+        * @return RunnableJob|bool
         */
        abstract protected function doPop();
 
@@ -393,11 +393,11 @@ abstract class JobQueue {
         * This does nothing for certain queue classes or if "claimTTL" is not set.
         * Outside callers should use JobQueueGroup::ack() instead of this function.
         *
-        * @param Job $job
+        * @param RunnableJob $job
         * @return void
         * @throws JobQueueError
         */
-       final public function ack( Job $job ) {
+       final public function ack( RunnableJob $job ) {
                $this->assertNotReadOnly();
                if ( $job->getType() !== $this->type ) {
                        throw new JobQueueError( "Got '{$job->getType()}' job; expected '{$this->type}'." );
@@ -408,9 +408,9 @@ abstract class JobQueue {
 
        /**
         * @see JobQueue::ack()
-        * @param Job $job
+        * @param RunnableJob $job
         */
-       abstract protected function doAck( Job $job );
+       abstract protected function doAck( RunnableJob $job );
 
        /**
         * Register the "root job" of a given job into the queue for de-duplication.
@@ -482,11 +482,11 @@ abstract class JobQueue {
        /**
         * Check if the "root" job of a given job has been superseded by a newer one
         *
-        * @param Job $job
+        * @param IJobSpecification $job
         * @throws JobQueueError
         * @return bool
         */
-       final protected function isRootJobOldDuplicate( Job $job ) {
+       final protected function isRootJobOldDuplicate( IJobSpecification $job ) {
                if ( $job->getType() !== $this->type ) {
                        throw new JobQueueError( "Got '{$job->getType()}' job; expected '{$this->type}'." );
                }
@@ -497,10 +497,10 @@ abstract class JobQueue {
 
        /**
         * @see JobQueue::isRootJobOldDuplicate()
-        * @param Job $job
+        * @param IJobSpecification $job
         * @return bool
         */
-       protected function doIsRootJobOldDuplicate( Job $job ) {
+       protected function doIsRootJobOldDuplicate( IJobSpecification $job ) {
                if ( !$job->hasRootJobParams() ) {
                        return false; // job has no de-deplication info
                }
@@ -686,6 +686,16 @@ abstract class JobQueue {
                return null; // not supported
        }
 
+       /**
+        * @param string $command
+        * @param array $params
+        * @return Job
+        */
+       protected function factoryJob( $command, $params ) {
+               // @TODO: dependency inject this as a callback
+               return Job::factory( $command, $params );
+       }
+
        /**
         * @throws JobQueueReadOnlyError
         */
index c2772a6..47ee588 100644 (file)
@@ -290,7 +290,7 @@ class JobQueueDB extends JobQueue {
 
        /**
         * @see JobQueue::doPop()
-        * @return Job|bool
+        * @return RunnableJob|bool
         */
        protected function doPop() {
                $dbw = $this->getMasterDB();
@@ -314,10 +314,12 @@ class JobQueueDB extends JobQueue {
                                        break; // nothing to do
                                }
                                $this->incrStats( 'pops', $this->type );
+
                                // Get the job object from the row...
-                               $title = Title::makeTitle( $row->job_namespace, $row->job_title );
-                               $job = Job::factory( $row->job_cmd, $title,
-                                       self::extractBlob( $row->job_params ) );
+                               $params = self::extractBlob( $row->job_params );
+                               $params = is_array( $params ) ? $params : []; // sanity
+                               $params += [ 'namespace' => $row->job_namespace, 'title' => $row->job_title ];
+                               $job = $this->factoryJob( $row->job_cmd, $params );
                                $job->setMetadata( 'id', $row->job_id );
                                $job->setMetadata( 'timestamp', $row->job_timestamp );
                                break; // done
@@ -481,10 +483,10 @@ class JobQueueDB extends JobQueue {
 
        /**
         * @see JobQueue::doAck()
-        * @param Job $job
+        * @param RunnableJob $job
         * @throws MWException
         */
-       protected function doAck( Job $job ) {
+       protected function doAck( RunnableJob $job ) {
                $id = $job->getMetadata( 'id' );
                if ( $id === null ) {
                        throw new MWException( "Job of type '{$job->getType()}' has no ID." );
@@ -617,11 +619,14 @@ class JobQueueDB extends JobQueue {
                        return new MappedIterator(
                                $dbr->select( 'job', self::selectFields(), $conds ),
                                function ( $row ) {
-                                       $job = Job::factory(
-                                               $row->job_cmd,
-                                               Title::makeTitle( $row->job_namespace, $row->job_title ),
-                                               strlen( $row->job_params ) ? unserialize( $row->job_params ) : []
-                                       );
+                                       $params = strlen( $row->job_params ) ? unserialize( $row->job_params ) : [];
+                                       $params = is_array( $params ) ? $params : []; // sanity
+                                       $params += [
+                                               'namespace' => $row->job_namespace,
+                                               'title' => $row->job_title
+                                       ];
+
+                                       $job = $this->factoryJob( $row->job_cmd, $params );
                                        $job->setMetadata( 'id', $row->job_id );
                                        $job->setMetadata( 'timestamp', $row->job_timestamp );
 
@@ -774,8 +779,8 @@ class JobQueueDB extends JobQueue {
                return [
                        // Fields that describe the nature of the job
                        'job_cmd' => $job->getType(),
-                       'job_namespace' => $job->getTitle()->getNamespace(),
-                       'job_title' => $job->getTitle()->getDBkey(),
+                       'job_namespace' => $job->getParams()['namespace'] ?? NS_SPECIAL,
+                       'job_title' => $job->getParams()['title'] ?? '',
                        'job_params' => self::makeBlob( $job->getParams() ),
                        // Additional job metadata
                        'job_timestamp' => $db->timestamp(),
index 30ab7e7..8b5a62e 100644 (file)
@@ -199,7 +199,7 @@ class JobQueueFederated extends JobQueue {
         * @param HashRing &$partitionRing
         * @param int $flags
         * @throws JobQueueError
-        * @return array List of Job object that could not be inserted
+        * @return IJobSpecification[] List of Job object that could not be inserted
         */
        protected function tryJobInsertions( array $jobs, HashRing &$partitionRing, $flags ) {
                $jobsLeft = [];
@@ -299,7 +299,7 @@ class JobQueueFederated extends JobQueue {
                return false;
        }
 
-       protected function doAck( Job $job ) {
+       protected function doAck( RunnableJob $job ) {
                $partition = $job->getMetadata( 'QueuePartition' );
                if ( $partition === null ) {
                        throw new MWException( "The given job has no defined partition name." );
@@ -308,7 +308,7 @@ class JobQueueFederated extends JobQueue {
                $this->partitionQueues[$partition]->ack( $job );
        }
 
-       protected function doIsRootJobOldDuplicate( Job $job ) {
+       protected function doIsRootJobOldDuplicate( IJobSpecification $job ) {
                $signature = $job->getRootJobParams()['rootJobSignature'];
                $partition = $this->partitionRing->getLiveLocation( $signature );
                try {
index 4bac304..83e5fb2 100644 (file)
@@ -234,7 +234,7 @@ class JobQueueGroup {
         * @param int|string $qtype JobQueueGroup::TYPE_* constant or job type string
         * @param int $flags Bitfield of JobQueueGroup::USE_* constants
         * @param array $blacklist List of job types to ignore
-        * @return Job|bool Returns false on failure
+        * @return RunnableJob|bool Returns false on failure
         */
        public function pop( $qtype = self::TYPE_DEFAULT, $flags = 0, array $blacklist = [] ) {
                global $wgJobClasses;
index cbcd4fb..cb20a76 100644 (file)
@@ -111,7 +111,7 @@ class JobQueueMemory extends JobQueue {
        /**
         * @see JobQueue::doPop
         *
-        * @return Job|bool
+        * @return RunnableJob|bool
         */
        protected function doPop() {
                if ( $this->doGetSize() == 0 ) {
@@ -143,9 +143,9 @@ class JobQueueMemory extends JobQueue {
        /**
         * @see JobQueue::doAck
         *
-        * @param Job $job
+        * @param RunnableJob $job
         */
-       protected function doAck( Job $job ) {
+       protected function doAck( RunnableJob $job ) {
                if ( $this->getAcquiredCount() == 0 ) {
                        return;
                }
@@ -206,11 +206,10 @@ class JobQueueMemory extends JobQueue {
 
        /**
         * @param IJobSpecification $spec
-        *
-        * @return Job
+        * @return RunnableJob
         */
        public function jobFromSpecInternal( IJobSpecification $spec ) {
-               return Job::factory( $spec->getType(), $spec->getTitle(), $spec->getParams() );
+               return $this->factoryJob( $spec->getType(), $spec->getParams() );
        }
 
        /**
index 98a5491..8864688 100644 (file)
@@ -307,7 +307,7 @@ LUA;
 
        /**
         * @see JobQueue::doPop()
-        * @return Job|bool
+        * @return RunnableJob|bool
         * @throws JobQueueError
         */
        protected function doPop() {
@@ -379,12 +379,12 @@ LUA;
 
        /**
         * @see JobQueue::doAck()
-        * @param Job $job
-        * @return Job|bool
+        * @param RunnableJob $job
+        * @return RunnableJob|bool
         * @throws UnexpectedValueException
         * @throws JobQueueError
         */
-       protected function doAck( Job $job ) {
+       protected function doAck( RunnableJob $job ) {
                $uuid = $job->getMetadata( 'uuid' );
                if ( $uuid === null ) {
                        throw new UnexpectedValueException( "Job of type '{$job->getType()}' has no UUID." );
@@ -463,11 +463,11 @@ LUA;
 
        /**
         * @see JobQueue::doIsRootJobOldDuplicate()
-        * @param Job $job
+        * @param IJobSpecification $job
         * @return bool
         * @throws JobQueueError
         */
-       protected function doIsRootJobOldDuplicate( Job $job ) {
+       protected function doIsRootJobOldDuplicate( IJobSpecification $job ) {
                if ( !$job->hasRootJobParams() ) {
                        return false; // job has no de-deplication info
                }
@@ -627,7 +627,7 @@ LUA;
         *
         * @param string $uid
         * @param RedisConnRef $conn
-        * @return Job|bool Returns false if the job does not exist
+        * @return RunnableJob|bool Returns false if the job does not exist
         * @throws JobQueueError
         * @throws UnexpectedValueException
         */
@@ -641,8 +641,10 @@ LUA;
                        if ( !is_array( $item ) ) { // this shouldn't happen
                                throw new UnexpectedValueException( "Could not find job with ID '$uid'." );
                        }
-                       $title = Title::makeTitle( $item['namespace'], $item['title'] );
-                       $job = Job::factory( $item['type'], $title, $item['params'] );
+
+                       $params = $item['params'];
+                       $params += [ 'namespace' => $item['namespace'], 'title' => $item['title'] ];
+                       $job = $this->factoryJob( $item['type'], $params );
                        $job->setMetadata( 'uuid', $item['uuid'] );
                        $job->setMetadata( 'timestamp', $item['timestamp'] );
                        // Add in attempt count for debugging at showJobs.php
@@ -684,8 +686,8 @@ LUA;
                return [
                        // Fields that describe the nature of the job
                        'type' => $job->getType(),
-                       'namespace' => $job->getTitle()->getNamespace(),
-                       'title' => $job->getTitle()->getDBkey(),
+                       'namespace' => $job->getParams()['namespace'] ?? NS_SPECIAL,
+                       'title' => $job->getParams()['title'] ?? '',
                        'params' => $job->getParams(),
                        // Some jobs cannot run until a "release timestamp"
                        'rtimestamp' => $job->getReleaseTimestamp() ?: 0,
@@ -700,11 +702,13 @@ LUA;
 
        /**
         * @param array $fields
-        * @return Job|bool
+        * @return RunnableJob|bool
         */
        protected function getJobFromFields( array $fields ) {
-               $title = Title::makeTitle( $fields['namespace'], $fields['title'] );
-               $job = Job::factory( $fields['type'], $title, $fields['params'] );
+               $params = $fields['params'];
+               $params += [ 'namespace' => $fields['namespace'], 'title' => $fields['title'] ];
+
+               $job = $this->factoryJob( $fields['type'], $params );
                $job->setMetadata( 'uuid', $fields['uuid'] );
                $job->setMetadata( 'timestamp', $fields['timestamp'] );
 
index b04aa83..80a46d0 100644 (file)
@@ -28,8 +28,7 @@
  * $job = new JobSpecification(
  *             'null',
  *             array( 'lives' => 1, 'usleep' => 100, 'pi' => 3.141569 ),
- *             array( 'removeDuplicates' => 1 ),
- *             Title::makeTitle( NS_SPECIAL, 'nullity' )
+ *             array( 'removeDuplicates' => 1 )
  * );
  * JobQueueGroup::singleton()->push( $job )
  * @endcode
@@ -63,8 +62,19 @@ class JobSpecification implements IJobSpecification {
                $this->validateParams( $opts );
 
                $this->type = $type;
+               if ( $title instanceof Title ) {
+                       // Make sure JobQueue classes can pull the title from parameters alone
+                       if ( $title->getDBkey() !== '' ) {
+                               $params += [
+                                       'namespace' => $title->getNamespace(),
+                                       'title' => $title->getDBkey()
+                               ];
+                       }
+               } else {
+                       $title = Title::makeTitle( NS_SPECIAL, '' );
+               }
                $this->params = $params;
-               $this->title = $title ?: Title::makeTitle( NS_SPECIAL, 'Blankpage' );
+               $this->title = $title;
                $this->opts = $opts;
        }
 
diff --git a/includes/jobqueue/RunnableJob.php b/includes/jobqueue/RunnableJob.php
new file mode 100644 (file)
index 0000000..e477b12
--- /dev/null
@@ -0,0 +1,54 @@
+<?php
+/**
+ * Job queue task instance that can be executed via a run() method
+ *
+ * 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
+ */
+
+/**
+ * Job that has a run() method and metadata accessors for JobQueue::pop() and JobQueue::ack()
+ *
+ * Instances are not only enqueueable via JobQueue::push(), but they can also be executed by
+ * by calling their run() method. When constructing a job to be enqueued via JobQueue::push(),
+ * it will not be possible to construct a RunnableJob instance if the class for that job is not
+ * loaded by the application for the local DB domain. In that case, the general-purpose
+ * JobSpecification class can be used instead.
+ *
+ * @ingroup JobQueue
+ * @since 1.33
+ */
+interface RunnableJob extends IJobSpecification {
+       /**
+        * Run the job
+        * @return bool Success
+        */
+       public function run();
+
+       /**
+        * @param string|null $field Metadata field or null to get all the metadata
+        * @return mixed|null Value; null if missing
+        */
+       public function getMetadata( $field = null );
+
+       /**
+        * @param string $field Key name to set the value for
+        * @param mixed $value The value to set the field for
+        * @return mixed|null The prior field value; null if missing
+        */
+       public function setMetadata( $field, $value );
+}
index 356eeba..d8cbf75 100644 (file)
  * @ingroup JobQueue
  * @since 1.27
  */
-class CdnPurgeJob extends Job {
-       /**
-        * @param Title $title
-        * @param array $params Job parameters (urls)
-        */
-       function __construct( Title $title, array $params ) {
-               parent::__construct( 'cdnPurge', $title, $params );
+class CdnPurgeJob extends Job implements GenericParameterJob {
+       function __construct( array $params ) {
+               parent::__construct( 'cdnPurge', $params );
                $this->removeDuplicates = false; // delay semantics are critical
        }
 
index 77adfa1..01fa46c 100644 (file)
@@ -10,7 +10,17 @@ use MediaWiki\MediaWikiServices;
  * @ingroup JobQueue
  * @since 1.31
  */
-class ClearUserWatchlistJob extends Job {
+class ClearUserWatchlistJob extends Job implements GenericParameterJob {
+       /**
+        * @param array $params
+        *  - userId,         The ID for the user whose watchlist is being cleared.
+        *  - maxWatchlistId, The maximum wl_id at the time the job was first created,
+        */
+       public function __construct( array $params ) {
+               parent::__construct( 'clearUserWatchlist', $params );
+
+               $this->removeDuplicates = true;
+       }
 
        /**
         * @param User $user User to clear the watchlist for.
@@ -19,26 +29,7 @@ class ClearUserWatchlistJob extends Job {
         * @return ClearUserWatchlistJob
         */
        public static function newForUser( User $user, $maxWatchlistId ) {
-               return new self(
-                       null,
-                       [ 'userId' => $user->getId(), 'maxWatchlistId' => $maxWatchlistId ]
-               );
-       }
-
-       /**
-        * @param Title|null $title Not used by this job.
-        * @param array $params
-        *  - userId,         The ID for the user whose watchlist is being cleared.
-        *  - maxWatchlistId, The maximum wl_id at the time the job was first created,
-        */
-       public function __construct( Title $title = null, array $params ) {
-               parent::__construct(
-                       'clearUserWatchlist',
-                       SpecialPage::getTitleFor( 'EditWatchlist', 'clear' ),
-                       $params
-               );
-
-               $this->removeDuplicates = true;
+               return new self( [ 'userId' => $user->getId(), 'maxWatchlistId' => $maxWatchlistId ] );
        }
 
        public function run() {
@@ -101,7 +92,7 @@ class ClearUserWatchlistJob extends Job {
                if ( count( $watchlistIds ) === (int)$batchSize ) {
                        // Until we get less results than the limit, recursively push
                        // the same job again.
-                       JobQueueGroup::singleton()->push( new self( $this->getTitle(), $this->getParams() ) );
+                       JobQueueGroup::singleton()->push( new self( $this->getParams() ) );
                }
 
                return true;
index 3b2c899..f53174a 100644 (file)
@@ -33,9 +33,9 @@ use MediaWiki\MediaWikiServices;
  * @ingroup JobQueue
  * @since 1.31
  */
-class ClearWatchlistNotificationsJob extends Job {
-       function __construct( Title $title, array $params ) {
-               parent::__construct( 'clearWatchlistNotifications', $title, $params );
+class ClearWatchlistNotificationsJob extends Job implements GenericParameterJob {
+       function __construct( array $params ) {
+               parent::__construct( 'clearWatchlistNotifications', $params );
 
                static $required = [ 'userId', 'casTime' ];
                $missing = implode( ', ', array_diff( $required, array_keys( $this->params ) ) );
index e6dfae4..b0ce6a5 100644 (file)
@@ -3,16 +3,13 @@
 /**
  * Class DeletePageJob
  */
-class DeletePageJob extends Job {
-       public function __construct( $title, $params = [] ) {
-               parent::__construct( 'deletePage', $title, $params );
+class DeletePageJob extends Job implements GenericParameterJob {
+       public function __construct( array $params ) {
+               parent::__construct( 'deletePage', $params );
+
+               $this->title = Title::makeTitle( $params['namespace'], $params['title'] );
        }
 
-       /**
-        * Execute the job
-        *
-        * @return bool
-        */
        public function run() {
                // Failure to load the page is not job failure.
                // A parallel deletion operation may have already completed the page deletion.
index c005a29..4231e15 100644 (file)
  *
  * @ingroup JobQueue
  */
-final class DuplicateJob extends Job {
+final class DuplicateJob extends Job implements GenericParameterJob {
        /**
         * Callers should use DuplicateJob::newFromJob() instead
         *
-        * @param Title $title
         * @param array $params Job parameters
         */
-       function __construct( Title $title, array $params ) {
-               parent::__construct( 'duplicate', $title, $params );
+       function __construct( array $params ) {
+               parent::__construct( 'duplicate', $params );
        }
 
        /**
         * Get a duplicate no-op version of a job
         *
-        * @param Job $job
+        * @param RunnableJob $job
         * @return Job
         */
-       public static function newFromJob( Job $job ) {
-               $djob = new self( $job->getTitle(), $job->getParams() );
+       public static function newFromJob( RunnableJob $job ) {
+               $djob = new self( $job->getParams() );
                $djob->command = $job->getType();
                $djob->params = is_array( $djob->params ) ? $djob->params : [];
                $djob->params = [ 'isDuplicate' => true ] + $djob->params;
-               $djob->metadata = $job->metadata;
+               $djob->metadata = $job->getMetadata();
 
                return $djob;
        }
index 72923ce..f9735d5 100644 (file)
  * @ingroup JobQueue
  * @since 1.25
  */
-final class EnqueueJob extends Job {
+final class EnqueueJob extends Job implements GenericParameterJob {
        /**
         * Callers should use the factory methods instead
         *
-        * @param Title $title
         * @param array $params Job parameters
         */
-       function __construct( Title $title, array $params ) {
-               parent::__construct( 'enqueue', $title, $params );
+       public function __construct( array $params ) {
+               parent::__construct( 'enqueue', $params );
        }
 
        /**
@@ -75,10 +74,7 @@ final class EnqueueJob extends Job {
                        }
                }
 
-               $eJob = new self(
-                       Title::makeTitle( NS_SPECIAL, 'Badtitle/' . __CLASS__ ),
-                       [ 'jobsByDomain' => $jobMapsByDomain ]
-               );
+               $eJob = new self( [ 'jobsByDomain' => $jobMapsByDomain ] );
                // If *all* jobs to be pushed are to be de-duplicated (a common case), then
                // de-duplicate this whole job itself to avoid build up in high traffic cases
                $eJob->removeDuplicates = $deduplicate;
index 80826fe..01afe6f 100644 (file)
@@ -31,7 +31,7 @@
  * @code
  * $ php maintenance/eval.php
  * > $queue = JobQueueGroup::singleton();
- * > $job = new NullJob( Title::newMainPage(), [ 'lives' => 10 ] );
+ * > $job = new NullJob( [ 'lives' => 10 ] );
  * > $queue->push( $job );
  * @endcode
  * You can then confirm the job has been enqueued by using the showJobs.php
  *
  * @ingroup JobQueue
  */
-class NullJob extends Job {
+class NullJob extends Job implements GenericParameterJob {
        /**
-        * @param Title $title
         * @param array $params Job parameters (lives, usleep)
         */
-       function __construct( Title $title, array $params ) {
-               parent::__construct( 'null', $title, $params );
+       function __construct( array $params ) {
+               parent::__construct( 'null', $params );
                if ( !isset( $this->params['lives'] ) ) {
                        $this->params['lives'] = 1;
                }
@@ -67,7 +66,7 @@ class NullJob extends Job {
                if ( $this->params['lives'] > 1 ) {
                        $params = $this->params;
                        $params['lives']--;
-                       $job = new self( $this->title, $params );
+                       $job = new self( $params );
                        JobQueueGroup::singleton()->push( $job );
                }
 
index bd0df5b..ac8f94a 100644 (file)
@@ -21,9 +21,9 @@
  * @ingroup JobQueue
  */
 
-class UserGroupExpiryJob extends Job {
-       public function __construct( $params = [] ) {
-               parent::__construct( 'userGroupExpiry', Title::newMainPage(), $params );
+class UserGroupExpiryJob extends Job implements GenericParameterJob {
+       public function __construct( array $params = [] ) {
+               parent::__construct( 'userGroupExpiry', $params );
                $this->removeDuplicates = true;
        }
 
index e238887..ccc76bb 100644 (file)
@@ -151,13 +151,11 @@ class ArrayUtils {
         * @since 1.23
         *
         * @param array $array1 The array to compare from
-        * @param array $array2,... More arrays to compare against
+        * @param array ...$arrays More arrays to compare against
         * @return array An array containing all the values from array1
         *               that are not present in any of the other arrays.
         */
-       public static function arrayDiffAssocRecursive( $array1 ) {
-               $arrays = func_get_args();
-               array_shift( $arrays );
+       public static function arrayDiffAssocRecursive( $array1, ...$arrays ) {
                $ret = [];
 
                foreach ( $array1 as $key => $value ) {
index 3129c5b..7a90082 100644 (file)
@@ -407,14 +407,14 @@ class CSSMin {
                        // FIXME: Simplify now we only support PHP 7.0.0+
                        // Note: PCRE doesn't support multiple capture groups with the same name by default.
                        // - PCRE 6.7 introduced the "J" modifier (PCRE_INFO_JCHANGED for PCRE_DUPNAMES).
-                       //   https://secure.php.net/manual/en/reference.pcre.pattern.modifiers.php
+                       //   https://www.php.net/manual/en/reference.pcre.pattern.modifiers.php
                        //   However this isn't useful since it just ignores all but the first one.
                        //   Also, while the modifier was introduced in PCRE 6.7 (PHP 5.2+) it was
                        //   not exposed to public preg_* functions until PHP 5.6.0.
                        // - PCRE 8.36 fixed this to work as expected (e.g. merge conceptually to
                        //   only return the one matched in the part that actually matched).
                        //   However MediaWiki supports 5.5.9, which has PCRE 8.32
-                       //   Per https://secure.php.net/manual/en/pcre.installation.php:
+                       //   Per https://www.php.net/manual/en/pcre.installation.php:
                        //   - PCRE 8.32 (PHP 5.5.0)
                        //   - PCRE 8.34 (PHP 5.5.10, PHP 5.6.0)
                        //   - PCRE 8.37 (PHP 5.5.26, PHP 5.6.9, PHP 7.0.0)
diff --git a/includes/libs/CryptRand.php b/includes/libs/CryptRand.php
deleted file mode 100644 (file)
index da0cae2..0000000
+++ /dev/null
@@ -1,119 +0,0 @@
-<?php
-/**
- * A cryptographic random generator class used for generating secret keys
- *
- * This is based in part on Drupal code as well as what we used in our own code
- * prior to introduction of this class.
- *
- * 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
- *
- * @author Daniel Friesen
- * @file
- */
-
-/**
- * @deprecated since 1.32, use random_bytes()/random_int()
- */
-class CryptRand {
-       /**
-        * @deprecated since 1.32, unused
-        */
-       const MIN_ITERATIONS = 1000;
-
-       /**
-        * @deprecated since 1.32, unused
-        */
-       const MSEC_PER_BYTE = 0.5;
-
-       /**
-        * Initialize an initial random state based off of whatever we can find
-        *
-        * @deprecated since 1.32, unused and does nothing
-        *
-        * @return string
-        */
-       protected function initialRandomState() {
-               wfDeprecated( __METHOD__, '1.32' );
-               return '';
-       }
-
-       /**
-        * Randomly hash data while mixing in clock drift data for randomness
-        *
-        * @deprecated since 1.32, unused and does nothing
-        *
-        * @param string $data The data to randomly hash.
-        * @return string The hashed bytes
-        * @author Tim Starling
-        */
-       protected function driftHash( $data ) {
-               wfDeprecated( __METHOD__, '1.32' );
-               return '';
-       }
-
-       /**
-        * Return a rolling random state initially build using data from unstable sources
-        *
-        * @deprecated since 1.32, unused and does nothing
-        *
-        * @return string A new weak random state
-        */
-       protected function randomState() {
-               wfDeprecated( __METHOD__, '1.32' );
-               return '';
-       }
-
-       /**
-        * Return a boolean indicating whether or not the source used for cryptographic
-        * random bytes generation in the previously run generate* call
-        * was cryptographically strong.
-        *
-        * @deprecated since 1.32, always returns true
-        *
-        * @return bool Always true
-        */
-       public function wasStrong() {
-               wfDeprecated( __METHOD__, '1.32' );
-               return true;
-       }
-
-       /**
-        * Generate a run of cryptographically random data and return
-        * it in raw binary form.
-        * You can use CryptRand::wasStrong() if you wish to know if the source used
-        * was cryptographically strong.
-        *
-        * @param int $bytes The number of bytes of random data to generate
-        * @return string Raw binary random data
-        */
-       public function generate( $bytes ) {
-               wfDeprecated( __METHOD__, '1.32' );
-               $bytes = floor( $bytes );
-               return random_bytes( $bytes );
-       }
-
-       /**
-        * Generate a run of cryptographically random data and return
-        * it in hexadecimal string format.
-        *
-        * @param int $chars The number of hex chars of random data to generate
-        * @return string Hexadecimal random data
-        */
-       public function generateHex( $chars ) {
-               wfDeprecated( __METHOD__, '1.32' );
-               return MWCryptRand::generateHex( $chars );
-       }
-}
index 9470413..7de60b1 100644 (file)
@@ -36,12 +36,11 @@ class DeferredStringifier {
 
        /**
         * @param callable $callback Callback that gets called by __toString
-        * @param mixed $param,... Parameters to the callback
+        * @param mixed ...$params Parameters to the callback
         */
-       public function __construct( $callback /*...*/ ) {
-               $this->params = func_get_args();
-               array_shift( $this->params );
+       public function __construct( $callback, ...$params ) {
                $this->callback = $callback;
+               $this->params = $params;
        }
 
        /**
index e9d1fc1..5e7485c 100644 (file)
@@ -135,11 +135,11 @@ class MemoizedCallable {
         *
         * Like MemoizedCallable::invokeArgs(), but variadic.
         *
-        * @param mixed $params,... Parameters for memoized function or method.
+        * @param mixed ...$params Parameters for memoized function or method.
         * @return mixed The memoized callable's return value.
         */
-       public function invoke() {
-               return $this->invokeArgs( func_get_args() );
+       public function invoke( ...$params ) {
+               return $this->invokeArgs( $params );
        }
 
        /**
index f3c1f01..593e617 100644 (file)
@@ -1,4 +1,24 @@
 <?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 FileBackend
+ */
+
 /**
  * File system based backend.
  *
@@ -101,7 +121,7 @@ class FSFileBackend extends FileBackendStore {
        public function getFeatures() {
                if ( $this->isWindows && version_compare( PHP_VERSION, '7.1', 'lt' ) ) {
                        // PHP before 7.1 used 8-bit code page for filesystem paths on Windows;
-                       // See https://secure.php.net/manual/en/migration71.windows-support.php
+                       // See https://www.php.net/manual/en/migration71.windows-support.php
                        return 0;
                } else {
                        return FileBackend::ATTR_UNICODE_PATHS;
@@ -802,189 +822,3 @@ class FSFileBackend extends FileBackendStore {
                return true; // suppress from PHP handler
        }
 }
-
-/**
- * @see FileBackendStoreOpHandle
- */
-class FSFileOpHandle extends FileBackendStoreOpHandle {
-       public $cmd; // string; shell command
-       public $chmodPath; // string; file to chmod
-
-       /**
-        * @param FSFileBackend $backend
-        * @param array $params
-        * @param callable $call
-        * @param string $cmd
-        * @param int|null $chmodPath
-        */
-       public function __construct(
-               FSFileBackend $backend, array $params, $call, $cmd, $chmodPath = null
-       ) {
-               $this->backend = $backend;
-               $this->params = $params;
-               $this->call = $call;
-               $this->cmd = $cmd;
-               $this->chmodPath = $chmodPath;
-       }
-}
-
-/**
- * Wrapper around RecursiveDirectoryIterator/DirectoryIterator that
- * catches exception or does any custom behavoir that we may want.
- * Do not use this class from places outside FSFileBackend.
- *
- * @ingroup FileBackend
- */
-abstract class FSFileBackendList implements Iterator {
-       /** @var Iterator */
-       protected $iter;
-
-       /** @var int */
-       protected $suffixStart;
-
-       /** @var int */
-       protected $pos = 0;
-
-       /** @var array */
-       protected $params = [];
-
-       /**
-        * @param string $dir File system directory
-        * @param array $params
-        */
-       public function __construct( $dir, array $params ) {
-               $path = realpath( $dir ); // normalize
-               if ( $path === false ) {
-                       $path = $dir;
-               }
-               $this->suffixStart = strlen( $path ) + 1; // size of "path/to/dir/"
-               $this->params = $params;
-
-               try {
-                       $this->iter = $this->initIterator( $path );
-               } catch ( UnexpectedValueException $e ) {
-                       $this->iter = null; // bad permissions? deleted?
-               }
-       }
-
-       /**
-        * Return an appropriate iterator object to wrap
-        *
-        * @param string $dir File system directory
-        * @return Iterator
-        */
-       protected function initIterator( $dir ) {
-               if ( !empty( $this->params['topOnly'] ) ) { // non-recursive
-                       # Get an iterator that will get direct sub-nodes
-                       return new DirectoryIterator( $dir );
-               } else { // recursive
-                       # Get an iterator that will return leaf nodes (non-directories)
-                       # RecursiveDirectoryIterator extends FilesystemIterator.
-                       # FilesystemIterator::SKIP_DOTS default is inconsistent in PHP 5.3.x.
-                       $flags = FilesystemIterator::CURRENT_AS_SELF | FilesystemIterator::SKIP_DOTS;
-
-                       return new RecursiveIteratorIterator(
-                               new RecursiveDirectoryIterator( $dir, $flags ),
-                               RecursiveIteratorIterator::CHILD_FIRST // include dirs
-                       );
-               }
-       }
-
-       /**
-        * @see Iterator::key()
-        * @return int
-        */
-       public function key() {
-               return $this->pos;
-       }
-
-       /**
-        * @see Iterator::current()
-        * @return string|bool String or false
-        */
-       public function current() {
-               return $this->getRelPath( $this->iter->current()->getPathname() );
-       }
-
-       /**
-        * @see Iterator::next()
-        * @throws FileBackendError
-        */
-       public function next() {
-               try {
-                       $this->iter->next();
-                       $this->filterViaNext();
-               } catch ( UnexpectedValueException $e ) { // bad permissions? deleted?
-                       throw new FileBackendError( "File iterator gave UnexpectedValueException." );
-               }
-               ++$this->pos;
-       }
-
-       /**
-        * @see Iterator::rewind()
-        * @throws FileBackendError
-        */
-       public function rewind() {
-               $this->pos = 0;
-               try {
-                       $this->iter->rewind();
-                       $this->filterViaNext();
-               } catch ( UnexpectedValueException $e ) { // bad permissions? deleted?
-                       throw new FileBackendError( "File iterator gave UnexpectedValueException." );
-               }
-       }
-
-       /**
-        * @see Iterator::valid()
-        * @return bool
-        */
-       public function valid() {
-               return $this->iter && $this->iter->valid();
-       }
-
-       /**
-        * Filter out items by advancing to the next ones
-        */
-       protected function filterViaNext() {
-       }
-
-       /**
-        * Return only the relative path and normalize slashes to FileBackend-style.
-        * Uses the "real path" since the suffix is based upon that.
-        *
-        * @param string $dir
-        * @return string
-        */
-       protected function getRelPath( $dir ) {
-               $path = realpath( $dir );
-               if ( $path === false ) {
-                       $path = $dir;
-               }
-
-               return strtr( substr( $path, $this->suffixStart ), '\\', '/' );
-       }
-}
-
-class FSFileBackendDirList extends FSFileBackendList {
-       protected function filterViaNext() {
-               while ( $this->iter->valid() ) {
-                       if ( $this->iter->current()->isDot() || !$this->iter->current()->isDir() ) {
-                               $this->iter->next(); // skip non-directories and dot files
-                       } else {
-                               break;
-                       }
-               }
-       }
-}
-
-class FSFileBackendFileList extends FSFileBackendList {
-       protected function filterViaNext() {
-               while ( $this->iter->valid() ) {
-                       if ( !$this->iter->current()->isFile() ) {
-                               $this->iter->next(); // skip non-files and dot files
-                       } else {
-                               break;
-                       }
-               }
-       }
-}
index 7bc3045..53a0ca0 100644 (file)
@@ -1575,11 +1575,10 @@ abstract class FileBackend implements LoggerAwareInterface {
         *   - StatusValue::newGood() if this method is called without parameters
         *   - StatusValue::newFatal() with all parameters to this method if passed in
         *
-        * @param string $args,...
+        * @param string ...$args
         * @return StatusValue
         */
-       final protected function newStatus() {
-               $args = func_get_args();
+       final protected function newStatus( ...$args ) {
                if ( count( $args ) ) {
                        $sv = StatusValue::newFatal( ...$args );
                } else {
diff --git a/includes/libs/filebackend/FileBackendError.php b/includes/libs/filebackend/FileBackendError.php
deleted file mode 100644 (file)
index e233535..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-<?php
-/**
- * File backend exception for checked exceptions (e.g. I/O errors)
- *
- * @ingroup FileBackend
- * @since 1.22
- */
-class FileBackendError extends Exception {
-}
index 97da557..3663637 100644 (file)
@@ -1866,133 +1866,3 @@ abstract class FileBackendStore extends FileBackend {
                return $mime ?: 'unknown/unknown';
        }
 }
-
-/**
- * FileBackendStore helper class for performing asynchronous file operations.
- *
- * For example, calling FileBackendStore::createInternal() with the "async"
- * param flag may result in a StatusValue that contains this object as a value.
- * This class is largely backend-specific and is mostly just "magic" to be
- * passed to FileBackendStore::executeOpHandlesInternal().
- */
-abstract class FileBackendStoreOpHandle {
-       /** @var array */
-       public $params = []; // params to caller functions
-       /** @var FileBackendStore */
-       public $backend;
-       /** @var array */
-       public $resourcesToClose = [];
-
-       public $call; // string; name that identifies the function called
-
-       /**
-        * Close all open file handles
-        */
-       public function closeResources() {
-               array_map( 'fclose', $this->resourcesToClose );
-       }
-}
-
-/**
- * FileBackendStore helper function to handle listings that span container shards.
- * Do not use this class from places outside of FileBackendStore.
- *
- * @ingroup FileBackend
- */
-abstract class FileBackendStoreShardListIterator extends FilterIterator {
-       /** @var FileBackendStore */
-       protected $backend;
-
-       /** @var array */
-       protected $params;
-
-       /** @var string Full container name */
-       protected $container;
-
-       /** @var string Resolved relative path */
-       protected $directory;
-
-       /** @var array */
-       protected $multiShardPaths = []; // (rel path => 1)
-
-       /**
-        * @param FileBackendStore $backend
-        * @param string $container Full storage container name
-        * @param string $dir Storage directory relative to container
-        * @param array $suffixes List of container shard suffixes
-        * @param array $params
-        */
-       public function __construct(
-               FileBackendStore $backend, $container, $dir, array $suffixes, array $params
-       ) {
-               $this->backend = $backend;
-               $this->container = $container;
-               $this->directory = $dir;
-               $this->params = $params;
-
-               $iter = new AppendIterator();
-               foreach ( $suffixes as $suffix ) {
-                       $iter->append( $this->listFromShard( $this->container . $suffix ) );
-               }
-
-               parent::__construct( $iter );
-       }
-
-       public function accept() {
-               $rel = $this->getInnerIterator()->current(); // path relative to given directory
-               $path = $this->params['dir'] . "/{$rel}"; // full storage path
-               if ( $this->backend->isSingleShardPathInternal( $path ) ) {
-                       return true; // path is only on one shard; no issue with duplicates
-               } elseif ( isset( $this->multiShardPaths[$rel] ) ) {
-                       // Don't keep listing paths that are on multiple shards
-                       return false;
-               } else {
-                       $this->multiShardPaths[$rel] = 1;
-
-                       return true;
-               }
-       }
-
-       public function rewind() {
-               parent::rewind();
-               $this->multiShardPaths = [];
-       }
-
-       /**
-        * Get the list for a given container shard
-        *
-        * @param string $container Resolved container name
-        * @return Iterator
-        */
-       abstract protected function listFromShard( $container );
-}
-
-/**
- * Iterator for listing directories
- */
-class FileBackendStoreShardDirIterator extends FileBackendStoreShardListIterator {
-       protected function listFromShard( $container ) {
-               $list = $this->backend->getDirectoryListInternal(
-                       $container, $this->directory, $this->params );
-               if ( $list === null ) {
-                       return new ArrayIterator( [] );
-               } else {
-                       return is_array( $list ) ? new ArrayIterator( $list ) : $list;
-               }
-       }
-}
-
-/**
- * Iterator for listing regular files
- */
-class FileBackendStoreShardFileIterator extends FileBackendStoreShardListIterator {
-       protected function listFromShard( $container ) {
-               $list = $this->backend->getFileListInternal(
-                       $container, $this->directory, $this->params );
-               if ( $list === null ) {
-                       return new ArrayIterator( [] );
-               } else {
-                       return is_array( $list ) ? new ArrayIterator( $list ) : $list;
-               }
-       }
-}
index 61b4d69..4ba1e1c 100644 (file)
@@ -1803,180 +1803,3 @@ class SwiftFileBackend extends FileBackendStore {
                $this->logger->error( $msg, $msgParams );
        }
 }
-
-/**
- * @see FileBackendStoreOpHandle
- */
-class SwiftFileOpHandle extends FileBackendStoreOpHandle {
-       /** @var array List of Requests for MultiHttpClient */
-       public $httpOp;
-       /** @var Closure */
-       public $callback;
-
-       /**
-        * @param SwiftFileBackend $backend
-        * @param Closure $callback Function that takes (HTTP request array, status)
-        * @param array $httpOp MultiHttpClient op
-        */
-       public function __construct( SwiftFileBackend $backend, Closure $callback, array $httpOp ) {
-               $this->backend = $backend;
-               $this->callback = $callback;
-               $this->httpOp = $httpOp;
-       }
-}
-
-/**
- * SwiftFileBackend helper class to page through listings.
- * Swift also has a listing limit of 10,000 objects for sanity.
- * Do not use this class from places outside SwiftFileBackend.
- *
- * @ingroup FileBackend
- */
-abstract class SwiftFileBackendList implements Iterator {
-       /** @var array List of path or (path,stat array) entries */
-       protected $bufferIter = [];
-
-       /** @var string List items *after* this path */
-       protected $bufferAfter = null;
-
-       /** @var int */
-       protected $pos = 0;
-
-       /** @var array */
-       protected $params = [];
-
-       /** @var SwiftFileBackend */
-       protected $backend;
-
-       /** @var string Container name */
-       protected $container;
-
-       /** @var string Storage directory */
-       protected $dir;
-
-       /** @var int */
-       protected $suffixStart;
-
-       const PAGE_SIZE = 9000; // file listing buffer size
-
-       /**
-        * @param SwiftFileBackend $backend
-        * @param string $fullCont Resolved container name
-        * @param string $dir Resolved directory relative to container
-        * @param array $params
-        */
-       public function __construct( SwiftFileBackend $backend, $fullCont, $dir, array $params ) {
-               $this->backend = $backend;
-               $this->container = $fullCont;
-               $this->dir = $dir;
-               if ( substr( $this->dir, -1 ) === '/' ) {
-                       $this->dir = substr( $this->dir, 0, -1 ); // remove trailing slash
-               }
-               if ( $this->dir == '' ) { // whole container
-                       $this->suffixStart = 0;
-               } else { // dir within container
-                       $this->suffixStart = strlen( $this->dir ) + 1; // size of "path/to/dir/"
-               }
-               $this->params = $params;
-       }
-
-       /**
-        * @see Iterator::key()
-        * @return int
-        */
-       public function key() {
-               return $this->pos;
-       }
-
-       /**
-        * @see Iterator::next()
-        */
-       public function next() {
-               // Advance to the next file in the page
-               next( $this->bufferIter );
-               ++$this->pos;
-               // Check if there are no files left in this page and
-               // advance to the next page if this page was not empty.
-               if ( !$this->valid() && count( $this->bufferIter ) ) {
-                       $this->bufferIter = $this->pageFromList(
-                               $this->container, $this->dir, $this->bufferAfter, self::PAGE_SIZE, $this->params
-                       ); // updates $this->bufferAfter
-               }
-       }
-
-       /**
-        * @see Iterator::rewind()
-        */
-       public function rewind() {
-               $this->pos = 0;
-               $this->bufferAfter = null;
-               $this->bufferIter = $this->pageFromList(
-                       $this->container, $this->dir, $this->bufferAfter, self::PAGE_SIZE, $this->params
-               ); // updates $this->bufferAfter
-       }
-
-       /**
-        * @see Iterator::valid()
-        * @return bool
-        */
-       public function valid() {
-               if ( $this->bufferIter === null ) {
-                       return false; // some failure?
-               } else {
-                       return ( current( $this->bufferIter ) !== false ); // no paths can have this value
-               }
-       }
-
-       /**
-        * Get the given list portion (page)
-        *
-        * @param string $container Resolved container name
-        * @param string $dir Resolved path relative to container
-        * @param string &$after
-        * @param int $limit
-        * @param array $params
-        * @return Traversable|array
-        */
-       abstract protected function pageFromList( $container, $dir, &$after, $limit, array $params );
-}
-
-/**
- * Iterator for listing directories
- */
-class SwiftFileBackendDirList extends SwiftFileBackendList {
-       /**
-        * @see Iterator::current()
-        * @return string|bool String (relative path) or false
-        */
-       public function current() {
-               return substr( current( $this->bufferIter ), $this->suffixStart, -1 );
-       }
-
-       protected function pageFromList( $container, $dir, &$after, $limit, array $params ) {
-               return $this->backend->getDirListPageInternal( $container, $dir, $after, $limit, $params );
-       }
-}
-
-/**
- * Iterator for listing regular files
- */
-class SwiftFileBackendFileList extends SwiftFileBackendList {
-       /**
-        * @see Iterator::current()
-        * @return string|bool String (relative path) or false
-        */
-       public function current() {
-               list( $path, $stat ) = current( $this->bufferIter );
-               $relPath = substr( $path, $this->suffixStart );
-               if ( is_array( $stat ) ) {
-                       $storageDir = rtrim( $this->params['dir'], '/' );
-                       $this->backend->loadListingStatInternal( "$storageDir/$relPath", $stat );
-               }
-
-               return $relPath;
-       }
-
-       protected function pageFromList( $container, $dir, &$after, $limit, array $params ) {
-               return $this->backend->getFileListPageInternal( $container, $dir, $after, $limit, $params );
-       }
-}
diff --git a/includes/libs/filebackend/exception/FileBackendError.php b/includes/libs/filebackend/exception/FileBackendError.php
new file mode 100644 (file)
index 0000000..e233535
--- /dev/null
@@ -0,0 +1,9 @@
+<?php
+/**
+ * File backend exception for checked exceptions (e.g. I/O errors)
+ *
+ * @ingroup FileBackend
+ * @since 1.22
+ */
+class FileBackendError extends Exception {
+}
diff --git a/includes/libs/filebackend/fileiteration/FSFileBackendDirList.php b/includes/libs/filebackend/fileiteration/FSFileBackendDirList.php
new file mode 100644 (file)
index 0000000..0545d9f
--- /dev/null
@@ -0,0 +1,32 @@
+<?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 FileBackend
+ */
+
+class FSFileBackendDirList extends FSFileBackendList {
+       protected function filterViaNext() {
+               while ( $this->iter->valid() ) {
+                       if ( $this->iter->current()->isDot() || !$this->iter->current()->isDir() ) {
+                               $this->iter->next(); // skip non-directories and dot files
+                       } else {
+                               break;
+                       }
+               }
+       }
+}
diff --git a/includes/libs/filebackend/fileiteration/FSFileBackendFileList.php b/includes/libs/filebackend/fileiteration/FSFileBackendFileList.php
new file mode 100644 (file)
index 0000000..980e999
--- /dev/null
@@ -0,0 +1,32 @@
+<?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 FileBackend
+ */
+
+class FSFileBackendFileList extends FSFileBackendList {
+       protected function filterViaNext() {
+               while ( $this->iter->valid() ) {
+                       if ( !$this->iter->current()->isFile() ) {
+                               $this->iter->next(); // skip non-files and dot files
+                       } else {
+                               break;
+                       }
+               }
+       }
+}
diff --git a/includes/libs/filebackend/fileiteration/FSFileBackendList.php b/includes/libs/filebackend/fileiteration/FSFileBackendList.php
new file mode 100644 (file)
index 0000000..9194efe
--- /dev/null
@@ -0,0 +1,157 @@
+<?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 FileBackend
+ */
+
+/**
+ * Wrapper around RecursiveDirectoryIterator/DirectoryIterator that
+ * catches exception or does any custom behavoir that we may want.
+ * Do not use this class from places outside FSFileBackend.
+ *
+ * @ingroup FileBackend
+ */
+abstract class FSFileBackendList implements Iterator {
+       /** @var Iterator */
+       protected $iter;
+
+       /** @var int */
+       protected $suffixStart;
+
+       /** @var int */
+       protected $pos = 0;
+
+       /** @var array */
+       protected $params = [];
+
+       /**
+        * @param string $dir File system directory
+        * @param array $params
+        */
+       public function __construct( $dir, array $params ) {
+               $path = realpath( $dir ); // normalize
+               if ( $path === false ) {
+                       $path = $dir;
+               }
+               $this->suffixStart = strlen( $path ) + 1; // size of "path/to/dir/"
+               $this->params = $params;
+
+               try {
+                       $this->iter = $this->initIterator( $path );
+               } catch ( UnexpectedValueException $e ) {
+                       $this->iter = null; // bad permissions? deleted?
+               }
+       }
+
+       /**
+        * Return an appropriate iterator object to wrap
+        *
+        * @param string $dir File system directory
+        * @return Iterator
+        */
+       protected function initIterator( $dir ) {
+               if ( !empty( $this->params['topOnly'] ) ) { // non-recursive
+                       # Get an iterator that will get direct sub-nodes
+                       return new DirectoryIterator( $dir );
+               } else { // recursive
+                       # Get an iterator that will return leaf nodes (non-directories)
+                       # RecursiveDirectoryIterator extends FilesystemIterator.
+                       # FilesystemIterator::SKIP_DOTS default is inconsistent in PHP 5.3.x.
+                       $flags = FilesystemIterator::CURRENT_AS_SELF | FilesystemIterator::SKIP_DOTS;
+
+                       return new RecursiveIteratorIterator(
+                               new RecursiveDirectoryIterator( $dir, $flags ),
+                               RecursiveIteratorIterator::CHILD_FIRST // include dirs
+                       );
+               }
+       }
+
+       /**
+        * @see Iterator::key()
+        * @return int
+        */
+       public function key() {
+               return $this->pos;
+       }
+
+       /**
+        * @see Iterator::current()
+        * @return string|bool String or false
+        */
+       public function current() {
+               return $this->getRelPath( $this->iter->current()->getPathname() );
+       }
+
+       /**
+        * @see Iterator::next()
+        * @throws FileBackendError
+        */
+       public function next() {
+               try {
+                       $this->iter->next();
+                       $this->filterViaNext();
+               } catch ( UnexpectedValueException $e ) { // bad permissions? deleted?
+                       throw new FileBackendError( "File iterator gave UnexpectedValueException." );
+               }
+               ++$this->pos;
+       }
+
+       /**
+        * @see Iterator::rewind()
+        * @throws FileBackendError
+        */
+       public function rewind() {
+               $this->pos = 0;
+               try {
+                       $this->iter->rewind();
+                       $this->filterViaNext();
+               } catch ( UnexpectedValueException $e ) { // bad permissions? deleted?
+                       throw new FileBackendError( "File iterator gave UnexpectedValueException." );
+               }
+       }
+
+       /**
+        * @see Iterator::valid()
+        * @return bool
+        */
+       public function valid() {
+               return $this->iter && $this->iter->valid();
+       }
+
+       /**
+        * Filter out items by advancing to the next ones
+        */
+       protected function filterViaNext() {
+       }
+
+       /**
+        * Return only the relative path and normalize slashes to FileBackend-style.
+        * Uses the "real path" since the suffix is based upon that.
+        *
+        * @param string $dir
+        * @return string
+        */
+       protected function getRelPath( $dir ) {
+               $path = realpath( $dir );
+               if ( $path === false ) {
+                       $path = $dir;
+               }
+
+               return strtr( substr( $path, $this->suffixStart ), '\\', '/' );
+       }
+}
diff --git a/includes/libs/filebackend/fileiteration/FileBackendStoreShardDirIterator.php b/includes/libs/filebackend/fileiteration/FileBackendStoreShardDirIterator.php
new file mode 100644 (file)
index 0000000..cd8ebff
--- /dev/null
@@ -0,0 +1,35 @@
+<?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 FileBackend
+ */
+
+/**
+ * Iterator for listing directories
+ */
+class FileBackendStoreShardDirIterator extends FileBackendStoreShardListIterator {
+       protected function listFromShard( $container ) {
+               $list = $this->backend->getDirectoryListInternal(
+                       $container, $this->directory, $this->params );
+               if ( $list === null ) {
+                       return new ArrayIterator( [] );
+               } else {
+                       return is_array( $list ) ? new ArrayIterator( $list ) : $list;
+               }
+       }
+}
diff --git a/includes/libs/filebackend/fileiteration/FileBackendStoreShardFileIterator.php b/includes/libs/filebackend/fileiteration/FileBackendStoreShardFileIterator.php
new file mode 100644 (file)
index 0000000..d65a5d7
--- /dev/null
@@ -0,0 +1,35 @@
+<?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 FileBackend
+ */
+
+/**
+ * Iterator for listing regular files
+ */
+class FileBackendStoreShardFileIterator extends FileBackendStoreShardListIterator {
+       protected function listFromShard( $container ) {
+               $list = $this->backend->getFileListInternal(
+                       $container, $this->directory, $this->params );
+               if ( $list === null ) {
+                       return new ArrayIterator( [] );
+               } else {
+                       return is_array( $list ) ? new ArrayIterator( $list ) : $list;
+               }
+       }
+}
diff --git a/includes/libs/filebackend/fileiteration/FileBackendStoreShardListIterator.php b/includes/libs/filebackend/fileiteration/FileBackendStoreShardListIterator.php
new file mode 100644 (file)
index 0000000..5f6b762
--- /dev/null
@@ -0,0 +1,94 @@
+<?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 FileBackend
+ */
+
+/**
+ * FileBackendStore helper function to handle listings that span container shards.
+ * Do not use this class from places outside of FileBackendStore.
+ *
+ * @ingroup FileBackend
+ */
+abstract class FileBackendStoreShardListIterator extends FilterIterator {
+       /** @var FileBackendStore */
+       protected $backend;
+
+       /** @var array */
+       protected $params;
+
+       /** @var string Full container name */
+       protected $container;
+
+       /** @var string Resolved relative path */
+       protected $directory;
+
+       /** @var array */
+       protected $multiShardPaths = []; // (rel path => 1)
+
+       /**
+        * @param FileBackendStore $backend
+        * @param string $container Full storage container name
+        * @param string $dir Storage directory relative to container
+        * @param array $suffixes List of container shard suffixes
+        * @param array $params
+        */
+       public function __construct(
+               FileBackendStore $backend, $container, $dir, array $suffixes, array $params
+       ) {
+               $this->backend = $backend;
+               $this->container = $container;
+               $this->directory = $dir;
+               $this->params = $params;
+
+               $iter = new AppendIterator();
+               foreach ( $suffixes as $suffix ) {
+                       $iter->append( $this->listFromShard( $this->container . $suffix ) );
+               }
+
+               parent::__construct( $iter );
+       }
+
+       public function accept() {
+               $rel = $this->getInnerIterator()->current(); // path relative to given directory
+               $path = $this->params['dir'] . "/{$rel}"; // full storage path
+               if ( $this->backend->isSingleShardPathInternal( $path ) ) {
+                       return true; // path is only on one shard; no issue with duplicates
+               } elseif ( isset( $this->multiShardPaths[$rel] ) ) {
+                       // Don't keep listing paths that are on multiple shards
+                       return false;
+               } else {
+                       $this->multiShardPaths[$rel] = 1;
+
+                       return true;
+               }
+       }
+
+       public function rewind() {
+               parent::rewind();
+               $this->multiShardPaths = [];
+       }
+
+       /**
+        * Get the list for a given container shard
+        *
+        * @param string $container Resolved container name
+        * @return Iterator
+        */
+       abstract protected function listFromShard( $container );
+}
diff --git a/includes/libs/filebackend/fileiteration/SwiftFileBackendDirList.php b/includes/libs/filebackend/fileiteration/SwiftFileBackendDirList.php
new file mode 100644 (file)
index 0000000..b0b784d
--- /dev/null
@@ -0,0 +1,40 @@
+<?php
+/**
+ * OpenStack Swift based file backend.
+ *
+ * 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 FileBackend
+ * @author Russ Nelson
+ */
+
+/**
+ * Iterator for listing directories
+ */
+class SwiftFileBackendDirList extends SwiftFileBackendList {
+       /**
+        * @see Iterator::current()
+        * @return string|bool String (relative path) or false
+        */
+       public function current() {
+               return substr( current( $this->bufferIter ), $this->suffixStart, -1 );
+       }
+
+       protected function pageFromList( $container, $dir, &$after, $limit, array $params ) {
+               return $this->backend->getDirListPageInternal( $container, $dir, $after, $limit, $params );
+       }
+}
diff --git a/includes/libs/filebackend/fileiteration/SwiftFileBackendFileList.php b/includes/libs/filebackend/fileiteration/SwiftFileBackendFileList.php
new file mode 100644 (file)
index 0000000..045b8f8
--- /dev/null
@@ -0,0 +1,47 @@
+<?php
+/**
+ * OpenStack Swift based file backend.
+ *
+ * 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 FileBackend
+ * @author Russ Nelson
+ */
+
+/**
+ * Iterator for listing regular files
+ */
+class SwiftFileBackendFileList extends SwiftFileBackendList {
+       /**
+        * @see Iterator::current()
+        * @return string|bool String (relative path) or false
+        */
+       public function current() {
+               list( $path, $stat ) = current( $this->bufferIter );
+               $relPath = substr( $path, $this->suffixStart );
+               if ( is_array( $stat ) ) {
+                       $storageDir = rtrim( $this->params['dir'], '/' );
+                       $this->backend->loadListingStatInternal( "$storageDir/$relPath", $stat );
+               }
+
+               return $relPath;
+       }
+
+       protected function pageFromList( $container, $dir, &$after, $limit, array $params ) {
+               return $this->backend->getFileListPageInternal( $container, $dir, $after, $limit, $params );
+       }
+}
diff --git a/includes/libs/filebackend/fileiteration/SwiftFileBackendList.php b/includes/libs/filebackend/fileiteration/SwiftFileBackendList.php
new file mode 100644 (file)
index 0000000..bcde8d9
--- /dev/null
@@ -0,0 +1,138 @@
+<?php
+/**
+ * OpenStack Swift based file backend.
+ *
+ * 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 FileBackend
+ * @author Russ Nelson
+ */
+
+/**
+ * SwiftFileBackend helper class to page through listings.
+ * Swift also has a listing limit of 10,000 objects for sanity.
+ * Do not use this class from places outside SwiftFileBackend.
+ *
+ * @ingroup FileBackend
+ */
+abstract class SwiftFileBackendList implements Iterator {
+       /** @var array List of path or (path,stat array) entries */
+       protected $bufferIter = [];
+
+       /** @var string List items *after* this path */
+       protected $bufferAfter = null;
+
+       /** @var int */
+       protected $pos = 0;
+
+       /** @var array */
+       protected $params = [];
+
+       /** @var SwiftFileBackend */
+       protected $backend;
+
+       /** @var string Container name */
+       protected $container;
+
+       /** @var string Storage directory */
+       protected $dir;
+
+       /** @var int */
+       protected $suffixStart;
+
+       const PAGE_SIZE = 9000; // file listing buffer size
+
+       /**
+        * @param SwiftFileBackend $backend
+        * @param string $fullCont Resolved container name
+        * @param string $dir Resolved directory relative to container
+        * @param array $params
+        */
+       public function __construct( SwiftFileBackend $backend, $fullCont, $dir, array $params ) {
+               $this->backend = $backend;
+               $this->container = $fullCont;
+               $this->dir = $dir;
+               if ( substr( $this->dir, -1 ) === '/' ) {
+                       $this->dir = substr( $this->dir, 0, -1 ); // remove trailing slash
+               }
+               if ( $this->dir == '' ) { // whole container
+                       $this->suffixStart = 0;
+               } else { // dir within container
+                       $this->suffixStart = strlen( $this->dir ) + 1; // size of "path/to/dir/"
+               }
+               $this->params = $params;
+       }
+
+       /**
+        * @see Iterator::key()
+        * @return int
+        */
+       public function key() {
+               return $this->pos;
+       }
+
+       /**
+        * @see Iterator::next()
+        */
+       public function next() {
+               // Advance to the next file in the page
+               next( $this->bufferIter );
+               ++$this->pos;
+               // Check if there are no files left in this page and
+               // advance to the next page if this page was not empty.
+               if ( !$this->valid() && count( $this->bufferIter ) ) {
+                       $this->bufferIter = $this->pageFromList(
+                               $this->container, $this->dir, $this->bufferAfter, self::PAGE_SIZE, $this->params
+                       ); // updates $this->bufferAfter
+               }
+       }
+
+       /**
+        * @see Iterator::rewind()
+        */
+       public function rewind() {
+               $this->pos = 0;
+               $this->bufferAfter = null;
+               $this->bufferIter = $this->pageFromList(
+                       $this->container, $this->dir, $this->bufferAfter, self::PAGE_SIZE, $this->params
+               ); // updates $this->bufferAfter
+       }
+
+       /**
+        * @see Iterator::valid()
+        * @return bool
+        */
+       public function valid() {
+               if ( $this->bufferIter === null ) {
+                       return false; // some failure?
+               } else {
+                       return ( current( $this->bufferIter ) !== false ); // no paths can have this value
+               }
+       }
+
+       /**
+        * Get the given list portion (page)
+        *
+        * @param string $container Resolved container name
+        * @param string $dir Resolved path relative to container
+        * @param string &$after
+        * @param int $limit
+        * @param array $params
+        * @return Traversable|array
+        */
+       abstract protected function pageFromList( $container, $dir, &$after, $limit, array $params );
+}
diff --git a/includes/libs/filebackend/fileophandle/FSFileOpHandle.php b/includes/libs/filebackend/fileophandle/FSFileOpHandle.php
new file mode 100644 (file)
index 0000000..2d65c43
--- /dev/null
@@ -0,0 +1,45 @@
+<?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 FileBackend
+ */
+
+/**
+ * @see FileBackendStoreOpHandle
+ */
+class FSFileOpHandle extends FileBackendStoreOpHandle {
+       public $cmd; // string; shell command
+       public $chmodPath; // string; file to chmod
+
+       /**
+        * @param FSFileBackend $backend
+        * @param array $params
+        * @param callable $call
+        * @param string $cmd
+        * @param int|null $chmodPath
+        */
+       public function __construct(
+               FSFileBackend $backend, array $params, $call, $cmd, $chmodPath = null
+       ) {
+               $this->backend = $backend;
+               $this->params = $params;
+               $this->call = $call;
+               $this->cmd = $cmd;
+               $this->chmodPath = $chmodPath;
+       }
+}
diff --git a/includes/libs/filebackend/fileophandle/FileBackendStoreOpHandle.php b/includes/libs/filebackend/fileophandle/FileBackendStoreOpHandle.php
new file mode 100644 (file)
index 0000000..c366a0f
--- /dev/null
@@ -0,0 +1,46 @@
+<?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 FileBackend
+ */
+
+/**
+ * FileBackendStore helper class for performing asynchronous file operations.
+ *
+ * For example, calling FileBackendStore::createInternal() with the "async"
+ * param flag may result in a StatusValue that contains this object as a value.
+ * This class is largely backend-specific and is mostly just "magic" to be
+ * passed to FileBackendStore::executeOpHandlesInternal().
+ */
+abstract class FileBackendStoreOpHandle {
+       /** @var array */
+       public $params = []; // params to caller functions
+       /** @var FileBackendStore */
+       public $backend;
+       /** @var array */
+       public $resourcesToClose = [];
+
+       public $call; // string; name that identifies the function called
+
+       /**
+        * Close all open file handles
+        */
+       public function closeResources() {
+               array_map( 'fclose', $this->resourcesToClose );
+       }
+}
diff --git a/includes/libs/filebackend/fileophandle/SwiftFileOpHandle.php b/includes/libs/filebackend/fileophandle/SwiftFileOpHandle.php
new file mode 100644 (file)
index 0000000..1119867
--- /dev/null
@@ -0,0 +1,44 @@
+<?php
+/**
+ * OpenStack Swift based file backend.
+ *
+ * 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 FileBackend
+ * @author Russ Nelson
+ */
+
+/**
+ * @see FileBackendStoreOpHandle
+ */
+class SwiftFileOpHandle extends FileBackendStoreOpHandle {
+       /** @var array List of Requests for MultiHttpClient */
+       public $httpOp;
+       /** @var Closure */
+       public $callback;
+
+       /**
+        * @param SwiftFileBackend $backend
+        * @param Closure $callback Function that takes (HTTP request array, status)
+        * @param array $httpOp MultiHttpClient op
+        */
+       public function __construct( SwiftFileBackend $backend, Closure $callback, array $httpOp ) {
+               $this->backend = $backend;
+               $this->callback = $callback;
+               $this->httpOp = $httpOp;
+       }
+}
index 3134e50..0eab860 100644 (file)
@@ -1700,7 +1700,7 @@ class JSNode
        public $funDecls = array();
        public $varDecls = array();
 
-       public function __construct($t, $type=0)
+       public function __construct($t, $type=0, ...$nodes)
        {
                if ($token = $t->currentToken())
                {
@@ -1716,11 +1716,9 @@ class JSNode
                        $this->lineno = $t->lineno;
                }
 
-               if (($numargs = func_num_args()) > 2)
+               foreach($nodes as $node)
                {
-                       $args = func_get_args();
-                       for ($i = 2; $i < $numargs; $i++)
-                               $this->addNode($args[$i]);
+                       $this->addNode($node);
                }
        }
 
index 413fb2a..a326df2 100644 (file)
@@ -806,9 +806,7 @@ EOT;
                if ( $eocdrPos !== false ) {
                        $this->logger->info( __METHOD__ . ": ZIP signature present in $file\n" );
                        // Check if it really is a ZIP file, make sure the EOCDR is at the end (T40432)
-                       // FIXME: unpack()'s third argument was added in PHP 7.1
-                       // @phan-suppress-next-line PhanParamTooManyInternal
-                       $commentLength = unpack( "n", $tail, $eocdrPos + 20 )[0];
+                       $commentLength = unpack( "n", substr( $tail, $eocdrPos + 20 ) )[0];
                        if ( $eocdrPos + 22 + $commentLength !== strlen( $tail ) ) {
                                $this->logger->info( __METHOD__ . ": ZIP EOCDR not at end. Not a ZIP file." );
                        } else {
index 0483ee7..ed4eb35 100644 (file)
@@ -54,13 +54,11 @@ class APCUBagOStuff extends BagOStuff {
        }
 
        public function set( $key, $value, $exptime = 0, $flags = 0 ) {
-               apcu_store(
+               return apcu_store(
                        $key . self::KEY_SUFFIX,
                        $this->serialize( $value ),
                        $exptime
                );
-
-               return true;
        }
 
        public function add( $key, $value, $exptime = 0, $flags = 0 ) {
index f3ab1c5..b216892 100644 (file)
@@ -46,8 +46,8 @@ class DBConnRef implements IDatabase {
 
        function __call( $name, array $arguments ) {
                if ( $this->conn === null ) {
-                       list( $db, $groups, $wiki, $flags ) = $this->params;
-                       $this->conn = $this->lb->getConnection( $db, $groups, $wiki, $flags );
+                       list( $index, $groups, $wiki, $flags ) = $this->params;
+                       $this->conn = $this->lb->getConnection( $index, $groups, $wiki, $flags );
                }
 
                return $this->conn->$name( ...$arguments );
@@ -211,6 +211,19 @@ class DBConnRef implements IDatabase {
        }
 
        public function getType() {
+               if ( $this->conn === null ) {
+                       // Avoid triggering a database connection
+                       if ( $this->params[self::FLD_INDEX] === ILoadBalancer::DB_MASTER ) {
+                               $index = $this->lb->getWriterIndex();
+                       } else {
+                               $index = $this->params[self::FLD_INDEX];
+                       }
+                       if ( $index >= 0 ) {
+                               // In theory, if $index is DB_REPLICA, the type could vary
+                               return $this->lb->getServerType( $index );
+                       }
+               }
+
                return $this->__call( __FUNCTION__, func_get_args() );
        }
 
@@ -304,6 +317,10 @@ class DBConnRef implements IDatabase {
                return $this->__call( __FUNCTION__, func_get_args() );
        }
 
+       public function limitResult( $sql, $limit, $offset = false ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
        public function selectRow(
                $table, $vars, $conds, $fname = __METHOD__,
                $options = [], $join_conds = []
index ba97887..beca663 100644 (file)
@@ -3191,34 +3191,16 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                $this->query( $sql, $fname );
        }
 
-       /**
-        * 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
-        * 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
-        * are theoretically arbitrary.
-        *
-        * $sql is expected to be a SELECT, if that makes a difference.
-        *
-        * The version provided by default works in MySQL and SQLite. It will very
-        * likely need to be overridden for most other DBMSes.
-        *
-        * @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
-        */
        public function limitResult( $sql, $limit, $offset = false ) {
                if ( !is_numeric( $limit ) ) {
                        throw new DBUnexpectedError( $this,
                                "Invalid non-numeric limit passed to limitResult()\n" );
                }
-
+               // This version works in MySQL and SQLite. It will very likely need to be
+               // overridden for most other RDBMS subclasses.
                return "$sql LIMIT "
-               . ( ( is_numeric( $offset ) && $offset != 0 ) ? "{$offset}," : "" )
-               . "{$limit} ";
+                       . ( ( is_numeric( $offset ) && $offset != 0 ) ? "{$offset}," : "" )
+                       . "{$limit} ";
        }
 
        public function unionSupportsOrderAndLimit() {
index 3041a33..d8be62f 100644 (file)
@@ -922,7 +922,7 @@ __INDEXATTR__;
 
        /**
         * Posted by cc[plus]php[at]c2se[dot]com on 25-Mar-2009 09:12
-        * to https://secure.php.net/manual/en/ref.pgsql.php
+        * to https://www.php.net/manual/en/ref.pgsql.php
         *
         * Parsing a postgres array can be a tricky problem, he's my
         * take on this, it handles multi-dimensional arrays plus
index 82a7e35..c9942a5 100644 (file)
@@ -259,7 +259,7 @@ class DatabaseSqlite extends Database {
         * Check if the searchindext table is FTS enabled.
         * @return bool False if not enabled.
         */
-       function checkForEnabledSearch() {
+       public function checkForEnabledSearch() {
                if ( self::$fulltextEnabled === null ) {
                        self::$fulltextEnabled = false;
                        $table = $this->tableName( 'searchindex' );
index e25b4d2..05f787c 100644 (file)
@@ -431,7 +431,7 @@ interface IDatabase {
 
        /**
         * Get the number of fields in a result object
-        * @see https://secure.php.net/mysql_num_fields
+        * @see https://www.php.net/mysql_num_fields
         *
         * @param mixed $res A SQL result
         * @return int
@@ -440,7 +440,7 @@ interface IDatabase {
 
        /**
         * Get a field name in a result object
-        * @see https://secure.php.net/mysql_field_name
+        * @see https://www.php.net/mysql_field_name
         *
         * @param mixed $res A SQL result
         * @param int $n
@@ -461,7 +461,7 @@ interface IDatabase {
 
        /**
         * Change the position of the cursor in a result object
-        * @see https://secure.php.net/mysql_data_seek
+        * @see https://www.php.net/mysql_data_seek
         *
         * @param mixed $res A SQL result
         * @param int $row
@@ -470,7 +470,7 @@ interface IDatabase {
 
        /**
         * Get the last error number
-        * @see https://secure.php.net/mysql_errno
+        * @see https://www.php.net/mysql_errno
         *
         * @return int
         */
@@ -478,7 +478,7 @@ interface IDatabase {
 
        /**
         * Get a description of the last error
-        * @see https://secure.php.net/mysql_error
+        * @see https://www.php.net/mysql_error
         *
         * @return string
         */
@@ -486,7 +486,7 @@ interface IDatabase {
 
        /**
         * Get the number of rows affected by the last write query
-        * @see https://secure.php.net/mysql_affected_rows
+        * @see https://www.php.net/mysql_affected_rows
         *
         * @return int
         */
@@ -1112,6 +1112,25 @@ interface IDatabase {
                $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
+        * 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
+        * are theoretically arbitrary.
+        *
+        * $sql is expected to be a SELECT, if that makes a difference.
+        *
+        * @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
+        */
+       public function limitResult( $sql, $limit, $offset = false );
+
        /**
         * Returns true if DBs are assumed to be on potentially different servers
         *
diff --git a/includes/logging/DatabaseLogEntry.php b/includes/logging/DatabaseLogEntry.php
new file mode 100644 (file)
index 0000000..db0f599
--- /dev/null
@@ -0,0 +1,231 @@
+<?php
+/**
+ * Contains a class for dealing with database log entries
+ *
+ * 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
+ * @author Niklas Laxström
+ * @license GPL-2.0-or-later
+ * @since 1.19
+ */
+
+use Wikimedia\Rdbms\IDatabase;
+
+/**
+ * A value class to process existing log entries. In other words, this class caches a log
+ * entry from the database and provides an immutable object-oriented representation of it.
+ *
+ * @since 1.19
+ */
+class DatabaseLogEntry extends LogEntryBase {
+
+       /**
+        * Returns array of information that is needed for querying
+        * log entries. Array contains the following keys:
+        * tables, fields, conds, options and join_conds
+        *
+        * @return array
+        */
+       public static function getSelectQueryData() {
+               $commentQuery = CommentStore::getStore()->getJoin( 'log_comment' );
+               $actorQuery = ActorMigration::newMigration()->getJoin( 'log_user' );
+
+               $tables = array_merge(
+                       [ 'logging' ], $commentQuery['tables'], $actorQuery['tables'], [ 'user' ]
+               );
+               $fields = [
+                       'log_id', 'log_type', 'log_action', 'log_timestamp',
+                       'log_namespace', 'log_title', // unused log_page
+                       'log_params', 'log_deleted',
+                       'user_id', 'user_name', 'user_editcount',
+               ] + $commentQuery['fields'] + $actorQuery['fields'];
+
+               $joins = [
+                       // IPs don't have an entry in user table
+                       'user' => [ 'LEFT JOIN', 'user_id=' . $actorQuery['fields']['log_user'] ],
+               ] + $commentQuery['joins'] + $actorQuery['joins'];
+
+               return [
+                       'tables' => $tables,
+                       'fields' => $fields,
+                       'conds' => [],
+                       'options' => [],
+                       'join_conds' => $joins,
+               ];
+       }
+
+       /**
+        * Constructs new LogEntry from database result row.
+        * Supports rows from both logging and recentchanges table.
+        *
+        * @param stdClass|array $row
+        * @return DatabaseLogEntry
+        */
+       public static function newFromRow( $row ) {
+               $row = (object)$row;
+               if ( isset( $row->rc_logid ) ) {
+                       return new RCDatabaseLogEntry( $row );
+               } else {
+                       return new self( $row );
+               }
+       }
+
+       /**
+        * Loads a LogEntry with the given id from database
+        *
+        * @param int $id
+        * @param IDatabase $db
+        * @return DatabaseLogEntry|null
+        */
+       public static function newFromId( $id, IDatabase $db ) {
+               $queryInfo = self::getSelectQueryData();
+               $queryInfo['conds'] += [ 'log_id' => $id ];
+               $row = $db->selectRow(
+                       $queryInfo['tables'],
+                       $queryInfo['fields'],
+                       $queryInfo['conds'],
+                       __METHOD__,
+                       $queryInfo['options'],
+                       $queryInfo['join_conds']
+               );
+               if ( !$row ) {
+                       return null;
+               }
+               return self::newFromRow( $row );
+       }
+
+       /** @var stdClass Database result row. */
+       protected $row;
+
+       /** @var User */
+       protected $performer;
+
+       /** @var array Parameters for log entry */
+       protected $params;
+
+       /** @var int A rev id associated to the log entry */
+       protected $revId = null;
+
+       /** @var bool Whether the parameters for this log entry are stored in new or old format. */
+       protected $legacy;
+
+       protected function __construct( $row ) {
+               $this->row = $row;
+       }
+
+       /**
+        * Returns the unique database id.
+        *
+        * @return int
+        */
+       public function getId() {
+               return (int)$this->row->log_id;
+       }
+
+       /**
+        * Returns whatever is stored in the database field.
+        *
+        * @return string
+        */
+       protected function getRawParameters() {
+               return $this->row->log_params;
+       }
+
+       public function isLegacy() {
+               // This extracts the property
+               $this->getParameters();
+               return $this->legacy;
+       }
+
+       public function getType() {
+               return $this->row->log_type;
+       }
+
+       public function getSubtype() {
+               return $this->row->log_action;
+       }
+
+       public function getParameters() {
+               if ( !isset( $this->params ) ) {
+                       $blob = $this->getRawParameters();
+                       Wikimedia\suppressWarnings();
+                       $params = LogEntryBase::extractParams( $blob );
+                       Wikimedia\restoreWarnings();
+                       if ( $params !== false ) {
+                               $this->params = $params;
+                               $this->legacy = false;
+                       } else {
+                               $this->params = LogPage::extractParams( $blob );
+                               $this->legacy = true;
+                       }
+
+                       if ( isset( $this->params['associated_rev_id'] ) ) {
+                               $this->revId = $this->params['associated_rev_id'];
+                               unset( $this->params['associated_rev_id'] );
+                       }
+               }
+
+               return $this->params;
+       }
+
+       public function getAssociatedRevId() {
+               // This extracts the property
+               $this->getParameters();
+               return $this->revId;
+       }
+
+       public function getPerformer() {
+               if ( !$this->performer ) {
+                       $actorId = isset( $this->row->log_actor ) ? (int)$this->row->log_actor : 0;
+                       $userId = (int)$this->row->log_user;
+                       if ( $userId !== 0 || $actorId !== 0 ) {
+                               // logged-in users
+                               if ( isset( $this->row->user_name ) ) {
+                                       $this->performer = User::newFromRow( $this->row );
+                               } elseif ( $actorId !== 0 ) {
+                                       $this->performer = User::newFromActorId( $actorId );
+                               } else {
+                                       $this->performer = User::newFromId( $userId );
+                               }
+                       } else {
+                               // IP users
+                               $userText = $this->row->log_user_text;
+                               $this->performer = User::newFromName( $userText, false );
+                       }
+               }
+
+               return $this->performer;
+       }
+
+       public function getTarget() {
+               $namespace = $this->row->log_namespace;
+               $page = $this->row->log_title;
+               return Title::makeTitle( $namespace, $page );
+       }
+
+       public function getTimestamp() {
+               return wfTimestamp( TS_MW, $this->row->log_timestamp );
+       }
+
+       public function getComment() {
+               return CommentStore::getStore()->getComment( 'log_comment', $this->row )->text;
+       }
+
+       public function getDeleted() {
+               return $this->row->log_deleted;
+       }
+}
diff --git a/includes/logging/LegacyLogFormatter.php b/includes/logging/LegacyLogFormatter.php
new file mode 100644 (file)
index 0000000..61104db
--- /dev/null
@@ -0,0 +1,127 @@
+<?php
+/**
+ * Contains a class for formatting log legacy entries
+ *
+ * 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
+ * @author Niklas Laxström
+ * @license GPL-2.0-or-later
+ * @since 1.19
+ */
+
+/**
+ * This class formats all log entries for log types
+ * which have not been converted to the new system.
+ * This is not about old log entries which store
+ * parameters in a different format - the new
+ * LogFormatter classes have code to support formatting
+ * those too.
+ * @since 1.19
+ */
+class LegacyLogFormatter extends LogFormatter {
+       /**
+        * Backward compatibility for extension changing the comment from
+        * the LogLine hook. This will be set by the first call on getComment(),
+        * then it might be modified by the hook when calling getActionLinks(),
+        * so that the modified value will be returned when calling getComment()
+        * a second time.
+        *
+        * @var string|null
+        */
+       private $comment = null;
+
+       /**
+        * Cache for the result of getActionLinks() so that it does not need to
+        * run multiple times depending on the order that getComment() and
+        * getActionLinks() are called.
+        *
+        * @var string|null
+        */
+       private $revert = null;
+
+       public function getComment() {
+               if ( $this->comment === null ) {
+                       $this->comment = parent::getComment();
+               }
+
+               // Make sure we execute the LogLine hook so that we immediately return
+               // the correct value.
+               if ( $this->revert === null ) {
+                       $this->getActionLinks();
+               }
+
+               return $this->comment;
+       }
+
+       /**
+        * @return string
+        * @return-taint onlysafefor_html
+        */
+       protected function getActionMessage() {
+               $entry = $this->entry;
+               $action = LogPage::actionText(
+                       $entry->getType(),
+                       $entry->getSubtype(),
+                       $entry->getTarget(),
+                       $this->plaintext ? null : $this->context->getSkin(),
+                       (array)$entry->getParameters(),
+                       !$this->plaintext // whether to filter [[]] links
+               );
+
+               $performer = $this->getPerformerElement();
+               if ( !$this->irctext ) {
+                       $sep = $this->msg( 'word-separator' );
+                       $sep = $this->plaintext ? $sep->text() : $sep->escaped();
+                       $action = $performer . $sep . $action;
+               }
+
+               return $action;
+       }
+
+       public function getActionLinks() {
+               if ( $this->revert !== null ) {
+                       return $this->revert;
+               }
+
+               if ( $this->entry->isDeleted( LogPage::DELETED_ACTION ) ) {
+                       $this->revert = '';
+                       return $this->revert;
+               }
+
+               $title = $this->entry->getTarget();
+               $type = $this->entry->getType();
+               $subtype = $this->entry->getSubtype();
+
+               // Do nothing. The implementation is handled by the hook modifiying the
+               // passed-by-ref parameters. This also changes the default value so that
+               // getComment() and getActionLinks() do not call them indefinitely.
+               $this->revert = '';
+
+               // This is to populate the $comment member of this instance so that it
+               // can be modified when calling the hook just below.
+               if ( $this->comment === null ) {
+                       $this->getComment();
+               }
+
+               $params = $this->entry->getParameters();
+
+               Hooks::run( 'LogLine', [ $type, $subtype, $title, $params,
+                       &$this->comment, &$this->revert, $this->entry->getTimestamp() ] );
+
+               return $this->revert;
+       }
+}
index c5e4a92..17f72bd 100644 (file)
@@ -1,11 +1,6 @@
 <?php
 /**
- * Contain classes for dealing with individual log entries
- *
- * This is how I see the log system history:
- * - appending to plain wiki pages
- * - formatting log entries based on database fields
- * - user is now part of the action message
+ * Contains a class for dealing with individual log entries
  *
  * 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
  * @since 1.19
  */
 
-use MediaWiki\ChangeTags\Taggable;
-use MediaWiki\Linker\LinkTarget;
-use MediaWiki\User\UserIdentity;
-use Wikimedia\Rdbms\IDatabase;
-use Wikimedia\Assert\Assert;
-
 /**
  * Interface for log entries. Every log entry has these methods.
  *
@@ -110,810 +99,3 @@ interface LogEntry {
         */
        public function isDeleted( $field );
 }
-
-/**
- * Extends the LogEntryInterface with some basic functionality
- *
- * @since 1.19
- */
-abstract class LogEntryBase implements LogEntry {
-
-       public function getFullType() {
-               return $this->getType() . '/' . $this->getSubtype();
-       }
-
-       public function isDeleted( $field ) {
-               return ( $this->getDeleted() & $field ) === $field;
-       }
-
-       /**
-        * Whether the parameters for this log are stored in new or
-        * old format.
-        *
-        * @return bool
-        */
-       public function isLegacy() {
-               return false;
-       }
-
-       /**
-        * Create a blob from a parameter array
-        *
-        * @since 1.26
-        * @param array $params
-        * @return string
-        */
-       public static function makeParamBlob( $params ) {
-               return serialize( (array)$params );
-       }
-
-       /**
-        * Extract a parameter array from a blob
-        *
-        * @since 1.26
-        * @param string $blob
-        * @return array
-        */
-       public static function extractParams( $blob ) {
-               return unserialize( $blob );
-       }
-}
-
-/**
- * A value class to process existing log entries. In other words, this class caches a log
- * entry from the database and provides an immutable object-oriented representation of it.
- *
- * @since 1.19
- */
-class DatabaseLogEntry extends LogEntryBase {
-
-       /**
-        * Returns array of information that is needed for querying
-        * log entries. Array contains the following keys:
-        * tables, fields, conds, options and join_conds
-        *
-        * @return array
-        */
-       public static function getSelectQueryData() {
-               $commentQuery = CommentStore::getStore()->getJoin( 'log_comment' );
-               $actorQuery = ActorMigration::newMigration()->getJoin( 'log_user' );
-
-               $tables = array_merge(
-                       [ 'logging' ], $commentQuery['tables'], $actorQuery['tables'], [ 'user' ]
-               );
-               $fields = [
-                       'log_id', 'log_type', 'log_action', 'log_timestamp',
-                       'log_namespace', 'log_title', // unused log_page
-                       'log_params', 'log_deleted',
-                       'user_id', 'user_name', 'user_editcount',
-               ] + $commentQuery['fields'] + $actorQuery['fields'];
-
-               $joins = [
-                       // IPs don't have an entry in user table
-                       'user' => [ 'LEFT JOIN', 'user_id=' . $actorQuery['fields']['log_user'] ],
-               ] + $commentQuery['joins'] + $actorQuery['joins'];
-
-               return [
-                       'tables' => $tables,
-                       'fields' => $fields,
-                       'conds' => [],
-                       'options' => [],
-                       'join_conds' => $joins,
-               ];
-       }
-
-       /**
-        * Constructs new LogEntry from database result row.
-        * Supports rows from both logging and recentchanges table.
-        *
-        * @param stdClass|array $row
-        * @return DatabaseLogEntry
-        */
-       public static function newFromRow( $row ) {
-               $row = (object)$row;
-               if ( isset( $row->rc_logid ) ) {
-                       return new RCDatabaseLogEntry( $row );
-               } else {
-                       return new self( $row );
-               }
-       }
-
-       /**
-        * Loads a LogEntry with the given id from database
-        *
-        * @param int $id
-        * @param IDatabase $db
-        * @return DatabaseLogEntry|null
-        */
-       public static function newFromId( $id, IDatabase $db ) {
-               $queryInfo = self::getSelectQueryData();
-               $queryInfo['conds'] += [ 'log_id' => $id ];
-               $row = $db->selectRow(
-                       $queryInfo['tables'],
-                       $queryInfo['fields'],
-                       $queryInfo['conds'],
-                       __METHOD__,
-                       $queryInfo['options'],
-                       $queryInfo['join_conds']
-               );
-               if ( !$row ) {
-                       return null;
-               }
-               return self::newFromRow( $row );
-       }
-
-       /** @var stdClass Database result row. */
-       protected $row;
-
-       /** @var User */
-       protected $performer;
-
-       /** @var array Parameters for log entry */
-       protected $params;
-
-       /** @var int A rev id associated to the log entry */
-       protected $revId = null;
-
-       /** @var bool Whether the parameters for this log entry are stored in new or old format. */
-       protected $legacy;
-
-       protected function __construct( $row ) {
-               $this->row = $row;
-       }
-
-       /**
-        * Returns the unique database id.
-        *
-        * @return int
-        */
-       public function getId() {
-               return (int)$this->row->log_id;
-       }
-
-       /**
-        * Returns whatever is stored in the database field.
-        *
-        * @return string
-        */
-       protected function getRawParameters() {
-               return $this->row->log_params;
-       }
-
-       public function isLegacy() {
-               // This extracts the property
-               $this->getParameters();
-               return $this->legacy;
-       }
-
-       public function getType() {
-               return $this->row->log_type;
-       }
-
-       public function getSubtype() {
-               return $this->row->log_action;
-       }
-
-       public function getParameters() {
-               if ( !isset( $this->params ) ) {
-                       $blob = $this->getRawParameters();
-                       Wikimedia\suppressWarnings();
-                       $params = LogEntryBase::extractParams( $blob );
-                       Wikimedia\restoreWarnings();
-                       if ( $params !== false ) {
-                               $this->params = $params;
-                               $this->legacy = false;
-                       } else {
-                               $this->params = LogPage::extractParams( $blob );
-                               $this->legacy = true;
-                       }
-
-                       if ( isset( $this->params['associated_rev_id'] ) ) {
-                               $this->revId = $this->params['associated_rev_id'];
-                               unset( $this->params['associated_rev_id'] );
-                       }
-               }
-
-               return $this->params;
-       }
-
-       public function getAssociatedRevId() {
-               // This extracts the property
-               $this->getParameters();
-               return $this->revId;
-       }
-
-       public function getPerformer() {
-               if ( !$this->performer ) {
-                       $actorId = isset( $this->row->log_actor ) ? (int)$this->row->log_actor : 0;
-                       $userId = (int)$this->row->log_user;
-                       if ( $userId !== 0 || $actorId !== 0 ) {
-                               // logged-in users
-                               if ( isset( $this->row->user_name ) ) {
-                                       $this->performer = User::newFromRow( $this->row );
-                               } elseif ( $actorId !== 0 ) {
-                                       $this->performer = User::newFromActorId( $actorId );
-                               } else {
-                                       $this->performer = User::newFromId( $userId );
-                               }
-                       } else {
-                               // IP users
-                               $userText = $this->row->log_user_text;
-                               $this->performer = User::newFromName( $userText, false );
-                       }
-               }
-
-               return $this->performer;
-       }
-
-       public function getTarget() {
-               $namespace = $this->row->log_namespace;
-               $page = $this->row->log_title;
-               $title = Title::makeTitle( $namespace, $page );
-
-               return $title;
-       }
-
-       public function getTimestamp() {
-               return wfTimestamp( TS_MW, $this->row->log_timestamp );
-       }
-
-       public function getComment() {
-               return CommentStore::getStore()->getComment( 'log_comment', $this->row )->text;
-       }
-
-       public function getDeleted() {
-               return $this->row->log_deleted;
-       }
-}
-
-/**
- * A subclass of DatabaseLogEntry for objects constructed from entries in the
- * recentchanges table (rather than the logging table).
- */
-class RCDatabaseLogEntry extends DatabaseLogEntry {
-
-       public function getId() {
-               return $this->row->rc_logid;
-       }
-
-       protected function getRawParameters() {
-               return $this->row->rc_params;
-       }
-
-       public function getAssociatedRevId() {
-               return $this->row->rc_this_oldid;
-       }
-
-       public function getType() {
-               return $this->row->rc_log_type;
-       }
-
-       public function getSubtype() {
-               return $this->row->rc_log_action;
-       }
-
-       public function getPerformer() {
-               if ( !$this->performer ) {
-                       $actorId = isset( $this->row->rc_actor ) ? (int)$this->row->rc_actor : 0;
-                       $userId = (int)$this->row->rc_user;
-                       if ( $actorId !== 0 ) {
-                               $this->performer = User::newFromActorId( $actorId );
-                       } elseif ( $userId !== 0 ) {
-                               $this->performer = User::newFromId( $userId );
-                       } else {
-                               $userText = $this->row->rc_user_text;
-                               // Might be an IP, don't validate the username
-                               $this->performer = User::newFromName( $userText, false );
-                       }
-               }
-
-               return $this->performer;
-       }
-
-       public function getTarget() {
-               $namespace = $this->row->rc_namespace;
-               $page = $this->row->rc_title;
-               $title = Title::makeTitle( $namespace, $page );
-
-               return $title;
-       }
-
-       public function getTimestamp() {
-               return wfTimestamp( TS_MW, $this->row->rc_timestamp );
-       }
-
-       public function getComment() {
-               return CommentStore::getStore()
-                       // Legacy because the row may have used RecentChange::selectFields()
-                       ->getCommentLegacy( wfGetDB( DB_REPLICA ), 'rc_comment', $this->row )->text;
-       }
-
-       public function getDeleted() {
-               return $this->row->rc_deleted;
-       }
-}
-
-/**
- * Class for creating new log entries and inserting them into the database.
- *
- * @since 1.19
- */
-class ManualLogEntry extends LogEntryBase implements Taggable {
-       /** @var string Type of log entry */
-       protected $type;
-
-       /** @var string Sub type of log entry */
-       protected $subtype;
-
-       /** @var array Parameters for log entry */
-       protected $parameters = [];
-
-       /** @var array */
-       protected $relations = [];
-
-       /** @var User Performer of the action for the log entry */
-       protected $performer;
-
-       /** @var Title Target title for the log entry */
-       protected $target;
-
-       /** @var string Timestamp of creation of the log entry */
-       protected $timestamp;
-
-       /** @var string Comment for the log entry */
-       protected $comment = '';
-
-       /** @var int A rev id associated to the log entry */
-       protected $revId = 0;
-
-       /** @var string[] Change tags add to the log entry */
-       protected $tags = [];
-
-       /** @var int Deletion state of the log entry */
-       protected $deleted;
-
-       /** @var int ID of the log entry */
-       protected $id;
-
-       /** @var bool Can this log entry be patrolled? */
-       protected $isPatrollable = false;
-
-       /** @var bool Whether this is a legacy log entry */
-       protected $legacy = false;
-
-       /**
-        * @since 1.19
-        * @param string $type
-        * @param string $subtype
-        */
-       public function __construct( $type, $subtype ) {
-               $this->type = $type;
-               $this->subtype = $subtype;
-       }
-
-       /**
-        * Set extra log parameters.
-        *
-        * You can pass params to the log action message by prefixing the keys with
-        * a number and optional type, using colons to separate the fields. The
-        * numbering should start with number 4, the first three parameters are
-        * hardcoded for every message.
-        *
-        * If you want to store stuff that should not be available in messages, don't
-        * prefix the array key with a number and don't use the colons.
-        *
-        * Example:
-        *   $entry->setParameters(
-        *     '4::color' => 'blue',
-        *     '5:number:count' => 3000,
-        *     'animal' => 'dog'
-        *   );
-        *
-        * @since 1.19
-        * @param array $parameters Associative array
-        */
-       public function setParameters( $parameters ) {
-               $this->parameters = $parameters;
-       }
-
-       /**
-        * Declare arbitrary tag/value relations to this log entry.
-        * These can be used to filter log entries later on.
-        *
-        * @param array $relations Map of (tag => (list of values|value))
-        * @since 1.22
-        */
-       public function setRelations( array $relations ) {
-               $this->relations = $relations;
-       }
-
-       /**
-        * Set the user that performed the action being logged.
-        *
-        * @since 1.19
-        * @param UserIdentity $performer
-        */
-       public function setPerformer( UserIdentity $performer ) {
-               $this->performer = User::newFromIdentity( $performer );
-       }
-
-       /**
-        * Set the title of the object changed.
-        *
-        * @since 1.19
-        * @param LinkTarget $target
-        */
-       public function setTarget( LinkTarget $target ) {
-               $this->target = Title::newFromLinkTarget( $target );
-       }
-
-       /**
-        * Set the timestamp of when the logged action took place.
-        *
-        * @since 1.19
-        * @param string $timestamp
-        */
-       public function setTimestamp( $timestamp ) {
-               $this->timestamp = $timestamp;
-       }
-
-       /**
-        * Set a comment associated with the action being logged.
-        *
-        * @since 1.19
-        * @param string $comment
-        */
-       public function setComment( $comment ) {
-               $this->comment = $comment;
-       }
-
-       /**
-        * Set an associated revision id.
-        *
-        * For example, the ID of the revision that was inserted to mark a page move
-        * or protection, file upload, etc.
-        *
-        * @since 1.27
-        * @param int $revId
-        */
-       public function setAssociatedRevId( $revId ) {
-               $this->revId = $revId;
-       }
-
-       /**
-        * Set change tags for the log entry.
-        *
-        * Passing `null` means the same as empty array,
-        * for compatibility with WikiPage::doUpdateRestrictions().
-        *
-        * @since 1.27
-        * @param string|string[]|null $tags
-        * @deprecated since 1.33 Please use addTags() instead
-        */
-       public function setTags( $tags ) {
-               if ( $this->tags ) {
-                       wfDebug( 'Overwriting existing ManualLogEntry tags' );
-               }
-               $this->tags = [];
-               if ( $tags !== null ) {
-                       $this->addTags( $tags );
-               }
-       }
-
-       /**
-        * Add change tags for the log entry
-        *
-        * @since 1.33
-        * @param string|string[] $tags Tags to apply
-        */
-       public function addTags( $tags ) {
-               if ( is_string( $tags ) ) {
-                       $tags = [ $tags ];
-               }
-               Assert::parameterElementType( 'string', $tags, 'tags' );
-               $this->tags = array_unique( array_merge( $this->tags, $tags ) );
-       }
-
-       /**
-        * Set whether this log entry should be made patrollable
-        * This shouldn't depend on config, only on whether there is full support
-        * in the software for patrolling this log entry.
-        * False by default
-        *
-        * @since 1.27
-        * @param bool $patrollable
-        */
-       public function setIsPatrollable( $patrollable ) {
-               $this->isPatrollable = (bool)$patrollable;
-       }
-
-       /**
-        * Set the 'legacy' flag
-        *
-        * @since 1.25
-        * @param bool $legacy
-        */
-       public function setLegacy( $legacy ) {
-               $this->legacy = $legacy;
-       }
-
-       /**
-        * Set the 'deleted' flag.
-        *
-        * @since 1.19
-        * @param int $deleted One of LogPage::DELETED_* bitfield constants
-        */
-       public function setDeleted( $deleted ) {
-               $this->deleted = $deleted;
-       }
-
-       /**
-        * Insert the entry into the `logging` table.
-        *
-        * @param IDatabase|null $dbw
-        * @return int ID of the log entry
-        * @throws MWException
-        */
-       public function insert( IDatabase $dbw = null ) {
-               global $wgActorTableSchemaMigrationStage;
-
-               $dbw = $dbw ?: wfGetDB( DB_MASTER );
-
-               if ( $this->timestamp === null ) {
-                       $this->timestamp = wfTimestampNow();
-               }
-
-               // Trim spaces on user supplied text
-               $comment = trim( $this->getComment() );
-
-               $params = $this->getParameters();
-               $relations = $this->relations;
-
-               // Ensure actor relations are set
-               if ( ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) &&
-                       empty( $relations['target_author_actor'] )
-               ) {
-                       $actorIds = [];
-                       if ( !empty( $relations['target_author_id'] ) ) {
-                               foreach ( $relations['target_author_id'] as $id ) {
-                                       $actorIds[] = User::newFromId( $id )->getActorId( $dbw );
-                               }
-                       }
-                       if ( !empty( $relations['target_author_ip'] ) ) {
-                               foreach ( $relations['target_author_ip'] as $ip ) {
-                                       $actorIds[] = User::newFromName( $ip, false )->getActorId( $dbw );
-                               }
-                       }
-                       if ( $actorIds ) {
-                               $relations['target_author_actor'] = $actorIds;
-                               $params['authorActors'] = $actorIds;
-                       }
-               }
-               if ( !( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) ) {
-                       unset( $relations['target_author_id'], $relations['target_author_ip'] );
-                       unset( $params['authorIds'], $params['authorIPs'] );
-               }
-
-               // Additional fields for which there's no space in the database table schema
-               $revId = $this->getAssociatedRevId();
-               if ( $revId ) {
-                       $params['associated_rev_id'] = $revId;
-                       $relations['associated_rev_id'] = $revId;
-               }
-
-               $data = [
-                       'log_type' => $this->getType(),
-                       'log_action' => $this->getSubtype(),
-                       'log_timestamp' => $dbw->timestamp( $this->getTimestamp() ),
-                       'log_namespace' => $this->getTarget()->getNamespace(),
-                       'log_title' => $this->getTarget()->getDBkey(),
-                       'log_page' => $this->getTarget()->getArticleID(),
-                       'log_params' => LogEntryBase::makeParamBlob( $params ),
-               ];
-               if ( isset( $this->deleted ) ) {
-                       $data['log_deleted'] = $this->deleted;
-               }
-               $data += CommentStore::getStore()->insert( $dbw, 'log_comment', $comment );
-               $data += ActorMigration::newMigration()
-                       ->getInsertValues( $dbw, 'log_user', $this->getPerformer() );
-
-               $dbw->insert( 'logging', $data, __METHOD__ );
-               $this->id = $dbw->insertId();
-
-               $rows = [];
-               foreach ( $relations as $tag => $values ) {
-                       if ( !strlen( $tag ) ) {
-                               throw new MWException( "Got empty log search tag." );
-                       }
-
-                       if ( !is_array( $values ) ) {
-                               $values = [ $values ];
-                       }
-
-                       foreach ( $values as $value ) {
-                               $rows[] = [
-                                       'ls_field' => $tag,
-                                       'ls_value' => $value,
-                                       'ls_log_id' => $this->id
-                               ];
-                       }
-               }
-               if ( count( $rows ) ) {
-                       $dbw->insert( 'log_search', $rows, __METHOD__, 'IGNORE' );
-               }
-
-               return $this->id;
-       }
-
-       /**
-        * Get a RecentChanges object for the log entry
-        *
-        * @param int $newId
-        * @return RecentChange
-        * @since 1.23
-        */
-       public function getRecentChange( $newId = 0 ) {
-               $formatter = LogFormatter::newFromEntry( $this );
-               $context = RequestContext::newExtraneousContext( $this->getTarget() );
-               $formatter->setContext( $context );
-
-               $logpage = SpecialPage::getTitleFor( 'Log', $this->getType() );
-               $user = $this->getPerformer();
-               $ip = "";
-               if ( $user->isAnon() ) {
-                       // "MediaWiki default" and friends may have
-                       // no IP address in their name
-                       if ( IP::isIPAddress( $user->getName() ) ) {
-                               $ip = $user->getName();
-                       }
-               }
-
-               return RecentChange::newLogEntry(
-                       $this->getTimestamp(),
-                       $logpage,
-                       $user,
-                       $formatter->getPlainActionText(),
-                       $ip,
-                       $this->getType(),
-                       $this->getSubtype(),
-                       $this->getTarget(),
-                       $this->getComment(),
-                       LogEntryBase::makeParamBlob( $this->getParameters() ),
-                       $newId,
-                       $formatter->getIRCActionComment(), // Used for IRC feeds
-                       $this->getAssociatedRevId(), // Used for e.g. moves and uploads
-                       $this->getIsPatrollable()
-               );
-       }
-
-       /**
-        * Publish the log entry.
-        *
-        * @param int $newId Id of the log entry.
-        * @param string $to One of: rcandudp (default), rc, udp
-        */
-       public function publish( $newId, $to = 'rcandudp' ) {
-               $canAddTags = true;
-               // FIXME: this code should be removed once all callers properly call publish()
-               if ( $to === 'udp' && !$newId && !$this->getAssociatedRevId() ) {
-                       \MediaWiki\Logger\LoggerFactory::getInstance( 'logging' )->warning(
-                               'newId and/or revId must be set when calling ManualLogEntry::publish()',
-                               [
-                                       'newId' => $newId,
-                                       'to' => $to,
-                                       'revId' => $this->getAssociatedRevId(),
-                                       // pass a new exception to register the stack trace
-                                       'exception' => new RuntimeException()
-                               ]
-                       );
-                       $canAddTags = false;
-               }
-
-               DeferredUpdates::addCallableUpdate(
-                       function () use ( $newId, $to, $canAddTags ) {
-                               $log = new LogPage( $this->getType() );
-                               if ( !$log->isRestricted() ) {
-                                       Hooks::runWithoutAbort( 'ManualLogEntryBeforePublish', [ $this ] );
-                                       $rc = $this->getRecentChange( $newId );
-
-                                       if ( $to === 'rc' || $to === 'rcandudp' ) {
-                                               // save RC, passing tags so they are applied there
-                                               $rc->addTags( $this->getTags() );
-                                               $rc->save( $rc::SEND_NONE );
-                                       } else {
-                                               $tags = $this->getTags();
-                                               if ( $tags && $canAddTags ) {
-                                                       $revId = $this->getAssociatedRevId();
-                                                       ChangeTags::addTags(
-                                                               $tags,
-                                                               null,
-                                                               $revId > 0 ? $revId : null,
-                                                               $newId > 0 ? $newId : null
-                                                       );
-                                               }
-                                       }
-
-                                       if ( $to === 'udp' || $to === 'rcandudp' ) {
-                                               $rc->notifyRCFeeds();
-                                       }
-                               }
-                       },
-                       DeferredUpdates::POSTSEND,
-                       wfGetDB( DB_MASTER )
-               );
-       }
-
-       public function getType() {
-               return $this->type;
-       }
-
-       public function getSubtype() {
-               return $this->subtype;
-       }
-
-       public function getParameters() {
-               return $this->parameters;
-       }
-
-       /**
-        * @return User
-        */
-       public function getPerformer() {
-               return $this->performer;
-       }
-
-       /**
-        * @return Title
-        */
-       public function getTarget() {
-               return $this->target;
-       }
-
-       public function getTimestamp() {
-               $ts = $this->timestamp ?? wfTimestampNow();
-
-               return wfTimestamp( TS_MW, $ts );
-       }
-
-       public function getComment() {
-               return $this->comment;
-       }
-
-       /**
-        * @since 1.27
-        * @return int
-        */
-       public function getAssociatedRevId() {
-               return $this->revId;
-       }
-
-       /**
-        * @since 1.27
-        * @return string[]
-        */
-       public function getTags() {
-               return $this->tags;
-       }
-
-       /**
-        * Whether this log entry is patrollable
-        *
-        * @since 1.27
-        * @return bool
-        */
-       public function getIsPatrollable() {
-               return $this->isPatrollable;
-       }
-
-       /**
-        * @since 1.25
-        * @return bool
-        */
-       public function isLegacy() {
-               return $this->legacy;
-       }
-
-       public function getDeleted() {
-               return (int)$this->deleted;
-       }
-}
diff --git a/includes/logging/LogEntryBase.php b/includes/logging/LogEntryBase.php
new file mode 100644 (file)
index 0000000..170fc29
--- /dev/null
@@ -0,0 +1,72 @@
+<?php
+/**
+ * Contains a class for dealing with individual log entries
+ *
+ * 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
+ * @author Niklas Laxström
+ * @license GPL-2.0-or-later
+ * @since 1.19
+ */
+
+/**
+ * Extends the LogEntry Interface with some basic functionality
+ *
+ * @since 1.19
+ */
+abstract class LogEntryBase implements LogEntry {
+
+       public function getFullType() {
+               return $this->getType() . '/' . $this->getSubtype();
+       }
+
+       public function isDeleted( $field ) {
+               return ( $this->getDeleted() & $field ) === $field;
+       }
+
+       /**
+        * Whether the parameters for this log are stored in new or
+        * old format.
+        *
+        * @return bool
+        */
+       public function isLegacy() {
+               return false;
+       }
+
+       /**
+        * Create a blob from a parameter array
+        *
+        * @since 1.26
+        * @param array $params
+        * @return string
+        */
+       public static function makeParamBlob( $params ) {
+               return serialize( (array)$params );
+       }
+
+       /**
+        * Extract a parameter array from a blob
+        *
+        * @since 1.26
+        * @param string $blob
+        * @return array
+        */
+       public static function extractParams( $blob ) {
+               return unserialize( $blob );
+       }
+}
index b9bb70c..3e942ae 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /**
- * Contains classes for formatting log entries
+ * Contains a class for formatting log entries
  *
  * 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
@@ -910,106 +910,3 @@ class LogFormatter {
                return [ $name => $value ];
        }
 }
-
-/**
- * This class formats all log entries for log types
- * which have not been converted to the new system.
- * This is not about old log entries which store
- * parameters in a different format - the new
- * LogFormatter classes have code to support formatting
- * those too.
- * @since 1.19
- */
-class LegacyLogFormatter extends LogFormatter {
-       /**
-        * Backward compatibility for extension changing the comment from
-        * the LogLine hook. This will be set by the first call on getComment(),
-        * then it might be modified by the hook when calling getActionLinks(),
-        * so that the modified value will be returned when calling getComment()
-        * a second time.
-        *
-        * @var string|null
-        */
-       private $comment = null;
-
-       /**
-        * Cache for the result of getActionLinks() so that it does not need to
-        * run multiple times depending on the order that getComment() and
-        * getActionLinks() are called.
-        *
-        * @var string|null
-        */
-       private $revert = null;
-
-       public function getComment() {
-               if ( $this->comment === null ) {
-                       $this->comment = parent::getComment();
-               }
-
-               // Make sure we execute the LogLine hook so that we immediately return
-               // the correct value.
-               if ( $this->revert === null ) {
-                       $this->getActionLinks();
-               }
-
-               return $this->comment;
-       }
-
-       /**
-        * @return string
-        * @return-taint onlysafefor_html
-        */
-       protected function getActionMessage() {
-               $entry = $this->entry;
-               $action = LogPage::actionText(
-                       $entry->getType(),
-                       $entry->getSubtype(),
-                       $entry->getTarget(),
-                       $this->plaintext ? null : $this->context->getSkin(),
-                       (array)$entry->getParameters(),
-                       !$this->plaintext // whether to filter [[]] links
-               );
-
-               $performer = $this->getPerformerElement();
-               if ( !$this->irctext ) {
-                       $sep = $this->msg( 'word-separator' );
-                       $sep = $this->plaintext ? $sep->text() : $sep->escaped();
-                       $action = $performer . $sep . $action;
-               }
-
-               return $action;
-       }
-
-       public function getActionLinks() {
-               if ( $this->revert !== null ) {
-                       return $this->revert;
-               }
-
-               if ( $this->entry->isDeleted( LogPage::DELETED_ACTION ) ) {
-                       $this->revert = '';
-                       return $this->revert;
-               }
-
-               $title = $this->entry->getTarget();
-               $type = $this->entry->getType();
-               $subtype = $this->entry->getSubtype();
-
-               // Do nothing. The implementation is handled by the hook modifiying the
-               // passed-by-ref parameters. This also changes the default value so that
-               // getComment() and getActionLinks() do not call them indefinitely.
-               $this->revert = '';
-
-               // This is to populate the $comment member of this instance so that it
-               // can be modified when calling the hook just below.
-               if ( $this->comment === null ) {
-                       $this->getComment();
-               }
-
-               $params = $this->entry->getParameters();
-
-               Hooks::run( 'LogLine', [ $type, $subtype, $title, $params,
-                       &$this->comment, &$this->revert, $this->entry->getTimestamp() ] );
-
-               return $this->revert;
-       }
-}
index 45d6e1f..1fc56bb 100644 (file)
@@ -104,7 +104,11 @@ class LogPage {
                        'log_page' => $this->target->getArticleID(),
                        'log_params' => $this->params
                ];
-               $data += CommentStore::getStore()->insert( $dbw, 'log_comment', $this->comment );
+               $data += MediaWikiServices::getInstance()->getCommentStore()->insert(
+                       $dbw,
+                       'log_comment',
+                       $this->comment
+               );
                $data += ActorMigration::newMigration()->getInsertValues( $dbw, 'log_user', $this->doer );
                $dbw->insert( 'logging', $data, __METHOD__ );
                $newId = $dbw->insertId();
diff --git a/includes/logging/ManualLogEntry.php b/includes/logging/ManualLogEntry.php
new file mode 100644 (file)
index 0000000..90c0a72
--- /dev/null
@@ -0,0 +1,515 @@
+<?php
+/**
+ * Contains a class for dealing with manual log entries
+ *
+ * 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
+ * @author Niklas Laxström
+ * @license GPL-2.0-or-later
+ * @since 1.19
+ */
+
+use MediaWiki\ChangeTags\Taggable;
+use MediaWiki\Linker\LinkTarget;
+use MediaWiki\User\UserIdentity;
+use Wikimedia\Rdbms\IDatabase;
+use Wikimedia\Assert\Assert;
+
+/**
+ * Class for creating new log entries and inserting them into the database.
+ *
+ * @since 1.19
+ */
+class ManualLogEntry extends LogEntryBase implements Taggable {
+       /** @var string Type of log entry */
+       protected $type;
+
+       /** @var string Sub type of log entry */
+       protected $subtype;
+
+       /** @var array Parameters for log entry */
+       protected $parameters = [];
+
+       /** @var array */
+       protected $relations = [];
+
+       /** @var User Performer of the action for the log entry */
+       protected $performer;
+
+       /** @var Title Target title for the log entry */
+       protected $target;
+
+       /** @var string Timestamp of creation of the log entry */
+       protected $timestamp;
+
+       /** @var string Comment for the log entry */
+       protected $comment = '';
+
+       /** @var int A rev id associated to the log entry */
+       protected $revId = 0;
+
+       /** @var string[] Change tags add to the log entry */
+       protected $tags = [];
+
+       /** @var int Deletion state of the log entry */
+       protected $deleted;
+
+       /** @var int ID of the log entry */
+       protected $id;
+
+       /** @var bool Can this log entry be patrolled? */
+       protected $isPatrollable = false;
+
+       /** @var bool Whether this is a legacy log entry */
+       protected $legacy = false;
+
+       /**
+        * @since 1.19
+        * @param string $type
+        * @param string $subtype
+        */
+       public function __construct( $type, $subtype ) {
+               $this->type = $type;
+               $this->subtype = $subtype;
+       }
+
+       /**
+        * Set extra log parameters.
+        *
+        * You can pass params to the log action message by prefixing the keys with
+        * a number and optional type, using colons to separate the fields. The
+        * numbering should start with number 4, the first three parameters are
+        * hardcoded for every message.
+        *
+        * If you want to store stuff that should not be available in messages, don't
+        * prefix the array key with a number and don't use the colons.
+        *
+        * Example:
+        *   $entry->setParameters(
+        *     '4::color' => 'blue',
+        *     '5:number:count' => 3000,
+        *     'animal' => 'dog'
+        *   );
+        *
+        * @since 1.19
+        * @param array $parameters Associative array
+        */
+       public function setParameters( $parameters ) {
+               $this->parameters = $parameters;
+       }
+
+       /**
+        * Declare arbitrary tag/value relations to this log entry.
+        * These can be used to filter log entries later on.
+        *
+        * @param array $relations Map of (tag => (list of values|value))
+        * @since 1.22
+        */
+       public function setRelations( array $relations ) {
+               $this->relations = $relations;
+       }
+
+       /**
+        * Set the user that performed the action being logged.
+        *
+        * @since 1.19
+        * @param UserIdentity $performer
+        */
+       public function setPerformer( UserIdentity $performer ) {
+               $this->performer = User::newFromIdentity( $performer );
+       }
+
+       /**
+        * Set the title of the object changed.
+        *
+        * @since 1.19
+        * @param LinkTarget $target
+        */
+       public function setTarget( LinkTarget $target ) {
+               $this->target = Title::newFromLinkTarget( $target );
+       }
+
+       /**
+        * Set the timestamp of when the logged action took place.
+        *
+        * @since 1.19
+        * @param string $timestamp
+        */
+       public function setTimestamp( $timestamp ) {
+               $this->timestamp = $timestamp;
+       }
+
+       /**
+        * Set a comment associated with the action being logged.
+        *
+        * @since 1.19
+        * @param string $comment
+        */
+       public function setComment( $comment ) {
+               $this->comment = $comment;
+       }
+
+       /**
+        * Set an associated revision id.
+        *
+        * For example, the ID of the revision that was inserted to mark a page move
+        * or protection, file upload, etc.
+        *
+        * @since 1.27
+        * @param int $revId
+        */
+       public function setAssociatedRevId( $revId ) {
+               $this->revId = $revId;
+       }
+
+       /**
+        * Set change tags for the log entry.
+        *
+        * Passing `null` means the same as empty array,
+        * for compatibility with WikiPage::doUpdateRestrictions().
+        *
+        * @since 1.27
+        * @param string|string[]|null $tags
+        * @deprecated since 1.33 Please use addTags() instead
+        */
+       public function setTags( $tags ) {
+               if ( $this->tags ) {
+                       wfDebug( 'Overwriting existing ManualLogEntry tags' );
+               }
+               $this->tags = [];
+               if ( $tags !== null ) {
+                       $this->addTags( $tags );
+               }
+       }
+
+       /**
+        * Add change tags for the log entry
+        *
+        * @since 1.33
+        * @param string|string[] $tags Tags to apply
+        */
+       public function addTags( $tags ) {
+               if ( is_string( $tags ) ) {
+                       $tags = [ $tags ];
+               }
+               Assert::parameterElementType( 'string', $tags, 'tags' );
+               $this->tags = array_unique( array_merge( $this->tags, $tags ) );
+       }
+
+       /**
+        * Set whether this log entry should be made patrollable
+        * This shouldn't depend on config, only on whether there is full support
+        * in the software for patrolling this log entry.
+        * False by default
+        *
+        * @since 1.27
+        * @param bool $patrollable
+        */
+       public function setIsPatrollable( $patrollable ) {
+               $this->isPatrollable = (bool)$patrollable;
+       }
+
+       /**
+        * Set the 'legacy' flag
+        *
+        * @since 1.25
+        * @param bool $legacy
+        */
+       public function setLegacy( $legacy ) {
+               $this->legacy = $legacy;
+       }
+
+       /**
+        * Set the 'deleted' flag.
+        *
+        * @since 1.19
+        * @param int $deleted One of LogPage::DELETED_* bitfield constants
+        */
+       public function setDeleted( $deleted ) {
+               $this->deleted = $deleted;
+       }
+
+       /**
+        * Insert the entry into the `logging` table.
+        *
+        * @param IDatabase|null $dbw
+        * @return int ID of the log entry
+        * @throws MWException
+        */
+       public function insert( IDatabase $dbw = null ) {
+               global $wgActorTableSchemaMigrationStage;
+
+               $dbw = $dbw ?: wfGetDB( DB_MASTER );
+
+               if ( $this->timestamp === null ) {
+                       $this->timestamp = wfTimestampNow();
+               }
+
+               // Trim spaces on user supplied text
+               $comment = trim( $this->getComment() );
+
+               $params = $this->getParameters();
+               $relations = $this->relations;
+
+               // Ensure actor relations are set
+               if ( ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) &&
+                       empty( $relations['target_author_actor'] )
+               ) {
+                       $actorIds = [];
+                       if ( !empty( $relations['target_author_id'] ) ) {
+                               foreach ( $relations['target_author_id'] as $id ) {
+                                       $actorIds[] = User::newFromId( $id )->getActorId( $dbw );
+                               }
+                       }
+                       if ( !empty( $relations['target_author_ip'] ) ) {
+                               foreach ( $relations['target_author_ip'] as $ip ) {
+                                       $actorIds[] = User::newFromName( $ip, false )->getActorId( $dbw );
+                               }
+                       }
+                       if ( $actorIds ) {
+                               $relations['target_author_actor'] = $actorIds;
+                               $params['authorActors'] = $actorIds;
+                       }
+               }
+               if ( !( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) ) {
+                       unset( $relations['target_author_id'], $relations['target_author_ip'] );
+                       unset( $params['authorIds'], $params['authorIPs'] );
+               }
+
+               // Additional fields for which there's no space in the database table schema
+               $revId = $this->getAssociatedRevId();
+               if ( $revId ) {
+                       $params['associated_rev_id'] = $revId;
+                       $relations['associated_rev_id'] = $revId;
+               }
+
+               $data = [
+                       'log_type' => $this->getType(),
+                       'log_action' => $this->getSubtype(),
+                       'log_timestamp' => $dbw->timestamp( $this->getTimestamp() ),
+                       'log_namespace' => $this->getTarget()->getNamespace(),
+                       'log_title' => $this->getTarget()->getDBkey(),
+                       'log_page' => $this->getTarget()->getArticleID(),
+                       'log_params' => LogEntryBase::makeParamBlob( $params ),
+               ];
+               if ( isset( $this->deleted ) ) {
+                       $data['log_deleted'] = $this->deleted;
+               }
+               $data += CommentStore::getStore()->insert( $dbw, 'log_comment', $comment );
+               $data += ActorMigration::newMigration()
+                       ->getInsertValues( $dbw, 'log_user', $this->getPerformer() );
+
+               $dbw->insert( 'logging', $data, __METHOD__ );
+               $this->id = $dbw->insertId();
+
+               $rows = [];
+               foreach ( $relations as $tag => $values ) {
+                       if ( !strlen( $tag ) ) {
+                               throw new MWException( "Got empty log search tag." );
+                       }
+
+                       if ( !is_array( $values ) ) {
+                               $values = [ $values ];
+                       }
+
+                       foreach ( $values as $value ) {
+                               $rows[] = [
+                                       'ls_field' => $tag,
+                                       'ls_value' => $value,
+                                       'ls_log_id' => $this->id
+                               ];
+                       }
+               }
+               if ( count( $rows ) ) {
+                       $dbw->insert( 'log_search', $rows, __METHOD__, 'IGNORE' );
+               }
+
+               return $this->id;
+       }
+
+       /**
+        * Get a RecentChanges object for the log entry
+        *
+        * @param int $newId
+        * @return RecentChange
+        * @since 1.23
+        */
+       public function getRecentChange( $newId = 0 ) {
+               $formatter = LogFormatter::newFromEntry( $this );
+               $context = RequestContext::newExtraneousContext( $this->getTarget() );
+               $formatter->setContext( $context );
+
+               $logpage = SpecialPage::getTitleFor( 'Log', $this->getType() );
+               $user = $this->getPerformer();
+               $ip = "";
+               if ( $user->isAnon() ) {
+                       // "MediaWiki default" and friends may have
+                       // no IP address in their name
+                       if ( IP::isIPAddress( $user->getName() ) ) {
+                               $ip = $user->getName();
+                       }
+               }
+
+               return RecentChange::newLogEntry(
+                       $this->getTimestamp(),
+                       $logpage,
+                       $user,
+                       $formatter->getPlainActionText(),
+                       $ip,
+                       $this->getType(),
+                       $this->getSubtype(),
+                       $this->getTarget(),
+                       $this->getComment(),
+                       LogEntryBase::makeParamBlob( $this->getParameters() ),
+                       $newId,
+                       $formatter->getIRCActionComment(), // Used for IRC feeds
+                       $this->getAssociatedRevId(), // Used for e.g. moves and uploads
+                       $this->getIsPatrollable()
+               );
+       }
+
+       /**
+        * Publish the log entry.
+        *
+        * @param int $newId Id of the log entry.
+        * @param string $to One of: rcandudp (default), rc, udp
+        */
+       public function publish( $newId, $to = 'rcandudp' ) {
+               $canAddTags = true;
+               // FIXME: this code should be removed once all callers properly call publish()
+               if ( $to === 'udp' && !$newId && !$this->getAssociatedRevId() ) {
+                       \MediaWiki\Logger\LoggerFactory::getInstance( 'logging' )->warning(
+                               'newId and/or revId must be set when calling ManualLogEntry::publish()',
+                               [
+                                       'newId' => $newId,
+                                       'to' => $to,
+                                       'revId' => $this->getAssociatedRevId(),
+                                       // pass a new exception to register the stack trace
+                                       'exception' => new RuntimeException()
+                               ]
+                       );
+                       $canAddTags = false;
+               }
+
+               DeferredUpdates::addCallableUpdate(
+                       function () use ( $newId, $to, $canAddTags ) {
+                               $log = new LogPage( $this->getType() );
+                               if ( !$log->isRestricted() ) {
+                                       Hooks::runWithoutAbort( 'ManualLogEntryBeforePublish', [ $this ] );
+                                       $rc = $this->getRecentChange( $newId );
+
+                                       if ( $to === 'rc' || $to === 'rcandudp' ) {
+                                               // save RC, passing tags so they are applied there
+                                               $rc->addTags( $this->getTags() );
+                                               $rc->save( $rc::SEND_NONE );
+                                       } else {
+                                               $tags = $this->getTags();
+                                               if ( $tags && $canAddTags ) {
+                                                       $revId = $this->getAssociatedRevId();
+                                                       ChangeTags::addTags(
+                                                               $tags,
+                                                               null,
+                                                               $revId > 0 ? $revId : null,
+                                                               $newId > 0 ? $newId : null
+                                                       );
+                                               }
+                                       }
+
+                                       if ( $to === 'udp' || $to === 'rcandudp' ) {
+                                               $rc->notifyRCFeeds();
+                                       }
+                               }
+                       },
+                       DeferredUpdates::POSTSEND,
+                       wfGetDB( DB_MASTER )
+               );
+       }
+
+       public function getType() {
+               return $this->type;
+       }
+
+       public function getSubtype() {
+               return $this->subtype;
+       }
+
+       public function getParameters() {
+               return $this->parameters;
+       }
+
+       /**
+        * @return User
+        */
+       public function getPerformer() {
+               return $this->performer;
+       }
+
+       /**
+        * @return Title
+        */
+       public function getTarget() {
+               return $this->target;
+       }
+
+       public function getTimestamp() {
+               $ts = $this->timestamp ?? wfTimestampNow();
+
+               return wfTimestamp( TS_MW, $ts );
+       }
+
+       public function getComment() {
+               return $this->comment;
+       }
+
+       /**
+        * @since 1.27
+        * @return int
+        */
+       public function getAssociatedRevId() {
+               return $this->revId;
+       }
+
+       /**
+        * @since 1.27
+        * @return string[]
+        */
+       public function getTags() {
+               return $this->tags;
+       }
+
+       /**
+        * Whether this log entry is patrollable
+        *
+        * @since 1.27
+        * @return bool
+        */
+       public function getIsPatrollable() {
+               return $this->isPatrollable;
+       }
+
+       /**
+        * @since 1.25
+        * @return bool
+        */
+       public function isLegacy() {
+               return $this->legacy;
+       }
+
+       public function getDeleted() {
+               return (int)$this->deleted;
+       }
+}
diff --git a/includes/logging/RCDatabaseLogEntry.php b/includes/logging/RCDatabaseLogEntry.php
new file mode 100644 (file)
index 0000000..4dc4037
--- /dev/null
@@ -0,0 +1,89 @@
+<?php
+/**
+ * Contains a class for dealing with recent changes database log entries
+ *
+ * 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
+ * @author Niklas Laxström
+ * @license GPL-2.0-or-later
+ * @since 1.19
+ */
+
+/**
+ * A subclass of DatabaseLogEntry for objects constructed from entries in the
+ * recentchanges table (rather than the logging table).
+ */
+class RCDatabaseLogEntry extends DatabaseLogEntry {
+
+       public function getId() {
+               return $this->row->rc_logid;
+       }
+
+       protected function getRawParameters() {
+               return $this->row->rc_params;
+       }
+
+       public function getAssociatedRevId() {
+               return $this->row->rc_this_oldid;
+       }
+
+       public function getType() {
+               return $this->row->rc_log_type;
+       }
+
+       public function getSubtype() {
+               return $this->row->rc_log_action;
+       }
+
+       public function getPerformer() {
+               if ( !$this->performer ) {
+                       $actorId = isset( $this->row->rc_actor ) ? (int)$this->row->rc_actor : 0;
+                       $userId = (int)$this->row->rc_user;
+                       if ( $actorId !== 0 ) {
+                               $this->performer = User::newFromActorId( $actorId );
+                       } elseif ( $userId !== 0 ) {
+                               $this->performer = User::newFromId( $userId );
+                       } else {
+                               $userText = $this->row->rc_user_text;
+                               // Might be an IP, don't validate the username
+                               $this->performer = User::newFromName( $userText, false );
+                       }
+               }
+
+               return $this->performer;
+       }
+
+       public function getTarget() {
+               $namespace = $this->row->rc_namespace;
+               $page = $this->row->rc_title;
+               return Title::makeTitle( $namespace, $page );
+       }
+
+       public function getTimestamp() {
+               return wfTimestamp( TS_MW, $this->row->rc_timestamp );
+       }
+
+       public function getComment() {
+               return CommentStore::getStore()
+                       // Legacy because the row may have used RecentChange::selectFields()
+                       ->getCommentLegacy( wfGetDB( DB_REPLICA ), 'rc_comment', $this->row )->text;
+       }
+
+       public function getDeleted() {
+               return $this->row->rc_deleted;
+       }
+}
index 7189179..fde43f4 100644 (file)
@@ -36,8 +36,9 @@ use MediaWiki\Shell\Shell;
  * @ingroup Media
  */
 class DjVuImage {
+
        /**
-        * @const DJVUTXT_MEMORY_LIMIT Memory limit for the DjVu description software
+        * Memory limit for the DjVu description software
         */
        const DJVUTXT_MEMORY_LIMIT = 300000;
 
index a54da7d..1a96c1d 100644 (file)
@@ -104,7 +104,7 @@ abstract class MediaHandler {
         *   Warning, FSFile::getPropsFromPath might pass an FSFile instead of File (!)
         * @param string $path The filename
         * @return array|bool Follow the format of PHP getimagesize() internal function.
-        *   See https://secure.php.net/getimagesize. MediaWiki will only ever use the
+        *   See https://www.php.net/getimagesize. MediaWiki will only ever use the
         *   first two array keys (the width and height), and the 'bits' associative
         *   key. All other array keys are ignored. Returning a 'bits' key is optional
         *   as not all formats have a notion of "bitdepth". Returns false on failure.
index f3b5d8f..fb1b015 100644 (file)
@@ -30,8 +30,7 @@ class MediaTransformError extends MediaTransformOutput {
        /** @var Message */
        private $msg;
 
-       function __construct( $msg, $width, $height /*, ... */ ) {
-               $args = array_slice( func_get_args(), 3 );
+       function __construct( $msg, $width, $height, ...$args ) {
                $this->msg = wfMessage( $msg )->params( $args );
                $this->width = intval( $width );
                $this->height = intval( $height );
index bc5eb09..ac332b7 100644 (file)
@@ -35,361 +35,3 @@ class SVGMetadataExtractor {
                return $svg->getMetadata();
        }
 }
-
-/**
- * @ingroup Media
- */
-class SVGReader {
-       const DEFAULT_WIDTH = 512;
-       const DEFAULT_HEIGHT = 512;
-       const NS_SVG = 'http://www.w3.org/2000/svg';
-       const LANG_PREFIX_MATCH = 1;
-       const LANG_FULL_MATCH = 2;
-
-       /** @var null|XMLReader */
-       private $reader = null;
-
-       /** @var bool */
-       private $mDebug = false;
-
-       /** @var array */
-       private $metadata = [];
-       private $languages = [];
-       private $languagePrefixes = [];
-
-       /**
-        * Creates an SVGReader drawing from the source provided
-        * @param string $source URI from which to read
-        * @throws MWException|Exception
-        */
-       function __construct( $source ) {
-               global $wgSVGMetadataCutoff;
-               $this->reader = new XMLReader();
-
-               // Don't use $file->getSize() since file object passed to SVGHandler::getMetadata is bogus.
-               $size = filesize( $source );
-               if ( $size === false ) {
-                       throw new MWException( "Error getting filesize of SVG." );
-               }
-
-               if ( $size > $wgSVGMetadataCutoff ) {
-                       $this->debug( "SVG is $size bytes, which is bigger than $wgSVGMetadataCutoff. Truncating." );
-                       $contents = file_get_contents( $source, false, null, 0, $wgSVGMetadataCutoff );
-                       if ( $contents === false ) {
-                               throw new MWException( 'Error reading SVG file.' );
-                       }
-                       $this->reader->XML( $contents, null, LIBXML_NOERROR | LIBXML_NOWARNING );
-               } else {
-                       $this->reader->open( $source, null, LIBXML_NOERROR | LIBXML_NOWARNING );
-               }
-
-               // Expand entities, since Adobe Illustrator uses them for xmlns
-               // attributes (T33719). Note that libxml2 has some protection
-               // against large recursive entity expansions so this is not as
-               // insecure as it might appear to be. However, it is still extremely
-               // insecure. It's necessary to wrap any read() calls with
-               // libxml_disable_entity_loader() to avoid arbitrary local file
-               // inclusion, or even arbitrary code execution if the expect
-               // extension is installed (T48859).
-               $oldDisable = libxml_disable_entity_loader( true );
-               $this->reader->setParserProperty( XMLReader::SUBST_ENTITIES, true );
-
-               $this->metadata['width'] = self::DEFAULT_WIDTH;
-               $this->metadata['height'] = self::DEFAULT_HEIGHT;
-
-               // The size in the units specified by the SVG file
-               // (for the metadata box)
-               // Per the SVG spec, if unspecified, default to '100%'
-               $this->metadata['originalWidth'] = '100%';
-               $this->metadata['originalHeight'] = '100%';
-
-               // Because we cut off the end of the svg making an invalid one. Complicated
-               // try catch thing to make sure warnings get restored. Seems like there should
-               // be a better way.
-               Wikimedia\suppressWarnings();
-               try {
-                       $this->read();
-               } catch ( Exception $e ) {
-                       // Note, if this happens, the width/height will be taken to be 0x0.
-                       // Should we consider it the default 512x512 instead?
-                       Wikimedia\restoreWarnings();
-                       libxml_disable_entity_loader( $oldDisable );
-                       throw $e;
-               }
-               Wikimedia\restoreWarnings();
-               libxml_disable_entity_loader( $oldDisable );
-       }
-
-       /**
-        * @return array Array with the known metadata
-        */
-       public function getMetadata() {
-               return $this->metadata;
-       }
-
-       /**
-        * Read the SVG
-        * @throws MWException
-        * @return bool
-        */
-       protected function read() {
-               $keepReading = $this->reader->read();
-
-               /* Skip until first element */
-               while ( $keepReading && $this->reader->nodeType != XMLReader::ELEMENT ) {
-                       $keepReading = $this->reader->read();
-               }
-
-               if ( $this->reader->localName != 'svg' || $this->reader->namespaceURI != self::NS_SVG ) {
-                       throw new MWException( "Expected <svg> tag, got " .
-                               $this->reader->localName . " in NS " . $this->reader->namespaceURI );
-               }
-               $this->debug( "<svg> tag is correct." );
-               $this->handleSVGAttribs();
-
-               $exitDepth = $this->reader->depth;
-               $keepReading = $this->reader->read();
-               while ( $keepReading ) {
-                       $tag = $this->reader->localName;
-                       $type = $this->reader->nodeType;
-                       $isSVG = ( $this->reader->namespaceURI == self::NS_SVG );
-
-                       $this->debug( "$tag" );
-
-                       if ( $isSVG && $tag == 'svg' && $type == XMLReader::END_ELEMENT
-                               && $this->reader->depth <= $exitDepth
-                       ) {
-                               break;
-                       } elseif ( $isSVG && $tag == 'title' ) {
-                               $this->readField( $tag, 'title' );
-                       } elseif ( $isSVG && $tag == 'desc' ) {
-                               $this->readField( $tag, 'description' );
-                       } elseif ( $isSVG && $tag == 'metadata' && $type == XMLReader::ELEMENT ) {
-                               $this->readXml( 'metadata' );
-                       } elseif ( $isSVG && $tag == 'script' ) {
-                               // We normally do not allow scripted svgs.
-                               // However its possible to configure MW to let them
-                               // in, and such files should be considered animated.
-                               $this->metadata['animated'] = true;
-                       } elseif ( $tag !== '#text' ) {
-                               $this->debug( "Unhandled top-level XML tag $tag" );
-
-                               // Recurse into children of current tag, looking for animation and languages.
-                               $this->animateFilterAndLang( $tag );
-                       }
-
-                       // Goto next element, which is sibling of current (Skip children).
-                       $keepReading = $this->reader->next();
-               }
-
-               $this->reader->close();
-
-               $this->metadata['translations'] = $this->languages + $this->languagePrefixes;
-
-               return true;
-       }
-
-       /**
-        * Read a textelement from an element
-        *
-        * @param string $name Name of the element that we are reading from
-        * @param string $metafield Field that we will fill with the result
-        */
-       private function readField( $name, $metafield = null ) {
-               $this->debug( "Read field $metafield" );
-               if ( !$metafield || $this->reader->nodeType != XMLReader::ELEMENT ) {
-                       return;
-               }
-               $keepReading = $this->reader->read();
-               while ( $keepReading ) {
-                       if ( $this->reader->localName == $name
-                               && $this->reader->namespaceURI == self::NS_SVG
-                               && $this->reader->nodeType == XMLReader::END_ELEMENT
-                       ) {
-                               break;
-                       } elseif ( $this->reader->nodeType == XMLReader::TEXT ) {
-                               $this->metadata[$metafield] = trim( $this->reader->value );
-                       }
-                       $keepReading = $this->reader->read();
-               }
-       }
-
-       /**
-        * Read an XML snippet from an element
-        *
-        * @param string $metafield Field that we will fill with the result
-        * @throws MWException
-        */
-       private function readXml( $metafield = null ) {
-               $this->debug( "Read top level metadata" );
-               if ( !$metafield || $this->reader->nodeType != XMLReader::ELEMENT ) {
-                       return;
-               }
-               // @todo Find and store type of xml snippet. metadata['metadataType'] = "rdf"
-               $this->metadata[$metafield] = trim( $this->reader->readInnerXml() );
-
-               $this->reader->next();
-       }
-
-       /**
-        * Filter all children, looking for animated elements.
-        * Also get a list of languages that can be targeted.
-        *
-        * @param string $name Name of the element that we are reading from
-        */
-       private function animateFilterAndLang( $name ) {
-               $this->debug( "animate filter for tag $name" );
-               if ( $this->reader->nodeType != XMLReader::ELEMENT ) {
-                       return;
-               }
-               if ( $this->reader->isEmptyElement ) {
-                       return;
-               }
-               $exitDepth = $this->reader->depth;
-               $keepReading = $this->reader->read();
-               while ( $keepReading ) {
-                       if ( $this->reader->localName == $name && $this->reader->depth <= $exitDepth
-                               && $this->reader->nodeType == XMLReader::END_ELEMENT
-                       ) {
-                               break;
-                       } elseif ( $this->reader->namespaceURI == self::NS_SVG
-                               && $this->reader->nodeType == XMLReader::ELEMENT
-                       ) {
-                               $sysLang = $this->reader->getAttribute( 'systemLanguage' );
-                               if ( !is_null( $sysLang ) && $sysLang !== '' ) {
-                                       // See https://www.w3.org/TR/SVG/struct.html#SystemLanguageAttribute
-                                       $langList = explode( ',', $sysLang );
-                                       foreach ( $langList as $langItem ) {
-                                               $langItem = trim( $langItem );
-                                               if ( Language::isWellFormedLanguageTag( $langItem ) ) {
-                                                       $this->languages[$langItem] = self::LANG_FULL_MATCH;
-                                               }
-                                               // Note, the standard says that any prefix should work,
-                                               // here we do only the initial prefix, since that will catch
-                                               // 99% of cases, and we are going to compare against fallbacks.
-                                               // This differs mildly from how the spec says languages should be
-                                               // handled, however it matches better how the MediaWiki language
-                                               // preference is generally handled.
-                                               $dash = strpos( $langItem, '-' );
-                                               // Intentionally checking both !false and > 0 at the same time.
-                                               if ( $dash ) {
-                                                       $itemPrefix = substr( $langItem, 0, $dash );
-                                                       if ( Language::isWellFormedLanguageTag( $itemPrefix ) ) {
-                                                               $this->languagePrefixes[$itemPrefix] = self::LANG_PREFIX_MATCH;
-                                                       }
-                                               }
-                                       }
-                               }
-                               switch ( $this->reader->localName ) {
-                                       case 'script':
-                                               // Normally we disallow files with
-                                               // <script>, but its possible
-                                               // to configure MW to disable
-                                               // such checks.
-                                       case 'animate':
-                                       case 'set':
-                                       case 'animateMotion':
-                                       case 'animateColor':
-                                       case 'animateTransform':
-                                               $this->debug( "HOUSTON WE HAVE ANIMATION" );
-                                               $this->metadata['animated'] = true;
-                                               break;
-                               }
-                       }
-                       $keepReading = $this->reader->read();
-               }
-       }
-
-       private function debug( $data ) {
-               if ( $this->mDebug ) {
-                       wfDebug( "SVGReader: $data\n" );
-               }
-       }
-
-       /**
-        * Parse the attributes of an SVG element
-        *
-        * The parser has to be in the start element of "<svg>"
-        */
-       private function handleSVGAttribs() {
-               $defaultWidth = self::DEFAULT_WIDTH;
-               $defaultHeight = self::DEFAULT_HEIGHT;
-               $aspect = 1.0;
-               $width = null;
-               $height = null;
-
-               if ( $this->reader->getAttribute( 'viewBox' ) ) {
-                       // min-x min-y width height
-                       $viewBox = preg_split( '/\s*[\s,]\s*/', trim( $this->reader->getAttribute( 'viewBox' ) ) );
-                       if ( count( $viewBox ) == 4 ) {
-                               $viewWidth = $this->scaleSVGUnit( $viewBox[2] );
-                               $viewHeight = $this->scaleSVGUnit( $viewBox[3] );
-                               if ( $viewWidth > 0 && $viewHeight > 0 ) {
-                                       $aspect = $viewWidth / $viewHeight;
-                                       $defaultHeight = $defaultWidth / $aspect;
-                               }
-                       }
-               }
-               if ( $this->reader->getAttribute( 'width' ) ) {
-                       $width = $this->scaleSVGUnit( $this->reader->getAttribute( 'width' ), $defaultWidth );
-                       $this->metadata['originalWidth'] = $this->reader->getAttribute( 'width' );
-               }
-               if ( $this->reader->getAttribute( 'height' ) ) {
-                       $height = $this->scaleSVGUnit( $this->reader->getAttribute( 'height' ), $defaultHeight );
-                       $this->metadata['originalHeight'] = $this->reader->getAttribute( 'height' );
-               }
-
-               if ( !isset( $width ) && !isset( $height ) ) {
-                       $width = $defaultWidth;
-                       $height = $width / $aspect;
-               } elseif ( isset( $width ) && !isset( $height ) ) {
-                       $height = $width / $aspect;
-               } elseif ( isset( $height ) && !isset( $width ) ) {
-                       $width = $height * $aspect;
-               }
-
-               if ( $width > 0 && $height > 0 ) {
-                       $this->metadata['width'] = intval( round( $width ) );
-                       $this->metadata['height'] = intval( round( $height ) );
-               }
-       }
-
-       /**
-        * Return a rounded pixel equivalent for a labeled CSS/SVG length.
-        * https://www.w3.org/TR/SVG11/coords.html#Units
-        *
-        * @param string $length CSS/SVG length.
-        * @param float|int $viewportSize Optional scale for percentage units...
-        * @return float Length in pixels
-        */
-       static function scaleSVGUnit( $length, $viewportSize = 512 ) {
-               static $unitLength = [
-                       'px' => 1.0,
-                       'pt' => 1.25,
-                       'pc' => 15.0,
-                       'mm' => 3.543307,
-                       'cm' => 35.43307,
-                       'in' => 90.0,
-                       'em' => 16.0, // fake it?
-                       'ex' => 12.0, // fake it?
-                       '' => 1.0, // "User units" pixels by default
-               ];
-               $matches = [];
-               if ( preg_match(
-                       '/^\s*([-+]?\d*(?:\.\d+|\d+)(?:[Ee][-+]?\d+)?)\s*(em|ex|px|pt|pc|cm|mm|in|%|)\s*$/',
-                       $length,
-                       $matches
-               ) ) {
-                       $length = floatval( $matches[1] );
-                       $unit = $matches[2];
-                       if ( $unit == '%' ) {
-                               return $length * 0.01 * $viewportSize;
-                       } else {
-                               return $length * $unitLength[$unit];
-                       }
-               } else {
-                       // Assume pixels
-                       return floatval( $length );
-               }
-       }
-}
diff --git a/includes/media/SVGReader.php b/includes/media/SVGReader.php
new file mode 100644 (file)
index 0000000..480aec5
--- /dev/null
@@ -0,0 +1,384 @@
+<?php
+/**
+ * Extraction of SVG image metadata.
+ *
+ * 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 Media
+ * @author "Derk-Jan Hartman <hartman _at_ videolan d0t org>"
+ * @author Brion Vibber
+ * @copyright Copyright © 2010-2010 Brion Vibber, Derk-Jan Hartman
+ * @license GPL-2.0-or-later
+ */
+
+/**
+ * @ingroup Media
+ */
+class SVGReader {
+       const DEFAULT_WIDTH = 512;
+       const DEFAULT_HEIGHT = 512;
+       const NS_SVG = 'http://www.w3.org/2000/svg';
+       const LANG_PREFIX_MATCH = 1;
+       const LANG_FULL_MATCH = 2;
+
+       /** @var null|XMLReader */
+       private $reader = null;
+
+       /** @var bool */
+       private $mDebug = false;
+
+       /** @var array */
+       private $metadata = [];
+       private $languages = [];
+       private $languagePrefixes = [];
+
+       /**
+        * Creates an SVGReader drawing from the source provided
+        * @param string $source URI from which to read
+        * @throws MWException|Exception
+        */
+       function __construct( $source ) {
+               global $wgSVGMetadataCutoff;
+               $this->reader = new XMLReader();
+
+               // Don't use $file->getSize() since file object passed to SVGHandler::getMetadata is bogus.
+               $size = filesize( $source );
+               if ( $size === false ) {
+                       throw new MWException( "Error getting filesize of SVG." );
+               }
+
+               if ( $size > $wgSVGMetadataCutoff ) {
+                       $this->debug( "SVG is $size bytes, which is bigger than $wgSVGMetadataCutoff. Truncating." );
+                       $contents = file_get_contents( $source, false, null, 0, $wgSVGMetadataCutoff );
+                       if ( $contents === false ) {
+                               throw new MWException( 'Error reading SVG file.' );
+                       }
+                       $this->reader->XML( $contents, null, LIBXML_NOERROR | LIBXML_NOWARNING );
+               } else {
+                       $this->reader->open( $source, null, LIBXML_NOERROR | LIBXML_NOWARNING );
+               }
+
+               // Expand entities, since Adobe Illustrator uses them for xmlns
+               // attributes (T33719). Note that libxml2 has some protection
+               // against large recursive entity expansions so this is not as
+               // insecure as it might appear to be. However, it is still extremely
+               // insecure. It's necessary to wrap any read() calls with
+               // libxml_disable_entity_loader() to avoid arbitrary local file
+               // inclusion, or even arbitrary code execution if the expect
+               // extension is installed (T48859).
+               $oldDisable = libxml_disable_entity_loader( true );
+               $this->reader->setParserProperty( XMLReader::SUBST_ENTITIES, true );
+
+               $this->metadata['width'] = self::DEFAULT_WIDTH;
+               $this->metadata['height'] = self::DEFAULT_HEIGHT;
+
+               // The size in the units specified by the SVG file
+               // (for the metadata box)
+               // Per the SVG spec, if unspecified, default to '100%'
+               $this->metadata['originalWidth'] = '100%';
+               $this->metadata['originalHeight'] = '100%';
+
+               // Because we cut off the end of the svg making an invalid one. Complicated
+               // try catch thing to make sure warnings get restored. Seems like there should
+               // be a better way.
+               Wikimedia\suppressWarnings();
+               try {
+                       $this->read();
+               } catch ( Exception $e ) {
+                       // Note, if this happens, the width/height will be taken to be 0x0.
+                       // Should we consider it the default 512x512 instead?
+                       Wikimedia\restoreWarnings();
+                       libxml_disable_entity_loader( $oldDisable );
+                       throw $e;
+               }
+               Wikimedia\restoreWarnings();
+               libxml_disable_entity_loader( $oldDisable );
+       }
+
+       /**
+        * @return array Array with the known metadata
+        */
+       public function getMetadata() {
+               return $this->metadata;
+       }
+
+       /**
+        * Read the SVG
+        * @throws MWException
+        * @return bool
+        */
+       protected function read() {
+               $keepReading = $this->reader->read();
+
+               /* Skip until first element */
+               while ( $keepReading && $this->reader->nodeType != XMLReader::ELEMENT ) {
+                       $keepReading = $this->reader->read();
+               }
+
+               if ( $this->reader->localName != 'svg' || $this->reader->namespaceURI != self::NS_SVG ) {
+                       throw new MWException( "Expected <svg> tag, got " .
+                               $this->reader->localName . " in NS " . $this->reader->namespaceURI );
+               }
+               $this->debug( "<svg> tag is correct." );
+               $this->handleSVGAttribs();
+
+               $exitDepth = $this->reader->depth;
+               $keepReading = $this->reader->read();
+               while ( $keepReading ) {
+                       $tag = $this->reader->localName;
+                       $type = $this->reader->nodeType;
+                       $isSVG = ( $this->reader->namespaceURI == self::NS_SVG );
+
+                       $this->debug( "$tag" );
+
+                       if ( $isSVG && $tag == 'svg' && $type == XMLReader::END_ELEMENT
+                               && $this->reader->depth <= $exitDepth
+                       ) {
+                               break;
+                       } elseif ( $isSVG && $tag == 'title' ) {
+                               $this->readField( $tag, 'title' );
+                       } elseif ( $isSVG && $tag == 'desc' ) {
+                               $this->readField( $tag, 'description' );
+                       } elseif ( $isSVG && $tag == 'metadata' && $type == XMLReader::ELEMENT ) {
+                               $this->readXml( 'metadata' );
+                       } elseif ( $isSVG && $tag == 'script' ) {
+                               // We normally do not allow scripted svgs.
+                               // However its possible to configure MW to let them
+                               // in, and such files should be considered animated.
+                               $this->metadata['animated'] = true;
+                       } elseif ( $tag !== '#text' ) {
+                               $this->debug( "Unhandled top-level XML tag $tag" );
+
+                               // Recurse into children of current tag, looking for animation and languages.
+                               $this->animateFilterAndLang( $tag );
+                       }
+
+                       // Goto next element, which is sibling of current (Skip children).
+                       $keepReading = $this->reader->next();
+               }
+
+               $this->reader->close();
+
+               $this->metadata['translations'] = $this->languages + $this->languagePrefixes;
+
+               return true;
+       }
+
+       /**
+        * Read a textelement from an element
+        *
+        * @param string $name Name of the element that we are reading from
+        * @param string $metafield Field that we will fill with the result
+        */
+       private function readField( $name, $metafield = null ) {
+               $this->debug( "Read field $metafield" );
+               if ( !$metafield || $this->reader->nodeType != XMLReader::ELEMENT ) {
+                       return;
+               }
+               $keepReading = $this->reader->read();
+               while ( $keepReading ) {
+                       if ( $this->reader->localName == $name
+                               && $this->reader->namespaceURI == self::NS_SVG
+                               && $this->reader->nodeType == XMLReader::END_ELEMENT
+                       ) {
+                               break;
+                       } elseif ( $this->reader->nodeType == XMLReader::TEXT ) {
+                               $this->metadata[$metafield] = trim( $this->reader->value );
+                       }
+                       $keepReading = $this->reader->read();
+               }
+       }
+
+       /**
+        * Read an XML snippet from an element
+        *
+        * @param string $metafield Field that we will fill with the result
+        * @throws MWException
+        */
+       private function readXml( $metafield = null ) {
+               $this->debug( "Read top level metadata" );
+               if ( !$metafield || $this->reader->nodeType != XMLReader::ELEMENT ) {
+                       return;
+               }
+               // @todo Find and store type of xml snippet. metadata['metadataType'] = "rdf"
+               $this->metadata[$metafield] = trim( $this->reader->readInnerXml() );
+
+               $this->reader->next();
+       }
+
+       /**
+        * Filter all children, looking for animated elements.
+        * Also get a list of languages that can be targeted.
+        *
+        * @param string $name Name of the element that we are reading from
+        */
+       private function animateFilterAndLang( $name ) {
+               $this->debug( "animate filter for tag $name" );
+               if ( $this->reader->nodeType != XMLReader::ELEMENT ) {
+                       return;
+               }
+               if ( $this->reader->isEmptyElement ) {
+                       return;
+               }
+               $exitDepth = $this->reader->depth;
+               $keepReading = $this->reader->read();
+               while ( $keepReading ) {
+                       if ( $this->reader->localName == $name && $this->reader->depth <= $exitDepth
+                               && $this->reader->nodeType == XMLReader::END_ELEMENT
+                       ) {
+                               break;
+                       } elseif ( $this->reader->namespaceURI == self::NS_SVG
+                               && $this->reader->nodeType == XMLReader::ELEMENT
+                       ) {
+                               $sysLang = $this->reader->getAttribute( 'systemLanguage' );
+                               if ( !is_null( $sysLang ) && $sysLang !== '' ) {
+                                       // See https://www.w3.org/TR/SVG/struct.html#SystemLanguageAttribute
+                                       $langList = explode( ',', $sysLang );
+                                       foreach ( $langList as $langItem ) {
+                                               $langItem = trim( $langItem );
+                                               if ( Language::isWellFormedLanguageTag( $langItem ) ) {
+                                                       $this->languages[$langItem] = self::LANG_FULL_MATCH;
+                                               }
+                                               // Note, the standard says that any prefix should work,
+                                               // here we do only the initial prefix, since that will catch
+                                               // 99% of cases, and we are going to compare against fallbacks.
+                                               // This differs mildly from how the spec says languages should be
+                                               // handled, however it matches better how the MediaWiki language
+                                               // preference is generally handled.
+                                               $dash = strpos( $langItem, '-' );
+                                               // Intentionally checking both !false and > 0 at the same time.
+                                               if ( $dash ) {
+                                                       $itemPrefix = substr( $langItem, 0, $dash );
+                                                       if ( Language::isWellFormedLanguageTag( $itemPrefix ) ) {
+                                                               $this->languagePrefixes[$itemPrefix] = self::LANG_PREFIX_MATCH;
+                                                       }
+                                               }
+                                       }
+                               }
+                               switch ( $this->reader->localName ) {
+                                       case 'script':
+                                               // Normally we disallow files with
+                                               // <script>, but its possible
+                                               // to configure MW to disable
+                                               // such checks.
+                                       case 'animate':
+                                       case 'set':
+                                       case 'animateMotion':
+                                       case 'animateColor':
+                                       case 'animateTransform':
+                                               $this->debug( "HOUSTON WE HAVE ANIMATION" );
+                                               $this->metadata['animated'] = true;
+                                               break;
+                               }
+                       }
+                       $keepReading = $this->reader->read();
+               }
+       }
+
+       private function debug( $data ) {
+               if ( $this->mDebug ) {
+                       wfDebug( "SVGReader: $data\n" );
+               }
+       }
+
+       /**
+        * Parse the attributes of an SVG element
+        *
+        * The parser has to be in the start element of "<svg>"
+        */
+       private function handleSVGAttribs() {
+               $defaultWidth = self::DEFAULT_WIDTH;
+               $defaultHeight = self::DEFAULT_HEIGHT;
+               $aspect = 1.0;
+               $width = null;
+               $height = null;
+
+               if ( $this->reader->getAttribute( 'viewBox' ) ) {
+                       // min-x min-y width height
+                       $viewBox = preg_split( '/\s*[\s,]\s*/', trim( $this->reader->getAttribute( 'viewBox' ) ) );
+                       if ( count( $viewBox ) == 4 ) {
+                               $viewWidth = $this->scaleSVGUnit( $viewBox[2] );
+                               $viewHeight = $this->scaleSVGUnit( $viewBox[3] );
+                               if ( $viewWidth > 0 && $viewHeight > 0 ) {
+                                       $aspect = $viewWidth / $viewHeight;
+                                       $defaultHeight = $defaultWidth / $aspect;
+                               }
+                       }
+               }
+               if ( $this->reader->getAttribute( 'width' ) ) {
+                       $width = $this->scaleSVGUnit( $this->reader->getAttribute( 'width' ), $defaultWidth );
+                       $this->metadata['originalWidth'] = $this->reader->getAttribute( 'width' );
+               }
+               if ( $this->reader->getAttribute( 'height' ) ) {
+                       $height = $this->scaleSVGUnit( $this->reader->getAttribute( 'height' ), $defaultHeight );
+                       $this->metadata['originalHeight'] = $this->reader->getAttribute( 'height' );
+               }
+
+               if ( !isset( $width ) && !isset( $height ) ) {
+                       $width = $defaultWidth;
+                       $height = $width / $aspect;
+               } elseif ( isset( $width ) && !isset( $height ) ) {
+                       $height = $width / $aspect;
+               } elseif ( isset( $height ) && !isset( $width ) ) {
+                       $width = $height * $aspect;
+               }
+
+               if ( $width > 0 && $height > 0 ) {
+                       $this->metadata['width'] = intval( round( $width ) );
+                       $this->metadata['height'] = intval( round( $height ) );
+               }
+       }
+
+       /**
+        * Return a rounded pixel equivalent for a labeled CSS/SVG length.
+        * https://www.w3.org/TR/SVG11/coords.html#Units
+        *
+        * @param string $length CSS/SVG length.
+        * @param float|int $viewportSize Optional scale for percentage units...
+        * @return float Length in pixels
+        */
+       static function scaleSVGUnit( $length, $viewportSize = 512 ) {
+               static $unitLength = [
+                       'px' => 1.0,
+                       'pt' => 1.25,
+                       'pc' => 15.0,
+                       'mm' => 3.543307,
+                       'cm' => 35.43307,
+                       'in' => 90.0,
+                       'em' => 16.0, // fake it?
+                       'ex' => 12.0, // fake it?
+                       '' => 1.0, // "User units" pixels by default
+               ];
+               $matches = [];
+               if ( preg_match(
+                       '/^\s*([-+]?\d*(?:\.\d+|\d+)(?:[Ee][-+]?\d+)?)\s*(em|ex|px|pt|pc|cm|mm|in|%|)\s*$/',
+                       $length,
+                       $matches
+               ) ) {
+                       $length = floatval( $matches[1] );
+                       $unit = $matches[2];
+                       if ( $unit == '%' ) {
+                               return $length * 0.01 * $viewportSize;
+                       } else {
+                               return $length * $unitLength[$unit];
+                       }
+               } else {
+                       // Assume pixels
+                       return floatval( $length );
+               }
+       }
+}
index 0deb89f..36cf422 100644 (file)
@@ -112,7 +112,7 @@ class ThumbnailImage extends MediaTransformOutput {
        function toHtml( $options = [] ) {
                global $wgPriorityHints, $wgElementTiming;
 
-               if ( count( func_get_args() ) == 2 ) {
+               if ( func_num_args() == 2 ) {
                        throw new MWException( __METHOD__ . ' called in the old style' );
                }
 
index 33f33bd..e47cc37 100644 (file)
@@ -67,7 +67,7 @@ class XCFHandler extends BitmapHandler {
                }
 
                # Forge a return array containing metadata information just like getimagesize()
-               # See PHP documentation at: https://secure.php.net/getimagesize
+               # See PHP documentation at: https://www.php.net/getimagesize
                return [
                        0 => $header['width'],
                        1 => $header['height'],
index 6c1ac39..86c59ad 100644 (file)
@@ -961,7 +961,7 @@ EOT
                $out->addHTML( "<ul class='mw-imagepage-duplicates'>\n" );
 
                /**
-                * @var $file File
+                * @var File $file
                 */
                foreach ( $dupes as $file ) {
                        $fromSrc = '';
index 96ed8ee..931740c 100644 (file)
@@ -2736,6 +2736,8 @@ class WikiPage implements Page, IDBAccessObject {
                        $dbw->endAtomic( __METHOD__ );
 
                        $jobParams = [
+                               'namespace' => $this->getTitle()->getNamespace(),
+                               'title' => $this->getTitle()->getDBkey(),
                                'wikiPageId' => $id,
                                'requestId' => $webRequestId ?? WebRequest::getRequestId(),
                                'reason' => $reason,
@@ -2745,7 +2747,7 @@ class WikiPage implements Page, IDBAccessObject {
                                'logsubtype' => $logsubtype,
                        ];
 
-                       $job = new DeletePageJob( $this->getTitle(), $jobParams );
+                       $job = new DeletePageJob( $jobParams );
                        JobQueueGroup::singleton()->push( $job );
 
                        $status->warning( 'delete-scheduled',
index d1d1a9c..7ce96be 100644 (file)
@@ -90,13 +90,13 @@ class CoreParserFunctions {
 
        /**
         * @param Parser $parser
-        * @param string $part1
+        * @param string $part1 Message key
+        * @param mixed ...$params To pass to wfMessage()
         * @return array
         */
-       public static function intFunction( $parser, $part1 = '' /*, ... */ ) {
+       public static function intFunction( $parser, $part1 = '', ...$params ) {
                if ( strval( $part1 ) !== '' ) {
-                       $args = array_slice( func_get_args(), 2 );
-                       $message = wfMessage( $part1, $args )
+                       $message = wfMessage( $part1, $params )
                                ->inLanguage( $parser->getOptions()->getUserLangObj() );
                        return [ $message->plain(), 'noparse' => false ];
                } else {
@@ -113,7 +113,7 @@ class CoreParserFunctions {
         */
        public static function formatDate( $parser, $date, $defaultPref = null ) {
                $lang = $parser->getFunctionLang();
-               $df = DateFormatter::getInstance( $lang );
+               $df = MediaWikiServices::getInstance()->getDateFormatterFactory()->get( $lang );
 
                $date = trim( $date );
 
@@ -313,11 +313,10 @@ class CoreParserFunctions {
        /**
         * @param Parser $parser
         * @param string $username
+        * @param string ...$forms What to output for each gender
         * @return string
         */
-       public static function gender( $parser, $username ) {
-               $forms = array_slice( func_get_args(), 2 );
-
+       public static function gender( $parser, $username, ...$forms ) {
                // Some shortcuts to avoid loading user data unnecessarily
                if ( count( $forms ) === 0 ) {
                        return '';
@@ -351,10 +350,10 @@ class CoreParserFunctions {
        /**
         * @param Parser $parser
         * @param string $text
+        * @param string ...$forms What to output for each number (singular, dual, plural, etc.)
         * @return string
         */
-       public static function plural( $parser, $text = '' ) {
-               $forms = array_slice( func_get_args(), 2 );
+       public static function plural( $parser, $text = '', ...$forms ) {
                $text = $parser->getFunctionLang()->parseFormattedNumber( $text );
                settype( $text, ctype_digit( $text ) ? 'int' : 'float' );
                return $parser->getFunctionLang()->convertPlural( $text, $forms );
index c9bbc43..b0c41d9 100644 (file)
 use MediaWiki\MediaWikiServices;
 
 /**
- * Date formatter, recognises dates in plain text and formats them according to user preferences.
- * @todo preferences, OutputPage
+ * Date formatter. Recognises dates and formats them according to a specified preference.
+ *
+ * This class was originally introduced to detect and transform dates in free text. It is now
+ * only used by the {{#dateformat}} parser function. This is a very rudimentary date formatter;
+ * Language::sprintfDate() has many more features and is the correct choice for most new code.
+ * The main advantage of this date formatter is that it is able to format incomplete dates with an
+ * unspecified year.
+ *
  * @ingroup Parser
  */
 class DateFormatter {
-       private $mSource, $mTarget;
-       private $monthNames = '';
-
+       /** @var string[] Date format regexes indexed the class constants */
        private $regexes;
-       private $rules, $xMonths, $preferences;
 
-       private $lang, $mLinked;
+       /**
+        * @var int[][] Array of special rules. The first key is the preference ID
+        * (one of the class constants), the second key is the detected source
+        * format, and the value is the ID of the target format that will be used
+        * in that case.
+        */
+       private $rules = [];
 
-       /** @var string[] */
-       private $keys;
+       /**
+        * @var int[] Month numbers by lowercase name
+        */
+       private $xMonths = [];
 
-       /** @var string[] */
-       private $targets;
+       /**
+        * @var string[] Month names by number
+        */
+       private $monthNames = [];
 
+       /**
+        * @var int[] A map of descriptive preference text to internal format ID
+        */
+       private $preferenceIDs;
+
+       /** @var string[] Format strings similar to those used by date(), indexed by ID */
+       private $targetFormats;
+
+       /** Used as a preference ID for rules that apply regardless of preference */
        const ALL = -1;
+
+       /** No preference: the date may be left in the same format as the input */
        const NONE = 0;
+
+       /** e.g. January 15, 2001 */
        const MDY = 1;
+
+       /** e.g. 15 January 2001 */
        const DMY = 2;
+
+       /** e.g. 2001 January 15 */
        const YMD = 3;
-       const ISO1 = 4;
+
+       /** e.g. 2001-01-15 */
+       const ISO = 4;
+
+       /** The highest ID that is a valid user preference */
        const LASTPREF = 4;
-       const ISO2 = 5;
-       const YDM = 6;
-       const DM = 7;
-       const MD = 8;
-       const LAST = 8;
+
+       /** e.g. 2001, 15 January */
+       const YDM = 5;
+
+       /** e.g. 15 January */
+       const DM = 6;
+
+       /** e.g. January 15 */
+       const MD = 7;
+
+       /** The highest ID that is a valid target format */
+       const LAST = 7;
 
        /**
         * @param Language $lang In which language to format the date
         */
        public function __construct( Language $lang ) {
-               $this->lang = $lang;
-
-               $this->monthNames = $this->getMonthRegex();
+               $monthRegexParts = [];
                for ( $i = 1; $i <= 12; $i++ ) {
-                       $this->xMonths[$this->lang->lc( $this->lang->getMonthName( $i ) )] = $i;
-                       $this->xMonths[$this->lang->lc( $this->lang->getMonthAbbreviation( $i ) )] = $i;
+                       $monthName = $lang->getMonthName( $i );
+                       $monthAbbrev = $lang->getMonthAbbreviation( $i );
+                       $this->monthNames[$i] = $monthName;
+                       $monthRegexParts[] = preg_quote( $monthName, '/' );
+                       $monthRegexParts[] = preg_quote( $monthAbbrev, '/' );
+                       $this->xMonths[mb_strtolower( $monthName )] = $i;
+                       $this->xMonths[mb_strtolower( $monthAbbrev )] = $i;
                }
 
-               $this->regexTrail = '(?![a-z])/iu';
-
-               # Partial regular expressions
-               $this->prxDM = '\[\[(\d{1,2})[ _](' . $this->monthNames . ')\]\]';
-               $this->prxMD = '\[\[(' . $this->monthNames . ')[ _](\d{1,2})\]\]';
-               $this->prxY = '\[\[(\d{1,4}([ _]BC|))\]\]';
-               $this->prxISO1 = '\[\[(-?\d{4})]]-\[\[(\d{2})-(\d{2})\]\]';
-               $this->prxISO2 = '\[\[(-?\d{4})-(\d{2})-(\d{2})\]\]';
-
-               # Real regular expressions
-               $this->regexes[self::DMY] = "/{$this->prxDM}(?: *, *| +){$this->prxY}{$this->regexTrail}";
-               $this->regexes[self::YDM] = "/{$this->prxY}(?: *, *| +){$this->prxDM}{$this->regexTrail}";
-               $this->regexes[self::MDY] = "/{$this->prxMD}(?: *, *| +){$this->prxY}{$this->regexTrail}";
-               $this->regexes[self::YMD] = "/{$this->prxY}(?: *, *| +){$this->prxMD}{$this->regexTrail}";
-               $this->regexes[self::DM] = "/{$this->prxDM}{$this->regexTrail}";
-               $this->regexes[self::MD] = "/{$this->prxMD}{$this->regexTrail}";
-               $this->regexes[self::ISO1] = "/{$this->prxISO1}{$this->regexTrail}";
-               $this->regexes[self::ISO2] = "/{$this->prxISO2}{$this->regexTrail}";
-
-               # Extraction keys
-               # See the comments in replace() for the meaning of the letters
-               $this->keys[self::DMY] = 'jFY';
-               $this->keys[self::YDM] = 'Y jF';
-               $this->keys[self::MDY] = 'FjY';
-               $this->keys[self::YMD] = 'Y Fj';
-               $this->keys[self::DM] = 'jF';
-               $this->keys[self::MD] = 'Fj';
-               $this->keys[self::ISO1] = 'ymd'; # y means ISO year
-               $this->keys[self::ISO2] = 'ymd';
-
-               # Target date formats
-               $this->targets[self::DMY] = '[[F j|j F]] [[Y]]';
-               $this->targets[self::YDM] = '[[Y]], [[F j|j F]]';
-               $this->targets[self::MDY] = '[[F j]], [[Y]]';
-               $this->targets[self::YMD] = '[[Y]] [[F j]]';
-               $this->targets[self::DM] = '[[F j|j F]]';
-               $this->targets[self::MD] = '[[F j]]';
-               $this->targets[self::ISO1] = '[[Y|y]]-[[F j|m-d]]';
-               $this->targets[self::ISO2] = '[[y-m-d]]';
-
-               # Rules
-               #            pref       source      target
+               // Partial regular expressions
+               $monthNames = implode( '|', $monthRegexParts );
+               $dm = "(?<day>\d{1,2})[ _](?<monthName>{$monthNames})";
+               $md = "(?<monthName>{$monthNames})[ _](?<day>\d{1,2})";
+               $y = '(?<year>\d{1,4}([ _]BC|))';
+               $iso = '(?<isoYear>-?\d{4})-(?<isoMonth>\d{2})-(?<isoDay>\d{2})';
+
+               $this->regexes = [
+                       self::DMY => "/^{$dm}(?: *, *| +){$y}$/iu",
+                       self::YDM => "/^{$y}(?: *, *| +){$dm}$/iu",
+                       self::MDY => "/^{$md}(?: *, *| +){$y}$/iu",
+                       self::YMD => "/^{$y}(?: *, *| +){$md}$/iu",
+                       self::DM => "/^{$dm}$/iu",
+                       self::MD => "/^{$md}$/iu",
+                       self::ISO => "/^{$iso}$/iu",
+               ];
+
+               // Target date formats
+               $this->targetFormats = [
+                       self::DMY => 'j F Y',
+                       self::YDM => 'Y, j F',
+                       self::MDY => 'F j, Y',
+                       self::YMD => 'Y F j',
+                       self::DM => 'j F',
+                       self::MD => 'F j',
+                       self::ISO => 'y-m-d',
+               ];
+
+               // Rules
+               //           pref       source      target
                $this->rules[self::DMY][self::MD] = self::DM;
                $this->rules[self::ALL][self::MD] = self::MD;
                $this->rules[self::MDY][self::DM] = self::MD;
                $this->rules[self::ALL][self::DM] = self::DM;
-               $this->rules[self::NONE][self::ISO2] = self::ISO1;
+               $this->rules[self::NONE][self::ISO] = self::ISO;
 
-               $this->preferences = [
+               $this->preferenceIDs = [
                        'default' => self::NONE,
                        'dmy' => self::DMY,
                        'mdy' => self::MDY,
                        'ymd' => self::YMD,
-                       'ISO 8601' => self::ISO1,
+                       'ISO 8601' => self::ISO,
                ];
        }
 
        /**
         * Get a DateFormatter object
         *
+        * @deprecated since 1.33 use MediaWikiServices::getDateFormatterFactory()
+        *
         * @param Language|null $lang In which language to format the date
         *     Defaults to the site content language
         * @return DateFormatter
         */
        public static function getInstance( Language $lang = null ) {
-               global $wgMainCacheType;
-
                $lang = $lang ?? MediaWikiServices::getInstance()->getContentLanguage();
-               $cache = ObjectCache::getLocalServerInstance( $wgMainCacheType );
-
-               static $dateFormatter = false;
-               if ( !$dateFormatter ) {
-                       $dateFormatter = $cache->getWithSetCallback(
-                               $cache->makeKey( 'dateformatter', $lang->getCode() ),
-                               $cache::TTL_HOUR,
-                               function () use ( $lang ) {
-                                       return new DateFormatter( $lang );
-                               }
-                       );
-               }
-
-               return $dateFormatter;
+               return MediaWikiServices::getInstance()->getDateFormatterFactory()->get( $lang );
        }
 
        /**
-        * @param string $preference User preference
+        * @param string $preference User preference, must be one of "default",
+        *   "dmy", "mdy", "ymd" or "ISO 8601".
         * @param string $text Text to reformat
-        * @param array $options Array can contain 'linked' and/or 'match-whole'
+        * @param array $options Ignored. Since 1.33, 'match-whole' is implied, and
+        *  'linked' has been removed.
         *
         * @return string
         */
-       public function reformat( $preference, $text, $options = [ 'linked' ] ) {
-               $linked = in_array( 'linked', $options );
-               $match_whole = in_array( 'match-whole', $options );
-
-               if ( isset( $this->preferences[$preference] ) ) {
-                       $preference = $this->preferences[$preference];
+       public function reformat( $preference, $text, $options = [] ) {
+               if ( isset( $this->preferenceIDs[$preference] ) ) {
+                       $preference = $this->preferenceIDs[$preference];
                } else {
                        $preference = self::NONE;
                }
-               for ( $i = 1; $i <= self::LAST; $i++ ) {
-                       $this->mSource = $i;
-                       if ( isset( $this->rules[$preference][$i] ) ) {
+               for ( $source = 1; $source <= self::LAST; $source++ ) {
+                       if ( isset( $this->rules[$preference][$source] ) ) {
                                # Specific rules
-                               $this->mTarget = $this->rules[$preference][$i];
-                       } elseif ( isset( $this->rules[self::ALL][$i] ) ) {
+                               $target = $this->rules[$preference][$source];
+                       } elseif ( isset( $this->rules[self::ALL][$source] ) ) {
                                # General rules
-                               $this->mTarget = $this->rules[self::ALL][$i];
+                               $target = $this->rules[self::ALL][$source];
                        } elseif ( $preference ) {
                                # User preference
-                               $this->mTarget = $preference;
+                               $target = $preference;
                        } else {
                                # Default
-                               $this->mTarget = $i;
+                               $target = $source;
                        }
-                       $regex = $this->regexes[$i];
+                       $regex = $this->regexes[$source];
 
-                       // Horrible hack
-                       if ( !$linked ) {
-                               $regex = str_replace( [ '\[\[', '\]\]' ], '', $regex );
-                       }
-
-                       if ( $match_whole ) {
-                               // Let's hope this works
-                               $regex = preg_replace( '!^/!', '/^', $regex );
-                               $regex = str_replace( $this->regexTrail,
-                                       '$' . $this->regexTrail, $regex );
-                       }
+                       $text = preg_replace_callback( $regex,
+                               function ( $match ) use ( $target ) {
+                                       $format = $this->targetFormats[$target];
 
-                       // Another horrible hack
-                       $this->mLinked = $linked;
-                       $text = preg_replace_callback( $regex, [ $this, 'replace' ], $text );
-                       unset( $this->mLinked );
-               }
-               return $text;
-       }
+                                       $text = '';
 
-       /**
-        * Regexp replacement callback
-        *
-        * @param array $matches
-        * @return string
-        */
-       private function replace( $matches ) {
-               # Extract information from $matches
-               $linked = $this->mLinked ?? true;
-
-               $bits = [];
-               $key = $this->keys[$this->mSource];
-               $keyLength = strlen( $key );
-               for ( $p = 0; $p < $keyLength; $p++ ) {
-                       if ( $key[$p] != ' ' ) {
-                               $bits[$key[$p]] = $matches[$p + 1];
-                       }
-               }
-
-               return $this->formatDate( $bits, $matches[0], $linked );
-       }
-
-       /**
-        * @param array $bits
-        * @param string $orig Original input string, to be returned
-        *  on formatting failure.
-        * @param bool $link
-        * @return string
-        */
-       private function formatDate( $bits, $orig, $link = true ) {
-               $format = $this->targets[$this->mTarget];
-
-               if ( !$link ) {
-                       // strip piped links
-                       $format = preg_replace( '/\[\[[^|]+\|([^\]]+)\]\]/', '$1', $format );
-                       // strip remaining links
-                       $format = str_replace( [ '[[', ']]' ], '', $format );
-               }
-
-               # Construct new date
-               $text = '';
-               $fail = false;
-
-               // Pre-generate y/Y stuff because we need the year for the <span> title.
-               if ( !isset( $bits['y'] ) && isset( $bits['Y'] ) ) {
-                       $bits['y'] = $this->makeIsoYear( $bits['Y'] );
-               }
-               if ( !isset( $bits['Y'] ) && isset( $bits['y'] ) ) {
-                       $bits['Y'] = $this->makeNormalYear( $bits['y'] );
-               }
-
-               if ( !isset( $bits['m'] ) ) {
-                       $m = $this->makeIsoMonth( $bits['F'] );
-                       if ( $m === false ) {
-                               $fail = true;
-                       } else {
-                               $bits['m'] = $m;
-                       }
-               }
-
-               if ( !isset( $bits['d'] ) ) {
-                       $bits['d'] = sprintf( '%02d', $bits['j'] );
-               }
-
-               $formatLength = strlen( $format );
-               for ( $p = 0; $p < $formatLength; $p++ ) {
-                       $char = $format[$p];
-                       switch ( $char ) {
-                               case 'd': # ISO day of month
-                                       $text .= $bits['d'];
-                                       break;
-                               case 'm': # ISO month
-                                       $text .= $bits['m'];
-                                       break;
-                               case 'y': # ISO year
-                                       $text .= $bits['y'];
-                                       break;
-                               case 'j': # ordinary day of month
-                                       if ( !isset( $bits['j'] ) ) {
-                                               $text .= intval( $bits['d'] );
-                                       } else {
-                                               $text .= $bits['j'];
+                                       // Pre-generate y/Y stuff because we need the year for the <span> title.
+                                       if ( !isset( $match['isoYear'] ) && isset( $match['year'] ) ) {
+                                               $match['isoYear'] = $this->makeIsoYear( $match['year'] );
+                                       }
+                                       if ( !isset( $match['year'] ) && isset( $match['isoYear'] ) ) {
+                                               $match['year'] = $this->makeNormalYear( $match['isoYear'] );
                                        }
-                                       break;
-                               case 'F': # long month
-                                       if ( !isset( $bits['F'] ) ) {
-                                               $m = intval( $bits['m'] );
-                                               if ( $m > 12 || $m < 1 ) {
-                                                       $fail = true;
+
+                                       if ( !isset( $match['isoMonth'] ) ) {
+                                               $m = $this->makeIsoMonth( $match['monthName'] );
+                                               if ( $m === false ) {
+                                                       // Fail
+                                                       return $match[0];
                                                } else {
-                                                       $text .= $this->lang->getMonthName( $m );
+                                                       $match['isoMonth'] = $m;
                                                }
-                                       } else {
-                                               $text .= ucfirst( $bits['F'] );
                                        }
-                                       break;
-                               case 'Y': # ordinary (optional BC) year
-                                       $text .= $bits['Y'];
-                                       break;
-                               default:
-                                       $text .= $char;
-                       }
-               }
-               if ( $fail ) {
-                       // This occurs when parsing a date with day or month outside the bounds
-                       // of possibilities.
-                       return $orig;
-               }
 
-               $isoBits = [];
-               if ( isset( $bits['y'] ) ) {
-                       $isoBits[] = $bits['y'];
-               }
-               $isoBits[] = $bits['m'];
-               $isoBits[] = $bits['d'];
-               $isoDate = implode( '-', $isoBits );
+                                       if ( !isset( $match['isoDay'] ) ) {
+                                               $match['isoDay'] = sprintf( '%02d', $match['day'] );
+                                       }
+
+                                       $formatLength = strlen( $format );
+                                       for ( $p = 0; $p < $formatLength; $p++ ) {
+                                               $char = $format[$p];
+                                               switch ( $char ) {
+                                                       case 'd': // ISO day of month
+                                                               $text .= $match['isoDay'];
+                                                               break;
+                                                       case 'm': // ISO month
+                                                               $text .= $match['isoMonth'];
+                                                               break;
+                                                       case 'y': // ISO year
+                                                               $text .= $match['isoYear'];
+                                                               break;
+                                                       case 'j': // ordinary day of month
+                                                               if ( !isset( $match['day'] ) ) {
+                                                                       $text .= intval( $match['isoDay'] );
+                                                               } else {
+                                                                       $text .= $match['day'];
+                                                               }
+                                                               break;
+                                                       case 'F': // long month
+                                                               $m = intval( $match['isoMonth'] );
+                                                               if ( $m > 12 || $m < 1 ) {
+                                                                       // Fail
+                                                                       return $match[0];
+                                                               } else {
+                                                                       $text .= $this->monthNames[$m];
+                                                               }
+                                                               break;
+                                                       case 'Y': // ordinary (optional BC) year
+                                                               $text .= $match['year'];
+                                                               break;
+                                                       default:
+                                                               $text .= $char;
+                                               }
+                                       }
 
-               // Output is not strictly HTML (it's wikitext), but <span> is whitelisted.
-               $text = Html::rawElement( 'span',
-                                       [ 'class' => 'mw-formatted-date', 'title' => $isoDate ], $text );
+                                       $isoBits = [];
+                                       if ( isset( $match['isoYear'] ) ) {
+                                               $isoBits[] = $match['isoYear'];
+                                       }
+                                       $isoBits[] = $match['isoMonth'];
+                                       $isoBits[] = $match['isoDay'];
+                                       $isoDate = implode( '-', $isoBits );
 
-               return $text;
-       }
+                                       // Output is not strictly HTML (it's wikitext), but <span> is whitelisted.
+                                       $text = Html::rawElement( 'span',
+                                               [ 'class' => 'mw-formatted-date', 'title' => $isoDate ], $text );
 
-       /**
-        * Return a regex that can be used to find month names in string
-        * @return string regex to find the months with
-        */
-       private function getMonthRegex() {
-               $names = [];
-               for ( $i = 1; $i <= 12; $i++ ) {
-                       $names[] = preg_quote( $this->lang->getMonthName( $i ), '/' );
-                       $names[] = preg_quote( $this->lang->getMonthAbbreviation( $i ), '/' );
+                                       return $text;
+                               }, $text
+                       );
                }
-               return implode( '|', $names );
+               return $text;
        }
 
        /**
@@ -348,7 +292,7 @@ class DateFormatter {
         * @return string|false ISO month name, or false if the input was invalid
         */
        private function makeIsoMonth( $monthName ) {
-               $isoMonth = $this->xMonths[$this->lang->lc( $monthName )] ?? false;
+               $isoMonth = $this->xMonths[mb_strtolower( $monthName )] ?? false;
                if ( $isoMonth === false ) {
                        return false;
                }
@@ -361,12 +305,11 @@ class DateFormatter {
         * @return string ISO year name
         */
        private function makeIsoYear( $year ) {
-               # Assumes the year is in a nice format, as enforced by the regex
+               // Assumes the year is in a nice format, as enforced by the regex
                if ( substr( $year, -2 ) == 'BC' ) {
                        $num = intval( substr( $year, 0, -3 ) ) - 1;
-                       # PHP bug note: sprintf( "%04d", -1 ) fails poorly
+                       // PHP bug note: sprintf( "%04d", -1 ) fails poorly
                        $text = sprintf( '-%04d', $num );
-
                } else {
                        $text = sprintf( '%04d', $year );
                }
@@ -374,7 +317,7 @@ class DateFormatter {
        }
 
        /**
-        * Make a year one from an ISO year, for instance: '400 BC' from '-0399'.
+        * Make a year from an ISO year, for instance: '400 BC' from '-0399'.
         * @param string $iso ISO year
         * @return int|string int representing year number in case of AD dates, or string containing
         *   year number and 'BC' at the end otherwise.
diff --git a/includes/parser/DateFormatterFactory.php b/includes/parser/DateFormatterFactory.php
new file mode 100644 (file)
index 0000000..d18ecf4
--- /dev/null
@@ -0,0 +1,18 @@
+<?php
+
+class DateFormatterFactory {
+       /** @var DateFormatter[] */
+       private $instances;
+
+       /**
+        * @param Language $lang
+        * @return DateFormatter
+        */
+       public function get( Language $lang ) {
+               $code = $lang->getCode();
+               if ( !isset( $this->instances[$code] ) ) {
+                       $this->instances[$code] = new DateFormatter( $lang );
+               }
+               return $this->instances[$code];
+       }
+}
index 47e5b40..c28d842 100644 (file)
@@ -280,6 +280,9 @@ class Parser {
        /** @var LinkRendererFactory */
        private $linkRendererFactory;
 
+       /** @var NamespaceInfo */
+       private $nsInfo;
+
        /**
         * @param array $parserConf See $wgParserConf documentation
         * @param MagicWordFactory|null $magicWordFactory
@@ -289,12 +292,14 @@ class Parser {
         * @param SpecialPageFactory|null $spFactory
         * @param Config|null $siteConfig
         * @param LinkRendererFactory|null $linkRendererFactory
+        * @param NamespaceInfo|null $nsInfo
         */
        public function __construct(
                array $parserConf = [], MagicWordFactory $magicWordFactory = null,
                Language $contLang = null, ParserFactory $factory = null, $urlProtocols = null,
                SpecialPageFactory $spFactory = null, Config $siteConfig = null,
-               LinkRendererFactory $linkRendererFactory = null
+               LinkRendererFactory $linkRendererFactory = null,
+               NamespaceInfo $nsInfo = null
        ) {
                $this->mConf = $parserConf;
                $this->mUrlProtocols = $urlProtocols ?? wfUrlProtocols();
@@ -325,10 +330,10 @@ class Parser {
 
                $this->factory = $factory ?? $services->getParserFactory();
                $this->specialPageFactory = $spFactory ?? $services->getSpecialPageFactory();
-               $this->siteConfig = $siteConfig ?? MediaWikiServices::getInstance()->getMainConfig();
-
+               $this->siteConfig = $siteConfig ?? $services->getMainConfig();
                $this->linkRendererFactory =
-                       $linkRendererFactory ?? MediaWikiServices::getInstance()->getLinkRendererFactory();
+                       $linkRendererFactory ?? $services->getLinkRendererFactory();
+               $this->nsInfo = $nsInfo ?? $services->getNamespaceInfo();
        }
 
        /**
@@ -2529,7 +2534,7 @@ class Parser {
         */
        public function areSubpagesAllowed() {
                # Some namespaces don't allow subpages
-               return MWNamespace::hasSubpages( $this->mTitle->getNamespace() );
+               return $this->nsInfo->hasSubpages( $this->mTitle->getNamespace() );
        }
 
        /**
@@ -2600,9 +2605,15 @@ class Parser {
                        $this->siteConfig->get( 'MiserMode' ) &&
                        !$this->mOptions->getInterfaceMessage() &&
                        // @TODO: disallow this word on all namespaces
-                       MWNamespace::isContent( $this->mTitle->getNamespace() )
+                       $this->nsInfo->isContent( $this->mTitle->getNamespace() )
                ) {
-                       return $this->mRevisionId ? '-' : '';
+                       if ( $this->mRevisionId || $this->mOptions->getSpeculativeRevId() ) {
+                               return '-';
+                       } else {
+                               $this->mOutput->setFlag( 'vary-revision-exists' );
+
+                               return '';
+                       }
                };
 
                $pageLang = $this->getFunctionLang();
@@ -3339,7 +3350,7 @@ class Parser {
                                                        );
                                                }
                                        }
-                               } elseif ( MWNamespace::isNonincludable( $title->getNamespace() ) ) {
+                               } elseif ( $this->nsInfo->isNonincludable( $title->getNamespace() ) ) {
                                        $found = false; # access denied
                                        wfDebug( __METHOD__ . ": template inclusion denied for " .
                                                $title->getPrefixedDBkey() . "\n" );
index 05c0622..cddacf4 100644 (file)
@@ -19,7 +19,7 @@
  * @ingroup Parser
  */
 use MediaWiki\Linker\LinkRendererFactory;
-
+use MediaWiki\MediaWikiServices;
 use MediaWiki\Special\SpecialPageFactory;
 
 /**
@@ -47,6 +47,9 @@ class ParserFactory {
        /** @var LinkRendererFactory */
        private $linkRendererFactory;
 
+       /** @var NamespaceInfo */
+       private $nsInfo;
+
        /**
         * @param array $parserConf See $wgParserConf documentation
         * @param MagicWordFactory $magicWordFactory
@@ -55,12 +58,18 @@ class ParserFactory {
         * @param SpecialPageFactory $spFactory
         * @param Config $siteConfig
         * @param LinkRendererFactory $linkRendererFactory
+        * @param NamespaceInfo|null $nsInfo
         * @since 1.32
         */
        public function __construct(
                array $parserConf, MagicWordFactory $magicWordFactory, Language $contLang, $urlProtocols,
-               SpecialPageFactory $spFactory, Config $siteConfig, LinkRendererFactory $linkRendererFactory
+               SpecialPageFactory $spFactory, Config $siteConfig,
+               LinkRendererFactory $linkRendererFactory, NamespaceInfo $nsInfo = null
        ) {
+               if ( !$nsInfo ) {
+                       wfDeprecated( __METHOD__ . ' with no NamespaceInfo argument', '1.34' );
+                       $nsInfo = MediaWikiServices::getInstance()->getNamespaceInfo();
+               }
                $this->parserConf = $parserConf;
                $this->magicWordFactory = $magicWordFactory;
                $this->contLang = $contLang;
@@ -68,6 +77,7 @@ class ParserFactory {
                $this->specialPageFactory = $spFactory;
                $this->siteConfig = $siteConfig;
                $this->linkRendererFactory = $linkRendererFactory;
+               $this->nsInfo = $nsInfo;
        }
 
        /**
@@ -77,6 +87,6 @@ class ParserFactory {
        public function create() : Parser {
                return new Parser( $this->parserConf, $this->magicWordFactory, $this->contLang, $this,
                        $this->urlProtocols, $this->specialPageFactory, $this->siteConfig,
-                       $this->linkRendererFactory );
+                       $this->linkRendererFactory, $this->nsInfo );
        }
 }
index bdca848..66b1612 100644 (file)
@@ -123,7 +123,7 @@ class ParserOptions {
         */
 
        /**
-        * Fetch an option, generically
+        * Fetch an option and track that is was accessed
         * @since 1.30
         * @param string $name Option name
         * @return mixed
@@ -133,15 +133,22 @@ class ParserOptions {
                        throw new InvalidArgumentException( "Unknown parser option $name" );
                }
 
-               if ( isset( self::$lazyOptions[$name] ) && $this->options[$name] === null ) {
-                       $this->options[$name] = call_user_func( self::$lazyOptions[$name], $this, $name );
-               }
+               $this->lazyLoadOption( $name );
                if ( !empty( self::$inCacheKey[$name] ) ) {
                        $this->optionUsed( $name );
                }
                return $this->options[$name];
        }
 
+       /**
+        * @param string $name Lazy load option without tracking usage
+        */
+       private function lazyLoadOption( $name ) {
+               if ( isset( self::$lazyOptions[$name] ) && $this->options[$name] === null ) {
+                       $this->options[$name] = call_user_func( self::$lazyOptions[$name], $this, $name );
+               }
+       }
+
        /**
         * Set an option, generically
         * @since 1.30
@@ -1197,22 +1204,16 @@ class ParserOptions {
         * @since 1.25
         */
        public function matches( ParserOptions $other ) {
-               // Populate lazy options
-               foreach ( self::$lazyOptions as $name => $callback ) {
-                       if ( $this->options[$name] === null ) {
-                               $this->options[$name] = call_user_func( $callback, $this, $name );
-                       }
-                       if ( $other->options[$name] === null ) {
-                               $other->options[$name] = call_user_func( $callback, $other, $name );
-                       }
-               }
-
                // Compare most options
                $options = array_keys( $this->options );
                $options = array_diff( $options, [
                        'enableLimitReport', // only affects HTML comments
                ] );
                foreach ( $options as $option ) {
+                       // Resolve any lazy options
+                       $this->lazyLoadOption( $option );
+                       $other->lazyLoadOption( $option );
+
                        $o1 = $this->optionToString( $this->options[$option] );
                        $o2 = $this->optionToString( $other->options[$option] );
                        if ( $o1 !== $o2 ) {
@@ -1238,6 +1239,27 @@ class ParserOptions {
                return true;
        }
 
+       /**
+        * @param ParserOptions $other
+        * @return bool Whether the cache key relevant options match those of $other
+        * @since 1.33
+        */
+       public function matchesForCacheKey( ParserOptions $other ) {
+               foreach ( self::allCacheVaryingOptions() as $option ) {
+                       // Populate any lazy options
+                       $this->lazyLoadOption( $option );
+                       $other->lazyLoadOption( $option );
+
+                       $o1 = $this->optionToString( $this->options[$option] );
+                       $o2 = $this->optionToString( $other->options[$option] );
+                       if ( $o1 !== $o2 ) {
+                               return false;
+                       }
+               }
+
+               return true;
+       }
+
        /**
         * Registers a callback for tracking which ParserOptions which are used.
         * This is a private API with the parser.
@@ -1314,10 +1336,9 @@ class ParserOptions {
                $inCacheKey = self::allCacheVaryingOptions();
 
                // Resolve any lazy options
-               foreach ( array_intersect( $forOptions, $inCacheKey, array_keys( self::$lazyOptions ) ) as $k ) {
-                       if ( $this->options[$k] === null ) {
-                               $this->options[$k] = call_user_func( self::$lazyOptions[$k], $this, $k );
-                       }
+               $lazyOpts = array_intersect( $forOptions, $inCacheKey, array_keys( self::$lazyOptions ) );
+               foreach ( $lazyOpts as $k ) {
+                       $this->lazyLoadOption( $k );
                }
 
                $options = $this->options;
index b6084d8..bdfedd6 100644 (file)
@@ -22,6 +22,7 @@
  */
 
 use MediaWiki\Logger\LoggerFactory;
+use MediaWiki\MediaWikiServices;
 
 /**
  * @ingroup Parser
@@ -73,10 +74,12 @@ abstract class Preprocessor {
                        return;
                }
 
-               $cache = ObjectCache::getLocalClusterInstance();
+               $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
                $key = $cache->makeKey(
                        defined( 'static::CACHE_PREFIX' ) ? static::CACHE_PREFIX : static::class,
-                       md5( $text ), $flags );
+                       md5( $text ),
+                       $flags
+               );
                $value = sprintf( "%08d", static::CACHE_VERSION ) . $tree;
 
                $cache->set( $key, $value, 86400 );
@@ -102,11 +105,13 @@ abstract class Preprocessor {
                        return false;
                }
 
-               $cache = ObjectCache::getLocalClusterInstance();
+               $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
 
                $key = $cache->makeKey(
                        defined( 'static::CACHE_PREFIX' ) ? static::CACHE_PREFIX : static::class,
-                       md5( $text ), $flags );
+                       md5( $text ),
+                       $flags
+               );
 
                $value = $cache->get( $key );
                if ( !$value ) {
index 4ed6b79..c27a635 100644 (file)
@@ -1421,12 +1421,10 @@ class PPFrame_DOM implements PPFrame {
        /**
         * @param string $sep
         * @param int $flags
-        * @param string|PPNode_DOM|DOMDocument $args,...
+        * @param string|PPNode_DOM|DOMDocument ...$args
         * @return string
         */
-       public function implodeWithFlags( $sep, $flags /*, ... */ ) {
-               $args = array_slice( func_get_args(), 2 );
-
+       public function implodeWithFlags( $sep, $flags, ...$args ) {
                $first = true;
                $s = '';
                foreach ( $args as $root ) {
@@ -1453,12 +1451,10 @@ class PPFrame_DOM implements PPFrame {
         * This previously called implodeWithFlags but has now been inlined to reduce stack depth
         *
         * @param string $sep
-        * @param string|PPNode_DOM|DOMDocument $args,...
+        * @param string|PPNode_DOM|DOMDocument ...$args
         * @return string
         */
-       public function implode( $sep /*, ... */ ) {
-               $args = array_slice( func_get_args(), 1 );
-
+       public function implode( $sep, ...$args ) {
                $first = true;
                $s = '';
                foreach ( $args as $root ) {
@@ -1485,11 +1481,10 @@ class PPFrame_DOM implements PPFrame {
         * with implode()
         *
         * @param string $sep
-        * @param string|PPNode_DOM|DOMDocument $args,...
+        * @param string|PPNode_DOM|DOMDocument ...$args
         * @return array
         */
-       public function virtualImplode( $sep /*, ... */ ) {
-               $args = array_slice( func_get_args(), 1 );
+       public function virtualImplode( $sep, ...$args ) {
                $out = [];
                $first = true;
 
@@ -1517,11 +1512,10 @@ class PPFrame_DOM implements PPFrame {
         * @param string $start
         * @param string $sep
         * @param string $end
-        * @param string|PPNode_DOM|DOMDocument $args,...
+        * @param string|PPNode_DOM|DOMDocument ...$args
         * @return array
         */
-       public function virtualBracketedImplode( $start, $sep, $end /*, ... */ ) {
-               $args = array_slice( func_get_args(), 3 );
+       public function virtualBracketedImplode( $start, $sep, $end, ...$args ) {
                $out = [ $start ];
                $first = true;
 
index eb869e2..a845047 100644 (file)
@@ -1232,12 +1232,10 @@ class PPFrame_Hash implements PPFrame {
        /**
         * @param string $sep
         * @param int $flags
-        * @param string|PPNode $args,...
+        * @param string|PPNode ...$args
         * @return string
         */
-       public function implodeWithFlags( $sep, $flags /*, ... */ ) {
-               $args = array_slice( func_get_args(), 2 );
-
+       public function implodeWithFlags( $sep, $flags, ...$args ) {
                $first = true;
                $s = '';
                foreach ( $args as $root ) {
@@ -1263,12 +1261,10 @@ class PPFrame_Hash implements PPFrame {
         * Implode with no flags specified
         * This previously called implodeWithFlags but has now been inlined to reduce stack depth
         * @param string $sep
-        * @param string|PPNode $args,...
+        * @param string|PPNode ...$args
         * @return string
         */
-       public function implode( $sep /*, ... */ ) {
-               $args = array_slice( func_get_args(), 1 );
-
+       public function implode( $sep, ...$args ) {
                $first = true;
                $s = '';
                foreach ( $args as $root ) {
@@ -1295,11 +1291,10 @@ class PPFrame_Hash implements PPFrame {
         * with implode()
         *
         * @param string $sep
-        * @param string|PPNode $args,...
+        * @param string|PPNode ...$args
         * @return PPNode_Hash_Array
         */
-       public function virtualImplode( $sep /*, ... */ ) {
-               $args = array_slice( func_get_args(), 1 );
+       public function virtualImplode( $sep, ...$args ) {
                $out = [];
                $first = true;
 
@@ -1328,11 +1323,10 @@ class PPFrame_Hash implements PPFrame {
         * @param string $start
         * @param string $sep
         * @param string $end
-        * @param string|PPNode $args,...
+        * @param string|PPNode ...$args
         * @return PPNode_Hash_Array
         */
-       public function virtualBracketedImplode( $start, $sep, $end /*, ... */ ) {
-               $args = array_slice( func_get_args(), 3 );
+       public function virtualBracketedImplode( $start, $sep, $end, ...$args ) {
                $out = [ $start ];
                $first = true;
 
@@ -1766,7 +1760,8 @@ class PPNode_Hash_Tree implements PPNode {
         *
         * @param array $store
         * @param int $index
-        * @return PPNode_Hash_Tree|PPNode_Hash_Attr|PPNode_Hash_Text
+        * @return PPNode_Hash_Tree|PPNode_Hash_Attr|PPNode_Hash_Text|false
+        * @throws MWException
         */
        public static function factory( array $store, $index ) {
                if ( !isset( $store[$index] ) ) {
@@ -1790,6 +1785,7 @@ class PPNode_Hash_Tree implements PPNode {
 
        /**
         * Convert a node to XML, for debugging
+        * @return string
         */
        public function __toString() {
                $inner = '';
index c45ab4c..e354d55 100644 (file)
@@ -43,7 +43,7 @@ use MWTimestamp;
 use OutputPage;
 use Parser;
 use ParserOptions;
-use PreferencesFormLegacy;
+use PreferencesFormOOUI;
 use Psr\Log\LoggerAwareTrait;
 use Psr\Log\NullLogger;
 use Skin;
@@ -1430,7 +1430,7 @@ class DefaultPreferencesFactory implements PreferencesFactory {
        public function getForm(
                User $user,
                IContextSource $context,
-               $formClass = PreferencesFormLegacy::class,
+               $formClass = PreferencesFormOOUI::class,
                array $remove = []
        ) {
                // We use ButtonWidgets in some of the getPreferences() functions
@@ -1450,7 +1450,7 @@ class DefaultPreferencesFactory implements PreferencesFactory {
                }
 
                /**
-                * @var $htmlForm HTMLForm
+                * @var HTMLForm $htmlForm
                 */
                $htmlForm = new $formClass( $formDescriptor, $context, 'prefs' );
 
index 478edce..6602a0a 100644 (file)
@@ -22,13 +22,14 @@ namespace MediaWiki\Preferences;
 
 use HTMLForm;
 use IContextSource;
+use PreferencesFormOOUI;
 use User;
 
 /**
  * A PreferencesFactory is a MediaWiki service that provides the definitions of preferences for a
  * given user. These definitions are in the form of an HTMLForm descriptor.
  *
- * PreferencesFormLegacy (a subclass of HTMLForm) is used to generate the Preferences form, and
+ * PreferencesFormOOUI (a subclass of HTMLForm) is used to generate the Preferences form, and
  * handles generic submission, CSRF protection, layout and other logic in a reusable manner.
  *
  * In order to generate the form, the HTMLForm object needs an array structure detailing the
@@ -62,7 +63,7 @@ interface PreferencesFactory {
        public function getForm(
                User $user,
                IContextSource $contextSource,
-               $formClass = \PreferencesFormLegacy::class,
+               $formClass = PreferencesFormOOUI::class,
                array $remove = []
        );
 
diff --git a/includes/profiler/SectionProfileCallback.php b/includes/profiler/SectionProfileCallback.php
new file mode 100644 (file)
index 0000000..4c25c50
--- /dev/null
@@ -0,0 +1,48 @@
+<?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 Profiler
+ */
+use Wikimedia\ScopedCallback;
+
+/**
+ * Subclass ScopedCallback to avoid call_user_func_array(), which is slow.
+ *
+ * @internal For use by SectionProfiler
+ * @since 1.25
+ */
+class SectionProfileCallback extends ScopedCallback {
+       /** @var SectionProfiler */
+       protected $profiler;
+       /** @var string */
+       protected $section;
+
+       /**
+        * @param SectionProfiler $profiler
+        * @param string $section
+        */
+       public function __construct( SectionProfiler $profiler, $section ) {
+               parent::__construct( null );
+               $this->profiler = $profiler;
+               $this->section = $section;
+       }
+
+       function __destruct() {
+               $this->profiler->profileOutInternal( $this->section );
+       }
+}
index b00401d..c27ab4f 100644 (file)
@@ -496,29 +496,3 @@ class SectionProfiler {
                }
        }
 }
-
-/**
- * Subclass ScopedCallback to avoid call_user_func_array(), which is slow
- *
- * This class should not be used outside of SectionProfiler
- */
-class SectionProfileCallback extends ScopedCallback {
-       /** @var SectionProfiler */
-       protected $profiler;
-       /** @var string */
-       protected $section;
-
-       /**
-        * @param SectionProfiler $profiler
-        * @param string $section
-        */
-       public function __construct( SectionProfiler $profiler, $section ) {
-               parent::__construct( null );
-               $this->profiler = $profiler;
-               $this->section = $section;
-       }
-
-       function __destruct() {
-               $this->profiler->profileOutInternal( $this->section );
-       }
-}
index c27cd2c..5329572 100644 (file)
@@ -58,6 +58,11 @@ class ExtensionDependencyError extends Exception {
         */
        public $missingPhpExtensions = [];
 
+       /**
+        * @var string[]
+        */
+       public $missingAbilities = [];
+
        /**
         * @param array $errors Each error has a 'msg' and 'type' key at minimum
         */
@@ -75,6 +80,9 @@ class ExtensionDependencyError extends Exception {
                                case 'missing-phpExtension':
                                        $this->missingPhpExtensions[] = $info['missing'];
                                        break;
+                               case 'missing-ability':
+                                       $this->missingAbilities[] = $info['missing'];
+                                       break;
                                case 'missing-skins':
                                        $this->missingSkins[] = $info['missing'];
                                        break;
index e3df499..2607e5a 100644 (file)
@@ -2,6 +2,8 @@
 
 use Composer\Semver\Semver;
 use Wikimedia\ScopedCallback;
+use MediaWiki\Shell\Shell;
+use MediaWiki\ShellDisabledError;
 
 /**
  * ExtensionRegistry class
@@ -144,7 +146,8 @@ class ExtensionRegistry {
                // A few more things to vary the cache on
                $versions = [
                        'registration' => self::CACHE_VERSION,
-                       'mediawiki' => $wgVersion
+                       'mediawiki' => $wgVersion,
+                       'abilities' => $this->getAbilities(),
                ];
 
                // We use a try/catch because we don't want to fail here
@@ -207,6 +210,38 @@ class ExtensionRegistry {
                $this->finished = true;
        }
 
+       /**
+        * Get the list of abilities and their values
+        * @return bool[]
+        */
+       private function getAbilities() {
+               return [
+                       'shell' => !Shell::isDisabled(),
+               ];
+       }
+
+       /**
+        * Queries information about the software environment and constructs an appropiate version checker
+        *
+        * @return VersionChecker
+        */
+       private function buildVersionChecker() {
+               global $wgVersion;
+               // array to optionally specify more verbose error messages for
+               // missing abilities
+               $abilityErrors = [
+                       'shell' => ( new ShellDisabledError() )->getMessage(),
+               ];
+
+               return new VersionChecker(
+                       $wgVersion,
+                       PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION . '.' . PHP_RELEASE_VERSION,
+                       get_loaded_extensions(),
+                       $this->getAbilities(),
+                       $abilityErrors
+               );
+       }
+
        /**
         * Process a queue of extensions and return their extracted data
         *
@@ -216,16 +251,11 @@ class ExtensionRegistry {
         * @throws ExtensionDependencyError
         */
        public function readFromQueue( array $queue ) {
-               global $wgVersion;
                $autoloadClasses = [];
                $autoloadNamespaces = [];
                $autoloaderPaths = [];
                $processor = new ExtensionProcessor();
-               $versionChecker = new VersionChecker(
-                       $wgVersion,
-                       PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION . '.' . PHP_RELEASE_VERSION,
-                       get_loaded_extensions()
-               );
+               $versionChecker = $this->buildVersionChecker();
                $extDependencies = [];
                $incompatible = [];
                $warnings = false;
index 586729d..a5d1fa1 100644 (file)
@@ -45,6 +45,16 @@ class VersionChecker {
         */
        private $phpExtensions = [];
 
+       /**
+        * @var bool[] List of provided abilities
+        */
+       private $abilities = [];
+
+       /**
+        * @var string[] List of provided ability errors
+        */
+       private $abilityErrors = [];
+
        /**
         * @var array Loaded extensions
         */
@@ -59,12 +69,19 @@ class VersionChecker {
         * @param string $coreVersion Current version of core
         * @param string $phpVersion Current PHP version
         * @param string[] $phpExtensions List of installed PHP extensions
+        * @param bool[] $abilities List of provided abilities
+        * @param string[] $abilityErrors Error messages for the abilities
         */
-       public function __construct( $coreVersion, $phpVersion, array $phpExtensions ) {
+       public function __construct(
+               $coreVersion, $phpVersion, array $phpExtensions,
+               array $abilities = [], array $abilityErrors = []
+       ) {
                $this->versionParser = new VersionParser();
                $this->setCoreVersion( $coreVersion );
                $this->setPhpVersion( $phpVersion );
                $this->phpExtensions = $phpExtensions;
+               $this->abilities = $abilities;
+               $this->abilityErrors = $abilityErrors;
        }
 
        /**
@@ -121,7 +138,8 @@ class VersionChecker {
         *         'MediaWiki' => '>= 1.25.0',
         *         'platform': {
         *           'php': '>= 7.0.0',
-        *           'ext-foo': '*'
+        *           'ext-foo': '*',
+        *           'ability-bar': true
         *         },
         *         'extensions' => {
         *           'FooBaz' => '>= 1.25.0'
@@ -193,6 +211,37 @@ class VersionChecker {
                                                                                'missing' => $phpExtension,
                                                                        ];
                                                                }
+                                                       } elseif ( substr( $dependency, 0, 8 ) === 'ability-' ) {
+                                                               // Other abilities the environment might provide.
+                                                               $ability = substr( $dependency, 8 );
+                                                               if ( !isset( $this->abilities[$ability] ) ) {
+                                                                       throw new UnexpectedValueException( 'Dependency type '
+                                                                       . $dependency . ' unknown in ' . $extension );
+                                                               }
+                                                               if ( !is_bool( $constraint ) ) {
+                                                                       throw new UnexpectedValueException( 'Only booleans are '
+                                                                               . 'allowed to to indicate the presence of abilities '
+                                                                               . 'in ' . $extension );
+                                                               }
+
+                                                               if ( $constraint === true &&
+                                                                       $this->abilities[$ability] !== true
+                                                               ) {
+                                                                       // add custom error message for missing ability if specified
+                                                                       $customMessage = '';
+                                                                       if ( isset( $this->abilityErrors[$ability] ) ) {
+                                                                               $customMessage = ': ' . $this->abilityErrors[$ability];
+                                                                       }
+
+                                                                       $errors[] = [
+                                                                               'msg' =>
+                                                                                       "{$extension} requires \"{$ability}\" ability"
+                                                                                       . $customMessage
+                                                                               ,
+                                                                               'type' => 'missing-ability',
+                                                                               'missing' => $ability,
+                                                                       ];
+                                                               }
                                                        } else {
                                                                // add other platform dependencies here
                                                                throw new UnexpectedValueException( 'Dependency type ' . $dependency .
index 02e02bb..8d60e0f 100644 (file)
@@ -1119,6 +1119,10 @@ MESSAGE;
 
                                if ( !$context->getDebug() ) {
                                        $strContent = self::filter( $filter, $strContent );
+                               } else {
+                                       // In debug mode, separate each response by a new line.
+                                       // For example, between 'mw.loader.implement();' statements.
+                                       $strContent = $this->ensureNewline( $strContent );
                                }
 
                                if ( $context->getOnly() === 'scripts' ) {
@@ -1567,7 +1571,7 @@ MESSAGE;
         * For example, `[ 'foo.bar', 'foo.baz', 'bar.baz', 'bar.quux' ]`
         * becomes `'foo.bar,baz|bar.baz,quux'`.
         *
-        * This process is reversed by ResourceLoaderContext::expandModuleNames().
+        * This process is reversed by ResourceLoader::expandModuleNames().
         * See also mw.loader#buildModulesString() which is a port of this, used
         * on the client-side.
         *
@@ -1591,6 +1595,44 @@ MESSAGE;
                return implode( '|', $arr );
        }
 
+       /**
+        * Expand a string of the form `jquery.foo,bar|jquery.ui.baz,quux` to
+        * an array of module names like `[ 'jquery.foo', 'jquery.bar',
+        * 'jquery.ui.baz', 'jquery.ui.quux' ]`.
+        *
+        * This process is reversed by ResourceLoader::makePackedModulesString().
+        *
+        * @since 1.33
+        * @param string $modules Packed module name list
+        * @return array Array of module names
+        */
+       public static function expandModuleNames( $modules ) {
+               $retval = [];
+               $exploded = explode( '|', $modules );
+               foreach ( $exploded as $group ) {
+                       if ( strpos( $group, ',' ) === false ) {
+                               // This is not a set of modules in foo.bar,baz notation
+                               // but a single module
+                               $retval[] = $group;
+                       } else {
+                               // This is a set of modules in foo.bar,baz notation
+                               $pos = strrpos( $group, '.' );
+                               if ( $pos === false ) {
+                                       // Prefixless modules, i.e. without dots
+                                       $retval = array_merge( $retval, explode( ',', $group ) );
+                               } else {
+                                       // We have a prefix and a bunch of suffixes
+                                       $prefix = substr( $group, 0, $pos ); // 'foo'
+                                       $suffixes = explode( ',', substr( $group, $pos + 1 ) ); // [ 'bar', 'baz' ]
+                                       foreach ( $suffixes as $suffix ) {
+                                               $retval[] = "$prefix.$suffix";
+                                       }
+                               }
+                       }
+               }
+               return $retval;
+       }
+
        /**
         * Determine whether debug mode was requested
         * Order of priority is 1) request param, 2) cookie, 3) $wg setting
index 372d12d..58152ea 100644 (file)
@@ -68,7 +68,7 @@ class ResourceLoaderContext implements MessageLocalizer {
 
                // List of modules
                $modules = $request->getRawVal( 'modules' );
-               $this->modules = $modules ? self::expandModuleNames( $modules ) : [];
+               $this->modules = $modules ? ResourceLoader::expandModuleNames( $modules ) : [];
 
                // Various parameters
                $this->user = $request->getRawVal( 'user' );
@@ -84,47 +84,25 @@ class ResourceLoaderContext implements MessageLocalizer {
 
                $this->skin = $request->getRawVal( 'skin' );
                $skinnames = Skin::getSkinNames();
-               // If no skin is specified, or we don't recognize the skin, use the default skin
                if ( !$this->skin || !isset( $skinnames[$this->skin] ) ) {
-                       $this->skin = $this->getConfig()->get( 'DefaultSkin' );
+                       // The 'skin' parameter is required. (Not yet enforced.)
+                       // For requests without a known skin specified,
+                       // use MediaWiki's 'fallback' skin for skin-specific decisions.
+                       $this->skin = 'fallback';
                }
        }
 
        /**
-        * Expand a string of the form `jquery.foo,bar|jquery.ui.baz,quux` to
-        * an array of module names like `[ 'jquery.foo', 'jquery.bar',
-        * 'jquery.ui.baz', 'jquery.ui.quux' ]`.
-        *
-        * This process is reversed by ResourceLoader::makePackedModulesString().
+        * Reverse the process done by ResourceLoader::makePackedModulesString().
         *
+        * @deprecated since 1.33 Use ResourceLoader::expandModuleNames instead.
         * @param string $modules Packed module name list
         * @return array Array of module names
+        * @codeCoverageIgnore
         */
        public static function expandModuleNames( $modules ) {
-               $retval = [];
-               $exploded = explode( '|', $modules );
-               foreach ( $exploded as $group ) {
-                       if ( strpos( $group, ',' ) === false ) {
-                               // This is not a set of modules in foo.bar,baz notation
-                               // but a single module
-                               $retval[] = $group;
-                       } else {
-                               // This is a set of modules in foo.bar,baz notation
-                               $pos = strrpos( $group, '.' );
-                               if ( $pos === false ) {
-                                       // Prefixless modules, i.e. without dots
-                                       $retval = array_merge( $retval, explode( ',', $group ) );
-                               } else {
-                                       // We have a prefix and a bunch of suffixes
-                                       $prefix = substr( $group, 0, $pos ); // 'foo'
-                                       $suffixes = explode( ',', substr( $group, $pos + 1 ) ); // [ 'bar', 'baz' ]
-                                       foreach ( $suffixes as $suffix ) {
-                                               $retval[] = "$prefix.$suffix";
-                                       }
-                               }
-                       }
-               }
-               return $retval;
+               wfDeprecated( __METHOD__, '1.33' );
+               return ResourceLoader::expandModuleNames( $modules );
        }
 
        /**
@@ -194,7 +172,9 @@ class ResourceLoaderContext implements MessageLocalizer {
                        $lang = $this->getRequest()->getRawVal( 'lang', '' );
                        // Stricter version of RequestContext::sanitizeLangCode()
                        if ( !Language::isValidBuiltInCode( $lang ) ) {
-                               $lang = $this->getConfig()->get( 'LanguageCode' );
+                               // The 'lang' parameter is required. (Not yet enforced.)
+                               // If omitted, localise with the dummy language code.
+                               $lang = 'qqx';
                        }
                        $this->language = $lang;
                }
index 27fa5ad..2e2da70 100644 (file)
@@ -40,6 +40,21 @@ class ResourceLoaderImage {
                'jpg' => 'image/jpg',
        ];
 
+       /** @var string */
+       private $name;
+       /** @var string */
+       private $module;
+       /** @var string|array */
+       private $descriptor;
+       /** @var string */
+       private $basePath;
+       /** @var array */
+       private $variants;
+       /** @var string|null */
+       private $defaultColor;
+       /** @var string */
+       private $extension;
+
        /**
         * @param string $name Image name
         * @param string $module Module name
index acc2503..da6063e 100644 (file)
@@ -49,15 +49,9 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
        private function getConfigSettings( $context ) {
                $conf = $this->getConfig();
 
-               // We can't use Title::newMainPage() if 'mainpage' is in
-               // $wgForceUIMsgAsContentMsg because that will try to use the session
-               // user's language and we have no session user. This does the
-               // equivalent but falling back to our ResourceLoaderContext language
-               // instead.
-               $mainPage = Title::newFromText( $context->msg( 'mainpage' )->inContentLanguage()->text() );
-               if ( !$mainPage ) {
-                       $mainPage = Title::newFromText( 'Main Page' );
-               }
+               // Passing a context is important as Title::newMainPage() may otherwise
+               // try to intialise a session, which is not allowed on load.php requests.
+               $mainPage = Title::newMainPage( $context );
 
                /**
                 * Namespace related preparation
index 9fad348..276d9a1 100644 (file)
@@ -82,7 +82,7 @@ class ResourceLoaderWikiModule extends ResourceLoaderModule {
         *  getPages.
         */
        public function __construct( array $options = null ) {
-               if ( is_null( $options ) ) {
+               if ( $options === null ) {
                        return;
                }
 
index 4cba19e..a3a8abe 100644 (file)
@@ -56,16 +56,16 @@ abstract class SearchEngine {
        /** @var array Feature values */
        protected $features = [];
 
-       /** @const string profile type for completionSearch */
+       /** Profile type for completionSearch */
        const COMPLETION_PROFILE_TYPE = 'completionSearchProfile';
 
-       /** @const string profile type for query independent ranking features */
+       /** Profile type for query independent ranking features */
        const FT_QUERY_INDEP_PROFILE_TYPE = 'fulltextQueryIndepProfile';
 
-       /** @const int flag for legalSearchChars: includes all chars allowed in a search query */
+       /** Integer flag for legalSearchChars: includes all chars allowed in a search query */
        const CHARS_ALL = 1;
 
-       /** @const int flag for legalSearchChars: includes all chars allowed in a search term */
+       /** Integer flag for legalSearchChars: includes all chars allowed in a search term */
        const CHARS_NO_SYNTAX = 2;
 
        /**
@@ -281,12 +281,11 @@ abstract class SearchEngine {
 
        /**
         * Get chars legal for search
-        * NOTE: usage as static is deprecated and preserved only as BC measure
         * @param int $type type of search chars (see self::CHARS_ALL
         * and self::CHARS_NO_SYNTAX). Defaults to CHARS_ALL
         * @return string
         */
-       public static function legalSearchChars( $type = self::CHARS_ALL ) {
+       public function legalSearchChars( $type = self::CHARS_ALL ) {
                return "A-Za-z_'.0-9\\x80-\\xFF\\-";
        }
 
index 9f10697..cae3426 100644 (file)
@@ -149,7 +149,7 @@ class SearchMySQL extends SearchDatabase {
                return $regex;
        }
 
-       public static function legalSearchChars( $type = self::CHARS_ALL ) {
+       public function legalSearchChars( $type = self::CHARS_ALL ) {
                $searchChars = parent::legalSearchChars( $type );
                if ( $type === self::CHARS_ALL ) {
                        // " for phrase, * for wildcard
index 0cbb41c..6b2b403 100644 (file)
@@ -267,7 +267,7 @@ class SearchOracle extends SearchDatabase {
                        [] );
        }
 
-       public static function legalSearchChars( $type = self::CHARS_ALL ) {
+       public function legalSearchChars( $type = self::CHARS_ALL ) {
                $searchChars = parent::legalSearchChars( $type );
                if ( $type === self::CHARS_ALL ) {
                        $searchChars = "\"" . $searchChars;
index 2f1a5c2..18331dd 100644 (file)
  * @ingroup Search
  */
 class SearchResultSet implements Countable, IteratorAggregate {
+
        /**
-        * Types of interwiki results
-        */
-       /**
-        * Results that are displayed only together with existing main wiki results
-        * @var int
+        * Identifier for interwiki results that are displayed only together with existing main wiki
+        * results.
         */
        const SECONDARY_RESULTS = 0;
+
        /**
-        * Results that can displayed even if no existing main wiki results exist
-        * @var int
+        * Identifier for interwiki results that can be displayed even if no existing main wiki results
+        * exist.
         */
        const INLINE_RESULTS = 1;
 
index f653796..c304797 100644 (file)
@@ -142,7 +142,7 @@ class SearchSqlite extends SearchDatabase {
                return $regex;
        }
 
-       public static function legalSearchChars( $type = self::CHARS_ALL ) {
+       public function legalSearchChars( $type = self::CHARS_ALL ) {
                $searchChars = parent::legalSearchChars( $type );
                if ( $type === self::CHARS_ALL ) {
                        // " for phrase, * for wildcard
index 1936d00..ba8133f 100644 (file)
@@ -111,11 +111,10 @@ class Command {
         * Adds parameters to the command. All parameters are sanitized via Shell::escape().
         * Null values are ignored.
         *
-        * @param string|string[] $args,...
+        * @param string|string[] ...$args
         * @return $this
         */
-       public function params( /* ... */ ) {
-               $args = func_get_args();
+       public function params( ...$args ) {
                if ( count( $args ) === 1 && is_array( reset( $args ) ) ) {
                        // If only one argument has been passed, and that argument is an array,
                        // treat it as a list of arguments
@@ -130,11 +129,10 @@ class Command {
         * Adds unsafe parameters to the command. These parameters are NOT sanitized in any way.
         * Null values are ignored.
         *
-        * @param string|string[] $args,...
+        * @param string|string[] ...$args
         * @return $this
         */
-       public function unsafeParams( /* ... */ ) {
-               $args = func_get_args();
+       public function unsafeParams( ...$args ) {
                if ( count( $args ) === 1 && is_array( reset( $args ) ) ) {
                        // If only one argument has been passed, and that argument is an array,
                        // treat it as a list of arguments
@@ -429,7 +427,7 @@ class Command {
                        }
 
                        // clear get_last_error without actually raising an error
-                       // from https://secure.php.net/manual/en/function.error-get-last.php#113518
+                       // from https://www.php.net/manual/en/function.error-get-last.php#113518
                        // TODO replace with clear_last_error when requirements are bumped to PHP7
                        set_error_handler( function () {
                        }, 0 );
index 01a88f4..c28b89e 100644 (file)
@@ -35,23 +35,19 @@ abstract class RedirectSpecialPage extends UnlistedSpecialPage {
 
        /**
         * @param string|null $subpage
-        * @return Title|bool
         */
        public function execute( $subpage ) {
                $redirect = $this->getRedirect( $subpage );
-               $query = $this->getRedirectQuery();
-               // Redirect to a page title with possible query parameters
+               $query = $this->getRedirectQuery( $subpage );
+
                if ( $redirect instanceof Title ) {
+                       // Redirect to a page title with possible query parameters
                        $url = $redirect->getFullUrlForRedirect( $query );
                        $this->getOutput()->redirect( $url );
-
-                       return $redirect;
                } elseif ( $redirect === true ) {
                        // Redirect to index.php with query parameters
                        $url = wfAppendQuery( wfScript( 'index' ), $query );
                        $this->getOutput()->redirect( $url );
-
-                       return $redirect;
                } else {
                        $this->showNoRedirectPage();
                }
@@ -70,9 +66,10 @@ abstract class RedirectSpecialPage extends UnlistedSpecialPage {
         * Return part of the request string for a special redirect page
         * This allows passing, e.g. action=history to Special:Mypage, etc.
         *
+        * @param string|null $subpage
         * @return array|bool
         */
-       public function getRedirectQuery() {
+       public function getRedirectQuery( $subpage ) {
                $params = [];
                $request = $this->getRequest();
 
index 58212dd..a3b7296 100644 (file)
@@ -34,6 +34,7 @@ use RequestContext;
 use SpecialPage;
 use Title;
 use User;
+use Wikimedia\Assert\Assert;
 
 /**
  * Factory for handling the special page list and generating SpecialPage objects.
@@ -215,17 +216,36 @@ class SpecialPageFactory {
        private $aliases;
 
        /** @var Config */
-       private $config;
+       private $options;
 
        /** @var Language */
        private $contLang;
 
        /**
-        * @param Config $config
+        * TODO Make this a const when HHVM support is dropped (T192166)
+        *
+        * @var array
+        * @since 1.33
+        * */
+       public static $constructorOptions = [
+               'ContentHandlerUseDB',
+               'DisableInternalSearch',
+               'EmailAuthentication',
+               'EnableEmail',
+               'EnableJavaScriptTest',
+               'PageLanguageUseDB',
+               'SpecialPages',
+       ];
+
+       /**
+        * @param array $options
         * @param Language $contLang
         */
-       public function __construct( Config $config, Language $contLang ) {
-               $this->config = $config;
+       public function __construct( array $options, Language $contLang ) {
+               Assert::parameter( count( $options ) === count( self::$constructorOptions ) &&
+                       !array_diff( self::$constructorOptions, array_keys( $options ) ),
+                       '$options', 'Wrong set of options present' );
+               $this->options = $options;
                $this->contLang = $contLang;
        }
 
@@ -248,32 +268,32 @@ class SpecialPageFactory {
                if ( !is_array( $this->list ) ) {
                        $this->list = self::$coreList;
 
-                       if ( !$this->config->get( 'DisableInternalSearch' ) ) {
+                       if ( !$this->options['DisableInternalSearch'] ) {
                                $this->list['Search'] = \SpecialSearch::class;
                        }
 
-                       if ( $this->config->get( 'EmailAuthentication' ) ) {
+                       if ( $this->options['EmailAuthentication'] ) {
                                $this->list['Confirmemail'] = \EmailConfirmation::class;
                                $this->list['Invalidateemail'] = \EmailInvalidation::class;
                        }
 
-                       if ( $this->config->get( 'EnableEmail' ) ) {
+                       if ( $this->options['EnableEmail'] ) {
                                $this->list['ChangeEmail'] = \SpecialChangeEmail::class;
                        }
 
-                       if ( $this->config->get( 'EnableJavaScriptTest' ) ) {
+                       if ( $this->options['EnableJavaScriptTest'] ) {
                                $this->list['JavaScriptTest'] = \SpecialJavaScriptTest::class;
                        }
 
-                       if ( $this->config->get( 'PageLanguageUseDB' ) ) {
+                       if ( $this->options['PageLanguageUseDB'] ) {
                                $this->list['PageLanguage'] = \SpecialPageLanguage::class;
                        }
-                       if ( $this->config->get( 'ContentHandlerUseDB' ) ) {
+                       if ( $this->options['ContentHandlerUseDB'] ) {
                                $this->list['ChangeContentModel'] = \SpecialChangeContentModel::class;
                        }
 
                        // Add extension special pages
-                       $this->list = array_merge( $this->list, $this->config->get( 'SpecialPages' ) );
+                       $this->list = array_merge( $this->list, $this->options['SpecialPages'] );
 
                        // This hook can be used to disable unwanted core special pages
                        // or conditionally register special pages.
diff --git a/includes/specials/SpecialActiveUsers.php b/includes/specials/SpecialActiveUsers.php
new file mode 100644 (file)
index 0000000..f52a6f3
--- /dev/null
@@ -0,0 +1,172 @@
+<?php
+/**
+ * Implements Special:Activeusers
+ *
+ * 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 SpecialPage
+ */
+
+/**
+ * Implements Special:Activeusers
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialActiveUsers extends SpecialPage {
+
+       public function __construct() {
+               parent::__construct( 'Activeusers' );
+       }
+
+       /**
+        * @param string|null $par Parameter passed to the page or null
+        */
+       public function execute( $par ) {
+               $out = $this->getOutput();
+
+               $this->setHeaders();
+               $this->outputHeader();
+
+               $opts = new FormOptions();
+
+               $opts->add( 'username', '' );
+               $opts->add( 'groups', [] );
+               $opts->add( 'excludegroups', [] );
+               // Backwards-compatibility with old URLs
+               $opts->add( 'hidebots', false, FormOptions::BOOL );
+               $opts->add( 'hidesysops', false, FormOptions::BOOL );
+
+               $opts->fetchValuesFromRequest( $this->getRequest() );
+
+               if ( $par !== null ) {
+                       $opts->setValue( 'username', $par );
+               }
+
+               $pager = new ActiveUsersPager( $this->getContext(), $opts );
+               $usersBody = $pager->getBody();
+
+               $this->buildForm();
+
+               if ( $usersBody ) {
+                       $out->addHTML(
+                               $pager->getNavigationBar() .
+                               Html::rawElement( 'ul', [], $usersBody ) .
+                               $pager->getNavigationBar()
+                       );
+               } else {
+                       $out->addWikiMsg( 'activeusers-noresult' );
+               }
+       }
+
+       /**
+        * Generate and output the form
+        */
+       protected function buildForm() {
+               $groups = User::getAllGroups();
+
+               $options = [];
+               foreach ( $groups as $group ) {
+                       $msg = htmlspecialchars( UserGroupMembership::getGroupName( $group ) );
+                       $options[$msg] = $group;
+               }
+               asort( $options );
+
+               // Backwards-compatibility with old URLs
+               $req = $this->getRequest();
+               $excludeDefault = [];
+               if ( $req->getCheck( 'hidebots' ) ) {
+                       $excludeDefault[] = 'bot';
+               }
+               if ( $req->getCheck( 'hidesysops' ) ) {
+                       $excludeDefault[] = 'sysop';
+               }
+
+               $formDescriptor = [
+                       'username' => [
+                               'type' => 'user',
+                               'name' => 'username',
+                               'label-message' => 'activeusers-from',
+                       ],
+                       'groups' => [
+                               'type' => 'multiselect',
+                               'dropdown' => true,
+                               'flatlist' => true,
+                               'name' => 'groups',
+                               'label-message' => 'activeusers-groups',
+                               'options' => $options,
+                       ],
+                       'excludegroups' => [
+                               'type' => 'multiselect',
+                               'dropdown' => true,
+                               'flatlist' => true,
+                               'name' => 'excludegroups',
+                               'label-message' => 'activeusers-excludegroups',
+                               'options' => $options,
+                               'default' => $excludeDefault,
+                       ],
+               ];
+
+               HTMLForm::factory( 'ooui', $formDescriptor, $this->getContext() )
+                       // For the 'multiselect' field values to be preserved on submit
+                       ->setFormIdentifier( 'specialactiveusers' )
+                       ->setIntro( $this->getIntroText() )
+                       ->setWrapperLegendMsg( 'activeusers' )
+                       ->setSubmitTextMsg( 'activeusers-submit' )
+                       // prevent setting subpage and 'username' parameter at the same time
+                       ->setAction( $this->getPageTitle()->getLocalURL() )
+                       ->setMethod( 'get' )
+                       ->prepareForm()
+                       ->displayForm( false );
+       }
+
+       /**
+        * Return introductory message.
+        * @return string
+        */
+       protected function getIntroText() {
+               $days = $this->getConfig()->get( 'ActiveUserDays' );
+
+               $intro = $this->msg( 'activeusers-intro' )->numParams( $days )->parse();
+
+               // Mention the level of cache staleness...
+               $dbr = wfGetDB( DB_REPLICA, 'recentchanges' );
+               $rcMax = $dbr->selectField( 'recentchanges', 'MAX(rc_timestamp)', '', __METHOD__ );
+               if ( $rcMax ) {
+                       $cTime = $dbr->selectField( 'querycache_info',
+                               'qci_timestamp',
+                               [ 'qci_type' => 'activeusers' ],
+                               __METHOD__
+                       );
+                       if ( $cTime ) {
+                               $secondsOld = wfTimestamp( TS_UNIX, $rcMax ) - wfTimestamp( TS_UNIX, $cTime );
+                       } else {
+                               $rcMin = $dbr->selectField( 'recentchanges', 'MIN(rc_timestamp)' );
+                               $secondsOld = time() - wfTimestamp( TS_UNIX, $rcMin );
+                       }
+                       if ( $secondsOld > 0 ) {
+                               $intro .= $this->msg( 'cachedspecial-viewing-cached-ttl' )
+                                       ->durationParams( $secondsOld )->parseAsBlock();
+                       }
+               }
+
+               return $intro;
+       }
+
+       protected function getGroupName() {
+               return 'users';
+       }
+}
diff --git a/includes/specials/SpecialActiveusers.php b/includes/specials/SpecialActiveusers.php
deleted file mode 100644 (file)
index f52a6f3..0000000
+++ /dev/null
@@ -1,172 +0,0 @@
-<?php
-/**
- * Implements Special:Activeusers
- *
- * 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 SpecialPage
- */
-
-/**
- * Implements Special:Activeusers
- *
- * @ingroup SpecialPage
- */
-class SpecialActiveUsers extends SpecialPage {
-
-       public function __construct() {
-               parent::__construct( 'Activeusers' );
-       }
-
-       /**
-        * @param string|null $par Parameter passed to the page or null
-        */
-       public function execute( $par ) {
-               $out = $this->getOutput();
-
-               $this->setHeaders();
-               $this->outputHeader();
-
-               $opts = new FormOptions();
-
-               $opts->add( 'username', '' );
-               $opts->add( 'groups', [] );
-               $opts->add( 'excludegroups', [] );
-               // Backwards-compatibility with old URLs
-               $opts->add( 'hidebots', false, FormOptions::BOOL );
-               $opts->add( 'hidesysops', false, FormOptions::BOOL );
-
-               $opts->fetchValuesFromRequest( $this->getRequest() );
-
-               if ( $par !== null ) {
-                       $opts->setValue( 'username', $par );
-               }
-
-               $pager = new ActiveUsersPager( $this->getContext(), $opts );
-               $usersBody = $pager->getBody();
-
-               $this->buildForm();
-
-               if ( $usersBody ) {
-                       $out->addHTML(
-                               $pager->getNavigationBar() .
-                               Html::rawElement( 'ul', [], $usersBody ) .
-                               $pager->getNavigationBar()
-                       );
-               } else {
-                       $out->addWikiMsg( 'activeusers-noresult' );
-               }
-       }
-
-       /**
-        * Generate and output the form
-        */
-       protected function buildForm() {
-               $groups = User::getAllGroups();
-
-               $options = [];
-               foreach ( $groups as $group ) {
-                       $msg = htmlspecialchars( UserGroupMembership::getGroupName( $group ) );
-                       $options[$msg] = $group;
-               }
-               asort( $options );
-
-               // Backwards-compatibility with old URLs
-               $req = $this->getRequest();
-               $excludeDefault = [];
-               if ( $req->getCheck( 'hidebots' ) ) {
-                       $excludeDefault[] = 'bot';
-               }
-               if ( $req->getCheck( 'hidesysops' ) ) {
-                       $excludeDefault[] = 'sysop';
-               }
-
-               $formDescriptor = [
-                       'username' => [
-                               'type' => 'user',
-                               'name' => 'username',
-                               'label-message' => 'activeusers-from',
-                       ],
-                       'groups' => [
-                               'type' => 'multiselect',
-                               'dropdown' => true,
-                               'flatlist' => true,
-                               'name' => 'groups',
-                               'label-message' => 'activeusers-groups',
-                               'options' => $options,
-                       ],
-                       'excludegroups' => [
-                               'type' => 'multiselect',
-                               'dropdown' => true,
-                               'flatlist' => true,
-                               'name' => 'excludegroups',
-                               'label-message' => 'activeusers-excludegroups',
-                               'options' => $options,
-                               'default' => $excludeDefault,
-                       ],
-               ];
-
-               HTMLForm::factory( 'ooui', $formDescriptor, $this->getContext() )
-                       // For the 'multiselect' field values to be preserved on submit
-                       ->setFormIdentifier( 'specialactiveusers' )
-                       ->setIntro( $this->getIntroText() )
-                       ->setWrapperLegendMsg( 'activeusers' )
-                       ->setSubmitTextMsg( 'activeusers-submit' )
-                       // prevent setting subpage and 'username' parameter at the same time
-                       ->setAction( $this->getPageTitle()->getLocalURL() )
-                       ->setMethod( 'get' )
-                       ->prepareForm()
-                       ->displayForm( false );
-       }
-
-       /**
-        * Return introductory message.
-        * @return string
-        */
-       protected function getIntroText() {
-               $days = $this->getConfig()->get( 'ActiveUserDays' );
-
-               $intro = $this->msg( 'activeusers-intro' )->numParams( $days )->parse();
-
-               // Mention the level of cache staleness...
-               $dbr = wfGetDB( DB_REPLICA, 'recentchanges' );
-               $rcMax = $dbr->selectField( 'recentchanges', 'MAX(rc_timestamp)', '', __METHOD__ );
-               if ( $rcMax ) {
-                       $cTime = $dbr->selectField( 'querycache_info',
-                               'qci_timestamp',
-                               [ 'qci_type' => 'activeusers' ],
-                               __METHOD__
-                       );
-                       if ( $cTime ) {
-                               $secondsOld = wfTimestamp( TS_UNIX, $rcMax ) - wfTimestamp( TS_UNIX, $cTime );
-                       } else {
-                               $rcMin = $dbr->selectField( 'recentchanges', 'MIN(rc_timestamp)' );
-                               $secondsOld = time() - wfTimestamp( TS_UNIX, $rcMin );
-                       }
-                       if ( $secondsOld > 0 ) {
-                               $intro .= $this->msg( 'cachedspecial-viewing-cached-ttl' )
-                                       ->durationParams( $secondsOld )->parseAsBlock();
-                       }
-               }
-
-               return $intro;
-       }
-
-       protected function getGroupName() {
-               return 'users';
-       }
-}
index 155d6a4..7d86663 100644 (file)
@@ -141,7 +141,9 @@ class SpecialBlock extends FormSpecialPage {
         * @return array
         */
        protected function getFormFields() {
-               global $wgBlockAllowsUTEdit;
+               $conf = $this->getConfig();
+               $enablePartialBlocks = $conf->get( 'EnablePartialBlocks' );
+               $blockAllowsUTEdit = $conf->get( 'BlockAllowsUTEdit' );
 
                $this->getOutput()->enableOOUI();
 
@@ -149,9 +151,6 @@ class SpecialBlock extends FormSpecialPage {
 
                $suggestedDurations = self::getSuggestedDurations();
 
-               $conf = $this->getConfig();
-               $enablePartialBlocks = $conf->get( 'EnablePartialBlocks' );
-
                $a = [];
 
                $a['Target'] = [
@@ -232,7 +231,7 @@ class SpecialBlock extends FormSpecialPage {
                        ];
                }
 
-               if ( $wgBlockAllowsUTEdit ) {
+               if ( $blockAllowsUTEdit ) {
                        $a['DisableUTEdit'] = [
                                'type' => 'check',
                                'label-message' => 'ipb-disableusertalk',
@@ -1076,7 +1075,7 @@ class SpecialBlock extends FormSpecialPage {
         *
         * @todo strtotime() only accepts English strings. This means the expiry input
         *       can only be specified in English.
-        * @see https://secure.php.net/manual/en/function.strtotime.php
+        * @see https://www.php.net/manual/en/function.strtotime.php
         *
         * @param string $expiry Whatever was typed into the form
         * @return string|bool Timestamp or 'infinity' or false on error.
diff --git a/includes/specials/SpecialBookSources.php b/includes/specials/SpecialBookSources.php
new file mode 100644 (file)
index 0000000..ea9ddaf
--- /dev/null
@@ -0,0 +1,212 @@
+<?php
+/**
+ * Implements Special:Booksources
+ *
+ * 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 SpecialPage
+ */
+
+use MediaWiki\MediaWikiServices;
+
+/**
+ * Special page outputs information on sourcing a book with a particular ISBN
+ * The parser creates links to this page when dealing with ISBNs in wikitext
+ *
+ * @author Rob Church <robchur@gmail.com>
+ * @ingroup SpecialPage
+ */
+class SpecialBookSources extends SpecialPage {
+       public function __construct() {
+               parent::__construct( 'Booksources' );
+       }
+
+       /**
+        * @param string|null $isbn ISBN passed as a subpage parameter
+        */
+       public function execute( $isbn ) {
+               $out = $this->getOutput();
+
+               $this->setHeaders();
+               $this->outputHeader();
+
+               // User provided ISBN
+               $isbn = $isbn ?: $this->getRequest()->getText( 'isbn' );
+               $isbn = trim( $isbn );
+
+               $this->buildForm( $isbn );
+
+               if ( $isbn !== '' ) {
+                       if ( !self::isValidISBN( $isbn ) ) {
+                               $out->wrapWikiMsg(
+                                       "<div class=\"error\">\n$1\n</div>",
+                                       'booksources-invalid-isbn'
+                               );
+                       }
+
+                       $this->showList( $isbn );
+               }
+       }
+
+       /**
+        * Return whether a given ISBN (10 or 13) is valid.
+        *
+        * @param string $isbn ISBN passed for check
+        * @return bool
+        */
+       public static function isValidISBN( $isbn ) {
+               $isbn = self::cleanIsbn( $isbn );
+               $sum = 0;
+               if ( strlen( $isbn ) == 13 ) {
+                       for ( $i = 0; $i < 12; $i++ ) {
+                               if ( $isbn[$i] === 'X' ) {
+                                       return false;
+                               } elseif ( $i % 2 == 0 ) {
+                                       $sum += $isbn[$i];
+                               } else {
+                                       $sum += 3 * $isbn[$i];
+                               }
+                       }
+
+                       $check = ( 10 - ( $sum % 10 ) ) % 10;
+                       if ( (string)$check === $isbn[12] ) {
+                               return true;
+                       }
+               } elseif ( strlen( $isbn ) == 10 ) {
+                       for ( $i = 0; $i < 9; $i++ ) {
+                               if ( $isbn[$i] === 'X' ) {
+                                       return false;
+                               }
+                               $sum += $isbn[$i] * ( $i + 1 );
+                       }
+
+                       $check = $sum % 11;
+                       if ( $check == 10 ) {
+                               $check = "X";
+                       }
+                       if ( (string)$check === $isbn[9] ) {
+                               return true;
+                       }
+               }
+
+               return false;
+       }
+
+       /**
+        * Trim ISBN and remove characters which aren't required
+        *
+        * @param string $isbn Unclean ISBN
+        * @return string
+        */
+       private static function cleanIsbn( $isbn ) {
+               return trim( preg_replace( '![^0-9X]!', '', $isbn ) );
+       }
+
+       /**
+        * Generate a form to allow users to enter an ISBN
+        *
+        * @param string $isbn
+        */
+       private function buildForm( $isbn ) {
+               $formDescriptor = [
+                       'isbn' => [
+                               'type' => 'text',
+                               'name' => 'isbn',
+                               'label-message' => 'booksources-isbn',
+                               'default' => $isbn,
+                               'autofocus' => true,
+                               'required' => true,
+                       ],
+               ];
+
+               $context = new DerivativeContext( $this->getContext() );
+               $context->setTitle( $this->getPageTitle() );
+               HTMLForm::factory( 'ooui', $formDescriptor, $context )
+                       ->setWrapperLegendMsg( 'booksources-search-legend' )
+                       ->setSubmitTextMsg( 'booksources-search' )
+                       ->setMethod( 'get' )
+                       ->prepareForm()
+                       ->displayForm( false );
+       }
+
+       /**
+        * Determine where to get the list of book sources from,
+        * format and output them
+        *
+        * @param string $isbn
+        * @throws MWException
+        * @return bool
+        */
+       private function showList( $isbn ) {
+               $out = $this->getOutput();
+
+               $isbn = self::cleanIsbn( $isbn );
+               # Hook to allow extensions to insert additional HTML,
+               # e.g. for API-interacting plugins and so on
+               Hooks::run( 'BookInformation', [ $isbn, $out ] );
+
+               # Check for a local page such as Project:Book_sources and use that if available
+               $page = $this->msg( 'booksources' )->inContentLanguage()->text();
+               $title = Title::makeTitleSafe( NS_PROJECT, $page ); # Show list in content language
+               if ( is_object( $title ) && $title->exists() ) {
+                       $rev = Revision::newFromTitle( $title, false, Revision::READ_NORMAL );
+                       $content = $rev->getContent();
+
+                       if ( $content instanceof TextContent ) {
+                               // XXX: in the future, this could be stored as structured data, defining a list of book sources
+
+                               $text = $content->getText();
+                               $out->addWikiTextAsInterface( str_replace( 'MAGICNUMBER', $isbn, $text ) );
+
+                               return true;
+                       } else {
+                               throw new MWException( "Unexpected content type for book sources: " . $content->getModel() );
+                       }
+               }
+
+               # Fall back to the defaults given in the language file
+               $out->addWikiMsg( 'booksources-text' );
+               $out->addHTML( '<ul>' );
+               $items = MediaWikiServices::getInstance()->getContentLanguage()->getBookstoreList();
+               foreach ( $items as $label => $url ) {
+                       $out->addHTML( $this->makeListItem( $isbn, $label, $url ) );
+               }
+               $out->addHTML( '</ul>' );
+
+               return true;
+       }
+
+       /**
+        * Format a book source list item
+        *
+        * @param string $isbn
+        * @param string $label Book source label
+        * @param string $url Book source URL
+        * @return string
+        */
+       private function makeListItem( $isbn, $label, $url ) {
+               $url = str_replace( '$1', $isbn, $url );
+
+               return Html::rawElement( 'li', [],
+                       Html::element( 'a', [ 'href' => $url, 'class' => 'external' ], $label )
+               );
+       }
+
+       protected function getGroupName() {
+               return 'wiki';
+       }
+}
diff --git a/includes/specials/SpecialBooksources.php b/includes/specials/SpecialBooksources.php
deleted file mode 100644 (file)
index ea9ddaf..0000000
+++ /dev/null
@@ -1,212 +0,0 @@
-<?php
-/**
- * Implements Special:Booksources
- *
- * 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 SpecialPage
- */
-
-use MediaWiki\MediaWikiServices;
-
-/**
- * Special page outputs information on sourcing a book with a particular ISBN
- * The parser creates links to this page when dealing with ISBNs in wikitext
- *
- * @author Rob Church <robchur@gmail.com>
- * @ingroup SpecialPage
- */
-class SpecialBookSources extends SpecialPage {
-       public function __construct() {
-               parent::__construct( 'Booksources' );
-       }
-
-       /**
-        * @param string|null $isbn ISBN passed as a subpage parameter
-        */
-       public function execute( $isbn ) {
-               $out = $this->getOutput();
-
-               $this->setHeaders();
-               $this->outputHeader();
-
-               // User provided ISBN
-               $isbn = $isbn ?: $this->getRequest()->getText( 'isbn' );
-               $isbn = trim( $isbn );
-
-               $this->buildForm( $isbn );
-
-               if ( $isbn !== '' ) {
-                       if ( !self::isValidISBN( $isbn ) ) {
-                               $out->wrapWikiMsg(
-                                       "<div class=\"error\">\n$1\n</div>",
-                                       'booksources-invalid-isbn'
-                               );
-                       }
-
-                       $this->showList( $isbn );
-               }
-       }
-
-       /**
-        * Return whether a given ISBN (10 or 13) is valid.
-        *
-        * @param string $isbn ISBN passed for check
-        * @return bool
-        */
-       public static function isValidISBN( $isbn ) {
-               $isbn = self::cleanIsbn( $isbn );
-               $sum = 0;
-               if ( strlen( $isbn ) == 13 ) {
-                       for ( $i = 0; $i < 12; $i++ ) {
-                               if ( $isbn[$i] === 'X' ) {
-                                       return false;
-                               } elseif ( $i % 2 == 0 ) {
-                                       $sum += $isbn[$i];
-                               } else {
-                                       $sum += 3 * $isbn[$i];
-                               }
-                       }
-
-                       $check = ( 10 - ( $sum % 10 ) ) % 10;
-                       if ( (string)$check === $isbn[12] ) {
-                               return true;
-                       }
-               } elseif ( strlen( $isbn ) == 10 ) {
-                       for ( $i = 0; $i < 9; $i++ ) {
-                               if ( $isbn[$i] === 'X' ) {
-                                       return false;
-                               }
-                               $sum += $isbn[$i] * ( $i + 1 );
-                       }
-
-                       $check = $sum % 11;
-                       if ( $check == 10 ) {
-                               $check = "X";
-                       }
-                       if ( (string)$check === $isbn[9] ) {
-                               return true;
-                       }
-               }
-
-               return false;
-       }
-
-       /**
-        * Trim ISBN and remove characters which aren't required
-        *
-        * @param string $isbn Unclean ISBN
-        * @return string
-        */
-       private static function cleanIsbn( $isbn ) {
-               return trim( preg_replace( '![^0-9X]!', '', $isbn ) );
-       }
-
-       /**
-        * Generate a form to allow users to enter an ISBN
-        *
-        * @param string $isbn
-        */
-       private function buildForm( $isbn ) {
-               $formDescriptor = [
-                       'isbn' => [
-                               'type' => 'text',
-                               'name' => 'isbn',
-                               'label-message' => 'booksources-isbn',
-                               'default' => $isbn,
-                               'autofocus' => true,
-                               'required' => true,
-                       ],
-               ];
-
-               $context = new DerivativeContext( $this->getContext() );
-               $context->setTitle( $this->getPageTitle() );
-               HTMLForm::factory( 'ooui', $formDescriptor, $context )
-                       ->setWrapperLegendMsg( 'booksources-search-legend' )
-                       ->setSubmitTextMsg( 'booksources-search' )
-                       ->setMethod( 'get' )
-                       ->prepareForm()
-                       ->displayForm( false );
-       }
-
-       /**
-        * Determine where to get the list of book sources from,
-        * format and output them
-        *
-        * @param string $isbn
-        * @throws MWException
-        * @return bool
-        */
-       private function showList( $isbn ) {
-               $out = $this->getOutput();
-
-               $isbn = self::cleanIsbn( $isbn );
-               # Hook to allow extensions to insert additional HTML,
-               # e.g. for API-interacting plugins and so on
-               Hooks::run( 'BookInformation', [ $isbn, $out ] );
-
-               # Check for a local page such as Project:Book_sources and use that if available
-               $page = $this->msg( 'booksources' )->inContentLanguage()->text();
-               $title = Title::makeTitleSafe( NS_PROJECT, $page ); # Show list in content language
-               if ( is_object( $title ) && $title->exists() ) {
-                       $rev = Revision::newFromTitle( $title, false, Revision::READ_NORMAL );
-                       $content = $rev->getContent();
-
-                       if ( $content instanceof TextContent ) {
-                               // XXX: in the future, this could be stored as structured data, defining a list of book sources
-
-                               $text = $content->getText();
-                               $out->addWikiTextAsInterface( str_replace( 'MAGICNUMBER', $isbn, $text ) );
-
-                               return true;
-                       } else {
-                               throw new MWException( "Unexpected content type for book sources: " . $content->getModel() );
-                       }
-               }
-
-               # Fall back to the defaults given in the language file
-               $out->addWikiMsg( 'booksources-text' );
-               $out->addHTML( '<ul>' );
-               $items = MediaWikiServices::getInstance()->getContentLanguage()->getBookstoreList();
-               foreach ( $items as $label => $url ) {
-                       $out->addHTML( $this->makeListItem( $isbn, $label, $url ) );
-               }
-               $out->addHTML( '</ul>' );
-
-               return true;
-       }
-
-       /**
-        * Format a book source list item
-        *
-        * @param string $isbn
-        * @param string $label Book source label
-        * @param string $url Book source URL
-        * @return string
-        */
-       private function makeListItem( $isbn, $label, $url ) {
-               $url = str_replace( '$1', $isbn, $url );
-
-               return Html::rawElement( 'li', [],
-                       Html::element( 'a', [ 'href' => $url, 'class' => 'external' ], $label )
-               );
-       }
-
-       protected function getGroupName() {
-               return 'wiki';
-       }
-}
diff --git a/includes/specials/SpecialEmailUser.php b/includes/specials/SpecialEmailUser.php
new file mode 100644 (file)
index 0000000..5f80215
--- /dev/null
@@ -0,0 +1,533 @@
+<?php
+/**
+ * Implements Special:Emailuser
+ *
+ * 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 SpecialPage
+ */
+use MediaWiki\MediaWikiServices;
+use MediaWiki\Preferences\MultiUsernameFilter;
+
+/**
+ * A special page that allows users to send e-mails to other users
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialEmailUser extends UnlistedSpecialPage {
+       protected $mTarget;
+
+       /**
+        * @var User|string $mTargetObj
+        */
+       protected $mTargetObj;
+
+       public function __construct() {
+               parent::__construct( 'Emailuser' );
+       }
+
+       public function doesWrites() {
+               return true;
+       }
+
+       public function getDescription() {
+               $target = self::getTarget( $this->mTarget, $this->getUser() );
+               if ( !$target instanceof User ) {
+                       return $this->msg( 'emailuser-title-notarget' )->text();
+               }
+
+               return $this->msg( 'emailuser-title-target', $target->getName() )->text();
+       }
+
+       protected function getFormFields() {
+               $linkRenderer = $this->getLinkRenderer();
+               return [
+                       'From' => [
+                               'type' => 'info',
+                               'raw' => 1,
+                               'default' => $linkRenderer->makeLink(
+                                       $this->getUser()->getUserPage(),
+                                       $this->getUser()->getName()
+                               ),
+                               'label-message' => 'emailfrom',
+                               'id' => 'mw-emailuser-sender',
+                       ],
+                       'To' => [
+                               'type' => 'info',
+                               'raw' => 1,
+                               'default' => $linkRenderer->makeLink(
+                                       $this->mTargetObj->getUserPage(),
+                                       $this->mTargetObj->getName()
+                               ),
+                               'label-message' => 'emailto',
+                               'id' => 'mw-emailuser-recipient',
+                       ],
+                       'Target' => [
+                               'type' => 'hidden',
+                               'default' => $this->mTargetObj->getName(),
+                       ],
+                       'Subject' => [
+                               'type' => 'text',
+                               'default' => $this->msg( 'defemailsubject',
+                                       $this->getUser()->getName() )->inContentLanguage()->text(),
+                               'label-message' => 'emailsubject',
+                               'maxlength' => 200,
+                               'size' => 60,
+                               'required' => true,
+                       ],
+                       'Text' => [
+                               'type' => 'textarea',
+                               'rows' => 20,
+                               'label-message' => 'emailmessage',
+                               'required' => true,
+                       ],
+                       'CCMe' => [
+                               'type' => 'check',
+                               'label-message' => 'emailccme',
+                               'default' => $this->getUser()->getBoolOption( 'ccmeonemails' ),
+                       ],
+               ];
+       }
+
+       public function execute( $par ) {
+               $out = $this->getOutput();
+               $request = $this->getRequest();
+               $out->addModuleStyles( 'mediawiki.special' );
+
+               $this->mTarget = $par ?? $request->getVal( 'wpTarget', $request->getVal( 'target', '' ) );
+
+               // Make sure, that HTMLForm uses the correct target.
+               $request->setVal( 'wpTarget', $this->mTarget );
+
+               // This needs to be below assignment of $this->mTarget because
+               // getDescription() needs it to determine the correct page title.
+               $this->setHeaders();
+               $this->outputHeader();
+
+               // error out if sending user cannot do this
+               $error = self::getPermissionsError(
+                       $this->getUser(),
+                       $this->getRequest()->getVal( 'wpEditToken' ),
+                       $this->getConfig()
+               );
+
+               switch ( $error ) {
+                       case null:
+                               # Wahey!
+                               break;
+                       case 'badaccess':
+                               throw new PermissionsError( 'sendemail' );
+                       case 'blockedemailuser':
+                               throw $this->getBlockedEmailError();
+                       case 'actionthrottledtext':
+                               throw new ThrottledError;
+                       case 'mailnologin':
+                       case 'usermaildisabled':
+                               throw new ErrorPageError( $error, "{$error}text" );
+                       default:
+                               # It's a hook error
+                               list( $title, $msg, $params ) = $error;
+                               throw new ErrorPageError( $title, $msg, $params );
+               }
+
+               // Make sure, that a submitted form isn't submitted to a subpage (which could be
+               // a non-existing username)
+               $context = new DerivativeContext( $this->getContext() );
+               $context->setTitle( $this->getPageTitle() ); // Remove subpage
+               $this->setContext( $context );
+
+               // A little hack: HTMLForm will check $this->mTarget only, if the form was posted, not
+               // if the user opens Special:EmailUser/Florian (e.g.). So check, if the user did that
+               // and show the "Send email to user" form directly, if so. Show the "enter username"
+               // form, otherwise.
+               $this->mTargetObj = self::getTarget( $this->mTarget, $this->getUser() );
+               if ( !$this->mTargetObj instanceof User ) {
+                       $this->userForm( $this->mTarget );
+               } else {
+                       $this->sendEmailForm();
+               }
+       }
+
+       /**
+        * Validate target User
+        *
+        * @param string $target Target user name
+        * @param User|null $sender User sending the email
+        * @return User|string User object on success or a string on error
+        */
+       public static function getTarget( $target, User $sender = null ) {
+               if ( $sender === null ) {
+                       wfDeprecated( __METHOD__ . ' without specifying the sending user', '1.30' );
+               }
+
+               if ( $target == '' ) {
+                       wfDebug( "Target is empty.\n" );
+
+                       return 'notarget';
+               }
+
+               $nu = User::newFromName( $target );
+               $error = self::validateTarget( $nu, $sender );
+
+               return $error ?: $nu;
+       }
+
+       /**
+        * Validate target User
+        *
+        * @param User $target Target user
+        * @param User|null $sender User sending the email
+        * @return string Error message or empty string if valid.
+        * @since 1.30
+        */
+       public static function validateTarget( $target, User $sender = null ) {
+               if ( $sender === null ) {
+                       wfDeprecated( __METHOD__ . ' without specifying the sending user', '1.30' );
+               }
+
+               if ( !$target instanceof User || !$target->getId() ) {
+                       wfDebug( "Target is invalid user.\n" );
+
+                       return 'notarget';
+               }
+
+               if ( !$target->isEmailConfirmed() ) {
+                       wfDebug( "User has no valid email.\n" );
+
+                       return 'noemail';
+               }
+
+               if ( !$target->canReceiveEmail() ) {
+                       wfDebug( "User does not allow user emails.\n" );
+
+                       return 'nowikiemail';
+               }
+
+               if ( $sender !== null && !$target->getOption( 'email-allow-new-users' ) &&
+                       $sender->isNewbie()
+               ) {
+                       wfDebug( "User does not allow user emails from new users.\n" );
+
+                       return 'nowikiemail';
+               }
+
+               if ( $sender !== null ) {
+                       $blacklist = $target->getOption( 'email-blacklist', '' );
+                       if ( $blacklist ) {
+                               $blacklist = MultiUsernameFilter::splitIds( $blacklist );
+                               $lookup = CentralIdLookup::factory();
+                               $senderId = $lookup->centralIdFromLocalUser( $sender );
+                               if ( $senderId !== 0 && in_array( $senderId, $blacklist ) ) {
+                                       wfDebug( "User does not allow user emails from this user.\n" );
+
+                                       return 'nowikiemail';
+                               }
+                       }
+               }
+
+               return '';
+       }
+
+       /**
+        * Check whether a user is allowed to send email
+        *
+        * @param User $user
+        * @param string $editToken Edit token
+        * @param Config|null $config optional for backwards compatibility
+        * @return null|string|array Null on success, string on error, or array on
+        *  hook error
+        */
+       public static function getPermissionsError( $user, $editToken, Config $config = null ) {
+               if ( $config === null ) {
+                       wfDebug( __METHOD__ . ' called without a Config instance passed to it' );
+                       $config = MediaWikiServices::getInstance()->getMainConfig();
+               }
+               if ( !$config->get( 'EnableEmail' ) || !$config->get( 'EnableUserEmail' ) ) {
+                       return 'usermaildisabled';
+               }
+
+               // Run this before $user->isAllowed, to show appropriate message to anons (T160309)
+               if ( !$user->isEmailConfirmed() ) {
+                       return 'mailnologin';
+               }
+
+               if ( !$user->isAllowed( 'sendemail' ) ) {
+                       return 'badaccess';
+               }
+
+               if ( $user->isBlockedFromEmailuser() ) {
+                       wfDebug( "User is blocked from sending e-mail.\n" );
+
+                       return "blockedemailuser";
+               }
+
+               // Check the ping limiter without incrementing it - we'll check it
+               // again later and increment it on a successful send
+               if ( $user->pingLimiter( 'emailuser', 0 ) ) {
+                       wfDebug( "Ping limiter triggered.\n" );
+
+                       return 'actionthrottledtext';
+               }
+
+               $hookErr = false;
+
+               Hooks::run( 'UserCanSendEmail', [ &$user, &$hookErr ] );
+               Hooks::run( 'EmailUserPermissionsErrors', [ $user, $editToken, &$hookErr ] );
+
+               if ( $hookErr ) {
+                       return $hookErr;
+               }
+
+               return null;
+       }
+
+       /**
+        * Form to ask for target user name.
+        *
+        * @param string $name User name submitted.
+        */
+       protected function userForm( $name ) {
+               $htmlForm = HTMLForm::factory( 'ooui', [
+                       'Target' => [
+                               'type' => 'user',
+                               'exists' => true,
+                               'label' => $this->msg( 'emailusername' )->text(),
+                               'id' => 'emailusertarget',
+                               'autofocus' => true,
+                               'value' => $name,
+                       ]
+               ], $this->getContext() );
+
+               $htmlForm
+                       ->setMethod( 'post' )
+                       ->setSubmitCallback( [ $this, 'sendEmailForm' ] )
+                       ->setFormIdentifier( 'userForm' )
+                       ->setId( 'askusername' )
+                       ->setWrapperLegendMsg( 'emailtarget' )
+                       ->setSubmitTextMsg( 'emailusernamesubmit' )
+                       ->show();
+       }
+
+       public function sendEmailForm() {
+               $out = $this->getOutput();
+
+               $ret = $this->mTargetObj;
+               if ( !$ret instanceof User ) {
+                       if ( $this->mTarget != '' ) {
+                               // Messages used here: notargettext, noemailtext, nowikiemailtext
+                               $ret = ( $ret == 'notarget' ) ? 'emailnotarget' : ( $ret . 'text' );
+                               return Status::newFatal( $ret );
+                       }
+                       return false;
+               }
+
+               $htmlForm = HTMLForm::factory( 'ooui', $this->getFormFields(), $this->getContext() );
+               // By now we are supposed to be sure that $this->mTarget is a user name
+               $htmlForm
+                       ->addPreText( $this->msg( 'emailpagetext', $this->mTarget )->parse() )
+                       ->setSubmitTextMsg( 'emailsend' )
+                       ->setSubmitCallback( [ __CLASS__, 'submit' ] )
+                       ->setFormIdentifier( 'sendEmailForm' )
+                       ->setWrapperLegendMsg( 'email-legend' )
+                       ->loadData();
+
+               if ( !Hooks::run( 'EmailUserForm', [ &$htmlForm ] ) ) {
+                       return false;
+               }
+
+               $result = $htmlForm->show();
+
+               if ( $result === true || ( $result instanceof Status && $result->isGood() ) ) {
+                       $out->setPageTitle( $this->msg( 'emailsent' ) );
+                       $out->addWikiMsg( 'emailsenttext', $this->mTarget );
+                       $out->returnToMain( false, $ret->getUserPage() );
+               }
+               return true;
+       }
+
+       /**
+        * Really send a mail. Permissions should have been checked using
+        * getPermissionsError(). It is probably also a good
+        * idea to check the edit token and ping limiter in advance.
+        *
+        * @param array $data
+        * @param IContextSource $context
+        * @return Status|bool
+        */
+       public static function submit( array $data, IContextSource $context ) {
+               $config = $context->getConfig();
+
+               $target = self::getTarget( $data['Target'], $context->getUser() );
+               if ( !$target instanceof User ) {
+                       // Messages used here: notargettext, noemailtext, nowikiemailtext
+                       return Status::newFatal( $target . 'text' );
+               }
+
+               $to = MailAddress::newFromUser( $target );
+               $from = MailAddress::newFromUser( $context->getUser() );
+               $subject = $data['Subject'];
+               $text = $data['Text'];
+
+               // Add a standard footer and trim up trailing newlines
+               $text = rtrim( $text ) . "\n\n-- \n";
+               $text .= $context->msg( 'emailuserfooter',
+                       $from->name, $to->name )->inContentLanguage()->text();
+
+               // Check and increment the rate limits
+               if ( $context->getUser()->pingLimiter( 'emailuser' ) ) {
+                       throw new ThrottledError();
+               }
+
+               $error = false;
+               if ( !Hooks::run( 'EmailUser', [ &$to, &$from, &$subject, &$text, &$error ] ) ) {
+                       if ( $error instanceof Status ) {
+                               return $error;
+                       } elseif ( $error === false || $error === '' || $error === [] ) {
+                               // Possibly to tell HTMLForm to pretend there was no submission?
+                               return false;
+                       } elseif ( $error === true ) {
+                               // Hook sent the mail itself and indicates success?
+                               return Status::newGood();
+                       } elseif ( is_array( $error ) ) {
+                               $status = Status::newGood();
+                               foreach ( $error as $e ) {
+                                       $status->fatal( $e );
+                               }
+                               return $status;
+                       } elseif ( $error instanceof MessageSpecifier ) {
+                               return Status::newFatal( $error );
+                       } else {
+                               // Ugh. Either a raw HTML string, or something that's supposed
+                               // to be treated like one.
+                               $type = is_object( $error ) ? get_class( $error ) : gettype( $error );
+                               wfDeprecated( "EmailUser hook returning a $type as \$error", '1.29' );
+                               return Status::newFatal( new ApiRawMessage(
+                                       [ '$1', Message::rawParam( (string)$error ) ], 'hookaborted'
+                               ) );
+                       }
+               }
+
+               if ( $config->get( 'UserEmailUseReplyTo' ) ) {
+                       /**
+                        * Put the generic wiki autogenerated address in the From:
+                        * header and reserve the user for Reply-To.
+                        *
+                        * This is a bit ugly, but will serve to differentiate
+                        * wiki-borne mails from direct mails and protects against
+                        * SPF and bounce problems with some mailers (see below).
+                        */
+                       $mailFrom = new MailAddress( $config->get( 'PasswordSender' ),
+                               $context->msg( 'emailsender' )->inContentLanguage()->text() );
+                       $replyTo = $from;
+               } else {
+                       /**
+                        * Put the sending user's e-mail address in the From: header.
+                        *
+                        * This is clean-looking and convenient, but has issues.
+                        * One is that it doesn't as clearly differentiate the wiki mail
+                        * from "directly" sent mails.
+                        *
+                        * Another is that some mailers (like sSMTP) will use the From
+                        * address as the envelope sender as well. For open sites this
+                        * can cause mails to be flunked for SPF violations (since the
+                        * wiki server isn't an authorized sender for various users'
+                        * domains) as well as creating a privacy issue as bounces
+                        * containing the recipient's e-mail address may get sent to
+                        * the sending user.
+                        */
+                       $mailFrom = $from;
+                       $replyTo = null;
+               }
+
+               $status = UserMailer::send( $to, $mailFrom, $subject, $text, [
+                       'replyTo' => $replyTo,
+               ] );
+
+               if ( !$status->isGood() ) {
+                       return $status;
+               } else {
+                       // if the user requested a copy of this mail, do this now,
+                       // unless they are emailing themselves, in which case one
+                       // copy of the message is sufficient.
+                       if ( $data['CCMe'] && $to != $from ) {
+                               $ccTo = $from;
+                               $ccFrom = $from;
+                               $ccSubject = $context->msg( 'emailccsubject' )->plaintextParams(
+                                       $target->getName(), $subject )->text();
+                               $ccText = $text;
+
+                               Hooks::run( 'EmailUserCC', [ &$ccTo, &$ccFrom, &$ccSubject, &$ccText ] );
+
+                               if ( $config->get( 'UserEmailUseReplyTo' ) ) {
+                                       $mailFrom = new MailAddress(
+                                               $config->get( 'PasswordSender' ),
+                                               $context->msg( 'emailsender' )->inContentLanguage()->text()
+                                       );
+                                       $replyTo = $ccFrom;
+                               } else {
+                                       $mailFrom = $ccFrom;
+                                       $replyTo = null;
+                               }
+
+                               $ccStatus = UserMailer::send(
+                                       $ccTo, $mailFrom, $ccSubject, $ccText, [
+                                               'replyTo' => $replyTo,
+                               ] );
+                               $status->merge( $ccStatus );
+                       }
+
+                       Hooks::run( 'EmailUserComplete', [ $to, $from, $subject, $text ] );
+
+                       return $status;
+               }
+       }
+
+       /**
+        * Return an array of subpages beginning with $search that this special page will accept.
+        *
+        * @param string $search Prefix to search for
+        * @param int $limit Maximum number of results to return (usually 10)
+        * @param int $offset Number of results to skip (usually 0)
+        * @return string[] Matching subpages
+        */
+       public function prefixSearchSubpages( $search, $limit, $offset ) {
+               $user = User::newFromName( $search );
+               if ( !$user ) {
+                       // No prefix suggestion for invalid user
+                       return [];
+               }
+               // Autocomplete subpage as user list - public to allow caching
+               return UserNamePrefixSearch::search( 'public', $search, $limit, $offset );
+       }
+
+       protected function getGroupName() {
+               return 'users';
+       }
+
+       /**
+        * Builds an error message based on the block params
+        *
+        * @return ErrorPageError
+        */
+       private function getBlockedEmailError() {
+               $block = $this->getUser()->mBlock;
+               $params = $block->getBlockErrorParams( $this->getContext() );
+
+               $msg = $block->isSitewide() ? 'blockedtext' : 'blocked-email-user';
+               return new ErrorPageError( 'blockedtitle', $msg, $params );
+       }
+}
diff --git a/includes/specials/SpecialEmailuser.php b/includes/specials/SpecialEmailuser.php
deleted file mode 100644 (file)
index 5f80215..0000000
+++ /dev/null
@@ -1,533 +0,0 @@
-<?php
-/**
- * Implements Special:Emailuser
- *
- * 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 SpecialPage
- */
-use MediaWiki\MediaWikiServices;
-use MediaWiki\Preferences\MultiUsernameFilter;
-
-/**
- * A special page that allows users to send e-mails to other users
- *
- * @ingroup SpecialPage
- */
-class SpecialEmailUser extends UnlistedSpecialPage {
-       protected $mTarget;
-
-       /**
-        * @var User|string $mTargetObj
-        */
-       protected $mTargetObj;
-
-       public function __construct() {
-               parent::__construct( 'Emailuser' );
-       }
-
-       public function doesWrites() {
-               return true;
-       }
-
-       public function getDescription() {
-               $target = self::getTarget( $this->mTarget, $this->getUser() );
-               if ( !$target instanceof User ) {
-                       return $this->msg( 'emailuser-title-notarget' )->text();
-               }
-
-               return $this->msg( 'emailuser-title-target', $target->getName() )->text();
-       }
-
-       protected function getFormFields() {
-               $linkRenderer = $this->getLinkRenderer();
-               return [
-                       'From' => [
-                               'type' => 'info',
-                               'raw' => 1,
-                               'default' => $linkRenderer->makeLink(
-                                       $this->getUser()->getUserPage(),
-                                       $this->getUser()->getName()
-                               ),
-                               'label-message' => 'emailfrom',
-                               'id' => 'mw-emailuser-sender',
-                       ],
-                       'To' => [
-                               'type' => 'info',
-                               'raw' => 1,
-                               'default' => $linkRenderer->makeLink(
-                                       $this->mTargetObj->getUserPage(),
-                                       $this->mTargetObj->getName()
-                               ),
-                               'label-message' => 'emailto',
-                               'id' => 'mw-emailuser-recipient',
-                       ],
-                       'Target' => [
-                               'type' => 'hidden',
-                               'default' => $this->mTargetObj->getName(),
-                       ],
-                       'Subject' => [
-                               'type' => 'text',
-                               'default' => $this->msg( 'defemailsubject',
-                                       $this->getUser()->getName() )->inContentLanguage()->text(),
-                               'label-message' => 'emailsubject',
-                               'maxlength' => 200,
-                               'size' => 60,
-                               'required' => true,
-                       ],
-                       'Text' => [
-                               'type' => 'textarea',
-                               'rows' => 20,
-                               'label-message' => 'emailmessage',
-                               'required' => true,
-                       ],
-                       'CCMe' => [
-                               'type' => 'check',
-                               'label-message' => 'emailccme',
-                               'default' => $this->getUser()->getBoolOption( 'ccmeonemails' ),
-                       ],
-               ];
-       }
-
-       public function execute( $par ) {
-               $out = $this->getOutput();
-               $request = $this->getRequest();
-               $out->addModuleStyles( 'mediawiki.special' );
-
-               $this->mTarget = $par ?? $request->getVal( 'wpTarget', $request->getVal( 'target', '' ) );
-
-               // Make sure, that HTMLForm uses the correct target.
-               $request->setVal( 'wpTarget', $this->mTarget );
-
-               // This needs to be below assignment of $this->mTarget because
-               // getDescription() needs it to determine the correct page title.
-               $this->setHeaders();
-               $this->outputHeader();
-
-               // error out if sending user cannot do this
-               $error = self::getPermissionsError(
-                       $this->getUser(),
-                       $this->getRequest()->getVal( 'wpEditToken' ),
-                       $this->getConfig()
-               );
-
-               switch ( $error ) {
-                       case null:
-                               # Wahey!
-                               break;
-                       case 'badaccess':
-                               throw new PermissionsError( 'sendemail' );
-                       case 'blockedemailuser':
-                               throw $this->getBlockedEmailError();
-                       case 'actionthrottledtext':
-                               throw new ThrottledError;
-                       case 'mailnologin':
-                       case 'usermaildisabled':
-                               throw new ErrorPageError( $error, "{$error}text" );
-                       default:
-                               # It's a hook error
-                               list( $title, $msg, $params ) = $error;
-                               throw new ErrorPageError( $title, $msg, $params );
-               }
-
-               // Make sure, that a submitted form isn't submitted to a subpage (which could be
-               // a non-existing username)
-               $context = new DerivativeContext( $this->getContext() );
-               $context->setTitle( $this->getPageTitle() ); // Remove subpage
-               $this->setContext( $context );
-
-               // A little hack: HTMLForm will check $this->mTarget only, if the form was posted, not
-               // if the user opens Special:EmailUser/Florian (e.g.). So check, if the user did that
-               // and show the "Send email to user" form directly, if so. Show the "enter username"
-               // form, otherwise.
-               $this->mTargetObj = self::getTarget( $this->mTarget, $this->getUser() );
-               if ( !$this->mTargetObj instanceof User ) {
-                       $this->userForm( $this->mTarget );
-               } else {
-                       $this->sendEmailForm();
-               }
-       }
-
-       /**
-        * Validate target User
-        *
-        * @param string $target Target user name
-        * @param User|null $sender User sending the email
-        * @return User|string User object on success or a string on error
-        */
-       public static function getTarget( $target, User $sender = null ) {
-               if ( $sender === null ) {
-                       wfDeprecated( __METHOD__ . ' without specifying the sending user', '1.30' );
-               }
-
-               if ( $target == '' ) {
-                       wfDebug( "Target is empty.\n" );
-
-                       return 'notarget';
-               }
-
-               $nu = User::newFromName( $target );
-               $error = self::validateTarget( $nu, $sender );
-
-               return $error ?: $nu;
-       }
-
-       /**
-        * Validate target User
-        *
-        * @param User $target Target user
-        * @param User|null $sender User sending the email
-        * @return string Error message or empty string if valid.
-        * @since 1.30
-        */
-       public static function validateTarget( $target, User $sender = null ) {
-               if ( $sender === null ) {
-                       wfDeprecated( __METHOD__ . ' without specifying the sending user', '1.30' );
-               }
-
-               if ( !$target instanceof User || !$target->getId() ) {
-                       wfDebug( "Target is invalid user.\n" );
-
-                       return 'notarget';
-               }
-
-               if ( !$target->isEmailConfirmed() ) {
-                       wfDebug( "User has no valid email.\n" );
-
-                       return 'noemail';
-               }
-
-               if ( !$target->canReceiveEmail() ) {
-                       wfDebug( "User does not allow user emails.\n" );
-
-                       return 'nowikiemail';
-               }
-
-               if ( $sender !== null && !$target->getOption( 'email-allow-new-users' ) &&
-                       $sender->isNewbie()
-               ) {
-                       wfDebug( "User does not allow user emails from new users.\n" );
-
-                       return 'nowikiemail';
-               }
-
-               if ( $sender !== null ) {
-                       $blacklist = $target->getOption( 'email-blacklist', '' );
-                       if ( $blacklist ) {
-                               $blacklist = MultiUsernameFilter::splitIds( $blacklist );
-                               $lookup = CentralIdLookup::factory();
-                               $senderId = $lookup->centralIdFromLocalUser( $sender );
-                               if ( $senderId !== 0 && in_array( $senderId, $blacklist ) ) {
-                                       wfDebug( "User does not allow user emails from this user.\n" );
-
-                                       return 'nowikiemail';
-                               }
-                       }
-               }
-
-               return '';
-       }
-
-       /**
-        * Check whether a user is allowed to send email
-        *
-        * @param User $user
-        * @param string $editToken Edit token
-        * @param Config|null $config optional for backwards compatibility
-        * @return null|string|array Null on success, string on error, or array on
-        *  hook error
-        */
-       public static function getPermissionsError( $user, $editToken, Config $config = null ) {
-               if ( $config === null ) {
-                       wfDebug( __METHOD__ . ' called without a Config instance passed to it' );
-                       $config = MediaWikiServices::getInstance()->getMainConfig();
-               }
-               if ( !$config->get( 'EnableEmail' ) || !$config->get( 'EnableUserEmail' ) ) {
-                       return 'usermaildisabled';
-               }
-
-               // Run this before $user->isAllowed, to show appropriate message to anons (T160309)
-               if ( !$user->isEmailConfirmed() ) {
-                       return 'mailnologin';
-               }
-
-               if ( !$user->isAllowed( 'sendemail' ) ) {
-                       return 'badaccess';
-               }
-
-               if ( $user->isBlockedFromEmailuser() ) {
-                       wfDebug( "User is blocked from sending e-mail.\n" );
-
-                       return "blockedemailuser";
-               }
-
-               // Check the ping limiter without incrementing it - we'll check it
-               // again later and increment it on a successful send
-               if ( $user->pingLimiter( 'emailuser', 0 ) ) {
-                       wfDebug( "Ping limiter triggered.\n" );
-
-                       return 'actionthrottledtext';
-               }
-
-               $hookErr = false;
-
-               Hooks::run( 'UserCanSendEmail', [ &$user, &$hookErr ] );
-               Hooks::run( 'EmailUserPermissionsErrors', [ $user, $editToken, &$hookErr ] );
-
-               if ( $hookErr ) {
-                       return $hookErr;
-               }
-
-               return null;
-       }
-
-       /**
-        * Form to ask for target user name.
-        *
-        * @param string $name User name submitted.
-        */
-       protected function userForm( $name ) {
-               $htmlForm = HTMLForm::factory( 'ooui', [
-                       'Target' => [
-                               'type' => 'user',
-                               'exists' => true,
-                               'label' => $this->msg( 'emailusername' )->text(),
-                               'id' => 'emailusertarget',
-                               'autofocus' => true,
-                               'value' => $name,
-                       ]
-               ], $this->getContext() );
-
-               $htmlForm
-                       ->setMethod( 'post' )
-                       ->setSubmitCallback( [ $this, 'sendEmailForm' ] )
-                       ->setFormIdentifier( 'userForm' )
-                       ->setId( 'askusername' )
-                       ->setWrapperLegendMsg( 'emailtarget' )
-                       ->setSubmitTextMsg( 'emailusernamesubmit' )
-                       ->show();
-       }
-
-       public function sendEmailForm() {
-               $out = $this->getOutput();
-
-               $ret = $this->mTargetObj;
-               if ( !$ret instanceof User ) {
-                       if ( $this->mTarget != '' ) {
-                               // Messages used here: notargettext, noemailtext, nowikiemailtext
-                               $ret = ( $ret == 'notarget' ) ? 'emailnotarget' : ( $ret . 'text' );
-                               return Status::newFatal( $ret );
-                       }
-                       return false;
-               }
-
-               $htmlForm = HTMLForm::factory( 'ooui', $this->getFormFields(), $this->getContext() );
-               // By now we are supposed to be sure that $this->mTarget is a user name
-               $htmlForm
-                       ->addPreText( $this->msg( 'emailpagetext', $this->mTarget )->parse() )
-                       ->setSubmitTextMsg( 'emailsend' )
-                       ->setSubmitCallback( [ __CLASS__, 'submit' ] )
-                       ->setFormIdentifier( 'sendEmailForm' )
-                       ->setWrapperLegendMsg( 'email-legend' )
-                       ->loadData();
-
-               if ( !Hooks::run( 'EmailUserForm', [ &$htmlForm ] ) ) {
-                       return false;
-               }
-
-               $result = $htmlForm->show();
-
-               if ( $result === true || ( $result instanceof Status && $result->isGood() ) ) {
-                       $out->setPageTitle( $this->msg( 'emailsent' ) );
-                       $out->addWikiMsg( 'emailsenttext', $this->mTarget );
-                       $out->returnToMain( false, $ret->getUserPage() );
-               }
-               return true;
-       }
-
-       /**
-        * Really send a mail. Permissions should have been checked using
-        * getPermissionsError(). It is probably also a good
-        * idea to check the edit token and ping limiter in advance.
-        *
-        * @param array $data
-        * @param IContextSource $context
-        * @return Status|bool
-        */
-       public static function submit( array $data, IContextSource $context ) {
-               $config = $context->getConfig();
-
-               $target = self::getTarget( $data['Target'], $context->getUser() );
-               if ( !$target instanceof User ) {
-                       // Messages used here: notargettext, noemailtext, nowikiemailtext
-                       return Status::newFatal( $target . 'text' );
-               }
-
-               $to = MailAddress::newFromUser( $target );
-               $from = MailAddress::newFromUser( $context->getUser() );
-               $subject = $data['Subject'];
-               $text = $data['Text'];
-
-               // Add a standard footer and trim up trailing newlines
-               $text = rtrim( $text ) . "\n\n-- \n";
-               $text .= $context->msg( 'emailuserfooter',
-                       $from->name, $to->name )->inContentLanguage()->text();
-
-               // Check and increment the rate limits
-               if ( $context->getUser()->pingLimiter( 'emailuser' ) ) {
-                       throw new ThrottledError();
-               }
-
-               $error = false;
-               if ( !Hooks::run( 'EmailUser', [ &$to, &$from, &$subject, &$text, &$error ] ) ) {
-                       if ( $error instanceof Status ) {
-                               return $error;
-                       } elseif ( $error === false || $error === '' || $error === [] ) {
-                               // Possibly to tell HTMLForm to pretend there was no submission?
-                               return false;
-                       } elseif ( $error === true ) {
-                               // Hook sent the mail itself and indicates success?
-                               return Status::newGood();
-                       } elseif ( is_array( $error ) ) {
-                               $status = Status::newGood();
-                               foreach ( $error as $e ) {
-                                       $status->fatal( $e );
-                               }
-                               return $status;
-                       } elseif ( $error instanceof MessageSpecifier ) {
-                               return Status::newFatal( $error );
-                       } else {
-                               // Ugh. Either a raw HTML string, or something that's supposed
-                               // to be treated like one.
-                               $type = is_object( $error ) ? get_class( $error ) : gettype( $error );
-                               wfDeprecated( "EmailUser hook returning a $type as \$error", '1.29' );
-                               return Status::newFatal( new ApiRawMessage(
-                                       [ '$1', Message::rawParam( (string)$error ) ], 'hookaborted'
-                               ) );
-                       }
-               }
-
-               if ( $config->get( 'UserEmailUseReplyTo' ) ) {
-                       /**
-                        * Put the generic wiki autogenerated address in the From:
-                        * header and reserve the user for Reply-To.
-                        *
-                        * This is a bit ugly, but will serve to differentiate
-                        * wiki-borne mails from direct mails and protects against
-                        * SPF and bounce problems with some mailers (see below).
-                        */
-                       $mailFrom = new MailAddress( $config->get( 'PasswordSender' ),
-                               $context->msg( 'emailsender' )->inContentLanguage()->text() );
-                       $replyTo = $from;
-               } else {
-                       /**
-                        * Put the sending user's e-mail address in the From: header.
-                        *
-                        * This is clean-looking and convenient, but has issues.
-                        * One is that it doesn't as clearly differentiate the wiki mail
-                        * from "directly" sent mails.
-                        *
-                        * Another is that some mailers (like sSMTP) will use the From
-                        * address as the envelope sender as well. For open sites this
-                        * can cause mails to be flunked for SPF violations (since the
-                        * wiki server isn't an authorized sender for various users'
-                        * domains) as well as creating a privacy issue as bounces
-                        * containing the recipient's e-mail address may get sent to
-                        * the sending user.
-                        */
-                       $mailFrom = $from;
-                       $replyTo = null;
-               }
-
-               $status = UserMailer::send( $to, $mailFrom, $subject, $text, [
-                       'replyTo' => $replyTo,
-               ] );
-
-               if ( !$status->isGood() ) {
-                       return $status;
-               } else {
-                       // if the user requested a copy of this mail, do this now,
-                       // unless they are emailing themselves, in which case one
-                       // copy of the message is sufficient.
-                       if ( $data['CCMe'] && $to != $from ) {
-                               $ccTo = $from;
-                               $ccFrom = $from;
-                               $ccSubject = $context->msg( 'emailccsubject' )->plaintextParams(
-                                       $target->getName(), $subject )->text();
-                               $ccText = $text;
-
-                               Hooks::run( 'EmailUserCC', [ &$ccTo, &$ccFrom, &$ccSubject, &$ccText ] );
-
-                               if ( $config->get( 'UserEmailUseReplyTo' ) ) {
-                                       $mailFrom = new MailAddress(
-                                               $config->get( 'PasswordSender' ),
-                                               $context->msg( 'emailsender' )->inContentLanguage()->text()
-                                       );
-                                       $replyTo = $ccFrom;
-                               } else {
-                                       $mailFrom = $ccFrom;
-                                       $replyTo = null;
-                               }
-
-                               $ccStatus = UserMailer::send(
-                                       $ccTo, $mailFrom, $ccSubject, $ccText, [
-                                               'replyTo' => $replyTo,
-                               ] );
-                               $status->merge( $ccStatus );
-                       }
-
-                       Hooks::run( 'EmailUserComplete', [ $to, $from, $subject, $text ] );
-
-                       return $status;
-               }
-       }
-
-       /**
-        * Return an array of subpages beginning with $search that this special page will accept.
-        *
-        * @param string $search Prefix to search for
-        * @param int $limit Maximum number of results to return (usually 10)
-        * @param int $offset Number of results to skip (usually 0)
-        * @return string[] Matching subpages
-        */
-       public function prefixSearchSubpages( $search, $limit, $offset ) {
-               $user = User::newFromName( $search );
-               if ( !$user ) {
-                       // No prefix suggestion for invalid user
-                       return [];
-               }
-               // Autocomplete subpage as user list - public to allow caching
-               return UserNamePrefixSearch::search( 'public', $search, $limit, $offset );
-       }
-
-       protected function getGroupName() {
-               return 'users';
-       }
-
-       /**
-        * Builds an error message based on the block params
-        *
-        * @return ErrorPageError
-        */
-       private function getBlockedEmailError() {
-               $block = $this->getUser()->mBlock;
-               $params = $block->getBlockErrorParams( $this->getContext() );
-
-               $msg = $block->isSitewide() ? 'blockedtext' : 'blocked-email-user';
-               return new ErrorPageError( 'blockedtitle', $msg, $params );
-       }
-}
index aaa405a..302a55f 100644 (file)
@@ -24,6 +24,8 @@
  * @ingroup SpecialPage
  */
 
+use MediaWiki\Permissions\PermissionManager;
+
 /**
  * MediaWiki page data importer
  *
@@ -83,11 +85,11 @@ class SpecialImport extends SpecialPage {
                # getUserPermissionsErrors() might actually be used for, hence the 'ns-specialprotected'
                $errors = wfMergeErrorArrays(
                        $this->getPageTitle()->getUserPermissionsErrors(
-                               'import', $user, true,
+                               'import', $user, PermissionManager::RIGOR_FULL,
                                [ 'ns-specialprotected', 'badaccess-group0', 'badaccess-groups' ]
                        ),
                        $this->getPageTitle()->getUserPermissionsErrors(
-                               'importupload', $user, true,
+                               'importupload', $user, PermissionManager::RIGOR_FULL,
                                [ 'ns-specialprotected', 'badaccess-group0', 'badaccess-groups' ]
                        )
                );
diff --git a/includes/specials/SpecialListFiles.php b/includes/specials/SpecialListFiles.php
new file mode 100644 (file)
index 0000000..e6e1048
--- /dev/null
@@ -0,0 +1,83 @@
+<?php
+/**
+ * Implements Special:Listfiles
+ *
+ * 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 SpecialPage
+ */
+
+class SpecialListFiles extends IncludableSpecialPage {
+       public function __construct() {
+               parent::__construct( 'Listfiles' );
+       }
+
+       public function execute( $par ) {
+               $this->setHeaders();
+               $this->outputHeader();
+
+               if ( $this->including() ) {
+                       $userName = $par;
+                       $search = '';
+                       $showAll = false;
+               } else {
+                       $userName = $this->getRequest()->getText( 'user', $par );
+                       $search = $this->getRequest()->getText( 'ilsearch', '' );
+                       $showAll = $this->getRequest()->getBool( 'ilshowall', false );
+               }
+
+               $pager = new ImageListPager(
+                       $this->getContext(),
+                       $userName,
+                       $search,
+                       $this->including(),
+                       $showAll
+               );
+
+               $out = $this->getOutput();
+               if ( $this->including() ) {
+                       $out->addParserOutputContent( $pager->getBodyOutput() );
+               } else {
+                       $user = $pager->getRelevantUser();
+                       $this->getSkin()->setRelevantUser( $user );
+                       $pager->getForm();
+                       $out->addParserOutputContent( $pager->getFullOutput() );
+               }
+       }
+
+       /**
+        * Return an array of subpages beginning with $search that this special page will accept.
+        *
+        * @param string $search Prefix to search for
+        * @param int $limit Maximum number of results to return (usually 10)
+        * @param int $offset Number of results to skip (usually 0)
+        * @return string[] Matching subpages
+        */
+       public function prefixSearchSubpages( $search, $limit, $offset ) {
+               $user = User::newFromName( $search );
+               if ( !$user ) {
+                       // No prefix suggestion for invalid user
+                       return [];
+               }
+               // Autocomplete subpage as user list - public to allow caching
+               return UserNamePrefixSearch::search( 'public', $search, $limit, $offset );
+       }
+
+       protected function getGroupName() {
+               return 'media';
+       }
+}
diff --git a/includes/specials/SpecialListGrants.php b/includes/specials/SpecialListGrants.php
new file mode 100644 (file)
index 0000000..ba16baf
--- /dev/null
@@ -0,0 +1,91 @@
+<?php
+/**
+ * Implements Special:Listgrants
+ *
+ * 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 SpecialPage
+ */
+
+/**
+ * This special page lists all defined rights grants and the associated rights.
+ * See also @ref $wgGrantPermissions and @ref $wgGrantPermissionGroups.
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialListGrants extends SpecialPage {
+       function __construct() {
+               parent::__construct( 'Listgrants' );
+       }
+
+       /**
+        * Show the special page
+        * @param string|null $par
+        */
+       public function execute( $par ) {
+               $this->setHeaders();
+               $this->outputHeader();
+
+               $out = $this->getOutput();
+               $out->addModuleStyles( 'mediawiki.special' );
+
+               $out->addHTML(
+                       \Html::openElement( 'table',
+                               [ 'class' => 'wikitable mw-listgrouprights-table' ] ) .
+                               '<tr>' .
+                               \Html::element( 'th', null, $this->msg( 'listgrants-grant' )->text() ) .
+                               \Html::element( 'th', null, $this->msg( 'listgrants-rights' )->text() ) .
+                               '</tr>'
+               );
+
+               foreach ( $this->getConfig()->get( 'GrantPermissions' ) as $grant => $rights ) {
+                       $descs = [];
+                       $rights = array_filter( $rights ); // remove ones with 'false'
+                       foreach ( $rights as $permission => $granted ) {
+                               $descs[] = $this->msg(
+                                       'listgrouprights-right-display',
+                                       \User::getRightDescription( $permission ),
+                                       '<span class="mw-listgrants-right-name">' . $permission . '</span>'
+                               )->parse();
+                       }
+                       if ( $descs === [] ) {
+                               $grantCellHtml = '';
+                       } else {
+                               sort( $descs );
+                               $grantCellHtml = '<ul><li>' . implode( "</li>\n<li>", $descs ) . '</li></ul>';
+                       }
+
+                       $id = Sanitizer::escapeIdForAttribute( $grant );
+                       $out->addHTML( \Html::rawElement( 'tr', [ 'id' => $id ],
+                               "<td>" .
+                               $this->msg(
+                                       "listgrants-grant-display",
+                                       \User::getGrantName( $grant ),
+                                       "<span class='mw-listgrants-grant-name'>" . $id . "</span>"
+                               )->parse() .
+                               "</td>" .
+                               "<td>" . $grantCellHtml . "</td>"
+                       ) );
+               }
+
+               $out->addHTML( \Html::closeElement( 'table' ) );
+       }
+
+       protected function getGroupName() {
+               return 'users';
+       }
+}
diff --git a/includes/specials/SpecialListGroupRights.php b/includes/specials/SpecialListGroupRights.php
new file mode 100644 (file)
index 0000000..1d10791
--- /dev/null
@@ -0,0 +1,293 @@
+<?php
+/**
+ * Implements Special:Listgrouprights
+ *
+ * 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 SpecialPage
+ */
+
+use MediaWiki\MediaWikiServices;
+
+/**
+ * This special page lists all defined user groups and the associated rights.
+ * See also @ref $wgGroupPermissions.
+ *
+ * @ingroup SpecialPage
+ * @author Petr Kadlec <mormegil@centrum.cz>
+ */
+class SpecialListGroupRights extends SpecialPage {
+       public function __construct() {
+               parent::__construct( 'Listgrouprights' );
+       }
+
+       /**
+        * Show the special page
+        * @param string|null $par
+        */
+       public function execute( $par ) {
+               $this->setHeaders();
+               $this->outputHeader();
+
+               $out = $this->getOutput();
+               $out->addModuleStyles( 'mediawiki.special' );
+
+               $out->wrapWikiMsg( "<div class=\"mw-listgrouprights-key\">\n$1\n</div>", 'listgrouprights-key' );
+
+               $out->addHTML(
+                       Xml::openElement( 'table', [ 'class' => 'wikitable mw-listgrouprights-table' ] ) .
+                               '<tr>' .
+                               Xml::element( 'th', null, $this->msg( 'listgrouprights-group' )->text() ) .
+                               Xml::element( 'th', null, $this->msg( 'listgrouprights-rights' )->text() ) .
+                               '</tr>'
+               );
+
+               $config = $this->getConfig();
+               $groupPermissions = $config->get( 'GroupPermissions' );
+               $revokePermissions = $config->get( 'RevokePermissions' );
+               $addGroups = $config->get( 'AddGroups' );
+               $removeGroups = $config->get( 'RemoveGroups' );
+               $groupsAddToSelf = $config->get( 'GroupsAddToSelf' );
+               $groupsRemoveFromSelf = $config->get( 'GroupsRemoveFromSelf' );
+               $allGroups = array_unique( array_merge(
+                       array_keys( $groupPermissions ),
+                       array_keys( $revokePermissions ),
+                       array_keys( $addGroups ),
+                       array_keys( $removeGroups ),
+                       array_keys( $groupsAddToSelf ),
+                       array_keys( $groupsRemoveFromSelf )
+               ) );
+               asort( $allGroups );
+
+               $linkRenderer = $this->getLinkRenderer();
+
+               foreach ( $allGroups as $group ) {
+                       $permissions = $groupPermissions[$group] ?? [];
+                       $groupname = ( $group == '*' ) // Replace * with a more descriptive groupname
+                               ? 'all'
+                               : $group;
+
+                       $groupnameLocalized = UserGroupMembership::getGroupName( $groupname );
+
+                       $grouppageLocalizedTitle = UserGroupMembership::getGroupPage( $groupname )
+                               ?: Title::newFromText( MWNamespace::getCanonicalName( NS_PROJECT ) . ':' . $groupname );
+
+                       if ( $group == '*' || !$grouppageLocalizedTitle ) {
+                               // Do not make a link for the generic * group or group with invalid group page
+                               $grouppage = htmlspecialchars( $groupnameLocalized );
+                       } else {
+                               $grouppage = $linkRenderer->makeLink(
+                                       $grouppageLocalizedTitle,
+                                       $groupnameLocalized
+                               );
+                       }
+
+                       if ( $group === 'user' ) {
+                               // Link to Special:listusers for implicit group 'user'
+                               $grouplink = '<br />' . $linkRenderer->makeKnownLink(
+                                       SpecialPage::getTitleFor( 'Listusers' ),
+                                       $this->msg( 'listgrouprights-members' )->text()
+                               );
+                       } elseif ( !in_array( $group, $config->get( 'ImplicitGroups' ) ) ) {
+                               $grouplink = '<br />' . $linkRenderer->makeKnownLink(
+                                       SpecialPage::getTitleFor( 'Listusers' ),
+                                       $this->msg( 'listgrouprights-members' )->text(),
+                                       [],
+                                       [ 'group' => $group ]
+                               );
+                       } else {
+                               // No link to Special:listusers for other implicit groups as they are unlistable
+                               $grouplink = '';
+                       }
+
+                       $revoke = $revokePermissions[$group] ?? [];
+                       $addgroups = $addGroups[$group] ?? [];
+                       $removegroups = $removeGroups[$group] ?? [];
+                       $addgroupsSelf = $groupsAddToSelf[$group] ?? [];
+                       $removegroupsSelf = $groupsRemoveFromSelf[$group] ?? [];
+
+                       $id = $group == '*' ? false : Sanitizer::escapeIdForAttribute( $group );
+                       $out->addHTML( Html::rawElement( 'tr', [ 'id' => $id ], "
+                               <td>$grouppage$grouplink</td>
+                                       <td>" .
+                                       $this->formatPermissions( $permissions, $revoke, $addgroups, $removegroups,
+                                               $addgroupsSelf, $removegroupsSelf ) .
+                                       '</td>
+                               '
+                       ) );
+               }
+               $out->addHTML( Xml::closeElement( 'table' ) );
+               $this->outputNamespaceProtectionInfo();
+       }
+
+       private function outputNamespaceProtectionInfo() {
+               $out = $this->getOutput();
+               $namespaceProtection = $this->getConfig()->get( 'NamespaceProtection' );
+
+               if ( count( $namespaceProtection ) == 0 ) {
+                       return;
+               }
+
+               $header = $this->msg( 'listgrouprights-namespaceprotection-header' )->text();
+               $out->addHTML(
+                       Html::rawElement( 'h2', [], Html::element( 'span', [
+                               'class' => 'mw-headline',
+                               'id' => substr( Parser::guessSectionNameFromStrippedText( $header ), 1 )
+                       ], $header ) ) .
+                       Xml::openElement( 'table', [ 'class' => 'wikitable' ] ) .
+                       Html::element(
+                               'th',
+                               [],
+                               $this->msg( 'listgrouprights-namespaceprotection-namespace' )->text()
+                       ) .
+                       Html::element(
+                               'th',
+                               [],
+                               $this->msg( 'listgrouprights-namespaceprotection-restrictedto' )->text()
+                       )
+               );
+               $linkRenderer = $this->getLinkRenderer();
+               ksort( $namespaceProtection );
+               $validNamespaces = MWNamespace::getValidNamespaces();
+               $contLang = MediaWikiServices::getInstance()->getContentLanguage();
+               foreach ( $namespaceProtection as $namespace => $rights ) {
+                       if ( !in_array( $namespace, $validNamespaces ) ) {
+                               continue;
+                       }
+
+                       if ( $namespace == NS_MAIN ) {
+                               $namespaceText = $this->msg( 'blanknamespace' )->text();
+                       } else {
+                               $namespaceText = $contLang->convertNamespace( $namespace );
+                       }
+
+                       $out->addHTML(
+                               Xml::openElement( 'tr' ) .
+                               Html::rawElement(
+                                       'td',
+                                       [],
+                                       $linkRenderer->makeLink(
+                                               SpecialPage::getTitleFor( 'Allpages' ),
+                                               $namespaceText,
+                                               [],
+                                               [ 'namespace' => $namespace ]
+                                       )
+                               ) .
+                               Xml::openElement( 'td' ) . Xml::openElement( 'ul' )
+                       );
+
+                       if ( !is_array( $rights ) ) {
+                               $rights = [ $rights ];
+                       }
+
+                       foreach ( $rights as $right ) {
+                               $out->addHTML(
+                                       Html::rawElement( 'li', [], $this->msg(
+                                               'listgrouprights-right-display',
+                                               User::getRightDescription( $right ),
+                                               Html::element(
+                                                       'span',
+                                                       [ 'class' => 'mw-listgrouprights-right-name' ],
+                                                       $right
+                                               )
+                                       )->parse() )
+                               );
+                       }
+
+                       $out->addHTML(
+                               Xml::closeElement( 'ul' ) .
+                               Xml::closeElement( 'td' ) .
+                               Xml::closeElement( 'tr' )
+                       );
+               }
+               $out->addHTML( Xml::closeElement( 'table' ) );
+       }
+
+       /**
+        * Create a user-readable list of permissions from the given array.
+        *
+        * @param array $permissions Array of permission => bool (from $wgGroupPermissions items)
+        * @param array $revoke Array of permission => bool (from $wgRevokePermissions items)
+        * @param array $add Array of groups this group is allowed to add or true
+        * @param array $remove Array of groups this group is allowed to remove or true
+        * @param array $addSelf Array of groups this group is allowed to add to self or true
+        * @param array $removeSelf Array of group this group is allowed to remove from self or true
+        * @return string HTML list of all granted permissions
+        */
+       private function formatPermissions( $permissions, $revoke, $add, $remove, $addSelf, $removeSelf ) {
+               $r = [];
+               foreach ( $permissions as $permission => $granted ) {
+                       // show as granted only if it isn't revoked to prevent duplicate display of permissions
+                       if ( $granted && ( !isset( $revoke[$permission] ) || !$revoke[$permission] ) ) {
+                               $r[] = $this->msg( 'listgrouprights-right-display',
+                                       User::getRightDescription( $permission ),
+                                       '<span class="mw-listgrouprights-right-name">' . $permission . '</span>'
+                               )->parse();
+                       }
+               }
+               foreach ( $revoke as $permission => $revoked ) {
+                       if ( $revoked ) {
+                               $r[] = $this->msg( 'listgrouprights-right-revoked',
+                                       User::getRightDescription( $permission ),
+                                       '<span class="mw-listgrouprights-right-name">' . $permission . '</span>'
+                               )->parse();
+                       }
+               }
+
+               sort( $r );
+
+               $lang = $this->getLanguage();
+               $allGroups = User::getAllGroups();
+
+               $changeGroups = [
+                       'addgroup' => $add,
+                       'removegroup' => $remove,
+                       'addgroup-self' => $addSelf,
+                       'removegroup-self' => $removeSelf
+               ];
+
+               foreach ( $changeGroups as $messageKey => $changeGroup ) {
+                       if ( $changeGroup === true ) {
+                               // For grep: listgrouprights-addgroup-all, listgrouprights-removegroup-all,
+                               // listgrouprights-addgroup-self-all, listgrouprights-removegroup-self-all
+                               $r[] = $this->msg( 'listgrouprights-' . $messageKey . '-all' )->escaped();
+                       } elseif ( is_array( $changeGroup ) ) {
+                               $changeGroup = array_intersect( array_values( array_unique( $changeGroup ) ), $allGroups );
+                               if ( count( $changeGroup ) ) {
+                                       $groupLinks = [];
+                                       foreach ( $changeGroup as $group ) {
+                                               $groupLinks[] = UserGroupMembership::getLink( $group, $this->getContext(), 'wiki' );
+                                       }
+                                       // For grep: listgrouprights-addgroup, listgrouprights-removegroup,
+                                       // listgrouprights-addgroup-self, listgrouprights-removegroup-self
+                                       $r[] = $this->msg( 'listgrouprights-' . $messageKey,
+                                               $lang->listToText( $groupLinks ), count( $changeGroup ) )->parse();
+                               }
+                       }
+               }
+
+               if ( empty( $r ) ) {
+                       return '';
+               } else {
+                       return '<ul><li>' . implode( "</li>\n<li>", $r ) . '</li></ul>';
+               }
+       }
+
+       protected function getGroupName() {
+               return 'users';
+       }
+}
diff --git a/includes/specials/SpecialListUsers.php b/includes/specials/SpecialListUsers.php
new file mode 100644 (file)
index 0000000..7aef4ae
--- /dev/null
@@ -0,0 +1,77 @@
+<?php
+/**
+ * Implements Special:Listusers
+ *
+ * Copyright © 2004 Brion Vibber, lcrocker, Tim Starling,
+ * Domas Mituzas, Antoine Musso, Jens Frank, Zhengzhu,
+ * 2006 Rob Church <robchur@gmail.com>
+ *
+ * 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 SpecialPage
+ */
+
+/**
+ * @ingroup SpecialPage
+ */
+class SpecialListUsers extends IncludableSpecialPage {
+
+       public function __construct() {
+               parent::__construct( 'Listusers' );
+       }
+
+       /**
+        * @param string|null $par (optional) A group to list users from
+        */
+       public function execute( $par ) {
+               $this->setHeaders();
+               $this->outputHeader();
+
+               $up = new UsersPager( $this->getContext(), $par, $this->including() );
+
+               # getBody() first to check, if empty
+               $usersbody = $up->getBody();
+
+               $s = '';
+               if ( !$this->including() ) {
+                       $s = $up->getPageHeader();
+               }
+
+               if ( $usersbody ) {
+                       $s .= $up->getNavigationBar();
+                       $s .= Html::rawElement( 'ul', [], $usersbody );
+                       $s .= $up->getNavigationBar();
+               } else {
+                       $s .= $this->msg( 'listusers-noresult' )->parseAsBlock();
+               }
+
+               $this->getOutput()->addHTML( $s );
+       }
+
+       /**
+        * Return an array of subpages that this special page will accept.
+        *
+        * @return string[] subpages
+        */
+       public function getSubpagesForPrefixSearch() {
+               return User::getAllGroups();
+       }
+
+       protected function getGroupName() {
+               return 'users';
+       }
+}
diff --git a/includes/specials/SpecialListfiles.php b/includes/specials/SpecialListfiles.php
deleted file mode 100644 (file)
index e6e1048..0000000
+++ /dev/null
@@ -1,83 +0,0 @@
-<?php
-/**
- * Implements Special:Listfiles
- *
- * 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 SpecialPage
- */
-
-class SpecialListFiles extends IncludableSpecialPage {
-       public function __construct() {
-               parent::__construct( 'Listfiles' );
-       }
-
-       public function execute( $par ) {
-               $this->setHeaders();
-               $this->outputHeader();
-
-               if ( $this->including() ) {
-                       $userName = $par;
-                       $search = '';
-                       $showAll = false;
-               } else {
-                       $userName = $this->getRequest()->getText( 'user', $par );
-                       $search = $this->getRequest()->getText( 'ilsearch', '' );
-                       $showAll = $this->getRequest()->getBool( 'ilshowall', false );
-               }
-
-               $pager = new ImageListPager(
-                       $this->getContext(),
-                       $userName,
-                       $search,
-                       $this->including(),
-                       $showAll
-               );
-
-               $out = $this->getOutput();
-               if ( $this->including() ) {
-                       $out->addParserOutputContent( $pager->getBodyOutput() );
-               } else {
-                       $user = $pager->getRelevantUser();
-                       $this->getSkin()->setRelevantUser( $user );
-                       $pager->getForm();
-                       $out->addParserOutputContent( $pager->getFullOutput() );
-               }
-       }
-
-       /**
-        * Return an array of subpages beginning with $search that this special page will accept.
-        *
-        * @param string $search Prefix to search for
-        * @param int $limit Maximum number of results to return (usually 10)
-        * @param int $offset Number of results to skip (usually 0)
-        * @return string[] Matching subpages
-        */
-       public function prefixSearchSubpages( $search, $limit, $offset ) {
-               $user = User::newFromName( $search );
-               if ( !$user ) {
-                       // No prefix suggestion for invalid user
-                       return [];
-               }
-               // Autocomplete subpage as user list - public to allow caching
-               return UserNamePrefixSearch::search( 'public', $search, $limit, $offset );
-       }
-
-       protected function getGroupName() {
-               return 'media';
-       }
-}
diff --git a/includes/specials/SpecialListgrants.php b/includes/specials/SpecialListgrants.php
deleted file mode 100644 (file)
index ba16baf..0000000
+++ /dev/null
@@ -1,91 +0,0 @@
-<?php
-/**
- * Implements Special:Listgrants
- *
- * 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 SpecialPage
- */
-
-/**
- * This special page lists all defined rights grants and the associated rights.
- * See also @ref $wgGrantPermissions and @ref $wgGrantPermissionGroups.
- *
- * @ingroup SpecialPage
- */
-class SpecialListGrants extends SpecialPage {
-       function __construct() {
-               parent::__construct( 'Listgrants' );
-       }
-
-       /**
-        * Show the special page
-        * @param string|null $par
-        */
-       public function execute( $par ) {
-               $this->setHeaders();
-               $this->outputHeader();
-
-               $out = $this->getOutput();
-               $out->addModuleStyles( 'mediawiki.special' );
-
-               $out->addHTML(
-                       \Html::openElement( 'table',
-                               [ 'class' => 'wikitable mw-listgrouprights-table' ] ) .
-                               '<tr>' .
-                               \Html::element( 'th', null, $this->msg( 'listgrants-grant' )->text() ) .
-                               \Html::element( 'th', null, $this->msg( 'listgrants-rights' )->text() ) .
-                               '</tr>'
-               );
-
-               foreach ( $this->getConfig()->get( 'GrantPermissions' ) as $grant => $rights ) {
-                       $descs = [];
-                       $rights = array_filter( $rights ); // remove ones with 'false'
-                       foreach ( $rights as $permission => $granted ) {
-                               $descs[] = $this->msg(
-                                       'listgrouprights-right-display',
-                                       \User::getRightDescription( $permission ),
-                                       '<span class="mw-listgrants-right-name">' . $permission . '</span>'
-                               )->parse();
-                       }
-                       if ( $descs === [] ) {
-                               $grantCellHtml = '';
-                       } else {
-                               sort( $descs );
-                               $grantCellHtml = '<ul><li>' . implode( "</li>\n<li>", $descs ) . '</li></ul>';
-                       }
-
-                       $id = Sanitizer::escapeIdForAttribute( $grant );
-                       $out->addHTML( \Html::rawElement( 'tr', [ 'id' => $id ],
-                               "<td>" .
-                               $this->msg(
-                                       "listgrants-grant-display",
-                                       \User::getGrantName( $grant ),
-                                       "<span class='mw-listgrants-grant-name'>" . $id . "</span>"
-                               )->parse() .
-                               "</td>" .
-                               "<td>" . $grantCellHtml . "</td>"
-                       ) );
-               }
-
-               $out->addHTML( \Html::closeElement( 'table' ) );
-       }
-
-       protected function getGroupName() {
-               return 'users';
-       }
-}
diff --git a/includes/specials/SpecialListgrouprights.php b/includes/specials/SpecialListgrouprights.php
deleted file mode 100644 (file)
index 1d10791..0000000
+++ /dev/null
@@ -1,293 +0,0 @@
-<?php
-/**
- * Implements Special:Listgrouprights
- *
- * 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 SpecialPage
- */
-
-use MediaWiki\MediaWikiServices;
-
-/**
- * This special page lists all defined user groups and the associated rights.
- * See also @ref $wgGroupPermissions.
- *
- * @ingroup SpecialPage
- * @author Petr Kadlec <mormegil@centrum.cz>
- */
-class SpecialListGroupRights extends SpecialPage {
-       public function __construct() {
-               parent::__construct( 'Listgrouprights' );
-       }
-
-       /**
-        * Show the special page
-        * @param string|null $par
-        */
-       public function execute( $par ) {
-               $this->setHeaders();
-               $this->outputHeader();
-
-               $out = $this->getOutput();
-               $out->addModuleStyles( 'mediawiki.special' );
-
-               $out->wrapWikiMsg( "<div class=\"mw-listgrouprights-key\">\n$1\n</div>", 'listgrouprights-key' );
-
-               $out->addHTML(
-                       Xml::openElement( 'table', [ 'class' => 'wikitable mw-listgrouprights-table' ] ) .
-                               '<tr>' .
-                               Xml::element( 'th', null, $this->msg( 'listgrouprights-group' )->text() ) .
-                               Xml::element( 'th', null, $this->msg( 'listgrouprights-rights' )->text() ) .
-                               '</tr>'
-               );
-
-               $config = $this->getConfig();
-               $groupPermissions = $config->get( 'GroupPermissions' );
-               $revokePermissions = $config->get( 'RevokePermissions' );
-               $addGroups = $config->get( 'AddGroups' );
-               $removeGroups = $config->get( 'RemoveGroups' );
-               $groupsAddToSelf = $config->get( 'GroupsAddToSelf' );
-               $groupsRemoveFromSelf = $config->get( 'GroupsRemoveFromSelf' );
-               $allGroups = array_unique( array_merge(
-                       array_keys( $groupPermissions ),
-                       array_keys( $revokePermissions ),
-                       array_keys( $addGroups ),
-                       array_keys( $removeGroups ),
-                       array_keys( $groupsAddToSelf ),
-                       array_keys( $groupsRemoveFromSelf )
-               ) );
-               asort( $allGroups );
-
-               $linkRenderer = $this->getLinkRenderer();
-
-               foreach ( $allGroups as $group ) {
-                       $permissions = $groupPermissions[$group] ?? [];
-                       $groupname = ( $group == '*' ) // Replace * with a more descriptive groupname
-                               ? 'all'
-                               : $group;
-
-                       $groupnameLocalized = UserGroupMembership::getGroupName( $groupname );
-
-                       $grouppageLocalizedTitle = UserGroupMembership::getGroupPage( $groupname )
-                               ?: Title::newFromText( MWNamespace::getCanonicalName( NS_PROJECT ) . ':' . $groupname );
-
-                       if ( $group == '*' || !$grouppageLocalizedTitle ) {
-                               // Do not make a link for the generic * group or group with invalid group page
-                               $grouppage = htmlspecialchars( $groupnameLocalized );
-                       } else {
-                               $grouppage = $linkRenderer->makeLink(
-                                       $grouppageLocalizedTitle,
-                                       $groupnameLocalized
-                               );
-                       }
-
-                       if ( $group === 'user' ) {
-                               // Link to Special:listusers for implicit group 'user'
-                               $grouplink = '<br />' . $linkRenderer->makeKnownLink(
-                                       SpecialPage::getTitleFor( 'Listusers' ),
-                                       $this->msg( 'listgrouprights-members' )->text()
-                               );
-                       } elseif ( !in_array( $group, $config->get( 'ImplicitGroups' ) ) ) {
-                               $grouplink = '<br />' . $linkRenderer->makeKnownLink(
-                                       SpecialPage::getTitleFor( 'Listusers' ),
-                                       $this->msg( 'listgrouprights-members' )->text(),
-                                       [],
-                                       [ 'group' => $group ]
-                               );
-                       } else {
-                               // No link to Special:listusers for other implicit groups as they are unlistable
-                               $grouplink = '';
-                       }
-
-                       $revoke = $revokePermissions[$group] ?? [];
-                       $addgroups = $addGroups[$group] ?? [];
-                       $removegroups = $removeGroups[$group] ?? [];
-                       $addgroupsSelf = $groupsAddToSelf[$group] ?? [];
-                       $removegroupsSelf = $groupsRemoveFromSelf[$group] ?? [];
-
-                       $id = $group == '*' ? false : Sanitizer::escapeIdForAttribute( $group );
-                       $out->addHTML( Html::rawElement( 'tr', [ 'id' => $id ], "
-                               <td>$grouppage$grouplink</td>
-                                       <td>" .
-                                       $this->formatPermissions( $permissions, $revoke, $addgroups, $removegroups,
-                                               $addgroupsSelf, $removegroupsSelf ) .
-                                       '</td>
-                               '
-                       ) );
-               }
-               $out->addHTML( Xml::closeElement( 'table' ) );
-               $this->outputNamespaceProtectionInfo();
-       }
-
-       private function outputNamespaceProtectionInfo() {
-               $out = $this->getOutput();
-               $namespaceProtection = $this->getConfig()->get( 'NamespaceProtection' );
-
-               if ( count( $namespaceProtection ) == 0 ) {
-                       return;
-               }
-
-               $header = $this->msg( 'listgrouprights-namespaceprotection-header' )->text();
-               $out->addHTML(
-                       Html::rawElement( 'h2', [], Html::element( 'span', [
-                               'class' => 'mw-headline',
-                               'id' => substr( Parser::guessSectionNameFromStrippedText( $header ), 1 )
-                       ], $header ) ) .
-                       Xml::openElement( 'table', [ 'class' => 'wikitable' ] ) .
-                       Html::element(
-                               'th',
-                               [],
-                               $this->msg( 'listgrouprights-namespaceprotection-namespace' )->text()
-                       ) .
-                       Html::element(
-                               'th',
-                               [],
-                               $this->msg( 'listgrouprights-namespaceprotection-restrictedto' )->text()
-                       )
-               );
-               $linkRenderer = $this->getLinkRenderer();
-               ksort( $namespaceProtection );
-               $validNamespaces = MWNamespace::getValidNamespaces();
-               $contLang = MediaWikiServices::getInstance()->getContentLanguage();
-               foreach ( $namespaceProtection as $namespace => $rights ) {
-                       if ( !in_array( $namespace, $validNamespaces ) ) {
-                               continue;
-                       }
-
-                       if ( $namespace == NS_MAIN ) {
-                               $namespaceText = $this->msg( 'blanknamespace' )->text();
-                       } else {
-                               $namespaceText = $contLang->convertNamespace( $namespace );
-                       }
-
-                       $out->addHTML(
-                               Xml::openElement( 'tr' ) .
-                               Html::rawElement(
-                                       'td',
-                                       [],
-                                       $linkRenderer->makeLink(
-                                               SpecialPage::getTitleFor( 'Allpages' ),
-                                               $namespaceText,
-                                               [],
-                                               [ 'namespace' => $namespace ]
-                                       )
-                               ) .
-                               Xml::openElement( 'td' ) . Xml::openElement( 'ul' )
-                       );
-
-                       if ( !is_array( $rights ) ) {
-                               $rights = [ $rights ];
-                       }
-
-                       foreach ( $rights as $right ) {
-                               $out->addHTML(
-                                       Html::rawElement( 'li', [], $this->msg(
-                                               'listgrouprights-right-display',
-                                               User::getRightDescription( $right ),
-                                               Html::element(
-                                                       'span',
-                                                       [ 'class' => 'mw-listgrouprights-right-name' ],
-                                                       $right
-                                               )
-                                       )->parse() )
-                               );
-                       }
-
-                       $out->addHTML(
-                               Xml::closeElement( 'ul' ) .
-                               Xml::closeElement( 'td' ) .
-                               Xml::closeElement( 'tr' )
-                       );
-               }
-               $out->addHTML( Xml::closeElement( 'table' ) );
-       }
-
-       /**
-        * Create a user-readable list of permissions from the given array.
-        *
-        * @param array $permissions Array of permission => bool (from $wgGroupPermissions items)
-        * @param array $revoke Array of permission => bool (from $wgRevokePermissions items)
-        * @param array $add Array of groups this group is allowed to add or true
-        * @param array $remove Array of groups this group is allowed to remove or true
-        * @param array $addSelf Array of groups this group is allowed to add to self or true
-        * @param array $removeSelf Array of group this group is allowed to remove from self or true
-        * @return string HTML list of all granted permissions
-        */
-       private function formatPermissions( $permissions, $revoke, $add, $remove, $addSelf, $removeSelf ) {
-               $r = [];
-               foreach ( $permissions as $permission => $granted ) {
-                       // show as granted only if it isn't revoked to prevent duplicate display of permissions
-                       if ( $granted && ( !isset( $revoke[$permission] ) || !$revoke[$permission] ) ) {
-                               $r[] = $this->msg( 'listgrouprights-right-display',
-                                       User::getRightDescription( $permission ),
-                                       '<span class="mw-listgrouprights-right-name">' . $permission . '</span>'
-                               )->parse();
-                       }
-               }
-               foreach ( $revoke as $permission => $revoked ) {
-                       if ( $revoked ) {
-                               $r[] = $this->msg( 'listgrouprights-right-revoked',
-                                       User::getRightDescription( $permission ),
-                                       '<span class="mw-listgrouprights-right-name">' . $permission . '</span>'
-                               )->parse();
-                       }
-               }
-
-               sort( $r );
-
-               $lang = $this->getLanguage();
-               $allGroups = User::getAllGroups();
-
-               $changeGroups = [
-                       'addgroup' => $add,
-                       'removegroup' => $remove,
-                       'addgroup-self' => $addSelf,
-                       'removegroup-self' => $removeSelf
-               ];
-
-               foreach ( $changeGroups as $messageKey => $changeGroup ) {
-                       if ( $changeGroup === true ) {
-                               // For grep: listgrouprights-addgroup-all, listgrouprights-removegroup-all,
-                               // listgrouprights-addgroup-self-all, listgrouprights-removegroup-self-all
-                               $r[] = $this->msg( 'listgrouprights-' . $messageKey . '-all' )->escaped();
-                       } elseif ( is_array( $changeGroup ) ) {
-                               $changeGroup = array_intersect( array_values( array_unique( $changeGroup ) ), $allGroups );
-                               if ( count( $changeGroup ) ) {
-                                       $groupLinks = [];
-                                       foreach ( $changeGroup as $group ) {
-                                               $groupLinks[] = UserGroupMembership::getLink( $group, $this->getContext(), 'wiki' );
-                                       }
-                                       // For grep: listgrouprights-addgroup, listgrouprights-removegroup,
-                                       // listgrouprights-addgroup-self, listgrouprights-removegroup-self
-                                       $r[] = $this->msg( 'listgrouprights-' . $messageKey,
-                                               $lang->listToText( $groupLinks ), count( $changeGroup ) )->parse();
-                               }
-                       }
-               }
-
-               if ( empty( $r ) ) {
-                       return '';
-               } else {
-                       return '<ul><li>' . implode( "</li>\n<li>", $r ) . '</li></ul>';
-               }
-       }
-
-       protected function getGroupName() {
-               return 'users';
-       }
-}
diff --git a/includes/specials/SpecialListusers.php b/includes/specials/SpecialListusers.php
deleted file mode 100644 (file)
index 7aef4ae..0000000
+++ /dev/null
@@ -1,77 +0,0 @@
-<?php
-/**
- * Implements Special:Listusers
- *
- * Copyright © 2004 Brion Vibber, lcrocker, Tim Starling,
- * Domas Mituzas, Antoine Musso, Jens Frank, Zhengzhu,
- * 2006 Rob Church <robchur@gmail.com>
- *
- * 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 SpecialPage
- */
-
-/**
- * @ingroup SpecialPage
- */
-class SpecialListUsers extends IncludableSpecialPage {
-
-       public function __construct() {
-               parent::__construct( 'Listusers' );
-       }
-
-       /**
-        * @param string|null $par (optional) A group to list users from
-        */
-       public function execute( $par ) {
-               $this->setHeaders();
-               $this->outputHeader();
-
-               $up = new UsersPager( $this->getContext(), $par, $this->including() );
-
-               # getBody() first to check, if empty
-               $usersbody = $up->getBody();
-
-               $s = '';
-               if ( !$this->including() ) {
-                       $s = $up->getPageHeader();
-               }
-
-               if ( $usersbody ) {
-                       $s .= $up->getNavigationBar();
-                       $s .= Html::rawElement( 'ul', [], $usersbody );
-                       $s .= $up->getNavigationBar();
-               } else {
-                       $s .= $this->msg( 'listusers-noresult' )->parseAsBlock();
-               }
-
-               $this->getOutput()->addHTML( $s );
-       }
-
-       /**
-        * Return an array of subpages that this special page will accept.
-        *
-        * @return string[] subpages
-        */
-       public function getSubpagesForPrefixSearch() {
-               return User::getAllGroups();
-       }
-
-       protected function getGroupName() {
-               return 'users';
-       }
-}
index cc7ed55..0bc9147 100644 (file)
@@ -106,7 +106,7 @@ class SpecialPreferences extends SpecialPage {
         * Get the preferences form to use.
         * @param User $user The user.
         * @param IContextSource $context The context.
-        * @return PreferencesFormLegacy|HTMLForm
+        * @return PreferencesFormOOUI|HTMLForm
         */
        protected function getFormObject( $user, IContextSource $context ) {
                $preferencesFactory = MediaWikiServices::getInstance()->getPreferencesFactory();
diff --git a/includes/specials/SpecialRecentChanges.php b/includes/specials/SpecialRecentChanges.php
new file mode 100644 (file)
index 0000000..d0846b3
--- /dev/null
@@ -0,0 +1,947 @@
+<?php
+/**
+ * Implements Special:Recentchanges
+ *
+ * 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 SpecialPage
+ */
+
+use MediaWiki\MediaWikiServices;
+use Wikimedia\Rdbms\IResultWrapper;
+use Wikimedia\Rdbms\FakeResultWrapper;
+
+/**
+ * A special page that lists last changes made to the wiki
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialRecentChanges extends ChangesListSpecialPage {
+
+       protected static $savedQueriesPreferenceName = 'rcfilters-saved-queries';
+       protected static $daysPreferenceName = 'rcdays'; // Use general RecentChanges preference
+       protected static $limitPreferenceName = 'rcfilters-limit'; // Use RCFilters-specific preference
+       protected static $collapsedPreferenceName = 'rcfilters-rc-collapsed';
+
+       private $watchlistFilterGroupDefinition;
+
+       public function __construct( $name = 'Recentchanges', $restriction = '' ) {
+               parent::__construct( $name, $restriction );
+
+               $this->watchlistFilterGroupDefinition = [
+                       'name' => 'watchlist',
+                       'title' => 'rcfilters-filtergroup-watchlist',
+                       'class' => ChangesListStringOptionsFilterGroup::class,
+                       'priority' => -9,
+                       'isFullCoverage' => true,
+                       'filters' => [
+                               [
+                                       'name' => 'watched',
+                                       'label' => 'rcfilters-filter-watchlist-watched-label',
+                                       'description' => 'rcfilters-filter-watchlist-watched-description',
+                                       'cssClassSuffix' => 'watched',
+                                       'isRowApplicableCallable' => function ( $ctx, $rc ) {
+                                               return $rc->getAttribute( 'wl_user' );
+                                       }
+                               ],
+                               [
+                                       'name' => 'watchednew',
+                                       'label' => 'rcfilters-filter-watchlist-watchednew-label',
+                                       'description' => 'rcfilters-filter-watchlist-watchednew-description',
+                                       'cssClassSuffix' => 'watchednew',
+                                       'isRowApplicableCallable' => function ( $ctx, $rc ) {
+                                               return $rc->getAttribute( 'wl_user' ) &&
+                                                       $rc->getAttribute( 'rc_timestamp' ) &&
+                                                       $rc->getAttribute( 'wl_notificationtimestamp' ) &&
+                                                       $rc->getAttribute( 'rc_timestamp' ) >= $rc->getAttribute( 'wl_notificationtimestamp' );
+                                       },
+                               ],
+                               [
+                                       'name' => 'notwatched',
+                                       'label' => 'rcfilters-filter-watchlist-notwatched-label',
+                                       'description' => 'rcfilters-filter-watchlist-notwatched-description',
+                                       'cssClassSuffix' => 'notwatched',
+                                       'isRowApplicableCallable' => function ( $ctx, $rc ) {
+                                               return $rc->getAttribute( 'wl_user' ) === null;
+                                       },
+                               ]
+                       ],
+                       'default' => ChangesListStringOptionsFilterGroup::NONE,
+                       'queryCallable' => function ( $specialPageClassName, $context, $dbr,
+                               &$tables, &$fields, &$conds, &$query_options, &$join_conds, $selectedValues ) {
+                               sort( $selectedValues );
+                               $notwatchedCond = 'wl_user IS NULL';
+                               $watchedCond = 'wl_user IS NOT NULL';
+                               $newCond = 'rc_timestamp >= wl_notificationtimestamp';
+
+                               if ( $selectedValues === [ 'notwatched' ] ) {
+                                       $conds[] = $notwatchedCond;
+                                       return;
+                               }
+
+                               if ( $selectedValues === [ 'watched' ] ) {
+                                       $conds[] = $watchedCond;
+                                       return;
+                               }
+
+                               if ( $selectedValues === [ 'watchednew' ] ) {
+                                       $conds[] = $dbr->makeList( [
+                                               $watchedCond,
+                                               $newCond
+                                       ], LIST_AND );
+                                       return;
+                               }
+
+                               if ( $selectedValues === [ 'notwatched', 'watched' ] ) {
+                                       // no filters
+                                       return;
+                               }
+
+                               if ( $selectedValues === [ 'notwatched', 'watchednew' ] ) {
+                                       $conds[] = $dbr->makeList( [
+                                               $notwatchedCond,
+                                               $dbr->makeList( [
+                                                       $watchedCond,
+                                                       $newCond
+                                               ], LIST_AND )
+                                       ], LIST_OR );
+                                       return;
+                               }
+
+                               if ( $selectedValues === [ 'watched', 'watchednew' ] ) {
+                                       $conds[] = $watchedCond;
+                                       return;
+                               }
+
+                               if ( $selectedValues === [ 'notwatched', 'watched', 'watchednew' ] ) {
+                                       // no filters
+                                       return;
+                               }
+                       }
+               ];
+       }
+
+       /**
+        * @param string|null $subpage
+        */
+       public function execute( $subpage ) {
+               // Backwards-compatibility: redirect to new feed URLs
+               $feedFormat = $this->getRequest()->getVal( 'feed' );
+               if ( !$this->including() && $feedFormat ) {
+                       $query = $this->getFeedQuery();
+                       $query['feedformat'] = $feedFormat === 'atom' ? 'atom' : 'rss';
+                       $this->getOutput()->redirect( wfAppendQuery( wfScript( 'api' ), $query ) );
+
+                       return;
+               }
+
+               // 10 seconds server-side caching max
+               $out = $this->getOutput();
+               $out->setCdnMaxage( 10 );
+               // Check if the client has a cached version
+               $lastmod = $this->checkLastModified();
+               if ( $lastmod === false ) {
+                       return;
+               }
+
+               $this->addHelpLink(
+                       '//meta.wikimedia.org/wiki/Special:MyLanguage/Help:Recent_changes',
+                       true
+               );
+               parent::execute( $subpage );
+       }
+
+       /**
+        * @inheritDoc
+        */
+       protected function transformFilterDefinition( array $filterDefinition ) {
+               if ( isset( $filterDefinition['showHideSuffix'] ) ) {
+                       $filterDefinition['showHide'] = 'rc' . $filterDefinition['showHideSuffix'];
+               }
+
+               return $filterDefinition;
+       }
+
+       /**
+        * @inheritDoc
+        */
+       protected function registerFilters() {
+               parent::registerFilters();
+
+               if (
+                       !$this->including() &&
+                       $this->getUser()->isLoggedIn() &&
+                       $this->getUser()->isAllowed( 'viewmywatchlist' )
+               ) {
+                       $this->registerFiltersFromDefinitions( [ $this->watchlistFilterGroupDefinition ] );
+                       $watchlistGroup = $this->getFilterGroup( 'watchlist' );
+                       $watchlistGroup->getFilter( 'watched' )->setAsSupersetOf(
+                               $watchlistGroup->getFilter( 'watchednew' )
+                       );
+               }
+
+               $user = $this->getUser();
+
+               $significance = $this->getFilterGroup( 'significance' );
+               $hideMinor = $significance->getFilter( 'hideminor' );
+               $hideMinor->setDefault( $user->getBoolOption( 'hideminor' ) );
+
+               $automated = $this->getFilterGroup( 'automated' );
+               $hideBots = $automated->getFilter( 'hidebots' );
+               $hideBots->setDefault( true );
+
+               $reviewStatus = $this->getFilterGroup( 'reviewStatus' );
+               if ( $reviewStatus !== null ) {
+                       // Conditional on feature being available and rights
+                       if ( $user->getBoolOption( 'hidepatrolled' ) ) {
+                               $reviewStatus->setDefault( 'unpatrolled' );
+                               $legacyReviewStatus = $this->getFilterGroup( 'legacyReviewStatus' );
+                               $legacyHidePatrolled = $legacyReviewStatus->getFilter( 'hidepatrolled' );
+                               $legacyHidePatrolled->setDefault( true );
+                       }
+               }
+
+               $changeType = $this->getFilterGroup( 'changeType' );
+               $hideCategorization = $changeType->getFilter( 'hidecategorization' );
+               if ( $hideCategorization !== null ) {
+                       // Conditional on feature being available
+                       $hideCategorization->setDefault( $user->getBoolOption( 'hidecategorization' ) );
+               }
+       }
+
+       /**
+        * Process $par and put options found in $opts. Used when including the page.
+        *
+        * @param string $par
+        * @param FormOptions $opts
+        */
+       public function parseParameters( $par, FormOptions $opts ) {
+               parent::parseParameters( $par, $opts );
+
+               $bits = preg_split( '/\s*,\s*/', trim( $par ) );
+               foreach ( $bits as $bit ) {
+                       if ( is_numeric( $bit ) ) {
+                               $opts['limit'] = $bit;
+                       }
+
+                       $m = [];
+                       if ( preg_match( '/^limit=(\d+)$/', $bit, $m ) ) {
+                               $opts['limit'] = $m[1];
+                       }
+                       if ( preg_match( '/^days=(\d+(?:\.\d+)?)$/', $bit, $m ) ) {
+                               $opts['days'] = $m[1];
+                       }
+                       if ( preg_match( '/^namespace=(.*)$/', $bit, $m ) ) {
+                               $opts['namespace'] = $m[1];
+                       }
+                       if ( preg_match( '/^tagfilter=(.*)$/', $bit, $m ) ) {
+                               $opts['tagfilter'] = $m[1];
+                       }
+               }
+       }
+
+       /**
+        * @inheritDoc
+        */
+       protected function doMainQuery( $tables, $fields, $conds, $query_options,
+               $join_conds, FormOptions $opts
+       ) {
+               $dbr = $this->getDB();
+               $user = $this->getUser();
+
+               $rcQuery = RecentChange::getQueryInfo();
+               $tables = array_merge( $tables, $rcQuery['tables'] );
+               $fields = array_merge( $rcQuery['fields'], $fields );
+               $join_conds = array_merge( $join_conds, $rcQuery['joins'] );
+
+               // JOIN on watchlist for users
+               if ( $user->isLoggedIn() && $user->isAllowed( 'viewmywatchlist' ) ) {
+                       $tables[] = 'watchlist';
+                       $fields[] = 'wl_user';
+                       $fields[] = 'wl_notificationtimestamp';
+                       $join_conds['watchlist'] = [ 'LEFT JOIN', [
+                               'wl_user' => $user->getId(),
+                               'wl_title=rc_title',
+                               'wl_namespace=rc_namespace'
+                       ] ];
+               }
+
+               // JOIN on page, used for 'last revision' filter highlight
+               $tables[] = 'page';
+               $fields[] = 'page_latest';
+               $join_conds['page'] = [ 'LEFT JOIN', 'rc_cur_id=page_id' ];
+
+               $tagFilter = $opts['tagfilter'] ? explode( '|', $opts['tagfilter'] ) : [];
+               ChangeTags::modifyDisplayQuery(
+                       $tables,
+                       $fields,
+                       $conds,
+                       $join_conds,
+                       $query_options,
+                       $tagFilter
+               );
+
+               if ( !$this->runMainQueryHook( $tables, $fields, $conds, $query_options, $join_conds,
+                       $opts )
+               ) {
+                       return false;
+               }
+
+               if ( $this->areFiltersInConflict() ) {
+                       return false;
+               }
+
+               $orderByAndLimit = [
+                       'ORDER BY' => 'rc_timestamp DESC',
+                       'LIMIT' => $opts['limit']
+               ];
+               if ( in_array( 'DISTINCT', $query_options ) ) {
+                       // ChangeTags::modifyDisplayQuery() adds DISTINCT when filtering on multiple tags.
+                       // In order to prevent DISTINCT from causing query performance problems,
+                       // we have to GROUP BY the primary key. This in turn requires us to add
+                       // the primary key to the end of the ORDER BY, and the old ORDER BY to the
+                       // start of the GROUP BY
+                       $orderByAndLimit['ORDER BY'] = 'rc_timestamp DESC, rc_id DESC';
+                       $orderByAndLimit['GROUP BY'] = 'rc_timestamp, rc_id';
+               }
+               // array_merge() is used intentionally here so that hooks can, should
+               // they so desire, override the ORDER BY / LIMIT condition(s); prior to
+               // MediaWiki 1.26 this used to use the plus operator instead, which meant
+               // that extensions weren't able to change these conditions
+               $query_options = array_merge( $orderByAndLimit, $query_options );
+               $rows = $dbr->select(
+                       $tables,
+                       $fields,
+                       // rc_new is not an ENUM, but adding a redundant rc_new IN (0,1) gives mysql enough
+                       // knowledge to use an index merge if it wants (it may use some other index though).
+                       $conds + [ 'rc_new' => [ 0, 1 ] ],
+                       __METHOD__,
+                       $query_options,
+                       $join_conds
+               );
+
+               return $rows;
+       }
+
+       protected function getDB() {
+               return wfGetDB( DB_REPLICA, 'recentchanges' );
+       }
+
+       public function outputFeedLinks() {
+               $this->addFeedLinks( $this->getFeedQuery() );
+       }
+
+       /**
+        * Get URL query parameters for action=feedrecentchanges API feed of current recent changes view.
+        *
+        * @return array
+        */
+       protected function getFeedQuery() {
+               $query = array_filter( $this->getOptions()->getAllValues(), function ( $value ) {
+                       // API handles empty parameters in a different way
+                       return $value !== '';
+               } );
+               $query['action'] = 'feedrecentchanges';
+               $feedLimit = $this->getConfig()->get( 'FeedLimit' );
+               if ( $query['limit'] > $feedLimit ) {
+                       $query['limit'] = $feedLimit;
+               }
+
+               return $query;
+       }
+
+       /**
+        * Build and output the actual changes list.
+        *
+        * @param IResultWrapper $rows Database rows
+        * @param FormOptions $opts
+        */
+       public function outputChangesList( $rows, $opts ) {
+               $limit = $opts['limit'];
+
+               $showWatcherCount = $this->getConfig()->get( 'RCShowWatchingUsers' )
+                       && $this->getUser()->getOption( 'shownumberswatching' );
+               $watcherCache = [];
+
+               $counter = 1;
+               $list = ChangesList::newFromContext( $this->getContext(), $this->filterGroups );
+               $list->initChangesListRows( $rows );
+
+               $userShowHiddenCats = $this->getUser()->getBoolOption( 'showhiddencats' );
+               $rclistOutput = $list->beginRecentChangesList();
+               if ( $this->isStructuredFilterUiEnabled() ) {
+                       $rclistOutput .= $this->makeLegend();
+               }
+
+               foreach ( $rows as $obj ) {
+                       if ( $limit == 0 ) {
+                               break;
+                       }
+                       $rc = RecentChange::newFromRow( $obj );
+
+                       # Skip CatWatch entries for hidden cats based on user preference
+                       if (
+                               $rc->getAttribute( 'rc_type' ) == RC_CATEGORIZE &&
+                               !$userShowHiddenCats &&
+                               $rc->getParam( 'hidden-cat' )
+                       ) {
+                               continue;
+                       }
+
+                       $rc->counter = $counter++;
+                       # Check if the page has been updated since the last visit
+                       if ( $this->getConfig()->get( 'ShowUpdatedMarker' )
+                               && !empty( $obj->wl_notificationtimestamp )
+                       ) {
+                               $rc->notificationtimestamp = ( $obj->rc_timestamp >= $obj->wl_notificationtimestamp );
+                       } else {
+                               $rc->notificationtimestamp = false; // Default
+                       }
+                       # Check the number of users watching the page
+                       $rc->numberofWatchingusers = 0; // Default
+                       if ( $showWatcherCount && $obj->rc_namespace >= 0 ) {
+                               if ( !isset( $watcherCache[$obj->rc_namespace][$obj->rc_title] ) ) {
+                                       $watcherCache[$obj->rc_namespace][$obj->rc_title] =
+                                               MediaWikiServices::getInstance()->getWatchedItemStore()->countWatchers(
+                                                       new TitleValue( (int)$obj->rc_namespace, $obj->rc_title )
+                                               );
+                               }
+                               $rc->numberofWatchingusers = $watcherCache[$obj->rc_namespace][$obj->rc_title];
+                       }
+
+                       $changeLine = $list->recentChangesLine( $rc, !empty( $obj->wl_user ), $counter );
+                       if ( $changeLine !== false ) {
+                               $rclistOutput .= $changeLine;
+                               --$limit;
+                       }
+               }
+               $rclistOutput .= $list->endRecentChangesList();
+
+               if ( $rows->numRows() === 0 ) {
+                       $this->outputNoResults();
+                       if ( !$this->including() ) {
+                               $this->getOutput()->setStatusCode( 404 );
+                       }
+               } else {
+                       $this->getOutput()->addHTML( $rclistOutput );
+               }
+       }
+
+       /**
+        * Set the text to be displayed above the changes
+        *
+        * @param FormOptions $opts
+        * @param int $numRows Number of rows in the result to show after this header
+        */
+       public function doHeader( $opts, $numRows ) {
+               $this->setTopText( $opts );
+
+               $defaults = $opts->getAllValues();
+               $nondefaults = $opts->getChangedValues();
+
+               $panel = [];
+               if ( !$this->isStructuredFilterUiEnabled() ) {
+                       $panel[] = $this->makeLegend();
+               }
+               $panel[] = $this->optionsPanel( $defaults, $nondefaults, $numRows );
+               $panel[] = '<hr />';
+
+               $extraOpts = $this->getExtraOptions( $opts );
+               $extraOptsCount = count( $extraOpts );
+               $count = 0;
+               $submit = ' ' . Xml::submitButton( $this->msg( 'recentchanges-submit' )->text() );
+
+               $out = Xml::openElement( 'table', [ 'class' => 'mw-recentchanges-table' ] );
+               foreach ( $extraOpts as $name => $optionRow ) {
+                       # Add submit button to the last row only
+                       ++$count;
+                       $addSubmit = ( $count === $extraOptsCount ) ? $submit : '';
+
+                       $out .= Xml::openElement( 'tr', [ 'class' => $name . 'Form' ] );
+                       if ( is_array( $optionRow ) ) {
+                               $out .= Xml::tags(
+                                       'td',
+                                       [ 'class' => 'mw-label mw-' . $name . '-label' ],
+                                       $optionRow[0]
+                               );
+                               $out .= Xml::tags(
+                                       'td',
+                                       [ 'class' => 'mw-input' ],
+                                       $optionRow[1] . $addSubmit
+                               );
+                       } else {
+                               $out .= Xml::tags(
+                                       'td',
+                                       [ 'class' => 'mw-input', 'colspan' => 2 ],
+                                       $optionRow . $addSubmit
+                               );
+                       }
+                       $out .= Xml::closeElement( 'tr' );
+               }
+               $out .= Xml::closeElement( 'table' );
+
+               $unconsumed = $opts->getUnconsumedValues();
+               foreach ( $unconsumed as $key => $value ) {
+                       $out .= Html::hidden( $key, $value );
+               }
+
+               $t = $this->getPageTitle();
+               $out .= Html::hidden( 'title', $t->getPrefixedText() );
+               $form = Xml::tags( 'form', [ 'action' => wfScript() ], $out );
+               $panel[] = $form;
+               $panelString = implode( "\n", $panel );
+
+               $rcoptions = Xml::fieldset(
+                       $this->msg( 'recentchanges-legend' )->text(),
+                       $panelString,
+                       [ 'class' => 'rcoptions cloptions' ]
+               );
+
+               // Insert a placeholder for RCFilters
+               if ( $this->isStructuredFilterUiEnabled() ) {
+                       $rcfilterContainer = Html::element(
+                               'div',
+                               // TODO: Remove deprecated rcfilters-container class
+                               [ 'class' => 'rcfilters-container mw-rcfilters-container' ]
+                       );
+
+                       $loadingContainer = Html::rawElement(
+                               'div',
+                               [ 'class' => 'mw-rcfilters-spinner' ],
+                               Html::element(
+                                       'div',
+                                       [ 'class' => 'mw-rcfilters-spinner-bounce' ]
+                               )
+                       );
+
+                       // Wrap both with rcfilters-head
+                       $this->getOutput()->addHTML(
+                               Html::rawElement(
+                                       'div',
+                                       // TODO: Remove deprecated rcfilters-head class
+                                       [ 'class' => 'rcfilters-head mw-rcfilters-head' ],
+                                       $rcfilterContainer . $rcoptions
+                               )
+                       );
+
+                       // Add spinner
+                       $this->getOutput()->addHTML( $loadingContainer );
+               } else {
+                       $this->getOutput()->addHTML( $rcoptions );
+               }
+
+               $this->setBottomText( $opts );
+       }
+
+       /**
+        * Send the text to be displayed above the options
+        *
+        * @param FormOptions $opts Unused
+        */
+       function setTopText( FormOptions $opts ) {
+               $message = $this->msg( 'recentchangestext' )->inContentLanguage();
+               if ( !$message->isDisabled() ) {
+                       $contLang = MediaWikiServices::getInstance()->getContentLanguage();
+                       // Parse the message in this weird ugly way to preserve the ability to include interlanguage
+                       // links in it (T172461). In the future when T66969 is resolved, perhaps we can just use
+                       // $message->parse() instead. This code is copied from Message::parseText().
+                       $parserOutput = MessageCache::singleton()->parse(
+                               $message->plain(),
+                               $this->getPageTitle(),
+                               /*linestart*/true,
+                               // Message class sets the interface flag to false when parsing in a language different than
+                               // user language, and this is wiki content language
+                               /*interface*/false,
+                               $contLang
+                       );
+                       $content = $parserOutput->getText( [
+                               'enableSectionEditLinks' => false,
+                       ] );
+                       // Add only metadata here (including the language links), text is added below
+                       $this->getOutput()->addParserOutputMetadata( $parserOutput );
+
+                       $langAttributes = [
+                               'lang' => $contLang->getHtmlCode(),
+                               'dir' => $contLang->getDir(),
+                       ];
+
+                       $topLinksAttributes = [ 'class' => 'mw-recentchanges-toplinks' ];
+
+                       if ( $this->isStructuredFilterUiEnabled() ) {
+                               // Check whether the widget is already collapsed or expanded
+                               $collapsedState = $this->getRequest()->getCookie( 'rcfilters-toplinks-collapsed-state' );
+                               // Note that an empty/unset cookie means collapsed, so check for !== 'expanded'
+                               $topLinksAttributes[ 'class' ] .= $collapsedState !== 'expanded' ?
+                                       ' mw-recentchanges-toplinks-collapsed' : '';
+
+                               $this->getOutput()->enableOOUI();
+                               $contentTitle = new OOUI\ButtonWidget( [
+                                       'classes' => [ 'mw-recentchanges-toplinks-title' ],
+                                       'label' => new OOUI\HtmlSnippet( $this->msg( 'rcfilters-other-review-tools' )->parse() ),
+                                       'framed' => false,
+                                       'indicator' => $collapsedState !== 'expanded' ? 'down' : 'up',
+                                       'flags' => [ 'progressive' ],
+                               ] );
+
+                               $contentWrapper = Html::rawElement( 'div',
+                                       array_merge(
+                                               [ 'class' => 'mw-recentchanges-toplinks-content mw-collapsible-content' ],
+                                               $langAttributes
+                                       ),
+                                       $content
+                               );
+                               $content = $contentTitle . $contentWrapper;
+                       } else {
+                               // Language direction should be on the top div only
+                               // if the title is not there. If it is there, it's
+                               // interface direction, and the language/dir attributes
+                               // should be on the content itself
+                               $topLinksAttributes = array_merge( $topLinksAttributes, $langAttributes );
+                       }
+
+                       $this->getOutput()->addHTML(
+                               Html::rawElement( 'div', $topLinksAttributes, $content )
+                       );
+               }
+       }
+
+       /**
+        * Get options to be displayed in a form
+        *
+        * @param FormOptions $opts
+        * @return array
+        */
+       function getExtraOptions( $opts ) {
+               $opts->consumeValues( [
+                       'namespace', 'invert', 'associated', 'tagfilter'
+               ] );
+
+               $extraOpts = [];
+               $extraOpts['namespace'] = $this->namespaceFilterForm( $opts );
+
+               $tagFilter = ChangeTags::buildTagFilterSelector(
+                       $opts['tagfilter'], false, $this->getContext() );
+               if ( count( $tagFilter ) ) {
+                       $extraOpts['tagfilter'] = $tagFilter;
+               }
+
+               // Don't fire the hook for subclasses. (Or should we?)
+               if ( $this->getName() === 'Recentchanges' ) {
+                       Hooks::run( 'SpecialRecentChangesPanel', [ &$extraOpts, $opts ] );
+               }
+
+               return $extraOpts;
+       }
+
+       /**
+        * Add page-specific modules.
+        */
+       protected function addModules() {
+               parent::addModules();
+               $out = $this->getOutput();
+               $out->addModules( 'mediawiki.special.recentchanges' );
+       }
+
+       /**
+        * Get last modified date, for client caching
+        * Don't use this if we are using the patrol feature, patrol changes don't
+        * update the timestamp
+        *
+        * @return string|bool
+        */
+       public function checkLastModified() {
+               $dbr = $this->getDB();
+               $lastmod = $dbr->selectField( 'recentchanges', 'MAX(rc_timestamp)', '', __METHOD__ );
+
+               return $lastmod;
+       }
+
+       /**
+        * Creates the choose namespace selection
+        *
+        * @param FormOptions $opts
+        * @return string[]
+        */
+       protected function namespaceFilterForm( FormOptions $opts ) {
+               $nsSelect = Html::namespaceSelector(
+                       [ 'selected' => $opts['namespace'], 'all' => '', 'in-user-lang' => true ],
+                       [ 'name' => 'namespace', 'id' => 'namespace' ]
+               );
+               $nsLabel = Xml::label( $this->msg( 'namespace' )->text(), 'namespace' );
+               $attribs = [ 'class' => [ 'mw-input-with-label' ] ];
+               // Hide the checkboxes when the namespace filter is set to 'all'.
+               if ( $opts['namespace'] === '' ) {
+                       $attribs['class'][] = 'mw-input-hidden';
+               }
+               $invert = Html::rawElement( 'span', $attribs, Xml::checkLabel(
+                       $this->msg( 'invert' )->text(), 'invert', 'nsinvert',
+                       $opts['invert'],
+                       [ 'title' => $this->msg( 'tooltip-invert' )->text() ]
+               ) );
+               $associated = Html::rawElement( 'span', $attribs, Xml::checkLabel(
+                       $this->msg( 'namespace_association' )->text(), 'associated', 'nsassociated',
+                       $opts['associated'],
+                       [ 'title' => $this->msg( 'tooltip-namespace_association' )->text() ]
+               ) );
+
+               return [ $nsLabel, "$nsSelect $invert $associated" ];
+       }
+
+       /**
+        * Filter $rows by categories set in $opts
+        *
+        * @deprecated since 1.31
+        *
+        * @param IResultWrapper &$rows Database rows
+        * @param FormOptions $opts
+        */
+       function filterByCategories( &$rows, FormOptions $opts ) {
+               wfDeprecated( __METHOD__, '1.31' );
+
+               $categories = array_map( 'trim', explode( '|', $opts['categories'] ) );
+
+               if ( $categories === [] ) {
+                       return;
+               }
+
+               # Filter categories
+               $cats = [];
+               foreach ( $categories as $cat ) {
+                       $cat = trim( $cat );
+                       if ( $cat == '' ) {
+                               continue;
+                       }
+                       $cats[] = $cat;
+               }
+
+               # Filter articles
+               $articles = [];
+               $a2r = [];
+               $rowsarr = [];
+               foreach ( $rows as $k => $r ) {
+                       $nt = Title::makeTitle( $r->rc_namespace, $r->rc_title );
+                       $id = $nt->getArticleID();
+                       if ( $id == 0 ) {
+                               continue; # Page might have been deleted...
+                       }
+                       if ( !in_array( $id, $articles ) ) {
+                               $articles[] = $id;
+                       }
+                       if ( !isset( $a2r[$id] ) ) {
+                               $a2r[$id] = [];
+                       }
+                       $a2r[$id][] = $k;
+                       $rowsarr[$k] = $r;
+               }
+
+               # Shortcut?
+               if ( $articles === [] || $cats === [] ) {
+                       return;
+               }
+
+               # Look up
+               $catFind = new CategoryFinder;
+               $catFind->seed( $articles, $cats, $opts['categories_any'] ? 'OR' : 'AND' );
+               $match = $catFind->run();
+
+               # Filter
+               $newrows = [];
+               foreach ( $match as $id ) {
+                       foreach ( $a2r[$id] as $rev ) {
+                               $k = $rev;
+                               $newrows[$k] = $rowsarr[$k];
+                       }
+               }
+               $rows = new FakeResultWrapper( array_values( $newrows ) );
+       }
+
+       /**
+        * Makes change an option link which carries all the other options
+        *
+        * @param string $title
+        * @param array $override Options to override
+        * @param array $options Current options
+        * @param bool $active Whether to show the link in bold
+        * @return string
+        */
+       function makeOptionsLink( $title, $override, $options, $active = false ) {
+               $params = $this->convertParamsForLink( $override + $options );
+
+               if ( $active ) {
+                       $title = new HtmlArmor( '<strong>' . htmlspecialchars( $title ) . '</strong>' );
+               }
+
+               return $this->getLinkRenderer()->makeKnownLink( $this->getPageTitle(), $title, [
+                       'data-params' => json_encode( $override ),
+                       'data-keys' => implode( ',', array_keys( $override ) ),
+               ], $params );
+       }
+
+       /**
+        * Creates the options panel.
+        *
+        * @param array $defaults
+        * @param array $nondefaults
+        * @param int $numRows Number of rows in the result to show after this header
+        * @return string
+        */
+       function optionsPanel( $defaults, $nondefaults, $numRows ) {
+               $options = $nondefaults + $defaults;
+
+               $note = '';
+               $msg = $this->msg( 'rclegend' );
+               if ( !$msg->isDisabled() ) {
+                       $note .= Html::rawElement(
+                               'div',
+                               [ 'class' => 'mw-rclegend' ],
+                               $msg->parse()
+                       );
+               }
+
+               $lang = $this->getLanguage();
+               $user = $this->getUser();
+               $config = $this->getConfig();
+               if ( $options['from'] ) {
+                       $resetLink = $this->makeOptionsLink( $this->msg( 'rclistfromreset' ),
+                               [ 'from' => '' ], $nondefaults );
+
+                       $noteFromMsg = $this->msg( 'rcnotefrom' )
+                               ->numParams( $options['limit'] )
+                               ->params(
+                                       $lang->userTimeAndDate( $options['from'], $user ),
+                                       $lang->userDate( $options['from'], $user ),
+                                       $lang->userTime( $options['from'], $user )
+                               )
+                               ->numParams( $numRows );
+                       $note .= Html::rawElement(
+                                       'span',
+                                       [ 'class' => 'rcnotefrom' ],
+                                       $noteFromMsg->parse()
+                               ) .
+                               ' ' .
+                               Html::rawElement(
+                                       'span',
+                                       [ 'class' => 'rcoptions-listfromreset' ],
+                                       $this->msg( 'parentheses' )->rawParams( $resetLink )->parse()
+                               ) .
+                               '<br />';
+               }
+
+               # Sort data for display and make sure it's unique after we've added user data.
+               $linkLimits = $config->get( 'RCLinkLimits' );
+               $linkLimits[] = $options['limit'];
+               sort( $linkLimits );
+               $linkLimits = array_unique( $linkLimits );
+
+               $linkDays = $config->get( 'RCLinkDays' );
+               $linkDays[] = $options['days'];
+               sort( $linkDays );
+               $linkDays = array_unique( $linkDays );
+
+               // limit links
+               $cl = [];
+               foreach ( $linkLimits as $value ) {
+                       $cl[] = $this->makeOptionsLink( $lang->formatNum( $value ),
+                               [ 'limit' => $value ], $nondefaults, $value == $options['limit'] );
+               }
+               $cl = $lang->pipeList( $cl );
+
+               // day links, reset 'from' to none
+               $dl = [];
+               foreach ( $linkDays as $value ) {
+                       $dl[] = $this->makeOptionsLink( $lang->formatNum( $value ),
+                               [ 'days' => $value, 'from' => '' ], $nondefaults, $value == $options['days'] );
+               }
+               $dl = $lang->pipeList( $dl );
+
+               $showhide = [ 'show', 'hide' ];
+
+               $links = [];
+
+               foreach ( $this->getLegacyShowHideFilters() as $key => $filter ) {
+                       $msg = $filter->getShowHide();
+                       $linkMessage = $this->msg( $msg . '-' . $showhide[1 - $options[$key]] );
+                       // Extensions can define additional filters, but don't need to define the corresponding
+                       // messages. If they don't exist, just fall back to 'show' and 'hide'.
+                       if ( !$linkMessage->exists() ) {
+                               $linkMessage = $this->msg( $showhide[1 - $options[$key]] );
+                       }
+
+                       $link = $this->makeOptionsLink( $linkMessage->text(),
+                               [ $key => 1 - $options[$key] ], $nondefaults );
+
+                       $attribs = [
+                               'class' => "$msg rcshowhideoption clshowhideoption",
+                               'data-filter-name' => $filter->getName(),
+                       ];
+
+                       if ( $filter->isFeatureAvailableOnStructuredUi( $this ) ) {
+                               $attribs['data-feature-in-structured-ui'] = true;
+                       }
+
+                       $links[] = Html::rawElement(
+                               'span',
+                               $attribs,
+                               $this->msg( $msg )->rawParams( $link )->parse()
+                       );
+               }
+
+               // show from this onward link
+               $timestamp = wfTimestampNow();
+               $now = $lang->userTimeAndDate( $timestamp, $user );
+               $timenow = $lang->userTime( $timestamp, $user );
+               $datenow = $lang->userDate( $timestamp, $user );
+               $pipedLinks = '<span class="rcshowhide">' . $lang->pipeList( $links ) . '</span>';
+
+               $rclinks = Html::rawElement(
+                       'span',
+                       [ 'class' => 'rclinks' ],
+                       $this->msg( 'rclinks' )->rawParams( $cl, $dl, '' )->parse()
+               );
+
+               $rclistfrom = Html::rawElement(
+                       'span',
+                       [ 'class' => 'rclistfrom' ],
+                       $this->makeOptionsLink(
+                               $this->msg( 'rclistfrom' )->plaintextParams( $now, $timenow, $datenow )->parse(),
+                               [ 'from' => $timestamp ],
+                               $nondefaults
+                       )
+               );
+
+               return "{$note}$rclinks<br />$pipedLinks<br />$rclistfrom";
+       }
+
+       public function isIncludable() {
+               return true;
+       }
+
+       protected function getCacheTTL() {
+               return 60 * 5;
+       }
+
+       public function getDefaultLimit() {
+               $systemPrefValue = $this->getUser()->getIntOption( 'rclimit' );
+               // Prefer the RCFilters-specific preference if RCFilters is enabled
+               if ( $this->isStructuredFilterUiEnabled() ) {
+                       return $this->getUser()->getIntOption( static::$limitPreferenceName, $systemPrefValue );
+               }
+
+               // Otherwise, use the system rclimit preference value
+               return $systemPrefValue;
+       }
+}
diff --git a/includes/specials/SpecialRecentChangesLinked.php b/includes/specials/SpecialRecentChangesLinked.php
new file mode 100644 (file)
index 0000000..8865654
--- /dev/null
@@ -0,0 +1,319 @@
+<?php
+/**
+ * Implements Special:Recentchangeslinked
+ *
+ * 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 SpecialPage
+ */
+
+/**
+ * This is to display changes made to all articles linked in an article.
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialRecentChangesLinked extends SpecialRecentChanges {
+       /** @var bool|Title */
+       protected $rclTargetTitle;
+
+       function __construct() {
+               parent::__construct( 'Recentchangeslinked' );
+       }
+
+       public function getDefaultOptions() {
+               $opts = parent::getDefaultOptions();
+               $opts->add( 'target', '' );
+               $opts->add( 'showlinkedto', false );
+
+               return $opts;
+       }
+
+       public function parseParameters( $par, FormOptions $opts ) {
+               $opts['target'] = $par;
+       }
+
+       /**
+        * @inheritDoc
+        */
+       protected function doMainQuery( $tables, $select, $conds, $query_options,
+               $join_conds, FormOptions $opts
+       ) {
+               $target = $opts['target'];
+               $showlinkedto = $opts['showlinkedto'];
+               $limit = $opts['limit'];
+
+               if ( $target === '' ) {
+                       return false;
+               }
+               $outputPage = $this->getOutput();
+               $title = Title::newFromText( $target );
+               if ( !$title || $title->isExternal() ) {
+                       $outputPage->addHTML(
+                               Html::errorBox( $this->msg( 'allpagesbadtitle' )->parse() )
+                       );
+                       return false;
+               }
+
+               $outputPage->setPageTitle( $this->msg( 'recentchangeslinked-title', $title->getPrefixedText() ) );
+
+               /*
+                * Ordinary links are in the pagelinks table, while transclusions are
+                * in the templatelinks table, categorizations in categorylinks and
+                * image use in imagelinks.  We need to somehow combine all these.
+                * Special:Whatlinkshere does this by firing multiple queries and
+                * merging the results, but the code we inherit from our parent class
+                * expects only one result set so we use UNION instead.
+                */
+
+               $dbr = wfGetDB( DB_REPLICA, 'recentchangeslinked' );
+               $id = $title->getArticleID();
+               $ns = $title->getNamespace();
+               $dbkey = $title->getDBkey();
+
+               $rcQuery = RecentChange::getQueryInfo();
+               $tables = array_merge( $tables, $rcQuery['tables'] );
+               $select = array_merge( $rcQuery['fields'], $select );
+               $join_conds = array_merge( $join_conds, $rcQuery['joins'] );
+
+               // left join with watchlist table to highlight watched rows
+               $uid = $this->getUser()->getId();
+               if ( $uid && $this->getUser()->isAllowed( 'viewmywatchlist' ) ) {
+                       $tables[] = 'watchlist';
+                       $select[] = 'wl_user';
+                       $join_conds['watchlist'] = [ 'LEFT JOIN', [
+                               'wl_user' => $uid,
+                               'wl_title=rc_title',
+                               'wl_namespace=rc_namespace'
+                       ] ];
+               }
+
+               // JOIN on page, used for 'last revision' filter highlight
+               $tables[] = 'page';
+               $join_conds['page'] = [ 'LEFT JOIN', 'rc_cur_id=page_id' ];
+               $select[] = 'page_latest';
+
+               $tagFilter = $opts['tagfilter'] ? explode( '|', $opts['tagfilter'] ) : [];
+               ChangeTags::modifyDisplayQuery(
+                       $tables,
+                       $select,
+                       $conds,
+                       $join_conds,
+                       $query_options,
+                       $tagFilter
+               );
+
+               if ( $dbr->unionSupportsOrderAndLimit() ) {
+                       if ( count( $tagFilter ) > 1 ) {
+                               // ChangeTags::modifyDisplayQuery() will have added DISTINCT.
+                               // To prevent this from causing query performance problems, we need to add
+                               // a GROUP BY, and add rc_id to the ORDER BY.
+                               $order = [
+                                       'GROUP BY' => 'rc_timestamp, rc_id',
+                                       'ORDER BY' => 'rc_timestamp DESC, rc_id DESC'
+                               ];
+                       } else {
+                               $order = [ 'ORDER BY' => 'rc_timestamp DESC' ];
+                       }
+               } else {
+                       $order = [];
+               }
+
+               if ( !$this->runMainQueryHook( $tables, $select, $conds, $query_options, $join_conds,
+                       $opts )
+               ) {
+                       return false;
+               }
+
+               if ( $ns == NS_CATEGORY && !$showlinkedto ) {
+                       // special handling for categories
+                       // XXX: should try to make this less kludgy
+                       $link_tables = [ 'categorylinks' ];
+                       $showlinkedto = true;
+               } else {
+                       // for now, always join on these tables; really should be configurable as in whatlinkshere
+                       $link_tables = [ 'pagelinks', 'templatelinks' ];
+                       // imagelinks only contains links to pages in NS_FILE
+                       if ( $ns == NS_FILE || !$showlinkedto ) {
+                               $link_tables[] = 'imagelinks';
+                       }
+               }
+
+               if ( $id == 0 && !$showlinkedto ) {
+                       return false; // nonexistent pages can't link to any pages
+               }
+
+               // field name prefixes for all the various tables we might want to join with
+               $prefix = [
+                       'pagelinks' => 'pl',
+                       'templatelinks' => 'tl',
+                       'categorylinks' => 'cl',
+                       'imagelinks' => 'il'
+               ];
+
+               $subsql = []; // SELECT statements to combine with UNION
+
+               foreach ( $link_tables as $link_table ) {
+                       $pfx = $prefix[$link_table];
+
+                       // imagelinks and categorylinks tables have no xx_namespace field,
+                       // and have xx_to instead of xx_title
+                       if ( $link_table == 'imagelinks' ) {
+                               $link_ns = NS_FILE;
+                       } elseif ( $link_table == 'categorylinks' ) {
+                               $link_ns = NS_CATEGORY;
+                       } else {
+                               $link_ns = 0;
+                       }
+
+                       if ( $showlinkedto ) {
+                               // find changes to pages linking to this page
+                               if ( $link_ns ) {
+                                       if ( $ns != $link_ns ) {
+                                               continue;
+                                       } // should never happen, but check anyway
+                                       $subconds = [ "{$pfx}_to" => $dbkey ];
+                               } else {
+                                       $subconds = [ "{$pfx}_namespace" => $ns, "{$pfx}_title" => $dbkey ];
+                               }
+                               $subjoin = "rc_cur_id = {$pfx}_from";
+                       } else {
+                               // find changes to pages linked from this page
+                               $subconds = [ "{$pfx}_from" => $id ];
+                               if ( $link_table == 'imagelinks' || $link_table == 'categorylinks' ) {
+                                       $subconds["rc_namespace"] = $link_ns;
+                                       $subjoin = "rc_title = {$pfx}_to";
+                               } else {
+                                       $subjoin = [ "rc_namespace = {$pfx}_namespace", "rc_title = {$pfx}_title" ];
+                               }
+                       }
+
+                       $query = $dbr->selectSQLText(
+                               array_merge( $tables, [ $link_table ] ),
+                               $select,
+                               $conds + $subconds,
+                               __METHOD__,
+                               $order + $query_options,
+                               $join_conds + [ $link_table => [ 'JOIN', $subjoin ] ]
+                       );
+
+                       if ( $dbr->unionSupportsOrderAndLimit() ) {
+                               $query = $dbr->limitResult( $query, $limit );
+                       }
+
+                       $subsql[] = $query;
+               }
+
+               if ( count( $subsql ) == 0 ) {
+                       return false; // should never happen
+               }
+               if ( count( $subsql ) == 1 && $dbr->unionSupportsOrderAndLimit() ) {
+                       $sql = $subsql[0];
+               } else {
+                       // need to resort and relimit after union
+                       $sql = $dbr->unionQueries( $subsql, $dbr::UNION_DISTINCT ) .
+                               ' ORDER BY rc_timestamp DESC';
+                       $sql = $dbr->limitResult( $sql, $limit, false );
+               }
+
+               $res = $dbr->query( $sql, __METHOD__ );
+
+               if ( $res->numRows() == 0 ) {
+                       $this->mResultEmpty = true;
+               }
+
+               return $res;
+       }
+
+       function setTopText( FormOptions $opts ) {
+               $target = $this->getTargetTitle();
+               if ( $target ) {
+                       $this->getOutput()->addBacklinkSubtitle( $target );
+                       $this->getSkin()->setRelevantTitle( $target );
+               }
+       }
+
+       /**
+        * Get options to be displayed in a form
+        *
+        * @param FormOptions $opts
+        * @return array
+        */
+       function getExtraOptions( $opts ) {
+               $extraOpts = parent::getExtraOptions( $opts );
+
+               $opts->consumeValues( [ 'showlinkedto', 'target' ] );
+
+               $extraOpts['target'] = [ $this->msg( 'recentchangeslinked-page' )->escaped(),
+                       Xml::input( 'target', 40, str_replace( '_', ' ', $opts['target'] ) ) .
+                       Xml::check( 'showlinkedto', $opts['showlinkedto'], [ 'id' => 'showlinkedto' ] ) . ' ' .
+                       Xml::label( $this->msg( 'recentchangeslinked-to' )->text(), 'showlinkedto' ) ];
+
+               $this->addHelpLink( 'Help:Related changes' );
+               return $extraOpts;
+       }
+
+       /**
+        * @return Title
+        */
+       function getTargetTitle() {
+               if ( $this->rclTargetTitle === null ) {
+                       $opts = $this->getOptions();
+                       if ( isset( $opts['target'] ) && $opts['target'] !== '' ) {
+                               $this->rclTargetTitle = Title::newFromText( $opts['target'] );
+                       } else {
+                               $this->rclTargetTitle = false;
+                       }
+               }
+
+               return $this->rclTargetTitle;
+       }
+
+       /**
+        * Return an array of subpages beginning with $search that this special page will accept.
+        *
+        * @param string $search Prefix to search for
+        * @param int $limit Maximum number of results to return (usually 10)
+        * @param int $offset Number of results to skip (usually 0)
+        * @return string[] Matching subpages
+        */
+       public function prefixSearchSubpages( $search, $limit, $offset ) {
+               return $this->prefixSearchString( $search, $limit, $offset );
+       }
+
+       protected function outputNoResults() {
+               $targetTitle = $this->getTargetTitle();
+               if ( $targetTitle === false ) {
+                       $this->getOutput()->addHTML(
+                               Html::rawElement(
+                                       'div',
+                                       [ 'class' => 'mw-changeslist-empty mw-changeslist-notargetpage' ],
+                                       $this->msg( 'recentchanges-notargetpage' )->parse()
+                               )
+                       );
+               } elseif ( !$targetTitle || $targetTitle->isExternal() ) {
+                       $this->getOutput()->addHTML(
+                               Html::rawElement(
+                                       'div',
+                                       [ 'class' => 'mw-changeslist-empty mw-changeslist-invalidtargetpage' ],
+                                       $this->msg( 'allpagesbadtitle' )->parse()
+                               )
+                       );
+               } else {
+                       parent::outputNoResults();
+               }
+       }
+}
diff --git a/includes/specials/SpecialRecentchanges.php b/includes/specials/SpecialRecentchanges.php
deleted file mode 100644 (file)
index c8f65c1..0000000
+++ /dev/null
@@ -1,945 +0,0 @@
-<?php
-/**
- * Implements Special:Recentchanges
- *
- * 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 SpecialPage
- */
-
-use MediaWiki\MediaWikiServices;
-use Wikimedia\Rdbms\IResultWrapper;
-use Wikimedia\Rdbms\FakeResultWrapper;
-
-/**
- * A special page that lists last changes made to the wiki
- *
- * @ingroup SpecialPage
- */
-class SpecialRecentChanges extends ChangesListSpecialPage {
-
-       protected static $savedQueriesPreferenceName = 'rcfilters-saved-queries';
-       protected static $daysPreferenceName = 'rcdays'; // Use general RecentChanges preference
-       protected static $limitPreferenceName = 'rcfilters-limit'; // Use RCFilters-specific preference
-       protected static $collapsedPreferenceName = 'rcfilters-rc-collapsed';
-
-       private $watchlistFilterGroupDefinition;
-
-       public function __construct( $name = 'Recentchanges', $restriction = '' ) {
-               parent::__construct( $name, $restriction );
-
-               $this->watchlistFilterGroupDefinition = [
-                       'name' => 'watchlist',
-                       'title' => 'rcfilters-filtergroup-watchlist',
-                       'class' => ChangesListStringOptionsFilterGroup::class,
-                       'priority' => -9,
-                       'isFullCoverage' => true,
-                       'filters' => [
-                               [
-                                       'name' => 'watched',
-                                       'label' => 'rcfilters-filter-watchlist-watched-label',
-                                       'description' => 'rcfilters-filter-watchlist-watched-description',
-                                       'cssClassSuffix' => 'watched',
-                                       'isRowApplicableCallable' => function ( $ctx, $rc ) {
-                                               return $rc->getAttribute( 'wl_user' );
-                                       }
-                               ],
-                               [
-                                       'name' => 'watchednew',
-                                       'label' => 'rcfilters-filter-watchlist-watchednew-label',
-                                       'description' => 'rcfilters-filter-watchlist-watchednew-description',
-                                       'cssClassSuffix' => 'watchednew',
-                                       'isRowApplicableCallable' => function ( $ctx, $rc ) {
-                                               return $rc->getAttribute( 'wl_user' ) &&
-                                                       $rc->getAttribute( 'rc_timestamp' ) &&
-                                                       $rc->getAttribute( 'wl_notificationtimestamp' ) &&
-                                                       $rc->getAttribute( 'rc_timestamp' ) >= $rc->getAttribute( 'wl_notificationtimestamp' );
-                                       },
-                               ],
-                               [
-                                       'name' => 'notwatched',
-                                       'label' => 'rcfilters-filter-watchlist-notwatched-label',
-                                       'description' => 'rcfilters-filter-watchlist-notwatched-description',
-                                       'cssClassSuffix' => 'notwatched',
-                                       'isRowApplicableCallable' => function ( $ctx, $rc ) {
-                                               return $rc->getAttribute( 'wl_user' ) === null;
-                                       },
-                               ]
-                       ],
-                       'default' => ChangesListStringOptionsFilterGroup::NONE,
-                       'queryCallable' => function ( $specialPageClassName, $context, $dbr,
-                               &$tables, &$fields, &$conds, &$query_options, &$join_conds, $selectedValues ) {
-                               sort( $selectedValues );
-                               $notwatchedCond = 'wl_user IS NULL';
-                               $watchedCond = 'wl_user IS NOT NULL';
-                               $newCond = 'rc_timestamp >= wl_notificationtimestamp';
-
-                               if ( $selectedValues === [ 'notwatched' ] ) {
-                                       $conds[] = $notwatchedCond;
-                                       return;
-                               }
-
-                               if ( $selectedValues === [ 'watched' ] ) {
-                                       $conds[] = $watchedCond;
-                                       return;
-                               }
-
-                               if ( $selectedValues === [ 'watchednew' ] ) {
-                                       $conds[] = $dbr->makeList( [
-                                               $watchedCond,
-                                               $newCond
-                                       ], LIST_AND );
-                                       return;
-                               }
-
-                               if ( $selectedValues === [ 'notwatched', 'watched' ] ) {
-                                       // no filters
-                                       return;
-                               }
-
-                               if ( $selectedValues === [ 'notwatched', 'watchednew' ] ) {
-                                       $conds[] = $dbr->makeList( [
-                                               $notwatchedCond,
-                                               $dbr->makeList( [
-                                                       $watchedCond,
-                                                       $newCond
-                                               ], LIST_AND )
-                                       ], LIST_OR );
-                                       return;
-                               }
-
-                               if ( $selectedValues === [ 'watched', 'watchednew' ] ) {
-                                       $conds[] = $watchedCond;
-                                       return;
-                               }
-
-                               if ( $selectedValues === [ 'notwatched', 'watched', 'watchednew' ] ) {
-                                       // no filters
-                                       return;
-                               }
-                       }
-               ];
-       }
-
-       /**
-        * @param string|null $subpage
-        */
-       public function execute( $subpage ) {
-               // Backwards-compatibility: redirect to new feed URLs
-               $feedFormat = $this->getRequest()->getVal( 'feed' );
-               if ( !$this->including() && $feedFormat ) {
-                       $query = $this->getFeedQuery();
-                       $query['feedformat'] = $feedFormat === 'atom' ? 'atom' : 'rss';
-                       $this->getOutput()->redirect( wfAppendQuery( wfScript( 'api' ), $query ) );
-
-                       return;
-               }
-
-               // 10 seconds server-side caching max
-               $out = $this->getOutput();
-               $out->setCdnMaxage( 10 );
-               // Check if the client has a cached version
-               $lastmod = $this->checkLastModified();
-               if ( $lastmod === false ) {
-                       return;
-               }
-
-               $this->addHelpLink(
-                       '//meta.wikimedia.org/wiki/Special:MyLanguage/Help:Recent_changes',
-                       true
-               );
-               parent::execute( $subpage );
-       }
-
-       /**
-        * @inheritDoc
-        */
-       protected function transformFilterDefinition( array $filterDefinition ) {
-               if ( isset( $filterDefinition['showHideSuffix'] ) ) {
-                       $filterDefinition['showHide'] = 'rc' . $filterDefinition['showHideSuffix'];
-               }
-
-               return $filterDefinition;
-       }
-
-       /**
-        * @inheritDoc
-        */
-       protected function registerFilters() {
-               parent::registerFilters();
-
-               if (
-                       !$this->including() &&
-                       $this->getUser()->isLoggedIn() &&
-                       $this->getUser()->isAllowed( 'viewmywatchlist' )
-               ) {
-                       $this->registerFiltersFromDefinitions( [ $this->watchlistFilterGroupDefinition ] );
-                       $watchlistGroup = $this->getFilterGroup( 'watchlist' );
-                       $watchlistGroup->getFilter( 'watched' )->setAsSupersetOf(
-                               $watchlistGroup->getFilter( 'watchednew' )
-                       );
-               }
-
-               $user = $this->getUser();
-
-               $significance = $this->getFilterGroup( 'significance' );
-               $hideMinor = $significance->getFilter( 'hideminor' );
-               $hideMinor->setDefault( $user->getBoolOption( 'hideminor' ) );
-
-               $automated = $this->getFilterGroup( 'automated' );
-               $hideBots = $automated->getFilter( 'hidebots' );
-               $hideBots->setDefault( true );
-
-               $reviewStatus = $this->getFilterGroup( 'reviewStatus' );
-               if ( $reviewStatus !== null ) {
-                       // Conditional on feature being available and rights
-                       if ( $user->getBoolOption( 'hidepatrolled' ) ) {
-                               $reviewStatus->setDefault( 'unpatrolled' );
-                               $legacyReviewStatus = $this->getFilterGroup( 'legacyReviewStatus' );
-                               $legacyHidePatrolled = $legacyReviewStatus->getFilter( 'hidepatrolled' );
-                               $legacyHidePatrolled->setDefault( true );
-                       }
-               }
-
-               $changeType = $this->getFilterGroup( 'changeType' );
-               $hideCategorization = $changeType->getFilter( 'hidecategorization' );
-               if ( $hideCategorization !== null ) {
-                       // Conditional on feature being available
-                       $hideCategorization->setDefault( $user->getBoolOption( 'hidecategorization' ) );
-               }
-       }
-
-       /**
-        * Process $par and put options found in $opts. Used when including the page.
-        *
-        * @param string $par
-        * @param FormOptions $opts
-        */
-       public function parseParameters( $par, FormOptions $opts ) {
-               parent::parseParameters( $par, $opts );
-
-               $bits = preg_split( '/\s*,\s*/', trim( $par ) );
-               foreach ( $bits as $bit ) {
-                       if ( is_numeric( $bit ) ) {
-                               $opts['limit'] = $bit;
-                       }
-
-                       $m = [];
-                       if ( preg_match( '/^limit=(\d+)$/', $bit, $m ) ) {
-                               $opts['limit'] = $m[1];
-                       }
-                       if ( preg_match( '/^days=(\d+(?:\.\d+)?)$/', $bit, $m ) ) {
-                               $opts['days'] = $m[1];
-                       }
-                       if ( preg_match( '/^namespace=(.*)$/', $bit, $m ) ) {
-                               $opts['namespace'] = $m[1];
-                       }
-                       if ( preg_match( '/^tagfilter=(.*)$/', $bit, $m ) ) {
-                               $opts['tagfilter'] = $m[1];
-                       }
-               }
-       }
-
-       /**
-        * @inheritDoc
-        */
-       protected function doMainQuery( $tables, $fields, $conds, $query_options,
-               $join_conds, FormOptions $opts
-       ) {
-               $dbr = $this->getDB();
-               $user = $this->getUser();
-
-               $rcQuery = RecentChange::getQueryInfo();
-               $tables = array_merge( $tables, $rcQuery['tables'] );
-               $fields = array_merge( $rcQuery['fields'], $fields );
-               $join_conds = array_merge( $join_conds, $rcQuery['joins'] );
-
-               // JOIN on watchlist for users
-               if ( $user->isLoggedIn() && $user->isAllowed( 'viewmywatchlist' ) ) {
-                       $tables[] = 'watchlist';
-                       $fields[] = 'wl_user';
-                       $fields[] = 'wl_notificationtimestamp';
-                       $join_conds['watchlist'] = [ 'LEFT JOIN', [
-                               'wl_user' => $user->getId(),
-                               'wl_title=rc_title',
-                               'wl_namespace=rc_namespace'
-                       ] ];
-               }
-
-               // JOIN on page, used for 'last revision' filter highlight
-               $tables[] = 'page';
-               $fields[] = 'page_latest';
-               $join_conds['page'] = [ 'LEFT JOIN', 'rc_cur_id=page_id' ];
-
-               $tagFilter = $opts['tagfilter'] ? explode( '|', $opts['tagfilter'] ) : [];
-               ChangeTags::modifyDisplayQuery(
-                       $tables,
-                       $fields,
-                       $conds,
-                       $join_conds,
-                       $query_options,
-                       $tagFilter
-               );
-
-               if ( !$this->runMainQueryHook( $tables, $fields, $conds, $query_options, $join_conds,
-                       $opts )
-               ) {
-                       return false;
-               }
-
-               if ( $this->areFiltersInConflict() ) {
-                       return false;
-               }
-
-               $orderByAndLimit = [
-                       'ORDER BY' => 'rc_timestamp DESC',
-                       'LIMIT' => $opts['limit']
-               ];
-               if ( in_array( 'DISTINCT', $query_options ) ) {
-                       // ChangeTags::modifyDisplayQuery() adds DISTINCT when filtering on multiple tags.
-                       // In order to prevent DISTINCT from causing query performance problems,
-                       // we have to GROUP BY the primary key. This in turn requires us to add
-                       // the primary key to the end of the ORDER BY, and the old ORDER BY to the
-                       // start of the GROUP BY
-                       $orderByAndLimit['ORDER BY'] = 'rc_timestamp DESC, rc_id DESC';
-                       $orderByAndLimit['GROUP BY'] = 'rc_timestamp, rc_id';
-               }
-               // array_merge() is used intentionally here so that hooks can, should
-               // they so desire, override the ORDER BY / LIMIT condition(s); prior to
-               // MediaWiki 1.26 this used to use the plus operator instead, which meant
-               // that extensions weren't able to change these conditions
-               $query_options = array_merge( $orderByAndLimit, $query_options );
-               $rows = $dbr->select(
-                       $tables,
-                       $fields,
-                       // rc_new is not an ENUM, but adding a redundant rc_new IN (0,1) gives mysql enough
-                       // knowledge to use an index merge if it wants (it may use some other index though).
-                       $conds + [ 'rc_new' => [ 0, 1 ] ],
-                       __METHOD__,
-                       $query_options,
-                       $join_conds
-               );
-
-               return $rows;
-       }
-
-       protected function getDB() {
-               return wfGetDB( DB_REPLICA, 'recentchanges' );
-       }
-
-       public function outputFeedLinks() {
-               $this->addFeedLinks( $this->getFeedQuery() );
-       }
-
-       /**
-        * Get URL query parameters for action=feedrecentchanges API feed of current recent changes view.
-        *
-        * @return array
-        */
-       protected function getFeedQuery() {
-               $query = array_filter( $this->getOptions()->getAllValues(), function ( $value ) {
-                       // API handles empty parameters in a different way
-                       return $value !== '';
-               } );
-               $query['action'] = 'feedrecentchanges';
-               $feedLimit = $this->getConfig()->get( 'FeedLimit' );
-               if ( $query['limit'] > $feedLimit ) {
-                       $query['limit'] = $feedLimit;
-               }
-
-               return $query;
-       }
-
-       /**
-        * Build and output the actual changes list.
-        *
-        * @param IResultWrapper $rows Database rows
-        * @param FormOptions $opts
-        */
-       public function outputChangesList( $rows, $opts ) {
-               $limit = $opts['limit'];
-
-               $showWatcherCount = $this->getConfig()->get( 'RCShowWatchingUsers' )
-                       && $this->getUser()->getOption( 'shownumberswatching' );
-               $watcherCache = [];
-
-               $counter = 1;
-               $list = ChangesList::newFromContext( $this->getContext(), $this->filterGroups );
-               $list->initChangesListRows( $rows );
-
-               $userShowHiddenCats = $this->getUser()->getBoolOption( 'showhiddencats' );
-               $rclistOutput = $list->beginRecentChangesList();
-               if ( $this->isStructuredFilterUiEnabled() ) {
-                       $rclistOutput .= $this->makeLegend();
-               }
-
-               foreach ( $rows as $obj ) {
-                       if ( $limit == 0 ) {
-                               break;
-                       }
-                       $rc = RecentChange::newFromRow( $obj );
-
-                       # Skip CatWatch entries for hidden cats based on user preference
-                       if (
-                               $rc->getAttribute( 'rc_type' ) == RC_CATEGORIZE &&
-                               !$userShowHiddenCats &&
-                               $rc->getParam( 'hidden-cat' )
-                       ) {
-                               continue;
-                       }
-
-                       $rc->counter = $counter++;
-                       # Check if the page has been updated since the last visit
-                       if ( $this->getConfig()->get( 'ShowUpdatedMarker' )
-                               && !empty( $obj->wl_notificationtimestamp )
-                       ) {
-                               $rc->notificationtimestamp = ( $obj->rc_timestamp >= $obj->wl_notificationtimestamp );
-                       } else {
-                               $rc->notificationtimestamp = false; // Default
-                       }
-                       # Check the number of users watching the page
-                       $rc->numberofWatchingusers = 0; // Default
-                       if ( $showWatcherCount && $obj->rc_namespace >= 0 ) {
-                               if ( !isset( $watcherCache[$obj->rc_namespace][$obj->rc_title] ) ) {
-                                       $watcherCache[$obj->rc_namespace][$obj->rc_title] =
-                                               MediaWikiServices::getInstance()->getWatchedItemStore()->countWatchers(
-                                                       new TitleValue( (int)$obj->rc_namespace, $obj->rc_title )
-                                               );
-                               }
-                               $rc->numberofWatchingusers = $watcherCache[$obj->rc_namespace][$obj->rc_title];
-                       }
-
-                       $changeLine = $list->recentChangesLine( $rc, !empty( $obj->wl_user ), $counter );
-                       if ( $changeLine !== false ) {
-                               $rclistOutput .= $changeLine;
-                               --$limit;
-                       }
-               }
-               $rclistOutput .= $list->endRecentChangesList();
-
-               if ( $rows->numRows() === 0 ) {
-                       $this->outputNoResults();
-                       if ( !$this->including() ) {
-                               $this->getOutput()->setStatusCode( 404 );
-                       }
-               } else {
-                       $this->getOutput()->addHTML( $rclistOutput );
-               }
-       }
-
-       /**
-        * Set the text to be displayed above the changes
-        *
-        * @param FormOptions $opts
-        * @param int $numRows Number of rows in the result to show after this header
-        */
-       public function doHeader( $opts, $numRows ) {
-               $this->setTopText( $opts );
-
-               $defaults = $opts->getAllValues();
-               $nondefaults = $opts->getChangedValues();
-
-               $panel = [];
-               if ( !$this->isStructuredFilterUiEnabled() ) {
-                       $panel[] = $this->makeLegend();
-               }
-               $panel[] = $this->optionsPanel( $defaults, $nondefaults, $numRows );
-               $panel[] = '<hr />';
-
-               $extraOpts = $this->getExtraOptions( $opts );
-               $extraOptsCount = count( $extraOpts );
-               $count = 0;
-               $submit = ' ' . Xml::submitButton( $this->msg( 'recentchanges-submit' )->text() );
-
-               $out = Xml::openElement( 'table', [ 'class' => 'mw-recentchanges-table' ] );
-               foreach ( $extraOpts as $name => $optionRow ) {
-                       # Add submit button to the last row only
-                       ++$count;
-                       $addSubmit = ( $count === $extraOptsCount ) ? $submit : '';
-
-                       $out .= Xml::openElement( 'tr', [ 'class' => $name . 'Form' ] );
-                       if ( is_array( $optionRow ) ) {
-                               $out .= Xml::tags(
-                                       'td',
-                                       [ 'class' => 'mw-label mw-' . $name . '-label' ],
-                                       $optionRow[0]
-                               );
-                               $out .= Xml::tags(
-                                       'td',
-                                       [ 'class' => 'mw-input' ],
-                                       $optionRow[1] . $addSubmit
-                               );
-                       } else {
-                               $out .= Xml::tags(
-                                       'td',
-                                       [ 'class' => 'mw-input', 'colspan' => 2 ],
-                                       $optionRow . $addSubmit
-                               );
-                       }
-                       $out .= Xml::closeElement( 'tr' );
-               }
-               $out .= Xml::closeElement( 'table' );
-
-               $unconsumed = $opts->getUnconsumedValues();
-               foreach ( $unconsumed as $key => $value ) {
-                       $out .= Html::hidden( $key, $value );
-               }
-
-               $t = $this->getPageTitle();
-               $out .= Html::hidden( 'title', $t->getPrefixedText() );
-               $form = Xml::tags( 'form', [ 'action' => wfScript() ], $out );
-               $panel[] = $form;
-               $panelString = implode( "\n", $panel );
-
-               $rcoptions = Xml::fieldset(
-                       $this->msg( 'recentchanges-legend' )->text(),
-                       $panelString,
-                       [ 'class' => 'rcoptions cloptions' ]
-               );
-
-               // Insert a placeholder for RCFilters
-               if ( $this->isStructuredFilterUiEnabled() ) {
-                       $rcfilterContainer = Html::element(
-                               'div',
-                               [ 'class' => 'rcfilters-container' ]
-                       );
-
-                       $loadingContainer = Html::rawElement(
-                               'div',
-                               [ 'class' => 'rcfilters-spinner' ],
-                               Html::element(
-                                       'div',
-                                       [ 'class' => 'rcfilters-spinner-bounce' ]
-                               )
-                       );
-
-                       // Wrap both with rcfilters-head
-                       $this->getOutput()->addHTML(
-                               Html::rawElement(
-                                       'div',
-                                       [ 'class' => 'rcfilters-head' ],
-                                       $rcfilterContainer . $rcoptions
-                               )
-                       );
-
-                       // Add spinner
-                       $this->getOutput()->addHTML( $loadingContainer );
-               } else {
-                       $this->getOutput()->addHTML( $rcoptions );
-               }
-
-               $this->setBottomText( $opts );
-       }
-
-       /**
-        * Send the text to be displayed above the options
-        *
-        * @param FormOptions $opts Unused
-        */
-       function setTopText( FormOptions $opts ) {
-               $message = $this->msg( 'recentchangestext' )->inContentLanguage();
-               if ( !$message->isDisabled() ) {
-                       $contLang = MediaWikiServices::getInstance()->getContentLanguage();
-                       // Parse the message in this weird ugly way to preserve the ability to include interlanguage
-                       // links in it (T172461). In the future when T66969 is resolved, perhaps we can just use
-                       // $message->parse() instead. This code is copied from Message::parseText().
-                       $parserOutput = MessageCache::singleton()->parse(
-                               $message->plain(),
-                               $this->getPageTitle(),
-                               /*linestart*/true,
-                               // Message class sets the interface flag to false when parsing in a language different than
-                               // user language, and this is wiki content language
-                               /*interface*/false,
-                               $contLang
-                       );
-                       $content = $parserOutput->getText( [
-                               'enableSectionEditLinks' => false,
-                       ] );
-                       // Add only metadata here (including the language links), text is added below
-                       $this->getOutput()->addParserOutputMetadata( $parserOutput );
-
-                       $langAttributes = [
-                               'lang' => $contLang->getHtmlCode(),
-                               'dir' => $contLang->getDir(),
-                       ];
-
-                       $topLinksAttributes = [ 'class' => 'mw-recentchanges-toplinks' ];
-
-                       if ( $this->isStructuredFilterUiEnabled() ) {
-                               // Check whether the widget is already collapsed or expanded
-                               $collapsedState = $this->getRequest()->getCookie( 'rcfilters-toplinks-collapsed-state' );
-                               // Note that an empty/unset cookie means collapsed, so check for !== 'expanded'
-                               $topLinksAttributes[ 'class' ] .= $collapsedState !== 'expanded' ?
-                                       ' mw-recentchanges-toplinks-collapsed' : '';
-
-                               $this->getOutput()->enableOOUI();
-                               $contentTitle = new OOUI\ButtonWidget( [
-                                       'classes' => [ 'mw-recentchanges-toplinks-title' ],
-                                       'label' => new OOUI\HtmlSnippet( $this->msg( 'rcfilters-other-review-tools' )->parse() ),
-                                       'framed' => false,
-                                       'indicator' => $collapsedState !== 'expanded' ? 'down' : 'up',
-                                       'flags' => [ 'progressive' ],
-                               ] );
-
-                               $contentWrapper = Html::rawElement( 'div',
-                                       array_merge(
-                                               [ 'class' => 'mw-recentchanges-toplinks-content mw-collapsible-content' ],
-                                               $langAttributes
-                                       ),
-                                       $content
-                               );
-                               $content = $contentTitle . $contentWrapper;
-                       } else {
-                               // Language direction should be on the top div only
-                               // if the title is not there. If it is there, it's
-                               // interface direction, and the language/dir attributes
-                               // should be on the content itself
-                               $topLinksAttributes = array_merge( $topLinksAttributes, $langAttributes );
-                       }
-
-                       $this->getOutput()->addHTML(
-                               Html::rawElement( 'div', $topLinksAttributes, $content )
-                       );
-               }
-       }
-
-       /**
-        * Get options to be displayed in a form
-        *
-        * @param FormOptions $opts
-        * @return array
-        */
-       function getExtraOptions( $opts ) {
-               $opts->consumeValues( [
-                       'namespace', 'invert', 'associated', 'tagfilter'
-               ] );
-
-               $extraOpts = [];
-               $extraOpts['namespace'] = $this->namespaceFilterForm( $opts );
-
-               $tagFilter = ChangeTags::buildTagFilterSelector(
-                       $opts['tagfilter'], false, $this->getContext() );
-               if ( count( $tagFilter ) ) {
-                       $extraOpts['tagfilter'] = $tagFilter;
-               }
-
-               // Don't fire the hook for subclasses. (Or should we?)
-               if ( $this->getName() === 'Recentchanges' ) {
-                       Hooks::run( 'SpecialRecentChangesPanel', [ &$extraOpts, $opts ] );
-               }
-
-               return $extraOpts;
-       }
-
-       /**
-        * Add page-specific modules.
-        */
-       protected function addModules() {
-               parent::addModules();
-               $out = $this->getOutput();
-               $out->addModules( 'mediawiki.special.recentchanges' );
-       }
-
-       /**
-        * Get last modified date, for client caching
-        * Don't use this if we are using the patrol feature, patrol changes don't
-        * update the timestamp
-        *
-        * @return string|bool
-        */
-       public function checkLastModified() {
-               $dbr = $this->getDB();
-               $lastmod = $dbr->selectField( 'recentchanges', 'MAX(rc_timestamp)', '', __METHOD__ );
-
-               return $lastmod;
-       }
-
-       /**
-        * Creates the choose namespace selection
-        *
-        * @param FormOptions $opts
-        * @return string[]
-        */
-       protected function namespaceFilterForm( FormOptions $opts ) {
-               $nsSelect = Html::namespaceSelector(
-                       [ 'selected' => $opts['namespace'], 'all' => '', 'in-user-lang' => true ],
-                       [ 'name' => 'namespace', 'id' => 'namespace' ]
-               );
-               $nsLabel = Xml::label( $this->msg( 'namespace' )->text(), 'namespace' );
-               $attribs = [ 'class' => [ 'mw-input-with-label' ] ];
-               // Hide the checkboxes when the namespace filter is set to 'all'.
-               if ( $opts['namespace'] === '' ) {
-                       $attribs['class'][] = 'mw-input-hidden';
-               }
-               $invert = Html::rawElement( 'span', $attribs, Xml::checkLabel(
-                       $this->msg( 'invert' )->text(), 'invert', 'nsinvert',
-                       $opts['invert'],
-                       [ 'title' => $this->msg( 'tooltip-invert' )->text() ]
-               ) );
-               $associated = Html::rawElement( 'span', $attribs, Xml::checkLabel(
-                       $this->msg( 'namespace_association' )->text(), 'associated', 'nsassociated',
-                       $opts['associated'],
-                       [ 'title' => $this->msg( 'tooltip-namespace_association' )->text() ]
-               ) );
-
-               return [ $nsLabel, "$nsSelect $invert $associated" ];
-       }
-
-       /**
-        * Filter $rows by categories set in $opts
-        *
-        * @deprecated since 1.31
-        *
-        * @param IResultWrapper &$rows Database rows
-        * @param FormOptions $opts
-        */
-       function filterByCategories( &$rows, FormOptions $opts ) {
-               wfDeprecated( __METHOD__, '1.31' );
-
-               $categories = array_map( 'trim', explode( '|', $opts['categories'] ) );
-
-               if ( $categories === [] ) {
-                       return;
-               }
-
-               # Filter categories
-               $cats = [];
-               foreach ( $categories as $cat ) {
-                       $cat = trim( $cat );
-                       if ( $cat == '' ) {
-                               continue;
-                       }
-                       $cats[] = $cat;
-               }
-
-               # Filter articles
-               $articles = [];
-               $a2r = [];
-               $rowsarr = [];
-               foreach ( $rows as $k => $r ) {
-                       $nt = Title::makeTitle( $r->rc_namespace, $r->rc_title );
-                       $id = $nt->getArticleID();
-                       if ( $id == 0 ) {
-                               continue; # Page might have been deleted...
-                       }
-                       if ( !in_array( $id, $articles ) ) {
-                               $articles[] = $id;
-                       }
-                       if ( !isset( $a2r[$id] ) ) {
-                               $a2r[$id] = [];
-                       }
-                       $a2r[$id][] = $k;
-                       $rowsarr[$k] = $r;
-               }
-
-               # Shortcut?
-               if ( $articles === [] || $cats === [] ) {
-                       return;
-               }
-
-               # Look up
-               $catFind = new CategoryFinder;
-               $catFind->seed( $articles, $cats, $opts['categories_any'] ? 'OR' : 'AND' );
-               $match = $catFind->run();
-
-               # Filter
-               $newrows = [];
-               foreach ( $match as $id ) {
-                       foreach ( $a2r[$id] as $rev ) {
-                               $k = $rev;
-                               $newrows[$k] = $rowsarr[$k];
-                       }
-               }
-               $rows = new FakeResultWrapper( array_values( $newrows ) );
-       }
-
-       /**
-        * Makes change an option link which carries all the other options
-        *
-        * @param string $title
-        * @param array $override Options to override
-        * @param array $options Current options
-        * @param bool $active Whether to show the link in bold
-        * @return string
-        */
-       function makeOptionsLink( $title, $override, $options, $active = false ) {
-               $params = $this->convertParamsForLink( $override + $options );
-
-               if ( $active ) {
-                       $title = new HtmlArmor( '<strong>' . htmlspecialchars( $title ) . '</strong>' );
-               }
-
-               return $this->getLinkRenderer()->makeKnownLink( $this->getPageTitle(), $title, [
-                       'data-params' => json_encode( $override ),
-                       'data-keys' => implode( ',', array_keys( $override ) ),
-               ], $params );
-       }
-
-       /**
-        * Creates the options panel.
-        *
-        * @param array $defaults
-        * @param array $nondefaults
-        * @param int $numRows Number of rows in the result to show after this header
-        * @return string
-        */
-       function optionsPanel( $defaults, $nondefaults, $numRows ) {
-               $options = $nondefaults + $defaults;
-
-               $note = '';
-               $msg = $this->msg( 'rclegend' );
-               if ( !$msg->isDisabled() ) {
-                       $note .= Html::rawElement(
-                               'div',
-                               [ 'class' => 'mw-rclegend' ],
-                               $msg->parse()
-                       );
-               }
-
-               $lang = $this->getLanguage();
-               $user = $this->getUser();
-               $config = $this->getConfig();
-               if ( $options['from'] ) {
-                       $resetLink = $this->makeOptionsLink( $this->msg( 'rclistfromreset' ),
-                               [ 'from' => '' ], $nondefaults );
-
-                       $noteFromMsg = $this->msg( 'rcnotefrom' )
-                               ->numParams( $options['limit'] )
-                               ->params(
-                                       $lang->userTimeAndDate( $options['from'], $user ),
-                                       $lang->userDate( $options['from'], $user ),
-                                       $lang->userTime( $options['from'], $user )
-                               )
-                               ->numParams( $numRows );
-                       $note .= Html::rawElement(
-                                       'span',
-                                       [ 'class' => 'rcnotefrom' ],
-                                       $noteFromMsg->parse()
-                               ) .
-                               ' ' .
-                               Html::rawElement(
-                                       'span',
-                                       [ 'class' => 'rcoptions-listfromreset' ],
-                                       $this->msg( 'parentheses' )->rawParams( $resetLink )->parse()
-                               ) .
-                               '<br />';
-               }
-
-               # Sort data for display and make sure it's unique after we've added user data.
-               $linkLimits = $config->get( 'RCLinkLimits' );
-               $linkLimits[] = $options['limit'];
-               sort( $linkLimits );
-               $linkLimits = array_unique( $linkLimits );
-
-               $linkDays = $config->get( 'RCLinkDays' );
-               $linkDays[] = $options['days'];
-               sort( $linkDays );
-               $linkDays = array_unique( $linkDays );
-
-               // limit links
-               $cl = [];
-               foreach ( $linkLimits as $value ) {
-                       $cl[] = $this->makeOptionsLink( $lang->formatNum( $value ),
-                               [ 'limit' => $value ], $nondefaults, $value == $options['limit'] );
-               }
-               $cl = $lang->pipeList( $cl );
-
-               // day links, reset 'from' to none
-               $dl = [];
-               foreach ( $linkDays as $value ) {
-                       $dl[] = $this->makeOptionsLink( $lang->formatNum( $value ),
-                               [ 'days' => $value, 'from' => '' ], $nondefaults, $value == $options['days'] );
-               }
-               $dl = $lang->pipeList( $dl );
-
-               $showhide = [ 'show', 'hide' ];
-
-               $links = [];
-
-               foreach ( $this->getLegacyShowHideFilters() as $key => $filter ) {
-                       $msg = $filter->getShowHide();
-                       $linkMessage = $this->msg( $msg . '-' . $showhide[1 - $options[$key]] );
-                       // Extensions can define additional filters, but don't need to define the corresponding
-                       // messages. If they don't exist, just fall back to 'show' and 'hide'.
-                       if ( !$linkMessage->exists() ) {
-                               $linkMessage = $this->msg( $showhide[1 - $options[$key]] );
-                       }
-
-                       $link = $this->makeOptionsLink( $linkMessage->text(),
-                               [ $key => 1 - $options[$key] ], $nondefaults );
-
-                       $attribs = [
-                               'class' => "$msg rcshowhideoption clshowhideoption",
-                               'data-filter-name' => $filter->getName(),
-                       ];
-
-                       if ( $filter->isFeatureAvailableOnStructuredUi( $this ) ) {
-                               $attribs['data-feature-in-structured-ui'] = true;
-                       }
-
-                       $links[] = Html::rawElement(
-                               'span',
-                               $attribs,
-                               $this->msg( $msg )->rawParams( $link )->parse()
-                       );
-               }
-
-               // show from this onward link
-               $timestamp = wfTimestampNow();
-               $now = $lang->userTimeAndDate( $timestamp, $user );
-               $timenow = $lang->userTime( $timestamp, $user );
-               $datenow = $lang->userDate( $timestamp, $user );
-               $pipedLinks = '<span class="rcshowhide">' . $lang->pipeList( $links ) . '</span>';
-
-               $rclinks = Html::rawElement(
-                       'span',
-                       [ 'class' => 'rclinks' ],
-                       $this->msg( 'rclinks' )->rawParams( $cl, $dl, '' )->parse()
-               );
-
-               $rclistfrom = Html::rawElement(
-                       'span',
-                       [ 'class' => 'rclistfrom' ],
-                       $this->makeOptionsLink(
-                               $this->msg( 'rclistfrom' )->plaintextParams( $now, $timenow, $datenow )->parse(),
-                               [ 'from' => $timestamp ],
-                               $nondefaults
-                       )
-               );
-
-               return "{$note}$rclinks<br />$pipedLinks<br />$rclistfrom";
-       }
-
-       public function isIncludable() {
-               return true;
-       }
-
-       protected function getCacheTTL() {
-               return 60 * 5;
-       }
-
-       public function getDefaultLimit() {
-               $systemPrefValue = $this->getUser()->getIntOption( 'rclimit' );
-               // Prefer the RCFilters-specific preference if RCFilters is enabled
-               if ( $this->isStructuredFilterUiEnabled() ) {
-                       return $this->getUser()->getIntOption( static::$limitPreferenceName, $systemPrefValue );
-               }
-
-               // Otherwise, use the system rclimit preference value
-               return $systemPrefValue;
-       }
-}
diff --git a/includes/specials/SpecialRecentchangeslinked.php b/includes/specials/SpecialRecentchangeslinked.php
deleted file mode 100644 (file)
index 8865654..0000000
+++ /dev/null
@@ -1,319 +0,0 @@
-<?php
-/**
- * Implements Special:Recentchangeslinked
- *
- * 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 SpecialPage
- */
-
-/**
- * This is to display changes made to all articles linked in an article.
- *
- * @ingroup SpecialPage
- */
-class SpecialRecentChangesLinked extends SpecialRecentChanges {
-       /** @var bool|Title */
-       protected $rclTargetTitle;
-
-       function __construct() {
-               parent::__construct( 'Recentchangeslinked' );
-       }
-
-       public function getDefaultOptions() {
-               $opts = parent::getDefaultOptions();
-               $opts->add( 'target', '' );
-               $opts->add( 'showlinkedto', false );
-
-               return $opts;
-       }
-
-       public function parseParameters( $par, FormOptions $opts ) {
-               $opts['target'] = $par;
-       }
-
-       /**
-        * @inheritDoc
-        */
-       protected function doMainQuery( $tables, $select, $conds, $query_options,
-               $join_conds, FormOptions $opts
-       ) {
-               $target = $opts['target'];
-               $showlinkedto = $opts['showlinkedto'];
-               $limit = $opts['limit'];
-
-               if ( $target === '' ) {
-                       return false;
-               }
-               $outputPage = $this->getOutput();
-               $title = Title::newFromText( $target );
-               if ( !$title || $title->isExternal() ) {
-                       $outputPage->addHTML(
-                               Html::errorBox( $this->msg( 'allpagesbadtitle' )->parse() )
-                       );
-                       return false;
-               }
-
-               $outputPage->setPageTitle( $this->msg( 'recentchangeslinked-title', $title->getPrefixedText() ) );
-
-               /*
-                * Ordinary links are in the pagelinks table, while transclusions are
-                * in the templatelinks table, categorizations in categorylinks and
-                * image use in imagelinks.  We need to somehow combine all these.
-                * Special:Whatlinkshere does this by firing multiple queries and
-                * merging the results, but the code we inherit from our parent class
-                * expects only one result set so we use UNION instead.
-                */
-
-               $dbr = wfGetDB( DB_REPLICA, 'recentchangeslinked' );
-               $id = $title->getArticleID();
-               $ns = $title->getNamespace();
-               $dbkey = $title->getDBkey();
-
-               $rcQuery = RecentChange::getQueryInfo();
-               $tables = array_merge( $tables, $rcQuery['tables'] );
-               $select = array_merge( $rcQuery['fields'], $select );
-               $join_conds = array_merge( $join_conds, $rcQuery['joins'] );
-
-               // left join with watchlist table to highlight watched rows
-               $uid = $this->getUser()->getId();
-               if ( $uid && $this->getUser()->isAllowed( 'viewmywatchlist' ) ) {
-                       $tables[] = 'watchlist';
-                       $select[] = 'wl_user';
-                       $join_conds['watchlist'] = [ 'LEFT JOIN', [
-                               'wl_user' => $uid,
-                               'wl_title=rc_title',
-                               'wl_namespace=rc_namespace'
-                       ] ];
-               }
-
-               // JOIN on page, used for 'last revision' filter highlight
-               $tables[] = 'page';
-               $join_conds['page'] = [ 'LEFT JOIN', 'rc_cur_id=page_id' ];
-               $select[] = 'page_latest';
-
-               $tagFilter = $opts['tagfilter'] ? explode( '|', $opts['tagfilter'] ) : [];
-               ChangeTags::modifyDisplayQuery(
-                       $tables,
-                       $select,
-                       $conds,
-                       $join_conds,
-                       $query_options,
-                       $tagFilter
-               );
-
-               if ( $dbr->unionSupportsOrderAndLimit() ) {
-                       if ( count( $tagFilter ) > 1 ) {
-                               // ChangeTags::modifyDisplayQuery() will have added DISTINCT.
-                               // To prevent this from causing query performance problems, we need to add
-                               // a GROUP BY, and add rc_id to the ORDER BY.
-                               $order = [
-                                       'GROUP BY' => 'rc_timestamp, rc_id',
-                                       'ORDER BY' => 'rc_timestamp DESC, rc_id DESC'
-                               ];
-                       } else {
-                               $order = [ 'ORDER BY' => 'rc_timestamp DESC' ];
-                       }
-               } else {
-                       $order = [];
-               }
-
-               if ( !$this->runMainQueryHook( $tables, $select, $conds, $query_options, $join_conds,
-                       $opts )
-               ) {
-                       return false;
-               }
-
-               if ( $ns == NS_CATEGORY && !$showlinkedto ) {
-                       // special handling for categories
-                       // XXX: should try to make this less kludgy
-                       $link_tables = [ 'categorylinks' ];
-                       $showlinkedto = true;
-               } else {
-                       // for now, always join on these tables; really should be configurable as in whatlinkshere
-                       $link_tables = [ 'pagelinks', 'templatelinks' ];
-                       // imagelinks only contains links to pages in NS_FILE
-                       if ( $ns == NS_FILE || !$showlinkedto ) {
-                               $link_tables[] = 'imagelinks';
-                       }
-               }
-
-               if ( $id == 0 && !$showlinkedto ) {
-                       return false; // nonexistent pages can't link to any pages
-               }
-
-               // field name prefixes for all the various tables we might want to join with
-               $prefix = [
-                       'pagelinks' => 'pl',
-                       'templatelinks' => 'tl',
-                       'categorylinks' => 'cl',
-                       'imagelinks' => 'il'
-               ];
-
-               $subsql = []; // SELECT statements to combine with UNION
-
-               foreach ( $link_tables as $link_table ) {
-                       $pfx = $prefix[$link_table];
-
-                       // imagelinks and categorylinks tables have no xx_namespace field,
-                       // and have xx_to instead of xx_title
-                       if ( $link_table == 'imagelinks' ) {
-                               $link_ns = NS_FILE;
-                       } elseif ( $link_table == 'categorylinks' ) {
-                               $link_ns = NS_CATEGORY;
-                       } else {
-                               $link_ns = 0;
-                       }
-
-                       if ( $showlinkedto ) {
-                               // find changes to pages linking to this page
-                               if ( $link_ns ) {
-                                       if ( $ns != $link_ns ) {
-                                               continue;
-                                       } // should never happen, but check anyway
-                                       $subconds = [ "{$pfx}_to" => $dbkey ];
-                               } else {
-                                       $subconds = [ "{$pfx}_namespace" => $ns, "{$pfx}_title" => $dbkey ];
-                               }
-                               $subjoin = "rc_cur_id = {$pfx}_from";
-                       } else {
-                               // find changes to pages linked from this page
-                               $subconds = [ "{$pfx}_from" => $id ];
-                               if ( $link_table == 'imagelinks' || $link_table == 'categorylinks' ) {
-                                       $subconds["rc_namespace"] = $link_ns;
-                                       $subjoin = "rc_title = {$pfx}_to";
-                               } else {
-                                       $subjoin = [ "rc_namespace = {$pfx}_namespace", "rc_title = {$pfx}_title" ];
-                               }
-                       }
-
-                       $query = $dbr->selectSQLText(
-                               array_merge( $tables, [ $link_table ] ),
-                               $select,
-                               $conds + $subconds,
-                               __METHOD__,
-                               $order + $query_options,
-                               $join_conds + [ $link_table => [ 'JOIN', $subjoin ] ]
-                       );
-
-                       if ( $dbr->unionSupportsOrderAndLimit() ) {
-                               $query = $dbr->limitResult( $query, $limit );
-                       }
-
-                       $subsql[] = $query;
-               }
-
-               if ( count( $subsql ) == 0 ) {
-                       return false; // should never happen
-               }
-               if ( count( $subsql ) == 1 && $dbr->unionSupportsOrderAndLimit() ) {
-                       $sql = $subsql[0];
-               } else {
-                       // need to resort and relimit after union
-                       $sql = $dbr->unionQueries( $subsql, $dbr::UNION_DISTINCT ) .
-                               ' ORDER BY rc_timestamp DESC';
-                       $sql = $dbr->limitResult( $sql, $limit, false );
-               }
-
-               $res = $dbr->query( $sql, __METHOD__ );
-
-               if ( $res->numRows() == 0 ) {
-                       $this->mResultEmpty = true;
-               }
-
-               return $res;
-       }
-
-       function setTopText( FormOptions $opts ) {
-               $target = $this->getTargetTitle();
-               if ( $target ) {
-                       $this->getOutput()->addBacklinkSubtitle( $target );
-                       $this->getSkin()->setRelevantTitle( $target );
-               }
-       }
-
-       /**
-        * Get options to be displayed in a form
-        *
-        * @param FormOptions $opts
-        * @return array
-        */
-       function getExtraOptions( $opts ) {
-               $extraOpts = parent::getExtraOptions( $opts );
-
-               $opts->consumeValues( [ 'showlinkedto', 'target' ] );
-
-               $extraOpts['target'] = [ $this->msg( 'recentchangeslinked-page' )->escaped(),
-                       Xml::input( 'target', 40, str_replace( '_', ' ', $opts['target'] ) ) .
-                       Xml::check( 'showlinkedto', $opts['showlinkedto'], [ 'id' => 'showlinkedto' ] ) . ' ' .
-                       Xml::label( $this->msg( 'recentchangeslinked-to' )->text(), 'showlinkedto' ) ];
-
-               $this->addHelpLink( 'Help:Related changes' );
-               return $extraOpts;
-       }
-
-       /**
-        * @return Title
-        */
-       function getTargetTitle() {
-               if ( $this->rclTargetTitle === null ) {
-                       $opts = $this->getOptions();
-                       if ( isset( $opts['target'] ) && $opts['target'] !== '' ) {
-                               $this->rclTargetTitle = Title::newFromText( $opts['target'] );
-                       } else {
-                               $this->rclTargetTitle = false;
-                       }
-               }
-
-               return $this->rclTargetTitle;
-       }
-
-       /**
-        * Return an array of subpages beginning with $search that this special page will accept.
-        *
-        * @param string $search Prefix to search for
-        * @param int $limit Maximum number of results to return (usually 10)
-        * @param int $offset Number of results to skip (usually 0)
-        * @return string[] Matching subpages
-        */
-       public function prefixSearchSubpages( $search, $limit, $offset ) {
-               return $this->prefixSearchString( $search, $limit, $offset );
-       }
-
-       protected function outputNoResults() {
-               $targetTitle = $this->getTargetTitle();
-               if ( $targetTitle === false ) {
-                       $this->getOutput()->addHTML(
-                               Html::rawElement(
-                                       'div',
-                                       [ 'class' => 'mw-changeslist-empty mw-changeslist-notargetpage' ],
-                                       $this->msg( 'recentchanges-notargetpage' )->parse()
-                               )
-                       );
-               } elseif ( !$targetTitle || $targetTitle->isExternal() ) {
-                       $this->getOutput()->addHTML(
-                               Html::rawElement(
-                                       'div',
-                                       [ 'class' => 'mw-changeslist-empty mw-changeslist-invalidtargetpage' ],
-                                       $this->msg( 'allpagesbadtitle' )->parse()
-                               )
-                       );
-               } else {
-                       parent::outputNoResults();
-               }
-       }
-}
diff --git a/includes/specials/SpecialRevisionDelete.php b/includes/specials/SpecialRevisionDelete.php
new file mode 100644 (file)
index 0000000..f0bac45
--- /dev/null
@@ -0,0 +1,685 @@
+<?php
+/**
+ * Implements Special:Revisiondelete
+ *
+ * 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 SpecialPage
+ */
+
+/**
+ * Special page allowing users with the appropriate permissions to view
+ * and hide revisions. Log items can also be hidden.
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialRevisionDelete extends UnlistedSpecialPage {
+       /** @var bool Was the DB modified in this request */
+       protected $wasSaved = false;
+
+       /** @var bool True if the submit button was clicked, and the form was posted */
+       private $submitClicked;
+
+       /** @var array Target ID list */
+       private $ids;
+
+       /** @var string Archive name, for reviewing deleted files */
+       private $archiveName;
+
+       /** @var string Edit token for securing image views against XSS */
+       private $token;
+
+       /** @var Title Title object for target parameter */
+       private $targetObj;
+
+       /** @var string Deletion type, may be revision, archive, oldimage, filearchive, logging. */
+       private $typeName;
+
+       /** @var array Array of checkbox specs (message, name, deletion bits) */
+       private $checks;
+
+       /** @var array UI Labels about the current type */
+       private $typeLabels;
+
+       /** @var RevDelList RevDelList object, storing the list of items to be deleted/undeleted */
+       private $revDelList;
+
+       /** @var bool Whether user is allowed to perform the action */
+       private $mIsAllowed;
+
+       /** @var string */
+       private $otherReason;
+
+       /**
+        * UI labels for each type.
+        */
+       private static $UILabels = [
+               'revision' => [
+                       'check-label' => 'revdelete-hide-text',
+                       'success' => 'revdelete-success',
+                       'failure' => 'revdelete-failure',
+                       'text' => 'revdelete-text-text',
+                       'selected' => 'revdelete-selected-text',
+               ],
+               'archive' => [
+                       'check-label' => 'revdelete-hide-text',
+                       'success' => 'revdelete-success',
+                       'failure' => 'revdelete-failure',
+                       'text' => 'revdelete-text-text',
+                       'selected' => 'revdelete-selected-text',
+               ],
+               'oldimage' => [
+                       'check-label' => 'revdelete-hide-image',
+                       'success' => 'revdelete-success',
+                       'failure' => 'revdelete-failure',
+                       'text' => 'revdelete-text-file',
+                       'selected' => 'revdelete-selected-file',
+               ],
+               'filearchive' => [
+                       'check-label' => 'revdelete-hide-image',
+                       'success' => 'revdelete-success',
+                       'failure' => 'revdelete-failure',
+                       'text' => 'revdelete-text-file',
+                       'selected' => 'revdelete-selected-file',
+               ],
+               'logging' => [
+                       'check-label' => 'revdelete-hide-name',
+                       'success' => 'logdelete-success',
+                       'failure' => 'logdelete-failure',
+                       'text' => 'logdelete-text',
+                       'selected' => 'logdelete-selected',
+               ],
+       ];
+
+       public function __construct() {
+               parent::__construct( 'Revisiondelete', 'deleterevision' );
+       }
+
+       public function doesWrites() {
+               return true;
+       }
+
+       public function execute( $par ) {
+               $this->useTransactionalTimeLimit();
+
+               $this->checkPermissions();
+               $this->checkReadOnly();
+
+               $output = $this->getOutput();
+               $user = $this->getUser();
+
+               // Check blocks
+               if ( $user->isBlocked() ) {
+                       throw new UserBlockedError( $user->getBlock() );
+               }
+
+               $this->setHeaders();
+               $this->outputHeader();
+               $request = $this->getRequest();
+               $this->submitClicked = $request->wasPosted() && $request->getBool( 'wpSubmit' );
+               # Handle our many different possible input types.
+               $ids = $request->getVal( 'ids' );
+               if ( !is_null( $ids ) ) {
+                       # Allow CSV, for backwards compatibility, or a single ID for show/hide links
+                       $this->ids = explode( ',', $ids );
+               } else {
+                       # Array input
+                       $this->ids = array_keys( $request->getArray( 'ids', [] ) );
+               }
+               // $this->ids = array_map( 'intval', $this->ids );
+               $this->ids = array_unique( array_filter( $this->ids ) );
+
+               $this->typeName = $request->getVal( 'type' );
+               $this->targetObj = Title::newFromText( $request->getText( 'target' ) );
+
+               # For reviewing deleted files...
+               $this->archiveName = $request->getVal( 'file' );
+               $this->token = $request->getVal( 'token' );
+               if ( $this->archiveName && $this->targetObj ) {
+                       $this->tryShowFile( $this->archiveName );
+
+                       return;
+               }
+
+               $this->typeName = RevisionDeleter::getCanonicalTypeName( $this->typeName );
+
+               # No targets?
+               if ( !$this->typeName || count( $this->ids ) == 0 ) {
+                       throw new ErrorPageError( 'revdelete-nooldid-title', 'revdelete-nooldid-text' );
+               }
+
+               # Allow the list type to adjust the passed target
+               $this->targetObj = RevisionDeleter::suggestTarget(
+                       $this->typeName,
+                       $this->targetObj,
+                       $this->ids
+               );
+
+               # We need a target page!
+               if ( $this->targetObj === null ) {
+                       $output->addWikiMsg( 'undelete-header' );
+
+                       return;
+               }
+
+               $this->typeLabels = self::$UILabels[$this->typeName];
+               $list = $this->getList();
+               $list->reset();
+               $this->mIsAllowed = $user->isAllowed( RevisionDeleter::getRestriction( $this->typeName ) );
+               $canViewSuppressedOnly = $this->getUser()->isAllowed( 'viewsuppressed' ) &&
+                       !$this->getUser()->isAllowed( 'suppressrevision' );
+               $pageIsSuppressed = $list->areAnySuppressed();
+               $this->mIsAllowed = $this->mIsAllowed && !( $canViewSuppressedOnly && $pageIsSuppressed );
+
+               $this->otherReason = $request->getVal( 'wpReason' );
+               # Give a link to the logs/hist for this page
+               $this->showConvenienceLinks();
+
+               # Initialise checkboxes
+               $this->checks = [
+                       # Messages: revdelete-hide-text, revdelete-hide-image, revdelete-hide-name
+                       [ $this->typeLabels['check-label'], 'wpHidePrimary',
+                               RevisionDeleter::getRevdelConstant( $this->typeName )
+                       ],
+                       [ 'revdelete-hide-comment', 'wpHideComment', Revision::DELETED_COMMENT ],
+                       [ 'revdelete-hide-user', 'wpHideUser', Revision::DELETED_USER ]
+               ];
+               if ( $user->isAllowed( 'suppressrevision' ) ) {
+                       $this->checks[] = [ 'revdelete-hide-restricted',
+                               'wpHideRestricted', Revision::DELETED_RESTRICTED ];
+               }
+
+               # Either submit or create our form
+               if ( $this->mIsAllowed && $this->submitClicked ) {
+                       $this->submit();
+               } else {
+                       $this->showForm();
+               }
+
+               if ( $user->isAllowed( 'deletedhistory' ) ) {
+                       $qc = $this->getLogQueryCond();
+                       # Show relevant lines from the deletion log
+                       $deleteLogPage = new LogPage( 'delete' );
+                       $output->addHTML( "<h2>" . $deleteLogPage->getName()->escaped() . "</h2>\n" );
+                       LogEventsList::showLogExtract(
+                               $output,
+                               'delete',
+                               $this->targetObj,
+                               '', /* user */
+                               [ 'lim' => 25, 'conds' => $qc, 'useMaster' => $this->wasSaved ]
+                       );
+               }
+               # Show relevant lines from the suppression log
+               if ( $user->isAllowed( 'suppressionlog' ) ) {
+                       $suppressLogPage = new LogPage( 'suppress' );
+                       $output->addHTML( "<h2>" . $suppressLogPage->getName()->escaped() . "</h2>\n" );
+                       LogEventsList::showLogExtract(
+                               $output,
+                               'suppress',
+                               $this->targetObj,
+                               '',
+                               [ 'lim' => 25, 'conds' => $qc, 'useMaster' => $this->wasSaved ]
+                       );
+               }
+       }
+
+       /**
+        * Show some useful links in the subtitle
+        */
+       protected function showConvenienceLinks() {
+               $linkRenderer = $this->getLinkRenderer();
+               # Give a link to the logs/hist for this page
+               if ( $this->targetObj ) {
+                       // Also set header tabs to be for the target.
+                       $this->getSkin()->setRelevantTitle( $this->targetObj );
+
+                       $links = [];
+                       $links[] = $linkRenderer->makeKnownLink(
+                               SpecialPage::getTitleFor( 'Log' ),
+                               $this->msg( 'viewpagelogs' )->text(),
+                               [],
+                               [ 'page' => $this->targetObj->getPrefixedText() ]
+                       );
+                       if ( !$this->targetObj->isSpecialPage() ) {
+                               # Give a link to the page history
+                               $links[] = $linkRenderer->makeKnownLink(
+                                       $this->targetObj,
+                                       $this->msg( 'pagehist' )->text(),
+                                       [],
+                                       [ 'action' => 'history' ]
+                               );
+                               # Link to deleted edits
+                               if ( $this->getUser()->isAllowed( 'undelete' ) ) {
+                                       $undelete = SpecialPage::getTitleFor( 'Undelete' );
+                                       $links[] = $linkRenderer->makeKnownLink(
+                                               $undelete,
+                                               $this->msg( 'deletedhist' )->text(),
+                                               [],
+                                               [ 'target' => $this->targetObj->getPrefixedDBkey() ]
+                                       );
+                               }
+                       }
+                       # Logs themselves don't have histories or archived revisions
+                       $this->getOutput()->addSubtitle( $this->getLanguage()->pipeList( $links ) );
+               }
+       }
+
+       /**
+        * Get the condition used for fetching log snippets
+        * @return array
+        */
+       protected function getLogQueryCond() {
+               $conds = [];
+               // Revision delete logs for these item
+               $conds['log_type'] = [ 'delete', 'suppress' ];
+               $conds['log_action'] = $this->getList()->getLogAction();
+               $conds['ls_field'] = RevisionDeleter::getRelationType( $this->typeName );
+               $conds['ls_value'] = $this->ids;
+
+               return $conds;
+       }
+
+       /**
+        * Show a deleted file version requested by the visitor.
+        * @todo Mostly copied from Special:Undelete. Refactor.
+        * @param string $archiveName
+        * @throws MWException
+        * @throws PermissionsError
+        */
+       protected function tryShowFile( $archiveName ) {
+               $repo = RepoGroup::singleton()->getLocalRepo();
+               $oimage = $repo->newFromArchiveName( $this->targetObj, $archiveName );
+               $oimage->load();
+               // Check if user is allowed to see this file
+               if ( !$oimage->exists() ) {
+                       $this->getOutput()->addWikiMsg( 'revdelete-no-file' );
+
+                       return;
+               }
+               $user = $this->getUser();
+               if ( !$oimage->userCan( File::DELETED_FILE, $user ) ) {
+                       if ( $oimage->isDeleted( File::DELETED_RESTRICTED ) ) {
+                               throw new PermissionsError( 'suppressrevision' );
+                       } else {
+                               throw new PermissionsError( 'deletedtext' );
+                       }
+               }
+               if ( !$user->matchEditToken( $this->token, $archiveName ) ) {
+                       $lang = $this->getLanguage();
+                       $this->getOutput()->addWikiMsg( 'revdelete-show-file-confirm',
+                               $this->targetObj->getText(),
+                               $lang->userDate( $oimage->getTimestamp(), $user ),
+                               $lang->userTime( $oimage->getTimestamp(), $user ) );
+                       $this->getOutput()->addHTML(
+                               Xml::openElement( 'form', [
+                                       'method' => 'POST',
+                                       'action' => $this->getPageTitle()->getLocalURL( [
+                                                       'target' => $this->targetObj->getPrefixedDBkey(),
+                                                       'file' => $archiveName,
+                                                       'token' => $user->getEditToken( $archiveName ),
+                                               ] )
+                                       ]
+                               ) .
+                               Xml::submitButton( $this->msg( 'revdelete-show-file-submit' )->text() ) .
+                               '</form>'
+                       );
+
+                       return;
+               }
+               $this->getOutput()->disable();
+               # We mustn't allow the output to be CDN cached, otherwise
+               # if an admin previews a deleted image, and it's cached, then
+               # a user without appropriate permissions can toddle off and
+               # nab the image, and CDN will serve it
+               $this->getRequest()->response()->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
+               $this->getRequest()->response()->header(
+                       'Cache-Control: no-cache, no-store, max-age=0, must-revalidate'
+               );
+               $this->getRequest()->response()->header( 'Pragma: no-cache' );
+
+               $key = $oimage->getStorageKey();
+               $path = $repo->getZonePath( 'deleted' ) . '/' . $repo->getDeletedHashPath( $key ) . $key;
+               $repo->streamFile( $path );
+       }
+
+       /**
+        * Get the list object for this request
+        * @return RevDelList
+        */
+       protected function getList() {
+               if ( is_null( $this->revDelList ) ) {
+                       $this->revDelList = RevisionDeleter::createList(
+                               $this->typeName, $this->getContext(), $this->targetObj, $this->ids
+                       );
+               }
+
+               return $this->revDelList;
+       }
+
+       /**
+        * Show a list of items that we will operate on, and show a form with checkboxes
+        * which will allow the user to choose new visibility settings.
+        */
+       protected function showForm() {
+               $userAllowed = true;
+
+               // Messages: revdelete-selected-text, revdelete-selected-file, logdelete-selected
+               $out = $this->getOutput();
+               $out->wrapWikiMsg( "<strong>$1</strong>", [ $this->typeLabels['selected'],
+                       $this->getLanguage()->formatNum( count( $this->ids ) ), $this->targetObj->getPrefixedText() ] );
+
+               $this->addHelpLink( 'Help:RevisionDelete' );
+               $out->addHTML( "<ul>" );
+
+               $numRevisions = 0;
+               // Live revisions...
+               $list = $this->getList();
+               for ( $list->reset(); $list->current(); $list->next() ) {
+                       $item = $list->current();
+
+                       if ( !$item->canView() ) {
+                               if ( !$this->submitClicked ) {
+                                       throw new PermissionsError( 'suppressrevision' );
+                               }
+                               $userAllowed = false;
+                       }
+
+                       $numRevisions++;
+                       $out->addHTML( $item->getHTML() );
+               }
+
+               if ( !$numRevisions ) {
+                       throw new ErrorPageError( 'revdelete-nooldid-title', 'revdelete-nooldid-text' );
+               }
+
+               $out->addHTML( "</ul>" );
+               // Explanation text
+               $this->addUsageText();
+
+               // Normal sysops can always see what they did, but can't always change it
+               if ( !$userAllowed ) {
+                       return;
+               }
+
+               // Show form if the user can submit
+               if ( $this->mIsAllowed ) {
+                       $out->addModules( [ 'mediawiki.special.revisionDelete' ] );
+                       $out->addModuleStyles( [ 'mediawiki.special',
+                               'mediawiki.interface.helpers.styles' ] );
+
+                       $form = Xml::openElement( 'form', [ 'method' => 'post',
+                                       'action' => $this->getPageTitle()->getLocalURL( [ 'action' => 'submit' ] ),
+                                       'id' => 'mw-revdel-form-revisions' ] ) .
+                               Xml::fieldset( $this->msg( 'revdelete-legend' )->text() ) .
+                               $this->buildCheckBoxes() .
+                               Xml::openElement( 'table' ) .
+                               "<tr>\n" .
+                                       '<td class="mw-label">' .
+                                               Xml::label( $this->msg( 'revdelete-log' )->text(), 'wpRevDeleteReasonList' ) .
+                                       '</td>' .
+                                       '<td class="mw-input">' .
+                                               Xml::listDropDown( 'wpRevDeleteReasonList',
+                                                       $this->msg( 'revdelete-reason-dropdown' )->inContentLanguage()->text(),
+                                                       $this->msg( 'revdelete-reasonotherlist' )->inContentLanguage()->text(),
+                                                       $this->getRequest()->getText( 'wpRevDeleteReasonList', 'other' ), 'wpReasonDropDown'
+                                               ) .
+                                       '</td>' .
+                               "</tr><tr>\n" .
+                                       '<td class="mw-label">' .
+                                               Xml::label( $this->msg( 'revdelete-otherreason' )->text(), 'wpReason' ) .
+                                       '</td>' .
+                                       '<td class="mw-input">' .
+                                               Xml::input( 'wpReason', 60, $this->otherReason, [
+                                                       'id' => 'wpReason',
+                                                       // 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 'wpRevDeleteReasonList' value.
+                                                       'maxlength' => CommentStore::COMMENT_CHARACTER_LIMIT - 155,
+                                               ] ) .
+                                       '</td>' .
+                               "</tr><tr>\n" .
+                                       '<td></td>' .
+                                       '<td class="mw-submit">' .
+                                               Xml::submitButton( $this->msg( 'revdelete-submit', $numRevisions )->text(),
+                                                       [ 'name' => 'wpSubmit' ] ) .
+                                       '</td>' .
+                               "</tr>\n" .
+                               Xml::closeElement( 'table' ) .
+                               Html::hidden( 'wpEditToken', $this->getUser()->getEditToken() ) .
+                               Html::hidden( 'target', $this->targetObj->getPrefixedText() ) .
+                               Html::hidden( 'type', $this->typeName ) .
+                               Html::hidden( 'ids', implode( ',', $this->ids ) ) .
+                               Xml::closeElement( 'fieldset' ) . "\n" .
+                               Xml::closeElement( 'form' ) . "\n";
+                       // Show link to edit the dropdown reasons
+                       if ( $this->getUser()->isAllowed( 'editinterface' ) ) {
+                               $link = $this->getLinkRenderer()->makeKnownLink(
+                                       $this->msg( 'revdelete-reason-dropdown' )->inContentLanguage()->getTitle(),
+                                       $this->msg( 'revdelete-edit-reasonlist' )->text(),
+                                       [],
+                                       [ 'action' => 'edit' ]
+                               );
+                               $form .= Xml::tags( 'p', [ 'class' => 'mw-revdel-editreasons' ], $link ) . "\n";
+                       }
+               } else {
+                       $form = '';
+               }
+               $out->addHTML( $form );
+       }
+
+       /**
+        * Show some introductory text
+        * @todo FIXME: Wikimedia-specific policy text
+        */
+       protected function addUsageText() {
+               // Messages: revdelete-text-text, revdelete-text-file, logdelete-text
+               $this->getOutput()->wrapWikiMsg(
+                       "<strong>$1</strong>\n$2", $this->typeLabels['text'],
+                       'revdelete-text-others'
+               );
+
+               if ( $this->getUser()->isAllowed( 'suppressrevision' ) ) {
+                       $this->getOutput()->addWikiMsg( 'revdelete-suppress-text' );
+               }
+
+               if ( $this->mIsAllowed ) {
+                       $this->getOutput()->addWikiMsg( 'revdelete-confirm' );
+               }
+       }
+
+       /**
+        * @return string HTML
+        */
+       protected function buildCheckBoxes() {
+               $html = '<table>';
+               // If there is just one item, use checkboxes
+               $list = $this->getList();
+               if ( $list->length() == 1 ) {
+                       $list->reset();
+                       $bitfield = $list->current()->getBits(); // existing field
+
+                       if ( $this->submitClicked ) {
+                               $bitfield = RevisionDeleter::extractBitfield( $this->extractBitParams(), $bitfield );
+                       }
+
+                       foreach ( $this->checks as $item ) {
+                               // Messages: revdelete-hide-text, revdelete-hide-image, revdelete-hide-name,
+                               // revdelete-hide-comment, revdelete-hide-user, revdelete-hide-restricted
+                               list( $message, $name, $field ) = $item;
+                               $innerHTML = Xml::checkLabel(
+                                       $this->msg( $message )->text(),
+                                       $name,
+                                       $name,
+                                       $bitfield & $field
+                               );
+
+                               if ( $field == Revision::DELETED_RESTRICTED ) {
+                                       $innerHTML = "<b>$innerHTML</b>";
+                               }
+
+                               $line = Xml::tags( 'td', [ 'class' => 'mw-input' ], $innerHTML );
+                               $html .= "<tr>$line</tr>\n";
+                       }
+               } else {
+                       // Otherwise, use tri-state radios
+                       $html .= '<tr>';
+                       $html .= '<th class="mw-revdel-checkbox">'
+                               . $this->msg( 'revdelete-radio-same' )->escaped() . '</th>';
+                       $html .= '<th class="mw-revdel-checkbox">'
+                               . $this->msg( 'revdelete-radio-unset' )->escaped() . '</th>';
+                       $html .= '<th class="mw-revdel-checkbox">'
+                               . $this->msg( 'revdelete-radio-set' )->escaped() . '</th>';
+                       $html .= "<th></th></tr>\n";
+                       foreach ( $this->checks as $item ) {
+                               // Messages: revdelete-hide-text, revdelete-hide-image, revdelete-hide-name,
+                               // revdelete-hide-comment, revdelete-hide-user, revdelete-hide-restricted
+                               list( $message, $name, $field ) = $item;
+                               // If there are several items, use third state by default...
+                               if ( $this->submitClicked ) {
+                                       $selected = $this->getRequest()->getInt( $name, 0 /* unchecked */ );
+                               } else {
+                                       $selected = -1; // use existing field
+                               }
+                               $line = '<td class="mw-revdel-checkbox">' . Xml::radio( $name, -1, $selected == -1 ) . '</td>';
+                               $line .= '<td class="mw-revdel-checkbox">' . Xml::radio( $name, 0, $selected == 0 ) . '</td>';
+                               $line .= '<td class="mw-revdel-checkbox">' . Xml::radio( $name, 1, $selected == 1 ) . '</td>';
+                               $label = $this->msg( $message )->escaped();
+                               if ( $field == Revision::DELETED_RESTRICTED ) {
+                                       $label = "<b>$label</b>";
+                               }
+                               $line .= "<td>$label</td>";
+                               $html .= "<tr>$line</tr>\n";
+                       }
+               }
+
+               $html .= '</table>';
+
+               return $html;
+       }
+
+       /**
+        * UI entry point for form submission.
+        * @throws PermissionsError
+        * @return bool
+        */
+       protected function submit() {
+               # Check edit token on submission
+               $token = $this->getRequest()->getVal( 'wpEditToken' );
+               if ( $this->submitClicked && !$this->getUser()->matchEditToken( $token ) ) {
+                       $this->getOutput()->addWikiMsg( 'sessionfailure' );
+
+                       return false;
+               }
+               $bitParams = $this->extractBitParams();
+               // from dropdown
+               $listReason = $this->getRequest()->getText( 'wpRevDeleteReasonList', 'other' );
+               $comment = $listReason;
+               if ( $comment === 'other' ) {
+                       $comment = $this->otherReason;
+               } elseif ( $this->otherReason !== '' ) {
+                       // Entry from drop down menu + additional comment
+                       $comment .= $this->msg( 'colon-separator' )->inContentLanguage()->text()
+                               . $this->otherReason;
+               }
+               # Can the user set this field?
+               if ( $bitParams[Revision::DELETED_RESTRICTED] == 1
+                       && !$this->getUser()->isAllowed( 'suppressrevision' )
+               ) {
+                       throw new PermissionsError( 'suppressrevision' );
+               }
+               # If the save went through, go to success message...
+               $status = $this->save( $bitParams, $comment );
+               if ( $status->isGood() ) {
+                       $this->success();
+
+                       return true;
+               } else {
+                       # ...otherwise, bounce back to form...
+                       $this->failure( $status );
+               }
+
+               return false;
+       }
+
+       /**
+        * Report that the submit operation succeeded
+        */
+       protected function success() {
+               // Messages: revdelete-success, logdelete-success
+               $this->getOutput()->setPageTitle( $this->msg( 'actioncomplete' ) );
+               $this->getOutput()->wrapWikiMsg(
+                       "<div class=\"successbox\">\n$1\n</div>",
+                       $this->typeLabels['success']
+               );
+               $this->wasSaved = true;
+               $this->revDelList->reloadFromMaster();
+               $this->showForm();
+       }
+
+       /**
+        * Report that the submit operation failed
+        * @param Status $status
+        */
+       protected function failure( $status ) {
+               // Messages: revdelete-failure, logdelete-failure
+               $this->getOutput()->setPageTitle( $this->msg( 'actionfailed' ) );
+               $this->getOutput()->wrapWikiTextAsInterface(
+                       'errorbox',
+                       $status->getWikiText( $this->typeLabels['failure'] )
+               );
+               $this->showForm();
+       }
+
+       /**
+        * Put together an array that contains -1, 0, or the *_deleted const for each bit
+        *
+        * @return array
+        */
+       protected function extractBitParams() {
+               $bitfield = [];
+               foreach ( $this->checks as $item ) {
+                       list( /* message */, $name, $field ) = $item;
+                       $val = $this->getRequest()->getInt( $name, 0 /* unchecked */ );
+                       if ( $val < -1 || $val > 1 ) {
+                               $val = -1; // -1 for existing value
+                       }
+                       $bitfield[$field] = $val;
+               }
+               if ( !isset( $bitfield[Revision::DELETED_RESTRICTED] ) ) {
+                       $bitfield[Revision::DELETED_RESTRICTED] = 0;
+               }
+
+               return $bitfield;
+       }
+
+       /**
+        * Do the write operations. Simple wrapper for RevDel*List::setVisibility().
+        * @param array $bitPars ExtractBitParams() bitfield array
+        * @param string $reason
+        * @return Status
+        */
+       protected function save( array $bitPars, $reason ) {
+               return $this->getList()->setVisibility(
+                       [ 'value' => $bitPars, 'comment' => $reason ]
+               );
+       }
+
+       protected function getGroupName() {
+               return 'pagetools';
+       }
+}
diff --git a/includes/specials/SpecialRevisiondelete.php b/includes/specials/SpecialRevisiondelete.php
deleted file mode 100644 (file)
index f0bac45..0000000
+++ /dev/null
@@ -1,685 +0,0 @@
-<?php
-/**
- * Implements Special:Revisiondelete
- *
- * 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 SpecialPage
- */
-
-/**
- * Special page allowing users with the appropriate permissions to view
- * and hide revisions. Log items can also be hidden.
- *
- * @ingroup SpecialPage
- */
-class SpecialRevisionDelete extends UnlistedSpecialPage {
-       /** @var bool Was the DB modified in this request */
-       protected $wasSaved = false;
-
-       /** @var bool True if the submit button was clicked, and the form was posted */
-       private $submitClicked;
-
-       /** @var array Target ID list */
-       private $ids;
-
-       /** @var string Archive name, for reviewing deleted files */
-       private $archiveName;
-
-       /** @var string Edit token for securing image views against XSS */
-       private $token;
-
-       /** @var Title Title object for target parameter */
-       private $targetObj;
-
-       /** @var string Deletion type, may be revision, archive, oldimage, filearchive, logging. */
-       private $typeName;
-
-       /** @var array Array of checkbox specs (message, name, deletion bits) */
-       private $checks;
-
-       /** @var array UI Labels about the current type */
-       private $typeLabels;
-
-       /** @var RevDelList RevDelList object, storing the list of items to be deleted/undeleted */
-       private $revDelList;
-
-       /** @var bool Whether user is allowed to perform the action */
-       private $mIsAllowed;
-
-       /** @var string */
-       private $otherReason;
-
-       /**
-        * UI labels for each type.
-        */
-       private static $UILabels = [
-               'revision' => [
-                       'check-label' => 'revdelete-hide-text',
-                       'success' => 'revdelete-success',
-                       'failure' => 'revdelete-failure',
-                       'text' => 'revdelete-text-text',
-                       'selected' => 'revdelete-selected-text',
-               ],
-               'archive' => [
-                       'check-label' => 'revdelete-hide-text',
-                       'success' => 'revdelete-success',
-                       'failure' => 'revdelete-failure',
-                       'text' => 'revdelete-text-text',
-                       'selected' => 'revdelete-selected-text',
-               ],
-               'oldimage' => [
-                       'check-label' => 'revdelete-hide-image',
-                       'success' => 'revdelete-success',
-                       'failure' => 'revdelete-failure',
-                       'text' => 'revdelete-text-file',
-                       'selected' => 'revdelete-selected-file',
-               ],
-               'filearchive' => [
-                       'check-label' => 'revdelete-hide-image',
-                       'success' => 'revdelete-success',
-                       'failure' => 'revdelete-failure',
-                       'text' => 'revdelete-text-file',
-                       'selected' => 'revdelete-selected-file',
-               ],
-               'logging' => [
-                       'check-label' => 'revdelete-hide-name',
-                       'success' => 'logdelete-success',
-                       'failure' => 'logdelete-failure',
-                       'text' => 'logdelete-text',
-                       'selected' => 'logdelete-selected',
-               ],
-       ];
-
-       public function __construct() {
-               parent::__construct( 'Revisiondelete', 'deleterevision' );
-       }
-
-       public function doesWrites() {
-               return true;
-       }
-
-       public function execute( $par ) {
-               $this->useTransactionalTimeLimit();
-
-               $this->checkPermissions();
-               $this->checkReadOnly();
-
-               $output = $this->getOutput();
-               $user = $this->getUser();
-
-               // Check blocks
-               if ( $user->isBlocked() ) {
-                       throw new UserBlockedError( $user->getBlock() );
-               }
-
-               $this->setHeaders();
-               $this->outputHeader();
-               $request = $this->getRequest();
-               $this->submitClicked = $request->wasPosted() && $request->getBool( 'wpSubmit' );
-               # Handle our many different possible input types.
-               $ids = $request->getVal( 'ids' );
-               if ( !is_null( $ids ) ) {
-                       # Allow CSV, for backwards compatibility, or a single ID for show/hide links
-                       $this->ids = explode( ',', $ids );
-               } else {
-                       # Array input
-                       $this->ids = array_keys( $request->getArray( 'ids', [] ) );
-               }
-               // $this->ids = array_map( 'intval', $this->ids );
-               $this->ids = array_unique( array_filter( $this->ids ) );
-
-               $this->typeName = $request->getVal( 'type' );
-               $this->targetObj = Title::newFromText( $request->getText( 'target' ) );
-
-               # For reviewing deleted files...
-               $this->archiveName = $request->getVal( 'file' );
-               $this->token = $request->getVal( 'token' );
-               if ( $this->archiveName && $this->targetObj ) {
-                       $this->tryShowFile( $this->archiveName );
-
-                       return;
-               }
-
-               $this->typeName = RevisionDeleter::getCanonicalTypeName( $this->typeName );
-
-               # No targets?
-               if ( !$this->typeName || count( $this->ids ) == 0 ) {
-                       throw new ErrorPageError( 'revdelete-nooldid-title', 'revdelete-nooldid-text' );
-               }
-
-               # Allow the list type to adjust the passed target
-               $this->targetObj = RevisionDeleter::suggestTarget(
-                       $this->typeName,
-                       $this->targetObj,
-                       $this->ids
-               );
-
-               # We need a target page!
-               if ( $this->targetObj === null ) {
-                       $output->addWikiMsg( 'undelete-header' );
-
-                       return;
-               }
-
-               $this->typeLabels = self::$UILabels[$this->typeName];
-               $list = $this->getList();
-               $list->reset();
-               $this->mIsAllowed = $user->isAllowed( RevisionDeleter::getRestriction( $this->typeName ) );
-               $canViewSuppressedOnly = $this->getUser()->isAllowed( 'viewsuppressed' ) &&
-                       !$this->getUser()->isAllowed( 'suppressrevision' );
-               $pageIsSuppressed = $list->areAnySuppressed();
-               $this->mIsAllowed = $this->mIsAllowed && !( $canViewSuppressedOnly && $pageIsSuppressed );
-
-               $this->otherReason = $request->getVal( 'wpReason' );
-               # Give a link to the logs/hist for this page
-               $this->showConvenienceLinks();
-
-               # Initialise checkboxes
-               $this->checks = [
-                       # Messages: revdelete-hide-text, revdelete-hide-image, revdelete-hide-name
-                       [ $this->typeLabels['check-label'], 'wpHidePrimary',
-                               RevisionDeleter::getRevdelConstant( $this->typeName )
-                       ],
-                       [ 'revdelete-hide-comment', 'wpHideComment', Revision::DELETED_COMMENT ],
-                       [ 'revdelete-hide-user', 'wpHideUser', Revision::DELETED_USER ]
-               ];
-               if ( $user->isAllowed( 'suppressrevision' ) ) {
-                       $this->checks[] = [ 'revdelete-hide-restricted',
-                               'wpHideRestricted', Revision::DELETED_RESTRICTED ];
-               }
-
-               # Either submit or create our form
-               if ( $this->mIsAllowed && $this->submitClicked ) {
-                       $this->submit();
-               } else {
-                       $this->showForm();
-               }
-
-               if ( $user->isAllowed( 'deletedhistory' ) ) {
-                       $qc = $this->getLogQueryCond();
-                       # Show relevant lines from the deletion log
-                       $deleteLogPage = new LogPage( 'delete' );
-                       $output->addHTML( "<h2>" . $deleteLogPage->getName()->escaped() . "</h2>\n" );
-                       LogEventsList::showLogExtract(
-                               $output,
-                               'delete',
-                               $this->targetObj,
-                               '', /* user */
-                               [ 'lim' => 25, 'conds' => $qc, 'useMaster' => $this->wasSaved ]
-                       );
-               }
-               # Show relevant lines from the suppression log
-               if ( $user->isAllowed( 'suppressionlog' ) ) {
-                       $suppressLogPage = new LogPage( 'suppress' );
-                       $output->addHTML( "<h2>" . $suppressLogPage->getName()->escaped() . "</h2>\n" );
-                       LogEventsList::showLogExtract(
-                               $output,
-                               'suppress',
-                               $this->targetObj,
-                               '',
-                               [ 'lim' => 25, 'conds' => $qc, 'useMaster' => $this->wasSaved ]
-                       );
-               }
-       }
-
-       /**
-        * Show some useful links in the subtitle
-        */
-       protected function showConvenienceLinks() {
-               $linkRenderer = $this->getLinkRenderer();
-               # Give a link to the logs/hist for this page
-               if ( $this->targetObj ) {
-                       // Also set header tabs to be for the target.
-                       $this->getSkin()->setRelevantTitle( $this->targetObj );
-
-                       $links = [];
-                       $links[] = $linkRenderer->makeKnownLink(
-                               SpecialPage::getTitleFor( 'Log' ),
-                               $this->msg( 'viewpagelogs' )->text(),
-                               [],
-                               [ 'page' => $this->targetObj->getPrefixedText() ]
-                       );
-                       if ( !$this->targetObj->isSpecialPage() ) {
-                               # Give a link to the page history
-                               $links[] = $linkRenderer->makeKnownLink(
-                                       $this->targetObj,
-                                       $this->msg( 'pagehist' )->text(),
-                                       [],
-                                       [ 'action' => 'history' ]
-                               );
-                               # Link to deleted edits
-                               if ( $this->getUser()->isAllowed( 'undelete' ) ) {
-                                       $undelete = SpecialPage::getTitleFor( 'Undelete' );
-                                       $links[] = $linkRenderer->makeKnownLink(
-                                               $undelete,
-                                               $this->msg( 'deletedhist' )->text(),
-                                               [],
-                                               [ 'target' => $this->targetObj->getPrefixedDBkey() ]
-                                       );
-                               }
-                       }
-                       # Logs themselves don't have histories or archived revisions
-                       $this->getOutput()->addSubtitle( $this->getLanguage()->pipeList( $links ) );
-               }
-       }
-
-       /**
-        * Get the condition used for fetching log snippets
-        * @return array
-        */
-       protected function getLogQueryCond() {
-               $conds = [];
-               // Revision delete logs for these item
-               $conds['log_type'] = [ 'delete', 'suppress' ];
-               $conds['log_action'] = $this->getList()->getLogAction();
-               $conds['ls_field'] = RevisionDeleter::getRelationType( $this->typeName );
-               $conds['ls_value'] = $this->ids;
-
-               return $conds;
-       }
-
-       /**
-        * Show a deleted file version requested by the visitor.
-        * @todo Mostly copied from Special:Undelete. Refactor.
-        * @param string $archiveName
-        * @throws MWException
-        * @throws PermissionsError
-        */
-       protected function tryShowFile( $archiveName ) {
-               $repo = RepoGroup::singleton()->getLocalRepo();
-               $oimage = $repo->newFromArchiveName( $this->targetObj, $archiveName );
-               $oimage->load();
-               // Check if user is allowed to see this file
-               if ( !$oimage->exists() ) {
-                       $this->getOutput()->addWikiMsg( 'revdelete-no-file' );
-
-                       return;
-               }
-               $user = $this->getUser();
-               if ( !$oimage->userCan( File::DELETED_FILE, $user ) ) {
-                       if ( $oimage->isDeleted( File::DELETED_RESTRICTED ) ) {
-                               throw new PermissionsError( 'suppressrevision' );
-                       } else {
-                               throw new PermissionsError( 'deletedtext' );
-                       }
-               }
-               if ( !$user->matchEditToken( $this->token, $archiveName ) ) {
-                       $lang = $this->getLanguage();
-                       $this->getOutput()->addWikiMsg( 'revdelete-show-file-confirm',
-                               $this->targetObj->getText(),
-                               $lang->userDate( $oimage->getTimestamp(), $user ),
-                               $lang->userTime( $oimage->getTimestamp(), $user ) );
-                       $this->getOutput()->addHTML(
-                               Xml::openElement( 'form', [
-                                       'method' => 'POST',
-                                       'action' => $this->getPageTitle()->getLocalURL( [
-                                                       'target' => $this->targetObj->getPrefixedDBkey(),
-                                                       'file' => $archiveName,
-                                                       'token' => $user->getEditToken( $archiveName ),
-                                               ] )
-                                       ]
-                               ) .
-                               Xml::submitButton( $this->msg( 'revdelete-show-file-submit' )->text() ) .
-                               '</form>'
-                       );
-
-                       return;
-               }
-               $this->getOutput()->disable();
-               # We mustn't allow the output to be CDN cached, otherwise
-               # if an admin previews a deleted image, and it's cached, then
-               # a user without appropriate permissions can toddle off and
-               # nab the image, and CDN will serve it
-               $this->getRequest()->response()->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
-               $this->getRequest()->response()->header(
-                       'Cache-Control: no-cache, no-store, max-age=0, must-revalidate'
-               );
-               $this->getRequest()->response()->header( 'Pragma: no-cache' );
-
-               $key = $oimage->getStorageKey();
-               $path = $repo->getZonePath( 'deleted' ) . '/' . $repo->getDeletedHashPath( $key ) . $key;
-               $repo->streamFile( $path );
-       }
-
-       /**
-        * Get the list object for this request
-        * @return RevDelList
-        */
-       protected function getList() {
-               if ( is_null( $this->revDelList ) ) {
-                       $this->revDelList = RevisionDeleter::createList(
-                               $this->typeName, $this->getContext(), $this->targetObj, $this->ids
-                       );
-               }
-
-               return $this->revDelList;
-       }
-
-       /**
-        * Show a list of items that we will operate on, and show a form with checkboxes
-        * which will allow the user to choose new visibility settings.
-        */
-       protected function showForm() {
-               $userAllowed = true;
-
-               // Messages: revdelete-selected-text, revdelete-selected-file, logdelete-selected
-               $out = $this->getOutput();
-               $out->wrapWikiMsg( "<strong>$1</strong>", [ $this->typeLabels['selected'],
-                       $this->getLanguage()->formatNum( count( $this->ids ) ), $this->targetObj->getPrefixedText() ] );
-
-               $this->addHelpLink( 'Help:RevisionDelete' );
-               $out->addHTML( "<ul>" );
-
-               $numRevisions = 0;
-               // Live revisions...
-               $list = $this->getList();
-               for ( $list->reset(); $list->current(); $list->next() ) {
-                       $item = $list->current();
-
-                       if ( !$item->canView() ) {
-                               if ( !$this->submitClicked ) {
-                                       throw new PermissionsError( 'suppressrevision' );
-                               }
-                               $userAllowed = false;
-                       }
-
-                       $numRevisions++;
-                       $out->addHTML( $item->getHTML() );
-               }
-
-               if ( !$numRevisions ) {
-                       throw new ErrorPageError( 'revdelete-nooldid-title', 'revdelete-nooldid-text' );
-               }
-
-               $out->addHTML( "</ul>" );
-               // Explanation text
-               $this->addUsageText();
-
-               // Normal sysops can always see what they did, but can't always change it
-               if ( !$userAllowed ) {
-                       return;
-               }
-
-               // Show form if the user can submit
-               if ( $this->mIsAllowed ) {
-                       $out->addModules( [ 'mediawiki.special.revisionDelete' ] );
-                       $out->addModuleStyles( [ 'mediawiki.special',
-                               'mediawiki.interface.helpers.styles' ] );
-
-                       $form = Xml::openElement( 'form', [ 'method' => 'post',
-                                       'action' => $this->getPageTitle()->getLocalURL( [ 'action' => 'submit' ] ),
-                                       'id' => 'mw-revdel-form-revisions' ] ) .
-                               Xml::fieldset( $this->msg( 'revdelete-legend' )->text() ) .
-                               $this->buildCheckBoxes() .
-                               Xml::openElement( 'table' ) .
-                               "<tr>\n" .
-                                       '<td class="mw-label">' .
-                                               Xml::label( $this->msg( 'revdelete-log' )->text(), 'wpRevDeleteReasonList' ) .
-                                       '</td>' .
-                                       '<td class="mw-input">' .
-                                               Xml::listDropDown( 'wpRevDeleteReasonList',
-                                                       $this->msg( 'revdelete-reason-dropdown' )->inContentLanguage()->text(),
-                                                       $this->msg( 'revdelete-reasonotherlist' )->inContentLanguage()->text(),
-                                                       $this->getRequest()->getText( 'wpRevDeleteReasonList', 'other' ), 'wpReasonDropDown'
-                                               ) .
-                                       '</td>' .
-                               "</tr><tr>\n" .
-                                       '<td class="mw-label">' .
-                                               Xml::label( $this->msg( 'revdelete-otherreason' )->text(), 'wpReason' ) .
-                                       '</td>' .
-                                       '<td class="mw-input">' .
-                                               Xml::input( 'wpReason', 60, $this->otherReason, [
-                                                       'id' => 'wpReason',
-                                                       // 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 'wpRevDeleteReasonList' value.
-                                                       'maxlength' => CommentStore::COMMENT_CHARACTER_LIMIT - 155,
-                                               ] ) .
-                                       '</td>' .
-                               "</tr><tr>\n" .
-                                       '<td></td>' .
-                                       '<td class="mw-submit">' .
-                                               Xml::submitButton( $this->msg( 'revdelete-submit', $numRevisions )->text(),
-                                                       [ 'name' => 'wpSubmit' ] ) .
-                                       '</td>' .
-                               "</tr>\n" .
-                               Xml::closeElement( 'table' ) .
-                               Html::hidden( 'wpEditToken', $this->getUser()->getEditToken() ) .
-                               Html::hidden( 'target', $this->targetObj->getPrefixedText() ) .
-                               Html::hidden( 'type', $this->typeName ) .
-                               Html::hidden( 'ids', implode( ',', $this->ids ) ) .
-                               Xml::closeElement( 'fieldset' ) . "\n" .
-                               Xml::closeElement( 'form' ) . "\n";
-                       // Show link to edit the dropdown reasons
-                       if ( $this->getUser()->isAllowed( 'editinterface' ) ) {
-                               $link = $this->getLinkRenderer()->makeKnownLink(
-                                       $this->msg( 'revdelete-reason-dropdown' )->inContentLanguage()->getTitle(),
-                                       $this->msg( 'revdelete-edit-reasonlist' )->text(),
-                                       [],
-                                       [ 'action' => 'edit' ]
-                               );
-                               $form .= Xml::tags( 'p', [ 'class' => 'mw-revdel-editreasons' ], $link ) . "\n";
-                       }
-               } else {
-                       $form = '';
-               }
-               $out->addHTML( $form );
-       }
-
-       /**
-        * Show some introductory text
-        * @todo FIXME: Wikimedia-specific policy text
-        */
-       protected function addUsageText() {
-               // Messages: revdelete-text-text, revdelete-text-file, logdelete-text
-               $this->getOutput()->wrapWikiMsg(
-                       "<strong>$1</strong>\n$2", $this->typeLabels['text'],
-                       'revdelete-text-others'
-               );
-
-               if ( $this->getUser()->isAllowed( 'suppressrevision' ) ) {
-                       $this->getOutput()->addWikiMsg( 'revdelete-suppress-text' );
-               }
-
-               if ( $this->mIsAllowed ) {
-                       $this->getOutput()->addWikiMsg( 'revdelete-confirm' );
-               }
-       }
-
-       /**
-        * @return string HTML
-        */
-       protected function buildCheckBoxes() {
-               $html = '<table>';
-               // If there is just one item, use checkboxes
-               $list = $this->getList();
-               if ( $list->length() == 1 ) {
-                       $list->reset();
-                       $bitfield = $list->current()->getBits(); // existing field
-
-                       if ( $this->submitClicked ) {
-                               $bitfield = RevisionDeleter::extractBitfield( $this->extractBitParams(), $bitfield );
-                       }
-
-                       foreach ( $this->checks as $item ) {
-                               // Messages: revdelete-hide-text, revdelete-hide-image, revdelete-hide-name,
-                               // revdelete-hide-comment, revdelete-hide-user, revdelete-hide-restricted
-                               list( $message, $name, $field ) = $item;
-                               $innerHTML = Xml::checkLabel(
-                                       $this->msg( $message )->text(),
-                                       $name,
-                                       $name,
-                                       $bitfield & $field
-                               );
-
-                               if ( $field == Revision::DELETED_RESTRICTED ) {
-                                       $innerHTML = "<b>$innerHTML</b>";
-                               }
-
-                               $line = Xml::tags( 'td', [ 'class' => 'mw-input' ], $innerHTML );
-                               $html .= "<tr>$line</tr>\n";
-                       }
-               } else {
-                       // Otherwise, use tri-state radios
-                       $html .= '<tr>';
-                       $html .= '<th class="mw-revdel-checkbox">'
-                               . $this->msg( 'revdelete-radio-same' )->escaped() . '</th>';
-                       $html .= '<th class="mw-revdel-checkbox">'
-                               . $this->msg( 'revdelete-radio-unset' )->escaped() . '</th>';
-                       $html .= '<th class="mw-revdel-checkbox">'
-                               . $this->msg( 'revdelete-radio-set' )->escaped() . '</th>';
-                       $html .= "<th></th></tr>\n";
-                       foreach ( $this->checks as $item ) {
-                               // Messages: revdelete-hide-text, revdelete-hide-image, revdelete-hide-name,
-                               // revdelete-hide-comment, revdelete-hide-user, revdelete-hide-restricted
-                               list( $message, $name, $field ) = $item;
-                               // If there are several items, use third state by default...
-                               if ( $this->submitClicked ) {
-                                       $selected = $this->getRequest()->getInt( $name, 0 /* unchecked */ );
-                               } else {
-                                       $selected = -1; // use existing field
-                               }
-                               $line = '<td class="mw-revdel-checkbox">' . Xml::radio( $name, -1, $selected == -1 ) . '</td>';
-                               $line .= '<td class="mw-revdel-checkbox">' . Xml::radio( $name, 0, $selected == 0 ) . '</td>';
-                               $line .= '<td class="mw-revdel-checkbox">' . Xml::radio( $name, 1, $selected == 1 ) . '</td>';
-                               $label = $this->msg( $message )->escaped();
-                               if ( $field == Revision::DELETED_RESTRICTED ) {
-                                       $label = "<b>$label</b>";
-                               }
-                               $line .= "<td>$label</td>";
-                               $html .= "<tr>$line</tr>\n";
-                       }
-               }
-
-               $html .= '</table>';
-
-               return $html;
-       }
-
-       /**
-        * UI entry point for form submission.
-        * @throws PermissionsError
-        * @return bool
-        */
-       protected function submit() {
-               # Check edit token on submission
-               $token = $this->getRequest()->getVal( 'wpEditToken' );
-               if ( $this->submitClicked && !$this->getUser()->matchEditToken( $token ) ) {
-                       $this->getOutput()->addWikiMsg( 'sessionfailure' );
-
-                       return false;
-               }
-               $bitParams = $this->extractBitParams();
-               // from dropdown
-               $listReason = $this->getRequest()->getText( 'wpRevDeleteReasonList', 'other' );
-               $comment = $listReason;
-               if ( $comment === 'other' ) {
-                       $comment = $this->otherReason;
-               } elseif ( $this->otherReason !== '' ) {
-                       // Entry from drop down menu + additional comment
-                       $comment .= $this->msg( 'colon-separator' )->inContentLanguage()->text()
-                               . $this->otherReason;
-               }
-               # Can the user set this field?
-               if ( $bitParams[Revision::DELETED_RESTRICTED] == 1
-                       && !$this->getUser()->isAllowed( 'suppressrevision' )
-               ) {
-                       throw new PermissionsError( 'suppressrevision' );
-               }
-               # If the save went through, go to success message...
-               $status = $this->save( $bitParams, $comment );
-               if ( $status->isGood() ) {
-                       $this->success();
-
-                       return true;
-               } else {
-                       # ...otherwise, bounce back to form...
-                       $this->failure( $status );
-               }
-
-               return false;
-       }
-
-       /**
-        * Report that the submit operation succeeded
-        */
-       protected function success() {
-               // Messages: revdelete-success, logdelete-success
-               $this->getOutput()->setPageTitle( $this->msg( 'actioncomplete' ) );
-               $this->getOutput()->wrapWikiMsg(
-                       "<div class=\"successbox\">\n$1\n</div>",
-                       $this->typeLabels['success']
-               );
-               $this->wasSaved = true;
-               $this->revDelList->reloadFromMaster();
-               $this->showForm();
-       }
-
-       /**
-        * Report that the submit operation failed
-        * @param Status $status
-        */
-       protected function failure( $status ) {
-               // Messages: revdelete-failure, logdelete-failure
-               $this->getOutput()->setPageTitle( $this->msg( 'actionfailed' ) );
-               $this->getOutput()->wrapWikiTextAsInterface(
-                       'errorbox',
-                       $status->getWikiText( $this->typeLabels['failure'] )
-               );
-               $this->showForm();
-       }
-
-       /**
-        * Put together an array that contains -1, 0, or the *_deleted const for each bit
-        *
-        * @return array
-        */
-       protected function extractBitParams() {
-               $bitfield = [];
-               foreach ( $this->checks as $item ) {
-                       list( /* message */, $name, $field ) = $item;
-                       $val = $this->getRequest()->getInt( $name, 0 /* unchecked */ );
-                       if ( $val < -1 || $val > 1 ) {
-                               $val = -1; // -1 for existing value
-                       }
-                       $bitfield[$field] = $val;
-               }
-               if ( !isset( $bitfield[Revision::DELETED_RESTRICTED] ) ) {
-                       $bitfield[Revision::DELETED_RESTRICTED] = 0;
-               }
-
-               return $bitfield;
-       }
-
-       /**
-        * Do the write operations. Simple wrapper for RevDel*List::setVisibility().
-        * @param array $bitPars ExtractBitParams() bitfield array
-        * @param string $reason
-        * @return Status
-        */
-       protected function save( array $bitPars, $reason ) {
-               return $this->getList()->setVisibility(
-                       [ 'value' => $bitPars, 'comment' => $reason ]
-               );
-       }
-
-       protected function getGroupName() {
-               return 'pagetools';
-       }
-}
index 391d9ab..2632092 100644 (file)
@@ -206,6 +206,7 @@ class SpecialVersion extends SpecialPage {
                        'Victor Vasiliev', 'Rotem Liss', 'Platonides', 'Antoine Musso',
                        'Timo Tijhof', 'Daniel Kinzler', 'Jeroen De Dauw', 'Brad Jorsch',
                        'Bartosz Dziewoński', 'Ed Sanders', 'Moriel Schottlender',
+                       'Kunal Mehta', 'James D. Forrester', 'Brian Wolff', 'Adam Shorland',
                        $othersLink, $translatorsLink
                ];
 
index 6defc9d..c6d9fc7 100644 (file)
@@ -699,15 +699,16 @@ class SpecialWatchlist extends ChangesListSpecialPage {
                if ( $this->isStructuredFilterUiEnabled() ) {
                        $rcfilterContainer = Html::element(
                                'div',
-                               [ 'class' => 'rcfilters-container' ]
+                               // TODO: Remove deprecated rcfilters-container class
+                               [ 'class' => 'rcfilters-container mw-rcfilters-container' ]
                        );
 
                        $loadingContainer = Html::rawElement(
                                'div',
-                               [ 'class' => 'rcfilters-spinner' ],
+                               [ 'class' => 'mw-rcfilters-spinner' ],
                                Html::element(
                                        'div',
-                                       [ 'class' => 'rcfilters-spinner-bounce' ]
+                                       [ 'class' => 'mw-rcfilters-spinner-bounce' ]
                                )
                        );
 
@@ -715,7 +716,8 @@ class SpecialWatchlist extends ChangesListSpecialPage {
                        $this->getOutput()->addHTML(
                                Html::rawElement(
                                        'div',
-                                       [ 'class' => 'rcfilters-head' ],
+                                       // TODO: Remove deprecated rcfilters-head class
+                                       [ 'class' => 'rcfilters-head mw-rcfilters-head' ],
                                        $rcfilterContainer . $form
                                )
                        );
diff --git a/includes/specials/SpecialWhatLinksHere.php b/includes/specials/SpecialWhatLinksHere.php
new file mode 100644 (file)
index 0000000..18c10bf
--- /dev/null
@@ -0,0 +1,591 @@
+<?php
+/**
+ * Implements Special:Whatlinkshere
+ *
+ * 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
+ * @todo Use some variant of Pager or something; the pagination here is lousy.
+ */
+
+use Wikimedia\Rdbms\IDatabase;
+
+/**
+ * Implements Special:Whatlinkshere
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialWhatLinksHere extends IncludableSpecialPage {
+       /** @var FormOptions */
+       protected $opts;
+
+       protected $selfTitle;
+
+       /** @var Title */
+       protected $target;
+
+       protected $limits = [ 20, 50, 100, 250, 500 ];
+
+       public function __construct() {
+               parent::__construct( 'Whatlinkshere' );
+       }
+
+       function execute( $par ) {
+               $out = $this->getOutput();
+
+               $this->setHeaders();
+               $this->outputHeader();
+               $this->addHelpLink( 'Help:What links here' );
+
+               $opts = new FormOptions();
+
+               $opts->add( 'target', '' );
+               $opts->add( 'namespace', '', FormOptions::INTNULL );
+               $opts->add( 'limit', $this->getConfig()->get( 'QueryPageDefaultLimit' ) );
+               $opts->add( 'from', 0 );
+               $opts->add( 'back', 0 );
+               $opts->add( 'hideredirs', false );
+               $opts->add( 'hidetrans', false );
+               $opts->add( 'hidelinks', false );
+               $opts->add( 'hideimages', false );
+               $opts->add( 'invert', false );
+
+               $opts->fetchValuesFromRequest( $this->getRequest() );
+               $opts->validateIntBounds( 'limit', 0, 5000 );
+
+               // Give precedence to subpage syntax
+               if ( $par !== null ) {
+                       $opts->setValue( 'target', $par );
+               }
+
+               // Bind to member variable
+               $this->opts = $opts;
+
+               $this->target = Title::newFromText( $opts->getValue( 'target' ) );
+               if ( !$this->target ) {
+                       if ( !$this->including() ) {
+                               $out->addHTML( $this->whatlinkshereForm() );
+                       }
+
+                       return;
+               }
+
+               $this->getSkin()->setRelevantTitle( $this->target );
+
+               $this->selfTitle = $this->getPageTitle( $this->target->getPrefixedDBkey() );
+
+               $out->setPageTitle( $this->msg( 'whatlinkshere-title', $this->target->getPrefixedText() ) );
+               $out->addBacklinkSubtitle( $this->target );
+               $this->showIndirectLinks(
+                       0,
+                       $this->target,
+                       $opts->getValue( 'limit' ),
+                       $opts->getValue( 'from' ),
+                       $opts->getValue( 'back' )
+               );
+       }
+
+       /**
+        * @param int $level Recursion level
+        * @param Title $target Target title
+        * @param int $limit Number of entries to display
+        * @param int $from Display from this article ID (default: 0)
+        * @param int $back Display from this article ID at backwards scrolling (default: 0)
+        */
+       function showIndirectLinks( $level, $target, $limit, $from = 0, $back = 0 ) {
+               $out = $this->getOutput();
+               $dbr = wfGetDB( DB_REPLICA );
+
+               $hidelinks = $this->opts->getValue( 'hidelinks' );
+               $hideredirs = $this->opts->getValue( 'hideredirs' );
+               $hidetrans = $this->opts->getValue( 'hidetrans' );
+               $hideimages = $target->getNamespace() != NS_FILE || $this->opts->getValue( 'hideimages' );
+
+               $fetchlinks = ( !$hidelinks || !$hideredirs );
+
+               // Build query conds in concert for all three tables...
+               $conds['pagelinks'] = [
+                       'pl_namespace' => $target->getNamespace(),
+                       'pl_title' => $target->getDBkey(),
+               ];
+               $conds['templatelinks'] = [
+                       'tl_namespace' => $target->getNamespace(),
+                       'tl_title' => $target->getDBkey(),
+               ];
+               $conds['imagelinks'] = [
+                       'il_to' => $target->getDBkey(),
+               ];
+
+               $namespace = $this->opts->getValue( 'namespace' );
+               $invert = $this->opts->getValue( 'invert' );
+               $nsComparison = ( $invert ? '!= ' : '= ' ) . $dbr->addQuotes( $namespace );
+               if ( is_int( $namespace ) ) {
+                       $conds['pagelinks'][] = "pl_from_namespace $nsComparison";
+                       $conds['templatelinks'][] = "tl_from_namespace $nsComparison";
+                       $conds['imagelinks'][] = "il_from_namespace $nsComparison";
+               }
+
+               if ( $from ) {
+                       $conds['templatelinks'][] = "tl_from >= $from";
+                       $conds['pagelinks'][] = "pl_from >= $from";
+                       $conds['imagelinks'][] = "il_from >= $from";
+               }
+
+               if ( $hideredirs ) {
+                       $conds['pagelinks']['rd_from'] = null;
+               } elseif ( $hidelinks ) {
+                       $conds['pagelinks'][] = 'rd_from is NOT NULL';
+               }
+
+               $queryFunc = function ( IDatabase $dbr, $table, $fromCol ) use (
+                       $conds, $target, $limit
+               ) {
+                       // Read an extra row as an at-end check
+                       $queryLimit = $limit + 1;
+                       $on = [
+                               "rd_from = $fromCol",
+                               'rd_title' => $target->getDBkey(),
+                               'rd_interwiki = ' . $dbr->addQuotes( '' ) . ' OR rd_interwiki IS NULL'
+                       ];
+                       $on['rd_namespace'] = $target->getNamespace();
+                       // Inner LIMIT is 2X in case of stale backlinks with wrong namespaces
+                       $subQuery = $dbr->buildSelectSubquery(
+                               [ $table, 'redirect', 'page' ],
+                               [ $fromCol, 'rd_from' ],
+                               $conds[$table],
+                               __CLASS__ . '::showIndirectLinks',
+                               // Force JOIN order per T106682 to avoid large filesorts
+                               [ 'ORDER BY' => $fromCol, 'LIMIT' => 2 * $queryLimit, 'STRAIGHT_JOIN' ],
+                               [
+                                       'page' => [ 'JOIN', "$fromCol = page_id" ],
+                                       'redirect' => [ 'LEFT JOIN', $on ]
+                               ]
+                       );
+                       return $dbr->select(
+                               [ 'page', 'temp_backlink_range' => $subQuery ],
+                               [ 'page_id', 'page_namespace', 'page_title', 'rd_from', 'page_is_redirect' ],
+                               [],
+                               __CLASS__ . '::showIndirectLinks',
+                               [ 'ORDER BY' => 'page_id', 'LIMIT' => $queryLimit ],
+                               [ 'page' => [ 'JOIN', "$fromCol = page_id" ] ]
+                       );
+               };
+
+               if ( $fetchlinks ) {
+                       $plRes = $queryFunc( $dbr, 'pagelinks', 'pl_from' );
+               }
+
+               if ( !$hidetrans ) {
+                       $tlRes = $queryFunc( $dbr, 'templatelinks', 'tl_from' );
+               }
+
+               if ( !$hideimages ) {
+                       $ilRes = $queryFunc( $dbr, 'imagelinks', 'il_from' );
+               }
+
+               if ( ( !$fetchlinks || !$plRes->numRows() )
+                       && ( $hidetrans || !$tlRes->numRows() )
+                       && ( $hideimages || !$ilRes->numRows() )
+               ) {
+                       if ( $level == 0 && !$this->including() ) {
+                               $out->addHTML( $this->whatlinkshereForm() );
+
+                               // Show filters only if there are links
+                               if ( $hidelinks || $hidetrans || $hideredirs || $hideimages ) {
+                                       $out->addHTML( $this->getFilterPanel() );
+                               }
+                               $msgKey = is_int( $namespace ) ? 'nolinkshere-ns' : 'nolinkshere';
+                               $link = $this->getLinkRenderer()->makeLink(
+                                       $this->target,
+                                       null,
+                                       [],
+                                       $this->target->isRedirect() ? [ 'redirect' => 'no' ] : []
+                               );
+
+                               $errMsg = $this->msg( $msgKey )
+                                       ->params( $this->target->getPrefixedText() )
+                                       ->rawParams( $link )
+                                       ->parseAsBlock();
+                               $out->addHTML( $errMsg );
+                               $out->setStatusCode( 404 );
+                       }
+
+                       return;
+               }
+
+               // Read the rows into an array and remove duplicates
+               // templatelinks comes second so that the templatelinks row overwrites the
+               // pagelinks row, so we get (inclusion) rather than nothing
+               if ( $fetchlinks ) {
+                       foreach ( $plRes as $row ) {
+                               $row->is_template = 0;
+                               $row->is_image = 0;
+                               $rows[$row->page_id] = $row;
+                       }
+               }
+               if ( !$hidetrans ) {
+                       foreach ( $tlRes as $row ) {
+                               $row->is_template = 1;
+                               $row->is_image = 0;
+                               $rows[$row->page_id] = $row;
+                       }
+               }
+               if ( !$hideimages ) {
+                       foreach ( $ilRes as $row ) {
+                               $row->is_template = 0;
+                               $row->is_image = 1;
+                               $rows[$row->page_id] = $row;
+                       }
+               }
+
+               // Sort by key and then change the keys to 0-based indices
+               ksort( $rows );
+               $rows = array_values( $rows );
+
+               $numRows = count( $rows );
+
+               // Work out the start and end IDs, for prev/next links
+               if ( $numRows > $limit ) {
+                       // More rows available after these ones
+                       // Get the ID from the last row in the result set
+                       $nextId = $rows[$limit]->page_id;
+                       // Remove undisplayed rows
+                       $rows = array_slice( $rows, 0, $limit );
+               } else {
+                       // No more rows after
+                       $nextId = false;
+               }
+               $prevId = $from;
+
+               // use LinkBatch to make sure, that all required data (associated with Titles)
+               // is loaded in one query
+               $lb = new LinkBatch();
+               foreach ( $rows as $row ) {
+                       $lb->add( $row->page_namespace, $row->page_title );
+               }
+               $lb->execute();
+
+               if ( $level == 0 && !$this->including() ) {
+                       $out->addHTML( $this->whatlinkshereForm() );
+                       $out->addHTML( $this->getFilterPanel() );
+
+                       $link = $this->getLinkRenderer()->makeLink(
+                               $this->target,
+                               null,
+                               [],
+                               $this->target->isRedirect() ? [ 'redirect' => 'no' ] : []
+                       );
+
+                       $msg = $this->msg( 'linkshere' )
+                               ->params( $this->target->getPrefixedText() )
+                               ->rawParams( $link )
+                               ->parseAsBlock();
+                       $out->addHTML( $msg );
+
+                       $prevnext = $this->getPrevNext( $prevId, $nextId );
+                       $out->addHTML( $prevnext );
+               }
+               $out->addHTML( $this->listStart( $level ) );
+               foreach ( $rows as $row ) {
+                       $nt = Title::makeTitle( $row->page_namespace, $row->page_title );
+
+                       if ( $row->rd_from && $level < 2 ) {
+                               $out->addHTML( $this->listItem( $row, $nt, $target, true ) );
+                               $this->showIndirectLinks(
+                                       $level + 1,
+                                       $nt,
+                                       $this->getConfig()->get( 'MaxRedirectLinksRetrieved' )
+                               );
+                               $out->addHTML( Xml::closeElement( 'li' ) );
+                       } else {
+                               $out->addHTML( $this->listItem( $row, $nt, $target ) );
+                       }
+               }
+
+               $out->addHTML( $this->listEnd() );
+
+               if ( $level == 0 && !$this->including() ) {
+                       $out->addHTML( $prevnext );
+               }
+       }
+
+       protected function listStart( $level ) {
+               return Xml::openElement( 'ul', ( $level ? [] : [ 'id' => 'mw-whatlinkshere-list' ] ) );
+       }
+
+       protected function listItem( $row, $nt, $target, $notClose = false ) {
+               $dirmark = $this->getLanguage()->getDirMark();
+
+               # local message cache
+               static $msgcache = null;
+               if ( $msgcache === null ) {
+                       static $msgs = [ 'isredirect', 'istemplate', 'semicolon-separator',
+                               'whatlinkshere-links', 'isimage', 'editlink' ];
+                       $msgcache = [];
+                       foreach ( $msgs as $msg ) {
+                               $msgcache[$msg] = $this->msg( $msg )->escaped();
+                       }
+               }
+
+               if ( $row->rd_from ) {
+                       $query = [ 'redirect' => 'no' ];
+               } else {
+                       $query = [];
+               }
+
+               $link = $this->getLinkRenderer()->makeKnownLink(
+                       $nt,
+                       null,
+                       $row->page_is_redirect ? [ 'class' => 'mw-redirect' ] : [],
+                       $query
+               );
+
+               // Display properties (redirect or template)
+               $propsText = '';
+               $props = [];
+               if ( $row->rd_from ) {
+                       $props[] = $msgcache['isredirect'];
+               }
+               if ( $row->is_template ) {
+                       $props[] = $msgcache['istemplate'];
+               }
+               if ( $row->is_image ) {
+                       $props[] = $msgcache['isimage'];
+               }
+
+               Hooks::run( 'WhatLinksHereProps', [ $row, $nt, $target, &$props ] );
+
+               if ( count( $props ) ) {
+                       $propsText = $this->msg( 'parentheses' )
+                               ->rawParams( implode( $msgcache['semicolon-separator'], $props ) )->escaped();
+               }
+
+               # Space for utilities links, with a what-links-here link provided
+               $wlhLink = $this->wlhLink( $nt, $msgcache['whatlinkshere-links'], $msgcache['editlink'] );
+               $wlh = Xml::wrapClass(
+                       $this->msg( 'parentheses' )->rawParams( $wlhLink )->escaped(),
+                       'mw-whatlinkshere-tools'
+               );
+
+               return $notClose ?
+                       Xml::openElement( 'li' ) . "$link $propsText $dirmark $wlh\n" :
+                       Xml::tags( 'li', null, "$link $propsText $dirmark $wlh" ) . "\n";
+       }
+
+       protected function listEnd() {
+               return Xml::closeElement( 'ul' );
+       }
+
+       protected function wlhLink( Title $target, $text, $editText ) {
+               static $title = null;
+               if ( $title === null ) {
+                       $title = $this->getPageTitle();
+               }
+
+               $linkRenderer = $this->getLinkRenderer();
+
+               if ( $text !== null ) {
+                       $text = new HtmlArmor( $text );
+               }
+
+               // always show a "<- Links" link
+               $links = [
+                       'links' => $linkRenderer->makeKnownLink(
+                               $title,
+                               $text,
+                               [],
+                               [ 'target' => $target->getPrefixedText() ]
+                       ),
+               ];
+
+               // if the page is editable, add an edit link
+               if (
+                       // check user permissions
+                       $this->getUser()->isAllowed( 'edit' ) &&
+                       // check, if the content model is editable through action=edit
+                       ContentHandler::getForTitle( $target )->supportsDirectEditing()
+               ) {
+                       if ( $editText !== null ) {
+                               $editText = new HtmlArmor( $editText );
+                       }
+
+                       $links['edit'] = $linkRenderer->makeKnownLink(
+                               $target,
+                               $editText,
+                               [],
+                               [ 'action' => 'edit' ]
+                       );
+               }
+
+               // build the links html
+               return $this->getLanguage()->pipeList( $links );
+       }
+
+       function makeSelfLink( $text, $query ) {
+               if ( $text !== null ) {
+                       $text = new HtmlArmor( $text );
+               }
+
+               return $this->getLinkRenderer()->makeKnownLink(
+                       $this->selfTitle,
+                       $text,
+                       [],
+                       $query
+               );
+       }
+
+       function getPrevNext( $prevId, $nextId ) {
+               $currentLimit = $this->opts->getValue( 'limit' );
+               $prev = $this->msg( 'whatlinkshere-prev' )->numParams( $currentLimit )->escaped();
+               $next = $this->msg( 'whatlinkshere-next' )->numParams( $currentLimit )->escaped();
+
+               $changed = $this->opts->getChangedValues();
+               unset( $changed['target'] ); // Already in the request title
+
+               if ( $prevId != 0 ) {
+                       $overrides = [ 'from' => $this->opts->getValue( 'back' ) ];
+                       $prev = $this->makeSelfLink( $prev, array_merge( $changed, $overrides ) );
+               }
+               if ( $nextId != 0 ) {
+                       $overrides = [ 'from' => $nextId, 'back' => $prevId ];
+                       $next = $this->makeSelfLink( $next, array_merge( $changed, $overrides ) );
+               }
+
+               $limitLinks = [];
+               $lang = $this->getLanguage();
+               foreach ( $this->limits as $limit ) {
+                       $prettyLimit = htmlspecialchars( $lang->formatNum( $limit ) );
+                       $overrides = [ 'limit' => $limit ];
+                       $limitLinks[] = $this->makeSelfLink( $prettyLimit, array_merge( $changed, $overrides ) );
+               }
+
+               $nums = $lang->pipeList( $limitLinks );
+
+               return $this->msg( 'viewprevnext' )->rawParams( $prev, $next, $nums )->escaped();
+       }
+
+       function whatlinkshereForm() {
+               // We get nicer value from the title object
+               $this->opts->consumeValue( 'target' );
+               // Reset these for new requests
+               $this->opts->consumeValues( [ 'back', 'from' ] );
+
+               $target = $this->target ? $this->target->getPrefixedText() : '';
+               $namespace = $this->opts->consumeValue( 'namespace' );
+               $nsinvert = $this->opts->consumeValue( 'invert' );
+
+               # Build up the form
+               $f = Xml::openElement( 'form', [ 'action' => wfScript() ] );
+
+               # Values that should not be forgotten
+               $f .= Html::hidden( 'title', $this->getPageTitle()->getPrefixedText() );
+               foreach ( $this->opts->getUnconsumedValues() as $name => $value ) {
+                       $f .= Html::hidden( $name, $value );
+               }
+
+               $f .= Xml::fieldset( $this->msg( 'whatlinkshere' )->text() );
+
+               # Target input (.mw-searchInput enables suggestions)
+               $f .= Xml::inputLabel( $this->msg( 'whatlinkshere-page' )->text(), 'target',
+                       'mw-whatlinkshere-target', 40, $target, [ 'class' => 'mw-searchInput' ] );
+
+               $f .= ' ';
+
+               # Namespace selector
+               $f .= Html::namespaceSelector(
+                       [
+                               'selected' => $namespace,
+                               'all' => '',
+                               'label' => $this->msg( 'namespace' )->text(),
+                               'in-user-lang' => true,
+                       ], [
+                               'name' => 'namespace',
+                               'id' => 'namespace',
+                               'class' => 'namespaceselector',
+                       ]
+               );
+
+               $f .= "\u{00A0}" .
+                       Xml::checkLabel(
+                               $this->msg( 'invert' )->text(),
+                               'invert',
+                               'nsinvert',
+                               $nsinvert,
+                               [ 'title' => $this->msg( 'tooltip-whatlinkshere-invert' )->text() ]
+                       );
+
+               $f .= ' ';
+
+               # Submit
+               $f .= Xml::submitButton( $this->msg( 'whatlinkshere-submit' )->text() );
+
+               # Close
+               $f .= Xml::closeElement( 'fieldset' ) . Xml::closeElement( 'form' ) . "\n";
+
+               return $f;
+       }
+
+       /**
+        * Create filter panel
+        *
+        * @return string HTML fieldset and filter panel with the show/hide links
+        */
+       function getFilterPanel() {
+               $show = $this->msg( 'show' )->escaped();
+               $hide = $this->msg( 'hide' )->escaped();
+
+               $changed = $this->opts->getChangedValues();
+               unset( $changed['target'] ); // Already in the request title
+
+               $links = [];
+               $types = [ 'hidetrans', 'hidelinks', 'hideredirs' ];
+               if ( $this->target->getNamespace() == NS_FILE ) {
+                       $types[] = 'hideimages';
+               }
+
+               // Combined message keys: 'whatlinkshere-hideredirs', 'whatlinkshere-hidetrans',
+               // 'whatlinkshere-hidelinks', 'whatlinkshere-hideimages'
+               // To be sure they will be found by grep
+               foreach ( $types as $type ) {
+                       $chosen = $this->opts->getValue( $type );
+                       $msg = $chosen ? $show : $hide;
+                       $overrides = [ $type => !$chosen ];
+                       $links[] = $this->msg( "whatlinkshere-{$type}" )->rawParams(
+                               $this->makeSelfLink( $msg, array_merge( $changed, $overrides ) ) )->escaped();
+               }
+
+               return Xml::fieldset(
+                       $this->msg( 'whatlinkshere-filters' )->text(),
+                       $this->getLanguage()->pipeList( $links )
+               );
+       }
+
+       /**
+        * Return an array of subpages beginning with $search that this special page will accept.
+        *
+        * @param string $search Prefix to search for
+        * @param int $limit Maximum number of results to return (usually 10)
+        * @param int $offset Number of results to skip (usually 0)
+        * @return string[] Matching subpages
+        */
+       public function prefixSearchSubpages( $search, $limit, $offset ) {
+               return $this->prefixSearchString( $search, $limit, $offset );
+       }
+
+       protected function getGroupName() {
+               return 'pagetools';
+       }
+}
diff --git a/includes/specials/SpecialWhatlinkshere.php b/includes/specials/SpecialWhatlinkshere.php
deleted file mode 100644 (file)
index 18c10bf..0000000
+++ /dev/null
@@ -1,591 +0,0 @@
-<?php
-/**
- * Implements Special:Whatlinkshere
- *
- * 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
- * @todo Use some variant of Pager or something; the pagination here is lousy.
- */
-
-use Wikimedia\Rdbms\IDatabase;
-
-/**
- * Implements Special:Whatlinkshere
- *
- * @ingroup SpecialPage
- */
-class SpecialWhatLinksHere extends IncludableSpecialPage {
-       /** @var FormOptions */
-       protected $opts;
-
-       protected $selfTitle;
-
-       /** @var Title */
-       protected $target;
-
-       protected $limits = [ 20, 50, 100, 250, 500 ];
-
-       public function __construct() {
-               parent::__construct( 'Whatlinkshere' );
-       }
-
-       function execute( $par ) {
-               $out = $this->getOutput();
-
-               $this->setHeaders();
-               $this->outputHeader();
-               $this->addHelpLink( 'Help:What links here' );
-
-               $opts = new FormOptions();
-
-               $opts->add( 'target', '' );
-               $opts->add( 'namespace', '', FormOptions::INTNULL );
-               $opts->add( 'limit', $this->getConfig()->get( 'QueryPageDefaultLimit' ) );
-               $opts->add( 'from', 0 );
-               $opts->add( 'back', 0 );
-               $opts->add( 'hideredirs', false );
-               $opts->add( 'hidetrans', false );
-               $opts->add( 'hidelinks', false );
-               $opts->add( 'hideimages', false );
-               $opts->add( 'invert', false );
-
-               $opts->fetchValuesFromRequest( $this->getRequest() );
-               $opts->validateIntBounds( 'limit', 0, 5000 );
-
-               // Give precedence to subpage syntax
-               if ( $par !== null ) {
-                       $opts->setValue( 'target', $par );
-               }
-
-               // Bind to member variable
-               $this->opts = $opts;
-
-               $this->target = Title::newFromText( $opts->getValue( 'target' ) );
-               if ( !$this->target ) {
-                       if ( !$this->including() ) {
-                               $out->addHTML( $this->whatlinkshereForm() );
-                       }
-
-                       return;
-               }
-
-               $this->getSkin()->setRelevantTitle( $this->target );
-
-               $this->selfTitle = $this->getPageTitle( $this->target->getPrefixedDBkey() );
-
-               $out->setPageTitle( $this->msg( 'whatlinkshere-title', $this->target->getPrefixedText() ) );
-               $out->addBacklinkSubtitle( $this->target );
-               $this->showIndirectLinks(
-                       0,
-                       $this->target,
-                       $opts->getValue( 'limit' ),
-                       $opts->getValue( 'from' ),
-                       $opts->getValue( 'back' )
-               );
-       }
-
-       /**
-        * @param int $level Recursion level
-        * @param Title $target Target title
-        * @param int $limit Number of entries to display
-        * @param int $from Display from this article ID (default: 0)
-        * @param int $back Display from this article ID at backwards scrolling (default: 0)
-        */
-       function showIndirectLinks( $level, $target, $limit, $from = 0, $back = 0 ) {
-               $out = $this->getOutput();
-               $dbr = wfGetDB( DB_REPLICA );
-
-               $hidelinks = $this->opts->getValue( 'hidelinks' );
-               $hideredirs = $this->opts->getValue( 'hideredirs' );
-               $hidetrans = $this->opts->getValue( 'hidetrans' );
-               $hideimages = $target->getNamespace() != NS_FILE || $this->opts->getValue( 'hideimages' );
-
-               $fetchlinks = ( !$hidelinks || !$hideredirs );
-
-               // Build query conds in concert for all three tables...
-               $conds['pagelinks'] = [
-                       'pl_namespace' => $target->getNamespace(),
-                       'pl_title' => $target->getDBkey(),
-               ];
-               $conds['templatelinks'] = [
-                       'tl_namespace' => $target->getNamespace(),
-                       'tl_title' => $target->getDBkey(),
-               ];
-               $conds['imagelinks'] = [
-                       'il_to' => $target->getDBkey(),
-               ];
-
-               $namespace = $this->opts->getValue( 'namespace' );
-               $invert = $this->opts->getValue( 'invert' );
-               $nsComparison = ( $invert ? '!= ' : '= ' ) . $dbr->addQuotes( $namespace );
-               if ( is_int( $namespace ) ) {
-                       $conds['pagelinks'][] = "pl_from_namespace $nsComparison";
-                       $conds['templatelinks'][] = "tl_from_namespace $nsComparison";
-                       $conds['imagelinks'][] = "il_from_namespace $nsComparison";
-               }
-
-               if ( $from ) {
-                       $conds['templatelinks'][] = "tl_from >= $from";
-                       $conds['pagelinks'][] = "pl_from >= $from";
-                       $conds['imagelinks'][] = "il_from >= $from";
-               }
-
-               if ( $hideredirs ) {
-                       $conds['pagelinks']['rd_from'] = null;
-               } elseif ( $hidelinks ) {
-                       $conds['pagelinks'][] = 'rd_from is NOT NULL';
-               }
-
-               $queryFunc = function ( IDatabase $dbr, $table, $fromCol ) use (
-                       $conds, $target, $limit
-               ) {
-                       // Read an extra row as an at-end check
-                       $queryLimit = $limit + 1;
-                       $on = [
-                               "rd_from = $fromCol",
-                               'rd_title' => $target->getDBkey(),
-                               'rd_interwiki = ' . $dbr->addQuotes( '' ) . ' OR rd_interwiki IS NULL'
-                       ];
-                       $on['rd_namespace'] = $target->getNamespace();
-                       // Inner LIMIT is 2X in case of stale backlinks with wrong namespaces
-                       $subQuery = $dbr->buildSelectSubquery(
-                               [ $table, 'redirect', 'page' ],
-                               [ $fromCol, 'rd_from' ],
-                               $conds[$table],
-                               __CLASS__ . '::showIndirectLinks',
-                               // Force JOIN order per T106682 to avoid large filesorts
-                               [ 'ORDER BY' => $fromCol, 'LIMIT' => 2 * $queryLimit, 'STRAIGHT_JOIN' ],
-                               [
-                                       'page' => [ 'JOIN', "$fromCol = page_id" ],
-                                       'redirect' => [ 'LEFT JOIN', $on ]
-                               ]
-                       );
-                       return $dbr->select(
-                               [ 'page', 'temp_backlink_range' => $subQuery ],
-                               [ 'page_id', 'page_namespace', 'page_title', 'rd_from', 'page_is_redirect' ],
-                               [],
-                               __CLASS__ . '::showIndirectLinks',
-                               [ 'ORDER BY' => 'page_id', 'LIMIT' => $queryLimit ],
-                               [ 'page' => [ 'JOIN', "$fromCol = page_id" ] ]
-                       );
-               };
-
-               if ( $fetchlinks ) {
-                       $plRes = $queryFunc( $dbr, 'pagelinks', 'pl_from' );
-               }
-
-               if ( !$hidetrans ) {
-                       $tlRes = $queryFunc( $dbr, 'templatelinks', 'tl_from' );
-               }
-
-               if ( !$hideimages ) {
-                       $ilRes = $queryFunc( $dbr, 'imagelinks', 'il_from' );
-               }
-
-               if ( ( !$fetchlinks || !$plRes->numRows() )
-                       && ( $hidetrans || !$tlRes->numRows() )
-                       && ( $hideimages || !$ilRes->numRows() )
-               ) {
-                       if ( $level == 0 && !$this->including() ) {
-                               $out->addHTML( $this->whatlinkshereForm() );
-
-                               // Show filters only if there are links
-                               if ( $hidelinks || $hidetrans || $hideredirs || $hideimages ) {
-                                       $out->addHTML( $this->getFilterPanel() );
-                               }
-                               $msgKey = is_int( $namespace ) ? 'nolinkshere-ns' : 'nolinkshere';
-                               $link = $this->getLinkRenderer()->makeLink(
-                                       $this->target,
-                                       null,
-                                       [],
-                                       $this->target->isRedirect() ? [ 'redirect' => 'no' ] : []
-                               );
-
-                               $errMsg = $this->msg( $msgKey )
-                                       ->params( $this->target->getPrefixedText() )
-                                       ->rawParams( $link )
-                                       ->parseAsBlock();
-                               $out->addHTML( $errMsg );
-                               $out->setStatusCode( 404 );
-                       }
-
-                       return;
-               }
-
-               // Read the rows into an array and remove duplicates
-               // templatelinks comes second so that the templatelinks row overwrites the
-               // pagelinks row, so we get (inclusion) rather than nothing
-               if ( $fetchlinks ) {
-                       foreach ( $plRes as $row ) {
-                               $row->is_template = 0;
-                               $row->is_image = 0;
-                               $rows[$row->page_id] = $row;
-                       }
-               }
-               if ( !$hidetrans ) {
-                       foreach ( $tlRes as $row ) {
-                               $row->is_template = 1;
-                               $row->is_image = 0;
-                               $rows[$row->page_id] = $row;
-                       }
-               }
-               if ( !$hideimages ) {
-                       foreach ( $ilRes as $row ) {
-                               $row->is_template = 0;
-                               $row->is_image = 1;
-                               $rows[$row->page_id] = $row;
-                       }
-               }
-
-               // Sort by key and then change the keys to 0-based indices
-               ksort( $rows );
-               $rows = array_values( $rows );
-
-               $numRows = count( $rows );
-
-               // Work out the start and end IDs, for prev/next links
-               if ( $numRows > $limit ) {
-                       // More rows available after these ones
-                       // Get the ID from the last row in the result set
-                       $nextId = $rows[$limit]->page_id;
-                       // Remove undisplayed rows
-                       $rows = array_slice( $rows, 0, $limit );
-               } else {
-                       // No more rows after
-                       $nextId = false;
-               }
-               $prevId = $from;
-
-               // use LinkBatch to make sure, that all required data (associated with Titles)
-               // is loaded in one query
-               $lb = new LinkBatch();
-               foreach ( $rows as $row ) {
-                       $lb->add( $row->page_namespace, $row->page_title );
-               }
-               $lb->execute();
-
-               if ( $level == 0 && !$this->including() ) {
-                       $out->addHTML( $this->whatlinkshereForm() );
-                       $out->addHTML( $this->getFilterPanel() );
-
-                       $link = $this->getLinkRenderer()->makeLink(
-                               $this->target,
-                               null,
-                               [],
-                               $this->target->isRedirect() ? [ 'redirect' => 'no' ] : []
-                       );
-
-                       $msg = $this->msg( 'linkshere' )
-                               ->params( $this->target->getPrefixedText() )
-                               ->rawParams( $link )
-                               ->parseAsBlock();
-                       $out->addHTML( $msg );
-
-                       $prevnext = $this->getPrevNext( $prevId, $nextId );
-                       $out->addHTML( $prevnext );
-               }
-               $out->addHTML( $this->listStart( $level ) );
-               foreach ( $rows as $row ) {
-                       $nt = Title::makeTitle( $row->page_namespace, $row->page_title );
-
-                       if ( $row->rd_from && $level < 2 ) {
-                               $out->addHTML( $this->listItem( $row, $nt, $target, true ) );
-                               $this->showIndirectLinks(
-                                       $level + 1,
-                                       $nt,
-                                       $this->getConfig()->get( 'MaxRedirectLinksRetrieved' )
-                               );
-                               $out->addHTML( Xml::closeElement( 'li' ) );
-                       } else {
-                               $out->addHTML( $this->listItem( $row, $nt, $target ) );
-                       }
-               }
-
-               $out->addHTML( $this->listEnd() );
-
-               if ( $level == 0 && !$this->including() ) {
-                       $out->addHTML( $prevnext );
-               }
-       }
-
-       protected function listStart( $level ) {
-               return Xml::openElement( 'ul', ( $level ? [] : [ 'id' => 'mw-whatlinkshere-list' ] ) );
-       }
-
-       protected function listItem( $row, $nt, $target, $notClose = false ) {
-               $dirmark = $this->getLanguage()->getDirMark();
-
-               # local message cache
-               static $msgcache = null;
-               if ( $msgcache === null ) {
-                       static $msgs = [ 'isredirect', 'istemplate', 'semicolon-separator',
-                               'whatlinkshere-links', 'isimage', 'editlink' ];
-                       $msgcache = [];
-                       foreach ( $msgs as $msg ) {
-                               $msgcache[$msg] = $this->msg( $msg )->escaped();
-                       }
-               }
-
-               if ( $row->rd_from ) {
-                       $query = [ 'redirect' => 'no' ];
-               } else {
-                       $query = [];
-               }
-
-               $link = $this->getLinkRenderer()->makeKnownLink(
-                       $nt,
-                       null,
-                       $row->page_is_redirect ? [ 'class' => 'mw-redirect' ] : [],
-                       $query
-               );
-
-               // Display properties (redirect or template)
-               $propsText = '';
-               $props = [];
-               if ( $row->rd_from ) {
-                       $props[] = $msgcache['isredirect'];
-               }
-               if ( $row->is_template ) {
-                       $props[] = $msgcache['istemplate'];
-               }
-               if ( $row->is_image ) {
-                       $props[] = $msgcache['isimage'];
-               }
-
-               Hooks::run( 'WhatLinksHereProps', [ $row, $nt, $target, &$props ] );
-
-               if ( count( $props ) ) {
-                       $propsText = $this->msg( 'parentheses' )
-                               ->rawParams( implode( $msgcache['semicolon-separator'], $props ) )->escaped();
-               }
-
-               # Space for utilities links, with a what-links-here link provided
-               $wlhLink = $this->wlhLink( $nt, $msgcache['whatlinkshere-links'], $msgcache['editlink'] );
-               $wlh = Xml::wrapClass(
-                       $this->msg( 'parentheses' )->rawParams( $wlhLink )->escaped(),
-                       'mw-whatlinkshere-tools'
-               );
-
-               return $notClose ?
-                       Xml::openElement( 'li' ) . "$link $propsText $dirmark $wlh\n" :
-                       Xml::tags( 'li', null, "$link $propsText $dirmark $wlh" ) . "\n";
-       }
-
-       protected function listEnd() {
-               return Xml::closeElement( 'ul' );
-       }
-
-       protected function wlhLink( Title $target, $text, $editText ) {
-               static $title = null;
-               if ( $title === null ) {
-                       $title = $this->getPageTitle();
-               }
-
-               $linkRenderer = $this->getLinkRenderer();
-
-               if ( $text !== null ) {
-                       $text = new HtmlArmor( $text );
-               }
-
-               // always show a "<- Links" link
-               $links = [
-                       'links' => $linkRenderer->makeKnownLink(
-                               $title,
-                               $text,
-                               [],
-                               [ 'target' => $target->getPrefixedText() ]
-                       ),
-               ];
-
-               // if the page is editable, add an edit link
-               if (
-                       // check user permissions
-                       $this->getUser()->isAllowed( 'edit' ) &&
-                       // check, if the content model is editable through action=edit
-                       ContentHandler::getForTitle( $target )->supportsDirectEditing()
-               ) {
-                       if ( $editText !== null ) {
-                               $editText = new HtmlArmor( $editText );
-                       }
-
-                       $links['edit'] = $linkRenderer->makeKnownLink(
-                               $target,
-                               $editText,
-                               [],
-                               [ 'action' => 'edit' ]
-                       );
-               }
-
-               // build the links html
-               return $this->getLanguage()->pipeList( $links );
-       }
-
-       function makeSelfLink( $text, $query ) {
-               if ( $text !== null ) {
-                       $text = new HtmlArmor( $text );
-               }
-
-               return $this->getLinkRenderer()->makeKnownLink(
-                       $this->selfTitle,
-                       $text,
-                       [],
-                       $query
-               );
-       }
-
-       function getPrevNext( $prevId, $nextId ) {
-               $currentLimit = $this->opts->getValue( 'limit' );
-               $prev = $this->msg( 'whatlinkshere-prev' )->numParams( $currentLimit )->escaped();
-               $next = $this->msg( 'whatlinkshere-next' )->numParams( $currentLimit )->escaped();
-
-               $changed = $this->opts->getChangedValues();
-               unset( $changed['target'] ); // Already in the request title
-
-               if ( $prevId != 0 ) {
-                       $overrides = [ 'from' => $this->opts->getValue( 'back' ) ];
-                       $prev = $this->makeSelfLink( $prev, array_merge( $changed, $overrides ) );
-               }
-               if ( $nextId != 0 ) {
-                       $overrides = [ 'from' => $nextId, 'back' => $prevId ];
-                       $next = $this->makeSelfLink( $next, array_merge( $changed, $overrides ) );
-               }
-
-               $limitLinks = [];
-               $lang = $this->getLanguage();
-               foreach ( $this->limits as $limit ) {
-                       $prettyLimit = htmlspecialchars( $lang->formatNum( $limit ) );
-                       $overrides = [ 'limit' => $limit ];
-                       $limitLinks[] = $this->makeSelfLink( $prettyLimit, array_merge( $changed, $overrides ) );
-               }
-
-               $nums = $lang->pipeList( $limitLinks );
-
-               return $this->msg( 'viewprevnext' )->rawParams( $prev, $next, $nums )->escaped();
-       }
-
-       function whatlinkshereForm() {
-               // We get nicer value from the title object
-               $this->opts->consumeValue( 'target' );
-               // Reset these for new requests
-               $this->opts->consumeValues( [ 'back', 'from' ] );
-
-               $target = $this->target ? $this->target->getPrefixedText() : '';
-               $namespace = $this->opts->consumeValue( 'namespace' );
-               $nsinvert = $this->opts->consumeValue( 'invert' );
-
-               # Build up the form
-               $f = Xml::openElement( 'form', [ 'action' => wfScript() ] );
-
-               # Values that should not be forgotten
-               $f .= Html::hidden( 'title', $this->getPageTitle()->getPrefixedText() );
-               foreach ( $this->opts->getUnconsumedValues() as $name => $value ) {
-                       $f .= Html::hidden( $name, $value );
-               }
-
-               $f .= Xml::fieldset( $this->msg( 'whatlinkshere' )->text() );
-
-               # Target input (.mw-searchInput enables suggestions)
-               $f .= Xml::inputLabel( $this->msg( 'whatlinkshere-page' )->text(), 'target',
-                       'mw-whatlinkshere-target', 40, $target, [ 'class' => 'mw-searchInput' ] );
-
-               $f .= ' ';
-
-               # Namespace selector
-               $f .= Html::namespaceSelector(
-                       [
-                               'selected' => $namespace,
-                               'all' => '',
-                               'label' => $this->msg( 'namespace' )->text(),
-                               'in-user-lang' => true,
-                       ], [
-                               'name' => 'namespace',
-                               'id' => 'namespace',
-                               'class' => 'namespaceselector',
-                       ]
-               );
-
-               $f .= "\u{00A0}" .
-                       Xml::checkLabel(
-                               $this->msg( 'invert' )->text(),
-                               'invert',
-                               'nsinvert',
-                               $nsinvert,
-                               [ 'title' => $this->msg( 'tooltip-whatlinkshere-invert' )->text() ]
-                       );
-
-               $f .= ' ';
-
-               # Submit
-               $f .= Xml::submitButton( $this->msg( 'whatlinkshere-submit' )->text() );
-
-               # Close
-               $f .= Xml::closeElement( 'fieldset' ) . Xml::closeElement( 'form' ) . "\n";
-
-               return $f;
-       }
-
-       /**
-        * Create filter panel
-        *
-        * @return string HTML fieldset and filter panel with the show/hide links
-        */
-       function getFilterPanel() {
-               $show = $this->msg( 'show' )->escaped();
-               $hide = $this->msg( 'hide' )->escaped();
-
-               $changed = $this->opts->getChangedValues();
-               unset( $changed['target'] ); // Already in the request title
-
-               $links = [];
-               $types = [ 'hidetrans', 'hidelinks', 'hideredirs' ];
-               if ( $this->target->getNamespace() == NS_FILE ) {
-                       $types[] = 'hideimages';
-               }
-
-               // Combined message keys: 'whatlinkshere-hideredirs', 'whatlinkshere-hidetrans',
-               // 'whatlinkshere-hidelinks', 'whatlinkshere-hideimages'
-               // To be sure they will be found by grep
-               foreach ( $types as $type ) {
-                       $chosen = $this->opts->getValue( $type );
-                       $msg = $chosen ? $show : $hide;
-                       $overrides = [ $type => !$chosen ];
-                       $links[] = $this->msg( "whatlinkshere-{$type}" )->rawParams(
-                               $this->makeSelfLink( $msg, array_merge( $changed, $overrides ) ) )->escaped();
-               }
-
-               return Xml::fieldset(
-                       $this->msg( 'whatlinkshere-filters' )->text(),
-                       $this->getLanguage()->pipeList( $links )
-               );
-       }
-
-       /**
-        * Return an array of subpages beginning with $search that this special page will accept.
-        *
-        * @param string $search Prefix to search for
-        * @param int $limit Maximum number of results to return (usually 10)
-        * @param int $offset Number of results to skip (usually 0)
-        * @return string[] Matching subpages
-        */
-       public function prefixSearchSubpages( $search, $limit, $offset ) {
-               return $this->prefixSearchString( $search, $limit, $offset );
-       }
-
-       protected function getGroupName() {
-               return 'pagetools';
-       }
-}
diff --git a/includes/specials/forms/PreferencesFormLegacy.php b/includes/specials/forms/PreferencesFormLegacy.php
deleted file mode 100644 (file)
index 951e5ce..0000000
+++ /dev/null
@@ -1,35 +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
- */
-
-/**
- * Form to edit user preferences.
- *
- * @since 1.32
- */
-class PreferencesFormLegacy extends PreferencesFormOOUI {
-       // No-op
-}
-
-/**
- * Retain the old class name for backwards compatibility.
- *
- * @deprecated since 1.32
- */
-class_alias( PreferencesFormLegacy::class, 'PreferencesForm' );
index c79e87c..8063804 100644 (file)
@@ -69,10 +69,10 @@ class ImportReporter extends ContextSource {
                );
        }
 
-       function reportLogItem( /* ... */ ) {
+       function reportLogItem( ...$args ) {
                $this->mLogItemCount++;
                if ( is_callable( $this->mOriginalLogCallback ) ) {
-                       call_user_func_array( $this->mOriginalLogCallback, func_get_args() );
+                       call_user_func_array( $this->mOriginalLogCallback, $args );
                }
        }
 
@@ -86,8 +86,7 @@ class ImportReporter extends ContextSource {
         */
        public function reportPage( $title, $foreignTitle, $revisionCount,
                        $successCount, $pageInfo ) {
-               $args = func_get_args();
-               call_user_func_array( $this->mOriginalPageOutCallback, $args );
+               call_user_func_array( $this->mOriginalPageOutCallback, func_get_args() );
 
                if ( $title === null ) {
                        # Invalid or non-importable title; a notice is already displayed
index 10fcfc6..44ecb6f 100644 (file)
@@ -301,11 +301,12 @@ class ContribsPager extends RangeChronologicalPager {
                                if ( isset( $conds['orconds']['actor'] ) ) {
                                        // @todo: This will need changing when revision_comment_temp goes away
                                        $queryInfo['options']['USE INDEX']['temp_rev_user'] = 'actor_timestamp';
-                                       // Alias 'rev_timestamp' => 'revactor_timestamp' so "ORDER BY rev_timestamp" is interpreted to
-                                       // use revactor_timestamp instead.
+                                       // Alias 'rev_timestamp' => 'revactor_timestamp' and 'rev_id' => 'revactor_rev' so
+                                       // "ORDER BY rev_timestamp, rev_id" is interpreted to use denormalized revision_actor_temp
+                                       // fields instead.
                                        $queryInfo['fields'] = array_merge(
-                                               array_diff( $queryInfo['fields'], [ 'rev_timestamp' ] ),
-                                               [ 'rev_timestamp' => 'revactor_timestamp' ]
+                                               array_diff( $queryInfo['fields'], [ 'rev_timestamp', 'rev_id' ] ),
+                                               [ 'rev_timestamp' => 'revactor_timestamp', 'rev_id' => 'revactor_rev' ]
                                        );
                                } else {
                                        $queryInfo['options']['USE INDEX']['revision'] =
index 3726202..f9cab24 100644 (file)
@@ -24,7 +24,7 @@
  * This is a utility class for dealing with namespaces that encodes all the "magic" behaviors of
  * them based on index.  The textual names of the namespaces are handled by Language.php.
  *
- * @since 1.33
+ * @since 1.34
  */
 class NamespaceInfo {
 
index 7d69fe1..2c4bc11 100644 (file)
@@ -572,195 +572,3 @@ class UploadStash {
                return true;
        }
 }
-
-/**
- * @ingroup Upload
- */
-class UploadStashFile extends UnregisteredLocalFile {
-       private $fileKey;
-       private $urlName;
-       protected $url;
-
-       /**
-        * A LocalFile wrapper around a file that has been temporarily stashed,
-        * so we can do things like create thumbnails for it. Arguably
-        * UnregisteredLocalFile should be handling its own file repo but that
-        * class is a bit retarded currently.
-        *
-        * @param FileRepo $repo Repository where we should find the path
-        * @param string $path Path to file
-        * @param string $key Key to store the path and any stashed data under
-        * @throws UploadStashBadPathException
-        * @throws UploadStashFileNotFoundException
-        */
-       public function __construct( $repo, $path, $key ) {
-               $this->fileKey = $key;
-
-               // resolve mwrepo:// urls
-               if ( FileRepo::isVirtualUrl( $path ) ) {
-                       $path = $repo->resolveVirtualUrl( $path );
-               } else {
-                       // check if path appears to be sane, no parent traversals,
-                       // and is in this repo's temp zone.
-                       $repoTempPath = $repo->getZonePath( 'temp' );
-                       if ( ( !$repo->validateFilename( $path ) ) ||
-                               ( strpos( $path, $repoTempPath ) !== 0 )
-                       ) {
-                               wfDebug( "UploadStash: tried to construct an UploadStashFile "
-                                       . "from a file that should already exist at '$path', but path is not valid\n" );
-                               throw new UploadStashBadPathException(
-                                       wfMessage( 'uploadstash-bad-path-invalid' )
-                               );
-                       }
-
-                       // check if path exists! and is a plain file.
-                       if ( !$repo->fileExists( $path ) ) {
-                               wfDebug( "UploadStash: tried to construct an UploadStashFile from "
-                                       . "a file that should already exist at '$path', but path is not found\n" );
-                               throw new UploadStashFileNotFoundException(
-                                       wfMessage( 'uploadstash-file-not-found-not-exists' )
-                               );
-                       }
-               }
-
-               parent::__construct( false, $repo, $path, false );
-
-               $this->name = basename( $this->path );
-       }
-
-       /**
-        * A method needed by the file transforming and scaling routines in File.php
-        * We do not necessarily care about doing the description at this point
-        * However, we also can't return the empty string, as the rest of MediaWiki
-        * demands this (and calls to imagemagick convert require it to be there)
-        *
-        * @return string Dummy value
-        */
-       public function getDescriptionUrl() {
-               return $this->getUrl();
-       }
-
-       /**
-        * Get the path for the thumbnail (actually any transformation of this file)
-        * The actual argument is the result of thumbName although we seem to have
-        * buggy code elsewhere that expects a boolean 'suffix'
-        *
-        * @param string $thumbName Name of thumbnail (e.g. "120px-123456.jpg" ),
-        *   or false to just get the path
-        * @return string Path thumbnail should take on filesystem, or containing
-        *   directory if thumbname is false
-        */
-       public function getThumbPath( $thumbName = false ) {
-               $path = dirname( $this->path );
-               if ( $thumbName !== false ) {
-                       $path .= "/$thumbName";
-               }
-
-               return $path;
-       }
-
-       /**
-        * Return the file/url base name of a thumbnail with the specified parameters.
-        * We override this because we want to use the pretty url name instead of the
-        * ugly file name.
-        *
-        * @param array $params Handler-specific parameters
-        * @param int $flags Bitfield that supports THUMB_* constants
-        * @return string|null Base name for URL, like '120px-12345.jpg', or null if there is no handler
-        */
-       function thumbName( $params, $flags = 0 ) {
-               return $this->generateThumbName( $this->getUrlName(), $params );
-       }
-
-       /**
-        * Helper function -- given a 'subpage', return the local URL,
-        * e.g. /wiki/Special:UploadStash/subpage
-        * @param string $subPage
-        * @return string Local URL for this subpage in the Special:UploadStash space.
-        */
-       private function getSpecialUrl( $subPage ) {
-               return SpecialPage::getTitleFor( 'UploadStash', $subPage )->getLocalURL();
-       }
-
-       /**
-        * Get a URL to access the thumbnail
-        * This is required because the model of how files work requires that
-        * the thumbnail urls be predictable. However, in our model the URL is
-        * not based on the filename (that's hidden in the db)
-        *
-        * @param string $thumbName Basename of thumbnail file -- however, we don't
-        *   want to use the file exactly
-        * @return string URL to access thumbnail, or URL with partial path
-        */
-       public function getThumbUrl( $thumbName = false ) {
-               wfDebug( __METHOD__ . " getting for $thumbName \n" );
-
-               return $this->getSpecialUrl( 'thumb/' . $this->getUrlName() . '/' . $thumbName );
-       }
-
-       /**
-        * The basename for the URL, which we want to not be related to the filename.
-        * Will also be used as the lookup key for a thumbnail file.
-        *
-        * @return string Base url name, like '120px-123456.jpg'
-        */
-       public function getUrlName() {
-               if ( !$this->urlName ) {
-                       $this->urlName = $this->fileKey;
-               }
-
-               return $this->urlName;
-       }
-
-       /**
-        * Return the URL of the file, if for some reason we wanted to download it
-        * We tend not to do this for the original file, but we do want thumb icons
-        *
-        * @return string Url
-        */
-       public function getUrl() {
-               if ( !isset( $this->url ) ) {
-                       $this->url = $this->getSpecialUrl( 'file/' . $this->getUrlName() );
-               }
-
-               return $this->url;
-       }
-
-       /**
-        * Parent classes use this method, for no obvious reason, to return the path
-        * (relative to wiki root, I assume). But with this class, the URL is
-        * unrelated to the path.
-        *
-        * @return string Url
-        */
-       public function getFullUrl() {
-               return $this->getUrl();
-       }
-
-       /**
-        * Getter for file key (the unique id by which this file's location &
-        * metadata is stored in the db)
-        *
-        * @return string File key
-        */
-       public function getFileKey() {
-               return $this->fileKey;
-       }
-
-       /**
-        * Remove the associated temporary file
-        * @return status Success
-        */
-       public function remove() {
-               if ( !$this->repo->fileExists( $this->path ) ) {
-                       // Maybe the file's already been removed? This could totally happen in UploadBase.
-                       return true;
-               }
-
-               return $this->repo->freeTemp( $this->path );
-       }
-
-       public function exists() {
-               return $this->repo->fileExists( $this->path );
-       }
-}
diff --git a/includes/upload/UploadStashFile.php b/includes/upload/UploadStashFile.php
new file mode 100644 (file)
index 0000000..53edba5
--- /dev/null
@@ -0,0 +1,211 @@
+<?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 Upload
+ */
+class UploadStashFile extends UnregisteredLocalFile {
+       private $fileKey;
+       private $urlName;
+       protected $url;
+
+       /**
+        * A LocalFile wrapper around a file that has been temporarily stashed,
+        * so we can do things like create thumbnails for it. Arguably
+        * UnregisteredLocalFile should be handling its own file repo but that
+        * class is a bit retarded currently.
+        *
+        * @param FileRepo $repo Repository where we should find the path
+        * @param string $path Path to file
+        * @param string $key Key to store the path and any stashed data under
+        * @throws UploadStashBadPathException
+        * @throws UploadStashFileNotFoundException
+        */
+       public function __construct( $repo, $path, $key ) {
+               $this->fileKey = $key;
+
+               // resolve mwrepo:// urls
+               if ( FileRepo::isVirtualUrl( $path ) ) {
+                       $path = $repo->resolveVirtualUrl( $path );
+               } else {
+                       // check if path appears to be sane, no parent traversals,
+                       // and is in this repo's temp zone.
+                       $repoTempPath = $repo->getZonePath( 'temp' );
+                       if ( ( !$repo->validateFilename( $path ) ) ||
+                               ( strpos( $path, $repoTempPath ) !== 0 )
+                       ) {
+                               wfDebug( "UploadStash: tried to construct an UploadStashFile "
+                                       . "from a file that should already exist at '$path', but path is not valid\n" );
+                               throw new UploadStashBadPathException(
+                                       wfMessage( 'uploadstash-bad-path-invalid' )
+                               );
+                       }
+
+                       // check if path exists! and is a plain file.
+                       if ( !$repo->fileExists( $path ) ) {
+                               wfDebug( "UploadStash: tried to construct an UploadStashFile from "
+                                       . "a file that should already exist at '$path', but path is not found\n" );
+                               throw new UploadStashFileNotFoundException(
+                                       wfMessage( 'uploadstash-file-not-found-not-exists' )
+                               );
+                       }
+               }
+
+               parent::__construct( false, $repo, $path, false );
+
+               $this->name = basename( $this->path );
+       }
+
+       /**
+        * A method needed by the file transforming and scaling routines in File.php
+        * We do not necessarily care about doing the description at this point
+        * However, we also can't return the empty string, as the rest of MediaWiki
+        * demands this (and calls to imagemagick convert require it to be there)
+        *
+        * @return string Dummy value
+        */
+       public function getDescriptionUrl() {
+               return $this->getUrl();
+       }
+
+       /**
+        * Get the path for the thumbnail (actually any transformation of this file)
+        * The actual argument is the result of thumbName although we seem to have
+        * buggy code elsewhere that expects a boolean 'suffix'
+        *
+        * @param string $thumbName Name of thumbnail (e.g. "120px-123456.jpg" ),
+        *   or false to just get the path
+        * @return string Path thumbnail should take on filesystem, or containing
+        *   directory if thumbname is false
+        */
+       public function getThumbPath( $thumbName = false ) {
+               $path = dirname( $this->path );
+               if ( $thumbName !== false ) {
+                       $path .= "/$thumbName";
+               }
+
+               return $path;
+       }
+
+       /**
+        * Return the file/url base name of a thumbnail with the specified parameters.
+        * We override this because we want to use the pretty url name instead of the
+        * ugly file name.
+        *
+        * @param array $params Handler-specific parameters
+        * @param int $flags Bitfield that supports THUMB_* constants
+        * @return string|null Base name for URL, like '120px-12345.jpg', or null if there is no handler
+        */
+       function thumbName( $params, $flags = 0 ) {
+               return $this->generateThumbName( $this->getUrlName(), $params );
+       }
+
+       /**
+        * Helper function -- given a 'subpage', return the local URL,
+        * e.g. /wiki/Special:UploadStash/subpage
+        * @param string $subPage
+        * @return string Local URL for this subpage in the Special:UploadStash space.
+        */
+       private function getSpecialUrl( $subPage ) {
+               return SpecialPage::getTitleFor( 'UploadStash', $subPage )->getLocalURL();
+       }
+
+       /**
+        * Get a URL to access the thumbnail
+        * This is required because the model of how files work requires that
+        * the thumbnail urls be predictable. However, in our model the URL is
+        * not based on the filename (that's hidden in the db)
+        *
+        * @param string $thumbName Basename of thumbnail file -- however, we don't
+        *   want to use the file exactly
+        * @return string URL to access thumbnail, or URL with partial path
+        */
+       public function getThumbUrl( $thumbName = false ) {
+               wfDebug( __METHOD__ . " getting for $thumbName \n" );
+
+               return $this->getSpecialUrl( 'thumb/' . $this->getUrlName() . '/' . $thumbName );
+       }
+
+       /**
+        * The basename for the URL, which we want to not be related to the filename.
+        * Will also be used as the lookup key for a thumbnail file.
+        *
+        * @return string Base url name, like '120px-123456.jpg'
+        */
+       public function getUrlName() {
+               if ( !$this->urlName ) {
+                       $this->urlName = $this->fileKey;
+               }
+
+               return $this->urlName;
+       }
+
+       /**
+        * Return the URL of the file, if for some reason we wanted to download it
+        * We tend not to do this for the original file, but we do want thumb icons
+        *
+        * @return string Url
+        */
+       public function getUrl() {
+               if ( !isset( $this->url ) ) {
+                       $this->url = $this->getSpecialUrl( 'file/' . $this->getUrlName() );
+               }
+
+               return $this->url;
+       }
+
+       /**
+        * Parent classes use this method, for no obvious reason, to return the path
+        * (relative to wiki root, I assume). But with this class, the URL is
+        * unrelated to the path.
+        *
+        * @return string Url
+        */
+       public function getFullUrl() {
+               return $this->getUrl();
+       }
+
+       /**
+        * Getter for file key (the unique id by which this file's location &
+        * metadata is stored in the db)
+        *
+        * @return string File key
+        */
+       public function getFileKey() {
+               return $this->fileKey;
+       }
+
+       /**
+        * Remove the associated temporary file
+        * @return bool Success
+        */
+       public function remove() {
+               if ( !$this->repo->fileExists( $this->path ) ) {
+                       // Maybe the file's already been removed? This could totally happen in UploadBase.
+                       return true;
+               }
+
+               return $this->repo->freeTemp( $this->path );
+       }
+
+       public function exists() {
+               return $this->repo->fileExists( $this->path );
+       }
+}
index 5b07315..981204d 100644 (file)
@@ -46,18 +46,20 @@ use Wikimedia\Rdbms\IDatabase;
  * of the database.
  */
 class User implements IDBAccessObject, UserIdentity {
+
        /**
-        * @const int Number of characters in user_token field.
+        * Number of characters required for the user_token field.
         */
        const TOKEN_LENGTH = 32;
 
        /**
-        * @const string An invalid value for user_token
+        * An invalid string value for the user_token field.
         */
        const INVALID_TOKEN = '*** INVALID ***';
 
        /**
-        * @const int Serialized record version.
+        * Version number to tag cached versions of serialized User objects. Should be increased when
+        * {@link $mCacheVars} or one of it's members changes.
         */
        const VERSION = 13;
 
index 2fc7bc0..260f66c 100644 (file)
@@ -1,4 +1,22 @@
 <?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
+ */
 
 /**
  * Accepts a list of files and directories to search for
@@ -368,190 +386,3 @@ EOD;
                }
        }
 }
-
-/**
- * Reads PHP code and returns the FQCN of every class defined within it.
- */
-class ClassCollector {
-
-       /**
-        * @var string Current namespace
-        */
-       protected $namespace = '';
-
-       /**
-        * @var array List of FQCN detected in this pass
-        */
-       protected $classes;
-
-       /**
-        * @var array Token from token_get_all() that started an expect sequence
-        */
-       protected $startToken;
-
-       /**
-        * @var array List of tokens that are members of the current expect sequence
-        */
-       protected $tokens;
-
-       /**
-        * @var array Class alias with target/name fields
-        */
-       protected $alias;
-
-       /**
-        * @param string $code PHP code (including <?php) to detect class names from
-        * @return array List of FQCN detected within the tokens
-        */
-       public function getClasses( $code ) {
-               $this->namespace = '';
-               $this->classes = [];
-               $this->startToken = null;
-               $this->alias = null;
-               $this->tokens = [];
-
-               foreach ( token_get_all( $code ) as $token ) {
-                       if ( $this->startToken === null ) {
-                               $this->tryBeginExpect( $token );
-                       } else {
-                               $this->tryEndExpect( $token );
-                       }
-               }
-
-               return $this->classes;
-       }
-
-       /**
-        * Determine if $token begins the next expect sequence.
-        *
-        * @param array $token
-        */
-       protected function tryBeginExpect( $token ) {
-               if ( is_string( $token ) ) {
-                       return;
-               }
-               // Note: When changing class name discovery logic,
-               // AutoLoaderStructureTest.php may also need to be updated.
-               switch ( $token[0] ) {
-                       case T_NAMESPACE:
-                       case T_CLASS:
-                       case T_INTERFACE:
-                       case T_TRAIT:
-                       case T_DOUBLE_COLON:
-                       case T_NEW:
-                               $this->startToken = $token;
-                               break;
-                       case T_STRING:
-                               if ( $token[1] === 'class_alias' ) {
-                                       $this->startToken = $token;
-                                       $this->alias = [];
-                               }
-               }
-       }
-
-       /**
-        * Accepts the next token in an expect sequence
-        *
-        * @param array $token
-        */
-       protected function tryEndExpect( $token ) {
-               switch ( $this->startToken[0] ) {
-                       case T_DOUBLE_COLON:
-                               // Skip over T_CLASS after T_DOUBLE_COLON because this is something like
-                               // "self::static" which accesses the class name. It doens't define a new class.
-                               $this->startToken = null;
-                               break;
-                       case T_NEW:
-                               // Skip over T_CLASS after T_NEW because this is a PHP 7 anonymous class.
-                               if ( !is_array( $token ) || $token[0] !== T_WHITESPACE ) {
-                                       $this->startToken = null;
-                               }
-                               break;
-                       case T_NAMESPACE:
-                               if ( $token === ';' || $token === '{' ) {
-                                       $this->namespace = $this->implodeTokens() . '\\';
-                               } else {
-                                       $this->tokens[] = $token;
-                               }
-                               break;
-
-                       case T_STRING:
-                               if ( $this->alias !== null ) {
-                                       // Flow 1 - Two string literals:
-                                       // - T_STRING  class_alias
-                                       // - '('
-                                       // - T_CONSTANT_ENCAPSED_STRING 'TargetClass'
-                                       // - ','
-                                       // - T_WHITESPACE
-                                       // - T_CONSTANT_ENCAPSED_STRING 'AliasName'
-                                       // - ')'
-                                       // Flow 2 - Use of ::class syntax for first parameter
-                                       // - T_STRING  class_alias
-                                       // - '('
-                                       // - T_STRING TargetClass
-                                       // - T_DOUBLE_COLON ::
-                                       // - T_CLASS class
-                                       // - ','
-                                       // - T_WHITESPACE
-                                       // - T_CONSTANT_ENCAPSED_STRING 'AliasName'
-                                       // - ')'
-                                       if ( $token === '(' ) {
-                                               // Start of a function call to class_alias()
-                                               $this->alias = [ 'target' => false, 'name' => false ];
-                                       } elseif ( $token === ',' ) {
-                                               // Record that we're past the first parameter
-                                               if ( $this->alias['target'] === false ) {
-                                                       $this->alias['target'] = true;
-                                               }
-                                       } elseif ( is_array( $token ) && $token[0] === T_CONSTANT_ENCAPSED_STRING ) {
-                                               if ( $this->alias['target'] === true ) {
-                                                       // We already saw a first argument, this must be the second.
-                                                       // Strip quotes from the string literal.
-                                                       $this->alias['name'] = substr( $token[1], 1, -1 );
-                                               }
-                                       } elseif ( $token === ')' ) {
-                                               // End of function call
-                                               $this->classes[] = $this->alias['name'];
-                                               $this->alias = null;
-                                               $this->startToken = null;
-                                       } elseif ( !is_array( $token ) || (
-                                               $token[0] !== T_STRING &&
-                                               $token[0] !== T_DOUBLE_COLON &&
-                                               $token[0] !== T_CLASS &&
-                                               $token[0] !== T_WHITESPACE
-                                       ) ) {
-                                               // Ignore this call to class_alias() - compat/Timestamp.php
-                                               $this->alias = null;
-                                               $this->startToken = null;
-                                       }
-                               }
-                               break;
-
-                       case T_CLASS:
-                       case T_INTERFACE:
-                       case T_TRAIT:
-                               $this->tokens[] = $token;
-                               if ( is_array( $token ) && $token[0] === T_STRING ) {
-                                       $this->classes[] = $this->namespace . $this->implodeTokens();
-                               }
-               }
-       }
-
-       /**
-        * Returns the string representation of the tokens within the
-        * current expect sequence and resets the sequence.
-        *
-        * @return string
-        */
-       protected function implodeTokens() {
-               $content = [];
-               foreach ( $this->tokens as $token ) {
-                       $content[] = is_string( $token ) ? $token : $token[1];
-               }
-
-               $this->tokens = [];
-               $this->startToken = null;
-
-               return trim( implode( '', $content ), " \n\t" );
-       }
-}
diff --git a/includes/utils/ClassCollector.php b/includes/utils/ClassCollector.php
new file mode 100644 (file)
index 0000000..c987354
--- /dev/null
@@ -0,0 +1,206 @@
+<?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
+ */
+
+/**
+ * Reads PHP code and returns the FQCN of every class defined within it.
+ */
+class ClassCollector {
+
+       /**
+        * @var string Current namespace
+        */
+       protected $namespace = '';
+
+       /**
+        * @var array List of FQCN detected in this pass
+        */
+       protected $classes;
+
+       /**
+        * @var array Token from token_get_all() that started an expect sequence
+        */
+       protected $startToken;
+
+       /**
+        * @var array List of tokens that are members of the current expect sequence
+        */
+       protected $tokens;
+
+       /**
+        * @var array Class alias with target/name fields
+        */
+       protected $alias;
+
+       /**
+        * @param string $code PHP code (including <?php) to detect class names from
+        * @return array List of FQCN detected within the tokens
+        */
+       public function getClasses( $code ) {
+               $this->namespace = '';
+               $this->classes = [];
+               $this->startToken = null;
+               $this->alias = null;
+               $this->tokens = [];
+
+               foreach ( token_get_all( $code ) as $token ) {
+                       if ( $this->startToken === null ) {
+                               $this->tryBeginExpect( $token );
+                       } else {
+                               $this->tryEndExpect( $token );
+                       }
+               }
+
+               return $this->classes;
+       }
+
+       /**
+        * Determine if $token begins the next expect sequence.
+        *
+        * @param array $token
+        */
+       protected function tryBeginExpect( $token ) {
+               if ( is_string( $token ) ) {
+                       return;
+               }
+               // Note: When changing class name discovery logic,
+               // AutoLoaderStructureTest.php may also need to be updated.
+               switch ( $token[0] ) {
+                       case T_NAMESPACE:
+                       case T_CLASS:
+                       case T_INTERFACE:
+                       case T_TRAIT:
+                       case T_DOUBLE_COLON:
+                       case T_NEW:
+                               $this->startToken = $token;
+                               break;
+                       case T_STRING:
+                               if ( $token[1] === 'class_alias' ) {
+                                       $this->startToken = $token;
+                                       $this->alias = [];
+                               }
+               }
+       }
+
+       /**
+        * Accepts the next token in an expect sequence
+        *
+        * @param array $token
+        */
+       protected function tryEndExpect( $token ) {
+               switch ( $this->startToken[0] ) {
+                       case T_DOUBLE_COLON:
+                               // Skip over T_CLASS after T_DOUBLE_COLON because this is something like
+                               // "self::static" which accesses the class name. It doens't define a new class.
+                               $this->startToken = null;
+                               break;
+                       case T_NEW:
+                               // Skip over T_CLASS after T_NEW because this is a PHP 7 anonymous class.
+                               if ( !is_array( $token ) || $token[0] !== T_WHITESPACE ) {
+                                       $this->startToken = null;
+                               }
+                               break;
+                       case T_NAMESPACE:
+                               if ( $token === ';' || $token === '{' ) {
+                                       $this->namespace = $this->implodeTokens() . '\\';
+                               } else {
+                                       $this->tokens[] = $token;
+                               }
+                               break;
+
+                       case T_STRING:
+                               if ( $this->alias !== null ) {
+                                       // Flow 1 - Two string literals:
+                                       // - T_STRING  class_alias
+                                       // - '('
+                                       // - T_CONSTANT_ENCAPSED_STRING 'TargetClass'
+                                       // - ','
+                                       // - T_WHITESPACE
+                                       // - T_CONSTANT_ENCAPSED_STRING 'AliasName'
+                                       // - ')'
+                                       // Flow 2 - Use of ::class syntax for first parameter
+                                       // - T_STRING  class_alias
+                                       // - '('
+                                       // - T_STRING TargetClass
+                                       // - T_DOUBLE_COLON ::
+                                       // - T_CLASS class
+                                       // - ','
+                                       // - T_WHITESPACE
+                                       // - T_CONSTANT_ENCAPSED_STRING 'AliasName'
+                                       // - ')'
+                                       if ( $token === '(' ) {
+                                               // Start of a function call to class_alias()
+                                               $this->alias = [ 'target' => false, 'name' => false ];
+                                       } elseif ( $token === ',' ) {
+                                               // Record that we're past the first parameter
+                                               if ( $this->alias['target'] === false ) {
+                                                       $this->alias['target'] = true;
+                                               }
+                                       } elseif ( is_array( $token ) && $token[0] === T_CONSTANT_ENCAPSED_STRING ) {
+                                               if ( $this->alias['target'] === true ) {
+                                                       // We already saw a first argument, this must be the second.
+                                                       // Strip quotes from the string literal.
+                                                       $this->alias['name'] = substr( $token[1], 1, -1 );
+                                               }
+                                       } elseif ( $token === ')' ) {
+                                               // End of function call
+                                               $this->classes[] = $this->alias['name'];
+                                               $this->alias = null;
+                                               $this->startToken = null;
+                                       } elseif ( !is_array( $token ) || (
+                                                       $token[0] !== T_STRING &&
+                                                       $token[0] !== T_DOUBLE_COLON &&
+                                                       $token[0] !== T_CLASS &&
+                                                       $token[0] !== T_WHITESPACE
+                                               ) ) {
+                                               // Ignore this call to class_alias() - compat/Timestamp.php
+                                               $this->alias = null;
+                                               $this->startToken = null;
+                                       }
+                               }
+                               break;
+
+                       case T_CLASS:
+                       case T_INTERFACE:
+                       case T_TRAIT:
+                               $this->tokens[] = $token;
+                               if ( is_array( $token ) && $token[0] === T_STRING ) {
+                                       $this->classes[] = $this->namespace . $this->implodeTokens();
+                               }
+               }
+       }
+
+       /**
+        * Returns the string representation of the tokens within the
+        * current expect sequence and resets the sequence.
+        *
+        * @return string
+        */
+       protected function implodeTokens() {
+               $content = [];
+               foreach ( $this->tokens as $token ) {
+                       $content[] = is_string( $token ) ? $token : $token[1];
+               }
+
+               $this->tokens = [];
+               $this->startToken = null;
+
+               return trim( implode( '', $content ), " \n\t" );
+       }
+}
index ec8bf5c..673586d 100644 (file)
  * @file
  */
 
-use MediaWiki\MediaWikiServices;
-
 class MWCryptRand {
-       /**
-        * @deprecated since 1.32
-        * @return CryptRand
-        */
-       protected static function singleton() {
-               wfDeprecated( __METHOD__, '1.32' );
-               return MediaWikiServices::getInstance()->getCryptRand();
-       }
-
-       /**
-        * Return a boolean indicating whether or not the source used for cryptographic
-        * random bytes generation in the previously run generate* call
-        * was cryptographically strong.
-        *
-        * @deprecated since 1.32, always returns true
-        *
-        * @return bool Always true
-        */
-       public static function wasStrong() {
-               wfDeprecated( __METHOD__, '1.32' );
-               return true;
-       }
-
-       /**
-        * Generate a run of cryptographically random data and return
-        * it in raw binary form.
-        *
-        * @deprecated since 1.32, use random_bytes()
-        *
-        * @param int $bytes The number of bytes of random data to generate
-        * @return string Raw binary random data
-        */
-       public static function generate( $bytes ) {
-               wfDeprecated( __METHOD__, '1.32' );
-               return random_bytes( floor( $bytes ) );
-       }
 
        /**
         * Generate a run of cryptographically random data and return
index 65b50e2..fea5404 100644 (file)
@@ -574,7 +574,7 @@ class UIDGenerator {
                $start = microtime( true );
                do {
                        $ct = time();
-                       // https://secure.php.net/manual/en/language.operators.comparison.php
+                       // https://www.php.net/manual/en/language.operators.comparison.php
                        if ( $ct >= $time ) {
                                // current time is higher than or equal to than $time
                                return $ct;
index 39d7a5d..fc95ebc 100644 (file)
@@ -32,9 +32,6 @@ class NoWriteWatchedItemStore implements WatchedItemStoreInterface {
         */
        private $actualStore;
 
-       /**
-        * @var string
-        */
        const DB_READONLY_ERROR = 'The watchlist is currently readonly.';
 
        /**
index 8aca689..e287a35 100644 (file)
@@ -904,10 +904,9 @@ class WatchedItemStore implements WatchedItemStoreInterface, StatsdAwareInterfac
                }
 
                // If the page is watched by the user (or may be watched), update the timestamp
-               $job = new ClearWatchlistNotificationsJob(
-                       $user->getUserPage(),
-                       [ 'userId'  => $user->getId(), 'timestamp' => $timestamp, 'casTime' => time() ]
-               );
+               $job = new ClearWatchlistNotificationsJob( [
+                       'userId'  => $user->getId(), 'timestamp' => $timestamp, 'casTime' => time()
+               ] );
 
                // Try to run this post-send
                // Calls DeferredUpdates::addCallableUpdate in normal operation
index c0efd24..48c624c 100644 (file)
@@ -16,17 +16,17 @@ use OOUI;
  * the results are from.
  */
 class InterwikiSearchResultSetWidget implements SearchResultSetWidget {
-       /** @var SpecialSearch */
+       /** @var SpecialSearch $specialSearch */
        protected $specialSearch;
-       /** @var SearchResultWidget */
+       /** @var SearchResultWidget $resultWidget */
        protected $resultWidget;
-       /** @var string[]|null */
+       /** @var string[]|null $customCaptions */
        protected $customCaptions;
-       /** @var LinkRenderer */
+       /** @var LinkRenderer $linkRenderer */
        protected $linkRenderer;
-       /** @var InterwikiLookup */
+       /** @var InterwikiLookup $iwLookup */
        protected $iwLookup;
-       /** @var $output */
+       /** @var \OutputPage $output */
        protected $output;
        /** @var bool $showMultimedia */
        protected $showMultimedia;
index fe94704..a9bbc20 100644 (file)
@@ -4403,18 +4403,6 @@ class Language {
                return $this->mHtmlCode;
        }
 
-       /**
-        * @param string $code
-        * @deprecated since 1.32, use Language::factory to create a new object instead.
-        */
-       public function setCode( $code ) {
-               wfDeprecated( __METHOD__, '1.32' );
-               $this->mCode = $code;
-               // Ensure we don't leave incorrect cached data lying around
-               $this->mHtmlCode = null;
-               $this->mParentLanguage = false;
-       }
-
        /**
         * Get the language code from a file name. Inverse of getFileName()
         * @param string $filename $prefix . $languageCode . $suffix
index c5af1e9..86da110 100644 (file)
        "page_first": "الأولى",
        "page_last": "الأخيرة",
        "histlegend": "اختيار الفرق: علم على صناديق النسخ للمقارنة واضغط قارن بين النسخ المختارة أو الزر بالأسفل.<br />\nمفتاح: (الحالي) = الفرق مع النسخة الحالية\n(السابق) = الفرق مع النسخة السابقة، ط = تغيير طفيف",
-       "history-fieldset-title": "اÙ\84بحث Ø¹Ù\86 المراجعات",
+       "history-fieldset-title": "تصÙ\81Ù\8aØ© المراجعات",
        "history-show-deleted": "المحذوفة فقط",
        "histfirst": "الأقدم",
        "histlast": "الأحدث",
        "action-changetags": "أضف وأزل وسوما في مراجعات ومدخلات سجل فردية",
        "action-deletechangetags": "حذف الوسوم من قاعدة البيانات",
        "action-purge": "إفراغ كاش هذه الصفحة",
+       "action-apihighlimits": "استخدام حدود أعلى في استعلامات API",
+       "action-autoconfirmed": "غير متأثر بحدود المعدل المستندة إلى الآيبي",
+       "action-bigdelete": "حذف الصفحات ذات التواريخ الكبيرة",
+       "action-blockemail": "منع مستخدم من إرسال بريد إلكتروني",
+       "action-bot": "يًعامَل كعملية آلية",
+       "action-editprotected": "تعديل الصفحات التي حمايتها \"{{int:protect-level-sysop}}\"",
+       "action-editsemiprotected": "تعديل الصفحات التي حمايتها \"{{int:protect-level-autoconfirmed}}\"",
+       "action-editinterface": "تعديل واجهة المستخدم",
+       "action-editusercss": "تعديل ملفات CSS للمستخدمين الآخرين",
+       "action-edituserjson": "تعديل ملفات جسون للمستخدمين الآخرين",
+       "action-edituserjs": "تعديل ملفات جافاسكريبت للمستخدمين الآخرين",
+       "action-editsitecss": "تحرير CSS للموقع بأكمله",
+       "action-editsitejson": "تعديل جسون على مستوى الموقع",
+       "action-editsitejs": "تعديل جافاسكريبت على مستوى الموقع",
+       "action-editmyusercss": "تحرير ملفات CSS المستخدم الخاصة بك",
+       "action-editmyuserjson": "تحرير ملفات جسون المستخدم الخاصة بك",
+       "action-editmyuserjs": "تحرير ملفات جافا سكريبت المستخدم الخاصة بك",
+       "action-viewsuppressed": "عرض المراجعات المخفية بواسطة أي مستخدم",
+       "action-hideuser": "منع اسم مستخدم، مخفيا إياه عن العامة",
+       "action-ipblock-exempt": "تفادي عمليات منع الأيبي والمنع التلقائي ومنع النطاق",
+       "action-unblockself": "رفع المنع عن أنفسهم",
+       "action-noratelimit": "غير متأثر بحدود المعدل",
+       "action-reupload-own": "الكتابة على ملف موجود تم رفعه بواسطة نفس المستخدم",
+       "action-nominornewtalk": "عدم وجود تعديلات طفيفة على صفحات النقاشة تؤدي إلى ظهور رسائل جديدة",
+       "action-markbotedits": "التعليم على تعديلات الاسترجاع كتعديلات بوت",
+       "action-patrolmarks": "رؤية علامات المراجعة في أحدث التغييرات",
+       "action-override-export-depth": "تصدير الصفحات متضمنة الصفحات الموصولة حتى عمق 5",
+       "action-suppressredirect": "عدم إنشاء تحويلة من الاسم القديم عند نقل صفحة",
        "nchanges": "{{PLURAL:$1|لا تغييرات|تغيير واحد|تغييران|$1 تغييرات|$1 تغييرا|$1 تغيير}}",
        "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|منذ الزيارة الأخيرة}}",
        "enhancedrc-history": "تاريخ",
index 19de701..8092ad1 100644 (file)
        "filerenameerror": "\"$1\" faylının adını \"$2\"-yə dəyişmək mümkün deyil",
        "filedeleteerror": "\"$1\" fayılını silə bilmədi.",
        "directorycreateerror": "\"$1\" direktoriyasını yaratmaq mümkün deyil",
-       "directoryreadonlyerror": "\"$1\" təlimatçısı yalnız oxunur.",
+       "directoryreadonlyerror": "\"$1\" təlimatçısı yalnız oxuna bilər.",
        "directorynotreadableerror": "\"$1\" təlimatçısı oxuna bilmir.",
        "filenotfound": "\"$1\" faylını tapa bilmədi.",
        "unexpected": "Uyğunsuzluq: \"$1\"=\"$2\".",
        "userlogin-signwithsecure": "Etibarlı bağlantıdan istifadə edin",
        "cannotlogin-title": "Daxil olmaq mümkün olmadı",
        "cannotlogin-text": "Daxil olmaq mümkün deyil.",
-       "cannotloginnow-title": "İndi daxil olmaq mümkün deyil",
+       "cannotloginnow-title": "Hazırda daxil olmaq mümkün deyil",
        "cannotloginnow-text": "$1 istifadə edərkən daxil olmaq mümkün deyil.",
        "cannotcreateaccount-title": "Hesablar yaradıla bilmədi",
        "cannotcreateaccount-text": "Bu vikidə birbaşa hesab yaratma aktiv deyil.",
        "nosuchusershort": "\"$1\" adlı istifadəçi mövcud deyil. Yazdığınızı yoxlayın.",
        "nouserspecified": "İstifadəçi adı daxil etməlisiniz.",
        "login-userblocked": "Bu istifadəçi bloklanıb. Sistemə giriş üçün icazə verilmir.",
-       "wrongpassword": "Yanlış istifadəçi adı və ya parol.\nZəhmət olmasa bir daha cəhd edin.",
+       "wrongpassword": "Yanlış istifadəçi adı və ya şifrə.\nZəhmət olmasa, bir daha cəhd edin.",
        "wrongpasswordempty": "Parol boş. Təkrar yazın.",
        "passwordtooshort": "Parolda ən azı {{PLURAL:$1|1 hərf yaxud simvol|$1 hərf yaxud simvol}} olmalıdır.",
        "passwordtoolong": "Parolda ən azı {{PLURAL:$1|1 hərf yaxud simvol|$1 hərf yaxud simvol}} olmalıdır.",
        "botpasswords-no-provider": "BotPasswordsSessionProvider mövcud deyil.",
        "botpasswords-restriction-failed": "Bot parol məhdudiyyətləri bu girişə mane olur.",
        "botpasswords-invalid-name": "Göstərilən istifadəçi adı bot ayırıcısını (\"$1\") ehtiva etmir.",
-       "botpasswords-not-exist": "\"$1\" istifadəçisinin \"$2\" adlı bot parolu yoxdur.",
-       "botpasswords-needs-reset": "\"$1\" adlı istifadəçinin \"$2\" bot adı üçün bot parolu sıfırlanmalıdır.",
-       "botpasswords-locked": "Hesabınız kilidləndiyinə görə bot parolu ilə giriş edə bilməzsiniz.",
+       "botpasswords-not-exist": "\"$1\" adlı istifadəçinin \"$2\" adlı bot şifrəsi yoxdur.",
+       "botpasswords-needs-reset": "\"$1\" adlı istifadəçinin \"$2\" bot adı üçün bot şifrəsi sıfırlanmalıdır.",
+       "botpasswords-locked": "Hesabınız kilidləndiyinə görə bot şifrəsi ilə giriş edə bilməzsiniz.",
        "resetpass_forbidden": "Parolu dəyişmək mümkün deyil",
-       "resetpass_forbidden-reason": "Parolu dəyişmək mümkün deyil: $1",
+       "resetpass_forbidden-reason": "Şifrəni dəyişmək mümkün deyil: $1",
        "resetpass-no-info": "Bu səhifəni birbaşa açmaq üçün sistemə daxil olmalısınız.",
        "resetpass-submit-loggedin": "Parolu dəyiş",
        "resetpass-submit-cancel": "Ləğv et",
        "resetpass-wrong-oldpass": "Müvəqqəti və ya daimi parolda yanlışlıq var.\nOla bilər siz parolu müvəffəqiyyətlə dəyişmisiniz, yaxud yeni müvəqqəti parol üçün müraciət etmisiniz.",
-       "resetpass-recycled": "Şifrənizi, mövcud parolunuzdan başqa bir şeyə dəyişdirin.",
-       "resetpass-temp-emailed": "Siz müvəqqəti e-poçt kodu ilə daxil oldunuz. \nDaxil olmağı bitirmək üçün buraya yeni bir parol yazmalısınız:",
+       "resetpass-recycled": "Şifrənizi mövcud şifrədən başqasına dəyişdirin.",
+       "resetpass-temp-emailed": "Siz müvəqqəti e-poçt kodu ilə daxil oldunuz. \nDaxil olmağı bitirmək üçün bura yeni bir şifrə yazmalısınız:",
        "resetpass-temp-password": "Müvəqqəti parol:",
        "resetpass-abort-generic": "Parol dəyişikliyi bir genişlənmə tərəfindən ləğv edildi.",
-       "resetpass-expired": "Parolunuzun müddəti doldu. Giriş etmək üçün yeni bir parol təyin edin.",
-       "resetpass-expired-soft": "Parolunuzun müddəti başa çatdı və dəyişdirilməlidir. Xahiş edirik yeni bir parol seçin və ya daha sonra dəyişdirmək üçün \"{{int:authprovider-resetpass-skip-label}}\" düyməsini basın.",
-       "resetpass-validity": "Parolunuz etibarlı deyil: $1\n\nGiriş etmək üçün yeni bir parol təyin edin.",
-       "resetpass-validity-soft": "Parolunuz etibarlı deyil: $1\n\nXahiş edirik yeni bir parol seçin və ya daha sonra dəyişdirmək üçün \"{{int: authprovider-resetpass-skip-label}}\" düyməsini basın.",
+       "resetpass-expired": "Şifrənizin müddəti doldu. Daxil olmaq üçün yeni bir şifrə təyin edin.",
+       "resetpass-expired-soft": "Şifrənizin müddəti başa çatdığı üçün dəyişdirilməlidir. Xahiş edirik, yeni bir şifrə seçin və ya daha sonra dəyişdirmək üçün \"{{int:authprovider-resetpass-skip-label}}\" düyməsinə basın.",
+       "resetpass-validity": "Şifrəniz etibarlı deyil: $1\n\nDaxil olmaq üçün yeni bir şifrə təyin edin.",
+       "resetpass-validity-soft": "Şifrəniz etibarlı deyil: $1\n\nXahiş edirik yeni bir şifrə seçin və ya daha sonra dəyişdirmək üçün \"{{int: authprovider-resetpass-skip-label}}\" düyməsinə basın.",
        "passwordreset": "Parolu yenilə",
        "passwordreset-text-one": "Parolunuzu sıfırlamaq üçün bu formanı doldurun.",
        "passwordreset-text-many": "{{PLURAL:$1|Parolunuzu sıfırlamaq üçün sahələrdən birini doldurun.}}",
        "changeemail-newemail": "Yeni e-poçt ünvanı:",
        "changeemail-newemail-help": "E-poçt ünvanınızı çıxarmaq istəyirsinizsə, bu sahə boş olmalıdır. Unudulan bir parolu sıfırlaya bilməyəcəksiniz və e-poçt ünvanı çıxarıldıqda bu vikidən e-poçt ala bilməyəcəksiniz.",
        "changeemail-none": "(yoxdur)",
-       "changeemail-password": "Sizin {{SITENAME}} parolunuz:",
+       "changeemail-password": "Sizin {{SITENAME}} şifrəniz:",
        "changeemail-submit": "E-poçtu dəyiş",
        "changeemail-throttled": "Sistemə daxil olmaq üçün həddən artıq cəhd etmisiniz.\nYeni cəhd etməzdən əvvəl $1 gözləyin.",
        "changeemail-nochange": "Fərqli bir yeni e-poçt ünvanı daxil edin.",
        "createaccountblock": "Yeni hesab yaratma bloklanıb",
        "emailblock": "E-mail bloklanıb",
        "blocklist-nousertalk": "Müzakirə səhifəsini redaktə edə bilməz.",
-       "blocklist-editing-page": "aəhifələr",
+       "blocklist-editing-page": "səhifələr",
        "blocklist-editing-ns": "adlar fəzası",
        "ipblocklist-empty": "Blok siyahısı boşdur.",
        "ipblocklist-no-results": "Tələb olunan IP ünvanı və ya istifadəçi bloklanmadı.",
        "confirm-purge-top": "Bu səhifə keşdən (cache) silinsin?",
        "confirm-watch-button": "OK",
        "confirm-unwatch-button": "OK",
-       "confirm-rollback-button": "TAMAM",
+       "confirm-rollback-button": "Oldu",
        "quotation-marks": "\"$1\"",
        "imgmultipageprev": "← əvvəlki səhifə",
        "imgmultipagenext": "sonrakı səhifə →",
index 7a5b6e0..03f7a7b 100644 (file)
        "page_first": "першая",
        "page_last": "апошняя",
        "histlegend": "Параўнаньне: адзначце пунктамі дзьве вэрсіі для параўнаньня і націсьніце «ўвод» альбо кнопку ўнізе.<br />\nТлумачэньне: <strong>({{int:cur}})</strong> = адрозьненьні ад цяперашняй вэрсіі, <strong>({{int:last}})</strong> = адрозьненьні ад папярэдняй вэрсіі, <strong>{{int:minoreditletter}}</strong> = дробная праўка.",
-       "history-fieldset-title": "Ð\9fоÑ\88Ñ\83к вэрсіяў",
+       "history-fieldset-title": "ФÑ\96лÑ\8cÑ\82аÑ\80 вэрсіяў",
        "history-show-deleted": "Толькі выдаленыя",
        "histfirst": "найстарэйшыя",
        "histlast": "найноўшыя",
        "action-changetags": "дадаваньне і выдаленьне адвольных метак да асобных вэрсіяў і запісаў у журнале падзеяў",
        "action-deletechangetags": "выдаленьне метак з базы зьвестак",
        "action-purge": "ачыстку кэшу гэтай старонкі",
+       "action-apihighlimits": "ўжываньне падвышаных лімітаў у API-запытах",
+       "action-autoconfirmed": "адсутнасьць абмежаваньня хуткасьці паводле IP-адрасу",
+       "action-bigdelete": "выдаленьне старонак зь вялікай гісторыяй",
        "nchanges": "$1 {{PLURAL:$1|зьмена|зьмены|зьменаў}}",
        "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|з апошняга візыту}}",
        "enhancedrc-history": "гісторыя",
        "allpagesbadtitle": "Пададзеная назва старонкі была няслушная ці пачыналася зь міжмоўнай ці міжвікі спасылкі. Яна яшчэ можа ўтрымліваць сымбалі, якія ня могуць ужывацца ў назвах.",
        "allpages-bad-ns": "{{SITENAME}} ня мае прасторы назваў «$1».",
        "allpages-hide-redirects": "Схаваць перанакіраваньні",
-       "cachedspecial-viewing-cached-ttl": "Ð\92Ñ\8b Ð¿Ñ\80аглÑ\8fдаеÑ\86е Ð·Ð°ÐºÑ\8dÑ\88аванÑ\83Ñ\8e Ð²Ñ\8dÑ\80Ñ\81Ñ\96Ñ\8e Ñ\81Ñ\82аÑ\80онкÑ\96, Ñ\8fкаÑ\8f Ð¼Ð°Ð³Ð»Ð° Ð±Ñ\8bÑ\86Ñ\8c Ð°Ð±Ð½Ð¾Ñ\9eленаÑ\8f $1 Ñ\82амÑ\83.",
-       "cachedspecial-viewing-cached-ts": "Ð\92Ñ\8b Ð¿Ñ\80аглÑ\8fдаеÑ\86е Ð·Ð°ÐºÑ\8dÑ\88аванÑ\83Ñ\8e Ð²Ñ\8dÑ\80Ñ\81Ñ\96Ñ\8e Ñ\81Ñ\82аÑ\80онкÑ\96, Ñ\8fкаÑ\8f Ð¼Ð¾Ð¶Ð° Ð±Ñ\8bÑ\86Ñ\8c Ð½ÐµÐ°ÐºÑ\82Ñ\83алÑ\8cнай.",
+       "cachedspecial-viewing-cached-ttl": "Вы праглядаеце кэшаваную вэрсію старонкі, якая магла быць абноўленая $1 таму.",
+       "cachedspecial-viewing-cached-ts": "Вы праглядаеце кэшаваную вэрсію старонкі, якая можа быць неактуальнай.",
        "cachedspecial-refresh-now": "Пабачыць апошнюю вэрсію.",
        "categories": "Катэгорыі",
        "categories-submit": "Паказаць",
        "categoriespagetext": "{{PLURAL:$1|1=Наступная катэгорыя існуе ў вікі і можа|Наступныя катэгорыі існуюць у вікі і могуць}} выкарыстоўвацца ці не выкарыстоўвацца.\nГлядзіце таксама [[Special:WantedCategories|сьпіс запатрабаваных катэгорыяў]].",
-       "categoriesfrom": "Паказаць катэгорыі, пачынаючы з:",
+       "categoriesfrom": "Паказаць катэгорыі ад:",
        "deletedcontributions": "Выдалены ўнёсак удзельніка",
        "deletedcontributions-title": "Выдалены ўнёсак удзельніка",
        "sp-deletedcontributions-contribs": "унёсак",
index 5aaea0d..14a625e 100644 (file)
        "index-category": "Pajennoù menegeret",
        "noindex-category": "Pajennoù n'int ket menegeret",
        "broken-file-category": "Pajennoù enno liamm torr war-zu restroù",
+       "categoryviewer-pagedlinks": "($1) ($2)",
+       "category-header-numerals": "$1–$2",
        "about": "Diwar-benn",
        "article": "Pennad",
        "newwindow": "(digeriñ en ur prenestr nevez)",
        "returnto": "Distreiñ d'ar bajenn $1.",
        "tagline": "Eus {{SITENAME}}",
        "help": "Skoazell",
+       "help-mediawiki": "Skoazell evit MediaWiki",
        "search": "Klask",
        "search-ignored-headings": " #<!-- lezel al linenn-mañ tre evel m'emañ --> <pre>\n# Titloù a vo lezet a-gostez gant ar c'hlask.\n# Ar c'hemmoù graet amañ a vo lakaet e pleustr kerkent ha menegeret ar bajenn gant an titl.\n# Gallout a rit forsiñ un advenegeriñ ma rit ur c'hemm goullo en ur bajenn.\n# Setu penaos emañ an ereadur :\n#   * Kement linenn zo, adalek an arouezenn \"#\" betek dibenn al linenn a zo un evezhiadenn.\n#   * Kement linenn ha n'eo ket goullo a verk an titl rik a zo da vezañ lezet a-gostez, pennlizherennoù hag all.\nDaveennoù\nLiammoù diavaez\nGwelet ivez\n #</pre> <!-- lezel al linenn-mañ tre evel m'emañ -->",
        "searchbutton": "Klask",
        "versionrequired": "Rekis eo Stumm $1 MediaWiki",
        "versionrequiredtext": "Rekis eo stumm $1 MediaWiki evit implijout ar bajenn-mañ. Sellit ouzh [[Special:Version]]",
        "ok": "Mat eo",
+       "pagetitle": "$1 - {{SITENAME}}",
+       "pagetitle-view-mainpage": "{{SITENAME}}",
+       "backlinksubtitle": "← $1",
        "retrievedfrom": "Adtapet diwar « $1 »",
        "youhavenewmessages": "$1 zo ganeoc'h ($2).",
        "youhavenewmessagesfromusers": "{{PLURAL:$4|Bez' hoc'h eus}} $1 a-berzh {{PLURAL:$3|un implijer all|$3 implijer}} ($2).",
        "site-atom-feed": "Lanv Atom evit $1",
        "page-rss-feed": "Lanv RSS evit \"$1\"",
        "page-atom-feed": "Lanv Atom evit \"$1\"",
+       "feed-atom": "Atom",
+       "feed-rss": "RSS",
        "red-link-title": "$1 (n'eus ket eus ar bajenn-mañ)",
        "sort-descending": "Urzhiañ war-draoñ",
        "sort-ascending": "Urzhiañ war-laez",
        "nocookiesnew": "Krouet eo bet ar gont implijer met n'oc'h ket kevreet. {{SITENAME}} a implij toupinoù evit ar c'hevreañ met diweredekaet eo an toupinoù ganeoc'h. Trugarez da weredekaat anezho ha da gevreañ en-dro.",
        "nocookieslogin": "{{SITENAME}} a implij toupinoù evit kevreañ met diweredekaet eo an toupinoù ganeoc'h. Trugarez da weredekaat anezho ha da gevreañ en-dro.",
        "nocookiesfornew": "N'eo ket bet krouet ar gont implijer peogwir n'eus ket bet gallet gwiriañ an orin anezhi.\nGwiriit eo bet gweredekaet an toupinoù, adkargit ar bajenn ha klaskit en-dro.",
+       "nocookiesforlogin": "{{int:nocookieslogin}}",
        "createacct-loginerror": "Krouet eo bet ar gont ervat met n'hallit ket kevreañ en un doare emgefre. [[Special:UserLogin|Kevreit gant an dorn]].",
        "noname": "N'hoc'h eus lakaet anv implijer ebet.",
        "loginsuccesstitle": "Kevreet oc'h.",
        "template-semiprotected": "(damwarezet)",
        "hiddencategories": "{{PLURAL:$1|1 rummad kuzhet|$1 rummad kuzhet}} m'emañ rollet ar bajenn-mañ :",
        "edittools": "<!-- Diskouezet e vo an destenn kinniget amañ dindan ar sternioù kemmañ ha kargañ. -->",
+       "edittools-upload": "-",
        "nocreatetext": "Strishaet eo bet an tu da grouiñ pajennoù nevez war {{SITENAME}}.\nGallout a rit mont war-gil ha lakaat kemmañ ur bajenn zo anezhi dija, pe [[Special:UserLogin|en em enrollañ ha krouiñ ur gont]].",
        "nocreate-loggedin": "N'oc'h ket aotreet da grouiñ pajennoù nevez.",
        "sectioneditnotsupported-title": "N'eo ket skoret ar c'hemmañ rannoù",
        "editpage-invalidcontentmodel-text": "N'eo ket skoret ar patrom danvez \"$1\".",
        "editpage-notsupportedcontentformat-title": "Furmad endalc'had ha n'eo ket kemeret e karg",
        "editpage-notsupportedcontentformat-text": "N'eo ket skoret ar patrom $1 gant ar patrom danvez $2.",
+       "slot-name-main": "Pennañ",
        "content-model-wikitext": "wikitestenn",
        "content-model-text": "testenn blaen",
        "content-model-javascript": "Javascript",
        "content-model-css": "CSS",
+       "content-model-json": "JSON",
        "content-json-empty-object": "Elfenn goullo",
        "content-json-empty-array": "Taolenn c'houllo",
        "deprecated-self-close-category": "Pajennoù a ra gant tikedennoù HTML emserriñ direizh",
        "page_first": "kentañ",
        "page_last": "diwezhañ",
        "histlegend": "Sellet ouzh an diforc'hioù : lakait un ask adal d'ar stummoù a fell deoc'h keñveriañ ha pouezit war kadarnaat pe war ar bouton en traoñ.<br />\nAlc'hwez : (red) = diforc'hioù gant ar stumm a-vremañ,\n(diwez) = diforc'hioù gant ar stumm kent, D = kemm dister",
-       "history-fieldset-title": "Klask adweladennoù",
+       "history-fieldset-title": "Silañ an adweladennoù",
        "history-show-deleted": "Stumm diverket hepken",
        "histfirst": "koshañ",
        "histlast": "nevezañ",
        "historysize": "({{PLURAL:$1|$1 okted|$1 okted}})",
-       "historyempty": "(goullo)",
+       "historyempty": "goullo",
        "history-feed-title": "Istor ar c'hemmoù",
        "history-feed-description": "Istor ar c'hemmoù degaset war ar bajenn-mañ eus ar wiki",
        "history-feed-item-nocomment": "$1 d'an $2",
        "mergehistory-comment": "Kendeuzet [[:$1]] gant [[:$2]] : $3",
        "mergehistory-same-destination": "N'hall ket ar pajennoù kein hag ar pajennoù tal bezañ an hevelep re",
        "mergehistory-reason": "Abeg :",
+       "mergehistory-revisionrow": "$1 ($2) $3 . . $4 $5 $6",
        "mergelog": "Marilh ar c'hendeuzadennoù",
        "revertmerge": "Nullañ ar c'hendeuziñ",
        "mergelogpagetext": "Setu aze roll kendeuzadennoù diwezhañ un eil pajenn istor gant eben.",
        "youremail": "Postel :",
        "username": "{{GENDER:$1|Anv implijer|Anv implijerez}}:",
        "prefs-memberingroups": "{{GENDER:$2|Ezel}} eus {{PLURAL:$1|ar strollad|ar strolladoù}}:",
+       "prefs-memberingroups-type": "$1",
        "group-membership-link-with-expiry": "$1 (betek $2)",
        "prefs-registration": "Deiziad enskrivañ :",
+       "prefs-registration-date-time": "$1",
        "yourrealname": "Anv gwir*",
        "yourlanguage": "Yezh an etrefas&nbsp;",
        "yourvariant": "Adstumm yezh :",
        "prefs-advancedwatchlist": "Dibarzhioù araokaet",
        "prefs-displayrc": "Dibarzhioù diskwel",
        "prefs-displaywatchlist": "Dibarzhioù diskwel",
+       "prefs-changesrc": "Kemmoù war wel",
+       "prefs-changeswatchlist": "Kemmoù war wel",
+       "prefs-pageswatchlist": "Pajennoù evezhiet",
        "prefs-tokenwatchlist": "Jedouer",
        "prefs-diffs": "Diforc'hioù",
        "prefs-help-prefershttps": "Efediñ a ray an dibarzh-mañ kentañ gwech ma kevreoc'h.",
        "saveusergroups": "Enrollañ strolladoù an {{GENDER:$1|implijer|implijerez}}",
        "userrights-groupsmember": "Ezel eus :",
        "userrights-groupsmember-auto": "Ezel emplegat eus :",
+       "userrights-groupsmember-type": "$1",
        "userrights-groups-help": "Cheñch strolladoù an implijer a c'hallit ober.\n* Ul log asket a verk emañ an implijer er strollad.\n* Ul log diask a verk n'emañ ket an implijer er strollad.\n* Ur * a verk n'hallit ket dilemel ar strollad ur wech bet ouzhpennet ganeoc'h, pe ar c'hontrol.\n* Ur # a verk e c'hallit astenn termen an emezelañ er strollad hepken ; n'hallit ket berraat anezhañ.",
        "userrights-reason": "Abeg :",
        "userrights-no-interwiki": "N'oc'h ket aotreet da gemmañ ar gwirioù implijer war wikioù all.",
        "userrights-nodatabase": "N'eus ket eus an diaz titouroù $1 pe n'eo ket lec'hel.",
        "userrights-changeable-col": "Ar strolladoù a c'hallit cheñch",
        "userrights-unchangeable-col": "Ar strolladoù n'hallit ket cheñch",
+       "userrights-irreversible-marker": "$1*",
+       "userrights-no-shorten-expiry-marker": "$1#",
        "userrights-expiry-current": "A ya d'e dermen d'an $1",
        "userrights-expiry-none": "Didermen",
        "userrights-expiry": "Termen :",
        "group-autoconfirmed": "Implijerien bet kadarnaet ent emgefre",
        "group-bot": "Robotoù",
        "group-sysop": "Merourien",
+       "group-interface-admin": "Merourien an etrefas",
        "group-bureaucrat": "Burevidi",
        "group-suppress": "Diverkerien",
        "group-all": "(pep tra)",
        "group-autoconfirmed-member": "{{GENDER:$1|Implijer bet kadarnaet ent emgefre}}",
        "group-bot-member": "{{GENDER:$1|robot}}",
        "group-sysop-member": "{{GENDER:$1|merour}}",
+       "group-interface-admin-member": "{{GENDER:$1|merour an etrefas|merourez an etrefas}}",
        "group-bureaucrat-member": "{{GENDER:$1|bureviad}}",
        "group-suppress-member": "{{GENDER:$1|diverker|diverkerez}}",
        "grouppage-user": "{{ns:project}}:Implijerien",
        "grouppage-autoconfirmed": "{{ns:project}}: Implijerien bet kadarnaet ent emgefre",
        "grouppage-bot": "{{ns:project}}:Botoù",
        "grouppage-sysop": "{{ns:project}}:Merourien",
+       "grouppage-interface-admin": "{{ns:project}}:Merourien etrefas",
        "grouppage-bureaucrat": "{{ns:project}}: Burevidi",
        "grouppage-suppress": "{{ns:project}}: Diverkerien",
        "right-read": "Lenn ar pajennoù",
        "right-reupload-own": "Frikañ ur restr bet pellgarget gant an-unan",
        "right-reupload-shared": "Gwaskañ restroù ent lec'hel war an diellaoueg vedia rannet",
        "right-upload_by_url": "Enporzhiañ ur restr adal ur chomlec'h URL",
-       "right-purge": "Spujañ krubuilh ar pajennoù hep kadarnaat",
+       "right-purge": "Spurjañ krubuilh ur bajenn eus al lec'hienn",
        "right-autoconfirmed": "Na vezañ trubuilhet gant ar bevennoù kas liammet ouzh ar chomlec'hioù IP",
        "right-bot": "Plediñ ganti evel gant un argerzh emgefre",
        "right-nominornewtalk": "Arabat diskouez ar c'hemenn \"Kemennoù nevez zo ganeoc'h\" pa vez lakaet kemmoù dister e pajenn gaozeal un implijer",
        "action-deletechangetags": "Diverkañ tikedennoù a-ziwar an diaz-roadennoù",
        "action-purge": "spurjañ ar bajenn-mañ",
        "nchanges": "$1 {{PLURAL:$1|kemm|kemm}}",
+       "ntimes": "$1×",
        "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|abaoe ho kweladenn diwezhañ}}",
        "enhancedrc-history": "istor",
        "recentchanges": "Kemmoù diwezhañ",
        "recentchanges-label-plusminus": "Kemmet eo ment ar bajenn eus an niver-mañ a oktedoù",
        "recentchanges-legend-heading": "<strong>Alc'hwez :</strong>",
        "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (gwelet ivez [[Special:NewPages|roll ar pajennoù nevez]])",
+       "recentchanges-legend-plusminus": "(<em>±123</em>)",
        "recentchanges-submit": "Diskouez",
        "rcfilters-tag-remove": "Dilemel '$1'",
        "rcfilters-other-review-tools": "Ostilhoù reizhañ all",
        "rcfilters-tag-prefix-namespace-inverted": "<strong>:not</strong> $1",
        "rcfilters-exclude-button-off": "Skarzhañ ar re dibabet",
        "rcfilters-exclude-button-on": "Re dibabet skarzhet",
+       "rcfilters-view-tags": "Kemmoù merket",
        "rcfilters-filter-showlinkedfrom-label": "Diskouez ar c'hemmoù war ar bajennoù liammet",
        "rcfilters-filter-showlinkedto-label": "Diskouez ar c'hemmoù war ar bajennoù liammet",
        "rcfilters-target-page-placeholder": "Skrivañ anv ar bajenn (pe rummad)",
        "minoreditletter": "D",
        "newpageletter": "N",
        "boteditletter": "b",
+       "unpatrolledletter": "!",
        "number_of_watching_users_pageview": "[$1 {{PLURAL:$1|implijer o heuliañ|implijer}} o heuliañ]",
+       "rc-change-size": "$1",
        "rc-change-size-new": "$1 {{PLURAL:$1|okted|okted}} goude kemmañ",
        "newsectionsummary": "/* $1 */ rann nevez",
        "rc-enhanced-expand": "Diskouez ar munudoù",
        "uploadnewversion-linktext": "Kargañ ur stumm nevez eus ar restr-mañ",
        "shared-repo-from": "eus $1",
        "shared-repo": "ur sanailh rannet",
+       "shared-repo-name-wikimediacommons": "Wikimedia Commons",
        "upload-disallowed-here": "Ne c'hallit ket erlec'hiañ ar restr-mañ",
        "filerevert": "Disteuler $1",
        "filerevert-legend": "Disteuler ar restr",
        "apisandbox-request-selectformat-label": "Diskouez roadennoù ar reked evel :",
        "apisandbox-request-format-url-label": "Neudennad reked an URL",
        "apisandbox-request-url-label": "Goulenn URL :",
+       "apisandbox-request-format-json-label": "JSON",
        "apisandbox-request-json-label": "Goulenn JSON :",
        "apisandbox-request-time": "Pad ar goulenn : {{PLURAL:$1|$1 ms}}",
        "apisandbox-results-fixtoken": "Reizhañ ar jedouer ha kas en-dro",
        "deleteprotected": "Ne c'hallit ket dilemel ar bajenn-mañ rak gwarezet eo bet.",
        "deleting-backlinks-warning": "<strong>Taolit pled :</strong> [[Special:WhatLinksHere/{{FULLPAGENAME}}|Pajennoù all]] zo enno liammoù war-zu ar bajenn emaoc'h o vont da zilemel pe a zo treuzkludet ar bajenn enno.",
        "rollback": "disteuler ar c'hemmoù",
+       "rollback-confirmation-no": "Nullañ",
        "rollbacklink": "disteuler",
        "rollbacklinkcount": "disteurel $1 {{PLURAL:$1|kemm}}",
        "rollbacklinkcount-morethan": "disteurel ouzhpenn $1 {{PLURAL:$1|kemm}}",
        "protect-fallback": "Degemer hepken an implijerien gant an aotre \"$1\"",
        "protect-level-autoconfirmed": "Degemer hepken an implijerien emgadarnaet",
        "protect-level-sysop": "Aotren ar verourien hepken",
+       "protect-summary-desc": "[$1=$2] ($3)",
        "protect-summary-cascade": "Gwareziñ dre skalierad",
        "protect-expiring": "a zeu d'e dermen d'an $1",
        "protect-expiring-local": "a ya d'e dermen d'an $1",
        "undelete-error-long": "Fazioù zo bet kavet e-ser diziverkañ ar restr :\n\n$1",
        "undelete-show-file-confirm": "Ha sur oc'h e fell deoc'h sellet ouzh ur stumm diverket eus ar restr \"<nowiki>$1</nowiki>\" a sav d'an $2 da $3?",
        "undelete-show-file-submit": "Ya",
+       "undelete-revision-row2": "$1 ($2) $3 . . $4 $5 $6 $7 $8",
        "namespace": "Esaouenn anv :",
        "invert": "Eilpennañ an dibab",
        "tooltip-invert": "Askañ ar voest-mañ da guzhat ar c'hemmoù er pajennoù stag ouzh an esaouenn anv diuzet (hag an esaouenn anv stag, m'emañ asket)",
        "uctop": "red",
        "month": "Abaoe miz (hag a-raok) :",
        "year": "Abaoe bloaz (hag a-raok) :",
+       "date": "Abaoe an (hag a-raok) :",
        "sp-contributions-newbies": "Diskouez hepken degasadennoù ar c'hontoù nevez",
        "sp-contributions-newbies-sub": "Evit an implijerien nevez",
        "sp-contributions-newbies-title": "Degasadennoù implijer evit ar c'hontoù nevez",
        "ipb-change-block": "Adstankañ an implijer-mañ gant an hevelep arventennoù",
        "ipb-confirm": "Kadarnaat ar stankadenn",
        "ipb-pages-label": "Pajennoù",
+       "ipb-namespaces-label": "Esaouennoù anv",
        "badipaddress": "Kamm eo ar chomlec'h IP.",
        "blockipsuccesssub": "Stankadenn deuet da benn vat",
        "blockipsuccesstext": "Stanket eo bet [[Special:Contributions/$1|$1]].<br />\nSellit ouzh [[Special:BlockList|roll ar chomlec'hioù IP ha kontoù stanket]] evit gwiriañ ar stankadennoù.",
        "ipb-blocklist-contribs": "Degasadennoù evit $1",
        "ipb-blocklist-duration-left": "$1 a chom",
        "block-expiry": "Pad ar stankadenn",
+       "block-prevent-edit": "O kemmañ",
+       "block-reason": "Abeg :",
+       "block-target": "Anv implijer pe chomlec'h IP :",
        "unblockip": "Distankañ ur chomlec'h IP",
        "unblockiptext": "Grit gant ar furmskrid a-is evit adsevel ar moned skrivañ ouzh ur chomlec'h IP bet stanket a-gent.",
        "ipusubmit": "Paouez gant ar stankadenn-mañ",
        "blocklist-nousertalk": "n'hall ket kemmañ e bajenn gaozeal dezhañ e-unan",
        "blocklist-editing": "O kemmañ",
        "blocklist-editing-sitewide": "o kemmañ (al lec'hienn a-bezh)",
+       "blocklist-editing-page": "pajennoù",
+       "blocklist-editing-ns": "esaouennoù anv",
        "ipblocklist-empty": "Goullo eo roll ar stankadennoù.",
        "ipblocklist-no-results": "An anv implijer pe ar chomlec'h IP goulennet n'eo ket stanket anezhañ.",
        "blocklink": "stankañ",
        "ip_range_toolarge": "N'eo ket aotreet stankañ pajennoù brasoc'h evit /$1.",
        "proxyblocker": "Stanker proksi",
        "proxyblockreason": "Stanket eo bet hoc'h IP rak ur proksi digor eo. Trugarez da gelaouiñ ho pourvezer moned ouzh ar Genrouedad pe ho skoazell deknikel eus ar gudenn surentez-mañ.",
+       "sorbs": "DNSBL",
        "sorbsreason": "Rollet eo ho chomlec'h IP evel ur proksi digor en DNSBL implijet gant {{SITENAME}}.",
        "sorbs_create_account_reason": "Rollet eo ho chomlec'h IP evel ur proksi digor war an DNSBL implijet gant {{SITENAME}}. N'hallit ket krouiñ ur gont",
        "softblockrangesreason": "N'eo ket aotreet kemmañ netra ebet gan tho chomlec'h IP ($1). Kevreit mar plij.",
        "tooltip-undo": "\"Dizober\" a zistaol ar c'hemm-mañ hag a zigor ar prenestr skridaozañ er mod rakwelet.\nTalvezout a ra da ouzhpennañ un displegadenn er c'hombod diverrañ.",
        "tooltip-preferences-save": "Enrollañ ar penndibaboù",
        "tooltip-summary": "Skrivit un diveradenn verr",
+       "interlanguage-link-title": "$1 – $2",
+       "interlanguage-link-title-nonlang": "$1 – $2",
        "common.css": "/** Talvezout a raio ar CSS lakaet amañ evit an holl wiskadurioù */",
        "print.css": "/* Talvezout a raio ar CSS lakaet amañ evit ar moullañ */",
        "noscript.css": "/* Talvezout a raio ar CSS lakaet amañ evit an implijerien o deus diweredekaet JavaScript */",
        "pageinfo-few-watchers": "Nebeutoc'h eget $1 {{PLURAL:$1|lenner}}",
        "pageinfo-few-visiting-watchers": "Gallout a ra bezañ, pe get, un implijer o teurel ur sell ouzh ar c'hemmoù diwezhañ",
        "pageinfo-redirects-name": "Niver a adkasoù war-zu ar bajenn-mañ",
+       "pageinfo-redirects-value": "$1",
        "pageinfo-subpages-name": "Ispajennoù eus ar bajenn-mañ",
        "pageinfo-subpages-value": "$1 ($2 {{PLURAL:$2|kasadur|kasadurioù}}; $3 {{PLURAL:$3|nann kasaduri|nann kasadurioù}})",
        "pageinfo-firstuser": "Krouer ar bajenn",
        "confirm-mcrundo-title": "Dizober ur c'hemm",
        "mcrundofailed": "Dizober c'hwitet",
        "mcrundo-missingparam": "Arventennoù rekis d'ar reked a vank.",
+       "ellipsis": "...",
+       "percent": "$1%",
+       "parentheses": "($1)",
+       "parentheses-start": "(",
+       "parentheses-end": ")",
+       "brackets": "[$1]",
        "quotation-marks": "« $1 »",
        "imgmultipageprev": "← pajenn gent",
        "imgmultipagenext": "pajenn war-lerc'h →",
        "imgmultigo": "Mont !",
        "imgmultigoto": "Mont d'ar bajenn $1",
+       "img-lang-opt": "$2 ($1)",
        "img-lang-default": "(yezh dre ziouer)",
        "img-lang-info": "Diskouez ar skeudenn-mañ e $1. $2",
        "img-lang-go": "Mont",
        "log-action-filter-suppress-reblock": "Diverkañ implijerien dre stankadennoù lies",
        "log-action-filter-upload-upload": "Enporzhiadenn nevez",
        "log-action-filter-upload-overwrite": "Adenporzhiañ",
+       "log-action-filter-upload-revert": "Disteuler",
        "authmanager-authn-not-in-progress": "Ne'z a ket war-raok an dilesadur pe kollet ez eus bet roadennoù dalc'h marteze. Adkrogit adalek ar penn-kentañ.",
        "authmanager-authn-no-primary": "N'eus ket bet gallet gwiriañ an titouroù kred lakaet.",
        "authmanager-authn-no-local-user": "An titouroù anaout pourchaset n'int ket liammet gant implijer ebet er wiki-mañ.",
index 99ee8dc..53d5b40 100644 (file)
        "page_first": "primera",
        "page_last": "última",
        "histlegend": "Simbologia: (act) = diferència amb la versió actual,\n(prev) = diferència amb la versió anterior, m = modificació menor",
-       "history-fieldset-title": "Cerca revisions",
+       "history-fieldset-title": "Filtra les revisions",
        "history-show-deleted": "Només revisions suprimides",
        "histfirst": "les més antigues",
        "histlast": "les més noves",
        "historysize": "({{PLURAL:$1|1 octet|$1 octets}})",
-       "historyempty": "(buit)",
+       "historyempty": "buit",
        "history-feed-title": "Historial de revisió",
        "history-feed-description": "Historial de revisió per a aquesta pàgina del wiki",
        "history-feed-item-nocomment": "$1 a $2",
index 6dfc160..fa11cb5 100644 (file)
        "page_first": "první",
        "page_last": "poslední",
        "histlegend": "(teď) = rozdíly oproti nynější verzi, (předchozí) = rozdíly oproti předchozí verzi, <b>m</b> = malá editace",
-       "history-fieldset-title": "Hledat revize",
+       "history-fieldset-title": "Filtrovat revize",
        "history-show-deleted": "Pouze smazané",
        "histfirst": "nejstarší",
        "histlast": "nejnovější",
index 4bcd9a1..1b4f6ff 100644 (file)
        "page_first": "Anfang",
        "page_last": "Ende",
        "histlegend": "Zur Anzeige der Änderungen einfach die zu vergleichenden Versionen auswählen und die Schaltfläche „{{int:compareselectedversions}}“ klicken.<br />\n* ({{int:cur}}) = Unterschied zur aktuellen Version, ({{int:last}}) = Unterschied zur vorherigen Version\n* Uhrzeit/Datum = Version zu dieser Zeit, Benutzername/IP-Adresse des Bearbeiters, {{int:minoreditletter}} = Kleine Änderung",
-       "history-fieldset-title": "Nach Versionen suchen",
+       "history-fieldset-title": "Versionen filtern",
        "history-show-deleted": "Nur gelöschte Versionen zeigen",
        "histfirst": "älteste",
        "histlast": "neueste",
        "action-changetags": "beliebige Markierungen zu einzelnen Versionen und Logbucheinträgen hinzuzufügen und zu entfernen",
        "action-deletechangetags": "Markierungen aus der Datenbank zu löschen",
        "action-purge": "den Cache dieser Seite zu leeren",
+       "action-apihighlimits": "höhere Beschränkungen in API-Anfragen zu verwenden",
+       "action-autoconfirmed": "nicht von IP-basierten Limits betroffen zu sein",
+       "action-bigdelete": "Seiten mit großer Versionsgeschichte zu löschen",
+       "action-blockemail": "Benutzer am Versenden von E-Mails zu hindern",
+       "action-bot": "als automatischer Prozess behandelt zu werden",
+       "action-editprotected": "Seiten zu bearbeiten, die als „{{int:protect-level-sysop}}“ geschützt sind",
+       "action-editsemiprotected": "Seiten zu bearbeiten, die als „{{int:protect-level-autoconfirmed}}“ geschützt sind",
+       "action-editinterface": "Systemnachrichten und Benutzeroberflächen zu bearbeiten",
+       "action-editusercss": "fremde CSS-Dateien zu bearbeiten",
+       "action-edituserjson": "JSON-Dateien anderer Benutzer zu bearbeiten",
+       "action-edituserjs": "fremde JavaScript-Dateien zu bearbeiten",
+       "action-editsitecss": "wikiweit CSS zu bearbeiten",
+       "action-editsitejson": "wikiweites JSON zu bearbeiten",
+       "action-editsitejs": "wikiweites JavaScript zu bearbeiten",
+       "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-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",
+       "action-unblockself": "dich zu entsperren",
+       "action-noratelimit": "nicht von Limits betroffen zu sein",
+       "action-reupload-own": "eine zuvor selbst hochgeladene Datei zu überschreiben",
+       "action-nominornewtalk": "dass kleine Bearbeitungen an Diskussionsseiten nicht die „Neue Nachrichten“-Anzeige auslösen",
+       "action-markbotedits": "schnell zurückgesetzte Bearbeitungen als Bot-Bearbeitung zu markieren",
+       "action-patrolmarks": "Kontrollmarkierungen in den letzten Änderungen anzusehen",
+       "action-override-export-depth": "Seiten einschließlich verlinkter Seiten bis zu einer Tiefe von 5 zu exportieren",
+       "action-suppressredirect": "beim Verschieben die Erstellung einer Weiterleitung zu unterdrücken",
        "nchanges": "$1 {{PLURAL:$1|Änderung|Änderungen}}",
        "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|seit dem letzten Besuch}}",
        "enhancedrc-history": "Versionsgeschichte",
        "unusedimages": "Verwaiste Dateien",
        "wantedcategories": "Gewünschte Kategorien",
        "wantedpages": "Gewünschte Seiten",
-       "wantedpages-summary": "Liste nicht vorhandener Seiten mit den meisten Links auf diese Seiten, ausschließlich solche, die nur Weiterleitungen haben. Für eine Liste nicht vorhandener Seiten mit Weiterleitungen, siehe [[{{#special:BrokenRedirects}}|die Liste defekter Weiterleitungen]].",
+       "wantedpages-summary": "Liste der am häufigsten verlinkten, aber nicht vorhandenen Seiten. Hiervon ausgenommen sind Seiten, die ausschließlich als Ziele defekter Weiterleitungen dienen. Siehe dafür [[{{#special:BrokenRedirects}}|die Liste defekter Weiterleitungen]].",
        "wantedpages-badtitle": "Ungültiger Titel im Ergebnis: $1",
        "wantedfiles": "Gewünschte Dateien",
        "wantedfiletext-cat": "Die folgenden Dateien werden verwendet, sind jedoch nicht vorhanden. Vorhandene Dateien aus fremden Repositorien können dennoch hier aufgelistet sein und werden <del>durchgestrichen</del> dargestellt. Zusätzlich werden Seiten, die nicht vorhandene Dateien enthalten, in die [[:$1]] eingeordnet.",
index dd50dea..b10bd1d 100644 (file)
        "acct_creation_throttle_hit": "Yew ten IP adresê şıma xebıtnayo u kewto no wiki, $2roco peyin de {{PLURAL:$1|1 hesabi|$1 hesaban}} vıraşto.\nxulasa ney kesê ke IP adresê şıma xebıtneni hini nêeşkeni ney ra zêdêr hesab akeri.",
        "emailauthenticated": "E-postay şıma $2 sehat $3 dı biya araşt",
        "emailnotauthenticated": "Adresa e-pota da şıma qebul nébiya.\nQandé céréna şımaré teba do nérışiyo.",
-       "noemailprefs": "Hesab biyo a.",
+       "noemailprefs": "Seba gurenayışê nê xısusiyetan yew adresa e-posteyi cı kerê.",
        "emailconfirmlink": "E-postayê xo araşt kerê",
        "invalidemailaddress": "No format de nuştışê e-postayi qebul nêbeno. Yew formato meqbul de adresê e-posta bınuse ya zi veng bıverde.",
        "cannotchangeemail": "E-postay hesabi ena wiki sera nêvurneyêno.",
        "page_first": "verên",
        "page_last": "peyên",
        "histlegend": "Ferqê weçinayışi: Qutiya versiyonan qandé  têversanayış işaret ke u dest be ''enter''i ya zi gocega cêrêne rone.<br />\nCetwel: <strong>({{int:ferq}})</strong> = ferqê versiyonê peyêni, <strong>({{int:peyên}})</strong> = ferqê versiyonê verêni, <strong>{{int:q}}</strong> = vırnayışo werdiyo.",
-       "history-fieldset-title": "Çımraviyarnayışan cı geyre",
+       "history-fieldset-title": "Çımraviyarnayışan parzûn ke",
        "history-show-deleted": "Tenya çımraviyarnayışanê esterıteyan bımocne",
        "histfirst": "Verênêr",
        "histlast": "Peyênêr",
        "prefs-misc": "ê bini",
        "prefs-resetpass": "Parola bıvurne",
        "prefs-changeemail": "E-postay bıvurne ya zi wedarne",
-       "prefs-setemail": "E-posta adresiyê xo saz kerê",
+       "prefs-setemail": "Adresa eposteyi cı kerê",
        "prefs-email": "Tercihê e-maili",
        "prefs-rendering": "Asayış",
        "saveprefs": "Qeyd ke",
        "email-blacklist-label": "Wa nê karberi mı rê mesac nêrışê:",
        "prefs-searchoptions": "Cı geyre",
        "prefs-namespaces": "Heruna nameyan",
-       "default": "qısur",
+       "default": "hesabiyaye",
        "prefs-files": "Dosyeyi",
        "prefs-custom-css": "CSSê xasi",
        "prefs-custom-json": "JSONo xısusi",
        "prefs-help-signature": "Mışewreyê ke pelanê werênayışi derê, gani be \"<nowiki>~~~~</nowiki>\" ra imza bıbê, no bahdo beno çerxê imza û wadeyê zemani.",
        "badsig": "Îmzayê tu raşt niyo.\nEtiketê HTMLî kontrol bike.",
        "badsiglength": "İmzaya şıma zaf derga.\nA gani be $1 {{PLURAL:$1|karakter|karakteran}} ra zêde mebo.",
-       "yourgender": "Şeklê xitabi?",
-       "gender-unknown": "Çımhal tı kerdê etiket, Nusner do çekuya Nerimaki cinsiyeti de şeno bıkar no",
-       "gender-male": "Perané wiki camérd deyne ezo vırnena",
-       "gender-female": "Perané wiki cıni deyne eza vırnena",
-       "prefs-help-gender": "Na tercih keyfiya.\nNa nustenek ercana qısan de qandé grameri karneyéna, na malumater herkes şeno bıvino .",
+       "yourgender": "Şımayê xo seni tarif kenê?",
+       "gender-unknown": "Nuşteker şıma ra behskerdış de, hende ke cı dest ra bêro, cınsiyeto elaqeyın gureneno.",
+       "gender-male": "Oyo pelanê wikiyi vurneno",
+       "gender-female": "Aya pelanê wikiyi vurnena",
+       "prefs-help-gender": "No eyarê tercihi keyfiyo.\nNo nuşteker xıtabkerdış de ercê xo u ê binan rê şıma ra behskerdış de cınsiyetê grameriyê hewli gureneno.\nNo melumat her kesi rê aseno.",
        "email": "E-posta",
        "prefs-help-realname": "Nameyo raşt waştena şıma rê mendo.\nEka tu wazene ke nameyo raşt xo bide, ma nameyo raşt ti iştirakanê ti de mocnenê.",
        "prefs-help-email": "Dayışê adresa e-postey keyfiyo, labelê seba eyarê parola lazıma, wexto ke şıma naye xo vira kerê.",
        "userrights-conflict": "Heqan de karberi de dıbare vıcyayo! Kerem ke vurnayışane xo çımser ra ravyarne  u tesdiq keri.",
        "group": "Grube:",
        "group-user": "Karberi",
-       "group-autoconfirmed": "Karberê ke otomatikmen biyê araşt",
+       "group-autoconfirmed": "Karberê otomatikraştbiyayeyi",
        "group-bot": "Boti",
        "group-sysop": "İdarekari",
        "group-interface-admin": "İdarekarê namnişani",
        "group-suppress": "Pawıteri",
        "group-all": "(pêro)",
        "group-user-member": "{{GENDER:$1|karber}}",
-       "group-autoconfirmed-member": "{{GENDER:$1|Karberê ke otomatikmen biyê araşt}}",
+       "group-autoconfirmed-member": "{{GENDER:$1|Karberê otomatikraştbiyayeyi}}",
        "group-bot-member": "{{GENDER:$1|bot}}",
        "group-sysop-member": "{{GENDER:$1|İdarekar}}",
        "group-interface-admin-member": "{{GENDER:$1|idarekarê namnişani}}",
        "group-bureaucrat-member": "{{GENDER:$1|buroqrat}}",
        "group-suppress-member": "{{GENDER:$1|Temaşekar}}",
        "grouppage-user": "{{ns:project}}:Karberi",
-       "grouppage-autoconfirmed": "{{ns:project}}:Karberê ke otomatikmen biyê araşt",
+       "grouppage-autoconfirmed": "{{ns:project}}:Karberê otomatikraştbiyayeyi",
        "grouppage-bot": "{{ns:project}}:Boti",
        "grouppage-sysop": "{{ns:project}}:İdarekeri",
        "grouppage-interface-admin": "{{ns:project}}:İdarekarê namnişani",
        "rcfilters-filtergroup-authorship": "Wayiriya iştırakan",
        "rcfilters-filter-editsbyself-label": "Vurnayışê şıma",
        "rcfilters-filter-editsbyself-description": "İştırakê şıma.",
-       "rcfilters-filter-editsbyother-label": "Ê binan ra vurnayışi",
+       "rcfilters-filter-editsbyother-label": "Vurnayışê merdımanê binan",
        "rcfilters-filter-editsbyother-description": "Bê vurnayışanê şıma vurnayışi pêro.",
        "rcfilters-filtergroup-userExpLevel": "Qeydê karberi u tecrube",
        "rcfilters-filter-user-experience-level-registered-label": "Qeydıni",
        "rcfilters-filter-user-experience-level-newcomer-label": "Ameyayeyê neweyi",
        "rcfilters-filter-user-experience-level-newcomer-description": "Karberê qeydınê ke 10 ra kemi vurnayışi ya zi 4 rocan ra fealiyetê xo estê.",
        "rcfilters-filter-user-experience-level-learner-label": "Musayoği",
+       "rcfilters-filter-user-experience-level-learner-description": "Vurnayoğê qeydınê ke cerrebnayışê cı \"Neweameyoği\" û \"Karberê westay\"an miyan dero.",
        "rcfilters-filter-user-experience-level-experienced-label": "Karberê mısayeyi",
+       "rcfilters-filter-user-experience-level-experienced-description": "Vurnayoğê qeydınê ke 30 roce ra zêdêr fealiyet û wayirê 500 ra zêdêr vurnayışanê.",
        "rcfilters-filtergroup-automated": "İştırakê otomatiki",
        "rcfilters-filter-bots-label": "Bot",
        "rcfilters-filter-bots-description": "Terefê hacetanê otomatikan ra vurnayışi vıraziyayi.",
        "rcfilters-filter-watchlistactivity-seen-label": "Vuriyayışê ke vêniyê",
        "rcfilters-filtergroup-changetype": "Tewrê vurnayışi",
        "rcfilters-filter-pageedits-label": "Vuriyayışê pelan",
+       "rcfilters-filter-pageedits-description": "Vurnayışê zerrekê wikiyi, werênayışi, şınasiya kategoriyan...",
        "rcfilters-filter-newpages-label": "Vıraştışê pelan",
        "rcfilters-filter-newpages-description": "Vurnayışê ke pelanê newiyab vırazenê.",
        "rcfilters-filter-categorization-label": "Vuriyayışê kategoriyan",
        "rcfilters-filter-categorization-description": "Kategoriyan ra qeydê cıkerdış u wedardışê pelan.",
        "rcfilters-filter-logactions-label": "Rocekê iştırakan",
+       "rcfilters-filter-logactions-description": "Karberiya xızmetkaran, vıraştışê hesaban, esterıtışê pelan, barkerdışi...",
        "rcfilters-filtergroup-lastRevision": "Çımraviyarnayışê tewr peyêni",
        "rcfilters-filter-lastrevision-label": "Çımraviyarnayışo peyên",
        "rcfilters-filter-lastrevision-description": "Tenya vurnayışê yew peleyo tewr peyên.",
        "rcfilters-filter-previousrevision-label": "Çımraviyarnayışo peyên niyo",
+       "rcfilters-filter-previousrevision-description": "Heme vurnayışê ke \"çımraviyarnayışo peyên\" niyê.",
        "rcfilters-filter-excluded": "Xarıc",
        "rcfilters-tag-prefix-namespace-inverted": "$1 <strong>:nê</strong>",
        "rcfilters-exclude-button-off": "Weçinayeyi ciya bıtepışê",
        "djvu_no_xml": "Qe DjVu nieşkenî XML fetch bikî",
        "thumbnail-temp-create": "İdare dosyay resimiya nêvırazêna",
        "thumbnail-dest-create": "Resimo werdiyo keyd nêbeno",
-       "thumbnail_invalid_params": "Parametreya thumbnailî raşt niyşê",
+       "thumbnail_invalid_params": "Parametreya xıraba resimanê werdiyan",
        "thumbnail_toobigimagearea": "Ebadê $1 ra gırd dosyeyi",
        "thumbnail_dest_directory": "Nieşkenî direktorê destinasyonî virazî",
        "thumbnail_image-type": "Tipê resimî kebul nibeno",
        "tooltip-preferences-save": "Terciha qeyd ke",
        "tooltip-summary": "Xulasa kılmek bınuse",
        "interlanguage-link-title": "$1 - $2",
+       "group-autoconfirmed.css": "/* CSSyo ke tiya roniyo, karberanê otomatikraştbiyayeyan rê tesir keno */",
+       "group-autoconfirmed.js": "/* No JavaScript teyna seba karberanê otomatikraştbiyayeyan rê do bar bo */",
        "anonymous": "{{PLURAL:$1|karberê|karberê}} anonimi yê keyepelê {{SITENAME}}i",
        "siteuser": "karberê {{SITENAME}}i $1",
        "anonuser": "karberê anonim o {{SITENAME}}i $1",
        "nextdiff": "Vurnayışo peyên →",
        "mediawarning": "'''Teme''': Na dosya de belkia kodê xırabıni estê.\nGurênayışê nae de, beno ke sistemê şıma zerar bıvêno.",
        "imagemaxsize": "Sinorê ebadê resımiyo ke pelanê şınasnayışê dosyeyan dero:",
-       "thumbsize": "Ebadê Thumbnaili",
+       "thumbsize": "Ebado werdi:",
        "widthheight": "$1 - $2",
        "widthheightpage": "$1 × $2, $3 {{PLURAL:$3|pele|peli}}",
        "file-info": "ebatê dosyayi: $1, MIME tip: $2",
index 70bf7e3..85b991b 100644 (file)
        "customcssprotected": "Δεν έχετε δικαιώματα για να επεξεργαστείτε αυτή τη σελίδα CSS, επειδή περιέχει προσωπικές ρυθμίσεις άλλου χρήστη.",
        "customjsonprotected": "Δεν έχετε δικαιώματα για να επεξεργαστείτε αυτή τη σελίδα JSON, επειδή περιέχει προσωπικές ρυθμίσεις άλλου χρήστη.",
        "customjsprotected": "Δεν έχετε δικαιώματα για να επεξεργαστείτε αυτή τη σελίδα JavaScript, επειδή περιέχει προσωπικές ρυθμίσεις άλλου χρήστη.",
+       "sitejsprotected": "Δεν έχετε την άδεια να επεξεργαστείτε αυτήν τη σελίδα JavaScript επειδή μπορεί να επηρεάσει όλους τους επισκέπτες.",
        "mycustomcssprotected": "Δεν έχετε άδεια για να επεξεργαστείτε αυτήν τη σελίδα CSS.",
        "mycustomjsonprotected": "Δεν έχετε άδεια για να επεξεργαστείτε αυτήν τη σελίδα JSON.",
        "mycustomjsprotected": "Δεν έχετε άδεια για να επεξεργαστείτε αυτήν τη σελίδα JavaScript.",
        "userlogin-yourname": "Όνομα χρήστη",
        "userlogin-yourname-ph": "Εισαγάγετε το όνομα χρήστη σας",
        "createacct-another-username-ph": "Εισαγάγετε το όνομα χρήστη",
-       "yourpassword": "Î\9aÏ\89δικÏ\8cÏ\82:",
-       "userlogin-yourpassword": "Î\9aÏ\89δικÏ\8cÏ\82",
+       "yourpassword": "ΣÏ\85νθημαÏ\84ικÏ\8c:",
+       "userlogin-yourpassword": "ΣÏ\85νθημαÏ\84ικÏ\8c",
        "userlogin-yourpassword-ph": "Εισαγάγετε το συνθηματικό σας",
        "createacct-yourpassword-ph": "Εισαγωγή κωδικού",
        "yourpasswordagain": "Επαναπληκτρολόγηση κωδικού:",
        "nosuchusershort": "Δεν υπάρχει χρήστης με το όνομα \"$1\". Παρακαλούμε ελέγξτε την ορθογραφία.",
        "nouserspecified": "Πρέπει να ορίσετε ένα όνομα χρήστη.",
        "login-userblocked": "Αυτός ο χρήστης έχει αποκλειστεί. Δεν επιτρέπεται σύνδεση.",
-       "wrongpassword": "Εισήχθη λανθασμένο όνομα χρήστη ή κωδικός.\nΠαρακαλούμε προσπαθήστε ξανά.",
-       "wrongpasswordempty": "Î\9f ÎºÏ\89δικÏ\8cÏ\82 Ï\80Ï\81Ï\8cÏ\83βαÏ\83ηÏ\82 Ï\80οÏ\85 ÎµÎ¹Ï\83άÏ\87θηκε Î®Ï\84αν ÎºÎµÎ½Ï\8cÏ\82. Παρακαλούμε προσπαθήστε ξανά.",
+       "wrongpassword": "Εισήχθη λανθασμένο όνομα χρήστη ή συνθηματικό.\nΠαρακαλούμε προσπαθήστε ξανά.",
+       "wrongpasswordempty": "Το Ï\83Ï\85νθημαÏ\84ικÏ\8c Ï\80οÏ\85 ÎµÎ¹Ï\83ήÏ\87θη Î®Ï\84αν ÎºÎµÎ½Ï\8c. Παρακαλούμε προσπαθήστε ξανά.",
        "passwordtooshort": "Οι κωδικοί πρέπει να περιέχουν τουλάχιστον {{PLURAL:$1|1 χαρακτήρα|$1 χαρακτήρες}}.",
        "passwordtoolong": "Οι κωδικοί πρόσβασης δεν μπορούν να υπερβαίνουν {{PLURAL:$1|τον 1 χαρακτήρα|τους $1 χαρακτήρες}}.",
        "passwordtoopopular": "Κοινότυπα συνθηματικά που τείνουν να επιλέγονται συχνά από τους χρήστες δεν μπορούν να χρησιμοποιηθούν. Παρακαλούμε επιλέξτε ένα συνθηματικό που είναι πιο δύσκολο να μαντεφθεί.",
-       "passwordinlargeblacklist": "Î\9f ÎºÏ\89δικÏ\8cÏ\82 Ï\80Ï\81Ï\8cÏ\83βαÏ\83ηÏ\82 Ï\80οÏ\85 ÎµÎ¹Ï\83ήÏ\87θηκε ÎµÎ¯Î½Î±Î¹ Ï\83ε Î¼Î¹Î± Î»Î¯Ï\83Ï\84α Ï\80ολÏ\8d ÎºÎ¿Î¹Î½Ï\8eν ÎºÏ\89δικÏ\8eν Ï\80Ï\81Ï\8cÏ\83βαÏ\83ηÏ\82 Ï\80οÏ\85 Ï\87Ï\81ηÏ\83ιμοÏ\80οιοÏ\8dνÏ\84αι. Î Î±Ï\81ακαλÏ\8e Î´Î¹Î±Î»Î­Î¾Ï\84ε Î­Î½Î±Î½ Ï\80ιο Ï\83Ï\85νηθιÏ\83μένο ÎºÏ\89δικÏ\8c Ï\80Ï\81Ï\8cÏ\83βαÏ\83ηÏ\82.",
+       "passwordinlargeblacklist": "Το Ï\83Ï\85νθημαÏ\84ικÏ\8c Ï\80οÏ\85 ÎµÎ¹Ï\83ήÏ\87θη ÎµÎ¯Î½Î±Î¹ Ï\83ε Î¼Î¹Î± Î»Î¯Ï\83Ï\84α Ï\80ολÏ\8d Ï\83Ï\85Ï\87νά Ï\87Ï\81ηÏ\83ιμοÏ\80οιοÏ\8dμενÏ\89ν Ï\83Ï\85νθημαÏ\84ικÏ\8eν. Î Î±Ï\81ακαλοÏ\8dμε ÎµÏ\80ιλέξÏ\84ε ÎºÎ¬Ï\80οιο Ï\80ιο Î¼Î¿Î½Î±Î´Î¹ÎºÏ\8c Ï\83Ï\85νθημαÏ\84ικÏ\8c.",
        "password-name-match": "Ο κωδικός σου θα πρέπει να είναι διαφορετικός από το όνομα χρήστη σου.",
        "password-login-forbidden": "Η χρήση αυτού του ονόματος χρήστη και συνθηματικού έχουν  απαγορευτεί.",
        "mailmypassword": "Επαναφορά κωδικού",
-       "passwordremindertitle": "Καινούργιος προσωρινός κωδικός για το {{SITENAME}}",
+       "passwordremindertitle": "Καινούργιο προσωρινό συνθηματικό για το {{SITENAME}}",
        "passwordremindertext": "Κάποιος (από τη διεύθυνση IP $1) αιτήθηκε νέο συνθηματικό για τον ιστότοπο {{SITENAME}} ($4). Έχει δημιουργηθεί το προσωρινό συνθηματικό «$3» για το χρήστη «$2». Αν ήταν αυτή η πρόθεσή σας, θα πρέπει να συνδεθείτε και να αλλάξετε το συνθηματικό σας τώρα. Το προσωρινό σας συνθηματικό θα λήξει σε {{PLURAL:$5|μια ημέρα|$5 ημέρες}}.\n\nΕάν αυτό το αίτημα έγινε από κάποιον τρίτο, ή αν θυμηθήκατε το συνθηματικό σας και δεν επιθυμείτε πλέον να το αλλάξετε, μπορείτε να αγνοήσετε αυτό το μήνυμα και να συνεχίσετε να χρησιμοποιείτε το παλιό σας συνθηματικό.",
        "noemail": "Δεν έχει καθοριστεί ηλεκτρονική διεύθυνση για τον χρήστη \"$1\".",
        "noemailcreate": "Είναι απαραίτητο να υποβάλλετε μια έγκυρη διεύθυνση ηλεκτρονικού ταχυδρομείου",
-       "passwordsent": "Σας έχει σταλεί ένας νέος κωδικός στην ηλεκτρονική διέθυνση που δηλώσατε για \"$1\".\nΣας παρακαλούμε να ξανασυνδεθείτε μόλις τον λάβετε.",
+       "passwordsent": "Σας έχει σταλεί ένα νέο συνθηματικό στη διεύθυνση ηλεκτρονικού ταχυδρομείου που δηλώσατε για «$1».\nΠαρακαλούμε να ξανασυνδεθείτε μόλις το λάβετε.",
        "blocked-mailpassword": "Η διεύθυνση IP σας δεν έχει δικαίωμα επεξεργασίας. Για την αποφυγή κατάχρησης, δεν επιτρέπεται να χρησιμοποιήσετε την λειτουργία ανάκτησης κωδικού πρόσβασης από αυτήν την IP.",
        "eauthentsent": "Ένα μήνυμα επαλήθευσης έχει σταλεί στην ηλεκτρονική διεύθυνση που έχετε δηλώσει.\nΠριν αρχίσει η αποστολή μηνυμάτων στη συγκεκριμένη διεύθυνση, πρέπει να ακολουθήσετε τις οδηγίες που βρίσκονται στο μήνυμα που σας έχει σταλεί, για να επαληθεύσετε ότι η συγκεκριμένη ηλεκτρονική διεύθυνση ανήκει πραγματικά σε εσάς.",
        "throttled-mailpassword": "Ένα email επαναφοράς κωδικού έχει ήδη αποσταλεί, μέσα {{PLURAL:$1|στην τελευταία ώρα|στις τελευταίες $1 ώρες}}.\nΓια την αποφυγή κατάχρησης, μόνο ένα email επαναφοράς κωδικού θα στέλνεται ανά {{PLURAL:$1|ώρα|$1 ώρες}}.",
        "resetpass_announce": "Για να ολοκληρώσετε τη σύνδεση πρέπει να καθορίσετε νέο συνθηματικό.",
        "resetpass_text": "<!-- Προσθέστε κείμενο εδώ -->",
        "resetpass_header": "Αλλαγή κωδικού πρόσβασης",
-       "oldpassword": "Παλιός κωδικός",
-       "newpassword": "Νέος κωδικός πρόσβασης",
+       "oldpassword": "Παλιό συνθηματικό:",
+       "newpassword": "Νέο συνθηματικό:",
        "retypenew": "Πληκτρολογήστε ξανά το νέο συνθηματικό:",
        "resetpass_submit": "Καθορίστε συνθηματικό και συνδεθείτε",
-       "changepassword-success": "Î\9f ÎºÏ\89δικÏ\8cÏ\82 Ï\80Ï\81Ï\8cÏ\83βαÏ\83ήÏ\82 σας άλλαξε!",
+       "changepassword-success": "Το Ï\83Ï\85νθημαÏ\84ικÏ\8c σας άλλαξε!",
        "changepassword-throttled": "Κάνατε πάρα πολλές πρόσφατες απόπειρες σύνδεσης.\nΠαρακαλούμε περιμένετε $1 προτού ξαναδοκιμάσετε.",
        "botpasswords": "Συνθηματικά για ρομπότ",
        "botpasswords-summary": "<em>Τα συνθηματικά για ρομπότ</em> επιτρέπουν πρόσβαση σε λογαριασμό χρήστη μέσω του API χωρίς να χρησιμοποιούνται τα κύρια διαπιστευτήρια σύνδεσης του λογαριασμού. Τα διαθέσιμα δικαιώματα χρήστη όταν είναι σε σύνδεση μέσω συνθηματικού για ρομπότ μπορεί να είναι περιορισμένα.\n\nΑν δεν ξέρετε γιατί να το κάνετε αυτό, καλύτερα να μην το κάνετε. Κανείς ποτέ δεν πρέπει να σας ζητήσει να δημιουργήσετε ένα τέτοιο συνθηματικό και να του το γνωστοποιήσετε.",
        "botpasswords-updated-body": "Το συνθηματικό για το ρομπότ με όνομα «$1» του χρήστη «$2» ενημερώθηκε.",
        "botpasswords-deleted-title": "Το συνθηματικό του ρομπότ διαγράφηκε",
        "botpasswords-deleted-body": "Το συνθηματικό για το όνομα μποτ \"$1\" του χρήστη \"$2\" διαγράφηκε.",
-       "botpasswords-newpassword": "Î\9f Î½Î­Î¿Ï\82 ÎºÏ\89δικÏ\8cÏ\82 Ï\80Ï\81Ï\8cÏ\83βαÏ\83ηÏ\82 Î³Î¹Î± Î½Î± Ï\83Ï\85νδεθείÏ\84ε Î¼Îµ Ï\84ο <strong>$1</strong> ÎµÎ¯Î½Î±Î¹ <strong>$2</strong>. <em>ΠαÏ\81ακαλοÏ\8dμε Ï\83ημειÏ\8eÏ\83Ï\84ε Ï\84ο Î³Î¹Î± Î¼ÎµÎ»Î»Î¿Î½Ï\84ική Î±Î½Î±Ï\86οÏ\81ά.</em><br />(Î\93ια Ï\80αλιά bot Ï\80οÏ\85 Î±Ï\80αιÏ\84οÏ\8dν Ï\84ο Ï\8cνομα Ï\83Ï\8dνδεÏ\83ηÏ\82 Î½Î± ÎµÎ¯Î½Î±Î¹ Ï\84ο Î¯Î´Î¹Î¿ Î¼Îµ Ï\84ο Ï\84ελικÏ\8c Ï\8cνομα Ï\87Ï\81ήÏ\83Ï\84η, Î¼Ï\80οÏ\81είÏ\84ε ÎµÏ\80ίÏ\83ηÏ\82 Î½Î± Ï\87Ï\81ηÏ\83ιμοÏ\80οιήÏ\83εÏ\84ε Ï\84ο  <strong>$3</strong> Ï\89Ï\82 Ï\8cνομα Ï\87Ï\81ήÏ\83Ï\84η ÎºÎ±Î¹ <strong>$4</strong> Ï\89Ï\82 ÎºÏ\89δικό.)",
+       "botpasswords-newpassword": "Το Î½Î­Î¿ Ï\83Ï\85νθημαÏ\84ικÏ\8c Î³Î¹Î± Î½Î± Ï\83Ï\85νδεθείÏ\84ε Î¼Îµ Ï\84ο <strong>$1</strong> ÎµÎ¯Î½Î±Î¹ <strong>$2</strong>. <em>ΠαÏ\81ακαλοÏ\8dμε Ï\83ημειÏ\8eÏ\83Ï\84ε Ï\84ο Î³Î¹Î± Î¼ÎµÎ»Î»Î¿Î½Ï\84ική Î±Î½Î±Ï\86οÏ\81ά.</em><br />(Î\93ια Ï\80αλιά Ï\81ομÏ\80Ï\8cÏ\84 Ï\80οÏ\85 Î±Ï\80αιÏ\84οÏ\8dν Ï\84ο Ï\8cνομα Ï\83Ï\8dνδεÏ\83ηÏ\82 Î½Î± ÎµÎ¯Î½Î±Î¹ Ï\84ο Î¯Î´Î¹Î¿ Î¼Îµ Ï\84ο ÎµÎ½Î´ÎµÏ\87Ï\8cμενο Ï\8cνομα Ï\87Ï\81ήÏ\83Ï\84η, Î¼Ï\80οÏ\81είÏ\84ε ÎµÏ\80ίÏ\83ηÏ\82 Î½Î± Ï\87Ï\81ηÏ\83ιμοÏ\80οιήÏ\83εÏ\84ε Ï\84ο <strong>$3</strong> Ï\89Ï\82 Ï\8cνομα Ï\87Ï\81ήÏ\83Ï\84η ÎºÎ±Î¹ Ï\84ο <strong>$4</strong> Ï\89Ï\82 Ï\83Ï\85νθημαÏ\84ικό.)",
        "botpasswords-no-provider": "Το BotPasswordsSessionProvider δεν είναι διαθέσιμο.",
        "botpasswords-restriction-failed": "Περιορισμοί κωδικών πρόσβασης bot εμποδίζουν τη συγκεκριμένη σύνδεση.",
        "botpasswords-invalid-name": "Το όνομα χρήστη που ορίζεται δεν περιέχει το διαχωριστικό συνθηματικού ρομπότ («$1»).",
        "resetpass-wrong-oldpass": "Μη έγκυρο προσωρινό ή τρέχον συνθηματικό.\nΜπορεί να έχετε ήδη αλλάξει το συνθηματικό σας ή να έχετε αιτηθεί νέο προσωρινό συνθηματικό.",
        "resetpass-recycled": "Παρακαλούμε επαναφέρετε το συνθηματικό σας επιλέγοντας κάτι διαφορετικό από το τρέχον συνθηματικό σας.",
        "resetpass-temp-emailed": "Έχετε συνδεθεί με έναν προσωρινό κωδικό μέσω ηλεκτρονικού ταχυδρομείου.\nΓια να ολοκληρώσετε τη σύνδεσή σας, πρέπει να ορίσετε νέο συνθηματικό εδώ:",
-       "resetpass-temp-password": "Προσωρινός κωδικός:",
+       "resetpass-temp-password": "Προσωρινό συνθηματικό:",
        "resetpass-abort-generic": "Η αλλαγή του κωδικού έχει απορριφθεί από μια προέκταση.",
        "resetpass-expired": "Το συνθηματικό σας έχει λήξει. Παρακαλούμε καθορίστε νέο συνθηματικό για να συνδεθείτε.",
        "resetpass-expired-soft": "Το συνθηματικό σας έχει λήξει και πρέπει να γίνει επαναφορά του. Παρακαλούμε επιλέξτε νέο συνθηματικό τώρα, ή κάντε κλικ στο «{{int:authprovider-resetpass-skip-label}}» για το επαναφέρετε αργότερα.",
-       "resetpass-validity-soft": "Î\9f ÎºÏ\89δικÏ\8cÏ\82 Ï\80Ï\81Ï\8cÏ\83βαÏ\83ήÏ\82 Ï\83αÏ\82 Î´ÎµÎ½ ÎµÎ¯Î½Î±Î¹ Î­Î³ÎºÏ\85Ï\81οÏ\82: $1\n\nΠαÏ\81ακαλοÏ\8dμε ÎµÏ\80ιλέξÏ\84ε Î­Î½Î±Î½ Î½Î­Î¿ ÎºÏ\89δικÏ\8c Ï\80Ï\81Ï\8cÏ\83βαÏ\83ηÏ\82 Ï\84Ï\8eÏ\81α, Î® Ï\80αÏ\84ήÏ\83Ï\84ε Â«{{int:authprovider-resetpass-skip-label}}» Î³Î¹Î± Î½Î± Ï\84ον ÎµÏ\80αναÏ\86έÏ\81ετε αργότερα.",
+       "resetpass-validity-soft": "Το Ï\83Ï\85νθημαÏ\84ικÏ\8c Ï\83αÏ\82 Î´ÎµÎ½ ÎµÎ¯Î½Î±Î¹ Î­Î³ÎºÏ\85Ï\81ο: $1\n\nΠαÏ\81ακαλοÏ\8dμε ÎµÏ\80ιλέξÏ\84ε Î½Î­Î¿ Ï\83Ï\85νθημαÏ\84ικÏ\8c Ï\84Ï\8eÏ\81α, Î® Ï\80αÏ\84ήÏ\83Ï\84ε Â«{{int:authprovider-resetpass-skip-label}}» Î³Î¹Î± Î½Î± Ï\84ο Î±Î»Î»Î¬Î¾ετε αργότερα.",
        "passwordreset": "Επαναφορά κωδικού",
        "passwordreset-text-one": "Συμπληρώστε αυτήν τη φόρμα για να λάβετε ένα προσωρινό συνθηματικό μέσω ηλεκτρονικού ταχυδρομείου.",
        "passwordreset-text-many": "{{PLURAL:$1|Συμπληρώστε ένα από τα πεδία για να λάβετε προσωρινό συνθηματικό μέσω ηλεκτρονικού ταχυδρομείου.}}",
        "passwordreset-emailtitle": "Λεπτομέρειες λογαριασμού για {{SITENAME}}",
        "passwordreset-emailtext-ip": "Κάποιος (κατά πάσα πιθανότητα εσείς, από την διεύθυνση IP $1) αιτήθηκε επαναφορά του συνθηματικού σας στον ιστότοπο {{SITENAME}} ($4).  {{PLURAL:$3|Ο ακόλουθος λογαριασμός|Οι ακόλουθοι λογαριασμοί}} χρήστη συνδέονται με αυτήν τη διεύθυνση ηλεκτρονικού ταχυδρομείου:\n\n$2\n\n{{PLURAL:$3|Αυτό το προσωρινό συνθηματικό θα λήξει| Αυτά τα προσωρινά συνθηματικά θα λήξουν}} σε {{PLURAL:$5|μία ημέρα|$5 ημέρες}}.\nΘα πρέπει να συνδεθείτε και να επιλέξετε νέο συνθηματικό τώρα. Αν κάποιος άλλος έκανε αυτό το αίτημα ή αν θυμηθήκατε το αρχικό συνθηματικό σας και δεν επιθυμείτε πια να το αλλάξετε, μπορείτε να αγνοήσετε αυτό το μήνυμα και να συνεχίσετε να χρησιμοποιείτε το παλιό σας συνθηματικό.",
        "passwordreset-emailtext-user": "Ο χρήστης $1 στον ιστότοπο {{SITENAME}} ζήτησε μια επαναφορά του συνθηματικού σας για τον ιστότοπο {{SITENAME}} ($4). {{PLURAL:$3|Ο ακόλουθος λογαριασμός|Οι ακόλουθοι λογαριασμοί}} χρήστη συνδέονται με αυτήν τη διεύθυνση ηλεκτρονικού ταχυδρομείου:\n\n$2\n\n{{PLURAL:$3|Αυτό το προσωρινό συνθηματικό θα λήξει| Αυτά τα προσωρινά συνθηματικά θα λήξουν}} σε {{PLURAL:$5|μία ημέρα|$5 ημέρες}}.\nΘα πρέπει να συνδεθείτε και να επιλέξετε νέο συνθηματικό τώρα. Αν κάποιος άλλος έκανε αυτό το αίτημα ή αν θυμηθήκατε το αρχικό συνθηματικό σας και δεν επιθυμείτε πια να το αλλάξετε, μπορείτε να αγνοήσετε αυτό το μήνυμα και να συνεχίσετε να χρησιμοποιείτε το παλιό σας συνθηματικό.",
-       "passwordreset-emailelement": "Όνομα χρήστη: \n$1\n\nΠροσωρινός κωδικός πρόσβασης:\n$2",
+       "passwordreset-emailelement": "Όνομα χρήστη: \n$1\n\nΠροσωρινό συνθηματικό:\n$2",
        "passwordreset-emailsentemail": "Αν αυτή η διεύθυνση ηλεκτρονικού ταχυδρομείου συνδέεται με το  λογαριασμό σας, τότε  θα σας αποσταλεί μήνυμα ηλεκτρονικού ταχυδρομείου για την επαναφορά του κωδικού πρόσβασης.",
        "passwordreset-emailsentusername": "Αν υπάρχει μια διεύθυνση ηλεκτρονικού ταχυδρομείου που συνδέεται με αυτό το όνομα χρήστη, τότε θα σας αποσταλεί ένα μήνυμα ηλεκτρονικού ταχυδρομείου για την επαναφορά του κωδικού πρόσβασης.",
        "passwordreset-nocaller": "Πρέπει να γίνει   κλήση",
        "changeemail-newemail": "Νέα διεύθυνση ηλεκτρονικού ταχυδρομείου:",
        "changeemail-newemail-help": "Αυτό το πεδίο θα πρέπει να μείνει κενό αν θέλετε να καταργήσετε τη διεύθυνσή σας ηλεκτρονικού ταχυδρομείου. Αν η διεύθυνση ηλεκτρονικού ταχυδρομείου καταργηθεί, δεν θα μπορείτε να επαναφέρετε τον κωδικό πρόσβασης σε περίπτωση που τον ξεχάσετε και δεν θα λαμβάνετε μηνύματα ηλεκτρονικού ταχυδρομείου από αυτό το wiki.",
        "changeemail-none": "(κανένα)",
-       "changeemail-password": "Î\9f ÎºÏ\89δικÏ\8cÏ\82 Ï\80Ï\81Ï\8cÏ\83βαÏ\83ήÏ\82 σας στο εγχείρημα {{SITENAME}}:",
+       "changeemail-password": "Το Ï\83Ï\85νθημαÏ\84ικÏ\8c σας στο εγχείρημα {{SITENAME}}:",
        "changeemail-submit": "Αλλαγή διεύθυνσης ηλεκτρονικού ταχυδρομείου",
        "changeemail-throttled": "Κάνατε πάρα πολλές απόπειρες σύνδεσης.\nΠαρακαλούμε περιμένετε $1 προτού ξαναδοκιμάσετε.",
        "changeemail-nochange": "Παρακαλούμε εισαγάγετε διαφορετική νέα διεύθυνση ηλεκτρονικού ταχυδρομείου.",
        "loginreqtitle": "Απαιτείται η σύνδεση του χρήστη.",
        "loginreqlink": "συνδεθείτε",
        "loginreqpagetext": "Πρέπει να $1 για να δείτε άλλες σελίδες.",
-       "accmailtitle": "Î\9f ÎºÏ\89δικÏ\8cÏ\82 έχει σταλεί.",
+       "accmailtitle": "Το Ï\83Ï\85νθημαÏ\84ικÏ\8c έχει σταλεί.",
        "accmailtext": "Ένας τυχαία παρηγμένος κωδικός για {{GENDER:$1|τον|την}} [[User talk:$1|$1]] έχει σταλεί στο $2.\n\nΜπορεί να αλλαχθεί από την σελίδα ''[[Special:ChangePassword|αλλαγή κωδικού]]'' μετά τη σύνδεση.",
        "newarticle": "(Νέο)",
        "newarticletext": "Ακολουθήσατε ένα σύνδεσμο προς μια σελίδα που δεν υπάρχει ακόμα. \nΓια να δημιουργήσετε τη σελίδα, αρχίστε να πληκτρολογείτε στο παρακάτω πλαίσιο (δείτε τη [$1 σελίδα βοήθειας] για περισσότερες πληροφορίες).\nΑν έχετε βρεθεί εδώ κατά λάθος, πατήστε το κουμπί '''πίσω''' στον περιηγητή σας.",
        "semiprotectedpagewarning": "<strong>Σημείωση:</strong> Αυτή η σελίδα έχει προστατευτεί ώστε μόνο αυτοεπιβεβαιωμένοι χρήστες μπορούν να την επεξεργαστούν.\nΗ πιο πρόσφατη καταχώρηση στο αρχείο καταγραφής παρέχεται παρακάτω για αναφορά:",
        "cascadeprotectedwarning": "<strong>Προσοχή:</strong> Αυτή η σελίδα έχει κλειδωθεί ώστε μόνο χρήστες με [[Special:ListGroupRights|συγκεκριμένα δικαιώματα]] να μπορούν να την επεξεργαστούν, επειδή περιλαμβάνεται {{PLURAL:$1|στην ακόλουθη|στις ακόλουθες}} διαδοχικά (cascaded) {{PLURAL:$1|προστατευμένη σελίδα|προστατευμένες σελίδες}}:",
        "titleprotectedwarning": "'''Προειδοποίηση: Αυτή η σελίδα έχει κλειδωθεί ώστε χρειάζονται [[Special:ListGroupRights|ειδικά δικαιώματα]] για να δημιουργηθεί.'''\nΗ πιο πρόσφατη καταχώρηση στο αρχείο καταγραφής παρέχεται παρακάτω για αναφορά:",
-       "templatesused": "{{PLURAL:$1|Πρότυπο που χρησιμοποιείται|Πρότυπα που χρησιμοποιούνται}} σε αυτή τη σελίδα:",
-       "templatesusedpreview": "{{PLURAL:$1|Πρότυπο που χρησιμοποιείται|Πρότυπα που χρησιμοποιούνται}} σε αυτή την προεπισκόπηση:",
-       "templatesusedsection": "{{PLURAL:$1|Πρότυπο|Πρότυπα}} που χρησιμοποιούνται σε αυτή την ενότητα:",
+       "templatesused": "{{PLURAL:$1|Πρότυπο που χρησιμοποιείται|Πρότυπα που χρησιμοποιούνται}} σε αυτήν τη σελίδα:",
+       "templatesusedpreview": "{{PLURAL:$1|Πρότυπο που χρησιμοποιείται|Πρότυπα που χρησιμοποιούνται}} σε αυτήν την προεπισκόπηση:",
+       "templatesusedsection": "{{PLURAL:$1|Πρότυπο|Πρότυπα}} που χρησιμοποιούνται σε αυτήν την ενότητα:",
        "template-protected": "(προστατευμένη)",
        "template-semiprotected": "(ημιπροστατευμένη)",
        "hiddencategories": "Αυτή η σελίδα είναι μέλος {{PLURAL:$1|μίας κρυμμένης κατηγορίας|$1 κρυμμένων κατηγοριών}}:",
        "prefs-watchlist-token": "Σημείο λίστας παρακολούθησης:",
        "prefs-watchlist-managetokens": "Διαχείριση tokens",
        "prefs-misc": "Διάφορες ρυθμίσεις",
-       "prefs-resetpass": "Αλλαγή κωδικού",
+       "prefs-resetpass": "Αλλαγή συνθηματικού",
        "prefs-changeemail": "Αλλαγή ή αφαίρεση της διεύθυνσης ηλεκτρονικού ταχυδρομείου",
        "prefs-setemail": "Ορίστε μια διεύθυνση ηλεκτρονικού ταχυδρομείου",
        "prefs-email": "Επιλογές διεύθυνσης ηλεκτρονικού ταχυδρομείου",
        "group-autoconfirmed-member": "{{GENDER:$1|αυτοεπιβεβαιωμένος χρήστης|αυτοεπιβεβαιωμένη χρήστρια}}",
        "group-bot-member": "ρομπότ",
        "group-sysop-member": "{{GENDER:$1|διαχειριστής|διαχειρίστρια}}",
-       "group-interface-admin-member": "{{GENDER:$1|διαχειριστής διεπαφής}}",
+       "group-interface-admin-member": "{{GENDER:$1|διαχειριστής|διαχειρίστρια}} διεπαφής",
        "group-bureaucrat-member": "{{GENDER:$1|γραφειοκράτης|γραφειοκράτις}}",
        "group-suppress-member": "{{GENDER:$1|επιτηρητής|επιτηρήτρια}}",
        "grouppage-user": "{{ns:project}}:Χρήστες",
        "statistics-articles": "Σελίδες περιεχομένου",
        "statistics-pages": "Σελίδες",
        "statistics-pages-desc": "Όλες οι σελίδες του wiki, συμπεριλαμβανομένων των σελίδων συζήτησης, ανακατευθύνσεων, κλπ.",
-       "statistics-files": "Αρχεία που έχουν επιφορτωθεί",
+       "statistics-files": "Ανεβασμένα αρχεία",
        "statistics-edits": "Επεξεργασίες σελίδων από τη δημιουργία του εγχειρήματος {{SITENAME}}",
        "statistics-edits-average": "Μέσος όρος επεξεργασιών ανά σελίδα",
        "statistics-users": "Εγγεγραμμένοι χρήστες",
        "booksources-text": "Παρακάτω είναι μια λίστα συνδέσμων σε άλλους ιστοτόπους οι οποίοι πωλούν νέα και μεταχειρισμένα βιβλία, και μπορεί επίσης να έχουν περισσότερες πληροφορίες για βιβλία για τα οποία ψάχνετε:",
        "booksources-invalid-isbn": "Το δοσμένο ISBN δεν φαίνεται να είναι έγκυρο· ελέγξτε για λάθη κατά την αντιγραφή από την αρχική πηγή.",
        "specialloguserlabel": "",
-       "speciallogtitlelabel": "Στόχος (τίτλος ή {{ns:user}}:χρήστης για χρήστη):",
+       "speciallogtitlelabel": "Στόχος (τίτλος ή «{{ns:user}}:Τάδε» για χρήστη):",
        "log": "Αρχεία καταγραφών",
        "logeventslist-submit": "Προβολή",
+       "logeventslist-more-filters": "Εμφάνιση επιπρόσθετων καταγραφών:",
+       "logeventslist-tag-log": "Καταγραφές ετικετών",
        "all-logs-page": "Όλες οι δημόσιες καταγραφές γεγονότων",
        "alllogstext": "Εποπτική εμφάνιση όλων των ενεργειών φόρτωσης αρχείων, διαγραφής, προστασίας, φραγής και όλων των καταγραφών των διαχειριστών στο αρχείο γεγονότων του {{SITENAME}}. Μπορείτε να περιορίσετε τα αποτελέσματα που εμφανίζονται επιλέγοντας συγκεκριμένο είδος γεγονότων, όνομα χρήστη ή τη σελίδα που επηρεάστηκε.",
        "logempty": "Δεν υπάρχουν στοιχεία που να ταιριάζουν στο αρχείο καταγραφών.",
-       "log-title-wildcard": "Î\91ναζήÏ\84ηÏ\83ε Ï\84ίÏ\84λοÏ\85Ï\82 που αρχίζουν με αυτό το κείμενο",
+       "log-title-wildcard": "Î\91ναζήÏ\84ηÏ\83η Ï\84ίÏ\84λÏ\89ν που αρχίζουν με αυτό το κείμενο",
        "showhideselectedlogentries": "Αλλαγή ορατότητας των επιλεγμένων καταχωρήσεων στο αρχείο καταγραφής συμβάντων",
        "log-edit-tags": "Επεξεργασία ετικετών των επιλεγμένων καταχωρήσεων του αρχείου καταγραφής",
        "checkbox-select": "Επιλογή: $1",
        "deleteprotected": "Δεν μπορείτε να διαγράψετε αυτή τη σελίδα επειδή είναι προστατευόμενη.",
        "deleting-backlinks-warning": "<strong>Προσοχή:</strong>  [[Special:WhatLinksHere/{{FULLPAGENAME}}|Άλλες σελίδες]] συνδέουν ή ενσωματώνουν τη σελίδα που πρόκειται να διαγράψετε.",
        "rollback": "Επαναφορά επεξεργασιών",
+       "rollback-confirmation-no": "Ακύρωση",
        "rollbacklink": "αναστροφή",
        "rollbacklinkcount": "Αναστροφή $1 {{PLURAL:$1|επεξεργασίας|επεξεργασιών}}",
        "rollbacklinkcount-morethan": "αναστροφή περισσότερων από $1 {{PLURAL:$1|επεξεργασία|επεξεργασίες}}",
        "uctop": "τρέχουσα",
        "month": "Από το μήνα (και νωρίτερα):",
        "year": "Από το έτος (και νωρίτερα):",
+       "date": "Από ημερομηνία (και νωρίτερα):",
        "sp-contributions-newbies": "Εμφάνιση των συνεισφορών των νέων λογαριασμών μόνο",
        "sp-contributions-newbies-sub": "Για νέους λογαριασμούς",
        "sp-contributions-newbies-title": "Συνεισφορές χρηστών για νέους λογαριασμούς",
        "ipb-confirm": "Επιβεβαίωση φραγής",
        "ipb-partial": "Μερική",
        "ipb-pages-label": "Σελίδες",
+       "ipb-namespaces-label": "Ονοματοχώροι",
        "badipaddress": "Άκυρη διεύθυνση IP.",
        "blockipsuccesssub": "Η φραγή ολοκληρώθηκε επιτυχώς.",
        "blockipsuccesstext": "{{GENDER:$1|Ο|Η}} [[Special:Contributions/$1|$1]] έχει υποστεί φραγή.<br />\nΔείτε τον [[Special:BlockList|κατάλογο φραγών]] για να εποπτεύσετε τις φραγές.",
        "ipb-blocklist-contribs": "Συνεισφορές {{GENDER:$1|του $1|της $1}}",
        "ipb-blocklist-duration-left": "Απομένουν $1",
        "block-expiry": "Λήξη",
+       "block-reason": "Αιτία:",
        "unblockip": "Άρση φραγής χρήστη",
        "unblockiptext": "Χρησιμοποιήστε την παρακάτω φόρμα για να αποκαταστήσετε την πρόσβαση σε επεξεργασία, σε μια διεύθυνση IP ή σε ένα χρήστη που είχε αποκλειστεί με φραγή.",
        "ipusubmit": "Άρση φραγής",
        "createaccountblock": "δημιουργία λογαριασμού μπλοκαρισμένη",
        "emailblock": "το ηλεκτρονικό ταχυδρομείο έχει απενεργοποιηθεί",
        "blocklist-nousertalk": "δεν μπορεί να επεξεργαστεί τη σελίδα συζήτησής του",
+       "blocklist-editing-page": "σελίδες",
+       "blocklist-editing-ns": "ονοματοχώροι",
        "ipblocklist-empty": "Η λίστα φραγών είναι άδεια.",
        "ipblocklist-no-results": "Η ζητούμενη διεύθυνση IP ή το όνομα χρήστη δεν είναι φραγμένα.",
        "blocklink": "φραγή",
        "markedaspatrollederror-noautopatrol": "Δεν επιτρέπεται να σημάνετε τις δικές σας αλλάγες ως υπό περιπολία.",
        "markedaspatrollednotify": "Αυτή η αλλαγή σε $1 έχει επισημανθεί ως ελεγμένη.",
        "markedaspatrollederrornotify": "Σήμανση ως ελεγμένη απέτυχε.",
-       "patrol-log-page": "Î\91Ï\81Ï\87είο ÎºÎ±Ï\84αγÏ\81αÏ\86ήÏ\82 Ï\80εÏ\81ιÏ\80ολιών",
+       "patrol-log-page": "Î\9aαÏ\84αγÏ\81αÏ\86έÏ\82 ÎµÎ»Î­Î³Ï\87Ï\89ν ÎµÏ\80εξεÏ\81γαÏ\83ιών",
        "patrol-log-header": "Αυτό είναι μητρώο ελεγμένων αναθεωρήσεων.",
        "confirm-markpatrolled-button": "Εντάξει",
        "deletedrevision": "Η παλιά έκδοση της $1 διαγράφτηκε",
        "confirmemail": "Επιβεβαίωση διεύθυνσης ηλεκτρονικού ταχυδρομείου",
        "confirmemail_noemail": "Δεν έχετε ορίσει μια έγκυρη διεύθυνση ηλεκτρονικού ταχυδρομείου στις [[Special:Preferences|προτιμήσεις χρήστη]] σας.",
        "confirmemail_text": "Το σύστημα χρειάζεται να επαληθεύσει τη διεύθυνση e-mail που δώσατε για να χρησιμοποιήσετε τις δυνατότητες αλληλογραφίας. Κάνετε κλικ στο παρακάτω κουμπί και θα σας αποσταλεί μήνυμα επαλήθευσης στη διεύθυνσή σας. Στο μήνυμα αυτό θα εμφανίζεται ένας σύνδεσμος που Θα περιέχει τον κωδικό επαλήθευσης -ακολουθήστε το σύνδεσμο αυτό για να μπορέσει το σύστημα να επαληθεύσει τη διεύθυνση αλληλογραφίας σας.",
-       "confirmemail_pending": "Ένας κωδικός επιβεβαίωσης σας έχει ήδη σταλεί μέσω μηνύματος e-mail. Αν δημιουργήσατε\nπρόσφατα το λογαριασμό σας, μπορεί να θέλετε να περιμένετε μερικά λεπτά\nγια να φτάσει αυτό πριν προσπαθήσετε να ζητήσετε ένα νέο κωδικό.",
+       "confirmemail_pending": "Ένας κωδικός επιβεβαίωσης σας έχει ήδη σταλεί μέσω μηνύματος ηλεκτρονικού ταχυδρομείου. \nΑν δημιουργήσατε πρόσφατα το λογαριασμό σας, ίσως θα θέλετε να περιμένετε μερικά λεπτά για να φτάσει το μήνυμα πριν προσπαθήσετε να ζητήσετε νέο κωδικό.",
        "confirmemail_send": "Αποστολή κωδικού επαλήθευσης με e-mail .",
        "confirmemail_sent": "Στάλθηκε το μήνυμα ηλεκτρονικού ταχυδρομείου για επιβεβαίωση.",
        "confirmemail_oncreate": "Ένας κωδικός επιβεβαίωσης σας έχει σταλεί στην διεύθυνση e-mail σας.\nΑυτός ο κωδικός δεν είναι απαραίτητος για να συνδεθείτε, αλλά θα χρειαστεί\nνα τον παρέχετε πριν ενεργοποιήσετε οποιαδήποτε χαρακτηριστικά βασισμένα σε e-mail, σε αυτό το wiki.",
        "confirm-unwatch-top": "Κατάργηση αυτής της σελίδας από τη λίστα παρακολούθησης σας;",
        "confirm-rollback-button": "Εντάξει",
        "confirm-rollback-top": "Επαναφέρετε τις επεξεργασίες σε αυτή τη σελίδα;",
+       "confirm-mcrrestore-title": "Επαναφορά αναθεώρησης",
+       "confirm-mcrundo-title": "Αναίρεση αλλαγής",
+       "mcrundofailed": "Η αναίρεση απέτυχε",
        "semicolon-separator": "•&#32;",
        "quotation-marks": "«$1»",
        "imgmultipageprev": "← προηγούμενη σελίδα",
        "redirect-file": "Όνομα αρχείου",
        "redirect-logid": "Αναγνωριστικό μητρώου",
        "redirect-not-exists": "Η τιμή δε βρέθηκε",
+       "redirect-not-numeric": "Η τιμή δεν είναι αριθμητική",
        "fileduplicatesearch": "Αναζήτηση για διπλά αρχεία",
        "fileduplicatesearch-summary": "Αναζήτηση για διπλά αρχεία με βάση την τιμή hash του αρχείου.",
        "fileduplicatesearch-filename": "Όνομα αρχείου:",
        "logentry-newusers-newusers": "Ο λογαριασμός χρήστη $1 {{GENDER:$2|δημιουργήθηκε}}",
        "logentry-newusers-create": "Ο λογαριασμός χρήστη $1 {{GENDER:$2|δημιουργήθηκε}}",
        "logentry-newusers-create2": "Ο λογαριασμός χρήστη $3 δημιουργήθηκε από {{GENDER:$2|τον|την}} $1",
-       "logentry-newusers-byemail": "Î\9f Î»Î¿Î³Î±Ï\81ιαÏ\83μÏ\8cÏ\82 Ï\87Ï\81ήÏ\83Ï\84η $3 Î´Î·Î¼Î¹Î¿Ï\85Ï\81γήθηκε Î±Ï\80Ï\8c {{GENDER:$2|Ï\84ον|Ï\84ην}} $1 ÎºÎ±Î¹ Î¿ ÎºÏ\89δικÏ\8cÏ\82 Ï\80Ï\81Ï\8cÏ\83βαÏ\83ηÏ\82 ÎµÏ\83Ï\84άλη μέσω ηλεκτρονικού ταχυδρομείου",
+       "logentry-newusers-byemail": "Î\9f Î»Î¿Î³Î±Ï\81ιαÏ\83μÏ\8cÏ\82 Ï\87Ï\81ήÏ\83Ï\84η $3 Î´Î·Î¼Î¹Î¿Ï\85Ï\81γήθηκε Î±Ï\80Ï\8c {{GENDER:$2|Ï\84ον|Ï\84ην}} $1 ÎºÎ±Î¹ Î±Ï\80εÏ\83Ï\84άλη Ï\83Ï\85νθημαÏ\84ικÏ\8c μέσω ηλεκτρονικού ταχυδρομείου",
        "logentry-newusers-autocreate": "Ο λογαριασμός χρήστη $1 δημιουργήθηκε αυτόματα",
        "logentry-protect-protect": "$1 {{GENDER:$2|προστατευμένος|προστατευμένη}} $3 $4",
        "logentry-protect-modify": "$1 {{GENDER:$2|άλλαξε}} επίπεδο προστασίας για $3 $4",
        "authmanager-email-help": "Διεύθυνση ηλεκτρονικού ταχυδρομείου:",
        "authmanager-realname-label": "Πραγματικό όνομα",
        "authmanager-realname-help": "Πραγματικό όνομα του χρήστη",
-       "authmanager-provider-temporarypassword": "Προσωρινός κωδικός",
+       "authmanager-provider-temporarypassword": "Προσωρινό συνθηματικό",
        "authprovider-resetpass-skip-label": "Παράβλεψη",
        "authprovider-resetpass-skip-help": "Παράβλεψη της επαναφοράς του κωδικού πρόσβασης.",
        "specialpage-securitylevel-not-allowed-title": "Δεν επιτρέπεται",
        "edit-error-short": "Σφάλμα: $1",
        "edit-error-long": "Σφάλματα: \n\n$1",
        "revid": "αναθεώρηση $1",
+       "interfaceadmin-info": "$1\n\nΟι άδειες για την επεξεργασία καθολικών αρχείων CSS/JS/JSON διαχωρίστηκαν πρόσφατα από το δικαίωμα <code>editinterface</code>. Εάν δεν καταλαβαίνετε γιατί εμφανίζεται αυτό το σφάλμα, ανατρέξτε στο [[mw:MediaWiki_1.32/interface-admin]].",
+       "gotointerwiki-invalid": "Ο καθορισμένος τίτλος δεν είναι έγκυρος.",
        "pagedata-title": "Δεδομένα σελίδας",
        "pagedata-bad-title": "Μη έγκυρος τίτλος: $1.",
-       "passwordpolicies-policy-passwordnotinlargeblacklist": "Ο κωδικός πρόσβασης δεν μπορεί να είναι στην λίστα των 100.000 πιο συνηθισμένων κωδικών πρόσβασης."
+       "unregistered-user-config": "Για λόγους ασφαλείας δεν είναι δυνατή η φόρτωση υποσελίδων χρήστη JavaScript, CSS και JSON για μη εγγεγραμμένους χρήστες.",
+       "passwordpolicies": "Πολιτικές συνθηματικού",
+       "passwordpolicies-summary": "Αυτή είναι μια λίστα με τις ισχύουσες πολιτικές συνθηματικού για τις ομάδες χρηστών που καθορίζονται σε αυτό το wiki.",
+       "passwordpolicies-group": "Ομάδα",
+       "passwordpolicies-policies": "Πολιτικές",
+       "passwordpolicies-policy-minimalpasswordlength": "Το συνθηματικό πρέπει να έχει μήκος τουλάχιστον $1 {{PLURAL:$1|χαρακτήρα|χαρακτήρων}}",
+       "passwordpolicies-policy-minimumpasswordlengthtologin": "Το συνθηματικό πρέπει να έχει μήκος τουλάχιστον $1 {{PLURAL:$1|χαρακτήρα|χαρακτήρων}} για να είναι δυνατή η σύνδεση",
+       "passwordpolicies-policy-passwordcannotmatchusername": "Το συνθηματικό δεν μπορεί να είναι ίδιο με το όνομα χρήστη",
+       "passwordpolicies-policy-passwordcannotmatchblacklist": "Το συνθηματικό δεν μπορεί να είναι ακριβώς ίδιο με κάποιο από τα συνθηματικά που βρίσκονται σε μαύρη λίστα",
+       "passwordpolicies-policy-maximalpasswordlength": "Το συνθηματικό πρέπει να έχει μήκος λιγότερο από $1 {{PLURAL:$1|χαρακτήρα|χαρακτήρες}}",
+       "passwordpolicies-policy-passwordcannotbepopular": "Το συνθηματικό δεν μπορεί να είναι {{PLURAL:$1|το δημοφιλέστερο συνθηματικό|στη λίστα με τα $1 δημοφιλέστερα συνθηματικά}}",
+       "passwordpolicies-policy-passwordnotinlargeblacklist": "Το συνθηματικό δεν μπορεί να είναι στην λίστα των 100.000 συνηθέστερα χρησιμοποιούμενων συνθηματικών.",
+       "passwordpolicies-policyflag-forcechange": "πρέπει να αλλαχθεί κατά τη σύνδεση",
+       "unprotected-js": "Για λόγους ασφαλείας δεν μπορεί να φορτωθεί JavaScript από μη προστατευμένες σελίδες. Παρακαλούμε δημιουργήστε javascript μόνο στον ονοματοχώρο MediaWiki: ή ως υποσελίδα χρήστη"
 }
index 197499c..56ff467 100644 (file)
        "page_first": "first",
        "page_last": "last",
        "histlegend": "Diff selection: Mark the radio boxes of the revisions to compare and hit enter or the button at the bottom.<br />\nLegend: <strong>({{int:cur}})</strong> = difference with latest revision, <strong>({{int:last}})</strong> = difference with preceding revision, <strong>{{int:minoreditletter}}</strong> = minor edit.",
-       "history-fieldset-title": "Search for revisions",
+       "history-fieldset-title": "Filter revisions",
        "history-show-deleted": "Revision deleted only",
        "history_copyright": "-",
        "histfirst": "oldest",
        "action-changetags": "add and remove arbitrary tags on individual revisions and log entries",
        "action-deletechangetags": "delete tags from the database",
        "action-purge": "purge this page",
+       "action-apihighlimits": "use higher limits in API queries",
+       "action-autoconfirmed": "not be affected by IP-based rate limits",
+       "action-bigdelete": "delete pages with large histories",
+       "action-blockemail": "block a user from sending email",
+       "action-bot": "be treated as an automated process",
+       "action-editprotected": "edit pages protected as \"{{int:protect-level-sysop}}\"",
+       "action-editsemiprotected": "edit pages protected as \"{{int:protect-level-autoconfirmed}}\"",
+       "action-editinterface": "edit the user interface",
+       "action-editusercss": "edit other users' CSS files",
+       "action-edituserjson": "edit other users' JSON files",
+       "action-edituserjs": "edit other users' JavaScript files",
+       "action-editsitecss": "edit sitewide CSS",
+       "action-editsitejson": "edit sitewide JSON",
+       "action-editsitejs": "edit sitewide JavaScript",
+       "action-editmyusercss": "edit your own user CSS files",
+       "action-editmyuserjson": "edit your own user JSON files",
+       "action-editmyuserjs": "edit your own user JavaScript files",
+       "action-viewsuppressed": "view revisions hidden from any user",
+       "action-hideuser": "block a username, hiding it from the public",
+       "action-ipblock-exempt": "bypass IP blocks, auto-blocks and range blocks",
+       "action-unblockself": "unblock oneself",
+       "action-noratelimit": "not be affected by rate limits",
+       "action-reupload-own": "overwrite existing files uploaded by oneself",
+       "action-nominornewtalk": "not have minor edits to discussion pages trigger the new messages prompt",
+       "action-markbotedits": "mark rolled-back edits as bot edits",
+       "action-patrolmarks": "view recent changes patrol marks",
+       "action-override-export-depth": "export pages including linked pages up to a depth of 5",
+       "action-suppressredirect": "not create redirects from source pages when moving pages",
        "nchanges": "$1 {{PLURAL:$1|change|changes}}",
        "ntimes": "$1×",
        "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|since last visit}}",
index ae49993..d53895f 100644 (file)
@@ -2,7 +2,8 @@
        "@metadata": {
                "authors": [
                        "Esbardu",
-                       "Xuacu"
+                       "Xuacu",
+                       "Enolp"
                ]
        },
        "exif-imagewidth": "Anchor",
@@ -29,7 +30,7 @@
        "exif-datetime": "Data y hora de cambiu del ficheru",
        "exif-imagedescription": "Títulu de la imaxe",
        "exif-make": "Fabricante de la cámara",
-       "exif-model": "Modelu de cámara",
+       "exif-model": "Modelu de la cámara",
        "exif-software": "Software usáu",
        "exif-artist": "Autor",
        "exif-copyright": "Titular del Copyright",
@@ -63,6 +64,7 @@
        "exif-lightsource": "Fonte de la lluz",
        "exif-flash": "Flax",
        "exif-focallength": "Llonxitú focal de la lente",
+       "exif-focallength-format": "$1 mm",
        "exif-subjectarea": "Área del suxetu",
        "exif-flashenergy": "Enerxía del flax",
        "exif-focalplanexresolution": "Resolución X del planu focal",
@@ -89,7 +91,7 @@
        "exif-gpsversionid": "Versión de la etiqueta GPS",
        "exif-gpslatituderef": "Llatitú Norte o Sur",
        "exif-gpslatitude": "Llatitú",
-       "exif-gpslongituderef": "Llonxitú Este o Oeste",
+       "exif-gpslongituderef": "Llonxitú este u oeste",
        "exif-gpslongitude": "Llonxitú",
        "exif-gpsaltituderef": "Referencia d'altitú",
        "exif-gpsaltitude": "Altitú",
index d64121d..84046fc 100644 (file)
        "page_first": "ensimmäinen sivu",
        "page_last": "viimeinen sivu",
        "histlegend": "Eroavaisuuksien valinta: Merkitse niiden versioiden valintaympyrät, joita haluat vertailla, ja paina enter tai alhaalla olevaa nappia.<br />\nSelitys: '''({{int:cur}})''' = eroavaisuudet uusimpaan versioon, '''({{int:last}})''' = eroavaisuudet edeltävään versioon, '''{{int:minoreditletter}}''' = pieni muutos.",
-       "history-fieldset-title": "Etsi versioita",
+       "history-fieldset-title": "Suodata versioita",
        "history-show-deleted": "Vain poistetut versiot",
        "histfirst": "vanhimmat",
        "histlast": "uusimmat",
        "action-changetags": "lisätä ja poistaa satunnaisia merkkauksia yksittäisissä sivuversioissa ja lokimerkinnöissä",
        "action-deletechangetags": "poistaa merkkauksia tietokannasta",
        "action-purge": "päivittää tämän sivun välimuistia",
+       "action-apihighlimits": "käyttää korkeampia rajoja API-kyselyissä",
+       "action-bigdelete": "poistaa sivuja, joilla on pitkä historia",
+       "action-blockemail": "estää käyttäjää lähettämästä sähköpostia",
+       "action-editusercss": "muokata toisten käyttäjien CSS-tiedostoja",
+       "action-edituserjson": "muokata toisten käyttäjien JSON-tiedostoja",
+       "action-edituserjs": "muokata toisten käyttäjien JavaScript-tiedostoja",
+       "action-editsitecss": "muokata CSS-koodia koko sivustolla",
+       "action-editsitejson": "muokata JSON-koodia koko sivustolla",
+       "action-editsitejs": "muokata JavaScriptiä koko sivustolla",
+       "action-editmyusercss": "muokata omia CSS-tiedostoja",
+       "action-editmyuserjson": "muokkata omia JSON-tiedostoja",
+       "action-editmyuserjs": "muokata omia JavaScript-tiedostoja",
+       "action-viewsuppressed": "katsoa versioita, jotka on piilotettu jokaiselta käyttäjältä",
+       "action-hideuser": "estää käyttäjätunnus ja piilottaa se näkyvistä",
+       "action-ipblock-exempt": "ohittaa IP-, automaattiset ja osoitealue-estot",
+       "action-unblockself": "poistaa esto itseltään",
+       "action-noratelimit": "ohittaa nopeusrajoitukset",
+       "action-reupload-own": "korvata itsetallennettu tiedosto uudella tiedostolla",
+       "action-nominornewtalk": "tehdä pieniä muokkauksia käyttäjien keskustelusivuille siten, että käyttäjälle ei ilmoiteta siitä uutena viestinä",
+       "action-markbotedits": "merkitä muokkausten palauttaminen botilla tehdyksi",
+       "action-patrolmarks": "nähdä tarkastusmerkit tuoreissa muutoksissa",
+       "action-override-export-depth": "viedä sivuja sisältäen viitatut sivut viiden syvyydellä",
+       "action-suppressredirect": "siirtää sivuja luomatta automaattisia ohjauksia",
        "nchanges": "$1 {{PLURAL:$1|muutos|muutosta}}",
        "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|viimeisen käynnin jälkeen}}",
        "enhancedrc-history": "historia",
        "rcfilters-watchlist-edit-watchlist-button": "Muokkaa tarkkailemiasi sivuja",
        "rcfilters-watchlist-showupdated": "Muutokset sivuihin, joilla et ole vieraillut sen jälkeen kun muutokset on tehty, on <strong>lihavoitu</strong> ja värimerkitty.",
        "rcfilters-preference-label": "Käytä ilman JavaScriptiä toimivaa käyttöliittymää",
-       "rcfilters-preference-help": "Lataa tuoreimmat muutokset -näkymän ilman suodattimia tai korostustoimintoa.",
+       "rcfilters-preference-help": "Lataa tuoreimmat muutokset -näkymän ilman suodattimien haku- tai korostustoimintoa.",
        "rcfilters-watchlist-preference-label": "Käytä ilman JavaScriptiä toimivaa käyttöliittymää",
-       "rcfilters-watchlist-preference-help": "Lataa tarkkailulistan ilman suodattimia tai korostustoimintoa.",
+       "rcfilters-watchlist-preference-help": "Lataa tarkkailulistan ilman suodattimien haku- tai korostustoimintoa.",
        "rcfilters-filter-showlinkedfrom-label": "Näytä muutokset sivuilla, jonne on linkki sivulta",
        "rcfilters-filter-showlinkedfrom-option-label": "<strong>Sivut, joihin linkitetään</strong> valitulta sivulta",
        "rcfilters-filter-showlinkedto-label": "Näytä muutokset sivuilla, joista on linkki sivulle",
        "delete-confirm": "Poista ”$1”",
        "delete-legend": "Sivun poisto",
        "historywarning": "<strong>Varoitus:</strong> Sivulla, jota olet poistamassa, on muokkaushistoriaa ja sitä on muokattu $1 {{PLURAL:$1|kerran|kertaa}}:",
-       "historyaction-submit": "Näytä",
+       "historyaction-submit": "Näytä muokkaushistoria",
        "confirmdeletetext": "Olet poistamassa sivun ja kaiken sen historian.\nVahvista, että olet aikeissa tehdä tämän ja että ymmärrät teon seuraukset ja teet poiston [[{{MediaWiki:Policy-url}}|käytäntöjen]] mukaisesti.",
        "actioncomplete": "Toiminto suoritettu",
        "actionfailed": "Toiminto epäonnistui",
        "deleting-backlinks-warning": "<strong>Varoitus:</strong> Sivulle, jota olet poistamassa, johtaa [[Special:WhatLinksHere/{{FULLPAGENAME}}|linkkejä muilta sivuilta]], taikka sivu on sisällytetty muuhun sivuun.",
        "deleting-subpages-warning": "<strong>Varoitus:</strong> Sivu jota olet poistamassa on [[Special:PrefixIndex/{{FULLPAGENAME}}/|{{PLURAL:$1|alasivu|$1 alasivua|51=yli 50 alasivua}}]].",
        "rollback": "palauta aiempaan versioon",
+       "rollback-confirmation-confirm": "Vahvista:",
        "rollback-confirmation-yes": "Palauta",
        "rollback-confirmation-no": "Peruuta",
        "rollbacklink": "palauta",
        "log-description-pagelang": "Tämä on loki, johon merkitään muutokset sivujen kieliasetuksissa.",
        "logentry-pagelang-pagelang": "$1 {{GENDER:$2|muutti}} kielen sivulla $3 kielestä $4 kieleksi $5.",
        "default-skin-not-found": "Hupsista! Oletuksena tuleva ulkoasu wikillesi, joka on määritelty koodissa <code dir=\"ltr\">$wgDefaultSkin</code> muotoon <code>$1</code>, ei ole saatavilla.\n\nAlla on ohjeet englannin kielellä:\n\nYour installation seems to include the following {{PLURAL:$4|skin|skins}}. See [https://www.mediawiki.org/wiki/Manual:Skin_configuration Manual: Skin configuration] for information how to enable {{PLURAL:$4|it|them and choose the default}}.\n\n$2\n\n; If you have just installed MediaWiki:\n: You probably installed from git, or directly from the source code using some other method. This is expected. Try installing some skins from [https://www.mediawiki.org/wiki/Category:All_skins mediawiki.org's skin directory], by:\n:* Downloading the [https://www.mediawiki.org/wiki/Download tarball installer], which comes with several skins and extensions. You can copy and paste the <code>skins/</code> directory from it.\n:* Downloading individual skin tarballs from [https://www.mediawiki.org/wiki/Special:SkinDistributor mediawiki.org].\n:* [https://www.mediawiki.org/wiki/Download_from_Git#Using_Git_to_download_MediaWiki_skins Using Git to download skins].\n: Doing this should not interfere with your git repository if you're a MediaWiki developer.\n\n; If you have just upgraded MediaWiki:\n: MediaWiki 1.24 and newer no longer automatically enables installed skins (see [https://www.mediawiki.org/wiki/Manual:Skin_autodiscovery Manual: Skin autodiscovery]). You can paste the following {{PLURAL:$5|line|lines}} into <code>LocalSettings.php</code> to enable {{PLURAL:$5|the|all}} installed {{PLURAL:$5|skin|skins}}:\n\n<pre dir=\"ltr\">$3</pre>\n\n; If you have just modified <code>LocalSettings.php</code>:\n: Double-check the skin names for typos.",
-       "default-skin-not-found-no-skins": "Hupsista! Oletusulkoasua sinun wikillesi ei ole saatavilla. Se on määritelty ulkoasuksi <code>$1</code> kohteessa <code>$wgDefaultSkin</code>.\n\nAlla on ohjeet englannin kielellä:\n\nYou have no installed skins.\n\n; If you have just installed or upgraded MediaWiki:\n: You probably installed from git, or directly from the source code using some other method. This is expected. MediaWiki 1.24 and newer doesn't include any skins in the main repository. Try installing some skins from [https://www.mediawiki.org/wiki/Category:All_skins mediawiki.org's skin directory], by:\n:* Downloading the [https://www.mediawiki.org/wiki/Download tarball installer], which comes with several skins and extensions. You can copy and paste the <code>skins/</code> directory from it.\n:* Downloading individual skin tarballs from [https://www.mediawiki.org/wiki/Special:SkinDistributor mediawiki.org].\n:* [https://www.mediawiki.org/wiki/Download_from_Git#Using_Git_to_download_MediaWiki_skins Using Git to download skins].\n: Doing this should not interfere with your git repository if you're a MediaWiki developer. See [https://www.mediawiki.org/wiki/Manual:Skin_configuration Manual: Skin configuration] for information how to enable skins and choose the default.",
+       "default-skin-not-found-no-skins": "Hupsista! Oletusulkoasua wikillesi ei ole saatavilla. Se on määritelty ulkoasuksi <code>$1</code> kohteessa <code>$wgDefaultSkin</code>.\n\nAlla on ohjeet englannin kielellä:\n\nYou have no installed skins.\n\n; If you have just installed or upgraded MediaWiki:\n: You probably installed from git, or directly from the source code using some other method. This is expected. MediaWiki 1.24 and newer doesn't include any skins in the main repository. Try installing some skins from [https://www.mediawiki.org/wiki/Category:All_skins mediawiki.org's skin directory], by:\n:* Downloading the [https://www.mediawiki.org/wiki/Download tarball installer], which comes with several skins and extensions. You can copy and paste the <code>skins/</code> directory from it.\n:* Downloading individual skin tarballs from [https://www.mediawiki.org/wiki/Special:SkinDistributor mediawiki.org].\n:* [https://www.mediawiki.org/wiki/Download_from_Git#Using_Git_to_download_MediaWiki_skins Using Git to download skins].\n: Doing this should not interfere with your git repository if you're a MediaWiki developer. See [https://www.mediawiki.org/wiki/Manual:Skin_configuration Manual: Skin configuration] for information how to enable skins and choose the default.",
        "default-skin-not-found-row-enabled": "* <code>$1</code> / $2 (käytössä)",
        "default-skin-not-found-row-disabled": "* <code>$1</code> / $2 (<strong>ei käytössä</strong>)",
        "mediastatistics": "Median tilastotiedot",
index 84ba5a1..a172ef7 100644 (file)
                        "Alacabe",
                        "Eihel",
                        "Tektasc",
-                       "DSwissK"
+                       "DSwissK",
+                       "Moyogo"
                ]
        },
        "tog-underline": "Soulignement des liens :",
        "oct": "oct.",
        "nov": "nov.",
        "dec": "déc.",
-       "january-date": "{{PLURAL:$1|1=1ᵉʳ|$1}} janvier",
-       "february-date": "{{PLURAL:$1|1=1ᵉʳ|$1}} février",
-       "march-date": "{{PLURAL:$1|1=1ᵉʳ|$1}} mars",
-       "april-date": "{{PLURAL:$1|1=1ᵉʳ|$1}} avril",
-       "may-date": "{{PLURAL:$1|1=1ᵉʳ|$1}} mai",
-       "june-date": "{{PLURAL:$1|1=1ᵉʳ|$1}} juin",
-       "july-date": "{{PLURAL:$1|1=1ᵉʳ|$1}} juillet",
-       "august-date": "{{PLURAL:$1|1=1ᵉʳ|$1}} août",
-       "september-date": "{{PLURAL:$1|1=1ᵉʳ|$1}} septembre",
-       "october-date": "{{PLURAL:$1|1=1ᵉʳ|$1}} octobre",
-       "november-date": "{{PLURAL:$1|1=1ᵉʳ|$1}} novembre",
-       "december-date": "{{PLURAL:$1|1=1ᵉʳ|$1}} décembre",
+       "january-date": "{{PLURAL:$1|1=1<sup>er</sup>|$1}} janvier",
+       "february-date": "{{PLURAL:$1|1=1<sup>er</sup>|$1}} février",
+       "march-date": "{{PLURAL:$1|1=1<sup>er</sup>|$1}} mars",
+       "april-date": "{{PLURAL:$1|1=1<sup>er</sup>|$1}} avril",
+       "may-date": "{{PLURAL:$1|1=1<sup>er</sup>|$1}} mai",
+       "june-date": "{{PLURAL:$1|1=1<sup>er</sup>|$1}} juin",
+       "july-date": "{{PLURAL:$1|1=1<sup>er</sup>|$1}} juillet",
+       "august-date": "{{PLURAL:$1|1=1<sup>er</sup>|$1}} août",
+       "september-date": "{{PLURAL:$1|1=1<sup>er</sup>|$1}} septembre",
+       "october-date": "{{PLURAL:$1|1=1<sup>er</sup>|$1}} octobre",
+       "november-date": "{{PLURAL:$1|1=1<sup>er</sup>|$1}} novembre",
+       "december-date": "{{PLURAL:$1|1=1<sup>er</sup>|$1}} décembre",
        "period-am": "AM",
        "period-pm": "PM",
        "pagecategories": "{{PLURAL:$1|Catégorie|Catégories}}",
        "page_first": "première",
        "page_last": "dernière",
        "histlegend": "Sélection du diff : cochez les boutons radio des versions à comparer et appuyez sur entrée ou sur le bouton en bas.<br />\nLégende : <strong>({{int:cur}})</strong> = différence avec la dernière version, <strong>({{int:last}})</strong> = différence avec la version précédente, <strong>{{int:minoreditletter}}</strong> = modification mineure.",
-       "history-fieldset-title": "Rechercher des révisions",
+       "history-fieldset-title": "Filtrer les révisions",
        "history-show-deleted": "Révision supprimée uniquement",
        "histfirst": "les plus anciennes",
        "histlast": "les plus récentes",
        "action-changetags": "ajouter et supprimer de façon arbitraire des balises sur des révisions individuelles et des entrées de journal",
        "action-deletechangetags": "supprimer des balises de la base de données",
        "action-purge": "purger cette page",
+       "action-apihighlimits": "utiliser de limites plus élevées dans les requêtes à l’API",
+       "action-autoconfirmed": "ne pas être impacté par les limites de taux basées sur l’IP",
+       "action-bigdelete": "supprimer des pages avec de grands historiques",
+       "action-blockemail": "empêcher un utilisateur d’envoyer des courriels",
+       "action-bot": "être traité comme un processus automatisé",
+       "action-editprotected": "modifier les pages protégées comme « {{int:protect-level-sysop}} »",
+       "action-editsemiprotected": "modifier des pages protégées avec le statut « {{int:protect-level-autoconfirmed}} »",
+       "action-editinterface": "modifier l’interface utilisateur",
+       "action-editusercss": "modifier les fichiers CSS d’autres utilisateurs",
+       "action-edituserjson": "modifier les fichiers JSON d’autres utilisateurs",
+       "action-edituserjs": "modifier les fichiers JavaScript d’autres utilisateurs",
+       "action-editsitecss": "modifier le CSS du site",
+       "action-editsitejson": "modifier le JSON du site",
+       "action-editsitejs": "modifier le JavaScript du site",
+       "action-editmyusercss": "modifier vos propres fichiers CSS utilisateur",
+       "action-editmyuserjson": "modifier vos propres fichiers JSON utilisateur",
+       "action-editmyuserjs": "modifier vos propres fichiers JavaScript utilisateur",
+       "action-viewsuppressed": "afficher les révisions masquées pour n’importe quel utilisateur",
+       "action-hideuser": "bloquer un nom d’utilisateur, en le masquant au public",
+       "action-ipblock-exempt": "contourner les blocages d’IP, blocages automatiques et blocages de plages d’IP",
+       "action-unblockself": "vous débloquer vous-même",
+       "action-noratelimit": "ne pas être impacté par les limites de taux",
+       "action-reupload-own": "écraser des fichiers que vous avez vous-même importés",
+       "action-nominornewtalk": "ne pas déclencher la notification de nouveau message lors d’une modification mineure sur une pages de discussion",
+       "action-markbotedits": "marquer des modifications révoquées comme ayant été faites par un robot",
+       "action-patrolmarks": "voir les indications de relecture dans les modifications récentes",
+       "action-override-export-depth": "exporter les pages en incluant les pages liées jusqu’à une profondeur de 5 niveaux",
+       "action-suppressredirect": "ne pas créer de redirection depuis le titre d’origine en renommant les pages",
        "nchanges": "$1 modification{{PLURAL:$1||s}}",
        "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|depuis la dernière visite}}",
        "enhancedrc-history": "historique",
index d1d9de9..c8c261c 100644 (file)
        "cancel": "Annulearje",
        "moredotdotdot": "Mear...",
        "mypage": "Myn side",
-       "mytalk": "Myn oerlis",
+       "mytalk": "Oerlis",
        "anontalk": "Oerlisside foar dit IP-adres",
        "navigation": "Navigaasje",
        "and": "&#32;en",
        "password-name-match": "Jo wachtwurd moat oars wêze as jo meidochnamme.",
        "mailmypassword": "Nij wachtwurd",
        "passwordremindertitle": "Nij tydlik wachtwurd foar {{SITENAME}}",
-       "passwordremindertext": "Immen (nei alle gedachten jo, fan ynternetadres $1) had in nij wachtwurd\nfoar {{SITENAME}} ($4) oanfrege. Der is in tydlik wachtwurd foar meidogger\n\"$2\"  makke en ynstelt as \"$3\". As dat jo bedoeling wie, melde jo jo dan\nno oan en kies in nij wachtwurd. Dyn tydlik wachtwurd komt yn {{PLURAL:$5|ien dei|$5 dagen}} te ferfallen.\nDer is in tydlik wachtwurd oanmakke foar meidogger \"$2\": \"$3\".\n\nAs immen oars as jo dit fersyk dien hat of at it wachtwurd jo tuskentiidsk wer yn 't sin kommen is en\njo it net langer feroarje wolle, dan kinne jo dit berjocht ferjitte en\nfierdergean mei it brûken fan jo âlde wachtwurd.",
+       "passwordremindertext": "Immen (mei it IP-adres $1) hat in nij\nwachtwurd  oanfrege foar {{SITENAME}} ($4). Der is in tydlik wachtwurd foar\nmeidogger \"$2\" oanmakke, en dat is \"$3\". At dat jo\ndoel wie, meld jo dan no oan en kies in nij wachtwurd.\nJo tydlik wachtwurd ferrint nei {{PLURAL:$5|ien dei|$5 dagen}}.\n\nAt immen oars dy oanfraach dien hat, of at jo jo wachtwurd wer witte\nen it net mear feroarje wolle, dan kinne jo dit berjocht negearje en\njo âlde wachtwurd brûken bliuwe.",
        "noemail": "Der is gjin e-postadres foar meidogger \"$1\".",
        "passwordsent": "Der is in nij wachtwurd ferstjoerd nei it opjûne e-mailadres fan \"$1\".\nMeld jo nei ûntfangst op 'e nij oan.",
        "blocked-mailpassword": "Jo IP-adres is blokkearre foar it meitsjen fan feroarings. Om misbrûk tefoaren te kommen is it net mûglik in oar wachtwurd oan te freegjen.",
        "mergehistory-into": "Bestimmingside:",
        "mergehistory-list": "Gearfoegbere bewurkingsskiednis",
        "mergehistory-merge": "De folgjende ferzjes fan [[:$1]] kinne gearfoege wurde nei [[:$2]].\nBrûk de kolom mei de karrûntsjes om allinne de ferzjes makke op en foar de oanjûne tiid gear te foegjen.\nTink derom it brûken fan de navigaasjeferwizings dy kolom op'e nij ynstelt.",
-       "mergehistory-go": "Besjen bewurkings dy't kombinearre wurde kinne",
+       "mergehistory-go": "Gearfoegbere bewurkings besjen",
        "mergehistory-submit": "Kombinearje ferzjes",
        "mergehistory-empty": "Gjin ferzjes kinne kombinearren wurde.",
        "mergehistory-done": "Kombinearjen slagge fan $3 {{PLURAL:$3|ferzje|ferzjes}} fan $1 no [[:$2]].",
        "nextn-title": "{{PLURAL:$1|Folgjend risseltaat|Folgjende $1 risseltaat}}",
        "shown-title": "Lit $1 {{PLURAL:$1|resultaat|resultaten}} de side sjen",
        "viewprevnext": "Besjoch ($1 {{int:pipe-separator}} $2) ($3)",
-       "searchmenu-exists": "<strong>Der is in side mei namme \"[[:$1]]\" yn dizze wiki</strong>",
+       "searchmenu-exists": "<strong>Der is in side mei de namme \"[[:$1]]\" op 'e wiki.</strong> {{PLURAL:$2|0=|Sjoch ek de oare sykresultaten.}}",
        "searchmenu-new": "<strong>Meitsje de side \"[[:$1]]\" op dizze wiki!</strong> {{PLURAL:$2|0=|Sjoch ek de side fûn mei jo sykopdracht.|Sjoch ek de sykresultaten dy't fûn binne.}}",
        "searchprofile-articles": "Ynhâldlike siden",
        "searchprofile-images": "Multymedia",
        "right-deletedhistory": "Wiske ferzjes besjen, sûnder sjen te kinnen wat wiske is.",
        "right-browsearchive": "Wiske siden besjen",
        "right-undelete": "Wiske siden tebeksette",
-       "right-suppressrevision": "Beskate sideferzjes fan in meidogger besjen, ferbergje of net ferbergje",
+       "right-suppressrevision": "Beskate sideferzjes fan in meidogger besjen, ferbergje of werombringe",
        "right-suppressionlog": "Net-publike logboeken besjen",
        "right-block": "Oare meidoggers de mûglikheid ta bewurkjen ôfnimme",
        "right-blockemail": "In meidogger it rjocht ta it ferstjoeren fan e-mail ôfnimme",
        "recentchanges-label-plusminus": "De sidegrutte is mei dit oantal bytes wizige",
        "recentchanges-legend-heading": "<strong>Leginda:</strong>",
        "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}}<br />(sjoch ek de [[Special:NewPages|list mei nije siden]])",
+       "recentchanges-submit": "Werjaan",
        "rcfilters-legend-heading": "<strong>List fan ôfkoartings:</strong>",
+       "rcfilters-group-results-by-page": "Resultaten op side groepearje",
+       "rcfilters-activefilters": "Aktive filters",
+       "rcfilters-activefilters-hide": "Ynklappe",
+       "rcfilters-activefilters-show": "Utklappe",
+       "rcfilters-activefilters-hide-tooltip": "Fek mei Aktive filters ferbergje",
+       "rcfilters-activefilters-show-tooltip": "Fek mei Aktive filters sjen litte",
+       "rcfilters-advancedfilters": "Utwreide filters",
+       "rcfilters-limit-title": "Resultaten werjaan",
+       "rcfilters-limit-and-date-label": "$1 {{PLURAL:$1|feroaring|feroarings}}, $2",
+       "rcfilters-date-popup-title": "Tiidsperioade trochsykje",
+       "rcfilters-days-title": "Lêste dagen",
+       "rcfilters-hours-title": "Lêste oeren",
+       "rcfilters-days-show-days": "$1 {{PLURAL:$1|dei|dagen}}",
+       "rcfilters-days-show-hours": "$1 {{PLURAL:$1|oere|oeren}}",
+       "rcfilters-quickfilters": "Bewarre filters",
+       "rcfilters-quickfilters-placeholder-title": "Noch gjin filters bewarre",
+       "rcfilters-quickfilters-placeholder-description": "Klik op it blêdwizerikoan yn it fek mei Aktive filters hjirûnder, en bewarje jo filterynstellings foar letter werbrûk.",
+       "rcfilters-savedqueries-defaultlabel": "Bewarre filters",
+       "rcfilters-savedqueries-rename": "Omneame",
+       "rcfilters-savedqueries-setdefault": "As standert ynstelle",
+       "rcfilters-savedqueries-unsetdefault": "As standert wiskje",
+       "rcfilters-savedqueries-remove": "Wiskje",
+       "rcfilters-savedqueries-new-name-label": "Namme",
+       "rcfilters-savedqueries-new-name-placeholder": "Beskriuw it doel fan it filter",
+       "rcfilters-savedqueries-apply-label": "Filter oanmeitsje",
+       "rcfilters-savedqueries-apply-and-setdefault-label": "Standertfilter oanmeitsje",
+       "rcfilters-savedqueries-cancel-label": "Annulearje",
+       "rcfilters-savedqueries-add-new-title": "De filterynstellings fan no bewarje",
+       "rcfilters-savedqueries-already-saved": "Dizze filters wurde al bewarre. Feroarje jo ynstellings om in nij filter bewarje te kinnen.",
+       "rcfilters-restore-default-filters": "Standertfilters werombringe",
+       "rcfilters-clear-all-filters": "Alle filters wiskje",
+       "rcfilters-search-placeholder": "Feroarings filterje (brûk it menu of sykje op filternamme)",
+       "rcfilters-empty-filter": "Gjin aktive filters. Alle bydragen wurde werjûn.",
+       "rcfilters-filterlist-feedbacklink": "Lit ús hearre wat jo fan dit filterark fine",
+       "rcfilters-highlightbutton-title": "Resultaten aksintuearje",
+       "rcfilters-highlightmenu-title": "Kies in kleur",
+       "rcfilters-highlightmenu-help": "Kleur kieze foar it aksintuearjen fan dizze eigenskip",
+       "rcfilters-filtergroup-automated": "Automatisearre bydragen",
+       "rcfilters-filter-bots-description": "Wizigings makke troch automatisearre helpmiddels.",
+       "rcfilters-filter-humans-label": "Minske (gjin bot)",
+       "rcfilters-filter-humans-description": "Wizigings makke troch minskehannen.",
+       "rcfilters-filtergroup-significance": "Belang",
+       "rcfilters-filter-minor-label": "Lytse betsjutting",
+       "rcfilters-filter-minor-description": "Feroarings troch de skriuwer lebele as fan lytse betsjutting.",
+       "rcfilters-filter-major-label": "Gjin lytse betsjutting",
+       "rcfilters-filter-major-description": "Feroarings net lebele as fan lytse betsjutting.",
+       "rcfilters-filtergroup-changetype": "Wizigingssoarte",
+       "rcfilters-filter-pageedits-label": "Sidebewurkings",
+       "rcfilters-filter-newpages-label": "Nije siden",
+       "rcfilters-filter-logactions-label": "Lochaksjes",
+       "rcfilters-filter-excluded": "Utsein",
+       "rcfilters-exclude-button-off": "Seleksje omkeare",
+       "rcfilters-exclude-button-on": "Omkearde seleksje",
+       "rcfilters-view-tags": "Lebele bewurkings",
+       "rcfilters-view-namespaces-tooltip": "Resultaten op nammeromte filterje",
+       "rcfilters-view-tags-tooltip": "Resultaten mei bewurkingslebels filterje",
+       "rcfilters-view-return-to-default-tooltip": "Werom nei it filterhaadmenu",
+       "rcfilters-view-tags-help-icon-tooltip": "Kom mear oan 'e weet oer Lebele bewurkings",
+       "rcfilters-liveupdates-button": "Daliks bywurkje",
+       "rcfilters-liveupdates-button-title-on": "Daliks bywurkjen stopje",
+       "rcfilters-liveupdates-button-title-off": "Nije feroarings op it stuit werjaan",
        "rcnotefrom": "Hjirûnder {{PLURAL:$5|stiet de feroaring|steane de feroarings}} sûnt <strong>$3, $4</strong> (maksimaal <strong>$1</strong> werjûn).",
        "rclistfrom": "Jou nije feroarings, begjinnende mei $3 $2",
        "rcshowhideminor": "Lytse feroarings $1",
        "movepagetext": "Dit werneamt in side, mei alle sideskiednis.\nDe âlde titel wurdt in trochferwizing nei de nije.\nKeppelings mei de âlde side wurde net feroare;\ngean sels nei of't der dûbele of misse ferwizings binne.\nIt hinget fan jo ôf of't de siden noch keppelen binne sa't it mient wie.\n\nDe side wurdt '''net''' werneamt as der al in side mei dy namme is, útsein as it in side\nsûnder skiednis is en de side leech is of in trochferwizing is. Sa kinne jo in side\ndaalks weromneame as jo in flater meitsje, mar jo kinne in oare side net oerskriuwe.",
        "movepagetalktext": "As der in oerlisside by heart, dan bliuwt dy oan de side keppele, '''útsein''':\n*De nije sidenamme yn in oare nammeromte is,\n*Der keppele oan de nije namme al in net-lege oerlisside is, of\n*Jo dêr net foar kieze.\n\nIn dizze gefallen is it oan jo hoe't jo de oerlisside omneame of ynfoegje wolle.",
        "movenologintext": "Jo moatte [[Special:UserLogin|oanmeld]] wêze om in side wer te neamen.",
-       "newtitle": "As nij titel",
+       "newtitle": "Nije titel:",
        "move-watch": "Folch dizze side",
        "movepagebtn": "Side omneame",
        "pagemovedsub": "Werneamen slagge",
        "import-logentry-upload-detail": "$1 {{PLURAL:$1|ferzje|ferzjes}}",
        "import-logentry-interwiki-detail": "$1 {{PLURAL:$1|ferzje|ferzjes}} fan $2",
        "tooltip-pt-userpage": "Jo {{GENDER:|meidogger}}side",
-       "tooltip-pt-mytalk": "Jo oerlisside",
+       "tooltip-pt-mytalk": "{{GENDER:|Jo}} oerlisside",
        "tooltip-pt-preferences": "Myn foarkarynstellings",
        "tooltip-pt-watchlist": "List fan siden dy'sto besjochst op feroarings",
        "tooltip-pt-mycontris": "Oersjocht fan jo bydragen",
        "tooltip-ca-protect": "Dizze side befeiligje",
        "tooltip-ca-delete": "Dizze side weidwaan",
        "tooltip-ca-undelete": "Fuorthelle bewurkings fan dizze side weromsette",
-       "tooltip-ca-move": "Dizze side ferskowe",
+       "tooltip-ca-move": "Dizze side omneame",
        "tooltip-ca-watch": "Dizze side oan jo folchlist taheakje",
        "tooltip-ca-unwatch": "Dizze side fan jo folchlist ôfhelje",
        "tooltip-search": "{{SITENAME}} trochsykje",
        "show-big-image": "Oarspronklik bestân",
        "show-big-image-other": "Oare {{PLURAL:$2|resolúsje|resolúsjes}}: $1.",
        "show-big-image-size": "$1 × $2 pixels",
-       "newimages": "Nije ôfbylden",
+       "newimages": "Galery mei nije ôfbylden",
        "imagelisttext": "Dit is in list fan '''$1''' {{PLURAL:$1|bestân|bestannen}}, op $2.",
        "newimages-summary": "Dizze bysûndere side lit de lêst opladen bestannen sjen.",
        "newimages-legend": "Filter",
        "tag-mw-rollback": "Weromdraaid",
        "tag-mw-rollback-description": "Bewurkings mei de keppeling 'weromdraaie', dy't foargeande wizigings ûngedien makke hawwe",
        "tag-mw-undo": "Ungedien meitsjen",
+       "tags-title": "Lebels",
+       "tags-intro": "Op dizze side steane de lebels en har betsjutting, wêrmei't de programmatuer bewurkings markearje kin.",
+       "tags-tag": "Lebelnamme",
+       "tags-display-header": "Werjefte yn feroaringslisten",
+       "tags-description-header": "Folsleine beskriuwing fan 'e betsjutting",
        "tags-source-header": "Boarne",
        "tags-active-header": "Aktyf?",
+       "tags-hitcount-header": "Lebele bewurkings",
        "tags-actions-header": "Aksjes",
        "tags-active-yes": "Ja",
        "tags-active-no": "Nee",
+       "tags-source-extension": "Definiearre yn de programmatuer",
+       "tags-source-manual": "Hânmjittich taheakke troch meidoggers en bots",
+       "tags-source-none": "Net mear yn gebrûk",
        "tags-edit": "bewurkje",
        "tags-delete": "fuortsmite",
        "tags-activate": "aktivearje",
        "tags-deactivate": "deaktivearje",
        "tags-hitcount": "$1 {{PLURAL:$1|bewurking|bewurkings}}",
+       "tags-create-tag-name": "Lebelnamme:",
        "tags-create-reason": "Reden:",
        "tags-delete-reason": "Reden:",
        "tags-activate-reason": "Reden:",
index fa36293..714b106 100644 (file)
        "page_first": "ראשון",
        "page_last": "אחרון",
        "histlegend": "בחירת גרסאות להשוואה: יש לבחור את הגרסאות שברצונך להשוות ולאחר מכן להקיש על Enter או ללחוץ על הכפתור שלמטה.<br />\nמקרא: <strong>({{int:cur}})</strong> = השוואה עם הגרסה הנוכחית, <strong>({{int:last}})</strong> = השוואה עם הגרסה הקודמת, <strong>{{int:minoreditletter}}</strong> = עריכה משנית.",
-       "history-fieldset-title": "×\97×\99פ×\95ש גרסאות",
+       "history-fieldset-title": "ס×\99× ×\95×\9f גרסאות",
        "history-show-deleted": "גרסאות מוסתרות בלבד",
        "histfirst": "הישנות ביותר",
        "histlast": "החדשות ביותר",
        "action-changetags": "להוסיף או להסיר תגיות מגרסאות ומרשומות יומן",
        "action-deletechangetags": "למחוק תגיות מבסיס הנתונים",
        "action-purge": "לנקות את זיכרון המטמון של דף זה",
+       "action-apihighlimits": "להשתמש בהגבלות גבוהות יותר בשאילתות API",
+       "action-autoconfirmed": "לא להיות תחת השפעת הגבלות קצב מבוססות IP",
+       "action-bigdelete": "למחוק דפים עם היסטוריות גדולות",
+       "action-blockemail": "לחסום משתמש מפני שליחת דור אלקטרוני",
+       "action-bot": "לקבל טיפול של תהליך אוטומטי",
+       "action-editprotected": "לערוך דפים שמוגנים עם \"{{int:protect-level-sysop}}\"",
+       "action-editsemiprotected": "לערוך דפים שמוגנים עם \"{{int:protect-level-autoconfirmed}}\"",
+       "action-editinterface": "לערוך את ממשק המשתמש",
+       "action-editusercss": "לערוך קובצי CSS של משתמשים אחרים",
+       "action-edituserjson": "לערוך קובצי JSON של משתמשים אחרים",
+       "action-edituserjs": "לערוך קובצי JavaSript של משתמשים אחרים",
+       "action-editsitecss": "לערוך CSS שחל על כל האתר",
+       "action-editsitejson": "לערוך JSON שחל על כל האתר",
+       "action-editsitejs": "לערוך JavaScript שחל על כל האתר",
+       "action-editmyusercss": "לערוך קובצי CSS של עצמך",
+       "action-editmyuserjson": "לערוך קובצי JSON של עצמך",
+       "action-editmyuserjs": "לערוך קובצי JavaScript של עצמך",
+       "action-viewsuppressed": "להציג גרסאות שמוסתרות מכל משתמש",
+       "action-hideuser": "לחסום שם משתמש, ולהסתיר אותו מהציבור",
+       "action-ipblock-exempt": "לעקוף חסימות IP, חסימות אוטומטיות, וחסימות טווח",
+       "action-unblockself": "לשחרר את עצמך מחסימה",
+       "action-noratelimit": "לא להיות תחת השפעה של הגבלות קצב עריכה",
+       "action-reupload-own": "לדרוס קבצים קיימים שהעלית בעצמך",
+       "action-nominornewtalk": "לא לגרום לעריכות משניות לדפי שיחה לגרום לשאלת הודעות חדשות",
+       "action-markbotedits": "לסמן עריכות ששוחזרו בתור עריכות של בוט",
+       "action-patrolmarks": "להציג סימוני בדיקה של שינויים אחרונים",
+       "action-override-export-depth": "לייצא דפים כולל דפים מקושרים עד עומק של 5 רמות",
+       "action-suppressredirect": "לא ליצור הפניות מדף המקור בעת העברת דפים",
        "nchanges": "{{PLURAL:$1|שינוי אחד|$1 שינויים}}",
        "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|מאז ביקורך האחרון}}",
        "enhancedrc-history": "היסטוריה",
        "delete-confirm": "מחיקת הדף \"$1\"",
        "delete-legend": "מחיקה",
        "historywarning": "<strong>אזהרה:</strong> לדף שעומד להימחק יש היסטוריית שינויים של {{PLURAL:$1|גרסה אחת|$1 גרסאות}}:",
-       "historyaction-submit": "×\94צ×\92×\94",
+       "historyaction-submit": "×\94צ×\92ת ×\94×\92רס×\90×\95ת",
        "confirmdeletetext": "ניתן להשתמש בטופס שלהלן כדי למחוק דף יחד עם כל ההיסטוריה שלו.\nנא לאשר שזה אכן מה שהתכוונת לעשות, שהתוצאות של הפעולה הזו ידועות לך, ושהמעשה מבוצע בהתאם ל[[{{MediaWiki:Policy-url}}|נוהלי האתר]].",
        "actioncomplete": "הפעולה בוצעה",
        "actionfailed": "הפעולה נכשלה",
index 2383108..4785e11 100644 (file)
@@ -87,6 +87,7 @@
        "tog-norollbackdiff": "Izostavi razliku nakon vraćanja",
        "tog-useeditwarning": "Upozori me kad napuštam stranicu za uređivanje bez spremanja izmjena",
        "tog-prefershttps": "Uvijek koristi sigurnu vezu kod prijave",
+       "tog-showrollbackconfirmation": "Prikaži odzivnu poruku za potvrđivanje škljocaja na poveznicu za brzo uklanjanje",
        "underline-always": "Uvijek",
        "underline-never": "Nikad",
        "underline-default": "Prema postavkama preglednika",
        "help": "Pomoć",
        "help-mediawiki": "Pomoć o MediaWikiju",
        "search": "Traži",
+       "search-ignored-headings": " #<!-- ne mijenjajte ništa u ovom retku --> <pre>\n# Zaglavlja koja će biti zanemarena pri pretrazi.\n# Izmjene koje ovdje načinite stupit će na snagu čim se stranica sa zaglavljem indeksira.\n# Možete forsirati da se stranica indeksira tako što ćete izvršiti prazno uređivanje.\n# Sintaksa je sljedeća:\n#   * Sve od znaka \"#\" pa do kraja retka je komentar.\n#   * Svaki redak koji nije prazan jest točan naziv koji će biti zanemaren, s tim da se razlikuju mala i velika slova i sve ostalo.\nIzvori\nVanjske poveznice\nVidi i\n #</pre> <!-- ne mijenjajte ništa u ovom retku -->",
        "searchbutton": "Traži",
        "go": "Kreni",
        "searcharticle": "Kreni",
        "pool-timeout": "Istek vremena (''timeout'') čekajući zaključavanje",
        "pool-queuefull": "Red čekanja je pun",
        "pool-errorunknown": "Nepoznata pogrješka",
+       "pool-servererror": "Usluga brojača redaka nije dostupna ($1).",
        "poolcounter-usage-error": "Greška korištenja: $1",
        "aboutsite": "O projektu {{SITENAME}}",
        "aboutpage": "Project:O_projektu_{{SITENAME}}",
        "ns-specialprotected": "Stranice u imenskom prostoru ''{{ns:special}}'' ne mogu se uređivati.",
        "titleprotected": "Ovaj naslov je od kreiranja zaštitio suradnik [[User:$1|$1]], uz razlog: <em>$2</em>.",
        "filereadonlyerror": "Ne mogu izmijeniti datoteku \"$1\" jer je spremište \"$2\" dostupno samo za čitanje.\n\nAdministrator koji je zaključao spremište naveo je sljedeći razlog: \"$3\".",
+       "invalidtitle": "Neispravan naslov",
        "invalidtitle-knownnamespace": "Neispravan naziv imenskog prostora \"$2\" i teksta \"$3\"",
        "invalidtitle-unknownnamespace": "Neispravan naziv imenskog prostora broj $1 i teksta \"$2\"",
        "exception-nologin": "Niste prijavljeni",
        "botpasswords-existing": "Postojeće zaporke botova",
        "botpasswords-createnew": "Stvorite novu zaporku bota",
        "botpasswords-editexisting": "Uredite postojeću zaporku bota",
+       "botpasswords-label-needsreset": "(zaporku treba ponovo postaviti)",
        "botpasswords-label-appid": "Ime bota:",
        "botpasswords-label-create": "Stvori",
        "botpasswords-label-update": "Ažuriraj",
        "passwordreset-domain": "Domena:",
        "passwordreset-email": "E-mail adresa:",
        "passwordreset-emailtitle": "Pojedinosti o računu na {{SITENAME}}",
-       "passwordreset-emailtext-ip": "Netko (vjerojatno Vi, s IP adrese $1) zatražio je podsjetnik za Vaše detalje računa\nza {{SITENAME}} ($4). Sljedeći {{PLURAL:$3|račun suradnika je|računi suradnika su}}\npovezani s ovom e-mail adresom:\n\n$2\n\n{{PLURAL:$3|Ova privremena zaporka|Ove privremene zaporke}} će isteći u {{PLURAL:$5|jedan dan|$5 dana}}.\nTrebate se prijaviti i odabrati novu zaporku. Ukoliko je netko drugi napravio ovaj\nzahtjev, ili ako ste se sjetili Vaše izvorne zaporke, a više je ne želite promijeniti, \nmožete zanemariti ovu poruku i nastavite koristiti staru zaporku.",
-       "passwordreset-emailtext-user": "Suradnik $1 na {{SITENAME}} zatražio je podsjetnik o pojedinostima vašeg računa za {{SITENAME}}\n($4). Sljedeći {{PLURAL:$3|račun suradnika je|računi suradnika su}} povezani s ovom e-mail adresom:\n\n$2\n\n{{PLURAL:$3|Ova privremena zaporka|Ove privremene zaporke}} će isteći u {{PLURAL:$5|jedan dan|$5 dana}}.\nTrebate se prijaviti i odabrati novu zaporku. Ukoliko je netko drugi napravio ovaj\nzahtjev, ili ako ste se sjetili Vaše izvorne zaporke, a više je ne želite promijeniti, \nmožete zanemariti ovu poruku i nastavite koristiti staru zaporku.",
+       "passwordreset-emailtext-ip": "Netko (vjerojatno Vi, s IP adrese $1) zatražio je podsjetnik za Vaše detalje računa\nza {{SITENAME}} ($4). Sljedeći {{PLURAL:$3|račun suradnika je|računi suradnika su}}\npovezani s ovom e-mail adresom:\n\n$2\n\n{{PLURAL:$3|Ova privremena zaporka|Ove privremene zaporke}} će isteći za {{PLURAL:$5|jedan dan|$5 dana}}.\nTrebate se prijaviti i odabrati novu zaporku. Ukoliko je netko drugi napravio ovaj\nzahtjev, ili ako ste se sjetili Vaše izvorne zaporke, a više je ne želite promijeniti, \nmožete zanemariti ovu poruku i nastavite koristiti staru zaporku.",
+       "passwordreset-emailtext-user": "Suradnik $1 na {{SITENAME}} zatražio je podsjetnik o pojedinostima vašeg računa za {{SITENAME}}\n($4). Sljedeći {{PLURAL:$3|račun suradnika je|računi suradnika su}} povezani s ovom e-mail adresom:\n\n$2\n\n{{PLURAL:$3|Ova privremena zaporka|Ove privremene zaporke}} će isteći za {{PLURAL:$5|jedan dan|$5 dana}}.\nTrebate se prijaviti i odabrati novu zaporku. Ukoliko je netko drugi napravio ovaj\nzahtjev, ili ako ste se sjetili Vaše izvorne zaporke, a više je ne želite promijeniti, \nmožete zanemariti ovu poruku i nastavite koristiti staru zaporku.",
        "passwordreset-emailelement": "Suradničko ime: \n$1\n\nPrivremena zaporka: \n$2",
        "passwordreset-emailsentemail": "Ako je ova adresa povezana s Vašim suradničkim računom, na nju će biti poslan podsjetnik na zaporku.",
        "passwordreset-emailsentusername": "Ukoliko je ova adresa povezana s Vašim suradničkim računom, na istu će biti poslan podsjetnik sa zaporkom.",
        "page_first": "prva",
        "page_last": "zadnja",
        "histlegend": "Izbor za usporedbu: označi kružiće pokraj dvije inačice koje želiš usporediti i pritisni \"Enter\" ili tipku na dnu.<br />\nUputa: <strong>({{int:cur}})</strong> = razlika od trenutačne inačice, <strong>({{int:last}})</strong> = razlika od prethodne inačice, <strong>{{int:minoreditletter}}</strong> = manja promjena.",
-       "history-fieldset-title": "Pretraži izmjene",
+       "history-fieldset-title": "Filtriranje inačica",
        "history-show-deleted": "Samo izbrisane izmjene",
        "histfirst": "najstarije",
        "histlast": "najnovije",
        "historysize": "({{PLURAL:$1|$1 bajt|$1 bajta|$1 bajtova}})",
-       "historyempty": "(prazna stranica)",
+       "historyempty": "prazno",
        "history-feed-title": "Povijest promjena",
        "history-feed-description": "Povijest promjena ove stranice na wikiju",
        "history-feed-item-nocomment": "$1 u (test) $2",
        "recentchangescount": "Zadani broj izmjena koje se prikazuju u nedavnim promjenama, povijesti stranica i u evidencijama:",
        "prefs-help-recentchangescount": "Najveći broj: 1000",
        "prefs-help-watchlist-token2": "Ovo je tajni ključ prema sažetku Vašeg popisa praćenja. \nSvaki suradnik kojem je poznat, moći će čitati Vaš popis praćenih stranica. Ne dijelite ga ni s kim.\nAko je potrebno možete ga [[Special:ResetTokens|ponovo postaviti]].",
+       "prefs-help-tokenmanagement": "Možete vidjeti te ponovo zadati tajni ključ za svoj račun kojim možete pristupiti svemrežnom izvodu Vašega popisa praćenja. Svatko tko zna ključ moći će čitati Vaš popis praćenja, stoga ga nemojte dijeliti.",
        "savedprefs": "Vaše postavke su sačuvane.",
        "savedrights": "Suradnička su prava {{GENDER:$1|suradnika $1|suradnice $1}} spremljena.",
        "timezonelegend": "Vremenska zona:",
        "delete-confirm": "Pobriši »$1«",
        "delete-legend": "Izbriši",
        "historywarning": "<strong>Upozorenje:</strong> stranica koju želite izbrisati ima starije izmjene s $1 {{PLURAL:$1|inačicom|inačice|inačica}}:",
-       "historyaction-submit": "Prikaži",
+       "historyaction-submit": "Prikaži inačice",
        "confirmdeletetext": "Zauvijek ćete izbrisati stranicu ili sliku zajedno s prijašnjim inačicama.\nMolim potvrdite svoju namjeru, da razumijete posljedice i da ovo radite u skladu s [[{{MediaWiki:Policy-url}}|pravilima]].",
        "actioncomplete": "Radnja je dovršena",
        "actionfailed": "Radnja nije uspjela",
        "deleting-backlinks-warning": "<strong>Upozorenje:</strong> \nbrišete stranicu koja je uključena u [[Special:WhatLinksHere/{{FULLPAGENAME}}|druge]] ili druge stranice povezuju na nju.",
        "deleting-subpages-warning": "<strong>Upozorenje:</strong> Stranica koju ste nakanili izbrisati ima [[Special:PrefixIndex/{{FULLPAGENAME}}/|{{PLURAL:$1|$1 podstranicu|$1 podstranice|$1 podstranica|51=preko 50 podstranica}}]].",
        "rollback": "Ukloni posljednju promjenu",
+       "rollback-confirmation-confirm": "Molimo Vas, potvrdite:",
+       "rollback-confirmation-yes": "ukloni",
+       "rollback-confirmation-no": "otkaži",
        "rollbacklink": "ukloni",
        "rollbacklinkcount": "ukloni $1 {{PLURAL:$1|uređivanje|uređivanja}}",
        "rollbacklinkcount-morethan": "ukloni više od $1 {{PLURAL:$1|uređivanje|uređivanja}}",
        "mycontris": "Doprinosi",
        "anoncontribs": "Doprinosi",
        "contribsub2": "Za {{GENDER:$3|$1}} ($2)",
+       "contributions-subtitle": "Za {{GENDER:$3|$1}}",
        "contributions-userdoesnotexist": "Suradnički račun \"$1\" nije registriran.",
+       "negative-namespace-not-supported": "Imenski prostori s negativnim vrijednostima nisu podržani.",
        "nocontribs": "Nema promjena koje udovoljavaju ovim kriterijima.",
        "uctop": "vrh",
        "month": "Od mjeseca (i ranije):",
        "pagedata-bad-title": "Naslov nije valjan: $1.",
        "passwordpolicies": "Pravila za zaporke",
        "passwordpolicies-summary": "Ovo je popis pravila za zaporke za suradničke grupe definirane na ovom wikiju.",
+       "passwordpolicies-group": "Skupina",
+       "passwordpolicies-policies": "Pravila",
        "passwordpolicies-policy-minimalpasswordlength": "Zaporka mora sadržavati najmanje {{PLURAL:$1|1 znak|$1 znaka|$1 znakova}}",
        "passwordpolicies-policy-minimumpasswordlengthtologin": "Da biste se prijavili, zaporka mora sadržavati najmanje {{PLURAL:$1|1 znak|$1 znaka|$1 znakova}}",
        "passwordpolicies-policy-passwordcannotmatchusername": "Zaporka ne može biti ista kao i suradničko ime",
index 4c10d9c..882fa1d 100644 (file)
@@ -33,7 +33,8 @@
                        "23artashes",
                        "Fitoschido",
                        "Սահակ",
-                       "ديفيد"
+                       "ديفيد",
+                       "Azniv Stepanian"
                ]
        },
        "tog-underline": "ընդգծել հղումները՝",
        "createacct-another-submit": "Ստեղծել հաշիվ",
        "createacct-continue-submit": "Շարունակել ստեղծել հաշիվ",
        "createacct-another-continue-submit": "Շարունակել ստեղծել հաշիվ",
-       "createacct-benefit-heading": "{{SITENAME}}՝ ստեղծվում է ձեր պես մարդկանց կողմից։",
+       "createacct-benefit-heading": "{{SITENAME}}՝ ստեղծվում է ձեզ պես մարդկանց կողմից։",
        "createacct-benefit-body1": "{{PLURAL:$1|խմբագրում}}",
        "createacct-benefit-body2": "{{PLURAL:$1|էջ}}",
        "createacct-benefit-body3": "վերջերս ակտիվ {{PLURAL:$1|մասնակից}}",
        "search-result-category-size": "{{PLURAL:$1|անդամ}} ({{PLURAL:$2|ենթակատեգորիա}}, {{PLURAL:$3|նիշք}})",
        "search-redirect": "(վերահղում $1 էջից)",
        "search-section": "(բաժին $1)",
+       "search-category": "(կատեգորիա $1)",
        "search-file-match": "(համապատասխանում է նիշքի բովանդակությանը)",
        "search-suggest": "Գուցե նկատի ունե՞ք՝ $1",
        "search-interwiki-caption": "Կից նախագծեր",
        "upload-dialog-button-upload": "Բեռնել",
        "upload-form-label-infoform-title": "Մանրամասներ",
        "upload-form-label-infoform-name": "Անուն",
+       "upload-form-label-infoform-description": "Նկարագրություն",
        "upload-form-label-usage-filename": "Նիշքի անուն",
        "upload-form-label-own-work": "Սա իմ անձնական աշխատանքն է",
        "upload-form-label-infoform-categories": "Կատեգորիաներ",
        "listgrouprights-rights": "Իրավունքներ",
        "listgrouprights-members": "(անդամների ցանկ)",
        "listgrouprights-addgroup": "Ավելացնեել {{PLURAL:$2|խումբ|խմբեր}}՝  $1",
+       "listgrouprights-namespaceprotection-namespace": "Անվանատարածք",
        "listgrants-rights": "Իրավունքներ",
        "mailnologin": "Ուղարկման հասցե չկա",
        "mailnologintext": "Անհրաժեշտ է [[Special:UserLogin|մտնել համակարգ]] և ունենալ գործող էլ-փոստի հասցե ձեր [[Special:Preferences|նախընտրություններում]]՝ ուրիշ մասնակիցներին էլեկտրոնային նամակներ ուղարկելու համար։",
        "tooltip-invert": "Նշե՛ք տվյալ անվանատարածքի և կից անվանատարածքների (եթե նշված է) էջերի փոփոխությունները թաքցնելու համար։",
        "namespace_association": "Կից անվանատարածք",
        "tooltip-namespace_association": "Նշեք տվյալ անվանատարածքի հետ կապված քննարկումների անվանատարածքը նույնպես ներառելու համար։",
-       "blanknamespace": "(Գլխավոր)",
+       "blanknamespace": "(Հիմնական)",
        "contributions": "{{GENDER:$1|Մասնակցի}} ներդրում",
        "contributions-title": "$1 մասնակցի ներդրումը",
        "mycontris": "Ներդրում",
        "movepage-page-unmoved": "$1 էջը հնարավոր չէր վերանվանել $2",
        "movelogpage": "Տեղափոխման տեղեկամատյան",
        "movelogpagetext": "Ստորև բերված է վերանվանված էջերի ցանկը։",
+       "movesubpage": "{{PLURAL:$1|Ենթաէջ|Ենթաէջեր}}",
        "movenosubpage": "Այս էջը ենթաէջեր չունի",
        "movereason": "Պատճառ.",
        "revertmove": "հետ շրջել",
        "minutes": "{{PLURAL:$1|$1 րոպե|$1 րոպե}}",
        "hours": "{{PLURAL:$1|$1 ժամ|$1 ժամ}}",
        "days": "{{PLURAL:$1|$1 օր|$1 օր}}",
+       "weeks": "{{PLURAL:$1|$1 շաբաթ|$1 շաբաթներ}}",
+       "months": "{{PLURAL:$1|$1 ամիս|$1 ամիսներ}}",
+       "years": "{{PLURAL:$1|$1 տարի|$1 տարիներ}}",
        "ago": "$1 առաջ",
        "bad_image_list": "Գրաձևը հետևյալն է.\n\nՀաշվի են առնվելու միայն ցանկի տարրերը (* սիմվոլով սկսվող տողերը)։\nՏողի առաջին հղումը պետք է լինի դեպի արգելված պատկերը։\nՏողի հետագա հղումները ընկալվելու են որպես բացառություններ, այսինքն էջեր, որտեղ նշված պատկերի փակցնումը չի արգելվում։",
        "metadata": "Մետատվյալներ",
        "htmlform-title-not-exists": "$1՝ գոյություն չունի",
        "logentry-delete-delete": "$1 {{GENDER:$2|ջնջեց}} $3 էջը",
        "logentry-delete-restore": "$1 վերականգնեց $3 ($4) էջը",
+       "restore-count-files": "{{PLURAL:$1|1 նիշք|$1 նիշքեր}}",
        "logentry-delete-event": "$1 փոխեց տեղեկամատյանի {{PLURAL:$5|1 գրանցման|$5 գրանցումների}} տեսանելությունը $3-ում. $4",
        "logentry-delete-revision": "$1 {{GENDER:$2|փոխեց}} {{PLURAL:$5|1 խմբագրման|$5 խմբագրումների}} տեսանելիությունը $3 էջում․ $4",
        "logentry-delete-event-legacy": "$1 փոխեց տեղեկամատյանի գրանցումների տեսանելությունը $3-ում",
        "expandtemplates": "Կաղապարների ընդարձակում",
        "expand_templates_ok": "Լավ",
        "pagelang-name": "Էջ",
+       "pagelang-language": "Լեզու",
        "pagelang-select-lang": "Ընտրեք լեզուն",
        "pagelang-reason": "Պատճառ",
        "pagelang-submit": "Հաստատել",
        "pagelang-nonexistent-page": "$1 էջը գոյություն չունի",
+       "mediastatistics-nbytes": "{{PLURAL:$1|$1 բայթ|$1 բայթեր}} ($2; $3%)",
        "mediastatistics-header-total": "Բոլոր նիշքեր",
        "special-characters-group-latin": "Լատիներեն",
        "special-characters-group-latinextended": "Լատիներեն ընդլայնված",
index b0fb634..17a3470 100644 (file)
@@ -9,6 +9,15 @@
                        "Kareyac"
                ]
        },
+       "tog-underline": "Ընդգծել յղումները․",
+       "tog-hideminor": "Թաքցնել չնչին խմբագրումները վերջին փոփոխութիւններու ցանկէն",
+       "tog-extendwatchlist": "Տարածել հսկացանկը՝ ցոյց տալով բոլոր փոփոխութիւնները, ոչ միայն վերջինները",
+       "tog-numberheadings": "Ինքնահամարակալել վերնագիրները",
+       "tog-editondblclick": "Խմբագրել էջերը կրկնակի սեղմամբ",
+       "tog-shownumberswatching": "Ցոյց տալ հսկող մասնակիցներուն թիւը",
+       "tog-watchlisthideown": "Թաքցնել իմ խմբագրումները հսկացանկէն",
+       "tog-watchlisthidebots": "Թաքցնել իմ խմբագրումները հսկացանկէն",
+       "tog-watchlisthideminor": "Թաքցնել իմ խմբագրումները հսկացանկէն",
        "underline-always": "Միշտ",
        "underline-never": "Երբեք",
        "editfont-serif": "Սերիֆ տառատեսակ",
        "views": "Տեսնուած",
        "toolbox": "Գործիքներ",
        "tool-link-userrights": "Փոխել {{GENDER:$1|գործածող}} խումբեր",
-       "tool-link-userrights-readonly": "Õ\8fÕ¥Õ½Õ¶Õ¡լ {{GENDER:$1|գործածող}} խումբեր",
+       "tool-link-userrights-readonly": "Õ\8fÕ¥Õ½Õ¶Õ¥լ {{GENDER:$1|գործածող}} խումբեր",
        "tool-link-emailuser": "Ղրկել ասիկա էլ-նամակով {{GENDER:$1|գործածողին}}",
        "imagepage": "Դիտել նիշքի էջը",
        "mediawikipage": "Դիտել հաղորդագրութեան էջը",
        "helppage-top-gethelp": "Օգնութիւն",
        "mainpage": "Գլխաւոր Էջ",
        "mainpage-description": "Գլխաւոր Էջ",
+       "policy-url": "Project:Քաղաքականութիւն",
        "portal": "Համայնքային դարպաս",
        "portal-url": "Project:Համայնքային դարպաս",
        "privacy": "Սեփական տուեալներու պահպանման քաղաքականութիւն",
        "badaccess-group0": "Արտունութիւն չունիք այս գործողութիւնը կատարել:",
        "badaccess-groups": "Տուեալ գործողութիւնը միայն $1 {{PLURAL:$2|խումբի|խումբերի}} մասնակիցները կ՛րնան կատարել։",
        "ok": "Լաւ",
+       "pagetitle": "Միացէ՛ք {{SITENAME}} նախագիծին",
        "retrievedfrom": "Վերցուած է «$1» էջէն",
        "youhavenewmessages": "{{PLURAL:$3|Դուք ունիք}} $1 ($2)։",
        "youhavenewmessagesfromusers": "{{PLURAL:$4|Դուք ունիք}} $1 {{PLURAL:$3|այլ մասնակից|$3 մասնակիցէն}} ($2):",
        "nosuchaction": "Այս գործողութիւնը չկայ",
        "nosuchspecialpage": "Այդպիսի յատուկ էջ չկայ",
        "nospecialpagetext": "<strong> Ձեր խնդրած յատուկ էջը անվաւեր է։</strong>\n\nՎաւերական յատուկ էջերու ցանկը կը գտնէք այստեղ՝ [[Special:SpecialPages|{{int:յատուկէջեր}}]].",
+       "error": "Սխալ",
        "databaseerror-query": "Հարցակէտ. $1",
        "databaseerror-function": "Գործառնութիւն. $1",
        "databaseerror-error": "Սխալ. $1",
        "viewsource": "Տեսնել աղբիւրը",
        "viewsource-title": "Տեսնել $1 էջի աղբիւրը",
        "viewsourcetext": "Կրնաք այս էջին աղբիւրը տեսնել ու ընդօրինակել։",
-       "yourname": "Գործածողի անուն.",
-       "userlogin-yourname": "Մասնակցային անուն",
-       "userlogin-yourname-ph": "Մուտքագրեցէ'ք ձեր մասնակցային անունը",
-       "createacct-another-username-ph": "Մուտքագրէք գործածողի անունը",
-       "yourpassword": "Գաղտնաբառ.",
+       "invalidtitle": "Անվաւեր վերնագիր",
+       "exception-nologin": "Դուք մուտք չէք գործած համակարգ",
+       "cannotlogoutnow-title": "Այժմ դուք չէք կրնար դուրս գալ",
+       "welcomeuser": "Բարի գալո՜ւստ, $1",
+       "welcomecreation-msg": "Ձեր հաշիւը ստեղծուած է։\nՉմոռնաք փոփոխել ձեր [[Special:Preferences|նախընտրութիւնները]]։",
+       "yourname": "Մասնակիցի անուն",
+       "userlogin-yourname": "Մասնակիցի անուն",
+       "userlogin-yourname-ph": "Մուտքագրեցէ'ք ձեր մասնակիցի անունը",
+       "createacct-another-username-ph": "Մուտքագրեցէ՛ք ձեր մասնակիցի անունը",
+       "yourpassword": "Անցաբառ՝",
        "userlogin-yourpassword": "Անցաբառ",
        "userlogin-yourpassword-ph": "Մուտքագրեցէ'ք ձեր անցաբառը",
-       "createacct-yourpassword-ph": "Մուտքագրեցէ'ք անցաբառ մը",
-       "yourpasswordagain": "Նորէն մուտքագրէք գաղտնաբառը",
-       "createacct-yourpasswordagain": "Հաստատեցէ'ք անցաբառը",
-       "createacct-yourpasswordagain-ph": "Դարձեալ մուտքագրեցէ'ք անցաբառը",
+       "createacct-yourpassword-ph": "Մուտքագրեցէ՛ք անցաբառ մը",
+       "yourpasswordagain": "Դարձեալ մուտքագրեցէ՛ք անցաբառը",
+       "createacct-yourpasswordagain": "Հաստատեցէ՛ք անցաբառը",
+       "createacct-yourpasswordagain-ph": "Դարձեալ մուտքագրեցէ՛ք անցաբառը",
        "userlogin-remembermypassword": "Մնալ համակարգին մէջ",
+       "cannotloginnow-title": "Այժմ դուք չէք կրնար մուտք գործել",
+       "cannotcreateaccount-title": "Չէք կրնար հաշիւներ ստեղծել",
+       "password-change-forbidden": "Այս Ուիքիին մէջ դուք չէք կրնար անցաբառեր փոխել։",
        "login": "Մուտք գործել",
+       "login-security": "Հաստատեցէէ՛ք Ձեր ինքնութիւնը",
+       "nav-login-createaccount": "Մուտք գործել/Հաշիւ ստեղծել",
+       "logout": "Դուրս գալ",
+       "userlogout": "Դուրս գալ",
+       "notloggedin": "Դուք մուտք չէք գործած համակարգ",
        "userlogin-noaccount": "Հաշիւ չունի՞ք։",
        "userlogin-joinproject": "Միացէ՛ք {{SITENAME}} նախագիծին",
        "createaccount": "Հաշիւ ստեղծել",
        "userlogin-resetpassword-link": "Անցաբառը մոռցա՞ծ էք",
        "userlogin-helplink2": "Մուտք գործելու օգնութիւն",
-       "createacct-emailoptional": "Իմակահասցէ (ոչ պարտադիր)",
-       "createacct-email-ph": "Մուտքագրեցէ՛ք ձեր իմակահասցէն",
+       "userlogin-createanother": "Այլ հաշիւ ստեղծել",
+       "createacct-emailrequired": "Ելեկտրոնային հասցէ",
+       "createacct-emailoptional": "Ելեկտրոնային հասցէ (ոչ պարտադիր)",
+       "createacct-email-ph": "Մուտքագրեցէ՛ք ձեր ելեկտրոնային հասցէն",
+       "createacct-another-email-ph": "Մուտքագրեցէ՛ք ձեր ելեկտրոնային հասցէն",
+       "createacct-realname": "Իրական անուն (ոչ պարտադիր)",
        "createacct-reason": "Պատճառ",
        "createacct-submit": "Ստեղծել ձեր հաշիւը",
+       "createacct-another-submit": "Հաշիւ ստեղծել",
+       "createacct-continue-submit": "Շարունակեցէ՛ք հաշիւի ստեղծումը",
+       "createacct-another-continue-submit": "Շարունակեցէ՛ք հաշիւի ստեղծումը",
        "createacct-benefit-heading": "{{SITENAME}}՝ ստեղծուած է ձեզի պէս մարդոց կողմէ։",
        "createacct-benefit-body1": "{{PLURAL:$1|խմբագրում}}",
        "createacct-benefit-body2": "$1 {{PLURAL:$1|էջ}}",
        "createacct-benefit-body3": "վերջին {{PLURAL:$1|մասնակից}}",
+       "badretype": "Ձեր նշած անցաբառերը իրարու չեն համապատասխաներ։",
+       "loginerror": "Մուտքի սխալ",
+       "createacct-error": "Հաշուի ստեղծման սխալ",
        "loginsuccesstitle": "Բարեյաջող մուտք",
        "loginsuccess": "'''Դուք մուտք գործեցիք {{SITENAME}}, իբր \"$1\"։'''",
        "nouserspecified": "Հարկաւոր է նշել մասնակցին անունը։",
        "password-login-forbidden": "Այս մասնակիցի անունը եւ գաղտաբարի օգտագործումը արգիլուած է:",
        "mailmypassword": "Վերականգնել գաղտնաբառը",
        "passwordremindertitle": "Նոր ժամանակաւոր գաղտնաբառ {{grammar:genitive|{{SITENAME}}}} համար",
+       "emailconfirmlink": "Հաստատեցէ՛ք ձեր ելեկտրոնային հասցէն",
        "accountcreated": "Հաշիւը ստեղծուեցաւ:",
        "loginlanguagelabel": "Լեզու՝ $1",
        "pt-login": "Մուտք գործել",
        "pt-userlogout": "Դուրս գալ",
        "php-mail-error-unknown": "Անյայտ սխալ PHP-ի mail() կախարկութեան մէջ:",
        "changepassword": "Գաղտնաբառը փոխել",
-       "oldpassword": "Հին գաղտնաբառը.",
-       "newpassword": "Նոր գաղտնաբառը.",
+       "resetpass_header": "Փոխել հաշիւի անցաբառը",
+       "oldpassword": "Հին անցաբառը.",
+       "newpassword": "Նոր անցաբառը.",
        "retypenew": "Նորէն մուտքագրէք գաղտնաբառը",
-       "changepassword-success": "Ձեր գաղտնաբառը փոխուեցաւ։",
+       "resetpass_submit": "Հաստատեցէ՛ք անցաբառը եւ մուտք գործեցէ՛ք համակարգ",
+       "changepassword-success": "Ձեր անցաբառը փոխուեցաւ։",
+       "botpasswords-label-appid": "Մեքենայիկի անուն՝",
        "botpasswords-label-create": "Ստեղծել",
+       "botpasswords-label-update": "Թարմացնել",
        "botpasswords-label-cancel": "Չեղարկել",
        "botpasswords-label-delete": "Ջնջել",
        "botpasswords-label-resetpassword": "Վերականգնել գաղտնաբառը",
+       "botpasswords-label-grants-column": "Արտօնութիւնը տրուած է",
+       "botpasswords-bad-appid": "\"$1\" մեքենայիկին անունը չէ վաւերացուած:",
+       "botpasswords-created-title": "Մեքենայիկին անցաբառը ստեղծուեցաւ",
+       "botpasswords-deleted-title": "Մեքենայիկին անցաբառը ջնջուեցաւ",
+       "resetpass_forbidden": "Անցաբառերը չեն կրնար փոխել",
+       "resetpass-submit-loggedin": "Անցաբառը փոխել",
        "resetpass-submit-cancel": "Չեղարկել",
        "resetpass-temp-password": "Ժամանակաւոր գաղտնաբառ.",
        "passwordreset": "Վերականգնել անցաբառը",
+       "passwordreset-username": "Մասնակիցի անուն՝",
        "passwordreset-domain": "Համակարգիչի պետութիւն.",
        "passwordreset-email": "Էլ-նամակաի հասցէն.",
        "passwordreset-emailtitle": "{{SITENAME}} հաշիւի մանրամասները",
+       "passwordreset-invalidemail": "Անվաւեր ելեկտրոնային հասցէ",
+       "changeemail": "Փոխել կամ հանել ելեկտրոնային հասցէն",
+       "changeemail-oldemail": "Այժմու ելեկտրոնային հասցէ․",
+       "changeemail-newemail": "Նոր ելեկտրոնային հասցէ․",
+       "changeemail-none": "(ոչ մէկ)",
+       "changeemail-password": "Ձեր {{SITENAME}} անցաբառը՝",
+       "changeemail-submit": "Փոխել ելեկտրոնային հասցէն",
        "bold_sample": "Շեշտուած տառերով գրութիւն",
        "bold_tip": "Շեշտուած տառերով գրութիւն",
        "italic_sample": "Շեղատառ գրութիւն",
        "headline_tip": "Երկրորդ մակարդակի վերնագիր",
        "nowiki_sample": "Մուտքագրեցէ՛ք չձեւաւորուած գրութիւնը այստեղ",
        "nowiki_tip": "Անտեսել ուիքի ձեւաւորումը",
+       "image_sample": "Օրինակ.jpg",
        "image_tip": "Ներփակ նիշք",
+       "media_sample": "Օրինակ.jpg",
        "media_tip": "Նիշքին յղումը",
        "sig_tip": "Ձեր ստորագրութիւնը ժամակնիքով",
        "hr_tip": "Հորիզոնական գիծ (գործածել խնայողաբար)",
        "preview": "Կանխաստուգել",
        "showpreview": "Կանխաստուգել",
        "showdiff": "Ցուցնել փոփոխութիւնները",
-       "anoneditwarning": "<strong>Զգուշացում։</strong> Մուտք գործած չէք համակարգ։ Որեւէ խմբագրումի պարագային ձեր IP հասցէն տեսանելի կը դառնայ բոլորին։ Եթե <strong>[$1 մուտք գործէք]</strong> կամ <strong>[$2 ստեղծէք մասնակցային հաշիւ]</strong>, ձեր կատարած խմբագրումները կը կապուին ձեր մասնակցային անունին հետ, ինչպէս նաեւ կ՚ունենաք այլ առաւելութիւններ։",
+       "anoneditwarning": "<strong>Զգուշացում։</strong> Մուտք գործած չէք համակարգ։ Որեւէ խմբագրումի պարագային ձեր IP հասցէն տեսանելի կը դառնայ բոլորին։ Եթէ <strong>[$1 մուտք գործէք]</strong> կամ <strong>[$2 ստեղծէք մասնակիցի հաշիւ]</strong>, ձեր կատարած խմբագրումները կը կապուին ձեր մասնակիցի անունին հետ, ինչպէս նաեւ կ՚ունենաք այլ առաւելութիւններ։",
+       "blockedtitle": "Մասնակիցը արգելափակուած է",
        "blockedtext": "<strong>Ձեր մասնակցային անոիւնը կամ IP հասցէն արգելակուած է։</strong>\n\nԱրգելակումը կատարուած է $1ի կողմէ.\nՊարտճառը՝ <em>$2</em>.\n\n* Արգելակման սկիբժ՝ $8\n* Արգելակման աւարտ՝ $6\n* արգելակուած առարկայ՝ $7\n\nԿրնաք կապուիլ $1ի կամ այլ անդատներու հետ [[{{MediaWiki:Grouppage-sysop}}|վարիչ]] արգելակման մասին զրուցելու համար.\nՉէք կրնար օգտագործել \"{{int:emailuser}}\" հնարաւորութիւնը բացի եթէ նշած էք իմակի վաւերական հասցէ մը ձեր [[Special:Preferences|account preferences]] եւ արգելակուած չէ վեր անոր օգտագործումը.\nՁեր ընթացիկ IP հասցէն է $3, եւ արգելակման ինքնութեան համարն է #$5.\nԿը շնդրենք որ այս մանրամասնութիւնները նշէք ձեր բոլոր թղթակցութիւններուն մէջ։",
-       "loginreqlink": "մուտք գործել",
+       "loginreqtitle": "Կը խնդրուի մուտք գործել համակարգ",
+       "loginreqlink": "Մուտք գործել",
+       "newarticle": "(Նոր)",
        "newarticletext": "Դուք յղուած էք տակաւին գոյութիւն չունեցող էջի մը։\nԷջը ստեղծելու համար, մեքենագրեցէք ներքեւի տուփիկին մէջ (յաւելեալ տեղեկութեանց համար տե՛ս [$1 օգնութեան ցուցմունքներու էջը])։\nԵթէ սխալմամբ հոս հասած էք, սեղմել դիտարկիչի <strong>ետ</strong> կոճակը։",
        "anontalkpagetext": "<em> Այս էջը առայժմ հաշիւ չստեղծած, կամ հաշիւ չօգտագործող, անանուն մասնակիցներու քննարկման էջն է։</em>\nՈւրեմն որպէս ինքնութիւն ստիպուած ենք օգտագործել անոնց IP հասցէն։\nԱյսպիսի IP հասցէ կրնան ունենալ մէկէ աւելի մասնակիցներ։\nԵթէ դուք անանուն մասնակից էք եւ կը խորհիք որ անկապ դիտողութիւններու թիրախ դարձած էք, կը խնդրուի [[Special:CreateAccount|Հաշիւ ստեղծել]] կամ [[Special:UserLogin|մուտք գործել]] խուսափելու համար ապագային այլ անդանուն մասնակիցներու հետ շփոթուելու հնարաւորութենէն։",
        "noarticletext": "Ներկայիս այս էջին վրայ որեւէ գրութիւն չկայ։\nԴուք կրնաք [[Special:Search/{{PAGENAME}}|որոնել այս անուանումը]] այլ էջերու մէջ, <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} որոնել համապատասխան տեղեկամատեանները] կամ [{{fullurl:{{FULLPAGENAME}}|action=edit}} ստեղծել նոր էջ այս անուանումով]</span>։",
        "noarticletext-nopermission": "Ներկայիս այս էջին վրայ որեւէ գրութիւն չկայ։\nԿրնաք [[Special:Search/{{PAGENAME}}|այս վերնագիրով էջը որոնել]] այլ էջերու մէջ, կամ <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} որոնել համապատասխան տեղեկատետրերը], սակայն իրաւունք չունիք այս էջը ստեղծելու։",
        "userpage-userdoesnotexist-view": "«$1» անունով գրանցուած մասնակից չկայ։",
        "clearyourcache": "<strong>Նշում՝</strong> Պահելէ ետք կրնայ ըլլալ որ պէտք ունենաք մաքրելու դիտարկիչին պաշարը (cache) փոփոխութիւնները կարենալ տեսնելու համար։\n* <strong>Firefox / Safari:</strong> Սեղմած պահել <em>Shiftը</em> մինչ կը սեղմէք  <em>Reload</em>, կամ ալ սեղմել <em>Ctrl-F5</em> կամ <em>Ctrl-R</em> (<em>⌘-R</em> Macի վրայ)\n* <strong>Google Chrome:</strong> Սեղմել <em>Ctrl-Shift-R</em> (<em>⌘-Shift-R</em> Macի վրայ)\n* <strong>Internet Explorer:</strong> Սեղմած պահել <em>Ctrl</em> մինչ կը սեղմէք  <em>Refresh</em>, կամ ալ սեղմել <em>Ctrl-F5</em>\n* <strong>Opera:</strong> Երթալ <em>Menu → Settings</em> (<em>Opera → Preferences</em> Macի վրայ) եւ ապա <em>Privacy & security → մաքրել թերթատման տեղեկութիւնները → Պաշարի մէջ դրուած նկարներ եւ նիշքեր</em>.",
+       "updated": "(Թարմացուած)",
        "previewnote": "<strong>Յիշել որ ասիկա միայն կանխաստուգում է. ձեր կատարած փոփոխութիւնները տակաւին չեն պահուած։<strong>",
        "continue-editing": "Շարունակել խմբագրել",
        "editing": "Կը խմբագրուի՝ $1 էջը",
        "creating": "«$1» էջի ստեղծում",
        "editingsection": "$1 բաժինի խմբագրում",
+       "yourtext": "Ձեր գրութիւնը",
+       "storedversion": "Պահուած տարբերակ",
        "yourdiff": "Տարբերութիւններ",
        "templatesused": "Այս էջին մէջ օգտագործուած {{PLURAL:$1|կաղապարը|կաղապարները}}.",
        "templatesusedpreview": "{{PLURAL:$1|Կաղապար}} օգտագործուած այս կանխաստուգումին մէջ՝",
        "recreate-moveddeleted-warn": "<strong>Զգուշացում. Նախապէս ջնջուած էջ մը պիտի վերստեղծուի։<strong>\n\nԿը խնդրուի մտածել այս էջի խմբագրման նպատակայարմարութեան մասին։ \nՁեր դիւրութեան համար ներքեւ կը գտնէք այս էջի ջնջումին և տեղափոխումին տեղեկատետրերը։",
        "moveddeleted-notice": "Այս էջը ջնջուած է։\nԷջին ջնջումի, պահպանումի եւ փոխադրումի տեղեկատետրը տրամադրելի է ներքեւ որպէս տեղեկութիւն։",
        "edit-conflict": "Խմբագրման ընհարում։",
+       "postedit-confirmation-created": "Էջը ստեղծուած է։",
+       "postedit-confirmation-saved": "Ձեր խմբագրումը պահուած է:",
+       "postedit-confirmation-published": "Ձեր խմբագրումը հրատարակուած է:",
        "slot-name-main": "Գլխաւոր",
        "content-model-wikitext": "ուիքիթէքսթ",
        "content-model-text": "պարզ բնաբան",
        "rev-delundel": "ցուցնել/թաքցնել",
        "rev-showdeleted": "Ցուցադրել",
        "revdelete-show-file-submit": "Այո",
+       "revdelete-hide-image": "Թաքցնել նիշքին բովանդակութիւնը",
+       "revdelete-radio-unset": "Տեսանելի",
        "revdelete-log": "Պատճառ.",
        "pagehist": "Էջի պատմութիւն",
        "revdelete-reasonotherlist": "Ուրիշ պատճառ.",
+       "revdelete-edit-reasonlist": "Խմբագրել ջնջման պատճառները",
        "mergehistory-reason": "Պատճառ.",
        "mergelog": "Ձուլման տեղեկատետր",
        "history-title": "«$1»ի վերանայումներու ցուցակ",
        "diff-multi-otherusers": "(նոյն մասնակիցի ցոյց չտրուած {{PLURAL:$1|մէկ միջանկեալ վերանայում|$2 միջանկեալ վերանայումներ}})",
        "searchresults": "Որոնման արդիւնքներ",
        "searchresults-title": "«$1»-ի որոնման արդիւնքները",
+       "notextmatches": "Չկան համապատասխան գրութիւնով էջեր",
        "prevn": "նախորդ {{PLURAL:$1|$1}}",
        "nextn": "յաջորդ {{PLURAL:$1|$1}}",
        "prev-page": "նախորդ էջ",
        "search-category": "(ստորոգութիւն $1)",
        "search-file-match": "(համապատասխան է նիշքի բովանդակութեան)",
        "search-suggest": "$1 Նկատի ունի՞ք",
+       "search-interwiki-default": "$1 արդիւնք.",
        "search-interwiki-more": "(աւելի)",
        "search-interwiki-more-results": "աւելի շատ արդիւնքներ",
        "search-relatedarticle": "Հարակից",
        "search-nonefound": "Որոնումին համապատասխանող արդիւնքներ չգտնուեցան",
        "powersearch-toggleall": "Բոլոր",
        "powersearch-togglenone": "Ոչ մէկ",
+       "search-external": "Արտաքին որոնում",
        "preferences": "Նախընտրութիւններ",
        "mypreferences": "Նախընտրութիւններ",
        "skin-preview": "Նախադիտել",
        "prefs-watchlist": "Հսկողութեան ցանկ",
+       "prefs-editwatchlist-clear": "Մաքրել հսկողութեան ցանկը",
        "saveprefs": "Յիշել",
        "searchresultshead": "Որոնել",
        "stub-threshold-sample-link": "օրինակ",
        "group": "Խումբ.",
        "group-bot": "Մեքենայիկներ",
        "group-sysop": "Վարիչներ",
+       "group-sysop-member": "{{GENDER:$1|վարիչ}}",
        "grouppage-bot": "{{ns:project}}:Մեքենայիկներ",
        "grouppage-sysop": "{{ns:project}}:Վարիչներ",
        "right-writeapi": "API գիրի օգտագործումը",
        "recentchanges-label-plusminus": "Էջին ծաւալը փոխուեցաւ այսքան պայթով",
        "recentchanges-legend-heading": "<strong>Ծանօթ.՝</strong>",
        "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (տե՛ս նաեւ՝  [[Special:NewPages|նոր էջերու ցանկ]])",
+       "rcfilters-activefilters": "Աշխոյժ զտիչներ",
+       "rcfilters-limit-title": "Ցուցադրուող արդիւնքներ",
        "rcnotefrom": "Ներքեւ {{PLURAL:$5|փոփոխութիւնն է|փոփոխութիւններն են}} սկսեալ <strong>$3, $4</strong> (մինչեւ <strong>$1</strong> ցոյց տրուած).",
        "rclistfrom": "Ցոյց տալ նոր փոփոխութիւնները սկսած $3 $2",
        "rcshowhideminor": "$1 չնչին խմբագրումներ",
        "upload-disallowed-here": "Այս նիշքը կարելի չէ ջնջել ու փոխարինել։",
        "randompage": "Պատահական էջ",
        "statistics": "Վիճակագրութիւն",
+       "statistics-header-pages": "Էջերու վիճակագրութիւն",
+       "statistics-header-edits": "Խմբագրումներու վիճակագրութիւն",
+       "statistics-header-users": "Մասնակիցներու վիճակագրութիւն",
+       "statistics-pages-desc": "Ուիքիի բոլոր էջերը՝ ներառեալ քննարկման էջերը, վերայղումները եւ այլն",
+       "statistics-files": "Բեռնուած նիշքեր",
+       "statistics-edits-average": "Իւրաքանչիւր էջի խմբագրումներուն միջին թիւը",
+       "statistics-users-active": "Աշխոյժ մասնակիցներ",
        "double-redirect-fixer": "Վերայղումներու շտկիչ",
        "nbytes": "$1 {{PLURAL:$1|պայթ}}",
        "nmembers": "$1 {{PLURAL:$1|անդամ|անդամներ}}",
        "move": "Տեղափոխել այս էջը",
        "pager-newer-n": "{{PLURAL:$1|նոր 1|աւելի նոր $1}}",
        "pager-older-n": "{{PLURAL:$1|աւելի հին 1|աւելի հին $1}}",
+       "apisandbox-results": "Արդիւնքներ",
        "booksources": "Գիրքի աղբիւրներ",
        "booksources-search-legend": "Որոնել գիրքի մասին",
        "booksources-search": "Որոնել",
        "allpagessubmit": "‎Յառաջանալ",
        "allpages-hide-redirects": "Թաքցնել վերայղումները",
        "categories": "Ստորոգութիւններ",
+       "activeusers": "Աշխոյժ մասնակիցներու ցանկ",
+       "activeusers-submit": "Ցոյց տալ աշխոյժ մասնակիցները",
        "listgrouprights-members": "(անդամներու ցանկ)",
        "emailuser": "էլ-նամակ ուղարկել այս մասնակիցին",
        "usermessage-editor": "Համակարգային սուրհանդակի անուն",
        "tooltip-diff": "Ցոյց տալ ձեր կատարած փոփոխութիւնները գրութեան մէջ",
        "tooltip-compareselectedversions": "Տեսնել այս էջին ընտրուած երկու տարբերակներուն միջեւ տարբերութիւնները",
        "tooltip-watch": "Աւելցնել այս էջը ձեր հսկողութեան ցանկին վրայ",
+       "tooltip-watchlistedit-raw-submit": "Թարմացնել Հսկացանկը",
        "tooltip-rollback": "«Յետարկում»ը մէկ սեղմումով վերջին մասնակիցին կատարած բոլոր խմբագրումները ետ կ՚ընէ",
        "tooltip-undo": "«Յետարկել»ը կը շրջէ կատարուած փոփոխութիւնը եւ խմբագրումը կը տրամադրէ կանխաստուգման վիճակով, թոյլ տալով նշել պատճառը ամփոփումին մէջ։",
        "tooltip-summary": "Մուտքագրեցէ՛ք հակիրճ ամփոփում մը",
        "imgmultipagenext": "յաջորդ էջը →",
        "imgmultigo": "Անցնի՛լ",
        "imgmultigoto": "Անցնիլ $1 էջը",
+       "watchlistedit-raw-submit": "Թարմացնել Հսկացանկը",
+       "watchlistedit-clear-title": "Մաքրել հսկողութեան ցանկը",
+       "watchlistedit-clear-legend": "Մաքրել հսկողութեան ցանկը",
        "watchlisttools-clear": "Մաքրել հսկողութեան ցանկը",
        "watchlisttools-view": "Ցուցադրել փոփոխութիւնները",
        "watchlisttools-edit": "Տեսնել եւ խմբագրել հսկողութեան ցանկը",
        "feedback-cancel": "Չեղարկել",
        "searchsuggest-search": "Որոնել {{SITENAME}} կայքին մէջ",
        "duration-days": "$1 {{PLURAL:$1|օր}}",
+       "special-characters-group-latin": "Լատիներէն",
+       "special-characters-group-arabic": "Արաբերէն",
        "randomrootpage": "Պատահական արմատ էջ"
 }
index 15d49ef..e748e28 100644 (file)
        "page_first": "prime",
        "page_last": "ultime",
        "histlegend": "Pro comparar duo versiones: marca lor circulos correspondente, e preme <code>Enter</code> o clicca le button in basso.<br />\nLegenda: '''({{int:cur}})''' = comparar con le version actual,\n'''({{int:last}})''' = comparar con le version precedente, '''{{int:minoreditletter}}''' = modification minor.",
-       "history-fieldset-title": "Cercar versiones",
+       "history-fieldset-title": "Filtrar versiones",
        "history-show-deleted": "Solmente versiones delite",
        "histfirst": "le plus ancian",
        "histlast": "le plus nove",
        "delete-confirm": "Deler \"$1\"",
        "delete-legend": "Deler",
        "historywarning": "<strong>Attention:</strong> Le pagina que tu vole deler ha un historia de circa $1 {{PLURAL:$1|version|versiones}}:",
-       "historyaction-submit": "Monstrar",
+       "historyaction-submit": "Monstrar versiones",
        "confirmdeletetext": "Tu va deler un pagina con tote su historia.\nPer favor confirma que tu ha le intention de facer isto, que tu comprende le consequentias, e que tu face isto in accordo con [[{{MediaWiki:Policy-url}}|le politicas]].",
        "actioncomplete": "Action complete",
        "actionfailed": "Action fallite",
index 817d403..95b8012 100644 (file)
        "deleting-backlinks-warning": "<strong>Peringatan:</strong> [[Special:WhatLinksHere/{{FULLPAGENAME}}|Halaman lain]] mengarah atau memiliki transklusi ke halaman yang akan Anda hapus.",
        "deleting-subpages-warning": "<strong>Peringatan:</strong> Halaman yang akan Anda hapus memiliki [[Special:PrefixIndex/{{FULLPAGENAME}}/|{{PLURAL:$1|subhalaman|$1 subhalaman|51=lebih dari 50 subhalaman}}]].",
        "rollback": "Kembalikan suntingan",
+       "rollback-confirmation-confirm": "Silakan konfirmasi:",
+       "rollback-confirmation-yes": "Kembalikan",
+       "rollback-confirmation-no": "Batal",
        "rollbacklink": "kembalikan",
        "rollbacklinkcount": "kembalikan $1 {{PLURAL:$1|suntingan}}",
        "rollbacklinkcount-morethan": "kembalikan lebih dari $1 {{PLURAL:$1|suntingan|suntingan}}",
index bf65f50..623c04a 100644 (file)
        "blocklog-showlog": "Ica uzero ja blokusesis antee.\nInfre esas l'informo pri la blokuso, por vua konoco:",
        "blocklogentry": "blokusis [[$1]] dum periodo di $2 $3",
        "reblock-logentry": "modifikis la tempo di blokuso [[$1]] por durado di $2 $3",
+       "blocklogtext": "Ca esas protokolo pri agadi di blokuso o desblokuso.\nL'adresi IP automatale blokusata ne montresas che la listo.\nVidez la [[Special:BlockList|listo pri blokuso]] por la nuna listo pri blokusi e proskriptadi.",
        "unblocklogentry": "desblokusis \"$1\"",
        "block-log-flags-anononly": "nur anonima uzeri",
        "block-log-flags-nocreate": "ne povas krear konto",
        "delete_and_move_reason": "Efacita por permisar la chanjo di la nomo di la pagino \"[[$1]]\"",
        "move-leave-redirect": "Mantenez ridirektilo inter la du",
        "export": "Exportacar pagini",
+       "exporttext": "Vu povas exportacar la texto e historio pri redakto de ula pagino o grupo di pagini envelopata en ula XML.\nCa povas importacar por altra wiki tra l'uzo di MediaWiki, per la [[Special:Import|pagino di importaco]].\n\nPor exportar pagini, mencionez la titulo en la texto-buxo adinfre, un titulo per singla lineo, e selektez se vu deziras vidar la nuna ed anke l'antea redakturi (singla antea revizo en singla lineo), o nur vidar la nuna revizo e l'informi pri ol.\n\nIn the latter case you can also use a link, for example [[{{#Special:Export}}/{{MediaWiki:Mainpage}}]] for the page \"[[{{MediaWiki:Mainpage}}]]\".",
        "exportcuronly": "On inkluzas nur la nuna revizo, ne la kompleta versionaro",
        "export-addcattext": "Adjuntar pagini ek kategorio:",
        "export-addcat": "Adjuntar",
        "tooltip-n-recentchanges": "Listo di recenta chanji en la wiki.",
        "tooltip-n-randompage": "Vizitez hazarda pagino",
        "tooltip-n-help": "La loko por trovar ulo.",
-       "tooltip-t-whatlinkshere": "Listo di omna Wikipagini qui ligesas adhike",
+       "tooltip-t-whatlinkshere": "Listo pri omna Wikipagini qui ligesas adhike",
        "tooltip-t-recentchangeslinked": "Recenta chanji di pagini ligita de ca pagino",
        "tooltip-feed-rss": "RSS provizero por ica pagino",
        "tooltip-feed-atom": "Atom provizero por ica pagino",
index 50b9441..01dcb64 100644 (file)
        "returnto": "Torna a $1.",
        "tagline": "Da {{SITENAME}}.",
        "help": "Aiuto",
+       "help-mediawiki": "Aiuto su MediaWiki",
        "search": "Ricerca",
        "search-ignored-headings": " #<!-- lascia questa riga esattamente come è --> <pre>\n# Elenco delle intestazioni che saranno ignorate dalla ricerca.\n# Le modifiche a questa pagina saranno effettive non appena la pagina sarà indicizzata.\n# Puoi forzare la re-indicizzazione di una pagina effettuando una modifica nulla.\n# La sintassi è la seguente:\n#   * Tutto dal carattere \"#\" alla fine della riga è un commento\n#   * Tutte le righe non vuote sono le intestazioni esatte da ignorare, maiuscolo/minuscolo e tutto\nNote\nVoci correlate\nCollegamenti esterni\n #</pre> <!-- lascia questa riga esattamente come è -->",
        "searchbutton": "Ricerca",
        "botpasswords-restriction-failed": "Le restrizioni di password bot impediscono questo accesso.",
        "botpasswords-invalid-name": "Il nome utente indicato non contiene il separatore per password bot (\"$1\").",
        "botpasswords-not-exist": "L'utente \"$1\" non dispone di una password bot chiamata \"$2\".",
+       "botpasswords-needs-reset": "La password per il bot di nome \"$2\" {{GENDER:$1|dell'utente}} \"$1\" deve essere reimpostata.",
+       "botpasswords-locked": "Non puoi accedere con una password bot poiché la tua utenza è bloccata.",
        "resetpass_forbidden": "Non è possibile modificare le password",
        "resetpass_forbidden-reason": "Non è possibile modificare le password: $1",
        "resetpass-no-info": "Devi aver effettuato l'accesso per accedere a questa pagina direttamente.",
        "userpage-userdoesnotexist-view": "L'utenza \"$1\" non è registrata.",
        "blocked-notice-logextract": "Questo utente è attualmente bloccato.\nL'ultimo elemento del registro dei blocchi è riportato di seguito per informazione:",
        "clearyourcache": "<strong>Nota:</strong> dopo aver salvato, potrebbe essere necessario pulire la cache del proprio browser per vedere i cambiamenti. \n*<strong>Firefox / Safari:</strong> tenere premuto il tasto delle maiuscole <em>Shift</em> e fare clic su <em>Ricarica</em>, oppure premere <em>Ctrl-F5</em> o <em>Ctrl-R</em> (<em>⌘-R</em> su Mac)\n*<strong>Google Chrome:</strong> premere <em>Ctrl-Shift-R</em> (<em>⌘-Shift-R</em> su un Mac)\n*<strong>Internet Explorer:</strong> tenere premuto il tasto <em>Ctrl</em> e fare clic su <em>Aggiorna</em>, oppure premere <em>Ctrl-F5</em>\n* <strong>Opera:</strong> Vai nel <em>Menu → Impostazioni</em> (<em>Opera → Preferenze</em> su un Mac) e poi in <em>Privacy & sicurezza → Pulisci dati del browser → Immagini e file nella cache</em>.",
-       "usercssyoucanpreview": "'''Suggerimento:''' usa il pulsante 'Visualizza anteprima' per provare il tuo nuovo CSS prima di salvarlo.",
-       "userjsyoucanpreview": "'''Suggerimento:''' usa il pulsante 'Visualizza anteprima' per provare il tuo nuovo JavaScript prima di salvarlo.",
+       "usercssyoucanpreview": "<strong>Suggerimento:<strong> usa il pulsante \"{{int:showpreview}}\" per provare il tuo nuovo CSS prima di salvarlo.",
+       "userjsonyoucanpreview": "<strong>Suggerimento:<strong> usa il pulsante \"{{int:showpreview}}\" per provare il tuo nuovo JSON prima di salvarlo.",
+       "userjsyoucanpreview": "<strong>Suggerimento:<strong> usa il pulsante \"{{int:showpreview}}\" per provare il tuo nuovo JavaScript prima di salvarlo.",
        "usercsspreview": "'''Questa è solo un'anteprima del proprio CSS personale. Le modifiche non sono ancora state salvate!'''",
        "userjsonpreview": "<strong>Questa è solo un'anteprima per provare il proprio JSON personale; le modifiche non sono ancora state salvate!</strong>",
        "userjspreview": "'''Questa è solo un'anteprima per provare il proprio JavaScript personale; le modifiche non sono ancora state salvate!'''",
        "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",
-       "history-fieldset-title": "Ricerca per versioni",
+       "history-fieldset-title": "Filtra versioni",
        "history-show-deleted": "Solo versioni cancellate",
        "histfirst": "prima",
        "histlast": "ultima",
        "right-editusercss": "Modifica i file CSS di altri utenti",
        "right-edituserjson": "Modifica i file JSON di altri utenti",
        "right-edituserjs": "Modifica i file JS di altri utenti",
+       "right-editsitecss": "Modifica CSS del sito",
+       "right-editsitejson": "Modifica JSON del sito",
+       "right-editsitejs": "Modifica JavaScript del sito",
        "right-editmyusercss": "Modifica il file CSS del proprio utente",
        "right-editmyuserjson": "Modifica il file JSON del proprio utente",
        "right-editmyuserjs": "Modifica il file JavaScript del proprio utente",
        "rcfilters-activefilters-hide": "Nascondi",
        "rcfilters-activefilters-show": "Mostra",
        "rcfilters-activefilters-hide-tooltip": "Nascondi area dei filtri attivi",
+       "rcfilters-activefilters-show-tooltip": "Mostra area dei filtri attivi",
        "rcfilters-advancedfilters": "Filtri avanzati",
        "rcfilters-limit-title": "Risultati da mostrare",
        "rcfilters-limit-and-date-label": "$1 {{PLURAL:$1|modifica|modifiche}}, $2",
index 5e81e28..7d58f19 100644 (file)
        "action-changetags": "個々の版および記録項目への任意のタグの追加と除去",
        "action-deletechangetags": "データベースからタグの削除",
        "action-purge": "このページのキャッシュ破棄",
+       "action-apihighlimits": "API要求でのより高い制限値の使用",
+       "action-bigdelete": "大きな履歴があるページの削除",
+       "action-blockemail": "利用者のメール送信のブロック",
+       "action-editprotected": "「{{int:protect-level-sysop}}」の保護を設定されたページの編集",
+       "action-editsemiprotected": "「{{int:protect-level-autoconfirmed}}」の保護を設定されたページの編集",
+       "action-editinterface": "ユーザーインターフェースの編集",
+       "action-editusercss": "他の利用者のCSSファイルの編集",
+       "action-edituserjson": "他の利用者のJSONファイルの編集",
+       "action-edituserjs": "他の利用者のJavaScriptファイルの編集",
+       "action-editsitecss": "サイト全体のCSSの編集",
+       "action-editsitejson": "サイト全体のJSONの編集",
+       "action-editsitejs": "サイト全体のJavaScriptの編集",
+       "action-editmyusercss": "自分のCSSファイルの編集",
+       "action-editmyuserjson": "自分のJSONファイルの編集",
+       "action-editmyuserjs": "自分のJavaScriptファイルの編集",
+       "action-viewsuppressed": "すべての利用者から隠された版の閲覧",
+       "action-ipblock-exempt": "IPブロック、自動ブロック、広域ブロックの回避",
+       "action-unblockself": "自分に対するブロックの解除",
+       "action-reupload-own": "自分がアップロードした既存のファイルへの上書き",
+       "action-patrolmarks": "最近の更新での巡回済み印の閲覧",
+       "action-override-export-depth": "リンク先ページの5階層まで含めた書き出し",
+       "action-suppressredirect": "転送ページの作成を伴わないページの移動",
        "nchanges": "$1 {{PLURAL:$1|回の変更}}",
        "ntimes": "$1×",
        "enhancedrc-since-last-visit": "最終閲覧以降 $1 {{PLURAL:$1|件}}",
        "uploadstash-file-not-found-missing-content-type": "コンテンツタイプヘッダーがありません。",
        "uploadstash-file-not-found-not-exists": "パスが見つからないか、またはプレーンファイルではありません。",
        "uploadstash-file-too-large": "$1バイトを超えるファイルは提供できません。",
-       "uploadstash-not-logged-in": "ユーザーはログインしていません。ファイルはユーザーに属している必要があります。",
-       "uploadstash-wrong-owner": "このファイル($1)は現在のユーザーに属していません。",
+       "uploadstash-not-logged-in": "利用者はログインしていません。ファイルは利用者に属している必要があります。",
+       "uploadstash-wrong-owner": "このファイル ($1) は現在の利用者に属していません。",
        "uploadstash-no-such-key": "そのような鍵($1)は削除できません。",
        "uploadstash-no-extension": "拡張機能がnullです。",
        "uploadstash-zero-length": "ファイルのサイズがゼロです。",
        "delete-confirm": "「$1」の削除",
        "delete-legend": "削除",
        "historywarning": "<strong>警告:</strong> 削除しようとしているページには、$1版の履歴があります:",
-       "historyaction-submit": "表示",
+       "historyaction-submit": "更新を表示",
        "confirmdeletetext": "ページをすべての履歴とともに削除しようとしています。\n本当にこの操作を行いたいか、操作の結果を理解しているか、およびこの操作が[[{{MediaWiki:Policy-url}}|方針]]に従っているかどうか、確認してください。",
        "actioncomplete": "操作を完了しました",
        "actionfailed": "操作に失敗しました",
        "import-mapping-subpage": "次のページの下位ページとしてインポート:",
        "import-upload-filename": "ファイル名:",
        "import-upload-username-prefix": "インターウィキ接頭辞:",
-       "import-assign-known-users": "指定されたユーザーがこのウィキに存在する場合そのユーザーに編集を割り当てる",
+       "import-assign-known-users": "指定された利用者がこのウィキに存在する場合その利用者に編集を割り当てる",
        "import-comment": "コメント:",
        "importtext": "元のウィキで[[Special:Export|書き出し機能]]を使用してファイルに書き出してください。\nそれをコンピューターに保存した後、こちらへアップロードしてください。",
        "importstart": "ページを取り込み中...",
index 3299d24..be41e53 100644 (file)
        "badretype": "입력한 비밀번호가 일치하지 않습니다.",
        "usernameinprogress": "이 사용자 이름에 대한 계정 생성이 이미 시작되었습니다. 기다려 주세요.",
        "userexists": "입력한 사용자 계정 이름이 이미 사용되고 있습니다.\n다른 이름을 선택하세요.",
+       "createacct-normalization": "당신의 사용자 이름은 기술적 한계로 인해 $2(으)로 조정됩니다.",
        "loginerror": "로그인 오류",
        "createacct-error": "계정 만들기 오류",
        "createaccounterror": "계정을 만들 수 없습니다: $1",
        "accmailtext": "[[User talk:$1|$1]]님의 비밀번호를 임의로 만들어 $2(으)로 보냈습니다. 로그인하고 나서 <em>[[Special:ChangePassword|비밀번호를 바꿀]]</em> 수 있습니다.",
        "newarticle": "(새 문서)",
        "newarticletext": "아직 없는 문서의 링크를 따라왔습니다.\n새 문서를 만들려면 아래 상자에 내용을 입력하면 됩니다. (자세한 내용은 [$1 도움말 문서]를 참조하세요)\n만약 잘못 찾아왔다면, 브라우저의 '''뒤로''' 버튼을 눌러 주세요.",
-       "anontalkpagetext": "----\n여기는 계정을 만들지 않았거나 사용하고 있지 않은 익명 사용자를 위한 토론 문서입니다. 익명 사용자를 구별하기 위해서는 숫자로 된 IP 주소를 사용해야만 합니다. IP 주소는 여러 사람이 공유할 수 있습니다. 자신과 관계없는 의견이 남겨져 있어 불쾌하다는 생각이 든다면 [[Special:CreateAccount|계정을 만들고]] [[Special:UserLogin|로그인해서]] 다른 사용자들에게 줄 혼란을 줄일 수 있습니다.",
+       "anontalkpagetext": "----\n<em>여기는 계정을 만들지 않았거나 사용하고 있지 않은 익명 사용자를 위한 토론 문서입니다.</em> 익명 사용자를 구별하기 위해서는 숫자로 된 IP 주소를 사용해야만 합니다. IP 주소는 여러 사람이 공유할 수 있습니다. 자신과 관계없는 의견이 남겨져 있어 불쾌하다는 생각이 든다면 [[Special:CreateAccount|계정을 만들거나]] [[Special:UserLogin|로그인해서]] 다른 사용자들에게 줄 혼란을 줄일 수 있습니다.",
        "noarticletext": "이 문서가 현재 존재하지 않습니다.\n이 문서와 제목이 비슷한 문서가 있는지 [[Special:Search/{{PAGENAME}}|검색하거나]],\n이 문서에 관련된 <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} 기록]을 확인하거나,\n문서를 직접 [{{fullurl:{{FULLPAGENAME}}|action=edit}} 생성]</span>할 수 있습니다.",
        "noarticletext-nopermission": "이 문서가 현재 존재하지 않습니다.\n이 문서와 제목이 비슷한 문서가 있는지 [[Special:Search/{{PAGENAME}}|검색하거나]], 이 문서에 관련된 <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} 기록]을 확인할 수 있습니다.</span> 그러나 이 문서를 만들 수 있는 권한은 없습니다.",
        "missing-revision": "\"{{FULLPAGENAME}}\"이라는 문서의 #$1판이 존재하지 않습니다.\n\n이 문제는 주로 삭제된 문서를 가리키는 오래된 문서 역사 링크로 인해 발생합니다.\n자세한 내용은 [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} 삭제 기록]에서 확인할 수 있습니다.",
        "page_first": "처음",
        "page_last": "마지막",
        "histlegend": "차이 선택: 비교하려는 판의 라디오 상자를 선택한 다음 엔터나 아래의 버튼을 누르세요.<br />\n설명: <strong>({{int:cur}})</strong> = 최신 판과 비교, <strong>({{int:last}})</strong> = 이전 판과 비교, <strong>{{int:minoreditletter}}</strong>= 사소한 편집",
-       "history-fieldset-title": "특정판 검색",
+       "history-fieldset-title": "특정판 필터링",
        "history-show-deleted": "특정판이 삭제된 것만",
        "histfirst": "오래됨",
        "histlast": "최신",
        "action-changetags": "문서의 특정 판과 특정 기록 항목에 임의의 태그를 추가하거나 제거하기",
        "action-deletechangetags": "데이터베이스에서 태그를 지우기",
        "action-purge": "이 문서 새로 고침",
+       "action-editinterface": "사용자 인터페이스 문서 편집",
+       "action-editusercss": "다른 사용자의 CSS 파일 편집",
+       "action-edituserjson": "다른 사용자의 JSON 파일 편집",
+       "action-edituserjs": "다른 사용자의 자바스크립트 파일 편집",
+       "action-unblockself": "자신을 차단 해제하기",
        "nchanges": "$1개 {{PLURAL:$1|바뀜}}",
        "enhancedrc-since-last-visit": "{{PLURAL:$1|마지막 방문 이후}} $1개",
        "enhancedrc-history": "역사",
        "delete-confirm": "\"$1\" 삭제",
        "delete-legend": "삭제",
        "historywarning": "<strong>경고:</strong> 삭제하려고 하는 문서에 {{PLURAL:$1|판}} $1개의 역사가 있습니다:",
-       "historyaction-submit": "보이기",
+       "historyaction-submit": "판 보이기",
        "confirmdeletetext": "문서와 문서 역사를 삭제하려고 합니다.\n삭제하려는 문서가 맞는지, 이 문서를 삭제하는 것이 [[{{MediaWiki:Policy-url}}|정책]]에 맞는 행동인지를 확인해 주세요.",
        "actioncomplete": "동작 완료",
        "actionfailed": "명령 실패",
index 207d587..848847b 100644 (file)
        "page_first": "биринчи",
        "page_last": "ахырынчы",
        "histlegend": "Тюзлевлерден сайлав: тенглешдирмеге сюеген бары тюрлерин белгилеп '''{{int:compare-submit}}''' бас.<br />\nАчыкълавлар: '''({{int:cur}})''' — гьалиги тюрюнден башгъалыкъ; '''({{int:last}})''' — алдагъы тюрюнден башгъалыкъ; '''{{int:minoreditletter}}''' — увакъ тюзелтив.",
-       "history-fieldset-title": "Тюзлевлеге излев",
+       "history-fieldset-title": "Тюзлев сюзгюч",
        "histfirst": "инг эсгилер",
        "histlast": "инг янгылар",
        "historyempty": "бош",
index 7679db6..c84af46 100644 (file)
@@ -88,7 +88,7 @@
        "october": "اوکتوبر",
        "november": "نوڤامر",
        "december": "دسامر",
-       "january-gen": "جانڤیٱ",
+       "january-gen": "ژانڤیٱ",
        "february-gen": "فڤریٱ",
        "march-gen": "مارس",
        "april-gen": "آڤریل",
        "category-article-count-limited": "نئها {{PLURAL:$1|بألگە هی|$1بألگە یا هئن}} د دأسە ئیسئنی.",
        "category-file-count": "{{PLURAL:$2|ای دٱسٱ فقٱت د ڤٱرگرتٱ جانؽا نهاییٱ.| نهایی {{PLURAL:$1|جانؽا هؽ|$1 جانیایا هؽسن}} د اؽ دٱسٱ، ڤ دٱر د کولٛ $2 .}}",
        "category-file-count-limited": " {{PLURAL:$1|[جانیا هی|1$جانیایا هین}} نئهایی هان د دأسە ئیسئنی.",
-       "listingcontinuesabbrev": "دومالٱ",
+       "listingcontinuesabbrev": "دۏمبالٱ",
        "index-category": "بألگە یا سیاە دار",
        "noindex-category": "بٱلگٱیا بؽ سؽاهٱ",
        "broken-file-category": "بٱلگٱیایی کاْ هوم پاٛڤٱن جانؽایا اْشگس ناْ دارٱن",
        "viewtalkpage": "دیئن چأک چئنە یا",
        "otherlanguages": "ڤ زڤونیٛا هنی",
        "redirectedfrom": "(ڤاگٱردونی د$1)",
-       "redirectpagesub": "بألگە ڤاگأردوٙنی",
+       "redirectpagesub": "بٱلگٱ ڤاگٱردونی",
        "redirectto": "ڤاگٱردونی سی:",
        "lastmodifiedat": "اؽ بٱلگٱ ایسنؽا آلشت بیٱ د $1، د $2.",
        "viewcount": "ئی بألگە ها د دأسرئسی {{PLURAL:$1|یئ گئل|$1 چأن گئل}}.",
        "aboutpage": "Project:دٱربارٱ",
        "copyright": "مینۊنٱیا هان د دٱسرس $1 مٱر یٱ کاٛ ڤ یاٛ گاٛل شیڤاٛ هٱنی نیسٱنٱ بۊٱ.",
        "copyrightpage": "{{ns:project}}:کوپی رایت",
-       "currentevents": "روخ ڤنؽا ایسنی",
-       "currentevents-url": "Project:روخ ڤنؽا ایسنی",
+       "currentevents": "رÙ\88Ø® Ú¤Ù±Ù\86ؽا Ø§Û\8cسÙ\86Û\8c",
+       "currentevents-url": "Project:رÙ\88Ø® Ú¤Ù±Ù\86ؽا Ø§Û\8cسÙ\86Û\8c",
        "disclaimers": "تیٱپۊشکاریٛا",
        "disclaimerpage": "پروژٱ: تیٱپۊشی کردن همٱگیر",
        "edithelp": "هومياری سی ڤیرایش",
        "resettokens-resetbutton": "نئشوٙنە گولئ ڤورچیە د نوٙ زئنە بینە",
        "bold_sample": "نیسسٱ مؽن پور",
        "bold_tip": "نیسسٱ مؽن پور",
-       "italic_sample": "نیسسٱ کٱج ۉ هار",
-       "italic_tip": "نیسسٱیا Ú©Ù±Ø¬ Û\89 Ú©Ù\88Ù\84Ù±",
+       "italic_sample": "نیسسٱیا هٱلٛ ۉ هار",
+       "italic_tip": "نیسسٱیا Ù\87Ù±Ù\84Ù\9b Û\89 Ù\87ار",
        "link_sample": "داسون هوم پاٛڤٱن",
        "link_tip": "هوم پاٛڤٱن مؽنجایی",
        "extlink_sample": "http://www.example.com داسون هوم پاٛڤٱن",
        "savearticle": "آمادٱ کردن بٱلگٱ",
        "preview": "پيش ساٛلٛ",
        "showpreview": "نشوݩ داٛئن پیش ساٛلٛ",
-       "showdiff": "نشوݩ داٛین آلشتکاریا",
+       "showdiff": "نشوݩ داٛئن آلشتکاریا",
        "blankarticle": "<strong>زنئار:</strong> بلگه ای که شما دروس کردیته حالیه.\nار شما د نو ری \"$1\" بپورنیت, بلگه وه شکل که هیچ مینونه ای دش نبا دروس بوئه.",
-       "anoneditwarning": "<strong>زٱنڳیار:</strong> شما هنی نۏمایتٱ ڤامؽن. تیرنشوݩ آی پی شما سی هٱر گاتؽ کاْ آلشتکاری بٱکؽت سی کولٛ خٱلک دؽاری مؽکٱ. ٱر <strong>[$1 رۉییت ڤامؽن]</strong> یا <strong>[$2 یاٛ هساو کاریاری دۏرس بٱکؽت]</strong>، ڤیرایشتؽا شما ڤ نوم کاریاری خوتو دیاری مؽکٱ ۉ سی شما بؽترٱ.",
+       "anoneditwarning": "<strong>زٱنڳیار:</strong> شما هنی نۏمایتٱ ڤامؽن. تیرنشوݩ آی پی شما سی هٱر گاتؽ کاْ آلشتکاری بٱکؽت سی کولٛ خٱلک دؽاری مؽکٱ. ٱر <strong>[$1 رۉؽت ڤامؽن]</strong> یا <strong>[$2 یاٛ هساو کاریاری دۏرس بٱکؽت]</strong>، ڤیرایشتؽا شما ڤ نوم کاریاری خوتو دؽاری مؽکٱ ۉ سی شما بؽترٱ.",
        "anonpreviewwarning": "<em>شوما نیوٙمایتە ڤامین. تیرنئشوٙن آی پی شوما د ڤیرگار ڤیرایئشت ئی بألگە ئمایە بوٙە.</em>",
        "missingsummary": "<strong>ڤیر دیارکو:</strong> شوما هأنی یئ گئل چئکئسە ڤیرایئشتی نە نئها ئمایە کاری نأکئردیتە.\nأر شوما د نۊ د ری \"$1\" بأپوٙرنیت، ڤیرایئشت کاری شوما حالی ئمایە بوٙە.",
        "selfredirect": "<strong>هوشدار:</strong> شوما د حال و بال ڤاگأردوٙنی ئی بألگە د خوش هیین.\nگاسی دال ئشتئڤایی سی ڤاگأردوٙنی ئنتئخاڤ کئردیتە، یا گاسی بألگە نە ئشتئڤایی ڤیرایئشت کاری می کیت.\nأر ری \"$1\" دۊ گئل بأپوٙرنیت، ڤاگأردوٙنی راس موٙە.",
        "newarticle": "تازە",
        "newarticletext": "شما هائؽت ڤا دما هوم پاٛڤٱنؽ اْ ڤوجۊد نارٱ.\nسی رٱڤٱندؽاری بٱلگٱ.شرۊ بٱکؽت مؽن جٱڤٱ هوری بٱنیسؽت (سی دونسن بؽشتر ساٛلٛ [$1 ] بٱکؽت).\nٱر شما سی اْشتبا کردن هائؽت ایچاْ، ری دۏگمٱ ڤادما رٱتن دوئارتٱ نیٱر بٱپۊرنؽت.",
        "anontalkpagetext": "----",
-       "noarticletext": "د ایسنؽا اؽ بٱلگٱ نیسسٱ ڤجۊد ناشتٱ.\nشما مؽ تونؽت د[[Special:Search/{{PAGENAME}}|بٱگٱردؽد]] د اؽ بٱلگٱ اؽ د بٱلگٱ هنی یا <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} د نۊ پاٛجۊری بۊئٱ]</span>، <span class=\"plainlinks\">[{{fullurl:{{FULLPAGENAME}}|action=edit}} یا ای بٱلگٱ ناْ ڤیرایش بٱکؽت]</span>.",
+       "noarticletext": "د ایسنؽا اؽ بٱلگٱ نیسسٱ ڤجۊد ناشتٱ.\nشما مؽ تونؽت د[[Special:Search/{{PAGENAME}}|ڤگٱردؽد]] د اؽ بٱلگٱ ڤ بٱلگٱ هنی یا <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} د نۊ پاٛجۊری بۊئٱ]</span>، <span class=\"plainlinks\">[{{fullurl:{{FULLPAGENAME}}|action=edit}} یا ای بٱلگٱ ناْ ڤیرایش بٱکؽت]</span>.",
        "noarticletext-nopermission": "د ایسنؽا اؽ بٱلگٱ نیسساٛیؽ ڤجۊد ناشتٱ.\nشما مؽ تونؽت د[[Special:Search/{{PAGENAME}}|بٱگردؽد]] د اؽ بٱلگٱیا د بٱلگٱ هنی یا <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} د نۊ پاٛجۊری بۊئٱ]</span>، <span class=\"plainlinks\">[{{fullurl:{{FULLPAGENAME}}|action=edit}}</span>.ڤلی شما سلا یٱناْ کاْ اؽ بٱلگٱ ناْ دۏرس بٱکؽت نارؽت.",
        "missing-revision": "ڤانئیأری #$1 د بألگە یی کئ نومئش ڤئنە \"{{FULLPAGENAME}}\" ڤوجوٙد نارە.\n\nگاسی سی یئ گئل ڤیرگار ڤئ هئنگوم نأبییە کئ د یئ گئل بألگە پاکسا بییە هوم پئیڤأند بییە رأڤأندیاری بییە.\nگاسی جوزئیات د[{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} deletion log] دیاری بأکأن.",
        "userpage-userdoesnotexist": "حئساڤ کاریاری \"$1\" ثأڤت نأم نأبییە.\nأر میھایت ئی بألگە نئ بأسازیت یا ڤیرایئشت کاری بأکیت یئ گئل ڤارئسی أنجوم بئیتوٙ.",
        "templatesused": "{{PLURAL:$1|چۊئٱ|چۊئٱ یا}} ڤ کار گرتٱ بیٱ د اؽ بٱلگٱ:",
        "templatesusedpreview": "{{PLURAL:$1| چۊئٱ|چۊئٱ یا}} ڤ کار گرتٱ بیٱ د پیش ساٛلٛ :",
        "templatesusedsection": "{{PLURAL:$1|چۊئٱ|چۊئٱ یا}} ڤ کار گرتٱ بیٱ د اؽ بٱلگٱ:",
-       "template-protected": "(پٱر ۉ پیم بیٱ)",
-       "template-semiprotected": "(نسم ۉ نیمٱ پٱر ۉ پیم بیٱ)",
+       "template-protected": "(پر ۉ پیم بیٱ)",
+       "template-semiprotected": "(نسم ۉ نیمٱ پر ۉ پیم بیٱ)",
        "hiddencategories": "اؽ بٱلگٱ یٱکؽ د ٱندومؽا ٱ {{PLURAL:$1|1 hidden category|$1 hidden categories}} :",
        "edittools-upload": "-",
        "nocreatetext": "{{SITENAME}} سی رأڤأندیاری بألگە یا تازە نئھاگئری بییە.\nشوما می توٙنیت روئیت ڤادئما و بألگە ئی کئ بییشە ڤیرایئشت کاری بأکیت،[[Special:ڤامین ئوٙمائن کاریار|بیائیت ڤامین یا یە کئ یئ گئل حئساڤ دوروس بأکیت]].",
        "sectioneditnotsupported-text": "ڤیرایئشت بأرجایی د ئی بألگە نیئش.",
        "permissionserrors": "خٱتا سلا داٛئن",
        "permissionserrorstext": "شوما حأق ناریت ڤئنە أنجوم بئیت، سی{{PLURAL:$1|دألیل|دألیلیا}} نئھایی:",
-       "permissionserrorstext-withaction": "شما سی $2 سلا \nنھاگری نارؽت {{PLURAL:$1|دلٛیلٛ|دلٛیلٛؽا}}:",
-       "recreate-moveddeleted-warn": "'''ڤ ڤیرتو با:شما بٱلگاٛیی کاْ ھا ڤادما ۉ پاکسا بیٱ د نۊ دۏرس کردؽتٱ.'''\nبایٱد د ڤیرتو با کاْ آیا ھنی نوئاگیری ڤیرایش اؽ بٱلگٱ خۊئٱ.\nپاکسا کاری ۉ جا ڤ جا کاری اؽ بٱلگٱ سی هال ۉ بال پٱلٛٱمار شما آمادٱ بیٱ:",
-       "moveddeleted-notice": "اؽ بٱلگٱ پاکسا بیٱ.\nپاکسا کاری ۉ جا ڤ جا کاری اؽ بٱلگٱ سی هال ۉ بال پٱلٛٱمار شما آمادٱ بیٱ.",
+       "permissionserrorstext-withaction": "شما سی $2 سلا \nنھاگیری نارؽت {{PLURAL:$1|دلٛیلٛ|دلٛیلٛؽا}}:",
+       "recreate-moveddeleted-warn": "'''ڤ ڤیرتو با:شما بٱلگاٛیی کاْ ھا ڤادما ۉ پاکسا بیٱ د نۊ دۏرس کردؽتٱ.'''\nبایٱد د ڤیرتو با کاْ آیا ھنی نوئاگیری ڤیرایش اؽ بٱلگٱ خۊئٱ.\nپاکسا کاری ۉ جا ڤ جا کاری اؽ بٱلگٱ سی هال ۉ بار پٱلٛٱمار شما آمادٱ بیٱ:",
+       "moveddeleted-notice": "اؽ بٱلگٱ پاکسا بیٱ.\nپاکسا کاری ۉ جا ڤ جا کاری اؽ بٱلگٱ سی هال ۉ بار پٱلٛٱمار شما آمادٱ بیٱ.",
        "log-fulllog": "دیئن هأمە پئهئرستنوٙمە یا",
        "edit-hook-aborted": "ڤیرایئشت ڤا قولاڤ نئھاگئری بییە.\nھیچ توضیی سیش نی.",
        "edit-gone-missing": "نأبوٙە ئی بألگە نە ڤئ ھئنگوم بأکیت.\nچئنی ڤئ نأظأر میا کئ ڤئ پاکسا بییە.",
        "previousrevision": "ڤانیٱری زیتری ←",
        "nextrevision": "ڤانؽٱری تازٱتر",
        "currentrevisionlink": "آخری ڤانؽٱری",
-       "cur": "تازٱ باو",
+       "cur": "تازٱ بۊ",
        "next": "نئهایی",
        "last": "دمایی",
        "page_first": "أڤئلی",
        "page_last": "آخئر",
-       "histlegend": "اÙ\92Ù\86تخاب Ù\81ٱرخدار:جٱڤٱÛ\8cا Ø±Ø§Ø¯Û\8cÙ\88 Ù\86اÙ\92 Ø³Û\8c Ø¯Ù\88ئارٱ Ø¯Û\8cئÙ\86 Û\89 Ú¤Ø§Ø±Ø³Û\8c Ù\86Ø´Ù\88Ý© Ø¯Ø§Ø± Ø¨Ù±Ú©Ø½Øª Û\89 Û\8cا Ø±Û\8c Ø±Ù±ØªÙ\86 Ú©Ù\84Ù\9bÛ\8cÚ© Ø¨Ù±Ú©Ø½Øª .<br />\nشرح Ù\86Ù\88شتÙ\87: '''({{int:cur}})''' = ڤا آخری دوئارٱ دیئن فٱرخ دارٱ '''({{ int:last}})'''= ڤا دوئارٱ دیئن ٱنجوم داٛئنی فٱرخ دارٱ  '''{{int:minoreditletter}}''' =ڤیرایش کوچک.",
+       "histlegend": "اÙ\92Ù\86تخاب Ù\81ٱرخدار:جٱڤٱÛ\8cا Ø±Ø§Ø¯Û\8cÙ\88 Ù\86اÙ\92 Ø³Û\8c Ø¯Ù\88ئارٱ Ø¯Û\8cئÙ\86 Û\89 Ú¤Ø§Ø±Ø³Û\8c Ù\86Ø´Ù\88Ý© Ø¯Ø§Ø± Ø¨Ù±Ú©Ø½Øª Û\89 Û\8cا Ø±Û\8c Ø±Ù±ØªÙ\86 Ú©Ù\84Ù\9bÛ\8cÚ© Ø¨Ù±Ú©Ø½Øª .<br />\nتÛ\89سÛ\8cÙ\81 Ù\86Ú¤Û\8cسٱ: '''({{int:cur}})''' = ڤا آخری دوئارٱ دیئن فٱرخ دارٱ '''({{ int:last}})'''= ڤا دوئارٱ دیئن ٱنجوم داٛئنی فٱرخ دارٱ  '''{{int:minoreditletter}}''' =ڤیرایش کوچک.",
        "history-fieldset-title": "ڤیرگار دوئارٱ نیٱری",
        "history-show-deleted": "فقط پاكسا بيه",
        "histfirst": "قاٛیمی تریݩ",
        "rev-suppressed-unhide-diff": "وانئری ای بلگه <strong>پاکساگری بیه</strong>.\nجزئیات هان د  [{{fullurl:{{#Special:Log}}/پاکساگری کردن|بلگه={{نوم کامل بلگه}}}} پهرستنومه پاکساگری کردن].\nشما  هنی تونیت [$1ای وانئری نه بونیت] ار بهاییت.",
        "rev-deleted-diff-view": "وانئری ای بلگه <strong>پاکسا بیه</strong>.\nجزئیات هان د  [{{fullurl:{{#Special:Log}}/پاکسا کردن|بلگه={{نوم کامل بلگه}}}} پهرستنومه پاکساکردن].",
        "rev-suppressed-diff-view": "وانئری بلگه <strong>پاکساگری</strong>.\nجزئیات هان د  [{{fullurl:{{#Special:Log}}/پاکساگری کردن|بلگه={{نوم کامل بلگه}}}} پهرستنومه پاکساگری کردن].",
-       "rev-delundel": "آلشت هال ۉ بال ديئن",
+       "rev-delundel": "آلشت هال ۉ بار ديئن",
        "rev-showdeleted": "نئشوٙ دأئن",
        "revisiondelete": "پاکسا کردن/زنه کردن وانئریا",
        "revdelete-nooldid-title": "وانیری تمارزی بیه نامعتوره",
        "difference-title-multipage": "فرخ مینجا بلگه یا \"$1\" و \"$2\"",
        "difference-multipage": "(فرخ مینجا بلگه یا)",
        "lineno": "خٱت $1:",
-       "compareselectedversions": "دوئارٱ دی ینؽایؽ کاْ اْنتخاو بینٱ ناْ موقایسٱ بٱکؽت",
+       "compareselectedversions": "دوئارٱ دیئنؽایؽ کاْ اْنتخاو بینٱ ناْ موقایسٱ بٱکؽت",
        "showhideselectedversions": "شلک دیئن وانیریا انتخاو بیه نه آلشت بکید",
        "editundo": "ناٱنجومگر کردن",
        "diff-empty": "(بؽ فٱرق)",
        "prev-page": "بلگه دمايی",
        "next-page": "بلگه نهایی",
        "prevn-title": "زیتر $1 {{PLURAL:$1|نٱتیجٱ|نٱتيجٱيا}}",
-       "nextn-title": "دمایی $1 {{PLURAL:$1|نتيجٱ|نتيجيا}}",
-       "shown-title": "نشوݩ داٛین $1 {{PLURAL:$1|نتیجٱ|نتیجٱیا}} سی هار بٱلگٱ",
-       "viewprevnext": "ديین ($1 {{int:pipe-separator}} $2) ($3)",
+       "nextn-title": "دمایی $1 {{PLURAL:$1|نٱتيجٱ|نٱتيجؽا}}",
+       "shown-title": "نشوݩ داٛین $1 {{PLURAL:$1|نتیجٱ|نتیجٱیا}} سی هٱر بٱلگٱ",
+       "viewprevnext": "ديئن ($1 {{int:pipe-separator}} $2) ($3)",
        "searchmenu-exists": "'''ایچاْ بٱلگاٛیؽ هؽ ڤ نوم\"[[:$1]]\" کاْ ها د اؽ ڤیکی'''",
        "searchmenu-new": "'''اؽ بٱلگٱ ناْ دۏرس كو \"[[:$1]]\" د اؽ ڤیکی!'''",
        "searchprofile-articles": "بٱلگٱيا مؽنونٱ دار",
-       "searchprofile-images": "ڤارسگرؽا خلکمن",
+       "searchprofile-images": "ڤارسگرؽا Ø®Ù\84Ú©Ù\85Ù±Ù\86",
        "searchprofile-everything": "همٱ چی",
        "searchprofile-advanced": "پیشکردٱ",
        "searchprofile-articles-tooltip": "بٱگٱرد مؽن $1",
-       "searchprofile-images-tooltip": "جانؽایاناْ پاٛ جۊری کو",
-       "searchprofile-everything-tooltip": "همٱ مؽنونٱیا ناْ پۊ جۊری كو (شاملاْ بٱلگٱيا چٱک چنٱ)",
+       "searchprofile-images-tooltip": "جانؽایا ناْ پاٛ جۊری کو",
+       "searchprofile-everything-tooltip": "همٱ مؽنونٱیا ناْ پاٛ جۊری كو (شامل بٱلگٱيا چٱک چنٱ)",
        "searchprofile-advanced-tooltip": "نوم جايا نوم دؽار بٱگٱرد",
        "search-result-size": "$1 ({{PLURAL:$2|1 کلٱمٱ|$2 کلٱمٱیا}})",
        "search-result-category-size": "{{PLURAL:$1|1 ٱندوم|$1 ٱندومؽا}} ({{PLURAL:$2|1 زؽردٱسٱ|$2 زؽردٱسٱیا}}، {{PLURAL:$3|1 جانؽا|$3 جانؽایا}}",
        "search-interwiki-more": "(بیشتر)",
        "search-relatedarticle": "مرتوط",
        "searchrelated": "مرتوط",
-       "searchall": "Ù\87Ù±Ù\85Ù±",
+       "searchall": "همٱ",
        "showingresults": "نمائشت بیشترونه {{PLURAL:$1|'''۱''' نتیجه|'''$1''' نتیجه}} د هار، شرو د شماره'''$2'''.",
        "showingresultsinrange": "نمائشت بیشترونه {{PLURAL:$1|'''۱''' نتیجه|'''$1''' نتیجه}} د هار، شرو د شماره'''$2''' تا شماره '''$3'''.",
        "search-showingresults": "{{PLURAL:$4|نٱتیجٱیا<strong>$1</strong> د <strong>$3</strong>|نٱتیجٱیا<strong>$1 - $2</strong د <strong>$3</strong>}}",
        "enhancedrc-history": "ڤیرگار",
        "recentchanges": "آلشتؽا ایسنی",
        "recentchanges-legend": "گوزینٱیا آلشتؽا ایسنی",
-       "recentchanges-summary": "دۏ بؽشتر آلشتؽا تازباو ناْ د ڤیکی د اؽ بٱلگٱ پاٛجۊری کو.",
-       "recentchanges-noresult": "هیچ آلشتؽ د درازا دۉرٱ دؽار بیٱ ڤا اؽ ماٛعیاؽا یٱکی ناٛی.",
+       "recentchanges-summary": "دۏ بؽشتر آلشتؽا تازٱبۊ ناْ د ڤیکی د اؽ بٱلگٱ پاٛجۊری کو.",
+       "recentchanges-noresult": "Ù\87Û\8cÚ\86 Ø¢Ù\84شتؽ Ø¯ Ø¯Ø±Ø§Ø²Ø§ Ø¯Û\89رٱ Ø¯Ø½Ø§Ø± Ø¨Û\8cÙ± Ú¤Ø§ Ø§Ø½ Ù\85اÙ\9bعÛ\8cارؽا Û\8cÙ±Ú©Û\8c Ù\86اÙ\9bÛ\8c.",
        "recentchanges-feed-description": "دۏ بؽشتر آلشتؽا تازباو ناْ د ڤیکی کا ها د هڤال هون پاٛگیری کو.",
        "recentchanges-label-newpage": "اؽ ڤیرایش یاٛ بٱلگٱ تازٱ دۏرس کردٱ.",
        "recentchanges-label-minor": "یٱ یاٛ ڤیرایش کوچکٱ",
        "recentchanges-label-bot": "اؽ ڤيرايش ناْ ياٛ بوت ٱنجوم داٛیٱ",
-       "recentchanges-label-unpatrolled": "اؽ ڤيرايش هنی تيٱ ڤاداشت ناٛیٱ",
+       "recentchanges-label-unpatrolled": "اؽ ڤيرايش هنی تيٱ ڤاداشت ناٛیئٱ",
        "recentchanges-label-plusminus": "ٱندازٱ بٱلگٱ ڤ شماراٛ اؽ بایتؽا آلشت کردٱ.",
        "recentchanges-legend-heading": "<strong>میرات:</strong>",
        "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (همچنو باٛینؽت [[ڤیژٱ:بٱلگٱیا تازٱ|نوم گٱ بٱلگٱیا تازٱ]])",
        "rcshowhideminor-show": "نشوݩ داٛئن",
        "rcshowhideminor-hide": "قایم کردن",
        "rcshowhidebots": "$1 روباتؽا یا بوتؽا",
-       "rcshowhidebots-show": "نشوݩ داٛین",
+       "rcshowhidebots-show": "نشوݩ داٛئن",
        "rcshowhidebots-hide": "قایم کردن",
        "rcshowhideliu": "$1 کاریاریا سٱبت نوم کردٱ",
        "rcshowhideliu-show": "نشوݩ داٛئن",
        "diff": "فٱرق",
        "hist": "ڤیرگار",
        "hide": "قایم کردن",
-       "show": "نشوݩ داٛین",
+       "show": "نشوݩ داٛئن",
        "minoreditletter": "م",
        "newpageletter": "ن",
        "boteditletter": "ب",
        "newsectionsummary": "/* $1 */ بهرجا تازه",
        "rc-enhanced-expand": "جزيات نشون بيئه",
        "rc-enhanced-hide": "جزياته قام كو",
-       "rc-old-title": "ذاتا چی \"$1\" راس بیه",
+       "rc-old-title": "کاملٱن چی \"$1\"دۏرس بیٱ",
        "recentchangeslinked": "آلشتؽا تاٛ یٱکؽ",
        "recentchangeslinked-feed": "آلشتؽا تاٛ یٱک",
        "recentchangeslinked-toolbox": "آلشتؽا تاٛ یٱک",
        "upload-curl-error6-text": "تیرنشون اینترنتی که دئیته د دسرس نئ.\nلطف بکیت درستیشه و یه نه که تیرنشون اینترنی میزونکاری بیه وارسی بکیت.",
        "upload-curl-error28": "تموم بیئن مئلت سی سوار کرد",
        "upload-curl-error28-text": "ای دیارگه فره دیر دتو واکنشت نشو دئه.\nلطف بکیت سی یه که دیارگه کنشگتر و ری خطه یه گل وارسی بکیت، اوسه یه گر واستید و هنی تلاش بکیت.\nشایت بیتر با که د گات خلوتری هنی تلاش بکیت.",
-       "license": "ليانس دار بيئن",
-       "license-header": "د هال ۉ بال لیسانس دار بیئن",
+       "license": "Ù\84Ù\8aساÙ\86س Ø¯Ø§Ø± Ø¨Ù\8aئÙ\86",
+       "license-header": "د هال ۉ بار لیسانس دار بیئن",
        "nolicense": "هیچی انتخاو نبیه",
        "licenses-edit": "گزینه یا مجوز ویرایشت",
        "license-nopreview": "(پیش سیل د دسرس نئ)",
        "filehist-revert": "لٛرنیئن",
        "filehist-current": "تازٱ با",
        "filehist-datetime": "ڤيرگار/ڤٱخت",
-       "filehist-thumb": "عٱسک کوچک بیٱ",
+       "filehist-thumb": "عٱسگ کوچک بیٱ",
        "filehist-thumbtext": "كوچک کردن سی نۏسخٱ چی $1",
        "filehist-nothumb": "بؽ بٱن کلٛکی",
        "filehist-user": "کاریار",
        "imagelinks": "ڤ کار گرتن جانؽا",
        "linkstoimage": "دۏنبال بيٱ {{PLURAL:$1|ديس ڤنؽا بٱلگٱ|$1 ديس ڤنؽا بٱلگٱيا}} د اؽ فایلٛ:",
        "linkstoimage-more": "بؽشتر د $1 بٱلگٱ د اؽ جانؽا هوم پاٛڤٱن {{PLURAL:$1|بٱ|بینٱ}}.\nنومگٱ هاری تٱنڳؽا{{PLURAL:$1|ٱڤلی هوم پاٛڤٱن|ٱڤلی $1 هوم پاٛڤٱن}} د ؽ بٱلگٱ ناْ نشوݩ مؽ یٱ.\n[[Special:WhatLinksHere/$2|نومگٱ کامل]] ٱم هؽسش.",
-       "nolinkstoimage": "ایچاْ هیچ بٱلگاٛیی سی هوم پیاٛڤٱن بیین ڤا اؽ جانؽا نؽ",
+       "nolinkstoimage": "ایچاْ هیچ بٱلگاٛیی سی هوم پیاٛڤٱن بیئن ڤا اؽ جانؽا نؽ",
        "morelinkstoimage": " [[ویجه:چه هوم پیوندی ها ایچه/$1|هوم پیوندیا هنی]]سی ای جانیا نه بونیت.",
        "linkstoimage-redirect": "$1 (ڤاگٱردونی جانؽا) $2",
        "duplicatesoffile": "{{PLURAL:$1|جانیا|جانیایا}} هاری نسقه تکراری ای جانیا {{PLURAL:$1|هئ|هئن}} ([[Special:FileDuplicateSearch/$2|دونسمنیا هنی]]):",
        "speciallogtitlelabel": "هاستنی(داسون یا نوم کاریاری سی کاریار):",
        "log": "پهرستنومٱیا",
        "all-logs-page": "همٱ پهرستنومٱیا عومۊمی",
-       "alllogstext": "نمایش یٱ جا همٱ پهرستنومٱیا کاْ هان د{{SITENAME}}.\nمؽ تونؽت ڤا اْنتخاو نۉع پهرستنومٱ، نوم کاریاری(هٱساس ڤ کوچکی ۉ گٱپی هرفؽا) ۉ بٱلگٱیا آلشت کردٱ(هٱساس ۉ گٱپی ۉ کوچکی هرنیا) نمایش ناْ دیر د ڤیرتر بٱکؽت.\n\n{{SITENAME}}.",
+       "alllogstext": "نمایش یاٛ جا همٱ پهرستنومٱیا کاْ هان د{{SITENAME}}.\nمؽ تونؽت ڤا اْنتخاو نۉع پهرستنومٱ، نوم کاریاری(هٱساس ڤ کوچکی ۉ گٱپی هرفؽا) ۉ بٱلگٱیا آلشت کردٱ(هٱساس ۉ گٱپی ۉ کوچکی هٱرفؽا) نمایش ناْ دیر د ڤیرتر بٱکؽت.\n\n{{SITENAME}}.",
        "logempty": "او چی کاْ شما مؽهایت د پهرستنومٱ نؽسش.",
        "log-title-wildcard": "بلگه یایی نه پی جوری کو که وا ای سرون شرو موئن",
        "showhideselectedlogentries": "آلشت دئن ورتیه گر پهرستنومه یا انتخاو بیه",
        "notvisiblerev": "آخری وانئری که د دس یه کاریار هنی انجوم بیه پاکسا بیه.",
        "watchlist-details": "{{PLURAL:$1|$1 بلگٱ|$1 بلگٱیا}} د ساٛلٛ بٱرگتو هیچ بٱلگٱ قسٱ کردنی نؽ.",
        "wlheader-enotif": "ڤارئسیاری أنجومانامە کونئشتکار بییە.",
-       "wlheader-showupdated": "بٱلگٱیایی کاٛ د آخری گلٛؽ کاْ کاْ شما ساٛیلٛشو کردؽتٱ د <strong>تۊپور</strong>نشوݩ داٛئٱ بۊئٱن",
+       "wlheader-showupdated": "بٱلگٱیایی کاٛ د آخری گلٛؽ کاْ شما ساٛیلٛشو کردؽتٱ د <strong>تۊپور</strong>نشوݩ داٛئٱ بۊئٱن",
        "wlnote": "د هار {{PLURAL:$1|آلشت|<strong>$1</strong> آلشتؽ}} کاْ د {{PLURAL:$2|ساعت|<strong>$2</strong> ساعٱت}} دماتر ٱنجوم بیٱ هؽسش، ڤیرگار آخری ڤاجۊری ٱنجوم بیٱ هؽسش، ڤیرگار آخری ڤاجۊری: $3، $4",
        "wlshowlast": "آخری$1 ساعٱتؽا $2ۉ رۊزؽا نشوݩ باٛیٱ",
        "wlshowtime": "نئشوٙ دأئن د آخأر",
        "changecontentmodel-success-title": "حال و بال مینوٙنە آلئشتکاری بی",
        "logentry-contentmodel-change-revertlink": "لئرنیئن",
        "logentry-contentmodel-change-revert": "لئرنیئن",
-       "protectlogpage": "پٱر ۉ پیم کاری پهرستنومٱ",
+       "protectlogpage": "پر ۉ پیم کاری پهرستنومٱ",
        "protectlogtext": "د ھار یئ گئل نومگە د آلئشتیا ریتئراز پأر و پیم کاری بألگە یا ئوٙماە.\n[[Special:ProtectedPages|نومگە بألگە یا پأر و پیم کاری بییە]] نە سی دیئن نومگە پأر و پیم کاری کارگئرا بألگە یا نە سئیل بأکیت.",
-       "protectedarticle": "پأر Ù\88 Ù¾Û\8cÙ\85 Ú©Ø§Ø±Û\8c Ø¨Û\8cÛ\8cÛ\95 [[$1]]",
+       "protectedarticle": "پر Û\89 Ù¾Û\8cÙ\85 Ú©Ø§Ø±Û\8c Ø¨Û\8cÙ± [[$1]]",
        "modifiedarticleprotection": "ریتراز هفازٱت د \"[[$1]]\" آلشت بیٱ",
        "unprotectedarticle": "بلگه«[[$1]]» نه د پر و پیم دراورد",
        "movedarticleprotection": "میزونکاری پر و پیم بیین د «[[$2]]» وه «[[$1]]» جا وه جا بیه",
        "undelete-show-file-confirm": "آیا یه دل بئیته که میهایت یه گل نسقه پاکسا بیه د جانیا \"<nowiki>$1</nowiki>\" که ها د ویرگار $2 ساعت $3 نه سیل بکیت؟",
        "undelete-show-file-submit": "هأری",
        "namespace": "نوم جا",
-       "invert": "Ú¯Ù\84Ù\9bٱڤرÚ\86Û\8c Ø¨Û\8cئÙ\86 Ø¨Ù±Ø±Ø¹Ù±Ø³Ú© بۊئٱ",
+       "invert": "Ú¯Ù\84Ù\9bٱڤرÚ\86Û\8c Ø¨Û\8cئÙ\86 Ø¨Ù±Ø±Ø¹Ù±Ø³Ú¯ بۊئٱ",
        "tooltip-invert": "د ری اؽ جٱڤٱ بٱپۊرنؽت ۉ آلشتؽایؽ ناْ کاْ د مؽنجا نوم ڤرگٱ اْنتخاو بیٱ ٱنجوم بینٱ قایم بٱکؽت(ۉ ٱر نوم ڤرگٱ شریکی ڤارسی بیٱ)",
        "tooltip-whatlinkshere-invert": "ای جعون نه سی نهو کردن هوم پیوند بلگه یایی که نوم جاشو انتخاو بیه، انتخاو بکیت.",
        "namespace_association": "نوم جایا یٱکاگرتٱ",
        "sp-contributions-blocked-notice-anon": "ای آی پی ایسنی دسرسی ناره.\nآخری برشت د پهرستنومه ها د سرچشمه هاری:",
        "sp-contributions-search": "سی هومیارؽا پاٛ جۊر با",
        "sp-contributions-username": "نوم نشوݩ آی پی يا نوم كارڤٱری:",
-       "sp-contributions-toponly": "فقٱت ڤیرایشؽایی کاْ جۏز آخری دۉرٱن نشو باٛیٱ",
+       "sp-contributions-toponly": "فقٱت ڤیرایشؽایؽ کاْ جۏز آخری دۉرٱن نشو باٛیٱ",
        "sp-contributions-newonly": "فقٱت ڤیرایشؽایؽ کاْ هؽن دۏرس کردن بٱلگان نشوݩ باٛیٱ.",
        "sp-contributions-submit": "پاٛ جۊری",
        "whatlinkshere": "کوم هوم پاٛڤٱنؽا هان ایچاْ",
        "whatlinkshere-prev": "{{PLURAL:$1|نوئایی|نوئایی $1}}",
        "whatlinkshere-next": "{{PLURAL:$1|دمایی|دمایی $1}}",
        "whatlinkshere-links": "هوم پاٛڤٱنؽا",
-       "whatlinkshere-hideredirs": "$1 ڤرگٱردونیا",
-       "whatlinkshere-hidetrans": "$1 چٱن نتیجاٛیی",
+       "whatlinkshere-hideredirs": "$1 ڤرگٱردونؽا",
+       "whatlinkshere-hidetrans": "$1 چٱن نٱتیجاٛیی",
        "whatlinkshere-hidelinks": "هوم پاٛڤٱنؽا $1",
        "whatlinkshere-hideimages": "جانؽا هوم پاٛڤٱنؽا $1",
        "whatlinkshere-filters": "فيلٛترؽا",
        "blocklist-nousertalk": "نبوئه بلگه چک چنه خوتونه ویرایشت بکید",
        "ipblocklist-empty": "جاگه نوم گه حالیه",
        "ipblocklist-no-results": "دسرسی نوم کاریاری یا تیرنشون آی پی حاسته بیه نهاگری نبیه.",
-       "blocklink": "نوئاگری بۊئٱ",
+       "blocklink": "نوئاگیری بۊئٱ",
        "unblocklink": "بی قطی",
        "change-blocklink": "اجازه نديئن سی  آلشت",
        "contribslink": "هومیاریٛا",
        "blocklog-showlog": "تیرنشون ای آی پی دماتر نهاگیری بیه.\nنهاگری ای پهرستنومه وا سرچشمه هاری دروس بیه:",
        "blocklog-showsuppresslog": "تیرنشون ای آی پی دماتر نهاگیری بیه.\nنهاگری ای پهرستنومه وا سرچشمه هاری دروس بیه:",
        "blocklogentry": " [[$1]] ڤا یاٛ ڤٱخت تموم بیئن $2 ۉ $3  قلف بییٱ",
-       "reblock-logentry": "میزوکاری سی نهاگری[[$1]] آلشت بیه سی آخر نهاگری د $2 $3",
+       "reblock-logentry": "میزوکاری سی نهاگیری[[$1]] آلشت بیٱ سی آخر نهاگیری د $2 $3",
        "blocklogtext": "ای پهرستنومه سی نهاگری یا واز کردن دسرسی کاریاریائه.\nتیرنشونیا آی پی که خود انجومن نهاگری بینه د ایسنی نومگه کاری نبینه.\nسی نومگه نهاگریا و بسه بینیا د ایسنی روئیت د [[Special:BlockList|نومگه نهاگری بیه یا]].",
        "unblocklogentry": "وا کردن قلف $1",
        "block-log-flags-anononly": "فقط کاریاریایی که نادیارن",
        "tooltip-ca-history": "دوئرٱ دیئن اؽ بٱلگٱ",
        "tooltip-ca-protect": "اؽ بٱلگٱ ناْ ڤیردار بٱکؽت",
        "tooltip-ca-unprotect": "پر و پیم گیری د ای بلگه نه آلشت بکیت",
-       "tooltip-ca-delete": "ای بلگه نه پاکسا کو",
+       "tooltip-ca-delete": "اؽ بٱلگٱ ناْ پاکسا کو",
        "tooltip-ca-undelete": "د نو زنه کردن ویرایشتیا ری ای بلگه دما یه که پاکساگری بان",
        "tooltip-ca-move": "اؽ بٱلگٱ ناْ جا ڤ جا كو",
        "tooltip-ca-watch": "اْزاف کردن اؽ بٱلگٱ ڤ نوم نڤشت پاٛگیریاتو",
        "tooltip-n-mainpage": "سرآسونٱ ناْ ساٛلٛ بٱکؽت",
        "tooltip-n-mainpage-description": "سرآسونٱ ناْ ساٛلٛ بٱکؽت",
        "tooltip-n-portal": "دبارٱ پرۉژٱ؛ شما مؽ تونؽت چؽ بٱکؽت؛ د کوجا اؽ چیاناْ بٱجۊرؽت.",
-       "tooltip-n-currentevents": "ساڤن دونسمنیایؽ کا هان د روخ ڤٱنؽا تازٱ بۊ دؽاری بٱک",
+       "tooltip-n-currentevents": "ساڤٱÙ\86 Ø¯Ù\88Ù\86سÙ\85Ù\86Û\8cاÛ\8cؽ Ú©Ø§ Ù\87اÙ\86 Ø¯ Ø±Ù\88Ø® Ú¤Ù±Ù\86ؽا ØªØ§Ø²Ù± Ø¨Û\8a Ø¯Ø½Ø§Ø±Û\8c Ø¨Ù±Ú©",
        "tooltip-n-recentchanges": "یاٛ نومگٱ سی آلشتکاریا د ڤیکی",
        "tooltip-n-randompage": "سڤار کرد بٱلگٱ بٱختٱکی",
        "tooltip-n-help": "یاٛ جاگٱ سی فٱمسن",
        "tooltip-watchlistedit-raw-submit": "وه هنگوم سازی سیل برگ",
        "tooltip-recreate": "د نو راس کردن بلگه بی یه که و پاکساگری دماتر وه سیل بکیم",
        "tooltip-upload": "شرو د سوار کرد",
-       "tooltip-rollback": "\"ڤرگٱشتن\" لٛرسن د هال و بال ٱڤٱل سی اؽ بٱلگٱ سی یٱ کاْ هومیاری نؽایی بؽتر کاری بیٱ ڤا یاٛ پۊرنین.",
-       "tooltip-undo": "Ù±Ù\86جÙ\88Ù\85 Ù\86ٱگرتÙ\86 Ø§Ø½ Ú¤Û\8cراÛ\8cØ´. Ú¤Ø±Ú¯Ù±Ø±Ø¯ Û\89 Ù\87Ù±Ù\85Ù± Ù\81Ù\88رÙ\85ؽا Ú¤Û\8cراÛ\8cشؽاÙ\86اÙ\92 Ø¯ Ù\87اÙ\84ٱت Ù¾Û\8cØ´ Ø³Ø§Ù\9bÙ\84Ù\9b Ú¤Ø§Ú©Ù\88\8cÙ± Ø§Ù\92جازٱ Ù\85اÙ\9bئٱ Ø³Û\8c Ø§Ù\92زاÙ\81 Ú©Ø±Ø¯Ù\86 Û\8cاÙ\9b Ø¯Ù±Ù\84Ù\9bÛ\8cÙ\84Ù\9b Ø¯ Ú\86کسٱ.",
+       "tooltip-rollback": "\"ڤرگٱشتن\" لٛرسن د هال و بار ٱڤٱل سی اؽ بٱلگٱ سی یٱ کاْ هومیاری نؽایی بؽتر کاری بیٱ ڤا یاٛ پۊرنیئن.",
+       "tooltip-undo": "ٱنجوم نٱگرتن اؽ ڤیرایش. ڤرگٱرد ۉ همٱ فورمؽا ڤیرایشؽاناْ د هالٱت پیش ساٛلٛ ڤاکو.یٱ اْجازٱ ماٛئٱ سی اْزاف کردن یاٛ دٱلٛیلٛ د چکسٱ.",
        "tooltip-preferences-save": "اولويتيا نه ذخيره بكيد",
        "tooltip-summary": "ياٛ چكسٱ کوچک ڤارد بٱکؽت",
        "interlanguage-link-title": "$1-$2",
        "pageinfo-redirects-name": "شمارٱ ڤاگٱردونیا اؽ بٱلگٱ",
        "pageinfo-redirects-value": "$1",
        "pageinfo-subpages-name": "ٱندازٱ زؽر بٱلگٱیا اؽ بٱلگٱ",
-       "pageinfo-subpages-value": "$1 ($2 {{PLURAL:$2|ڤاگٱردونی|ڤاگٱردونیا}}; $3 {{PLURAL:$3|بؽ ڤاگٱردونی|بؽ ڤاگٱردونیا}})",
+       "pageinfo-subpages-value": "$1 ($2 {{PLURAL:$2|ڤاگٱردونی|ڤاگٱردونؽا}}; $3 {{PLURAL:$3|بؽ ڤاگٱردونی|بؽ ڤاگٱردونؽا}})",
        "pageinfo-firstuser": "بٱلگٱ دۏرس کن",
        "pageinfo-firsttime": "گات دۏرس بیئن بٱلگٱ",
        "pageinfo-lastuser": "آخری ڤیرایشکار",
        "widthheight": "$1 × $2",
        "widthheightpage": "$1 × $2, $3 {{PLURAL:$3|بٱلگٱ|بٱلگٱیا}}",
        "file-info": "انازه جانیا: $1, MIME type: $2",
-       "file-info-size": "$1 × $2 پیکسل, ٱندازٱ فایل: $3, MIME نوع: $4",
+       "file-info-size": "$1 × $2 پیکسل, ٱندازٱ فایلٛ: $3, MIME نوع: $4",
        "file-info-size-pages": "$1 × $2 pixels, ٱندازٱ جانؽا: $3, MIME type: $4, $5 {{PLURAL:$5|بٱلگٱ|بٱلگٱیا}}",
-       "file-nohires": "عٱسک ڤٱن بالاترؽ دش نؽ",
+       "file-nohires": "عٱسگ ڤٱن بالاترؽ دش نؽ",
        "svg-long-desc": "جانؽا اْس ڤی جی, نومی $1 × $2 پیکسل, ٱندازٱ جانؽا: $3",
        "svg-long-desc-animated": "جانیا جمشدار اس وی جی .نومنا $1 × $2 پيكسل،انازه جانیا:$3",
        "svg-long-error": "جانیا اس وی جی نامعتور:$1",
        "yesterday-at": "دیرو د $1",
        "bad_image_list": "دونسمنديانه وه ای شلگ وارد بكيت:\n\nفقط سرخط يایی که وا * شرو بوئن د وير گرته بوئن. اولی چسب ون مئن هر سرخط، باید چسب ونی وه یک عسگ گن با.\nچسب ونيا نيایی د همو سرخط، وه عنوان چيا استثنادار د وير گرته بوئن",
        "metadata": "رسینٱیا فرٱ گٱپ",
-       "metadata-help": "اؽ فایلٛ شامل دونسمنیا هنی یٱ.شایت د دیر بیین رقٱم ڤٱن یا اسکنری کاْ سی راس کردنشو اْستفادٱ بیٱ، ڤ ایچاْ اْزاف بیٱ",
+       "metadata-help": "اؽ فایلٛ شامل دونسمنیا هنی یٱ.شایٱت د دیر بیئن رقٱم ڤٱن یا اْسکٱنرؽ کاْ سی دۏرس کردنشو اْستفادٱ بیٱ، ڤ ایچاْ اْزاف بیٱ",
        "metadata-expand": "نشو دئن جزییات دمادیسگری",
        "metadata-collapse": "قام کردن جزییات دمادیسگری",
        "metadata-fields": "رشتٱیا یا گٱپ دونسمنیا کاْ د اؽ پاٛغوم نومگٱ کاری بینٱ د ڤٱر گرتٱ بٱلگٱ عٱسک کاْ ڤٱختؽ جٱدڤٱل گٱپ دونسمنیا ڤاز مۊئٱ نشوݩ داٛیٱ بۊئٱن.\nچی یا هنی سی یٱ کا پیش فٱرزٱن قایم مۊئٱن.\n*راس کو\n*مودل\n*دم ڤٱخت ٱسل\n*ڤٱخت آشگار\n*اْف اْن شمارٱ\n*ایزو نرخ من سرعت\n*فوکالنس\n*هونٱرمٱن\n*کوپی رایت\n*هالٱت جی پی اْس \n*جی پی اْس گٱپ هالٱت\n*جی پی اْس هٱمٱ هالٱت",
        "metadata-langitem": "<strong>$2:</strong> $1",
        "metadata-langitem-default": "$1",
        "namespacesall": "همٱشو",
-       "monthsall": "Ù\87Ù±Ù\85Ù±",
+       "monthsall": "همٱ",
        "confirmemail": "پشت راس کردن تیرنشون انجومانامه",
        "confirmemail_noemail": "شما د بلگه [[Special:Preferences|ترجیحات کاریاری]] خوتو یه گل تیرنشون انجومانامه نامعتور نه دئیته.",
        "confirmemail_text": "ای ویکی، شما نه مژبور می که وه پشت راسکاری تیرنشون انجومانامه خوتو، دما د یه که خدمات انجومانامه نه وه کار د ایچه وه کار بئیریت می که.دگمه هاری نه کنشتیار بکیت تا یه گل انجومانامه پشت راسکاری سی تیرنشون انجومانامه شما کل بوئه. ای انجومانامه د ور گرته یه گل رازینه ئه. هوم پیوند نه د دوارته نیئر خوتو واز بکیت تا تیرنشون انجومانامه تو پشت راسکاری با.",
        "watchlistedit-too-many": "ایچه بلگه یا فره ای سی نشو دئن هئ.",
        "watchlisttools-clear": "پاک کردن ساٛلٛ بٱرگ",
        "watchlisttools-view": "آلشتؽا مٱربوت ناْ بونؽت",
-       "watchlisttools-edit": "ساٛلٛ بٱرگ بوینؽتو ۉ ڤیرایش بٱکؽت",
+       "watchlisttools-edit": "ساٛلٛ بٱرگ ناْ باٛینؽتو ۉ ڤیرایش بٱکؽت",
        "watchlisttools-raw": "ساٛلٛ بٱرگ ناْ ردیفی ڤیرایش کو",
        "iranian-calendar-m1": "فروردین",
        "iranian-calendar-m2": "اردیبهشت",
index aac9a3d..6d328e6 100644 (file)
        "histfirst": "Senākās",
        "histlast": "Jaunākās",
        "historysize": "({{PLURAL:$1|$1 baiti|1 baits|$1 baiti}})",
-       "historyempty": "(tukša)",
+       "historyempty": "tukša",
        "history-feed-title": "Versiju hronoloģija",
        "history-feed-description": "Šīs wiki lapas versiju hronoloģija",
        "history-feed-item-nocomment": "$1 : $2",
        "delete-confirm": "Dzēst \"$1\"",
        "delete-legend": "Dzēšana",
        "historywarning": "'''Brīdinājums:''' Lapai, ko tu gatavojies dzēst, ir vēsture ar aptuveni $1 {{PLURAL:$1|versijām|versiju|versijām}}:",
-       "historyaction-submit": "Rādīt",
+       "historyaction-submit": "Rādīt versijas",
        "confirmdeletetext": "Tu tūlīt no datubāzes dzēsīsi lapu vai attēlu, kā arī to iepriekšējās versijas. Lūdzu, apstiprini, ka tu tiešām to vēlies darīt, ka tu apzinies sekas un ka tu to dari saskaņā ar [[{{MediaWiki:Policy-url}}|vadlīnijām]].",
        "actioncomplete": "Darbība pabeigta",
        "actionfailed": "Darbība neizdevās",
index c7ce646..b6cfd45 100644 (file)
@@ -45,7 +45,7 @@
        "tog-watchrollback": "Додај ги страниците сум ги отповикал во набљудувани",
        "tog-minordefault": "Обележувај ги сите уредувања како ситни по основно",
        "tog-previewontop": "Прикажи го прегледот пред кутијата за уредување",
-       "tog-previewonfirst": "Ð\9fÑ\80икажи Ð¿Ñ\80еглед Ð½Ð° првото уредување",
+       "tog-previewonfirst": "Ð\9fÑ\80икажи Ð¿Ñ\80еглед Ð¿Ñ\80и првото уредување",
        "tog-enotifwatchlistpages": "Испраќај ми е-пошта при промена на страница или податотека од мојот список на набљудувања",
        "tog-enotifusertalkpages": "Испраќај ми е-пошта при промена на мојата разговорна страница",
        "tog-enotifminoredits": "Испраќај ми е-пошта и за ситни промени во страниците и податотеките",
@@ -53,7 +53,7 @@
        "tog-shownumberswatching": "Прикажи го бројот на корисници кои набљудуваат",
        "tog-oldsig": "Вашиот постоечки потпис:",
        "tog-fancysig": "Сметај го потписот за викитекст (без автоматска врска)",
-       "tog-uselivepreview": "Ð\9fÑ\80егледи Ð²Ð¾ Ð¶Ð¸Ð²Ð¾ без превчитување на страницата",
+       "tog-uselivepreview": "Ð\9fÑ\80икажÑ\83ваÑ\98 Ð¿Ñ\80егледи без превчитување на страницата",
        "tog-forceeditsummary": "Извести ме кога нема опис на промените",
        "tog-watchlisthideown": "Скриј мои уредувања од набљудуваните",
        "tog-watchlisthidebots": "Скриј ботовски уредувања од набљудуваните",
        "missingarticle-rev": "(измена#: $1)",
        "missingarticle-diff": "(разлика: $1, $2)",
        "readonly_lag": "Базата е автоматски заклучена додека помошните опслужувачи не се усогласат",
-       "nonwrite-api-promise-error": "HTTP-заглавиеÑ\82о â\80\9ePromise-Non-Write-API-Actionâ\80\9c Ð±ÐµÑ\88е Ð¸Ñ\81пÑ\80аÑ\82ено, Ð½Ð¾ Ð±Ð°Ñ\80аÑ\9aеÑ\82о Ð±ÐµÑ\88е Ñ\83паÑ\82ено ÐºÐ¾Ð½ Ð·Ð°Ð¿Ð¸Ñ\81ен Ð¼Ð¾Ð´Ñ\83л на API.",
+       "nonwrite-api-promise-error": "HTTP-заглавиеÑ\82о â\80\9ePromise-Non-Write-API-Actionâ\80\9c Ð±ÐµÑ\88е Ð¸Ñ\81пÑ\80аÑ\82ено, Ð½Ð¾ Ð±Ð°Ñ\80аÑ\9aеÑ\82о Ð±ÐµÑ\88е Ñ\83паÑ\82ено ÐºÐ¾Ð½ Ð¼Ð¾Ð´Ñ\83л Ð·Ð° Ð¿Ð¸Ñ\88Ñ\83ваÑ\9aе на API.",
        "internalerror": "Внатрешна грешка",
        "internalerror_info": "Внатрешна грешка: $1",
        "internalerror-fatal-exception": "Кобен исклучок на типот „$1“",
        "blankarticle": "<strong>Предупредување:</strong> Страницата што ја создавате е празна.\nАко повторно стиснете на „$1“, страницата ќе биде создадена без никаква содржина во неа.",
        "anoneditwarning": "<strong>Предупредување:</strong> Не сте најавени. Вашата IP-адреса ќе биде јавно видлива ако уредувате. Ако <strong>[$1 се најавите]</strong> или <strong>[$2 направите сметка]</strong>, тогаш уредувањата ќе се припишуваат на вашето корисничко име, покрај другите погодности.",
        "anonpreviewwarning": "''Не сте најавени. Ако ја зачувате, Вашата IP-адреса ќе биде заведена во историјата на уредување на страницата.''",
-       "missingsummary": "'''Потсетник:''' Не внесовте опис на измените. Ако притиснете Зачувај повторно, вашите измени ќе се зачуваат без опис.",
+       "missingsummary": "<strong>Потсетник:</strong> Не внесовте опис на измените.\nАко притиснете „$1“ повторно, вашите измени ќе се зачуваат без опис.",
        "selfredirect": "<strong>Предупредување:</strong> Создавате пренасочување кон истата статија.\nМоже да сте укажале грешна целна страница, или пак уредувате погрешна страница.\nАко стиснете на „$1“ повторно, тогаш пренасочувањето бездруго ќе се создаде.",
        "missingcommenttext": "Внесете коментар.",
        "missingcommentheader": "<strong>Потсетување:</strong> Не внесовте наслов за овој коментар.\nАко повторно стиснете на „$1“, уредувањето ќе биде зачувано без наслов.",
        "page_first": "прв",
        "page_last": "последен",
        "histlegend": "Разлика помеѓу преработките: Означете ги преработките што сакате да ги споредите и притиснете на Enter или копчето на дното од страницата.<br />\nЛегенда: '''({{int:cur}})''' = разлика со последна преработка, '''({{int:last}})''' = разлика со претходна преработка, '''{{int:minoreditletter}}''' = ситна промена.",
-       "history-fieldset-title": "Ð\9fÑ\80ебарај преработки",
+       "history-fieldset-title": "ФилÑ\82Ñ\80ирај преработки",
        "history-show-deleted": "Само избришани преработки",
        "histfirst": "најстари",
        "histlast": "најнови",
        "revdelete-suppress": "Притајувај податоци и од администраторите",
        "revdelete-unsuppress": "Отстрани ограничувања на обновени преработки",
        "revdelete-log": "Причина:",
-       "revdelete-submit": "Ð\9fÑ\80имени Ð²Ñ\80з {{PLURAL:$1|одбÑ\80ана Ð¿Ñ\80еÑ\80абоÑ\82ка|одбÑ\80ани преработки}}",
+       "revdelete-submit": "Ð\9fÑ\80имени Ð²Ñ\80з {{PLURAL:$1|избÑ\80анаÑ\82а Ð¿Ñ\80еÑ\80абоÑ\82ка|избÑ\80аниÑ\82е преработки}}",
        "revdelete-success": "Видливоста на преработката е изменета.",
        "revdelete-failure": "'''Видливоста на преработката не можеше да се измени:'''\n$1",
        "logdelete-success": "Дневникот на видливост е поставен.",
        "right-suppressredirect": "Не прави пренасочување од старото име при преместување на страница",
        "right-upload": "Подигни податотеки",
        "right-reupload": "Заменување на постоечки податотеки",
-       "right-reupload-own": "Преснимување на постоечка податотека подигната од вас",
+       "right-reupload-own": "Презапишување врз постоечка податотека подигната од вас",
        "right-reupload-shared": "Презапис на едни податотеки врз други на заедничкото мултимедијално складиште месно",
        "right-upload_by_url": "Подигање на податотека од URL-адреса",
        "right-purge": "Бришење на меѓусклад на страница",
        "right-block": "Оневозможување на останати корисници да уредуваат",
        "right-blockemail": "Оневозможување корисници да праќаат е-пошта",
        "right-hideuser": "Блокирање корисници, сокривање од јавноста",
-       "right-ipblock-exempt": "Заобиколување на IP блокирања, авто-блокирања и блокирања на IP рангови",
+       "right-ipblock-exempt": "Заобиколување на IP-блокови, автоблокови и опсежни блокови",
        "right-unblockself": "Сопствено одблокирање",
        "right-protect": "Менување на степени на заштита и уредување на каскадно заштитени страници",
        "right-editprotected": "Уредување на страници заштитени како „{{int:protect-level-sysop}}“",
        "action-changetags": "додавање и отстранување на произволни ознаки во поединечни преработки и дневнички записи",
        "action-deletechangetags": "бришење ознаки од базата",
        "action-purge": "превчитување на оваа страница",
+       "action-apihighlimits": "користење на помалку ограничени барања од извршникот",
+       "action-autoconfirmed": "без ограничувања на стапки за IP-адреса",
+       "action-bigdelete": "бришење на страници со долга историја",
+       "action-blockemail": "оневозможување корисници да праќаат е-пошта",
+       "action-bot": "бидете третирани како автоматски процес",
+       "action-editprotected": "уредување на страници заштитени како „{{int:protect-level-sysop}}“",
+       "action-editsemiprotected": "уредување на страници заштитени како „{{int:protect-level-autoconfirmed}}“",
+       "action-editinterface": "уредување на корисничкиот посредник",
+       "action-editusercss": "уредување на CSS-податотеки на други корисници",
+       "action-edituserjson": "уредување на JSON-податотеките на други корисници",
+       "action-edituserjs": "уредување на JavaScript-податотеки на други корисници",
+       "action-editsitecss": "уредување на CSS за цело вики",
+       "action-editsitejson": "уредување на JSON за цело вики",
+       "action-editsitejs": "уредување на JavaScript за цело вики",
+       "action-editmyusercss": "уредување на сопствените кориснички каскадни стилски податотеки (CSS)",
+       "action-editmyuserjson": "уредување на сопствените кориснички JSON-податотеки",
+       "action-editmyuserjs": "уредување на сопствените кориснички податотеки со JavaScript",
+       "action-viewsuppressed": "преглед на преработки скриени од било кој корисник",
+       "action-hideuser": "блокирање корисници, сокривање од јавноста",
+       "action-ipblock-exempt": "заобиколување на IP-блокови, автоблокови и опсежни блокови",
+       "action-unblockself": "блокирање на самите себеси",
+       "action-noratelimit": "да не бидете засегнати од временски ограничувања на уредување",
+       "action-reupload-own": "презапишување врз постоечка податотека подигната од вас",
+       "action-nominornewtalk": "ситните уредувања на разговорни страници да не поттикнуваат потсетник за нова порака",
+       "action-markbotedits": "означување на вратени уредувања како ботовски уредувања",
+       "action-patrolmarks": "преглед на одбележаните патролирања на скорешните промени",
+       "action-override-export-depth": "извезување на страници вклучувајќи поврзани страници со продорност до 5",
+       "action-suppressredirect": "не се прави пренасочување од старото име при преместување на страница",
        "nchanges": "$1 {{PLURAL:$1|промена|промени}}",
        "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|од последната посаета}}",
        "enhancedrc-history": "историја",
        "upload-tryagain": "Поднеси изменет опис на податотеката",
        "upload-tryagain-nostash": "Поднеси преподигната податотека и изменет опис",
        "uploadnologin": "Не сте најавени",
-       "uploadnologintext": "Ð\9cоÑ\80а Ð´Ð° Ñ\81Ñ\82е $1 Ð·Ð° Ð´Ð° Ð¼Ð¾Ð¶ÐµÑ\82е Ð´Ð° Ð¿Ð¾Ð´Ð¸Ð³Ð°Ñ\82е.",
+       "uploadnologintext": "Ð\9cоÑ\80а Ð´Ð° Ñ\81Ñ\82е $1 Ð·Ð° Ð´Ð° Ð¿Ð¾Ð´Ð¸Ð³Ð½Ñ\83ваÑ\82е Ð¿Ð¾Ð´Ð°Ñ\82оÑ\82еки.",
        "upload_directory_missing": "Папката за подигање на слики ($1) не постои и не може да биде создадена од опслужувачот.",
        "upload_directory_read_only": "Опслужувачот не може да запишува во именикот за подигање ($1).",
        "uploaderror": "Грешка во подигањето",
        "upload-preferred": "{{PLURAL:$2|Претпочитан податотечен тип|Претпочитани податотечни типови}}: $1.",
        "upload-prohibited": "{{PLURAL:$2|Недозволен податотечен тип|Недозволени податотечни типови}}: $1.",
        "uploadlogpage": "Дневник на подигања",
-       "uploadlogpagetext": "Ð\9dаведен Ðµ список на најновите подигања на податотеки.\nПогледнете ја [[Special:NewFiles|галеријата на нови податотеки]] за нагледен преглед.",
+       "uploadlogpagetext": "Ð\9fодолÑ\83 Ðµ Ð½Ð°Ð²ÐµÐ´ÐµÐ½ список на најновите подигања на податотеки.\nПогледнете ја [[Special:NewFiles|галеријата на нови податотеки]] за нагледен преглед.",
        "filename": "Име на податотеката",
        "filedesc": "Опис",
        "fileuploadsummary": "Опис:",
        "ignorewarning": "Занемари ги предупредувањата и зачувај ја податотеката",
        "ignorewarnings": "Занемари предупредувања",
        "minlength1": "Името на податотеката мора да содржи барем една буква.",
-       "illegalfilename": "Името на податотеката „$1“ содржи знаци што не се дозволени во наслови на страници.\nПреименувајте ја подигнете ја повторно.",
+       "illegalfilename": "Ð\98меÑ\82о Ð½Ð° Ð¿Ð¾Ð´Ð°Ñ\82оÑ\82екаÑ\82а â\80\9e$1â\80\9c Ñ\81одÑ\80жи Ð·Ð½Ð°Ñ\86и Ñ\88Ñ\82о Ð½Ðµ Ñ\81е Ð´Ð¾Ð·Ð²Ð¾Ð»ÐµÐ½Ð¸ Ð²Ð¾ Ð½Ð°Ñ\81лови Ð½Ð° Ñ\81Ñ\82Ñ\80аниÑ\86и.\nÐ\9fÑ\80еименÑ\83ваÑ\98Ñ\82е Ñ\98а Ð¿Ð¾Ð´Ð°Ñ\82оÑ\82екаÑ\82а Ð¸ Ð¿Ð¾Ð´Ð¸Ð³Ð½ÐµÑ\82е Ñ\98а Ð¿Ð¾Ð²Ñ\82оÑ\80но.",
        "filename-toolong": "Имињата на податотеките не смеат да бидат подолги од 240 бајти.",
        "badfilename": "Името на податотеката е променето во „$1“.",
        "filetype-mime-mismatch": "Податотечната наставка „.$1“ не одговара на утврдениот MIME-тип на податотеката ($2).",
        "uncategorizedtemplates": "Некатегоризирани преуредувања",
        "uncategorized-categories-exceptionlist": " # Содржи список на категории кои не треба да се споменуваат во Special:UncategorizedCategories. По една во секој нов ред што почнува со „*“. Редовите што почнуваат со друг знак (заклучно со празни места) ќе се занемарат. Користете „#“ за прибелешки.",
        "unusedcategories": "Неискористени категории",
-       "unusedimages": "Неискористени слики",
+       "unusedimages": "Неискористени податотеки",
        "wantedcategories": "Потребни категории",
        "wantedpages": "Потребни страници",
        "wantedpages-summary": "Список на непостоечки страници со највеќе врски што водат до нив, исклучувајќи страниците до кои водат само пренасочувања. Список на непостоечки страници до кои водат пренасочувања ќе најдете на [[{{#special:BrokenRedirects}}|списокот на прекинати пренасочувања]].",
        "notanarticle": "Не е статија",
        "notvisiblerev": "Преработката била избришана",
        "watchlist-details": "Во вашите набљудувани имате {{PLURAL:$1|$1 страница|$1 страници}} (не броејќи ги разговорните страници).",
-       "wlheader-enotif": "Ð\98звеÑ\81Ñ\82Ñ\83ваÑ\9aеÑ\82о Ð¿Ð¾ Ðµ-поÑ\88Ñ\82а Ðµ Ð²ÐºÐ»Ñ\83Ñ\87ено.",
+       "wlheader-enotif": "Ð\98звеÑ\81Ñ\82Ñ\83ваÑ\9aеÑ\82о Ð¿Ð¾ Ðµ-поÑ\88Ñ\82а Ðµ Ð¾Ð²Ð¾Ð·Ð¼Ð¾Ð¶ено.",
        "wlheader-showupdated": "Страниците кои се променети од вашата последна посета се прикажани со <strong>задебелени</strong> букви.",
        "wlnote": "Подолу {{PLURAL:$1|е прикажана последната промена|се прикажани последните <strong>$1</strong> промени}} во {{PLURAL:$2|последниов час|последниве <strong>$2</strong> часа}}, заклучно со $3, $4 ч.",
-       "wlshowlast": "Прикажи ги последните $1 часа, $2 дена,",
+       "wlshowlast": "Прикажи ги последните $1 часа, $2 дена",
        "watchlist-hide": "Скриј",
        "watchlist-submit": "Прикажи",
        "wlshowtime": "Период за приказ:",
        "undeletepagetext": "{{PLURAL:$1|Следната страница била избришана но сè уште е во архивот и може да биде вратена.|Следните $1 страници биле избришани но сè уште се во архивот и можат да бидат вратени.}}\nАрхивот може периодично да се чисти.",
        "undelete-fieldset-title": "Врати преработки",
        "undeleteextrahelp": "За да вратите целосна историја на една страница, отштиклирајте ги сите полиња и притиснете на „'''{{int:undeletebtn}}'''“.\nЗа да извршите делумно враќање, штиклирајте ги соодветните преработки за враќање и притиснете на „'''{{int:undeletebtn}}'''“.",
-       "undeleterevisions": "{{PLURAL:$1|Избришана една преработка|Избришани $1 преработки}}",
+       "undeleterevisions": "$1 {{PLURAL:$1|преработка е избришана|преработки се избришани}}",
        "undeletehistory": "Ако ја обновите страницата, сите преработки ќе бидат вратени во историјата.\nАко нова страница со исто име е создадена по бришењето, обновените преработки ќе се појават во претходната историја.",
        "undeleterevdel": "Избришаното нема да биде вратено ако тоа значи дека со тоа најгорната страница или преработката на податотеката делумно ќе се избрише.\nВо такви случаи, морате да ја отштиклирате или откриете (ако е скриена) најновата избришана преработка.",
        "undeletehistorynoadmin": "Оваа статија е избришана. Причината за бришењето е наведена подолу,\nзаедно со информации за корисникот кој ја уредувал страницата пред бришењето. Целиот текст\nод избришаните верзии е достапен само за администраторите.",
        "block-prevent-edit": "Уредување",
        "block-reason": "Причина:",
        "block-target": "Корисничко име или IP-адреса:",
-       "unblockip": "Ð\94еблокирај корисник",
+       "unblockip": "Ð\9eдблокирај корисник",
        "unblockiptext": "Користете го долниот образец да го вратите правото на пишување на претходно блокирана IP-адреса или корисничко име.",
        "ipusubmit": "Избриши го ова блокирање",
        "unblocked": "[[User:$1|$1]] беше деблокиран",
        "proxyblocker": "Блокер на застапници (proxy)",
        "proxyblockreason": "Вашата IP-адреса е блокирана бидејќи претставува отворен застапник (proxy).\nВе молиме контактирајте со вашиот семрежен услужник и или техничката поддршка и информирајте ги за овој сериозен безбедносен проблем.",
        "sorbs": "DNSBL",
-       "sorbsreason": "Вашата IP-адреса е запишана како отворен застапник (proxy) во DNSBL кој го користи {{SITENAME}}..",
+       "sorbsreason": "Вашата IP-адреса е запишана како отворен застапник (прокси) во DNSBL кој го користи {{SITENAME}}.",
        "sorbs_create_account_reason": "Вашата IP-адреса е наведена како отворен застапник (прокси) во DNSBL користена од {{SITENAME}}.\nНе можете да создадете корисничка сметка.",
        "softblockrangesreason": "Анонимните придонеси не се дозволени од вашата IP-адреса ($1). Најавете се.",
        "xffblockreason": "Блокирана е IP-адреса присутна во заглавието X-Forwarded-For, која е ваша или на застапничкиот опслужувач што го користите. Наведеното образложение гласи: $1",
        "tags-deactivate-question": "На пат сте да ја деактивирате ознаката „$1“.",
        "tags-deactivate-reason": "Причина:",
        "tags-deactivate-not-allowed": "Не можам да ја деактивирам ознаката „$1“.",
-       "tags-deactivate-submit": "Ð\94екативирај",
+       "tags-deactivate-submit": "Ð\94еактивирај",
        "tags-apply-no-permission": "Немате дозвола да ставате ознаки за промени заедно со измените што ги правите.",
        "tags-apply-blocked": "Не можете да задавате ознаки за промени додека {{GENDER:$1|сте}} блокирани.",
        "tags-apply-not-allowed-one": "Не е дозволено ознаката „$1“ да се става рачно.",
        "tags-edit-manage-link": "Раководство со ознаки",
        "tags-edit-revision-selected": "{{PLURAL:$1|Одбрана преработка|Одбрани преработки}} на [[:$2]]:",
        "tags-edit-logentry-selected": "{{PLURAL:$1|Одбран настан од дневник|Одбрани настани од дневник}}:",
-       "tags-edit-revision-legend": "Ð\94одаÑ\98Ñ\82е Ð¸Ð»Ð¸ Ð¾Ñ\82Ñ\81Ñ\82Ñ\80анеÑ\82е Ð¾Ð·Ð½Ð°ÐºÐ¸ Ð¾Ð´ {{PLURAL:$1|пÑ\80еÑ\80абоÑ\82кава|сите $1 преработки}}",
+       "tags-edit-revision-legend": "Ð\94одаÑ\98Ñ\82е Ð¸Ð»Ð¸ Ð¾Ñ\82Ñ\81Ñ\82Ñ\80анеÑ\82е Ð¾Ð·Ð½Ð°ÐºÐ¸ Ð¾Ð´ {{PLURAL:$1|оваа Ð¿Ñ\80еÑ\80абоÑ\82ка|сите $1 преработки}}",
        "tags-edit-logentry-legend": "Додајте или отстранете ознаки од {{PLURAL:$1|овој дневнички запис|сите $1 дневнички записи}}",
        "tags-edit-existing-tags": "Постоечки ознаки",
        "tags-edit-existing-tags-none": "<em>Нема</em>",
        "tags-edit-new-tags": "Нови ознаки:",
-       "tags-edit-add": "Додај ги следниве ознаки:",
-       "tags-edit-remove": "Отстрани ги следниве ознаки:",
+       "tags-edit-add": "Додај ги овие ознаки:",
+       "tags-edit-remove": "Отстрани ги овие ознаки:",
        "tags-edit-remove-all-tags": "(отстрани ги сите ознаки)",
        "tags-edit-chosen-placeholder": "Одберете некои ознаки",
        "tags-edit-chosen-no-results": "Не пронајдов одговарачки ознаки",
        "log-action-filter-suppress-event": "Притајување дневник",
        "log-action-filter-suppress-revision": "Притајување преработка",
        "log-action-filter-suppress-delete": "Притајување страница",
-       "log-action-filter-suppress-block": "Притајување на корисникот преку блокирање",
-       "log-action-filter-suppress-reblock": "Притајување на корисникот преку преблокирање",
+       "log-action-filter-suppress-block": "Притајување на корисник со блокирање",
+       "log-action-filter-suppress-reblock": "Притајување на корисник со повторно блокирање",
        "log-action-filter-upload-upload": "Ново подигање",
        "log-action-filter-upload-overwrite": "Преподигање",
        "log-action-filter-upload-revert": "Отповикај",
index 50dfe18..6d8f5e6 100644 (file)
        "copyrightwarning": "{{SITENAME}} တွင် ရေးသားမှုအားလုံးကို $2 အောက်တွင် ဖြန့်ဝေရန် ဆုံးဖြတ်ပြီး ဖြစ်သည်ကို ကျေးဇူးပြု၍ သတိပြုပါ။။ (အသေးစိတ်ကို $1 တွင်ကြည့်ပါ။)\nအကယ်၍ သင့်ရေးသားချက်များကို အညှာအတာမရှိ တည်းဖြတ်ခံရခြင်း၊ စိတ်တိုင်းကျ ဖြန့်ဝေခံရခြင်းတို့ကို အလိုမရှိပါက ဤနေရာတွင် မတင်ပါနှင့်။<br />\nသင်သည် ဤဆောင်းပါးကို သင်ကိုယ်တိုင်ရေးသားခြင်း၊ သို့မဟုတ် အများပြည်သူဆိုင်ရာဒိုမိန်းများ၊ ယင်းကဲ့သို့ လွတ်လပ်သည့် ရင်းမြစ်မှ ကူးယူထားခြင်း ဖြစ်ကြောင်းလည်း ဝန်ခံ ကတိပြုပါသည်။\n<strong>မူပိုင်ခွင့်ရှိသော စာ၊ပုံများကို ခွင့်ပြုချက်မရှိဘဲ မတင်ပါနှင့်။</strong>",
        "copyrightwarning2": "{{SITENAME}} တွင် ရေးသားမှုအားလုံးသည် အခြားပုံပိုးသူများ၏ တည်းဖြတ်၊ ပြောင်းလဲ၊ ဖယ်ရှားခံရနိုင်သည်ကို သတိပြုပါ။\nအကယ်၍ သင့်ရေးသားချက်များကို အညှာအတာမရှိ တည်းဖြတ်ခံရခြင်း၊ စိတ်တိုင်းကျ ဖြန့်ဝေခံရခြင်းတို့ကို အလိုမရှိပါက ဤနေရာတွင် မတင်ပါနှင့်။<br />\nသင်သည် ဤဆောင်းပါးကို သင်ကိုယ်တိုင်ရေးသားခြင်း၊ သို့မဟုတ် အများပြည်သူဆိုင်ရာဒိုမိန်းများ၊ ယင်းကဲ့သို့ လွတ်လပ်သည့် ရင်းမြစ်မှ ကူးယူထားခြင်း ဖြစ်ကြောင်းလည်း ဝန်ခံ ကတိပြုပါသည် (အသေးစိတ်ကို $1 တွင်ကြည့်ပါ)။\n<strong>မူပိုင်ခွင့်ရှိသော စာ၊ပုံများကို ခွင့်ပြုချက်မရှိဘဲ မတင်ပါနှင့်။</strong>",
        "editpage-cannot-use-custom-model": "ဤစာမျက်နှာ၏ မာတိကာမော်ဒယ်ကို မပြောင်းလဲနိုင်ခဲ့ပါ။",
+       "readonlywarning": "<strong>သတိပေးချက်: ပြုပြင်ထိန်းသိမ်းရေးအတွက် ဒေတာဘေ့စ်ကို ပိတ်ထားလိုက်ပါပြီ၊ ယခုလက်ရှိ တည်းဖြတ်နိုင်တာ့မည် မဟုတ်ပါ။</strong>\n\n\nယင်းအားပိတ်ခဲ့သည့် စနစ်အက်ဒမင်က ဤရှင်းပြချက်ကို ပေးထားပါသည်: $1",
        "protectedpagewarning": "<strong>သတိပေးချက်။ ဤစာမျက်နှာအား စီမံခန့်ခွဲသူအဆင့်ရှိသူများသာ ပြင်ဆင်နိုင်ရန် ကာကွယ်ထားသည်။</strong>\nနောက်ဆုံးမှတ်တမ်းအား ကိုးကားနိုင်ရန် အောက်တွင် ဖော်ပြထားသည်။",
        "semiprotectedpagewarning": "<strong>မှတ်ချက်။</strong> ဤစာမျက်နှာအား အလိုအလျောက် အတည်ပြုထားသော အသုံးပြုသူအဆင့်ရှိသူများသာ တည်းဖြတ်နိုင်ရန် ကာကွယ်ထားသည်။\nနောက်ဆုံးမှတ်တမ်းအား ကိုးကားနိုင်ရန် အောက်တွင် ဖော်ပြထားသည်။",
        "titleprotectedwarning": "<strong>သတိပေးချက်။ ဤစာမျက်နှာကို ကာကွယ်ထားပြီး ဖန်တီးနိုင်ရန်အတွက် [[Special:ListGroupRights|အထူး အခွင့်အရေးများ]]ရှိထားရန် လိုအပ်သည်။</strong>\nနောက်ဆုံးမှတ်တမ်းအား ကိုးကားနိုင်ရန် အောက်တွင် ဖော်ပြထားသည်။",
index e0afd43..1786795 100644 (file)
        "page_first": "første",
        "page_last": "siste",
        "histlegend": "Valg av diff: merk i radioboksene de revisjonene du ønsker å sammenligne og trykk enter eller knappen nederst på siden.<br />\nForklaring: '''({{int:cur}})''' = forskjell fra nåværende revisjon, '''({{int:last}})''' = forskjell fra foregående revisjon, '''{{int:minoreditletter}}''' = mindre endring.",
-       "history-fieldset-title": "Søk etter revisjoner",
+       "history-fieldset-title": "Filtrer revisjoner",
        "history-show-deleted": "Kun slettede revisjoner",
        "histfirst": "eldste",
        "histlast": "nyeste",
        "historysize": "({{PLURAL:$1|1 byte|$1 byte}})",
-       "historyempty": "(tom)",
+       "historyempty": "tom",
        "history-feed-title": "Revisjonshistorikk",
        "history-feed-description": "Revisjonshistorikk for denne siden",
        "history-feed-item-nocomment": "$1 på $2",
        "right-reupload-own": "Skrive over egne filer",
        "right-reupload-shared": "Skrive over delte filer lokalt",
        "right-upload_by_url": "Laste opp en fil via URL",
-       "right-purge": "Rense mellomlageret for sider",
+       "right-purge": "Rens mellomlageret for en side",
        "right-autoconfirmed": "Redigere halvlåste sider",
        "right-bot": "Bli behandlet som en automatisk prosess",
        "right-nominornewtalk": "Får ikke «Du har nye meldinger»-beskjeden ved mindre endringer på diskusjonsside",
        "action-changetags": "legg til og fjern vilkårlige merker på individuelle revisjoner og loggposter",
        "action-deletechangetags": "slette tagger fra databasen",
        "action-purge": "gjenoppfriske denne siden",
+       "action-apihighlimits": "bruke høyere grenser for API-spørringer",
+       "action-autoconfirmed": "ikke bli påvirket av IP-baserte hastighetsbegrensninger",
+       "action-bigdelete": "slette sider med lang historikk",
+       "action-blockemail": "blokkere en bruker fra å sende e-post",
+       "action-bot": "bli behandlet som en automatisert prosess",
+       "action-editprotected": "redigere sider som er beskyttet som «{{int:protect-level-sysop}}»",
+       "action-editsemiprotected": "redigere sider som er beskyttet som «{{int:protect-level-autoconfirmed}}»",
+       "action-editinterface": "redigere brukergrensesnittet",
+       "action-editusercss": "redigere andre brukeres CSS-filer",
+       "action-edituserjson": "redigere andre brukeres JSON-filer",
+       "action-edituserjs": "redigere andre brukeres JavaScript-filer",
+       "action-editsitecss": "redigere CSS som påvirker hele nettstedet",
+       "action-editsitejson": "redigere JSON som påvirker hele nettstedet",
+       "action-editsitejs": "redigere JavaScript som påvirker hele nettstedet",
+       "action-editmyusercss": "redigere dine egne CSS-filer",
+       "action-editmyuserjson": "redigere dine egne JSON-filer",
+       "action-editmyuserjs": "redigere dine egne JavaScript-filer",
+       "action-viewsuppressed": "viser skjulte revisjoner",
+       "action-hideuser": "blokkere et brukernavn og skjule det fra offentligheten",
+       "action-ipblock-exempt": "omgå IP-blokkeringer, autoblokkeringer og rekkeblokkeringer",
+       "action-unblockself": "avblokkere deg selv",
+       "action-noratelimit": "ikke bli påvirket av hastighetsbegrensninger",
+       "action-reupload-own": "overskrive eksisterende filer lastet opp av deg selv",
+       "action-nominornewtalk": "hindre at mindre redigeringer på diskusjonssider utløser spørsmål om nye meldinger",
+       "action-markbotedits": "markere tilbakestillinger som botredigeringer",
+       "action-patrolmarks": "vise patruljeringsmerker i siste endringer",
+       "action-override-export-depth": "eksportere sider inkludert lenkede sider til og med en lenkedybde på 5",
+       "action-suppressredirect": "ikke opprette omdirigeringer fra kildesider ved flytting av sider",
        "nchanges": "$1 {{PLURAL:$1|endring|endringer}}",
        "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|siden forrige besøk}}",
        "enhancedrc-history": "historikk",
        "delete-confirm": "Slett «$1»",
        "delete-legend": "Slett",
        "historywarning": "<strong>Advarsel:</strong> Siden du er i ferd med å slette har en historikk med $1 {{PLURAL:$1|revisjon|revisjoner}}:",
-       "historyaction-submit": "Vis",
+       "historyaction-submit": "Vis revisjoner",
        "confirmdeletetext": "Du holder på å slette en side sammen med historikken.\nBekreft at du virkelig vil slette denne siden, at du forstår konsekvensene og at du gjør det i samsvar med [[{{MediaWiki:Policy-url}}|retningslinjene]].",
        "actioncomplete": "Gjennomført",
        "actionfailed": "Handling mislyktes",
        "deleting-backlinks-warning": "<strong>Advarsel:</strong> [[Special:WhatLinksHere/{{FULLPAGENAME}}|Andre sider]] lenker til eller inkluderer siden du er i ferd med å slette.",
        "deleting-subpages-warning": "<strong>Advarsel:</strong> Siden du er i ferd med å slette har [[Special:PrefixIndex/{{FULLPAGENAME}}/|{{PLURAL:$1|en underside|$1 undersider|51=over 50 undersider}}]].",
        "rollback": "Fjern redigeringer",
+       "rollback-confirmation-confirm": "Vennligst bekreft:",
+       "rollback-confirmation-yes": "Tilbakestill",
+       "rollback-confirmation-no": "Avbryt",
        "rollbacklink": "tilbakestill",
        "rollbacklinkcount": "tilbakestill {{PLURAL:$1|én endring|$1 endringer}}",
        "rollbacklinkcount-morethan": "tilbakestill mer enn $1 {{PLURAL:$1|endring|endringer}}",
        "mycontris": "Bidrag",
        "anoncontribs": "Bidrag",
        "contribsub2": "For {{GENDER:$3|$1}} ($2)",
+       "contributions-subtitle": "For {{GENDER:$3|$1}}",
        "contributions-userdoesnotexist": "Brukerkontoen «$1» er ikke registrert.",
        "negative-namespace-not-supported": "Navnerom med negative verdier støttes ikke.",
        "nocontribs": "Ingen endringer er funnet som passer disse kriteriene.",
        "confirm-unwatch-top": "Fjern denne siden fra overvåkningslisten din?",
        "confirm-rollback-button": "OK",
        "confirm-rollback-top": "Tilbakestill redigeringer på denne siden?",
+       "confirm-rollback-bottom": "Dette vil tilbakestille de valgte endringene til denne siden umiddelbart.",
        "confirm-mcrrestore-title": "Gjenopprett en revisjon",
        "confirm-mcrundo-title": "Fjern en endring",
        "mcrundofailed": "Fjerning mislyktes",
index f1ec37d..cb7179e 100644 (file)
        "mediastatistics-header-audio": "Audio",
        "mediastatistics-header-video": "Video's",
        "mediastatistics-header-multimedia": "Interaktieve media",
-       "special-characters-group-latin": "Latien",
-       "special-characters-group-latinextended": "Latien uutgebreid",
-       "special-characters-group-ipa": "Internasionaal Klankeschrift",
+       "special-characters-group-latin": "Latyn",
+       "special-characters-group-latinextended": "Latyn üütgebreided",
+       "special-characters-group-ipa": "Internationål Fonetisk Alfabet",
        "special-characters-group-symbols": "Symbolen",
-       "special-characters-group-greek": "Grieks",
-       "special-characters-group-cyrillic": "Kyrillies",
-       "special-characters-group-arabic": "Arabies",
-       "special-characters-group-arabicextended": "Arabies uutgebreid",
+       "special-characters-group-greek": "Gryksk",
+       "special-characters-group-greekextended": "Gryksk üütgebreided",
+       "special-characters-group-cyrillic": "Kyrillisk",
+       "special-characters-group-arabic": "Arabisk",
+       "special-characters-group-arabicextended": "Arabisk üütgebreided",
        "special-characters-group-persian": "Perzies",
-       "special-characters-group-hebrew": "Hebreeuws",
-       "special-characters-group-bangla": "Bengaals",
+       "special-characters-group-hebrew": "Hebreywsk",
+       "special-characters-group-bangla": "Bengaalsk",
        "special-characters-group-tamil": "Tamil",
        "special-characters-group-telugu": "Telugu",
-       "special-characters-group-sinhala": "Singalees",
+       "special-characters-group-sinhala": "Singalääsk",
        "special-characters-group-gujarati": "Gujarati",
        "special-characters-group-devanagari": "Devanagari",
-       "special-characters-group-thai": "Thai",
-       "special-characters-group-lao": "Laotiaans",
+       "special-characters-group-thai": "Taisk",
+       "special-characters-group-lao": "Laotiaansk",
        "special-characters-group-khmer": "Khmer",
+       "special-characters-group-canadianaboriginal": "Kanadääsk lettergreapenskrivt",
        "special-characters-title-endash": "liggend streepjen",
        "special-characters-title-emdash": "gedachtenstreepjen",
        "special-characters-title-minus": "minteken",
index 489fba6..70956e3 100644 (file)
        "right-browsearchive": "Verwijderde pagina's zoeken",
        "right-undelete": "Verwijderde pagina's terugplaatsen",
        "right-suppressrevision": "Specifieke versies bekijken, verbergen en weer zichtbaar maken op pagina's van elke gebruiker",
-       "right-viewsuppressed": "Versies verborgen door elke gebruiker bekijken",
+       "right-viewsuppressed": "Versies verborgen voor elke gebruiker bekijken",
        "right-suppressionlog": "Niet-openbare logboeken bekijken",
        "right-block": "Andere gebruikers de mogelijkheid ontnemen te bewerken",
        "right-blockemail": "Een gebruiker het recht ontnemen om e-mail te versturen",
        "right-hideuser": "Een gebruiker voor de overige gebruikers verbergen",
-       "right-ipblock-exempt": "IP-blokkades omzeilen",
+       "right-ipblock-exempt": "IP-blokkades, automatische blokkades en IP-bereik blokkades omzeilen",
        "right-unblockself": "Eigen gebruiker deblokkeren",
        "right-protect": "Beveiligingsniveaus wijzigen",
        "right-editprotected": "Pagina's bewerken die beveiligd zijn als \"{{int:protect-level-sysop}}\"",
        "action-changetags": "willekeurige labels toe te voegen aan en te verwijderen van versies en logboekregels",
        "action-deletechangetags": "labels uit de database te verwijderen",
        "action-purge": "deze pagina te verversen",
+       "action-apihighlimits": "de hogere limieten in API-zoekopdrachten te gebruiken",
+       "action-autoconfirmed": "uitgezonderd te zijn van IP-adresgebaseerde tijdsafhankelijke beperkingen",
+       "action-bigdelete": "pagina's met een grote geschiedenis te verwijderen",
+       "action-blockemail": "een gebruiker het recht te ontnemen om e-mail te versturen",
+       "action-bot": "behandeld te worden als een geautomatiseerd proces",
+       "action-editprotected": "pagina's bewerken die beveiligd zijn als \"{{int:protect-level-sysop}}\"",
+       "action-editsemiprotected": "pagina's bewerken die beveiligd zijn als \"{{int:protect-level-autoconfirmed}}\"",
+       "action-editinterface": "de gebruikersinterface te bewerken",
+       "action-editusercss": "de CSS-bestanden van andere gebruikers te bewerken",
+       "action-edituserjson": "de JSON-bestanden van andere gebruikers te bewerken",
+       "action-edituserjs": "de JavaScriptbestanden van andere gebruikers te bewerken",
+       "action-editmyusercss": "uw eigen CSS-pagina's te bewerken",
+       "action-editmyuserjson": "uw eigen JSON-pagina's te bewerken",
+       "action-editmyuserjs": "uw eigen JavaScriptpagina's te bewerken",
+       "action-viewsuppressed": "versies verborgen voor elke gebruiker te bekijken",
+       "action-hideuser": "een gebruiker voor de overige gebruikers te verbergen",
+       "action-ipblock-exempt": "IP-blokkades, automatische blokkades en IP-bereik blokkades te omzeilen",
+       "action-unblockself": "uzelf te deblokkeren",
+       "action-noratelimit": "tijdsafhankelijke beperkingen te negeren",
+       "action-reupload-own": "uw eigen bestandsuploads te overschrijven",
+       "action-nominornewtalk": "kleine bewerkingen aan een overlegpagina niet te laten leiden tot een melding 'nieuwe berichten'",
+       "action-markbotedits": "teruggedraaide bewerkingen te markeren als botbewerkingen",
+       "action-override-export-depth": "pagina's te exporteren, inclusief pagina's waarnaar verwezen wordt, tot een diepte van vijf",
+       "action-suppressredirect": "geen doorverwijzingen achter te laten bij het hernoemen van pagina's",
        "nchanges": "$1 {{PLURAL:$1|bewerking|bewerkingen}}",
        "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|sinds uw laatste bezoek}}",
        "enhancedrc-history": "geschiedenis",
index 0af2815..6e14ad1 100644 (file)
        "ipb-unblock": "Opphev blokkeringa av eit brukarnamn eller ei IP-adresse",
        "ipb-blocklist": "Vis gjeldande blokkeringar",
        "ipb-blocklist-contribs": "Bidrag frå $1",
+       "block-actions": "Handlingar som skal blokkerast:",
        "block-expiry": "Opphøyrstid:",
        "unblockip": "Opphev blokkering",
        "unblockiptext": "Bruk skjemaet nedanfor for å oppheve blokkeringa av ein tidlegare blokkert brukar.",
index aa6717e..2f13552 100644 (file)
        "page_first": "początek",
        "page_last": "koniec",
        "histlegend": "Wybór porównania – zaznacz kropeczkami dwie wersje do porównania i wciśnij enter lub przycisk ''Porównaj wybrane wersje''.<br />\nLegenda: (bież.) – pokaż zmiany od tej wersji do bieżącej,\n(poprz.) – pokaż zmiany od wersji poprzedzającej, m – mała (drobna) zmiana",
-       "history-fieldset-title": "Szukaj wersji",
+       "history-fieldset-title": "Filtruj wersje",
        "history-show-deleted": "Tylko usunięte edycje",
        "histfirst": "od najstarszych",
        "histlast": "od najnowszych",
        "right-reupload-shared": "Lokalne nadpisywanie pliku istniejącego w repozytorium mediów",
        "right-upload_by_url": "Przesyłanie plików z adresu URL",
        "right-purge": "Czyszczenie pamięci podręcznej stron",
-       "right-autoconfirmed": "Wyłączenie z ograniczeń prędkości zakładanych na IP",
+       "right-autoconfirmed": "Wyłączenie z ograniczeń przepustowości nałożonych na IP",
        "right-bot": "Oznaczanie edycji jako wykonanych automatycznie",
        "right-nominornewtalk": "Drobne zmiany na stronach dyskusji użytkowników nie włączają powiadomienia o nowej wiadomości",
        "right-apihighlimits": "Zwiększony limit w zapytaniach wykonywanych poprzez interfejs API",
        "action-changetags": "dodawania i usuwania dowolnych znaczników z poszczególnych wersji i wpisów w rejestrze",
        "action-deletechangetags": "usuwania znaczników z bazy danych",
        "action-purge": "wyczyść pamięć podręczną tej strony",
+       "action-apihighlimits": "używania zwiększonych limitów w zapytaniach wykonywanych poprzez interfejs API",
+       "action-autoconfirmed": "nie bycia objętym ograniczeniem przepustowości nałożonym na IP",
+       "action-bigdelete": "usuwania stron z długą historią edycji",
+       "action-blockemail": "blokowania użytkownikom możliwości wysyłania wiadomości",
+       "action-bot": "oznaczania edycji jako wykonanych automatycznie",
+       "action-editprotected": "edytowania stron zabezpieczonych na poziomie „{{int:protect-level-sysop}}”",
+       "action-editsemiprotected": "edytowania stron zabezpieczonych na poziomie „{{int:protect-level-autoconfirmed}}”",
+       "action-editinterface": "edytowania interfejsu użytkownika",
+       "action-editusercss": "edytowania plików CSS innych użytkowników",
+       "action-edituserjson": "edytowania plików JSON innych użytkowników",
+       "action-edituserjs": "edytowania plików JavaScript innych użytkowników",
+       "action-editsitecss": "edytowania plików CSS projektu",
+       "action-editsitejson": "edytowania plików JSON projektu",
+       "action-editsitejs": "edytowania plików JavaScript projektu",
+       "action-editmyusercss": "edytowania własnych plików CSS",
+       "action-editmyuserjson": "edytowania własnych plików JSON",
+       "action-editmyuserjs": "edytowania własnych plików JavaScript",
+       "action-viewsuppressed": "podglądu wersji ukrytych przed każdym użytkownikiem",
+       "action-hideuser": "blokowania użytkownika i ukrywania go od publiczności",
+       "action-ipblock-exempt": "obchodzenia blokad i blokad zakresów adresów IP",
+       "action-unblockself": "odblokowania samego siebie",
+       "action-noratelimit": "nie bycia objętym ograniczeniem przepustowości",
+       "action-reupload-own": "nadpisywania wcześniej przesłanych plików",
+       "action-nominornewtalk": "nie włączania powiadomień o nowej wiadomości na stronach dyskusji użytkowników poprzez wykonanie drobnej edycji",
+       "action-markbotedits": "oznaczania rewertu jako edycji bota",
+       "action-patrolmarks": "podglądu znaczników patrolowania ostatnich zmian",
+       "action-override-export-depth": "eksportowania stron wraz z linkowanymi do głębokości 5 linków",
+       "action-suppressredirect": "przenoszenia stron bez tworzenia przekierowania w miejscu starej nazwy",
        "nchanges": "$1 {{PLURAL:$1|zmiana|zmiany|zmian}}",
        "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|od ostatniej wizyty}}",
        "enhancedrc-history": "historia",
        "delete-confirm": "Usuwanie „$1”",
        "delete-legend": "Usuń",
        "historywarning": "<strong>Uwaga:</strong> Strona, którą chcesz usunąć, ma {{PLURAL:$1|jedną starszą wersję|$1 starsze wersje|$1 starszych wersji}}:",
-       "historyaction-submit": "Pokaż",
+       "historyaction-submit": "Pokaż wersje",
        "confirmdeletetext": "Zamierzasz usunąć stronę razem z całą dotyczącą jej historią.\nUpewnij się, czy na pewno chcesz to zrobić, że rozumiesz konsekwencje i że robisz to w zgodzie z [[{{MediaWiki:Policy-url}}|zasadami]].",
        "actioncomplete": "Operacja wykonana",
        "actionfailed": "Operacja się nie powiodła",
index f9ab222..1a1825a 100644 (file)
        "page_first": "primeira",
        "page_last": "última",
        "histlegend": "Como selecionar: marque as caixas de seleção das versões que deseja comparar e pressione enter ou clique no botão na parte inferior do formulário.<br />\nLegenda: <strong>({{int:cur}})</strong> = diferenças em relação a última versão, <strong>({{int:last}})</strong> = diferenças em relação a versão anterior, <strong>{{int:minoreditletter}}</strong> = edição menor.",
-       "history-fieldset-title": "Pesquisar revisões",
+       "history-fieldset-title": "Filtrar revisões",
        "history-show-deleted": "Apenas as revisões excluídas",
        "histfirst": "Mais antigas",
        "histlast": "Mais novas",
        "action-changetags": "adicionar e remover etiquetas arbitrárias em revisões e ''logs'' individuais",
        "action-deletechangetags": "deletar marcações da base de dados",
        "action-purge": "purgar esta página",
+       "action-apihighlimits": "usar limites mais altos em consultas da API",
+       "action-autoconfirmed": "não ser afetado por limites de taxa baseados em IP",
+       "action-bigdelete": "excluir páginas com grandes históricos",
+       "action-blockemail": "bloquear um usuário de enviar e-mail",
+       "action-bot": "ser tratado como um processo automatizado",
+       "action-editprotected": "editar páginas protegidas como \"{{int:protect-level-sysop}}\"",
+       "action-editsemiprotected": "editar páginas protegidas como \"{{int:protect-level-autoconfirmed}}\"",
+       "action-editinterface": "editar a interface do usuário",
+       "action-editusercss": "editar arquivos CSS de outros usuários",
+       "action-edituserjson": "editar arquivos JSON de outros usuários",
+       "action-edituserjs": "editar arquivos JavaScript de outros usuários",
+       "action-editsitecss": "editar CSS de todo o site",
+       "action-editsitejson": "editar JSON em todo o site",
+       "action-editsitejs": "edite JavaScript em todo o site",
+       "action-editmyusercss": "edite seus próprios arquivos CSS de usuário",
+       "action-editmyuserjson": "editar seus próprios arquivos JSON do usuário",
+       "action-editmyuserjs": "editar seus próprios arquivos JavaScript de usuário",
+       "action-viewsuppressed": "ver revisões ocultas de qualquer usuário",
+       "action-hideuser": "bloquear um nome de usuário, escondendo-o do público",
+       "action-ipblock-exempt": "ignorar bloqueios IP, bloqueios automáticos e bloqueios de alcance",
+       "action-unblockself": "desbloquear a si mesmo",
+       "action-noratelimit": "não ser afetado por limites de taxa",
+       "action-reupload-own": "sobrescrever os arquivos existentes carregados por você",
+       "action-nominornewtalk": "não ter edições menores nas páginas de discussão aciona o novo prompt de mensagens",
+       "action-markbotedits": "marcar edições revertidas como edições de robô",
+       "action-patrolmarks": "ver marcas de patrulha de mudanças recentes",
+       "action-override-export-depth": "Exportar páginas incluindo páginas ligadas até uma profundidade de 5",
+       "action-suppressredirect": "Não crie redirecionamentos de páginas de origem ao mover páginas",
        "nchanges": "$1 {{PLURAL:$1|alteração|alterações}}",
        "ntimes": "$1×",
        "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|desde a última visita}}",
        "delete-confirm": "Eliminar \"$1\"",
        "delete-legend": "Eliminar",
        "historywarning": "<strong>Aviso:</strong> A página que está prestes a eliminar tem um histórico com aproximadamente $1 {{PLURAL:$1|revisão|revisões}}:",
-       "historyaction-submit": "Exibir",
+       "historyaction-submit": "Mostrar revisões",
        "confirmdeletetext": "Encontra-se prestes a eliminar uma página juntamente com todo o seu histórico.\nPor favor, confirme que possui a intenção de fazer isto, que compreende as consequências e que encontra-se a fazer isto de acordo com as [[{{MediaWiki:Policy-url}}|políticas]] do projeto.",
        "actioncomplete": "Ação efetuada com sucesso",
        "actionfailed": "Falha na ação",
index a658900..c7f2733 100644 (file)
                        "The Discoverer",
                        "Bencemac",
                        "Zoranzoki21",
-                       "Woytecr"
+                       "Woytecr",
+                       "PiefPafPier"
                ]
        },
        "sidebar": "{{notranslate}}",
        "page_first": "This is part of the navigation message on the top and bottom of Special pages which are lists of things in alphabetical order, e.g. the '[[Special:Categories|Categories]]' special page. It is followed by the message {{msg-mw|Viewprevnext}}.\n{{Identical|First}}",
        "page_last": "This is part of the navigation message on the top and bottom of Special pages which are lists of things in alphabetical order, e.g. the '[[Special:Categories|Categories]]' special page. It is followed by the message {{msg-mw|Viewprevnext}}.\n\n{{Identical|Last}}",
        "histlegend": "Text in history page.\n\nSee also:\n* {{msg-mw|Cur}}\n* {{msg-mw|Last}}\n* {{msg-mw|Minoreditletter}}",
-       "history-fieldset-title": "Fieldset label in the edit history pages.",
+       "history-fieldset-title": "Form legend label in the edit history page.",
        "history-show-deleted": "CheckBox to show only per [[mw:Manual:RevisionDelete|RevisionDelete]] deleted versions.\n\nUsed in History and [[Special:Contributions]].",
        "history_copyright": "{{notranslate}}",
        "histfirst": "This is part of the navigation message on the top and bottom of Page History pages which are lists of things in date order, e.g. [{{canonicalurl:Support|action=history}} Page History of Support].\n\nIt is followed by the message {{msg-mw|Viewprevnext}}.\n{{Identical|Oldest}}",
        "action-changetags": "{{doc-action|changetags}}",
        "action-deletechangetags": "{{doc-action|deletechangetags}}",
        "action-purge": "{{doc-action|purge}}",
+       "action-apihighlimits": "{{doc-action|apihighlimits}}",
+       "action-autoconfirmed": "{{doc-action|autoconfirmed}}",
+       "action-bigdelete": "{{doc-action|bigdelete}}",
+       "action-blockemail": "{{doc-action|blockemail}}",
+       "action-bot": "{{doc-action|bot}}",
+       "action-editprotected": "{{doc-action|editprotected}}",
+       "action-editsemiprotected": "{{doc-action|editsemiprotected}}",
+       "action-editinterface": "{{doc-action|editinterface}}",
+       "action-editusercss": "{{doc-action|editusercss}}",
+       "action-edituserjson": "{{doc-action|edituserjson}}",
+       "action-edituserjs": "{{doc-action|edituserjs}}",
+       "action-editsitecss": "{{doc-action|editsitecss}}",
+       "action-editsitejson": "{{doc-action|editsitejson}}",
+       "action-editsitejs": "{{doc-action|editsitejs}}",
+       "action-editmyusercss": "{{doc-action|editmyusercss}}",
+       "action-editmyuserjson": "{{doc-action|editmyuserjson}}",
+       "action-editmyuserjs": "{{doc-action|editmyuserjs}}",
+       "action-viewsuppressed": "{{doc-action|viewsuppressed}}",
+       "action-hideuser": "{{doc-action|hideuser}}",
+       "action-ipblock-exempt": "{{doc-action|ipblock-exempt}}",
+       "action-unblockself": "{{doc-action|unblockself}}",
+       "action-noratelimit": "{{doc-action|noratelimit}}",
+       "action-reupload-own": "{{doc-action|reupload-own}}",
+       "action-nominornewtalk": "{{doc-action|nominornewtalk}}",
+       "action-markbotedits": "{{doc-action|markbotedits}}",
+       "action-patrolmarks": "{{doc-action|patrolmarks}}",
+       "action-override-export-depth": "{{doc-action|override-export-depth}}",
+       "action-suppressredirect": "{{doc-action|suppressredirect}}",
        "nchanges": "Appears on enhanced watchlist and recent changes when page has more than one change on given date, linking to a diff of the changes.\n\nParameters:\n* $1 - the number of changes on that day (2 or more)\nThree messages are shown side-by-side: ({{msg-mw|Nchanges}} | {{msg-mw|Enhancedrc-since-last-visit}} | {{msg-mw|Enhancedrc-history}}).",
        "ntimes": "Used to indicate how many times an event occurred (eg. on enhanced recent change when a user did more than one change to the page). Parameters:\n* $1 - number (integer)",
        "enhancedrc-since-last-visit": "Appears on enhanced watchlist and recent changes when page has more than one change on given date and at least one that the user hasn't seen yet, linking to a diff of the unviewed changes.\n\nParameters:\n* $1 - the number of unviewed changes (1 or more)\nThree messages are shown side-by-side: ({{msg-mw|nchanges}} | {{msg-mw|enhancedrc-since-last-visit}} | {{msg-mw|enhancedrc-history}}).",
        "rcfilters-activefilters-show-tooltip": "Tooltip for the button that shows the active filters list and dropdown in [[Special:RecentChanges]].",
        "rcfilters-advancedfilters": "Title for the buttons allowing the user to switch to the various advanced filters views.",
        "rcfilters-limit-title": "Title for the options to change the number of results shown.",
-       "rcfilters-limit-and-date-label": "Title for the button that opens the operation to control how many results to show and in which time period to search. \n\nParameters: $1 - Number of results shown\n\n$2 - Time period to search. One of {{msg-mw|rcfilters-days-title}} or {{msg-mw|rcfilters-hours-title}} is used as $2\n{{Identical|Change}}",
+       "rcfilters-limit-and-date-label": "Title for the button that opens the operation to control how many results to show and in which time period to search. \n\nParameters: $1 - Number of results shown\n\n$2 - Time period to search. One of {{msg-mw|Rcfilters-days-show-days}} or {{msg-mw|Rcfilters-days-show-hours}} is used as $2\n{{Identical|Change}}",
        "rcfilters-date-popup-title": "Section title of date options on recent changes results",
        "rcfilters-days-title": "Title for the options to change the number of days for the results shown.",
        "rcfilters-hours-title": "Title for the options to change the number of hours for the results shown.",
        "mediastatistics-header-3d": "Header on [[Special:MediaStatistics]] for file types that are in the 3D category. Includes STL files.",
        "mediastatistics-header-total": "Header on [[Special:MediaStatistics]] for a summary of all file types.",
        "json-warn-trailing-comma": "A warning message notifying that JSON text was automatically corrected by removing erroneous commas.\n\nParameters:\n* $1 - number of commas that were removed\n{{Related|Json-error}}",
-       "json-error-unknown": "User error message when there’s an unknown error.\n\nThis error is shown if we received an unexpected value from PHP. See https://secure.php.net/manual/en/function.json-last-error.php\n\nParameters:\n* $1 - integer error code\n{{Related|Json-error}}",
-       "json-error-depth": "User error message when the maximum stack depth is exceeded.\nSee https://secure.php.net/manual/en/function.json-last-error.php\n{{Related|Json-error}}",
-       "json-error-state-mismatch": "User error message when underflow or the modes mismatch.\n\n'''Underflow''': A data-processing error arising when the absolute value of a computed quantity is smaller than the limits of precision of the computing device, retaining at least one significant digit.\n\nSee https://secure.php.net/manual/en/function.json-last-error.php\n{{Related|Json-error}}",
-       "json-error-ctrl-char": "User error message when an unexpected control character has been found.\nSee https://secure.php.net/manual/en/function.json-last-error.php\n{{Related|Json-error}}",
-       "json-error-syntax": "User error message when there is a syntax error; a malformed JSON.\nSee https://secure.php.net/manual/en/function.json-last-error.php\n{{Related|Json-error}}\n{{Identical|Syntax error}}",
-       "json-error-utf8": "User error message when there are malformed UTF-8 characters, possibly incorrectly encoded.\nSee https://secure.php.net/manual/en/function.json-last-error.php\n{{Related|Json-error}}",
-       "json-error-recursion": "PHP JSON encoding/decoding error. See https://secure.php.net/manual/en/function.json-last-error.php\n{{Related|Json-error}}",
-       "json-error-inf-or-nan": "PHP JSON encoding/decoding error. See https://secure.php.net/manual/en/function.json-last-error.php\n{{Related|Json-error}}",
-       "json-error-unsupported-type": "PHP JSON encoding/decoding error. See https://secure.php.net/manual/en/function.json-last-error.php\n{{Related|Json-error}}",
+       "json-error-unknown": "User error message when there’s an unknown error.\n\nThis error is shown if we received an unexpected value from PHP. See https://www.php.net/manual/en/function.json-last-error.php\n\nParameters:\n* $1 - integer error code\n{{Related|Json-error}}",
+       "json-error-depth": "User error message when the maximum stack depth is exceeded.\nSee https://www.php.net/manual/en/function.json-last-error.php\n{{Related|Json-error}}",
+       "json-error-state-mismatch": "User error message when underflow or the modes mismatch.\n\n'''Underflow''': A data-processing error arising when the absolute value of a computed quantity is smaller than the limits of precision of the computing device, retaining at least one significant digit.\n\nSee https://www.php.net/manual/en/function.json-last-error.php\n{{Related|Json-error}}",
+       "json-error-ctrl-char": "User error message when an unexpected control character has been found.\nSee https://www.php.net/manual/en/function.json-last-error.php\n{{Related|Json-error}}",
+       "json-error-syntax": "User error message when there is a syntax error; a malformed JSON.\nSee https://www.php.net/manual/en/function.json-last-error.php\n{{Related|Json-error}}\n{{Identical|Syntax error}}",
+       "json-error-utf8": "User error message when there are malformed UTF-8 characters, possibly incorrectly encoded.\nSee https://www.php.net/manual/en/function.json-last-error.php\n{{Related|Json-error}}",
+       "json-error-recursion": "PHP JSON encoding/decoding error. See https://www.php.net/manual/en/function.json-last-error.php\n{{Related|Json-error}}",
+       "json-error-inf-or-nan": "PHP JSON encoding/decoding error. See https://www.php.net/manual/en/function.json-last-error.php\n{{Related|Json-error}}",
+       "json-error-unsupported-type": "PHP JSON encoding/decoding error. See https://www.php.net/manual/en/function.json-last-error.php\n{{Related|Json-error}}",
        "headline-anchor-title": "Title tooltip for the section anchor symbol, which is a link to the current section. Can be interpreted both as a noun (\"this is a link\") or as a verb (\"use this to link\").",
        "special-characters-group-latin": "This is the name of a script, or alphabet, not a language.",
        "special-characters-group-latinextended": "The name of the Latin Extended character set.",
index 28bfd42..99f2a77 100644 (file)
        "querypage-disabled": "Această pagină specială este dezactivată din motive de performanță.",
        "apihelp": "Ajutor API",
        "apihelp-no-such-module": "Modulul „$1” nu a fost găsit.",
-       "apisandbox": "Cutia cu nisip pentru API",
-       "apisandbox-jsonly": "Este nevoie de JavaScript pentru a folosi Cutia cu nisip pentru API.",
+       "apisandbox": "Pagina de teste pentru API",
+       "apisandbox-jsonly": "Este nevoie de JavaScript pentru a folosi pagina de teste pentru API.",
        "apisandbox-api-disabled": "API este dezactivat pe acest site.",
        "apisandbox-intro": "Folosiți această pagină pentru a experimenta cu <strong>API-ul MediaWiki</strong>. Citiți [[mw:API:Main page|documentația API-ului]] pentru mai multe detalii de utilizare. Exemplu: [https://www.mediawiki.org/wiki/API#A_simple_example obțineți conținutul paginii principale]. Selectați o acțiune pentru a vedea mai multe exemple.",
        "apisandbox-submit": "Efectuați cererea",
        "apisandbox-sending-request": "Se trimite solicitarea API...",
        "apisandbox-loading-results": "Se obțin rezultatele API...",
        "apisandbox-results-error": "A apărut o eroare la încărcarea răspunsului solicitării API: $1.",
-       "apisandbox-results-login-suppressed": "Această cerere a fost procesată ca venind din partea unui utilizator neautentificat deoarece poate fi folosită pentru a evita verificările cu privire la originea comună făcute de browser. Metoda automată de administrare a token-urilor din groapa cu nisip pentru APU nu funcționează corect cu aceste cereri, vă rugăm să le completați manual.",
+       "apisandbox-results-login-suppressed": "Această cerere a fost procesată ca venind din partea unui utilizator neautentificat deoarece poate fi folosită pentru a evita verificările cu privire la originea comună făcute de browser. Metoda automată de administrare a token-urilor din pagina de teste pentru API nu funcționează corect cu aceste cereri, vă rugăm să le completați manual.",
        "apisandbox-request-selectformat-label": "Afișați datele solicitate ca:",
        "apisandbox-request-format-url-label": "Șir de interogări URL",
        "apisandbox-request-url-label": "URL cerere:",
index 7c601f4..534d0a4 100644 (file)
@@ -58,6 +58,7 @@
        "tog-norollbackdiff": "Non sce penzanne a le differenze apprisse l'esecuzione de 'nu rollback",
        "tog-useeditwarning": "Avvisave quanne jie lasse 'na pàgene cangiate senze ca agghie sarvate le cangiaminde",
        "tog-prefershttps": "Ause sembre 'na connessione secure quanne trase",
+       "tog-showrollbackconfirmation": "Fà 'ndrucà 'na richieste de conferme quanne ste cazze sus a 'nu collegamende de annullamende",
        "underline-always": "Sembre",
        "underline-never": "Maje",
        "underline-default": "Valore de default d'u browser o scheme",
        "badretype": "Le passuord ca è scritte non ge sonde uguale.",
        "usernameinprogress": "'Na ccrejazzione de 'nu cunde pe stu nome utende ste già in esecuzione.\nPe piacere, aspitte.",
        "userexists": "'U nome de l'utende ca è scritte jè già ausate.\nPe piacere scacchiane n'otre.",
+       "createacct-normalization": "'U nome utende tune avène corrette jndr'à \"$2\" purcé stonne de le restriziune tecniche.",
        "loginerror": "Errore de collegamende",
        "createacct-error": "Errore sus 'a ccrejazione d'u cunde",
        "createaccounterror": "Non ge puè ccrejà 'u cunde utende: $1",
        "page_first": "prime",
        "page_last": "urteme",
        "histlegend": "Differenze de selezione: signe le radio box de le versiune ca vuè cu combronde e cazze ''invio'' o 'u buttone ca ste sotte.<br />\nLeggenda: (cur) = differenze cu 'a versiona corrende,\n(last) = differenze ca 'a versione precedende, M = cangiaminde stuédeche.",
-       "history-fieldset-title": "cirche pe revisiune",
+       "history-fieldset-title": "Filtre le revisiune",
        "history-show-deleted": "Sule le revisiune scangellate",
        "histfirst": "Prime",
        "histlast": "Urteme",
        "delete-confirm": "Scangille \"$1\"",
        "delete-legend": "Scangille",
        "historywarning": "<strong>Vide Bbuene:</strong> 'A pàgene ca ste scangille tène 'na storie de cangiaminde cu cchiù o mene $1 {{PLURAL:$1|revisione|revisiune}}:",
-       "historyaction-submit": "Fà 'ndrucà",
+       "historyaction-submit": "Fà 'ndrucà le revisiune",
        "confirmdeletetext": "Vide Bbuene, vide ca ste scangille 'na pàgene ca tène pure nu sbuenne de cangiaminde.\nConferme quidde ca ste face, ce si sicure ca è capite quidde ca ste cumbine e ce è corrette rispette a [[{{MediaWiki:Policy-url}}|le regole de scangellazione]], ce no statte quiete.",
        "actioncomplete": "Aziona Combletete",
        "actionfailed": "Aziona fallite",
index 277dd77..e5108cf 100644 (file)
        "subject-preview": "Pretpregled naslova:",
        "previewerrortext": "Dogodila se greška prilikom prikazivanja vaših izmjena.",
        "blockedtitle": "Korisnik je blokiran",
-       "blockedtext": "'''Vaše korisničko ime ili IP adresa je blokirana.'''\n\nBlokada izvršena od strane $1.\nDati razlog je slijedeći: ''$2''.\n\n*Početak blokade: $8\n*Kraj perioda blokade: $6\n*Ime blokiranog korisnika: $7\n\nMožete kontaktirati $1 ili nekog drugog [[{{MediaWiki:Grouppage-sysop}}|administratora]] da biste razgovarali o blokadi.\n\nNe možete koristiti opciju ''Pošalji e-mail korisniku'' osim ako niste unijeli e-mail adresu u [[Special:Preferences|Vaše postavke]].\nVaša trenutna IP adresa je $3, a oznaka blokade je #$5.\nMolimo Vas da navedete gornje podatke u zahtjevu za deblokadu.",
-       "autoblockedtext": "Vaša IP adresa je automatski blokirana jer je korištena od strane drugog korisnika, a blokirao ju je $1.\nNaveden je slijedeći razlog:\n\n:''$2''\n\n* Početak blokade: $8\n* Kraj blokade: $6\n* Blokirani korisnik: $7\n\nMožete kontaktirati $1 ili nekog drugog iz grupe [[{{MediaWiki:Grouppage-sysop}}|administratora]] i zahtijevati da Vas deblokira.\n\nZapamtite da ne možete koristiti opciju \"pošalji e-mail ovom korisniku\" sve dok ne unesete validnu e-mail adresu pri registraciji u Vašim [[Special:Preferences|korisničkim postavkama]] te Vas ne spriječava ga je koristite.\n\nVaša trenutna IP adresa je $3, a ID blokade je $5.\nMolimo da navedete sve gore navedene detalje u zahtjevu za deblokadu.",
+       "blocked-email-user": "<strong>Vašem korisničkom imenu je zabranjeno slanje e-poruka. Još uvijek možete da uređujete druge stranice na ovom wikiju.</strong> \nSve pojedinosti o zabrani naći ćete u [[Special:MyContributions|doprinosima naloga]].\n\nZabranu je dao/la $1.\n\nNaveden je sljedeći razlog: <em>$2</em>.\n\n* Početak zabrane: $8\n* Istek zabrane: $6\n* Namijenjena korisniku/ci ili IP adresi: $7\n* ID zabrane #$5",
+       "blockedtext-partial": "<strong>Vašem korisničkom imenu ili IP adresi je zabranjeno pravljenje izmjena na ovoj stranici. Još uvijek možete da uređujete druge stranice na ovom wikiju.</strong> Sve pojedinosti o zabrani naći ćete u [[Special:MyContributions|doprinosima naloga]].\n\nZabranu je dao/la $1.\n\nNaveden je sljedeći razlog: <em>$2</em>.\n\n* Početak zabrane: $8\n* Istek zabrane: $6\n* Namijenjena korisniku/ci ili IP adresi: $7\n* ID zabrane #$5",
+       "blockedtext": "<strong>Vaše korisničko ime ili IP adresa je blokirana.</strong>\n\nBlokada izvršena od strane $1.\nDati razlog je slijedeći: <em>$2</em>.\n\n* Početak blokade: $8\n* Kraj perioda blokade: $6\n* Ime blokiranog korisnika: $7\n\nMožete kontaktirati $1 ili nekog drugog [[{{MediaWiki:Grouppage-sysop}}|administratora]] da biste razgovarali o blokadi.\nMožete koristiti opciju \"{{int:emailuser}}\" ako je navedena valjana adresa e-pošte u [[Special:Preferences|Vašim postavkama]] i Vam nije zabranjeno je koristiti.\nVaša trenutna IP adresa je $3, a oznaka blokade je #$5.\nMolimo Vas da navedete gornje podatke u zahtjevu za deblokadu.",
+       "autoblockedtext": "Vaša IP adresa je automatski blokirana jer je korištena od strane drugog korisnika, a blokirao ju je $1.\nNaveden je slijedeći razlog:\n\n:<em>$2</em>\n\n* Početak blokade: $8\n* Kraj blokade: $6\n* Blokirani korisnik: $7\n\nMožete kontaktirati $1 ili nekog drugog iz grupe [[{{MediaWiki:Grouppage-sysop}}|administratora]] i zahtijevati da Vas deblokira.\n\nZapamtite da ne možete koristiti opciju \"{{int:emailuser}}\" ukoliko nije unesena validna e-mail adresa u [[Special:Preferences|Vašim postavkama]] te Vas ne spriječava ga je koristite.\n\nVaša trenutna IP adresa je $3, a ID blokade je $5.\nMolimo da navedete sve gore navedene detalje u zahtjevu za deblokadu.",
+       "systemblockedtext": "MediaWiki je automatski blokirao Vaše korisničko ime ili IP-adresu.\nDat je sljedeći razlog:\n\n:<em>$2</em>\n\n* Početak bloka: $8\n* Istek bloka: $6\n* Blok je namijenjen za: $7\n\nVaša trenutna IP-adresa je $3.\nPrepišite sve gorenavedene pojedinosti ukoliko želite da vlasti pitaju za blok.",
        "blockednoreason": "razlog nije naveden",
        "whitelistedittext": "Da bi ste uređivali stranice, morate se $1.",
        "confirmedittext": "Morate potvrditi Vašu e-mail adresu prije nego počnete mijenjati stranice.\nMolimo da postavite i verifikujete Vašu e-mail adresu putem Vaših [[Special:Preferences|korisničkih opcija]].",
        "continue-editing": "Idi na područje uređivanja",
        "previewconflict": "Ovaj pretpregled reflektuje tekst u gornjem polju\nkako će izgledati ako pritisnete \"Snimi stranicu\".",
        "session_fail_preview": "Nažalost, nisam mogao obraditi izmjenu zbog gubitka podataka o sesiji.\n\nMožda ste odjavljeni. <strong>Provjerite jeste li i dalje prijavljeni i pokušajte ponovno.<strong>\nAko se problem nastavi pojavljivati, [[Special:UserLogout|odjavite se]] i ponovno prijavite, te provjerite da preglednik dopušta kolačiće s ovog sajta.",
-       "session_fail_preview_html": "'''Žao nam je! Nismo mogli da obradimo vašu izmjenu zbog gubitka podataka.'''\n\n''Zbog toga što {{SITENAME}} ima omogućen izvorni HTML, predpregled je sakriven kao predostrožnost protiv JavaScript napada.''\n\n'''Ako ste pokušali da napravite pravu izmjenu, molimo pokušajte ponovo. Ako i dalje ne radi, pokušajte da se [[Special:UserLogout|odjavite]] i ponovo prijavite.'''",
+       "session_fail_preview_html": "Žao nam je! Nismo mogli da obradimo vašu izmjenu zbog gubitka sesijskih podataka.\n\n<em>Zbog toga što {{SITENAME}} ima omogućen izvorni HTML, predpregled je sakriven kao predostrožnost protiv JavaScript napada.</em>\n\n<strong>Ako ste pokušali da napravite pravu izmjenu, molimo pokušajte ponovo.</strong>\nAko to ne riješi problem, pokušajte da se [[Special:UserLogout|odjavite]] i ponovo prijavite, i provjerite da preglednik dopušta kolačiće s ovog sajta.",
        "token_suffix_mismatch": "'''Vaša izmjena nije prihvaćena jer je Vaš web preglednik ubacio znakove interpunkcije u token uređivanja.'''\nIzmjena je odbačena da bi se spriječilo uništavanje teksta stranice.\nTo se događa ponekad kad korisite problematični anonimni proxy koji je baziran na web-u.",
        "edit_form_incomplete": "'''Neki dijelovi uređivačkog obrasca nisu došli do servera; dvaput provjerite da su vaše izmjene nepromjenjene i pokušajte ponovno.'''",
        "editing": "Uređujete $1",
        "editingsection": "Uređujete $1 (sekciju)",
        "editingcomment": "Uređujete $1 (nova sekcija)",
        "editconflict": "Sukobljenje izmjene: $1",
-       "explainconflict": "Neko drugi je promujenio ovu stranicu otkad ste Vi počeli da je mijenjate.\nGornje tekstualno polje sadrži tekst stranice koji trenutno postoji.\nVaše izmjene su prikazane u donjem tekstu.\nMoraćete da unesete svoje promjene u postojeći tekst.\n'''Samo''' tekst u gornjem tekstualnom polju će biti snimljen kad pritisnete \"$1\".",
+       "explainconflict": "Neko drugi je promijenio ovu stranicu otkad ste Vi počeli da je mijenjate.\nGornje tekstualno polje sadrži tekst stranice koji trenutno postoji.\nVaše izmjene su prikazane u donjem tekstu.\nMoraćete da unesete svoje promjene u postojeći tekst.\n<strong>Samo</strong> tekst u gornjem tekstualnom polju će biti snimljen kad pritisnete \"$1\".",
        "yourtext": "Vaš tekst / Ваш текст",
        "storedversion": "Uskladištena verzija",
        "editingold": "'''PAŽNJA:  Vi mijenjate stariju reviziju ove stranice.\nAko je snimite, sve promjene učinjene od ove revizije će biti izgubljene.'''",
        "page_first": "prva",
        "page_last": "zadnja",
        "histlegend": "Odabir razlika: označite radio dugme verzija za usporedbu i pritisnite enter ili dugme na dnu.<br />\nObjašnjenje: '''({{int:cur}})''' = razlika sa trenutnom verzijom,\n'''({{int:last}})''' = razlika sa prethodnom verzijom, '''{{int:minoreditletter}}''' = manja izmjena.",
-       "history-fieldset-title": "Pretraga izmjena",
+       "history-fieldset-title": "Filtriraj izmjene",
        "history-show-deleted": "Samo obrisane izmjene",
        "histfirst": "najstarije",
        "histlast": "najnovije",
        "historysize": "({{PLURAL:$1|1 bajt|$1 bajta|$1 bajtova}})",
-       "historyempty": "(prazno)",
+       "historyempty": "prazno",
        "history-feed-title": "Historija izmjena",
        "history-feed-description": "Historija promjena ove stranice na wikiju",
        "history-feed-item-nocomment": "$1 u $2",
        "recentchangesdays-max": "(najviše $1 {{PLURAL:$1|dan|dana}})",
        "recentchangescount": "Podrazumevani broj izmjena za prikaz u skorašnjim izmjenama, istorijama stranica i dnevnicima:",
        "prefs-help-recentchangescount": "Najveći broj: 1000",
-       "prefs-help-watchlist-token2": "Ovo je tajni ključ prema sažetku Vašeg popisa praćenja. Svaki suradnik kojem je poznat, moći će čitati Vaš popis praćenih stranica. Ne dijelite ga ni s kim. [[Special:ResetTokens|Kliknite ovdje ako ga želite ponovo postaviti]].",
+       "prefs-help-watchlist-token2": "Ovo je tajni ključ prema sažetku Vašeg popisa praćenja.\nSvaki suradnik kojem je poznat, moći će čitati Vaš popis praćenih stranica, pa zato ne dijelite ga ni s kim.\nAko je potrebno, [[Special:ResetTokens|možete staviti novi]].",
+       "prefs-help-tokenmanagement": "Možete pogledati i odnovno zadati tajni ključ za svoj nalog koji može da pristupi svemrežnom fidu Vašeg popisa praćenih. Svako ko zna ključ moći će da vam čita spisak praćenih — zato nikome ne govori.",
        "savedprefs": "Vaša postavke su snimljene.",
        "savedrights": "Korisnička prava {{GENDER:$1|$1}} su snimljena.",
        "timezonelegend": "Vremenska zona / Временска зона",
        "userrights-expiry": "Ističe:",
        "userrights-expiry-existing": "Postojeće vrijeme isticanja: $3, $2",
        "userrights-expiry-othertime": "Drugo vrijeme:",
+       "userrights-expiry-options": "1 dan:1 day,1 sedmica:1 week,1 mjesec:1 month,3 mjeseca:3 months,6 mjeseci:6 months,1 godina:1 year",
        "userrights-invalid-expiry": "Vrijeme isticanja grupe \"$1\" nije ispravno.",
        "userrights-expiry-in-past": "Vrijeme isticanja grupe \"$1\" je u prošlosti.",
        "userrights-cannot-shorten-expiry": "Ne možete ubrzati istek članstva u grupi \"$1\". To mogu učiniti samo korisnici s dozvolom za dodavanje ili uklanjanje ove grupe.",
        "right-reupload-own": "Postavljanje nove verzije datoteke koju je postavio korisnik",
        "right-reupload-shared": "Postavljanje novih lokalnih verzija datoteka identičnih onima u zajedničkoj ostavi",
        "right-upload_by_url": "Postavljanje datoteke sa URL adrese",
-       "right-purge": "Osvježavanje keša za stranice bez potvrde",
+       "right-purge": "Osvježavanje keša stranice",
        "right-autoconfirmed": "Izbjegavanje ograničenja stopa temeljenih na IP-u",
        "right-bot": "Postavljen kao automatski proces",
        "right-nominornewtalk": "Male izmjene na stranicama za razgovor ne uzrokuju obavještenje o novim porukama",
        "right-editsitejson": "Uređivanje JSON-a za cijelo wiki",
        "right-editsitejs": "Uređivanje JavaScripta za cijelo wiki",
        "right-editmyusercss": "Uredite svoje vlastite CSS datoteke",
+       "right-editmyuserjson": "Uređivanje vlastitih JSON datoteka",
        "right-editmyuserjs": "Uredite vlastite korisničke JavaScript datoteke",
        "right-viewmywatchlist": "Pregled vlastitog popisa praćenih stranica",
        "right-editmywatchlist": "Uređivanje vlastitih praćenih. Obratite pažnju da će neke akcije dodati stranice čak bez ovog prava.",
        "grant-delete": "Brisanje stranica, izmjena i unosa u zapisnicima",
        "grant-editinterface": "Uređivanje imenskog prostora \"MediaWiki\" i JSON za cijelo wiki/za korisnika",
        "grant-editmycssjs": "Uređivanje Vašeg korisničkog CSS/JSON/JavaScripta",
-       "grant-editmyoptions": "Uređivanje vaših korisničkih podešavanja i postavljenosti JSON-a",
+       "grant-editmyoptions": "Uređivanje vaših korisničkih podešavanja i JSON konfiguracije",
        "grant-editmywatchlist": "Uređivanje Vaših praćenih",
        "grant-editsiteconfig": "Uređivanje CSS/JS za cijelo wiki i za korisnika",
        "grant-editpage": "Uređivanje postojećih stranica",
        "action-changetags": "dodajte ili uklonite razne oznake na pojedinačnim verzijama i unosima u zapisnicima",
        "action-deletechangetags": "brišete oznake iz baze podataka",
        "action-purge": "preučitavanje ove stranice",
+       "action-apihighlimits": "korištenje viših ograničenja u API upitima",
+       "action-autoconfirmed": "izbjegavanje ograničenja stopa temeljenih na IP-u",
+       "action-bigdelete": "brisanje stranica sa velikom historijom",
+       "action-blockemail": "blokiranje korisnika da šalje e-mail",
+       "action-bot": "tretiranje kao automatski proces",
+       "action-editprotected": "uređivanje stranica zaštićenih kao \"{{int:protect-level-sysop}}\"",
+       "action-editsemiprotected": "uređivanje stranica zaštićenih kao \"{{int:protect-level-autoconfirmed}}\"",
+       "action-editinterface": "uređivanje korisničkog interfejsa",
+       "action-editusercss": "uređivanje CSS-datoteka drugih korisnika",
+       "action-edituserjson": "uređivanje JSON-datoteka drugih korisnika",
+       "action-edituserjs": "uređivanje JavaScript-datoteka drugih korisnika",
+       "action-editsitecss": "uređivanje CSS za cijelo wiki",
+       "action-editsitejson": "uređivanje JSONa za cijelo wiki",
+       "action-editsitejs": "uređivanje JavaScripta za cijelo wiki",
+       "action-editmyusercss": "uređivanje vlastitih CSS datoteka",
+       "action-editmyuserjson": "uređivanje vlastitih JSON datoteka",
+       "action-editmyuserjs": "uređivanje vlastitih JavaScript datoteka",
+       "action-viewsuppressed": "pregledaj izmjene skrivene od svih korisnika",
+       "action-hideuser": "blokiranje korisničkog imena, i njegovo sakrivanje od javnosti",
+       "action-ipblock-exempt": "zaobilaženje IP blokova, autoblokova i opsežnih blokova",
+       "action-unblockself": "deblokiraj samog sebe",
+       "action-noratelimit": "ne utječu na vremenska ograničenja uređivanja",
+       "action-reupload-own": "prezapis datoteke koju je postavio korisnik",
+       "action-nominornewtalk": "male izmjene na stranicama za razgovor ne uzrokuju obavještenje o novim porukama",
+       "action-markbotedits": "označavanje vraćenih izmjena kao izmjene bota",
+       "action-patrolmarks": "pregled oznaka patroliranja u spisku nedavnih izmjena",
+       "action-override-export-depth": "izvoz stranica uključujući povezane stranice do dubine od 5 linkova",
+       "action-suppressredirect": "ne se pravi preusmjerenje sa starog imena pri premještanju stranica",
        "nchanges": "$1 {{PLURAL:$1|izmjena|izmjene|izmjena}}",
        "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|izmjena od Vaše posljedne posjete}}",
        "enhancedrc-history": "historija",
        "delete-confirm": "Brisanje \"$1\"",
        "delete-legend": "Obriši",
        "historywarning": "<strong>Upozorenje</strong>: Stranica koju želite da obrišete ima historiju sa otprilike $1 {{PLURAL:$1|revizijom|revizije|revizija}}:",
-       "historyaction-submit": "Prikaži",
+       "historyaction-submit": "Prikaži revizije",
        "confirmdeletetext": "Upravo ćete obrisati stranicu sa svom njenom historijom.\nMolimo da potvrdite da ćete to učiniti, da razumijete posljedice te da to činite u skladu sa [[{{MediaWiki:Policy-url}}|pravilima]].",
        "actioncomplete": "Radnja završena\n\nРадња завршена",
        "actionfailed": "Akcija nije uspjela",
        "deleting-backlinks-warning": "<strong>Upozorenje:</strong>  Na stranicu koju želite izbrisati vode [[Special:WhatLinksHere/{{FULLPAGENAME}}|ostale stranice]] ili pak su uključene u nju.",
        "deleting-subpages-warning": "<strong>Upozorenje:</strong> Stranica koju želite izbrisati ima [[Special:PrefixIndex/{{FULLPAGENAME}}/|{{PLURAL:$1|podstranicu|$1 podstranice|$1 podstranica|51=preko 50 podstranica}}]].",
        "rollback": "Vrati izmjene",
+       "rollback-confirmation-yes": "Vraćanje",
+       "rollback-confirmation-no": "Otkaži",
        "rollbacklink": "vrati",
        "rollbacklinkcount": "vrati $1 {{PLURAL:$1|izmjenu|izmjene|izmjena}}",
        "rollbacklinkcount-morethan": "vrati više od $1 {{PLURAL:$1|izmjene|izmjene|izmjena}}",
        "revertpage-nouser": "Vraćene izmjene skrivenog korisnika na posljednju reviziju, koju je {{GENDER:$1|napravio|napravila}} [[User:$1|$1]]",
        "rollback-success": "Vraćene su izmjene korisnika {{GENDER:$3|$1}};\nvraćeno na posljednju verziju koju je snimio {{GENDER:$4|$2}}.",
        "sessionfailure-title": "Greška u sesiji",
-       "sessionfailure": "Izgleda da postoji problem sa vašom sesijom; ova akcija je otkazana kao prevencija protiv napadanja sesija. Kliknite \"back\" (''nazad'') i osvježite stranicu sa koje ste došli, i opet pokušajte.",
+       "sessionfailure": "Izgleda da postoji problem sa vašom sesijom;\nova radnja je otkazana kao prevencija protiv napadanja sesija.\nMolimo ponovno pošaljite obrazac.",
        "changecontentmodel": "Promijeni model sadržaja stranice",
        "changecontentmodel-legend": "Promijeni model sadržaja",
        "changecontentmodel-title-label": "Naslov stranice",
        "mycontris": "Doprinosi",
        "anoncontribs": "Doprinosi",
        "contribsub2": "Za {{GENDER:$3|$1}} ($2)",
+       "contributions-subtitle": "Za {{GENDER:$3|$1}}",
        "contributions-userdoesnotexist": "Korisnički račun \"$1\" nije registrovan.",
        "negative-namespace-not-supported": "Nisu podržani imenski prostori s negativnim vrijednostima.",
        "nocontribs": "Nisu nađene promjene koje zadovoljavaju ove uslove.",
        "block": "Blokiraj korisnika",
        "unblock": "Odblokiraj korisnika",
        "blockip": "Blokiraj {{GENDER:$1|korisnika|korisnicu}}",
-       "blockiptext": "Upotrebite donji upitnik da biste uklonili prava pisanja sa određene IP adrese ili korisničkog imena.  \nOvo bi trebalo da bude urađeno samo da bi se spriječio vandalizam, i u skladu sa [[{{MediaWiki:Policy-url}}|smjernicama]]. \nUnesite konkretan razlog ispod (na primjer, navodeći koje konkretne stranice su vandalizovane).",
+       "blockiptext": "Upotrebite donji upitnik da biste uklonili prava pisanja sa određene IP adrese ili korisničkog imena.  \nOvo bi trebalo da bude urađeno samo da bi se spriječio vandalizam, i u skladu sa [[{{MediaWiki:Policy-url}}|smjernicama]]. \nUnesite konkretan razlog ispod (na primjer, navodeći koje konkretne stranice su vandalizovane).\nMožete blokirati IP-opsege koristeći [https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing sintaksu CIDR-a]; najveći dozvoljeni opseg za IPv4 je /$1, a za IPv6 /$2.",
        "ipaddressorusername": "IP adresa ili korisničko ime:",
        "ipbreason": "Razlog:",
        "ipbreason-dropdown": "*Najčešći razlozi blokiranja\n**Unošenje netačnih informacija\n**Uklanjanje sadržaja stranica\n**Postavljanje spam vanjskih linkova\n**Ubacivanje gluposti/grafita\n**Osobni napadi (ili napadačko ponašanje)\n**Čarapare (zloupotreba više korisničkih računa)\n**Neprihvatljivo korisničko ime",
        "pageinfo-category-files": "Broj datoteka",
        "pageinfo-user-id": "Korisnička naznaka",
        "pageinfo-file-hash": "Tarabna vrijednost",
-       "pageinfo-view-protect-log": "Pogl. evidenciju zaštite ove stranice.",
+       "pageinfo-view-protect-log": "Vidi evidenciju zaštićivanja ove stranice.",
        "markaspatrolleddiff": "Označi kao patrolirano",
        "markaspatrolledtext": "Označi ovu stranicu kao patroliranu",
        "markaspatrolledtext-file": "Označi ovu verziju kao ispatroliranu",
        "confirmemail_subject": "{{SITENAME}} adresa e-pošte za potvrdu",
        "confirmemail_body": "Neko, vjerovatno Vi, je sa IP adrese $1 registrovao nalog \"$2\" sa ovom adresom e-pošte na {{SITENAME}}.\n\nDa potvrdite da ovaj nalog stvarno pripada vama i da aktivirate mogućnost e-pošte na {{SITENAME}}, otvorite ovaj link u vašem pregledniku:\n\n$3\n\nAko ovo niste vi, pratite ovaj link da prekinete prijavu:\n$5\n\nOvaj kod za potvrdu će isteći u $4.",
        "confirmemail_body_changed": "Neko, vjerovatno Vi, je sa IP adrese $1\nje promijenio adresu e-pošte računa \"$2\" na ovu adresu za {{SITENAME}}.\n\nDa potvrdite da ovaj nalog stvarno pripada Vama i da reaktivirate mogućnosti e-pošte na {{SITENAME}}, otvorite ovaj link u Vašem pregledniku:\n\n$3\n\nAko ovaj račun *ne* pripada Vama, pratite ovaj link da prekinete odobravanje adrese e-pošte:\n\n$5\n\nOvaj kod za potvrdu će isteći u $4.",
-       "confirmemail_body_set": "Netko, vjerovatno Vi, sa IP adrese $1,\nje postavio e-mail adresu za račun \"$2\" na ovoj adresi za {{SITENAME}}.\n\nDa potvrdite kako ovaj račun uistinu pripada Vama i reaktivirate\ne-mail postavke na {{SITENAME}}, otvoriti ovaj link u vašem pregledniku:\n\n$3\n\nAko račun *ne* pripada Vama, pratite ovaj link\nkako bi poništili potvrdu e-mail adrese:\n\n$5\n\nOvaj kod za potvrdu će isteći u $4.",
+       "confirmemail_body_set": "Netko, vjerovatno Vi, sa IP adrese $1,\nje postavio e-mail adresu za račun \"$2\" na ovoj adresi za {{SITENAME}}.\n\nDa potvrdite kako ovaj račun stvarno pripada Vama i da uključite\nmogućnosti e-pošte na {{SITENAME}}, otvorite ovaj link u Vašem pregledniku:\n\n$3\n\nAko račun *ne* pripada Vama, pratite ovaj link\nkako bi poništili potvrdu:\n\n$5\n\nOvaj kod za potvrdu će isteći u $4.",
        "confirmemail_invalidated": "Potvrda e-mail adrese otkazana",
        "invalidateemail": "Odustani od e-mail potvrde",
        "notificationemail_subject_changed": "Registrirana adresa e-pošte na projektu {{SITENAME}} je smijenjena",
        "confirm-unwatch-top": "Izbrisati ovu stranicu sa Vašeg spiska praćenja?",
        "confirm-rollback-button": "U redu",
        "confirm-rollback-top": "Ukloniti uređivanja na ovoj stranici?",
+       "confirm-rollback-bottom": "Ova će radnja odmah poništiti izabrane promjene ove stranice.",
        "confirm-mcrrestore-title": "Povrati reviziju",
        "confirm-mcrundo-title": "Otkaži promjenu",
        "mcrundofailed": "Otkazivanje nije uspjelo",
        "tags-create-invalid-chars": "Nazivi oznaka ne smiju sadržavati zareze (<code>,</code>), uspravne (<code>|</code>) ni kose crte (<code>/</code>).",
        "tags-create-invalid-title-chars": "Imena oznaka ne smiju sadržavati znakove što se ne mogu koristiti u naslovima stranica.",
        "tags-create-already-exists": "Oznaka \"$1\" već postoji.",
+       "tags-create-warnings-above": "Pokušavajući stvoriti oznaku \"$1\" sam dogodio {{PLURAL:$2|sljedeće upozorenje|sljedeća upozorenja}}:",
+       "tags-create-warnings-below": "Želite li nastaviti stvaranje oznake?",
+       "tags-delete-title": "Izbriši oznaku",
+       "tags-delete-explanation-initial": "Brišete oznaku \"$1\" iz baze podataka.",
+       "tags-delete-explanation-in-use": "Bit će obrisana sa {{PLURAL:$2|jedne verzije ili unosa u zapisniku na kojim|svih $2 verzija i/ili unosa u zapisniku na kojima}} je primijenjena.",
+       "tags-delete-explanation-warning": "Ova radnja je <strong>nepovratna</strong> te se <strong>ne može poništiti</strong>. Ovo ne mogu uraditi čak ni administratori baze podataka. Zato, osigurajte da ovo stvarno je oznaka koju želite obrisati.",
+       "tags-delete-explanation-active": "<strong>Oznaka \"$1\" je i dalje aktivna te u budućnosti će se nastaviti primjenjivati.</strong> Kako biste ovo spriječili, otiđite na mjesto (ili mjesta) na kojima joj je postavljeno da se postavlja i onemogućite je tamo.",
+       "tags-delete-reason": "Razlog:",
+       "tags-delete-submit": "Nepovratno obriši ovu oznaku",
+       "tags-delete-not-allowed": "Oznake zadane dodatkom se ne mogu obrisati, osim ako to nije izričito dopušteno dodatkom.",
+       "tags-delete-not-found": "Oznaka \"$1\" ne postoji.",
+       "tags-delete-too-many-uses": "Oznaka \"$1\" primjenjuje se na više od {{PLURAL:$2|jedne revizije|$2 revizija}}, što znači da se ne može obrisati.",
+       "tags-delete-warnings-after-delete": "Oznaka \"$1\" je obrisana, ali naišao sam na {{PLURAL:$2|sljedeće upozorenje|sljedeća upozorenja}}:",
+       "tags-delete-no-permission": "Nemate dopuštenje da brišete oznake promjena.",
+       "tags-activate-title": "Aktiviraj oznaku",
+       "tags-activate-question": "Aktivirate oznaku \"$1\".",
+       "tags-activate-reason": "Razlog:",
+       "tags-activate-not-allowed": "Ne mogu aktivirati oznaku \"$1\".",
+       "tags-activate-not-found": "Oznaka \"$1\" ne postoji.",
+       "tags-activate-submit": "Aktiviraj",
+       "tags-deactivate-title": "Deaktiviraj oznaku",
+       "tags-deactivate-question": "Deaktivirate oznaku \"$1\".",
+       "tags-deactivate-reason": "Razlog:",
+       "tags-deactivate-not-allowed": "Ne mogu deaktivirati oznaku \"$1\".",
+       "tags-deactivate-submit": "Deaktiviraj",
+       "tags-apply-no-permission": "Nemate dopuštenje da stavljate oznake za promjene zajedno s izmjenama koje pravite.",
+       "tags-apply-blocked": "Ne možete zadavati oznake za promjenu dok {{GENDER:$1|ste}} blokirani.",
+       "tags-apply-not-allowed-one": "Nije dozvoljeno da se oznaka \"$1\" staje ručno.",
+       "tags-apply-not-allowed-multi": "Nije dozvoljeno da se {{PLURAL:$2|sljedeća oznaka staje ručno|sljedeće oznake staju ručno}}: $1",
+       "tags-update-no-permission": "Nemate dopuštenje da dodate ili uklonite oznake promjena sa zasebnih verzija ili zapisničkih unosa.",
+       "tags-update-blocked": "Ne možete dodavati niti uklanjati oznake za promjenu dok {{GENDER:$1|ste}} blokirani.",
+       "tags-update-add-not-allowed-one": "Nije dozvoljeno da se oznaka \"$1\" dodaje ručno.",
+       "tags-update-add-not-allowed-multi": "Nije dozvoljeno da se {{PLURAL:$2|sljedeća oznaka dodaje ručno|sljedeće oznake dodaju ručno}}: $1",
+       "tags-update-remove-not-allowed-one": "Nije dozvoljeno da se uklanja oznaka \"$1\".",
+       "tags-update-remove-not-allowed-multi": "Nije dozvoljeno da se {{PLURAL:$2|sljedeća oznaka uklanja ručno|sljedeće oznake uklanjaju ručno}}: $1",
+       "tags-edit-title": "Mijenjanje oznaka",
+       "tags-edit-manage-link": "Upravljaj oznakama",
+       "tags-edit-revision-selected": "{{PLURAL:$1|Odabrana izmjena|Odabrane izmjene}} [[:$2]]:",
+       "tags-edit-logentry-selected": "{{PLURAL:$1|Odabran unos registra|Odabrani unosi registra}}:",
+       "tags-edit-revision-legend": "Dodajte ili uklonite oznake sa {{PLURAL:$1|ove izmjene|svih $1 izmjena}}",
+       "tags-edit-logentry-legend": "Dodajte ili uklonite oznake sa {{PLURAL:$1|ovog zapisničkog unosa|svih $1 zapisničkih unosa}}",
+       "tags-edit-existing-tags": "Postojeće oznake:",
+       "tags-edit-existing-tags-none": "<em>Nema</em>",
+       "tags-edit-new-tags": "Nove oznake:",
+       "tags-edit-add": "Dodaj ove oznake:",
+       "tags-edit-remove": "Ukloni ove oznake:",
+       "tags-edit-remove-all-tags": "(ukloni sve oznake)",
+       "tags-edit-chosen-placeholder": "Odaberite neke oznake",
+       "tags-edit-chosen-no-results": "Nisam pronašao odgovarajuće oznake",
+       "tags-edit-reason": "Razlog:",
+       "tags-edit-revision-submit": "Primijeni izmjene {{PLURAL:$1|ovoj reviziji|svim $1 revizijama}}",
+       "tags-edit-logentry-submit": "Primijeni izmjene {{PLURAL:$1|ovom zapisničkom unosu|svim $1 zapisničkim unosima}}",
+       "tags-edit-success": "Izmjene su primijenjene.",
+       "tags-edit-failure": "Nisam mogao primijeniti izmjene:\n$1",
+       "tags-edit-nooldid-title": "Nevažeća odredišna revizija",
+       "tags-edit-nooldid-text": "Niste naveli odredišnu verziju na koju treba primijeniti izmjene, ili pak navedena verzija na postoji.",
+       "tags-edit-none-selected": "Odaberite bar jednu oznaku koju treba dodati ili ukloniti.",
        "comparepages": "Usporedi stranice",
        "compare-page1": "Stranica 1",
        "compare-page2": "Stranica 2",
index 7bcf82a..41224ce 100644 (file)
        "delete-confirm": "Brisanje »$1«",
        "delete-legend": "Izbriši",
        "historywarning": "<strong>Opozorilo:</strong> Stran, ki jo nameravate izbrisati, ima zgodovino s približno $1 {{PLURAL:$1|redakcijo|redakcijama|redakcijami}}:",
-       "historyaction-submit": "Prikaži",
+       "historyaction-submit": "Pokaži redakcije",
        "confirmdeletetext": "Iz zbirke podatkov boste izbrisali stran ali sliko skupaj z vso njeno zgodovino.\nProsimo, '''potrdite''', da to resnično želite, da razumete posledice dejanja in da se ravnate po [[{{MediaWiki:Policy-url}}|pravilih]].",
        "actioncomplete": "Poseg je končan",
        "actionfailed": "Dejanje spodletelo",
index b03fd93..cb996c9 100644 (file)
        "newwindow": "(отвара се у новом прозору)",
        "cancel": "Откажи",
        "moredotdotdot": "Више…",
-       "morenotlisted": "Ова листа можда није потпуна.",
+       "morenotlisted": "Овај списак можда није потпун.",
        "mypage": "Страница",
        "mytalk": "Разговор",
        "anontalk": "Разговор",
        "feed-atom": "Atom",
        "feed-rss": "RSS",
        "red-link-title": "$1 (страница не постоји)",
-       "sort-descending": "СоÑ\80Ñ\82иÑ\80ај опадајуће",
-       "sort-ascending": "СоÑ\80Ñ\82иÑ\80ај растуће",
+       "sort-descending": "Ð\9fоÑ\80еÑ\92ај опадајуће",
+       "sort-ascending": "Ð\9fоÑ\80еÑ\92ај растуће",
        "nstab-main": "Страница",
        "nstab-user": "{{GENDER:{{BASEPAGENAME}}|Корисник|Корисница}}",
        "nstab-media": "Медији",
        "nosuchaction": "Нема такве радње",
        "nosuchactiontext": "Радња која је наведена у УРЛ-у није важећа.\nМожда сте погрешно откуцали УРЛ или сте следили покварену везу.\nОво такође може да указује на грешку у софтверу који користи {{SITENAME}}.",
        "nosuchspecialpage": "Нема такве посебне странице",
-       "nospecialpagetext": "<strong>Ð\97аÑ\85Ñ\82евали Ñ\81Ñ\82е Ð½ÐµÐ²Ð°Ð¶ÐµÑ\9bÑ\83 Ð¿Ð¾Ñ\81ебнÑ\83 Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\83.</strong>\n\nÐ\9bиÑ\81Ñ\82а Ð²Ð°Ð¶ÐµÑ\9bиÑ\85 Ð¿Ð¾Ñ\81ебниÑ\85 Ñ\81Ñ\82Ñ\80аниÑ\86а може се пронађи на „[[Special:SpecialPages|{{int:specialpages}}]]”.",
+       "nospecialpagetext": "<strong>ТÑ\80ажили Ñ\81Ñ\82е Ð½ÐµÐ²Ð°Ð¶ÐµÑ\9bÑ\83 Ð¿Ð¾Ñ\81ебнÑ\83 Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\83.</strong>\n\nСпиÑ\81ак Ð²Ð°Ð¶ÐµÑ\9bиÑ\85 може се пронађи на „[[Special:SpecialPages|{{int:specialpages}}]]”.",
        "error": "Грешка",
        "databaseerror": "Грешка у бази података",
        "databaseerror-text": "Дошло је до грешке у упиту базе података. \nОво може да указује на грешку у софтверу.",
        "revdelete-edit-reasonlist": "Уреди разлоге за брисање",
        "revdelete-offender": "Аутор измене:",
        "suppressionlog": "Дневник сакривања",
-       "suppressionlogtext": "Испод се налази листа брисања и блокирања који укључује садржај сакривен од администратора. Погледајте [[Special:BlockList|листу блокирања]] за списак тренутних операција забрана и блокирања.",
+       "suppressionlogtext": "Испод се налази списак брисања и блокирања који укључује садржај сакривен од администратора. Погледајте [[Special:BlockList|списак блокирања]] за списак текућих забрана и блокирања.",
        "mergehistory": "Обједињавање историја странице",
        "mergehistory-header": "Ова страница вам допушта да обједините историју измена неке изворне странице у новију страницу.\nУверите се да ће ова промена оставити непромењен садржај историје странице.",
-       "mergehistory-box": "Обједини измене двеју страница:",
+       "mergehistory-box": "Обједињавање измена двеју страница:",
        "mergehistory-from": "Изворна страница:",
        "mergehistory-into": "Одредишна страница:",
        "mergehistory-list": "Обједињива историја измена",
        "mergehistory-revisionrow": "$1 ($2) $3 . . $4 $5 $6",
        "mergelog": "Дневник обједињавања",
        "revertmerge": "растави",
-       "mergelogpagetext": "Испод се налази листа најновијих обједињавања историја једне странице у другу.",
+       "mergelogpagetext": "Испод се налази списак најновијих обједињавања историја једне странице у другу.",
        "history-title": "Историја измена странице „$1”",
        "difference-title": "Разлика између измена на страници „$1”",
        "difference-title-multipage": "Разлика између страница „$1“ и „$2“",
        "upload-preferred": "Препоручени {{PLURAL:$2|тип|типови}} датотека: $1.",
        "upload-prohibited": "Забрањени {{PLURAL:$2|тип|типови}} датотека: $1.",
        "uploadlogpage": "Дневник отпремања",
-       "uploadlogpagetext": "Испод се налази листа најновијих отпремања.\nПогледајте [[Special:NewFiles|галерију нових датотека]] за визуелнији преглед.",
+       "uploadlogpagetext": "Испод се налази списак најновијих отпремања.\nПогледајте [[Special:NewFiles|галерију нових датотека]] за визуелнији преглед.",
        "filename": "Назив датотеке",
        "filedesc": "Опис измене",
        "fileuploadsummary": "Опис измене:",
        "listfiles_search_for": "Претражи име медија:",
        "listfiles-userdoesnotexist": "Кориснички налог „$1“ није отворен.",
        "imgfile": "датотека",
-       "listfiles": "Ð\9bиÑ\81Ñ\82а датотека",
+       "listfiles": "СпиÑ\81ак датотека",
        "listfiles_thumb": "Сличица",
        "listfiles_date": "Датум",
        "listfiles_name": "Назив",
        "filehist-comment": "Коментар",
        "imagelinks": "Употреба датотеке",
        "linkstoimage": "{{PLURAL:$1|Следећа страница користи|$1 следеће странице користе|$1 следећих страница користи}} ову датотеку:",
-       "linkstoimage-more": "Ð\92иÑ\88е Ð¾Ð´ $1 {{PLURAL:$1|Ñ\81Ñ\82Ñ\80аниÑ\86а ÐºÐ¾Ñ\80иÑ\81Ñ\82и|Ñ\81Ñ\82Ñ\80аниÑ\86е ÐºÐ¾Ñ\80иÑ\81Ñ\82е|Ñ\81Ñ\82Ñ\80аниÑ\86а ÐºÐ¾Ñ\80иÑ\81Ñ\82и}} Ð¾Ð²Ñ\83 Ð´Ð°Ñ\82оÑ\82екÑ\83.\nСледеÑ\9bа Ð»Ð¸Ñ\81Ñ\82а Ð¿Ñ\80иказÑ\83Ñ\98е {{PLURAL:$1|пÑ\80вÑ\83 Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\83 ÐºÐ¾Ñ\98а ÐºÐ¾Ñ\80иÑ\81Ñ\82и|пÑ\80ве $1 Ñ\81Ñ\82Ñ\80аниÑ\86е ÐºÐ¾Ñ\98е ÐºÐ¾Ñ\80иÑ\81Ñ\82е|пÑ\80виÑ\85 $1 Ñ\81Ñ\82Ñ\80аниÑ\86а ÐºÐ¾Ñ\98е ÐºÐ¾Ñ\80иÑ\81Ñ\82е}} Ñ\81амо Ð¾Ð²Ñ\83 Ð´Ð°Ñ\82оÑ\82екÑ\83.\nÐ\94оÑ\81Ñ\82Ñ\83пна Ñ\98е Ð¸ [[Special:WhatLinksHere/$2|поÑ\82пÑ\83на Ð»Ð¸Ñ\81Ñ\82а]].",
+       "linkstoimage-more": "Ð\92иÑ\88е Ð¾Ð´ $1 {{PLURAL:$1|Ñ\81Ñ\82Ñ\80аниÑ\86а ÐºÐ¾Ñ\80иÑ\81Ñ\82и|Ñ\81Ñ\82Ñ\80аниÑ\86е ÐºÐ¾Ñ\80иÑ\81Ñ\82е|Ñ\81Ñ\82Ñ\80аниÑ\86а ÐºÐ¾Ñ\80иÑ\81Ñ\82и}} Ð¾Ð²Ñ\83 Ð´Ð°Ñ\82оÑ\82екÑ\83.\nСледеÑ\9bи Ñ\81пиÑ\81ак Ð¿Ñ\80иказÑ\83Ñ\98е {{PLURAL:$1|пÑ\80вÑ\83 Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\83 ÐºÐ¾Ñ\98а ÐºÐ¾Ñ\80иÑ\81Ñ\82и|пÑ\80ве $1 Ñ\81Ñ\82Ñ\80аниÑ\86е ÐºÐ¾Ñ\98е ÐºÐ¾Ñ\80иÑ\81Ñ\82е|пÑ\80виÑ\85 $1 Ñ\81Ñ\82Ñ\80аниÑ\86а ÐºÐ¾Ñ\98е ÐºÐ¾Ñ\80иÑ\81Ñ\82е}} Ñ\81амо Ð¾Ð²Ñ\83 Ð´Ð°Ñ\82оÑ\82екÑ\83.\nÐ\94оÑ\81Ñ\82Ñ\83пан Ñ\98е Ð¸ [[Special:WhatLinksHere/$2|поÑ\82пÑ\83ни Ñ\81пиÑ\81ак]].",
        "nolinkstoimage": "Нема страница које користе ову датотеку.",
        "morelinkstoimage": "Погледајте [[Special:WhatLinksHere/$1|више веза]] до ове датотеке.",
        "linkstoimage-redirect": "$1 (преусмерење датотеке) $2",
        "mimetype": "МИМЕ врста:",
        "download": "преузми",
        "unwatchedpages": "Ненадгледане странице",
-       "listredirects": "Ð\9bиÑ\81Ñ\82а преусмерења",
-       "listduplicatedfiles": "Ð\9bиÑ\81Ñ\82а датотека са дупликатима",
-       "listduplicatedfiles-summary": "Ово је листа датотека чија је најновија верзија дупликат неких других датотека. Само локалне датотеке су приказане.",
+       "listredirects": "СпиÑ\81ак преусмерења",
+       "listduplicatedfiles": "СпиÑ\81ак датотека са дупликатима",
+       "listduplicatedfiles-summary": "Ово је списак датотека чија је најновија верзија дупликат неких других датотека. Само локалне датотеке су приказане.",
        "listduplicatedfiles-entry": "[[:File:$1|$1]] има [[$3|{{PLURAL:$2|један дупликат|$2 дупликата}}]].",
        "unusedtemplates": "Некоришћени шаблони",
        "unusedtemplatestext": "Ова страница наводи све странице у именском простору {{ns:template}} које нису укључене ни на једној другој страници.\nПре брисања проверите да ли друге странице воде до тих шаблона.",
        "pageswithprop-text": "Ова страна излистава стране које имају одређену особину",
        "pageswithprop-prop": "Име особине:",
        "pageswithprop-reverse": "Поређај у супротном редоследу",
-       "pageswithprop-sortbyvalue": "СоÑ\80Ñ\82иÑ\80ај по вредности својства",
+       "pageswithprop-sortbyvalue": "Ð\9fоÑ\80еÑ\92ај по вредности својства",
        "pageswithprop-submit": "Иди",
        "pageswithprop-prophidden-long": "сакривено дуго текстуално својство ($1)",
        "pageswithprop-prophidden-binary": "сакривено дуго бинарно својство ($1)",
        "unusedimages": "Некоришћене датотеке",
        "wantedcategories": "Тражене категорије",
        "wantedpages": "Тражене странице",
-       "wantedpages-summary": "Ð\9bиÑ\81Ñ\82а Ð½ÐµÐ¿Ð¾Ñ\81Ñ\82оÑ\98еÑ\9bиÑ\85 Ñ\81Ñ\82Ñ\80аниÑ\86а Ñ\81а Ð½Ð°Ñ\98виÑ\88е Ð²ÐµÐ·Ð° Ð½Ð° Ñ\9aиÑ\85, Ð½Ð° Ð¾Ð²Ð¾Ð¼ Ñ\81пиÑ\81кÑ\83 Ñ\81е Ð½Ðµ Ð½Ð°Ð»Ð°Ð·Ðµ Ñ\81Ñ\82Ñ\80аниÑ\86е Ð´Ð¾ ÐºÐ¾Ñ\98иÑ\85 Ð²Ð¾Ð´Ðµ Ð¿Ñ\80еÑ\83Ñ\81меÑ\80еÑ\9aа. Ð\97а Ñ\81пиÑ\81ак Ð¿Ð¾ÐºÐ²Ð°Ñ\80ениÑ\85 Ð¿Ñ\80еÑ\83Ñ\81меÑ\80еÑ\9aа, Ð¿Ð¾Ð³Ð»ÐµÐ´Ð°Ñ\98Ñ\82е [[{{#special:BrokenRedirects}}|лиÑ\81Ñ\82Ñ\83 покварених преусмерења]].",
+       "wantedpages-summary": "СпиÑ\81ак Ð½ÐµÐ¿Ð¾Ñ\81Ñ\82оÑ\98еÑ\9bиÑ\85 Ñ\81Ñ\82Ñ\80аниÑ\86а Ñ\81а Ð½Ð°Ñ\98виÑ\88е Ð²ÐµÐ·Ð° Ð½Ð° Ñ\9aиÑ\85, Ð½Ð° Ð¾Ð²Ð¾Ð¼ Ñ\81пиÑ\81кÑ\83 Ñ\81е Ð½Ðµ Ð½Ð°Ð»Ð°Ð·Ðµ Ñ\81Ñ\82Ñ\80аниÑ\86е Ð´Ð¾ ÐºÐ¾Ñ\98иÑ\85 Ð²Ð¾Ð´Ðµ Ð¿Ñ\80еÑ\83Ñ\81меÑ\80еÑ\9aа. Ð\97а Ñ\81пиÑ\81ак Ð¿Ð¾ÐºÐ²Ð°Ñ\80ениÑ\85 Ð¿Ñ\80еÑ\83Ñ\81меÑ\80еÑ\9aа, Ð¿Ð¾Ð³Ð»ÐµÐ´Ð°Ñ\98Ñ\82е [[{{#special:BrokenRedirects}}|Ñ\81пиÑ\81ак покварених преусмерења]].",
        "wantedpages-badtitle": "Невалидан наслов у скупу резултата: $1",
        "wantedfiles": "Тражене датотеке",
        "wantedfiletext-cat": "Следеће датотеке се користе, али не постоје. Датотеке из других ризница могу бити наведене иако не постоје. Такве датотеке ће бити <del>поништене</del> са списка. Поред тога, странице које садрже непостојеће датотеке се налазе [[:$1|овде]].",
        "protectedtitles-summary": "Ова страница наводи наслове који су тренутно заштићени од прављења. За листу постојећих страница које су заштићене, погледајте [[{{#special:ProtectedPages}}|{{int:protectedpages}}]].",
        "protectedtitlesempty": "Нема заштићених наслова с овим параметрима.",
        "protectedtitles-submit": "Прикажи наслове",
-       "listusers": "Ð\9bиÑ\81Ñ\82а корисника",
+       "listusers": "СпиÑ\81ак корисника",
        "listusers-editsonly": "Прикажи само кориснике који су уређивали",
        "listusers-temporarygroupsonly": "Прикажи само кориснике у привременим корисничким групама",
-       "listusers-creationsort": "СоÑ\80Ñ\82иÑ\80аÑ\98 Ð¿Ð¾ Ð´Ð°Ñ\82Ñ\83мÑ\83 Ð¿Ñ\80авÑ\99еÑ\9aа",
+       "listusers-creationsort": "Ð\9fоÑ\80еÑ\92аÑ\98 Ð¿Ð¾ Ð´Ð°Ñ\82Ñ\83мÑ\83 Ð¾Ñ\82ваÑ\80аÑ\9aа Ð½Ð°Ð»Ð¾Ð³а",
        "listusers-desc": "Поређај у опадајућем редоследу",
        "usereditcount": "$1 {{PLURAL:$1|измена|измене|измена}}",
        "usercreated": "{{GENDER:$3|је направио|је направила|је направио}} дана $1 у $2",
        "booksources-search-legend": "Претражи штампане изворе",
        "booksources-isbn": "ISBN:",
        "booksources-search": "Претражи",
-       "booksources-text": "Испод се налази листа веза на друге сајтове који се баве продајом нових и половних књига, а који би могли имати додатне податке о књигама које тражите:",
+       "booksources-text": "Испод се налази списак веза на друге сајтове који се баве продајом нових и половних књига, а који би могли имати додатне податке о књигама које тражите:",
        "booksources-invalid-isbn": "Наведени ISBN број није валидан. Проверите да није дошло до грешке при копирању из првобитног извора.",
        "magiclink-tracking-rfc": "Странице са чаробним RFC везама",
        "magiclink-tracking-pmid": "Странице са чаробним PMID везама",
        "listusers-submit": "Прикажи",
        "listusers-noresult": "Корисник није пронађен.",
        "listusers-blocked": "({{GENDER:$1|блокиран|блокирана|блокиран}})",
-       "activeusers": "Ð\9bиÑ\81Ñ\82а активних корисника",
-       "activeusers-intro": "Ово је листа корисника који су били активни {{PLURAL:$1|1=претходни дан|у последња $1 дана|у последњих $1 дана}}.",
+       "activeusers": "СпиÑ\81ак активних корисника",
+       "activeusers-intro": "Ово је списак корисника који су били активни {{PLURAL:$1|1=претходни дан|у последња $1 дана|у последњих $1 дана}}.",
        "activeusers-count": "$1 {{PLURAL:$1|радња|радње|радњи}} {{PLURAL:$3|претходни дан|у последња $3 дана|у последњих $3 дана}}",
        "activeusers-from": "Прикажи кориснике почев од:",
        "activeusers-groups": "Прикажи кориснике који су чланови група:",
        "actionfailed": "Радња није успела",
        "deletedtext": "Страница „$1“ је избрисана.\nПогледајте $2 за запис недавних брисања.",
        "dellogpage": "Дневник брисања",
-       "dellogpagetext": "Испод се налази листа најновијих брисања.",
+       "dellogpagetext": "Испод се налази списак најновијих брисања.",
        "deletionlog": "дневник брисања",
        "log-name-create": "Дневник прављења страница",
-       "log-description-create": "Испод се налази листа најновијих прављења страница.",
+       "log-description-create": "Испод се налази списак најновијих прављења страница.",
        "logentry-create-create": "$1 је {{GENDER:$2|направио|направила}} страницу $3",
        "reverted": "Враћено на ранију измену",
        "deletecomment": "Разлог:",
        "logentry-contentmodel-change-revertlink": "врати",
        "logentry-contentmodel-change-revert": "врати",
        "protectlogpage": "Дневник заштите",
-       "protectlogtext": "Испод се налази листа промена заштите страница.\nПогледајте [[Special:ProtectedPages|листу заштићених страница]] за тренутно оперативне заштите страница.",
+       "protectlogtext": "Испод се налази списак промена заштите страница.\nПогледајте [[Special:ProtectedPages|списак заштићених страница]] за текуће заштите страница.",
        "protectedarticle": "је {{GENDER:|заштитио|заштитила}} страницу „[[$1]]“",
        "modifiedarticleprotection": "је {{GENDER:|променио|променила}} ниво заштите странице „[[$1]]“",
        "unprotectedarticle": "је скинуо заштиту са странице „[[$1]]“",
        "blocklist": "Блокирани корисници",
        "autoblocklist": "Аутоблокови",
        "autoblocklist-submit": "Претражи",
-       "autoblocklist-legend": "Ð\9bиÑ\81Ñ\82а аутоблокирања",
+       "autoblocklist-legend": "СпиÑ\81ак аутоблокирања",
        "autoblocklist-localblocks": "{{PLURAL:$1|Локални аутоблок|Локални аутоблокови}}",
        "autoblocklist-total-autoblocks": "Укупно аутоблокова: $1",
-       "autoblocklist-empty": "Ð\9bиÑ\81Ñ\82а Ð°Ñ\83Ñ\82облокиÑ\80аÑ\9aа Ñ\98е Ð¿Ñ\80азна.",
+       "autoblocklist-empty": "СпиÑ\81ак Ð°Ñ\83Ñ\82облокиÑ\80аÑ\9aа Ñ\98е Ð¿Ñ\80азан.",
        "autoblocklist-otherblocks": "{{PLURAL:$1|Други аутоблок|Други аутоблокови}}",
        "ipblocklist": "Блокирани корисници",
        "ipblocklist-legend": "Проналажење блокираног корисника",
        "blocklist-editing-sitewide": "уређивање (на целом сајту)",
        "blocklist-editing-page": "странице",
        "blocklist-editing-ns": "именски простори",
-       "ipblocklist-empty": "Ð\9bиÑ\81Ñ\82а Ð±Ð»Ð¾ÐºÐ¸Ñ\80аÑ\9aа Ñ\98е Ð¿Ñ\80азна.",
+       "ipblocklist-empty": "СпиÑ\81ак Ð±Ð»Ð¾ÐºÐ¸Ñ\80аÑ\9aа Ñ\98е Ð¿Ñ\80азан.",
        "ipblocklist-no-results": "Тражена IP адреса или корисничко име није блокирано.",
        "blocklink": "блокирај",
        "unblocklink": "деблокирај",
        "movepage-page-unmoved": "Страница $1 не може да се премести на $2.",
        "movepage-max-pages": "Највише $1 {{PLURAL:$1|страница је премештена|странице су премештене|страница је премештено}} и више не може да буде аутоматски премештено.",
        "movelogpage": "Дневник премештања",
-       "movelogpagetext": "Испод се налази листа свих премештања страница.",
+       "movelogpagetext": "Испод се налази списак свих премештања страница.",
        "movesubpage": "{{PLURAL:$1|Подстраница|Подстранице}}",
        "movesubpagetext": "Ова страница има $1 {{PLURAL:$1|подстраницу приказану|подстранице приказане|подстраница приказаних}} испод.",
        "movenosubpage": "Ова страница нема подстрана.",
        "allmessagesname": "Назив",
        "allmessagesdefault": "Подразумевани текст",
        "allmessagescurrent": "Актуелни текст поруке",
-       "allmessagestext": "Ово је листа системских порука доступних у именском простору „Медијавики”.\nПосетите [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation локализацију Медијавикија] и [https://translatewiki.net translatewiki.net] ако желите да допринесете општој локализацији Медијавикија.",
+       "allmessagestext": "Ово је списак системских порука доступних у именском простору „Медијавики”.\nПосетите [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation локализацију Медијавикија] и [https://translatewiki.net translatewiki.net] ако желите да допринесете општој локализацији Медијавикија.",
        "allmessagesnotsupportedDB": "Ова страница не може да се користи јер је '''$wgUseDatabaseMessages''' онемогућен.",
        "allmessages-filter-legend": "Филтер",
        "allmessages-filter": "Филтрирај по стању:",
        "tooltip-pt-mytalk": "{{GENDER:|Ваша}} страница за разговор",
        "tooltip-pt-anontalk": "Дискусија о изменама са ове IP адресе",
        "tooltip-pt-preferences": "{{GENDER:|Ваша}} подешавања",
-       "tooltip-pt-watchlist": "Ð\9bиÑ\81Ñ\82а страница чије промене надгледате",
-       "tooltip-pt-mycontris": "Ð\9bиÑ\81Ñ\82а {{GENDER:|ваших}} доприноса",
-       "tooltip-pt-anoncontribs": "Ð\9bиÑ\81Ñ\82а измена направљених са ове IP адресе",
+       "tooltip-pt-watchlist": "СпиÑ\81ак страница чије промене надгледате",
+       "tooltip-pt-mycontris": "СпиÑ\81ак {{GENDER:|ваших}} доприноса",
+       "tooltip-pt-anoncontribs": "СпиÑ\81ак измена направљених са ове IP адресе",
        "tooltip-pt-login": "Предлажемо вам да се пријавите, иако то није обавезно",
        "tooltip-pt-login-private": "Морате да се пријавите да бисте користили овај Вики",
        "tooltip-pt-logout": "Одјавите се",
        "tooltip-n-mainpage-description": "Посетите главну страну",
        "tooltip-n-portal": "О пројекту, шта можете да радите и где да пронађете ствари",
        "tooltip-n-currentevents": "Пронађите информације о актуелностима",
-       "tooltip-n-recentchanges": "Ð\9bиÑ\81Ñ\82а недавних промена на викију",
+       "tooltip-n-recentchanges": "СпиÑ\81ак недавних промена на викију",
        "tooltip-n-randompage": "Учитајте случајну страницу",
        "tooltip-n-help": "Место где можете да се информишете",
-       "tooltip-t-whatlinkshere": "Ð\9bиÑ\81Ñ\82а свих вики страница које воде овде",
+       "tooltip-t-whatlinkshere": "СпиÑ\81ак свих вики страница које воде овде",
        "tooltip-t-recentchangeslinked": "Недавне промене на страницама које воде на ову страницу",
        "tooltip-feed-rss": "RSS фид за ову страницу",
        "tooltip-feed-atom": "Atom фид за ову страницу",
-       "tooltip-t-contributions": "Ð\9bиÑ\81Ñ\82а доприноса {{GENDER:$1|овог корисника|ове кориснице|овог корисника}}",
+       "tooltip-t-contributions": "СпиÑ\81ак доприноса {{GENDER:$1|овог корисника|ове кориснице|овог корисника}}",
        "tooltip-t-emailuser": "Пошаљите е-поруку {{GENDER:$1|овом кориснику|овој корисници|кориснику/ци}}",
        "tooltip-t-info": "Више информација о овој страници",
        "tooltip-t-upload": "Отпремите датотеке",
-       "tooltip-t-specialpages": "Ð\9bиÑ\81Ñ\82а свих посебних страница",
+       "tooltip-t-specialpages": "СпиÑ\81ак свих посебних страница",
        "tooltip-t-print": "Верзија ове странице за штампање",
        "tooltip-t-permalink": "Трајна веза ка овој измени странице",
        "tooltip-ca-nstab-main": "Погледајте страницу са садржајем",
        "pageinfo-header-restrictions": "Заштита странице",
        "pageinfo-header-properties": "Својства странице",
        "pageinfo-display-title": "Наслов за приказ",
-       "pageinfo-default-sort": "Ð\9fодÑ\80азÑ\83мевани ÐºÑ\99Ñ\83Ñ\87 Ñ\81оÑ\80Ñ\82иÑ\80ања",
+       "pageinfo-default-sort": "Ð\9fодÑ\80азÑ\83мевани ÐºÑ\99Ñ\83Ñ\87 Ñ\80еÑ\92ања",
        "pageinfo-length": "Дужина странице (у бајтовима)",
        "pageinfo-namespace": "Именски простор",
        "pageinfo-article-id": "ID странице",
        "file-no-thumb-animation": "<strong>Напомена: Због техничких ограничења, сличице ове датотеке неће да се анимирају.</strong>",
        "file-no-thumb-animation-gif": "'''Напомена: због техничких ограничења, минијатуре GIF слика високе резолуције као што је ова неће се анимирати.'''",
        "newimages": "Галерија нових датотека",
-       "imagelisttext": "Испод се налази списак од <strong>$1</strong> {{PLURAL:$1|датотеке|датотеке|датотека}} сортираних $2.",
+       "imagelisttext": "Испод се налази списак од <strong>$1</strong> {{PLURAL:$1|датотеке|датотеке|датотека}} поређаних $2.",
        "newimages-summary": "Ова посебна страница приказује последње отпремљене датотеке.",
        "newimages-legend": "Филтер",
        "newimages-label": "Назив датотеке (или њен део):",
        "tags-deactivate-question": "Деактивирате ознаку „$1“.",
        "tags-deactivate-reason": "Разлог:",
        "tags-deactivate-not-allowed": "Није могуће деактивирати ознаку „$1”.",
-       "tags-deactivate-submit": "Ð\94екативирај",
+       "tags-deactivate-submit": "Ð\94еактивирај",
        "tags-apply-no-permission": "Немате дозволу да примените ознаке промена заједно са својим променама.",
        "tags-apply-blocked": "Не можете да примените ознаке промена заједно са вашим променама све док {{GENDER:$1|сте}} блокирани.",
        "tags-update-no-permission": "Немате дозволу да додате или уклоните ознаке промена из појединачних измена или уноса у дневнику.",
        "pagedata-bad-title": "Невалидан наслов: $1.",
        "unregistered-user-config": "Из безбедоносних разлога, јаваскрипт, Це-Ес-Ес и ЈСОН корисничке подстранице не могу бити учитане за нерегистроване кориснике.",
        "passwordpolicies": "Правила за лозинке",
-       "passwordpolicies-summary": "Ово је листа делотворних смерница за лозинке за корисничке групе дефинисане на овом викију.",
+       "passwordpolicies-summary": "Ово је списак делотворних смерница за лозинке за корисничке групе дефинисане на овом викију.",
        "passwordpolicies-group": "Група",
        "passwordpolicies-policies": "Правила",
        "passwordpolicies-policy-display": "<span class=\"passwordpolicies-policy\">$1 <code>($2)</code></span>",
index 9c5db0f..c6e520d 100644 (file)
        "page_first": "перша",
        "page_last": "остання",
        "histlegend": "Вибір версії: позначте у кружечках версії для порівняння і натисніть «Enter» або кнопку внизу.<br />\nПояснення: <strong>({{int:cur}})</strong> = відмінності від поточної версії, <strong>({{int:last}})</strong> = відмінності від попередньої версії, <strong>{{int:minoreditletter}}</strong> = незначне редагування.",
-       "history-fieldset-title": "Ð\9fоÑ\88Ñ\83к Ð²ÐµÑ\80Ñ\81Ñ\96й",
+       "history-fieldset-title": "ФÑ\96лÑ\8cÑ\82Ñ\80Ñ\83ваÑ\82и Ð²ÐµÑ\80Ñ\81Ñ\96Ñ\97",
        "history-show-deleted": "Лише вилучені версії",
        "histfirst": "найдавніші",
        "histlast": "найновіші",
        "historysize": "($1 {{PLURAL:$1|байт|байти|байтів}})",
-       "historyempty": "(порожня)",
+       "historyempty": "порожня",
        "history-feed-title": "Історія редагувань",
        "history-feed-description": "Історія редагувань цієї сторінки в вікі",
        "history-feed-item-nocomment": "$1 в $2",
        "action-changetags": "додавання або вилучення будь-яких міток для певних версій сторінок або записів журналів",
        "action-deletechangetags": "вилучення міток з бази даних",
        "action-purge": "очищення кешу цієї сторінки",
+       "action-apihighlimits": "використання вищих лімітів у API-запитах",
+       "action-autoconfirmed": "без обмежень швидкості за IP",
+       "action-bigdelete": "вилучення сторінок з великою історією",
+       "action-blockemail": "блокування користувача від надсилання електронної пошти",
+       "action-bot": "вважатися автоматичним процесом",
+       "action-editprotected": "редагування сторінок з рівнем захисту «{{int:protect-level-sysop}}»",
+       "action-editsemiprotected": "редагування сторінок з рівнем захисту «{{int:protect-level-autoconfirmed}}»",
+       "action-editinterface": "редагування інтерфейсу користувача",
+       "action-editusercss": "редагування CSS-файлів інших користувачів",
+       "action-edituserjson": "редагування JSON-файлів інших користувачів",
+       "action-edituserjs": "редагування JavaScript-файлів інших користувачів",
+       "action-editsitecss": "редагування загального CSS",
+       "action-editsitejson": "редагування загального JSON",
+       "action-editsitejs": "редагування загального JavaScript",
+       "action-editmyusercss": "редагування власних CSS-файлів користувача",
+       "action-editmyuserjson": "редагування власних JSON-файлів користувача",
+       "action-editmyuserjs": "редагування власних JavaScript-файлів користувача",
+       "action-viewsuppressed": "перегляд змін, прихованих від усіх користувачів",
+       "action-hideuser": "блокування імені користувача і приховування його",
+       "action-ipblock-exempt": "уникнення IP-блокування, автоблокування і блокування діапазонів",
+       "action-unblockself": "розблоковування себе",
+       "action-noratelimit": "звільнення від обмежень швидкості",
+       "action-reupload-own": "перезаписування існуючих файлів, завантажених тим самим користувачем",
+       "action-nominornewtalk": "незначні редагування на сторінках обговорень користувачів не викликають попередження про нові повідомлення",
+       "action-markbotedits": "позначення відкинутих редагувань як редагування бота",
+       "action-patrolmarks": "перегляд позначок патрулювання у нових редагуваннях",
+       "action-override-export-depth": "експорт сторінок, включаючи пов'язані сторінки з глибиною до 5",
+       "action-suppressredirect": "нестворення перенаправлення зі старої назви на нову при перейменуванні сторінки",
        "nchanges": "$1 {{PLURAL:$1|зміна|зміни|змін}}",
        "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|зміна з останнього візиту|зміни з останнього візиту|змін з останнього візиту}}",
        "enhancedrc-history": "історія",
        "delete-confirm": "Вилучення «$1»",
        "delete-legend": "Вилучення",
        "historywarning": "<strong>Попередження:</strong> Сторінка, яку ви збираєтеся вилучити, має історію редагувань з приблизно $1 {{PLURAL:$1|1=версії|версій}}:",
-       "historyaction-submit": "Показати",
+       "historyaction-submit": "Показати версії",
        "confirmdeletetext": "Ви збираєтесь вилучити сторінку і всі її журнали редагувань з бази даних.\nБудь ласка, підтвердіть, що Ви бажаєте зробити це, повністю розумієте наслідки і що робите це у відповідності з [[{{MediaWiki:Policy-url}}|правилами]].",
        "actioncomplete": "Дію виконано",
        "actionfailed": "Виконати дію не вдалося",
index fd4f1bd..401b68f 100644 (file)
        "history-feed-description": "響哩個wiki嘅哩一頁嘅修訂歷史",
        "history-feed-item-nocomment": "$1 響 $2",
        "history-feed-empty": "要求嘅頁面並唔存在。\n佢可能響哩個 wiki 度刪除咗或者改咗名。\n試吓[[Special:Search|響哩個wiki度搵]]有關新頁面嘅資料。",
-       "history-edit-tags": "編輯已經揀咗嘅分頁",
+       "history-edit-tags": "改揀咗嘅改動嘅標籤",
        "rev-deleted-comment": "(編輯摘要已經移除咗)",
        "rev-deleted-user": "(用戶名已經移除咗)",
        "rev-deleted-event": "(日誌詳情已經移除咗)",
index 232aef2..9be74fd 100644 (file)
        "page_first": "首页",
        "page_last": "末页",
        "histlegend": "差异选择:选中要对比的版本的单选按钮,按Enter键或下方的按钮。<br />说明:<strong>({{int:cur}})</strong>=与最后版本之间的差异,<strong>({{int:last}})</strong>=与上一版本之间的差异,<strong>{{int:minoreditletter}}</strong>=小编辑。",
-       "history-fieldset-title": "搜索修订版本",
+       "history-fieldset-title": "筛选修订版本",
        "history-show-deleted": "仅限修订版本删除",
        "histfirst": "最旧",
        "histlast": "最新",
        "historysize": "($1字节)",
-       "historyempty": "(空)",
+       "historyempty": "",
        "history-feed-title": "版本历史",
        "history-feed-description": "本wiki的该页面的版本历史",
        "history-feed-item-nocomment": "$2 $1",
        "right-reupload-own": "覆盖自己上传的文件",
        "right-reupload-shared": "本地覆盖共享文件库的文件",
        "right-upload_by_url": "从URL上传文件",
-       "right-purge": "æ\97 ç¡®è®¤æ¸\85é\99¤é¡µé\9d¢ç¼\93å­\98",
+       "right-purge": "清除页面缓存",
        "right-autoconfirmed": "不受基于IP的速率限制",
        "right-bot": "被视为自动程序",
        "right-nominornewtalk": "不使小编辑在讨论页面引发新信息提示",
        "action-changetags": "在个别修订和日志记录中添加和移除任意标签",
        "action-deletechangetags": "从数据库删除标签",
        "action-purge": "刷新此页面",
+       "action-apihighlimits": "在API查询中使用更高的上限",
+       "action-autoconfirmed": "不受基于IP的速率限制",
+       "action-bigdelete": "删除有大型历史的页面",
+       "action-blockemail": "阻止用户发送电子邮件",
+       "action-bot": "被视为自动程序",
+       "action-editprotected": "编辑保护级别为“{{int:protect-level-sysop}}”的页面",
+       "action-editsemiprotected": "编辑保护级别为“{{int:protect-level-autoconfirmed}}”的页面",
+       "action-editinterface": "编辑用户界面",
+       "action-editusercss": "编辑其他用户的CSS文件",
+       "action-edituserjson": "编辑其他用户的JSON文件",
+       "action-edituserjs": "编辑其他用户的JavaScript文件",
+       "action-editsitecss": "编辑全站CSS",
+       "action-editsitejson": "编辑全站JSON",
+       "action-editsitejs": "编辑全站JavaScript",
+       "action-editmyusercss": "编辑您的用户CSS文件",
+       "action-editmyuserjson": "编辑您的用户JSON文件",
+       "action-editmyuserjs": "编辑您的用户JavaScript文件",
+       "action-viewsuppressed": "查看被隐藏的任何用户的修订",
+       "action-hideuser": "封禁并隐藏用户名",
+       "action-ipblock-exempt": "绕过IP封禁、自动封禁和段封禁",
+       "action-unblockself": "自我解封",
+       "action-noratelimit": "不受速率限制影响",
+       "action-reupload-own": "覆盖自己上传的文件",
+       "action-nominornewtalk": "不使小编辑在讨论页面引发新信息提示",
+       "action-markbotedits": "标记回退编辑为机器人编辑",
+       "action-patrolmarks": "查看最近更改的巡查标记",
+       "action-override-export-depth": "导出页面,包括最多5层链接",
+       "action-suppressredirect": "移动页面时不创建来源页面的重定向",
        "nchanges": "$1次更改",
        "enhancedrc-since-last-visit": "{{PLURAL:$1|自上次访问}}有$1次",
        "enhancedrc-history": "历史",
        "delete-confirm": "删除“$1”",
        "delete-legend": "删除",
        "historywarning": "<strong>警告:</strong>您将要删除的页面有约$1次{{PLURAL:$1|修订}}的历史:",
-       "historyaction-submit": "显示",
+       "historyaction-submit": "显示版本",
        "confirmdeletetext": "您即将删除一个页面或图像以及其历史。请确定您要进行此项操作,并且了解其后果,同时您的行为符合[[{{MediaWiki:Policy-url}}|方针]]。",
        "actioncomplete": "操作完成",
        "actionfailed": "操作失败",
index 33ce701..0d376f5 100644 (file)
        "page_first": "第一頁",
        "page_last": "最後頁",
        "histlegend": "比對選擇的版本差異:選擇要比對修訂版本的單選方塊並點選底部的按鈕進行比對。<br />\n符號說明:<strong>({{int:cur}})</strong> = 與最新的修訂版本比對,<strong>({{int:last}})</strong> = 與前一筆修訂版本比對,<strong>{{int:minoreditletter}}</strong> = 次要修訂。",
-       "history-fieldset-title": "搜尋修訂",
+       "history-fieldset-title": "篩選修訂",
        "history-show-deleted": "只顯示已刪除的修訂",
        "histfirst": "最舊",
        "histlast": "最新",
        "rev-suppressed-text-view": "此頁面修訂已被 <strong>禁止顯示</strong>。\n您可繼續檢視修訂,可至 [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} 禁止顯示日誌] 取得詳細資訊。",
        "rev-deleted-no-diff": "因頁面的其中一次修訂已被 <strong>刪除</strong>,您無法檢視差異。\n可至 [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} 刪除日誌] 取得詳細資訊。",
        "rev-suppressed-no-diff": "因頁面的其中一次修訂已被 <strong>刪除</strong>,您無法檢視差異。",
-       "rev-deleted-unhide-diff": "檢視差異的其中一個修訂已被 <strong>刪除</strong>。\n可至 [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} 刪除日誌] 取得詳細資料。\n若您要繼續,您仍可以 [$1 檢視此差異]。",
+       "rev-deleted-unhide-diff": "檢視差異的其中一個修訂已被 <strong>刪除</strong>。可至[{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} 刪除日誌]取得詳細資料。若您要繼續,您仍可以[$1 檢視此差異]。",
        "rev-suppressed-unhide-diff": "檢視差異的其中一個修訂已被 <strong>禁止顯示</strong>。\n可至 [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} 禁止顯示日誌] 取得詳細資訊。\n若您要繼續,您仍可以 [$1 檢視此差異]。",
        "rev-deleted-diff-view": "檢視差異的其中一個修訂已被 <strong>刪除</strong>。\n您可繼續檢視差異,可至 [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} 刪除日誌] 取得詳細資訊。",
        "rev-suppressed-diff-view": "檢視差異的其中一個修訂已被 <strong>禁止顯示</strong>。\n您可繼續檢視差異,可至 [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} 禁止顯示日誌] 取得詳細資訊。",
        "right-suppressredirect": "移動頁面時不在原頁面位置建立重新導向頁面",
        "right-upload": "上傳檔案",
        "right-reupload": "覆蓋現有的檔案",
-       "right-reupload-own": "覆蓋自己上傳的檔案",
+       "right-reupload-own": "覆蓋自己上傳的現有檔案",
        "right-reupload-shared": "覆蓋共用媒體檔案庫於本地的檔案",
        "right-upload_by_url": "使用 URL 上傳檔案",
        "right-purge": "清除頁面的站台快取",
        "right-block": "封鎖其他使用者的編輯權限",
        "right-blockemail": "封鎖使用者傳送電子郵件",
        "right-hideuser": "封鎖使用者名稱,避免公開顯示",
-       "right-ipblock-exempt": "略過 IP 封鎖、自動封鎖及範圍封鎖檢查",
+       "right-ipblock-exempt": "略過IP封鎖、自動封鎖及範圍封鎖檢查",
        "right-unblockself": "解除封鎖自己",
        "right-protect": "更改保護層級及編輯被連鎖保護的頁面",
        "right-editprotected": "編輯保護層級為「{{int:protect-level-sysop}}」的頁面",
        "right-editmyprivateinfo": "編輯自己的隱私資料 (如:電子郵件地址及真實姓名)",
        "right-editmyoptions": "編輯自己的偏好設定",
        "right-rollback": "快速回退最後一位使用者對某一頁面的編輯",
-       "right-markbotedits": "標示還原編輯為機人編輯",
+       "right-markbotedits": "標示還原編輯為機人編輯",
        "right-noratelimit": "不受使用頻率限制",
        "right-import": "由其他 Wiki 匯入頁面",
        "right-importupload": "由檔案上傳匯入頁面",
        "right-userrights": "編輯所有使用者的權限",
        "right-userrights-interwiki": "編輯使用者在其它 Wiki 上的權限",
        "right-siteadmin": "鎖定和解除鎖定資料庫",
-       "right-override-export-depth": "匯出頁面包含連結內容,深度上限為 5 層",
+       "right-override-export-depth": "匯出頁面包含連結內容,深度上限為5層",
        "right-sendemail": "傳送電子郵件聯絡其他使用者",
        "right-managechangetags": "建立並自資料庫 (取消) 啟用 [[Special:Tags|標籤]]",
        "right-applychangetags": "連同自己的變更一起套用[[Special:Tags|標籤]]",
        "action-changetags": "加入與移除任何於各別修訂與日誌項目的標籤",
        "action-deletechangetags": "從資料庫刪除標籤",
        "action-purge": "清除此頁面",
+       "action-apihighlimits": "提高API查詢上限值",
+       "action-autoconfirmed": "不受基於IP的使用頻率限制",
+       "action-bigdelete": "刪除有大量歷史記錄的頁面",
+       "action-blockemail": "封鎖使用者傳送電子郵件",
+       "action-bot": "將其視為自動程序",
+       "action-editprotected": "編輯保護層級為「{{int:protect-level-sysop}}」的頁面",
+       "action-editsemiprotected": "編輯保護層級為「{{int:protect-level-autoconfirmed}}」的頁面",
+       "action-editinterface": "編輯使用者介面",
+       "action-editusercss": "編輯其他使用者的 CSS 檔",
+       "action-edituserjson": "編輯其他使用者的 JSON 檔",
+       "action-edituserjs": "編輯其他使用者的 JavaScript 檔",
+       "action-editsitecss": "編輯全站 CSS",
+       "action-editsitejson": "編輯全站 JSON",
+       "action-editsitejs": "編輯全站 JavaScript",
+       "action-editmyusercss": "編輯您自己的使用者 CSS 檔",
+       "action-editmyuserjson": "編輯您自己的使用者 JSON 檔",
+       "action-editmyuserjs": "編輯自己的 JavaScript 檔",
+       "action-viewsuppressed": "檢視所有使用者隱藏的修訂",
+       "action-hideuser": "封鎖使用者名稱,避免公開顯示",
+       "action-ipblock-exempt": "略過IP封鎖、自動封鎖及範圍封鎖檢查",
+       "action-unblockself": "解除封鎖自己",
+       "action-noratelimit": "不受使用頻率限制",
+       "action-reupload-own": "覆蓋自己上傳的現有檔案",
+       "action-nominornewtalk": "不顯示討論頁面中次要編輯的新訊息提示",
+       "action-markbotedits": "標示還原編輯為機器人編輯",
+       "action-patrolmarks": "檢視近期變更的巡查標記",
+       "action-override-export-depth": "匯出頁面包含連結內容,深度上限為5層",
+       "action-suppressredirect": "移動頁面時不在原頁面位置建立重新導向頁面",
        "nchanges": "$1 次變更",
        "enhancedrc-since-last-visit": "{{PLURAL:$1|自上次拜訪}}有$1次",
        "enhancedrc-history": "歷史",
        "delete-confirm": "刪除 \"$1\"",
        "delete-legend": "刪除",
        "historywarning": "<strong>警告:</strong>您正要刪除的頁面內含 $1 次{{PLURAL:$1|的修訂}}歷史:",
-       "historyaction-submit": "顯示",
+       "historyaction-submit": "顯示修訂",
        "confirmdeletetext": "您正要刪除一個頁面或圖片以及其所有歷史。請確定您要進行此操作,並了解其後果,同時您的行為符合[[{{MediaWiki:Policy-url}}|方針]]。",
        "actioncomplete": "操作完成",
        "actionfailed": "操作失敗",
        "logentry-newusers-autocreate": "已自動{{GENDER:$2|建立}}使用者帳號 $1",
        "logentry-protect-move_prot": "$1 {{GENDER:$2|已移動}}保護設定從 $4 至 $3",
        "logentry-protect-unprotect": "$1 {{GENDER:$2|已移除}} $3 的保護",
-       "logentry-protect-protect": "$1 {{GENDER:$2|pó-hō͘ liáu}} $3 $4",
+       "logentry-protect-protect": "$1 {{GENDER:$2|已保護}} $3 $4",
        "logentry-protect-protect-cascade": "$1 {{GENDER:$2|已保護}} $3 $4 [連鎖]",
        "logentry-protect-modify": "$1 {{GENDER:$2|已變更}} $3 的保護層級 $4",
        "logentry-protect-modify-cascade": "$1 {{GENDER:$2|已變更}} $3 的保護層級 $4 [連鎖]",
index 0d4f14c..c88a1a0 100644 (file)
@@ -42,6 +42,13 @@ define( 'DO_MAINTENANCE', RUN_MAINTENANCE_IF_MAIN ); // original name, harmless
 
 $maintClass = false;
 
+// Some extensions rely on MW_INSTALL_PATH to find core files to include. Setting it here helps them
+// if they're included by a core script (like DatabaseUpdater) after Maintenance.php has already
+// been run.
+if ( strval( getenv( 'MW_INSTALL_PATH' ) ) === '' ) {
+       putenv( 'MW_INSTALL_PATH=' . realpath( __DIR__ . '/..' ) );
+}
+
 use Wikimedia\Rdbms\IDatabase;
 use MediaWiki\Logger\LoggerFactory;
 use MediaWiki\MediaWikiServices;
@@ -171,11 +178,8 @@ abstract class Maintenance {
         * their own constructors
         */
        public function __construct() {
-               // Setup $IP, using MW_INSTALL_PATH if it exists
                global $IP;
-               $IP = strval( getenv( 'MW_INSTALL_PATH' ) ) !== ''
-                       ? getenv( 'MW_INSTALL_PATH' )
-                       : realpath( __DIR__ . '/..' );
+               $IP = getenv( 'MW_INSTALL_PATH' );
 
                $this->addDefaultParams();
                register_shutdown_function( [ $this, 'outputChanneled' ], false );
@@ -717,7 +721,7 @@ abstract class Maintenance {
                }
 
                /**
-                * @var $child Maintenance
+                * @var Maintenance $child
                 */
                $child = new $maintClass();
                $child->loadParamsAndArgs( $this->mSelf, $this->mOptions, $this->mArgs );
@@ -1191,7 +1195,7 @@ abstract class Maintenance {
 
                        if ( $wgDBservers ) {
                                /**
-                                * @var $wgDBservers array
+                                * @var array $wgDBservers
                                 */
                                foreach ( $wgDBservers as $i => $server ) {
                                        $wgDBservers[$i]['user'] = $wgDBuser;
index 04f278f..e1ea247 100644 (file)
@@ -68,7 +68,7 @@ class CleanupSpam extends Maintenance {
                        $this->output( "Finding spam on " . count( $wgLocalDatabases ) . " wikis\n" );
                        $found = false;
                        foreach ( $wgLocalDatabases as $wikiID ) {
-                               /** @var $dbr Database */
+                               /** @var Database $dbr */
                                $dbr = $this->getDB( DB_REPLICA, [], $wikiID );
 
                                foreach ( $protConds as $conds ) {
@@ -97,7 +97,7 @@ class CleanupSpam extends Maintenance {
                        // Clean up spam on this wiki
 
                        $count = 0;
-                       /** @var $dbr Database */
+                       /** @var Database $dbr */
                        $dbr = $this->getDB( DB_REPLICA );
                        foreach ( $protConds as $prot => $conds ) {
                                $res = $dbr->select(
index 1f34b5a..d767df0 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /**
  * Router for the php cli-server built-in webserver.
- * https://secure.php.net/manual/en/features.commandline.webserver.php
+ * https://www.php.net/manual/en/features.commandline.webserver.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
index 1e3d410..cc5e6e5 100755 (executable)
@@ -14,7 +14,7 @@ fi
 
 VER=5.6.32
 TAR="php-$VER.tar.gz"
-PHPURL="https://secure.php.net/get/$TAR/from/this/mirror"
+PHPURL="https://www.php.net/get/$TAR/from/this/mirror"
 
 cd "$DEV"
 
index dc1de4f..af5373c 100644 (file)
@@ -230,7 +230,7 @@ class GenerateSitemap extends Maintenance {
                // Custom priorities
                if ( $wgSitemapNamespacesPriorities !== false ) {
                        /**
-                        * @var $wgSitemapNamespacesPriorities array
+                        * @var array $wgSitemapNamespacesPriorities
                         */
                        foreach ( $wgSitemapNamespacesPriorities as $namespace => $priority ) {
                                $float = floatval( $priority );
index f43d75f..5843f67 100644 (file)
@@ -23,6 +23,8 @@
 
 require_once __DIR__ . '/../Maintenance.php';
 
+use Wikimedia\StaticArrayWriter;
+
 /**
  * Generate first letter data files for Collation.php
  *
index 336495a..e689f7c 100644 (file)
@@ -23,6 +23,8 @@
 
 require_once __DIR__ . '/../Maintenance.php';
 
+use Wikimedia\StaticArrayWriter;
+
 /**
  * Generates the normalizer data file for Arabic.
  *
index 1b8ea09..5f865ce 100644 (file)
@@ -23,6 +23,8 @@
 
 require_once __DIR__ . '/../Maintenance.php';
 
+use Wikimedia\StaticArrayWriter;
+
 /**
  * Generates the normalizer data file for Malayalam.
  *
index 68184ea..eed8019 100644 (file)
@@ -104,7 +104,7 @@ class CheckStorage {
                        );
                        foreach ( $res as $row ) {
                                /**
-                                * @var $flags int
+                                * @var int $flags
                                 */
                                $flags = $row->old_flags;
                                $id = $row->old_id;
index 8a1069e..ac4e120 100644 (file)
@@ -104,7 +104,7 @@ class CompressOld extends Maintenance {
                global $wgDBname;
                if ( !function_exists( "gzdeflate" ) ) {
                        $this->fatalError( "You must enable zlib support in PHP to compress old revisions!\n" .
-                               "Please see https://secure.php.net/manual/en/ref.zlib.php\n" );
+                               "Please see https://www.php.net/manual/en/ref.zlib.php\n" );
                }
 
                $type = $this->getOption( 'type', 'concat' );
index de52e7a..f17b00c 100644 (file)
@@ -272,8 +272,7 @@ class RecompressTracked {
         * Dispatch a command to the next available replica DB.
         * This may block until a replica DB finishes its work and becomes available.
         */
-       function dispatch( /*...*/ ) {
-               $args = func_get_args();
+       function dispatch( ...$args ) {
                $pipes = $this->replicaPipes;
                $x = [];
                $y = [];
index 511cd03..81cb92d 100644 (file)
@@ -2,6 +2,8 @@
  * Copy of CC standard stylesheet, plus tweaks for iframe usage
  */
 
+/* stylelint-disable selector-class-pattern */
+
 body {
        margin: 0;
        background: #eee;
index 1b2574d..8b3b39e 100644 (file)
@@ -1,3 +1,5 @@
+/* stylelint-disable selector-class-pattern */
+
 .env-check {
        font-size: 90%;
        margin: 1em 0 1em 2.5em;
index ba61488..174c7d9 100644 (file)
@@ -2100,7 +2100,15 @@ return [
                ],
        ],
        'mediawiki.special.block' => [
-               'scripts' => 'resources/src/mediawiki.special.block.js',
+               'localBasePath' => "$IP/resources/src",
+               'remoteBasePath' => "$wgResourceBasePath/resources/src",
+               'packageFiles' => [
+                       'mediawiki.special.block.js',
+                       [ 'name' => 'config.json', 'config' => [
+                               'EnablePartialBlocks',
+                               'BlockAllowsUTEdit',
+                       ] ],
+               ],
                'dependencies' => [
                        'oojs-ui-core',
                        'oojs-ui.styles.icons-editing-core',
index 651acc8..dc7379a 100644 (file)
@@ -192,11 +192,11 @@ mustache:
   type: multi-file
   files:
     mustache.js:
-      src: https://raw.githubusercontent.com/janl/mustache.js/v1.0.0/mustache.js
-      integrity: sha384-k2UYqmzoiq/qgIzZvcYBxbXQW4YdPAsXDOTkHTGb9TCZ9sjCkyT4TlaUN0wQRkql
+      src: https://raw.githubusercontent.com/janl/mustache.js/v3.0.1/mustache.js
+      integrity: sha384-YjAj6Nll7fkEWzxTlE9o3NWC9qdZt1Upat6Afjib9eLs8lTODpSKEBHeXq8o/VUH
     LICENSE:
-      src: https://raw.githubusercontent.com/janl/mustache.js/v1.0.0/LICENSE
-      integrity: sha384-MYVwXwula9+YkyXexOJVZ0v0DaVvG22uX57mNq5Di+7u8OH9EG9q3yuXkp1Iehiq
+      src: https://raw.githubusercontent.com/janl/mustache.js/v3.0.1/LICENSE
+      integrity: sha384-j2EDj6YtCRgFvYDtzo6pXzbskIj/K1Yg65BL0j3/L6UIHxbMtRMJwC/W+XoYx0FZ
 
 oojs:
   type: tar
index aa1b831..4df7d1a 100644 (file)
@@ -2,6 +2,7 @@ The MIT License
 
 Copyright (c) 2009 Chris Wanstrath (Ruby)
 Copyright (c) 2010-2014 Jan Lehnardt (JavaScript)
+Copyright (c) 2010-2015 The mustache.js community
 
 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
 
index c7ffbef..8ec1b44 100644 (file)
@@ -3,54 +3,86 @@
  * http://github.com/janl/mustache.js
  */
 
-/*global define: false*/
+/*global define: false Mustache: true*/
 
-(function (global, factory) {
-  if (typeof exports === "object" && exports) {
+(function defineMustache (global, factory) {
+  if (typeof exports === 'object' && exports && typeof exports.nodeName !== 'string') {
     factory(exports); // CommonJS
-  } else if (typeof define === "function" && define.amd) {
+  } else if (typeof define === 'function' && define.amd) {
     define(['exports'], factory); // AMD
   } else {
-    factory(global.Mustache = {}); // <script>
+    global.Mustache = {};
+    factory(global.Mustache); // script, wsh, asp
   }
-}(this, function (mustache) {
+}(this, function mustacheFactory (mustache) {
 
-  var Object_toString = Object.prototype.toString;
-  var isArray = Array.isArray || function (object) {
-    return Object_toString.call(object) === '[object Array]';
+  var objectToString = Object.prototype.toString;
+  var isArray = Array.isArray || function isArrayPolyfill (object) {
+    return objectToString.call(object) === '[object Array]';
   };
 
-  function isFunction(object) {
+  function isFunction (object) {
     return typeof object === 'function';
   }
 
-  function escapeRegExp(string) {
-    return string.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&");
+  /**
+   * More correct typeof string handling array
+   * which normally returns typeof 'object'
+   */
+  function typeStr (obj) {
+    return isArray(obj) ? 'array' : typeof obj;
+  }
+
+  function escapeRegExp (string) {
+    return string.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&');
+  }
+
+  /**
+   * Null safe way of checking whether or not an object,
+   * including its prototype, has a given property
+   */
+  function hasProperty (obj, propName) {
+    return obj != null && typeof obj === 'object' && (propName in obj);
+  }
+
+  /**
+   * Safe way of detecting whether or not the given thing is a primitive and
+   * whether it has the given property
+   */
+  function primitiveHasOwnProperty (primitive, propName) {  
+    return (
+      primitive != null
+      && typeof primitive !== 'object'
+      && primitive.hasOwnProperty
+      && primitive.hasOwnProperty(propName)
+    );
   }
 
   // Workaround for https://issues.apache.org/jira/browse/COUCHDB-577
   // See https://github.com/janl/mustache.js/issues/189
-  var RegExp_test = RegExp.prototype.test;
-  function testRegExp(re, string) {
-    return RegExp_test.call(re, string);
+  var regExpTest = RegExp.prototype.test;
+  function testRegExp (re, string) {
+    return regExpTest.call(re, string);
   }
 
   var nonSpaceRe = /\S/;
-  function isWhitespace(string) {
+  function isWhitespace (string) {
     return !testRegExp(nonSpaceRe, string);
   }
 
   var entityMap = {
-    "&": "&amp;",
-    "<": "&lt;",
-    ">": "&gt;",
+    '&': '&amp;',
+    '<': '&lt;',
+    '>': '&gt;',
     '"': '&quot;',
     "'": '&#39;',
-    "/": '&#x2F;'
+    '/': '&#x2F;',
+    '`': '&#x60;',
+    '=': '&#x3D;'
   };
 
-  function escapeHtml(string) {
-    return String(string).replace(/[&<>"'\/]/g, function (s) {
+  function escapeHtml (string) {
+    return String(string).replace(/[&<>"'`=\/]/g, function fromEntityMap (s) {
       return entityMap[s];
     });
   }
    * array of tokens in the subtree and 2) the index in the original template at
    * which the closing tag for that section begins.
    */
-  function parseTemplate(template, tags) {
+  function parseTemplate (template, tags) {
     if (!template)
       return [];
 
 
     // Strips all whitespace tokens array for the current line
     // if there was a {{#tag}} on it and otherwise only space.
-    function stripSpace() {
+    function stripSpace () {
       if (hasTag && !nonSpace) {
         while (spaces.length)
           delete tokens[spaces.pop()];
     }
 
     var openingTagRe, closingTagRe, closingCurlyRe;
-    function compileTags(tags) {
-      if (typeof tags === 'string')
-        tags = tags.split(spaceRe, 2);
+    function compileTags (tagsToCompile) {
+      if (typeof tagsToCompile === 'string')
+        tagsToCompile = tagsToCompile.split(spaceRe, 2);
 
-      if (!isArray(tags) || tags.length !== 2)
-        throw new Error('Invalid tags: ' + tags);
+      if (!isArray(tagsToCompile) || tagsToCompile.length !== 2)
+        throw new Error('Invalid tags: ' + tagsToCompile);
 
-      openingTagRe = new RegExp(escapeRegExp(tags[0]) + '\\s*');
-      closingTagRe = new RegExp('\\s*' + escapeRegExp(tags[1]));
-      closingCurlyRe = new RegExp('\\s*' + escapeRegExp('}' + tags[1]));
+      openingTagRe = new RegExp(escapeRegExp(tagsToCompile[0]) + '\\s*');
+      closingTagRe = new RegExp('\\s*' + escapeRegExp(tagsToCompile[1]));
+      closingCurlyRe = new RegExp('\\s*' + escapeRegExp('}' + tagsToCompile[1]));
     }
 
     compileTags(tags || mustache.tags);
    * Combines the values of consecutive text tokens in the given `tokens` array
    * to a single token.
    */
-  function squashTokens(tokens) {
+  function squashTokens (tokens) {
     var squashedTokens = [];
 
     var token, lastToken;
    * all tokens that appear in that section and 2) the index in the original
    * template that represents the end of that section.
    */
-  function nestTokens(tokens) {
+  function nestTokens (tokens) {
     var nestedTokens = [];
     var collector = nestedTokens;
     var sections = [];
       token = tokens[i];
 
       switch (token[0]) {
-      case '#':
-      case '^':
-        collector.push(token);
-        sections.push(token);
-        collector = token[4] = [];
-        break;
-      case '/':
-        section = sections.pop();
-        section[5] = token[2];
-        collector = sections.length > 0 ? sections[sections.length - 1][4] : nestedTokens;
-        break;
-      default:
-        collector.push(token);
+        case '#':
+        case '^':
+          collector.push(token);
+          sections.push(token);
+          collector = token[4] = [];
+          break;
+        case '/':
+          section = sections.pop();
+          section[5] = token[2];
+          collector = sections.length > 0 ? sections[sections.length - 1][4] : nestedTokens;
+          break;
+        default:
+          collector.push(token);
       }
     }
 
    * A simple string scanner that is used by the template parser to find
    * tokens in template strings.
    */
-  function Scanner(string) {
+  function Scanner (string) {
     this.string = string;
     this.tail = string;
     this.pos = 0;
   /**
    * Returns `true` if the tail is empty (end of string).
    */
-  Scanner.prototype.eos = function () {
-    return this.tail === "";
+  Scanner.prototype.eos = function eos () {
+    return this.tail === '';
   };
 
   /**
    * Tries to match the given regular expression at the current position.
    * Returns the matched text if it can match, the empty string otherwise.
    */
-  Scanner.prototype.scan = function (re) {
+  Scanner.prototype.scan = function scan (re) {
     var match = this.tail.match(re);
 
     if (!match || match.index !== 0)
    * Skips all text until the given regular expression can be matched. Returns
    * the skipped string, which is the entire tail if no match can be made.
    */
-  Scanner.prototype.scanUntil = function (re) {
+  Scanner.prototype.scanUntil = function scanUntil (re) {
     var index = this.tail.search(re), match;
 
     switch (index) {
-    case -1:
-      match = this.tail;
-      this.tail = "";
-      break;
-    case 0:
-      match = "";
-      break;
-    default:
-      match = this.tail.substring(0, index);
-      this.tail = this.tail.substring(index);
+      case -1:
+        match = this.tail;
+        this.tail = '';
+        break;
+      case 0:
+        match = '';
+        break;
+      default:
+        match = this.tail.substring(0, index);
+        this.tail = this.tail.substring(index);
     }
 
     this.pos += match.length;
    * Represents a rendering context by wrapping a view object and
    * maintaining a reference to the parent context.
    */
-  function Context(view, parentContext) {
-    this.view = view == null ? {} : view;
+  function Context (view, parentContext) {
+    this.view = view;
     this.cache = { '.': this.view };
     this.parent = parentContext;
   }
    * Creates a new context using the given view with this context
    * as the parent.
    */
-  Context.prototype.push = function (view) {
+  Context.prototype.push = function push (view) {
     return new Context(view, this);
   };
 
    * Returns the value of the given name in this context, traversing
    * up the context hierarchy if the value is absent in this context's view.
    */
-  Context.prototype.lookup = function (name) {
+  Context.prototype.lookup = function lookup (name) {
     var cache = this.cache;
 
     var value;
-    if (name in cache) {
+    if (cache.hasOwnProperty(name)) {
       value = cache[name];
     } else {
-      var context = this, names, index;
+      var context = this, intermediateValue, names, index, lookupHit = false;
 
       while (context) {
         if (name.indexOf('.') > 0) {
-          value = context.view;
+          intermediateValue = context.view;
           names = name.split('.');
           index = 0;
 
-          while (value != null && index < names.length)
-            value = value[names[index++]];
-        } else if (typeof context.view == 'object') {
-          value = context.view[name];
+          /**
+           * Using the dot notion path in `name`, we descend through the
+           * nested objects.
+           *
+           * To be certain that the lookup has been successful, we have to
+           * check if the last object in the path actually has the property
+           * we are looking for. We store the result in `lookupHit`.
+           *
+           * This is specially necessary for when the value has been set to
+           * `undefined` and we want to avoid looking up parent contexts.
+           *
+           * In the case where dot notation is used, we consider the lookup
+           * to be successful even if the last "object" in the path is
+           * not actually an object but a primitive (e.g., a string, or an
+           * integer), because it is sometimes useful to access a property
+           * of an autoboxed primitive, such as the length of a string.
+           **/
+          while (intermediateValue != null && index < names.length) {
+            if (index === names.length - 1)
+              lookupHit = (
+                hasProperty(intermediateValue, names[index]) 
+                || primitiveHasOwnProperty(intermediateValue, names[index])
+              );
+
+            intermediateValue = intermediateValue[names[index++]];
+          }
+        } else {
+          intermediateValue = context.view[name];
+
+          /**
+           * Only checking against `hasProperty`, which always returns `false` if
+           * `context.view` is not an object. Deliberately omitting the check
+           * against `primitiveHasOwnProperty` if dot notation is not used.
+           *
+           * Consider this example:
+           * ```
+           * Mustache.render("The length of a football field is {{#length}}{{length}}{{/length}}.", {length: "100 yards"})
+           * ```
+           *
+           * If we were to check also against `primitiveHasOwnProperty`, as we do
+           * in the dot notation case, then render call would return:
+           *
+           * "The length of a football field is 9."
+           *
+           * rather than the expected:
+           *
+           * "The length of a football field is 100 yards."
+           **/
+          lookupHit = hasProperty(context.view, name);
         }
 
-        if (value != null)
+        if (lookupHit) {
+          value = intermediateValue;
           break;
+        }
 
         context = context.parent;
       }
    * string, given a context. It also maintains a cache of templates to
    * avoid the need to parse the same template twice.
    */
-  function Writer() {
+  function Writer () {
     this.cache = {};
   }
 
   /**
    * Clears all cached templates in this writer.
    */
-  Writer.prototype.clearCache = function () {
+  Writer.prototype.clearCache = function clearCache () {
     this.cache = {};
   };
 
   /**
-   * Parses and caches the given `template` and returns the array of tokens
+   * Parses and caches the given `template` according to the given `tags` or
+   * `mustache.tags` if `tags` is omitted,  and returns the array of tokens
    * that is generated from the parse.
    */
-  Writer.prototype.parse = function (template, tags) {
+  Writer.prototype.parse = function parse (template, tags) {
     var cache = this.cache;
-    var tokens = cache[template];
+    var cacheKey = template + ':' + (tags || mustache.tags).join(':');
+    var tokens = cache[cacheKey];
 
     if (tokens == null)
-      tokens = cache[template] = parseTemplate(template, tags);
+      tokens = cache[cacheKey] = parseTemplate(template, tags);
 
     return tokens;
   };
    * names and templates of partials that are used in the template. It may
    * also be a function that is used to load partial templates on the fly
    * that takes a single argument: the name of the partial.
+   *
+   * If the optional `tags` argument is given here it must be an array with two
+   * string values: the opening and closing tags used in the template (e.g.
+   * [ "<%", "%>" ]). The default is to mustache.tags.
    */
-  Writer.prototype.render = function (template, view, partials) {
-    var tokens = this.parse(template);
+  Writer.prototype.render = function render (template, view, partials, tags) {
+    var tokens = this.parse(template, tags);
     var context = (view instanceof Context) ? view : new Context(view);
-    return this.renderTokens(tokens, context, partials, template);
+    return this.renderTokens(tokens, context, partials, template, tags);
   };
 
   /**
    * If the template doesn't use higher-order sections, this argument may
    * be omitted.
    */
-  Writer.prototype.renderTokens = function (tokens, context, partials, originalTemplate) {
+  Writer.prototype.renderTokens = function renderTokens (tokens, context, partials, originalTemplate, tags) {
     var buffer = '';
 
-    // This function is used to render an arbitrary template
-    // in the current context by higher-order sections.
-    var self = this;
-    function subRender(template) {
-      return self.render(template, context, partials);
-    }
-
-    var token, value;
+    var token, symbol, value;
     for (var i = 0, numTokens = tokens.length; i < numTokens; ++i) {
+      value = undefined;
       token = tokens[i];
+      symbol = token[0];
 
-      switch (token[0]) {
-      case '#':
-        value = context.lookup(token[1]);
+      if (symbol === '#') value = this.renderSection(token, context, partials, originalTemplate);
+      else if (symbol === '^') value = this.renderInverted(token, context, partials, originalTemplate);
+      else if (symbol === '>') value = this.renderPartial(token, context, partials, tags);
+      else if (symbol === '&') value = this.unescapedValue(token, context);
+      else if (symbol === 'name') value = this.escapedValue(token, context);
+      else if (symbol === 'text') value = this.rawValue(token);
 
-        if (!value)
-          continue;
+      if (value !== undefined)
+        buffer += value;
+    }
 
-        if (isArray(value)) {
-          for (var j = 0, valueLength = value.length; j < valueLength; ++j) {
-            buffer += this.renderTokens(token[4], context.push(value[j]), partials, originalTemplate);
-          }
-        } else if (typeof value === 'object' || typeof value === 'string') {
-          buffer += this.renderTokens(token[4], context.push(value), partials, originalTemplate);
-        } else if (isFunction(value)) {
-          if (typeof originalTemplate !== 'string')
-            throw new Error('Cannot use higher-order sections without the original template');
+    return buffer;
+  };
 
-          // Extract the portion of the original template that the section contains.
-          value = value.call(context.view, originalTemplate.slice(token[3], token[5]), subRender);
+  Writer.prototype.renderSection = function renderSection (token, context, partials, originalTemplate) {
+    var self = this;
+    var buffer = '';
+    var value = context.lookup(token[1]);
 
-          if (value != null)
-            buffer += value;
-        } else {
-          buffer += this.renderTokens(token[4], context, partials, originalTemplate);
-        }
+    // This function is used to render an arbitrary template
+    // in the current context by higher-order sections.
+    function subRender (template) {
+      return self.render(template, context, partials);
+    }
 
-        break;
-      case '^':
-        value = context.lookup(token[1]);
+    if (!value) return;
 
-        // Use JavaScript's definition of falsy. Include empty arrays.
-        // See https://github.com/janl/mustache.js/issues/186
-        if (!value || (isArray(value) && value.length === 0))
-          buffer += this.renderTokens(token[4], context, partials, originalTemplate);
+    if (isArray(value)) {
+      for (var j = 0, valueLength = value.length; j < valueLength; ++j) {
+        buffer += this.renderTokens(token[4], context.push(value[j]), partials, originalTemplate);
+      }
+    } else if (typeof value === 'object' || typeof value === 'string' || typeof value === 'number') {
+      buffer += this.renderTokens(token[4], context.push(value), partials, originalTemplate);
+    } else if (isFunction(value)) {
+      if (typeof originalTemplate !== 'string')
+        throw new Error('Cannot use higher-order sections without the original template');
 
-        break;
-      case '>':
-        if (!partials)
-          continue;
+      // Extract the portion of the original template that the section contains.
+      value = value.call(context.view, originalTemplate.slice(token[3], token[5]), subRender);
 
-        value = isFunction(partials) ? partials(token[1]) : partials[token[1]];
+      if (value != null)
+        buffer += value;
+    } else {
+      buffer += this.renderTokens(token[4], context, partials, originalTemplate);
+    }
+    return buffer;
+  };
 
-        if (value != null)
-          buffer += this.renderTokens(this.parse(value), context, partials, value);
+  Writer.prototype.renderInverted = function renderInverted (token, context, partials, originalTemplate) {
+    var value = context.lookup(token[1]);
 
-        break;
-      case '&':
-        value = context.lookup(token[1]);
+    // Use JavaScript's definition of falsy. Include empty arrays.
+    // See https://github.com/janl/mustache.js/issues/186
+    if (!value || (isArray(value) && value.length === 0))
+      return this.renderTokens(token[4], context, partials, originalTemplate);
+  };
 
-        if (value != null)
-          buffer += value;
+  Writer.prototype.renderPartial = function renderPartial (token, context, partials, tags) {
+    if (!partials) return;
 
-        break;
-      case 'name':
-        value = context.lookup(token[1]);
+    var value = isFunction(partials) ? partials(token[1]) : partials[token[1]];
+    if (value != null)
+      return this.renderTokens(this.parse(value, tags), context, partials, value);
+  };
 
-        if (value != null)
-          buffer += mustache.escape(value);
+  Writer.prototype.unescapedValue = function unescapedValue (token, context) {
+    var value = context.lookup(token[1]);
+    if (value != null)
+      return value;
+  };
 
-        break;
-      case 'text':
-        buffer += token[1];
-        break;
-      }
-    }
+  Writer.prototype.escapedValue = function escapedValue (token, context) {
+    var value = context.lookup(token[1]);
+    if (value != null)
+      return mustache.escape(value);
+  };
 
-    return buffer;
+  Writer.prototype.rawValue = function rawValue (token) {
+    return token[1];
   };
 
-  mustache.name = "mustache.js";
-  mustache.version = "1.0.0";
-  mustache.tags = [ "{{", "}}" ];
+  mustache.name = 'mustache.js';
+  mustache.version = '3.0.1';
+  mustache.tags = [ '{{', '}}' ];
 
   // All high-level mustache.* functions use this writer.
   var defaultWriter = new Writer();
   /**
    * Clears all cached templates in the default writer.
    */
-  mustache.clearCache = function () {
+  mustache.clearCache = function clearCache () {
     return defaultWriter.clearCache();
   };
 
    * array of tokens it contains. Doing this ahead of time avoids the need to
    * parse templates on the fly as they are rendered.
    */
-  mustache.parse = function (template, tags) {
+  mustache.parse = function parse (template, tags) {
     return defaultWriter.parse(template, tags);
   };
 
   /**
    * Renders the `template` with the given `view` and `partials` using the
-   * default writer.
+   * default writer. If the optional `tags` argument is given here it must be an
+   * array with two string values: the opening and closing tags used in the
+   * template (e.g. [ "<%", "%>" ]). The default is to mustache.tags.
    */
-  mustache.render = function (template, view, partials) {
-    return defaultWriter.render(template, view, partials);
+  mustache.render = function render (template, view, partials, tags) {
+    if (typeof template !== 'string') {
+      throw new TypeError('Invalid template! Template should be a "string" ' +
+                          'but "' + typeStr(template) + '" was given as the first ' +
+                          'argument for mustache#render(template, view, partials)');
+    }
+
+    return defaultWriter.render(template, view, partials, tags);
   };
 
-  // This is here for backwards compatibility with 0.4.x.
-  mustache.to_html = function (template, view, partials, send) {
+  // This is here for backwards compatibility with 0.4.x.,
+  /*eslint-disable */ // eslint wants camel cased function name
+  mustache.to_html = function to_html (template, view, partials, send) {
+    /*eslint-enable*/
+
     var result = mustache.render(template, view, partials);
 
     if (isFunction(send)) {
   mustache.Context = Context;
   mustache.Writer = Writer;
 
+  return mustache;
 }));
index c941da0..7ff7c11 100644 (file)
@@ -1,5 +1,7 @@
 @import 'mediawiki.mixins';
 
+/* stylelint-disable selector-class-pattern */
+
 /* Table Sorting */
 
 .client-js .sortable:not( .jquery-tablesorter ) > thead > :last-of-type > th:not( .unsortable ),
index ea60702..c239a8f 100644 (file)
@@ -1,3 +1,5 @@
+/* stylelint-disable selector-class-pattern */
+
 .tipsy {
        padding: 5px;
        position: absolute;
index 78c4c04..ac68b7a 100644 (file)
@@ -1,3 +1,5 @@
+/* stylelint-disable selector-class-pattern */
+
 .jquery-confirmable-button {
        /* Automatically flipped */
        margin-left: 1ex;
index 825c7ca..7c6d032 100644 (file)
@@ -1,5 +1,7 @@
 /* suggestions plugin */
 
+/* stylelint-disable selector-class-pattern */
+
 .suggestions {
        overflow: hidden;
        position: absolute;
index b5a9665..b8c3a44 100644 (file)
@@ -12,6 +12,7 @@
 
 /* Show/hide animation is incorrect if the table has a margin set. Extra
  * ".wikitable" is needed in the selector for CSS specificity. */
+/* stylelint-disable-next-line selector-class-pattern */
 .wikitable.preview-limit-report {
        margin: 0;
 }
index 1367426..fc806c6 100644 (file)
@@ -2,6 +2,8 @@
  * Styles for elements of the editing form.
  */
 
+/* stylelint-disable selector-class-pattern */
+
 /*
  * Add a bit of margin space between the preview and the toolbar.
  * This replaces the ugly <p><br /></p> we used to insert into the page source
index 520917a..5425990 100644 (file)
@@ -1,3 +1,5 @@
+/* stylelint-disable selector-class-pattern */
+
 /* Styles for the JavaScript enhancements of the history page */
 
 #pagehistory li.before input[ name='oldid' ],
index 257f153..c6f5b49 100644 (file)
@@ -1,4 +1,8 @@
-/* Basic styles for the history page */
+/**
+ * Basic styles for the edit revision history page 'HistoryAction.php'
+ */
+
+/* stylelint-disable selector-class-pattern */
 
 #pagehistory .history-user {
        margin-left: 0.4em;
index abdee12..274b3d3 100644 (file)
@@ -16,6 +16,7 @@
        }
 }
 
+/* stylelint-disable-next-line selector-class-pattern */
 .redirect-in-category {
        font-style: italic;
 }
index b643d76..b8d4e70 100644 (file)
@@ -2,6 +2,8 @@
  * File description page
  */
 
+/* stylelint-disable selector-class-pattern */
+
 .mw-filepage-resolutioninfo {
        font-size: smaller;
 }
index f21b111..dad3238 100644 (file)
@@ -13,6 +13,7 @@
 }
 
 @media print {
+       /* stylelint-disable-next-line selector-class-pattern */
        .mw_metadata .mw-metadata-show-hide-extended {
                display: none;
        }
index 46976d4..c40b1c3 100644 (file)
@@ -1,5 +1,7 @@
 @import 'mediawiki.mixins';
 
+/* stylelint-disable selector-class-pattern */
+
 .postedit-container {
        margin: 0 auto;
        position: fixed;
index dccbacc..b5eaf8e 100644 (file)
@@ -2,6 +2,8 @@
  * Display neat icons on redirect pages.
  */
 
+/* stylelint-disable selector-class-pattern */
+
 /* Hide, but keep accessible for screen-readers. */
 .redirectMsg p {
        overflow: hidden;
index 7528fdb..d1f32ab 100644 (file)
@@ -1,3 +1,5 @@
+/* stylelint-disable selector-class-pattern */
+
 .apihelp-header {
        clear: both;
        margin-bottom: 0.1em;
index 99e4569..3e921f4 100644 (file)
@@ -1,3 +1,5 @@
+/* stylelint-disable selector-class-pattern */
+
 .mw-special-ApiHelp h1.firstHeading {
        display: none;
 }
index e084ab8..63a652d 100644 (file)
        padding: 0.5em 1em;
 }
 
+/* TODO: Remove this old class once the content caches have cleared */
+/* stylelint-disable-next-line selector-class-pattern */
 .mw-json .value,
+.mw-json-value,
 .mw-json-single-value {
        background-color: #dcfae3;
        font-family: monospace, monospace;
        background-color: #fff;
        font-weight: normal;
 }
-
-.mw-json caption {
-       /* For stylistic reasons, suppress the caption of the outermost table */
-       display: none;
-}
-
-.mw-json table caption {
-       color: #72777d;
-       display: inline-block;
-       font-size: 10px;
-       font-style: italic;
-       margin-bottom: 0.5em;
-       text-align: left;
-}
index a56e459..272e7e0 100644 (file)
                        border-bottom: 1px solid #eee;
                        word-wrap: break-word;
 
+                       /* stylelint-disable-next-line selector-class-pattern */
                        &.nr {
                                text-align: right;
                        }
 
+                       /* stylelint-disable-next-line selector-class-pattern */
                        span.stats {
                                color: #727272;
                        }
@@ -78,6 +80,7 @@
                cursor: pointer;
        }
 
+       /* stylelint-disable-next-line selector-class-pattern */
        &.current {
                background-color: #dedede;
        }
index 2053843..6382ac8 100644 (file)
@@ -2,6 +2,8 @@
  * Diff rendering
  */
 
+/* stylelint-disable selector-class-pattern */
+
 .diff {
        border: 0;
        border-spacing: 4px;
index 76b5c9b..159e7ae 100644 (file)
@@ -1,3 +1,4 @@
+/* stylelint-disable selector-class-pattern */
 /*!
  * Diff rendering
  */
index 37808d5..13d0ba1 100644 (file)
@@ -1,5 +1,6 @@
 /* Styles for links to RSS/Atom feeds in sidebar */
 
+/* stylelint-disable-next-line selector-class-pattern */
 a.feedlink {
        /* SVG support using a transparent gradient to guarantee cross-browser
         * compatibility (browsers able to understand gradient syntax support also SVG).
index bf9634f..a608437 100644 (file)
@@ -1,5 +1,7 @@
 @import 'mediawiki.ui/variables';
 
+/* stylelint-disable selector-class-pattern */
+
 // Increase the area of the button, so that the user can move the mouse cursor
 // to the popup without the popup disappearing. (T157544)
 .mediawiki-filewarning-anchor {
index e25a92f..d9612a8 100644 (file)
@@ -2,6 +2,9 @@
  * Stylesheet for mediawiki.hlist module
  * @author [[User:Edokter]]
  */
+
+/* stylelint-disable selector-class-pattern */
+
 /* Generate interpuncts */
 .hlist dt:after {
        content: ':';
index d7071e4..5bc6a68 100644 (file)
@@ -1,3 +1,4 @@
+/* stylelint-disable-next-line selector-class-pattern */
 .hlist {
        dl,
        ol,
index ecf728b..470d826 100644 (file)
@@ -1,5 +1,7 @@
 @import 'mediawiki.mixins';
 
+/* stylelint-disable selector-class-pattern */
+
 // OOUIHTMLForm styles
 @ooui-font-size-browser: 16; // assumed browser default of `16px`
 @ooui-font-size-base: 0.875em; // equals `14px` at browser default of `16px`
 @ooui-padding-vertical: 4 / @ooui-font-size-browser / @ooui-font-size-base; // equals `0.285714em`≈`4px`
 
 .mw-htmlform-ooui-wrapper.oo-ui-panelLayout-padded {
+       // Reducing `padding-top`, as the heading's `line-height` provides similar distance.
        padding: @ooui-spacing-medium @ooui-spacing-large @ooui-spacing-large;
+
+       // Trigger only when collapsible & JS is available via `.mw-collapsed`.
+       .client-js & .oo-ui-fieldsetLayout.mw-collapsed .oo-ui-fieldsetLayout-header {
+               // Negative margin to match the reduced distance on the top caused by the previous rule.
+               margin-bottom: -( @ooui-spacing-large - @ooui-spacing-medium );
+
+               .oo-ui-labelElement-label {
+                       margin-bottom: 0;
+               }
+       }
 }
 
 .mw-htmlform-ooui {
index cfabab6..a0e9f15 100644 (file)
@@ -8,6 +8,7 @@
        content: '. .';
 }
 
+/* stylelint-disable-next-line selector-class-pattern */
 .comment--without-parentheses,
 .mw-changeslist-links,
 .mw-diff-bytes,
index c21b254..e58e677 100644 (file)
@@ -6,6 +6,8 @@
  * Copyright Alexander Limi
  */
 
+/* stylelint-disable selector-class-pattern */
+
 /**
  * Hide all the elements irrelevant for printing
  * Skins however can and should override.
index caaebad..92c0207 100644 (file)
@@ -4,6 +4,8 @@
  * CologneBlue, the old pre-Monobook skins
  */
 
+/* stylelint-disable selector-class-pattern */
+
 /* For clarity, explicitly state some recommendations from
  * https://www.w3.org/TR/CSS21/sample.html to make sure the editsection links scale right
  */
index a63c5c6..baf2c56 100644 (file)
@@ -9,6 +9,8 @@
  * blocking CSS common to all pages.
  */
 
+/* stylelint-disable selector-class-pattern */
+
 /* GENERAL CLASSES FOR DIRECTIONALITY SUPPORT */
 
 /**
index 6a331b6..b7a424f 100644 (file)
@@ -1,3 +1,5 @@
+/* stylelint-disable selector-class-pattern */
+
 /* Galleries */
 /* These display attributes look nonsensical, but are needed to support IE and FF2 */
 /* Don't forget to update gallery.print.css */
index f7a3f0d..2b596ab 100644 (file)
@@ -1,3 +1,4 @@
+/* stylelint-disable selector-class-pattern */
 li.gallerybox {
        vertical-align: top;
        display: inline-block;
index 1cccb88..4c82192 100644 (file)
@@ -3,6 +3,8 @@
  * in MediaWiki (used e.g. on Special:ListFiles).
  */
 
+/* stylelint-disable selector-class-pattern */
+
 @import 'mediawiki.mixins';
 
 // TablePager uses `.mw-datatable` and is loaded in the right order by RL
index b6284fb..97b73ae 100644 (file)
@@ -34,7 +34,6 @@ Controller = function MwRcfiltersController( filtersModel, changesListModel, sav
        this.pollingRate = require( './config.json' ).StructuredChangeFiltersLiveUpdatePollingRate;
 
        this.requestCounter = {};
-       this.baseFilterState = {};
        this.uriProcessor = null;
        this.initialized = false;
        this.wereSavedQueriesSaved = false;
index 8bd5eb2..d1f700c 100644 (file)
@@ -362,15 +362,6 @@ FilterGroup.prototype.getDefaultFilters = function () {
        return this.defaultFilters;
 };
 
-/**
- * This is for a single_option and string_options group types
- * it returns the value of the default
- *
- * @return {string} Value of the default
- */
-FilterGroup.prototype.getDefaulParamValue = function () {
-       return this.defaultParams[ this.getName() ];
-};
 /**
  * Get the messags defining the 'whats this' popup for this group
  *
@@ -423,21 +414,6 @@ FilterGroup.prototype.setConflicts = function ( conflicts ) {
        this.conflicts = conflicts;
 };
 
-/**
- * Set conflicts for each filter item in the group based on the
- * given conflict map
- *
- * @param {Object} conflicts Object representing the conflict map,
- *  keyed by the item name, where its value is an object for all its conflicts
- */
-FilterGroup.prototype.setFilterConflicts = function ( conflicts ) {
-       this.getItems().forEach( function ( filterItem ) {
-               if ( conflicts[ filterItem.getName() ] ) {
-                       filterItem.setConflicts( conflicts[ filterItem.getName() ] );
-               }
-       } );
-};
-
 /**
  * Check whether this item has a potential conflict with the given item
  *
@@ -871,7 +847,6 @@ FilterGroup.prototype.getView = function () {
 /**
  * Get the prefix used for the filter names inside this group.
  *
- * @param {string} [name] Filter name to prefix
  * @return {string} Group prefix
  */
 FilterGroup.prototype.getNamePrefix = function () {
index 8725f51..50057af 100644 (file)
@@ -64,11 +64,10 @@ FilterItem.prototype.getState = function () {
 /**
  * Get the message for the display area for the currently active conflict
  *
- * @private
  * @return {string} Conflict result message key
  */
 FilterItem.prototype.getCurrentConflictResultMessage = function () {
-       var details = {};
+       var details;
 
        // First look in filter's own conflicts
        details = this.getConflictDetails( this.getOwnConflicts(), 'globalDescription' );
index 07c484b..4b219de 100644 (file)
@@ -225,7 +225,7 @@ FiltersViewModel.prototype.getFirstConflictedItem = function () {
  */
 FiltersViewModel.prototype.initializeFilters = function ( filterGroups, views ) {
        var filterConflictResult, groupConflictResult,
-               allViews = {},
+               allViews,
                model = this,
                items = [],
                groupConflictMap = {},
index 4e5e0fe..0ba75fb 100644 (file)
@@ -105,7 +105,7 @@ function init() {
                {
                        $wrapper: $( 'body' ),
                        $topSection: $topSection,
-                       $filtersContainer: $( '.rcfilters-container' ),
+                       $filtersContainer: $( '.mw-rcfilters-container' ),
                        $changesListContainer: $( '.mw-changeslist, .mw-changeslist-empty' ),
                        $formContainer: $initialFieldset,
                        collapsed: initialCollapsedState
index 689f322..d3fce46 100644 (file)
 
 // Corrections for the standard special page
 .client-js {
+       /* stylelint-disable-next-line selector-class-pattern */
        .cloptions {
                border: 0;
        }
 
        // Reserve space for the UI while it loads
-       .rcfilters-head {
+       .mw-rcfilters-head {
                min-height: @rcfilters-head-min-height;
                margin-bottom: @rcfilters-head-margin-bottom;
        }
 
        // On the watchlist, reserve a bit more
-       .mw-special-Watchlist .rcfilters-head {
+       .mw-special-Watchlist .mw-rcfilters-head {
                min-height: @rcfilters-wl-head-min-height;
        }
 
        .mw-rcfilters-collapsed {
-               .rcfilters-head {
+               .mw-rcfilters-head {
                        min-height: @rcfilters-head-min-height-collapsed;
                }
 
                // On the watchlist, reserve a bit more
-               &.mw-special-Watchlist .rcfilters-head {
+               &.mw-special-Watchlist .mw-rcfilters-head {
                        min-height: @rcfilters-wl-head-min-height-collapsed;
                }
        }
                        }
                }
 
-               .rcfilters-head {
+               .mw-rcfilters-head {
                        opacity: 0.5;
                        pointer-events: none;
 
+                       /* stylelint-disable-next-line selector-class-pattern */
                        .cloptions {
                                display: none;
                        }
                display: none;
        }
 
+       /* stylelint-disable-next-line selector-class-pattern */
        .errorbox {
                display: none;
        }
                opacity: 0.5;
        }
 
-       .rcfilters-spinner {
+       .mw-rcfilters-spinner {
                display: none;
                position: absolute;
                left: 50%;
                margin-left: -3 * @rcfilters-spinner-size / 2;
                white-space: nowrap;
 
-               & .rcfilters-spinner-bounce,
+               & .mw-rcfilters-spinner-bounce,
                &:before,
                &:after {
                        content: '';
                }
        }
 
-       body:not( .mw-rcfilters-ui-initialized ) .rcfilters-spinner {
+       body:not( .mw-rcfilters-ui-initialized ) .mw-rcfilters-spinner {
                display: block;
                // When initializing, display the spinner on top of the area where the UI will appear
                margin-top: -( @rcfilters-head-min-height + @rcfilters-head-margin-bottom ) / 1.5;
        }
 
-       body.mw-rcfilters-ui-loading .rcfilters-spinner {
+       body.mw-rcfilters-ui-loading .mw-rcfilters-spinner {
                display: block;
                // When loading new results, display the spinner on top of the results area
                margin-top: -( @rcfilters-head-min-height + @rcfilters-head-margin-bottom ) / 8;
 
        // Make the watchlist-details message display while loading, but make it not take up any
        // space. This makes the min-height trick work better.
+       /* stylelint-disable-next-line selector-class-pattern */
        .watchlistDetails {
                float: left;
                // The 20em should match the min-width we are setting up
index 4764bd8..ac5bbae 100644 (file)
@@ -147,8 +147,6 @@ ChangesLimitAndDateButtonWidget.prototype.onPopupDays = function ( filterName )
 
 /**
  * Respond to limit choose event
- *
- * @param {string} filterName Filter name
  */
 ChangesLimitAndDateButtonWidget.prototype.updateButtonLabel = function () {
        var message,
index 09b802e..78cd8f4 100644 (file)
@@ -28,7 +28,6 @@ var ChangesListWrapperWidget = function MwRcfiltersUiChangesListWrapperWidget(
        this.filtersViewModel = filtersViewModel;
        this.changesListViewModel = changesListViewModel;
        this.controller = controller;
-       this.highlightClasses = null;
 
        // Events
        this.filtersViewModel.connect( this, {
@@ -52,22 +51,6 @@ var ChangesListWrapperWidget = function MwRcfiltersUiChangesListWrapperWidget(
 
 OO.inheritClass( ChangesListWrapperWidget, OO.ui.Widget );
 
-/**
- * Get all available highlight classes
- *
- * @return {string[]} An array of available highlight class names
- */
-ChangesListWrapperWidget.prototype.getHighlightClasses = function () {
-       if ( !this.highlightClasses || !this.highlightClasses.length ) {
-               this.highlightClasses = this.filtersViewModel.getItemsSupportingHighlights()
-                       .map( function ( filterItem ) {
-                               return filterItem.getCssClass();
-                       } );
-       }
-
-       return this.highlightClasses;
-};
-
 /**
  * Respond to the highlight feature being toggled on and off
  *
index 6634e30..12d53bb 100644 (file)
@@ -6,7 +6,7 @@
  *
  * @constructor
  * @param {Object} [config] Configuration object
- * @param {Object} [events] Events to aggregate. The object represent the
+ * @cfg {Object} [events] Events to aggregate. The object represent the
  *  event name to aggregate and the event value to emit on aggregate for items.
  */
 var GroupWidget = function MwRcfiltersUiViewSwitchWidget( config ) {
index 4057c48..806b9a3 100644 (file)
@@ -262,7 +262,6 @@ SavedLinksListItemWidget.prototype.onInputChange = function ( value ) {
 /**
  * Save the name of the query
  *
- * @param {string} [value] The value to save
  * @fires edit
  */
 SavedLinksListItemWidget.prototype.save = function () {
index 8d56906..3907329 100644 (file)
@@ -1,3 +1,5 @@
+/* stylelint-disable selector-class-pattern */
+
 /* Make sure the links are not underlined or colored, ever. */
 /* There is already a :focus / :hover indication on the <div>. */
 .suggestions a.mw-searchSuggest-link,
index 3104a69..054bc27 100644 (file)
@@ -5,6 +5,8 @@
  * (ie: the CSS classing built into the system), like the TOC.
  */
 
+/* stylelint-disable selector-class-pattern */
+
 /* Table of Contents */
 .toc,
 .mw-warning,
index c6390c0..b01c518 100644 (file)
@@ -2,6 +2,8 @@
  * Icons and colors for external links.
  */
 
+/* stylelint-disable selector-class-pattern */
+
 @import 'mediawiki.mixins';
 
 .mw-parser-output a.external,
index 8b2657d..51018f7 100644 (file)
@@ -2,6 +2,8 @@
  * Style Parsoid HTML+RDFa output consistent with wikitext from PHP parser.
  */
 
+/* stylelint-disable selector-class-pattern */
+
 /*
  * Auto-numbered external links
  * Parsoid renders those as link without content, and lets CSS do the
index a33595c..df27f78 100644 (file)
@@ -6,6 +6,8 @@
  * This style sheet is used by the Monobook and Vector skins.
  */
 
+/* stylelint-disable selector-class-pattern */
+
 /* Links */
 a {
        text-decoration: none;
@@ -108,7 +110,6 @@ img {
 
 hr {
        height: 1px;
-       color: #a2a9b1;
        background-color: #a2a9b1;
        border: 0;
        margin: 0.2em 0;
index e9a2b08..c559048 100644 (file)
@@ -6,6 +6,8 @@
  * they are outputted by the actual MonoBook/Vector code by convention.
  */
 
+/* stylelint-disable selector-class-pattern */
+
 /* Categories */
 .catlinks {
        border: 1px solid #a2a9b1;
index d7415c9..c071199 100644 (file)
        min-width: 6em;
 }
 
+/* stylelint-disable-next-line selector-class-pattern */
 .apihelp-deprecated {
        font-weight: bold;
        color: #d33;
 }
 
+/* stylelint-disable-next-line selector-class-pattern */
 .apihelp-deprecated-value .oo-ui-labelElement-label {
        text-decoration: line-through;
 }
index b46df85..58657db 100644 (file)
        }
 
        $( function () {
-               // This code is also loaded on the "block succeeded" page where there is no form,
-               // so username and expiry fields might also be missing.
-               var blockTargetWidget = infuseIfExists( $( '#mw-bi-target' ) ),
-                       anonOnlyField = infuseIfExists( $( '#mw-input-wpHardBlock' ).closest( '.oo-ui-fieldLayout' ) ),
-                       enableAutoblockField = infuseIfExists( $( '#mw-input-wpAutoBlock' ).closest( '.oo-ui-fieldLayout' ) ),
-                       hideUserWidget = infuseIfExists( $( '#mw-input-wpHideUser' ) ),
-                       hideUserField = infuseIfExists( $( '#mw-input-wpHideUser' ).closest( '.oo-ui-fieldLayout' ) ),
-                       watchUserField = infuseIfExists( $( '#mw-input-wpWatch' ).closest( '.oo-ui-fieldLayout' ) ),
-                       expiryWidget = infuseIfExists( $( '#mw-input-wpExpiry' ) ),
-                       editingWidget = infuseIfExists( $( '#mw-input-wpEditing' ) ),
-                       editingRestrictionWidget = infuseIfExists( $( '#mw-input-wpEditingRestriction' ) ),
-                       preventTalkPageEdit = infuseIfExists( $( '#mw-input-wpDisableUTEdit' ) ),
-                       pageRestrictionsWidget = infuseIfExists( $( '#mw-input-wpPageRestrictions' ) ),
-                       namespaceRestrictionsWidget = infuseIfExists( $( '#mw-input-wpNamespaceRestrictions' ) ),
-                       createAccountWidget = infuseIfExists( $( '#mw-input-wpCreateAccount' ) ),
-                       userChangedCreateAccount = mw.config.get( 'wgCreateAccountDirty' ),
-                       updatingBlockOptions = false;
+               var blockTargetWidget, anonOnlyWidget, enableAutoblockWidget, hideUserWidget, watchUserWidget,
+                       expiryWidget, editingWidget, editingRestrictionWidget, preventTalkPageEditWidget,
+                       pageRestrictionsWidget, namespaceRestrictionsWidget, createAccountWidget, data,
+                       enablePartialBlocks, blockAllowsUTEdit, userChangedCreateAccount, updatingBlockOptions;
 
                function updateBlockOptions() {
                        var blocktarget = blockTargetWidget.getValue().trim(),
                                // infinityValues are the values the SpecialBlock class accepts as infinity (sf. wfIsInfinity)
                                infinityValues = [ 'infinite', 'indefinite', 'infinity', 'never' ],
                                isIndefinite = infinityValues.indexOf( expiryValue ) !== -1,
-                               // editingRestrictionWidget only exists if partial blocks is enabled; if not, block must be sitewide
-                               editingRestrictionValue = editingRestrictionWidget ? editingRestrictionWidget.getValue() : 'sitewide',
-                               editingIsSelected = editingWidget ? editingWidget.isSelected() : false,
+                               editingRestrictionValue = enablePartialBlocks ? editingRestrictionWidget.getValue() : 'sitewide',
+                               editingIsSelected = editingWidget.isSelected(),
                                isSitewide = editingIsSelected && editingRestrictionValue === 'sitewide';
 
-                       if ( enableAutoblockField ) {
-                               enableAutoblockField.toggle( !isNonEmptyIp );
+                       enableAutoblockWidget.setDisabled( isNonEmptyIp );
+                       if ( enableAutoblockWidget.isDisabled() ) {
+                               enableAutoblockWidget.setSelected( false );
+                       }
+
+                       anonOnlyWidget.setDisabled( !isIp && !isEmpty );
+                       if ( anonOnlyWidget.isDisabled() ) {
+                               anonOnlyWidget.setSelected( false );
                        }
-                       if ( hideUserField ) {
-                               hideUserField.toggle( !isNonEmptyIp && isIndefinite && isSitewide );
-                               if ( !hideUserField.isVisible() ) {
+
+                       if ( hideUserWidget ) {
+                               hideUserWidget.setDisabled( isNonEmptyIp || !isIndefinite || !isSitewide );
+                               if ( hideUserWidget.isDisabled() ) {
                                        hideUserWidget.setSelected( false );
                                }
                        }
-                       if ( anonOnlyField ) {
-                               anonOnlyField.toggle( isIp || isEmpty );
-                       }
-                       if ( watchUserField ) {
-                               watchUserField.toggle( !isIpRange || isEmpty );
+
+                       if ( watchUserWidget ) {
+                               watchUserWidget.setDisabled( isIpRange && !isEmpty );
+                               if ( watchUserWidget.isDisabled() ) {
+                                       watchUserWidget.setSelected( false );
+                               }
                        }
-                       if ( editingRestrictionWidget ) {
+
+                       if ( enablePartialBlocks ) {
                                editingRestrictionWidget.setDisabled( !editingIsSelected );
-                       }
-                       if ( pageRestrictionsWidget ) {
                                pageRestrictionsWidget.setDisabled( !editingIsSelected || isSitewide );
-                       }
-                       if ( namespaceRestrictionsWidget ) {
                                namespaceRestrictionsWidget.setDisabled( !editingIsSelected || isSitewide );
+                               if ( blockAllowsUTEdit ) {
+                                       // This option is disabled for partial blocks unless a namespace restriction
+                                       // for the User_talk namespace is in place.
+                                       preventTalkPageEditWidget.setDisabled(
+                                               editingIsSelected &&
+                                               editingRestrictionValue === 'partial' &&
+                                               namespaceRestrictionsWidget.getValue().indexOf(
+                                                       String( mw.config.get( 'wgNamespaceIds' ).user_talk )
+                                               ) === -1
+                                       );
+                               }
                        }
-                       if ( preventTalkPageEdit && namespaceRestrictionsWidget ) {
-                               // This option is disabled for partial blocks unless a namespace restriction
-                               // for the User_talk namespace is in place.
-                               preventTalkPageEdit.setDisabled(
-                                       editingIsSelected &&
-                                       editingRestrictionValue === 'partial' &&
-                                       namespaceRestrictionsWidget.getValue().indexOf(
-                                               String( mw.config.get( 'wgNamespaceIds' ).user_talk )
-                                       ) === -1
-                               );
-                       }
+
                        if ( !userChangedCreateAccount ) {
                                updatingBlockOptions = true;
                                createAccountWidget.setSelected( isSitewide );
 
                }
 
+               // This code is also loaded on the "block succeeded" page where there is no form,
+               // so check for block target widget; if it exists, the form is present
+               blockTargetWidget = infuseIfExists( $( '#mw-bi-target' ) );
+
                if ( blockTargetWidget ) {
-                       // Bind functions so they're checked whenever stuff changes
+                       data = require( './config.json' );
+                       enablePartialBlocks = data.EnablePartialBlocks;
+                       blockAllowsUTEdit = data.BlockAllowsUTEdit;
+                       userChangedCreateAccount = mw.config.get( 'wgCreateAccountDirty' );
+                       updatingBlockOptions = false;
+
+                       // Always present if blockTargetWidget is present
+                       editingWidget = OO.ui.infuse( $( '#mw-input-wpEditing' ) );
+                       expiryWidget = OO.ui.infuse( $( '#mw-input-wpExpiry' ) );
+                       createAccountWidget = OO.ui.infuse( $( '#mw-input-wpCreateAccount' ) );
+                       enableAutoblockWidget = OO.ui.infuse( $( '#mw-input-wpAutoBlock' ) );
+                       anonOnlyWidget = OO.ui.infuse( $( '#mw-input-wpHardBlock' ) );
                        blockTargetWidget.on( 'change', updateBlockOptions );
+                       editingWidget.on( 'change', updateBlockOptions );
                        expiryWidget.on( 'change', updateBlockOptions );
-                       if ( editingWidget ) {
-                               editingWidget.on( 'change', updateBlockOptions );
-                       }
-                       if ( editingRestrictionWidget ) {
-                               editingRestrictionWidget.on( 'change', updateBlockOptions );
-                       }
-                       if ( namespaceRestrictionsWidget ) {
-                               namespaceRestrictionsWidget.on( 'change', updateBlockOptions );
-                       }
-
                        createAccountWidget.on( 'change', function () {
                                if ( !updatingBlockOptions ) {
                                        userChangedCreateAccount = true;
                                }
                        } );
 
-                       // Call them now to set initial state (ie. Special:Block/Foobar?wpBlockExpiry=2+hours)
+                       // Present for certain rights
+                       watchUserWidget = infuseIfExists( $( '#mw-input-wpWatch' ) );
+                       hideUserWidget = infuseIfExists( $( '#mw-input-wpHideUser' ) );
+
+                       // Present for certain global configs
+                       if ( enablePartialBlocks ) {
+                               editingRestrictionWidget = OO.ui.infuse( $( '#mw-input-wpEditingRestriction' ) );
+                               pageRestrictionsWidget = OO.ui.infuse( $( '#mw-input-wpPageRestrictions' ) );
+                               namespaceRestrictionsWidget = OO.ui.infuse( $( '#mw-input-wpNamespaceRestrictions' ) );
+                               editingRestrictionWidget.on( 'change', updateBlockOptions );
+                               namespaceRestrictionsWidget.on( 'change', updateBlockOptions );
+                       }
+                       if ( blockAllowsUTEdit ) {
+                               preventTalkPageEditWidget = infuseIfExists( $( '#mw-input-wpDisableUTEdit' ) );
+                       }
+
                        updateBlockOptions();
                }
        } );
index d7923f4..f0b6913 100644 (file)
@@ -28,6 +28,7 @@ td.mw-enhanced-rc {
 }
 
 /* Show/hide arrows in enhanced changeslist */
+/* stylelint-disable-next-line selector-class-pattern */
 .mw-enhanced-rc .collapsible-expander {
        float: none;
 }
@@ -53,6 +54,7 @@ td.mw-enhanced-rc {
        font-weight: bold;
 }
 
+/* stylelint-disable-next-line selector-class-pattern */
 span.changedby {
        font-size: 95%;
 }
index 81c8dc9..5f99f82 100644 (file)
@@ -4,6 +4,8 @@
 @import 'mediawiki.ui/variables.less';
 @import 'mediawiki.mixins';
 
+/* stylelint-disable selector-class-pattern */
+
 .mw-searchresults-has-iw {
        .iw-headline {
                font-weight: bold;
index 0f27420..dcb51fa 100644 (file)
@@ -1,5 +1,7 @@
 /* Special:Search */
 
+/* stylelint-disable selector-class-pattern */
+
 /*
  * Fixes sister projects box moving down the extract
  * of the first result (T18886).
index 2366249..9f27150 100644 (file)
@@ -39,6 +39,8 @@ section.mw-form-header {
        margin-top: 6px;
 }
 
+/* FIXME: These should be namespaced to mw-ext-confirmedit-fancycaptcha-, and really shouldn't be in core at all */
+/* stylelint-disable-next-line selector-class-pattern */
 .fancycaptcha-captcha-container {
        background-color: #f8f9fa;
        margin-bottom: 15px;
@@ -54,6 +56,7 @@ section.mw-form-header {
 }
 
 /* Put a border around the fancycaptcha-image-container. */
+/* stylelint-disable-next-line selector-class-pattern */
 .fancycaptcha-captcha-and-reload {
        border: 1px solid #c8ccd1;
        border-radius: 2px 2px 0 0;
@@ -63,6 +66,7 @@ section.mw-form-header {
        background-color: #fff;
 }
 
+/* stylelint-disable-next-line selector-class-pattern */
 .fancycaptcha-captcha-container .mw-ui-input {
        margin-top: -1px;
        border-color: #c8ccd1;
@@ -70,6 +74,7 @@ section.mw-form-header {
 }
 
 /* Make the fancycaptcha-image-container full-width within its parent. */
+/* stylelint-disable-next-line selector-class-pattern */
 .fancycaptcha-image-container {
        width: 100%;
 }
index 3cfa5a8..d8b773c 100644 (file)
        margin-bottom: 30px;
 }
 
+/* stylelint-disable-next-line selector-class-pattern */
 .mw-number-text.icon-edits {
        /* @embed */
        background: url( images/icon-edits.png ) no-repeat left center;
 }
 
+/* stylelint-disable-next-line selector-class-pattern */
 .mw-number-text.icon-pages {
        /* @embed */
        background: url( images/icon-pages.png ) no-repeat left center;
 }
 
+/* stylelint-disable-next-line selector-class-pattern */
 .mw-number-text.icon-contributors {
        /* @embed */
        background: url( images/icon-contributors.png ) no-repeat left center;
index 9428fed..25113ea 100644 (file)
@@ -2,6 +2,7 @@
  * Styles for Special:MovePage
  */
 
+/* stylelint-disable-next-line selector-class-pattern */
 .movepage-wrapper {
        width: 50em;
 }
index 7240bd4..b0cc932 100644 (file)
@@ -3,6 +3,7 @@
  */
 
 /* Distinguish actual data from information about it being hidden visually. */
+/* stylelint-disable-next-line selector-class-pattern */
 .prop-value-hidden {
        font-style: italic;
 }
index 3798f1e..3f76cf0 100644 (file)
@@ -3,6 +3,8 @@
  */
 @import 'mediawiki.mixins';
 
+/* stylelint-disable selector-class-pattern */
+
 /* Special:AllMessages */
 /* Visually hide repeating text, but leave in for better form navigation on screen readers */
 .mw-special-Allmessages .mw-htmlform-ooui .oo-ui-fieldsetLayout:first-child .oo-ui-fieldsetLayout-header {
index 31a8826..d89cc2a 100644 (file)
@@ -1,6 +1,7 @@
 /* This style is loaded on all media. */
 
 /* Hide the content of the TOC when the checkbox is checked. */
+/* stylelint-disable-next-line selector-class-pattern */
 .toctogglecheckbox:checked ~ ul {
        display: none;
 }
index e905dbe..2081d35 100644 (file)
@@ -1,4 +1,5 @@
 /* Hide the complete TOC on print when the TOC is hidden. */
+/* stylelint-disable-next-line selector-class-pattern */
 .toctogglecheckbox:checked + .toctitle {
        display: none;
 }
index ff41b5e..7d7727c 100644 (file)
@@ -1,5 +1,7 @@
 /* This style adds a toggle button with internationalized message for the TOC. */
 
+/* stylelint-disable selector-class-pattern */
+
 /* When the browser supports :checked then overwrite the style="display:none" and make the */
 /* checkbox invisible on another way to allow to focus the checkbox with keyboard. */
 :not( :checked ) > .toctogglecheckbox {
index 3490ebc..a85ecd7 100644 (file)
@@ -1,6 +1,8 @@
 @import 'mediawiki.mixins';
 @import 'mediawiki.ui/variables';
 
+/* stylelint-disable selector-class-pattern */
+
 // Buttons
 // Helper mixins
 // Primary buttons mixin
index 5fa8e5a..d08fff5 100644 (file)
@@ -3,6 +3,8 @@
 @import 'mediawiki.mixins';
 @import 'mediawiki.ui/variables';
 
+/* stylelint-disable selector-class-pattern */
+
 // --------------------------------------------------------------------------
 // Layouts
 // --------------------------------------------------------------------------
index 0a8dfb8..4b65ed5 100644 (file)
                         * Utility function for execute()
                         *
                         * @ignore
-                        * @param {string} [media] Media attribute
                         * @param {string} url URL
+                        * @param {string} [media] Media attribute
+                        * @param {Node|null} [nextNode]
                         */
-                       function addLink( media, url ) {
+                       function addLink( url, media, nextNode ) {
                                var el = document.createElement( 'link' );
 
                                el.rel = 'stylesheet';
                                // see #addEmbeddedCSS, T33676, T43331, and T49277 for details.
                                el.href = url;
 
-                               if ( marker && marker.parentNode ) {
-                                       marker.parentNode.insertBefore( el, marker );
+                               if ( nextNode && nextNode.parentNode ) {
+                                       nextNode.parentNode.insertBefore( el, nextNode );
                                } else {
                                        document.head.appendChild( el );
                                }
                                                        for ( i = 0; i < value.length; i++ ) {
                                                                if ( key === 'bc-url' ) {
                                                                        // back-compat: { <media>: [url, ..] }
-                                                                       addLink( media, value[ i ] );
+                                                                       addLink( value[ i ], media, marker );
                                                                } else if ( key === 'css' ) {
                                                                        // { "css": [css, ..] }
                                                                        addEmbeddedCSS( value[ i ], cssHandle() );
                                                        for ( media in value ) {
                                                                urls = value[ media ];
                                                                for ( i = 0; i < urls.length; i++ ) {
-                                                                       addLink( media, urls[ i ] );
+                                                                       addLink( urls[ i ], media, marker );
                                                                }
                                                        }
                                                }
                         * to a query string of the form `foo.bar,baz|bar.baz,quux`.
                         *
                         * See `ResourceLoader::makePackedModulesString()` in PHP, of which this is a port.
-                        * On the server, unpacking is done by `ResourceLoaderContext::expandModuleNames()`.
+                        * On the server, unpacking is done by `ResourceLoader::expandModuleNames()`.
                         *
                         * Note: This is only half of the logic, the other half has to be in #batchRequest(),
                         * because its implementation needs to keep track of potential string size in order
                                 *  "text/javascript"; if no type is provided, text/javascript is assumed.
                                 */
                                load: function ( modules, type ) {
-                                       var l;
-
-                                       // Allow calling with a url or single dependency as a string
-                                       if ( typeof modules === 'string' ) {
-                                               // "https://example.org/x.js", "http://example.org/x.js", "//example.org/x.js", "/x.js"
-                                               if ( /^(https?:)?\/?\//.test( modules ) ) {
-                                                       if ( type === 'text/css' ) {
-                                                               l = document.createElement( 'link' );
-                                                               l.rel = 'stylesheet';
-                                                               l.href = modules;
-                                                               document.head.appendChild( l );
-                                                               return;
-                                                       }
-                                                       if ( type === 'text/javascript' || type === undefined ) {
-                                                               addScript( modules );
-                                                               return;
-                                                       }
+                                       if ( typeof modules === 'string' && /^(https?:)?\/?\//.test( modules ) ) {
+                                               // Called with a url like so:
+                                               // - "https://example.org/x.js"
+                                               // - "http://example.org/x.js"
+                                               // - "//example.org/x.js"
+                                               // - "/x.js"
+                                               if ( type === 'text/css' ) {
+                                                       addLink( modules );
+                                               } else if ( type === 'text/javascript' || type === undefined ) {
+                                                       addScript( modules );
+                                               } else {
                                                        // Unknown type
                                                        throw new Error( 'type must be text/css or text/javascript, found ' + type );
                                                }
-                                               // Called with single module
-                                               modules = [ modules ];
+                                       } else {
+                                               // One or more modules
+                                               modules = typeof modules === 'string' ? [ modules ] : modules;
+                                               // Resolve modules into flat list for internal queuing.
+                                               // This also filters out unknown modules and modules with
+                                               // unknown dependencies, allowing the rest to continue. (T36853)
+                                               enqueue( resolveStubbornly( modules ), undefined, undefined );
                                        }
-
-                                       // Resolve modules into flat list for internal queuing.
-                                       // This also filters out unknown modules and modules with
-                                       // unknown dependencies, allowing the rest to continue. (T36853)
-                                       enqueue( resolveStubbornly( modules ), undefined, undefined );
                                },
 
                                /**
index ee33f1d..0facec2 100644 (file)
@@ -24146,6 +24146,49 @@ language=nl title=[[MediaWiki:Common.css]]
 </p>
 !! end
 
+!! test
+formatdate with invalid month
+!! wikitext
+{{#formatdate:2019-22-22|dmy}}
+!! html
+<p>2019-22-22
+</p>
+!! end
+
+!! test
+formatdate: dots in month name do not match any char (T220563)
+!! options
+language=de
+!! wikitext
+{{#formatdate:jun. 3|dmy}}
+{{#formatdate:junx 3|dmy}}
+!! html
+<p><span class="mw-formatted-date" title="06-03">3 Juni</span>
+junx 3
+</p>
+!! end
+
+!! test
+formatdate uses correct capitalisation in French
+!! options
+language=fr
+!! wikitext
+{{#formatdate:Juin 3|dmy}}
+!! html
+<p><span class="mw-formatted-date" title="06-03">3 juin</span>
+</p>
+!! end
+
+!! test
+formatdate uses correct capitalisation in English
+!! wikitext
+{{#formatdate:june 3|dmy}}
+!! html
+<p><span class="mw-formatted-date" title="06-03">3 June</span>
+</p>
+!! end
+
+
 #
 #
 #
index c7fb48b..57f56f4 100644 (file)
@@ -29,7 +29,7 @@ abstract class ResourceLoaderTestCase extends MediaWikiTestCase {
                        'debug' => 'true',
                        'lang' => 'en',
                        'dir' => 'ltr',
-                       'skin' => 'vector',
+                       'skin' => 'fallback',
                        'modules' => 'startup',
                        'only' => 'scripts',
                        'safemode' => null,
index 22fe3ce..9443b19 100644 (file)
@@ -14,6 +14,7 @@ class GlobalTest extends MediaWikiTestCase {
                unlink( $readOnlyFile );
 
                $this->setMwGlobals( [
+                       'wgReadOnly' => null,
                        'wgReadOnlyFile' => $readOnlyFile,
                        'wgUrlProtocols' => [
                                'http://',
@@ -108,10 +109,6 @@ class GlobalTest extends MediaWikiTestCase {
         * @covers ::wfReadOnly
         */
        public function testReadOnlyEmpty() {
-               global $wgReadOnly;
-               $wgReadOnly = null;
-
-               MediaWiki\MediaWikiServices::getInstance()->getReadOnlyMode()->clearCache();
                $this->assertFalse( wfReadOnly() );
                $this->assertFalse( wfReadOnly() );
        }
@@ -121,23 +118,17 @@ class GlobalTest extends MediaWikiTestCase {
         * @covers ::wfReadOnly
         */
        public function testReadOnlySet() {
-               global $wgReadOnly, $wgReadOnlyFile;
-
-               $readOnlyMode = MediaWiki\MediaWikiServices::getInstance()->getReadOnlyMode();
-               $readOnlyMode->clearCache();
+               global $wgReadOnlyFile;
 
                $f = fopen( $wgReadOnlyFile, "wt" );
                fwrite( $f, 'Message' );
                fclose( $f );
-               $wgReadOnly = null; # Check on $wgReadOnlyFile
+
+               // Reset the service to avoid cached results
+               $this->overrideMwServices();
 
                $this->assertTrue( wfReadOnly() );
                $this->assertTrue( wfReadOnly() ); # Check cached
-
-               unlink( $wgReadOnlyFile );
-               $readOnlyMode->clearCache();
-               $this->assertFalse( wfReadOnly() );
-               $this->assertFalse( wfReadOnly() );
        }
 
        /**
@@ -146,9 +137,12 @@ class GlobalTest extends MediaWikiTestCase {
         */
        public function testReadOnlyGlobalChange() {
                $this->assertFalse( wfReadOnlyReason() );
+
                $this->setMwGlobals( [
                        'wgReadOnly' => 'reason'
                ] );
+               $this->overrideMwServices();
+
                $this->assertSame( 'reason', wfReadOnlyReason() );
        }
 
index 1cd40ed..8fa0cd6 100644 (file)
@@ -11,7 +11,7 @@ use Wikimedia\Services\ServiceDisabledException;
  * @group MediaWiki
  */
 class MediaWikiServicesTest extends MediaWikiTestCase {
-       private $deprecatedServices = [ 'CryptRand' ];
+       private $deprecatedServices = [];
 
        /**
         * @return Config
@@ -364,7 +364,7 @@ class MediaWikiServicesTest extends MediaWikiTestCase {
                } ) );
 
                $sortedNames = $names;
-               sort( $sortedNames );
+               natcasesort( $sortedNames );
 
                $this->assertSame( $sortedNames, $names,
                        'Please keep service getters sorted alphabetically' );
index 7f5ec40..a6c00dc 100644 (file)
@@ -993,6 +993,7 @@ class PermissionManagerTest extends MediaWikiLangTestCase {
                $this->setMwGlobals( [
                        'wgEmailConfirmToEdit' => true,
                        'wgEmailAuthentication' => true,
+                       'wgBlockDisablesLogin' => false,
                ] );
 
                $this->overrideMwServices();
index b14424f..2d91d4d 100644 (file)
@@ -167,28 +167,4 @@ class ReadOnlyModeTest extends MediaWikiTestCase {
                $rom->setReason( 'override' );
                $this->assertSame( 'override', $rom->getReason() );
        }
-
-       /**
-        * @covers ReadOnlyMode::clearCache
-        * @covers ConfiguredReadOnlyMode::clearCache
-        */
-       public function testClearCache() {
-               $fileName = $this->getNewTempFile();
-               unlink( $fileName );
-               $config = new HashConfig( [
-                       'ReadOnly' => null,
-                       'ReadOnlyFile' => $fileName,
-               ] );
-               $cro = new ConfiguredReadOnlyMode( $config );
-               $lb = $this->createLB( [ 'lbMessage' => false ] );
-               $rom = new ReadOnlyMode( $cro, $lb );
-
-               $this->assertSame( false, $rom->getReason(), 'initial' );
-
-               file_put_contents( $fileName, 'file' );
-               $this->assertSame( false, $rom->getReason(), 'stale' );
-
-               $rom->clearCache();
-               $this->assertSame( 'file', $rom->getReason(), 'fresh' );
-       }
 }
index 74e8e1b..02e06f8 100644 (file)
@@ -8,7 +8,7 @@ class ServiceWiringTest extends MediaWikiTestCase {
                global $IP;
                $services = array_keys( require "$IP/includes/ServiceWiring.php" );
                $sortedServices = $services;
-               sort( $sortedServices );
+               natcasesort( $sortedServices );
 
                $this->assertSame( $sortedServices, $services,
                        'Please keep services sorted alphabetically' );
index a6a92c6..b0d89a6 100644 (file)
@@ -10,11 +10,11 @@ class SiteStatsTest extends MediaWikiTestCase {
                $this->setService( 'MainWANObjectCache', $cache );
                $jobq = JobQueueGroup::singleton();
 
-               $jobq->push( new NullJob( Title::newMainPage(), [] ) );
+               $jobq->push( Job::factory( 'null', Title::newMainPage(), [] ) );
                $this->assertEquals( 1, SiteStats::jobs(),
                         'A single job enqueued bumps jobscount stat to 1' );
 
-               $jobq->push( new NullJob( Title::newMainPage(), [] ) );
+               $jobq->push( Job::factory( 'null', Title::newMainPage(), [] ) );
                $this->assertEquals( 1, SiteStats::jobs(),
                        'SiteStats::jobs() count does not reflect addition ' .
                        'of a second job (cached)'
index 92c6f62..cd19cca 100644 (file)
@@ -24,6 +24,7 @@ use User;
 use Wikimedia\TestingAccessWrapper;
 use WikiPage;
 use WikitextContent;
+use DeferredUpdates;
 
 /**
  * @group Database
@@ -60,16 +61,20 @@ class DerivedPageDataUpdaterTest extends MediaWikiTestCase {
 
        /**
         * @param string|Title|WikiPage $page
+        * @param RevisionRecord|null $rec
+        * @param User|null $user
         *
         * @return DerivedPageDataUpdater
         */
-       private function getDerivedPageDataUpdater( $page, RevisionRecord $rec = null ) {
+       private function getDerivedPageDataUpdater(
+               $page, RevisionRecord $rec = null, User $user = null
+       ) {
                if ( is_string( $page ) || $page instanceof Title ) {
                        $page = $this->getPage( $page );
                }
 
                $page = TestingAccessWrapper::newFromObject( $page );
-               return $page->getDerivedDataUpdater( null, $rec );
+               return $page->getDerivedDataUpdater( $user, $rec );
        }
 
        /**
@@ -78,11 +83,12 @@ class DerivedPageDataUpdaterTest extends MediaWikiTestCase {
         * @param WikiPage $page
         * @param string|Message|CommentStoreComment $summary
         * @param null|string|Content $content
+        * @param User|null $user
         *
         * @return RevisionRecord|null
         */
-       private function createRevision( WikiPage $page, $summary, $content = null ) {
-               $user = $this->getTestUser()->getUser();
+       private function createRevision( WikiPage $page, $summary, $content = null, $user = null ) {
+               $user = $user ?: $this->getTestUser()->getUser();
                $comment = CommentStoreComment::newUnsavedComment( $summary );
 
                if ( $content === null || is_string( $content ) ) {
@@ -945,6 +951,68 @@ class DerivedPageDataUpdaterTest extends MediaWikiTestCase {
                // TODO: test category membership update (with setRcWatchCategoryMembership())
        }
 
+       /**
+        * @covers \MediaWiki\Storage\DerivedPageDataUpdater::doUpdates()
+        * @covers \MediaWiki\Storage\DerivedPageDataUpdater::doSecondaryDataUpdates()
+        * @covers \MediaWiki\Storage\DerivedPageDataUpdater::doParserCacheUpdate()
+        */
+       public function testDoUpdatesCacheSaveDeferral_canonical() {
+               $page = $this->getPage( __METHOD__ );
+
+               // Case where user has canonical parser options
+               $content = [ 'main' => new WikitextContent( 'rev ID ver #1: {{REVISIONID}}' ) ];
+               $rev = $this->createRevision( $page, 'first', $content );
+               $pcache = MediaWikiServices::getInstance()->getParserCache();
+               $pcache->deleteOptionsKey( $page );
+
+               $this->db->startAtomic( __METHOD__ ); // let deferred updates queue up
+
+               $updater = $this->getDerivedPageDataUpdater( $page, $rev );
+               $updater->prepareUpdate( $rev, [] );
+               $updater->doUpdates();
+
+               $this->assertGreaterThan( 0, DeferredUpdates::pendingUpdatesCount(), 'Pending updates' );
+               $this->assertNotFalse( $pcache->get( $page, $updater->getCanonicalParserOptions() ) );
+
+               $this->db->endAtomic( __METHOD__ ); // run deferred updates
+
+               $this->assertEquals( 0, DeferredUpdates::pendingUpdatesCount(), 'No pending updates' );
+       }
+
+       /**
+        * @covers \MediaWiki\Storage\DerivedPageDataUpdater::doUpdates()
+        * @covers \MediaWiki\Storage\DerivedPageDataUpdater::doSecondaryDataUpdates()
+        * @covers \MediaWiki\Storage\DerivedPageDataUpdater::doParserCacheUpdate()
+        */
+       public function testDoUpdatesCacheSaveDeferral_noncanonical() {
+               $page = $this->getPage( __METHOD__ );
+
+               // Case where user does not have canonical parser options
+               $user = $this->getMutableTestUser()->getUser();
+               $user->setOption(
+                       'thumbsize',
+                       $user->getOption( 'thumbsize' ) + 1
+               );
+               $content = [ 'main' => new WikitextContent( 'rev ID ver #2: {{REVISIONID}}' ) ];
+               $rev = $this->createRevision( $page, 'first', $content, $user );
+               $pcache = MediaWikiServices::getInstance()->getParserCache();
+               $pcache->deleteOptionsKey( $page );
+
+               $this->db->startAtomic( __METHOD__ ); // let deferred updates queue up
+
+               $updater = $this->getDerivedPageDataUpdater( $page, $rev, $user );
+               $updater->prepareUpdate( $rev, [] );
+               $updater->doUpdates();
+
+               $this->assertGreaterThan( 1, DeferredUpdates::pendingUpdatesCount(), 'Pending updates' );
+               $this->assertFalse( $pcache->get( $page, $updater->getCanonicalParserOptions() ) );
+
+               $this->db->endAtomic( __METHOD__ ); // run deferred updates
+
+               $this->assertEquals( 0, DeferredUpdates::pendingUpdatesCount(), 'No pending updates' );
+               $this->assertNotFalse( $pcache->get( $page, $updater->getCanonicalParserOptions() ) );
+       }
+
        /**
         * @covers \MediaWiki\Storage\DerivedPageDataUpdater::doParserCacheUpdate()
         */
index 4c2494a..f42e557 100644 (file)
@@ -62,23 +62,23 @@ class NameTableStoreTest extends MediaWikiTestCase {
                        ->getMock();
                $mock->expects( $this->exactly( $insertCalls ) )
                        ->method( 'insert' )
-                       ->willReturnCallback( function () {
-                               return call_user_func_array( [ $this->db, 'insert' ], func_get_args() );
+                       ->willReturnCallback( function ( ...$args ) {
+                               return call_user_func_array( [ $this->db, 'insert' ], $args );
                        } );
                $mock->expects( $this->exactly( $selectCalls ) )
                        ->method( 'select' )
-                       ->willReturnCallback( function () {
-                               return call_user_func_array( [ $this->db, 'select' ], func_get_args() );
+                       ->willReturnCallback( function ( ...$args ) {
+                               return call_user_func_array( [ $this->db, 'select' ], $args );
                        } );
                $mock->expects( $this->exactly( $insertCalls ) )
                        ->method( 'affectedRows' )
-                       ->willReturnCallback( function () {
-                               return call_user_func_array( [ $this->db, 'affectedRows' ], func_get_args() );
+                       ->willReturnCallback( function ( ...$args ) {
+                               return call_user_func_array( [ $this->db, 'affectedRows' ], $args );
                        } );
                $mock->expects( $this->any() )
                        ->method( 'insertId' )
-                       ->willReturnCallback( function () {
-                               return call_user_func_array( [ $this->db, 'insertId' ], func_get_args() );
+                       ->willReturnCallback( function ( ...$args ) {
+                               return call_user_func_array( [ $this->db, 'insertId' ], $args );
                        } );
                return $mock;
        }
@@ -216,17 +216,23 @@ class NameTableStoreTest extends MediaWikiTestCase {
        /**
         * @dataProvider provideGetName
         */
-       public function testGetName( $cacheBag, $insertCalls, $selectCalls ) {
+       public function testGetName( BagOStuff $cacheBag, $insertCalls, $selectCalls ) {
+               $now = microtime( true );
+               $cacheBag->setMockTime( $now );
                // Check for operations to in-memory cache (IMC) and persistent cache (PC)
                $store = $this->getNameTableSqlStore( $cacheBag, $insertCalls, $selectCalls );
 
                // Get 1 ID and make sure getName returns correctly
                $fooId = $store->acquireId( 'foo' ); // regen PC, set IMC, update IMC, tombstone PC
+               $now += 0.01;
                $this->assertSame( 'foo', $store->getName( $fooId ) ); // use IMC
+               $now += 0.01;
 
                // Get another ID and make sure getName returns correctly
                $barId = $store->acquireId( 'bar' ); // update IMC, tombstone PC
+               $now += 0.01;
                $this->assertSame( 'bar', $store->getName( $barId ) ); // use IMC
+               $now += 0.01;
 
                // Blitz the cache and make sure it still returns
                TestingAccessWrapper::newFromObject( $store )->tableCache = null; // clear IMC
@@ -236,6 +242,7 @@ class NameTableStoreTest extends MediaWikiTestCase {
                // Blitz the cache again and get another ID and make sure getName returns correctly
                TestingAccessWrapper::newFromObject( $store )->tableCache = null; // clear IMC
                $bazId = $store->acquireId( 'baz' ); // set IMC using interim PC, update IMC, tombstone PC
+               $now += 0.01;
                $this->assertSame( 'baz', $store->getName( $bazId ) ); // uses IMC
                $this->assertSame( 'baz', $store->getName( $bazId ) ); // uses IMC
        }
diff --git a/tests/phpunit/includes/Storage/PreparedEditTest.php b/tests/phpunit/includes/Storage/PreparedEditTest.php
new file mode 100644 (file)
index 0000000..29999ee
--- /dev/null
@@ -0,0 +1,22 @@
+<?php
+
+namespace MediaWiki\Edit;
+
+use ParserOutput;
+use MediaWikiTestCase;
+
+/**
+ * @covers \MediaWiki\Edit\PreparedEdit
+ */
+class PreparedEditTest extends MediaWikiTestCase {
+       function testCallback() {
+               $output = new ParserOutput();
+               $edit = new PreparedEdit();
+               $edit->parserOutputCallback = function () {
+                       return new ParserOutput();
+               };
+
+               $this->assertEquals( $output, $edit->getOutput() );
+               $this->assertEquals( $output, $edit->output );
+       }
+}
index f7ffe8d..e5e265f 100644 (file)
@@ -891,6 +891,7 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
                $this->setMwGlobals( [
                        'wgEmailConfirmToEdit' => true,
                        'wgEmailAuthentication' => true,
+                       'wgBlockDisablesLogin' => false,
                ] );
                $this->overrideMwServices();
 
index f976540..095f373 100644 (file)
@@ -2,6 +2,7 @@
 
 use MediaWiki\Linker\LinkTarget;
 use MediaWiki\MediaWikiServices;
+use Wikimedia\TestingAccessWrapper;
 
 /**
  * @group Database
@@ -20,6 +21,12 @@ class TitleTest extends MediaWikiTestCase {
                $this->setContentLang( 'en' );
        }
 
+       protected function tearDown() {
+               // For testNewMainPage
+               MessageCache::destroyInstance();
+               parent::tearDown();
+       }
+
        /**
         * @covers Title::legalChars
         */
@@ -1092,4 +1099,32 @@ class TitleTest extends MediaWikiTestCase {
                        $firstValue->equals( $secondValue )
                );
        }
+
+       /**
+        * @covers Title::newMainPage
+        */
+       public function testNewMainPage() {
+               $msgCache = TestingAccessWrapper::newFromClass( MessageCache::class );
+               $msgCache->instance = $this->createMock( MessageCache::class );
+               $msgCache->instance->method( 'get' )->willReturn( 'Foresheet' );
+               $msgCache->instance->method( 'transform' )->willReturn( 'Foresheet' );
+
+               $this->assertSame(
+                       'Foresheet',
+                       Title::newMainPage()->getText()
+               );
+       }
+
+       /**
+        * @covers Title::newMainPage
+        */
+       public function testNewMainPageWithLocal() {
+               $local = $this->createMock( MessageLocalizer::class );
+               $local->method( 'msg' )->willReturn( new RawMessage( 'Prime Article' ) );
+
+               $this->assertSame(
+                       'Prime Article',
+                       Title::newMainPage( $local )->getText()
+               );
+       }
 }
index 08af755..7869bbd 100644 (file)
@@ -32,14 +32,14 @@ STR;
 
        /**
         * Merges all requests parameter + expected values into one
-        * @param array $v,... List of arrays, each of which contains exactly two
+        * @param array ...$arrays List of arrays, each of which contains exactly two
         * @return array
         */
-       protected function merge( /*...*/ ) {
+       protected function merge( ...$arrays ) {
                $request = [];
                $expected = [];
-               foreach ( func_get_args() as $v ) {
-                       list( $req, $exp ) = $this->validateRequestExpectedPair( $v );
+               foreach ( $arrays as $array ) {
+                       list( $req, $exp ) = $this->validateRequestExpectedPair( $array );
                        $request = array_merge_recursive( $request, $req );
                        $this->mergeExpected( $expected, $exp );
                }
index cc2771c..fc1930a 100644 (file)
@@ -53,9 +53,8 @@ class UserDataAuthenticationRequestTest extends AuthenticationRequestTestCase {
         * @dataProvider provideLoadFromSubmission
         */
        public function testLoadFromSubmission(
-               array $args, array $data, $expectState /* $hiddenPref, $enableEmail */
+               array $args, array $data, $expectState, $hiddenPref = null, $enableEmail = null
        ) {
-               list( $args, $data, $expectState, $hiddenPref, $enableEmail ) = func_get_args();
                $this->setMwGlobals( 'wgHiddenPrefs', $hiddenPref );
                $this->setMwGlobals( 'wgEnableEmail', $enableEmail );
                parent::testLoadFromSubmission( $args, $data, $expectState );
index ca3ac1b..7ad6541 100644 (file)
@@ -39,8 +39,8 @@ class CategoryMembershipChangeTest extends MediaWikiLangTestCase {
         */
        private static $pageName = 'CategoryMembershipChangeTestPage';
 
-       public static function newForCategorizationCallback() {
-               self::$lastNotifyArgs = func_get_args();
+       public static function newForCategorizationCallback( ...$args ) {
+               self::$lastNotifyArgs = $args;
                self::$notifyCallCounter += 1;
                return self::$mockRecentChange;
        }
index f42f8b4..4eecd4d 100644 (file)
@@ -519,7 +519,7 @@ class ContentHandlerTest extends MediaWikiTestCase {
                $customContentHandler->expects( $this->any() )
                        ->method( 'createDifferenceEngine' )
                        ->willReturn( $customDifferenceEngine );
-               /** @var $customContentHandler ContentHandler */
+               /** @var ContentHandler $customContentHandler */
                $slotDiffRenderer = $customContentHandler->getSlotDiffRenderer( RequestContext::getMain() );
                $this->assertInstanceOf( DifferenceEngineSlotDiffRenderer::class, $slotDiffRenderer );
                $this->assertSame(
@@ -553,7 +553,7 @@ class ContentHandlerTest extends MediaWikiTestCase {
                $customContentHandler2->expects( $this->any() )
                        ->method( 'getSlotDiffRendererInternal' )
                        ->willReturn( $customSlotDiffRenderer );
-               /** @var $customContentHandler2 ContentHandler */
+               /** @var ContentHandler $customContentHandler2 */
                $slotDiffRenderer = $customContentHandler2->getSlotDiffRenderer( RequestContext::getMain() );
                $this->assertSame( $customSlotDiffRenderer, $slotDiffRenderer );
        }
@@ -577,7 +577,7 @@ class ContentHandlerTest extends MediaWikiTestCase {
                $customContentHandler->expects( $this->any() )
                        ->method( 'createDifferenceEngine' )
                        ->willReturn( $customDifferenceEngine );
-               /** @var $customContentHandler ContentHandler */
+               /** @var ContentHandler $customContentHandler */
 
                $customSlotDiffRenderer = $this->getMockBuilder( SlotDiffRenderer::class )
                        ->disableOriginalConstructor()
@@ -592,7 +592,7 @@ class ContentHandlerTest extends MediaWikiTestCase {
                $customContentHandler2->expects( $this->any() )
                        ->method( 'getSlotDiffRendererInternal' )
                        ->willReturn( $customSlotDiffRenderer );
-               /** @var $customContentHandler2 ContentHandler */
+               /** @var ContentHandler $customContentHandler2 */
 
                $customSlotDiffRenderer2 = $this->getMockBuilder( SlotDiffRenderer::class )
                        ->disableOriginalConstructor()
index 7cddbad..8546d96 100644 (file)
@@ -113,27 +113,27 @@ class JsonContentTest extends MediaWikiLangTestCase {
                        ],
                        [
                                (object)[ 'foo' ],
-                               '<table class="mw-json"><tbody><tr><th>0</th><td class="value">"foo"</td></tr>' .
+                               '<table class="mw-json"><tbody><tr><th>0</th><td class="mw-json-value">"foo"</td></tr>' .
                                '</tbody></table>'
                        ],
                        [
                                (object)[ 'foo', 'bar' ],
-                               '<table class="mw-json"><tbody><tr><th>0</th><td class="value">"foo"</td></tr>' .
-                               '<tr><th>1</th><td class="value">"bar"</td></tr></tbody></table>'
+                               '<table class="mw-json"><tbody><tr><th>0</th><td class="mw-json-value">"foo"</td></tr>' .
+                               '<tr><th>1</th><td class="mw-json-value">"bar"</td></tr></tbody></table>'
                        ],
                        [
                                (object)[ 'baz' => 'foo', 'bar' ],
-                               '<table class="mw-json"><tbody><tr><th>baz</th><td class="value">"foo"</td></tr>' .
-                               '<tr><th>0</th><td class="value">"bar"</td></tr></tbody></table>'
+                               '<table class="mw-json"><tbody><tr><th>baz</th><td class="mw-json-value">"foo"</td></tr>' .
+                               '<tr><th>0</th><td class="mw-json-value">"bar"</td></tr></tbody></table>'
                        ],
                        [
                                (object)[ 'baz' => 1000, 'bar' ],
-                               '<table class="mw-json"><tbody><tr><th>baz</th><td class="value">1000</td></tr>' .
-                               '<tr><th>0</th><td class="value">"bar"</td></tr></tbody></table>'
+                               '<table class="mw-json"><tbody><tr><th>baz</th><td class="mw-json-value">1000</td></tr>' .
+                               '<tr><th>0</th><td class="mw-json-value">"bar"</td></tr></tbody></table>'
                        ],
                        [
                                (object)[ '<script>alert("evil!")</script>' ],
-                               '<table class="mw-json"><tbody><tr><th>0</th><td class="value">"' .
+                               '<table class="mw-json"><tbody><tr><th>0</th><td class="mw-json-value">"' .
                                '&lt;script>alert("evil!")&lt;/script>"' .
                                '</td></tr></tbody></table>',
                        ],
index 6d0a3d5..e1281cd 100644 (file)
@@ -37,14 +37,14 @@ class TextContentHandlerTest extends MediaWikiLangTestCase {
                        } );
 
                /**
-                * @var $mockEngine SearchEngine
+                * @var SearchEngine $mockEngine
                 */
                $fields = $handler->getFieldsForSearchIndex( $mockEngine );
                $mappedFields = [];
                foreach ( $fields as $name => $field ) {
                        $this->assertInstanceOf( SearchIndexField::class, $field );
                        /**
-                        * @var $field SearchIndexField
+                        * @var SearchIndexField $field
                         */
                        $mappedFields[$name] = $field->getMapping( $mockEngine );
                }
index 4f82ff1..ba31b4f 100644 (file)
@@ -313,14 +313,14 @@ class DifferenceEngineTest extends MediaWikiTestCase {
                $customContentHandler->expects( $this->any() )
                        ->method( 'createDifferenceEngine' )
                        ->willReturn( $customDifferenceEngine );
-               /** @var $customContentHandler ContentHandler */
+               /** @var ContentHandler $customContentHandler */
                $customContent = $this->getMockBuilder( Content::class )
                        ->setMethods( [ 'getContentHandler' ] )
                        ->getMockForAbstractClass();
                $customContent->expects( $this->any() )
                        ->method( 'getContentHandler' )
                        ->willReturn( $customContentHandler );
-               /** @var $customContent Content */
+               /** @var Content $customContent */
                $customContent2 = clone $customContent;
 
                $slotDiffRenderer = $customContentHandler->getSlotDiffRenderer( RequestContext::getMain() );
index fedfac6..eee4296 100644 (file)
@@ -202,7 +202,7 @@ class HttpTest extends MediaWikiTestCase {
 
        /**
         * Constant values are from PHP 5.3.28 using cURL 7.24.0
-        * @see https://secure.php.net/manual/en/curl.constants.php
+        * @see https://www.php.net/manual/en/curl.constants.php
         *
         * All constant values are present so that developers don’t need to remember
         * to add them if added at a later date. The commented out constants were
@@ -515,7 +515,7 @@ class MWHttpRequestTester extends MWHttpRequest {
                if ( !Http::$httpEngine ) {
                        Http::$httpEngine = 'guzzle';
                } elseif ( Http::$httpEngine == 'curl' && !function_exists( 'curl_init' ) ) {
-                       throw new DomainException( __METHOD__ . ': curl (https://secure.php.net/curl) is not ' .
+                       throw new DomainException( __METHOD__ . ': curl (https://www.php.net/curl) is not ' .
                                'installed, but Http::$httpEngine is set to "curl"' );
                }
 
@@ -528,7 +528,7 @@ class MWHttpRequestTester extends MWHttpRequest {
                                if ( !wfIniGetBool( 'allow_url_fopen' ) ) {
                                        throw new DomainException( __METHOD__ .
                                                ': allow_url_fopen needs to be enabled for pure PHP HTTP requests to work. '
-                                                       . 'If possible, curl should be used instead. See https://secure.php.net/curl.' );
+                                                       . 'If possible, curl should be used instead. See https://www.php.net/curl.' );
                                }
 
                                return new PhpHttpRequestTester( $url, $options, $caller );
index 81a80b6..1baaa54 100644 (file)
@@ -380,12 +380,12 @@ class JobQueueTest extends MediaWikiTestCase {
        }
 
        function newJob( $i = 0, $rootJob = [] ) {
-               return new NullJob( Title::newMainPage(),
+               return Job::factory( 'null', Title::newMainPage(),
                        [ 'lives' => 0, 'usleep' => 0, 'removeDuplicates' => 0, 'i' => $i ] + $rootJob );
        }
 
        function newDedupedJob( $i = 0, $rootJob = [] ) {
-               return new NullJob( Title::newMainPage(),
+               return Job::factory( 'null', Title::newMainPage(),
                        [ 'lives' => 0, 'usleep' => 0, 'removeDuplicates' => 1, 'i' => $i ] + $rootJob );
        }
 }
index 769b193..9fe3e3d 100644 (file)
@@ -29,31 +29,31 @@ class JobTest extends MediaWikiTestCase {
                return [
                        [
                                $this->getMockJob( false ),
-                               'someCommand  ' . $requestId
+                               'someCommand Special: ' . $requestId
                        ],
                        [
                                $this->getMockJob( [ 'key' => 'val' ] ),
-                               'someCommand  key=val ' . $requestId
+                               'someCommand Special: key=val ' . $requestId
                        ],
                        [
                                $this->getMockJob( [ 'key' => [ 'inkey' => 'inval' ] ] ),
-                               'someCommand  key={"inkey":"inval"} ' . $requestId
+                               'someCommand Special: key={"inkey":"inval"} ' . $requestId
                        ],
                        [
                                $this->getMockJob( [ 'val1' ] ),
-                               'someCommand  0=val1 ' . $requestId
+                               'someCommand Special: 0=val1 ' . $requestId
                        ],
                        [
                                $this->getMockJob( [ 'val1', 'val2' ] ),
-                               'someCommand  0=val1 1=val2 ' . $requestId
+                               'someCommand Special: 0=val1 1=val2 ' . $requestId
                        ],
                        [
                                $this->getMockJob( [ new stdClass() ] ),
-                               'someCommand  0=object(stdClass) ' . $requestId
+                               'someCommand Special: 0=object(stdClass) ' . $requestId
                        ],
                        [
                                $this->getMockJob( [ $mockToStringObj ] ),
-                               'someCommand  0={STRING_OBJ_VAL} ' . $requestId
+                               'someCommand Special: 0={STRING_OBJ_VAL} ' . $requestId
                        ],
                        [
                                $this->getMockJob( [
@@ -72,7 +72,7 @@ class JobTest extends MediaWikiTestCase {
                                        ],
                                        "triggeredRecursive" => true
                                ] ),
-                               'someCommand  pages={"932737":[0,"Robert_James_Waller"]} ' .
+                               'someCommand Special: pages={"932737":[0,"Robert_James_Waller"]} ' .
                                'rootJobSignature=45868e99bba89064e4483743ebb9b682ef95c1a7 ' .
                                'rootJobTimestamp=20160309110158 masterPos=' .
                                '{"file":"db1023-bin.001288","pos":"308257743","asOfTime":' .
@@ -85,11 +85,13 @@ class JobTest extends MediaWikiTestCase {
        }
 
        public function getMockJob( $params ) {
+               $title = new Title();
                $mock = $this->getMockForAbstractClass(
                        Job::class,
-                       [ 'someCommand', new Title(), $params ],
+                       [ 'someCommand', $title, $params ],
                        'SomeJob'
                );
+
                return $mock;
        }
 
@@ -115,7 +117,7 @@ class JobTest extends MediaWikiTestCase {
                return [
                        'class name' => [ 'NullJob' ],
                        'closure' => [ function ( Title $title, array $params ) {
-                               return new NullJob( $title, $params );
+                               return Job::factory( 'null', $title, $params );
                        } ],
                        'function' => [ [ $this, 'newNullJob' ] ],
                        'static function' => [ self::class . '::staticNullJob' ]
@@ -123,11 +125,91 @@ class JobTest extends MediaWikiTestCase {
        }
 
        public function newNullJob( Title $title, array $params ) {
-               return new NullJob( $title, $params );
+               return Job::factory( 'null', $title, $params );
        }
 
        public static function staticNullJob( Title $title, array $params ) {
-               return new NullJob( $title, $params );
+               return Job::factory( 'null', $title, $params );
+       }
+
+       /**
+        * @covers Job::factory
+        * @covers Job::__construct()
+        */
+       public function testJobSignatureGeneric() {
+               $testPage = Title::makeTitle( NS_PROJECT, 'x' );
+               $blankTitle = Title::makeTitle( NS_SPECIAL, '' );
+               $params = [ 'z' => 1, 'lives' => 1, 'usleep' => 0 ];
+               $paramsWithTitle = $params + [ 'namespace' => NS_PROJECT, 'title' => 'x' ];
+
+               $job = new NullJob( [ 'namespace' => NS_PROJECT, 'title' => 'x' ] + $params );
+               $this->assertEquals( $testPage->getPrefixedText(), $job->getTitle()->getPrefixedText() );
+               $this->assertJobParamsMatch( $job, $paramsWithTitle );
+
+               $job = Job::factory( 'null', $testPage, $params );
+               $this->assertEquals( $blankTitle->getPrefixedText(), $job->getTitle()->getPrefixedText() );
+               $this->assertJobParamsMatch( $job, $params );
+
+               $job = Job::factory( 'null', $paramsWithTitle );
+               $this->assertEquals( $testPage->getPrefixedText(), $job->getTitle()->getPrefixedText() );
+               $this->assertJobParamsMatch( $job, $paramsWithTitle );
+
+               $job = Job::factory( 'null', $params );
+               $this->assertTrue( $blankTitle->equals( $job->getTitle() ) );
+               $this->assertJobParamsMatch( $job, $params );
+       }
+
+       /**
+        * @covers Job::factory
+        * @covers Job::__construct()
+        */
+       public function testJobSignatureTitleBased() {
+               $testPage = Title::makeTitle( NS_PROJECT, 'x' );
+               $blankTitle = Title::makeTitle( NS_SPECIAL, '' );
+               $params = [ 'z' => 1, 'causeAction' => 'unknown', 'causeAgent' => 'unknown' ];
+               $paramsWithTitle = $params + [ 'namespace' => NS_PROJECT, 'title' => 'x' ];
+
+               $job = new RefreshLinksJob( $testPage, $params );
+               $this->assertEquals( $testPage->getPrefixedText(), $job->getTitle()->getPrefixedText() );
+               $this->assertSame( $testPage, $job->getTitle() );
+               $this->assertJobParamsMatch( $job, $paramsWithTitle );
+               $this->assertSame( $testPage, $job->getTitle() );
+
+               $job = Job::factory( 'refreshLinks', $testPage, $params );
+               $this->assertEquals( $testPage->getPrefixedText(), $job->getTitle()->getPrefixedText() );
+               $this->assertJobParamsMatch( $job, $paramsWithTitle );
+
+               $job = Job::factory( 'refreshLinks', $paramsWithTitle );
+               $this->assertEquals( $testPage->getPrefixedText(), $job->getTitle()->getPrefixedText() );
+               $this->assertJobParamsMatch( $job, $paramsWithTitle );
+
+               $job = Job::factory( 'refreshLinks', $params );
+               $this->assertTrue( $blankTitle->equals( $job->getTitle() ) );
+               $this->assertJobParamsMatch( $job, $params );
+       }
+
+       /**
+        * @covers Job::factory
+        * @covers Job::__construct()
+        */
+       public function testJobSignatureTitleBasedIncomplete() {
+               $testPage = Title::makeTitle( NS_PROJECT, 'x' );
+               $blankTitle = Title::makeTitle( NS_SPECIAL, '' );
+               $params = [ 'z' => 1, 'causeAction' => 'unknown', 'causeAgent' => 'unknown' ];
+
+               $job = new RefreshLinksJob( $testPage, $params + [ 'namespace' => 0 ] );
+               $this->assertEquals( $blankTitle->getPrefixedText(), $job->getTitle()->getPrefixedText() );
+               $this->assertJobParamsMatch( $job, $params + [ 'namespace' => 0 ] );
+
+               $job = new RefreshLinksJob( $testPage, $params + [ 'title' => 'x' ] );
+               $this->assertEquals( $blankTitle->getPrefixedText(), $job->getTitle()->getPrefixedText() );
+               $this->assertJobParamsMatch( $job, $params + [ 'title' => 'x' ] );
        }
 
+       private function assertJobParamsMatch( IJobSpecification $job, array $params ) {
+               $actual = $job->getParams();
+               unset( $actual['requestId'] );
+
+               $this->assertEquals( $actual, $params );
+       }
 }
index 27cae8a..1a2941d 100644 (file)
@@ -51,13 +51,9 @@ class ClearUserWatchlistJobTest extends MediaWikiTestCase {
                $this->setMwGlobals( 'wgUpdateRowsPerQuery', 2 );
 
                JobQueueGroup::singleton()->push(
-                       new ClearUserWatchlistJob(
-                               null,
-                               [
-                                       'userId' => $user->getId(),
-                                       'maxWatchlistId' => $maxId,
-                               ]
-                       )
+                       new ClearUserWatchlistJob( [
+                               'userId' => $user->getId(), 'maxWatchlistId' => $maxId,
+                       ] )
                );
 
                $this->assertEquals( 1, JobQueueGroup::singleton()->getQueueSizes()['clearUserWatchlist'] );
index d5ac77b..12b6320 100644 (file)
@@ -133,9 +133,7 @@ class ArrayUtilsTest extends PHPUnit\Framework\TestCase {
         * @covers ArrayUtils::arrayDiffAssocRecursive
         * @dataProvider provideArrayDiffAssocRecursive
         */
-       function testArrayDiffAssocRecursive( $expected ) {
-               $args = func_get_args();
-               array_shift( $args );
+       function testArrayDiffAssocRecursive( $expected, ...$args ) {
                $this->assertEquals( call_user_func_array(
                        'ArrayUtils::arrayDiffAssocRecursive', $args
                ), $expected );
index 5487556..3a3feee 100644 (file)
@@ -71,7 +71,7 @@ abstract class WikiPageDbTestBase extends MediaWikiLangTestCase {
 
        protected function tearDown() {
                foreach ( $this->pagesToDelete as $p ) {
-                       /* @var $p WikiPage */
+                       /* @var WikiPage $p */
 
                        try {
                                if ( $p->exists() ) {
index 6413ddd..01fde35 100644 (file)
@@ -284,6 +284,31 @@ class ParserOptionsTest extends MediaWikiTestCase {
                ScopedCallback::consume( $reset );
        }
 
+       public function testMatchesForCacheKey() {
+               $cOpts = ParserOptions::newCanonical( null, 'en' );
+
+               $uOpts = ParserOptions::newFromAnon();
+               $this->assertTrue( $cOpts->matchesForCacheKey( $uOpts ) );
+
+               $user = new User();
+               $uOpts = ParserOptions::newFromUser( $user );
+               $this->assertTrue( $cOpts->matchesForCacheKey( $uOpts ) );
+
+               $user = new User();
+               $user->setOption( 'thumbsize', 251 );
+               $uOpts = ParserOptions::newFromUser( $user );
+               $this->assertFalse( $cOpts->matchesForCacheKey( $uOpts ) );
+
+               $user = new User();
+               $user->setOption( 'stubthreshold', 800 );
+               $uOpts = ParserOptions::newFromUser( $user );
+               $this->assertFalse( $cOpts->matchesForCacheKey( $uOpts ) );
+
+               $user = new User();
+               $uOpts = ParserOptions::newFromUserAndLang( $user, Language::factory( 'zh' ) );
+               $this->assertFalse( $cOpts->matchesForCacheKey( $uOpts ) );
+       }
+
        public function testAllCacheVaryingOptions() {
                $this->setTemporaryHook( 'ParserOptionsRegister', null );
                $this->assertSame( [
index 19baf5a..28cd503 100644 (file)
@@ -78,7 +78,7 @@ class PoolCounterTest extends MediaWikiTestCase {
 // That call will die if the contructor is not public, unless we use disableOriginalConstructor(),
 // in which case we could not test the constructor.
 abstract class PoolCounterAbstractMock extends PoolCounter {
-       public function __construct() {
-               call_user_func_array( 'parent::__construct', func_get_args() );
+       public function __construct( ...$args ) {
+               call_user_func_array( 'parent::__construct', $args );
        }
 }
index 94c0667..48a9ecd 100644 (file)
@@ -67,7 +67,7 @@ class DefaultPreferencesFactoryTest extends \MediaWikiTestCase {
 
                $testUser = $this->getTestUser();
                $form = $this->getPreferencesFactory()->getForm( $testUser->getUser(), $this->context );
-               $this->assertInstanceOf( PreferencesFormLegacy::class, $form );
+               $this->assertInstanceOf( PreferencesFormOOUI::class, $form );
                $this->assertCount( 5, $form->getPreferenceSections() );
        }
 
@@ -162,7 +162,7 @@ class DefaultPreferencesFactoryTest extends \MediaWikiTestCase {
                $configMock = new HashConfig( [
                        'HiddenPrefs' => []
                ] );
-               $form = $this->getMockBuilder( PreferencesFormLegacy::class )
+               $form = $this->getMockBuilder( PreferencesFormOOUI::class )
                        ->disableOriginalConstructor()
                        ->getMock();
 
index 6b92444..e824e3f 100644 (file)
@@ -101,7 +101,21 @@ class VersionCheckerTest extends PHPUnit\Framework\TestCase {
         * @dataProvider provideType
         */
        public function testType( $given, $expected ) {
-               $checker = new VersionChecker( '1.0.0', '7.0.0', [ 'phpLoadedExtension' ] );
+               $checker = new VersionChecker(
+                       '1.0.0',
+                       '7.0.0',
+                       [ 'phpLoadedExtension' ],
+                       [
+                               'presentAbility' => true,
+                               'presentAbilityWithMessage' => true,
+                               'missingAbility' => false,
+                               'missingAbilityWithMessage' => false,
+                       ],
+                       [
+                               'presentAbilityWithMessage' => 'Present.',
+                               'missingAbilityWithMessage' => 'Missing.',
+                       ]
+               );
                $checker->setLoadedExtensionsAndSkins( [
                                'FakeDependency' => [
                                        'version' => '1.0.0',
@@ -218,6 +232,83 @@ class VersionCheckerTest extends PHPUnit\Framework\TestCase {
                                        ],
                                ],
                        ],
+                       [
+                               [
+                                       'platform' => [
+                                               'ability-presentAbility' => true,
+                                       ],
+                               ],
+                               [],
+                       ],
+                       [
+                               [
+                                       'platform' => [
+                                               'ability-presentAbilityWithMessage' => true,
+                                       ],
+                               ],
+                               [],
+                       ],
+                       [
+                               [
+                                       'platform' => [
+                                               'ability-presentAbility' => false,
+                                       ],
+                               ],
+                               [],
+                       ],
+                       [
+                               [
+                                       'platform' => [
+                                               'ability-presentAbilityWithMessage' => false,
+                                       ],
+                               ],
+                               [],
+                       ],
+                       [
+                               [
+                                       'platform' => [
+                                               'ability-missingAbility' => true,
+                                       ],
+                               ],
+                               [
+                                       [
+                                               'missing' => 'missingAbility',
+                                               'type' => 'missing-ability',
+                                               'msg' => 'FakeExtension requires "missingAbility" ability',
+                                       ],
+                               ],
+                       ],
+                       [
+                               [
+                                       'platform' => [
+                                               'ability-missingAbilityWithMessage' => true,
+                                       ],
+                               ],
+                               [
+                                       [
+                                               'missing' => 'missingAbilityWithMessage',
+                                               'type' => 'missing-ability',
+                                               // phpcs:ignore Generic.Files.LineLength.TooLong
+                                               'msg' => 'FakeExtension requires "missingAbilityWithMessage" ability: Missing.',
+                                       ],
+                               ],
+                       ],
+                       [
+                               [
+                                       'platform' => [
+                                               'ability-missingAbility' => false,
+                                       ],
+                               ],
+                               [],
+                       ],
+                       [
+                               [
+                                       'platform' => [
+                                               'ability-missingAbilityWithMessage' => false,
+                                       ],
+                               ],
+                               [],
+                       ],
                ];
        }
 
@@ -282,6 +373,26 @@ class VersionCheckerTest extends PHPUnit\Framework\TestCase {
                                ],
                                'phpLoadedExtension',
                        ],
+                       [
+                               [
+                                       'FakeExtension' => [
+                                               'platform' => [
+                                                       'ability-invalidAbility' => true,
+                                               ],
+                                       ],
+                               ],
+                               'ability-invalidAbility',
+                       ],
+                       [
+                               [
+                                       'FakeExtension' => [
+                                               'platform' => [
+                                                       'presentAbility' => true,
+                                               ],
+                                       ],
+                               ],
+                               'presentAbility',
+                       ],
                        [
                                [
                                        'FakeExtension' => [
@@ -308,7 +419,15 @@ class VersionCheckerTest extends PHPUnit\Framework\TestCase {
         * @dataProvider provideInvalidDependency
         */
        public function testInvalidDependency( $depencency, $type ) {
-               $checker = new VersionChecker( '1.0.0', '7.0.0', [ 'phpLoadedExtension' ] );
+               $checker = new VersionChecker(
+                       '1.0.0',
+                       '7.0.0',
+                       [ 'phpLoadedExtension' ],
+                       [
+                               'presentAbility' => true,
+                               'missingAbility' => false,
+                       ]
+               );
                $this->setExpectedException(
                        UnexpectedValueException::class,
                        "Dependency type $type unknown in FakeExtension"
@@ -330,4 +449,31 @@ class VersionCheckerTest extends PHPUnit\Framework\TestCase {
                        ],
                ] );
        }
+
+       /**
+        * @dataProvider provideInvalidAbilityType
+        */
+       public function testInvalidAbilityType( $value ) {
+               $checker = new VersionChecker( '1.0.0', '7.0.0', [], [ 'presentAbility' => true ] );
+               $this->setExpectedException(
+                       UnexpectedValueException::class,
+                       'Only booleans are allowed to to indicate the presence of abilities in FakeExtension'
+               );
+               $checker->checkArray( [
+                       'FakeExtension' => [
+                               'platform' => [
+                                       'ability-presentAbility' => $value,
+                               ],
+                       ],
+               ] );
+       }
+
+       public function provideInvalidAbilityType() {
+               return [
+                       [ null ],
+                       [ 1 ],
+                       [ '1' ],
+               ];
+       }
+
 }
index 1b7e0fe..60cd4a8 100644 (file)
@@ -2,10 +2,9 @@
 
 /**
  * See also:
- * - ResourceLoaderTest::testExpandModuleNames
  * - ResourceLoaderImageModuleTest::testContext
  *
- * @group Cache
+ * @group ResourceLoader
  * @covers ResourceLoaderContext
  */
 class ResourceLoaderContextTest extends PHPUnit\Framework\TestCase {
@@ -15,8 +14,6 @@ class ResourceLoaderContextTest extends PHPUnit\Framework\TestCase {
        protected static function getResourceLoader() {
                return new EmptyResourceLoader( new HashConfig( [
                        'ResourceLoaderDebug' => false,
-                       'DefaultSkin' => 'fallback',
-                       'LanguageCode' => 'nl',
                        'LoadScript' => '/w/load.php',
                ] ) );
        }
@@ -26,7 +23,7 @@ class ResourceLoaderContextTest extends PHPUnit\Framework\TestCase {
 
                // Request parameters
                $this->assertEquals( [], $ctx->getModules() );
-               $this->assertEquals( 'nl', $ctx->getLanguage() );
+               $this->assertEquals( 'qqx', $ctx->getLanguage() );
                $this->assertEquals( false, $ctx->getDebug() );
                $this->assertEquals( null, $ctx->getOnly() );
                $this->assertEquals( 'fallback', $ctx->getSkin() );
@@ -35,7 +32,7 @@ class ResourceLoaderContextTest extends PHPUnit\Framework\TestCase {
 
                // Misc
                $this->assertEquals( 'ltr', $ctx->getDirection() );
-               $this->assertEquals( 'nl|fallback||||||||', $ctx->getHash() );
+               $this->assertEquals( 'qqx|fallback||||||||', $ctx->getHash() );
                $this->assertInstanceOf( User::class, $ctx->getUserObj() );
        }
 
index ea220f1..30e9f30 100644 (file)
@@ -56,9 +56,9 @@ class ResourceLoaderOOUIImageModuleTest extends ResourceLoaderTestCase {
                        'Generated styles use the default image (embed)'
                );
                $this->assertRegExp(
-                       '/vector/',
+                       '/fallback/',
                        $styles['all'],
-                       'Generated styles use the default image (link)'
+                       'Generated styles use the default skin (link)'
                );
        }
 
index 3f7925f..7239afc 100644 (file)
@@ -288,12 +288,12 @@ class ResourceLoaderTest extends ResourceLoaderTestCase {
 
        /**
         * @dataProvider providePackedModules
-        * @covers ResourceLoaderContext::expandModuleNames
+        * @covers ResourceLoader::expandModuleNames
         */
        public function testExpandModuleNames( $desc, $modules, $packed, $unpacked = null ) {
                $this->assertEquals(
                        $unpacked ?: $modules,
-                       ResourceLoaderContext::expandModuleNames( $packed ),
+                       ResourceLoader::expandModuleNames( $packed ),
                        $desc
                );
        }
index d86e63d..0c6520e 100644 (file)
@@ -271,7 +271,7 @@ class SearchEngineTest extends MediaWikiLangTestCase {
         */
        public function testSearchIndexFields() {
                /**
-                * @var $mockEngine SearchEngine
+                * @var SearchEngine $mockEngine
                 */
                $mockEngine = $this->getMockBuilder( SearchEngine::class )
                        ->setMethods( [ 'makeSearchFieldMapping' ] )->getMock();
index 16f2367..63c2b82 100644 (file)
@@ -204,8 +204,7 @@ class WatchedItemQueryServiceUnitTest extends MediaWikiTestCase {
                        } ) );
                $mock->expects( $this->any() )
                        ->method( 'isAllowedAny' )
-                       ->will( $this->returnCallback( function () use ( $notAllowedAction ) {
-                               $actions = func_get_args();
+                       ->will( $this->returnCallback( function ( ...$actions ) use ( $notAllowedAction ) {
                                return !in_array( $notAllowedAction, $actions );
                        } ) );
 
index a6b2162..2f95688 100644 (file)
@@ -89,8 +89,8 @@ class WatchedItemStoreUnitTest extends MediaWikiTestCase {
                        ->getMock();
                $mock->expects( $this->any() )
                        ->method( 'makeKey' )
-                       ->will( $this->returnCallback( function () {
-                               return implode( ':', func_get_args() );
+                       ->will( $this->returnCallback( function ( ...$args ) {
+                               return implode( ':', $args );
                        } ) );
                return $mock;
        }
index a7d947e..57b063d 100644 (file)
@@ -54,6 +54,17 @@ class AvailableRightsTest extends PHPUnit\Framework\TestCase {
                );
        }
 
+       /**
+        * Test, if for all rights an action- message exist,
+        * which is used on Special:ListGroupRights as help text
+        * Extensions and core
+        *
+        * @coversNothing
+        */
+       public function testAllActionsWithMessages() {
+               $this->checkMessagesExist( 'action-' );
+       }
+
        /**
         * Test, if for all rights a right- message exist,
         * which is used on Special:ListGroupRights as help text
@@ -62,27 +73,34 @@ class AvailableRightsTest extends PHPUnit\Framework\TestCase {
         * @coversNothing
         */
        public function testAllRightsWithMessage() {
+               $this->checkMessagesExist( 'right-' );
+       }
+
+       /**
+        * @param string $prefix
+        */
+       private function checkMessagesExist( $prefix ) {
                // Getting all user rights, for core: User::$mCoreRights, for extensions: $wgAvailableRights
                $allRights = User::getAllRights();
                $allMessageKeys = Language::getMessageKeysFor( 'en' );
 
-               $rightsWithMessage = [];
+               $messagesToCheck = [];
                foreach ( $allMessageKeys as $message ) {
                        // === 0: must be at beginning of string (position 0)
-                       if ( strpos( $message, 'right-' ) === 0 ) {
-                               $rightsWithMessage[] = substr( $message, strlen( 'right-' ) );
+                       if ( strpos( $message, $prefix ) === 0 ) {
+                               $messagesToCheck[] = substr( $message, strlen( $prefix ) );
                        }
                }
 
                $missing = array_diff(
                        $allRights,
-                       $rightsWithMessage
+                       $messagesToCheck
                );
 
                $this->assertEquals(
                        [],
                        $missing,
-                       'Each user rights (core/extensions) has a corresponding right- message.'
+                       "Each user right (core/extensions) has a corresponding $prefix message."
                );
        }
 }
index 77461c5..28547d1 100644 (file)
@@ -113,7 +113,7 @@ class ParserTestTopLevelSuite extends PHPUnit_Framework_TestSuite {
                        $testsName = $extensionName . '__' . basename( $fileName, '.txt' );
                        $parserTestClassName = ucfirst( $testsName );
 
-                       // Official spec for class names: https://secure.php.net/manual/en/language.oop5.basic.php
+                       // Official spec for class names: https://www.php.net/manual/en/language.oop5.basic.php
                        // Prepend 'ParserTest_' to be paranoid about it not starting with a number
                        $parserTestClassName = 'ParserTest_' .
                                preg_replace( '/[^a-zA-Z0-9_\x7f-\xff]/', '_', $parserTestClassName );
index 2238fce..9f57190 100644 (file)
@@ -77,8 +77,8 @@ mw.loader.implement( 'testUrlOrder.b', function () {} );
 
 $response = '';
 
-// Does not support the full behaviour of ResourceLoaderContext::expandModuleNames(),
-// Only supports dotless module names joined by comma,
+// Does not support the full behaviour of the real load.php.
+// This only supports dotless module names joined by comma,
 // with the exception of the hardcoded cases for testUrl*.
 if ( isset( $_GET['modules'] ) ) {
        if ( $_GET['modules'] === 'testUrlInc,testUrlIncDump|testUrlInc.a,b' ) {
index 8b3427f..ed1288b 100644 (file)
 
        QUnit.test( '.implement( package files )', function ( assert ) {
                var done = assert.async(),
-                       initJsRan = false;
+                       initJsRan = false,
+                       counter = 41;
                mw.loader.implement(
                        'test.implement.packageFiles',
                        {
                                files: {
                                        'resources/src/foo/data/hello.json': { hello: 'world' },
                                        'resources/src/foo/foo.js': function ( require, module ) {
-                                               window.mwTestFooJsCounter = window.mwTestFooJsCounter || 41;
-                                               window.mwTestFooJsCounter++;
-                                               module.exports = { answer: window.mwTestFooJsCounter };
+                                               counter++;
+                                               module.exports = { answer: counter };
                                        },
                                        'resources/src/bar/bar.js': function ( require, module ) {
                                                var core = require( './core.js' );
index 80e12cd..93e0b87 100644 (file)
@@ -80,7 +80,8 @@ describe( 'Page', function () {
 
                // check
                assert.strictEqual( EditPage.heading.getText(), name );
-               assert.strictEqual( EditPage.displayedContent.getText(), editContent );
+               // eslint-disable-next-line no-restricted-syntax
+               assert( EditPage.displayedContent.getText().includes( editContent ) );
        } );
 
        it( 'should have history @daily', function () {
index 648e52f..970fb9e 100644 (file)
@@ -48,7 +48,7 @@ describe( 'Rollback with confirmation', function () {
                assert.strictEqual( HistoryPage.rollbackConfirmableNo.getText(), 'Cancel' );
        } );
 
-       it( 'should offer a way to cancel rollbacks', function () {
+       it.skip( 'should offer a way to cancel rollbacks', function () {
                HistoryPage.rollback.click();
 
                browser.pause( 300 );
@@ -60,7 +60,7 @@ describe( 'Rollback with confirmation', function () {
                assert.strictEqual( HistoryPage.heading.getText(), 'Revision history of "' + name + '"' );
        } );
 
-       it( 'should perform rollbacks after confirming intention', function () {
+       it.skip( 'should perform rollbacks after confirming intention', function () {
                HistoryPage.rollback.click();
 
                browser.pause( 300 );